Alain Zanchetta

Conseil et développement logiciel

Windows, Windows Mobile (Smartphone & Pocket PC), .NET, C#, C++, WPF, Silverlight
Introduction à WPF

Introduction

Windows Vista, le nouveau système d’exploitation de Microsoft, et son lot de nouveautés ont été largement présentés dans la presse. Tout le monde a déjà vu la nouvelle interface utilisateur faisant la part belle à la transparence et aux effets graphiques et aussi entendu parler des nouvelles fonctions liées à la sécurité apportées par Vista. Mais pour le développeur, qu’y a-t-il de nouveau ?

En fait, la sortie de Windows Vista est accompagnée d’un ensemble de technologies composant le .NET Framework 3.0 (auparavant appelé WinFX) qui va étendre la plate-forme de développement Windows déjà très riche avec Win32 et les bibliothèques du Microsoft .NET Framework.

Le .NET Framework 3.0 peut s’installer aussi sur Windows XP SP2 ainsi que Windows Server 2003 et comprend trois composants principaux :

  • Windows Communication Foundation (WCF)
    Auparavant connu par son nom de code « Indigo », Windows Communication Foundation est à la fois une uniformisation et une extension des technologies de communications disponibles sur la plate-forme Windows. Uniformisation car les développeurs pourront appeler des services applicatifs indépendamment des protocoles utilisés au final pour cette invocation : un appel synchrone de service web à l’aide de SOAP ou un appel asynchrone et même différé à l’aide de MSMQ seront écrits de la même manière. Extension car de nouveaux services seront proposés en plus des mécanismes existants, comme par exemple le support de WS-AtomicTransaction.
  • Workflow Foundation (WF)
    Workflow Foundation est un composant permettant l’incorporation de la notion de Workflow dans n’importe quelle application Windows, aussi bien une application distribuée exposée via le web qu’une application console enchaînant des tâches unitaires. Beaucoup de processus métiers peuvent en fait être modélisés par des Workflows, qu’il s’agisse du traitement d’une commande sur un site de commerce électronique, d’une demande de congés dans une entreprise ou d’un appel à un service de support technique ou d’intervention. Workflow Foundation offre les briques de base facilitant la modélisation puis l’implémentation de ces processus.
  • Windows Presentation Foundation (WPF)
    Windows Presentation Foundation sera le composant de WinFX le plus visible pour les utilisateurs finaux. Si WCF et WF vont faciliter la vie du développeur, l’utilisation de WPF se traduira par des applications à la fois plus jolies (utilisation de la 3D, de la transparence, des animations, etc) mais aussi plus faciles à utiliser (utilisation d’une navigation de type web).

Après une première introduction aux principaux objectifs et concepts de WPF, cet article va illustrer la mise en œuvre de cette technologie à travers quelques exemples concrets et montrer quelles évolutions des processus de développements des interfaces utilisateurs on peut d’ores et déjà entrevoir.

Objectifs et concepts de Windows Presentation Foundation

Windows Presentation Foundation capitalise sur l’expérience acquise avec les évolutions de Windows depuis plusieurs années mais aussi sur l’expérience d’Internet et du HTML.

Un développeur d’applications est aujourd’hui confronté à un certain nombre de difficultés et limitations auxquelles Windows Presentation Foundation va essayer de répondre.

Tout d’abord, le développement des interfaces pour les applications Windows (ou riches, smart, etc…) et celui des interfaces pour les applications Web sont différents : on ne manipule pas les mêmes objets, certaines opérations se faisant plus facilement dans un environnement ou dans l’autre.

Par exemple, le développeur Internet peut facilement modifier l’aspect de son application en utilisant la notion de style : comme dans un traitement de texte, une feuille de style permet de modifier simplement les polices de caractères de toute une application, ses couleurs, etc… Le développeur d’application Windows n’a pas une telle possibilité, il doit la réinventer.

Par ailleurs, le développeur Windows est généralement limité par le choix de son outil de développement et de la technologie utilisée : historiquement, le développement d’interfaces en C++ était bien plus compliqué qu’avec des outils comme Visual Basic. Par exemple, un bouton au sens Windows est juste un rectangle avec des bords plus ou moins arrondis et un texte au milieu. Si le développeur C++ a du temps et du courage, il peut décider de redéfinir complètement la notion de bouton : il peut alors donner n’importe quel aspect à ses boutons mais plus ceux-ci seront riches et plus leur temps de développement sera long.

Le développeur Visual Basic a pour sa part un composant initialement plus riche (couleur, police, cohabitation image et texte) mais n’a en revanche pas les mêmes possibilités d’extension.
Les Windows Forms combinent un peu les deux approches en offrant à la fois des composants de base assez riches et de bonnes possibilités d’extension mais ne changent pas fondamentalement la donne : tous les enrichissements d’un composant de base doivent être prévus par le développeur ou la bibliothèque de composants qu’il utilise.

On entrevoit ici le troisième grief qu’on peut faire au développement d’interfaces sous Windows : c’est le développeur qui implémente à la fois la partie « métier » de l’application et sa partie graphique. La situation est un peu différente pour les applications Internet, notamment à l’aide d’ASP.NET où une première séparation de la présentation et du comportement est proposée (via le mécanisme de « Code Behind ») même si elle a certaines limitations.

Windows Presentation Foundation se base sur un modèle permettant de répondre à ces différents soucis :

  • Tous les composants implémentent la notion de style.
    Les apports de cette notion déjà présente pour les applications Internet sont donc généralisés aux applications Windows. Ceci permet à la fois de changer l’aspect global d’une application à l’aide de modifications localisées, mais aussi de personnaliser dynamiquement cet aspect en fonction des préférences de l’utilisateur.
  • Les interfaces sont définies en XML et non par du code .NET.
    L’interface est définie au sein d’un fichier au format XAML. Le XAML correspond à un schéma XML permettant de définir des hiérarchies d’objets. L’intérêt d’un tel modèle est double :
    •  d’une part, il va faciliter le développement d’outils de définition des interfaces interopérables. Microsoft va par exemple proposer une gamme d’outils, appelée Expression, capables de lire et générer du XAML mais d’autres éditeurs (Adobe par exemple) proposent d’ores et déjà l’export XAML dans leurs applications de conception graphique.
    • d’autre part, ce fichier peut être interprété dans des environnements différents : WPF/E (Windows Presentation Foundation Everywhere) permettra l’affichage d’interfaces définies en XAML dans différents navigateurs et sur différentes plates-formes : il sera donc ainsi possible de partager la définition d’une interface entre des applications Windows et des applications Internet.

Malgré tout, il est bien évidemment possible de manipuler tous les éléments de l’interface utilisateur à l’aide de code, tout comme de créer ainsi de nouveaux composants dynamiquement.

  • Cette définition de l’interface utilisateur est séparée du code qui l’anime.
    Le développeur, même s’il conservera vraisemblablement le rôle d’intégrateur, n’a plus à se préoccuper en détails de la définition de l’interface qui peut alors être confié à des graphiques ou designers : le travail en équipe est lui aussi grandement facilité.
  •  Cette séparation de l’aspect graphique et de l’aspect comportement est étendue à tous les niveaux de l’interface.
    Par exemple, un bouton est un objet graphique qui déclenche un événement (« Click ») lorsque l’utilisateur appuie et relâche le bouton de la souris sur cet objet. Pourquoi un bouton serait-il nécessairement un rectangle avec un texte et une image ? Plus encore, pourquoi le développeur devrait-il s’en préoccuper ? Avec WPF, le bouton a deux aspects :
    • pour le développeur, c’est un composant qui génère un événement « click »,
    • pour le designer, c’est un objet graphique qu’il peut personnaliser complètement.

Ces points constituent l’essentiel des fondements de WPF mais il y a bien d’autres aspects qui vont faciliter le développement d’interfaces utilisateurs riches et faciles d’utilisation comme le support des graphiques vectoriels, les animations, la notion de document, la gestion de la navigation, etc.
La suite de cet article va maintenant illustrer par des exemples concrets les points évoqués ci-dessus.

Variations sur un thème classique

La première application écrite dans n’importe quelle technologie doit nécessairement dire bonjour au monde et nous n’échapperons par à cette tradition.

Le projet est créé sous Visual Studio .NET 2005 et comprend deux composants principaux :

  • l’application,
  • la fenêtre principale de celle-ci.

Nous allons nous intéresser plus spécialement à la fenêtre (« Window1 »). Voici sa définition XAML :

<Window x:Class="HelloWorld.Window1"
        xmlns="http://schemas.microsoft.com/ winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/ winfx/2006/xaml"
        Title="HelloWorld" Height="300" Width="300"
>
    <Button Click="SayHello">Dire Hello</Button>
</Window>

Et le code C# associé :

using System;
using System.Windows;

namespace HelloWorld {
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>

    public partial class Window1 : Window {

        public Window1() {
            InitializeComponent();
        }

        public void SayHello(object sender, RoutedEventArgs e) {
            MessageBox.Show("Hello world", "Helloworld App");
        }
    }
}

Le lien entre la définition graphique de la fenêtre (dans Window1.xaml) et la classe implémentant son comportement (définie dans Window1.cs) est réalisé par l’attribut x:Class de l’élément Window du fichier XAML.

Dans le fonctionnement « nominal » de Visual Studio, le fichier XAML est compilé ; cette opération crée deux composants :

  • une version « compilée » du XAML (« BAML ») contenant la définition de la fenêtre et incorporée en tant que ressource dans le binaire final,
  • un fichier Window1.g.cs contenant le code effectuant la liaison entre le code C# et les éléments de l’interface : chargement de la ressource BAML dans la méthode « InitializeComponent » et enregistrement de la méthode « SayHello » sur l’événement Click du bouton.

Remarque : le mot clé « partial » présent dans le code ci-dessus est une nouveauté du C# 2.0, il indique que la définition de la classe Window1 est répartie sur plusieurs fichiers. Cette approche améliore la lisibilité du code en séparant complètement celui écrit par le développeur et le code généré par les outils.

L’exécution de l’application compilée donne les résultats ci-dessous. On peut d’ores et déjà remarquer que l’application supporte très bien le redimensionnement.

Avant d’aller plus loin dans la découverte de Windows Presentation Foundation et du XAML, appliquons successivement deux petites modifications à notre application.

Tout d’abord, enrichissons l’aspect du bouton en lui ajoutant une image et en modifiant un peu le texte associé :

<Button Click="SayHello">
    <StackPanel Orientation="Horizontal">
        <Image Source="Image1.png"/>
        <TextBlock VerticalAlignment="Center" FontFamily="Calibri" FontSize="36">
            Dire <Bold>Hello</Bold>
        </TextBlock>
    </StackPanel>
</Button>

Le code C# n’a pas été modifié, le bouton fonctionne de la même manière qu’auparavant mais a un aspect plus sophistiqué (même si dans cet exemple il n’est pas très différent de ce qu’on sait faire aujourd’hui avec les Windows Forms).

Le contenu du bouton est maintenant un « StackPanel ». Le « StackPanel » fait partie d’une série de composants de base de WPF capables d’organiser la présentation (typiquement, la taille et la position) des composants qu’il contient. Il est nécessaire de mettre un tel composant ici car le composant « Button » ne peut quant à lui héberger qu’un seul contenu. Les principaux composants de WPF permettant l’organisation d’ensembles de composants sont les suivants :

GridOrganisation en lignes et colonnes, redimensionnables ou non (assez proche des tables HTML) des composants
Stackpanel Organisation en une ligne (orientation horizontale) ou en une colonne (orientation verticale) des composants
Canvas Positionnement absolu des composants
DockPanel Positionnement des composants sur les bords du conteneur
WrapPanel Positionnement des composants à la suite : comme en HTML sans instruction particulière, les composants sont affichés à la droite les uns des autres et un retour à la ligne est effectué lorsqu’il n’y a plus de place en fin de ligne

Nous allons profiter de cet exemple simple pour présenter un nouvel aspect de WPF : les fonctions de navigation. Actuellement, on a d’une part les applications Windows classiques qui affichent une fenêtre principale puis empilent sur celle-ci des boîtes de dialogue et d’autre part les applications Web qui proposent plutôt une progression arborescente où chaque fenêtre remplace sa précédente (même si les boîtes de dialogue sont aussi parfois utilisées dans le mode web).

WPF couvre ces différents types de navigation grâce à des classes conteneurs différentes :

  • la classe Window correspond aux fenêtres des applications Windows,
  • la classe NavigationWindow permet une navigation de page en page à la manière des applications web.

Remplaçons donc la boîte de message « Hello World » par une deuxième page sur laquelle on va naviguer à la manière intenet. Pour cela, il faut appliquer les modifications suivantes à notre petit exemple :

  1.  La première fenêtre hérite de NavigationWindow à la place de Window :
    <NavigationWindow x:Class="HelloWorldStep3.Window1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="HelloWorldStep3" Height="300" Width="500"
    >
        <Button Click="SayHello">
            <StackPanel Orientation="Horizontal">
                <Image Source="Image1.png"/>
                <TextBlock VerticalAlignment="Center"
                           FontFamily="Calibri" FontSize="36">
                           Dire <Bold>Hello</Bold>
                </TextBlock>
            </StackPanel>
        </Button>
    </NavigationWindow>
  2. La boîte de dialogue est remplacée par une définition XAML : pour cela on va utiliser la classe « Page » dont le contenu peut être affiché à l’intérieur d’une NavigationWindow :
    <Page x:Class="HelloWorldStep3.Page2"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="HelloWorldStep3"
    >
    <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center"
    FontFamily="Calibri" FontSize="36">
    Hello <Italic>World !</Italic>
    </TextBlock>
    </Page>
  3. Et pour finir, il faut bien entendu modifier le code du bouton pour remplacer l’appel à MessageBox :
    public void SayHello(object sender, RoutedEventArgs e) {
    Navigate(new Uri("Page2.xaml", UriKind.RelativeOrAbsolute));
    }

Lorsqu’on clique sur le bouton constituant la première page, la deuxième page apparaît. Les boutons de navigation en haut de cette page permettent de revenir à la page précédente comme lors d’une navigation sur Internet.

Dans beaucoup d’applications, on utilise des boîtes de dialogue modales pour effectuer des saisies complémentaires : l’avantage d’une boîte de dialogue modale est qu’elle ne perturbe pas l’application principale puisqu’elle disparaît une fois sa tâche accomplie ; dans certains cas, ce « sous-processus » peut même être implémenté à l’aide de plusieurs fenêtres à la manière d’un assistant.

Une navigation simple comme celle présentée dans l’exemple WPF précédent ne suffit pas à reproduire ce type de comportement car les pages secondaires « pollueraient » l’historique de navigation principale. La classe WPF « PageFunction » répond exactement à ce besoin ; il s’agit d’une page définie comme les autres en XAML mais qui offre une méthode la retirant de l’historique de navigation et ainsi que la possibilité de transférer des données à la page qui l’a appelée.

Elle se met en œuvre de la manière suivante :

  1. Dans la page appelante, on enregistre un délégué recevant le résultat du traitement de la PageFunction :
    SaisiePrenom page2 = new SaisiePrenom();
    page2.Return += new ReturnEventHandler<string>(page2_Return);
    
    NavigationWindow navWindow =
    (NavigationWindow)Applic(NavigationWindow)Application.Current.MainWindow;
    navWindow.Navigate(page2);
  2. Ce délégué de la page appelante récupère les données transmises et peut les traiter:
    void page2_Return(object sender, ReturnEventArgs<string>e) {
    BlockPrenom.Text = e.Result;
    }}
  3. Sur le bouton de validation de la PageFunction, on déclenche l’événement OnReturn en lui passant les données à transmettre à la page appelante :
    public void ValiderPrenom(object sender, RoutedEventArgs e) {
    OnReturn(new ReturnEventArgs<String>(TxtPrenom.Text));
    }
Affichage initial de la page principale: aucune navigation n’a été effectuée.
Seul le bouton permet de passer à une autre page.
PageFunction affichée : il est possible de revenir en arrière sans valider la saisie en cliquant sur la flèche arrière, ou de valider la saisie en cliquant sur le bouton.
Si la saisie est validée, la page d’appel reçoit les données saisies (cf. code plus haut). L’historique de navigation est effacée.

 

Un exemple plus conséquent

Pour illustrer d’autres mécanismes de WPF ainsi que l’utilisation d’Expression Interactive Designer, nous allons maintenant écrire une application simple mais complète : un lecteur de fichiers audio : il va afficher les albums présents dans le dossier « Musique » de l’utilisateur courant et permettre d’en jouer les différentes pistes. Voici le résultat fini (OK, il n’est pas particulièrement beau mais c’est à moi et non à WPF qu’on doit ce résultat graphiquement discutable) :

Je m’étais fixé une contrainte pour la réalisation de cette petite application : gérer complètement la partie présentation sous Microsoft Expression Interactive Designer sans écrire la moindre ligne de code En effet, un des objectifs de WPF est d’améliorer l’ergonomie des applications en confiant cette tâche à des Designers et non plus des développeurs C#.

Il n’est cependant pas réellement possible de développer une application entière sans écrire la moindre ligne de code : dans le cas de ce lecteur audio, il faut par exemple parcourir une arborescence de répertoires pour dresser la liste des albums présents. Cependant, il est possible d’isoler complètement cette partie fonctionnelle de la partie graphique. Le projet WpfMediaPlayer est donc être constitué de deux parties distinctes :

  • Un assembly .NET (MediaEntites.dll) chargé de la récupération des données par parcours du disque et de leur présentation sous forme de classes manipulables sous EID.
  • L’exécutable chargé de la présentation et entièrement développé en XAML.

Assemly « fonctionnel » MediaEntites.dll

Avant de passer à la description de l’exécutable et d’Expression Interactive Designer, jetons un coup d’œil à l’assembly MediaEntites.dll. Voici les trois classes exportées par cet assembly :

Dans la mesure où les objets représentant l’arborescence des morceaux musicaux ne seront pas modifiés une fois le parcours initial de cette arborescence effectué, les trois classes de l’assembly n’ont pas de contrainte d’implémentation particulière. Si ces objets pouvaient être modifiés à l’exécution (soit par appels de méthodes à la suite d’actions de l’utilisateur, soit par surveillance du répertoire de musique afin de détecter d’éventuelles modifications sur les fichiers audio), il serait nécessaire d’implémenter certaines interfaces .NET pour bénéficier complètement des mécanismes de Databinding de WPF.

Par exemple, une classe dont les propriétés peuvent être modifiées gagnera à implémenter l’interface INotifyPropertyChanged : lorsque WPF lie un composant graphique à un objet implémentant cette interface, il enregistre un délégué sur l’événement INotifyPropertyChanged.PropertyChanged, ce qui permet de mettre à jour l’interface utilisateur en cas de modification de la propriété sans avoir à écrire de code supplémentaire.

L’implémentation de la propriété Titre de la classe Piste pourrait être implémentée de la manière suivante :

public class Piste : INotifyPropertyChanged {
    private string _titre;

    public string Titre {
        get {
            return _titre;
        }
        set {
            if (_titre != value) {
                _titre = value;
                NotifyPropertyChanged("Titre");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(string property) {
        if (PropertyChanged != null) {
            PropertyChanged(this,new PropertyChangedEventArgs(property));
        }
    }
}

De la même manière, les listes doivent aussi publier leurs modifications pour une prise en compte automatique par le Databinding WPF ; une manière simple d’implémenter ceci est d’hériter de la classe ObservableCollection :

public class Album {
    …
    public ObservableCollection<Piste>Pistes { … }
    …
}

public class AlbumCollection : ObservableCollection<Album>{
    …
}

En effet, ObservableCollection implémente à la fois INotifyPropertyChanged et INotifyCollectionChanged.

Globalement, le fonctionnement de MediaEntites.dll est très simple :

  • Le constructeur de la classe AlbumCollection récupère le dossier « Ma Musique » de l’utilisateur courant à l’aide de l’API .NET Environment.GetFolderPath(Environment.SpecialFolder.MyMusic)et effectue un parcours récursif de celui-ci.
  • Quand un répertoire contient un fichier desktop.ini avec une ligne « FolderType=MusicAlbum », ce répertoire est considéré comme un album et l’ensemble des fichiers wma est ajouté à la collection des pistes.
  • Les couvertures des albums sont des fichiers image nommés AlbumArt_ZZZ_small.jpg et AlbumArt_ZZZ_large.jpg.

Cette localisation de tout le code « fonctionnel » dans une DLL séparée de l’interface utilisateur n’est pas une exclusivité WPF : c’est une bonne pratique de programmation que de séparer le code lié à l’interface utilisateur du code métier, celui-ci est alors réutilisable dans différentes applications. En revanche, dans des applications plus complexes que ce petit exemple, ce code métier sera plus riche et offrira bien d’autres services qu’un simple accès en lecture à des données.

Réalisation de l’interface utilisateur

Afin d’exploiter complètement les possibilités offertes par WPF, il est nécessaire de faire intervenir des designers ou graphistes lors de l’élaboration de l’interface utilisateur des applications. Ceux-ci vont non seulement définir l’aspect général de l’application et ses principaux modes d’interaction mais aussi participer au développement de celle-ci. En fonction des projets et des compétences disponibles, le schéma de développement d’une application WPF variera mais un certain nombre de grands principes resteront constants, en particulier l’utilisation de Microsoft Expression Interactive Designer comme outil principal de développement des interfaces WPF.

EID est un outil complet qui permet non seulement la manipulation du XAML définissant l’interface utilisateur d’une application mais aussi celle du code associé à cette interface. EID permet ainsi l’exécution et la mise au point des applications WPF, accélérant ainsi le cycle de développement : si le développeur prépare convenablement les composants exécutant le code métier, le designer élaborant une interface utilisateur pourra s’appuyer sur ces composants.

L’interface utilisateur de EID est constituée d’une zone de travail présentant l’interface en cours d’élaboration entourée d’une multitude de boîtes à outils permettant de voir l’arborescence des composants, de sélectionner les composants à installer, d’en modifier les propriétés, etc… EID lui-même est développé avec WPF et exploite entièrement les possibilités de redimensionnement et zoom offertes par cette technologie : ainsi, deux curseurs dans l’interface de EID permettent non seulement de modifier de manière continue le facteur de zoom de la zone de travail mais aussi de régler, toujours de manière continue, le facteur de zoom de toute l'application elle-même, y compris les barres d'outils.

La conception d’une fenêtre doit commencer par une réflexion quant aux options de redimensionnement de l’application. En effet, non seulement WPF est basé sur des graphiques vectoriels permettant de faire varier de manière continue la taille des objets mais la plupart des composants WPF savent adapter leur taille et celle de leurs sous-composants en fonction de l’espace disponible. Le composant idéal pour gérer le redimensionnement d’une fenêtre est la Grid, qui s’apparente aux tables utilisées dans les pages Web pour gérer l’organisation de celles-ci.

La figure ci-dessous présente la fenêtre principale du WpfMediaPlayer : le composant principal est constitué d’une Grid, coupée en deux colonnes qui vont se redimensionner proportionnellement à la fenêtre globale (les cadenas sont ouverts).

L’objet sélectionné est une image qui présentera la couverture de l’album courant. Les petits maillons reliés à cet objet par des lignes qui partent de chacun de ses côtés indiquent la manière dont l’image va se redimensionner lorsque la grille sera redimensionnée :

  • Les distances aux bords gauche et haut seront fixes (maillons fermés),
  • Les distances aux bords droite et bas seront libres (maillons ouverts)

ð L’image gardera donc une taille fixe et sera à une distance fixe du haut de la fenêtre et du bord gauche de sa colonne.

Styles et Templates

Pour le développeur, un bouton est un contrôle générant un événement « Click » et servant à déclencher une action. On a vu dans la première partie de cet article comment on pouvait composer le contenu d’un bouton pour dépasser les contraintes classiques (texte et/ou image). WPF permet d’aller plus loin en permettant par exemple d’avoir des boutons rond : ceci se réalise facilement en XAML ou sous Interactive Designer :

  • on définit dans les ressources de la fenêtre un Template centrant (à l’aide d’une Grid) une ellipse et le contenu du bouton :
    <ControlTemplate x:Key="TemplateBouton" TargetType="{x:Type Button}">
        <Grid x:Name="Grid">
           <Ellipse Stroke="#FF000000" x:Name="Ellipse">… </Ellipse>
            <ContentPresenter>…</ContentPresenter>
        </Grid>
    </ControlTemplate>
  • et les boutons se voient appliquer ce template :
    <Button Template="{DynamicResource TemplateBouton}"…>

Le point important du template utilisé ici est l’élément ContentPresenter : un bouton sait afficher différents contenus comme on l’a vu dans la première partie de cet article : il aurait été dommage que les les templates brident cette possibilité. Le ContentPresenter est fait pour cela : lorsque le template du bouton sera appliqué sur à un bouton donné, le contenu de celui-ci sera inséré dans le template à la place de l’élément ContentPresenter de celui-ci. Il existe dans WPF d’autres éléments similaires au ContentPresenter : typiquement, un « ItemsPresenter » est utilisé dans tous les contrôles de type Liste pour préciser où doivent être affichés les éléments de la liste. Dans le cas d’une listbox, on peut utiliser la notion de template pour la liste elle-même mais aussi pour chacun de ses éléments.

Le style d’un contrôle permet de modifier les propriétés de celui-ci : typiquement modifier la police ou la couleur de fond. Cependant, le style peut aller plus loin puisqu’il peut préciser le template à utiliser ! On peut donc avoir pour une même application un style « Arrondi » ou un style « Anguleux » …

Databinding

La liste affichée dans la colonne de gauche doit afficher la liste des albums musicaux de l’utilisateur : elle va être remplie à l’aide du mécanisme de Databinding. WPF est capable d’effectuer du Databinding sur des objets .NET ou sur des sources XML. La boîte à outils « Data » permet de créer une source de données. Ensuite, le binding lui-même peut être effectué soit en modifiant le XAML manuellement, soit en déposant un objet représentant des données (la source ou un de ses composants) sur un objet graphique :

L’étape suivante est de préciser comment les items de la liste seront affichés. Ici, ce sont des objets .NET typés qui sont affichés dans la liste, des « Albums », il est donc possible de préciser leur mode d’affichage de deux manière :

  • en donnant un « Template » d’affichage pour tous les objets de type « Album ». Cette option est intéressante quand des objets élémentaires sont affichés à de nombreux endroits dans une application.
  • en précisant le Template pour les éléments de cette liste particulière.

Dans le cas du WpfMediaPlayer, le XAML résultant est celui-ci :

<ListBox …
    ItemsSource="{Binding Mode=OneWay, Source={StaticResource AlbumCollectionDS}}"
    ItemTemplate="{DynamicResource TemplateAlbum}" … />

ItemsSource précise la source de données alimentant les éléments de la liste, ItemTemplate définit leur mode d’affichage. Le template de l’album est un assemblage de composants WPF qui utilise aussi le mécanisme de Databinding pour afficher son contenu (typiquement le titre de l’album) :

Image :
<Image Source="{Binding SmallImagePath}"…
Titre :
<TextBlock Text="{Binding Titre}"…

Le databinding permet aussi une liaison dynamique entre différents composants de l’interface utilisateur, l’album sélectionné dans la liste de gauche est affiché en grand dans la partie droite de l’application. En fait, le Databinding est utilisé à de nombreux endroits dans une application WPF ; dans cet exemple, il est aussi utilisé :

  • pour faire jouer automatiquement par un « MediaElement » la piste sélectionnée dans la liste des pistes de l’album courant,
  • pour régler le volume de ce même MediaElement,
  • pour afficher le reflet du titre de l’album sélectionné.

La figure ci-dessous met en évidence l’omniprésence du databinding dans cet exemple.

Conclusion

WPF est bien trop vaste pour être entièrement décrit dans un article comme celui-ci, cependant ces petits exemples devraient permettre d’appréhender un peu les principaux concepts de WPF :
la richesse graphique et la séparation entre la présentation et le comportement qui s’appliquent aussi bien au niveau composant qu’au niveau application et permettent de nouveaux modes de développement des applications combinant les compétences classiques des développeurs à celles des graphiques et designers auparavant souvent cantonnés aux sites Internet.

Aujourd’hui, WPF est une technologie de type Smartclient mais dont les applications peuvent néanmoins s’exécuter au sein d’Internet Explorer ou se télécharger via une technologie comme Click Once et s’appuyer sur des appels de services web pour réaliser leurs traitements. Demain, avec WPF/E, Microsoft va proposer une solution permettant de toucher un encore plus grand nombre de clients, même un fonctionnement étendu sur des plates-formes multiples signifie nécessairement une réduction des possibilités.