Привязать отдельные компоненты одного свойства к двум отдельным элементам управления в WPF

Моя цель состоит в том, чтобы одно свойство Size обновлялось двумя разными текстовыми полями. В частности, каждый компонент подключен к одному текстовому полю, которое выделено в XAML ниже. В частности, строка, в которой говорится: {Binding Path = Dimensions.Width, ....}

private Size _dimension;
    public Size Dimensions
    {
        get { return _dimension; }
        set
        {
            _dimension = value;
            OnPropertyChanged("Dimensions");
        }
    }

Мой XAML выглядит следующим образом:

<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
   <Label Content="Width" />
   <TextBox Width="50" Text="{Binding Path=Dimensions.Width, 
            Converter={StaticResource myStringToDoubleConverter},
            UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
   <Label Content="Height" />
   <TextBox Width="50" Text="{Binding Path=Dimensions.Height,
            Converter={StaticResource myStringToDoubleConverter},
            UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</StackPanel>

У меня проблема в том, что функция set не вызывается, когда я изменяю текст в текстовом поле. Я предполагаю, что причина в том, что я предоставляю не объект Size, а в лучшем случае двойное значение, а в худшем - строку. У меня есть преобразователь для преобразования строки в двойную, но это не похоже на трюк. Я чувствую, что Multibinding дает мое решение, однако все примеры, которые я видел, объединяют две строки в третий элемент управления. Меня не интересует создание элемента управления с добавленными значениями, а скорее обновление компонентов моего «сложного» свойства.

Я понимаю, что могу создать два «вспомогательных» свойства, «Высоту» и «Ширина», а затем обновлять свойство «Размер» по частям, но кажется, что должно существовать более элегантное решение.

Почему мое текстовое поле не привязано должным образом к одному компоненту свойства Dimension и соответствующим образом не обновляет измерение?

Создание класса-оболочки, реализующего INotifyPropertyChanged, кажется излишним.

public class MySizeClass : Size, INotifyPropertyChanged

person mwjohnson    schedule 01.08.2013    source источник
comment
Я думаю, что ваш ответ является разумным решением. Ваш код в исходном сообщении определенно имеет некоторые проблемы: 1. Вы можете установить только ширину, а не высоту. 2. Как ваш преобразователь преобразует ширину в размер, не зная его высоты? Как вы сохраняете высоту от предыдущего объекта размера?   -  person Bill Zhang    schedule 01.08.2013
comment
@BillZhang Преобразователь на самом деле просто меняет String на Double, а не на размер. Чтобы преобразовать в размер, мне также нужно было бы каким-то образом получить параметр «Высота». Мне не ясно, что это на самом деле возможно. Если только мой преобразователь не сделал что-то особенное, обновив только один из двух компонентов. Что-то вроде: return (new Size(input, this.Height)) , но это тоже не особенно элегантно и, вероятно, хуже... Изменить: код в OP будет скорректирован, чтобы он был более однородным.   -  person mwjohnson    schedule 01.08.2013
comment
Это будет очевидно. Поскольку нет неявного преобразователя, преобразующего double в Size, ваше свойство Size определенно никогда не вызывается. Поскольку View Model — это специальная Модель для View, вам следует подумать, насколько удобно View может использовать свою Модель.   -  person Bill Zhang    schedule 01.08.2013


Ответы (2)


Если тип Size, который вы используете здесь, предоставлен WPF, то есть System.Windows.Size, проблема в том, что это не класс. Это тип значения (т.е. struct). И хотя он предоставляет сеттеры для своих Width и Height, он, вероятно, не должен этого делать, потому что они не делают того, что вы могли бы ожидать.

C# также не позволит вам изменить Dimensions.Width, если вы напишете следующее:

src.Dimensions.Width = 123;

где src — это экземпляр типа, который содержит те определения свойств, которые вы показали в своем «Решении привязки компонентов», вы получите эту ошибку:

Cannot modify the return value of 'WpfApplication1.Source.Dimensions' because it is not a variable

С# отказывается здесь из-за той же основной проблемы, которая мешает этому работать в WPF: это потому, что Size является типом значения. Любая попытка получить свойство Dimensions вернет полностью новую копию значения.

Конечно, этот код C# фактически укорочен для:

Size dimensions = src.Dimensions;
dimensions.Width = 123;

C# на самом деле позволит вам написать это, но если вы попробуете, вы поймете, почему это остановило вас от написания более короткой версии: src.Dimensions.Width по-прежнему будет возвращать старое значение. (Что делает этот код, так это берет копию src.Dimensions, сохраняет эту копию в локальной переменной с именем dimensions, а затем изменяет эту локальную переменную, оставляя оригинал без изменений.)

По сути, нет способа получить ссылку на экземпляр Size, возвращаемый вашим свойством Dimensions, — вы можете получить только копию значения. И единственный способ обновить его — написать на его место полностью новый Size. Вот почему вы не можете изменить Width или Height сами по себе.

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

(В принципе, Microsoft могла бы заставить WPF обнаружить это, прочитать свойство Dimensions, изменить копию полученного значения, а затем записать это измененное значение обратно в Dimensions. И хотя в данном случае это было бы удобно, также будет значительным изменением по сравнению с тем, как привязка данных обычно работает с обновлениями свойств. Я думаю, что, вероятно, лучше, чтобы они не пытались, потому что это может легко замаскировать другие проблемы.)

В C# вы можете писать в отдельные свойства, когда у вас есть локальная переменная или поле, содержащее значение Size, конечно, потому что переменная или поле обозначает конкретный экземпляр значения. Поля, локальные переменные, аргументы ref или out и элементы массива являются особыми, поскольку на самом деле можно работать со значениями, которые они содержат на месте. Но вы не можете сделать это при просмотре свойств. По той же причине, если вы определяете List<Size>, вы не можете написать myList[0].Width = 123;, даже если это нормально для массива.

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

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

person Ian Griffiths    schedule 01.08.2013
comment
Блестящий ответ, большое спасибо. Я обычно восхищаюсь опытом и знаниями сообщества stackoverflow. Вы упомянули, ... System.Windows.Size, ... не является классом. Это тип значения (т.е. структура). Будет ли работать этот подход, если я создам новый класс MySize с двумя переменными типа double, Height и Width, а затем определю свойство Dimensions, используя этот тип? то есть Public MySize Dimensions Таким образом, определяя мое свойство, используя класс, а не встроенную структуру. - person mwjohnson; 02.08.2013

Вот решение для привязки компонентов, которое работает совершенно нормально.

    private Double m_Width;
    public Double Width
    {
        get { return (Dimensions.Width); }
        set
        {
            m_Width = value;
            Dimensions= new Size(m_Width, m_Height);
        }
    }
    private Double m_Height;
    public Double Height
    {
        get { return (Dimensions.Height); }
        set
        {
            m_Height = value;
            Dimensions= new Size(m_Width, m_Height);
        }
    }
    private Size _dimension;
    public Size Dimensions
    {
        get { return _dimension; }
        set
        {
            _dimension = value;
            OnPropertyChanged("Dimensions");
        }
    }

Просто кажется, что привязка непосредственно к Dimensions.Width - это то, что будет поддерживаться WPF.

person mwjohnson    schedule 01.08.2013