Общие сведения о привязке данных (WPF .NET)

Привязка данных в Windows Presentation Foundation (WPF) — это простой и последовательный способ представления данных и взаимодействия с ними для приложений. Элементы можно связывать с данными из различных источников в виде объектов .NET и XML. Любые ContentControl, такие как Button, и любые ItemsControl, такие как ListBox и ListView, имеют встроенную функциональность, позволяющую гибко стилизовать отдельные элементы данных или коллекции элементов данных. Представления сортировки, фильтрации и группировки могут быть организованы поверх данных.

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

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

Важно!

Документация по рабочему столу для .NET 7 и .NET 6 находится в стадии разработки.

Что такое привязка данных?

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

Привязка к данным обычно используется для того, чтобы поместить серверные или локальные данные конфигурации в формы или другие элементы управления пользовательского интерфейса. В WPF в эту концепцию добавлена привязка широкого диапазона свойств к различным источникам данных. В WPF свойства зависимости элементов можно привязать к объектам .NET (включая объекты ADO.NET или объекты, связанные с веб-службами и веб-свойствами) и к данным XML.

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

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

Diagram that shows the basic data binding model.

Как показано на рисунке, привязка данных является, по существу, мостом между целью привязки и источником привязки. На рисунке представлены следующие основные концепции привязки данных WPF.

  • Как правило, каждая привязка состоит из четырех компонентов:

    • целевой объект привязки;
    • целевое свойство;
    • источник привязки;
    • путь к значению в источнике привязки для использования.

    Например, если вы привязываете содержимое объекта TextBox к свойству Employee.Name, следует настроить привязку с параметрами, приведенными в следующей таблице:

    Параметр Значение
    Назначение TextBox
    Целевое свойство Text
    Исходный объект Employee
    Путь к значению исходного объекта Name
  • Целевое свойство должно быть свойством зависимостей.

    Большинство свойств UIElement являются свойствами зависимостей, а большинство свойств зависимостей, за исключением доступных только для чтения, по умолчанию поддерживают привязку данных. Определять свойства зависимостей могут только типы, производные от DependencyObject. Все типы UIElement являются производными от DependencyObject.

  • Источники привязки не ограничиваются пользовательскими объектами .NET.

    Хотя это не показано на изображении, следует отметить, что объект источника привязки не обязательно должен быть пользовательским объектом .NET. Привязка данных WPF поддерживает данные в виде объектов .NET, XML и даже объектов элементов XAML. Чтобы предоставить несколько примеров, источником привязки может быть UIElement, любой объект списка, объект ADO.NET или веб-служб или XmlNode, содержащий XML-данные. Дополнительные сведения см. в разделе Общие сведения об источниках привязки.

Важно помнить, что при связывании целевой объект привязки привязывается к источнику привязки. Например, при отображении некоторых базовых данных XML в ListBox с помощью привязки данных вы привязываете ListBox к XML-данным.

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

Контекст данных

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

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

При изменении свойства DataContext все привязки, на которые может повлиять контекст данных, оцениваются повторно.

Направление потока данных

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

Возможно, требуется, чтобы в приложении пользователи могли изменить данные и передать их обратно объекту источника. Или может потребоваться не предоставлять пользователям возможности обновления источника данных. Для управления потоком данных можно задать Binding.Mode.

На следующем рисунке показаны различные типы потоков данных.

Data binding data flow

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

  • ПривязкаTwoWay приводит к изменению либо исходного свойства, либо целевого свойства для автоматического обновления другого свойства. Этот тип привязки подходит для изменяемых форм или других полностью интерактивных сценариев пользовательского интерфейса. Большинство свойств по умолчанию применяют привязку OneWay, но некоторые свойства зависимостей (обычно такие свойства пользовательских элементов управления, как TextBox.Text и CheckBox.IsChecked) по умолчанию имеют значение привязки TwoWay.

    Программный способ определить, привязывает ли свойство зависимостей односторонняя или двухсторонняя по умолчанию, — получить метаданные свойства.DependencyProperty.GetMetadata Возвращаемый тип этого метода, PropertyMetadataкоторый не содержит метаданных о привязке. Однако если этот тип можно привести к производнойFrameworkPropertyMetadata, логическое значение FrameworkPropertyMetadata.BindsTwoWayByDefault свойства можно проверка. В следующем примере кода показано получение метаданных для TextBox.Text свойства:

    public static void PrintMetadata()
    {
        // Get the metadata for the property
        PropertyMetadata metadata = TextBox.TextProperty.GetMetadata(typeof(TextBox));
    
        // Check if metadata type is FrameworkPropertyMetadata
        if (metadata is FrameworkPropertyMetadata frameworkMetadata)
        {
            System.Diagnostics.Debug.WriteLine($"TextBox.Text property metadata:");
            System.Diagnostics.Debug.WriteLine($"  BindsTwoWayByDefault: {frameworkMetadata.BindsTwoWayByDefault}");
            System.Diagnostics.Debug.WriteLine($"  IsDataBindingAllowed: {frameworkMetadata.IsDataBindingAllowed}");
            System.Diagnostics.Debug.WriteLine($"        AffectsArrange: {frameworkMetadata.AffectsArrange}");
            System.Diagnostics.Debug.WriteLine($"        AffectsMeasure: {frameworkMetadata.AffectsMeasure}");
            System.Diagnostics.Debug.WriteLine($"         AffectsRender: {frameworkMetadata.AffectsRender}");
            System.Diagnostics.Debug.WriteLine($"              Inherits: {frameworkMetadata.Inherits}");
        }
    
        /*  Displays:
         *  
         *  TextBox.Text property metadata:
         *    BindsTwoWayByDefault: True
         *    IsDataBindingAllowed: True
         *          AffectsArrange: False
         *          AffectsMeasure: False
         *           AffectsRender: False
         *                Inherits: False
        */
    }
    
    Public Shared Sub PrintMetadata()
    
        Dim metadata As PropertyMetadata = TextBox.TextProperty.GetMetadata(GetType(TextBox))
        Dim frameworkMetadata As FrameworkPropertyMetadata = TryCast(metadata, FrameworkPropertyMetadata)
    
        If frameworkMetadata IsNot Nothing Then
    
            System.Diagnostics.Debug.WriteLine($"TextBox.Text property metadata:")
            System.Diagnostics.Debug.WriteLine($"  BindsTwoWayByDefault: {frameworkMetadata.BindsTwoWayByDefault}")
            System.Diagnostics.Debug.WriteLine($"  IsDataBindingAllowed: {frameworkMetadata.IsDataBindingAllowed}")
            System.Diagnostics.Debug.WriteLine($"        AffectsArrange: {frameworkMetadata.AffectsArrange}")
            System.Diagnostics.Debug.WriteLine($"        AffectsMeasure: {frameworkMetadata.AffectsMeasure}")
            System.Diagnostics.Debug.WriteLine($"         AffectsRender: {frameworkMetadata.AffectsRender}")
            System.Diagnostics.Debug.WriteLine($"              Inherits: {frameworkMetadata.Inherits}")
    
            '  Displays:
            '
            '  TextBox.Text property metadata:
            '    BindsTwoWayByDefault: True
            '    IsDataBindingAllowed: True
            '          AffectsArrange: False
            '          AffectsMeasure: False
            '           AffectsRender: False
            '                Inherits: False
        End If
    
    
    End Sub
    
  • OneWayToSource — это обратная привязка OneWay; она обновляет свойство источника при изменении свойства цели. Одним из примеров является пересчет исходного значения из пользовательского интерфейса.

  • На изображении не показана привязка OneTime, которая приводит к инициализации целевого свойства исходным свойством, но последующие изменения при этом не распространяются. Если в контексте данных производятся изменения или меняется объект, это изменение не отражается в свойстве цели. Этот тип привязки подходит, если приемлемо использовать снимок текущего состояния или данные действительно являются статичными. Кроме того, этот тип привязки полезен, если нужно инициализировать целевое свойство с использованием какого-либо значения из исходного свойства, а контекст данных заранее неизвестен. Этот режим, по сути, — упрощенная форма привязки OneWay, которая обеспечивает более высокую производительность в случаях, когда значение в источнике не меняется.

Чтобы обнаружить изменения источника (применимые к привязкам OneWay и TwoWay), источник должен реализовать подходящий механизм уведомления об изменении свойств, например INotifyPropertyChanged. См. практическое руководство по реализации уведомления об изменении свойства (.NET Framework) в качестве примера реализации INotifyPropertyChanged.

Свойство Binding.Mode содержит дополнительные сведения о режимах привязки и пример того, как указать направление привязки.

Что инициирует обновления источника

Привязки, которые являются TwoWay или OneWayToSource, ожидают изменений в свойстве цели и распространяют их обратно в источник, что называется обновлением источника. Например, можно изменять текст элемента TextBox для изменения базового значение источника.

Но обновляется ли значение в источнике во время изменения текста или когда изменения текста завершается, а элемент управления теряет фокус? Свойство Binding.UpdateSourceTrigger определяет, что инициирует обновление источника. Точки правой стрелки на следующем рисунке иллюстрируют роль свойства Binding.UpdateSourceTrigger.

Diagram that shows the role of the UpdateSourceTrigger property.

Если значение UpdateSourceTrigger равно UpdateSourceTrigger.PropertyChanged, то значение, на которое указывает стрелка вправо TwoWay или привязки OneWayToSource, обновляется, как только изменяется свойство цели. Однако если значение UpdateSourceTrigger равно LostFocus, то это значение обновляется с новым значением, только когда целевое свойство теряет фокус.

Как и в случае со свойством Mode, различные свойства зависимостей имеют разные значения UpdateSourceTrigger по умолчанию. Значение по умолчанию для большинства свойств зависимостей — PropertyChanged, что приводит к мгновенному изменению значения свойства источника при изменении значения свойства целевого объекта. Мгновенное изменение подходит для CheckBox и других простых элементов управления. Однако для текстовых полей обновления после каждого нажатия клавиши уменьшают производительность и не дают пользователю обычной возможности удаления предыдущего символа и исправления ошибок ввода до того, как новое значение будет зафиксировано. Например, для свойства TextBox.Text по умолчанию значение UpdateSourceTrigger равно LostFocus, что приводит к изменению исходного значения только при потере фокуса элементом управления, а не при изменении свойства TextBox.Text. Сведения о том, как найти значение свойства зависимости по умолчанию, см. на странице свойств UpdateSourceTrigger.

В следующей таблице приведен пример сценария для каждого значения UpdateSourceTrigger с использованием TextBox в качестве примера.

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

Пример см. в статье Практическое руководство. Управление обновлением источника из поля TextBox (.NET Framework).

Пример привязки данных

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

Data binding sample screenshot

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

  • Содержимое ListBox привязано к коллекции объектов AuctionItem. Объект AuctionItem имеет такие свойства, как Description, StartPrice, StartDate, Category и SpecialFeatures.

  • Данные (объекты AuctionItem), отображаемые в ListBox, шаблонизированы таким образом, чтобы описание и текущая цена были показаны для каждого элемента. Шаблон создается с помощью DataTemplate. Кроме того, внешний вид каждого элемента зависит от значения SpecialFeatures отображаемого объекта AuctionItem. Если значением SpecialFeatures объекта AuctionItem является Color, элемент имеет синюю границу. Если значением является Highlight, элемент имеет оранжевые границы и помечается звездочкой. Раздел Создание шаблонов данных содержит сведения о создании шаблонов данных.

  • Пользователь может группировать, фильтровать или сортировать данные с помощью предоставленного CheckBoxes. На изображении выше выбраны CheckBoxesГруппировать по категориям и Сортировать по категориям и дате. Можно увидеть, что данные группируются по категориям продуктов, а имена категорий приводятся в алфавитном порядке. На изображении трудно заметить, что элементы в каждой категории также сортируются по начальной дате. Сортировка выполняется с помощью представления коллекции. Представления коллекций рассматриваются в разделе Привязка к коллекциям.

  • Когда пользователь выбирает элемент, ContentControl отображает сведения о выбранном элементе. Этот процесс называется Сценарий отношения "основной — подробности". Сведения об этом типе привязки см. в разделе Сценарий "основной — подробности".

  • Типом свойства StartDate является DateTime, который возвращает дату, включая время с точностью до миллисекунды. В этом приложении пользовательский преобразователь использовался для отображения даты в укороченном формате. Сведения о преобразователях см. в разделе Преобразование данных.

При нажатии кнопки Добавить продукт появляется следующая форма.

Add Product Listing page

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

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

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

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

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

Источники привязки данных привязаны к активному значению DataContext для элемента. Элементы автоматически наследуют значениеDataContext, если для них нет соответствующего явно определенного значения.

Рассмотрим следующий пример, в котором объектом источника привязки является класс с именем MyData, определенный в пространстве имен SDKSample. В качестве демонстрационного примера MyData имеет строковое свойство с именем ColorName со значением Red. Таким образом, в этом примере создается кнопка с красным фоном.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <DockPanel.DataContext>
        <Binding Source="{StaticResource myDataSource}"/>
    </DockPanel.DataContext>
    <Button Background="{Binding Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

Дополнительные сведения о синтаксисе объявления привязки и примеры настройки привязки в коде см. в разделе Общие сведения об объявлении привязок.

Если применить этот пример к основной диаграмме, полученное изображение будет выглядеть следующим образом. На этом рисунке описывается привязка OneWay, так как свойство Background поддерживает привязку OneWay по умолчанию.

Diagram that shows the data binding Background property.

Может возникнуть вопрос, почему эта привязка работает, даже несмотря на то, что свойство ColorName имеет тип "строка", а свойство Background имеет тип Brush. Эта привязка происходит в результате преобразования типов по умолчанию, которое обсуждается в разделе Преобразование данных.

Указание источника привязки

Обратите внимание, что в предыдущем примере источник привязки задается путем установки свойства DockPanel.DataContext. Затем Button наследует значение DataContext от DockPanel, которое является его родительским элементом. Повторим, что объект источника привязки является одним из четырех необходимых компонентов привязки. Таким образом, без указания объекта источника привязки эта привязка не имела бы смысла.

Есть несколько способов для указания объекта источника привязки. С помощью свойства DataContext родительского элемента удобно привязывать несколько свойств к одному источнику. Однако иногда удобнее указывать источник привязки в отдельных объявлениях привязки. В предыдущем примере вместо использования свойства DataContext можно указать источник привязки, задав свойство Binding.Source непосредственно в объявлении привязки кнопки, как показано в следующем примере.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

Кроме установки свойства DataContext непосредственно на элемент, наследуя значение DataContext из родительского элемента (например, кнопки в первом примере), и явного указания источника привязки путем установки свойства Binding.Source для привязки (например, кнопку в последнем примере), можно также использовать свойство Binding.ElementName или свойство Binding.RelativeSource, чтобы указать источник привязки. Свойство ElementName полезно при привязке к другим элементам в приложении, например при использовании ползунка для настройки ширины кнопки. Свойство RelativeSource полезно, когда привязка указана в ControlTemplate или Style. Дополнительные сведения см. в разделе Общие сведения об источниках привязки.

Указание пути к значению

Если источник привязки является объектом, используйте свойство Binding.Path для указания значения, используемого для привязки. Если вы выполняете привязку к данным XML, для указания значения используется свойство Binding.XPath. В некоторых случаях удобно применять свойство Path, даже если это данные XML. Например, если требуется получить доступ к свойству Name возвращаемого XmlNode (в результате выполнения запроса XPath), в дополнение к свойству XPath следует использовать свойство Path.

Дополнительные сведения см. в описаниях свойств Path и XPath.

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

<ListBox ItemsSource="{Binding}"
         IsSynchronizedWithCurrentItem="true"/>

В приведенном выше примере используется синтаксис пустой привязки: {Binding}. В этом случае ListBox наследует DataContext от родительского элемента DockPanel (не показан в этом примере). Если путь не указан, по умолчанию производится привязка ко всему объекту. Другими словами, в этом примере путь не был указан, так как мы выполнили привязку свойства ItemsSource ко всему объекту. (Подробное описание см. в разделе Привязка к коллекциям.)

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

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

Привязка и класс BindingExpression

Прежде чем перейти к другим возможностям и использованию привязки данных, полезно ознакомиться с классом BindingExpression. Как было показано в предыдущих разделах, класс Binding является классом высокого уровня для объявления привязки. Он предоставляет множество свойств, которые позволяют указать характеристики привязки. Связанный класс BindingExpressionявляется базовым объектом, поддерживающим связь между источником и целью. Привязка содержит всю информацию, которая может использоваться совместно в нескольких выражениях привязки. BindingExpression — это выражение экземпляра, которое не может быть общим и содержит все сведения об экземпляре Binding.

Рассмотрим следующий пример, где myDataObject является экземпляром класса MyData , myBinding является объектом источника Binding, а MyData — это определенный класс, содержащий строковое свойство с именем ColorName. Этот пример связывает текстовое содержимое myText, экземпляр TextBlock, с ColorName.

// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
{
    Source = myDataObject
};

// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
' Make a New source
Dim myDataObject As New MyData
Dim myBinding As New Binding("ColorName")
myBinding.Source = myDataObject

' Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding)

Один и тот же объект myBinding можно использовать для создания других привязок. Например, объект myBinding можно использовать для привязки текстового содержимого флажка к ColorName. В этом сценарии будут два экземпляра BindingExpression, совместно использующие объект myBinding.

Объект BindingExpression возвращается путем вызова GetBindingExpression для объекта, привязанного к данным. В следующих статьях демонстрируются некоторые случаи использования класса BindingExpression.

Преобразование данных

В разделе Создание привязки кнопка имеет красный цвет, так как ее свойство Background привязано к строковому свойству со значением "Red". Это строковое значение работает, так как преобразователь типов находится в типе Brush для преобразования строкового значения в Brush.

Если добавить эти сведения на рисунок из раздела Создание привязки, схема будет выглядеть следующим образом.

Diagram that shows the data binding Default property.

Однако что делать, если вместо свойства строкового типа объект источника привязки имеет свойство Color типа Color? В этом случае для создания привязки в первую очередь необходимо преобразовать значение свойства Color в нечто, что примет свойство Background. Понадобится создать пользовательский преобразователь, реализовав интерфейс IValueConverter, как показано в следующем примере.

[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Color color = (Color)value;
        return new SolidColorBrush(color);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}
<ValueConversion(GetType(Color), GetType(SolidColorBrush))>
Public Class ColorBrushConverter
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
        Dim color As Color = CType(value, Color)
        Return New SolidColorBrush(color)
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
        Return Nothing
    End Function
End Class

Дополнительные сведения см. в разделе IValueConverter.

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

Diagram that shows the data binding custom converter.

Таким образом, преобразования по умолчанию могут быть доступны благодаря преобразователям типов, присутствующим в типе, к которому производится привязка. Такое поведение будет зависеть от того, какие преобразователи типов доступны в целевом объекте. Если существуют какие-то сомнением, создайте свой собственный преобразователь.

Ниже приведены некоторые типовые сценарии, где имеет смысл реализация преобразователя данных.

  • Данные должны отображаться по-разному в зависимости от региональных стандартов. Например, можно реализовать преобразователь валюты или преобразователь даты/времени в календаре на основе соглашений, используемых в определенных региональных стандартах.

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

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

  • В целевом свойстве есть набор привязок, который называется MultiBinding. Чтобы получить конечное значение из значений привязок, для MultiBinding используется пользовательский IMultiValueConverter. Например, цвет может быть вычислен из соотношения красного, синего и зеленого значений, которые могут быть значениями одних и тех же или разных объектов источника привязки. Примеры и сведения см. в разделе MultiBinding.

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

Объект источника привязки может рассматриваться как отдельный объект, свойства которого содержат данные, или как коллекция данных полиморфных объектов, часто группируемых вместе (например, в результате запроса к базе данных). До сих пор мы обсуждали привязку только к отдельным объектам. Однако распространенным сценарием также является привязка к коллекции данных. Например, обычным сценарием является использование ItemsControl как ListBox, ListView или TreeView для отображения коллекции данных, например, в приложении, показанном в разделе Понятие привязки данных.

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

Diagram that shows the data binding ItemsControl object.

Как показано на данной схеме, для привязки свойства ItemsControl к объекту коллекции свойство ItemsControl.ItemsSource является свойством, которое необходимо использовать. ItemsSource можно представить как содержимое ItemsControl. Привязка — OneWay, потому что свойство ItemsSource поддерживает привязку OneWay по умолчанию.

Способы реализации коллекций

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

WPF предоставляет класс ObservableCollection<T>, который является встроенной реализацией коллекции данных, предоставляющей интерфейс INotifyCollectionChanged. Для полной поддержки передачи значений данных от объектов источника в цели каждый объект в коллекции, который поддерживает свойства связывания, должен также реализовывать интерфейс INotifyPropertyChanged. Дополнительные сведения см. в разделе Общие сведения об источниках привязки.

Перед реализацией собственной коллекции рассмотрите возможность использования ObservableCollection<T> или одного из существующих классов коллекций, таких как List<T>, Collection<T> и BindingList<T>, среди многих других. Если у вас есть расширенный скрипт и требуется реализовать собственную коллекцию, следует рассмотреть возможность использования IList, что представляет коллекцию объектов, к которым можно по отдельности обращаться по индексу, и, следовательно, обеспечивать лучшую производительность.

Представления коллекций

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

Понятие о представлениях коллекций

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

Так как представления не меняют базовые исходные коллекции, каждая исходная коллекция может иметь несколько связанных с ней представлений. Например, имеется коллекция объектов Task. С помощью представлений можно отображать одни и те же данные различными способами. Например, в левой части страницы можно отображать задачи, отсортированные по приоритету, а справа — сгруппированные по областям.

Создание представления

Одним из способов создания и использования представления является непосредственное создание объекта представления и затем использование его в качестве источника привязки. В качестве примера рассмотрим приложение Пример привязки данных, показанное в разделе Понятие привязки данных. Приложение реализовано таким образом, что ListBox привязывается к представлению коллекции данных, а не к коллекции данных напрямую. Следующий пример извлекается из приложения Пример привязки данных. Класс CollectionViewSource является XAML-прокси класса, который наследуется от CollectionView. В данном конкретном примере Source представления привязывается к коллекции AuctionItems (типа ObservableCollection<T>) текущего объекта приложения.

<Window.Resources>
    <CollectionViewSource 
      Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"   
      x:Key="listingDataView" />
</Window.Resources>

Затем ресурс listingDataView служит источником привязки для элементов в приложении, таких как ListBox.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />

Чтобы создать другое представление для той же коллекции, можно создать другой экземпляр CollectionViewSource и присвоить ему другое имя x:Key.

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

Тип исходной коллекции Тип представления коллекции Примечания.
IEnumerable Внутренний тип, основанный на CollectionView Невозможно группировать элементы.
IList ListCollectionView Самый быстрый.
IBindingList BindingListCollectionView

Использование представления по умолчанию

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

Чтобы получить представление по умолчанию, используйте метод GetDefaultView. Пример см. в статье Практическое руководство. Получение представления по умолчанию для коллекции данных (.NET Framework).

Использование представлений коллекций с таблицами данных ADO.NET

Для улучшения производительности представления коллекции для ADO.NET DataTable или DataView объектов делегируют сортировку и фильтрацию DataView, что приводит к тому, что сортировка и фильтрация совместно используются всеми представлениями коллекции источника данных. Чтобы включить возможность независимой сортировки и фильтрации для каждого представления коллекции, инициализируйте каждое представление коллекции с использованием собственного объекта DataView.

Сортировка

Как уже отмечалось ранее, представления могут применять сортировку для коллекции. Так как данные находятся в базовой коллекции, они могут иметь или не иметь некий порядок следования. Представление коллекции позволяет установить порядок или изменить порядок, используемый по умолчанию, на основе введенных признаков сравнения. Так как это представление данных на стороне клиента, распространен сценарий, когда пользователь сортирует столбцы табличных данных по значениям, содержащимся в столбце. С использованием представлений управляемая пользователем сортировка может применяться еще раз без необходимости внесения изменений в основную коллекцию или создания повторного запроса к содержимому коллекции. Пример см. в статье Практическое руководство. Сортировка столбцов GridView при нажатии на заголовок (.NET Framework).

В следующем примере показана логика сортировки для флажка "Sort by category and date" (Сортировать по категории и дате) в CheckBox пользовательского интерфейса приложения, описанного в подразделе Понятие привязки данных.

private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e)
{
    // Sort the items first by Category and then by StartDate
    listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
    listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending));
}
Private Sub AddSortCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    ' Sort the items first by Category And then by StartDate
    listingDataView.SortDescriptions.Add(New SortDescription("Category", ListSortDirection.Ascending))
    listingDataView.SortDescriptions.Add(New SortDescription("StartDate", ListSortDirection.Ascending))
End Sub

Фильтрация

Представления могут также применять фильтр к коллекции, так чтобы представление отображало только определенное подмножество полной коллекции. Возможна фильтрация по условию в данных. Например, как показано в приложении из подраздела Понятие привязки данных, флажок CheckBox "Show only bargains" (Показывать только товары по сниженным ценам) содержит логику фильтрации товаров с ценой 25 долл. США и выше. Следующий код выполняется для установки ShowOnlyBargainsFilter в качестве обработчика событий Filter при выборе CheckBox.

private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e)
{
    if (((CheckBox)sender).IsChecked == true)
        listingDataView.Filter += ListingDataView_Filter;
    else
        listingDataView.Filter -= ListingDataView_Filter;
}
Private Sub AddFilteringCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    Dim checkBox = DirectCast(sender, CheckBox)

    If checkBox.IsChecked = True Then
        AddHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    Else
        RemoveHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    End If
End Sub

Обработчик события ShowOnlyBargainsFilter реализуется следующим образом.

private void ListingDataView_Filter(object sender, FilterEventArgs e)
{
    // Start with everything excluded
    e.Accepted = false;

    // Only inlcude items with a price less than 25
    if (e.Item is AuctionItem product && product.CurrentPrice < 25)
        e.Accepted = true;
}
Private Sub ListingDataView_Filter(sender As Object, e As FilterEventArgs)

    ' Start with everything excluded
    e.Accepted = False

    Dim product As AuctionItem = TryCast(e.Item, AuctionItem)

    If product IsNot Nothing Then

        ' Only include products with prices lower than 25
        If product.CurrentPrice < 25 Then e.Accepted = True

    End If

End Sub

Если вместо CollectionViewSource используется один из классов CollectionView, для указания обратного вызова следует использовать свойство Filter. Пример см. в статье Практическое руководство. Фильтрация данных в представлении (.NET Framework).

Группировка

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

В следующем примере показана логика флажка "Группировка по категориям" CheckBox.

// This groups the items in the view by the property "Category"
var groupDescription = new PropertyGroupDescription();
groupDescription.PropertyName = "Category";
listingDataView.GroupDescriptions.Add(groupDescription);
' This groups the items in the view by the property "Category"
Dim groupDescription = New PropertyGroupDescription()
groupDescription.PropertyName = "Category"
listingDataView.GroupDescriptions.Add(groupDescription)

Другой пример группировки см. в статье Практическое руководство. Группировка элементов в объекте ListView, реализующем GridView (.NET Framework).

Указатели на текущий элемент

В представлениях также присутствует понятие текущего элемента. Существует возможность перемещаться по объектам в представлении коллекции. При переходе перемещается указатель элемента, позволяющий извлечь объект, расположенный в определенном расположении в коллекции. Пример см. в статье Практическое руководство. Перемещение по объектам в Data CollectionView (.NET Framework).

Поскольку WPF выполняет привязку к коллекции только с помощью представления (либо указанного пользователем, либо представления коллекции по умолчанию), для всех привязок к коллекциям имеется указатель на текущий элемент. При привязке к представлению символ косой черты ("/") в значении Path указывает на текущий элемент представления. В следующем примере контекст данных является представлением коллекции. В первой строке выполняется привязка к коллекции. Во второй строке выполняется привязка к текущему элементу коллекции. В третьей строке выполняется привязка к свойству Description текущего элемента коллекции.

<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />

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

<Button Content="{Binding /Offices/}" />

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

Сценарий "основной — подробности"

Понятие текущего элемента применимо не только для перемещения элементов в коллекции, но также для сценария привязки "основной — подробности". Еще раз рассмотрим пользовательский интерфейс приложения из подраздела Понятие привязки данных. В этом приложении выбор внутри ListBox определяет содержимое, отображаемое в ContentControl. Другими словами, когда выбран элемент ListBox, ContentControl показывает сведения выбранного элемента.

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

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />
<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"
                Content="{Binding Source={StaticResource listingDataView}}"
                ContentTemplate="{StaticResource detailsProductListingTemplate}" 
                Margin="9,0,0,0"/>

Обратите внимание, что оба элемента управления привязаны к одному источнику, статическому ресурсу listingDataView (просмотреть определение этого ресурса можно в подразделе Создание представления). Эта привязка работает потому, что если отдельный объект (ContentControl в данном случае) привязан к представлению коллекции, то он автоматически привязывается к CurrentItem представления. Объекты CollectionViewSource автоматически синхронизируют валюту и выделение. Если ваш элемент управления "Список" не привязан к объекту CollectionViewSource, как в этом примере, для корректной работы необходимо задать его свойству IsSynchronizedWithCurrentItem значение true.

Другие примеры см. в статьях Практическое руководство. Выполнение привязки к коллекции и вывод сведений в зависимости от выделенного элемента (.NET Framework) и Практическое руководство. Использование шаблона "основной — подробности" с иерархическими данными (.NET Framework).

Можно заметить, что в приведенном выше примере используется шаблон. Фактически данные не будут отображаться выбранным способом без использования шаблонов (один явно используется элементом ContentControl, а другой — неявно элементом ListBox). К шаблонам данных мы перейдем в следующем разделе.

Шаблоны данных

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

Data Binding Demo without Data Templates

Как показано в примере из предыдущего раздела, оба элемента управления — как ListBox, так и ContentControl, привязываются ко всему объекту коллекции (а точнее к представлению объекта коллекции) элементов AuctionItem. При отсутствии особых инструкций по способу отображения коллекции данных элемент управления ListBox отвечает за отображение строкового представления каждого объекта в базовой коллекции, а элемент управления ContentControl отвечает за отображение строкового представления привязанного к ней объекта.

Чтобы решить эту проблему, приложение определяет DataTemplates. Как показано в примере в предыдущем разделе, ContentControl явно использует шаблон данных detailsProductListingTemplate. Элемент управления ListBox неявно использует следующий шаблон данных при отображении объектов AuctionItem в коллекции.

<DataTemplate DataType="{x:Type src:AuctionItem}">
    <Border BorderThickness="1" BorderBrush="Gray"
            Padding="7" Name="border" Margin="3" Width="500">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20"/>
                <ColumnDefinition Width="86"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"
                     Fill="Yellow" Stroke="Black" StrokeThickness="1"
                     StrokeLineJoin="Round" Width="20" Height="20"
                     Stretch="Fill"
                     Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"
                     Visibility="Hidden" Name="star"/>

            <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"
                       Name="descriptionTitle"
                       Style="{StaticResource smallTitleStyle}">Description:</TextBlock>
            
            <TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"
                       Text="{Binding Path=Description}"
                       Style="{StaticResource textStyleTextBlock}"/>

            <TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"
                       Name="currentPriceTitle"
                       Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>
            
            <StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
                <TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/>
                <TextBlock Name="CurrentPriceDTDataType"
                           Text="{Binding Path=CurrentPrice}" 
                           Style="{StaticResource textStyleTextBlock}"/>
            </StackPanel>
        </Grid>
    </Border>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Color</src:SpecialFeatures>
            </DataTrigger.Value>
            <DataTrigger.Setters>
                <Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" />
                <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
                <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
                <Setter Property="BorderThickness" Value="3" TargetName="border" />
                <Setter Property="Padding" Value="5" TargetName="border" />
            </DataTrigger.Setters>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Highlight</src:SpecialFeatures>
            </DataTrigger.Value>
            <Setter Property="BorderBrush" Value="Orange" TargetName="border" />
            <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
            <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
            <Setter Property="Visibility" Value="Visible" TargetName="star" />
            <Setter Property="BorderThickness" Value="3" TargetName="border" />
            <Setter Property="Padding" Value="5" TargetName="border" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

При использовании двух шаблонов DataTemplate результирующий пользовательский интерфейс будет аналогичен пользовательскому интерфейсу, показанному в разделе Понятие привязки данных. Как можно увидеть на этом снимке, в дополнение к тому, что шаблоны DataTemplate дают возможность располагать данные в элементах управления, они позволяют определять подходящие визуальные элементы для данных. Например, элементы DataTrigger используются в приведенном выше шаблоне DataTemplate, чтобы элементы AuctionItem со значением SpecialFeatures для HighLight отображались с оранжевой границей и звездочкой.

Дополнительные сведения о шаблонах данных см. в статье Общие сведения о шаблонах данных (.NET Framework).

Проверка данных

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

Связь правил проверки и привязки

Модель привязки данных WPF позволяет связать ValidationRules с объектом Binding. Например, в следующем примере TextBox привязывается к свойству с именем StartPrice и в свойство Binding.ValidationRules добавляется объект ExceptionValidationRule.

<TextBox Name="StartPriceEntryForm" Grid.Row="2"
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <ExceptionValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Объект ValidationRule проверяет, является ли значение свойства допустимым. В состав WPF входят два типа встроенных объектов ValidationRule:

  • ExceptionValidationRule выполняет проверку исключений, возникающих во время обновления свойства источника привязки. В предыдущем примере StartPrice имеет тип integer. Когда пользователь вводит значение, которое невозможно преобразовать в целое число, создается исключение, приводящее к тому, что привязка будет помечена как недопустимая. Альтернативным синтаксисом для явной установки свойства ExceptionValidationRule является установка свойства ValidatesOnExceptions в true для вашего объекта Binding или MultiBinding.

  • Объект DataErrorValidationRule проверяет наличие ошибок, вызванных объектами, реализующими интерфейс IDataErrorInfo. Дополнительные сведения об использовании этого правила проверки см. в разделе DataErrorValidationRule. Альтернативным синтаксисом для явной установки свойства DataErrorValidationRule является установка свойства ValidatesOnDataErrors в true для вашего объекта Binding или MultiBinding.

Вы можете также создать собственное правило проверки, выполнив наследование от класса ValidationRule и реализовав метод Validate. В следующем примере показано правило, используемое элементом управления "Дата начала" для Добавление списка продуктовTextBox, показанного в разделе Понятие привязки данных.

public class FutureDateRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        // Test if date is valid
        if (DateTime.TryParse(value.ToString(), out DateTime date))
        {
            // Date is not in the future, fail
            if (DateTime.Now > date)
                return new ValidationResult(false, "Please enter a date in the future.");
        }
        else
        {
            // Date is not a valid date, fail
            return new ValidationResult(false, "Value is not a valid date.");
        }

        // Date is valid and in the future, pass
        return ValidationResult.ValidResult;
    }
}
Public Class FutureDateRule
    Inherits ValidationRule

    Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult

        Dim inputDate As Date

        ' Test if date is valid
        If Date.TryParse(value.ToString, inputDate) Then

            ' Date is not in the future, fail
            If Date.Now > inputDate Then
                Return New ValidationResult(False, "Please enter a date in the future.")
            End If

        Else
            ' // Date Is Not a valid date, fail
            Return New ValidationResult(False, "Value is not a valid date.")
        End If

        ' Date is valid and in the future, pass
        Return ValidationResult.ValidResult

    End Function

End Class

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

<TextBox Name="StartDateEntryForm" Grid.Row="3"
         Validation.ErrorTemplate="{StaticResource validationTemplate}" 
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged" 
                 Converter="{StaticResource dateConverter}" >
            <Binding.ValidationRules>
                <src:FutureDateRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Поскольку значение UpdateSourceTrigger равно PropertyChanged, механизм привязки обновляет исходное значение при каждом нажатии клавиши, что означает, что он также проверяет каждое правило в коллекции ValidationRules при каждом нажатии клавиши. Это будет обсуждаться далее в разделе "Процесс проверки".

Предоставление визуального отклика

Если пользователь вводит недопустимое значение, можно сформировать отклик пользовательского интерфейса приложения на ошибку. Одним из способов предоставления такой обратной связи является установка присоединенного свойства Validation.ErrorTemplate для пользовательского ControlTemplate. Как показано в предыдущем подразделе, TextBoxStartDateEntryForm использует ErrorTemplate под названием validationTemplate. В следующем примере показано определение элемента validationTemplate.

<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
        <AdornedElementPlaceholder/>
    </DockPanel>
</ControlTemplate>

Элемент AdornedElementPlaceholder указывает, где должен размещаться элемент управления.

Кроме того, вы можете использовать ToolTip для вывода сообщения об ошибке. Как StartDateEntryForm, так и StartPriceEntryFormTextBox используют стиль textStyleTextBox, который создает ToolTip, отображающий сообщение об ошибке. В следующем примере показано определение элемента textStyleTextBox. Присоединенное свойство Validation.HasError равно true, когда одна или несколько привязок к свойствам связанного элемента ошибочны.

<Style x:Key="textStyleTextBox" TargetType="TextBox">
    <Setter Property="Foreground" Value="#333333" />
    <Setter Property="MaxLength" Value="40" />
    <Setter Property="Width" Value="392" />
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" 
                    Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Self}}" />
        </Trigger>
    </Style.Triggers>
</Style>

Если применяются пользовательские ErrorTemplate и ToolTip, TextBoxStartDateEntryForm при наличии ошибки проверки выглядит следующим образом:

Data binding validation error for date

Если к Binding присоединены правила проверки, но для привязанного элемента управления не указано значениеErrorTemplate, для уведомления пользователей об ошибке проверки по умолчанию будет использоваться ErrorTemplate. ErrorTemplate по умолчанию — это шаблон элемента управления, определяющий красную границу на слое декоративных элементов. Если используются ErrorTemplate и ToolTip по умолчанию, пользовательский интерфейс TextBoxStartPriceEntryForm при наличии ошибки проверки выглядит следующим образом:

Data binding validation error for price

Пример предоставления логики проверки всех элементов управления в диалоговом окне см. в подразделе "Пользовательские диалоговые окна" раздела Общие сведения о диалоговых окнах.

Процесс проверки

Проверка обычно выполняется, когда целевое значение передается свойству источника привязки. Этот перенос выполняется для привязок TwoWay и OneWayToSource. Таким образом, причина обновления источника зависит от значения свойства UpdateSourceTrigger, как описано в подразделе Что инициирует обновления источника.

Следующие элементы описывают процесс проверки. При возникновении ошибки проверки или ошибки другого типа на любом этапе данного процесса процесс будет прерван.

  1. Обработчик привязки проверяет, есть ли определенные пользовательские объекты ValidationRule, где для ValidationStep задано значение RawProposedValue для этого Binding. В этом случае он вызывает метод Validate на каждом ValidationRule до тех пор, пока один из них не выдаст ошибку или пока все они не сработают.

  2. Обработчик привязки вызывает преобразователь, если таковой существует.

  3. Если преобразователь сработает корректно, механизм связывания проверяет, нет ли каких-либо пользовательских объектов ValidationRule, чей ValidationStep имеет значение ConvertedProposedValue для этого Binding, и в этом случае он вызывает метод Validate для каждого ValidationRule, чей ValidationStep имеет значение ConvertedProposedValue, пока один из них не встретится с ошибкой или пока все они не пройдут.

  4. Обработчик привязки присваивает значение исходному свойству.

  5. Обработчик привязки проверяет, есть ли определенные пользовательские объекты ValidationRule, где для ValidationStep задано значение UpdatedValue для этого Binding. В этом случае он вызывает метод Validate на каждом ValidationRule, чей ValidationStep имеет значение UpdatedValue, до тех пор пока один из них не выдаст ошибку или пока все они не сработают. Если DataErrorValidationRule связан с привязкой, а для его ValidationStep задано значение по умолчанию UpdatedValue, DataErrorValidationRule проверяется на этом этапе. В этот момент проверяется любая привязка, где ValidatesOnDataErrors имеет значение true.

  6. Обработчик привязки проверяет, есть ли определенные пользовательские объекты ValidationRule, где для ValidationStep задано значение CommittedValue для этого Binding. В этом случае он вызывает метод Validate на каждом ValidationRule, чей ValidationStep имеет значение CommittedValue, до тех пор пока один из них не выдаст ошибку или пока все они не сработают.

Если ValidationRule не передается в течение этого процесса, обработчик привязки создает объект ValidationError и добавляет его в коллекцию Validation.Errors связанного элемента. Перед тем как обработчик привязки запустит объекты ValidationRule на любом шаге, он удаляет все ValidationError, добавленные во вложенное свойство Validation.Errors связанного элемента на этом шаге. Например, если ValidationRule, чей ValidationStep имеет значение UpdatedValue, завершилось с ошибкой, то в следующий раз, когда происходит процесс проверки, механизм привязки удаляет ValidationError непосредственно перед вызовом любого ValidationRule, у которого для ValidationStep установлено значение UpdatedValue.

Если значение Validation.Errors не пустое, присоединенное свойство Validation.HasError элемента имеет значение true. Также если свойство NotifyOnValidationErrorBinding имеет значение true, то обработчик привязки вызывает в элементе Validation.Error присоединенное событие.

Также обратите внимание, что при передаче допустимого значения в любом направлении (от целевого объекта к источнику и от источника к целевому объекту) очищается вложенное свойство Validation.Errors.

Если привязка связана с ExceptionValidationRule или имеет свойство ValidatesOnExceptions со значением true и при установке источника привязки срабатывает исключение, обработчик привязки проверяет наличие UpdateSourceExceptionFilter. Вы можете использовать обратный вызов UpdateSourceExceptionFilter, чтобы предоставить пользовательский обработчик для обработки исключений. Если для элемента Binding не указано UpdateSourceExceptionFilter, обработчик привязки создает ValidationError с исключением и добавляет его в коллекцию Validation.Errors связанного элемента.

Механизм отладки

Для получения сведений о состоянии конкретной привязки можно задать присоединенное свойство PresentationTraceSources.TraceLevel на объекте, связанном с привязкой.

См. также