1、場景express
有過WPF項目經驗的朋友可能都知道,若是一個DataGrid要綁定靜態的數據是很是的簡單的(所謂靜態是指綁定的數據源的類型是靜態的),以下圖所示,想要顯示產品數據,只需綁定到一個產品列表便可,這個你們都清楚,因此這個要講的確定不是這個。框架
可是如今有一個新的需求,根據所選擇產品的不一樣,要動態生成第二個表格中的不一樣數據,以便進行編輯,以下圖一、2所示,當選擇的產品不一樣時,第二個表格顯示的內容是徹底不同的。ide
這樣就會產生一個問題,沒法直接對第二個表格進行綁定,由於它的數據源類型都是不同的,沒法按照傳統方法進行綁定。如何解決,先本身思考一下,也許會有更好的解決方案。ui
2、思路this
一、定義Domainspa
既然沒法知道要綁定的數據類型是什麼,由於它是動態的,沒法事先預知的,根據所選擇的產品(由於產品數據都存儲於DB中,因此將類型定義於Domain項目中)不一樣而變化的。那麼能夠在 Product 中定義 SKUFields 屬性用於表明產品可顯示的字段。產品定義以下所示。3d
/// <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 接口,用於定義獲取產品的方法。code
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項目結構所圖所示。orm
二、定義服務,用於獲取全部產品信息xml
第二部創建 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 = Path.Combine(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; } }
項目結構如圖所示
三、定義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="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: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); } }