C# WPF ListView doesnt update Items from ObservableCollection when removed - c#

trying to have selectable Items removed from an ListView which ItemsSource is binded to an ObservableCollection. Debugging shows that the Items get properly flagged as selected and removed the List itself. But the shown ListView doesnt update to the changes thus the deleted Items stay shown.
View
<ListView BorderThickness = "0"
AlternationCount = "2"
BorderBrush = "Transparent"
ItemsSource = "{Binding TestEntries}"
Style = "{StaticResource ListViewStyle}"
ItemContainerStyle = "{StaticResource ListViewItemStyle}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden">
Observable Collection
public ObservableCollection<Entry> TestEntries
{
get => new ObservableCollection<Entry>(_testEntries
.Where(x => x.Title.ToUpper().Contains(FilterText.ToUpper()))
.OrderByDescending(x => x.Date));
set
{
_testEntries = value;
OnPropertyChanged();
}
}
Command (to delete Items)
private void ButtonDeleteCommandExecute()
{
var toDeleteEntries = TestEntries.Where(x => x.IsSelected);
toDeleteEntries.ToList().ForEach((obj) =>
{
TestEntries.Remove(obj as Entry);
});
}
Hope someone can point me in the right direction

Try this:
Return the same object of your ObservableCollection in your getter so it can notify your Target to update itself as #George Alexandria Suggested.
public ObservableCollection<Communication> TestEntries
{
get
{
return _testEntries;
}
set
{
_testEntries = value;
//OnPropertyChanged(); <-- of no use
}
}
Since you want to filter data in you listView:
TestEntries = new ObservableCollection<Communication>(_testEntries.Where(x=>x.Title.ToUpper().Contains(FilterText.ToUpper())).OrderByDescending(x=>x.Date).ToList());

Related

How to make some ListView items 'greyed-out' and unselectable?

Some items in the ListView control will be selectable and have normal text.
Some items however, although included in the ListView as items, will be unselectable/unclickable and 'greyed-out'.
In Windows-Store-Apps we have the ability to select Single/Multiple/None items in a ListView. But how can make certain items at certain indexes unselectable/unclickable and 'greyed-out', in code mainly?
I managed to access the Item of the ListView at a certain index:
myListView.ItemContainerGenerator.ContainerFromIndex(i)
But I couldn't find any option to customize its selected event handler.
Any idea how to achieve that?
In Single selection mode.
First Add a boolean property to class of binding type which defines which items are clickable like this
class TestClass
{
Boolean IsClickAllowed{get;set;}
string name{get;set;}
}
then create a source list of TestClass type and set it as itemssource of Listview like this
var TempList=new List<>()
{
new TextClass(){IsClickAllowed=false,name="First Item"},
new TextClass(){IsClickAllowed=true,name="Second Item"},
new TextClass(){IsClickAllowed=false,name="Third Item"},
};
MyList.ItemsSource=TempList;
and for greying out Set Different DataTemplate for nonClickable items implementing DataTemplateSelector and finally for click handle in ItemClick event. You need to set IsItemClickEnabled as true.
private void MyList_ItemClick(object sender, ItemClickEventArgs e)
{
var item = e.ClickedItem as TestClass;
if (item != null){
if(item.IsClickAllowed){
//Do Stuff here
}else
{
//Do Nothing
}
}}
Hope it helps.
I have found a solution:
I have override the ListView control and create a StripedListView. Then by overriding the PrepareContainerForItemOverride, which is responsible for the setting up the ListViewItem control after it’s be created, you could modify the background color and set the ItemListView.isEnabled option to false:
public class StripedListView : ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var listViewItem = element as ListViewItem;
if (listViewItem != null)
{
var index = IndexFromContainer(element);
if (Words.arrayW[index].Length > 0)
{
listViewItem.Foreground = new SolidColorBrush(Colors.Black);
}
else
{
listViewItem.Foreground = new SolidColorBrush(Colors.Gray);
listViewItem.IsEnabled = false;
}
}
}
}
In Xaml:
<controls:StripedListView x:Name="letterListView" ItemsSource="{Binding}">
<controls:StripedListView.ItemTemplate>
<DataTemplate>
etc...
</DataTemplate>
</controls:StripedListView.ItemTemplate>
</controls:StripedListView>

ObservableCollection<> not loading properly

I am trying to implement filtering on an ObservableCollection<>. My current ObservableCollection<Payee> is working fine as an ItemsSource on a GridView. I added a second ObservableCollection<Payee> called FilteredPayees to use as the ItemsSource. For some reason, when I try to filter the items, the GridView is showing up blank.
Here is the code I'm using:
private void FilterPayees()
{
if (!_settings.ShowInactivePayees)
{
var filtered = _payees.Where(p => p.IsOpen == true);
_filteredPayees = new ObservableCollection<Payee>(filtered);
}
else
{
_filteredPayees = _payees;
}
this.FilteredPayees = _filteredPayees;
}
Basically, if the ShowInactivePayees setting is turned off, it should filter out the inactive payees. If it is on, then just use the full _payees collection. The strange thing, if I change the last line to:
this.FilteredPayees = _payees;
then the GridView will display all of the payees, just as it should if the "show inactive payees" settings is turned on. I set breakpoints and the _filteredPayees collection has 35 items in it (or 65 when not filtering). It does not appear to be any type of "object not set to an instance of an object" or anything like that. Is there some reason that
this.FilteredPayees = _payees;
would work, but
_filteredPayees = _payees;
this.FilteredPayees = _filteredPayees;
would not?
EDIT
I was able to get it to work for now by getting rid of the FilteredPayees property. I just filter the original Payees collection in the OnNavigatedTo() event handler, which is exactly the same place where I was calling FilteredPayees().
// load payees
var payees = await _payeesRepository.LoadAllAsync();
if (!_settings.ShowInactivePayees)
{
payees = payees.Where(p => p.IsOpen);
}
payees = payees.OrderBy(p => p.CompanyName);
this.Payees = new ObservableCollection<Payee>(payees);
The only part I added was the if (!_settings.ShowInactivePayees) ... block. My reasoning to use the FilteredPayees property was so that I could have the full collection loaded in the Payees property and not need to reload if the ShowInactivePayees setting was changed - just change the filter of the collection.
You are assigning a new object to FilteredPayees property, so GridView has to be notified that the property FilteredPayees is changed. There should be RaisePropertyChanged("FilteredPayees") or your notification code in the setter of FilteredPayees.
Also, the binding mode of GridView.ItemsSource should not be BindingMode.OneTime.
For filtering a collection in WPF it may be easier to use ICollectionView. For example:
public class Foo
{
private List<Payee> _payees;
private ICollectionView _filteredPayees;
public ICollectionView FilteredPayees
{
get { return _filteredPayees; }
}
public Foo()
{
_payees = GetPayees();
_filteredPayees = CollectionViewSource.GetDefaultView(_payees);
_filteredPayees.Filter = FilterPayees;
}
private bool FilterPayees(object item)
{
var payee = item as Payee;
if (payee == null)
{
return false;
}
if (_settings.ShowInactivePayees)
{
return true;
}
return payee.IsOpen;
}
}
You can bind the property FilteredPayees like any other property. The advantage is, that you don't need two properties and you can avoid the logic of which collection you want to bind.
_filteredPayees = new ObservableCollection<Payee>(filtered);
Here you create a completely new object, and that's not something ObservableCollection can automatically observe. The possible solution is to set ItemsSource on your GridView again after this line.

chart not updating when changing the value of displaymember

I have an Modernui Piechart binded to an ObservableCollection.
If I change the name of an item it is not updating, but if I change the value of on item then it will be updated properly(add/remove works fine).
Chart XAML :
<chart:PieChart Grid.RowSpan="2"
Style="{StaticResource MinimalChartStyle}"
ChartTitle="Minimal Pie Chart"
ChartSubTitle="Chart with fixed width and height"
>
<chart:PieChart.Series>
<chart:ChartSeries
SeriesTitle="Categories"
DisplayMember="CategoryName"
ValueMember="CategoryExpenseLimit"
ItemsSource="{Binding Path=Cat}" />
</chart:PieChart.Series>
</chart:PieChart>
Code where I add item: (this updates the chart properly when adding item)
TransactionCategoryModel category = new TransactionCategoryModel() { TheCategory = { CategoryName = CategoryName, CategoryExpenseLimit = (decimal)CategoryExpenseLimit }};
context.TransactionCategories.Add(category.TheCategory);
context.SaveChanges();
var obs = Application.Current.Resources["CategoryObs"] as ObservableCollection<CategoryViewModel>;
obs.Add(new CategoryViewModel(category));
Code to edit an Item :(retrieve it from the database and update it then update the observable collection as well)
var category = context.TransactionCategories.Where(i => i.CategoryId == this.CategoryId).First();
var tCategory = new TransactionCategoryModel() { TheCategory = category };
tCategory.TheCategory.CategoryId = (int)CategoryId;
tCategory.TheCategory.CategoryName = CategoryName;
tCategory.TheCategory.CategoryExpenseLimit = (decimal)CategoryExpenseLimit;
context.SaveChanges();
var obs = Application.Current.Resources["CategoryObs"] as ObservableCollection<CategoryViewModel>;
var x = obs.Where(i => i.CategoryId == this.CategoryId).FirstOrDefault();
CategoryViewModel cvm = new CategoryViewModel(tCategory);
x = cvm;
With this I edit an item. Problem is if I edit it and change the name the chart does not update the displaymember, but if I also change the expenselimit(this is the valuemember of the chart) then the chart will update properly.
The fact that the name does not update happens only with the chart. I made a datagrid in another view and binded the Observablecollection and the data grid updates properly even when I change only the name.
In the ViewModel of the chart :
private ObservableCollection<CategoryViewModel> cat;
public ObservableCollection<CategoryViewModel> Cat
{
get { return cat; }
set
{
cat = value;
OnPropertyChanged("Cat");
}
}
And in the constructor:
if (cat == null)
cat = new ObservableCollection<CategoryViewModel>();
cat = Application.Current.Resources["CategoryObs"] as ObservableCollection<CategoryViewModel>;
And when I the app starts : to retrieve the values
private void GetCategories()
{
List<CategoryViewModel> categories = new List<CategoryViewModel>();
using( var context = new Ents())
{
foreach(var item in context.TransactionCategories)
{
TransactionCategoryModel tcm = new TransactionCategoryModel() { TheCategory = item };
categories.Add(new CategoryViewModel(tcm));
}
}
ObservableCollection<CategoryViewModel> Categories = new ObservableCollection<CategoryViewModel>(categories);
Application.Current.Resources.Add("CategoryObs", Categories);
}
..edit it and change the name the chart does not update the
displaymember
Two things, an ObservableCollection is primarily a signaler for when items are added or removed from its list. It doesn't help binding or processing of items which are edited.
Secondly for any changes of individual items to be noticed, the property which it is bound needs to signal its change. That signaling is done when the class it resides on implements INotifyPropertyChange and it calls an OnPropertyChanged method with its name.
So looking at your code, you are learning I get that, the code
public ObservableCollection<CategoryViewModel> Cat {
get { return cat; }
set { cat = value; OnPropertyChanged("Cat"); }}
Only signals when a new observable collection is assigned. Now that may be helpful in certain situations, such as swapping in and out of lists (I do it to change combobox drop down selections) but in your case its a one trick pony which is useful to the chart and needed, but the chart doesn't care you have an ObservableCollection. Because one has to purposefully subscribe to the ObservableCollection events to do anything. Frankly you could and should just use a List<CategoryViewModel> instead.
chart does not update the displaymember
Does the property CategoryName on the class CategoryViewModel call an PropertyChange because CategoryViewModel implements INotifyPropertyChanged?
Note...even it if does, the charting tool may not be subscribing to the change notification, because its a reporting tool and wasn't designed as such. If that is the case, you may need to wink the whole CategoryViewModel instance in and out (remove it then re-add it) to make the reporting chart control show the change in the name.

ListView SelectedItem not highlighted when set in ViewModel

I have a ListView with a ItemSource data binding and a SelectedItem data binding.
The ListView is populated with a new ItemSource every time I press the Next or Previous button.
The SelectedItem is updated accordingly, the items in the ItemSource have the Selected state, so it can be remembered when the user navigates back and forth.
While debugging, everything seems to work perfectly. The VM updates the controls as expected, and I can also see that the ListView has the correct selected value when I navigate with the next and previous buttons.
The problem is, that regardless of the fact that the ListView has a correct SelectedItem, the ListView does not visualize the SelectedItem as highlighted.
XAML:
<ListView
x:Name="_matchingTvShowsFromOnlineDatabaseListView"
Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
ItemsSource="{Binding AvailableMatchingTvShows}"
SelectedItem="{Binding AcceptedMatchingTvShow, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Behaviour in ViewModel responsible for repopulating the ItemSource and the SelectedItem:
private void UpdateForCurrentVisibleTvShow()
{
var selectedTvShow = FoundTvShows[CurrentTvShow];
// Update the available matches
var availableMatchingTvShows = new ObservableCollection<IWebApiTvShow>();
if (AvailableTvShowMatches[selectedTvShow] != null)
{
foreach (var webApiTvShow in AvailableTvShowMatches[selectedTvShow])
{
availableMatchingTvShows.Add(webApiTvShow);
}
}
AvailableMatchingTvShows = availableMatchingTvShows;
// Update the selected item
AcceptedMatchingTvShow = availableMatchingTvShows.FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
// Update the progress text
CurrentTvShowInfoText = string.Format(
"TV Show: {0} ({1} of {2} TV Shows)",
FoundTvShows[CurrentTvShow],
CurrentTvShow + 1,
FoundTvShows.Count);
// Update the AcceptedMatchingTvShow selection in the listview
OnPropertyChanged("AcceptedMatchingTvShow");
}
The implementation of AcceptedMatchingTvShow:
public IWebApiTvShow AcceptedMatchingTvShow
{
get
{
IWebApiTvShow acceptedTvShow = null;
if (FoundTvShows.Count > 0)
{
var tvShowName = FoundTvShows[CurrentTvShow];
acceptedTvShow = AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
}
return acceptedTvShow;
}
set
{
if (value != null)
{
var tvShowName = FoundTvShows[CurrentTvShow];
var currentlyAcceptedTvShow =
AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
if (currentlyAcceptedTvShow != null)
{
currentlyAcceptedTvShow.Accepted = false;
}
value.Accepted = true;
}
OnPropertyChanged();
}
}
I hope somebody can point me in the right direction. Just to be clear, the ListView has the correct items, and the SelectedItem is set with the correct item.
Well, I found 'a solution' to the problem after a lot of debugging and digging. I would REALLY like to understand if this is how WPF meant the control to behave, or if this is a bug in the ListViews data binding part. If anyone could tell me that, I am very very curious to the correct answer (and maybe I solved this problem in the wrong way, and somebody could explain me how I should've done this).
Anyway, the problem seems to be resolved when I create a copy of the object:
public IWebApiTvShow AcceptedMatchingTvShow
{
get
{
IWebApiTvShow acceptedTvShow = null;
if (FoundTvShows.Count > CurrentTvShow)
{
var tvShowName = FoundTvShows[CurrentTvShow];
acceptedTvShow = AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
}
if (acceptedTvShow != null)
{
// I MUST create a new instance of the original object for the ListView to update the selected item (why??)
return new WebApiTvShow(acceptedTvShow);
}
return null;
}
set
{
if (value != null)
{
var tvShowName = FoundTvShows[CurrentTvShow];
var availableTvShowMatch = AvailableTvShowMatches[tvShowName];
var currentlyAcceptedTvShow = availableTvShowMatch.FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
if (currentlyAcceptedTvShow != null)
{
currentlyAcceptedTvShow.Accepted = false;
}
value.Accepted = true;
}
OnPropertyChanged();
}
}
Note the call to the copy constructor :
return new WebApiTvShow(acceptedTvShow);
It works, but seems really ridiculous and smells like a bug in ListView to me. Is it?
I tried to explain the same problem in a simpler example here, if anybody can confirm the bug or can explain me how this should've been implemented I would greatly appreciate the insights.
A bit late to the game, but I had been jumping through hoops to solve this Problem in a similar setup. Setting the SelectedItem in a ListView using a bound Property in the Viewmodel or similar using a bound SelectedIndex just would not work. Until I tried to do it async:
Task.Factory.StartNew(() =>
{
BoundSelectedIndex = index;
});
Seems to work - more advanced contributors may answer why...
i know this is an old post but what worked is overriding the Equals and GetHashCode on your SelectedItem object so the listview can compare the SelectedItem with the bound collection

Filtered Combobox ItemsSource binding issue

What I'm trying to do :
I have 2 comboboxes, a "regular" one, and a filtrable one. On the filtrable combobox ItemsSource is binded to a property of the first combobox SelectedItem.
Here is some XAML to demonstrate the relation (I removed some properties):
<ComboBox Name="cbx_poche" ItemsSource="{Binding DataContext.ListePoches, ElementName=Main}" SelectedItem="{Binding PocheCible}" />
<controls:FilteredComboBox ItemsSource="{Binding SelectedItem.SupportsEligibles, ElementName=cbx_poche}" SelectedItem="{Binding Support}" />
The FilteredComboBox is a derived class from ComboBox inspired by those articles : Building a Filtered ComboBox for WPF / WPF auto-filtering combo.
The user can type in the combobox and it filters the list to display the items matching. The default behavior of the combobox is NOT desired (it automatically completes what the user types), this is why it's derived.
Those comboboxes above are in a ItemsControl element, because I need to have one row for each item in a specific collection. The SelectedItem properties of the comboboxes are binded to the item in this collection.
The result :
The problem :
It works quite well... as long as you don't select the same item in the first combobox (like in the example above : if I type some text that doesn't match the above combo, it will be reset).
As soon as several FilteredComboBox are linked to the same item in the first combobox (so binded to SelectedItem.SupportsEligibles), typing text in the FilteredComboBox filters both lists.
I know why it does that, I don't know how to fix it. So I tried two things :
Code 1 (current code) :
The problem is that the code uses the default view on the list, so all controls binded to this list will apply the same filter :
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
if (ItemsSourceView != null)
ItemsSourceView.Filter -= this.FilterPredicate;
ItemsSourceView = CollectionViewSource.GetDefaultView(newValue);
ItemsSourceView.Filter += this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
Code 2 :
So my (naïve) idea was to get a local view from the binded view. I works well for filtering, but breaks the binding (changing the selected item in the first combo doesn't update the list after the first pass)
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null && newValue != ItemsSourceView)
{
if (ItemsSourceView != null)
ItemsSourceView.Filter -= this.FilterPredicate;
ItemsCollectionViewSource = new CollectionViewSource { Source = newValue };
ItemsSourceView = ItemsCollectionViewSource.View;
ItemsSourceView.Filter += this.FilterPredicate;
this.ItemsSource = ItemsSourceView; // Breaks the binding !!
}
base.OnItemsSourceChanged(oldValue, newValue);
}
I'm stuck here.
I'm looking for some event or Binding class that I could use to be notified of the binding change, in order to update the view. Or maybe getting the view applied without having to change the ItemsSource
I ended up using a quite lame workaround, so I'm still interested in smart answers.
For people interested : I added another similar ItemsSource2 Dependency Property (still didn't find a nice name for it) on which I bind my item list, instead of the original ItemsSource.
When this items source is changed, the control gets a new CollectionView (not the default one) and sets it to the "standard" ItemsSource.
The other elements of the control remains identical (similar to the code in the linked articles).
public static readonly DependencyProperty ItemsSource2Property =
DependencyProperty.Register(
"ItemsSource2",
typeof(IEnumerable),
typeof(FilteredComboBox),
new UIPropertyMetadata((IEnumerable)null, new PropertyChangedCallback(OnItemsSource2Changed)));
[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IEnumerable ItemsSource2
{
get { return (IEnumerable)GetValue(ItemsSource2Property); }
set
{
if (value == null)
{
ClearValue(ItemsSource2Property);
}
else
{
SetValue(ItemsSource2Property, value);
}
}
}
private static void OnItemsSource2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ic = (FilteredComboBox)d;
var oldValue = (IEnumerable)e.OldValue;
var newValue = (IEnumerable)e.NewValue;
if (newValue != null)
{
//Prevents the control to select the first item automatically
ic.IsSynchronizedWithCurrentItem = false;
var viewSource = new CollectionViewSource { Source = newValue };
ic.ItemsSource = viewSource.View;
}
else
{
ic.ItemsSource = null;
}
}

Categories

Resources