I'm having problem with with my WPF application, where the search filter is applied to the observablecollection, when I add a filter to the ICollectionView.
I got two views which two separate viewmodels. In this case, one view allows you to search on a collection and manipulate it, and the second view has a combobox which allows the user to choose an item from the collection.
At first, I'm retrieving the items to my observablecollection as you can see in the code under. Then I'm setting the CollectionViewSource. As now, I'm adding filter to the CollectionView, which is a search I've implemented. My problem is that I thought that the filter would only apply to the ICollectionView collection, which I'm using in the listbox, but it shows out that it also applies to the ObservableCollection. The listbox is using the CollectionView and the combobox is using the ObservableCollection of the categories. But I don't want the filter to be applied to the combobox collection, which uses the observablecolelction, as I want to show all the available items all the time.
How can I fix this?
public ViewModel ()
{
CollectionViewSource.GetDefaultView(Categories);
}
public ObservableCollection<Category> Categories
{
get
{
return this._categories;
}
set
{
if (this._categories!= value)
{
this._categories= value;
this.OnPropertyChanged("Categories");
}
}
}
private ICollectionView _categoriesCollection;
public ICollectionView CategoriesCollection
{
get
{
return this._categoriesCollection;
}
set
{
if (this._categoriesCollection!= value)
{
this._categoriesCollection= value;
this.OnPropertyChanged("CategoriesCollection");
}
}
}
You are binding to the same view: Should I bind to ICollectionView or ObservableCollection
Instead of setting your CategoriesCollection property to the return value of CollectionViewSource.GetDefaultView(_categories), you could create a new view to "fix" this:
CategoriesCollection = new ListCollectionView(_categories);
Related
First of all, what I'm trying to do is a "simple" binding of a ComboBox to my source.
The structure is something like:
public class Data
{
public ObservableList<string> List {get;set;}
public string Selected {get;set;}
}
Also, it implements INotifyPropertyChanged interface.
My problem is, i found several solutions to do this via XAML, unfortunately i can't do it with XAML since my ComboBoxes have to be generated during runtime.
So my question is, how i can bind my ComboBox to Data.List, and also the selected item (value?) to Data.Selected, and this one should be TwoWay so my Data class knows that something was selected. Keep in mind this has to be through c# code (XAML is no option unfortunately).
Thanks in advance. :)
It's pretty easy. Assuming, that Data has properties instead of fields:
public class Data
{
public Data()
{
List = new ObservableCollection<string>
{
"Apple", "Orange", "Lime"
};
}
public ObservableCollection<string> List { get; private set; }
public string Selected { get; set; }
}
you can write this:
var comboBox = new ComboBox
{
DataContext = new Data()
};
comboBox.SetBinding(ComboBox.ItemsSourceProperty, new Binding("List"));
comboBox.SetBinding(ComboBox.SelectedItemProperty, new Binding("Selected")
{
Mode = BindingMode.TwoWay
});
To add ComboBox into visual tree, just call proper method for the container. E.g., this will work with any ContentControl (like Window):
AddChild(comboBox);
how i can bind my combobox to Data.List, and also the selected item (value?)
Create a custom composite user control which contains the combobox. Map the combobox's properties to two dependencies properties created on the custom control, one to load the data and the other to provide an on demand selected item's data. Any plumbing needs are done inside the codebehind which ultimately provides all the magic.
Then you can create/bind this control dynamically in codebehind as needed in the other page you are working on.
Sounds like a sort of "recursive binding". If your combos are in a container control, what you need is bound the container to a collection of your single combo model, so each view in the ItemsControl will be bound to a single combo model.
I'm trying to use WPF binding to an object that is contained by the DataContext object and it is not working.
If I place all binding elements in the DataContext ViewModel object, everything works, but if I separate out the data list and data elements into a separate class that is contained by the ViewModel class, the ListBox data will work, but the binding to the individual data elements is not working.
With my experimentation, I assume that the bound object needs to be directly bound to the DataContext ViewModel class. I can do that and it works, but it's not as object oriented or reusable as I would like it to be. I've separated out the data list for the ListBox into it's own class and I'm assuming that since it is an ObservableCollection it works regardless of it being attached to the contained object. Since the individual data elements of the objects are only notified through OnPropertyChanged, no matter what I've tried, I can't get the WPF form to recognize the data, even though the DataContext.CurrentRecord shows the correct data. I can only assume that the OnPropertyChanged notification are not going where I need them to go.
Partial Code for DataContext ViewModel is as follows:
public ObservableCollection<ItemModel> Items
{
get { return ItemListMgr.Items; }
set { ItemListMgr.Items = value; }
}
public ItemModel CurrentItem
{
get { return ItemListMgr.CurrentItem; }
set { ItemListMgr.CurrentItem = value; }
}
Corresponding code in Contained ItemListMgr object is as follows:
public readonly ItemListModel _ItemList;
public ObservableCollection<ItemModel> Items
{
get { return _ItemList.Items; }
set
{
_ItemList.Items = value;
OnPropertyChanged("Items");
}
}
private ItemModel _currentItem;
public ItemModel CurrentItem
{
get { return _currentItem; }
set
{
_currentItem = value;
OnPropertyChanged("CurrentItem");
}
}
The "Items" object is the list that gets displayed and even using the contained object "ItemListMgr", this part still works great. As the user scrolls through the list CurrentItem is set to the active item in "Items", but even though the WPF data entry elements are bound to the CurrentItem elements, they will not change when I scroll through the list.
The Xaml code to bind a text box is as follows:
Text="{Binding CurrentItem.ItemName, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}"
The Xaml code binds to the DataContext object, which is using a simple pass through to the contained ItemListMgr class. I've tried to add the OnPropertyChange to the pass through elements, but that didn't help and I would guess only added to the confusion.
As this is a large amount of code and fairly complex, I hope that I've given enough key elements for you to understand what I'm doing. Hopefully there is a way to allow this type of binding as it will greatly enhance what I'm doing.
Whatever help you can give will be greatly appreciated,
Paul
In order to filter an list of objects I've defined this property:
private ICollectionView _view;
public ICollectionView View
{
get
{
return _view;
}
set
{
_view = value;
OnPropertyChanged("View");
}
}
And then my filter:
item.View = CollectionViewSource.GetDefaultView(item.myList);
item.View.Filter = delegate(object o)
{
if (myCondition)
{
return true;
}
}
The filter works fine but as ICollectionView is an interface I can't work with my items: if I call them this way:
element1 = item.View[0].SomeProperty;
I recieve
Cannot apply indexing with [] to an expression of type 'System.ComponentModel.ICollectionView'
I've tried to set in the beginning View not as interface but later I couldn't make the filter work.
Doing so and trying to cast:
item.View = (ICollectionView)CollectionViewSource.GetDefaultView(item.myList);
Haven't brought me good results either.
What can I do in order not only to filter (in my case I display the items in a ComboBox) but also work with them... My aim is to be able to make a foreach loop for all elements remaining in the ComboBox. Is this possible?
Store the view separately from the list. Shorthand below, fill in with appropiate INotifyPropertyChanged and such.
List<SomeType> list;
ICollectionView view;
view = list as ICollectionView;
view.Filter = obj => obj.someprop == somevalue;
list[ 10 ].someprop = somevalue
Given List and ICollectionView:
List<SomeType> list;
ICollectionView view=CollectionViewSourse.GetDefaultView(list);
view.Filter=filter;
You can use
var filteredList=view.Cast<SomeType>().ToList();
to enable indexer for filtered collection. The list will change with the collection view. Therefore in some way it is not deterministic. I am not sure but quite interested to know what is your use case to force you to use indexer on collection view.
I'm working on implementing a master/details view in my application using a TreeView and a custom details view control. I'm also trying to stick to the MVVM pattern.
Right now the TreeView is bound to a collection of view model objects that contain all of the details and the details view is bound to the selected item of the TreeView.
This works great... until one of the TreeView nodes has 5,000 children and the application is suddenly taking up 500MB of RAM.
Main window view model:
public class MainWindowViewModel
{
private readonly List<ItemViewModel> rootItems;
public List<ItemViewModel> RootItems { get { return rootItems; } } // TreeView is bound to this property.
public MainWindowViewModel()
{
rootItems = GetRootItems();
}
// ...
}
Item view model:
public ItemViewModel
{
private readonly ModelItem item; // Has a TON of properties
private readonly List<ItemViewModel> children;
public List<ItemViewModel> Children { get { return children; } }
// ...
}
Here's how I'm binding the details view:
<View:ItemDetails DataContext="{Binding SelectedItem, ElementName=ItemTreeView}" />
I'm fairly new to WPF and the MVVM pattern, but it seems like a waste to I want to bind the TreeView to a collection of a smaller, simplified object that only has properties necessary for displaying the item (like Name and ID), then once it is selected have all of the details loaded. How would I go about doing something like this?
Overview
This should be a simple matter of binding the TreeView's selected item property to something on your source. However, because of the way the TreeView control was built, you have to write more code to get an MVVM-friendly solution, using out-of-the-box WPF.
If you're using vanilla WPF (which I'm assuming you are), then I'd recommend going with an attached behavior. The attached behavior would bind to an action on your main view model that would be invoked when the TreeView's selection changes. You could also invoke a command instead of an action, but I'm going to show you how to use an action.
Basically, the overall idea is to use one instance of your details view model that will be made available as a property of your master view model. Then, instead of your RootItems collection having hundreds of instances of view models, you can use light-weight objects that simply have a display name for the node and perhaps some kind of id field behind them. When the selection on your TreeView changes, you want to notify your details view model by either calling a method or setting a property. In the demonstration code below, I'm setting a property on the DetailsViewModel called Selection.
Walkthrough with Code
Here's the code for the attached behavior:
public static class TreeViewBehavior
{
public static readonly DependencyProperty SelectionChangedActionProperty =
DependencyProperty.RegisterAttached("SelectionChangedAction", typeof (Action<object>), typeof (TreeViewBehavior), new PropertyMetadata(default(Action), OnSelectionChangedActionChanged));
private static void OnSelectionChangedActionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var treeView = sender as TreeView;
if (treeView == null) return;
var action = GetSelectionChangedAction(treeView);
if (action != null)
{
// Remove the next line if you don't want to invoke immediately.
InvokeSelectionChangedAction(treeView);
treeView.SelectedItemChanged += TreeViewOnSelectedItemChanged;
}
else
{
treeView.SelectedItemChanged -= TreeViewOnSelectedItemChanged;
}
}
private static void TreeViewOnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var treeView = sender as TreeView;
if (treeView == null) return;
InvokeSelectionChangedAction(treeView);
}
private static void InvokeSelectionChangedAction(TreeView treeView)
{
var action = GetSelectionChangedAction(treeView);
if (action == null) return;
var selectedItem = treeView.GetValue(TreeView.SelectedItemProperty);
action(selectedItem);
}
public static void SetSelectionChangedAction(TreeView treeView, Action<object> value)
{
treeView.SetValue(SelectionChangedActionProperty, value);
}
public static Action<object> GetSelectionChangedAction(TreeView treeView)
{
return (Action<object>) treeView.GetValue(SelectionChangedActionProperty);
}
}
Then, in the XAML on your TreeView element, apply the following: local:TreeViewBehavior.SelectionChangedAction="{Binding Path=SelectionChangedAction}". Note that you will have to substitute local for the namespace of the TreeViewBehavior class.
Now, add the following properties to your MainWindowViewModel:
public Action<object> SelectionChangedAction { get; private set; }
public DetailsViewModel DetailsViewModel { get; private set; }
In your MainWindowViewModel's constructor, you need to set the SelectionChangedAction property to something. You might do SelectionChangedAction = item => DetailsViewModel.Selection = item; if your DetailsViewModel has a Selection property on it. That's entirely up to you.
And finally, in your XAML, wire the details view up to its view model like so:
<View:ItemDetails DataContext="{Binding Path=DetailsViewModel}" />
That's the basic architecture of an MVVM friendly solution using straight WPF. Now, with that said, if you're using a framework like Caliburn.Micro or PRISM, your approach would probably be different than what I've provided here. Just keep that in mind.
I have a user control that contains a combobox. I want to be able to edit the Items property for the combo box but im not really sure how to do that. I've tried adding the Items property to my user control class but im not sure what the value is thats returned when you set the property in the properties menu of visual studio. I have the property setup like this:
[Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design",
"System.Drawing.Design.UITypeEditor, System.Drawing")]
public ComboBox.ObjectCollection Items
{
get
{
return this.comboBox.Items;
}
set
{
this.comboBox.Items.Add(value);
}
}
Wrap the Items property of your UserControl's ComboBox in a property like this:
[Description("The items in the UserControl's ComboBox."),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design", typeof(System.Drawing.Design.UITypeEditor))]
public System.Windows.Forms.ComboBox.ObjectCollection MyItems {
get {
return comboBox1.Items;
}
}
The EditorAttribute in the property specifies the UI element used for changing the property in the designer.
Try adding two methods for adding and removing ComboBox items:
public void AddItem(Object item)
{
this.comboBox.Items.Add(item);
}
public void RemoveItem(Object item)
{
this.comboBox.Items.Remove(item);
}