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

FCi*_*Cin 7 c# data-binding wpf 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>
Run Code Online (Sandbox Code Playgroud)

视图模型:

private DataTable _sampledData = new DataTable();

public DataTable SampledData
{
    get => _sampledData;
    set { _sampledData = value; NotifyOfPropertyChange(() => SampledData); }
}
Run Code Online (Sandbox Code Playgroud)

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

编辑:我一直在努力使用ListViewModel工作,但没有运气:

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 }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

视图

<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>
Run Code Online (Sandbox Code Playgroud)

public class MyRowViewModel : PropertyChangedBase
{
    public string Column1 { get; set; }
    public int Column2 { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

EDIT2:

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

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

  1. 让用户看看数据的外观
  2. 验证是否可以实际解析列(例如68.35,无法解析为DateTime)

另一件事是命名每一列.有人可能会为每个名为...的列加载csv C1,C2但是他们想要分配有意义的名称,例如Temperature,Average.这当然也必须在以后解析,因为两列不能具有相同的名称,但是一旦我有一个可绑定的我就可以解决这个问题DataGrid.

Sam*_*Dev 3

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

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

public class DataGridModel
{
    public string FirstProperty { get; set; }   
    public string SecondProperty { get; set; }   
}
Run Code Online (Sandbox Code Playgroud)

现在,假设您有一个MainWindow(带有 ViewModel 或DataContext后面的代码集),DataGrid其中包含 a ,让我们将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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

其次,现在有趣的部分是列结构。让我们为您的DataGrid列定义一个模型,该模型将保存设置DataGrid列所需的所有属性,包括:

- DataTypesCollection:保存组合框项源的集合。- HeaderPropertyCollection:元组的集合,每个元组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));
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

        private DataGridColumnsModel _dataGridColumnsModel=new DataGridColumnsModel();
    public DataGridColumnsModel DataGridColumnsModel
    {
        get { return _dataGridColumnsModel; }
        set
        {
            if (Equals(value, _dataGridColumnsModel)) return;
            _dataGridColumnsModel = value;
            OnPropertyChanged();
        }
    }
Run Code Online (Sandbox Code Playgroud)

第三,获取列TextBox的值。为此,我们将使用 aMultiBinding和 a MultiValueConverter,我们将传递给的第一个属性MultiBinding是我们定义的元组集合(列名称和数据类型):,HeaderPropertyCollection第二个属性是 w' 的当前列索引将DisplayIndex使用祖先绑定来检索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>
Run Code Online (Sandbox Code Playgroud)

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

第四DataGrid,最后一部分是ItemSource当选择更改时更新Combobox,因为您可以使用System.Windows.Interactivity命名空间中定义的交互工具(它是Expression.Blend.Sdk的一部分,使用 NuGet 来安装它:安装包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>
Run Code Online (Sandbox Code Playgroud)

每次selectionChanged事件发生时,更新您的DataGrid,应将其添加到主窗口的 ViewModel 中ItemSourceUpdateItemSourceCommand

 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
                       }));
        }
    }
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

并查看模型/代码隐藏:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

在此输入图像描述

更新

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