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
Related
I am currently working on a WPF project in .NET Core 3.1. During the execution I have to read some file names to a combobox. I do that by first adding them to a list. After that I iterate through that list and add them to the combobox.
Problem
Because they should only be accessible if an Item from another combobox is selected, I need to disable the items I just added. Because of that, and to later enable them, I need to name them. But I could not figure our how to do that.
Here is my code:
string[] allAircraft;
allAircraft = Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + #"\ApplicationName\Aircraft\", "*.lxml", SearchOption.TopDirectoryOnly);
foreach (var currentAircraft in allAircraft)
{
aircraft.Add(System.IO.Path.GetFileNameWithoutExtension(currentAircraft));
}
foreach (string currentListItem in aircraft)
{
comboAircraft.Items.Add(currentListItem);
}
foreach (ComboBoxItem currentComboItems in comboAircraft.Items)
{
currentComboItems.Name = "comboAircraftItems" + currentComboItems.Content;
}
At the line foreach (ComboBoxItem currentItems in comboAircraft.Items) I get the Exception:
System.InvalidCastException: 'Unable to cast object of type 'System.String' to type 'System.Windows.Controls.ComboBoxItem'.'
I am really running out of ideas. I googled one entire day but could not find anything.
Have a nice day everyone!
# t-kldw, Your task is very difficult to solve when working directly with UI elements.
But it is very simple to solve, if you separate the Data and their Presentation, as it should be in MVVM.
Unfortunately, what you wrote contains few details for an example with full implementation.
I can give only partial advice.
It is necessary to create an additional container-class for the data needed in one combo box.
This class MUST have an INotifyPropertyChanged implementation.
To simplify, take implementation from the topic OnPropertyChanged and instance property
/// <summary>Base class implementing INotifyPropertyChanged.</summary>
public abstract class BaseINPC : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Called AFTER the property value changes.</summary>
/// <param name="propertyName">The name of the property.
/// In the property setter, the parameter is not specified. </param>
public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
An example class with two properties: File Name and IsEnabled to allow selection.
public class FileItem : BaseINPC
{
private string _fileName;
private bool _isEnabled;
public string FileName { get => _fileName; set { _fileName = value; RaisePropertyChanged(); } }
public bool IsEnabled { get => _isEnabled; set { _isEnabled = value; RaisePropertyChanged(); } }
}
So that I can give further advice, you need to provide more details of your task.
For example, I don’t even have a clue why currentComboItems.Name might be needed ....
I think that you are clearly doing something wrong.
You are trying to convert String to ComboBoxItem. When you populate your combobox with data, you add String, not ComboBoxItem. So you should use String in the last foreach loop.
foreach (string currentComboItems in comboAircraft.Items)
{
currentComboItems.Name = "comboAircraftItems" + currentComboItems;
}
Another solution is to add ComboBoxItem to your combobox instead of String.
foreach (string currentListItem in aircraft)
{
ComboBoxItem item = new ComboBoxItem()
{
Content = currentListItem,
Name = "comboAircraftItems" + currentListItem
};
comboAircraft.Items.Add(item);
}
ItemsControl.Items only holds data objects. In order to render this items, each is wrapped into a container e.g., ComboBoxItem. These UI containers are generated by a ItemContainerGenerator, which is associated with the current ItemsControl. The ItemContainerGenerator is also responsible to track the mapping between the data item and its UI container.
To obtain the container of an item you use the ItemsControl.ItemContainerGenerator:
DependencyObject itemContainer = ItemContainerGenerator.ContainerFromItem(item);
Your simplified code becomes:
List<string> allAircrafts = Directory.EnumerateFiles(
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
#"\ApplicationName\Aircraft\"),
"*.lxml",
SearchOption.TopDirectoryOnly)
.Select(Path.GetFileNameWithoutExtension)
.ToList();
comboAircraft.ItemsSource = allAircrafts;
foreach (string item in allAircrafts)
{
var itemContainer = comboAircraft.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem;
itemContainer.Name = "comboAircraftItems" + itemContainer.Content;
}
But note, that the approach you chose has issues. When UI virtualization is enabled for ItemsCopntrol, then only the visible items have their containers generated. Items out side the viewport of the scroll viewer are not generated and can't be returned by the ItemContainerGenerator.
Since UI virtualization is not enabled for the ComboBox by default, this still can work, as long you don't enable this performance optimization feature.
The optimal solution would need you to create a filtered source collection for the ComboBox that displays the filtered items. This way you won't clutter the control with items that are not of interest for the user and are not selectable anyway.
Create a collection of type ObservableCollection<string> which contains the selected items of the primary ComboBox you named comboAircraft.
Set this collection as ItemsSource of the second filtered ComboBox.
Alternatively create a CollectionViewSource of the original source collection and bind it to the secondary ComboBox. ICollectionView supports filtering.
The proper solution
Using either of the two recommended solutions eliminates the requirement to care about item containers. This adds flexibility and simplifies your code.
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();
...
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}" />
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.
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();