Partager via


WPF Architecture

Cette rubrique fournit une visite guidée de la hiérarchie de classes Windows Presentation Foundation (WPF). Il couvre la plupart des principaux sous-systèmes de WPF et décrit comment ils interagissent. Il détaille également certains des choix effectués 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 plusieurs débats sur l’endroit où la ligne devait être dessinée entre les composants gérés du système et les composants non gérés. Le CLR fournit un certain nombre de fonctionnalités qui rendent le développement plus productif et plus robuste (notamment la gestion de la mémoire, la gestion des erreurs, le système de type commun, etc.), mais ils viennent à un coût.

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. D’entre eux, un seul est un composant non managé - milcore. Milcore est écrit dans du code non managé afin d’activer 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 a dû renoncer à de nombreux avantages du CLR pour obtenir des performances.

Position de WPF dans le .NET Framework.

La communication entre les parties managées et non managées de WPF est abordée plus loin dans cette rubrique. Le reste du modèle de programmation managé est décrit ci-dessous.

System.Threading.DispatcherObject

La plupart des objets dans WPF dérivent de DispatcherObject, qui fournit les constructions de base pour la gestion de la concurrence et des threads. 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 lorsque l'on discute de la concurrence dans WPF : le répartiteur et l'affinité de thread.

Pendant la phase de conception de WPF, l’objectif était de passer à un seul fil d’exécution, mais avec un modèle sans affinitisation de fil. L’affinité de thread se produit lorsqu’un composant utilise l’identité du thread en cours d’exécution pour stocker un certain type d’état. La forme la plus courante consiste à utiliser le magasin local de 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, ce qui peut devenir gourmand en mémoire. En fin de compte, le modèle de thread de WPF a été synchronisé avec le modèle de thread existant de User32 pour une exécution monothread avec affinité de thread. La principale raison en était l'interopérabilité : des systèmes comme OLE 2.0, le Presse-papiers et Internet Explorer nécessitent tous une exécution avec affinité de thread unique (STA).

Étant donné que vous avez des objets avec le thread STA, vous avez besoin d’un moyen de communiquer entre les threads et de vérifier que vous êtes sur le thread correct. Ici se trouve le rôle du répartiteur. Le répartiteur est un système de distribution de messages de base, avec plusieurs files d’attente hiérarchisées. Les exemples de messages incluent des notifications d'entrée brutes (mouvement de la souris), des fonctions de cadre (disposition) ou des commandes utilisateur (exécutez cette méthode). En dérivant de DispatcherObject, vous créez un objet CLR qui a un comportement STA et reçoit 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 sont déclaratives et vous permettent de spécifier plus facilement l’intention au lieu d’une action. Cela a également pris en charge un système piloté par un modèle ou piloté par les données pour afficher le contenu de l’interface utilisateur. Cette philosophie avait l’effet prévu de créer davantage de propriétés auxquelles vous pouviez établir une liaison, afin de mieux contrôler le comportement d’une application.

Afin d’avoir plus du système piloté par les propriétés, un système de propriétés plus riche que ce que le CLR fournit était nécessaire. Un exemple simple de cette richesse est les notifications de changement. Pour activer la liaison bidirectionnelle, il est nécessaire que les deux côtés de la liaison supportent la notification de modification. Pour avoir un comportement lié aux valeurs de propriété, vous devez être averti lorsque la valeur de propriété change. 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 vraiment un système de propriétés « dépendance » dans lequel il suit les dépendances entre les expressions de propriété et revalide automatiquement les valeurs de propriété lorsque les dépendances changent. Par exemple, si vous avez une propriété qui hérite, comme FontSize, le système est automatiquement mis à jour si la propriété change sur le 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. Les expressions sont la raison pour laquelle le système de propriétés n’a pas de liaison de données, ni de style, ni d’héritage codés en dur, mais sont plutôt fournis par des couches ultérieures dans le cadre.

Le système de propriétés propose également un stockage économe en espace pour les valeurs de propriété. Étant donné que les objets peuvent avoir des dizaines (si pas des centaines) de propriétés, et la plupart des valeurs sont dans leur état par défaut (hérité, défini par des styles, etc.), pas chaque instance d’un objet doit avoir le poids total de chaque propriété définie dessus.

La dernière fonctionnalité du système de propriétés est la notion de propriétés jointes. Les éléments WPF sont basés sur le principe de la composition et de la réutilisation des composants. Il arrive souvent que certains éléments contenant (comme un élément de disposition Grid) nécessitent des données supplémentaires sur les éléments enfants pour contrôler leur comportement (comme les informations de lignes/colonnes). Au lieu d’associer toutes ces propriétés à chaque élément, tout objet est autorisé à fournir des définitions de propriétés pour tout autre objet. Ceci est similaire aux fonctionnalités « expando » de JavaScript.

System.Windows.Media.Visual

Avec un système défini, l'étape suivante consiste à faire dessiner des pixels à 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, n’est accessible qu’à l’aide d’un protocole de messagerie.

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

Arborescence Visuelle Windows Presentation Foundation.

Il existe ici un détail architectural très important : l’arborescence entière des visuels et des instructions de dessin est mise en cache. En termes graphiques, WPF utilise un système de rendu conservé. Cela permet au système de repeindre à des taux d’actualisation élevés sans que le système de composition bloque lors des rappels au code utilisateur. Cela aide à éviter l'apparition d'une application non réactive.

Un autre détail important qui n’est pas vraiment visible dans le diagramme 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. Lorsqu’un composant doit être rendu, le système établit une limite de découpage en dehors de laquelle le composant n’est pas autorisé à toucher les pixels, puis le composant est invité à peindre des pixels dans cette zone. Ce système fonctionne très bien dans les systèmes limités en mémoire, car lorsque quelque chose change, vous n’avez qu’à toucher le composant concerné , aucun deux composants ne contribuent jamais à la couleur d’un seul pixel.

WPF utilise un modèle de rendu « algorithme du peintre ». Cela signifie qu’au lieu de découper chaque composant, chaque composant est invité à effectuer le rendu de l’arrière vers l’avant de l’affichage. Cela permet à chaque composant de peindre sur l’affichage du composant précédent. L’avantage de ce modèle est que vous pouvez avoir des formes complexes et 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 a été créé).

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. Dans le système visuel, cela s’affiche dans quelques endroits intéressants.

Tout d’abord, si vous pensez au système graphique en mode conservé, cela s’éloigne vraiment d’un modèle de type DrawLine/DrawLine impératif, vers un modèle orienté données – nouvelle ligne()/nouvelle ligne(). Ce déplacement vers le 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érivant de Drawing constituent effectivement le modèle objet pour le rendu.

Deuxièmement, si vous évaluez le système d’animation, vous verrez qu’il est presque complètement déclaratif. Au lieu d’exiger qu’un développeur calcule l’emplacement suivant ou la couleur suivante, vous pouvez exprimer des animations sous la forme d’un ensemble de propriétés sur un objet d’animation. Ces animations peuvent ensuite exprimer l’intention du développeur ou du concepteur (déplacer ce bouton d’ici jusqu’à là en 5 secondes), et le système peut déterminer la façon la plus efficace 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 de nombreux systèmes, il existe un ensemble fixe de modèles de disposition (HTML prend en charge trois modèles pour la disposition ; flux, absolu et tables) ou aucun modèle pour la disposition (User32 ne prend vraiment en charge que le positionnement absolu). WPF a commencé avec l’hypothèse que les développeurs et les concepteurs voulaient un modèle de disposition flexible et extensible, qui pourrait être piloté par des valeurs de propriété plutôt que par une logique impérative. Au niveau UIElement, le contrat de base pour la disposition est introduit : un modèle à deux phases avec Measure et Arrange passes.

Measure permet à un composant de déterminer la taille à prendre. Il s'agit d'une phase distincte de celle de Arrange, car il existe de nombreuses situations où un élément parent demande à un enfant de se 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 : ajuster la taille au contenu. Tous les contrôles de WPF peuvent être dimensionnés selon la taille naturelle de leur contenu. Cela facilite beaucoup la localisation et permet une disposition dynamique des éléments à mesure que les éléments sont redimensionnés. 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é sortie de WPF – Visual et des objets connexes. Toutefois, il existe également une quantité considérable d'innovations du côté des intrants. 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 provient d’un signal sur un pilote de périphérique en mode noyau et est acheminée vers le processus et le thread appropriés via un processus complexe impliquant le noyau Windows et User32. 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 bas niveau du système avec une livraison garantie.

Chaque événement d’entrée est converti en au moins deux événements : un événement « aperçu » 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 traversent d’une cible vers la racine, et sont dits « tunnel » s’ils commencent à la racine et traversent jusqu’à une cible. Tunnel des événements de prévisualisation d'entrée, permettant à tout élément de l'arborescence de filtrer ou d'agir sur l'événement. Les événements standard (non-prévisualisation) remontent ensuite de leur point d'origine jusqu’à la racine.

Ce découpage entre la phase de tunnel et la phase de bulle permet aux fonctionnalités comme les accélérateurs de clavier de fonctionner de façon cohérente dans un environnement composite. Dans User32, vous implémentez des accélérateurs clavier en ayant une table globale unique contenant tous les accélérateurs que vous souhaitez prendre en charge (mappage Ctrl+N sur « Nouveau »). Dans le répartiteur de votre application, vous appelez TranslateAccelerator, qui intercepte les messages d’entrée dans User32 et détermine si un accélérateur enregistré correspond. Dans WPF, cela ne fonctionne pas car le système est entièrement « composable » : tout élément peut gérer et utiliser n’importe quel accélérateur clavier. Le fait de disposer de ce modèle de deux phases pour l’entrée permet aux composants d’implémenter leur propre « TranslateAccelerator ».

Pour aller plus loin, UIElement présente é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. Les liaisons de commande permettent à un élément de 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 commandes sont extensibles et peuvent être câblés ensemble au moment de l’utilisation. Cela rend trivial, par exemple, de permettre à un utilisateur final de personnaliser les liaisons de clé 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 nette entre les éléments fondamentaux (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é bas dans la pile qui permettrait aux développeurs externes de créer leurs propres cadres si nécessaire.

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 les couches inférieures de WPF. Il introduit également un ensemble de nouveaux sous-systèmes.

La politique principale introduite par FrameworkElement concerne 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 les propriétés. Les propriétés telles que HorizontalAlignment, VerticalAlignment, MinWidthet Margin (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 offre une méthode pour créer des scripts de plusieurs animations sur un ensemble de propriétés.

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

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. Dans chacun de ces systèmes, il existe un moyen simple d’exprimer que vous souhaitez qu’une ou plusieurs propriétés d’un élément donné soient liées à une partie de données. WPF prend en charge de manière complète la liaison de propriétés, la transformation et la liaison de listes.

L’une des fonctionnalités les plus intéressantes de la liaison de données dans WPF est l’introduction de modèles de données. Les modèles de données vous permettent de spécifier de manière déclarative la façon dont un élément de données doit être visualisé. Au lieu de créer une interface utilisateur personnalisée qui peut être liée aux données, vous pouvez à la place transformer le problème et laisser les données déterminer l’affichage qui sera créé.

Le style est vraiment une forme légère de liaison des données. En utilisant le style, vous pouvez lier un ensemble 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 fonctionnalité la plus importante du contrôle est la création de modèles. Si vous pensez au système de composition de WPF comme un système de rendu en mode conservé, la création de modèles permet à un contrôle de décrire son rendu de manière paramétrable et déclarative. Un ControlTemplate est vraiment rien de plus qu’un script pour créer un ensemble d’éléments enfants, liées aux propriétés offertes 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 ensemble de commandes (comme Close pour une fenêtre) et des liaisons aux mouvements d’entrée (comme cliquer sur le X rouge dans le coin supérieur de la fenêtre). Le modèle de données fournit un ensemble de propriétés pour personnaliser le modèle d’interaction ou personnaliser l’affichage (déterminé par le modèle).

Ce fractionnement 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 une personnalisation complète de l’apparence et du comportement d’un contrôle.

Un aspect commun du modèle de données des contrôles est le modèle de contenu. Si vous examinez un contrôle comme Button, vous verrez qu’il a une propriété nommée « Content » de type Object. Dans Windows Forms et ASP.NET, cette propriété est généralement une chaîne. Toutefois, 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 d’éléments entière. Dans le cas d’un objet de données, le modèle de données est utilisé pour construire 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 de l'ensemble est conçue pour créer des objets par le biais de jeux de propriétés qui déterminent le comportement. La liaison de données est une partie fondamentale du système et est intégrée à chaque couche.

Les applications traditionnelles créent un affichage, puis se lient à 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 trouvé à l’intérieur d’un bouton s’affiche 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, cela devrait vous sembler 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 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