Общие сведения о привязке данных

Завершено

Tech logo of U W P and W P F.

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

Tech logo of U W P and W P F. W P F appears dimmed.

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

Если он еще не запущен, откройте Visual Studio. Создайте универсальный проект C# Windows с помощью шаблона Пустое приложение (универсальное приложение для Windows). Назовите его DatabindingSample. Этот проект является тем, с которым вы будете работать во время всего модуля пользовательского интерфейса и данных.

Screenshot of the Visual Studio Create a new project dialog box.

При нажатии кнопки "ОК" Visual Studio предложит ввести целевые и минимальные версии Windows. Этот проект является только проектом упражнений, и вы не планируете развертывать его на компьютерах с более старой версией Windows. Поэтому можно выбрать последнюю версию Windows как для минимальной, так и целевой версии, а затем нажмите кнопку ОК.

2. Добавление TextBlock для отображения часов

Когда проект будет полностью инициализирован и загружен, откройте файл MainPage.xaml, дважды щелкнув его в обозревателе решений.

Совет

Если у вас мало места на экране, используйте раскрывающийся список в левом верхнем углу, чтобы включить в редакторе имитацию экрана с более низким разрешением. Для этого модуля рекомендуется использовать рабочий стол 13,3" (1280 x 720) в масштабе 100 %, но вы можете выбрать тот, который для вас более удобен.

Добавьте следующую строку между открывающим и закрывающим тегами элемента Grid.

<TextBlock HorizontalAlignment="Right" 
           Margin="10" 
           Text="{x:Bind CurrentTime}" />

Это создает новый TextBlock в правой верхней части окна с 10-единицным полем с края. Далее давайте разберемся с адресом TextTextBlock.

Часть Text={x:Bind CurrentTime} — это первая встреча с привязкой данных. x:Bind — это расширение разметки XAML, компилируемое в код C# вместе с остальной частью приложения. Здесь оно соединяет свойство Text элемента TextBlock со свойством CurrentTime. Если вы попытаетесь скомпилировать проект сейчас, вы получите следующее сообщение об ошибке:

Ошибка XamlCompiler WMC1110: недопустимый путь привязки CurrentTime: свойство CurrentTime не удается найти в типе MainPage.

Это означает, что в компиляторе отсутствует свойство CurrentTime из MainPage. После создания этого свойства его содержимое отображается в TextBlock правом верхнем углу.

Примечание.

UWP также поддерживает более старый метод привязки данных, который выглядит следующим образом: Text={Bind CurrentTime}. Этот старый метод работает несколько иначе, чем {x:Bind}. Прежде всего, он не проверяет ошибки во время компиляции и не выводит предупреждение при возникновения опечатки. В этом модуле мы сосредоточимся исключительно на новом способе привязки {x:Bind}, который обеспечивает проверку ошибок во время компиляции. Тем не менее, {x:Bind} менее зрелый, чем {Bind}и получает новые функции, поэтому мы решили перейти с последней версией Windows для создания проекта.

3. Создание CurrentTime свойства

Откройте файл MainPage.xaml.cs и добавьте в класс MainPage следующее определение свойства.

public string CurrentTime => DateTime.Now.ToLongTimeString();

Если вы не знакомы с приведенным выше синтаксисом, он называется элементом, воплощающим выражение. Он впервые появился в C# 6.0 и является сокращенным вариантом следующего:

public string CurrentTime 
{
    get { return DateTime.Now.ToLongTimeString(); }
}

4. Запуск приложения

Теперь при запуске приложения (с помощью клавиши F5 или команды Отладка/Начать отладку в меню) оно компилируется и запускается. Похоже, все работает как надо. Текущее время отображается в правом верхнем углу.

Screenshot of the running app with the clock.

Однако что-то не так, потому что часы не обновляются. Это зависло в то время, когда приложение было запущено. Как приложение будет знать, когда обновить значение в TextBlock? Мы должны указать среде выполнения UWP обновлять его один раз в секунду.

5. Указание режимов привязки

Привязки {x:Bind} высоко оптимизированы для производительности. Это означает, что они ничего не делают, что разработчик не попросил явно. Поэтому по умолчанию привязка {x:Bind} вычисляет источник привязки (в нашем случае свойство CurrentTime) только один раз. Эта привязка называется OneTime привязкой. Если мы хотим, чтобы платформа UWP продолжала обновлять пользовательский интерфейс, необходимо явно указать другой режим привязки: OneWay или TwoWay.

Режим привязки TwoWay указывает на двунаправленную привязку между кодом C# (логикой) и пользовательским интерфейсом. Этот тип привязки будет полезен позже, когда мы привязымся к элементам управления, которым пользователь может управлять. Но для TextBlockпривязки предпочтительнее, OneWay так как изменения данных будут возникать только в коде, никогда не в пользовательском интерфейсе.

Чтобы указать режим привязки OneWay для элемента TextBlock, измените {x:Bind CurrentTime} на {x:Bind CurrentTime, Mode=OneWay}. Теперь весь TextBlock тег в ней Grid должен выглядеть как эта разметка.

<TextBlock HorizontalAlignment="Right" 
           Margin="10" 
           Text="{x:Bind CurrentTime, Mode=OneWay}" />

Привязка указывает среде выполнения UWP создать необходимую инфраструктуру для отслеживания изменений CurrentTime в свойстве и отразить ее в Text элементе TextBlock. Эта дополнительная инфраструктура использует незначительный объем ресурсов памяти и число тактов центрального процессора, поэтому она не используется по умолчанию.

Если вы запустите приложение сейчас, часы все равно не будут обновляться. Нам нужно уведомить систему, что свойство CurrentTime изменилось.

6. Реализация INotifyPropertyChanged интерфейса

Это уведомление происходит через интерфейс INotifyPropertyChanged. Это простой интерфейс с одним событием.

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

Любой класс с простым свойством C# в качестве источника привязки данных должен реализовать интерфейс INotifyPropertyChanged. Этот класс должен инициировать событие PropertyChanged, когда пользовательский интерфейс нужно обновить. Продолжим и добавим интерфейс в объявление класса MainPage.

public sealed partial class MainPage : Page, INotifyPropertyChanged

Мы также должны реализовать интерфейс, добавив событие PropertyChanged в класс.

public event PropertyChangedEventHandler PropertyChanged;

7. Вызов события каждую секунду PropertyChanged

Осталось настроить вызов события PropertyChanged каждый раз, когда мы хотим обновить часы (то есть каждую секунду). Для начала давайте объявим DispatcherTimer объект в MainPage классе.

private DispatcherTimer _timer;

Теперь давайте настроим это в конструкторе (после вызова InitializeComponent) так, чтобы этот объект срабатывал каждую секунду.

_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };

_timer.Tick += (sender, o) =>
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));

_timer.Start();

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

PropertyChanged?.Invoke является сокращением для проверки того, имеет ли событие нулевое значение. Если значение отличается, это событие вызывается. Как и в большинстве событий, первым аргументом является отправитель (this). Вторым аргументом для события PropertyChanged является недавно созданный объект PropertyChangedEventArgs, конструктор которого ожидает строку в качестве имени свойства. Таким образом, подписчики события PropertyChanged (в данном случае система UWP) получат имя обновленного свойства и смогут действовать соответствующим образом.

Совет

Не используйте для имени свойства строковые литералы (например, "CurrentTime"). При пользовании строки часто возникают опечатки, что может привести к проблемам, которые трудно устранить, когда пользовательский интерфейс не обновляется. Кроме того, случайное переименование свойства может также привести к ошибкам, если строковые константы не обновляются. Мы советуем всегда использовать выражение nameof, которое не восприимчиво к опечаткам и поддерживает переименование.

Весь файл MainPage.xaml.cs должен выглядеть следующим образом:

namespace DatabindingSample
{
    public sealed partial class MainPage : Page, INotifyPropertyChanged
    {
        private DispatcherTimer _timer;

        public MainPage()
        {
            this.InitializeComponent();
            _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };

            _timer.Tick += (sender, o) =>
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));

            _timer.Start();
        }

        public string CurrentTime => DateTime.Now.ToLongTimeString();
        public event PropertyChangedEventHandler PropertyChanged;
    }
}

8. Запуск приложения

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

9. Сводка

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

Tech logo of U W P and W P F. U W P appears dimmed.

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

Если он еще не запущен, откройте Visual Studio. Создайте проект WPF C# с помощью шаблона приложения WPF. Назовите его DatabindingSampleWPF, а затем нажмите кнопку OK. Этот проект является тем, с которым вы будете работать во время всего модуля пользовательского интерфейса и данных.

Screenshot of the Visual Studio Create a new WPF project dialog box.

2. Создание класса Clock

Так как нам нужно отобразить текущее время, сначала нужно создать класс Clock. В обозревателе решений щелкните проект DatabindingSampleWPF правой кнопкой мыши, выберите Добавить/Класс и введите Clock в качестве имени класса.

Скопируйте следующий код в созданный файл:

using System;

namespace DatabindingSampleWPF
{
    public class Clock
    {
        public string CurrentTime => DateTime.Now.ToLongTimeString();
    }
}

Если вы не знакомы с приведенным выше синтаксисом для свойства CurrentTime, он называется элементом, воплощающим выражение. Он впервые появился в C# 6.0 и является сокращенным вариантом следующего:

public string CurrentTime 
{
    get { return DateTime.Now.ToLongTimeString(); }
}

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

3. Добавление TextBlock для отображения часов

Если вы MainWindow.xaml открыли в Visual Studio, выберите ее вкладку. Если нет, его можно открыть, дважды щелкнув его в Обозреватель решений.

Добавьте следующую строку между открывающим и закрывающим тегами элемента Grid.

<TextBlock HorizontalAlignment="Right" 
           VerticalAlignment="Top"
           Margin="10" 
           Text="{Binding CurrentTime}">
    <TextBlock.DataContext>
        <local:Clock/>
    </TextBlock.DataContext>
</TextBlock>

Эта разметка создаст новую TextBlock в правой верхней части окна с полем 10 единиц с края.

Часть Text="{Binding CurrentTime}" — это первая встреча с привязкой данных. {Binding} является расширением разметки XAML. Здесь оно соединяет свойство Text элемента TextBlock со свойством CurrentTime. Вопрос в том, к какому объекту относится свойство CurrentTime?

Объект, к которому относится привязка данных, создается в свойстве DataContext элемента TextBlock. Таким образом, приведенный выше код XAML не только создает элемент управления TextBlock, но и создает объект Clock. Кроме того, код привязывает Text свойство создаваемого Clock объекта к CurrentTime свойствуTextBlock. Свойство CurrentTime называется источником привязки, а свойство Text называется назначением привязки.

4. Запуск приложения

Теперь при запуске приложения (с помощью клавиши F5 или команды Отладка/Начать отладку в меню) оно компилируется и запускается. Похоже, все работает как надо. Текущее время отображается в правом верхнем углу.

Screenshot of the running app with the clock.

Однако что-то не так, потому что часы не обновляются. Это зависло в то время, когда приложение было запущено. Как приложение будет знать, когда обновить значение в TextBlock? Мы должны указать среде выполнения WPF обновлять его один раз в секунду.

Другими словами, нам нужно уведомить систему, что свойство CurrentTime изменилось.

5. Реализация INotifyPropertyChanged интерфейса

Это уведомление происходит через интерфейс INotifyPropertyChanged. Это простой интерфейс с одним событием.

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

Любой класс с простым свойством C# в качестве источника привязки данных должен реализовать интерфейс INotifyPropertyChanged. Этот класс должен инициировать событие PropertyChanged, когда пользовательский интерфейс нужно обновить. Продолжим и добавим интерфейс в объявление класса Clock.

using System.ComponentModel;

public class Clock : INotifyPropertyChanged
{

Мы также должны реализовать интерфейс, добавив событие PropertyChanged в класс.

public event PropertyChangedEventHandler? PropertyChanged;

6. Вызов события каждую секунду PropertyChanged

Осталось настроить вызов события PropertyChanged каждый раз, когда мы хотим обновить часы (то есть каждую секунду). Чтобы начать, давайте добавим System.Windows.Threading пространство usingимен в s и объявим DispatcherTimer объект в Clock классе.

private DispatcherTimer _timer;

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

public Clock()
{
    _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };

    _timer.Tick += (sender, o) => PropertyChanged?.Invoke(this,
            new PropertyChangedEventArgs(nameof(CurrentTime)));

    _timer.Start();
}

Первая строка в конструкторе создает таймер с интервалом в одну секунду, а последняя строка запускает его. Рассмотрим, что происходит при срабатывании таймера (во второй строке).

PropertyChanged?.Invoke является сокращением для проверки того, имеет ли событие нулевое значение. Если значение отличается, это событие вызывается. Как и в большинстве событий, первым аргументом является отправитель (this). Вторым аргументом для события PropertyChanged является недавно созданный объект PropertyChangedEventArgs, конструктор которого ожидает строку в качестве имени свойства. Таким образом, подписчики события PropertyChanged (в данном случае система WPF) получат имя обновленного свойства и смогут действовать соответствующим образом.

Совет

Не используйте для имени свойства строковые литералы (например, "CurrentTime"). При пользовании строки часто возникают опечатки, что может привести к проблемам, которые трудно устранить, когда пользовательский интерфейс не обновляется. Кроме того, случайное переименование свойства может также привести к ошибкам, если строковые константы не обновляются. Рекомендуется всегда использовать nameof выражение, которое является иммунным от опечаток и может следовать операциям переименования.

Все Clock.cs должно выглядеть следующим образом:

namespace DatabindingSampleWPF
{
    using System;
    using System.ComponentModel;
    using System.Windows.Threading;

    public class Clock : INotifyPropertyChanged
    {
        private DispatcherTimer _timer;

        public string CurrentTime => DateTime.Now.ToLongTimeString();

        public event PropertyChangedEventHandler PropertyChanged;

        public Clock()
        {
            // setup _timer to refresh CurrentTime
            _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
            _timer.Tick += (sender, o) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
            _timer.Start();
        }
    }
}

7. Запуск приложения

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

8. Сводка

Теперь вы знаете, как использовать {Binding} для создания быстрого, автоматического метода получения данных из кода в пользовательском интерфейсе приложения WPF. Вы также знакомы с интерфейсом INotifyPropertyChanged . Этот интерфейс позволяет приложению уведомлять платформу WPF об изменении свойства привязки данных и необходимости обновления пользовательского интерфейса.