Множественные привязки

63

В WPF в одном элементе управления разрешается использовать сколько угодно привязок. Можно модифицировать пример из предыдущей статьи, добавив к элементу TextBlock еще несколько привязок:

<StackPanel>
            <Slider Name="sld" Minimum="1" Maximum="72" Margin="10"
                TickFrequency="5" TickPlacement="BottomRight" Value="12"></Slider>
            <TextBox Name="txt" Margin="10" MinHeight="26" VerticalContentAlignment="Center"></TextBox>
            <ListBox Margin="10" Name="lst">
                <ListBoxItem Tag="DarkBlue">
                    <Label>Dark Blue</Label>
                </ListBoxItem>
                <ListBoxItem Tag="Blue">
                    <Label>Blue</Label>
                </ListBoxItem>
                <ListBoxItem Tag="LightBlue">
                    <Label>Light Blue</Label>
                </ListBoxItem>
            </ListBox>
            <!-- Используем несколько привязок -->
            <TextBlock Name="txb" Margin="10,5,0,10" 
                       FontSize="{Binding ElementName=sld, Path=Value, Mode=TwoWay}"
                       Text="{Binding ElementName=txt, Path=Text}"
                       Foreground="{Binding ElementName=lst, Path=SelectedItem.Tag, Mode=OneWay}"></TextBlock>
</StackPanel>
Использование нескольких привязок

Допускается также реализовать привязку данных. Например, можно создать выражение привязки для свойства TextBox.Text, связывающее его со свойством TextBlock.FontSize, которое содержит выражение привязки, связывающее со свойством Slider.Value. В этом случае, когда пользователь перетаскивает ползунок в новую позицию, значение передается от Slider в TextBlock и затем из TextBlock в TextBox.

Хотя все работает прозрачно, более ясный подход состоит в том, чтобы привязать элементы как можно ближе к данным, которые они используют. В описанном здесь примере необходимо предусмотреть привязку и TextBlock, и TextBox непосредственно к свойству Slider.Value.

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

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

Чтобы понять, как это работает, рассмотрим вариацию примера элемента — панели с ползунком, который включает текстовое поле, куда можно поместить точное значение размера шрифта. В этом примере свойство TextBlock.FontSize может быть установлено двумя путями: перетаскиванием ползунка или вводом в текстовом поле размера шрифта. Все элементы управления синхронизированы так, что если вводится новое число в текстовом поле, размер шрифта текста примера изменяется и ползунок перемещается в соответствующую позицию.

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

Хотя добавить другую привязку к свойству FontSize нельзя, можно привязать новый элемент управления TextBox к свойству TextBlock.FontSize. Ниже показана необходимая для этого разметка:

<TextBox Name="txt" Margin="10" MinHeight="26" VerticalContentAlignment="Center"
        Text="{Binding ElementName=txb, Path=FontSize, Mode=TwoWay}"></TextBox>
Привязка двух свойств

Теперь при каждом изменении свойства TextBlock.FontSize текущее значение будет вставляться в текстовое поле. Более того, значение в текстовом поле можно редактировать, применяя указанный размер шрифта. Обратите внимание, что для того, чтобы пример работал, свойство TextBox.Text должно использовать двунаправленную привязку, передающую данные в обоих направлениях. В противном случае текстовое поле сможет отображать значение TextBlock.FontSize, но не позволит изменять его.

С этим примером связано несколько нюансов:

Интересно, что показанное здесь решение — не единственный способ привязки текстового поля. Столь же разумно конфигурировать текстовое поле таким образом, чтобы оно изменяло значение свойства Slider.Value вместо свойства TextBlock.FontSize:

<TextBox Name="txt" Margin="10" MinHeight="26" VerticalContentAlignment="Center"
           Text="{Binding ElementName=sld, Path=Value, Mode=TwoWay}"></TextBox>

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

И, наконец, можно поменять местами роли ползунка и текстового поля, чтобы ползунок привязывался к текстовому полю.

В случае привязки Slider.Value текстовое поле ведет себя несколько иначе, чем в предыдущих двух примерах. Любые изменения, которые вносятся в текстовое поле, применяются немедленно, вместо того, чтобы ожидать момента утери фокуса.

Как видно из примера, двунаправленные привязки обеспечивает значительную гибкость. Их можно использовать для применения изменений от источника к цели и от цели к источнику. Допускается их применение в комбинации, что позволяет создать неожиданно сложные окна без какого-либо кода.

Обычно решение относительно того, куда применять выражение привязки, диктуется логикой модели кодирования. В предыдущем примере, возможно, было бы больше смысла поместить привязку в свойство TextBox.Text вместо свойства Slider.Value, потому что текстовое поле — это необязательное дополнение к вполне готовому примеру, а не основной ингредиент, на который полагается ползунок.

Также имело бы больше смысла привязать текстовое поле непосредственно к свойству TextBlock.FontSize вместо свойства Slider.Value. (Концептуально вы заинтересованы в том, чтобы видеть текущий размер шрифта, и ползунок — только один из способов его установки. Даже несмотря на то, что положение ползунка совпадает с размером шрифта, это — необязательная дополнительная деталь, если вы пытаетесь написать максимально ясную разметку.) Конечно, эти решения субъективны и определяются стилем кодирования. Наиболее важный урок состоит в том, что три подхода могут обеспечить одинаковое поведение.

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

Пройди тесты
Лучший чат для C# программистов