As far as I know (and I don't claim to know much about this!), direct binding to ListView.SelectedItems
isn't possible in WPF. I've seen work-arounds involving code-behind which I'm not too crazy about, especially since I'm having a hard time with getting a DelegateCommand
to work, and decided to use RoutedCommands
. For the following XAML command binding...
<UserControl.CommandBindings>
<CommandBinding Command="{x:Static my:ClientSearchSection.AddSelectionCommand}"
CanExecute="CanExecuteAddSelectionCommand"
Executed="ExecuteAddSelectionCommand"/>
</UserControl.CommandBindings>
...I have the following code-behind:
public static readonly RoutedCommand AddSelectionCommand = new RoutedCommand();
private void CanExecuteAddSelectionCommand(object sender, CanExecuteRoutedEventArgs e)
{
if (Commands != null)
{
Commands.SelectedClients = SearchResultsList.SelectedItems.Cast<ClientViewModel>().ToList();
e.CanExecute = Commands.CanExecuteAddSelectionCommand();
}
e.Handled = true;
}
private void ExecuteAddSelectionCommand(object sender, ExecutedRoutedEventArgs e)
{
Commands.SelectedClients = SearchResultsList.SelectedItems.Cast<ClientViewModel>().ToList();
Commands.OnExecuteAddSelectionCommand();
e.Handled = true;
}
Where Commands
is a get-only private property that returns an interface implemented by the ViewModel, which defines all the CanExecuteXXXX
and ExecuteXXXX
methods.
Here's how I have it:
private IViewModel _viewModel;
private IClientsSearchSectionCommands Commands { get { return _viewModel as IClientsSearchSectionCommands; } }
public ClientSearchSection()
{
InitializeComponent();
DataContextChanged += ClientsSearchSection_DataContextChanged;
}
void ClientSearchSection_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
_viewModel = e.NewValue as IViewModel;
}
This allows me to implement the command code in the ViewModel, for example:
public bool CanExecuteAddSelectionCommand()
{
return SelectedClients.Count > 0;
}
public void OnExecuteAddSelectionCommand()
{
// whatever needs to happen here, I can access my model as needed
}
The trick that allows the SelectedItems
to work is with this property, exposed by the IClientsSearchSectionCommands
interface implemented by the ViewModel:
public IList<ClientViewModel> SelectedClients { get; set; }
...and the fact that I'm setting them in the commands' CanExecute
and Executed
handlers.
This works beautifully... but is it weird in any way? [How] could it be done better?
I don't want to dive into behaviors at this point, firstly because I have no clue about them, second, because I'm dragging a more junior developper into WPF & MVVM, coming from WinForms and inline-SQL-in-the-form's-code-behind, so I'd like to know if this code is easy enough to follow...
1 Answer 1
Your approach looks fine to me, except I would probably use prefix-casting, to get cast exceptions straight away if something goes wrong, instead of using as
.
This can be achieved without modifying code-behind though. You can bind container's IsSelected
property to appropriate item's viewmodel property.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" >
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
Then you can access selected items in your viewmodel by using:
protected IEnumerable<Item> SelectedItems { get { return Items.Where(x => x.IsSelected); } }
-
\$\begingroup\$ Wow this is amazing - turns out I have those "item view models" implementing a
ISelectable
interface so they all have aIsSelected
property, and with this XAML I can perform that binding at item level, only the designer blue-squigles{Binding IsSelected}
saying it can't resolve that property.. but it works! Is there a way to make theListView.ItemContainerStyle
block understand that the view model it should validate at design-time against is that ofListView.ItemsSource
? (likeListView.ItemTemplate
does) ...'cause for a novice dev it's not obvious that it works at run-time! \$\endgroup\$Mathieu Guindon– Mathieu Guindon2013年09月30日 17:21:11 +00:00Commented Sep 30, 2013 at 17:21 -
1\$\begingroup\$ @retailcoder, yes, you should either set an actual DataContext in Xaml or specify a design-time DataContext (see stackoverflow.com/questions/13863099/… for example). Normally it doesn't worth the effort tho. \$\endgroup\$Nikita B– Nikita B2013年10月01日 06:14:05 +00:00Commented Oct 1, 2013 at 6:14
-
\$\begingroup\$ I meant this:stackoverflow.com/questions/19100882/… \$\endgroup\$Mathieu Guindon– Mathieu Guindon2013年10月01日 13:27:45 +00:00Commented Oct 1, 2013 at 13:27