Как правильно связать значение, отображая понятное имя из списка предустановок, также определенных для этого элемента?

Получил жесткий. Рассмотрим ViewModel, который состоит из списка объектов, где каждый объект определяет значение int, а некоторые из этих объектов также определяют словарь предустановок для целочисленных значений, снабженных "дружественной" строкой, представляющей это значение в пользовательском интерфейсе.

Вот пример...

List<SomeItem> AllItems;

public class SomeItem : INotifyPropertyChanged
{
    public SomeItem(int initialValue, Dictionary<int,string> presets)
    {
        this.CurrentValue = initialValue;
        this.Presets = presets;
    }
    public int CurrentValue{ get; set; } // Shortened for readability. Assume this property supports INPC
    public Dictionary<int,string> Presets{ get; private set; }
}

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

Нашей первой попыткой было использовать TextBox и ComboBox, изменяя их видимость в зависимости от того, были ли пресеты или нет, как это...

<ComboBox ItemsSource="{Binding Presets}"
    DisplayMemberPath="Key"
    SelectedValuePath="Value"
    SelectedValue="{Binding CurrentValue, Mode=TwoWay}"
    Visibility={Binding HasPresets, Converter=...}">

<TextBox Text="{Binding CurrentValue}"
    Visibility={Binding HasPresets, Converter...}" /> // Assume the inverse of the combo

... но когда мы используем это в DataTemplate списка, который поддерживает виртуализацию, комбо иногда отображает пробелы. Я полагаю, это потому, что когда элемент используется повторно и DataContext изменяется, SelectedValue обновляется до ItemsSource, что означает, что у него потенциально нет предустановленного значения для сопоставления, поэтому предлагаемое значение SelectedValue будет выброшено элементом управления, затем ItemsSource обновляется, но нет выбранного значения, поэтому оно показывает пробел.

Моя следующая мысль (и то, что мы предпочли в любом случае) состояла в том, чтобы использовать только TextBox, который отображал имя предустановки, но на самом деле был привязан к значению, а затем использовал конвертер для работы с магией и позволял пользователю вводить либо понятное имя, либо фактическое значение., Если пользователь вводил что-то, что не было допустимым значением или предустановкой, мы просто выдавали ошибку. Если бы не было предустановок, это просто действовало бы как передача значения.

Однако там я не уверен, как передать пресеты в конвертер. Вы не можете установить привязку для ConverterParameter, чтобы передавать их таким образом, и если вы используете мульти-привязку, то я не уверен, как структурировать вызов 'ConvertBack', так как там мне тоже нужно, чтобы они передавались, а не отправили обратно.

Я думаю, что правильный способ - реализовать UiValue в ViewModel, которую мы просто привязали бы к этому...

<TextBox Text="{Binding UiValue}" />

... затем переместите код, который был бы в конвертере, в реализацию getter / setter этого свойства или просто передайте значение Value, если нет предустановок. Тем не менее, кажется, что слишком много логики происходит в ViewModel, где он должен быть в View (ala конвертер или аналогичный). Опять же, возможно, в этом и заключается точка зрения ViewModel. Я не знаю. Мысли приветствуются.

2 ответа

Лично я хотел бы добавить "код конвертера" в свойство, как вы предложили... Я не вижу проблем с наличием там кода. На самом деле, это, вероятно, лучше, чем иметь его в Converter потому что тогда вы можете легко проверить это тоже.

Извините, это не очень хороший ответ, но я чувствовал, что ваш вопрос заслуживает хотя бы одного.

Мне нравится ваш вопрос, потому что он иллюстрирует способ мышления, стоящий за существованием ViewModel в WPF. Иногда они кажутся неизбежными.

Конвертеры спроектированы так, что они не сохраняют состояния, поэтому трудно передавать переменные контекста, такие как presets, ViewModel это слой, ответственность за который должен подготовить Model в обязательных целях. Роль "модели" заключается в управлении логикой. Таким образом, ViewModel может подробно обработать поведение (логику) View, Это именно то, что вы хотите. Большую часть времени я вообще не нуждаюсь в конвертерах.

Иногда кажется более естественным, что логика представления должна быть в View, но потом ViewModel кажется лишним. Однако, когда эта логика находится в ViewModel обычно проще выполнить автоматическое тестирование. Я бы не боялся помещать подобные вещи в ViewModel совсем. Часто это самый простой (и правильный) способ.

Есть UiValue недвижимость в ViewModel и обработать преобразование там:

public string UiValue{ get{/*...*/} set{/*...*/} }

Перефразируя, в WPF нет чистого способа заменить свойство, к которому вы привязываете. Например, если вы хотели иметь

<TextBox Text="{Binding IntValue}" />

изменить в какой-то момент:

<TextBox Text="{Binding PresetValue}" />

ты в ловушке Это не так, как все делается. Лучше иметь постоянную привязку вроде

<TextBox Text="{Binding UiValue}" />

и иметь дело с логикой UiValue имущество.

Другой возможный подход (вместо игры с видимостью ComboBox а также TextBox) должен иметь DataTemplateSelector, который будет решать, следует ли создавать ComboBox или TextBox для SomeItem, Если presets являются нулевыми или пустыми выберите на основе TextBox DataTemplateВ противном случае возьмите ComboBox. Если я не ошибаюсь, вам придется расследовать FrameworkElement.DataContext свойство из селектора, чтобы найти контекст (presets).

Учитывая ваши сомнения по поводу ConvertBack метод, чаще всего value или же Binding.DoNothing возвращается, если вам не нужно преобразование ни в одном из направлений.

Другие вопросы по тегам