WPF DataGrid用法

284 阅读2分钟

WPF中的表格数据展示一般使用DataGrid控件。

DataGrid可用AutoGenerateColumns属性自动生成对应的列。其中Column的类型包含 DataGridTextColumnDataGridCheckBoxColumnDataGridComboBoxColumnDataGridHyperlinkColumnDataGridTemplateColumn等5种格式。

1. DataGridCheckBoxColumn 实现效果

复选框类型列,显示效果如下:

image.png

2. xaml中设置CheckBox的主要代码

                <DataGridCheckBoxColumn Width="100" 
                                        Binding="{Binding IsSelected,NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
                    <DataGridCheckBoxColumn.HeaderTemplate>
                        <DataTemplate>
                            <CheckBox IsChecked="{Binding DataContext.IsAllSelected, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                                      Command="{Binding DataContext.SelectAllCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                                      CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self},Path=IsChecked}"/>
                        </DataTemplate>
                    </DataGridCheckBoxColumn.HeaderTemplate>
                    <DataGridCheckBoxColumn.HeaderStyle>
                        <Style BasedOn="{StaticResource CenteredHeaderStyle}" TargetType="{x:Type DataGridColumnHeader}"/>
                    </DataGridCheckBoxColumn.HeaderStyle>
                    <DataGridCheckBoxColumn.ElementStyle>
                        <Style TargetType="CheckBox">
                            <Setter Property="HorizontalAlignment" Value="Center"/>
                            <Setter Property="VerticalAlignment" Value="Center"/>
                        </Style>
                    </DataGridCheckBoxColumn.ElementStyle>
                    <DataGridCheckBoxColumn.EditingElementStyle>
                        <Style TargetType="CheckBox">
                            <Setter Property="HorizontalAlignment" Value="Center"/>
                            <Setter Property="VerticalAlignment" Value="Center"/>
                        </Style>
                    </DataGridCheckBoxColumn.EditingElementStyle>
                </DataGridCheckBoxColumn>

2.1 实现 CheckBoxColumnHeader中显示复选框

自定义HeaderTemplate

<DataGridCheckBoxColumn.HeaderTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding DataContext.IsAllSelected, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                  Command="{Binding DataContext.SelectAllCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self},Path=IsChecked}"/>
    </DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>

实现HeaderCheckBox选择状态和CheckBoxColumn列单元格的选中状态联动。当选中HeaderCheckBox或者取消选中HeaderCheckBox时,CheckBoxColumn列单元格的选中状态相应地发生变化。同样,当选择CheckBoxColumn列单元格时,Header中的CheckBox也相应地会有三种选择状态切换。

  • 实现方案
  1. ViewModel中定义 IsAllSelected 属性用于标识所有数据行的选中状态;
  2. ViewModel中定义 SelectAllCommand 用于处理HeaderCheckBox选中或者取消选中操作;
  3. ViewModel中监听 IsSelected 修改状态,从而及时更新Header中的CheckBox选中状态;

3. 完整样例代码

3.1 MainWindow.xaml

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestDataGrid"
        xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2" x:Class="TestDataGrid.MainWindow"
        mc:Ignorable="d"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style x:Key="ColumnHeaderGripperStyle" TargetType="{x:Type Thumb}">
            <Setter Property="Width" Value="8"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Cursor" Value="SizeWE"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Thumb}">
                        <Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="DataGridColumnHeaderStyle1" TargetType="{x:Type DataGridColumnHeader}">
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
                        <CheckBox x:Name="chkToggleSelection" VerticalAlignment="Center">
                        </CheckBox>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="CheckBoxHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
                        <CheckBox x:Name="chkToggleSelection"   VerticalAlignment="Center">
                        </CheckBox>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="CenteredHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
            <Setter Property="HorizontalContentAlignment" Value="Center"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
        </Style>
    </Window.Resources>
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>

    <Grid>
        <DataGrid x:Name="mydg" ItemsSource="{Binding Products}" AutoGenerateColumns="False" Margin="10"
                  CanUserAddRows="False">
            <!--<i:Interaction.Triggers>
                <i:EventTrigger EventName="CellEditEnding">
                    <i:InvokeCommandAction Command="{Binding CellValueChangedCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>-->
            <DataGrid.Columns>
                <DataGridCheckBoxColumn Width="100" 
                                        Binding="{Binding IsSelected,NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
                    <DataGridCheckBoxColumn.HeaderTemplate>
                        <DataTemplate>
                            <CheckBox IsChecked="{Binding DataContext.IsAllSelected, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                                      Command="{Binding DataContext.SelectAllCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                                      CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self},Path=IsChecked}"/>
                        </DataTemplate>
                    </DataGridCheckBoxColumn.HeaderTemplate>
                    <DataGridCheckBoxColumn.HeaderStyle>
                        <Style BasedOn="{StaticResource CenteredHeaderStyle}" TargetType="{x:Type DataGridColumnHeader}"/>
                    </DataGridCheckBoxColumn.HeaderStyle>
                    <DataGridCheckBoxColumn.ElementStyle>
                        <Style TargetType="CheckBox">
                            <Setter Property="HorizontalAlignment" Value="Center"/>
                            <Setter Property="VerticalAlignment" Value="Center"/>
                        </Style>
                    </DataGridCheckBoxColumn.ElementStyle>
                    <DataGridCheckBoxColumn.EditingElementStyle>
                        <Style TargetType="CheckBox">
                            <Setter Property="HorizontalAlignment" Value="Center"/>
                            <Setter Property="VerticalAlignment" Value="Center"/>
                        </Style>
                    </DataGridCheckBoxColumn.EditingElementStyle>
                </DataGridCheckBoxColumn>
                <DataGridTextColumn Header="Product Name" Binding="{Binding Name}"/>
                <DataGridTextColumn Header="Price" Binding="{Binding Price, StringFormat=C}"/>

                <!-- DataGridComboBoxColumn 绑定 -->
                <DataGridComboBoxColumn Header="Category">
                    <DataGridComboBoxColumn.EditingElementStyle>
                        <Style TargetType="{x:Type ComboBox}">
                            <Setter Property="ItemsSource" Value="{Binding DataContext.Categories, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                            <Setter Property="DisplayMemberPath" Value="Name"/>
                            <Setter Property="SelectedValuePath" Value="Id"/>
                            <Setter Property="SelectedValue" Value="{Binding CategoryId, UpdateSourceTrigger=PropertyChanged}"/>
                        </Style>
                    </DataGridComboBoxColumn.EditingElementStyle>
                    <DataGridComboBoxColumn.ElementStyle>
                        <Style TargetType="{x:Type ComboBox}">
                            <Setter Property="ItemsSource" Value="{Binding DataContext.Categories, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                            <Setter Property="DisplayMemberPath" Value="Name"/>
                            <Setter Property="SelectedValuePath" Value="Id"/>
                            <Setter Property="SelectedValue" Value="{Binding CategoryId, UpdateSourceTrigger=PropertyChanged}"/>
                        </Style>
                    </DataGridComboBoxColumn.ElementStyle>
                </DataGridComboBoxColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

3.2 MainViewModel.cs

 public class MainViewModel : INotifyPropertyChanged
    {
        private bool? isAllSelected;

        public bool? IsAllSelected
        {
            get { return isAllSelected; }
            set
            {
                if (value != isAllSelected)
                {
                    isAllSelected = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsAllSelected)));
                }
            }
        }

        public ObservableCollection<Product> Products { get; set; }
        public ObservableCollection<Category> Categories { get; set; }
        public ObservableCollection<string> MySources { get; set; }
        public ICommand SelectAllCommand { get; }
        public ICommand CellValueChangedCommand { get; }

        public MainViewModel()
        {
            // 初始化分类数据
            Categories = new ObservableCollection<Category>
            {
                new Category { Id = 1, Name = "Electronics" },
                new Category { Id = 2, Name = "Clothing" },
                new Category { Id = 3, Name = "Home Appliances" }
            };

            // 初始化产品数据
            Products = new ObservableCollection<Product>
            {
                new Product { Name = "Laptop", Price = 999.99m, CategoryId = 1 },
                new Product { Name = "Shirt", Price = 29.99m, CategoryId = 2 },
                new Product { Name = "Washing Machine", Price = 499.99m, CategoryId = 3 }
            };
            foreach (var item in Products)
            {
                item.PropertyChanged += Item_PropertyChanged;
            }
            MySources = new ObservableCollection<string>()
            {
                "Laptop" ,
                "Shirt" ,
                "Washing Machine"
            };
            SelectAllCommand = new RelayCommand(SelectAll);
            CellValueChangedCommand = new RelayCommand(CellValueChanged);
            IsAllSelected = false;
        }

        private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            UpdateHeaderCheckBoxState();
        }

        private void SelectAll(object obj)
        {
            if (obj is bool isSelected)
            {
                foreach (var item in Products)
                {
                    item.IsSelected = isSelected;
                }
            }
        }
        private void CellValueChanged(object obj)
        {
            UpdateHeaderCheckBoxState();
        }
        public void UpdateHeaderCheckBoxState()
        {
            var allChecked = Products.All(item => item.IsSelected == true);
            var allUnchecked = Products.All(item => item.IsSelected == false);

            IsAllSelected = allChecked ? true : (allUnchecked ? false : (bool?)null);
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }

3.3 Models

public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class Product:INotifyPropertyChanged
    {
        private bool isSelected;

        public bool IsSelected
        {
            get { return isSelected; }
            set {
                if (value != IsSelected)
                {
                    isSelected = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
                }
            }
        }

        public string Name { get; set; }
        public decimal Price { get; set; }
        public int CategoryId { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;
    }

3.4 RelayCommand.cs

public class RelayCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;

        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    }

完整项目参考:github.com/RunWangA/WP…