Опубликован: 08.07.2011 | Доступ: свободный | Студентов: 1772 / 93 | Оценка: 4.15 / 4.08 | Длительность: 15:28:00
Лекция 3:

Базовые инструменты WPF

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >
Аннотация: Свойства зависимостей и маршрутизируемые события являются новыми объектами в технологии WPF, которые обеспечивают такие возможности как анимация, привязка данных и стили. Использование свойств зависимостей позволяет отслеживать их изменение на различных уровнях иерархии элементов приложения. Маршрутизируемое событие может возникать в одном элементе, а обрабатываться в других элементах, поднимаясь или опускаясь по иерархии элементов дерева визуализации. Привязка данных позволяет связывать элементы управления и интерфейсные элементы с данными, используя свойства зависимостей. В процессе привязки данных можно проводить их преобразование и проверку.

Цель

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

Свойства зависимостей и маршрутизируемые события

В технологии WPF определены свойства зависимостей, которые являются элементами более высокого уровня функциональности по сравнению с обычными свойствами Microsoft.NET [ 4 ] , [ 8 ] . Свойства зависимостей обеспечивают возможность работы с основными средствами WPF, такими как анимация, привязка данных и стили. Большинство свойств, которыми обладают элементы WPF, являются свойствами зависимостей. Их можно рассматривать как обычные свойства .Net, но они обладают дополнительным набором возможностей WPF. В концептуальном отношении поведение свойств зависимостей не отличается от поведения обычных свойств, но на более низком уровне представления имеется иная реализация. Свойства зависимостей эффективно потребляют память и поддерживают такие высокоуровневые особенности, как уведомления об изменениях и наследование значений свойств. Кроме того, в WPF введены маршрутизируемые события, которые обладают большими возможностями по сравнению с обычными событиями .NET. Маршрутизируемые события могут перемещаться по дереву элементов (спускаться и подниматься) и позволяют выполнять обработку события в одном элементе при его возникновении в другом элементе.

Свойства зависимостей

При создании свойства зависимостей необходимо определить экземпляр класса 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 определяет базовое значение, учитывая следующие факторы:

  • значение по умолчанию;
  • унаследованное значение;
  • значение из стиля темы;
  • значение из стиля проекта;
  • локальное значение.

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

Маршрутизируемые события

Маршрутизируемое событие – это сообщение о событии, которое возникло в одном элементе, а генерация может осуществляться в другом элементе. Маршрутизируемое событие представляется статическим полем, доступным только для чтения, которое зарегистрировано в конструкторе и упаковано стандартным определением события .NET.

Например, для класса NodeImage необходимо определить маршрутизируемое событие DeleteNode. Для этого создаем статический экземпляр класса RoutedEvent, который объявляется с модификатором readonly. К имени события должен добавлять постфикс Event.

public static readonly RoutedEvent DeleteNodeEvent;

Регистрация маршрутизируемого события должна проводиться в статическом конструкторе класса с помощью метода RegisterRoutedEvent.

DeleteNodeEvent = EventManager.RegisterRoutedEvent("DeleteNode",
	RoutingStrategy.Bubble,  typeof(RoutedEventHandler), typeof(NodeImage));

При регистрации задается имя события ( DeleteNode ), тип маршрута ( RoutingStrategy.Bubble ), делегат, определяющий синтаксис обработчика события (в нашем случае RoutedEventHandler ) и класс, к которому принадлежит событие ( 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 также можно выполнить в XAML описании элемента управления.

Маршрутизируемые события бывают трех видов:

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

Тип события определяется при регистрации параметром перечисления RoutingStrategy, который может принимать следующие значения:

  • Direct – маршрутизируемое событие не проходит через дерево элементов, но поддерживает другие возможности маршрутизируемых событий, например, обработку класса, EventTrigger или EventSetter ;
  • Bubble – маршрутизируемое событие использует восходящую стратегию, где экземпляр события перемещается вверх по дереву, от источника события к корневому элементу;
  • Tunnel – маршрутизируемое событие использует нисходящую стратегию, где экземпляр события перемещается вниз по дереву, от корневого элемента к исходному.

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

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >
Александр Петров
Александр Петров

При загрузке данных из БД возникает исключение InvalidOperationException с сообщением: Элемент коллекции должен быть пустым перед использованием ItemsSource. Знаю, что для заполнения DataGrid можно использовать коллекции Items или ItemsSource, но одновременно их использовать нельзя: если задано значение для свойства ItemsSource и в коде C# добавляется элемент в Items, возникает исключение. 
Вопрос, как отследить и отключить добавление элемента в Items?

Максим Спиридонов
Максим Спиридонов

В пятой лекции на второй странице в компиляторе выскакивает ошибка в строчке :

ObjectQuery<Employee> employees = DataEntitiesEmployee.Employees;

Ошибка CS0029

Не удается неявно преобразовать тип "System.Data.Entity.DbSet<WpfApplProject.Employee>" в "System.Data.Entity.Core.Objects.ObjectQuery<WpfApplProject.Employee>".

в using прописал все как положено, здесь похоже именно с преобразованием типов проблемы