在DataGrid中绑定自定义标头控件

时间:2018-02-27 18:09:50

标签: c# wpf data-binding caliburn.micro

我有一个自定义列标题,每个列的标题都包含TextBox,其中包含列的名称和ComboBox,其中包含有关列类型的信息,例如: "日期","号码"等

我试图绑定ComboBox并将其值保留在某个位置,以便当用户从ComboBox中选择新值时,我可以重新创建表,并更改了列的类型。基本上我只需要以某种方式将每个ComboBox的值以某种方式存储在列表中。我想对TextBox执行相同操作,其中应包含列的名称。

这是我到目前为止所做的。

<DataGrid x:Name="SampleGrid" Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2" ItemsSource="{Binding SampledData}">
            <DataGrid.Resources>
                <Style TargetType="{x:Type DataGridColumnHeader}">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <StackPanel>
                                    <TextBox Text="{Binding ., Mode=OneWay}"/>
                                    <ComboBox>
                                        // How can I set it from ViewModel?
                                        <ComboBoxItem Content="Date"></ComboBoxItem>
                                        <ComboBoxItem Content="Number"></ComboBoxItem>
                                    </ComboBox>
                                </StackPanel>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </DataGrid.Resources>
        </DataGrid>

视图模型:

private DataTable _sampledData = new DataTable();

public DataTable SampledData
{
    get => _sampledData;
    set { _sampledData = value; NotifyOfPropertyChange(() => SampledData); }
}

我也欢迎代码背后的解决方案,只要我稍后可以将映射传递给ViewModel。

编辑: 我一直试图使用List ViewModel来完成这项工作,但没有运气:

public class ShellViewModel : Screen
{

    public List<MyRowViewModel> Rows { get; set; }

    public ShellViewModel()
    {
        Rows = new List<MyRowViewModel>
        {
            new MyRowViewModel { Column1 = "Test 1", Column2= 1 },
            new MyRowViewModel { Column1 = "Test 2", Column2= 2 }
        };
    }
}

查看

<DataGrid ItemsSource="{Binding Rows}">
    <DataGrid.Resources>
        <Style TargetType="{x:Type DataGridColumnHeader}">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <StackPanel>
                            <TextBox Text="{Binding ., Mode=OneWay}"/>
                            <ComboBox ItemsSource="{Binding ??????}" />
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </DataGrid.Resources>
</DataGrid>

public class MyRowViewModel : PropertyChangedBase
{
    public string Column1 { get; set; }
    public int Column2 { get; set; }
}

EDIT2:

为了澄清,我需要一个能处理动态列数的解决方案,因此有些文件可能存储3列,有些文件可能存储40列。我使用它来解析csv文件以便稍后显示数据。为此,我必须知道文件包含哪些类型的值。因为某些类型可能不明确,我让用户决定他们想要哪些类型。这与Excel&#34;从文件加载&#34;相同。向导。

向导加载一小块数据(100条记录),并允许用户决定列的类型。它会自动将列解析为:

  1. 让用户看看数据的外观如何
  2. 验证是否可以实际解析列(例如68.35不能 被解析为DateTime
  3. 另一件事是命名每一列。有人可能会为每个名为C1C2的列添加csv,但他们希望分配有意义的名称,例如TemperatureAverage。这当然也必须在以后解析,因为两列不能具有相同的名称,但是一旦我有一个可绑定的DataGrid,我就可以处理这个。

3 个答案:

答案 0 :(得分:4)

让我们将您的问题分解为多个部分并分别解决每个部分。

首先DataGrid itemsource,为了让事情变得更轻松,让我们说DataGrid只有两列,第1列第2列DataGrid项的基本模型应如下所示:

public class DataGridModel
{
    public string FirstProperty { get; set; }   
    public string SecondProperty { get; set; }   
}

现在,假设您有一个MainWindow(ViewModel或DataContext设置为后面的代码),其中包含DataGrid,请定义{{1 } DataGridCollection

ItemSource

第二,现在是有趣的部分,列结构。让我们为您的private ObservableCollection<DataGridModel> _dataGridCollection=new ObservableCollection<DataGridModel>() { new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"}, new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"}, new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"} }; public ObservableCollection<DataGridModel> DataGridCollection { get { return _dataGridCollection; } set { if (Equals(value, _dataGridCollection)) return; _dataGridCollection = value; OnPropertyChanged(); } } 列定义一个模型,该模型将包含设置DataGrid列所需的所有属性,包括:

- DataTypesCollection:一个包含组合框项目源的集合。 - HeaderPropertyCollection:一组元组,每个DataGrid代表列名数据类型,数据类型基本上是列的选定项目Tuple

combobox

现在,您在MainWindow的viewmodel(或代码隐藏)中定义了我们将用于保存 public class DataGridColumnsModel:INotifyPropertyChanged { private ObservableCollection<string> _dataTypesCollection = new ObservableCollection<string>() { "Date","String","Number" }; public ObservableCollection<string> DataTypesCollection { get { return _dataTypesCollection; } set { if (Equals(value, _dataTypesCollection)) return; _dataTypesCollection = value; OnPropertyChanged(); } } private ObservableCollection<Tuple<string, string>> _headerPropertiesCollection=new ObservableCollection<Tuple<string, string>>() { new Tuple<string, string>("Column 1", "Date"), new Tuple<string, string>("Column 2", "String") }; //The Dictionary has a PropertyName (Item1), and a PropertyDataType (Item2) public ObservableCollection<Tuple<string,string>> HeaderPropertyCollection { get { return _headerPropertiesCollection; } set { if (Equals(value, _headerPropertiesCollection)) return; _headerPropertiesCollection = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } 结构的DataGridColumnsModel实例:

DataGrid

第三次,获取列 private DataGridColumnsModel _dataGridColumnsModel=new DataGridColumnsModel(); public DataGridColumnsModel DataGridColumnsModel { get { return _dataGridColumnsModel; } set { if (Equals(value, _dataGridColumnsModel)) return; _dataGridColumnsModel = value; OnPropertyChanged(); } } 的值。为此,我将使用TextBoxMultiBinding,第一个传递给MultiValueConverter的属性是我们定义的元组集合(列&#39;名称和数据类型):MultiBinding,第二个是使用祖先绑定到HeaderPropertyCollectionDisplayIndex检索的当前列索引:

DataGridColumnHeader

转换器将使用元组集合中的索引来检索正确的项目:

<TextBox >
    <TextBox.Text>
       <MultiBinding Converter="{StaticResource GetPropertConverter}">
            <Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}}" Path="DataGridColumnsModel.HeaderPropertyCollection"/>
            <Binding  Path="DisplayIndex" Mode="OneWay" RelativeSource="{RelativeSource RelativeSource={x:Type DataGridColumnHeader}}"/>
      </MultiBinding> 
  </TextBox.Text>

第四,最后一部分是在public class GetPropertConverter:IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { try { var theCollection = values[0] as ObservableCollection<Tuple<string, string>>; return (theCollection?[(int)values[1]])?.Item1; //Item1 is the column name, Item2 is the column's ocmbobox's selectedItem } catch (Exception) { //use a better implementation! return null; } } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } 的选择更改时更新DataGrid ItemSource,你可以使用 System.Windows.Interactivity 命名空间中定义的交互工具(它是 Expression.Blend.Sdk 的一部分,使用NuGet来安装它:安装-Package Expression.Blend.Sdk ):

Combobox

每次发生<ComboBox ItemsSource="{Binding DataGridColumnsModel.DataTypesCollection,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"> <i:Interaction.Triggers> <i:EventTrigger EventName="SelectionChanged"> <i:InvokeCommandAction Command="{Binding UpdateItemSourceCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" /> </i:EventTrigger> </i:Interaction.Triggers> </ComboBox> 事件时,请更新selectionChanged中应添加到您的mainWindow ViewModel中的DataGrid ItemSource UpdateItemSourceCommand

 private RelayCommand _updateItemSourceCommand;
    public RelayCommand UpdateItemSourceCommand
    {
        get
        {
            return _updateItemSourceCommand
                   ?? (_updateItemSourceCommand = new RelayCommand(
                       () =>
                       {
                           //Update your DataGridCollection, you could also pass a parameter and use it.
                           //Update your DataGridCollection based on DataGridColumnsModel.HeaderPropertyCollection
                       }));
        }
    }

Ps:我正在使用的RelayCommand类是GalaSoft.MvvmLight.Command命名空间的一部分,你可以通过NuGet添加它,或者定义你自己的命令。

最后是完整的xaml代码:

Window x:Class="WpfApp1.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:WpfApp1"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
    <local:GetPropertConverter x:Key="GetPropertConverter"/>
</Window.Resources>
<Grid>
    <DataGrid x:Name="SampleGrid" ItemsSource="{Binding DataGridCollection}" AutoGenerateColumns="False">
        <DataGrid.Resources>
            <Style TargetType="{x:Type DataGridColumnHeader}">
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <StackPanel>
                                <TextBox >
                                    <TextBox.Text>
                                        <MultiBinding Converter="{StaticResource GetPropertConverter}">
                                            <Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}}" Path="DataGridColumnsModel.HeaderPropertyCollection"/>
                                            <Binding  Path="DisplayIndex" Mode="OneWay" RelativeSource="{RelativeSource AncestorType={x:Type DataGridColumnHeader}}"/>
                                        </MultiBinding> 
                                    </TextBox.Text>
                                </TextBox>
                                <ComboBox ItemsSource="{Binding DataGridColumnsModel.DataTypesCollection,RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
                                    <i:Interaction.Triggers>
                                        <i:EventTrigger EventName="SelectionChanged">
                                           <i:InvokeCommandAction Command="{Binding UpdateItemSourceCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
                                        </i:EventTrigger>
                                    </i:Interaction.Triggers>
                                </ComboBox>
                            </StackPanel>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </DataGrid.Resources>
        <DataGrid.Columns>
            <DataGridTextColumn Header="First Column" Binding="{Binding FirstProperty}" />
            <DataGridTextColumn Header="Second Column" Binding="{Binding SecondProperty}"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

查看模型/代码隐藏:

public class GetPropertConverter:IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        try
        {
            var theCollection = values[0] as ObservableCollection<Tuple<string, string>>;
            return (theCollection?[(int)values[1]])?.Item1; //Item1 is the column name, Item2 is the column's ocmbobox's selectedItem
        }
        catch (Exception)
        {
            //use a better implementation!
            return null;
        }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
public class DataGridColumnsModel:INotifyPropertyChanged
{
    private ObservableCollection<string> _dataTypesCollection = new ObservableCollection<string>()
    {
        "Date","String","Number"
    };
    public ObservableCollection<string> DataTypesCollection         
    {
        get { return _dataTypesCollection; }
        set
        {
            if (Equals(value, _dataTypesCollection)) return;
            _dataTypesCollection = value;
            OnPropertyChanged();
        }
    }

    private ObservableCollection<Tuple<string, string>> _headerPropertiesCollection=new ObservableCollection<Tuple<string, string>>()
    {
        new Tuple<string, string>("Column 1", "Date"),
        new Tuple<string, string>("Column 2", "String")

    };   //The Dictionary has a PropertyName (Item1), and a PropertyDataType (Item2)
    public ObservableCollection<Tuple<string,string>> HeaderPropertyCollection
    {
        get { return _headerPropertiesCollection; }
        set
        {
            if (Equals(value, _headerPropertiesCollection)) return;
            _headerPropertiesCollection = value;
            OnPropertyChanged();
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class DataGridModel
{
    public string FirstProperty { get; set; }   
    public string SecondProperty { get; set; }   
}
public partial class MainWindow : Window,INotifyPropertyChanged
{
    private RelayCommand _updateItemSourceCommand;
    public RelayCommand UpdateItemSourceCommand
    {
        get
        {
            return _updateItemSourceCommand
                   ?? (_updateItemSourceCommand = new RelayCommand(
                       () =>
                       {
                           //Update your DataGridCollection, you could also pass a parameter and use it.
                           MessageBox.Show("Update has ocured");
                       }));
        }
    }

    private ObservableCollection<DataGridModel> _dataGridCollection=new ObservableCollection<DataGridModel>()
    {
        new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"},
        new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"},
        new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"}
    };
    public ObservableCollection<DataGridModel> DataGridCollection
    {
        get { return _dataGridCollection; }
        set
        {
            if (Equals(value, _dataGridCollection)) return;
            _dataGridCollection = value;
            OnPropertyChanged();
        }
    }

    private DataGridColumnsModel _dataGridColumnsModel=new DataGridColumnsModel();
    public DataGridColumnsModel DataGridColumnsModel
    {
        get { return _dataGridColumnsModel; }
        set
        {
            if (Equals(value, _dataGridColumnsModel)) return;
            _dataGridColumnsModel = value;
            OnPropertyChanged();
        }
    }

    public MainWindow()
    {
        InitializeComponent();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

<强>结果:

enter image description here

<强>更新

通过设置AutoGenerateColumns="True"并动态创建列,您将获得相同的结果。

答案 1 :(得分:2)

试试这个。

Window1.xaml

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:this="clr-namespace:WpfApplication1"
        Title="Window1" Height="300" Width="300">

    <Window.Resources>
        <this:RowDataConverter x:Key="RowDataConverter1" />
    </Window.Resources>
    <Grid>

        <DataGrid ItemsSource="{Binding Rows, Mode=OneWay}">
            <DataGrid.Columns>
                <DataGridTextColumn>
                    <DataGridTextColumn.Binding>
                        <MultiBinding Converter="{StaticResource RowDataConverter1}">
                            <Binding Path="Column1" Mode="OneWay" />
                            <Binding Path="Column1OptionString" Mode="OneWay" RelativeSource="{RelativeSource AncestorType=Window, Mode=FindAncestor}" />
                        </MultiBinding>
                    </DataGridTextColumn.Binding>

                    <DataGridTextColumn.HeaderTemplate>
                        <DataTemplate>
                            <StackPanel>
                                <TextBlock Text="Column Header 1" />
                                <ComboBox ItemsSource="{Binding ColumnOptions, Mode=OneWay, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
                                          SelectedValue="{Binding Column1OptionString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
                                          SelectedValuePath="Option">
                                    <ComboBox.ItemTemplate>
                                        <DataTemplate DataType="this:ColumnOption">
                                            <TextBlock Text="{Binding Option, Mode=OneTime}" />
                                        </DataTemplate>
                                    </ComboBox.ItemTemplate>
                                </ComboBox>
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTextColumn.HeaderTemplate>
                </DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

Window1.xaml.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace WpfApplication1
{
    public partial class Window1 : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public List<RowData> Rows { get; set; }
        public List<ColumnOption> ColumnOptions { get; set; }

        private string _column1OptionString;
        public string Column1OptionString
        {
            get
            {
                return _column1OptionString;
            }
            set
            {
                _column1OptionString = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Column1OptionString"));
            }
        }

        public Window1()
        {
            InitializeComponent();

            ColumnOptions = new List<ColumnOption>() {
                new ColumnOption(){ Option = "String", StringFormat = "" },
                new ColumnOption(){ Option = "Int32", StringFormat = "" }
            };

            Rows = new List<RowData>() {
                new RowData(){ Column1 = "01234" }
            };

            _column1OptionString = "String";

            this.DataContext = this;
        }
    }

    public class ColumnOption
    {
        public string Option { get; set; }
        public string StringFormat { get; set; }
    }

    public class RowData : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private object _column1;
        public object Column1
        {
            get
            {
                return _column1;
            }
            set
            {
                _column1 = value;
                if (PropertyChanged!= null)
                PropertyChanged(this, new PropertyChangedEventArgs("Column1"));
            }
        }
    }

    public class RowDataConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (values[1] == null)
                return values[0].ToString();

            switch (values[1].ToString())
            {
                case "String":
                    return values[0].ToString();
                case "Int32":
                    Int32 valueInt;
                    Int32.TryParse(values[0].ToString(), out valueInt);
                    return valueInt.ToString();
                default:
                    return values[0].ToString();
            }
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

<强>更新
基于@FCin评论
&#34;这很好,但我使用它来加载csv文件,并且列数根据csv文件而变化。在这里,我必须核心每一列,但有些文件可能有1列,有些可能有30列&#34;。

使用格式假设您的csv文件:
第1行:标题,
第2行:数据类型,
line3-end:记录。

示例data1.csv:
ColumnHeader1,ColumnHeader2
INT32,字符串
1,&#34; A&#34;
2,&#34; B&#34;
3,&#34; C&#34;

我尝试使用TextFieldParser解析csv文件,然后以编程方式生成DataGrid的列。

Window2.xaml

<Window x:Class="WpfApplication1.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window2" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0">
            <Label Content="File:" />

            <ComboBox x:Name="FileOption"
                      SelectionChanged="FileOption_SelectionChanged">
                <ComboBox.Items>
                    <Run Text="Data1.csv" />
                    <Run Text="Data2.csv" />
                </ComboBox.Items>

            </ComboBox>

        </StackPanel>

        <DataGrid x:Name="DataGrid1" Grid.Row="1"
                  AutoGenerateColumns="False"
                  ItemsSource="{Binding ListOfRecords, Mode=OneWay}">
        </DataGrid>

    </Grid>

</Window>

Window2.xaml.cs

using Microsoft.VisualBasic.FileIO;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;

namespace WpfApplication1
{
    public partial class Window2 : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        List<myDynamicObject> _listOfRecords;
        public List<myDynamicObject> ListOfRecords
        {
            get
            {
                return _listOfRecords;
            }
        }

        public Window2()
        {
            InitializeComponent();
            DataContext = this;
        }

        public void LoadData(string fileName)
        {
            _listOfRecords = new List<myDynamicObject>();
            myDynamicObject record;

            TextFieldParser textFieldParser = new TextFieldParser(fileName);
            textFieldParser.TextFieldType = FieldType.Delimited;
            textFieldParser.SetDelimiters(",");

            string[] headers = null;
            string[] dataTypes = null;
            string[] fields;

            int i = 0;
            while(!textFieldParser.EndOfData)
            {
                fields = textFieldParser.ReadFields();

                if (i == 0)
                {
                    headers = fields;
                }
                else if (i == 1)
                {
                    dataTypes = fields;
                }
                else
                {
                    record = new myDynamicObject();
                    for (int j = 0; j < fields.Length; j++)
                    {
                        switch(dataTypes[j].ToLower())
                        {
                            case "string":
                                record.SetMember(headers[j], fields[j]);
                                break;
                            case "int32":
                                Int32 data;
                                if (Int32.TryParse(fields[j], out data))
                                {
                                    record.SetMember(headers[j], data);
                                }
                                break;
                            default:
                                record.SetMember(headers[j], fields[j]);
                                break;
                        }
                    }

                    _listOfRecords.Add(record);
                }

                i += 1;
            }

            PropertyChanged(this, new PropertyChangedEventArgs("ListOfRecords"));

            DataGrid1.Columns.Clear();
            for (int j = 0; j < headers.Length; j++)
            {
                DataGrid1.Columns.Add(new DataGridTextColumn()
                {
                    Header = headers[j],
                    Binding = new Binding()
                    {
                        Path = new PropertyPath(headers[j]),
                        Mode = BindingMode.OneWay
                    }
                });
            }
        }

        private void FileOption_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            LoadData((FileOption.SelectedItem as Run).Text);
        }
    }

    public class myDynamicObject : DynamicObject
    {
        Dictionary<string, object> dictionary = new Dictionary<string, object>();

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            string name = binder.Name;
            return dictionary.TryGetValue(name, out result);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            dictionary[binder.Name] = value;
            return true;
        }

        public void SetMember(string propertyName, object value)
        {
            dictionary[propertyName] = value;
        }
    }
}

答案 2 :(得分:2)

这不完全是一个完整的答案,但更多的是暗示我认为你想做的事情,如果是这样,你可以向我询问其他信息。

我认为您要做的是定义一个DataGridColumDef类型,例如:

 public class DataGridColumnDef : NotifyPropertyChangeModel
    {
        public string Name
        {
            get => _Name;
            set => SetValue(ref _Name, value);
        }
        private string _Name;

        public Type DataType
        {
            get => _DataType;
            set => SetValue(ref _DataType, value);
        }
        private Type _DataType;

        public DataGridColumnDef(string name, Type type)
        {
            Name = name ?? throw new ArgumentNullException(nameof(name));
            DataType = type ?? throw new ArgumentNullException(nameof(type));
        }
    }

然后我想你的视图模型充当DataGrid的数据上下文可能看起来像这样:

public class MainViewModel : NotifyPropertyChangeModel
    {
        public ObservableList<DataGridColumnDef> ColumnDefinitions
        {
            get => _ColumnDefinitions;
            set => SetValue(ref _ColumnDefinitions, value);
        }
        private ObservableList<DataGridColumnDef> _ColumnDefinitions;

        public ObservableList<DataGridRowDef> RowDefinitions
        {
            get => _RowDefinitions;
            set => SetValue(ref _RowDefinitions, value);
        }
        private ObservableList<DataGridRowDef> _RowDefinitions;

        public MainViewModel()
        {
            // Define initial columns
            ColumnDefinitions = new ObservableList<DataGridColumnDef>()
            {
                new DataGridColumnDef("Column 1", typeof(string)),
                new DataGridColumnDef("Column 2", typeof(int)),
            };

            // Create row models from initial column definitions
            RowDefinitions = new ObservableList<DataGridRowDef>();
            for(int i = 0; i < 100; ++i)
            {
                RowDefinitions.Add(new DataGridRowDef(ColumnDefinitions));
                // OR
                //RowDefinitions.Add(new DataGridRowDef(ColumnDefinitions, new object[] { "default", 10 }));
            }
        }
    }

这样在主视图模型上,您可以订阅ColumnDefinitions属性中的集合/属性更改事件,然后重新创建行集合。

现在我不是100%肯定的技巧会起作用,但不确定为什么它不会让你的DataGridRowDef类型继承自DynamicObject,这样你就可以欺骗成员及其值,比如这样:

public class DataGridRowDef : DynamicObject
    {
        private readonly object[] _columnData;
        private readonly IList<DataGridColumnDef> _columns;

        public static object GetDefault(Type type)
        {
            if (type.IsValueType)
            {
                return Activator.CreateInstance(type);
            }
            return null;
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return _columns.Select(c => c.Name).Union(base.GetDynamicMemberNames());
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var columnNames = _columns.Select(c => c.Name).ToList();
            if(columnNames.Contains(binder.Name))
            {
                var columnIndex = columnNames.IndexOf(binder.Name);
                result = _columnData[columnIndex];
                return true;
            }
            return base.TryGetMember(binder, out result);
        }

        public DataGridRowDef(IEnumerable<DataGridColumnDef> columns, object[] columnData = null)
        {
            _columns = columns.ToList() ?? throw new ArgumentNullException(nameof(columns));
            if (columnData == null)
            {
                _columnData = new object[_columns.Count()];
                for (int i = 0; i < _columns.Count(); ++i)
                {
                    _columnData[i] = GetDefault(_columns[i].DataType);
                }
            }
            else
            {
                _columnData = columnData;
            }
        }
    }

无论如何,如果这种解决方案对您来说似乎很平易近人,我可以尝试更多地解决这个问题。