Общие сведения о привязке данных
На этом занятии вы узнаете, как создать приложение, показывающее текущее время. В уроке представлены основы привязки данных, получение данных из кода в пользовательский интерфейс приложения и обновление его для обновления часов в пользовательском интерфейсе. Этот урок формирует основу для более сложных задач привязки данных в последующих уроках. Давайте приступим.
1. Создание проекта
Если он еще не запущен, откройте Visual Studio. Создайте универсальный проект C# Windows с помощью шаблона Пустое приложение (универсальное приложение для Windows). Назовите его DatabindingSample. Этот проект является тем, с которым вы будете работать во время всего модуля пользовательского интерфейса и данных.
При нажатии кнопки "ОК" 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-единицным полем с края. Далее давайте разберемся с адресом Text
TextBlock
.
Часть 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 или команды Отладка/Начать отладку в меню) оно компилируется и запускается. Похоже, все работает как надо. Текущее время отображается в правом верхнем углу.
Однако что-то не так, потому что часы не обновляются. Это зависло в то время, когда приложение было запущено. Как приложение будет знать, когда обновить значение в 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 об изменении свойства привязки данных и необходимости обновления пользовательского интерфейса.
1. Создание проекта
Если он еще не запущен, откройте Visual Studio. Создайте проект WPF C# с помощью шаблона приложения WPF. Назовите его DatabindingSampleWPF, а затем нажмите кнопку OK. Этот проект является тем, с которым вы будете работать во время всего модуля пользовательского интерфейса и данных.
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 или команды Отладка/Начать отладку в меню) оно компилируется и запускается. Похоже, все работает как надо. Текущее время отображается в правом верхнем углу.
Однако что-то не так, потому что часы не обновляются. Это зависло в то время, когда приложение было запущено. Как приложение будет знать, когда обновить значение в 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 об изменении свойства привязки данных и необходимости обновления пользовательского интерфейса.