Архитектура WPF
Обновлен: Ноябрь 2007
В этом разделе предлагается ознакомительная демонстрация иерархии классов WPF (Windows Presentation Foundation). Она охватывает большую часть подсистем WPF и описывает их взаимодействие. Здесь также подробно рассматриваются некоторые архитектурные решения WPF.
В этом разделе содержатся следующие подразделы.
- System.Object
- System.Threading.DispatcherObject
- System.Windows.DependencyObject
- System.Windows.Media.Visual
- System.Windows.UIElement
- System.Windows.FrameworkElement
- System.Windows.Controls.Control
- Заключение
- Связанные разделы
System.Object
Основная модель программирования WPF доступна через управляемый код. Ранее, на этапе проектирования WPF, несколько раз обсуждалось, где следует провести черту между управляемыми и неуправляемыми компонентами системы. CLR предоставляет ряд возможностей, которые делают разработку более продуктивной и надежной (включая управление памятью, обработку ошибок, систему общих типов и т. д.), но требуют некоторых затрат.
Основные компоненты WPF показаны на расположенном ниже рисунке. Красные разделы диаграммы (PresentationFramework, PresentationCore и milcore) представляют собой основные части кода WPF. Только один из этих компонентов является неуправляемым — milcore. Компонент milcore написан в неуправляемом коде, чтобы обеспечить тесную интеграцию с DirectX. Все, что отображается в WPF, осуществляется через механизм DirectX для эффективной аппаратно-программной визуализации. WPF также необходим тонкий контроль памяти и процесса выполнения. Механизм композиции в milcore крайне чувствителен к производительности и требует отказа от многих преимуществ CLR в пользу производительности.
Связь между управляемыми и неуправляемыми частями WPF обсуждается далее в этом разделе. Остальная часть управляемой модели программирования описана ниже.
System.Threading.DispatcherObject
Большинство объектов в WPF являются производными от DispatcherObject, который предоставляет основные конструкции для одновременной обработки и работы с потоками. WPF основывается на системе обмена сообщениями, реализованной диспетчером. Его работа очень похожа на обычную загрузку сообщений в Win32; в действительности диспетчер WPF использует сообщения User32 для выполнения перекрестных вызовов потоков.
Существует два основных понятия при обсуждении параллельной работы в WPF — диспетчер и сходство потоков.
На этапе проектирования WPF основной целью был переход к одному потоку выполнения при непотоковой модели «со сходством». Сходство потоков случается, когда компонент использует идентификацию выполняемого потока для сохранения некоторых типов состояния. Наиболее распространенной формой этого является использование локальной памяти потока (TLS) для сохранения состояния. Сходство потоков требует, чтобы каждый логический поток выполнения принадлежал только одному физическому потоку в операционной системе, что создает большую нагрузку на память. В итоге сохранилась синхронизация потоковой модели WPF с существующей потоковой моделью User32 однопотокового выполнения со сходством потоков. Основной причиной этого явилась функциональная совместимость — для всех систем, таких как OLE 2.0, буфер обмена и Internet Explorer, требуется выполнение в одном сходном потоке (STA).
Пусть имеются объекты с организацией потоковой обработки STA и необходим способ связи между потоками и проверка нахождения в правильном потоке. В этом заключается роль диспетчера. Диспетчер — это основная диспетчерская система управления сообщениями, включающая несколько очередей с назначенными приоритетами. Примеры сообщений включают необработанные входящие уведомления (перемещение мыши), функции Framework (макет) или пользовательские команды (выполнение этого метода). Благодаря наследованию из DispatcherObject создается объект CLR, который обладает функциями STA и получает во время создания указатель на диспетчер.
System.Windows.DependencyObject
Одним из основных архитектурных принципов, используемых в построении WPF, является предпочтение свойств методам или событиям. Свойства являются декларативными и с их помощью проще указать цель, а не действие. Поддерживается также система для отображения содержимого пользовательского интерфейса на основе моделей (или данных). Такой подход оказал влияние на создание дополнительных свойств, к которым можно осуществить привязку в целях лучшего управления поведением приложения.
Чтобы иметь больше свойств управления системой, требовалась более полная система свойств, чем предоставляемая CLR. Простым примером такой полноты являются уведомления об изменении. Для включения двусторонней привязки необходимо, чтобы обе стороны привязки поддерживали уведомления об изменениях. Чтобы поведение зависело от значений свойств, необходимо получать уведомление в случае изменения значения свойства. В Microsoft .NET Framework имеется интерфейс INotifyPropertyChange, который позволяет объекту публиковать уведомления об изменениях (однако это необязательно).
WPF предоставляет обширную систему свойств, полученную из DependencyObject. Система свойств действительно является системой свойств «зависимостей» в том смысле, что она отслеживает зависимости между выражениями свойств и автоматически проверяет значения свойства при изменении зависимости. Например, если имеется наследующее свойство (например FontSize), то система автоматически обновляется при изменении свойства в родительском объекте элемента, наследующего значение.
Основой системы свойств WPF является понятие «выражение свойства». В этом первом выпуске WPF система выражений свойств закрыта, и все выражения предоставлены как часть Framework. Выражения объясняют, почему система свойств не поддерживает привязку к данным, создание стилей или жестко запрограммированное наследование, но вместо этого представлена макетами более поздних версий в Framework.
Система свойств также предоставляет способ разреженного хранения значений свойств. Поскольку объекты могут иметь десятки (если не сотни) свойств, и большинство значений находятся в состоянии по умолчанию (унаследованы, задаются стилем и т. д.), не каждый экземпляр объекта должен иметь все определенные в нем свойства в полном объеме.
Последней новой возможностью системы свойств является уведомление о вложенных свойствах зависимостей. Элементы WPF построены по принципу композиции и повторного использования компонентов. Часто бывает так, что некоторому содержащему элементу (например элементу макета Grid) требуются дополнительные данные о дочерних элементах для управления их поведением (например сведения о строках и столбцах). Вместо того чтобы сопоставлять все эти свойства с каждым элементом, любой объект может предоставить определения свойств для любого другого объекта. Это похоже на возможности «expando» JavaScript.
System.Windows.Media.Visual
После определения системы следующим шагом является рисование точек на экране. Класс Visual предназначен для построения дерева визуальных объектов, каждый из которых дополнительно содержит инструкции по рисованию и метаданные о том, как отобразить эти инструкции (обрезка, преобразование и т. д.). Класс Visual спроектирован чрезвычайно упрощенным и гибким, поэтому большинство возможностей не имеют открытого доступа к API-интерфейс и полагаются на защищенные функции обратного вызова.
Visual в действительности является точкой входа в систему композиции WPF. Visual — это точка соединения между двумя подсистемами: управляемой API-интерфейс и неуправляемой milcore.
WPF отображает данные, проходя по неуправляемым структурам данных под управлением milcore. Эти структуры, называемые узлами композиции, представляют собой иерархическое дерево отображения с инструкциями по визуализации в каждом узле. Это дерево, показанное в правой части расположенного ниже рисунка, доступно только через протокол обмена сообщениями.
При программировании WPF создаются элементы Visual и производные типы, которые осуществляют внутреннее взаимодействие с деревом композиции через этот протокол обмена сообщениями. Каждый элемент Visual в WPF может создать один, ни одного или несколько узлов композиции.
Здесь имеется один очень важный архитектурный момент — все дерево визуальных объектов и инструкций по рисованию кэшируется. С графической точки зрения WPF использует систему сохраненной визуализации. Это позволяет системе осуществлять перерисовку с высокой частотой без блокирования системы композиции при обратных вызовах, обращенных к коду пользователя. Это помогает предотвратить признаки неотвечающего приложения.
Другим важным моментом, который не заметен на диаграмме, является то, как система в действительности выполняет композицию.
В User32 и Интерфейс GDI система работает в немедленном режиме системы обрезки. Когда требуется визуализация компонента, система устанавливает границы обрезки, вне которых компонент не может изменять точки, а затем компонент запрашивает рисование точек в этой области. Эта система работает очень хорошо в системах с ограниченной памятью, поскольку в случае каких-либо изменений приходится иметь дело только с измененным компонентом — два компонента никогда не воздействуют на цвет одной точки.
WPF использует «алгоритм художника» для модели рисования. Это означает, что вместо обрезки каждого компонента каждый компонент запрашивается для отрисовки, начиная с заднего плана и до переднего плана отображения. Это позволяет рисовать каждый компонент поверх отображения предыдущего компонента. Преимуществом этой модели является то, что можно создавать сложные, полупрозрачные фигуры. В сочетании с современным графическим оборудованием эта модель является относительно быстрой (чего нельзя было сказать о создании User32/Интерфейс GDI).
Как упоминалось ранее, основным принципом WPF является переход к более декларативной, «сфокусированной на свойствах» модели программирования. В визуальной системе это проявляется в паре любопытных моментов.
Во-первых, если говорить о сохраненном режиме графической системы, он действительно отражает переход от обязательной модели DrawLine/DrawLine к модели, ориентированной на данные — new Line ()/new Line(). Этот переход к управляемой данными визуализации позволяет выполнять сложные операции в инструкциях по рисованию, выражаемых с помощью свойств. Типы, получаемые из Drawing, являются эффективной объектной моделью для визуализации.
Во-вторых, оценивая систему анимации, можно увидеть, что она является практически полностью декларативной. Вместо обязательного вычисления разработчиком следующего положения или цвета можно выразить анимации как набор свойств для объекта анимации. Эти анимации могут выражать замыслы разработчика или проектировщика (переместить эту кнопку отсюда туда в течение 5 секунд), и система может определить наиболее эффективный способ для их выполнения.
System.Windows.UIElement
UIElement определяет основные подсистемы, включая «Макет», «Ввод данных» и «События».
Макет представляет собой основное понятие в WPF. Во многих системах либо имеется фиксированный набор моделей макета (HTML поддерживает три модели для макета: потоковую, абсолютную и табличную), либо нет модели макета (User32 фактически поддерживает только абсолютное позиционирование). WPF запускается с предположением, что разработчики и проектировщики хотели бы получить гибкую, расширяемую модель макета, которая может управляться значениями свойств, а не принудительной логикой. На уровне UIElement вводится основное соглашение для макета — двухэтапная модель с передачей Measure и Arrange.
Measure позволяет компоненту определить требуемый размер. Этот этап является отдельным от Arrange, поскольку существует множество ситуаций, когда родительский элемент запрашивает несколько раз измерение дочернего элемента для определения его оптимального положения и размера. Тот факт, что родительские элементы запрашивают измерение дочерних, демонстрирует еще один ключевой принцип WPF — размер по содержимому. Все элементы управления в WPF поддерживает возможность изменения размера по размеру их содержимого. Это значительно упрощает локализацию и позволяет осуществлять динамическую разметку элементов в соответствии с изменением размеров. На этапе Arrange родительский элемент может расположить каждый дочерний элемент и определить его конечный размер.
Много времени часто затрачивается на обсуждение внешней стороны WPF — Visual и связанных объектов. Однако существует также множество новшеств со стороны ввода данных. Вероятно, наиболее фундаментальным изменением в модели ввода для WPF является согласованная модель, согласно которой события ввода направляются через систему.
Ввод возникает как сигнал драйверу устройства режима ядра и направляется в нужный процесс и поток через сложный процесс, в котором участвуют ядро Windows и User32. Как только сообщение User32, соответствующее вводу, направляется в WPF, оно преобразуется в необработанное входящее сообщение WPF и отправляется диспетчеру. WPF позволяет преобразовать необработанные входящие события в несколько фактических событий, обеспечивая такие функции, как «MouseEnter», реализуемые на низком уровне системы с гарантированной доставкой.
Каждое событие ввода преобразуется, по крайней мере, в два события — событие «предварительного просмотра» и фактическое событие. Все события в WPF имеют представление о маршрутизации через дерево элементов. События называются «всплывающими», если они перемещаются от конечной точки вверх по дереву к корню, и «нисходящими», если они начинаются от корня дерева и перемещаются вниз к конечной точке. События предварительного просмотра ввода перемещаются по нисходящей, позволяя любому элементу в дереве фильтровать или обрабатывать событие. Обычные события (не предварительного просмотра) перемещаются по восходящей от конечной точки вверх к корню.
Это разделение между нисходящим и всплывающим этапами делает возможным согласованную реализацию таких возможностей, как сочетания клавиш. В User32 сочетания клавиш реализуются посредством одной глобальной таблицы, содержащей все необходимые сочетания клавиш (Ctrl + N сопоставляется с командой «Создать»). В диспетчере приложения вызывается метод TranslateAccelerator, который будет анализировать входящие сообщения в User32 и определять их соответствие зарегистрированному сочетанию клавиш. В WPF это не работает, поскольку система полностью «компонуема» — любой элемент может обрабатывать и использовать любые сочетания клавиш. Наличие этой двухэтапной модели для ввода позволяет компонентам реализовать собственные методы TranslateAccelerator.
Забегая на шаг вперед, в UIElement вводится также понятие CommandBindings. Система команд WPF позволяет разработчикам определять функциональность в терминах конечной точки команды — нечто, что реализует ICommand. Команды привязки включают элемент для определения соответствия между действием ввода (CTRL + N) и командой («Создать»). И действия ввода, и определения команд являются расширяемыми и могут быть связаны вместе во время использования. Это делается, например, для того чтобы разрешить конечному пользователю настраивать привязки клавиш, которые ему необходимо использовать в приложении.
До этого момента в разделе рассматривались «основные» возможности WPF — возможности, реализованные в сборке PresentationCore. При построении WPF предполагалось четкое разделение между фундаментальными частями (такими как соглашение для макета с использованием Measure и Arrange) и частями Framework (такими как реализация определенного макета, например Grid). Целью являлось предоставление точки расширения внизу стека, что позволило бы внешним разработчикам при необходимости создавать свои собственные структуры.
System.Windows.FrameworkElement
Элемент FrameworkElement можно рассматривать с двух разных сторон. Он представляет набор политик и настроек подсистем, введенных на нижнем уровне WPF. В нем также вводится набор новых подсистем.
Основная политика, представленная в FrameworkElement, затрагивает макет приложения. FrameworkElement строится на основе базового соглашения макета, введенного в UIElement, и добавляет понятие «ячейки» макета, что упрощает для авторов макета создание согласованного набора управляемых свойствами семантик макета. Свойства, такие как HorizontalAlignment, VerticalAlignment, MinWidth и Margin (для нескольких именований), обеспечивают всем компонентам, производным от FrameworkElement, согласованное поведение внутри контейнеров макета.
FrameworkElement упрощает также доступ к API-интерфейс для множества функций на уровне ядра WPF. Например, FrameworkElement предоставляет прямой доступ к анимации с помощью метода BeginStoryboard. Storyboard обеспечивает способ создания сценариев нескольких анимаций вместо набора свойств.
Две наиболее важные вещи, представленные в FrameworkElement — это привязка данных и стили.
Подсистема привязки данных в WPF должна быть относительно знакома каждому, кто использовал Windows Forms или ASP.NET, чтобы создать пользовательский интерфейс приложения. В каждой из этих систем имеется простой способ выражения того, что требуется привязка одного или нескольких свойств из данного элемента к фрагменту данных. WPF обеспечивает полную поддержку привязки свойств, преобразования и привязки списков.
Одной из наиболее интересных возможностей привязки данных в WPF является введение шаблонов данных. Шаблоны данных позволяют декларативно указать способ визуализации фрагмента данных. Вместо создания настраиваемого пользовательского интерфейса, который может быть привязан к данным, можно обойти проблему и позволить данным определять отображение, которое будет создано.
Создание стилей — это облегченная форма привязки данных. Посредством создания стилей можно привязать набор свойств из общего определения к одному или нескольким экземплярам элемента. Стили применяются к элементу посредством либо явной ссылки (путем задания свойства Style), либо неявного связывания стиля с типом CLR элемента.
System.Windows.Controls.Control
Наиболее значимая возможность для элемента управления — это использование шаблонов. Если представлять себе систему композиции WPF как сохраненный режим системы визуализации, то шаблоны позволяют элементу управления описывать свою отрисовку в параметризированной, декларативной форме. ControlTemplate в действительности не более чем сценарий для создания набора дочерних элементов с привязками к свойствам, предлагаемым элементом управления.
Control предоставляет набор стандартных свойств Foreground, Background, Padding и т. д., которые авторы шаблона могут затем использовать для настройки отображения элемента управления. Реализация элемента управления обеспечивает модель данных и модель взаимодействия. Модель взаимодействия определяет набор команд (таких как «Закрыть» для окна) и привязки к действиям ввода (таким как нажатие красного символа X в верхнем углу окна). Модель данных предоставляет набор свойств либо для настройки модели взаимодействия, либо для настройки отображения (определяется шаблоном).
Это разделение между моделью данных (свойства), моделью взаимодействия (команды и события) и моделью отображения (шаблоны) позволяет полностью настроить внешний вид и поведение элемента управления.
Типичным аспектом модели данных элементов управления является модель содержимого. Если представить себе элемент управления, например Button, то можно увидеть, что он имеет свойство с именем «Content» типа Object. В Windows Forms и ASP.NET это свойство было бы строкой, но это ограничивает тип содержимого, который можно поместить на кнопку. Содержимое для кнопки может представлять собой простую строку, сложный объект данных или все дерево элементов. В случае объекта данных используется шаблон данных для создания отображения.
Заключение
WPF предназначается для создания динамических, управляемых данными систем представления данных. Каждая часть системы предназначена для создания объектов с помощью наборов свойств, которые определяют их поведение. Привязка данных является основополагающей частью системы и интегрирована на каждом уровне.
Традиционные приложения создают отображение, а затем привязывают его к некоторым данным. В WPF все, что касается элемента управления, каждый аспект отображения, создается посредством некоторого типа привязки данных. Текст внутри кнопки отображается путем создания составного элемента управления внутри кнопки и путем привязки его отображения к свойству содержимого кнопки.
В начале разработки приложений на основе WPF все должно быть знакомо. Можно задавать свойства, использовать объекты и привязывать данные практически так же, как с помощью Windows Forms или ASP.NET. При углублении в архитектуру WPF вы обнаружите, что можно создавать значительно более сложные приложения, которые фундаментально обрабатывают данные, как драйвер ядра приложения.
См. также
Основные понятия
Общие сведения о связывании данных
Общие сведения об эффектах анимации