WPF ListView: Changing ItemsSource does not change ListView - c#

I am using a ListView control to display some lines of data. There is a background task which receives external updates to the content of the list. The newly received data may contain less, more or the same number of items and also the items itself may have changed.
The ListView.ItemsSource is bound to an OberservableCollection (_itemList) so that changes to _itemList should be visible also in the ListView.
_itemList = new ObservableCollection<PmemCombItem>();
_itemList.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
L_PmemCombList.ItemsSource = _itemList;
In order to avoid refreshing the complete ListView I do a simple comparison of the newly retrieved list with the current _itemList, change items which are not the same and add/remove items if necessary. The collection "newList" contains newly created objects, so replacing an item in _itemList is correctly sending a "Refresh" notification (which I can log by using the event handler OnCollectionChanged of the ObservableCollection`)
Action action = () =>
{
for (int i = 0; i < newList.Count; i++)
{
// item exists in old list -> replace if changed
if (i < _itemList.Count)
{
if (!_itemList[i].SameDataAs(newList[i]))
_itemList[i] = newList[i];
}
// new list contains more items -> add items
else
_itemList.Add(newList[i]);
}
// new list contains less items -> remove items
for (int i = _itemList.Count - 1; i >= newList.Count; i--)
_itemList.RemoveAt(i);
};
Dispatcher.BeginInvoke(DispatcherPriority.Background, action);
My problem is that if many items are changed in this loop, the ListView is NOT refreshing and the data on screen stay as they are...and this I don't understand.
Even a simpler version like this (exchanging ALL elements)
List<PmemCombItem> newList = new List<PmemCombItem>();
foreach (PmemViewItem comb in combList)
newList.Add(new PmemCombItem(comb));
if (_itemList.Count == newList.Count)
for (int i = 0; i < newList.Count; i++)
_itemList[i] = newList[i];
else
{
_itemList.Clear();
foreach (PmemCombItem item in newList)
_itemList.Add(item);
}
is not working properly
Any clue on this?
UPDATE
If I call the following code manually after updating all elements, everything works fine
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
But of course this causes the UI to update everything which I still want to avoid.

After a change, you can use the following to refresh the Listview, it's more easy
listView.Items.Refresh();

This is what I had to do to get it to work.
MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;

I know that's an old question, but I just stumbled upon this issue. I didn't really want to use the null assignation trick or the refresh for just a field that was updated.
So, after looking at MSDN, I found this article:
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2
To summarize, you just need the item to implement this interface and it will automatically detect that this object can be observed.
public class MyItem : INotifyPropertyChanged
{
private string status;
public string Status
{
get => status;
set
{
OnPropertyChanged(nameof(Status));
status = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
So, the event will be called everytime someone changes the Status. And, in your case, the listview will add a handler automatically on the PropertyChanged event.
This doesn't really handle the issue in your case (add/remove).
But for that, I would suggest that you have a look at BindingList<T>
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2
Using the same pattern, your listview will be updated properly without using any tricks.

You should not reset ItemsSource of ListView each time observable collection changed. Just set proper binding that will do your trick. In xaml:
<ListView ItemsSource='{Binding ItemsCollection}'
...
</ListView>
And in code-behind (suggest to use MVVM) property that will be responsible for holding _itemList:
public ObservableCollection<PmemCombItem> ItemsCollection
{
get
{
if (_itemList == null)
{
_itemList = new ObservableCollection<PmemCombItem>();
}
return _itemList;
}
}
UPDATE:
There is similar post which most probably will Answer your question: How do I update an ObservableCollection via a worker thread?

I found a way to do it. It is not really that great but it works.
YourList.ItemsSource = null;
// Update the List containing your elements (lets call it x)
YourList.ItemsSource = x;
this should refresh your ListView (it works for my UAP :) )

An alternative on Xopher's answer.
MyListView.ItemsSource = MyDataSource.ToList();
This refreshes the Listview because it's a other list.

Please check this answer:
Passing ListView Items to Commands using Prism Library
List view Items needs to notify about changes (done is setter)
public ObservableCollection<Model.Step> Steps
{
get { return _steps; }
set { SetProperty(ref _steps, value); }
}
and UpdateSourceTrigger need to be set in xaml
<Image Source="{Binding ImageData, UpdateSourceTrigger=PropertyChanged}" />

Related

How to get Item being added before commit in WPF DataGrid?

I've got a ViewModel, which is used by a View with a DataGrid.
In the DataGrid I try to allow the User directly to add new rows, but some of the columns need to open a new Dialog for the value.
Therefor I set the SelectedItem - Binding and added some Extensions to allow binding OnBeginningEdit and OnRowEditEnding to custom commands in my ViewModel.
Now I have the problem, that I want to get the current "new" item, which is partly filled by the GUI, before calling CommitEdit on the DataGrid.
I tried to use IEditableCollectionView and use CurrentAddItem of it, to achieve this.
But the CurrentAddItem does not get filled with the items entered in the GUI.
How may I achieve that properly in MVVM?
public ICommand DataGridDoubleClickCommand
{
get
{
return new Command(
delegate (object obj)
{
var dataGrid = ((object[])obj)[0] as DataGrid;
var args = ((object[])obj)[1] as MouseButtonEventArgs;
if (dataGrid.CurrentColumn.DisplayIndex != 2)
{
return;
}
args.Handled = true;
ICollectionView view = CollectionViewSource.GetDefaultView(dataGrid.Items);
IEditableCollectionView iecv = (IEditableCollectionView)view;
if(iecv.IsAddingNew)
{
// tried to use iecv.CurrentAddItem here - but was empty
}
dataGrid.CommitEdit();
...

Update ObservableCollection without blinking

Could someone help me, how can I update ObservableCollection, which is binded to ListView ItemSource, without blinking? When I do:
Contacs = _contacs;
the whole ListView is blinking. I would like to search in ListView too, but always after replacing the old results with new one, the listview blinks.
The problem here is, that you are reassigning the whole collection. This does not take advantage of the observability and forces the ListView to reload all items. Try to remove/add the items instead so the ListView only needs to update the Items that actually changed.
In the case of searching hiding the unmatched results might be a viable solution too. To do that create a property on your Contact type (called "IsVisible" for example) and bind it to the ListViewItems Visibility Property. (You might need the build in BooleanToVisibility Converter here)
Update
As pointed out in the comments using a CollectionViewSource is the correct wpf way of implementing a search filter. See this answer for details on how to use it.
If you want to enable filtering in your collection then there is no actual need to perform operations directly on your collection.
Use ICollectionView and CollectionViewSource for this purpose.
As you have an ObservableCollection so you can do something like this.
ICollectionView contactsView;
public ICollectionView ContactsView
{
get { return contactsView; }
set
{
if(contactsView != value)
{
contactsView = value;
}
}
}
And in the setter of the ObservableCollection
public ObservableCollection<ContactType> Contacs
{
get { return _contacs; }
set
{
if(_contacs != value)
{
_contacs = value;
ContactsView = CollectionViewSource.GetDefaultView(value);
ContactsView.Filter = ContactsFilter;
}
}
}
where ContactsFilter is a function with following definition.
bool ContactsFilter(object item)
{
var contact = item as ContactType;
if(condition)
return true; //show this item in ListView.
return false; //Do not show this item in ListView
}
and whenever you want to filter items you can do that just by
ContactsView.Refresh();
which I think will be in the TextChanged Event of your text box in which you are entering search query.
More detailed article is at CollectionViewSource

How to remove item from generic list that relates to item in listbox?

I have searched around but could not find any references.
How do I delete an item in a generic list that relates to items in a listbox?
I currently have a public static List<Employees> and a listbox named lstRecords, I can remove the item in the listbox just fine, but either everything is removed from the list or nothing at all.
This was my first set of code I was working with:
private void DeleteRecord()
{
if (lstRecords.Items.Count > 0)
{
for (int i = 0; i < lstRecords.Items.Count; i++)
{
if (lstRecords.GetSelected(i) == true)
{
Employees employeeRecord = lstRecords.SelectedItem as Employees;
employee.Remove(employeeRecord);
}
}
lstRecords.Items.Remove(lstRecords.SelectedItem);
}
}
}
This is my 2nd set of code I was working with, I have my List right under partial class, but this is all contained in a method.
private void DeleteRecord()
{
ListBox lstRecords = new ListBox();
List<object> employee = new List<object>();
employee.RemoveAt(lstRecords.SelectedIndex);
lstRecords.Items.RemoveAt(lstRecords.SelectedIndex);
}
So far I haven't gotten either set of code to work the way I would like it to, I'm obviously doing something wrong.
I have a few other blocks of code I played around with but these seemed to be headed in the right direction.
Eventually I'll need to be able to double click an item in the list to pull up the properties menu.
Your code runs fine you just have to make some small changes.
The first code block is Ok however I dont know where your lstRecords are.
But have a look at this just copy the code and run it after you have some records in your employee object.
It's createing a listbox in code then adds it to the form(Winforms) and having the lstRecords globaly.
ListBox lstRecords;
private void IntializeDemoListbox()
{
lstRecords = new ListBox();
this.Controls.Add(lstRecords);
foreach (var item in employee)
{
lstRecords.Items.Add(item);
}
}
And then you will be able to use your first set of code the other set will be like this.
private void DeleteRecord()
{
employee.RemoveAt(lstRecords.SelectedIndex);
lstRecords.Items.RemoveAt(lstRecords.SelectedIndex);
}
What you want to do is bind your ListBox to you List of employees. This post shows the binding and the comments shows the removing code as well. The idea is that when you remove an item from the DataSource, then you won't see it in the ListBox.
Binding Listbox to List<object>
The problem with the DeleteRecord() method is that the lstRecords object you just created isn't the ListBox that is on the form.

WPF iterate through datagrid

Using WPF C#.NET4.5 using visual studio 2012 ulti.
Old winforms code:
foreach (DataGridViewRow paretoRow in ParetoGrid.Rows)
{
if ((Convert.ToInt32(paretoRow.Cells["CurrentPareto"].Value) < (Convert.ToInt32(paretoRow.Cells["NewPareto"].Value))))
{
paretoRow.Cells["pNew"].Value = downArrow
}
}
As you can see each row I cycle through I check a specific cell, if true I then populate another cell. This was good old winforms code I used many times before...however.
Switching over to WPF was alot more different than i previously assumed.
DataGrid does not contain the Row property. Instead, I think you need to use:
DataGridRow paretoRow in paretogrid.Items
But im still at a loss on who to now get the cell.
So my question is, is there syntax changes to perform, if so where? Or as I'm beginning to believe datagrids in WPF operate with Objects more so than winforms thus not needing to use a propertie called "row", if this is the case what logic/syntax should i know use in this example?
Thanks for your patience guys, think when I go home for the bank holiday I'll do a bit of WPF digging to see how different it actually is.
People seem to be overcomplicating this, this worked for me:
foreach (System.Data.DataRowView dr in yourDataGrid.ItemsSource)
{
MessageBox.Show(dr[0].ToString());
}
I think first think you want to do is to get all rows of your DataGrid:
public IEnumerable<Microsoft.Windows.Controls.DataGridRow> GetDataGridRows(Microsoft.Windows.Controls.DataGrid grid)
{
var itemsSource = grid.ItemsSource as IEnumerable;
if (null == itemsSource) yield return null;
foreach (var item in itemsSource)
{
var row = grid.ItemContainerGenerator.ContainerFromItem(item) as Microsoft.Windows.Controls.DataGridRow;
if (null != row) yield return row;
}
}
and then iterate through your grid:
var rows = GetDataGridRows(nameofyordatagrid);
foreach (DataGridRow row in rows)
{
DataRowView rowView = (DataRowView)row.Item;
foreach (DataGridColumn column in nameofyordatagrid.Columns)
{
if (column.GetCellContent(row) is TextBlock)
{
TextBlock cellContent = column.GetCellContent(row) as TextBlock;
MessageBox.Show(cellContent.Text);
}
}
Yes, you are right. WPF DataGrid is built around better supporting the use of objects.
You could use a ViewModel similar to the following. Build them all into a collection and then set that collection as your ItemsSource. You would also need to use a ValueConverter if you want to display and image instead of a checkmark for pNew being true/false.
public class FooViewModel : INotifyPropertyChanged
{
private int currentPareto;
public int CurrentPareto
{
get
{
return currentPareto;
}
set
{
if (currentPareto == value)
return;
currentPareto = value;
OnPropertyChanged("CurrentPareto");
OnPropertyChanged("pNew");
}
}
private int newPareto;
public int NewPareto
{
get
{
return newPareto;
}
set
{
if (newPareto == value)
return;
newPareto = value;
OnPropertyChanged("NewPareto");
OnPropertyChanged("pNew");
}
}
public bool pNew
{
get
{
return CurrentPareto < NewPareto;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Edit
To simplify it a little, you could use a base ViewModel class and use PropertyChanged weaving. The code would simplify to this:
public class FooViewModel : ViewModelBase
{
public int CurrentPareto { get; set; }
public int NewPareto { get; set; }
public bool pNew { get { return CurrentPareto < NewPareto; } }
}
I don't even understand why is it just so complicated to get rows and their values in a datagrid. It feels like hell finding how. The api even give funny funny event names which is not so direct to the point also. Why can't just people concentrate on the baseline and give what exactly is needed and not all sorts of different options with no use and sense at all. I mean to eat all you need is a spoon and fork right. Never even changed since 100,000 years ago. This is my code thanks to the guy who mentioned some people just try to over-complicate things and waste your time.
private void dtaResultGrid_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ActivateTestDatagridAccess();
}
public async void ActivateTestDatagridAccess()
{
try
{
await Task.Delay(500);
foreach (System.Data.DataRowView dr in dtaResultGrid.ItemsSource)
{
for (int j = 0; j < dtaResultGrid.Columns.Count; j++)
{
Console.WriteLine(dr[j].ToString());
}
Console.Write(Environment.NewLine);
}
}
catch (Exception exrr)
{
Console.WriteLine(exrr.ToString());
}
}
The 'simplest' answer, from Charles, did it for me. But I used Items instead of ItemsSource.
Now, for people getting this error:
System.InvalidCastException
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.Data.DataRowView'.
What did it for me was disabling the DataGrid's property CanUserAddRows. This removes the placeholder line for a new line, thus removing the placeholder object (which is NOT a DataRowView, but something else). If you already have this disabled, then I don't know.
Since I wanted to loop through each element of each row, I added another foreach:
foreach (System.Data.DataRowView dr in nameofyourgrid.Items)
{
foreach (var item in dr.Row.ItemArray)
{
MessageBox.Show(item.ToString());
}
}
In WPF you go about it a lot more dynamic and ObjectOrientated. You can bind the Column "pNew" on a Property of the element you put in the DataGrid, which returns downarrow.
If the value changes you can raise the Event PropertyChanged (Interface INotifyPropertyChanged) and the bound Property will get reevaluated.
Also interesting for beginning with WPF is DataTemplate, ControlTemplate, Converter.
Converter changes the Property Value to a usable Value for WPF (e.g. BoolToVisibility) when the Property gets called.
DataTemplate and ControlTemplate can be used to alter the appearance of the Control.
There are several good Tutorials for WPF out there. I would also recommend to look into the MVVM-Pattern to use as a between layer of your Businessobject and your WPF-Control, especially to handle things like what you try to do here.
if you fill your datagridview rows using an instance of a class (like struct_class)
this would be the fastest way to have a foreach loop
foreach (struct_class row in dgv.Items)
{
MessageBox.Show(row.name);
}
Why can't you just use this property to get the number of rows and then use a For loop to iterate through?
dataGridView1.Rows.Count

Why does the DataGrid not update when the ItemsSource is changed?

I have a datagrid in my wpf application and I have a simple problem. I have a generic list and I want to bind this collection to my datagrid data source every time an object is being added to the collection. and I'm not interested to use observable collection.
the point is I'm using the same method somewhere else and that works fine. but this time when i press Add button an object is added and datagrid updates correctly but from the second item added to collection datagrid does not update anymore.
Here is the Code :
private void btnAddItem_Click(object sender, RoutedEventArgs e)
{
OrderDetailObjects.Add(new OrderDetailObject
{
Price = currentitem.Price.Value,
Quantity = int.Parse(txtQuantity.Text),
Title = currentitem.DisplayName,
TotalPrice = currentitem.Price.Value * int.Parse(txtQuantity.Text)
});
dgOrderDetail.ItemsSource = OrderDetailObjects;
dgOrderDetail.UpdateLayout();
}
any idea ?
The ItemsSource is always the same, a reference to your collection, no change, no update. You could null it out before:
dgOrderDetail.ItemsSource = null;
dgOrderDetail.ItemsSource = OrderDetailObjects;
Alternatively you could also just refresh the Items:
dgOrderDetail.ItemsSource = OrderDetailObjects; //Preferably do this somewhere else, not in the add method.
dgOrderDetail.Items.Refresh();
I do not think you actually want to call UpdateLayout there...
(Refusing to use an ObservableCollection is not quite a good idea)
I also found that just doing
dgOrderDetails.Items.Refresh();
would also accomplish the same behavior.
If you bind the ItemSource to a filtered list with for example Lambda its not updated.
Use ICollectionView to solve this problem (Comment dont work):
//WindowMain.tvTemplateSolutions.ItemsSource = this.Context.Solutions.Local.Where(obj=>obj.IsTemplate); // templates
ICollectionView viewTemplateSolution = CollectionViewSource.GetDefaultView(this.Context.Solutions.Local);
viewTemplateSolution.SortDescriptions.Clear();
viewTemplateSolution.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
viewTemplateSolution.Filter = obj =>
{
Solution solution = (Solution) obj;
return solution.IsTemplate;
};
WindowMain.tvTemplateSolutions.ItemsSource = viewTemplateSolution;
i use ObservableCollection as my items collection and than in the view model
call CollectionViewSource.GetDefaultView(my_collection).Refresh();

Categories

Resources