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


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

Привязка данных в приложениях WinUI позволяет эффективно подключать элементы управления к источникам данных. Узнайте, как привязать элемент управления к одному элементу или коллекции элементов, управлять отрисовкой элементов, реализовать детализированные представления и форматировать данные для отображения. Дополнительные сведения см. в подробной статье о привязке данных.

Необходимые условия

В этом разделе предполагается, что вы знаете, как создать базовое приложение WinUI с помощью пакета SDK для приложений Windows. Инструкции по созданию первого приложения WinUI см. в статье "Создание приложения WinUI".

Создание проекта

Создайте новый пустой проект C#, упакованное приложение WinUI. Назовите его "Быстрый старт".

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

Каждая привязка состоит из целевого объекта привязки и источника привязки. Как правило, целевой объект — это свойство элемента управления или другого элемента пользовательского интерфейса, а источник — это свойство экземпляра класса (модель данных или модель представления). В этом примере показано, как привязать элемент управления к одному элементу. Цель - это свойство Text объекта TextBlock. Источник — это экземпляр простого класса с именем Recording, представляющий запись звука. Давайте сначала рассмотрим класс.

Добавьте новый класс в проект и присвойте классу имя Recording.

namespace Quickstart
{
    public class Recording
    {
        public string ArtistName { get; set; }
        public string CompositionName { get; set; }
        public DateTime ReleaseDateTime { get; set; }
        public Recording()
        {
            ArtistName = "Wolfgang Amadeus Mozart";
            CompositionName = "Andante in C for Piano";
            ReleaseDateTime = new DateTime(1761, 1, 1);
        }
        public string OneLineSummary
        {
            get
            {
                return $"{CompositionName} by {ArtistName}, released: "
                    + ReleaseDateTime.ToString("d");
            }
        }
    }
    public class RecordingViewModel
    {
        private Recording defaultRecording = new();
        public Recording DefaultRecording { get { return defaultRecording; } }
    }
}

Затем предоставьте исходный класс привязки из класса, представляющего окно разметки. Добавьте свойство типа RecordingViewModel в MainWindow.xaml.cs.

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

Последний элемент заключается в привязке TextBlock к свойству ViewModel.DefaultRecording.OneLineSummary.

<Window x:Class="Quickstart.MainWindow" ... >
    <Grid>
        <TextBlock Text="{x:Bind ViewModel.DefaultRecording.OneLineSummary}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"/>
    </Grid>
</Window>

Вот результат.

Снимок экрана: приложение WinUI с привязкой TextBlock к одному элементу.

Привязка к коллекции элементов

Распространенный сценарий — привязка к коллекции бизнес-объектов. В C#используйте универсальный класс ObservableCollection<T> для привязки данных. Он реализует интерфейс INotifyCollectionChanged , который предоставляет уведомление об изменении привязок при добавлении или удалении элементов. Однако из-за известной ошибки режима Release WinUI с .NET 8 и более поздних версий может потребоваться использовать List<T> в некоторых сценариях, особенно если коллекция статична и не изменяется после инициализации. Если пользовательский интерфейс должен обновиться при изменении коллекции во время выполнения, используйте ObservableCollection<T>. Если необходимо отобразить только фиксированный набор элементов, List<T> достаточно. Кроме того, если вы хотите, чтобы связанные элементы управления обновлялись с изменениями свойств объектов в коллекции, эти объекты должны реализовать INotifyPropertyChanged. Дополнительные сведения см. в разделе Подробное изучение привязки данных.

Заметка

При использовании List<T>вы можете не получать уведомления об изменениях коллекции. Если вам нужно ответить на изменения, рассмотрите возможность использования ObservableCollection<T>. В этом примере не нужно реагировать на изменения коллекции, поэтому List<T> достаточно.

Следующий пример привязывает ListView к коллекции Recording объектов. Сначала добавьте коллекцию в модель представления. Добавьте эти новые члены в RecordingViewModel класс.

public class RecordingViewModel
{
    ...
    private List<Recording> recordings = new();
    public List<Recording> Recordings{ get{ return recordings; } }
    public RecordingViewModel()
    {
        recordings.Add(new Recording(){ ArtistName = "Johann Sebastian Bach",
            CompositionName = "Mass in B minor", ReleaseDateTime = new DateTime(1748, 7, 8) });
        recordings.Add(new Recording(){ ArtistName = "Ludwig van Beethoven",
            CompositionName = "Third Symphony", ReleaseDateTime = new DateTime(1805, 2, 11) });
        recordings.Add(new Recording(){ ArtistName = "George Frideric Handel",
            CompositionName = "Serse", ReleaseDateTime = new DateTime(1737, 12, 3) });
    }
}

Затем привязать ListView к свойству ViewModel.Recordings .

<Window x:Class="Quickstart.MainWindow" ... >
    <Grid>
        <ListView ItemsSource="{x:Bind ViewModel.Recordings}"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"/>
    </Grid>
</Window>

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

Привязка представления списка 1

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

<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:Recording">
            <TextBlock Text="{x:Bind OneLineSummary}"/>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Привязка вида списка 2

<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:Recording">
            <StackPanel Orientation="Horizontal" Margin="6">
                <SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
                <StackPanel>
                    <TextBlock Text="{x:Bind ArtistName}" FontWeight="Bold"/>
                    <TextBlock Text="{x:Bind CompositionName}"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Привязка представления списка 3

Дополнительные сведения о синтаксисе XAML см. в статье Создание пользовательского интерфейса с помощью XAML. Дополнительные сведения о макете элемента управления см. в статье Определение макетов с помощью XAML.

** Добавить детальный просмотр

Вы можете выбрать, чтобы отобразить все сведения об объектах Recording в элементах ListView . Но этот подход занимает много места. Вместо этого можно отобразить достаточно данных в элементе для его идентификации. Когда пользователь делает выбор, вы можете отобразить все сведения о выбранном элементе в отдельном элементе интерфейса, известном как вид подробностей. Такое расположение также называется представлением "основное и детали" или представлением "список и сведения".

Вы можете реализовать это соглашение двумя способами. Представление сведений можно привязать к свойству SelectedItem объекта ListView. Вы также можете использовать CollectionViewSource. В этом случае необходимо привязать ListView и представление сведений к CollectionViewSource. Этот подход заботится о текущем выбранном объекте. Оба метода показаны в следующих разделах, и они оба дают одинаковые результаты (показанные на рисунке).

Заметка

До сих пор в этом разделе вы использовали только расширение разметки {x:Bind}. Но оба метода, показанные в следующих разделах, требуют более гибкого (но менее производительного) расширения разметки {Binding} .

Во-первых, вот метод SelectedItem. Для приложения C# необходимо внести только изменения в разметку.

<Window x:Class="Quickstart.MainWindow" ... >
    <Grid>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <ListView x:Name="recordingsListView" ItemsSource="{x:Bind ViewModel.Recordings}">
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="local:Recording">
                        <StackPanel Orientation="Horizontal" Margin="6">
                            <SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
                            <StackPanel>
                                <TextBlock Text="{x:Bind CompositionName}"/>
                            </StackPanel>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            <StackPanel DataContext="{Binding SelectedItem, ElementName=recordingsListView}"
            Margin="0,24,0,0">
                <TextBlock Text="{Binding ArtistName}"/>
                <TextBlock Text="{Binding CompositionName}"/>
                <TextBlock Text="{Binding ReleaseDateTime}"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

Для метода CollectionViewSource сначала добавьте CollectionViewSource в качестве ресурса Gridверхнего уровня.

<Grid.Resources>
    <CollectionViewSource x:Name="RecordingsCollection" Source="{x:Bind ViewModel.Recordings}"/>
</Grid.Resources>

Заметка

Класс Window в WinUI не имеет свойства Resources. Вместо этого можно добавить CollectionViewSource в элемент верхнего уровня Grid (или другой родительский элемент пользовательского интерфейса, например StackPanel). Если вы работаете в рамках Page, вы можете добавить CollectionViewSource в Page.Resources.

Затем настройте привязки в ListView (который больше не нужно называть) и в представлении сведений, чтобы использовать CollectionViewSource. Привязав представление деталей непосредственно к CollectionViewSource объекту, вы указываете, что хотите связаться с текущим элементом в привязках, если путь не может быть найден в самой коллекции. Нет необходимости указывать свойство CurrentItem в качестве пути для привязки, хотя это можно сделать, если есть какая-либо неоднозначность.

...
<ListView ItemsSource="{Binding Source={StaticResource RecordingsCollection}}">
...
<StackPanel DataContext="{Binding Source={StaticResource RecordingsCollection}}" ...>
...

И вот одинаковый результат в каждом случае.

Привязка списка представлений 4

Форматирование или преобразование значений данных для отображения

В приведенной выше отрисовке возникла проблема. Свойство ReleaseDateTime — это не просто дата; это DateTime. Таким образом, он отображается с большей точностью, чем вам нужно. Одним из решений является добавление строкового свойства в класс Recording, возвращающий эквивалент ReleaseDateTime.ToString("d"). Именование этого свойства ReleaseDate указывает, что возвращает дату, а не дату и время. При присвоении ему имени ReleaseDateAsString это указывает на то, что он возвращает строку.

Более гибкое решение — использовать преобразователь значений. Ниже приведен пример создания собственного преобразователя значений. Добавьте следующий код в файл исходного кода Recording.cs .

public class StringFormatter : Microsoft.UI.Xaml.Data.IValueConverter
{
    // This converts the value object to the string to display.
    // This will work with most simple types.
    public object Convert(object value, Type targetType,
        object parameter, string language)
    {
        // Retrieve the format string and use it to format the value.
        string formatString = parameter as string;
        if (!string.IsNullOrEmpty(formatString))
        {
            return string.Format(formatString, value);
        }

        // If the format string is null or empty, simply
        // call ToString() on the value.
        return value.ToString();
    }

    // No need to implement converting back on a one-way binding
    public object ConvertBack(object value, Type targetType,
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

Теперь можно добавить экземпляр StringFormatter в качестве ресурса и использовать его в привязке TextBlock, который отображает свойство ReleaseDateTime.

<Grid.Resources>
    ...
    <local:StringFormatter x:Key="StringFormatterValueConverter"/>
</Grid.Resources>
...
<TextBlock Text="{Binding ReleaseDateTime,
    Converter={StaticResource StringFormatterValueConverter},
    ConverterParameter=Released: \{0:d\}}"/>
...

Как видно, для обеспечения гибкости форматирования разметка передает строку формата в преобразователь через параметр конвертера. В примере кода, приведенном в этом разделе, преобразователь значений C# использует этот параметр.

Вот результат.

отображение даты с пользовательским форматированием

Различия между привязкой и x:Bind

При работе с привязкой данных в приложениях WinUI может возникнуть два основных механизма привязки: Binding и x:Bind. Хотя оба служат для подключения элементов пользовательского интерфейса к источникам данных, они имеют различные отличия:

  • x:Bind: предлагает проверку во время компиляции, более высокую производительность и строго типизированную. Это идеально подходит для сценариев, когда вы знаете структуру данных во время компиляции.
  • Binding: обеспечивает оценку среды выполнения и более гибкую для динамических сценариев, например если структура данных не известна во время компиляции.

Сценарии, не поддерживаемые x:Bind

Хотя x:Bind это мощный, его нельзя использовать в определенных сценариях:

  • Динамические структуры данных: если структура данных не известна во время компиляции, нельзя использовать x:Bind.
  • Привязка между элементами: x:Bind не поддерживает привязку непосредственно между двумя элементами пользовательского интерфейса.
  • Привязка к : DataContextx:Bind не наследует автоматически свойство родительского элемента.
  • Двусторонняя привязка с Mode=TwoWay: хотя поддерживается, для любого свойства, которое вам нужно обновить при изменении источника, независимо от того, используется односторонняя или двусторонняя привязка, необходима явная реализация x:BindINotifyPropertyChanged. Ключевое отличие двухсторонних привязок заключается в том, что изменения также происходят из пользовательского интерфейса обратно к источнику.

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

  • привязка данных