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

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


我正在尝试通过视图模型执行此操作,但我不确定如何在 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>

视图模型:


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 将是将在运行时填充的组合框中的项目列表。


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


一只萌萌小番薯
浏览 187回答 1
1回答

尚方宝剑之说

1.组织状态参数数据模型在查看所需的用户交互时,存在不同类别的状态参数,这些状态参数是关于它们如何呈现给用户/由用户编辑的。在问题的范围内,我们可以确定以下类别:可切换参数 (&nbsp;bool)一个选择参数,其中参数的值是给定集合中的一个(实际上就像枚举或任何其他数据类型)为了更好地衡量,文本参数 (&nbsp;string)2.实现状态参数数据模型状态参数具有状态名称/标识符和值。该值可以是不同的类型。这本质上是问题中StateParameters类的定义。但是,正如我稍后的回答中将变得更加明显的那样,具有代表上面列出的不同类别状态参数的不同类型/类将有利于在 UI 中连接表示和交互逻辑。当然,无论其类别如何,每个状态参数都应该由相同的基类型表示。显而易见的选择是使状态参数基类型成为抽象类或接口。在这里,我选择了一个界面:public interface IStateParameter{&nbsp; &nbsp; string State { get; }&nbsp; &nbsp; object Value { get; set; }}我现在没有根据上面列出的类别直接创建具体的状态参数类,而是创建了一个额外的抽象基类。这个类将是通用的,使得以类型安全的方式处理状态参数更容易一些:public abstract class StateParameter<T> : IStateParameter, INotifyPropertyChanged{&nbsp; &nbsp; public event PropertyChangedEventHandler PropertyChanged;&nbsp; &nbsp; public string State { get; set; }&nbsp; &nbsp; public T Value&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; get { return _v; }&nbsp; &nbsp; &nbsp; &nbsp; set&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if ((_v as IEquatable<T>)?.Equals(value) == true || ReferenceEquals(_v, value) || _v?.Equals(value) == true)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _v = value;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; private T _v;&nbsp; &nbsp; object IStateParameter.Value&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; get { return this.Value; }&nbsp; &nbsp; &nbsp; &nbsp; set { this.Value = (T) value; }&nbsp; &nbsp; }}(虽然该State属性有一个设置器,但该属性只能设置一次,因此不需要属性更改通知。从技术上讲,您可以随时更改该属性;我只是选择在这里使用一个设置器来保存代码我的回答相对简短。)请注意接口的实现INotifyPropertyChanged,这是必要的,因为 UI 将Value通过绑定操作属性。还要注意接口属性的显式接口实现,它将“隐藏”它,除非您将状态参数对象引用显式转换为。这是有意为之的,因为它提供了自己的类型属性,该类型与StateParameter<T>的泛型类型参数相匹配。此外,不幸的是,setter 中的相等比较有点尴尬,因为泛型类型参数IStateParameterValueIStateParameterStateParameter<T>ValueValueT这里是完全不受约束的,可以是某种值类型或某种引用类型。因此,相等比较必须涵盖所有可能发生的情况。因此,完成这些准备工作后,是时候将我们的注意力放回到实际问题上了。我们现在将根据答案开头概述的类别来实现具体的状态参数类型:&nbsp; &nbsp; public class BoolStateParameter : StateParameter<bool>&nbsp; &nbsp; { }&nbsp; &nbsp; public class TextStateParameter : StateParameter<string>&nbsp; &nbsp; { }&nbsp; &nbsp; public class ChoiceStateParameter : StateParameter<object>&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; public Array Choices { get; set; }&nbsp; &nbsp; }ChoiceStateParameter类声明了一个附加属性,该属性用于保存数组,其中包含可供特定状态参数选择的可能值。(就像上面的StateParameter<T>.State一样,这个属性只能设置一次,我在这里给它一个 setter 的原因是为了让我的答案中的代码相对简短。)除了ChoiceStateParameter类之外,其他类中没有任何声明。你会问,如果我们可以直接使用StateParameter<bool>/StateParameter<string>为什么还需要BoolStateParameter/TextStateParameter ?这是个好问题。如果我们不必处理 XAML,我们可以轻松地直接使用StateParameter<bool>/StateParameter<string>(假设 _StateParameter<T> 不是抽象类)。但是,尝试从 XAML 标记中引用泛型类型是一件介于非常痛苦和完全不可能之间的事情。因此,非通用具体状态参数类BoolStateParameter、TextStateParameter和ChoiceStateParameter已被定义。哦,在我们忘记之前,因为我们已经将公共状态参数基类型声明为名为 的接口IStateParameter,所以必须相应地调整视图模型中属性的类型参数StateParametersList(当然还有它的支持字段):public ObservableCollection<IStateParameter> StateParametersList { get ..... set ..... }完成后,我们就完成了 C# 代码方面的部分,我们将继续处理 DataGrid。3. 用户界面/XAML由于不同的状态参数类别需要不同的交互元素(CheckBoxes、TextBoxes、ComboBoxes),我们将尝试利用 DataTemplates 来定义每个状态参数类别应如何在 DataGrid 单元格内表示。现在,我们努力定义这些类别并为每个类别声明不同的状态参数类型的原因也将变得显而易见。因为 DataTemplates 可以与特定类型相关联。我们现在将为每个BoolStateParameter,TextStateParameter和ChoiceStateParameter类型定义那些数据模板。DataTemplates 将放置在 DataGrid 中,作为 DataGrid 资源字典的一部分:<DataGrid Name="dataGridView" ItemsSource="{Binding Path=StateParametersList}" ... >&nbsp; &nbsp; <DataGrid.Resources>&nbsp; &nbsp; &nbsp; &nbsp; <DataTemplate DataType="{x:Type local:BoolStateParameter}">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <CheckBox IsChecked="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />&nbsp; &nbsp; &nbsp; &nbsp; </DataTemplate>&nbsp; &nbsp; &nbsp; &nbsp; <DataTemplate DataType="{x:Type local:TextStateParameter}">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />&nbsp; &nbsp; &nbsp; &nbsp; </DataTemplate>&nbsp; &nbsp; &nbsp; &nbsp; <DataTemplate DataType="{x:Type local:ChoiceStateParameter}">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <ComboBox ItemsSource="{Binding Choices}" SelectedItem="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />&nbsp; &nbsp; &nbsp; &nbsp; </DataTemplate>&nbsp; &nbsp; </DataGrid.Resources>(注意:您可能需要调整local:我在此处使用的名称空间,或将其与映射到声明状态参数类的 C# 名称空间的 XML 名称空间交换。)下一步是让DataGridTemplateColumn根据它在给定列单元格中处理的状态参数的实际类型选择适当的 DataTemplate。但是,DataGridTemplateColumn不能从资源字典本身中选择 DataTemplate,DataGrid控件也不能代表DataGridTemplateColumn这样做。所以现在怎么办?幸运的是,WPF 中有一些 UI 元素使用资源字典中的 DataTemplate 呈现一些值/对象,并根据值/对象的类型选择 DataTemplate。一个这样的 UI 元素是ContentPresenter,我们将在DataGridTemplateColumn中使用它:&nbsp; &nbsp; <DataGrid.Columns>&nbsp; &nbsp; &nbsp; &nbsp; <DataGridTextColumn Binding="{Binding State}"/>&nbsp; &nbsp; &nbsp; &nbsp; <DataGridTemplateColumn Width="*">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <DataGridTemplateColumn.CellTemplate>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <DataTemplate>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <ContentPresenter Content="{Binding}" />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </DataTemplate>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </DataGridTemplateColumn.CellTemplate>&nbsp; &nbsp; &nbsp; &nbsp; </DataGridTemplateColumn>&nbsp; &nbsp; </DataGrid.Columns></DataGrid>就是这样。随着底层数据模型(状态参数类)的小幅扩展,XAML 问题就消失了(我希望如此)。4. 演示数据集用于演示实际代码的快速测试数据集(使用随机选择的枚举类型作为示例):StateParametersList = new ObservableCollection<IStateParameter>{&nbsp; &nbsp; new BoolStateParameter&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; State = "Bool1",&nbsp; &nbsp; &nbsp; &nbsp; Value = false&nbsp; &nbsp; },&nbsp; &nbsp; new ChoiceStateParameter&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; State = "Enum FileShare",&nbsp; &nbsp; &nbsp; &nbsp; Value = System.IO.FileShare.ReadWrite,&nbsp; &nbsp; &nbsp; &nbsp; Choices = Enum.GetValues(typeof(System.IO.FileShare))&nbsp; &nbsp; },&nbsp; &nbsp; new TextStateParameter&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; State = "Text1",&nbsp; &nbsp; &nbsp; &nbsp; Value = "Hello"&nbsp; &nbsp; },&nbsp; &nbsp; new BoolStateParameter&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; State = "Bool2",&nbsp; &nbsp; &nbsp; &nbsp; Value = true&nbsp; &nbsp; },&nbsp; &nbsp; new ChoiceStateParameter&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; State = "Enum ConsoleKey",&nbsp; &nbsp; &nbsp; &nbsp; Value = System.ConsoleKey.Backspace,&nbsp; &nbsp; &nbsp; &nbsp; Choices = Enum.GetValues(typeof(System.ConsoleKey))&nbsp; &nbsp; },&nbsp; &nbsp; new TextStateParameter&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; State = "Text2",&nbsp; &nbsp; &nbsp; &nbsp; Value = "World"&nbsp; &nbsp; }};它看起来像这样:
打开App,查看更多内容
随时随地看视频慕课网APP