【问题标题】:Bind custom header controls in DataGrid在 DataGrid 中绑定自定义标题控件
【发布时间】:2018-02-27 18:09:50
【问题描述】:

我有一个自定义列标题,其中每个列的标题都有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。

编辑: 我一直在尝试使用 ViewModels 的List 来完成这项工作,但没有运气:

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 的“从文件加载”向导相同。

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

  1. 让用户看到数据的样子
  2. 验证列是否可以实际解析(例如,68.35 不能 被解析为DateTime)

另一件事是命名每一列。有人可能会在 csv 中加载名为 C1C2... 的每一列,但他们希望分配有意义的名称,例如 TemperatureAverage。当然,这也必须在以后进行解析,因为两列不能具有相同的名称,但是一旦我有一个可绑定的DataGrid,我就可以处理这个问题。

【问题讨论】:

  • 将您的数据表转换为视图模型 - 您可以创建一个 IEnumerable&lt;ViewModelType&gt;(如果需要,IList/List),其中包含每一行作为视图模型实例 - 将其绑定到您的数据网格并绑定组合框列 - 然后您可以将此视图模型重构回数据表中。
  • @Charleh 我不确定我是否理解。我如何将列的标题绑定到这个?为什么我需要行在单独的视图模型中?我只需要绑定标题而不是行。
  • 您永远不需要屈服于背后代码中的解决方案 :) 这可能就是 charleh 的做法。我会亲自使用IEnumerable&lt;ModelType&gt; 并将ColumnType 对象添加到模型并绑定到ColumnType
  • @Charleh 我很抱歉,但我已经玩了几个小时了,我得到的最远的结果是将每一列显示为一个属性,这不是我想要的,因为我不不知道我将拥有多少列,所以我无法对它们进行硬编码。我用我尝试过的方法更新了我的问题。
  • 你的列可以是一个列表,你也可以绑定到一个列表,这样它就可以工作了

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


【解决方案1】:

让我们把你的问题分解成几个部分,分别解决每个部分。

首先DataGriditemsource,为了让事情更简单,假设我们的DataGrid只有两列,第1列第 2 列DataGrid 项目的基本模型应如下所示:

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

现在,假设您有一个带有 DataGridMainWindow(带有 ViewModel 或 DataContext 设置为代码后面),让我们将 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 的列定义一个模型,该模型将包含设置DataGrid 列所需的所有属性,包括:

-DataTypesCollection: 包含组合框项目源的集合。 -HeaderPropertyCollection:一个Tuples的集合,每个Tuple代表一个列名和一个数据类型,数据类型基本上就是被选中的项列的combobox

 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));
        }
    }

现在在 MainWindow 的视图模型(或代码隐藏)中定义 DataGridColumnsModel 的实例,我们将使用它来保存 DataGrid 结构:

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

第三,获取列的TextBox的值。为此,我们将使用MultiBindingMultiValueConverter,将传递给MultiBinding 的第一个属性是我们定义的元组集合(列的名称和数据类型):HeaderPropertyCollection ,第二个是当前列索引,它将使用与DataGridColumnHeader 的祖先绑定从DisplayIndex 检索:

<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();
    }
}

第四,最后一部分是在Combobox的选择发生变化时更新DataGridItemSource,为此您可以使用中定义的交互工具System.Windows.Interactivity 命名空间(它是 Expression.Blend.Sdk 的一部分,使用 NuGet 安装它:Install-Package Expression.Blend.Sdk) :

<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 事件时,更新DataGrid 中的ItemSource 应添加到主窗口的ViewModel 中:

 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));
    }
}

结果:

更新

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

【讨论】:

  • 谢谢,但和上一个答案一样,这不处理动态列数。我需要它来显示 csv 文件以供以后操作。每个 csv 包含不同数量的列,因此我无法对其进行硬编码。
  • @FCin 我不明白为什么这不适用于autogeneratedcolumns=True
  • 好的,我会尝试使用任意数量的列,然后将结果回复给您。
  • 确实,设置AutoGenerateColumns="True" 并在运行时动态生成列就像上面代码的魅力一样。
  • 非常感谢。这引导我朝着正确的方向前进。
【解决方案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;
            }
        }
    }

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

【讨论】:

    【解决方案3】:

    试试这个。

    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 评论
    “这很好,但我用它来加载 csv 文件,列数会根据 csv 文件而变化。在这里我必须对每一列进行硬核,但有些文件可能有 1 列,有些可能有 30 列”。

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

    示例 data1.csv:
    ColumnHeader1,ColumnHeader2
    Int32,字符串
    1,"A"
    2,"B"
    3,"C"

    我尝试使用 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;
            }
        }
    }
    

    【讨论】:

    • 这很好,但我用它来加载 csv 文件,并且列数根据 csv 文件而变化。在这里,我必须对每一列进行硬核,但有些文件可能有 1 列,有些可能有 30 列。
    猜你喜欢
    • 2011-01-12
    • 2012-05-24
    • 1970-01-01
    • 2011-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-17
    相关资源
    最近更新 更多