Множественные привязки
63WPF --- Привязка, команды и стили WPF --- Множественные привязки
В 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 имеет тип double, при перетаскивании ползунка получается дробное значение размера. Установив свойство TickFrequency в 1 (или в некоторый целочисленный интервал), a свойство IsSnapToTickEnabled в true, можно ограничить значение ползунка только целыми величинами.
Текстовое поле позволяет вводить буквы и другие нечисловые символы. В таком случае значение текстового поля не сможет быть интерпретировано как число. В результате привязка данных молча потерпит неудачу, а значение размера шрифта станет равно О. Другой подход мог бы состоять в обработке нажатий клавиш в текстовом поле, чтобы вообще предотвратить неправильный ввод, либо в использовании проверки достоверности.
Изменения, которые вносятся в текстовое поле, не будут применены до тех пор, пока текстовое поле не потеряет фокус (например, когда с помощью клавиши <Tab> происходит переход на другой элемент управления). Если такое поведение не подходит, можно обеспечить непрерывное обновление, используя свойство UpdateSourceTrigger объекта Binding.
Интересно, что показанное здесь решение — не единственный способ привязки текстового поля. Столь же разумно конфигурировать текстовое поле таким образом, чтобы оно изменяло значение свойства 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, когда имеет смысл обновлять исходное свойство при двунаправленной привязке.