Привязка данных
Привязка связывает свойство элемента со свойством ваших данных: экран показывает состояние игры, а ввод игрока его меняет — без кода, который копирует значения туда и обратно. Данные описываются моделью представления — обычным классом C#, — а разметка читает из неё.
Контекст данных
У каждого элемента есть DataContext: объект, из которого читают его привязки. Он наследуется,
поэтому если задать его у корня меню (это делает LoadMarkup("Menu.xml", viewModel)), всё меню получит одну и ту же
модель представления. Path привязки называет свойство этого объекта: {Binding Title}, {Binding Player.Name} для
вложенного или {Binding Levels[0]} для индексатора.
Чтобы экран следил за изменениями, модель представления должна вызывать PropertyChanged. Это делает наследование от
ObservableObject из CommunityToolkit.Mvvm (IcyUI уже зависит от этого пакета) и установка свойств через
SetProperty. Модель представления без уведомлений читается один раз, при запуске привязки.
Привязка следит за своим источником, объектом контекста данных, и ни за чем дальше по пути. {Binding Player.Name}
читается заново, когда модель представления вызывает PropertyChanged для Player, но не когда Player вызывает его
для Name. Чтобы показать объект, который меняется сам (например, модель представления настроек), задайте ему
отдельный контекст данных, а не обращайтесь через родителя.
Режимы
<!-- One-way (the default), two-way, and element-to-element bindings. The data context is the view model. -->
<StackPanel Orientation="Vertical" Width="260">
<TextBlock Text="{Binding Title}" FontSize="20" Margin="0,0,0,8" />
<TextBox Text="{Binding PlayerName, Mode=TwoWay}" Margin="0,0,0,8" />
<Slider x:Name="Volume" Minimum="0" Maximum="100" Value="70" />
<TextBlock Text="{Binding Value, ElementName=Volume}" />
</StackPanel>
| Режим | Направление | Когда |
|---|---|---|
OneWay |
модель представления → элемент | По умолчанию для отображаемых свойств: текста, цветов, размеров. |
TwoWay |
в обе стороны | По умолчанию для свойств, которые меняет игрок: текст поля ввода, значение ползунка, флажок, выделение в списке, состояние раскрывающейся панели. |
OneTime |
модель представления → элемент, один раз | Значения, которые не меняются после открытия экрана. |
OneWayToSource |
элемент → модель представления | Сообщить состояние элемента, не показывая своё. |
Пишите Mode= только чтобы изменить режим по умолчанию: {Binding Title, Mode=OneTime}.
Привязка к другому элементу
ElementName привязывает к именованному элементу (x:Name) вместо контекста данных. В примере подпись показывает
значение ползунка, пока его двигают. Source привязывает к конкретному объекту; вкладывать {StaticResource} внутрь
{Binding} разметка пока не умеет, поэтому источник такой привязки задают из кода.
Преобразование значений
Привязка преобразует значение, когда типы двух свойств различаются:
- числа, перечисления и другие значения, показанные в текстовом свойстве, форматируются как текст;
- текст, привязанный к числу, разбирается в культуре интерфейса, и числа в тексте форматируются в ней же (
3,5по-русски); поле ввода с двусторонней привязкой к числу сохраняет прежнее значение, пока текст не разбирается, и показывает состояниеInvalid(красную рамку в теме по умолчанию); - во вводимых числах десятичным разделителем может быть и
., и,, на любом языке, потому что клавиатура игрока может не совпадать с языком игры, а разделителей разрядов нет:1,5и1.5— это 1,5, а1,000— это 1. Целочисленные типы не принимают ни того, ни другого; - строка, привязанная к свойству
Content, становитсяTextBlock.
Своё преобразование задаётся через TypeConverter из кода (Binding.ConverterParameters); Converter= в разметке
нет.
Культура — это Culture конфигурации. По умолчанию она следует языку интерфейса
игры (CultureInfo.CurrentUICulture), а не региональным настройкам игрока, поэтому числа выглядят так, как их пишут на
языке игры. Задайте её через WithCulture, если игра сама выбирает язык; существующие привязки перейдут на новую
культуру при следующем обновлении. Отдельной привязке можно задать собственную культуру через Binding.Culture. Это
также язык текстов {Localize} (см. Локализация). Атрибуты разметки всегда читаются в инвариантной
культуре: Width="3.5" означает одно и то же на любом языке.
Виды привязок: динамические, статические, компилируемые
Привязка читает свой путь одним из трёх способов:
| Вид | Когда | Опечатка в пути | Чтение |
|---|---|---|---|
| Динамическая | Тип источника неизвестен: нет x:DataType, ElementName или Source. |
Предупреждение (событие 2001) при первом чтении и null. |
Ищет члены при каждом чтении. |
| Статическая | Тип источника известен. Тогда это вариант по умолчанию. | Страница не загружается, ошибка указывает строку. | Через члены, найденные один раз. |
| Компилируемая | Compiled=True при известном типе источника. |
Страница не загружается. | Через скомпилированный код: быстрее всего. |
Объявите тип контекста данных через x:DataType; каждый {Binding} ниже него будет статическим, а Compiled=True
делает привязку компилируемой:
<!-- x:DataType declares the data context's type: paths are checked when the page loads. -->
<StackPanel Orientation="Vertical" x:DataType="docs:DocsSnippetViewModel"
xmlns:docs="clr-namespace:Icy.Tests.Docs;assembly=IcyUI.Tests">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="{Binding Title, Compiled=True}" />
</StackPanel>
x:DataTypeдействует на элемент и всё, что внутри него, кроме шаблонов: содержимоеDataTemplateиControlTemplateне видит внешнийx:DataType.DataTemplateобъявляет тип своих элементов собственнымx:DataType;ControlTemplateтоже может.- Привязки с
ElementName=иSource=статические и безx:DataType: их источник известен при загрузке страницы. - Если во время работы контекст данных окажется другого типа, статическая привязка запишет предупреждение в журнал (событие 2002) и продолжит работать динамически, а компилируемая бросит исключение: её ведь выбрали намеренно. Наследник объявленного типа подходит.
- Как и динамический путь, статический или компилируемый читает
null, если какое-то значение на пути равноnull(Child.Name, покаChildне задан), а двусторонняя привязка в этом случае ничего не записывает. - Член свойства типа
objectизвестен только во время работы, поэтому путь через такое свойство, например{Binding SelectedItem.Name, ElementName=list}, читается динамически, даже сCompiled=True. - Компиляция пути занимает некоторое время, когда страница впервые его использует; все привязки с тем же путём делят результат. В сборке NativeAOT компилируемые пути интерпретируются и работают примерно со скоростью статических.
Обновление каждый кадр
UpdateTargetTrigger=EveryFrame перечитывает источник каждый кадр, а не ждёт PropertyChanged. Используйте его для
значений, которые игра меняет постоянно и без уведомлений, например позиции или таймера; это стоит одного чтения свойства
за кадр.
Типичные ошибки
- Опечатка в динамическом пути не бросает исключение. Без
x:DataType,ElementNameилиSourceэлемент ничего не показывает, а IcyUI пишет в журнал предупреждение (событие 2001) с именем члена и типом, в котором его искали; см. Журнал и диагностика. При известном типе источника страница вместо этого не загружается. - Установка привязанного свойства из кода заменяет значение привязки только до следующего изменения источника. Для отображаемого свойства меняйте модель представления.
- Вложенный путь не следит за изменениями вложенного объекта.
{Binding Settings.IsOpen}обновляется, когда модель представления вызываетPropertyChangedдляSettings, но не когдаSettingsвызывает его дляIsOpen. Задайте вложенный объект какDataContextэлемента, который его показывает, и привязывайтесь там к{Binding IsOpen}. - Привязка в шаблоне элемента привязывается к самому элементу списка, а не к контексту данных списка.
См. также
- Ваш первый экран: меню, привязанное к модели представления.
- Контекст данных в справочнике по разметке.
- Списки и выбор:
ItemsSourceиSelectedItem.