a praveen
a praveen

Reputation: 318

WPF - MVVM : How to Check/Uncheck all Items in a ListView

I have the following requirements:

  1. Window will show a ListView with multiple items.
  2. User should be able to check (Checkbox) any item. a) If one item, all items should be unchecked and disabled. b) If checked item is unchecked, than all items should be enabled.

As of now, I have the following incomplete code.

MainWindow XAML:

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="520.149" Width="732.463">
    <Window.Resources>
        <ResourceDictionary Source="MainWindowResource.xaml" />
    </Window.Resources>

    <Grid>
     <ListView x:Name="myListBox" ItemTemplate="{StaticResource OfferingTemplate}">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Columns="3" VerticalAlignment="Top"/>
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
        </ListView>
    </Grid>
</Window>

DataTemplete for ListView:

<DataTemplate x:Key="OfferingTemplate">
    <StackPanel>
        <Grid IsEnabled="{Binding IsEnabled}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="8"></ColumnDefinition>
                <ColumnDefinition Width="120"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="40"></RowDefinition>
                <RowDefinition Height="50"></RowDefinition>
                <RowDefinition Height="30"></RowDefinition>
            </Grid.RowDefinitions>
            <Rectangle Grid.Column="0" Grid.RowSpan="3" Fill="#F4CA16" />
            <Label
                Grid.Column="1"
                Grid.Row="0"
                Content="{Binding Title}"
                FontSize="18" FontWeight="Bold"
                Margin="0,0,0,0" />
            <TextBlock
                Grid.Column="1"
                Grid.Row="1"
                FontSize="10"
                Text="{Binding Description}"
                Foreground="Black"
                TextWrapping="WrapWithOverflow"
                Margin="5,0,0,0" />
            <CheckBox
                Grid.Column="1"
                Grid.Row="2"
                FontSize="14"
                IsChecked="{Binding IsSelected}"
                VerticalAlignment="Bottom"
                Margin="5,0,0,0">

                <TextBlock Text="Select" Margin="0,-2,0,0"/>
            </CheckBox>
        </Grid>
    </StackPanel>
</DataTemplate>

Model:

class MyModel
{
    public string Title { get; set; }
    public string Description { get; set; }
    public bool IsSelected { get; set; }
    public bool IsEnabled { get; set; }
}

ViewModel:

class MyViewModel : INotifyPropertyChanged
{
    private MyModel offering;

    public MyViewModel()
    {
        offering = new MyModel();
    }

    public int ID { get; set; }
    public string Title
    {
        get { return offering.Title; }
        set
        {
            offering.Title = value;
            RaisePropertyChanged("Title");
        }
    }
    public string Description
    {
        get { return offering.Description; }
        set
        {
            offering.Description = value;
            RaisePropertyChanged("Description");
        }
    }
    public bool IsSelected
    {
        get { return offering.IsSelected; }
        set
        {
            offering.IsSelected = value;
            RaisePropertyChanged("IsSelected");
        }
    }
    public bool IsEnabled
    {
        get { return offering.IsEnabled; }
        set
        {
            offering.IsEnabled = value;
            RaisePropertyChanged("IsEnabled");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Upvotes: 4

Views: 8971

Answers (1)

Bill Zhang
Bill Zhang

Reputation: 1919

This is an interesting question. Since the action you want applies to all items in the list, this logic should in list class level. Your MyViewModel class is fine. You need add some logic in your list class and XAML but thanks to Prism, it is quite easy.

The list class (not shown in your post) Contains:

    public ObservableCollection<MyViewModel> MyItems { get; set; } //Binding to ItemsSource

    private ICommand _selectCommand;

    public ICommand SelectCommand
    {
        get { return _selectCommand ?? (_selectCommand = new DelegateCommand<MyViewModel>(DoSelect)); }
    }

    private void DoSelect(MyViewModel myViewModel)
    {
        foreach(var item in MyItems)
            if (item != myViewModel)
            {
                item.IsSelected = false;
                item.IsEnabled = false;
            }
    }

    private ICommand _unselectCommand;

    public ICommand UnselectCommand
    {
        get { return _unselectCommand ?? (_unselectCommand = new DelegateCommand<MyViewModel>(DoUnselect)); }
    }

    private void DoUnselect(MyViewModel myViewModel)
    {
        foreach (var item in MyItems)
            if (item != myViewModel)
            {
                item.IsEnabled = true;
            }
    }

There are two commands, one for selecting and the other for unselecting. The magic is on XAML:

      <ListView ItemsSource="{Binding Path=MyItems}" x:Name="listView">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <CheckBox IsChecked="{Binding Path=IsSelected}" IsEnabled="{Binding Path=IsEnabled}">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Checked">
                                <i:InvokeCommandAction Command="{Binding ElementName=listView, Path=DataContext.SelectCommand}"
                                                       CommandParameter="{Binding}"/>
                            </i:EventTrigger>
                            <i:EventTrigger EventName="Unchecked">
                                <i:InvokeCommandAction Command="{Binding ElementName=listView, Path=DataContext.UnselectCommand}"
                                                       CommandParameter="{Binding}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </CheckBox>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

Using Prism's triggers, you can map CheckBox's Checked and Unchecked event to your list view model's commands and passing the item view model as parameter.

It is working perfectly but one thing is annoying, that setting item's IsSelected is separate. When you check a CheckBox, the item behind is set to true through DataBinding but all others are set through parent view model. If your post is all your requirement, you can remove IsChecked binding and put the logic of setting one IsSelected inside list view model, which looks clenaer and easier to write test code.

Upvotes: 5

Related Questions