Получить начальные знания о свойствах зависимостей и маршрутизируемых событиях, способах их определения, создания и использования. Изучить назначение и возможности
В технологии WPF определены , . Свойства зависимостей обеспечивают возможность работы с основными средствами WPF, такими как анимация,
При создании свойства зависимостей необходимо определить экземпляр класса DependencyProperty, который должен быть статическим и доступным только для чтения. Предположим, что в классе NodeImage необходимо определить свойство зависимости Fill, которое будет описывать кисть для заливки каких-либо графических объектов.
public class NodeImage: Control
{
...
public static readonly DependencyProperty FillProperty;
...
}
В соответствии с принятыми правилами именования свойств зависимостей имя объявляемого свойства должно состоять из двух частей – имени, в нашем случае Fill, и слова Property, то есть FillProperty. Модификатор readonly означает, что создаваемое свойство зависимости FillProperty доступно только для чтения и может получить значение только в статическом конструкторе класса, содержащем данное поле, то есть в нашем случае – конструкторе класса NodeImage. Объявленное свойство зависимости должно быть зарегистрировано в WPF, что выполняется в статическом конструкторе соответствующего класса.
static NodeImage()
{
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.DefaultValue = null;
metadata.AffectsRender = true;
FillProperty = DependencyProperty.Register(
"Fill", typeof(Brush), typeof(NodeImage), metadata);
}
В конструкторе для регистрации свойства зависимости вначале создаются метаданные metadata – объект класса FrameworkPropertyMetadata. В метаданных для свойства FillProperty задается значение по умолчанию ( null ) и необходимость перерисовки объекта при изменении его свойств ( AffectsRender = true ). Регистрация производится методом DependencyProperty.Register(), для которого параметрами задается имя свойства ( Fill ), его тип ( typeof(Brush) ), тип объекта, в котором регистрируется свойство ( typeof(NodeImage) ) и метаданные ( metadata ). В общем случае при регистрации свойства зависимостей дополнительно может задаваться метод обратного вызова, который будет производить проверку правильности свойств.
Для обеспечения доступа к свойствам зависимостей необходимо объявить свойство Fill, то есть упаковать его в обычную оболочку свойств, используя методы SetValue() и GetValue().
public Brush Fill
{
set { SetValue(FillProperty, value); }
get { return (Brush)GetValue(FillProperty); }
}
Созданное свойство зависимостей Fill можно использовать в коде класса NodeImage как обычное свойство.
Некоторые классы могут совместно использовать одно и то же свойство зависимостей, даже если они имеют отдельные
Разновидностью свойств зависимостей являются присоединяемые свойства зависимостей. Данные свойства применяется к классу, отличному от того, в котором оно определено. Для регистрации присоединяемого свойства зависимостей используется метод RegisterAttached(). Упаковывать данное свойство нет необходимости
Свойства зависимостей поддерживают уведомление об изменениях и динамическое разрешение значений.
При изменении значений свойства зависимостей осуществляется обратный вызов, который отвечает за обновление привязок данных и запуск триггеров. Из этого следует, что для поддержания заданной функциональности при изменении свойств зависимостей необходимо создать привязку, которая будет использовать значение свойства, или написать триггер, который будет автоматически изменять другое свойство или производить другие действия.
Динамическое разрешение значений свойств зависимостей определяет механизм извлечения значений из свойств зависимостей. При извлечении значений из свойств система WPF определяет базовое значение, учитывая следующие факторы:
Наиболее высокий приоритет имеет локальное значение, если оно установлено в программе. Если локальное значение не задано, установка значения передается вверх по списку следующему элементу, который может его принять, вплоть до значения по умолчанию.
Маршрутизируемое событие – это сообщение о событии, которое возникло в одном элементе, а генерация может осуществляться в другом элементе. Маршрутизируемое событие представляется
Например, для класса NodeImage необходимо определить маршрутизируемое событие DeleteNode. Для этого создаем статический экземпляр класса RoutedEvent, который объявляется с модификатором readonly. К имени события должен добавлять постфикс Event.
public static readonly RoutedEvent DeleteNodeEvent;
Регистрация маршрутизируемого события должна проводиться в статическом конструкторе класса с помощью метода RegisterRoutedEvent.
DeleteNodeEvent = EventManager.RegisterRoutedEvent("DeleteNode",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NodeImage));
При регистрации задается
Упаковщик события определяет методы для добавления и удаления прослушивания событий: AddHandler() и RemoveHandler().
public event RoutedEventHandler DeleteNode
{
add { AddHandler(DeleteNodeEvent, value); }
remove { RemoveHandler(DeleteNodeEvent, value); }
}
Класс, в котором создано маршрутизируемое событие, должен отвечать за его генерацию. В классе NodeImage в контекстном меню имеется пункт ( itemDelete ), который отвечает за генерацию события DeleteNodeEvent.
void ContextMenu_OnClick(object sender, RoutedEventArgs e)
{
MenuItem item = e.Source as MenuItem;
if (item == itemDelete)
{
RoutedEventArgs argsEvent = new RoutedEventArgs();
argsEvent.RoutedEvent = NodeImage.DeleteNodeEvent;
argsEvent.Source = this;
RaiseEvent(argsEvent);
}
}
В обработчике ContextMenu_OnClick() создается экземпляр ( argsEvent ) класса RoutedEventArgs, который содержит информацию о состоянии и данные события, связанные с маршрутизируемым событием.
RoutedEventArgs argsEvent = new RoutedEventArgs();
Для объекта argsEvent задаются событие и источник.
argsEvent.RoutedEvent = NodeImage.DeleteNodeEvent; argsEvent.Source = this;
Метод RaiseEvent() производит генерацию события для вызывающего объекта.
RaiseEvent(argsEvent);
Присоединение обработчика событий можно выполнить в коде. Если в каком-либо классе объявлен и создан объект nodeSet класса NodeImage, то подключение обработчика Node_Delete() к событию DeleteNode можно выполнить следующим образом.
nodeSet.DeleteNode += Node_Delete;
Подключение обработчиков события в WPF также можно выполнить в
Маршрутизируемые события бывают трех видов:
Обрабатывать поднимающиеся и опускающиеся события можно не только в том элементе, где они были сгенерированы, но и в других элементах визуального дерева.
В общем случае, , , , .
При проектировании приложений возникает задача создания привязки для некоторых свойств объектов и элементов интерфейса. Такая привязка возможна только в том случае, если свойство объекта-источника является общедоступным.
Для изучения технологии
Создадим проект для интерфейсного элемента ввода данных (рис 3.1).
(рис 3.1) Окно ввода вещественных чисел<Window x:Class="Wpf_Primer1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Ввод вещественного числа" Height="140" Width="300">
<Grid Name="gridPrimir" Height="85">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Margin="3,3,3,15" Name="label1">Параметр</Label>
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1" />
<Button Grid.Column="1" Grid.Row="1" Margin="5,5,28,12" Name="button1"
Click="button1_Click">Проверить</Button>
</Grid>
</Window>
Код класса Window1:
using System;
using System.Windows;
using System.Windows.Controls;
namespace Wpf_Primer1
{
public partial class Window1 : Window
{
public static float Factor { get; set; }
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Factor.ToString(), "Проверка значения параметра");
}
}
}
Свойство Factor класса Window1 необходимо для приема вводимого вещественного числа и его целесообразно сделать статическим для доступа из других
В общем случае для привязки к объекту, не являющемуся элементом, можно использовать следующие свойства:
В нашем случае целесообразно использовать свойство RelativeSource. Для реализации привязки введем изменение в
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1"
Text="{Binding Path = Factor, RelativeSource =
{RelativeSource FindAncestor, AncestorType = {x:Type Window}}}"/>
В выражении
Свойство AncestorType определяет элемент в визуальном дереве, где содержится объект-источник. В нашем случае свойство Factor является членом класса Window1 элемента Window.
Протестируем созданное приложение. При вводе строки 0.1, что соответствует формату вещественного числа, получаем результат проверки вещественное число 0,1 (рис 3.2).
(рис 3.2) Проверка ввода строки "0.1"При выводе вещественное число преобразуется к строковому представлению методом ToString():
MessageBox.Show(Factor.ToString(), "Проверка значения параметра");
В результате этого десятичная точка заменяется на запятую и в окне "Проверка значения параметра" выводится строка "0,1", а не введенная "0.1":
При вводе строки "0,1", что не соответствует формату вещественного числа с плавающей точкой, получаем в результате преобразования по умолчания целое число "1" (рис 3.3).
(рис 3.3) Проверка ввода строки "0,1"При вводе недопустимого символа, получаем в результате проверки целое число 1, которое было присвоено свойству
(рис 3.4) Проверка ввода недопустимого символаРезультаты тестирования разработанной программы показывают, что при вводе корректного представления вещественного числа программа работает правильно, а при вводе недопустимых символов или нарушении формата вещественного числа (ввод запятой вместо точки) программа выдает неправильный результат. Это определяет необходимость проверки достоверности ввода для предотвращения некорректных значений.
При корректном вводе данных мы убедились, что взаимодействие между свойством Text элемента TextBox и свойством Factor класса Window1 правильно поддерживается созданной привязкой, то есть информация от целевого объекта передается к объекту-источнику.
В приложениях часто возникают ситуации, когда данные изменяются в коде программы и это изменение желательно увидеть в интерфейсном элементе. Проверим корректность связи от объекта-источника к целевому объекту. Для этого добавим в форму ещё одну кнопку, при нажатии на которую значение свойства ).
(рис 3.5) Проверка корректности программного изменения объекта-источникаПроверка корректности программного изменения объекта-источника
Это является результатом того, что свойство Text элемента TextBox не уведомляется об изменении свойства Factor, которое не является свойством зависимостей. Для обеспечения уведомления между объектом-источником и целевым объектом можно поступить следующим образом:
Продемонстрируем первый и третий способ решения задачи обеспечения синхронизации представления данных между объектом-источником и целевым объектом.
Определим в классе Window1 свойство зависимостей Factor. Измененный код принимает следующий вид:
public partial class Window1 : Window
{
public static readonly DependencyProperty FactorProperty;
public float Factor
{
set{SetValue(FactorProperty, value);}
get{return (float)GetValue(FactorProperty);}
}
static Window1()
{
FactorProperty = DependencyProperty.Register("Factor",
typeof(float), typeof(Window1),
new FrameworkPropertyMetadata (0.0F,
FrameworkPropertyMetadataOptions.AffectsRender));
}
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Factor.ToString(), "Проверка значения параметра");
}
private void button2_Click(object sender, RoutedEventArgs e)
{
Factor *= 2;
}
}
Вначале определяется свойство зависимостей:
public static readonly DependencyProperty FactorProperty;
Затем формируют методы доступа к свойству зависимостей:
public float Factor
{
set{SetValue(FactorProperty, value);}
get{return (float)GetValue(FactorProperty);}
}
На последнем шаге свойство зависимостей регистрируется в статическом конструкторе:
static Window1()
{
FactorProperty = DependencyProperty.Register("Factor",
typeof(float), typeof(Window1),
new FrameworkPropertyMetadata (0.0F,
FrameworkPropertyMetadataOptions.AffectsRender));
}
Теперь при нажатии кнопки button2 изменяется значение свойства Factor и целевого объекта свойства Text элемента TextBox.
Для
public class FactorClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
private float factor;
public float Factor
{
get { return factor; }
set
{
factor = value;
OnPropertyChanged((new PropertyChangedEventArgs("Factor"));
}
}
}
В классе объявляется событие PropertyChanged:
event PropertyChangedEventHandler PropertyChanged;
Затем кодируется обработчик события OnPropertyChanged:
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
Для свойства Factor при его изменении производится генерация события:
public float Factor
{
get { return factor; }
set
{
factor = value;
OnPropertyChanged OnPropertyChanged(new
PropertyChangedEventArgs("Factor"));
}
}
С учетом создания класса FactorClass код окна Window1 требует корректировки:
public partial class Window1 : Window
{
private static FactorClass factor = new FactorClass();
public static FactorClass Factor
{
get { return Window1.factor; }
}
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Factor.Factor.ToString(),
"Проверка значения параметра");
}
private void button2_Click(object sender, RoutedEventArgs e)
{
Factor.Factor *= 2;
}
}
В коде окна Window1 добавляется поле и свойства Factor типа FactorClass:
private static FactorClass factor = new FactorClass();
public static FactorClass Factor
{
get { return Window1.factor; }
}
В обработчиках кнопок обращение к свойству Factor производят через экземпляр класса FactorClass.
Тестирование внутреннего изменения значение свойства Factor и целевого объекта свойства Text элемента TextBox показывают синхронизацию изменений свойств.
При использовании класса FactorClass выражение привязки можно упростить. В этом случае создается привязка без указания источника, то есть необходимо указать только свойство Factor класса FactorClass:
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1"
Text="{Binding Path = Factor}"/>
Элемент Grid спроектированного окна является контейнером для всех визуальных элементов пользовательского интерфейса. Если свойству DataContext элемента Grid задать значение экземпляра класса FactorClass, то выражение
public Window1()
{
InitializeComponent();
gridPrimir.DataContext = Factor;
}
Проведем тестирование созданного приложения. Вначале введем значение 0.3 и проверим результат (рис 3.6).
(рис 3.6) Проверка корректности привязки данныхДалее нажмем кнопку "Увеличить в 2 раза" и посмотрим реакцию приложения (рис 3.7). Приложение работает корректно.
(рис 3.7) Проверка корректности привязки данных при программном изменении объекта-источникаВ рассматриваемом примере поддерживается двусторонняя связь между объектом-источником и целевым объектом. В общем случае направление привязки может быть однонаправленным и двунаправленным. Это определяется свойством Binding.Mode, которое может принимать следующие значения:
Для явного задания направления привязки необходимо сформировать свойство Mode целевого объекта:
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1"
Text="{Binding Path = Factor, Mode = TwoWay}" />
При применении привязки нужно принимать во внимание режим обновления данных между источником и целевым свойством. Режим обновления задается свойством Binding.UpdateSourceTrigger, которое может принимать следующие значения:
Если нам необходимо немедленное обновление целевого свойства и источника, то необходимо добавить изменения с
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1"
Text="{Binding Path = Factor, Mode = TwoWay, UpdateSourceTrigger = PropertyChanged}" />
При выполнении
В технологии WPF
Для проектирования преобразователя значений необходимо выполнить следующее:
В случае преобразования вещественного значения в строковое можно использовать метод ToString(), а для обратного преобразования - Single.Parse().
Спроектируем класс конвертора StringToFloatConvert для преобразования строковых данных в вещественное число.
[ValueConversion(typeof(float),typeof(string))]
public class StringToFloatConvert : IValueConverter
{
public object Convert(object value, Type typeTarget,
object param, CultureInfo culture)
{
if (value != null)
return value.ToString();
else
return string.Empty;
}
public object ConvertBack(object value, Type typeTarget,
object param, CultureInfo culture)
{
if ((value != null) (value.ToString() != ""))
return Single.Parse(value.ToString(), NumberStyles.Float);
else
return 0;
}
}
Обратите внимание на то, что для преобразования строки данных в вещественное число используется метод Single.Parse(value.ToString(), NumberStyles.Float). Данный метод преобразует строковое представление числа в указанном стиле в эквивалентное ему число одиночной точности с
Для подключения преобразователя необходимо к дескриптору окна добавить атрибут с отображением пространства имен проекта на префикс пространства имен XML:
xmlns:s="clr-namespace:Wpf_Primer1"
Далее необходимо создать экземпляр класса StringToFloatConvert и присвоить его свойству Convert в привязке элемента TextBox:
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1">
<TextBox.Text>
<Binding Path="Factor" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.Converter>
<s:StringToFloatConvert />
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>
Тестирование приложения показывает, что при вводе цифровой строки с запятой, то есть вещественного
(рис 3.8) Проверка корректности привязки с преобразованием данныхТеперь вернемся к вопросу проверки достоверности вводимых данных. Логика проверки достоверности вводимых данных должна перехватывать некорректные значения и отвергать их.
Технология WPF предполагает два варианта проверки достоверности данных для перехвата неверных значений:
Спроектируем правило проверки достоверности ввода данных путем создания класса ValidationFactor, который должен быть наследником класса правил ValidationRule из пространства имен System.Windows.Control:
public class ValidationFactor: ValidationRule
{
public float Min { get; set;}
public float Max { get; set;}
public override ValidationResult Validate(object value,
System.Globalization.CultureInfo cultureInfo)
{
try
{
if (((string)value).Length > 0)
Window1.Factor.Factor = Single.Parse((string)value,
NumberStyles.Float);
if((string)value == "")
Window1.Factor.Factor = 0;
}
catch (Exception e)
{
return new ValidationResult(false, " Введен недопустимый символ! ");
}
if ((Window1.Factor.Factor < Min) || (Window1.Factor.Factor > Max))
return new ValidationResult(false, " Вводимое значение вне диапазона от
" + Min + " до " + Max + ". ");
else
return new ValidationResult(true, null);
}
}
В классе ValidationFactor определены свойства Min и Max, описывающие минимальное и максимально возможное значения вводимого вещественного числа и
Создадим экземпляр класса ValidationFactor и присвоим его свойству ValidationRules в привязке элемента TextBox:
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1">
<TextBox.Text>
<Binding Path="Factor" Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged">
<Binding.Converter>
<s:StringToFloatConvert />
</Binding.Converter>
<Binding.ValidationRules>
<s:ValidationFactor Min="0" Max="1"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Свойствам Min и Max класса ValidationFactor определим значения соответственно 0 и 1.
Тестирование приложения показывает, что при вводе корректного значения приложение работает правильно, а при вводе некорректного символа или числа вне интервала [0,1] происходит подсвет рамки элемента TextBox красным цветом (рис 3.9).
(рис 3.9) Проверка корректности ввода символовНа рис 3.9 показан ввод недопустимого нецифрового символа и недопустимого по значению числа.
Следует отметить, что коллекция Binding.ValidationRules может состоять из неограниченного числа правил. Когда значение фиксируется в источнике, WPF будет проверять каждое правило проверки достоверности по порядку. Если все проверки достоверности пройдут успешно, то WPF вызовет преобразователь данных и применит значения к источнику. Если проверка не пройдет, то текстовое поле будет очерчено красным цветом, будут установлены свойства HasError и Error и инициируется событие Error.
Для обработки события Error необходимо установить свойство Binding.NotifyOnValidationError в True:
<Binding Path="Factor" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
Событие Error является маршрутизируемым, использует передачу вверх и поэтому его можно обработать в родительском контейнере, например в Grid окна:
<Grid Name="gridPrimir" Height="85" Validation.Error="gridPrimir_Error">
Код обработчика будет отображать сообщение с информацией об ошибке:
private void gridPrimir_Error(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added)
{
MessageBox.Show(e.Error.ErrorContent.ToString(), "Предупреждение",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
Свойство и рис 3.11 приведены реакция приложения на ввод некорректных данных.

(рис 3.11) Реакция на ввод недопустимого символа(рис 3.10) Реакция на ввод недопустимого числаИнформацию об ошибке можно получить и другим способом. Можно отобразить сообщение об ошибке в ToolTip элемента TextBox. При этом сообщение об ошибке будет появляться, когда пользователь поместим курсор мыши над элементом TextBox. Для реализации такого варианта необходимо создать шаблон ошибок, который будет содержать триггер, реагирующий на присваивание свойству Validation.HasError значения true и применит ToolTip с сообщением об ошибке:
<Window.Resources>
<Style TargetType="{x:Type TextBox}" >
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value=
"{Binding RelativeSource = {RelativeSource Self},
Path = (Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
При формировании свойства и рис 3.13 приведены примеры применения подсказки для проверки ввода данных.

(рис 3.13) Подсказка при вводе недопустимого символа(рис 3.12) Подсказка при вводе недопустимого числа
Свойство зависимостей, присоединяемое свойство зависимостей, маршрутизируемое событие,
DependencyProperty, FrameworkPropertyMetadata, DependencyProperty.Register, RoutedEvent, RoutedEventHandler, RoutedEventArgs, IValueConverter, ValidationRule.
Свойства зависимостей и маршрутизируемые события являются эффективными инструментами технологии WPF для построения привязки объектов и обеспечения гибкости обработки разнообразных событий в приложении.
Получить начальные знания о свойствах зависимостей и маршрутизируемых событиях, способах их определения, создания и использования. Изучить назначение и возможности
В технологии WPF определены , . Свойства зависимостей обеспечивают возможность работы с основными средствами WPF, такими как анимация,
При создании свойства зависимостей необходимо определить экземпляр класса DependencyProperty, который должен быть статическим и доступным только для чтения. Предположим, что в классе NodeImage необходимо определить свойство зависимости Fill, которое будет описывать кисть для заливки каких-либо графических объектов.
public class NodeImage: Control
{
...
public static readonly DependencyProperty FillProperty;
...
}
В соответствии с принятыми правилами именования свойств зависимостей имя объявляемого свойства должно состоять из двух частей – имени, в нашем случае Fill, и слова Property, то есть FillProperty. Модификатор readonly означает, что создаваемое свойство зависимости FillProperty доступно только для чтения и может получить значение только в статическом конструкторе класса, содержащем данное поле, то есть в нашем случае – конструкторе класса NodeImage. Объявленное свойство зависимости должно быть зарегистрировано в WPF, что выполняется в статическом конструкторе соответствующего класса.
static NodeImage()
{
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.DefaultValue = null;
metadata.AffectsRender = true;
FillProperty = DependencyProperty.Register(
"Fill", typeof(Brush), typeof(NodeImage), metadata);
}
В конструкторе для регистрации свойства зависимости вначале создаются метаданные metadata – объект класса FrameworkPropertyMetadata. В метаданных для свойства FillProperty задается значение по умолчанию ( null ) и необходимость перерисовки объекта при изменении его свойств ( AffectsRender = true ). Регистрация производится методом DependencyProperty.Register(), для которого параметрами задается имя свойства ( Fill ), его тип ( typeof(Brush) ), тип объекта, в котором регистрируется свойство ( typeof(NodeImage) ) и метаданные ( metadata ). В общем случае при регистрации свойства зависимостей дополнительно может задаваться метод обратного вызова, который будет производить проверку правильности свойств.
Для обеспечения доступа к свойствам зависимостей необходимо объявить свойство Fill, то есть упаковать его в обычную оболочку свойств, используя методы SetValue() и GetValue().
public Brush Fill
{
set { SetValue(FillProperty, value); }
get { return (Brush)GetValue(FillProperty); }
}
Созданное свойство зависимостей Fill можно использовать в коде класса NodeImage как обычное свойство.
Некоторые классы могут совместно использовать одно и то же свойство зависимостей, даже если они имеют отдельные
Разновидностью свойств зависимостей являются присоединяемые свойства зависимостей. Данные свойства применяется к классу, отличному от того, в котором оно определено. Для регистрации присоединяемого свойства зависимостей используется метод RegisterAttached(). Упаковывать данное свойство нет необходимости
Свойства зависимостей поддерживают уведомление об изменениях и динамическое разрешение значений.
При изменении значений свойства зависимостей осуществляется обратный вызов, который отвечает за обновление привязок данных и запуск триггеров. Из этого следует, что для поддержания заданной функциональности при изменении свойств зависимостей необходимо создать привязку, которая будет использовать значение свойства, или написать триггер, который будет автоматически изменять другое свойство или производить другие действия.
Динамическое разрешение значений свойств зависимостей определяет механизм извлечения значений из свойств зависимостей. При извлечении значений из свойств система WPF определяет базовое значение, учитывая следующие факторы:
Наиболее высокий приоритет имеет локальное значение, если оно установлено в программе. Если локальное значение не задано, установка значения передается вверх по списку следующему элементу, который может его принять, вплоть до значения по умолчанию.
Маршрутизируемое событие – это сообщение о событии, которое возникло в одном элементе, а генерация может осуществляться в другом элементе. Маршрутизируемое событие представляется
Например, для класса NodeImage необходимо определить маршрутизируемое событие DeleteNode. Для этого создаем статический экземпляр класса RoutedEvent, который объявляется с модификатором readonly. К имени события должен добавлять постфикс Event.
public static readonly RoutedEvent DeleteNodeEvent;
Регистрация маршрутизируемого события должна проводиться в статическом конструкторе класса с помощью метода RegisterRoutedEvent.
DeleteNodeEvent = EventManager.RegisterRoutedEvent("DeleteNode",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NodeImage));
При регистрации задается
Упаковщик события определяет методы для добавления и удаления прослушивания событий: AddHandler() и RemoveHandler().
public event RoutedEventHandler DeleteNode
{
add { AddHandler(DeleteNodeEvent, value); }
remove { RemoveHandler(DeleteNodeEvent, value); }
}
Класс, в котором создано маршрутизируемое событие, должен отвечать за его генерацию. В классе NodeImage в контекстном меню имеется пункт ( itemDelete ), который отвечает за генерацию события DeleteNodeEvent.
void ContextMenu_OnClick(object sender, RoutedEventArgs e)
{
MenuItem item = e.Source as MenuItem;
if (item == itemDelete)
{
RoutedEventArgs argsEvent = new RoutedEventArgs();
argsEvent.RoutedEvent = NodeImage.DeleteNodeEvent;
argsEvent.Source = this;
RaiseEvent(argsEvent);
}
}
В обработчике ContextMenu_OnClick() создается экземпляр ( argsEvent ) класса RoutedEventArgs, который содержит информацию о состоянии и данные события, связанные с маршрутизируемым событием.
RoutedEventArgs argsEvent = new RoutedEventArgs();
Для объекта argsEvent задаются событие и источник.
argsEvent.RoutedEvent = NodeImage.DeleteNodeEvent; argsEvent.Source = this;
Метод RaiseEvent() производит генерацию события для вызывающего объекта.
RaiseEvent(argsEvent);
Присоединение обработчика событий можно выполнить в коде. Если в каком-либо классе объявлен и создан объект nodeSet класса NodeImage, то подключение обработчика Node_Delete() к событию DeleteNode можно выполнить следующим образом.
nodeSet.DeleteNode += Node_Delete;
Подключение обработчиков события в WPF также можно выполнить в
Маршрутизируемые события бывают трех видов:
Обрабатывать поднимающиеся и опускающиеся события можно не только в том элементе, где они были сгенерированы, но и в других элементах визуального дерева.
В общем случае, , , , .
При проектировании приложений возникает задача создания привязки для некоторых свойств объектов и элементов интерфейса. Такая привязка возможна только в том случае, если свойство объекта-источника является общедоступным.
Для изучения технологии
Создадим проект для интерфейсного элемента ввода данных (рис 3.1).
(рис 3.1) Окно ввода вещественных чисел<Window x:Class="Wpf_Primer1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Ввод вещественного числа" Height="140" Width="300">
<Grid Name="gridPrimir" Height="85">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Margin="3,3,3,15" Name="label1">Параметр</Label>
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1" />
<Button Grid.Column="1" Grid.Row="1" Margin="5,5,28,12" Name="button1"
Click="button1_Click">Проверить</Button>
</Grid>
</Window>
Код класса Window1:
using System;
using System.Windows;
using System.Windows.Controls;
namespace Wpf_Primer1
{
public partial class Window1 : Window
{
public static float Factor { get; set; }
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Factor.ToString(), "Проверка значения параметра");
}
}
}
Свойство Factor класса Window1 необходимо для приема вводимого вещественного числа и его целесообразно сделать статическим для доступа из других
В общем случае для привязки к объекту, не являющемуся элементом, можно использовать следующие свойства:
В нашем случае целесообразно использовать свойство RelativeSource. Для реализации привязки введем изменение в
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1"
Text="{Binding Path = Factor, RelativeSource =
{RelativeSource FindAncestor, AncestorType = {x:Type Window}}}"/>
В выражении
Свойство AncestorType определяет элемент в визуальном дереве, где содержится объект-источник. В нашем случае свойство Factor является членом класса Window1 элемента Window.
Протестируем созданное приложение. При вводе строки 0.1, что соответствует формату вещественного числа, получаем результат проверки вещественное число 0,1 (рис 3.2).
(рис 3.2) Проверка ввода строки "0.1"При выводе вещественное число преобразуется к строковому представлению методом ToString():
MessageBox.Show(Factor.ToString(), "Проверка значения параметра");
В результате этого десятичная точка заменяется на запятую и в окне "Проверка значения параметра" выводится строка "0,1", а не введенная "0.1":
При вводе строки "0,1", что не соответствует формату вещественного числа с плавающей точкой, получаем в результате преобразования по умолчания целое число "1" (рис 3.3).
(рис 3.3) Проверка ввода строки "0,1"При вводе недопустимого символа, получаем в результате проверки целое число 1, которое было присвоено свойству
(рис 3.4) Проверка ввода недопустимого символаРезультаты тестирования разработанной программы показывают, что при вводе корректного представления вещественного числа программа работает правильно, а при вводе недопустимых символов или нарушении формата вещественного числа (ввод запятой вместо точки) программа выдает неправильный результат. Это определяет необходимость проверки достоверности ввода для предотвращения некорректных значений.
При корректном вводе данных мы убедились, что взаимодействие между свойством Text элемента TextBox и свойством Factor класса Window1 правильно поддерживается созданной привязкой, то есть информация от целевого объекта передается к объекту-источнику.
В приложениях часто возникают ситуации, когда данные изменяются в коде программы и это изменение желательно увидеть в интерфейсном элементе. Проверим корректность связи от объекта-источника к целевому объекту. Для этого добавим в форму ещё одну кнопку, при нажатии на которую значение свойства ).
(рис 3.5) Проверка корректности программного изменения объекта-источникаПроверка корректности программного изменения объекта-источника
Это является результатом того, что свойство Text элемента TextBox не уведомляется об изменении свойства Factor, которое не является свойством зависимостей. Для обеспечения уведомления между объектом-источником и целевым объектом можно поступить следующим образом:
Продемонстрируем первый и третий способ решения задачи обеспечения синхронизации представления данных между объектом-источником и целевым объектом.
Определим в классе Window1 свойство зависимостей Factor. Измененный код принимает следующий вид:
public partial class Window1 : Window
{
public static readonly DependencyProperty FactorProperty;
public float Factor
{
set{SetValue(FactorProperty, value);}
get{return (float)GetValue(FactorProperty);}
}
static Window1()
{
FactorProperty = DependencyProperty.Register("Factor",
typeof(float), typeof(Window1),
new FrameworkPropertyMetadata (0.0F,
FrameworkPropertyMetadataOptions.AffectsRender));
}
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Factor.ToString(), "Проверка значения параметра");
}
private void button2_Click(object sender, RoutedEventArgs e)
{
Factor *= 2;
}
}
Вначале определяется свойство зависимостей:
public static readonly DependencyProperty FactorProperty;
Затем формируют методы доступа к свойству зависимостей:
public float Factor
{
set{SetValue(FactorProperty, value);}
get{return (float)GetValue(FactorProperty);}
}
На последнем шаге свойство зависимостей регистрируется в статическом конструкторе:
static Window1()
{
FactorProperty = DependencyProperty.Register("Factor",
typeof(float), typeof(Window1),
new FrameworkPropertyMetadata (0.0F,
FrameworkPropertyMetadataOptions.AffectsRender));
}
Теперь при нажатии кнопки button2 изменяется значение свойства Factor и целевого объекта свойства Text элемента TextBox.
Для
public class FactorClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
private float factor;
public float Factor
{
get { return factor; }
set
{
factor = value;
OnPropertyChanged((new PropertyChangedEventArgs("Factor"));
}
}
}
В классе объявляется событие PropertyChanged:
event PropertyChangedEventHandler PropertyChanged;
Затем кодируется обработчик события OnPropertyChanged:
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
Для свойства Factor при его изменении производится генерация события:
public float Factor
{
get { return factor; }
set
{
factor = value;
OnPropertyChanged OnPropertyChanged(new
PropertyChangedEventArgs("Factor"));
}
}
С учетом создания класса FactorClass код окна Window1 требует корректировки:
public partial class Window1 : Window
{
private static FactorClass factor = new FactorClass();
public static FactorClass Factor
{
get { return Window1.factor; }
}
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Factor.Factor.ToString(),
"Проверка значения параметра");
}
private void button2_Click(object sender, RoutedEventArgs e)
{
Factor.Factor *= 2;
}
}
В коде окна Window1 добавляется поле и свойства Factor типа FactorClass:
private static FactorClass factor = new FactorClass();
public static FactorClass Factor
{
get { return Window1.factor; }
}
В обработчиках кнопок обращение к свойству Factor производят через экземпляр класса FactorClass.
Тестирование внутреннего изменения значение свойства Factor и целевого объекта свойства Text элемента TextBox показывают синхронизацию изменений свойств.
При использовании класса FactorClass выражение привязки можно упростить. В этом случае создается привязка без указания источника, то есть необходимо указать только свойство Factor класса FactorClass:
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1"
Text="{Binding Path = Factor}"/>
Элемент Grid спроектированного окна является контейнером для всех визуальных элементов пользовательского интерфейса. Если свойству DataContext элемента Grid задать значение экземпляра класса FactorClass, то выражение
public Window1()
{
InitializeComponent();
gridPrimir.DataContext = Factor;
}
Проведем тестирование созданного приложения. Вначале введем значение 0.3 и проверим результат (рис 3.6).
(рис 3.6) Проверка корректности привязки данныхДалее нажмем кнопку "Увеличить в 2 раза" и посмотрим реакцию приложения (рис 3.7). Приложение работает корректно.
(рис 3.7) Проверка корректности привязки данных при программном изменении объекта-источникаВ рассматриваемом примере поддерживается двусторонняя связь между объектом-источником и целевым объектом. В общем случае направление привязки может быть однонаправленным и двунаправленным. Это определяется свойством Binding.Mode, которое может принимать следующие значения:
Для явного задания направления привязки необходимо сформировать свойство Mode целевого объекта:
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1"
Text="{Binding Path = Factor, Mode = TwoWay}" />
При применении привязки нужно принимать во внимание режим обновления данных между источником и целевым свойством. Режим обновления задается свойством Binding.UpdateSourceTrigger, которое может принимать следующие значения:
Если нам необходимо немедленное обновление целевого свойства и источника, то необходимо добавить изменения с
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1"
Text="{Binding Path = Factor, Mode = TwoWay, UpdateSourceTrigger = PropertyChanged}" />
При выполнении
В технологии WPF
Для проектирования преобразователя значений необходимо выполнить следующее:
В случае преобразования вещественного значения в строковое можно использовать метод ToString(), а для обратного преобразования - Single.Parse().
Спроектируем класс конвертора StringToFloatConvert для преобразования строковых данных в вещественное число.
[ValueConversion(typeof(float),typeof(string))]
public class StringToFloatConvert : IValueConverter
{
public object Convert(object value, Type typeTarget,
object param, CultureInfo culture)
{
if (value != null)
return value.ToString();
else
return string.Empty;
}
public object ConvertBack(object value, Type typeTarget,
object param, CultureInfo culture)
{
if ((value != null) (value.ToString() != ""))
return Single.Parse(value.ToString(), NumberStyles.Float);
else
return 0;
}
}
Обратите внимание на то, что для преобразования строки данных в вещественное число используется метод Single.Parse(value.ToString(), NumberStyles.Float). Данный метод преобразует строковое представление числа в указанном стиле в эквивалентное ему число одиночной точности с
Для подключения преобразователя необходимо к дескриптору окна добавить атрибут с отображением пространства имен проекта на префикс пространства имен XML:
xmlns:s="clr-namespace:Wpf_Primer1"
Далее необходимо создать экземпляр класса StringToFloatConvert и присвоить его свойству Convert в привязке элемента TextBox:
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1">
<TextBox.Text>
<Binding Path="Factor" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.Converter>
<s:StringToFloatConvert />
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>
Тестирование приложения показывает, что при вводе цифровой строки с запятой, то есть вещественного
(рис 3.8) Проверка корректности привязки с преобразованием данныхТеперь вернемся к вопросу проверки достоверности вводимых данных. Логика проверки достоверности вводимых данных должна перехватывать некорректные значения и отвергать их.
Технология WPF предполагает два варианта проверки достоверности данных для перехвата неверных значений:
Спроектируем правило проверки достоверности ввода данных путем создания класса ValidationFactor, который должен быть наследником класса правил ValidationRule из пространства имен System.Windows.Control:
public class ValidationFactor: ValidationRule
{
public float Min { get; set;}
public float Max { get; set;}
public override ValidationResult Validate(object value,
System.Globalization.CultureInfo cultureInfo)
{
try
{
if (((string)value).Length > 0)
Window1.Factor.Factor = Single.Parse((string)value,
NumberStyles.Float);
if((string)value == "")
Window1.Factor.Factor = 0;
}
catch (Exception e)
{
return new ValidationResult(false, " Введен недопустимый символ! ");
}
if ((Window1.Factor.Factor < Min) || (Window1.Factor.Factor > Max))
return new ValidationResult(false, " Вводимое значение вне диапазона от
" + Min + " до " + Max + ". ");
else
return new ValidationResult(true, null);
}
}
В классе ValidationFactor определены свойства Min и Max, описывающие минимальное и максимально возможное значения вводимого вещественного числа и
Создадим экземпляр класса ValidationFactor и присвоим его свойству ValidationRules в привязке элемента TextBox:
<TextBox Grid.Column="1" Margin="5,5,28,15" Name="textBox1">
<TextBox.Text>
<Binding Path="Factor" Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged">
<Binding.Converter>
<s:StringToFloatConvert />
</Binding.Converter>
<Binding.ValidationRules>
<s:ValidationFactor Min="0" Max="1"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Свойствам Min и Max класса ValidationFactor определим значения соответственно 0 и 1.
Тестирование приложения показывает, что при вводе корректного значения приложение работает правильно, а при вводе некорректного символа или числа вне интервала [0,1] происходит подсвет рамки элемента TextBox красным цветом (рис 3.9).
(рис 3.9) Проверка корректности ввода символовНа рис 3.9 показан ввод недопустимого нецифрового символа и недопустимого по значению числа.
Следует отметить, что коллекция Binding.ValidationRules может состоять из неограниченного числа правил. Когда значение фиксируется в источнике, WPF будет проверять каждое правило проверки достоверности по порядку. Если все проверки достоверности пройдут успешно, то WPF вызовет преобразователь данных и применит значения к источнику. Если проверка не пройдет, то текстовое поле будет очерчено красным цветом, будут установлены свойства HasError и Error и инициируется событие Error.
Для обработки события Error необходимо установить свойство Binding.NotifyOnValidationError в True:
<Binding Path="Factor" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
Событие Error является маршрутизируемым, использует передачу вверх и поэтому его можно обработать в родительском контейнере, например в Grid окна:
<Grid Name="gridPrimir" Height="85" Validation.Error="gridPrimir_Error">
Код обработчика будет отображать сообщение с информацией об ошибке:
private void gridPrimir_Error(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added)
{
MessageBox.Show(e.Error.ErrorContent.ToString(), "Предупреждение",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
Свойство и рис 3.11 приведены реакция приложения на ввод некорректных данных.

(рис 3.11) Реакция на ввод недопустимого символа(рис 3.10) Реакция на ввод недопустимого числаИнформацию об ошибке можно получить и другим способом. Можно отобразить сообщение об ошибке в ToolTip элемента TextBox. При этом сообщение об ошибке будет появляться, когда пользователь поместим курсор мыши над элементом TextBox. Для реализации такого варианта необходимо создать шаблон ошибок, который будет содержать триггер, реагирующий на присваивание свойству Validation.HasError значения true и применит ToolTip с сообщением об ошибке:
<Window.Resources>
<Style TargetType="{x:Type TextBox}" >
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value=
"{Binding RelativeSource = {RelativeSource Self},
Path = (Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
При формировании свойства и рис 3.13 приведены примеры применения подсказки для проверки ввода данных.

(рис 3.13) Подсказка при вводе недопустимого символа(рис 3.12) Подсказка при вводе недопустимого числа
Свойство зависимостей, присоединяемое свойство зависимостей, маршрутизируемое событие,
DependencyProperty, FrameworkPropertyMetadata, DependencyProperty.Register, RoutedEvent, RoutedEventHandler, RoutedEventArgs, IValueConverter, ValidationRule.
Свойства зависимостей и маршрутизируемые события являются эффективными инструментами технологии WPF для построения привязки объектов и обеспечения гибкости обработки разнообразных событий в приложении.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.