Managing multiple selections with MVVM - c#

On my journey to learning MVVM I've established some basic understanding of WPF and the ViewModel pattern. I'm using the following abstraction when providing a list and am interested in a single selected item.
public ObservableCollection<OrderViewModel> Orders { get; private set; }
public ICollectionView OrdersView
{
get
{
if( _ordersView == null )
_ordersView = CollectionViewSource.GetDefaultView( Orders );
return _ordersView;
}
}
private ICollectionView _ordersView;
public OrderViewModel CurrentOrder
{
get { return OrdersView.CurrentItem as OrderViewModel; }
set { OrdersView.MoveCurrentTo( value ); }
}
I can then bind the OrdersView along with supporting sorting and filtering to a list in WPF:
<ListView ItemsSource="{Binding Path=OrdersView}"
IsSynchronizedWithCurrentItem="True">
This works really well for single selection views. But I'd like to also support multiple selections in the view and have the model bind to the list of selected items.
How would I bind the ListView.SelectedItems to a backer property on the ViewModel?

Add an IsSelected property to your child ViewModel (OrderViewModel in your case):
public bool IsSelected { get; set; }
Bind the selected property on the container to this (for ListBox in this case):
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
IsSelected is updated to match the corresponding field on the container.
You can get the selected children in the view model by doing the following:
public IEnumerable<OrderViewModel> SelectedOrders
{
get { return Orders.Where(o => o.IsSelected); }
}

I can assure you: SelectedItems is indeed bindable as a XAML CommandParameter
There is a simple solution to this common issue; to make it work you must follow ALL the following rules:
Following Ed Ball's suggestion, on your XAML command databinding, define the CommandParameter attribute BEFORE the Command attribute. This a very time-consuming bug.
Make sure your ICommand's CanExecute and Execute methods have a parameter of type object. This way you can prevent silenced cast exceptions that occur whenever the databinding's CommandParameter type does not match your Command method's parameter type:
private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)
{
// Your code goes here
}
private bool OnDeleteSelectedItemsExecute(object SelectedItems)
{
// Your code goes here
}
For example, you can either send a ListView/ListBox's SelectedItems property to your ICommand methods or the ListView/ListBox itself. Great, isn't it?
I hope this prevents someone from spending the huge amount of time I did to figure out how to receive SelectedItems as a CanExecute parameter.

One can try creating an attached property.
Doing so will save one from adding the IsSelected property for each and every list you bind. I have done it for ListBox, but it can be modified for use a in a list view.
<ListBox SelectionMode="Multiple"
local:ListBoxMultipleSelection.SelectedItems="{Binding SelectedItems}" >
More info: WPF – Binding ListBox SelectedItems – Attached Property VS Style .

If you're using MVVM-LIGHT you can use this pattern:
https://galasoft.ch/posts/2010/05/handling-datagrid-selecteditems-in-an-mvvm-friendly-manner
Not especially elegant but looks like it should be reliable at least

Related

WPF/MVVM - Checking A MenuItem Based On String Match

I'm attempting to implement a theme-selection menu in a WPF/MVVM application. I've got the selection itself working, but can't seem to figure out how to set IsChecked on the appropriate MenuItem with pure databinding (aka without breaking the MVVM pattern).
XAML:
<MenuItem Header="_Theme">
<MenuItem Header="Classic" Command="{Binding ChangeThemeCommand}" CommandParameter="Classic" />
<MenuItem Header="Metro White" Command="{Binding ChangeThemeCommand}" CommandParameter="MetroWhite" />
</MenuItem>
ViewModel:
RelayCommand _changeThemeCommand;
public ICommand ChangeThemeCommand
{
get
{
return _changeThemeCommand ?? (_changeThemeCommand = new RelayCommand(param =>
{
ThemeManager.CurrentTheme = param.ToString();
}));
}
}
The theming is being handled by Actipro's WPF control suite (http://www.actiprosoftware.com); as you can see, the current theme is represented as a string only.
My problem lies in figuring out how to bind IsChecked in a way that will mark the MenuItem for the active theme. The way the XAML is currently structured, that would mean matching the current theme name to the MenuItem's CommandParameter.
Any tips/pointers would be greatly appreciated.
Your first problem is that you are hard-coding all your themes. Better would be to create a class called Theme:
public class Theme : INotifyPropertyChanged
{
public string Name { get; set; } // Implement PropertyChanged event on this.
public bool Checked { get; set; } // Implement PropertyChanged event on this.
}
In your main view model, have an observable collection of these, then fill it up with your themes, i.e.:
ObservableCollection<Theme> Themes { get; private set; }
In constructor, something like:
Themes.Add(new Theme() { Name = "Classic" });
Themes.Add(new Theme() { Name = "MetroWhite" });
Now your context menu should look something like:
<MenuItem Header="_Theme" ItemsSource="{Binding Themes}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Name}" IsChecked="{Binding Checked}" IsCheckable="True"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
Now, this gives you a set of themes, and when you click on one it's Checked property is set. Now you can assign your Command to the MenuItems, preferably as part of the Theme class (i.e. Theme.Set() seems like a reasonable OO design to me). Should all be pretty straightforward from here on.
Update
How do I enforce that only one theme is selected at once?
Assuming you have a MainViewModel, extend the Theme constructor to take a reference back to the MainViewModel. Then in your SetTheme() command, iterate over all other themes making sure they are not Checked.
void SetTheme()
{
foreach (Theme theme in MainViewModel.Themes)
{
if (theme != this)
{
theme.Checked = false;
}
}
// Do actual theme setting .
}
Why should I implement INotifyPropertyChanged?
Because the above doesn't work if you dont. Sure, you could just implement it for Checked, but as a matter of good practice I recommend implementing it for all public accessible properties that form part of the interface. That way if you use this ViewModel with some different View later on that wants to edit these properties, everything will just work.

changing the bound object in a datatemplate

Data templates are great, but I'm having a problem with binding in a particular situation. I have a class, Value, that has various descendants like StringValue, DateValue, etc. These Values show up in a Listbox. This template works fine, binding to a specific property of StringValue:
<DataTemplate DataType="{x:Type values:StringValue}">
<TextBox Margin="0.5"
Text="{Binding Path=Native}" />
</DataTemplate>
However, when I bind to an object itself, instead of a specific property, the changes don't update the object, as in this template:
<DataTemplate DataType="{x:Type values:LookupValue}">
<qp:IncrementalLookupBox SelectedValue="{Binding Path=., Mode=TwoWay}"
LookupProvider="{Binding ElementName=EditWindow, Path=ViewModel.LookupProvider}">
</qp:IncrementalLookupBox>
</DataTemplate>
IncrementalLookupBox is a UserControl that ultimately allows a user to select a LookupValue, which should replace the item bound in the template. If this was bound to a simple type like an int or string, the binding would replace the object, so I'm not sure what the difference is with a more complex object. I know that the IncrementalLookBox is working, because binding some textboxes to the properties of SelectedValue (which is a dependency property) shows the correctly selected LookupValue.
In case it makes the situation more clear, here is the implementation of SelectedValue:
public LookupValue SelectedValue
{
get { return (LookupValue)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(LookupValue), typeof(IncrementalLookupBox), new PropertyMetadata(OnSelectedValuePropertyChanged));
private static void OnSelectedValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as IncrementalLookupBox;
obj.OnSelectedValuePropertyChanged(e);
}
private void OnSelectedValuePropertyChanged(DependencyPropertyChangedEventArgs e)
{
CheckForSelectedValueInLookups();
}
If all else fails consider using a ValueConverter to get the value you require.
Edit: this does not work. See link in comments below.
Make sure your class implements INotifyPropertyChanged and raise PropertyChanaged here:
private void OnSelectedValuePropertyChanged(DependencyPropertyChangedEventArgs e)
{
CheckForSelectedValueInLookups();
// RaisePropertyChanged();
}
My issue is the same as described here:
WPF TwoWay Binding of ListBox using DataTemplate
Apparently if I don't write enough text here, my answer will be converted to a comment and not close out the question. So, to summarize the issue, a two-way Binding=. in a datatemplate used in a ListBox (or any ItemsControl I image) won't work, because it is not the object itself being bound, but the ListBoxItem that contains it.

UserControl depends on TreeView's (WPF) SelectedItem

I have two user controls, one contains a TreeView, one contains a ListView.
The TreeView has an itemsource and hierarchical data templates that fill the nodes and leafes (node=TvShow, leaf=Season).
The ListView should show the children of the selected TreeView item (thus, the selected season): the episodes of that season.
This worked fine when I had both the TreeView and the Listview defined in the same window, I could use something like this:
<ListView
x:Name="_listViewEpisodes"
Grid.Column="2"
ItemsSource="{Binding ElementName=_tvShowsTreeView, Path=SelectedItem.Episodes}">
How can I achieve this, when both controls are defined in separate user controls? (because in the context of one user control, I miss the context of the other user control)
This seems something pretty basic and I am getting frustrated that I can't figure it out by myself. I refuse to solve this with code-behind, I have a very clean MVVM project so far and I would like to keep it that way.
Hope that somebody can give me some advise!
First of all you have to created the SelectedValue proeprty in your ViewModel and bind the TreeView.SelectedItem property to it. Since the SelectedItem property is read-only I suggest you to create a helper to create OneWayToSource-like binding. The code should be like the following:
public class BindingWrapper {
public static object GetSource(DependencyObject obj) { return (object)obj.GetValue(SourceProperty); }
public static void SetSource(DependencyObject obj, object value) { obj.SetValue(SourceProperty, value); }
public static object GetTarget(DependencyObject obj) { return (object)obj.GetValue(TargetProperty); }
public static void SetTarget(DependencyObject obj, object value) { obj.SetValue(TargetProperty, value); }
public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null));
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null, OnSourceChanged));
static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SetTarget(d, e.NewValue);
}
}
The idea is simple: you have two attached properties, the Source and the Target. When the first one changes the PropertyChangedCallback is called and you simply setting the NewValue as the Target property value. In my opinion this scenario is helpful in a lot of cases when you need to bind the read-only property in XAML (especially in control templates).
I've created a simple model to demonstrate how to use this helper:
public class ViewModel : INotifyPropertyChanged {
public ViewModel() {
this.values = new ObservableCollection<string>()
{
"first",
"second",
"third"
};
}
ObservableCollection<string> values;
string selectedValue;
public ObservableCollection<string> Values { get { return values; } }
public string SelectedValue {
get { return selectedValue; }
set {
if (Equals(selectedValue, values))
return;
selectedValue = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedValue"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So, we have data source, selected value and we'll bind it like this:
<StackPanel>
<TreeView ItemsSource="{Binding Values}"
local:BindingWrapper.Source="{Binding SelectedItem, RelativeSource={RelativeSource Self}, Mode=OneWay}"
local:BindingWrapper.Target="{Binding SelectedValue, Mode=OneWayToSource}"
>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Header" Value="{Binding}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<TextBlock Text="{Binding SelectedValue}"/>
</StackPanel>
In the TreeView bound to the ItemsSource from the ViewModel I've created two bindings so they are changing the SelectedValue property in your ViewModel. TextBlock in the end of the sample is used just to show that this approach works.
About the very clean MVVM - I think that it is not the same as the "no code-behind". In my sample the ViewModel still doesn't know anything about your view and if you'll use another control to show your data e.g. ListBox you will be able to use the simple two-way binding and the "BindingWrapper" helper will not make your code unreadable or unportable or anything else.
Create a SelectedSeason property in your ViewModel and bind the ListView's ItemsSource to SelectedSeason.Episodes.
In a perfect world, you could now use a Two-Way binding in the TreeView to automatically update this property when the SelectedItem changes. However, the TreeView's SelectedItem property is readonly and cannot be bound. You can use just a little bit of code-behind and create an event handler for the SelectionChanged event of the TreeView to update your ViewModel's SelectedSeason there. IMHO this doesn't violate the the MVVM principles.
If you want a pure XAML solution, that a look at this answer.

Raise Property Changed event from other class

In my project I want to display a List on a user control. For that I have a CategoryView the user control with an ListView-control, where I want to display the List. And the CategoryViewModel. On the ViewModel I have a list - property where I also raise the property changed event.
public class CategoryViewModel : NotificationObject
{
private List<string> categoryList;
public List<string> CategoryList
{
get
{
return this.categoryList;
}
set
{
this.categoryList = value;
this.RaisePropertyChanged("CategoryList");
}
}
}
This List is binded to the ListView-element in the view.
If I change the List in the CategoryViewModel, it works fine and the property change event is raised. If I change the List from the MainWindowViewModel. No property Changed event is Raised and the View will not be updated. How do I have to do that?
On the MainWindowViewModel I change the CategoryList. The List will be filled correctly.
CategoryViewModel categoryViewModel = new CategoryViewModel();
categoryViewModel.CategoryList = logger.ReadLogfile(this.logFileName).ToList();
You seem to be somewhat confused. You have one ListView in your CategoryView UserControl. Its ItemsSource property can only be data bound to one collection, so clearly, when changing the collections in the main view model and the CategoryViewModel, only one will affect the ListView.
It seems from your code that the CategoryViewModel is set as the DataContext for the UserControl, so the collection in the main view model will not be connected to the ListView. If you want to data bind from the ListView to the collection in the main view model instead, then you'll need to use a RelativeSource Binding instead:
<ListView ItemsSource="{Binding SomeCollection, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourParentType}}}" ... />
Even so, now the collection in your CategoryViewModel will no longer be connected, so you'd better decide exactly what you want to do here.

Filter itemssource of one control with the selected value of another control in WPF

I have a combobox and a listbox in a WPF window.
The combobox's itemssource is set to a List of all Team objects. Team has 2 properties (TeamId and TeamName).
The listbox's itemssource is set to a List of all Player objects. Player on of Players properties is TeamId.
I would like to filter the list of Players in the Listbox to only show those Players whose TeamId matches the TeamId of the SelectedItem in my combobox.
I would prefer to do this all in XAML but I'm not really sure on what the correct way to do it in C# would be either. Any help would be appreciated.
I'm not sure you can do it entirely in xaml, i think you might need a tiny bit of work somewhere else. This is how i did it for something else.
Wrap your collection with a CollectionViewSource in your xaml (this makes one that has a sort on a specific property name):
<CollectionViewSource x:Key="ViewName" Source="{Binding YourBinding}">
<CollectionViewSource.SortDescriptions>
<comp:SortDescription PropertyName="Name" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
somewhere else, bind your listview to have this source as the itemssource:
<ListView x:Name="MyList" ItemsSource="{Binding Source={StaticResource ViewName}}" />
then somewhere in code, i have mine on a textbox property change listener, but you get the general idea. the ICollectionView interface has a filter member that you can use to filter things out.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var text = FilterTextBox.Text;
var source = MyList.Items as ICollectionView;
if (string.IsNullOrWhiteSpace(filter))
{
source.Filter = null;
}
else
{
source.Filter = delegate(object item)
{
var s = item as INamedItem;
return s.Name.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase) != -1;
};
}
}
Firstly, change all your bound collections to ObservableCollection.
Then, on the combobox, bind the SelectedValue to another property on your DataContext of type Team (you have implemented INotifyPropertyChanged right?).
When the SelectedValue changes, refresh the ListBox's bound collection with a filtered list from the collection of all players:
public ObservableCollection<Team> Teams { get;set;}
public ObservableCollection<Player> Players { get;set;}
private List<Player> AllPlayers {get;set}
public Team CurrentTeam
{
get
{
return this._currentTeam;
}
set
{
this._currentTeam = value;
this.Players = new ObservableCollection(this.AllPlayers.Where(x => x.TeamId = this._currentTeam.TeamId));
RaisePropertyChanged("CurrentTeam");
}
}
This is the quickest and easist way to do it. You could probably achieve this through CollectionView, but I think this is simpler to understand.

Categories

Resources