Here is some XAML
<HierarchicalDataTemplate DataType="{x:Type data:FolderEntity}"
ItemsSource="{Binding Path=FolderEntities,UpdateSourceTrigger=PropertyChanged}">
<Label Content="{Binding FolderName}"/>
</HierarchicalDataTemplate>
<TreeView/>
data:FolderEntity is a LINQ to SQL data class which implements the INotifyPropertyChanging and INotifyPropertyChanged interfaces.
My problem is that when I change the FolderEntities property the binding doesn't update. If I change the FolderName property the tree node that corresponds to that item will change, but the collection that is FolderEntities just wont.
I'm thinking WPF checks to see whether the collection reference has changed, or does the ItemsSource object have to be an ObservableCollection`1 for this to work?
Any input on the matter is much appreciated.
Yes, the underlying collection (FolderEntities) will need to be an ObservableCollection<T> for the HierarchicalDataTemplate to be notified of the changes. Either that or a collection that implements INotifyCollectionChanged.
This is how I got it to work, thanks Matt!
public abstract class ObservableHierarchy<T>
{
public T Current { get; set; }
public ObservableCollection<ObservableHierarchy<T>> Children { get; set; }
public ObservableHierarchy( T current, Func<T, IEnumerable<T>> expand )
{
this.Current = current;
this.Children = new ObservableCollection<ObservableHierarchy<T>>();
foreach ( var item in expand( current ) )
{
Children.Add( Create( item ) );
}
}
protected abstract ObservableHierarchy<T> Create( T item );
}
Then I specialize that base class to whatever data class I'm currently using.
public class ObservableFolderHierarchy:
ObservableHierarchy<FolderEntity>
{
public ObservableFolderHierarchy( FolderEntity root )
: base( root, x => x.FolderEntities )
{
}
protected override ObservableHierarchy<FolderEntity> Create( FolderEntity item )
{
return new ObservableFolderHierarchy( item );
}
}
Slightly modified XAML and it works just great! Changes to the collection or properties of Current updates the TreeView accordingly.
<HierarchicalDataTemplate DataType="{x:Type ui:ObservableFolderHierarchy}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="/UI/Resources/folder.png" Width="16" Height="16"/>
<TextBlock Text="{Binding Current.FolderName}"/>
</StackPanel>
</HierarchicalDataTemplate>
Matt is correct. For the data binding engine to be notified of a change within a collection it need to bind to an ObservableCollection.
What you were binding to was the property and PropertyChanged (from INotifyPropertyChanged) was only being called when the property was being set e.g. FolderEntities = aNewValue; the binding engine was unaware of any changes that occurred within the collection.
Related
EDIT : Question was not clear enough. In fact there are two of them.
Q1 :
I have a UserControl "CustomView" that is dynamically created with a template:
<Window.Resources>
<DataTemplate DataType="{x:Type my:CustomViewModel}">
<my:CustomView/>
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Path=CustomList}"/>
Where CustomList is a Property of type ObservableCollection<'CustomViewModel> belonging to MainWindowViewModel, which is the Window's DataContext.
In CustomView's Xaml code, there are some Properties binded to CustomViewModel's Properties. Everything works properly. But when I try to do this in CustomView's code behind :
public CustomView()
{
InitializeComponents();
if (this.DataContext == null) Console.WriteLine ("DataContext is null");
else Console.WriteLine(this.DataContext.GetType().ToString());
}
It is written in Console : 'DataContext is null', even if bindings are working betweeen CustomView and CustomViewModel. Do you know why it's working?
Q2 :
Now, imagine that CustomView has another UserControl (IndexPicker) inside of it. IndexPicker has an associated ViewModel too (IndexPickerViewModel) who's in charge with data access. I need to bind one property ("Index") of this IndexPickerViewModel to the previous CustomViewModel's property "Id". I want to instantiate it in StaticResources and bind it to the CustomViewModel (which I believe is the dataContext according to my previous question):
<UserControl x:Class="MyView.CustomView"
...
<UserControl.Resources>
<DataTemplate DataType="{x:Type myPicker:IndexPickerViewModel}">
<myPicker:IndexPicker/>
</DataTemplate>
<myPicker:IndexPickerViewModel x:Key="pickerViewModel" Index="{Binding Path=Id}/>
</Window.Resources/>
<ContentControl Content={StaticResource pickerViewModel}/>
What I have tried : I tried to make "IndexPickerViewModel" inherit from "DependencyObject" and make "Index" a DependencyProperty. But the following error message shows up :
"System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Id; DataItem=null; target element is 'IndexPickerViewModel' (HashCode=59604175); target property is 'Index' (type 'Nullable`1')
I believe this is because of what I asked just above. But is it possible to do something like that? If yes, what am I missing? And : Is this a stupid idea?
Thank you in advance for any help.
Now, imagine that CustomView has another UserControl (IndexPicker) inside of it. IndexPicker has an associated ViewModel too (IndexPickerViewModel) who's in charge with data access. I need to bind one property ("Index") of this IndexPickerViewModel to the previous CustomViewModel's property "Id". I want to instantiate it in StaticResources and bind it to the CustomViewModel (which I believe is the dataContext according to my previous question)
If IndexPicker doesn't have an explicitly set datacontext then IndexPicker will inherit the datacontext from it's parent element.
However if IndexPicker does already have a datacontext then you will have to use relative source binding with an ancestor search:
Index="{Binding Id, RelaticeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, FallbackValue={x:Null}}"
Of course you can probably already sense that this is messy. Going after standard properties of a UIElement or Control is quite safe (and common), but when you start going after custom properties then you are introducing dependencies between the child control and its parent (when the child control shouldn't know much of anything about its parent), and you are also bound to start getting binding errors at some stage (hence the use of a fallback value).
It seems that I've asked too early because I've found answers by myself.
Answer to Question1
When you have a UserControl that is dynamically created from a DataTemplate in which it is associated with another object (belonging to a ViewModel or to a Resource), this object is defined as the DataContext of the UserControl. However, you cannot reach it in the UserControl's constructor, you have to wait until the "Loaded" event is raised :
public CustomUserControl()
{
InitializeComponent();
Console.WriteLine(this.DataContext.ToString());
// This doesn't work : DataContext is null
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Console.WriteLine(this.DataContext.ToString());
// or
Console.WriteLine((sender as UserControl).DataContext.ToString());
// this is Ok.
}
Answer to Question2
This is how you do to get a UserControl whose ViewModel is instantiated in a parent UserControl.Resources :
You don't do it.
Instead, you instantiate its ViewModel in its parent ViewModel. Full example :
MainWindow.xaml:
<Window x:Class="MainWindow"
...
xmlns:local="clr-namespace:my_project_namespace"
xmlns:cust="clr-namespace:CustomUserControl;assembly=CustomUserControl"
...>
<Window.Resources>
<DataTemplate DataType="{x:Type cust:CustomControlViewModel}">
<cust:CustomControlView>
</DataTemplate>
<!-- Here are listed all the types inheriting from CustomControlViewModel and CustomControlView.-->
<!-- CustomControlViewModel and CustomControlView are used as "abstract" classes-->
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Path=CustomVMList}"/>
</Grid>
</Window>
MainWindowViewModel.cs:
namespace my_project_namespace
{
public class MainWindowViewModel
{
public ObservableCollection<CustomControlViewModel> CustomVMList { get; set; }
public MainWindowViewModel()
{
CustomVMList = new ObservableCollection<CustomControlViewModel>();
// Fill in the list...
}
}
}
CustomControlView.xaml
<UserControl x:class="CustomUserControl.CustomControlView"
...
xmlns:my="clr-namespace:IndexPicker;assembly=IndexPicker"
...>
<UserControl.Resources>
<DataTemplate DataType="{x:Type my:IndexPickerViewModel}">
<my:IndexPickerView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}/>
<ContentControl Content="{Binding Path=MyIndexPicker}"/>
</Grid>
</UserControl>
And this is where it's interesting :
CustomControlViewModel.cs:
namespace CustomUserControl
{
public class CustomControlViewModel : INotifyPropertyChanged
{
public IndexPickerViewModel MyIndexPicker{ get; set; }
public string Name { get ; set; }
public int Id
{
get
{
return MyIndexPicker.Index;
}
set
{
if (value != MyIndexPicker.Index)
{
MyIndexPicker.Index = value;
NotifyPropertyChanged("Id");
}
}
}
public CustomControlViewModel(string _name)
{
Name = _name;
MyIndexPicker = new IndexPickerViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
}
IndexPickerView.xaml:
<UserControl x:Class="IndexPicker.IndexPickerView"
...
...>
<Grid>
<Combobox ItemsSource="{Binding Path=MyTable}"
DisplayMemberPath="ColumnXYZ"
SelectedItem={Binding Path=SelectedRow}/>
</Grid>
</UserControl>
Finally
IndexPickerViewModel.cs:
namespace IndexPicker
{
public class IndexPickerViewModel : INotifyPropertyChanged
{
private DataAccess data;
public DataView MyTable { get; set; }
private DataRowView selectedRow;
public DataRowView SelectedRow
{
get { return selectedRow; }
set
{
selectedRow = value;
NotifyPropertyChanged("SelectedRow");
}
}
public int? Index
{
get
{
if (SelectedRow != null) return (int?)selectedRow.Row["Column_Id"];
else return null;
}
set
{
SelectedRow = MyTable[MyTable.Find((int)value)];
NotifyPropertyChanged("Index");
}
}
public IndexPickerViewModel()
{
data = new DataAccess();
MyTable = data.GetTableView("tableName");
MyTable.Sort = "Column_Id";
}
// And don't forget INotifyPropertyChanged implementation
}
}
This configuration is used with several different UserControls inheriting from CustomControlView and their ViewModel inheriting from CustomControlViewModel. They are dynamically created and listed in CustomVMList. Here CustomControlViewModel containing an IndexPicker is already a specialization.
Concrete use: Generic Dialog for CRUD database Tables, which can dynamically create UserControls depending on each Table Columns. The specialization shown here is used in case of a column containing a foreign key.
I hope its clear.
The code listed above may contain mistakes. Criticisms and remarks are welcome.
in my View, there are a DataGrid and a TextBox, which is bound to the DataGrid's Items.Count property:
<DataGrid x:Name="dataGrid" ItemsSource="{Binding dataTable}"/>
<TextBox Text="{Binding Items.Count,ElementName=dataGrid,Mode=OneWay,StringFormat={}{0:#}}"/>
The ViewModel has a property (e.g. ItemsCount) which I'd like to be bound to the Items.Count property of the DataGrid, but have no idea, how to achieve this.
class ViewModel : INotifyPropertyChanged
{
public DataTable dataTable {get;set;}
public int ItemsCount {get;set;}
}
Maybe I could also use the Rows.Count property of the DataTable the DataGrid is bound to, but how would i bind or link the two properties in the ViewModel?
So I basically want the ItemsCount property to be synchronized with the dataTable.Rows.Count property.
A common way to achieve your requirements are to declare properties to data bind to the UI controls:
<DataGrid x:Name="dataGrid" ItemsSource="{Binding Items}" />
<TextBox Text="{Binding ItemsCount}" />
...
// You need to implement the INotifyPropertyChanged interface properly here
private ObservableCollection<YourDataType> items = new ObservableCollection<YourDataType>();
public ObservableCollection<YourDataType> Items
{
get { return items; }
set { items = value; NotifyPropertyChanged("Items"); NotifyPropertyChanged("ItemCount"); }
}
public string ItemCount
{
get { Items.Count.ToString("{0:#}"); }
}
UPDATE >>>
As #Sivasubramanian has added his own requirement to your question, in case you need to update the item count specifically by adding to your collection, you can manually call the NotifyPropertyChanged method:
Items.Add(new YourDataType());
NotifyPropertyChanged("ItemCount");
ComboBox items do not reflect changes made from its source
Here is what I am trying to accomplish:
I have a WPF datagrid that binding to a database table, inside the datagrid there is a combobox(group ID) column bind to one of the columns from the database table; the combobox items are from another table(a list of group ID). The problem now is when the groupd ID list is changed from other table, the combo box items does not take effect.
Can anyone help? Have been stuct for a long time.
Here is XAML code:
<DataGridTemplateColumn Header="Group ID">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding GroupID, Mode=OneWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="ComboBoxTeamGrpID" SelectedItem="{Binding GroupID, Mode=TwoWay}" ItemsSource="{StaticResource ResourceKey=GroupIDList}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
Here is the code for GroupIDList:
public class GroupIDList : List<string>
{
public GroupIDList()
{
try
{
string tmp = ConfigurationManager.AppSettings["DataSvcAddress"];
Uri svcUri = new Uri(tmp);
JP790DBEntities context = new JP790DBEntities(svcUri);
var deviceQry = from o in context.Devices
where o.GroupID == true
select o;
DataServiceCollection<Device> cList = new DataServiceCollection<Device>(deviceQry);
for (int i = 0; i < cList.Count; i++)
{
this.Add(cList[i].ExtensionID.Trim());
}
this.Add("None");
//this.Add("1002");
//this.Add("1111");
//this.Add("2233");
//this.Add("5544");
}
catch (Exception ex)
{
string str = ex.Message;
}
}
}
Here is another problem related, can anyone help? thank you.
It is either because your GroupIdList is a List and not an ObservableCollection, or because you're binding to a StaticResource, which WPF assumes is unchanged so is only loaded once.
Change your List<string> to an ObservableCollection<string> which will automatically notify the UI when it's collection gets changed, and if that still doesn't work than change your ItemsSource from a StaticResource to a RelativeSource binding, such as
ItemsSource="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=DataContext.GroupIdList}"
Edit
Your parent ViewModel which has your DataGrid's ItemsSource collection should look something like below. Simply add another public property for GroupIdList and have it return your list. Then use the above RelativeSource binding to access it, assuming your DataGrid's ItemsSource is bound in the form of <DataGrid ItemsSource="{Binding MyDataGridItemsSource}" ... />
public class MyViewModel
{
private ObservableCollection<MyDataObject> _myDataGridItemsSource;
public ObservableCollection<MyDataObject> MyDataGridItemsSource
{
get { return _myDataGridItemsSource; }
set
{
if (value != _myDataGridItemsSource)
{
_myObjects = value;
ReportPropertyChanged("MyDataGridItemsSource");
}
}
}
private ObservableCollection<string> _groupIdList = new GroupIdList();
public ObservableCollection<string> GroupIdList
{
get { return _groupIdList; }
}
}
WPF will not poll everytime and check if your list changed. In Order to do this, as Rachel pointed at you should do something like :
public class GroupIDList : ObseravableCollection<string>
EDIT :
Here is my suggestion :
I actually wouldn't do it the way you did. What I do is I create a View Model for the whole grid, that looks like :
public class MyGridViewModel : DependencyObject
Which I would use as data context for my grid:
DataContext = new MyGridViewModel ();
Now the implementation of MyGridViewModel will contain a list of ViewModel that represent my GridRows, which is an ObservableCollection
public ObservableCollection<RowGridViewModel> RowItemCollection { get; private set; }
I will this in my dataGrid as such :
<Grid>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding RowItemCollection}" SelectionMode="Extended" SelectionUnit="Cell">
<DataGrid.Columns>
and All you need to do, is to fill in you RowItemColleciton with the correct data, and then bind you Columns to the correct Property in RowGridViewModel...in your case it would look like (but you have to initialize the GroupIDList :
public class RowGridViewModel: DependencyObject
{
public List<String> GroudIDList { get; set;
}
}
Let me if that help
I am working on a WPF desktop application using the MVVM pattern.
I am trying to filter some items out of a ListView based on the text typed in a TextBox. I want the ListView items to be filtered as I change the text.
I want to know how to trigger the filter when the filter text changes.
The ListView binds to a CollectionViewSource, which binds to the ObservableCollection on my ViewModel. The TextBox for the filter text binds to a string on the ViewModel, with UpdateSourceTrigger=PropertyChanged, as it should be.
<CollectionViewSource x:Key="ProjectsCollection"
Source="{Binding Path=AllProjects}"
Filter="CollectionViewSource_Filter" />
<TextBox Text="{Binding Path=FilterText, UpdateSourceTrigger=PropertyChanged}" />
<ListView DataContext="{StaticResource ProjectsCollection}"
ItemsSource="{Binding}" />
The Filter="CollectionViewSource_Filter" links to an event handler in the code behind, which simply calls a filter method on the ViewModel.
Filtering is done when the value of FilterText changes - the setter for the FilterText property calls a FilterList method that iterates over the ObservableCollection in my ViewModel and sets a boolean FilteredOut property on each item ViewModel.
I know the FilteredOut property is updated when the filter text changes, but the List does not refresh. The CollectionViewSource filter event is only fired when I reload the UserControl by switching away from it and back again.
I've tried calling OnPropertyChanged("AllProjects") after updating the filter info, but it did not solve my problem.
("AllProjects" is the ObservableCollection property on my ViewModel to which the CollectionViewSource binds.)
How can I get the CollectionViewSource to refilter itself when the value of the FilterText TextBox changes?
Many thanks
Don't create a CollectionViewSource in your view. Instead, create a property of type ICollectionView in your view model and bind ListView.ItemsSource to it.
Once you've done this, you can put logic in the FilterText property's setter that calls Refresh() on the ICollectionView whenever the user changes it.
You'll find that this also simplifies the problem of sorting: you can build the sorting logic into the view model and then expose commands that the view can use.
EDIT
Here's a pretty straightforward demo of dynamic sorting and filtering of a collection view using MVVM. This demo doesn't implement FilterText, but once you understand how it all works, you shouldn't have any difficulty implementing a FilterText property and a predicate that uses that property instead of the hard-coded filter that it's using now.
(Note also that the view model classes here don't implement property-change notification. That's just to keep the code simple: as nothing in this demo actually changes property values, it doesn't need property-change notification.)
First a class for your items:
public class ItemViewModel
{
public string Name { get; set; }
public int Age { get; set; }
}
Now, a view model for the application. There are three things going on here: first, it creates and populates its own ICollectionView; second, it exposes an ApplicationCommand (see below) that the view will use to execute sorting and filtering commands, and finally, it implements an Execute method that sorts or filters the view:
public class ApplicationViewModel
{
public ApplicationViewModel()
{
Items.Add(new ItemViewModel { Name = "John", Age = 18} );
Items.Add(new ItemViewModel { Name = "Mary", Age = 30} );
Items.Add(new ItemViewModel { Name = "Richard", Age = 28 } );
Items.Add(new ItemViewModel { Name = "Elizabeth", Age = 45 });
Items.Add(new ItemViewModel { Name = "Patrick", Age = 6 });
Items.Add(new ItemViewModel { Name = "Philip", Age = 11 });
ItemsView = CollectionViewSource.GetDefaultView(Items);
}
public ApplicationCommand ApplicationCommand
{
get { return new ApplicationCommand(this); }
}
private ObservableCollection<ItemViewModel> Items =
new ObservableCollection<ItemViewModel>();
public ICollectionView ItemsView { get; set; }
public void ExecuteCommand(string command)
{
ListCollectionView list = (ListCollectionView) ItemsView;
switch (command)
{
case "SortByName":
list.CustomSort = new ItemSorter("Name") ;
return;
case "SortByAge":
list.CustomSort = new ItemSorter("Age");
return;
case "ApplyFilter":
list.Filter = new Predicate<object>(x =>
((ItemViewModel)x).Age > 21);
return;
case "RemoveFilter":
list.Filter = null;
return;
default:
return;
}
}
}
Sorting kind of sucks; you need to implement an IComparer:
public class ItemSorter : IComparer
{
private string PropertyName { get; set; }
public ItemSorter(string propertyName)
{
PropertyName = propertyName;
}
public int Compare(object x, object y)
{
ItemViewModel ix = (ItemViewModel) x;
ItemViewModel iy = (ItemViewModel) y;
switch(PropertyName)
{
case "Name":
return string.Compare(ix.Name, iy.Name);
case "Age":
if (ix.Age > iy.Age) return 1;
if (iy.Age > ix.Age) return -1;
return 0;
default:
throw new InvalidOperationException("Cannot sort by " +
PropertyName);
}
}
}
To trigger the Execute method in the view model, this uses an ApplicationCommand class, which is a simple implementation of ICommand that routes the CommandParameter on buttons in the view to the view model's Execute method. I implemented it this way because I didn't want to create a bunch of RelayCommand properties in the application view model, and I wanted to keep all the sorting/filtering in one method so that it was easy to see how it's done.
public class ApplicationCommand : ICommand
{
private ApplicationViewModel _ApplicationViewModel;
public ApplicationCommand(ApplicationViewModel avm)
{
_ApplicationViewModel = avm;
}
public void Execute(object parameter)
{
_ApplicationViewModel.ExecuteCommand(parameter.ToString());
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
Finally, here's the MainWindow for the application:
<Window x:Class="CollectionViewDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CollectionViewDemo="clr-namespace:CollectionViewDemo"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<CollectionViewDemo:ApplicationViewModel />
</Window.DataContext>
<DockPanel>
<ListView ItemsSource="{Binding ItemsView}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}"
Header="Name" />
<GridViewColumn DisplayMemberBinding="{Binding Age}"
Header="Age"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel DockPanel.Dock="Right">
<Button Command="{Binding ApplicationCommand}"
CommandParameter="SortByName">Sort by name</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="SortByAge">Sort by age</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="ApplyFilter">Apply filter</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="RemoveFilter">Remove filter</Button>
</StackPanel>
</DockPanel>
</Window>
Nowadays, you often don't need to explicitly trigger refreshes. CollectionViewSource implements ICollectionViewLiveShaping which updates automatically if IsLiveFilteringRequested is true, based upon the fields in its LiveFilteringProperties collection.
An example in XAML:
<CollectionViewSource
Source="{Binding Items}"
Filter="FilterPredicateFunction"
IsLiveFilteringRequested="True">
<CollectionViewSource.LiveFilteringProperties>
<system:String>FilteredProperty1</system:String>
<system:String>FilteredProperty2</system:String>
</CollectionViewSource.LiveFilteringProperties>
</CollectionViewSource>
CollectionViewSource.View.Refresh();
CollectionViewSource.Filter is reevaluated in this way!
Perhaps you've simplified your View in your question, but as written, you don't really need a CollectionViewSource - you can bind to a filtered list directly in your ViewModel (mItemsToFilter is the collection that is being filtered, probably "AllProjects" in your example):
public ReadOnlyObservableCollection<ItemsToFilter> AllFilteredItems
{
get
{
if (String.IsNullOrEmpty(mFilterText))
return new ReadOnlyObservableCollection<ItemsToFilter>(mItemsToFilter);
var filtered = mItemsToFilter.Where(item => item.Text.Contains(mFilterText));
return new ReadOnlyObservableCollection<ItemsToFilter>(
new ObservableCollection<ItemsToFilter>(filtered));
}
}
public string FilterText
{
get { return mFilterText; }
set
{
mFilterText = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("FilterText"));
PropertyChanged(this, new PropertyChangedEventArgs("AllFilteredItems"));
}
}
}
Your View would then simply be:
<TextBox Text="{Binding Path=FilterText,UpdateSourceTrigger=PropertyChanged}" />
<ListView ItemsSource="{Binding AllFilteredItems}" />
Some quick notes:
This eliminates the event in the code behind
It also eliminates the "FilterOut" property, which is an artificial, GUI-only property and thus really breaks MVVM. Unless you plan to serialize this, I wouldn't want it in my ViewModel, and certainly not in my Model.
In my example, I use a "Filter In" rather than a "Filter Out". It seems more logical to me (in most cases) that the filter I am applying are things I do want to see. If you really want to filter things out, just negate the Contains clause (i.e. item => ! Item.Text.Contains(...)).
You may have a more centralized way of doing your Sets in your ViewModel. The important thing to remember is that when you change the FilterText, you also need to notify your AllFilteredItems collection. I did it inline here, but you could also handle the PropertyChanged event and call PropertyChanged when the e.PropertyName is FilterText.
Please let me know if you need any clarifications.
If I understood well what you are asking:
In the set part of your FilterText property just call Refresh() to your CollectionView.
I just discovered a much more elegant solution to this issue. Instead of creating a ICollectionView in your ViewModel (as the accepted answer suggests) and setting your binding to
ItemsSource={Binding Path=YourCollectionViewSourceProperty}
The better way is to create a CollectionViewSource property in your ViewModel. Then bind your ItemsSource as follows
ItemsSource={Binding Path=YourCollectionViewSourceProperty.View}
Notice the addition of .View This way the ItemsSource binding is still notified whenever there is a change to the CollectionViewSource and you never have to manually call Refresh() on the ICollectionView
Note: I can't determine why this is the case. If you bind directly to a CollectionViewSource property the binding fails. However, if you define a CollectionViewSource in your Resources element of a XAML file and you bind directly to the resource key, the binding works fine. The only thing I can guess is that when you do it completely in XAML it knows you really want to bind to the CollectionViewSource.View value and binds it for you acourdingly behind the scenes (how helpful! :/) .
I have a combo box that I have bound to a list that exists in my viewmodel. Now when a users makes a selection in that combo box I want a second combo box to update its content.
So, for example, combobox1 is States and combobox2 should contain only the Zipcodes of that state.
But in my case I don't have a predefined lists before hand for combobox2, I need to go fetch from a db.
Also, if needed, I could get all the potential values for combobox2 (for each combobox1 value) before hand, but I'd like to avoiding that if I can.
How do I implement in WPF and using MVVM? I'm fairly new to this whole wpf\databinding\mvvm world.
Something like the following. Note that the code is drastically simplified for the sake of example. In reality, your ViewModel would implement INotifyPropertyChanged and raise PropertyChanged events when the properties were modified.
The key though is the setter of SelectedState. Your ComboBox would bind its SelectedValue property to the ViewModel's SelectedState property. When the property changed, the ZipCodes collection gets re-loaded which another combobox would be bound to.
class MyViewModel {
public ObservableCollection<string> States {
get;
private set;
}
public ObservableCollection<string> ZipCodes {
get;
private set;
}
public string SelectedState {
get { return _selectedState; }
set {
_selectedState = value;
LoadZipCodes(_selectedState);
}
}
public string SelectedZipCode {
get;
set;
}
void LoadZipCodes(string state) {
// repopulate the ZipCodes property
}
}
Another solution. The approximate model:
class StateViewModel
{
public string StateName
{
get {...}
set {...}
}
public ObservableCollection<ZipCodeViewModel> ZipCodes
{
get {...}
set {...}
}
}
class ZipCodeViewModel
{
public string ZipCodeName
{
get {...}
set {...}
}
}
class MainViewModel
{
public ObservableCollection<StateViewModel> States
{
get {...}
set {...}
}
}
And XAML:
<ComboBox ItemsSource="{Binding Path=States}" IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=StateName}"></Label>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ContentControl Content="{Binding Path=States}">
<ContentControl.ContentTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=ZipCodes}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=ZipCodeName}"></Label>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>