如何通过WMVVM将WPF数据网格绑定到包含组合框和测试框组合的列

时间:2019-06-22 21:00:43

标签: c# wpf mvvm datagridviewcolumn

在我的datagrid中,我有一个textbox列和另一个应包含组合框和文本框组合的列,应将其动态设置。例如,我让用户设置计算机的状态。因此,State和Value是每一列的标题,其中Value可以根据State的类型包含comboBox或TextBox。其类型可以是布尔值或枚举。如果是枚举,则显示组合框,否则显示textBox。

我正在尝试通过视图模型执行此操作,但不确定如何在xaml中设置DataGridview。还是在这种情况下有可能...?

<DataGrid Name="dataGridView" ItemsSource="{Binding Path=StateParametersList}" CanUserAddRows="False" 
                      IsReadOnly="True" >

                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding State}"/>
                    <DataGridTemplateColumn>
                        <DataGridTemplateColumn.CellEditingTemplate>
                            <DataTemplate>
                                <ComboBox ItemsSource="{Binding ValueCell}" SelectedItem="{Binding Value}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellEditingTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>

            </DataGrid>

viewModel:

private ObservableCollection<StateParameters> StateParametersList =
        new ObservableCollection<StateParameters>();

    public ObservableCollection<StateParameters> StateParametersList
    {
        get { return StateParametersList; }
        set
        {
            StateParametersList = value;
            NotifyPropertyChanged(nameof(StateParametersList));
        }
    }
[Serializable]
public class StateParameters
{
    public string State { get; set; }
    public object Value { get; set; }
}

List<string> ValueCell = new List<string>();

其中ValueCell是comboBox中将在运行时填充的项目列表。

因此,我可以通过xaml.cs文件完成此操作,并根据其枚举与否创建组合框,但我想通过视图模型来实现。并且,每个comboBox将具有不同的值,这些值会在运行时动态填充。我在这里苦苦挣扎,因此,如果有人能指出正确的方向,我将不胜感激。

1 个答案:

答案 0 :(得分:0)

1。组织状态参数数据模型

当查看所需的用户交互时,关于如何向用户呈现/编辑状态参数,存在不同类别的状态参数。在问题范围内,我们可以确定以下类别:

  • 可切换参数(bool
  • 选择参数,其中参数的值是给定集合中的一个(实际上是像枚举或任何其他数据类型)
  • 为了很好,请使用文本参数(string


2。实现状态参数数据模型

状态参数具有状态名称/标识符和值。该值可以是不同的类型。这实质上是问题中 StateParameters 类的定义。

但是,如稍后在我的回答中将变得更加明显的那样,具有不同类型/类来代表状态参数的不同类别将有助于在UI中连接表示和交互逻辑。

当然,无论其类别如何,每个状态参数都应由相同的基本类型表示。显而易见的选择是使状态参数基类型成为抽象类或接口。在这里,我选择了一个接口:

public interface IStateParameter
{
    string State { get; }
    object Value { get; set; }
}

我现在不再根据上面列出的类别直接创建具体的状态参数类,而是创建了一个附加的抽象基类。此类将是通用的,从而使以类型安全的方式处理状态参数更加容易:

public abstract class StateParameter<T> : IStateParameter, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string State { get; set; }

    public T Value
    {
        get { return _v; }
        set
        {
            if ((_v as IEquatable<T>)?.Equals(value) == true || ReferenceEquals(_v, value) || _v?.Equals(value) == true)
                return;

            _v = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
        }
    }

    private T _v;

    object IStateParameter.Value
    {
        get { return this.Value; }
        set { this.Value = (T) value; }
    }
}

((虽然State属性有一个setter,但是该属性只能设置一次,因此属性更改通知不是必需的。从技术上讲,您可以随时更改该属性;我只是选择使用设置器,以使我的答案中的代码相对简短。)

请注意INotifyPropertyChanged接口的实现,这是必需的,因为UI将通过绑定来操纵Value属性。还要注意IStateParameter接口属性Value的{​​{3}},除非您将状态参数对象引用显式转换为IStateParameter,否则它将“隐藏”它。这是有意的,因为StateParameter<T>提供了自己的Value属性,该属性的类型与 StateParameter 的泛型类型参数匹配。另外,不幸的是,Value设置器中的相等比较有些笨拙,因为此处的泛型类型参数T完全不受约束,可以是某些值类型或某些引用类型。因此,平等比较必须涵盖所有可能的情况。

因此,在完成这些准备工作之后,就该将我们的重点转向实际问题了。现在,我们将根据答案开头概述的类别来实现具体的状态参数类型:

    public class BoolStateParameter : StateParameter<bool>
    { }
    public class TextStateParameter : StateParameter<string>
    { }
    public class ChoiceStateParameter : StateParameter<object>
    {
        public Array Choices { get; set; }
    }

ChoiceStateParameter 类声明一个附加属性,该属性用于保存具有特定状态参数的可能值的数组。 (就像上面的 StateParameter .State 一样,该属性只能设置一次,而我在这里给它提供一个setter的原因是使答案中的代码相对简短。 )

除了 ChoiceStateParameter 类,其他任何类中都没有任何声明。您问为什么如果可以直接使用 StateParameter / StateParameter ,我们为什么需要 BoolStateParameter / TextStateParameter ?这是个好问题。如果我们不必处理XAML,则可以轻松地直接使用 StateParameter / StateParameter (假设_StateParameter 不是抽象类)。但是,尝试从XAML标记中引用泛型类型是非常痛苦的,完全是不可能的。因此,已经定义了非通用的具体状态参数类 BoolStateParameter TextStateParameter ChoiceStateParameter

哦,在忘记之前,由于我们已经将公共状态参数基本类型声明为名为IStateParameter的接口,因此必须相应地调整viewmodel中StateParametersList属性的type参数(及其支持领域,当然):

public ObservableCollection<IStateParameter> StateParametersList { get ..... set ..... }

完成此操作后,我们已经在C#代码端完成了该部分,然后转到DataGrid。


3。 UI / XAML

由于不同的状态参数类别需要不同的交互元素(CheckBoxes,TextBoxes,ComboBoxes),因此我们将尝试利用DataTemplates定义应如何在DataGrid单元格内表示每个状态参数类别。

现在,我们为什么要努力定义这些类别并为每个类别声明不同的状态参数类型,这也将变得显而易见。因为DataTemplates可以与特定类型相关联。现在,我们将为每种BoolStateParameterTextStateParameterChoiceStateParameter类型定义那些DataTemplates。

DataTemplates将放置在DataGrid中,作为DataGrid资源字典的一部分:

<DataGrid Name="dataGridView" ItemsSource="{Binding Path=StateParametersList}" ... >

    <DataGrid.Resources>
        <DataTemplate DataType="{x:Type local:BoolStateParameter}">
            <CheckBox IsChecked="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:TextStateParameter}">
            <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:ChoiceStateParameter}">
            <ComboBox ItemsSource="{Binding Choices}" SelectedItem="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>
    </DataGrid.Resources>

注意:您可能需要调整我在这里使用的local:命名空间,或与映射到您在其中声明状态参数类的C#命名空间的XML命名空间进行交换。

下一步是根据给定列单元格中要处理的状态参数的实际类型,使 DataGridTemplateColumn 选择适当的DataTemplate。但是, DataGridTemplateColumn 不能从资源本身中选择一个DataTemplate, DataGrid 控件也不能代表 DataGridTemplateColumn 进行操作。那么,现在怎么办?

幸运的是,WPF中有UI元素使用资源字典中的DataTemplate呈现一些值/对象,并根据值/对象的类型选择DataTemplate。其中一个UI元素是explicit interface implementation,我们将在 DataGridTemplateColumn 中使用它:

    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding State}"/>

        <DataGridTemplateColumn Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ContentPresenter Content="{Binding}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>

</DataGrid>

就是这样。随着基础数据模型(状态参数类)的少量扩展,XAML问题就消失了(或者我希望如此)。


4。演示数据集

一个快速测试数据集,以演示实际的代码(以随机选取的枚举类型为例):

StateParametersList = new ObservableCollection<IStateParameter>
{
    new BoolStateParameter
    {
        State = "Bool1",
        Value = false
    },
    new ChoiceStateParameter
    {
        State = "Enum FileShare",
        Value = System.IO.FileShare.ReadWrite,
        Choices = Enum.GetValues(typeof(System.IO.FileShare))
    },
    new TextStateParameter
    {
        State = "Text1",
        Value = "Hello"
    },
    new BoolStateParameter
    {
        State = "Bool2",
        Value = true
    },
    new ChoiceStateParameter
    {
        State = "Enum ConsoleKey",
        Value = System.ConsoleKey.Backspace,
        Choices = Enum.GetValues(typeof(System.ConsoleKey))
    },
    new TextStateParameter
    {
        State = "Text2",
        Value = "World"
    }
};

它看起来像这样:

ContentPresenter