Руководство. Создание привязок данных

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

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

Начнем с упрощенной версии примера PhotoLab. Эта начальная версия включает в себя полный уровень данных и базовые макеты страниц на языке XAML, но для удобства просмотра кода в ней отсутствуют многие возможности. В этом руководстве не будет создано полноценное приложение, поэтому ознакомьтесь с окончательной версией, в которой присутствуют такие возможности, как пользовательские анимации и адаптивные макеты. Окончательную версию можно найти в корневой папке репозитория Windows-appsample-photo-lab.

Пример приложения PhotoLab содержит две страницы. Главная страница, на которой отображается представление фотоальбома, а также сведения о каждом файле изображения.

Screenshot of the Photo lab main page.

Страница сведений, на которой отображается одна выбранная фотография. Всплывающее меню редактирования позволяет изменить, переименовать и сохранить фотографию.

Screenshot of the Photo lab detail page.

Необходимые компоненты

Часть 0. Получение начального кода с GitHub

В этом учебнике мы начнем с упрощенной версии примера PhotoLab.

  1. Перейдите на страницу GitHub с примером кода: https://github.com/Microsoft/Windows-appsample-photo-lab.

  2. Далее необходимо клонировать или скачать пример. Нажмите кнопку Clone or download (Клонировать или скачать). Откроется подменю. The Clone or download menu on the PhotoLab sample's GitHub page

    Если вы не знакомы с GitHub:

    a. Нажмите кнопку Download ZIP (Скачать ZIP-файл) и сохраните файл в локальном расположении. Будет выполнено скачивание ZIP-файла с необходимыми файлами проекта.

    b. Извлеките файл. Используйте проводник, чтобы перейти к скачанному ZIP-файлу, щелкните его правой кнопкой мыши и выберите Извлечь все.

    c. Перейдите к локальной копии примера кода, а затем в каталог Windows-appsample-photo-lab-master\xaml-basics-starting-points\data-binding.

    Если вы знакомы с GitHub:

    a. Клонируйте главную ветвь репозитория локально.

    b. Перейдите в каталог Windows-appsample-photo-lab\xaml-basics-starting-points\data-binding.

  3. Дважды щелкните Photolab.sln, чтобы открыть решение в Visual Studio.

Часть 1. Замена заполнителей

В этой части вы создадите однократные привязки в шаблоне данных XAML для отображения реальных изображений и их метаданных вместо заполнителей.

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

Замена заполнителей однократными привязками

  1. Откройте папку xaml-basics-starting-points\data-binding и запустите файл PhotoLab.sln в Visual Studio.

  2. Убедитесь, что в качестве платформы решения выбрана архитектура x86 или x64, а не ARM, а затем запустите приложение. Ниже показано состояние приложения с заполнителями пользовательского интерфейса до добавления привязок.

    Running app with placeholder images and text

  3. Откройте файл MainPage.xaml и найдите шаблон DataTemplate с именем ImageGridView_DefaultItemTemplate. Этот шаблон будет обновлен для использования привязок данных.

    До:

    <DataTemplate x:Key="ImageGridView_DefaultItemTemplate">
    

    Значение x:Key используется ImageGridView для выбора этого шаблона для отображения объектов данных.

  4. x:DataType Добавьте значение в шаблон.

    После:

    <DataTemplate x:Key="ImageGridView_DefaultItemTemplate"
                  x:DataType="local:ImageFileInfo">
    

    x:DataType указывает тип, для которого используется шаблон. В этом случае шаблон предназначен для класса ImageFileInfo (где local: указывает на локальное пространство имен, как определено в объявлении xmlns в верхней части файла).

    x:DataType требуется при использовании x:Bind выражений в шаблоне данных, как описано далее.

  5. DataTemplateНайдите Image элемент с именем ItemImage и замените его Source значение, как показано ниже.

    До:

    <Image x:Name="ItemImage"
           Source="/Assets/StoreLogo.png"
           Stretch="Uniform" />
    

    После:

    <Image x:Name="ItemImage"
           Source="{x:Bind ImageSource}"
           Stretch="Uniform" />
    

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

    Выражения x:Bind получают значение от свойства data-object и предоставляют это значение свойству пользовательского интерфейса. В шаблонах указанное свойство является свойством любого x:DataType заданного значения. Таким образом, в этом случае источник данных является свойством ImageFileInfo.ImageSource .

    Примечание.

    Значение x:Bind также позволяет редактору знать о типе данных, поэтому вместо ввода имени свойства в x:Bind выражении можно использовать IntelliSense. Попробуйте использовать код, который вы только что вставили: поместите курсор сразу после x:Bind и нажмите пробел, чтобы просмотреть список свойств, к которые можно привязать.

  6. Замените значения других элементов управления пользовательского интерфейса таким же образом. (Попробуйте сделать это с помощью функции IntelliSense вместо копирования и вставки.)

    До:

    <TextBlock Text="Placeholder" ... />
    <StackPanel ... >
        <TextBlock Text="PNG file" ... />
        <TextBlock Text="50 x 50" ... />
    </StackPanel>
    <muxc:RatingControl Value="3" ... />
    

    После:

    <TextBlock Text="{x:Bind ImageTitle}" ... />
    <StackPanel ... >
        <TextBlock Text="{x:Bind ImageFileType}" ... />
        <TextBlock Text="{x:Bind ImageDimensions}" ... />
    </StackPanel>
    <muxc:RatingControl Value="{x:Bind ImageRating}" ... />
    

Запустите приложение и посмотрите, как оно выглядит на данном этапе. Больше никаких заполнителей! Отличное начало.

Running app with real images and text instead of placeholders

Примечание.

Если вы хотите еще поэкспериментировать, попробуйте добавить новый элемент TextBlock в шаблон данных и используйте функцию IntelliSense и x:Bind, чтобы найти свойство для отображения.

В этой части мы создадим однократные привязки на странице XAML, чтобы связать представление коллекции с набором изображений. Для этого необходимо заменить существующий процедурный код, выполняющий эту функцию, в коде программной части. Также мы создадим кнопку Delete (Удалить), чтобы проверить, как изменится представление коллекции при удалении изображений из набора. Также вы узнаете, как привязывать события к обработчикам событий, чтобы обеспечить большую гибкость по сравнению с традиционными обработчиками событий.

Все привязки, описанные до сих пор, находятся внутри шаблонов данных и ссылаются на свойства класса, указанного значением x:DataType . Как насчет остального кода XAML на вашей странице?

x:Bind выражения вне шаблонов данных всегда привязаны к самой странице. Это означает, что вы можете ссылаться на все, что вы помещаете в код или объявляете в XAML, включая настраиваемые свойства и свойства других элементов управления пользовательского интерфейса на странице (если они имеют x:Name значение).

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

Привязка основного элемента управления GridView к коллекции изображений

  1. В MainPage.xaml.cs найдите GetItemsAsync метод и удалите код, который задает ItemsSource.

    До:

    ImageGridView.ItemsSource = Images;
    

    После:

    // Replaced with XAML binding:
    // ImageGridView.ItemsSource = Images;
    
  2. В MainPage.xaml найдите именованный GridViewImageGridViewItemsSource и добавьте атрибут. Для значения используйте x:Bind выражение, которое ссылается на Images свойство, реализованное в коде.

    До:

    <GridView x:Name="ImageGridView"
    

    После:

    <GridView x:Name="ImageGridView"
              ItemsSource="{x:Bind Images}"
    

    Свойство Images относится к типу ObservableCollection<ImageFileInfo>, поэтому отдельные элементы, отображаемые в GridView, относятся к типу ImageFileInfo. Это соответствует значению, описанному x:DataType в части 1.

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

ItemsSource Даже добавленная привязка — это однократная привязка только для чтения к неизменному значению свойства, но здесь есть важное различие. Неизменное значение Images свойства — это отдельный экземпляр коллекции, инициализированный один раз, как показано здесь.

private ObservableCollection<ImageFileInfo> Images { get; }
    = new ObservableCollection<ImageFileInfo>();

Значение свойства Images не изменяется, но так как это свойство относится к типу ObservableCollection<T>. Содержимое коллекции может меняться; при этом привязка позволит автоматически обнаружить изменения и обновить пользовательский интерфейс.

Чтобы проверить это, мы временно добавим кнопку, которая удаляет выбранное на данный момент изображение. Этой кнопки нет в окончательной версии, так как при выборе изображения вы перейдете на страницу сведений. При этом поведение ObservableCollection<T> по-прежнему играет важную роль в окончательной версии примера PhotoLab, так как код XAML инициализируется в конструкторе страниц (путем вызова метода InitializeComponent), но коллекция Images заполняется в дальнейшем через метод GetItemsAsync.

Добавление кнопки удаления

  1. В файле MainPage.xaml найдите CommandBar с именем MainCommandBa и добавьте новую кнопку перед кнопкой масштабирования. (Элементы управления масштабом пока не работают. Вы подключите их в следующей части руководства.)

    <AppBarButton Icon="Delete"
                  Label="Delete selected image"
                  Click="{x:Bind DeleteSelectedImage}" />
    

    Если вы уже знакомы с XAML, это Click значение может выглядеть необычно. В предыдущих версиях XAML в качестве этого значения использовался метод с определенной подписью обработчика событий, включавшей в себя, как правило, параметры отправителя событий и объект аргументов, характерных для разных событий. Этот способ также можно использовать, если вам нужны аргументы событий, но с помощью x:Bind можно обеспечивать связь и с другими методами. Например, если вам не нужны данные событий, можно выполнить привязку к методам без параметров, как в нашем случае.

  2. В Файле MainPage.xaml.cs добавьте DeleteSelectedImage метод.

    private void DeleteSelectedImage() =>
        Images.Remove(ImageGridView.SelectedItem as ImageFileInfo);
    

    Этот метод просто удаляет выбранный образ из Images коллекции.

Теперь запустите приложение и удалите несколько изображений с помощью кнопки. Как видно, пользовательский интерфейс обновляется автоматически благодаря привязке данных и типу ObservableCollection<T>.

Примечание.

Этот код удаляет экземпляр ImageFileInfo только из коллекции Images в работающем приложении. Он не удаляет файл образа с компьютера.

Часть 3. Настройка ползунка масштабирования

В этой части мы создадим односторонние привязки между элементом управления в шаблоне данных и ползунком масштабирования, расположенным за пределами шаблона. Вы также узнаете, что вы можете использовать привязку данных со многими свойствами элемента управления, а не только наиболее очевидными, как TextBlock.Text и Image.Source.

Привязка шаблона данных изображения к ползунку масштабирования

  • Найдите именованный DataTemplateImageGridView_DefaultItemTemplate и замените **Height**Width значения Grid элемента управления в верхней части шаблона.

    До:

    <DataTemplate x:Key="ImageGridView_DefaultItemTemplate"
                  x:DataType="local:ImageFileInfo">
        <Grid Height="200"
              Width="200"
              Margin="{StaticResource LargeItemMargin}">
    

    После:

    <DataTemplate x:Key="ImageGridView_DefaultItemTemplate"
                  x:DataType="local:ImageFileInfo">
        <Grid Height="{Binding Value, ElementName=ZoomSlider}"
              Width="{Binding Value, ElementName=ZoomSlider}"
              Margin="{StaticResource LargeItemMargin}">
    

Вы заметили, что это Binding выражения, а не x:Bind выражения? Это старый способ создания привязок данных, который практически полностью устарел. x:Bind делает почти все, что Binding делает, и многое другое. Однако при использовании x:Bind в шаблоне данных он привязывается к типу, объявленному в значении x:DataType . Как привязать какой-либо элемент в шаблоне к элементу в коде XAML страницы или в коде программной части? Необходимо использовать старое Binding выражение.

Выражения Binding не распознают значение x:DataType, но у этих выражений Binding есть значения ElementName, которые работают практически так же. Эти значения сообщают модулю привязки, что Binding Value — это привязка к свойству Value указанного элемента на странице (то есть элемента с этим значением x:Name). Если вы хотите привязать к свойству в коде программной части, это будет выглядеть примерно так, где {Binding MyCodeBehindProperty, ElementName=page}page ссылается на x:Name значение, заданное в элементе Page в XAML.

Примечание.

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

И наоборот, по умолчанию привязка x:Bind является однократной, то есть все изменения в привязанном свойстве игнорируются. Такова ситуация по умолчанию, поскольку это наиболее высокопроизводительный вариант, а большинство привязок относятся к статическим данным с доступом только для чтения.

В этом уроке показано, что если вы используете x:Bind со свойствами, которые могут изменить их значения, обязательно добавьте Mode=OneWay или Mode=TwoWay. Примеры этого приведены в следующем разделе.

Запустите приложение и измените размеры шаблона изображения с помощью ползунка. Как видно, результат вполне ощутим без использования больших объемов кода.

Running app with zoom slider showing

Примечание.

Для проблемы попробуйте привязать другие свойства пользовательского интерфейса к свойству ползунка Value масштабирования или другим ползункам, добавленным после ползунка масштабирования. Например, можно привязать свойство FontSize элемента TitleTextBlock к новому ползунку со значением по умолчанию 24. Следует установить минимальное и максимальное значения в разумных пределах.

Часть 4. Улучшение возможностей масштабирования

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

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

Создание свойства ItemSize для обновления пользовательского интерфейса

  1. В MainPage.xaml.cs измените сигнатуру MainPage класса, чтобы он реализовал INotifyPropertyChanged интерфейс.

    До:

    public sealed partial class MainPage : Page
    

    После:

    public sealed partial class MainPage : Page, INotifyPropertyChanged
    

    Таким образом система привязки узнает, что у MainPage есть событие PropertyChanged (добавляется ниже), которое могут ожидать привязки для обновления пользовательского интерфейса.

  2. PropertyChanged Добавьте событие в MainPage класс.

    public event PropertyChangedEventHandler PropertyChanged;
    

    Это событие обеспечивает полную реализацию, необходимую интерфейсом INotifyPropertyChanged . Однако чтобы это событие дало результат, необходимо явным образом породить его в пользовательских свойствах.

  3. ItemSize Добавьте свойство и вызовите PropertyChanged событие в методе задания.

    public double ItemSize
    {
        get => _itemSize;
        set
        {
            if (_itemSize != value)
            {
                _itemSize = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ItemSize)));
            }
        }
    }
    private double _itemSize;
    

    Свойство ItemSize предоставляет значение частного _itemSize поля. Использование резервного поля, подобного этому, позволяет свойству проверка, совпадает ли новое значение со старым значением, прежде чем оно вызывает потенциально ненужное PropertyChanged событие.

    Само событие вызывается методом Invoke . Вопросительный знак проверка указывает, имеет ли PropertyChanged событие значение NULL, т. е. добавляется ли какие-либо обработчики событий. Каждая односторонняя или двухсторонняя привязка незаметно добавляет обработчик событий, но если прослушиватели отсутствуют, никаких дальнейших действий выполнено не будет. Но, если значение события PropertyChanged не нулевое, вызывается метод Invoke со ссылкой на источник события (саму страницу, представленную ключевым словом this) и объект аргументы события, указывающий имя свойства. С помощью этой информации любые одностороннюю или двухстороннюю привязки к ItemSize свойству будут проинформированы о любых изменениях, чтобы они могли обновить привязанный пользовательский интерфейс.

  4. В MainPage.xaml найдите именованный DataTemplateImageGridView_DefaultItemTemplate и замените WidthHeight значения Grid элемента управления в верхней части шаблона. (Если вы выполнили привязку между элементами управления в предыдущей части этого руководства, нужно лишь заменить Value на ItemSize и ZoomSlider на page. Обязательно выполните это для Height и Width!)

    До:

    <DataTemplate x:Key="ImageGridView_DefaultItemTemplate"
                  x:DataType="local:ImageFileInfo">
        <Grid Height="{Binding Value, ElementName=ZoomSlider}"
            Width="{Binding Value, ElementName=ZoomSlider}"
            Margin="{StaticResource LargeItemMargin}">
    

    После:

    <DataTemplate x:Key="ImageGridView_DefaultItemTemplate"
                  x:DataType="local:ImageFileInfo">
        <Grid Height="{Binding ItemSize, ElementName=page}"
              Width="{Binding ItemSize, ElementName=page}"
              Margin="{StaticResource LargeItemMargin}">
    

Теперь, когда пользовательский интерфейс может реагировать на ItemSize изменения, необходимо внести некоторые изменения. Как упоминание ранее, ItemSize значение вычисляется из текущего состояния различных элементов управления пользовательского интерфейса, но вычисление должно выполняться всякий раз, когда эти элементы управления изменяют состояние. Для этого вы будете использовать привязку событий, чтобы определенные изменения пользовательского интерфейса вызывали вспомогательный метод, который обновляет ItemSize.

Обновление значения свойства ItemSize

  1. Добавьте метод в DetermineItemSize MainPage.xaml.cs.

    private void DetermineItemSize()
    {
        if (FitScreenToggle != null
            && FitScreenToggle.IsOn == true
            && ImageGridView != null
            && ZoomSlider != null)
        {
            // The 'margins' value represents the total of the margins around the
            // image in the grid item. 8 from the ItemTemplate root grid + 8 from
            // the ItemContainerStyle * (Right + Left). If those values change,
            // this value needs to be updated to match.
            int margins = (int)this.Resources["LargeItemMarginValue"] * 4;
            double gridWidth = ImageGridView.ActualWidth -
                (int)this.Resources["DefaultWindowSidePaddingValue"];
            double ItemWidth = ZoomSlider.Value + margins;
            // We need at least 1 column.
            int columns = (int)Math.Max(gridWidth / ItemWidth, 1);
    
            // Adjust the available grid width to account for margins around each item.
            double adjustedGridWidth = gridWidth - (columns * margins);
    
            ItemSize = (adjustedGridWidth / columns);
        }
        else
        {
            ItemSize = ZoomSlider.Value;
        }
    }
    
  2. В MainPage.xaml перейдите в начало файла и добавьте SizeChanged привязку события к элементу Page .

    До:

    <Page x:Name="page"
    

    После:

    <Page x:Name="page"
          SizeChanged="{x:Bind DetermineItemSize}"
    
  3. Найдите Slider с именем ZoomSlider (в разделе Page.Resources) и добавьте привязку событий ValueChanged.

    До:

    <Slider x:Name="ZoomSlider"
    

    После:

    <Slider x:Name="ZoomSlider"
            ValueChanged="{x:Bind DetermineItemSize}"
    
  4. Найдите именованную ToggleSwitchFitScreenToggle и добавьте привязку Toggled события.

    До:

    <ToggleSwitch x:Name="FitScreenToggle"
    

    После:

    <ToggleSwitch x:Name="FitScreenToggle"
                  Toggled="{x:Bind DetermineItemSize}"
    

Запустите приложение и измените размеры шаблона изображения с помощью ползунка масштабирования и переключателя Fit to screen (По размеру экрана). Как видно, последние изменения повышают удобство масштабирования и изменения размера, не нарушая организацию кода.

Running app with fit-to-screen enabled

Примечание.

Для вызова попробуйте добавить после TextBlockZoomSlider свойства и привязать Text свойство к свойству ItemSize . Так как он не находится в шаблоне данных, его можно использовать x:Bind вместо Binding предыдущих ItemSize привязок.

Часть 5. Включение правок пользователей

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

Чтобы добиться этого, вы обновите существующий DetailPage, который предоставляет средство просмотра с одним изображением, элемент управления масштабированием и пользовательский интерфейс редактирования.

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

Подключение DetailPage

  1. В файле MainPage.xaml найдите GridView с именем ImageGridView. Чтобы сделать элементы доступными для щелчка, задайте для параметраIsItemClickEnabled значение True и добавьте обработчик событий ItemClick.

    Совет

    Если изменение ввести ниже, а не скопировать и вставить, отобразится всплывающее окно IntelliSense с текстом <New Event Handler> (Новый обработчик событий). При нажатии клавиши TAB в качестве значения будет использовано имя обработчика метода по умолчанию, а метод, показанный в следующем шаге, будет автоматически снабжен заглушкой. Затем можно перейти к методу в коде программной части с помощью клавиши F12.

    До:

    <GridView x:Name="ImageGridView">
    

    После:

    <GridView x:Name="ImageGridView"
              IsItemClickEnabled="True"
              ItemClick="ImageGridView_ItemClick">
    

    Примечание.

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

  2. В файле MainPage.xaml.cs добавьте обработчик событий (или заполните его, если вы воспользовались подсказкой на предыдущем шаге).

    private void ImageGridView_ItemClick(object sender, ItemClickEventArgs e)
    {
        this.Frame.Navigate(typeof(DetailPage), e.ClickedItem);
    }
    

    Этот метод переходит на страницу сведений, передает выбранный пользователем элемент, который является объектом ImageFileInfo, используемым методом DetailPage.OnNavigatedTo для инициализации страницы. В этом руководстве вам не придется реализовывать данный метод, но вы можете ознакомиться с его функциями.

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

Подключив две страницы, запустите приложение и изучите его. Работает все, кроме элементов управления на панели редактирования: они не реагируют при попытке изменения значений.

Как видно, в текстовом поле названия отображается название, а также его можно изменить. Для подтверждения изменений необходимо перевести фокус на другой элемент управления, но название в верхнем левом углу экрана еще не обновляется.

Все элементы управления уже привязаны с помощью простых x:Bind выражений, которые мы рассмотрели в части 1. Если вы помните, это значит, что все эти привязки являются однократными, поэтому изменения значений не регистрируются. Чтобы устранить эту проблему, нужно лишь превратить их в двухсторонние привязки.

Обеспечение интерактивности элементов управления для редактирования

  1. В файле DetailPage.xaml найдите TextBlock с именем TitleTextBlock и элемент управления RatingControl после него, а затем измените их выражения x:Bind, чтобы они включали в себя Mode=TwoWay.

    До:

    <TextBlock x:Name="TitleTextBlock"
               Text="{x:Bind item.ImageTitle}"
               ... >
    <muxc:RatingControl Value="{x:Bind item.ImageRating}"
                            ... >
    

    После:

    <TextBlock x:Name="TitleTextBlock"
               Text="{x:Bind item.ImageTitle, Mode=TwoWay}"
               ... >
    <muxc:RatingControl Value="{x:Bind item.ImageRating, Mode=TwoWay}"
                            ... >
    
  2. Сделайте то же самое для всех ползунков эффектов, расположенных после элемента управления оценкой.

    <Slider Header="Exposure"    ... Value="{x:Bind item.Exposure, Mode=TwoWay}" ...
    <Slider Header="Temperature" ... Value="{x:Bind item.Temperature, Mode=TwoWay}" ...
    <Slider Header="Tint"        ... Value="{x:Bind item.Tint, Mode=TwoWay}" ...
    <Slider Header="Contrast"    ... Value="{x:Bind item.Contrast, Mode=TwoWay}" ...
    <Slider Header="Saturation"  ... Value="{x:Bind item.Saturation, Mode=TwoWay}" ...
    <Slider Header="Blur"        ... Value="{x:Bind item.Blur, Mode=TwoWay}" ...
    

Двухсторонний режим ожидаемо означает, что данные перемещаются в обоих направлениях при внесении изменений на любой из сторон.

Как и односторонние привязки, описанные ранее, эти двусторонние привязки теперь обновляют пользовательский интерфейс всякий раз, когда изменяются привязанные свойства, благодаря INotifyPropertyChanged реализации в ImageFileInfo классе. Однако при двухсторонней привязке значения также будут перемещаться из пользовательского интерфейса в привязанные свойства при каждом взаимодействии пользователя с элементом управления. На стороне XAML больше не требуется никаких действий.

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

Часть 6. Форматирование значений с помощью привязки функции

Остается последняя проблема. При перемещении ползунков эффектов метки рядом с ними не меняются.

Effect sliders with default label values

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

Привязка меток ползунков эффектов и форматирование отображаемых значений

  1. TextBlock Найдите ползунок Exposure после ползунка и замените Text значение выражением привязки, показанным здесь.

    До:

    <Slider Header="Exposure" ... />
    <TextBlock ... Text="0.00" />
    

    После:

    <Slider Header="Exposure" ... />
    <TextBlock ... Text="{x:Bind item.Exposure.ToString('N', culture), Mode=OneWay}" />
    

    Это называется привязкой функции, так как выполняется привязка к возвращаемому значению метода. Метод должен быть доступен с помощью кода страницы или x:DataType типа, если вы находитесь в шаблоне данных. В этом случае метод — это знакомый метод .NET ToString , к которому осуществляется доступ через свойство элемента страницы, а затем через Exposure свойство элемента. (В этом примере показано, как выполнить привязку к методам и свойствам, расположенным глубоко в цепи связей.)

    Привязка функции — отличный способ форматирования отображаемых значений, так как при этом можно передать другие источники привязки, например аргументы методов, а выражение привязки будет ожидать передачи этих значений, как и должно происходить в одностороннем режиме. В этом примере аргумент culture является ссылкой на неизменное поле, реализованное в коде программной части, но он также мог бы быть свойством, вызывающим события PropertyChanged. В этом случае любые изменения значения свойства могут вызвать x:Bind выражение ToString с новым значением, а затем обновить пользовательский интерфейс с результатом.

  2. Сделайте то же самое для TextBlock, служащих метками для других ползунков эффектов.

    <Slider Header="Temperature" ... />
    <TextBlock ... Text="{x:Bind item.Temperature.ToString('N', culture), Mode=OneWay}" />
    
    <Slider Header="Tint" ... />
    <TextBlock ... Text="{x:Bind item.Tint.ToString('N', culture), Mode=OneWay}" />
    
    <Slider Header="Contrast" ... />
    <TextBlock ... Text="{x:Bind item.Contrast.ToString('N', culture), Mode=OneWay}" />
    
    <Slider Header="Saturation" ... />
    <TextBlock ... Text="{x:Bind item.Saturation.ToString('N', culture), Mode=OneWay}" />
    
    <Slider Header="Blur" ... />
    <TextBlock ... Text="{x:Bind item.Blur.ToString('N', culture), Mode=OneWay}" />
    

Теперь при запуске приложения все работает, включая метки ползунков.

Effect sliders with working labels

Заключение

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

Примером задачи, которую невозможно решить с помощью привязки, служит ситуация, когда элемент управления лишен подходящих для привязки свойств, например если это функция масштабирования страницы сведений. Этот ползунок масштабирования должен взаимодействовать с ScrollViewer изображением, но ScrollViewer его можно обновить только с помощью его ChangeView метода. В этом случае мы используем стандартные обработчики событий, чтобы обеспечить синхронизацию ScrollViewer и ползунка масштабирования. Подробные сведения приведены в разделах, посвященных методам ZoomSlider_ValueChanged и MainImageScroll_ViewChanged в DetailPage.

Тем не менее, привязка — эффективный и универсальный способ упростить код и отделить логику пользовательского интерфейса от логики данных. Это поможет вам в регулировке обоих типов логики и сократит вероятность появления в них ошибок.

Одним из примеров разделения пользовательского интерфейса и данных является ImageFileInfo.ImageTitle свойство. Это свойство (и ImageRating свойство) немного отличается от свойства, созданного ItemSize в части 4, так как значение хранится в метаданных файла (предоставляемых через ImageProperties тип) вместо поля. Кроме того, ImageTitle возвращает ImageName значение (задайте имя файла), если в метаданных файла нет заголовка.

public string ImageTitle
{
    get => String.IsNullOrEmpty(ImageProperties.Title) ? ImageName : ImageProperties.Title;
    set
    {
        if (ImageProperties.Title != value)
        {
            ImageProperties.Title = value;
            var ignoreResult = ImageProperties.SavePropertiesAsync();
            OnPropertyChanged();
        }
    }
}

Как видно, средство задания обновляет ImageProperties.Title свойство, а затем вызывает SavePropertiesAsync запись нового значения в файл. (Это асинхронный метод, но мы не можем использовать ключевое слово await в свойстве и не рекомендуем делать это вам, так как методы получения и задания свойств должны быть завершены немедленно. Поэтому вместо этого нужно вызвать метод и игнорировать возвращаемый объект Task.)

Дальнейшее продвижение в этом направлении

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

Как вы могли заметить, при изменении уровня масштабирования на странице сведений он автоматически сбрасывается при возврате и повторном выборе того же изображения. Сможете ли вы разобраться, как сохранить и восстановить уровень масштабирования отдельно для каждого изображения? Всего хорошего!

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