4
\$\begingroup\$

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...

\$\endgroup\$
3
\$\begingroup\$

Your approach looks fine to me. Except i would probably use prefix-casting, to get cast exceptions straigt away if something goes wrong, instead of using as.

This can be achieved without modifying code-behind tho. 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); } }
\$\endgroup\$
  • \$\begingroup\$ Wow this is amazing - turns out I have those "item view models" implementing a ISelectable interface so they all have a IsSelected 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 the ListView.ItemContainerStyle block understand that the view model it should validate at design-time against is that of ListView.ItemsSource? (like ListView.ItemTemplate does) ...'cause for a novice dev it's not obvious that it works at run-time! \$\endgroup\$ – Mathieu Guindon Sep 30 '13 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 Oct 1 '13 at 6:14
  • \$\begingroup\$ I meant this:stackoverflow.com/questions/19100882/… \$\endgroup\$ – Mathieu Guindon Oct 1 '13 at 13:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.