WPF之DataGrid的使用及案例
1.参考资料
官方文档:DataGrid - WPF .NET Framework | Microsoft Learn
2.DataGrid的数据源绑定
绑定viewModel中的数据源,设置itemSource属性即可,dataGrid会根据绑定的属性自动生成表头。绑定类型为IList。
<DataGrid Name="DataGrid" ItemsSource="{Binding Students}"/>
注意:如果想使用自定义的表头和内容,则需要关闭自动生成列表(设置AutoGenerateColumns
= false),否则二者会同时出现。
3.常用属性的设置
官方文档里有写,这里省略。
补充:
-
GridLinesVisibility
,设置是否显示表格横竖线 - 生成的表格中默认会有一个空白行用于添加新数据,如果不想要可以关闭,设置
CanUserAddRows = False
4.自定义单元格Template
有四种列形式,如下图
text用于显示文本框,checkBox显示多选框,Combox复选框,hyperLink超链接。
除去默认的控件以外,还支持自定义单元格,使用DataGridTemplateColumn
,如图,我在白表格中添加了一个按钮用于修改数据。
<DataGrid Name="DataGrid" ItemsSource="{Binding Students}"
SelectedItems="{Binding SelectedItems}"
VerticalContentAlignment="Center"
AutoGenerateColumns="False"
GridLinesVisibility="Horizontal"
AlternationCount="2"
AlternatingRowBackground="LightGray"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="姓名" Binding="{Binding UserName}"></DataGridTextColumn>
<DataGridTextColumn Header="年龄" Binding="{Binding userAge}"></DataGridTextColumn>
<DataGridTextColumn Header="地址" Binding="{Binding UserAddress}"></DataGridTextColumn>
<DataGridTextColumn Header="手机号" Binding="{Binding UserNumber}"></DataGridTextColumn>
<DataGridCheckBoxColumn Header="多选"></DataGridCheckBoxColumn>
<DataGridTemplateColumn Width="auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button>点击修改</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
5.常见用法
5.1.多选框,批量删除,右键菜单编辑,添加新数据,显示图片
实现的关键在于获取到多选的内容,wpf提供了一个Selecteditem(s)来获取选中的行的值,也可以获取单个表格的值(SelectedItemCell).由于这个值是只读属性,无法使用Binding来获取到。所以,可以使用代码来获取。
界面布局:
代码:
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<ContextMenu x:Key="DataGridContextMenu">
<MenuItem Name="edit" Header="编辑" Click="edit_Click"/>
</ContextMenu>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<DataGrid x:Name="studentData" Grid.Row="0" Height="auto" CanUserDeleteRows="False"
ContextMenu="{StaticResource DataGridContextMenu}"
ItemsSource="{Binding CollectionsDisplay}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightGray"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding userId}" IsReadOnly="True"/>
<DataGridTextColumn Header="用户名" Binding="{Binding userName}" IsReadOnly="True" />
<DataGridTextColumn Header="地址" Binding="{Binding userAddress}" IsReadOnly="True" />
<DataGridTextColumn Header="邮箱" Binding="{Binding userEmail}" IsReadOnly="True"/>
<DataGridCheckBoxColumn Header="多选" IsReadOnly="False" CanUserSort="False" Binding="{Binding isChecked}" >
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox VerticalContentAlignment="Center" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=DataContext.IsAllSelected}"/>
<Label>全选</Label>
</StackPanel>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
<DataGridTextColumn Width="*"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="Height" Value="20"></Setter>
<Setter Property="Margin" Value="5 0 0 0"></Setter>
</Style>
</StackPanel.Resources>
<Button Command="{Binding DeleteItemsCommand}">删除</Button>
<Button Command="{Binding NextPageCommand}">下一页</Button>
<TextBox Margin="5 0 0 0" Height="20" Width="40" Text="{Binding PageNum}"></TextBox>
<TextBlock Margin="5 0 0 0" Height="20" Width="40">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}/{1}页">
<Binding Path="PageNum"/>
<Binding Path="TotalPage"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Button Command="{Binding GotoPageCommand}">转到</Button>
</StackPanel>
</Grid>
1.多选框的实现
使用自定义模板,使用checkBox作为表头。checkBox绑定了ViewModel中的一个值并重写OnPropertyChanged方法,在发生变化时,更改所有item的isChecked的值。
<DataGridCheckBoxColumn Header="多选" IsReadOnly="False" CanUserSort="False" Binding="{Binding isChecked}" >
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox VerticalContentAlignment="Center" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=DataContext.IsAllSelected}"/>
<Label>全选</Label>
</StackPanel>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
代码:
//重写方法
partial void OnIsAllSelectedChanged(bool value)
{
foreach (var item in CollectionsDisplay)
{
item.isChecked = value;
}
//更新界面
CollectionsDisplay = new ObservableCollection<Student>(CollectionsDisplay);
}
2.批量删除
找出所有isChecked的值(使用LINQ),然后将其删除即可。
var items = CollectionsDisplay.Where(m => m.isChecked == true);
if (items.Any())
{
foreach (var item in items)
{
Collections.Remove(item);
}
CollectionsDisplay = new ObservableCollection<Student>(Collections.Take(PageSize));
}
3.右键菜单修改
<Window.Resources>
<ContextMenu x:Key="DataGridContextMenu">
<MenuItem Name="edit" Header="编辑" Click="edit_Click"/>
</ContextMenu>
</Window.Resources>
右键菜单,绑定了一个点击事件,该事件打开一个dialog用于更新数据。打开window时需要传递当前选择的item的值,这里使用构造方法传入。在dialog接收到传入的值后,将其传入他的viewModel,并绑定它。为了实现不同的值的修改效果,可以使用converter将传放入的obj转换成字典类以实现复用效果。
ps:关于使用converter转换字典类的实现方式
1.使用反射,使用反射获取到属性名和值装入字典类
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var info = value.GetType().GetProperties();
var dic = new Dictionary<string, object>();
foreach(var item in info)
{
dic.Add(item.Name,item.GetValue(value));
}
return dic;
}
2.使用JsonSerialzer先序列化再反序列化成字典类
使用这种方式可以使用源生成器自定义序列化后的属性别名,这样在生成字典类后可以显示属性别名而非属性名
属性别名:
class Student
{
[JsonPropertyName("用户ID")]
public int UserId { get; set; }
[JsonPropertyName("用户名")]
public string? UserName { get; set; }
[JsonPropertyName("用户地址")]
public string? UserAddress { get; set; }
[JsonPropertyName("用户创建时间")]
public DateTimeOffset UserCreateTime { get; set; }
}
var stu = new Student()
{
UserId = 1,
UserName = "张三",
UserAddress = "浙江省杭州市萧山区",
UserCreateTime = DateTimeOffset.Now
};
JsonSerialzer.Serialze(stu);
//结果:
//{
// "用户ID": 1,
// "用户名": "张三",
// "用户地址": "浙江省杭州市萧山区",
// "用户创建时间": "2024-05-28T16:17:41.7146546\u002B08:00"
//}
4.显示图片
使用自定义模板
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="xxx"></Image>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
5.2.使用DataGrid实现分页
其中一种思路
页面布局:
页面代码:
<Button Command="{Binding NextPageCommand}">下一页</Button>
<TextBox Margin="5 0 0 0" Height="20" Width="40" Text="{Binding PageNum}"></TextBox>
<TextBlock Margin="5 0 0 0" Height="20" Width="40">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}/{1}页">
<Binding Path="PageNum"/>
<Binding Path="TotalPage"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Button Command="{Binding GotoPageCommand}">转到</Button>
viewModel中的定义:
//原始数据
public List<Student> Collections { get; set; } = [];
//用于显示的数据
[ObservableProperty] private ObservableCollection<Student> _collectionsDisplay = [];
//当前页码
[ObservableProperty] private int _pageNum = 1;
//总页码
[ObservableProperty] private int _totalPage;
实现思路是,可以根据每页显示的数量和当前的页码,可以计算出当前需要显示的是哪些条目。
1.页面跳转
if (PageNum >= 1)
//跳过之前的条目,然后拿到固定数目的数据即为当前页的数据,然后更新页面
CollectionsDisplay = new ObservableCollection<Student>(Collections.Skip((PageNum - 1) * PageSize).Take(PageSize));
2.下一页
PageNum++;
CollectionsDisplay = new ObservableCollection<Student>(Collections.Skip((PageNum - 1) * PageSize).Take(PageSize));
5.3.实现数据按条件筛选
使用LINQ筛选出需要的数据然后显示到表中即可。