WPF基础DataGrid(VS2015+Win10)

在WPF中,主要使用DataGrid控件进行表格化数据的展示,控件提供一个用户界面,用于 ADO.NET 数据集(ADO.NET是微软的一个组件库,作为数据访问接口使用,详细内容直接参考百科ADO.NET),并显示表格数据和启用数据源更新。DataGrid 控件设置为有效数据源时,则自动填充该控件,同时根据数据的形状创建列和行。 DataGrid 控件可用于显示单个表或显示一组表之间的分层关系,本篇就简单介绍下DataGrid的常见用法。

1.使用DataGrid显示数据

第一步,在前端xaml文件上添加两个控件,一个是DataGrid控件,用于展示数据;另一个是Button控件,用于添加数据。前端PageUser.xaml文件的内容如下:

<Page x:Class="wpfbase.PageUser"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:wpfbase"
      mc:Ignorable="d" 
      d:DesignHeight="325" d:DesignWidth="525"
      Title="PageUser">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="20" />
      <RowDefinition Height="5"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Button Grid.Row="0" Click="AddUser" Content="添加用户" />
    <DataGrid Grid.Row="2" AutoGenerateColumns="False"
              CanUserAddRows="False" CanUserDeleteRows="False" 
              CanUserReorderColumns="False" CanUserResizeColumns="False"
              CanUserResizeRows="False" ColumnHeaderHeight="30"
              FontSize="16" GridLinesVisibility="Horizontal" 
              HeadersVisibility="Column" IsReadOnly="True" Name="datagrid"
              ItemsSource="{Binding UserDB}" RowHeight="28">
      <DataGrid.Columns>
        <DataGridTextColumn Width="Auto" MinWidth="25" Binding="{Binding UserID}" Header=""/>
        <DataGridTextColumn Width="*" Binding="{Binding UserName}" Header="用户名"/>
        <DataGridTextColumn Width="*" Binding="{Binding UserAccount}" Header="帐号"/>
        <DataGridTextColumn Width="*" Binding="{Binding UserPasswd}" Header="密码"/>
        <DataGridTextColumn Width="*" Binding="{Binding UserPhone}" Header="电话"/>
        <DataGridTextColumn Width="*" Binding="{Binding UserEmail}" Header="邮箱"/>
        <DataGridTextColumn Width="0.6*" Binding="{Binding UserSex}" Header="性别"/>
      </DataGrid.Columns>
    </DataGrid>
  </Grid>
</Page>

DataGrid控件中的ItemsSource="{Binding UserDB}"将DataGrid的数据源与UserDB属性绑定,也就表明数据源将从传入DataGrid控件数据对象的UserDB属性获取。DataGrid.Columns定义的是数据表的列(行头),Header的值是行头名,Binding绑定的是数据源对象中的子属性。数据源对象是一个数据对象数组,每一个数据对象需要包含UserID,UserName,UserAccount,UserPasswd,UserPhone,UserEmail,UserSex等属性。

DataGrid属性说明:

Datagrid属性的意义及使用方法参考WPF DataGrid常用属性记录。

第二步,后台cs文件实现数据展示及按钮触发的函数。前端PageUser.xaml.cs文件的内容如下:

using System;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace wpfbase
{
  public partial class PageUser : Page
  {
    private UserDBModel userdb;
    public PageUser()
    {
      InitializeComponent();
      userdb = new UserDBModel();
      datagrid.DataContext = userdb;
    }
    private void AddUser(object sender, RoutedEventArgs e) {
      int num = userdb.UserDB.Count+1;
      UserModel user = new UserModel((UInt32)num, 
                                     "test"+num.ToString(),
                                     (UInt32)(1000000000+num),
                                     "passwd"+num.ToString(),
                                     (UInt64)(10000000000+num),
                                     "test@"+num.ToString()+".com",
                                     num%2==0);
      userdb.UserDB.Add(user);
    }
  }

  public class UserModel{
    public UInt32 UserID {get; set;}
    public string UserName {get; set;}
    public UInt32 UserAccount {get; set;}
    public string UserPasswd {get; set;}
    public UInt64 UserPhone {get; set;}
    public string UserEmail {get; set;}
    public bool UserSex {get; set;}
    public UserModel() {}
    public UserModel(UInt32 id, 
                     string name,
                     UInt32 account,
                     string passwd,
                     UInt64 phone,
                     string email,
                     bool sex) {
      UserID = id;
      UserName = name;
      UserAccount = account;
      UserPasswd = passwd;
      UserPhone = phone;
      UserEmail = email;
      UserSex = sex;
    }
  }
  public class UserDBModel: INotifyPropertyChanged{
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(PropertyChangedEventArgs e) {
      if (PropertyChanged != null)
        PropertyChanged(this, e);
      }
    private ObservableCollection<UserModel> userdb = new ObservableCollection<UserModel>();
    public ObservableCollection<UserModel> UserDB {
      get { return userdb; }
      set {
        userdb = value;
        OnPropertyChanged(new PropertyChangedEventArgs("UserDB"));
      }
    }
  }
}

在后台文件中,首先定义了两个类UserModel,UserDBModel。其中UserModel类就是前端所需的数据对象类型,而UserDBModel是前端所需的数据源对象类。写法与之前1篇提到的动态刷新LIstBox写法类似,要求UserDBModel继承自INotifyPropertyChanged,实现实时刷新。 在界面类中实例化一个UserDBModel类型的成员userdb,作为数据源对象。在按钮响应函数AddUser中,我们构造一个UserModel对象user,并将user添加进userdb的UserDB成员。

如下图所示,点击添加用户按钮就可以往数据表中添加数据。

wpf datagrid 虚拟化无效 wpf的datagrid_wpf datagrid 虚拟化无效


备注1: 如果不需要实现刷新,那么直接创建一个List<UserModel>类型的列表,作为datagrid的ItemSource也可。

2.DataGrid样式设计

1.设计交替变换的样式

前端xaml文件修改为:

<Page ...>
  <Grid>
    ...
    <DataGrid Grid.Row="2" AlternationCount="2" ...>
      <DataGrid.RowStyle>
        <Style TargetType="{x:Type DataGridRow}">
          <Style.Triggers>
            <Trigger Property="AlternationIndex" Value="0">
              <Setter Property="Background" Value="#FFD9EEED"/>
            </Trigger>
            <Trigger Property="AlternationIndex" Value="1">
              <Setter Property="Background" Value="#FF84B3F9"/>
            </Trigger>
          </Style.Triggers>
        </Style>
      </DataGrid.RowStyle>
      ...
    </DataGrid>
  </Grid>
</Page>

如上,在DataGrid中设置属性AlternationCount的值,AlternationCount表示交替长度,我们设置交替长度为2;再添加DataGrid.RowStyle样式,如上,当交替索引为0时,触发第一种Trigger样式,当交替索引为1时,触发第二种Trigger样式。效果如下:

wpf datagrid 虚拟化无效 wpf的datagrid_数据_02

2.禁止用户按列对表中内容进行排序

我们可以通过设置DataGrid属性CanUserSortColumns的值来控制是否允许用户按列对表中内容进行排序,默认情况下CanUserSortColumns属性的值为True,我们将这个属性的值设置为False即可禁止用户按列对表中内容进行排序。

3.禁止列排序、禁止列宽调整

我们可以通过设置DataGrid属性CanUserReorderColumns的值来控制是否允许用户对列排序,默认情况下CanUserReorderColumns属性的值为True,我们将这个属性的值设置为False即可禁止用户对列排序。

我们可以通过设置DataGrid属性CanUserResizeColumns的值来控制是否允许用户对列宽调整,默认情况下CanUserResizeColumns属性的值为True,我们将这个属性的值设置为False即可禁止用户对列宽调整。

4.设置固定排序规则对列表排序

DataGrid的固定排序可以利用CollectionViewSource实现,通过CollectionViewSource将数据源先进行排序,再作为新的数据源传给DataGrid。

前端xaml文件如下所示:

<Page ...
      xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase">
  <Page.Resources>
    <CollectionViewSource x:Key="SortUserDB" Source="{Binding UserDB}">
      <CollectionViewSource.SortDescriptions>
        <scm:SortDescription PropertyName="UserID" Direction="Descending"/>
      </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>
  </Page.Resources>
  <Grid>
    ...
    <DataGrid ...ItemsSource="{Binding Source={StaticResource SortUserDB}}">
      ...
    </DataGrid>
  </Grid>
</Page>

原本后台数据源的数据UserDB是直接传送给DataGrid的ItemsSource进行数据绑定,现在改为了先将数据传送给页面静态资源CollectionViewSource的Source,而DataGrid的ItemsSource绑定的是静态资源SortUserDB,即CollectionViewSource资源。在CollectionViewSource中,我们通过设置<CollectionViewSource.SortDescriptions>实现对列表的排序,<scm:SortDescription PropertyName="UserID" Direction="Descending"/>这一行是按照属性UserID的值对数据源进行排序,Direction="Descending"是逆序,默认情况为正序!(这里可以设置多种排序,如在按照UserID排序后,可以在此基础上按照其它属性再排序,直接在下方按照同样的手法再加一行而已。)

后台cs文件需要修改传输的数据源方式,如下:

...
    public PageUser()
    {
      InitializeComponent();
      userdb = new UserDBModel();
      //datagrid.DataContext = userdb;
      this.DataContext = userdb;
    }
...

原本数据传输方式为datagrid.DataContext = userdb;

现在修改为this.DataContext = userdb;

效果如下:

wpf datagrid 虚拟化无效 wpf的datagrid_数据_03

5.设置列宽为自适应宽度

只需要将列宽设置为Auto,就可以根据列表内容自适应宽度!

6.更换显示内容

在数据源中,我们通常采用bool类型来保存性别,但在前端,我们需要将性别显示为正常的男或女,要实现这个功能,有多种思路。第一种,从数据源头修改,即我们设置数据对象的UserSex属性值时,直接设置为"男"或"女";第二种,使用模板列(这个小编不会);第三种,触发后台修改,每次数据源更新时,触发后台函数,修改前端数据表性别的显示方法;第四种,采用数据源转换器,在Binding时,使用Converter转换器直接将数据源转换后传给目标。

我们介绍下第四种方法:

第一步,在后台cs文件中创建转换器类

...
namespace wpfbase
{
  ...
  public class SexConverter: IValueConverter {
    public object Convert(
        object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture) {
      if((bool)value) return "男";
      else return "女";
    }
    public object ConvertBack(
        object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture) {
      if((string)value == "男") return true;
      else return false;
    }
  }
}

在后台我们创建了一个继承至IValueConverter的SexConverter类,在这个类中我们必须覆盖两个接口函数,Convert和ConvertBack。Convert函数实现了从数据源到呈现目标的数据转换,其中true对应的是"男",false对应的是"女";ConvertBack函数实现了从呈现目标到数据源的转换,"男"对应的是true,"女"对应的是false。

第二步,在前端文件中使用转换器

<Page ...
      xmlns:local="clr-namespace:wpfbase"
      ...>
  <Page.Resources>
    <local:SexConverter x:Key="UserSexConverter"/>
    ...
  </Page.Resources>
  <Grid>
    ...
    <DataGrid ...>
      ...
      <DataGrid.Columns>
        ...
        <DataGridTextColumn Width="Auto" Binding="{Binding UserSex, Converter={StaticResource UserSexConverter}}" Header="性别"/>
      </DataGrid.Columns>
    </DataGrid>
  </Grid>
</Page>

首先引入转换器类的命名空间,由于我们实在项目的命名空间下wpfbase定义的类,不需要再引入命名空间。在页面的资源中添加转换器资源,<local:SexConverter x:Key="UserSexConverter"/>,SexConverter是后台文件中定义的类名,UserSexConverter是现在定义的转换器资源名称,在后面使用,这里注意,添加这行代码时编译器可能会提醒"命名空间中找不到此名称",不用在意这个,继续往下做。在性别那一列的绑定数据中,Binding="{Binding UserSex, Converter={StaticResource UserSexConverter}}"使用转换器资源实现数据内容的转换。效果如下:

wpf datagrid 虚拟化无效 wpf的datagrid_数据源_04

7.设置单元格区间范围,超出范围修改样式

3.DataGrid连接MySQL数据库显示数据表

使用datagrid控件将数据表展示在前端界面上。

前端xaml文件内容如下:

<Page ... Title="PageClient">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="20" />
      <RowDefinition Height="5"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <DataGrid Grid.Row="2" AlternationCount="2" AutoGenerateColumns="False"
              CanUserAddRows="False" CanUserDeleteRows="False" 
              CanUserReorderColumns="False" CanUserResizeColumns="False"
              CanUserSortColumns="False"
              CanUserResizeRows="False" ColumnHeaderHeight="30"
              FontSize="16" GridLinesVisibility="Horizontal" 
              HeadersVisibility="Column" IsReadOnly="True" Name="datagrid"
              RowHeight="28">
      <DataGrid.RowStyle>...</DataGrid.RowStyle>
      <DataGrid.Columns>
        <DataGridTextColumn Width="Auto" MinWidth="25" Binding="{Binding UserID}" Header=""/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserName}" Header="用户名"/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserAccount}" Header="帐号"/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserPasswd}" Header="密码"/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserPhone}" Header="电话"/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserEmail}" Header="邮箱"/>
        <DataGridTextColumn Width="Auto" Binding="{Binding UserSex}" Header="性别"/>
      </DataGrid.Columns>
    </DataGrid>
  </Grid>
</Page>

前端文件的内容基本不变,DataGrid的源ItemSource暂不绑定,采用第一大点中的备注1的方式确定数据源。

后端文件内容如下:

using System.Data;
using System.Windows.Controls;
using MySql.Data.MySqlClient;

namespace wpfbase
{
  public partial class PageClient : Page
  {
    MySqlConnection conn;
    MySqlDataAdapter msda;
    MySqlCommand cmd;
    public PageClient()
    {
      InitializeComponent();
      string connStr = "server=127.0.0.1;user=root;password=rootmysql; database=wpfbasedb;";
      conn = new MySqlConnection(connStr);
      conn.Open();
      string sql = "select * from usertable";
      cmd = new MySqlCommand(sql, conn);
      cmd.CommandType = CommandType.Text;
      DataTable dt = new DataTable();
      msda = new MySqlDataAdapter(cmd);
      msda.Fill(dt);
      datagrid.ItemsSource = dt.DefaultView; // 数据绑定
      conn.Close();
    }
  }
}

后台将从数据库查到的数据以DataTable.DefaultView的方式传给datagrid。

wpf datagrid 虚拟化无效 wpf的datagrid_数据_05

样式和转换器的写法与之前相同!

4.DataGrid实时刷新MySQL数据库显示数据

如果要实时刷新数据库的状态,有两种方式,一种是定时轮询的方式访问数据库,另一种则是监控数据库的数据表,当数据表发生变化时,向客户端发送通知。