Поделиться через


Подробная привязка данных Windows

В этой статье описаны функции привязки данных пакета SDK для приложений Windows для API, которые находятся в пространстве имен Microsoft.UI.Xaml.Data.

Примечание.

В этом разделе подробно описаны функции привязки данных. Краткое практическое введение см. в обзоре привязки данных.

Важные API

Введение

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

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

  • Можно использовать одноразовый режим для привязки объекта Image к фотографии текущего пользователя.
  • Односторонняя привязка позволяет привязать объект ListView к коллекции актуальных новостных статей, сгруппированных по разделу газеты.
  • С помощью двусторонней привязки вы можете привязать объект TextBox к имени пользователя в форме.

Независимо от режима существует два типа привязки, для объявления которых обычно используется разметка пользовательского интерфейса. Вы можете использовать расширение разметки {x:Bind} или расширение разметки {Binding}. И вы даже можете использовать смесь двух в одном приложении, даже в одном элементе пользовательского интерфейса. {x:Bind} был новым в UWP для Windows 10 и имеет лучшую производительность. Все сведения, описанные в этом разделе, применяются к обоим типам привязки, если мы явно не говорим в противном случае.

Примеры приложений UWP, демонстрирующие {x:Bind}

Примеры приложений UWP, демонстрирующие {Binding}

Каждая привязка включает в себя эти части

  • Источник привязки. Это источник данных для привязки, и он может быть экземпляром любого класса, имеющего элементы, значения которых необходимо отобразить в пользовательском интерфейсе.
  • Целевой объект привязки. Это зависимостьProperty FrameworkElement в пользовательском интерфейсе, отображающая данные.
  • Объект привязки. Это часть, которая передает значения данных из источника в целевой объект и при необходимости из целевого объекта обратно в источник. Объект привязки создается во время загрузки XAML из расширения разметки {x:Bind} или {Binding} .

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

Источник привязки

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

public class HostViewModel
{
    public HostViewModel()
    {
        NextButtonText = "Next";
    }

    public string NextButtonText { get; set; }
}

Эта реализация HostViewModelи его свойство NextButtonTextподходят только для однократной привязки. Но односторонняя и двусторонняя привязка крайне распространена, и в этих типах привязка пользовательского интерфейса автоматически обновляется в ответ на изменения значений данных источника привязки. Чтобы эти типы привязки работали правильно, необходимо сделать источник привязки наблюдаемым для объекта привязки. Поэтому в нашем примере, если мы хотим выполнить односторонняя или двусторонняя привязка к NextButtonText свойству, все изменения, происходящие во время выполнения, к значению этого свойства необходимо сделать наблюдаемым для объекта привязки.

Один из способов сделать это — наследовать класс, представляющий источник привязки из DependencyObject, и предоставить значение данных через DependencyProperty. Вот как платформа FrameworkElement становится наблюдаемой. Это FrameworkElement хороший источник привязки прямо из поля.

Более упрощенный способ создания наблюдаемого класса (и необходимого для классов, у которых уже есть базовый класс), — реализовать System.ComponentModel.INotifyPropertyChanged. Это действительно просто включает реализацию одного события с именем PropertyChanged. Ниже приведен пример использования HostViewModel .

...
using System.ComponentModel;
using System.Runtime.CompilerServices;
...
public class HostViewModel : INotifyPropertyChanged
{
    private string nextButtonText;

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public HostViewModel()
    {
        NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return nextButtonText; }
        set
        {
            nextButtonText = value;
            OnPropertyChanged();
        }
    }

    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        // Raise the PropertyChanged event, passing the name of the property whose value has changed.
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

NextButtonText Теперь свойство наблюдаемо. При создании односторонняя или двусторонняя привязка к такому свойству (мы покажем, как позже), результирующий объект привязки подписывается на PropertyChanged событие. При возникновении этого события обработчик объекта привязки получает аргумент, содержащий имя измененного свойства. Вот как объект привязки знает, какое значение свойства следует идти и читать снова.

Таким образом, вам не нужно реализовать шаблон, показанный выше несколько раз, если вы используете C#, вы можете просто наследовать от BindableBase базового класса, который вы найдете в примере QuizGame (в папке Common). Ниже приведен пример того, как это выглядит.

public class HostViewModel : BindableBase
{
    private string nextButtonText;

    public HostViewModel()
    {
        NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return nextButtonText; }
        set { SetProperty(ref nextButtonText, value); }
    }
}

PropertyChanged Вызов события с аргументом String.Empty или null указывает, что все свойства неиндексатора в объекте должны быть перечитаны. Это событие можно вызвать для указания того, что свойства индексатора в объекте изменены, с помощью аргумента "Item[indexer]" для конкретных индексаторов (где indexer — это значение индекса) или значения "Item[]" для всех индексаторов.

Источник привязки можно рассматривать как один объект, свойства которого содержат данные или как коллекцию объектов. В коде C# можно однократно привязаться к объекту, реализующего list<T> , чтобы отобразить коллекцию, которая не изменяется во время выполнения. Для наблюдаемой коллекции (наблюдая за добавлением и удалением элементов из коллекции), односторонняя привязка к ObservableCollection<T> . Чтобы выполнить привязку к собственным классам коллекции, используйте инструкции в следующей таблице.

Сценарий C# (CLR) C++/WinRT
Привязка к объекту. Может быть любым объектом. Может быть любым объектом.
Получение уведомлений об изменении свойств от привязанного объекта Объект должен реализовывать INotifyPropertyChanged. Объект должен реализовывать INotifyPropertyChanged.
Привязка к коллекции. Список<T> IVector от IInspectable или IBindableObservableVector. Дополнительные сведения см. в статьях Элементы управления XAML; привязка к коллекции C++/WinRT и Коллекции с C++/WinRT.
Получение уведомлений об изменениях коллекции от привязанной коллекции. ObservableCollection<T> IObservableVector от IInspectable. Например, winrt::single_threaded_observable_vector<T>.
Реализуйте коллекцию, поддерживающую привязку. Расширение списка<T> или реализация IList, IList< Object>, IEnumerable или IEnumerable< Object.> Привязка к универсальной IList<T> и IEnumerable<T> не поддерживается. Реализуйте IVector от IInspectable. Дополнительные сведения см. в статьях Элементы управления XAML; привязка к коллекции C++/WinRT и Коллекции с C++/WinRT.
Реализация коллекции, которая поддерживает уведомления о ее изменении Расширение ObservableCollection T> или реализация (не универсального) IList и INotifyCollectionChanged<. Реализуйте IObservableVector от IInspectable или IBindableObservableVector.
Реализуйте коллекцию, которая поддерживает добавочную загрузку. Расширение ObservableCollection T> или реализация (не универсального) IList и INotifyCollectionChanged<. Кроме того, реализуйте ISupportIncrementalLoading. Реализуйте IObservableVector от IInspectable или IBindableObservableVector. Кроме того, реализуйте ISupportIncrementalLoading

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

Цель привязки

В двух примерах ниже Button.Content свойство является целевым объектом привязки, а его значение имеет расширение разметки, которое объявляет объект привязки. Сначала отображается {x:Bind} , а затем {Binding}. Объявление привязок в разметке является обычным случаем (удобно, доступно для чтения и инструментирования). Но при необходимости можно избежать разметки и императивно (программно) создать экземпляр класса Binding .

<Button Content="{x:Bind ...}" ... />
<Button Content="{Binding ...}" ... />

Объект привязки, объявленный с помощью {x:Bind}

Перед созданием разметки {x:Bind} необходимо выполнить один шаг. Нам нужно предоставить исходный класс привязки из класса, представляющего страницу разметки. Мы делаем это путем добавления свойства (типа HostViewModel в данном случае) в MainWindow класс окна.

namespace DataBindingInDepth
{
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
            ViewModel = new HostViewModel();
        }
    
        public HostViewModel ViewModel { get; set; }
    }
}

Это сделано, теперь можно более подробно взглянуть на разметку, которая объявляет объект привязки. В приведенном ниже примере используется тот же Button.Content целевой объект привязки, который мы использовали в разделе "Целевая привязка" ранее и показывает, что он привязан к свойству HostViewModel.NextButtonText .

<!-- MainWindow.xaml -->
<Window x:Class="DataBindingInDepth.MainWindow" ... >
    <Button Content="{x:Bind Path=ViewModel.NextButtonText, Mode=OneWay}" ... />
</Window>

Обратите внимание на значение, для Pathкоторое мы указываем. Это значение интерпретируется в контексте самого окна, и в этом случае путь начинается путем ссылки ViewModel на свойство, которое мы только что добавили на MainWindow страницу. Это свойство возвращает HostViewModel экземпляр, и поэтому мы можем указать этот объект для доступа к свойству HostViewModel.NextButtonText . И мы указываем Mode, чтобы переопределить значение по умолчанию {x:Bind} однократно.

Свойство Path поддерживает различные варианты синтаксиса привязки к вложенным свойствам, присоединенным свойствам и целым числам и строковым индексаторам. Дополнительные сведения см. в разделе "Синтаксис пути свойства". Привязка к строковым индексаторам дает эффект привязки к динамическим свойствам без реализации ICustomPropertyProvider. Сведения о других параметрах см. в разделе {x:Bind}.

Чтобы иллюстрировать, что HostViewModel.NextButtonText свойство действительно наблюдаемо, добавьте Click обработчик событий в кнопку и обновите значение HostViewModel.NextButtonText. Выполните сборку, запустите и нажмите кнопку, чтобы увидеть значение обновления кнопки Content .

// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    ViewModel.NextButtonText = "Updated Next button text";
}

Примечание.

Изменения, внесенные в свойство TextBox.Text, отправляются в источник двусторонней привязки, когда фокус переключается с класса TextBox, а не после каждого нажатия клавиши пользователем.

DataTemplate и x:DataType

Внутри DataTemplate (используется ли он в качестве шаблона элемента, шаблона содержимого или шаблона заголовка), значение Path не интерпретируется в контексте окна, но в контексте шаблона объекта данных. При использовании {x:Bind} в шаблоне данных, чтобы его привязки можно было проверить (и эффективный код, созданный для них) во время компиляции, DataTemplate необходимо объявить тип объекта данных с помощью x:DataType. Приведенный ниже пример можно использовать в качестве ItemTemplate элемента управления элементами, привязанным к коллекции SampleDataGroup объектов.

<DataTemplate x:Key="SimpleItemTemplate" x:DataType="data:SampleDataGroup">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{x:Bind Title}"/>
      <TextBlock Text="{x:Bind Description}"/>
    </StackPanel>
  </DataTemplate>

Слабо типизированные объекты в пути

Рассмотрим, например, тип с именем SampleDataGroup, который реализует строковое свойство с именем Title. И у вас есть свойство MainWindow.SampleDataGroupAsObject, которое имеет тип object, но на самом деле возвращает экземпляр SampleDataGroup. Привязка <TextBlock Text="{x:Bind SampleDataGroupAsObject.Title}"/> приведет к ошибке компиляции, так как Title свойство не найдено в типе object. Исправление этого заключается в добавлении приведения к Path синтаксису, как показано ниже <TextBlock Text="{x:Bind ((data:SampleDataGroup)SampleDataGroupAsObject).Title}"/>. Вот еще один пример, где Element объявляется какobject, но на самом деле <TextBlock Text="{x:Bind Element.Text}"/>TextBlock: . И приведение средств устранения проблемы: <TextBlock Text="{x:Bind ((TextBlock)Element).Text}"/>

Если данные загружаются асинхронно

Код для поддержки {x:Bind} создается во время компиляции в частичных классах для окон. Эти файлы можно найти в папке obj с такими именами, как <view name>.g.cs (для C#). Созданный код содержит обработчик события загрузки окна, и этот обработчик вызывает Initialize метод в созданном классе, представляющем привязки окна. Initialize в свою очередь вызовы Update для начала перемещения данных между источником привязки и целевым объектом. Loading вызывается непосредственно перед первым прохождением меры окна или пользовательского элемента управления. Таким образом, если данные загружаются асинхронно, к тому времени Initialize может не быть готовым. Таким образом, после загрузки данных можно принудительно инициализировать однократные привязки путем вызова this.Bindings.Update();. Если вам нужны одноразовые привязки только для асинхронно загружаемых данных, намного эффективнее инициализировать их так, чем создать односторонние привязки и ожидать изменений. Если данные не проходят детализированные изменения, и если они, скорее всего, будут обновлены в рамках определенного действия, вы можете выполнить однократные привязки и принудительно обновить вручную при вызове Update.

Примечание.

{x:Bind} не подходит для сценариев с поздней привязкой, таких как навигация по структуре словаря объекта JSON, а также для ввода текста утки. "Утиная типизация" — это слабая форма типизации, основанная на лексических соответствиях имен свойств ("если ходит, плавает и крякает как утка — это утка"). При вводе утки привязка к Age свойству будет одинаково удовлетворена Person объектом или Wine объектом (при условии, что эти типы имеют Age свойство). Для этих сценариев используйте {Binding} расширение разметки.

Объект привязки, объявленный с помощью {Binding}

{Binding} Предполагает, что по умолчанию вы привязываетесь к DataContext окна разметки. Поэтому мы задали DataContext окно экземпляром исходного класса привязки (типа HostViewModel в данном случае). В приведенном ниже примере показана разметка, которая объявляет объект привязки. Мы используем тот же Button.Content целевой объект привязки, который мы использовали в разделе "Назначение привязки" ранее, и мы привязываемся к свойству HostViewModel.NextButtonText .

<Window xmlns:viewmodel="using:DataBindingInDepth" ... >
    <Window.DataContext>
        <viewmodel:HostViewModel x:Name="viewModelInDataContext"/>
    </Window.DataContext>
    ...
    <Button Content="{Binding Path=NextButtonText}" ... />
</Window>
// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    viewModelInDataContext.NextButtonText = "Updated Next button text";
}

Обратите внимание на значение, для Pathкоторое мы указываем. Это значение интерпретируется в контексте DataContext окна, в котором в этом примере устанавливается экземплярHostViewModel. Путь ссылается на HostViewModel.NextButtonText свойство. Мы можем пропустить Mode, так как {Binding} по умолчанию используется односторонняя работа.

Значение dataContext по умолчанию для элемента пользовательского интерфейса является унаследованным значением родительского элемента. Конечно, можно переопределить это значение по умолчанию, установив явное значение DataContext , которое, в свою очередь, наследуется дочерними элементами по умолчанию. Явное задание DataContext элемента полезно при наличии нескольких привязок, использующих один и тот же источник.

Объект привязки имеет Source свойство, которое по умолчанию соответствует DataContext элемента пользовательского интерфейса, на котором объявлена привязка. Этот параметр по умолчанию можно переопределить по умолчанию SourceRelativeSourceили ElementName явно в привязке (дополнительные сведения см. в разделе {Binding}).

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

<DataTemplate x:Key="SimpleItemTemplate">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{Binding Title}"/>
      <TextBlock Text="{Binding Description"/>
    </StackPanel>
  </DataTemplate>

Примечание.

По умолчанию изменения, внесенные в свойство TextBox.Text, отправляются в источник двусторонней привязки, когда фокус переключается с класса TextBox. Чтобы вызвать отправку изменений после каждого нажатия клавиш пользователя, задайте UpdateSourceTrigger для PropertyChanged привязки в разметке. Вы также можете полностью контролировать, когда изменения отправляются в источник, задав для этого значение UpdateSourceTrigger Explicit. Затем вы обрабатываете события в текстовом поле (обычно TextBox.TextChanged), вызываете GetBindingExpression в целевом объекте, чтобы получить объект BindingExpression, и, наконец, вызовите BindingExpression.UpdateSource для программного обновления источника данных.

Свойство Path поддерживает различные варианты синтаксиса привязки к вложенным свойствам, присоединенным свойствам и целым числам и строковым индексаторам. Дополнительные сведения см. в разделе "Синтаксис пути свойства". Привязка к строковым индексаторам дает эффект привязки к динамическим свойствам без реализации ICustomPropertyProvider. Свойство ElementName полезно для привязки элемента к элементу. Свойство RelativeSource имеет несколько вариантов использования, один из которых является более мощной альтернативой привязке шаблона внутри ControlTemplate. Сведения о других параметрах см. в разделе "Расширение разметки {Binding} и класс Binding".

Что делать, если источник и целевой объект не совпадают с типом?

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

  • При использовании {x:Bind} можно привязать непосредственно к функции для этого преобразования.
  • Или можно указать преобразователь значений, который является объектом, предназначенным для выполнения преобразования.

Преобразователи значений

Ниже приведен преобразователь значений, подходящий для однократной привязки или односторонняя привязка, которая преобразует значение DateTime в string значение, содержащее месяц. Класс реализует IValueConverter.

public class DateToStringConverter : IValueConverter
{
    // Define the Convert method to convert a DateTime value to 
    // a month string.
    public object Convert(object value, Type targetType, 
        object parameter, string language)
    {
        // value is the data from the source object.
        DateTime thisDate = (DateTime)value;
        int monthNum = thisDate.Month;
        string month;
        switch (monthNum)
        {
            case 1:
                month = "January";
                break;
            case 2:
                month = "February";
                break;
            default:
                month = "Month not found";
                break;
        }
        // Return the value to pass to the target.
        return month;
    }

    // ConvertBack is not implemented for a OneWay binding.
    public object ConvertBack(object value, Type targetType, 
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

И вот как вы используете этот преобразователь значений в разметке объекта привязки.

<UserControl.Resources>
  <local:DateToStringConverter x:Key="Converter1"/>
</UserControl.Resources>
...
<TextBlock Grid.Column="0" 
  Text="{x:Bind ViewModel.Month, Converter={StaticResource Converter1}}"/>
<TextBlock Grid.Column="0" 
  Text="{Binding Month, Converter={StaticResource Converter1}}"/>

Подсистема привязки вызывает методы Convert и ConvertBack, если параметр Converter определен для привязки. Когда данные передаются из источника, подсистема привязки вызывает Convert и передает возвращенные данные целевому объекту. При передаче данных из целевого объекта (для двусторонней привязки) подсистема привязки вызывает ConvertBack и передает возвращенные данные источнику.

Преобразователь также имеет необязательные параметры: ConverterLanguage, который позволяет указывать язык, используемый в преобразовании, и ConverterParameter, что позволяет передавать параметр для логики преобразования. Пример использования параметра преобразователя см. в разделе IValueConverter.

Примечание.

Если в преобразовании возникает ошибка, не вызывайте исключение. Вместо этого возвращает значение DependencyProperty.UnsetValue, которое остановит передачу данных.

Чтобы отобразить значение по умолчанию, используемое всякий раз, когда источник привязки не может быть разрешен, задайте FallbackValue свойство объекта привязки в разметке. Это полезно для обработки ошибок преобразования и форматирования. Также полезно привязать к свойствам источника, которые могут не существовать во всех объектах в связанной коллекции разнородных типов.

Если привязать текстовый элемент управления к значению, которое не является строкой, подсистема привязки данных преобразует значение в строку. Если значение является ссылочным типом, подсистема привязки данных извлекает строковое значение путем вызова ICustomPropertyProvider.GetStringRepresentation или IStringable.ToString, если оно доступно, и в противном случае вызовет Object.ToString. Обратите внимание, что подсистема привязки будет игнорировать любую ToString реализацию, которая скрывает реализацию базового класса. Вместо этого реализации подклассов следует переопределить метод базового класса ToString . Аналогичным образом, на собственных языках все управляемые объекты, как представляется, реализуют ICustomPropertyProvider и IStringable. Однако все вызовы GetStringRepresentation и IStringable.ToString перенаправлены в Object.ToString этот метод или переопределение этого метода, и никогда не в новую ToString реализацию, которая скрывает реализацию базового класса.

Примечание.

Набор средств сообщества Windows предоставляет BoolToVisibilityConverter. Преобразователь сопоставляется true со значением перечисления и false Collapsed позволяет привязать Visibility свойство к логическому объекту без создания преобразователяVisible. Чтобы использовать преобразователь, проект должен добавить пакет NuGet CommunityToolkit.WinUI.Converters .

Привязка функции в {x:Bind}

{x:Bind} позволяет последнему шагу в пути привязки быть функцией. Это можно использовать для выполнения преобразований и выполнения привязок, зависящих от нескольких свойств. Дополнительные сведения см. в статье Функции в x:Bind.

Привязка между элементами

Вы можете привязать свойство одного элемента XAML к свойству другого элемента XAML. Вот как это выглядит в разметке:

<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />

Словари ресурсов с {x:Bind}

Расширение разметки {x:Bind} зависит от создания кода, поэтому для инициализации созданного кода требуется файл программной части, содержащий конструктор InitializeComponent , вызывающий (инициализировать созданный код). Вы повторно используете словарь ресурсов, создав его тип (так называемый InitializeComponent ), а не ссылаясь на его имя файла. Ниже приведен пример того, что делать, если у вас есть существующий словарь ресурсов и вы хотите использовать {x:Bind} его.

<!-- TemplatesResourceDictionary.xaml -->
<ResourceDictionary
    x:Class="ExampleNamespace.TemplatesResourceDictionary"
    .....
    xmlns:examplenamespace="using:ExampleNamespace">
    
    <DataTemplate x:Key="EmployeeTemplate" x:DataType="examplenamespace:IEmployee">
        <Grid>
            <TextBlock Text="{x:Bind Name}"/>
        </Grid>
    </DataTemplate>
</ResourceDictionary>
// TemplatesResourceDictionary.xaml.cs
using Microsoft.UI.Xaml.Data;
 
namespace ExampleNamespace
{
    public partial class TemplatesResourceDictionary
    {
        public TemplatesResourceDictionary()
        {
            InitializeComponent();
        }
    }
}
<!-- MainWindow.xaml -->
<Window x:Class="ExampleNamespace.MainWindow"
    ....
    xmlns:examplenamespace="using:ExampleNamespace">

    <Window.Resources>
        <ResourceDictionary>
            .... 
            <ResourceDictionary.MergedDictionaries>
                <examplenamespace:TemplatesResourceDictionary/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
</Window>

Привязка событий и ICommand

{x:Bind} поддерживает функцию, называемую привязкой событий. С помощью этой функции можно указать обработчик события с помощью привязки, которая является дополнительным параметром поверх обработки событий с помощью метода в файле кода программной части. Предположим, у вас есть ListViewDoubleTapped обработчик событий в MainWindow классе.

public sealed partial class MainWindow : Window
{
    ...
    public void ListViewDoubleTapped()
    {
        // Handle double-tapped logic
    }
}

Затем можно привязать событие DoubleTapped ListView к методу в MainWindow, как показано ниже.

<ListView DoubleTapped="{x:Bind ListViewDoubleTapped}" />

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

Метод привязки событий аналогичен реализации и потреблению команд (команда — это свойство, возвращающее объект, реализующий интерфейс ICommand ). Как {x:Bind}, так и {Binding} работают с командами. Так что вам не нужно реализовать шаблон команды несколько раз, можно использовать DelegateCommand вспомогательный класс, который вы найдете в примере UWP QuizGame (в папке Common).

Привязка к коллекции папок или файлов

API-интерфейсы в пространстве имен Windows.Storage можно использовать для получения данных папок и файлов в упакованных приложениях пакета SDK для приложений windows. Однако различные GetFilesAsyncGetFoldersAsyncметоды и GetItemsAsync методы не возвращают значения, которые подходят для привязки к элементам управления списком. Вместо этого необходимо привязать к возвращаемым значениям методов GetVirtualizedFilesVector, GetVirtualizedFoldersVector и GetVirtualizedItemsVector класса FileInformationFactory. В следующем примере кода из примера StorageDataSource и GetVirtualizedFilesVector UWP показан типичный шаблон использования. Не забудьте объявить функцию picturesLibrary в манифесте пакета приложения и убедиться, что в папке библиотеки изображений есть изображения.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    var library = Windows.Storage.KnownFolders.PicturesLibrary;
    var queryOptions = new Windows.Storage.Search.QueryOptions();
    queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep;
    queryOptions.IndexerOption = Windows.Storage.Search.IndexerOption.UseIndexerWhenAvailable;

    var fileQuery = library.CreateFileQueryWithOptions(queryOptions);

    var fif = new Windows.Storage.BulkAccess.FileInformationFactory(
        fileQuery,
        Windows.Storage.FileProperties.ThumbnailMode.PicturesView,
        190,
        Windows.Storage.FileProperties.ThumbnailOptions.UseCurrentScale,
        false
        );

    var dataSource = fif.GetVirtualizedFilesVector();
    this.PicturesListView.ItemsSource = dataSource;
}

Обычно этот подход используется для создания представления сведений о файлах и папках только для чтения. Можно создать двусторонние привязки к свойствам файла и папки, например, чтобы пользователи могли оценить песню в музыкальном представлении. Однако любые изменения не сохраняются до вызова соответствующего SavePropertiesAsync метода (например, MusicProperties.SavePropertiesAsync). Если элемент теряет фокус, необходимо зафиксировать изменения, так как это активирует сброс выделения.

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

Обратите внимание также, что виртуализированный вектор может возвращать null некоторые элементы перед заполнением их значения. Например, перед использованием значения SelectedItem элемента управления списком, привязанного к виртуализированному вектору, или использовать SelectedIndex вместо этого.null

Привязка к данным, сгруппированных по ключу

Если вы принимаете плоскую коллекцию элементов (например, книги, представленные BookSku классом) и группируете элементы с помощью общего свойства в качестве ключа ( BookSku.AuthorName например, свойства), результат называется сгруппированными данными. При группировке данных она больше не является плоской коллекцией. Сгруппированные данные — это коллекция объектов группы, где каждый объект группы имеет:

  • ключ;
  • коллекция элементов, свойство которых соответствует этому ключу.

Чтобы повторить пример книг, результат группировки книг по имени автора приводит к коллекции групп имен автора, где каждая группа имеет:

  • ключ (в этом случае — имя автора);
  • коллекция BookSku объектов, свойство которых AuthorName соответствует ключу группы.

Как правило, для отображения коллекции привязывается элемент управления ItemsSource элемента управления (например , ListView или GridView) непосредственно к свойству, возвращающего коллекцию. Если это плоская коллекция элементов, вам не нужно делать ничего специального. Но если это коллекция объектов группы (как и при привязке к группируемым данным), то вам нужны службы промежуточного объекта с именем CollectionViewSource , который находится между элементом управления элементами и источником привязки. Вы привязываете CollectionViewSource свойство, возвращающее сгруппированные данные, и привязываете элемент управления items к элементу CollectionViewSourceуправления . Дополнительное добавление CollectionViewSource значения заключается в том, что он отслеживает текущий элемент, поэтому вы можете сохранить несколько элементов управления в синхронизации, привязав их ко всем одинаковым CollectionViewSource. Вы также можете получить доступ к текущему элементу программным способом с помощью свойства ICollectionView.CurrentItem объекта, возвращаемого свойством CollectionViewSource.View.

Чтобы активировать объект группировки объекта CollectionViewSource, задайте для IsSourceGrouped значение true. Необходимо ли также задать свойство ItemsPath , зависит от того, как создавать объекты группы. Существует два способа создания объекта группы: шаблона is-a-group и шаблона has-a-group. В шаблоне "is-a-group" объект группы является производным от типа коллекции (например, List<T>поэтому объект группы фактически является группой элементов. При использовании этого шаблона не требуется задавать ItemsPath. В шаблоне "has-a-group" объект группы имеет одно или несколько свойств типа коллекции (например List<T>), поэтому группа имеет группу элементов в виде свойства (или несколько групп элементов в виде нескольких свойств). В этом шаблоне необходимо задать ItemsPath имя свойства, содержащего группу элементов.

В приведенном ниже примере показан шаблон "has-a-group". Класс окна имеет свойство DataContext, которое возвращает экземпляр модели представления. CollectionViewSource привязывается к Authors свойству модели представления (Authorsявляется коллекцией объектов группы), а также указывает, что это Author.BookSkus свойство, содержащее сгруппированные элементы. Наконец, GridView привязан к CollectionViewSourceобъекту и имеет свой стиль группы, чтобы он смог отобразить элементы в группах.

<Window.Resources>
    <CollectionViewSource
    x:Name="AuthorHasACollectionOfBookSku"
    Source="{x:Bind ViewModel.Authors}"
    IsSourceGrouped="true"
    ItemsPath="BookSkus"/>
</Window.Resources>
...
<GridView
ItemsSource="{x:Bind AuthorHasACollectionOfBookSku}" ...>
    <GridView.GroupStyle>
        <GroupStyle
            HeaderTemplate="{StaticResource AuthorGroupHeaderTemplateWide}" ... />
    </GridView.GroupStyle>
</GridView>

Вы можете реализовать шаблон "is-a-group" одним из двух способов. Одним из способов является создание собственного класса группы. Наследуйте класс ( List<T> где T является типом элементов). Например, public class Author : List<BookSku>. Второй способ — использовать выражение LINQ для динамического создания объектов группы (и класса группы) из таких значений свойств элементов BookSku . Такой подход — обслуживание только плоского списка элементов и группирование их вместе на лету — обычно это приложение, которое обращается к данным из облачной службы. Вы получаете гибкость для группирования книг по автору или по жанру (например), не нуждаясь в специальных классах группы, таких как автор и жанр.

В приведенном ниже примере показан шаблон "is-a-group" с помощью LINQ. На этот раз мы группируем книги по жанру, отображаемым с именем жанра в заголовках группы. Это означает путь свойства Key в ссылке на значение ключа группы.

using System.Linq;
...
private IOrderedEnumerable<IGrouping<string, BookSku>> genres;

public IOrderedEnumerable<IGrouping<string, BookSku>> Genres
{
    get
    {
        if (genres == null)
        {
            genres = from book in bookSkus
                     group book by book.genre into grp
                     orderby grp.Key
                     select grp;
        }
        return genres;
    }
}

Помните, что при использовании {x:Bind} с шаблонами данных необходимо указать тип, к которому привязан тип, задав x:DataType значение. Если тип является универсальным, мы не можем выразить это в разметке, поэтому вместо этого необходимо использовать {Binding} в шаблоне заголовка стиля группы.

    <Grid.Resources>
        <CollectionViewSource x:Name="GenreIsACollectionOfBookSku"
        Source="{x:Bind Genres}"
        IsSourceGrouped="true"/>
    </Grid.Resources>
    <GridView ItemsSource="{x:Bind GenreIsACollectionOfBookSku}">
        <GridView.ItemTemplate x:DataType="local:BookTemplate">
            <DataTemplate>
                <TextBlock Text="{x:Bind Title}"/>
            </DataTemplate>
        </GridView.ItemTemplate>
        <GridView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Key}"/>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </GridView.GroupStyle>
    </GridView>

Элемент управления SemanticZoom — отличный способ просмотра и навигации пользователей сгруппированных данных. Пример приложения UWP Bookstore2 иллюстрирует использование SemanticZoomприложения . В этом приложении можно просмотреть список книг, сгруппированных по автору (увеличенное представление) или просмотреть список переходов авторов (увеличенное представление). Список переходов обеспечивает гораздо более быструю навигацию, чем прокрутка по списку книг. Масштабированные и увеличенные представления фактически ListView GridView или элементы управления привязаны к одному и тому же CollectionViewSource.

Иллюстрация семантики

При привязке к иерархическим данным, таким как подкатегории в категориях, можно выбрать отображение иерархических уровней в пользовательском интерфейсе с рядом элементов управления. Выбор в одном элементе управления элементами определяет содержимое последующих элементов управления. Списки можно синхронизировать, привязав каждый список к собственному Объекту CollectionViewSource и привязав CollectionViewSource экземпляры к цепочке. Это называется представлением master/details (или list/details). Дополнительные сведения см. в разделе "Как привязать к иерархическим данным" и создать представление master/details.

Диагностика и отладка проблем привязки данных

Разметка привязки содержит имена свойств (и для C#, иногда поля и методы). Поэтому при переименовании свойства также потребуется изменить любую привязку, которая ссылается на нее. Забыли это сделать, что приводит к типичному примеру ошибки привязки данных, и ваше приложение либо не будет компилироваться, либо не будет работать правильно.

Как правило, объекты привязки, создаваемые с помощью расширений разметки {x:Bind} и {Binding}, выполняют аналогичные функции. Но {x:Bind} имеет сведения о типе источника привязки, и он создает исходный код во время компиляции. При {x:Bind} обнаружении проблем такого же типа, что и в остальной части кода. Это включает проверку во время компиляции выражений привязки и отладку путем задания точек останова в исходном коде, созданного в качестве частичного класса для страницы. Эти классы можно найти в файлах в obj папке с такими именами, как (для C#). <view name>.g.cs Если у вас возникли проблемы с привязкой, включите необработанные исключения в отладчике Microsoft Visual Studio. Отладчик разорвит выполнение на этом этапе, и вы можете отладить то, что произошло неправильно. Код, созданный {x:Bind} по одному шаблону для каждой части графа исходных узлов привязки, и вы можете использовать сведения в окне стека вызовов, чтобы определить последовательность вызовов, которые привели к проблеме.

{Binding} не содержит сведений о типе источника привязки. Но при запуске приложения с присоединенным отладчиком все ошибки привязки отображаются в окнах вывода и привязок XAML в Visual Studio. Дополнительные сведения об ошибках отладки привязок в Visual Studio см. в диагностика привязки данных XAML.

Создание привязок в коде

Примечание.

Этот раздел применяется только к {Binding}, так как в коде нельзя создавать привязки {x:Bind} . Однако некоторые из этих преимуществ {x:Bind} можно добиться с помощью DependencyObject.RegisterPropertyChangedCallback, что позволяет зарегистрировать уведомления об изменениях в любом свойстве зависимостей.

Вы также можете подключить элементы пользовательского интерфейса к данным с помощью процедурного кода вместо XAML. Для этого создайте объект Binding, задайте соответствующие свойства, а затем вызовите FrameworkElement.SetBinding или BindingOperations.SetBinding.SetBinding. Создание привязок программным способом полезно при выборе значений свойств привязки во время выполнения или совместного использования одной привязки между несколькими элементами управления. Обратите внимание, что после вызова SetBindingневозможно изменить значения свойств привязки.

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

<TextBox x:Name="MyTextBox" Text="Text"/>
// Create an instance of the MyColors class 
// that implements INotifyPropertyChanged.
var textcolor = new MyColors();

// Brush1 is set to be a SolidColorBrush with the value Red.
textcolor.Brush1 = new SolidColorBrush(Colors.Red);

// Set the DataContext of the TextBox MyTextBox.
MyTextBox.DataContext = textcolor;

// Create the binding and associate it with the text box.
var binding = new Binding { Path = new PropertyPath("Brush1") };
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding);

Сравнение функций {x:Bind} и {Binding}

Функция {x:Bind} в сравнении с {Binding} Примечания.
Путь — это свойство по умолчанию {x:Bind a.b.c}
-
{Binding a.b.c}
Свойство Path {x:Bind Path=a.b.c}
-
{Binding Path=a.b.c}
Path По x:Bindумолчанию корень находится в окне, а не DataContext.
Индексатор {x:Bind Groups[2].Title}
-
{Binding Groups[2].Title}
Привязывается к указанному элементу в коллекции. Поддерживаются только целые индексы.
Вложенные свойства {x:Bind Button22.(Grid.Row)}
-
{Binding Button22.(Grid.Row)}
Присоединенные свойства задаются с помощью скобок. Если свойство не объявляется в пространстве имен XAML, префиксируйте его с пространством имен XML, которое должно быть сопоставлено с пространством имен кода в начале документа.
Приведение {x:Bind groups[0].(data:SampleDataGroup.Title)}
-
Не требуется {Binding}.
Приведения задаются с помощью круглых скобок. Если свойство не объявляется в пространстве имен XAML, префиксируйте его с пространством имен XML, которое должно быть сопоставлено с пространством имен кода в начале документа.
Преобразователь {x:Bind IsShown, Converter={StaticResource BoolToVisibility}}
-
{Binding IsShown, Converter={StaticResource BoolToVisibility}}
Преобразователи должны быть объявлены в корне окна, элемента управления или resourceDictionary или в App.xaml.
ConverterParameter, ConverterLanguage {x:Bind IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}
-
{Binding IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr}
Преобразователи должны быть объявлены в корне окна, элемента управления или resourceDictionary или в App.xaml.
TargetNullValue {x:Bind Name, TargetNullValue=0}
-
{Binding Name, TargetNullValue=0}
Используется, когда конечная часть выражения привязки имеет значение NULL. Используйте одинарные кавычки для строкового значения.
Резервное значение {x:Bind Name, FallbackValue='empty'}
-
{Binding Name, FallbackValue='empty'}
Используется, если любая часть пути для привязки (за исключением конечной) имеет значение NULL.
ElementName {x:Bind slider1.Value}
-
{Binding Value, ElementName=slider1}
При {x:Bind} привязке к полю; Path корень в окне по умолчанию, поэтому любой именованный элемент можно получить через его поле.
RelativeSource: Self <Rectangle x:Name="rect1" Width="200" Height="{x:Bind rect1.Width}" ... />
-
<Rectangle Width="200" Height="{Binding Width, RelativeSource={RelativeSource Self}}" ... />
Присвойите {x:Bind}элементу имя и используйте его имя в Path.
RelativeSource: TemplatedParent Не требуется {x:Bind}
-
{Binding <path>, RelativeSource={RelativeSource TemplatedParent}}
TargetType ControlTemplate С параметром {x:Bind}on указывает привязку к родительскому элементу шаблона. Для {Binding}большинства используемых шаблонов можно использовать обычную привязку шаблонов в шаблонах элементов управления. Но используйте TemplatedParent , где необходимо использовать преобразователь или двусторонняя привязка.
Исходный код Не требуется {x:Bind}
-
<ListView ItemsSource="{Binding Orders, Source={StaticResource MyData}}"/>
Для {x:Bind} прямого использования именованного элемента используйте свойство или статический путь.
Режим {x:Bind Name, Mode=OneWay}
-
{Binding Name, Mode=TwoWay}
Mode может иметь значение OneTime, OneWay или TwoWay. {x:Bind}OneTimeпо умолчанию — {Binding} по умолчаниюOneWay.
UpdateSourceTrigger {x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
-
{Binding UpdateSourceTrigger=PropertyChanged}
UpdateSourceTrigger может иметь значение Default, LostFocus или PropertyChanged. {x:Bind} не поддерживает UpdateSourceTrigger=Explicit. {x:Bind} использует PropertyChanged поведение для всех случаев, за исключением TextBox.Textслучаев, когда оно использует LostFocus поведение.

См. также