WPF dynamic columns and values for DataGrid

2019-08-14 04:46发布

问题:

I have two DataGrids. One list the list of items and the other holds the specification.

If i select Monitor in the 1st grid , The second grid should show its own specifications . and if i select CPU it should display its specifications on the other gird.

I looking for a good solution to do this . As of now im planing to create a model with a property of string and a DataTable so i can create a bind the dynamic table to second grid . But im looking for something like dynamic property or some better Solution .

Thanks

回答1:

For such a case I use my GenericRow and GenericTable classes:

public class GenericRow : CustomTypeDescriptor, INotifyPropertyChanged
{

    #region Private Fields
    List<PropertyDescriptor> _property_list = new List<PropertyDescriptor>();
    #endregion

    #region INotifyPropertyChange Implementation

    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion INotifyPropertyChange Implementation

    #region Public Methods

    public void SetPropertyValue<T>(string propertyName, T propertyValue)
    {
        var properties = this.GetProperties()
                                .Cast<PropertyDescriptor>()
                                .Where(prop => prop.Name.Equals(propertyName));

        if (properties == null || properties.Count() != 1)
        {
            throw new Exception("The property doesn't exist.");
        }

        var property = properties.First();
        property.SetValue(this, propertyValue);

        OnPropertyChanged(propertyName);
    }

    public T GetPropertyValue<T>(string propertyName)
    {
        var properties = this.GetProperties()
                            .Cast<PropertyDescriptor>()
                            .Where(prop => prop.Name.Equals(propertyName));

        if (properties == null || properties.Count() != 1)
        {
            throw new Exception("The property doesn't exist.");
        }

        var property = properties.First();
        return (T)property.GetValue(this);
    }

    public void AddProperty<T, U>(string propertyName) where U : GenericRow
    {
        var customProperty =
                new CustomPropertyDescriptor<T>(
                                        propertyName,
                                        typeof(U));

        _property_list.Add(customProperty);
    }

    #endregion

    #region Overriden Methods

    public override PropertyDescriptorCollection GetProperties()
    {
        var properties = base.GetProperties();
        return new PropertyDescriptorCollection(
                            properties.Cast<PropertyDescriptor>()
                                      .Concat(_property_list).ToArray());
    }

    #endregion

}

and:

 public class GenericTable
{

    private string tableName = "";
    public string TableName
    {
        get { return tableName; }
        set { tableName = value; }
    }

    private ObservableCollection<DataGridColumn> columnCollection;
    public ObservableCollection<DataGridColumn> ColumnCollection
    {
        get { return columnCollection; }
        private set { columnCollection = value; }
    }

    private ObservableCollection<GenericRow> genericRowCollection;
    public ObservableCollection<GenericRow> GenericRowCollection
    {
        get { return genericRowCollection; }
        set { genericRowCollection = value; }
    }




    public GenericTable(string tableName)
    {
        this.TableName = tableName;
        ColumnCollection = new ObservableCollection<DataGridColumn>();
        GenericRowCollection = new ObservableCollection<GenericRow>(); 
    }

    /// <summary>
    /// ColumnName is also binding property name
    /// </summary>
    /// <param name="columnName"></param>
    public void AddColumn(string columnName)
    {
        DataGridTextColumn column = new DataGridTextColumn();
        column.Header = columnName;
        column.Binding = new Binding(columnName);
        ColumnCollection.Add(column);
    }



    public override string ToString()
    {
        return TableName; 
    }


}

You can use these two classes to create dynamic rows and columns. Using GenericRow class, you can generate row with desired property name and you can use the same property name for the columns for prober binding.

And for the XAML side:

<DataGrid Name="dataGrid"
          local:DataGridColumnsBehavior.BindableColumns="{Binding ColumnCollection}"
          AutoGenerateColumns="False"
          ...>

and finally DataGridColumsBehaior :

public class DataGridColumnsBehavior
{
    public static readonly DependencyProperty BindableColumnsProperty =
        DependencyProperty.RegisterAttached("BindableColumns",
                                            typeof(ObservableCollection<DataGridColumn>),
                                            typeof(DataGridColumnsBehavior),
                                            new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
    private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        DataGrid dataGrid = source as DataGrid;
        ObservableCollection<DataGridColumn> columns = e.NewValue as ObservableCollection<DataGridColumn>;
        dataGrid.Columns.Clear();
        if (columns == null)
        {
            return;
        }
        foreach (DataGridColumn column in columns)
        {
            dataGrid.Columns.Add(column);
        }
        columns.CollectionChanged += (sender, e2) =>
        {
            NotifyCollectionChangedEventArgs ne = e2 as NotifyCollectionChangedEventArgs;
            if (ne.Action == NotifyCollectionChangedAction.Reset)
            {
                dataGrid.Columns.Clear();
                foreach (DataGridColumn column in ne.NewItems)
                {
                    dataGrid.Columns.Add(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (DataGridColumn column in ne.NewItems)
                {
                    dataGrid.Columns.Add(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Move)
            {
                dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
            }
            else if (ne.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (DataGridColumn column in ne.OldItems)
                {
                    dataGrid.Columns.Remove(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Replace)
            {
                dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
            }
        };
    }
    public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
    {
        element.SetValue(BindableColumnsProperty, value);
    }
    public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
    {
        return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
    }
}

In the end, you can create GenericRows and add them into GenericTable. It is like a small viewmodel. In XAML side, dont forget to bind rowscollection as itemsource of the DataGrid.



标签: c# wpf xaml