Architecture de WPF

Cette rubrique fournit une visite guidée de la hiérarchie de classes Windows Presentation Foundation (WPF). Il couvre la plupart des sous-systèmes principaux de WPF et décrit comment ils interagissent. Il détaille également certains des choix faits par les architectes de WPF.

System.Object

Le modèle de programmation WPF principal est exposé via du code managé. Au début de la phase de conception du WPF, il y avait un certain nombre de débats sur l’endroit où la ligne devrait être dessinée entre les composants gérés du système et les éléments non gérés. Le CLR fournit un certain nombre de fonctionnalités qui rendent le développement plus productif et robuste (notamment la gestion de la mémoire, la gestion des erreurs, le système de type commun, etc.), mais ils sont coûteux.

Les principaux composants de WPF sont illustrés dans la figure ci-dessous. Les sections rouges du diagramme (PresentationFramework, PresentationCore et milcore) sont les principales parties de code de WPF. Parmi celles-ci, milcore est le seul composant non managé. Milcore est écrit en code non managé pour permettre une intégration étroite avec DirectX. Tout l’affichage dans WPF est effectué via le moteur DirectX, ce qui permet un rendu matériel et logiciel efficace. WPF exige également un contrôle précis de la mémoire et de l’exécution. Le moteur de composition dans milcore est extrêmement sensible aux performances et nécessite de renoncer à de nombreux avantages du CLR pour obtenir des performances.

The position of WPF within the .NET Framework.

La communication entre les parties managées et non managées de WPF est abordée plus loin dans cette rubrique. Les autres aspects du modèle de programmation managé sont décrits ci-dessous.

System.Threading.DispatcherObject

La plupart des objets dans WPF dérivent de DispatcherObject, qui fournit les constructions de base pour traiter la concurrence et le threading. WPF est basé sur un système de messagerie implémenté par le répartiteur. Cela fonctionne beaucoup comme la pompe de message Win32 familière; en fait, le répartiteur WPF utilise des messages User32 pour effectuer des appels entre threads.

Il existe vraiment deux concepts fondamentaux à comprendre lors de la discussion de l’accès concurrentiel dans WPF : le répartiteur et l’affinité de thread.

Pendant la phase de conception de WPF, l’objectif était de passer à un seul thread d’exécution, mais à un modèle non thread « affinitisé ». L’affinité de thread intervient quand un composant utilise l’identité du thread d’exécution pour stocker un type d’état. Sa forme la plus courante est l’utilisation du stockage local des threads (TLS) pour stocker l’état. L’affinité de thread nécessite que chaque thread logique d’exécution soit détenu par un seul thread physique dans le système d’exploitation, susceptible d’utiliser beaucoup de mémoire. Au final, les architectes ont opté pour un modèle de thread WPF semblable au modèle de thread User32 existant, basé sur un seul thread d’exécution avec affinité de thread. La principale raison de cette interopérabilité était l’interopérabilité : les systèmes comme OLE 2.0, le Presse-papiers et Internet Explorer nécessitent toutes une exécution d’affinité de thread unique (STA).

Étant donné que vous avez des objets avec des threads STA, vous devez prévoir un moyen de communiquer entre les threads et valider que vous êtes sur le thread approprié. C’est le rôle du répartiteur. Le répartiteur est un système de répartition de messages de base, avec plusieurs files d’attente de priorités différentes. Les messages concernés sont, par exemple, les notifications d’entrées brutes (déplacements de la souris), les fonctions de framework (disposition) ou les commandes utilisateur (exécuter cette méthode). En dérivant de DispatcherObject, vous créez un objet CLR qui a un comportement STA et recevez un pointeur vers un répartiteur au moment de la création.

System.Windows.DependencyObject

L’une des philosophies architecturales principales utilisées dans la construction de WPF était une préférence pour les propriétés sur les méthodes ou les événements. Les propriétés, qui sont déclaratives, permettent plus facilement de spécifier l’intention au lieu de l’action. Cette approche permettait également la prise en charge d’un système piloté par un modèle, ou piloté par les données, pour afficher le contenu de l’interface utilisateur. Cette philosophie produisait l’effet recherché de créer davantage de propriétés qu’il n’en fallait pour établir des liaisons, afin de mieux contrôler le comportement d’une application.

Afin d’avoir davantage de système piloté par les propriétés, un système de propriétés plus riche que ce que fournit le CLR a été nécessaire. Les notifications de modification en sont un exemple simple. Pour permettre une liaison bidirectionnelle, les deux côtés de la liaison doivent prendre en charge la notification de modification. Pour que le comportement soit lié aux valeurs des propriétés, vous devez être notifié de chaque modification d’une valeur de propriété. Microsoft .NET Framework a une interface, INotifyPropertyChange, qui permet à un objet de publier des notifications de modification, mais il est facultatif.

WPF fournit un système de propriétés plus riche, dérivé du DependencyObject type. Le système de propriétés est un vrai système de propriétés de « dépendance », car il effectue le suivi des dépendances entre les expressions de propriété et revalide automatiquement les valeurs de propriété après un changement des dépendances. Par exemple, si vous avez une propriété qui hérite (par FontSizeexemple), le système est automatiquement mis à jour si la propriété change sur un parent d’un élément qui hérite de la valeur.

La base du système de propriétés WPF est le concept d’une expression de propriété. Dans cette première version de WPF, le système d’expressions de propriété est fermé et les expressions sont toutes fournies dans le cadre de l’infrastructure. L’utilisation d’expressions explique pourquoi la liaison de données, les styles ou l’héritage ne sont pas codés en dur dans le système de propriétés, mais plutôt fournis par des couches supérieures dans le framework.

Le système de propriétés offre également un stockage fragmenté des valeurs de propriété. Étant donné que les objets peuvent avoir des dizaines (voire des centaines) de propriétés, et que la plupart des valeurs conservent leur état par défaut (héritée, définie par les styles, etc.), il n’est pas nécessaire de définir chacune de ces propriétés pour chaque instance d’un objet.

La notion de propriétés jointes est la dernière nouvelle fonctionnalité du système de propriétés. Les éléments WPF reposent sur le principe de la composition et de la réutilisation des composants. Il est souvent vrai que certains éléments contenant (comme un Grid élément de disposition) ont besoin de données supplémentaires sur les éléments enfants pour contrôler son comportement (comme les informations de ligne/colonne). Au lieu d’associer toutes ces propriétés à chaque élément, les objets peuvent fournir des définitions de propriétés pour d’autres objets. Ceci est similaire aux fonctionnalités « expando » de JavaScript.

System.Windows.Media.Visual

Une fois qu’un système a été défini, l’étape suivante est l’obtention des pixels dessinés à l’écran. La Visual classe fournit une arborescence d’objets visuels, chacune contenant éventuellement des instructions de dessin et des métadonnées sur le rendu de ces instructions (découpage, transformation, etc.). Visual est conçu pour être extrêmement léger et flexible, de sorte que la plupart des fonctionnalités n’ont pas d’exposition d’API publique et s’appuient fortement sur les fonctions de rappel protégées.

Visual est vraiment le point d’entrée du système de composition WPF. Visual est le point de connexion entre ces deux sous-systèmes, l’API managée et le milcore non managé.

WPF affiche les données en parcourant les structures de données non managées gérées par le milcore. Ces structures, appelées nœuds de composition, représentent une arborescence d’affichage hiérarchique, avec des instructions de rendu à chaque nœud. Cette arborescence, illustrée à droite de la figure ci-dessous, est accessible uniquement par le biais d’un protocole de messagerie.

Lors de la programmation de WPF, vous créez Visual des éléments et des types dérivés, qui communiquent en interne à l’arborescence de composition via ce protocole de messagerie. Chacun Visual dans WPF peut en créer un, aucun ou plusieurs nœuds de composition.

The Windows Presentation Foundation Visual Tree.

Il faut souligner un point important de cette architecture : toute l’arborescence des éléments visuels et des instructions de dessin est mise en cache. En termes graphiques, WPF utilise un système de rendu conservé. Le système peut ainsi actualiser les dessins à une fréquence élevée sans que cela entraîne un blocage du système de composition lors des rappels de code utilisateur. Cela aide à prévenir l’apparition d’une application qui ne répond pas.

Un autre point important à noter, que le diagramme ne montre pas vraiment, est la façon dont le système effectue réellement la composition.

Dans User32 et GDI, le système fonctionne sur un système de découpage en mode immédiat. Quand un composant doit être affiché, le système établit les limites de la zone de découpage en dehors de laquelle le composant n’est pas autorisé à manipuler les pixels, puis il demande au composant de dessiner les pixels dans cette zone. Cette approche est particulièrement efficace dans les systèmes à mémoire limitée, car lorsqu’un changement est effectué, vous avez uniquement à intervenir sur le composant concerné (la couleur d’un pixel étant toujours définie par un seul composant).

WPF utilise un modèle de peinture « algorithme de peintre ». Cela signifie que chaque composant doit effectuer le rendu de l’arrière vers l’avant de l’affichage, au lieu de procéder par découpage. Chaque composant peut ainsi dessiner par-dessus l’affichage du composant précédent. L’avantage de ce modèle est que vous pouvez afficher des formes complexes partiellement transparentes. Avec le matériel graphique moderne d’aujourd’hui, ce modèle est relativement rapide (ce qui n’était pas le cas lorsque User32/ GDI ont été créés).

Comme mentionné précédemment, une philosophie fondamentale de WPF consiste à passer à un modèle de programmation plus déclaratif et « centré sur les propriétés ». Cela s’observe à deux endroits intéressants du système visuel.

Tout d’abord, si vous réfléchissez au système graphique en mode conservation, cela revient en fait à passer d’un modèle de type DrawLine/DrawLine impératif à un modèle orienté données new Line()/new Line(). Cette évolution vers un rendu piloté par les données permet d’exprimer des opérations complexes sur les instructions de dessin à l’aide de propriétés. Les types dérivés Drawing sont effectivement le modèle objet pour le rendu.

Ensuite, si vous examinez le système d’animation, vous verrez qu’il est presque entièrement déclaratif. Pour éviter au développeur d’avoir à calculer l’emplacement suivant, ou la couleur suivante, vous pouvez exprimer des animations dans un jeu de propriétés sur un objet animation. Ces animations peuvent alors exprimer l’intention du développeur ou du concepteur (par exemple, déplacer ce bouton d’ici à là dans cinq secondes), et le système peut déterminer le meilleur moyen d’y parvenir.

System.Windows.UIElement

UIElement définit les sous-systèmes principaux, notamment la disposition, l’entrée et les événements.

La disposition est un concept de base dans WPF. Dans la plupart des systèmes, soit il y a un jeu fixe de modèles de disposition (HTML prend en charge trois modèles de disposition : fluide, absolu et tables), soit il n’y a aucun modèle de disposition (User32 prend réellement en charge uniquement le positionnement absolu). WPF a commencé avec l’hypothèse que les développeurs et les concepteurs souhaitaient un modèle de disposition flexible et extensible, qui pouvait être piloté par des valeurs de propriété plutôt que par une logique impérative. UIElement Au niveau, le contrat de base pour la disposition est introduit : un modèle en deux phases avec Measure et Arrange passe.

Measure permet à un composant de déterminer la taille qu’il souhaite prendre. Il s’agit d’une phase distincte du Arrange fait qu’il existe de nombreuses situations où un élément parent demande à un enfant de mesurer plusieurs fois pour déterminer sa position et sa taille optimales. Le fait que les éléments parents demandent aux éléments enfants de mesurer illustre une autre philosophie clé de WPF : la taille du contenu. Tous les contrôles dans WPF prennent en charge la capacité de dimensionner à la taille naturelle de leur contenu. Cela facilite la localisation et permet une disposition dynamique des éléments lors du redimensionnement. La Arrange phase permet à un parent de positionner et de déterminer la taille finale de chaque enfant.

Beaucoup de temps est souvent passé à parler du côté de sortie de WPF – Visual et des objets connexes. Toutefois, il existe aussi de nombreuses innovations côté entrée. Probablement le changement le plus fondamental dans le modèle d’entrée pour WPF est le modèle cohérent par lequel les événements d’entrée sont routés via le système.

L’entrée est générée sous forme de signal sur un pilote de périphérique en mode noyau, puis elle est routée vers le processus et le thread appropriés selon un processus complexe impliquant User32 et le noyau Windows. Une fois que le message User32 correspondant à l’entrée est routé vers WPF, il est converti en message d’entrée brut WPF et envoyé au répartiteur. WPF permet aux événements d’entrée brutes d’être convertis en plusieurs événements réels, ce qui permet aux fonctionnalités telles que « MouseEnter » d’être implémentées à un niveau faible du système avec une remise garantie.

Chaque événement d’entrée est converti en deux événements au moins : un événement « preview » et l’événement réel. Tous les événements dans WPF ont une notion de routage via l’arborescence d’éléments. Les événements sont dits « bulles » s’ils passent d’une cible vers la racine, et sont dits « tunnel » s’ils commencent à la racine et passent vers une cible. Les événements preview d’entrée passent par un tunnel, ce qui permet aux éléments dans l’arborescence d’appliquer un filtre ou d’effectuer une action sur un événement. Les événements standard (non-preview) se propagent vers le haut de l’arborescence, de l’élément cible à la racine.

Grâce à cette distinction entre les phases de tunneling et de propagation, l’implémentation de fonctions comme les accélérateurs de clavier s’effectue de façon cohérente dans un environnement composite. Dans User32, vous pouvez implémenter les accélérateurs de clavier en utilisant une seule table globale qui contient tous les accélérateurs à prendre en charge (Ctrl+N correspondant à « Nouveau »). Dans le répartiteur de votre application, vous pouvez appeler TranslateAccelerator qui détecte les messages d’entrée dans User32 et détermine si l’un d’eux correspond à un accélérateur enregistré. Dans WPF, cela ne fonctionnerait pas car le système est entièrement « composable » : n’importe quel élément peut gérer et utiliser n’importe quel accélérateur de clavier. Avec ce modèle d’entrée à deux phases, les composants peuvent implémenter leur propre « TranslateAccelerator ».

Pour effectuer cette étape plus loin, UIElement introduit également la notion de CommandBindings. Le système de commande WPF permet aux développeurs de définir des fonctionnalités en termes de point de terminaison de commande – quelque chose qui implémente ICommand. Avec les liaisons de commande, un élément peut définir un mappage entre un mouvement d’entrée (Ctrl+N) et une commande (Nouveau). Les mouvements d’entrée et les définitions de commande sont extensibles, et peuvent être liés ensemble au moment de l’utilisation. Vous pouvez alors facilement, par exemple, autoriser un utilisateur final à personnaliser les combinaisons de touches qu’il souhaite utiliser dans une application.

À ce stade de la rubrique, les fonctionnalités « principales » de WPF – fonctionnalités implémentées dans l’assembly PresentationCore, ont été le focus. Lors de la création de WPF, une séparation propre entre les éléments de base (comme le contrat pour la disposition avec Measure et Arrange) et les éléments de framework (comme l’implémentation d’une disposition spécifique comme Grid) était le résultat souhaité. L’objectif était de fournir un point d’extensibilité en bas dans la pile qui permette aux développeurs externes de créer éventuellement leurs propres frameworks.

System.Windows.FrameworkElement

FrameworkElement peut être examiné de deux façons différentes. Il introduit un ensemble de stratégies et de personnalisations sur les sous-systèmes introduits dans des couches inférieures de WPF. Il introduit également un ensemble de nouveaux sous-systèmes.

La stratégie principale introduite est FrameworkElement autour de la disposition de l’application. FrameworkElement s’appuie sur le contrat de disposition de base introduit par UIElement et ajoute la notion d’un « emplacement » de disposition qui permet aux auteurs de disposition de disposer d’un ensemble cohérent de sémantiques de disposition pilotées par la propriété. Les propriétés telles que HorizontalAlignment, , VerticalAlignmentet MinWidthMargin (pour nommer quelques-uns) donnent à tous les composants dérivés du FrameworkElement comportement cohérent à l’intérieur des conteneurs de disposition.

FrameworkElement offre également une exposition d’API plus facile à de nombreuses fonctionnalités trouvées dans les couches principales de WPF. Par exemple, FrameworkElement fournit un accès direct à l’animation via la BeginStoryboard méthode. Un Storyboard moyen de scripter plusieurs animations par rapport à un ensemble de propriétés.

Les deux éléments les plus critiques introduits FrameworkElement sont la liaison et les styles de données.

Le sous-système de liaison de données dans WPF doit être relativement familier à toute personne qui a utilisé Windows Forms ou ASP.NET pour créer une interface utilisateur d’application. Chaque système offre un moyen simple d’exprimer votre intention de lier une ou plusieurs propriétés d’un élément spécifique à une donnée. WPF prend entièrement en charge la liaison de propriétés, la transformation et la liaison de liste.

L’une des fonctionnalités les plus intéressantes de la liaison de données dans WPF est l’introduction des modèles de données. Ils permettent de spécifier de manière déclarative comment afficher les données. Au lieu de créer une interface utilisateur personnalisée pouvant être liée à des données, vous pouvez contourner le problème et laisser les données déterminer l’affichage à créer.

Les styles constituent une forme légère de liaison de données. Vous pouvez utiliser les styles pour lier un jeu de propriétés d’une définition partagée à une ou plusieurs instances d’un élément. Les styles sont appliqués à un élément par référence explicite (en définissant la Style propriété) ou implicitement en associant un style au type CLR de l’élément.

System.Windows.Controls.Control

La création de modèles est la fonctionnalité la plus importante du contrôle. Si vous considérez le système de composition de WPF comme un système de rendu en mode conservation, la création de modèles permet à un contrôle de décrire son rendu d’une manière déclarative et paramétrable. Un ControlTemplate n’est vraiment rien d’autre qu’un script pour créer un ensemble d’éléments enfants, avec des liaisons aux propriétés proposées par le contrôle.

Control fournit un ensemble de propriétés de stock, , Foreground, Background, Paddingpour nommer quelques-uns, que les auteurs de modèles peuvent ensuite utiliser pour personnaliser l’affichage d’un contrôle. L’implémentation d’un contrôle fournit un modèle de données et un modèle d’interaction. Le modèle d’interaction définit un jeu de commandes (Fermer une fenêtre, par exemple) et des liaisons aux mouvements d’entrée (comme le fait de cliquer sur le X rouge dans le coin supérieur d’une fenêtre). Le modèle de données fournit un jeu de propriétés pour personnaliser le modèle d’interaction ou l’affichage (déterminé par le modèle).

Cette distinction entre le modèle de données (propriétés), le modèle d’interaction (commandes et événements) et le modèle d’affichage (modèles) permet d’effectuer une personnalisation complète de l’apparence et du comportement d’un contrôle.

Le modèle de contenu est un aspect commun du modèle de données des contrôles. Si vous examinez un contrôle comme Button, vous verrez qu’il a une propriété nommée « Contenu » de type Object. Dans Windows Forms et ASP.NET, cette propriété serait généralement une chaîne , mais cela limite le type de contenu que vous pouvez placer dans un bouton. Le contenu d’un bouton peut être une chaîne simple, un objet de données complexe ou une arborescence entière d’éléments. Dans le cas d’un objet de données, le modèle de données sert à créer un affichage.

Résumé

WPF est conçu pour vous permettre de créer des systèmes de présentation dynamiques pilotés par les données. Chaque partie du système est destinée à la création d’objets à l’aide de jeux de propriétés qui pilotent le comportement. La liaison de données est une fonctionnalité essentielle du système qui est intégrée au niveau de chaque couche.

Les applications standard créent un affichage, puis effectuent une liaison avec certaines données. Dans WPF, tout ce qui concerne le contrôle, chaque aspect de l’affichage, est généré par un type de liaison de données. Le texte placé dans un bouton est affiché en créant un contrôle composé à l’intérieur du bouton et en liant son affichage à la propriété de contenu du bouton.

Lorsque vous commencez à développer des applications basées sur WPF, il doit se sentir très familier. Vous pouvez définir des propriétés, utiliser des objets et lier des données de la même façon que vous pouvez utiliser Windows Forms ou ASP.NET. Avec un examen plus approfondi de l’architecture de WPF, vous constaterez que la possibilité existe de créer des applications beaucoup plus riches qui traitent fondamentalement les données comme le pilote principal de l’application.

Voir aussi