WPF动态生成DataGrid及动态绑定解决方案

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

WPF动态⽣成DataGrid及动态绑定解决⽅案
⼀、场景
有过WPF项⽬经验的朋友可能都知道,如果⼀个DataGrid要绑定静态的数据是⾮常的简单的(所谓静态是指绑定的数据源的类型是静态的),如下图所⽰,想要显⽰产品数据,只需绑定到⼀个产品列表即可,这个⼤家都清楚,所以这个要讲的肯定不是这个。

但是现在有⼀个新的需求,根据所选择产品的不同,要动态⽣成第⼆个表格中的不同数据,以便进⾏编辑,如下图1、2所⽰,当选择的产品不同时,第⼆个表格显⽰的内容是完全不⼀样的。

这样就会产⽣⼀个问题,⽆法直接对第⼆个表格进⾏绑定,因为它的数据源类型都是不⼀样的,⽆法按照传统⽅法进⾏绑定。

如何解决,先⾃⼰思考⼀下,也许会有更好的解决⽅案。

⼆、思路
1、定义Domain
既然⽆法知道要绑定的数据类型是什么,因为它是动态的,⽆法事先预知的,根据所选择的产品(因为产品数据都存储于DB中,所以将类型定义于Domain项⽬中)不同⽽变化的。

那么可以在 Product 中定义 SKUFields 属性⽤于代表产品可显⽰的字段。

产品定义如下所⽰。

///<summary>
///产品
///</summary>
[Table("Product")]
public class Product
{
public int ProductId { get; set; }
public string ProductDesc { get; set; }
public bool IsRFID { get; set; }
public bool IsTID { get; set; }
public string CustomerItemCode { get; set; }
public string ProductCode { get; set; }
public string SuggestedPrinter { get; set; }
///<summary>
///产品对应的⽂件名称
///</summary>
public string ProfileName { get; set; }
///<summary>
///产品对应的SKU字段列表
///</summary>
public ICollection<SKUField> SKUFields { get; set; }
}
///<summary>
/// SKU 字段
///</summary>
public class SKUField
{
///<summary>
///产品ID
///</summary>
public int ProductId { get; set; }
///<summary>
///字段ID
///</summary>
public int SKUFieldId { get; set; }
///<summary>
///字段名称
///</summary>
public string FieldName { get; set; }
///<summary>
///字段标题,⽤于显⽰
///</summary>
public string FieldTitle { get; set; }
///<summary>
///字段是否可编辑
///</summary>
public bool IsEditable { get; set; }
///<summary>
///字段的值
///</summary>
public object DefaultValue { get; set; }
///<summary>
///字段值是否可选
///</summary>
public bool IsValueSelectable { get; set; }
///<summary>
///字段的类型,如int、string或其他
///</summary>
public Type FieldType { get; set; }
///<summary>
///如果IsValueSelectable = True,那么SKUFieldValues代表可选择的值列表
///</summary>
public ICollection<SKUFieldValue> SKUFieldValues { get; set; }
}
///<summary>
///⽤于定义SKU字段的取值
///</summary>
public class SKUFieldValue
{
public int SKUFieldId { get; set; }
public int SKUFieldValueId { get; set; }
public string FieldValue { get; set; }
}
然后在项⽬中定义 IProductRepository 接⼝,⽤于定义获取产品的⽅法。

public interface IProductRepository
{
///<summary>
///获取默认的产品列表
///</summary>
///<returns></returns>
IEnumerable<Product> GetDefaultProducts();
///<summary>
///获取特定客户账号的产品列表
///</summary>
///<param name="account">客户账号如YTST02DY、G99999CG</param>
///<returns></returns>
IEnumerable<Product> GetProductsByAccount(string account);
}
Domain项⽬结构所图所⽰。

2、定义服务,⽤于获取所有产品信息
第⼆部建⽴ Application 项⽬,⽤于提供 WPFUI 项⽬所需要的服务,这⾥我不打算使⽤真实的数据源来提供数据,所以只是使⽤ Mock 来模拟服务。

///<summary>
///产品接⼝,⽤于提供产品数据
///</summary>
public interface IProductService
{
IEnumerable<Product> GetAllProducts();
}
/// <summary>
/// 真实的服务,使⽤EF框架来获取数据,但是这⾥不合适这个服务,为了⽅便演⽰
/// </summary>
public class ProductService : IProductService
{
public IEnumerable<Product> GetAllProducts()
{
using (var context = new SQLiteDataContext())
{
return context.Products.ToList();
}
}
}
/// <summary>
/// 模拟的服务,为了⽅便演⽰
/// </summary>
public class MockProductService : IProductService
{
public IEnumerable<Product> GetAllProducts()
{
var product2 = GetProduct2();
var product3 = GetProduct3();
var product4 = GetProduct4();
return new List<Product>()
{
product2,
product3,
product4
};
}
public Profile GetProfile(Product product)
{
string filePath = bine(string.Format(@"Resources\Products\{0}", product.ProductCode), product.ProfileName); Profile profile = new Profile(filePath, TempFolder.CreateTempFolder());
return profile;
}
private Product GetProduct2()
{
var product = new Product()
{
ProductId = 2,
IsRFID = true,
IsTID = true,
CustomerItemCode = "FP-PT#003",
ProductDesc = "Coated Stock",
ProductCode = "88CEMPH006",
ProfileName = "88CEMPH006.spkg",
SuggestedPrinter = "SMLPrinter"
};
product.SKUFields = new List<SKUField>();
SKUField skuField1 = new SKUField();
skuField1.FieldName = "Qty";
skuField1.FieldTitle = "Order Qty";
skuField1.FieldType = typeof(System.Int32);
skuField1.IsEditable = true;
product.SKUFields.Add(skuField1);
SKUField skuField2 = new SKUField();
skuField2.FieldName = "Size";
skuField2.FieldTitle = "Size";
skuField2.FieldType = typeof(System.String);
skuField2.IsEditable = true;
skuField2.IsValueSelectable = true;
skuField2.SKUFieldValues = new List<SKUFieldValue>()
{
new SKUFieldValue() { FieldValue = "Large" },
new SKUFieldValue() { FieldValue = "Middle" },
new SKUFieldValue() { FieldValue = "Small" },
};
product.SKUFields.Add(skuField2);
SKUField skuField3 = new SKUField();
skuField3.FieldName = "Retail";
skuField3.FieldTitle = "Retail";
skuField3.FieldType = typeof(System.String);
skuField3.IsEditable = true;
product.SKUFields.Add(skuField3);
return product;
}
private Product GetProduct3()
{
var product = new Product()
{
ProductId = 3,
IsRFID = false,
IsTID = false,
CustomerItemCode = "FP-PT#004",
ProductDesc = "Coated Stock",
ProductCode = "88CEMNH006",
ProfileName = "88CEMNH006.spkg",
SuggestedPrinter = "SML FP300R (Copy 1)",
};
product.SKUFields = new List<SKUField>();
SKUField skuField1 = new SKUField();
skuField1.FieldName = "Qty";
skuField1.FieldTitle = "Order Qty";
skuField1.FieldType = typeof(System.Int32);
skuField1.IsEditable = true;
product.SKUFields.Add(skuField1);
SKUField skuField2 = new SKUField();
skuField2.FieldName = "Size";
skuField2.FieldTitle = "Size";
skuField2.FieldType = typeof(System.String);
skuField2.IsEditable = true;
skuField2.IsValueSelectable = true;
skuField2.SKUFieldValues = new List<SKUFieldValue>() {
new SKUFieldValue() { FieldValue = "Large" },
new SKUFieldValue() { FieldValue = "Middle" },
new SKUFieldValue() { FieldValue = "Small" },
};
product.SKUFields.Add(skuField2);
SKUField skuField3 = new SKUField();
skuField3.FieldName = "Style";
skuField3.FieldTitle = "Style";
skuField3.FieldType = typeof(System.String);
skuField3.IsEditable = true;
skuField3.IsValueSelectable = true;
skuField3.SKUFieldValues = new List<SKUFieldValue>() {
new SKUFieldValue() { FieldValue = "001" },
new SKUFieldValue() { FieldValue = "002" },
new SKUFieldValue() { FieldValue = "003" },
};
product.SKUFields.Add(skuField3);
SKUField skuField4 = new SKUField();
skuField4.FieldName = "CollectionName";
skuField4.FieldTitle = "Collection Name";
skuField4.FieldType = typeof(System.String);
skuField4.IsEditable = false;
skuField4.DefaultValue = "100% COTTON";
product.SKUFields.Add(skuField4);
return product;
}
private Product GetProduct4()
{
var product = new Product()
{
ProductId = 4,
IsRFID = false,
IsTID = false,
CustomerItemCode = "FP-PT#004",
ProductDesc = "Coated Stock",
ProductCode = "88CEMNH004",
ProfileName = "88CEMNH004.spkg",
SuggestedPrinter="Fax",
};
product.SKUFields = new List<SKUField>();
SKUField skuField1 = new SKUField();
skuField1.FieldName = "Qty";
skuField1.FieldTitle = "Order Qty";
skuField1.FieldType = typeof(System.Int32);
skuField1.IsEditable = true;
product.SKUFields.Add(skuField1);
SKUField skuField2 = new SKUField();
skuField2.FieldName = "Size";
skuField2.FieldTitle = "Size";
skuField2.FieldType = typeof(System.String);
skuField2.IsEditable = true;
skuField2.IsValueSelectable = true;
skuField2.SKUFieldValues = new List<SKUFieldValue>()
{
new SKUFieldValue() { FieldValue = "Large" },
new SKUFieldValue() { FieldValue = "Middle" },
new SKUFieldValue() { FieldValue = "Small" },
};
product.SKUFields.Add(skuField2);
SKUField skuField3 = new SKUField();
skuField3.FieldName = "Style";
skuField3.FieldTitle = "Style";
skuField3.FieldType = typeof(System.String);
skuField3.IsEditable = true;
skuField3.IsValueSelectable = true;
skuField3.SKUFieldValues = new List<SKUFieldValue>()
{
new SKUFieldValue() { FieldValue = "001" },
new SKUFieldValue() { FieldValue = "002" },
new SKUFieldValue() { FieldValue = "003" },
};
product.SKUFields.Add(skuField3);
SKUField skuField4 = new SKUField();
skuField4.FieldName = "CollectionName";
skuField4.FieldTitle = "Collection Name";
skuField4.FieldType = typeof(System.String);
skuField4.IsEditable = false;
skuField4.DefaultValue = "100% COTTON";
product.SKUFields.Add(skuField4);
return product;
}
}
项⽬结构如图所⽰
3、定义WPF项⽬,⽤于显⽰UI
Note: 项⽬有使⽤CM、Ninject框架
项⽬结构如下
(1) 定义Model类
UISKURecord ⽤于定义第⼆个表格的数据源中的⼀⾏数据,包含 UISKUField 列表,⽤于代表不确定的列。

public class UISKURecord
{
private readonly ObservableCollection<UISKUField> _uiSKUFields =
new ObservableCollection<UISKUField>();
public ObservableCollection<UISKUField> UISKUFields
{
get
{
return _uiSKUFields;
}
}
public void AddSKUField(UISKUField uiSKUField)
{
_uiSKUFields.Add(uiSKUField);
}
}
UISKUField ⽤于定义要显⽰的属性,继承⾃SKUField中,添加了两个⽤于绑定UI的属性。

public class UISKUField : SKUField, INotifyPropertyChanged
{
///<summary>
///两种情况下UI⽂本框绑定此属性
/// 1. IsEditable = False
/// 2. IsEditable = True But IsValueSelectable = False
///</summary>
public object Value { get; set; }
///<summary>
///当IsValueSelectable = True时,UI显⽰下拉列表供⽤户选择值
///此时下拉列表SelectedItem绑定此属性
///</summary>
private SKUFieldValue _selectedUISKUFieldValue;
public SKUFieldValue SelectedUISKUFieldValue
{
get { return _selectedUISKUFieldValue; }
set
{
_selectedUISKUFieldValue = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
SKUFieldExtensionMethods⽤于定义将服务提供的数据类型转化成UI绑定所使⽤的数据类型。

public static class SKUFieldExtensionMethods
{
public static UISKUField ToUISKUField(this SKUField skuField)
{
UISKUField uiSKUField = new UISKUField();
uiSKUField.Value = skuField.DefaultValue;
uiSKUField.FieldName = skuField.FieldName;
uiSKUField.FieldTitle = skuField.FieldTitle;
uiSKUField.IsEditable = skuField.IsEditable;
uiSKUField.IsValueSelectable = skuField.IsValueSelectable;
if (uiSKUField.IsValueSelectable)
{
uiSKUField.SKUFieldValues = skuField.SKUFieldValues;
uiSKUField.SelectedUISKUFieldValue = uiSKUField.SKUFieldValues.FirstOrDefault();
}
return uiSKUField;
}
}
(2) 定义 ViewModel 类
public class MainViewModel : PropertyChangedBase
{
#region Field
private IProductService _productService = null;
#endregion
#region Ctor
public MainViewModel(IProductService productService)
{
_productService = productService;
SKURecords = new ObservableCollection<UISKURecord>();
Products = new ObservableCollection<Product>(_productService.GetAllProducts());
}
#endregion
#region Prop
private ObservableCollection<Product> _products;
///<summary>
///所有产品
///</summary>
public ObservableCollection<Product> Products
{
get { return _products; }
set
{
_products = value;
SelectedProduct = value.FirstOrDefault();
NotifyOfPropertyChange(() => Products);
}
}
private Product _selectedProduct;
///<summary>
///所选产品
///</summary>
public Product SelectedProduct
{
get { return _selectedProduct; }
set
{
_selectedProduct = value;
NotifyOfPropertyChange(() => SelectedProduct);
//切换产品时,先清空SKU表格中的数据,再添加⼀⾏
SKURecords.Clear();
AddSKU();
}
}
private ObservableCollection<UISKURecord> _skuRecords;
public ObservableCollection<UISKURecord> SKURecords
{
get { return _skuRecords; }
set
{
_skuRecords = value;
NotifyOfPropertyChange(() => SKURecords);
}
}
private UISKURecord _selectedSKURecord;
public UISKURecord SelectedSKURecord
{
get { return _selectedSKURecord; }
set
{
_selectedSKURecord = value;
NotifyOfPropertyChange(() => SelectedSKURecord);
}
}
#endregion
#region ICommand Method
public void AddSKU()
{
UISKURecord skuRecord = new UISKURecord();
foreach (var skuField in _selectedProduct.SKUFields)
{
skuRecord.AddSKUField(skuField.ToUISKUField());
}
SKURecords.Add(skuRecord);
}
#endregion
}
(3) 定义View
<Window x:Class="WpfApplication3.MainWindow"
xmlns="/winfx/2006/xaml/presentation"
xmlns:x="/winfx/2006/xaml"
xmlns:d="/expression/blend/2008"
xmlns:mc="/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApplication3"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="customerDataGridTextBlockColumnDataTemplate">
<TextBlock Text="{Binding Path = Value}"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="customerDataGridTextColumnDataTemplate">
<TextBox Text="{Binding Path = Value}"></TextBox>
</DataTemplate>
<DataTemplate x:Key="customerDataGridComboboxColumnDataTemplate">
<ComboBox ItemsSource="{Binding Path = SKUFieldValues}"
DisplayMemberPath="FieldValue"
SelectedItem="{Binding SelectedUISKUFieldValue}">
</ComboBox>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<DataGrid x:Name="ProductsDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding Products}"
SelectionChanged="ProductsDataGrid_SelectionChanged"
SelectedItem="{Binding SelectedProduct}" Margin="10">
<DataGrid.Columns>
<DataGridTextColumn Header="Product Code" Width="150" Binding="{Binding Path=ProductCode}"/>
<DataGridTextColumn Header="Customer Item Code" Width="150" Binding="{Binding Path=CustomerItemCode}"/> <DataGridTextColumn Header="Product Desc" Width="150" Binding="{Binding Path=ProductDesc}"/>
</DataGrid.Columns>
</DataGrid>
<DataGrid x:Name="SKUsDataGrid"
Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding SKURecords}"
SelectedItem="{Binding SelectedSKURecord}"
Margin="10"
CanUserAddRows="False"
CanUserDeleteRows="False">
</DataGrid>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Left" Margin="10 0 0 0">
<Button x:Name="AddSKU" Content="Add" Width="75" Height="25" Margin="0,0,0,2" VerticalAlignment="Bottom"/> <Button x:Name="RemoveSKU" Content="Remove" Width="75" Height="25" Margin="5 0 0 0"/>
</StackPanel>
</Grid>
</Window>
///<summary>
/// MainWindow.xaml 的交互逻辑
///</summary>
public partial class MainWindow : Window
{
MainViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
this.DataContext = _viewModel = new MainViewModel();
}
private void ProductsDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SKUsDataGrid.Columns.Clear();
var viewModel = (MainViewModel)DataContext;
var fields = _viewModel.SKURecords.First().SKUProperties.ToList();
for (int i = 0; i < fields.Count; i++)
{
var field = fields[i];
//列不可编辑
if (!field.IsEditable)
{
var column = new CustomBoundColumn();
column.IsReadOnly = true;
column.Header = field.FieldTitle;
column.Binding = new Binding(string.Format("SKUProperties[{0}]", i));
column.TemplateName = "customerDataGridTextBlockColumnDataTemplate";
column.Width = 100;
SKUsDataGrid.Columns.Add(column);
}
else
{
if (!field.IsValueSelectable)
{
var column = new CustomBoundColumn();
column.IsReadOnly = false;
column.Header = field.FieldTitle;
column.Binding = new Binding(string.Format("SKUProperties[{0}]", i));
column.TemplateName = "customerDataGridTextColumnDataTemplate";
column.Width = 100;
SKUsDataGrid.Columns.Add(column);
}
else
{
var column = new CustomBoundColumn();
column.IsReadOnly = false;
column.Header = field.FieldTitle;
column.Binding = new Binding(string.Format("SKUProperties[{0}]", i));
column.TemplateName = "customerDataGridComboboxColumnDataTemplate";
column.Width = 100;
SKUsDataGrid.Columns.Add(column);
}
}
}
}
}
public class CustomBoundColumn : DataGridBoundColumn
{
public string TemplateName { get; set; }
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var binding = new Binding(((Binding)Binding).Path.Path);
binding.Source = dataItem;
var content = new ContentControl();
content.ContentTemplate = (DataTemplate)cell.FindResource(TemplateName);
content.SetBinding(ContentControl.ContentProperty, binding);
return content;
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) {
return GenerateElement(cell, dataItem);
}
}。

相关文档
最新文档