WPF⾃定义表格控件(动态添加删除⾏)
最近在项⽬开发中遇到⼀个⼩问题,我们的设备管理模块中有⼀项叫做“技术参数”,具体来说就是不同的设备具有不同的属性,⽽且属性的数量也不同。举个例⼦,桌⼦有长、宽、⾼、材质四个属性,⽇光灯有安装⾼度、额定功率两个属性。我们希望根据设备类型能够⾃主添加/修改/删除属性,另⼀⽅⾯其他模块也会⽤到此功能,所以考虑做⼀个⾃定义控件,将增、删、改操作封装在控件内部,数据对外开放。
环境版本
操作系统Windows 10
编译器Visual Studio 2015 update3
期望⽬标
期望达到的效果如下图所⽰:
包含两列数据(属性名称和属性值),可以⼿动添加/删除⾏,同时⽀持编辑,对外提供⼀个数据集合(DataTable,Dictionary或List)。创建控件并添加依赖项属性
在WPF项⽬⾥添加⼀个UserControl,命名为TableControl。我们希望这个控件的某个属性具有这样的特性:属性值发⽣变化时,控件的数据呈现⽴刻跟随变化;任意时刻访问控件的这个属性,都能保证属性值与呈现的数据保持⼀致。由此我们就需要⼀个⾃定义的依赖项属性,另外考虑到通⽤性,我们决定使⽤DataTable作为这个依赖项属性的数据类型。(创建⾃定义依赖项属性快捷键:输⼊propdp,按两次Tab键)。
public partial class TableControl : UserControl
{
public TableControl()
{
InitializeComponent();
}
#region ⾃定义依赖项属性
public DataTable DataSource
{
get { return (DataTable)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for DataSource. This enables animation, styling, binding,
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(DataTable), typeof(TableControl), new PropertyMetadata(new DataTable(), DataSourceChang
private static void DataSourceChanged(DependencyObject d, DependencyPropertyChangedEventAr
gs e)
{
TableControl control = d as TableControl;
if (e.NewValue != e.OldValue)
{
DataTable dt = e.NewValue as DataTable;
}
}
#endregion
}
创建⾃定义依赖项属性DataSource后,⼜为其添加了⼀个属性改变事件DataSourceChanged,以确保属性值发⽣变化后能够进⾏相应的
操作。
修改控件布局
WPF⾃带的DataGrid表格控件本⾝就⽀持增删改数据⾏,所以控件主体仍为DataGrid。要让DataGrid增加⾏,只需要将其
CanUserAddRows属性设为true即可,但使⽤起来不那么⽅便,所以另外添加了⼀个按钮⽤来添加⾏。删除和编辑⾏均可以在DataGrid的
模板列⾥实现,代码如下:
<Grid>
<StackPanel>
<DataGrid HeadersVisibility="None"AutoGenerateColumns="False"CanUserAddRows="False"x:Name="dgData"GridLinesVisibility="Horizontal"
<DataGrid.Columns>
<DataGridTemplateColumn Width="3*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ParamKey,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=ParamKey,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,TargetNullValue=请输⼊}"
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="10">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text=":"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="3*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ParamValue,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=ParamValue,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,TargetNullValue=请输⼊}"
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Click="btnDel_Click">htmlborder
<Button.Content>
<Border Width="32"Height="32"CornerRadius="16"Background="CornflowerBlue"VerticalAlignment="Center"HorizontalAlignme
<Path Data="M0 0L22 0"Stroke="WhiteSmoke"StrokeThickness="4"VerticalAlignment="Center"HorizontalAlignment=
</Border>
</Button.Content>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Grid Width="35">
<Button x:Name="btnAdd"Click="btnAdd_Click">
<Button.Content>
<Border Width="32"Height="32"CornerRadius="16"Background="CornflowerBlue"VerticalAlignment="Center"HorizontalAlignment
<Path Data="M0 11L22 11M11 0L11 22"Stroke="WhiteSmoke"StrokeThickness="4"VerticalAlignment="Center"HorizontalAlignment
</Border>
</Button.Content>
</Button>
</Grid>
</StackPanel>
</Grid>
数据处理
在数据处理上主要应⽤了WPF的双向绑定模式。控件初始化时就为DataGrid进⾏数据绑定。添加⾏时,创建⼀个与当前数据源具有相同结
构的DataRow,将其追加到数据源上,重新进⾏数据绑定。由于采⽤了双向绑定,在页⾯上进⾏修改
操作时,数据源也会随之发⽣变化,
这样后台数据源和前台页⾯展⽰能始终保持⼀致,修改后的代码如下:
public partial class TableControl : UserControl
{
private DataTable _dt = new DataTable();
public TableControl()
{
InitializeComponent();
_dt.Columns.Add(new DataColumn("ParamKey", typeof(string)));
_dt.Columns.Add(new DataColumn("ParamValue", typeof(string)));
this.dgData.ItemsSource = null;
this.dgData.ItemsSource = _dt.DefaultView;
}
#region ⾃定义依赖项属性
/// <summary>
/// 数据源
/// </summary>
public DataTable DataSource
{
get { return ((DataView)this.dgData.ItemsSource).Table; }
set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(DataTable), typeof(TableControl), new PropertyMetadata(new DataTable(), DataSourceChang
private static void DataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TableControl control = d as TableControl;
if (e.NewValue != e.OldValue)
{
DataTable dt = e.NewValue as DataTable;
control._dt = dt;
control.dgData.ItemsSource = null;
control.dgData.ItemsSource = control._dt.DefaultView;
}
}
#endregion
/// <summary>
/// 删除⾏
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnDel_Click(object sender, RoutedEventArgs e)
{
((DataRowView)this.dgData.SelectedItem).Row.Delete();
}
/// <summary>
/// 添加⾏
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
{
_dt = ((DataView)this.dgData.ItemsSource).Table;
DataRow dr = _dt.NewRow();
_dt.Rows.Add(dr);
this.dgData.ItemsSource = _dt.DefaultView;
}
}
改动主要有四处:
1.中间变量_dt
创建了⼀个DataTable类型的局部变量_dt作为中间变量,⽤于数据初始化和数据绑定:
private DataTable _dt = new DataTable();
_dt.Columns.Add(new DataColumn("ParamKey", typeof(string)));
_dt.Columns.Add(new DataColumn("ParamValue", typeof(string)));
this.dgData.ItemsSource = null;
this.dgData.ItemsSource = _dt.DefaultView;
2.DataSource的返回值
由于使⽤了双向绑定,我们想要的数据就在DataGrid的数据源⾥,将其返回即可:
get { return ((DataView)this.dgData.ItemsSource).Table; }
3.删除⾏
((DataRowView)this.dgData.SelectedItem).Row.Delete();
进⾏删除⾏操作时,这段代码获取选择的DataRowView对象,查到相应的DataRow对象,并使⽤Delete()⽅法将其标识为即将删除。这时可以看到删除的DataRow对象从列表中消失了,但实际上它仍位于DataTable.Rows集合中。原因是DataView中的默认过滤设置隐藏了所有已删除的记录(只是将其标识为删除,但并未真正删除)。这也是官⽅推荐使⽤的⽅法。
另外⼀种⽅式就会导致选择的DataRowView对象被真正删除,代码如下,仅作参考,不建议使⽤:
_dt.Rows.Remove(((DataRowView)this.dgData.SelectedItem).Row);
4.新增⾏
获取DataGrid当前的数据源,创建⼀个具有相同结构的空⾏,追加到中间变量_dt,然后重新绑定。这样就保证了原有数据不丢失,同时⼜增加⼀个空⾏。
_dt = ((DataView)this.dgData.ItemsSource).Table;
DataRow dr = _dt.NewRow();
_dt.Rows.Add(dr);
this.dgData.ItemsSource = _dt.DefaultView;
其他
添加样式
简单的添加了样式,主要是确保⾏列对齐,看起来不那么丑
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论