Поделиться через


Архитектура WPF

В этом разделе представлен обзор иерархии классов Windows Presentation Foundation (WPF). Он охватывает большую часть основных подсистем WPF и описывает, как они взаимодействуют. В нем также подробно описаны некоторые варианты, сделанные архитекторами WPF.

System.Object

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

Основные компоненты WPF показаны на рисунке ниже. Красные разделы схемы (PresentationFramework, PresentationCore и milcore) являются основными частями кода WPF. Из них только один является неуправляемым компонентом - милкор. Милкор написан в неуправляемом коде, чтобы обеспечить тесную интеграцию с DirectX. Все отображение в WPF выполняется с помощью подсистемы DirectX, что позволяет эффективно выполнять отрисовку оборудования и программного обеспечения. WPF также требует точного контроля над памятью и выполнением. Система компоновки в milcore крайне чувствительна к производительности, и для повышения этой чувствительности пришлось отказаться от многих преимуществ CLR.

Позиция WPF в .NET Framework.

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

System.Threading.DispatcherObject

Большинство объектов в WPF являются производными от DispatcherObject, которое предоставляет основные конструкции для работы с параллелизмом и потоком. WPF основан на системе обмена сообщениями, реализованной диспетчером. Это работает так же, как знакомый насос сообщений Win32; На самом деле диспетчер WPF использует сообщения User32 для выполнения межпоточных вызовов.

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

На этапе разработки WPF цель заключалась в переходе к одному потоку выполнения, но непоточной модели "affinitized". Привязка потока происходит, когда компонент использует идентификатор выполняемого потока для хранения определенного типа состояния. Наиболее распространенной формой этого является использование локального хранилища потоков (TLS) для хранения состояния. Сопоставление потоков требует, чтобы каждый логический поток выполнения принадлежал только одному физическому потоку в операционной системе, что может стать требовательным к ресурсам памяти. В итоге, модель потоков WPF была синхронизирована с существующей моделью потоков User32 однопоточного выполнения с привязкой к потоку. Основная причина заключалась во взаимодействии: такие системы, как OLE 2.0, буфер обмена и Internet Explorer, требуют выполнения в однопоточном режиме (STA).

Учитывая, что у вас есть объекты с потоком STA, требуется способ обмена данными между потоками и проверка того, что вы находитесь в правильном потоке. Здесь лежит роль диспетчера. Диспетчер — это базовая система отправки сообщений с несколькими приоритетными очередями. Примерами сообщений являются необработанные входные уведомления (перемещение мыши), функции платформы (макет) или пользовательские команды (выполнение этого метода). Создав DispatcherObjectобъект CLR с поведением STA, который получает указатель на диспетчер во время создания.

System.Windows.DependencyObject

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

Чтобы система была больше ориентирована на управление свойствами, потребовалась более богатая система свойств, чем та, которую предоставляет среда CLR. Простой пример этого богатства — уведомления об изменениях. Чтобы включить двустороннюю привязку, необходимо, чтобы обе стороны привязки поддерживали уведомления об изменениях. Чтобы иметь поведение, связанное со значениями свойств, необходимо получать уведомления при изменении значения свойства. Microsoft .NET Framework имеет интерфейс INotifyPropertyChange, который позволяет объекту публиковать уведомления об изменениях, однако это необязательно.

WPF предоставляет более богатую систему свойств, свойства в которой производны от типа DependencyObject. Система свойств действительно является системой свойств "зависимости", которая отслеживает зависимости между выражениями свойств и автоматически обновляет значения свойств при изменении зависимостей. Например, если у вас есть свойство, наследуемое (например FontSize), система автоматически обновляется, если свойство изменяется на родительском элементе элемента, наследующего значение.

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

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

Последней новой функцией системы свойств является понятие присоединенных свойств. Элементы WPF основаны на принципе композиции и повторного использования компонентов. Часто некоторые содержащие элементы (например Grid , элемент макета) требуют дополнительных данных о дочерних элементах для управления его поведением (например, сведения о строке или столбце). Вместо ассоциации всех этих свойств с каждым элементом любой объект может предоставлять определения свойств для любого другого объекта. Это похоже на функции "expando" в JavaScript.

System.Windows.Media.Visual

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

Visual — это действительно точка входа в систему композиции WPF. Visual — это точка соединения между этими двумя подсистемами: управляемым API и неуправляемым milcore.

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

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

Визуальное дерево Windows Presentation Foundation.

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

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

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

WPF использует алгоритм "Painter's Algorithm" для отрисовки. Это означает, что вместо обрезки каждого компонента, каждый компонент должен быть запрограммирован на отрисовку с задней стороны на переднюю часть экрана. Это позволяет каждому компоненту закрашивать экран предыдущего компонента. Преимущество этой модели заключается в том, что у вас могут быть сложные, частично прозрачные фигуры. С современным графическим оборудованием эта модель работает относительно быстро, что не было возможным во времена создания 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 с "New"). В диспетчере вашего приложения вы вызовете TranslateAccelerator, который будет проверять входные сообщения, обрабатываемые в User32, и определять, соответствует ли какой-либо из них зарегистрированному акселератору. В WPF это не работает, так как система полностью "компонуема" — любой элемент может обрабатывать и использовать любой акселератор клавиатуры. Наличие этой двухфазной модели ввода позволяет компонентам реализовать собственный "TranslateAccelerator".

Чтобы продвинуться на шаг вперёд, UIElement также вводится понятие CommandBindings. Система команд WPF позволяет разработчикам определять функциональность через точку завершения команды — элемент, реализующий ICommand. Привязки команд позволяют элементу определить сопоставление между входным жестом (CTRL+N) и командой (New). Жесты ввода и определения команд расширяемы и могут быть подключены во время использования. Это упрощает, например, возможность пользователя настраивать сочетания клавиш, которые он хочет использовать в приложении.

До этого момента основное внимание в теме уделялось "основным" функциям WPF — функциям, реализованным в сборке PresentationCore. При построении WPF чистое разделение между базовыми частями (например, контрактом макета с измерением и размещением) и частями платформы (например, реализацией конкретного макета, например Grid) являлось целью. Цель заключалась в том, чтобы обеспечить низкую точку расширяемости в стеке, которая позволит внешним разработчикам создавать собственные платформы при необходимости.

System.Windows.FrameworkElement

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

Основная политика, представленная FrameworkElement, касается макета приложения. FrameworkElement строится на базовом контракте макета, представленном UIElement, и добавляет понятие "ячейка макета", что позволяет авторам макета проще использовать согласованный набор семантики макета на основе свойств. Свойства, такие как HorizontalAlignment, VerticalAlignment, MinWidth и Margin (и это лишь некоторые из них) обеспечивают всем компонентам, производным от FrameworkElement, согласованное поведение внутри контейнеров макета.

FrameworkElement также обеспечивает более простой доступ к API для многих функций, имеющихся в основных слоях WPF. Например, FrameworkElement предоставляет прямой доступ к анимации через BeginStoryboard метод. A 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 вы узнаете, что существует возможность создания гораздо более богатых приложений, которые принципиально рассматривают данные как основной драйвер приложения.

См. также