是否可以在 mvvm 模式中获取 wpf 数据网格上的动态列?

Posted

技术标签:

【中文标题】是否可以在 mvvm 模式中获取 wpf 数据网格上的动态列?【英文标题】:Is it possible to get dynamic columns on wpf datagrid in mvvm pattern? 【发布时间】:2013-03-11 01:28:12 【问题描述】:

我正在使用 wpf 开发产品(使用 MVVM 模式)。根据用户的自定义(用户选择列),我必须将一组数据显示到数据网格中。目前,我正在将带有一组属性的ObservableCollection 绑定到数据网格的ItemSource。这将我限制在固定的列大小。

注意:列出了 n 个列名供用户选择。

如果它在后面的代码中完成,“datagrid.columns.add()”很容易。在这种情况下,任何人都可以帮助我吗?

我的 xaml:

<my:DataGrid
    AutoGenerateColumns="False"
    Margin="357,121.723,82,41"
    Name="dataGrid3"
    c:DataGridExtension.Columns="Binding ColumnCollection"
    />

我的命令类:

public static class DataGridExtension

    public static ObservableCollection<DataGridColumn> GetColumns(DependencyObject obj)
    
        return (ObservableCollection<DataGridColumn>)obj.GetValue(ColumnsProperty);
    

    public static void SetColumns(
        DependencyObject obj, ObservableCollection<DataGridColumn> value)
    
        obj.SetValue(ColumnsProperty, value);
    

    public static readonly DependencyProperty ColumnsProperty = 
        DependencyProperty.RegisterAttached(
            "Columns",
            typeof(ObservableCollection<DataGridColumn>),typeof(DataGridExtension),
         new UIPropertyMetadata(new ObservableCollection<DataGridColumn>(),
            OnDataGridColumnsPropertyChanged));

    private static void OnDataGridColumnsPropertyChanged(DependencyObject d,
           DependencyPropertyChangedEventArgs e)
    
        if (d.GetType() == typeof(DataGrid))
        
            DataGrid myGrid = d as DataGrid;

            var Columns = (ObservableCollection<DataGridColumn>)e.NewValue;

            if (Columns != null)
            
                myGrid.Columns.Clear();

                if (Columns != null && Columns.Count > 0)
                
                    foreach (DataGridColumn dataGridColumn in Columns)
                    
                        myGrid.Columns.Add(dataGridColumn);
                    
                

                Columns.CollectionChanged +=
                    (object sender, NotifyCollectionChangedEventArgs args)
                
                    if (args.NewItems != null)
                    
                        //foreach (DataGridColumn column in args.NewItems.Cast<DataGridColumn>())
                        //    myGrid.Columns.Add(column);
                    

                    if (args.OldItems != null)
                    
                        //foreach (DataGridColumn column in args.OldItems.Cast<DataGridColumn>())
                        //    myGrid.Columns.Remove(column);
                    
                ;
            
        
    

以及我在视图模型中的属性:

private ObservableCollection<DataGridColumn> _columnCollection =
    new ObservableCollection<DataGridColumn>();
    
public ObservableCollection<DataGridColumn> ColumnCollection

    get
    
        return this._columnCollection;
    
    set
    
        _columnCollection = value;
        base.OnPropertyChanged("ColumnCollection");
        //Error
        //base.OnPropertyChanged<ObservableCollection<DataGridColumn>>(
        //   () => this.ColumnCollection);
    

【问题讨论】:

mvvm 不限制您使用代码隐藏 粗略方式 - 预先创建所有列并隐藏用户未选择的内容。 blindmeis,正如你所说的 mvvm 并没有限制我的代码隐藏,我的 TL 是...... 正如我所说,我使用 ObservableCollection 所以属性(列)是我的朋友“Angshuman Agarwal”预定义的。 另一种选择是使用附加属性并将其绑定到 VM 中的某些“ColumnInfo”对象列表,并在附加属性的 PropertyChanged 回调中的代码中创建列。 【参考方案1】:

谢谢你的努力家伙......终于我找到了解决方案......

在这里..(完整的 wpf mvvm)

在我的命令文件中:

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();
                    if (ne.NewItems != null)
                    
                        foreach (DataGridColumn column in ne.NewItems)
                        
                            dataGrid.Columns.Add(column);
                        
                    
                
                else if (ne.Action == NotifyCollectionChangedAction.Add)
                
                    if (ne.NewItems != null)
                    
                        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)
                
                    if (ne.OldItems != null)
                    
                        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);
        
    

在我的 xaml 中:

<my:DataGrid AutoGenerateColumns="False" Margin="357,121.723,82,41" Name="dataGrid3" ItemsSource="Binding Path=Datatable" c:DataGridColumnsBehavior.BindableColumns="Binding ColumnCollection" />

最后在我的视图模型中:

private ObservableCollection<DataGridColumn> _columnCollection = new ObservableCollection<DataGridColumn>();
        public ObservableCollection<DataGridColumn> ColumnCollection
        
            get
            
                return this._columnCollection;
            
            set
            
                _columnCollection = value;
                base.OnPropertyChanged("ColumnCollection");
                //Error
                //base.OnPropertyChanged<ObservableCollection<DataGridColumn>>(() => this.ColumnCollection);
            
        
        private DataTable _datatable = new DataTable();
        public DataTable Datatable
        
            get
            
                return _datatable;
            
            set
            
                if (_datatable != value)
                
                    _datatable = value;
                
                base.OnPropertyChanged("Datatable");
            
        

在我的构造函数中:

public MainViewModel()
        
Datatable.Columns.Add("Name",typeof(string));
            Datatable.Columns.Add("Color", typeof(string));
            Datatable.Columns.Add("Phone", typeof(string));
            Datatable.Rows.Add("Vinoth", "#00FF00", "456345654");
            Datatable.Rows.Add("lkjasdgl", "Blue", "45654");
            Datatable.Rows.Add("Vinoth", "#FF0000", "456456");
System.Windows.Data.Binding bindings = new System.Windows.Data.Binding("Name");
            System.Windows.Data.Binding bindings1 = new System.Windows.Data.Binding("Phone");
            System.Windows.Data.Binding bindings2 = new System.Windows.Data.Binding("Color");
            DataGridTextColumn s = new DataGridTextColumn();
            s.Header = "Name";
            s.Binding = bindings;
            DataGridTextColumn s1 = new DataGridTextColumn();
            s1.Header = "Phone";
            s1.Binding = bindings1;
            DataGridTextColumn s2 = new DataGridTextColumn();
            s2.Header = "Color";
            s2.Binding = bindings2;

            FrameworkElementFactory textblock = new FrameworkElementFactory(typeof(TextBlock));
            textblock.Name = "text";
            System.Windows.Data.Binding prodID = new System.Windows.Data.Binding("Name");
            System.Windows.Data.Binding color = new System.Windows.Data.Binding("Color");
            textblock.SetBinding(TextBlock.TextProperty, prodID);
            textblock.SetValue(TextBlock.TextWrappingProperty, TextWrapping.Wrap);
            //textblock.SetValue(TextBlock.BackgroundProperty, color);
            textblock.SetValue(TextBlock.NameProperty, "textblock");
            //FrameworkElementFactory border = new FrameworkElementFactory(typeof(Border));
            //border.SetValue(Border.NameProperty, "border");
            //border.AppendChild(textblock);
            DataTrigger t = new DataTrigger();
            t.Binding = new System.Windows.Data.Binding  Path = new PropertyPath("Name"), Converter = new EnableConverter(), ConverterParameter ="Phone" ;
            t.Value = 1;
            t.Setters.Add(new Setter(TextBlock.BackgroundProperty, Brushes.LightGreen, textblock.Name));
            t.Setters.Add(new Setter(TextBlock.ToolTipProperty, bindings, textblock.Name));
            DataTrigger t1 = new DataTrigger();
            t1.Binding = new System.Windows.Data.Binding  Path = new PropertyPath("Name"), Converter = new EnableConverter(), ConverterParameter = "Phone" ;
            t1.Value = 2;
            t1.Setters.Add(new Setter(TextBlock.BackgroundProperty, Brushes.LightYellow, textblock.Name));
            t1.Setters.Add(new Setter(TextBlock.ToolTipProperty, bindings, textblock.Name));

            DataTemplate d = new DataTemplate();
            d.VisualTree = textblock;
            d.Triggers.Add(t);
            d.Triggers.Add(t1);

            DataGridTemplateColumn s3 = new DataGridTemplateColumn();
            s3.Header = "Name 1";
            s3.CellTemplate = d;
            s3.Width = 140;

            ColumnCollection.Add(s); 
            ColumnCollection.Add(s1);
            ColumnCollection.Add(s2);
            ColumnCollection.Add(s3);
    

【讨论】:

当您在 ViewModel 中使用 System.Windows.Controls 中的“DataGridColumn”时,这种纯 MVVM 方法如何? 注意 - 此代码已在另一个 Stack Overflow 答案中修复:***.com/questions/17986380/…。请参阅琼斯的回答。【参考方案2】:

我想扩展前面的示例(答案)订阅和取消订阅事件 CollectionChanged 的​​能力。

行为(在 System.Windows.Interactivity 上添加参考):

 public class ColumnsBindingBehaviour : Behavior<DataGrid>

    public ObservableCollection<DataGridColumn> Columns
    
        get  return (ObservableCollection<DataGridColumn>) base.GetValue(ColumnsProperty); 
        set  base.SetValue(ColumnsProperty, value); 
    

    public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns",
        typeof(ObservableCollection<DataGridColumn>), typeof(ColumnsBindingBehaviour),
            new PropertyMetadata(OnDataGridColumnsPropertyChanged));

    private static void OnDataGridColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    
        var context = source as ColumnsBindingBehaviour;

        var oldItems = e.OldValue as ObservableCollection<DataGridColumn>;

        if (oldItems != null)
        
            foreach (var one in oldItems)
                context._datagridColumns.Remove(one);

            oldItems.CollectionChanged -= context.collectionChanged;
        

        var newItems = e.NewValue as ObservableCollection<DataGridColumn>;

        if (newItems != null)
        
            foreach (var one in newItems)
                context._datagridColumns.Add(one);

            newItems.CollectionChanged += context.collectionChanged;
        
    

    private ObservableCollection<DataGridColumn> _datagridColumns;

    protected override void OnAttached()
    
        base.OnAttached();

        this._datagridColumns = AssociatedObject.Columns;
    


    private void collectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    
        switch (e.Action)
        
            case NotifyCollectionChangedAction.Add:
                if (e.NewItems != null)
                    foreach (DataGridColumn one in e.NewItems)
                        _datagridColumns.Add(one);
                break;

            case NotifyCollectionChangedAction.Remove:
                if (e.OldItems != null)
                    foreach (DataGridColumn one in e.OldItems)
                        _datagridColumns.Remove(one);
                break;

            case NotifyCollectionChangedAction.Move:
                _datagridColumns.Move(e.OldStartingIndex, e.NewStartingIndex);
                break;

            case NotifyCollectionChangedAction.Reset:
                _datagridColumns.Clear();
                if (e.NewItems != null)
                    foreach (DataGridColumn one in e.NewItems)
                        _datagridColumns.Add(one);
                break;
        
    

查看

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:loc="clr-namespace:WpfApplication1"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <DataGrid x:Name="_dataGrid">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Test" />
        </DataGrid.Columns>
        <i:Interaction.Behaviors>
            <loc:ColumnsBindingBehaviour Columns="Binding DataGridColumns"/>        
        </i:Interaction.Behaviors>
    </DataGrid>
</Grid>

视图模型:

     private ObservableCollection<DataGridColumn> _dataGridColumns;
     public ObservableCollection<DataGridColumn> DataGridColumns
     
         get
         
             if (_dataGridColumns == null)
                 _dataGridColumns = new ObservableCollection<DataGridColumn>()
                 
                     new DataGridTextColumn()
                     
                         Header = "Column1"
                     
                 ;

             return _dataGridColumns;
         
         set
         
             _dataGridColumns = value;
             OnPropertyChanged("DataGridColumns");
         
     

【讨论】:

以上是关于是否可以在 mvvm 模式中获取 wpf 数据网格上的动态列?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 MVVM 模式“禁用”WPF 中的按钮?

Wpf数据网格CurrentCell属性只触发一次MVVM

C# WPF MVVM模式下在主窗体显示子窗体并获取结果

wpf mvvm获取列表行数据

C# WPF MVVM模式下在主窗体显示子窗体并获取结果

WPF MVVM 创建动态控件