如何使用 MVVM 将命令附加到 WPF 中的事件 - DataGrid 选择更改为更新显示在 TextBlock 中的 RowCount

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17558863/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 09:11:21  来源:igfitidea点击:

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

c#wpfeventsmvvmcommand

提问by MoonKnight

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 TextBlockwith the currently selected row count of a DataGridwhen it scrolls. I have attempted to introduce the AttachedCommandBehaviourdesigned by Marlon Grech [an amazing class structure that allows you to bind commands to specific events of a given control].

为了尝试解决我在Binding Selected RowCount to TextBlock not Firing OnPropertyChanged after DataGrid Scroll 中概述的问题;即在滚动时使用aTextBlock的当前选定行数更新 a DataGrid。我试图介绍AttachedCommandBehaviour由 Marlon Grech 设计的 [一个惊人的类结构,它允许您将命令绑定到给定控件的特定事件]。

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

现在的问题是,使用 this AttachedCommandBehaviour,我怎样才能获得TextBlock基于 aDataGrid的更新SelectionChangedProperty

The XMAL is

XMAL 是

<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 AttachedCommandBehaviouris Here. Your help is most appreciated...

使用 的可下载示例的链接AttachedCommandBehaviour这里。非常感谢您的帮助...

回答by Marc

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

所以,据我所知,你想要 TextBlock 说:

"5 of 100 items selected"

“已选择 100 个项目中的 5 个”

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:

那是对的吗?如果这真的是您想要做的,那么您根本不需要涉及 ViewModel:

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 SelectedItemsproperty of a DataGrid. Here is how I solved it:

您的问题基本上可以归结为“我如何绑定到SelectedItemsDataGrid 的属性。这是我解决它的方法:

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

为 DataGrid 定义一个具有依赖属性的行为,它将 DataGrid 的 中继SelectedItems到 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 SelectedItemsproperty to a property on your ViewModel:

将此行为附加到 DataGrid 并将该SelectedItems属性绑定到您的 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:

使用您的 ViewModel 上的属性,如下所示:

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:

如果您只想获取所选项目的数量,您可以直接绑定到行为,并且不需要 ViewModel 上的字段:

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

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

在您的 ViewModel 中,您可以使用该SelectedItems属性来处理选择:

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

我希望这会有所帮助,并祝你好运!在 WPF 中总是有一千种方法可以做事,有时真的很难找到最好的……