Как привязать сетку данных wpf к столбцу, который содержит комбинацию поля со списком и окна теста через mvvm - PullRequest
0 голосов
/ 23 июня 2019

В моей таблице данных у меня есть столбец текстового поля и другой столбец, который должен содержать комбинацию поля со списком и текстового поля, которое должно быть установлено динамически.Например, я разрешаю пользователю устанавливать статус машины.Итак, State и Value - это заголовки каждого столбца, где Value может содержать comboBox или TextBox в зависимости от типа State.Где его тип может быть Boolean или enum.Если это enum, то отобразить поле со списком else textBox.

Я пытаюсь сделать это с помощью модели представления, и я не уверен, как установить DataGridview в xaml.Или это возможно в этом сценарии ...?

<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 будет списком элементов в комбинированном ящике, которые будут заполнены во время выполнения.

Итак, я мог бы сделать это с помощью файла xaml.cs и создать поле со списком в зависимости от того, является ли его enum или нет, но я хочу добиться этого через представление Model.И каждый comboBox будет иметь разные значения, которые заполняются динамически во время выполнения.Я борюсь здесь, поэтому, если кто-нибудь и укажет мне правильное направление, я буду очень признателен за это.

1 Ответ

0 голосов
/ 24 июня 2019

1.Организация модели данных параметров состояния

При рассмотрении желаемых пользовательских взаимодействий существуют различные категории параметров состояния в отношении того, как они должны быть представлены / отредактированы пользователем.В рамках вопроса мы можем выделить следующие категории:

  • Переключаемый параметр (bool)
  • Параметр выбора, где значение параметра равно единице.заданного набора (например, перечисления или любого другого типа данных)
  • И, если быть точным, текстовый параметр (string)


2.Реализация модели данных параметра состояния

Параметр состояния имеет имя / идентификатор состояния и значение.Значение может быть различного типа.По сути, это определение класса StateParameters в вопросе.

Однако, как станет более очевидным позже в моем ответе, различные типы / классы представляют различные категории параметров состояния какперечисленные выше будут полезны для подключения логики представления и взаимодействия в пользовательском интерфейсе.

Конечно, независимо от его категории, каждый параметр состояния должен быть представлен одним и тем же базовым типом.Очевидный выбор - сделать базовый тип параметров состояния абстрактным классом или интерфейсом.Здесь я выбрал интерфейс:

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 имеет установщик, это свойство должно устанавливаться только один разпоэтому уведомления об изменении свойства не должны быть необходимы для него. Технически вы можете изменить свойство в любое время; я просто решил использовать установщик, чтобы код в моем ответе был относительно коротким и простым.интерфейса INotifyPropertyChanged, что необходимо, поскольку пользовательский интерфейс будет манипулировать свойством Value через привязки.Также обратите внимание на явную реализацию интерфейса свойства интерфейса IStateParameter Value, которое будет "скрывать" его, если вы явно не приведете ссылку на объект параметра состояния как 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 выше, это свойство должно устанавливаться только один раз, и причина, по которой я дал ему установщик, заключается в том, чтобы код в моем ответе был относительно коротким и простым.)

Кроме ChoiceStateParameter класса, ни у одного другого класса нет никакого объявления в нем.Вы спросите, зачем нам BoolStateParameter / TextStateParameter , если бы мы могли напрямую использовать StateParameter / StateParameter ?Это хороший вопрос.Если бы нам не пришлось иметь дело с XAML, мы могли бы легко использовать StateParameter / StateParameter напрямую (при условии, что _StateParameter не был абстрактным классом).Тем не менее, попытка ссылаться на универсальные типы из разметки XAML является чем-то между довольно болезненным и совершенно невозможным.Таким образом, неуниверсальные классы параметров конкретного состояния BoolStateParameter , TextStateParameter и ChoiceStateParameter были определены.

О, и прежде чем мы забудем,поскольку мы объявили базовый тип параметра общего состояния в качестве интерфейса с именем IStateParameter, параметр типа свойства StateParametersList в viewmodel должен быть соответствующим образом скорректирован (и, конечно, и его вспомогательное поле):

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

После этого мы завершили часть на стороне кода C # и переходим к DataGrid.


3.UI / XAML

Поскольку различные категории параметров состояния требуют разных элементов взаимодействия (CheckBoxes, TextBoxes, ComboBoxes), мы попытаемся использовать DataTemplates, чтобы определить, как каждая из этих категорий параметров состояния должна быть представлена ​​внутриЯчейки DataGrid.

Теперь также станет понятно, почему мы предприняли попытку определить эти категории и объявили разные типы параметров состояния для каждой из них.Потому что DataTemplates могут быть связаны с определенным типом.И теперь мы собираемся определить эти DataTemplates для каждого типа BoolStateParameter, TextStateParameter и ChoiceStateParameter.

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: или заменить его пространством имен XML, которое сопоставлено с пространством имен C #, в котором вы объявляете классы параметров состояния. )

Следующий шаг - заставить DataGridTemplateColumn выбрать соответствующий DataTemplate в зависимости от фактического типа параметра состояния, с которым он имеет дело в данной ячейке столбца.Однако DataGridTemplateColumn не может выбрать DataTemplate из самого словаря ресурсов, а элемент управления DataGrid не делает этого от имени DataGridTemplateColumn .Итак, что теперь?

К счастью, в WPF есть элементы пользовательского интерфейса, которые представляют некоторое значение / объект, используя DataTemplate из словаря ресурсов, причем DataTemplate выбирается на основе типа значения / объекта.Одним из таких элементов пользовательского интерфейса является ContentPresenter, который мы будем использовать в 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"
    }
};

Это будет выглядеть так:

enter image description here

...