How to Attach Command to Event in WPF Using MVVM - DataGrid Selection Changed to Update the RowCount Displayed in a TextBlock

10,913

So, as far as I understand, you want to have TextBlock which says:

"5 of 100 items selected"

I've reconstructed your window, the button below the list shows how many items you have selected.

Capture

Is that correct? If that is really all you want to do, you don't need to involve the ViewModel at all:

All I did, was this:

<Button Content="{Binding SelectedItems.Count, ElementName=cultureDataGrid}" />

Does this help in some way already or is there a reason for using the pretty complex method with event to command etc.?

EDIT

Your problem basically melts down to "How can I bind to SelectedItems property of a DataGrid. Here is how I solved it:

Define a behavior with a dependency property for the DataGrid, which relays the SelectedItems of the DataGrid to the DependencyProperty:

public class BindableSelectedItems : Behavior<DataGrid>
{
    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.Register("SelectedItems", typeof (IList), typeof (BindableSelectedItems), new PropertyMetadata(default(IList), OnSelectedItemsChanged));

    private static void OnSelectedItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        var grid = ((BindableSelectedItems) sender).AssociatedObject;
        if (grid == null) return;

        // Add logic to select items in grid
    }

    public IList SelectedItems
    {
        get { return (IList) GetValue(SelectedItemsProperty); }
        set { SetValue(SelectedItemsProperty, value); }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
    }

    void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var grid = (DataGrid) sender;
        SelectedItems = grid.SelectedItems;
    }
}

Attach this behavior to the DataGrid and bind the SelectedItems property to a property on your ViewModel:

<DataGrid x:Name="cultureDataGrid" ItemsSource="{Binding Cultures}">

    <i:Interaction.Behaviors>
        <behaviors:BindableSelectedItems x:Name="CulturesSelection" 
                                         SelectedItems="{Binding SelectedCultures, Mode=OneWayToSource}"/>
    </i:Interaction.Behaviors>

with the property on your ViewModel like this:

public IList SelectedCultures
{
    get { return _selectedCultures; }
    set
    {
        _selectedCultures = value;
        OnPropertyChanged("SelectedCultures");
    }
}

If you only want to get the count of selected items, you can bind to the behavior directly and don't need a field on the ViewModel for this:

<TextBlock Text="{Binding Path=SelectedItems.Count, ElementName=CulturesSelection}" Margin="5,0,0,0" Foreground="White"/>

In your ViewModel you can use the SelectedItems property to work with the selection:

var selectedCultures= SelectedCultures.OfType<CultureViewModel>();

I've uploaded the solution, see the link in the comments.

I hope this helps and whish you good luck! There always are a thousand ways to do stuff in WPF and it is really hard to find the best one sometimes...

Share:
10,913
MoonKnight
Author by

MoonKnight

Updated on June 04, 2022

Comments

  • MoonKnight
    MoonKnight almost 2 years

    In order to attempt to get around the problem I outline in Binding Selected RowCount to TextBlock not Firing OnPropertyChanged after DataGrid Scroll; namely updating a TextBlock with the currently selected row count of a DataGrid when it scrolls. I have attempted to introduce the AttachedCommandBehaviour designed by Marlon Grech [an amazing class structure that allows you to bind commands to specific events of a given control].

    Now for the question, using this AttachedCommandBehaviour, how can I get a TextBlock to update based upon a DataGrid's SelectionChangedProperty?

    The XMAL is

    <Window x:Class="ResourceStudio.Views.AddCultureWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:viewModels="clr-namespace:ResourceStudio.ViewModels" 
            xmlns:converters="clr-namespace:ResourceStudio.Converters" 
            xmlns:dataAccess="clr-namespace:ResourceStudio.DataAccess" 
            xmlns:attachedCommand="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
            Title="Add Culture" 
            Height="510" Width="400" 
            WindowStartupLocation="CenterOwner" 
            VerticalContentAlignment="Stretch" 
            MinWidth="380" MinHeight="295">
       <DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
          <Grid>
             <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="24"/>
             </Grid.RowDefinitions>
    
             <Grid>
                <Grid.ColumnDefinitions>
                   <ColumnDefinition Width="15*"/>
                   <ColumnDefinition Width="377*"/>
                   <ColumnDefinition Width="15*"/>
                </Grid.ColumnDefinitions>
                <Grid Grid.Column="1">
                   <Grid>
                      <Grid.Resources>
                         <converters:EnumToBooleanConverter x:Key="enumToBooleanConverter"/>
                         <Style x:Key="HyperlinkButton" TargetType="Button">
                            <Setter Property="Template">
                               <Setter.Value>
                                  <ControlTemplate TargetType="Button">
                                     <ContentPresenter/>
                                  </ControlTemplate>
                               </Setter.Value>
                            </Setter>
                         </Style>
                      </Grid.Resources>
                      <Grid.RowDefinitions>
                         <RowDefinition Height="Auto"/>
                         <RowDefinition Height="1*"/>
                         <RowDefinition Height="Auto"/>
                         <RowDefinition Height="Auto"/>
                         <RowDefinition Height="Auto"/>
                      </Grid.RowDefinitions>
                      <GroupBox Header="Filters" Grid.Row="0" Margin="0,0,0,5">
                         <StackPanel VerticalAlignment="Top">
                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="2,2,2,2">
                               <RadioButton Content="All Cultures" Margin="10,5,10,5" 
                                            HorizontalAlignment="Left" 
                                            IsChecked="{Binding SelectedFilterType, 
                                                        Converter={StaticResource enumToBooleanConverter}, 
                                                        ConverterParameter=AllCultures}"/>
                               <RadioButton Content="Neutral Cultures" Margin="10,5,10,5" 
                                            HorizontalAlignment="Left" 
                                            IsChecked="{Binding SelectedFilterType, 
                                                        Converter={StaticResource enumToBooleanConverter}, 
                                                        ConverterParameter=NeutralCultures}"/>
                               <RadioButton Content="Specific Cultures" Margin="10,5,10,5" 
                                            HorizontalAlignment="Left" 
                                            IsChecked="{Binding SelectedFilterType, 
                                                        Converter={StaticResource enumToBooleanConverter}, 
                                                        ConverterParameter=SpecificCultures}"/>
                            </StackPanel>
                            <Grid>
                               <Grid.ColumnDefinitions>
                                  <ColumnDefinition Width="Auto"/>
                                  <ColumnDefinition Width="*"/>
                               </Grid.ColumnDefinitions>
                               <Label Content="Language:" Grid.Column="0"/>
                               <TextBox HorizontalAlignment="Stretch" Grid.Column="1" 
                                        Margin="2,0,2,0" Height="22"/>
                            </Grid>
                         </StackPanel>
                      </GroupBox>
                      <DataGrid x:Name="cultureDataGrid" Grid.Row="1" AlternatingRowBackground="Gainsboro" AlternationCount="2" 
                                HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
                                AutoGenerateColumns="False" RowHeaderWidth="0" IsReadOnly="True"
                                CanUserAddRows="False" CanUserDeleteRows="False" SelectionMode="Extended" 
                                EnableRowVirtualization="True" EnableColumnVirtualization="True" 
                                ItemsSource="{Binding Cultures, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
                         <DataGrid.Columns>
                            <DataGridTextColumn Header="Code" Binding="{Binding Code}" IsReadOnly="True"/>
                            <DataGridTextColumn Header="Language" Binding="{Binding Language}" IsReadOnly="True"/>
                            <DataGridTextColumn Header="LocalName" Binding="{Binding LocalName}" IsReadOnly="True"/>
                         </DataGrid.Columns>
    
                         <DataGrid.CellStyle>
                            <Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
                               <Style.Triggers>
                                  <Trigger Property="IsSelected" Value="True">
                                     <Setter Property="Background" Value="#FF007ACC"/>
                                     <Setter Property="Foreground" Value="White"/>
                                  </Trigger>
                               </Style.Triggers>
                            </Style>
                         </DataGrid.CellStyle>
                         <DataGrid.RowStyle>
                            <Style TargetType="DataGridRow">
                               <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, IsAsync=True}" />
                            </Style>
                         </DataGrid.RowStyle>
                      </DataGrid>
                      <StackPanel Grid.Row="2" HorizontalAlignment="Right">
                         <Button Name="button1" Style="{StaticResource HyperlinkButton}" 
                                 Focusable="False">
                            <TextBlock>
                                <Hyperlink Focusable="False">
                                    Select All
                                </Hyperlink>
                            </TextBlock>
                         </Button>
                      </StackPanel>
                      <GroupBox Grid.Row="3" Header="Options">
                         <CheckBox Content="Copy default values" Margin="3,3"/>
                      </GroupBox>
                      <StackPanel Grid.Row="4" Orientation="Horizontal" 
                                  HorizontalAlignment="Right" Margin="0,2,0,2">
                         <Button Content="Select" Width="70" Height="25" 
                                 Margin="0,5,5,5" HorizontalAlignment="Right" 
                                 VerticalContentAlignment="Center" IsDefault="True"/>
                         <Button Content="Cancel" Width="70" Height="25" 
                                 Margin="5,5,0,5" HorizontalAlignment="Right" 
                                 VerticalContentAlignment="Center" IsCancel="True"/>
                      </StackPanel>
                   </Grid>
                </Grid>
             </Grid>
    
             <StatusBar Grid.Row="1" Margin="0,0.4,0.4,-0.4">
                <StatusBarItem DockPanel.Dock="Left" Background="#FF007ACC" Margin="0,2,0,0">
                   <TextBlock Text="{Binding TotalSelectedCultures}"  Margin="5,0,0,0" Foreground="White"/>
                </StatusBarItem>
             </StatusBar>
          </Grid>
       </DockPanel>
    </Window>
    

    My ViewModel is

    public class CultureDataViewModel : ViewModelBase
    {
        public FilterType SelectedFilterType { get; private set; }
        public ICollectionView CulturesView { get; private set; }
        public MultiSelectCollectionView<CultureViewModel> Cultures { get; private set; }
    
        public CultureDataViewModel()
        {
            SelectedFilterType = FilterType.AllCultures;
            LoadCultures();
        }
    
        void OnCultureViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            string IsSelected = "IsSelected";
            (sender as CultureViewModel).VerifyPropertyName(IsSelected);
            if (e.PropertyName == IsSelected)
                this.OnPropertyChanged("TotalSelectedCultures");
        }
    
        void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null && e.NewItems.Count != 0)
                foreach (CultureViewModel cultVm in e.NewItems)
                    cultVm.PropertyChanged += this.OnCultureViewModelPropertyChanged;
    
            if (e.OldItems != null && e.OldItems.Count != 0)
                foreach (CultureViewModel cultVm in e.OldItems)
                    cultVm.PropertyChanged -= this.OnCultureViewModelPropertyChanged;
        }
    
        public void LoadCultures()
        {
            // Fill the Culutres collection...
        }
    
        public string TotalSelectedCultures
        {
            get
            {
                int selectedCultures = this.Cultures.SelectedItems.Count;
                return String.Format("{0:n0} of {1:n0} cultures selected",
                                            selectedCultures,
                                            Cultures.Count);
            }
        }
    }
    

    The link to a downloadable example of the use of the AttachedCommandBehaviour is Here. Your help is most appreciated...