This example is a simplified version of my requirement. If i can solve this then the rest is simple (I hope). Assume I have a class hierarchy of
Class A:
Name
List <Class B>
Class B:
Name
List <Class C>
Class C:
Name
Each class has a separate view, view model and model. My main view places each view in a DockPanel. The view of each class is a DataGrid that simply places the names of each list. When i click on a Name of class A it shows me names of class B below. and when i click a Name of class B it shows names of class C. So basically 3 user controls that are all DataGrid controls being shown on a dockpanel in my MainView.
I am still finding my feet in MVVM and I am not sure if this is the best way to go about doing this. The way i'd go about it is that each view model will have a property called SelectedItem being bound to the SelectedItem in the DataGrid on its view. When the property is set, another property called ChildViewModel will be set. But i am not sure how to set this up in my MainView and also in each control. How do i bind the control to it's parent datacontext and how do i bind the item source to the selected item from the parent?
I would have one ViewModel that is the DataContext for a Window/UserControl/etc that these controls are all grouped in. Then set up the collection of ClassA plus 3 SelectedItem properties. In the SelectedItem properties, you will want to set the next one to null or a default value.
private ObservableCollection<ClassA> _CollectionOfA;
public ObservableCollection<ClassA> CollectionOfA
{
get { return _CollectionOfA; }
set
{
if (value == _CollectionOfA)
return;
_CollectionOfA = value;
RaisePropertyChanged(() => CollectionOfA);
}
}
private ClassA _SelectedClassA;
public ClassA SelectedClassA
{
get { return _SelectedClassA; }
set
{
if (value == _SelectedClassA)
return;
_SelectedClassA = value;
SelectedClassB = null; //Or default this to first item in the list, etc.
RaisePropertyChanged(() => SelectedClassA);
}
}
//Repeat SelectedClassA pattern for SelectedClassB/SelectedClassC
public ClassB SelectedClassB { get; set; }
public ClassC SelectedClassC { get; set; }
Then in your bindings, you'll use SelectedClassA.CollectionOfClassB property as the ItemsSource for the 2nd DataGrid, etc.
<DataGrid ItemsSource="{Binding CollectionOfClassA}" SelectedItem="{Binding SelectedClassA}">
//columns defined here
</DataGrid>
<DataGrid ItemsSource="{Binding SelectedClassA.CollectionOfClassB}" SelectedItem="{Binding SelectedClassB}">
//columns defined here
</DataGrid>
<DataGrid ItemsSource="{Binding SelectedClassB.CollectionOfClassC}" SelectedItem="{Binding SelectedClassC}">
//columns defined here
</DataGrid>
EDIT:
The first option is my KISS approach. Another option would be to use something like MVVM Light which implements a weak event pattern available through it's messenger class. In the constructor of your second ViewModel, you register to listen for changes to the SelectedItem in the first ViewModel. And in the setter of the first ViewModel you send a message that the SelectedItem changed. Here is an example of what ClassBViewModel would look like (ClassAViewModel would only need its SelectedClassA setter to send a message, no listener in the constructor. While ClassCViewModel would listen but not need to send).
public class ClassBViewModel : ViewModelBase
{
private ClassA _SelectedClassA;
public ClassA SelectedClassA
{
get { return _SelectedClassA; }
set
{
if (value == _SelectedClassA)
return;
_SelectedClassA = value;
RaisePropertyChanged(() => SelectedClassA);
}
}
private ClassB _SelectedClassB;
public ClassB SelectedClassB
{
get { return _SelectedClassB; }
set
{
if (value == _SelectedClassB)
return;
var oldValue = _SelectedClassB;
_SelectedClassB = value;
Messenger.Default.Send(new PropertyChangedMessage<ClassB>(oldValue, value, "SelectedClassB"));
RaisePropertyChanged(() => SelectedClassB);
}
}
public ClassBViewModel()
{
Messenger.Default.Register<PropertyChangedMessage<ClassA>>(this, (message) => SelectedClassA = message.NewValue);
}
}
Your ClassBView xaml for the DataGrid would still look the same:
<DataGrid ItemsSource="{Binding SelectedClassA.CollectionOfClassB}" SelectedItem="{Binding SelectedClassB}">
//columns defined here
</DataGrid>
In my WPF Caliburn.Micro application, I have a datagrid and a checkbox with a corresponding ModelView bool property. I need to bind the checkbox to one of the datagrid's fields OneWay (which is easy). But also I want to bind the same checkbox to the property OneWayToSource. Could you please tell me how I can do that? I don't see how Multibinding can help here.
Thanks.
I don't know if this is a checkbox per row of the DataGrid, or a checkbox for a row with a particular id or index. Either way, you can use TwoWay binding, which will be the default anyway if your view model property has a getter and setter.
Your view model property should point to the instance of the record that the DataGrid is binding to.
E.g.
View Model
public ObservableCollection<Item> MyGridItems { get; set; }
public MyViewModel()
{
this.MyGridItems = ...
this.MySpecialItem = this.MyGridItems[0];
}
public Item MySpecialItem
{
get { return this.mySpecialItem; }
set { this.mySpecialItem = value; // notify of property change here }
}
View
<CheckBox IsChecked="{Binding MySpecialItem.MyBooleanProperty}" />
I have model:
public class Song
{
public int ContentID { get; set; }
public bool IsSelected
{
get
{
var song = PlayerHelper.ReadNowPlaying();
return song.Id == ContentID;
}
}
}
I have a view with ListBox:
<ListBox x:Name="songsLstBox" ItemsSource="{Binding Top100Songs}" />
And ViewModel with list of Songs items. So, sometimes i want to refresh (redraw) the listbox. It's need to display that IsSelected is changed (No, i can't using INotifyPropertyChanged in model and setting it in viewmodel).
So how i can redraw a listbox in WP7? I can't find any Refresh or Update method for UIElements.
I tried calling this.OnPropertyChanged("Top100Songs"); but it doesn't work. I tried calling UpdateLayout - the same.
One way is setting DataContex for page to null and then revert to my ViewModel. It works, but is so long (about 5 secs. for changing).
So any ideas?
Write your own collection wrapper and use it for the Top100Songs property
class SongCollection : ObservableCollection<Song>
{
public Refresh()
{
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
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 am trying to implement a search as you type screen in my Silverlight application. The idea is that I have a screen with a textedit control and a listbox. The listbox is filled with all my data.
When the user types something in the textbox the following happens:
All the items that are not containing all the letters from the user input are hidden.
The matching letters of the visible list items are highlighted with a different color.
I am not sure how to start with this, so all pointers, samples and hints are welcome!
I would suggest using a CollectionViewSource. A CollectionViewSource has the ability to filter items. You can bind your ListBox to the CollectionViewSource and handle Filter event to do the filtering. Bind your "Search Box" to a Text property which you can use in your Filter event. You can handle the "KeyUp" event of the TextBox control to kick off your filtering, by calling the Refresh method on the CollectionViewSource View.
Filtering Data using CollectionViewSource: http://xamlcoder.com/blog/2010/10/27/filtering-data-using-collectionviewsource/
http://msdn.microsoft.com/en-us/library/system.windows.data.collectionviewsource.filter.aspx
http://msdn.microsoft.com/en-us/library/system.componentmodel.icollectionview.aspx
http://timheuer.com/blog/archive/2009/11/04/updated-silverlight-3-datagrid-grouping-data-pagedcollectionview.aspx
http://bea.stollnitz.com/blog/?p=392
Sudo code:
// ViewModel - properties should fire NotifyPropertyChanged
public class ViewModel : INotifyPropertyChanged
{
public ViewModel
{
this.Data = new CollectionViewSource();
this.Data.Source = this.GenerateObjects();
this.Data.Filter += (s,e) =>
{
// TODO: add filter logic
DataObject item = e.Item as DataObject;
return item.Name.Contains(this.SearchText);
};
}
public string SearchText{get;set;}
public CollectionViewSource Data {get;set;}
private List<DataObject> GenerateObjects(){ // generate list of data objects }
}
// View XAML
<StackPanel>
<TextBox Text="{Binding SearchText, Mode=TwoWay}" KeyUp="OnKeyUp"/>
<ListBox ItemsSource="{Binding Data.View}"/>
</StackPanel>
// View Code Behind
public class View : UserControl
{
public View() { this.DataContext = new ViewModel(); }
private ViewModel ViewModel { get { return this.DataContext as ViewModel; } }
private OnKeyUp()
{
this.ViewModel.Data.View.Refresh();
}
}
You may want to start with the AutocompleteBox from the Silverlight Toolkit.
It has a number of handy points where you would be able to extend it's functionality, for example in the instance searching your pool of values.