Table of Contents

Привязка данных

Привязка связывает свойство элемента со свойством ваших данных: экран показывает состояние игры, а ввод игрока его меняет — без кода, который копирует значения туда и обратно. Данные описываются моделью представления — обычным классом 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}.
  • Привязка в шаблоне элемента привязывается к самому элементу списка, а не к контексту данных списка.

См. также