复选框与数据网格 WPF

6

我正在尝试使用MVVM在WPF 4.0中创建DataGrid...

所需功能:

  1. 使用复选框(单击)选择多行
  2. 选择全部复选框以检查数据网格中的所有复选框

类似于这样 -

enter image description here

已经过去了两天,我无法有效地解决问题...

现在我需要一个可用的示例...

如果有人有工作的解决方案与我分享,我将不胜感激...

请不要告诉我谷歌这个东西,因为没有一个能帮到我...

更新 -

  1. 我正在使用列的自动生成
  2. 我不想在我的模型中添加“IsSelected”或任何此类属性。
  3. 我仅面临两个问题 -

首先,“全选”功能,即在列标头中存在的复选框点击上检查所有复选框...(我可以选择和取消选择数据网格,但无法勾选/取消勾选复选框)

其次,鼠标单击多个选择,而无需按住Ctrl键..


哪个部分给你带来了最大的问题?是将“CheckBox”放在标题中吗?还是让它选中/取消所有项目框? - dkozl
1
第一个谷歌搜索结果:这里。兄弟,说真的。我倾向于在Stackoverflow中提供完整的工作示例,但您缺乏兴趣是如此明显,以至于我不会回答这个问题。 - Federico Berasategui
HighCore - 这个我本来可以在5分钟内搞定,不用查这些东西!
  1. 我正在使用列的自动生成功能。
  2. 我不想在我的模型中添加“IsSelected”或任何类似的属性。
  3. 我只面临两个问题。首先,“全选”功能,即在单击列标题中存在的复选框时检查所有复选框。其次,在鼠标单击时进行多重选择而无需按住Ctrl键。
- Rajat Suneja
3个回答

19
当您使用MVVM时,必须了解什么被认为是数据,什么是严格的UI。
您的SelectedItems是否将成为数据的一部分,还是仅限于UI?
如果它是数据的一部分,您真的应该在数据模型上拥有一个IsSelected属性,即使这意味着扩展数据类以包括一个IsSelected属性,或者创建一个仅包含bool IsSelected和object MyDataItem的包装器类。第一个选项可能更好,因为您可以保持AutoGenerateColumns="True",并且它使列绑定更简单。
然后,您只需将DataGridRow.SelectedItem绑定到数据项的IsSelected属性即可:
<Style TargetType="{x:Type DataGridRow}">
    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>

但是,如果您的SelectedItems只是用于UI,或者在这种情况下由于某些原因打破了MVVM模式,那么您可以创建未绑定的CheckBox并使用一些代码来确保CheckBox正确地与SelectedItem同步。

我做了一个快速示例应用程序,这是我的代码:

首先,我只是使用DataGridTemplateColumn将未绑定的CheckBox列添加到列列表中。这将在AutoGenerateColumns列列表之前添加。

<DataGrid x:Name="TestDataGrid" ItemsSource="{Binding Test}" 
          SelectionMode="Extended" CanUserAddRows="False"
          PreviewMouseLeftButtonDown="TestDataGrid_PreviewMouseLeftButtonDown_1">
    <DataGrid.Columns>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <CheckBox x:Name="TestCheckBox"
                              PreviewMouseLeftButtonDown="CheckBox_PreviewMouseLeftButtonDown" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

其次,我给 CheckBox 添加了一个 PreviewMouseDown 事件,使其设置行的 IsSelected 属性。

private void CheckBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var chk = (CheckBox)sender;
    var row = VisualTreeHelpers.FindAncestor<DataGridRow>(chk);
    var newValue = !chk.IsChecked.GetValueOrDefault();

    row.IsSelected = newValue;
    chk.IsChecked = newValue;

    // Mark event as handled so that the default 
    // DataGridPreviewMouseDown doesn't handle the event
    e.Handled = true;
}

它需要导航VisualTree以找到与所点击的CheckBox相关联的DataGridRow以选择它,并且为了使生活更加轻松,我正在使用一些自定义VisualTreeHelpers,在我的博客上来查找DataGridRow。您可以使用相同的代码,或者创建自己的搜索VisualTree方法。
最后,如果用户单击除CheckBox之外的任何地方,我们希望禁用默认的DataGrid选择事件。这确保了只有在单击CheckBoxIsSelected值才会发生变化。
有多种方法可以在不同级别上禁用此操作,但为了简单起见,我只是在用户没有单击CheckBox的情况下禁用了DataGrid.PreviewMouseLeftButtonDown事件。
private void TestDataGrid_PreviewMouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
{
    var chk = VisualTreeHelpers.FindAncestor<CheckBox>((DependencyObject)e.OriginalSource, "TestCheckBox");

    if (chk == null)
        e.Handled = true;
}

我再次使用自定义的VisualTreeHelpers来遍历可视树,查找是否单击了复选框,并在用户单击除CheckBox以外的任何地方时取消事件。

至于您第二个请求,即将CheckBox添加到SelectAllUnselectAll项中,这将再次取决于您的选择是UI的一部分还是数据的一部分。

如果它是UI的一部分,只需将CheckBox添加到DataGridTemplateColumn.HeaderTemplate中,当单击它时,循环遍历DataGrid.Rows,查找第一列中的CheckBox,并选中或取消选中它。

如果它是数据的一部分,您仍然可以做同样的事情(只需在 DataGrid.Items 中设置绑定值,而不是来自 DataGrid.RowsCheckBox.IsChecked),或者您可以像Adolfo Perez suggested那样将其绑定到ViewModel上的属性。

2
真棒的 Rachel.. 真的很棒..稍微调整了一下代码以优化工作..否则它已经非常好了.. - Rajat Suneja
我已经苦恼了相当长一段时间,因为我们需要通过编程方式创建DataGridTemplateColumns。最终,您的PreviewMouseLeftButtonDown处理程序实现似乎可以工作,并附加到CheckBox上,如下所示: checkBoxFactory.AddHandler(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(ChkBox_PreviewMouseLeftButtonDown)); - Number8

4
对于一个MVVM解决方案,您可以尝试以下做法:
    <StackPanel>
    <DataGrid ItemsSource="{Binding Path=TestItems}" AutoGenerateColumns="False" Name="MyDataGrid"
              CanUserAddRows="False">
        <DataGrid.Columns>
            <DataGridCheckBoxColumn Binding="{Binding IsSelected}" Width="50" >
                <DataGridCheckBoxColumn.HeaderTemplate>
                    <DataTemplate x:Name="dtAllChkBx">
                        <CheckBox Name="cbxAll" Content="All" IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                    </DataTemplate>
                </DataGridCheckBoxColumn.HeaderTemplate>
            </DataGridCheckBoxColumn>
            <DataGridTemplateColumn Header="Name" Width="SizeToCells" IsReadOnly="True">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Name}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</StackPanel>

在你的 ViewModel 中:
private void PopulateTestItems()
        {
            TestItems = new ObservableCollection<TestItem>();
            for (int i = 0; i < 5; i++)
            {
                TestItem ti = new TestItem();
                ti.Name = "TestItem" + i;
                ti.IsSelected = true;
                TestItems.Add(ti);
            }
        }

        private bool _AllSelected;
        public bool AllSelected
        {
            get { return _AllSelected; }
            set
            {
                _AllSelected = value;
                TestItems.ToList().ForEach(x => x.IsSelected = value);
                NotifyPropertyChanged(m => m.AllSelected);
            }
        }

    private ObservableCollection<TestItem> _TestItems;
    public ObservableCollection<TestItem> TestItems
    {
        get { return _TestItems; }
        set
        {
            _TestItems = value;
            NotifyPropertyChanged(m => m.TestItems);
        }
    }

最后,这是示例的模型类:

public class TestItem : ModelBase<TestItem>
{
    private string _Name;
    public string Name
    {
        get { return _Name; }
        set
        {
            _Name = value;
            NotifyPropertyChanged(m => m.Name);
        }
    }
    private bool _IsSelected;
    public bool IsSelected
    {
        get { return _IsSelected; }
        set
        {
            _IsSelected = value;
            NotifyPropertyChanged(m => m.IsSelected);
        }
    }
}

大部分以上的代码应该是不言自明的,但如果您有任何问题,请让我知道。

2
你的视图可以像这样:
    <DataGrid Name="SomeDataGrid" Grid.Row="0" ItemsSource="{Binding Path=SomeCollection}">
            <DataGrid.Columns>
            <DataGridTemplateColumn>
            <DataGridTemplateColumn.HeaderTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding
            RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
            Path=DataContext.AllItemsAreChecked}" />
    </DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate DataType="{x:Type local:SomeType}">
                            <CheckBox Focusable="False" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"  
                                      HorizontalAlignment="Left" VerticalAlignment="Center"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>

                <DataGridTemplateColumn Header="RandomNumber" Width="160">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate DataType="{x:Type local:SomeType}">
                            <TextBlock Text="{Binding Path=RandomNumber}" TextWrapping="Wrap"  HorizontalAlignment="Left" VerticalAlignment="Center"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="Date" Width="160">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate DataType="{x:Type local:SomeType}">
                            <TextBlock Text="{Binding Path=Date}" TextWrapping="Wrap"  HorizontalAlignment="Left" VerticalAlignment="Center"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="Time" Width="50">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate DataType="{x:Type local:SomeType}">


                                    <TextBlock Text="{Binding Time}" HorizontalAlignment="Left" VerticalAlignment="Center"/>


                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>                    

            </DataGrid.Columns>
        </DataGrid>

在视图模型中,SomeCollection绑定属性是一个ObservableCollection。sometype包含诸如IsSelected、RandomNumber、Date和Time等属性。

例如:

    class ViewModel
    {
      public ObservableCollection<SomeType> SomeCollection{get;set;}
    }

    class SomeType
    {
       public string Date {get;set;}
       public string Time {get;set;}
       public string RandomNumber {get;set;}
       public bool IsSelected {get;set;}
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接