WPF 動態生成DataGrid及動態綁定解決方案

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);
    }
}
相關文章
相關標籤/搜索