À la découverte de XAML avec C#

Jean-Alain Baeyens

Cet article est un premier aperçu de la technologie XAML pour la gestion de l'affichage. Rappelons que la technologie XAML sera introduite par Microsoft sur ses futurs OS. Pour réaliser cet aperçu, j'ai utilisé le produit Xamlon.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : À la découverte de XAML avec C#

- Auteur : Jean-Alain Baeyens

- Parution : 4 janvier 2005

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2005 Jean-Alain Baeyens. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Avertissement

- II. Remerciements

- III. Qu'est-ce que XAML

- IV. Installation de Xamlon

- V. Analyse de Xolitaire

- V-A. Premiers regards

- V-B. La procédure principale

- V-C. Une « sous procédure » XAML

- V-D. La classe C# Xamlon.Xolitaire

- VI. Notre première réalisation

- VI-A. Le code XAML

- VI-B. Le code C#

- VII. Une autre approche de la liaison XAML C#

- VIII. Compiler une application XAML, C#

- IX. XAML et Internet Explorer

- X. Conclusion

I - Avertissement

Il ne s'agit ni d'un test de Xamlon ni d'évaluer les performances ou les avantages de la technologie XAML (prononcez zammel). Il s'agit uniquement d'une première approche de XAML et surtout de son interaction avec C#. L'exposé se veut le compte-rendu de ma première expérience en la matière.

II - Remerciements

Je remercie la société Xamlon de m'avoir autorisé à utiliser son exemple Xolitaire dans cet article. Je remercie également David Pédehourcq pour son avis éclairé et Jennifer Moulard ainsi que mon épouse pour le travail de correction.

III - Qu'est-ce que XAML

XAML, Extensible Application Markup Language, est un langage déclaratif basé sur XML. Il a pour vocation la déclaration de l'interface graphique. Typiquement, chaque page d'interface est décrite dans un fichier XAML. Une page XAMl décrit la classe qui sera générée lors du runtime.

Cette technologie sera partie intégrante du futur OS de Microsoft baptisé actuellement Longhorn. Pour rappel, Longhorn introduit une nouvelle interface graphique vectorielle nommée actuellement Avalon. XAML pourra être utilisé aussi bien en Intranet, Internet via IE que pour les applications WinForm. Une nouvelle version de Visual Studio, nom de code ORCA, permettra la manipulation de XAML. Microsoft a annoncé que cette technologie sera également disponible sur les anciennes versions de Windows via l'installation d'un kit complémentaire. Il sera également possible de générer des applications avec XAML en utilisant Visual Studio Whidbey après installation du Longhorn SDK.

Aujourd'hui, il est déjà possible via le produit Xamlon d'utiliser cette technologie sans installer le Longhorn SDK. Il existe aussi d'autres initiatives comme MyXAML, mais si le principe est le même, le codage XML est différent.

IV - Installation de Xamlon

Vous devez télécharger la version d'essai 30 jours du produit à l'adresse http://www.xamlon.com/software/xamlonpro/winforms/. L'installation est classique et ne demande pas d'explication particulière. Toutefois, avant de vous lancer dans cette installation, n'oubliez pas d'attendre votre numéro de série qui vous sera transmis par email. Sur la machine de test, je ne dispose que de SharpDevelop 1.0.1 et de Visual C# 2005 bêta. L'AddIn d'intégration à Visual studio Net 2003 n'a dès lors pu être installé. Je me suis donc concentré sur l'analyse du code de l'un des exemples fournis, en l'occurrence Xolitaire.

V - Analyse de Xolitaire

V-A - Premiers regards

Première surprise, on ne retrouve pas d'exe dans le répertoire de l'exemple. L'application est lancée depuis le fichier XAML Xolitaire. Ce type de fichier est associé à un viewer. La partie C# est quant à elle compilée sous forme d'une dll. Nous verrons en fin d'article qu'il ne s'agit pas d'une fatalité.

V-B - La procédure principale

Xolitaire.xaml

<?xml version="1.0"?>
<?Mapping
XmlNamespace="events"
ClrNamespace="Xamlon"
Assembly="Xolitaire"?>

<Window ID="container"
xmlns="http://schemas.microsoft.com/2003/xaml"
xmlns:def="Definition"
def:Class="Xamlon.Xolitaire"
Width="720" Height="500"
Text="Xolitaire"
Load="OnLoad" MouseDown="MouseDown" MouseMove="MouseMove" MouseUp="MouseUp"
Resize="Resize" KeyPress="KeyPress">

<Frame ID="root" Source="Playfield.xaml" />
</Window>

Je ne m'attacherai pas ici à la syntaxe XAML qui n'est pas l'objet de cet article. De plus on peut raisonnablement imaginer que le code XAML étant dédié à l'interface graphique, il sera généré par votre RAD.

Nous pouvons toutefois identifier quatre éléments importants :

1. Le fichier fait référence à la dll via Assembly=« Xolitaire » ;

2. Le code XAML fait référence à une class Xamlon.Xolitaire qui est une classe définie dans l'assembly. Cette classe est écrite en C# ;

3. Des événements sont associés à des méthodes de la classe définie ;

4. Le fichier fait appel au fichier Playfield.xaml.

Les trois premiers points définissent comment XAML va communiquer avec C#.

V-C - Une « sous procédure » XAML

PlayField.xaml est une sous procédure de Xolitaire.xaml. Il contient le code XAML permettant l'affichage du fond d'écran et la définition des zones du jeu. Aucun appel à C# n'est généré depuis cette partie du code. Comme nous avons pu le voir plus haut, l'appel se fait avec le code suivant.

Chargement de Playfield.xaml

<Frame ID="root" Source="Playfield.xaml" />

Le code complet de Playfield.xaml

<?xml version="1.0"?>

<TransformDecorator
xmlns="http://schemas.microsoft.com/2003/xaml"
xmlns:def="Definition"
ID="zoom"
AffectsLayout="false">

<Canvas ID="board" Width="720" Height="500" Background="Green">
<Canvas.Resources>
<Style>
<Rectangle
Fill="{shadowGrad}"
Canvas.Left="4"
Canvas.Top="4"
RectangleWidth="79"
RectangleHeight="113"
RadiusX="4"
RadiusY="4"
Stroke="#334EFF00"
/>
</Style>
<LinearGradientBrush def:Name="shadowGrad" StartPoint="0.5,0"
EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#00000000" Offset="0" />
<GradientStop Color="#33000000" Offset="0.95" />
<GradientStop Color="#66000000" Offset="1" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Canvas.Resources>
<Canvas ID="deck" Canvas.Left="10" Canvas.Top="10">
<Rectangle />
</Canvas>
<Canvas ID="discard" Canvas.Left="110" Canvas.Top="10">
<Rectangle />
</Canvas>

<Canvas ID="ace1" Canvas.Left="310" Canvas.Top="10">
<Rectangle />
</Canvas>
<Canvas ID="ace2" Canvas.Left="410" Canvas.Top="10">
<Rectangle />
</Canvas>
<Canvas ID="ace3" Canvas.Left="510" Canvas.Top="10">
<Rectangle />
</Canvas>
<Canvas ID="ace4" Canvas.Left="610" Canvas.Top="10">
<Rectangle />
</Canvas>

<Canvas ID="stack1" Canvas.Left="10" Canvas.Top="150">
<Rectangle />
</Canvas>
<Canvas ID="stack2" Canvas.Left="110" Canvas.Top="150">
<Rectangle />
</Canvas>
<Canvas ID="stack3" Canvas.Left="210" Canvas.Top="150">
<Rectangle />
</Canvas>
<Canvas ID="stack4" Canvas.Left="310" Canvas.Top="150">
<Rectangle />
</Canvas>
<Canvas ID="stack5" Canvas.Left="410" Canvas.Top="150">
<Rectangle />
</Canvas>
<Canvas ID="stack6" Canvas.Left="510" Canvas.Top="150">
<Rectangle />
</Canvas>
<Canvas ID="stack7" Canvas.Left="610" Canvas.Top="150">
<Rectangle />
</Canvas>
</Canvas>
</TransformDecorator>

Nous reviendrons sur ce code ultérieurement.

V-D - La classe C# Xamlon.Xolitaire

Vue synthétique de la classe Xamlon.Xolitaire

using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Threading;

using Xamlon.CardStacks;

namespace Xamlon
{
/// <summary>
/// Summary description for Xolitaire.
/// </summary>
public class Xolitaire : System.Windows.Forms.Panel,
System.Windows.Serialization.IPageConnector,
Xamlon.Windows.Forms.IIEViewerEvents
{
public static Xolitaire FirstInstance = null;

+    Fields

+    Properties

+    Constructors

+    Methods

+    Event Handlers

+    IPageConnector Members

+    IIEViewersEvent Members

}
}

Remarquons les références à System.Windows.Controls et System.Windows.Media qui sont définies dans l'assembly Xamlon.dll et non pas dans le framework.
Nous pouvons aussi remarquer dans le code que notre classe hérite de System.Windows.Forms.Panel et implémente les interfaces System.Windows.Serialization.IPageConnector et Xamlon.Windows.Forms.IIEViewerEvents. Vous trouverez sans problèmes la classe Panel dans l'aide du framework mais par contre l'interface IPageConnector est définie dans l'assembly Xamlon.dll. Il décrit la méthode Connect que l'on retrouve dans la région IPageConnector Members. Sous Longhorn, ces classes seront intégrées dans le système d'exploitation lui-même. Les dll fournies par Xamlon réalisent l'extension nécessaire aux versions actuelles de Windows.

La méthode Connect

#region IPageConnector Members
public bool Connect(string id, object target)
{
this._ids[id] = target;
return false;
}

La méthode se contente de placer des objets dans une HashTable.

Pour l'interface IIEViewerEvents on implémente :

La méthode DoAction

#region IIEViewerEvents Members
public void DoAction(string action)
{
switch(action)
{
case "NewGame":
FirstInstance.NewGame();
break;
}
}

Pour rappel FirstInstance est une propriété publique et statique du type Xolitaire. Vous pourrez voir dans le constructeur qu'elle contient la première instance de la classe.

Si l'on détaille les fields définis,nous pouvons constater la présence de types de données inconnues. Certains de ces types sont propres à l'application. D'autres sont définis dans les espaces de nom System.Windows.Controls et System.Windows.Media. Il s'agit de Canvas, TransformDecorator, Frame, Matrix et Point. Il n'y a rien d'étonnant à ce que nous retrouvions ces termes dans le fichier PlayField.XAML. Nous venons de découvrir le premier pas pour communiquer avec les objets définis dans les balises.

Définition des fields de la classe

#region Fields
// key elements
Hashtable _ids;
TransformDecorator _zoom;
Canvas _board;
Frame _root;
//Window _container;
Matrix _toUserSpace = Matrix.Identity;
// card stacks
CardStack[] _allStacks;
Deck _deck;
Waste _waste;
Foundation[] _foundations;
Tableau[] _tableaux;

// drag-and-drop state info
CardStack _originalStack;
CardInfo[] _currentCards;
Point[] _diffs;
#endregion

L'étude du constructeur ne nous apporte pas d'information complémentaire.

Le constructeur de la classe

#region Constructors
public Xolitaire()
{
if(FirstInstance == null)
{
FirstInstance = this;
}

// create ID lookup table
this._ids = new Hashtable();

// clear not-yet-defined Canvases - set during OnLoad
this._board = null;

// init stacks
this._allStacks = new CardStack[13];
this._deck = null;
this._waste = null;
this._foundations = new Foundation[4];
this._tableaux = new Tableau[7];

// init d-n-d drop state
this._originalStack = null;
this._currentCards = new CardInfo[0];
this._diffs = new Point[0];
}

Regardons maintenant la méthode OnLoad associée à l'événement Load de notre fichier XAML.

La méthode OnLoad associée à l'événement Load

public void OnLoad(object sender, EventArgs e)
{
// get board canvas and top-level zoom
this._root = (Frame) this._ids["root"];

// lookup all child IDs
FindChildIDs(this._root);

// get board
this._board = (Canvas) this._ids["board"];

// get top-level zoom transform
this._zoom = (TransformDecorator) this._ids["zoom"];

// init all card stacks
this._deck = new Deck(this, (Canvas) this._ids["deck"]);
this._waste = new Waste(this, (Canvas) this._ids["discard"]);

this._foundations[0] = new Foundation(this, (Canvas) this._ids["ace1"]);
this._foundations[1] = new Foundation(this, (Canvas) this._ids["ace2"]);
this._foundations[2] = new Foundation(this, (Canvas) this._ids["ace3"]);
this._foundations[3] = new Foundation(this, (Canvas) this._ids["ace4"]);

this._tableaux[0] = new Tableau(this, (Canvas) this._ids["stack1"]);
this._tableaux[1] = new Tableau(this, (Canvas) this._ids["stack2"]);
this._tableaux[2] = new Tableau(this, (Canvas) this._ids["stack3"]);
this._tableaux[3] = new Tableau(this, (Canvas) this._ids["stack4"]);
this._tableaux[4] = new Tableau(this, (Canvas) this._ids["stack5"]);
this._tableaux[5] = new Tableau(this, (Canvas) this._ids["stack6"]);
this._tableaux[6] = new Tableau(this, (Canvas) this._ids["stack7"]);

// init convenience array containing all card stacks - used in drag-and-drop
this._allStacks[0] = this._deck;
this._allStacks[1] = this._waste;
this._allStacks[2] = this._foundations[0];
this._allStacks[3] = this._foundations[1];
this._allStacks[4] = this._foundations[2];
this._allStacks[5] = this._foundations[3];
this._allStacks[6] = this._tableaux[0];
this._allStacks[7] = this._tableaux[1];
this._allStacks[8] = this._tableaux[2];
this._allStacks[9] = this._tableaux[3];
this._allStacks[10] = this._tableaux[4];
this._allStacks[11] = this._tableaux[5];
this._allStacks[12] = this._tableaux[6];

// start a new game
this.NewGame();

// If we're in a control, like IE, then resize right away to the control's size
this.Resize(sender, e);
}

Nous pouvons voir sans problèmes que la méthode Connect a servi à collecter les différents objets de l'écran. La méthode OnLoad va entre autres réassigner ces éléments maintenant contenus dans une Hashtable vers les fields correctement typés.

Pour finir, les autres méthodes vont manipuler ces objets de manière à effectuer les traitements voulus, mais c'est une autre histoire qui dépasse le cadre de cet article.

VI - Notre première réalisation

Essayons de réaliser un écran simple.

VI-A - Le code XAML

le minimum pour une fenêtre

<?xml version="1.0"?>
<Window xmlns:wfi="wfi"
xmlns:wf="wf"
Width="200" Height="150" Text="Mon application">
</Window>

Ce code génère une fenêtre vierge.

Préparer la fenêtre à recevoir des contrôles

<?xml version="1.0"?>
<Window xmlns:wfi="wfi"
xmlns:wf="wf"
Width="200" Height="150" Text="Mon application">
<Canvas>
<wfi:WindowsFormsHost>
</wfi:WindowsFormsHost>
</Canvas>
</Window>

Si nous ajoutons les tags Canvas et WindowsFormHost, notre fenêtre est prête pour recevoir des contrôles.

Ajout d'un bouton

<?xml version="1.0"?>
<Window xmlns:wfi="wfi"
xmlns:wf="wf"
Width="200" Height="150" Text="Mon application">
<Canvas>
<wfi:WindowsFormsHost>
<wf:Button Top="55" Left="60" Width="80" Height="18" Text="Hello!" />
</wfi:WindowsFormsHost>
</Canvas>
</Window>

Le bouton est bien là, mais occupe l'intégralité de la fenêtre !

Il nous faut encore ajouter le tag Panel

le tag Panel

<?xml version="1.0"?>
<Window xmlns:wfi="wfi"
xmlns:wf="wf"
Width="200" Height="150" Text="Mon application">
<Canvas>
<wfi:WindowsFormsHost>
<wf:Panel>
<wf:Button Top="55" Left="60" Width="80" Height="18"
Text="Hello!" />
</wf:Panel>
</wfi:WindowsFormsHost>
</Canvas>
</Window>

Nous obtenons enfin le résultat voulu.

VI-B - Le code C#

Nous allons associer une action au bouton. Pour cela nous allons ajouter l'événement Click à notre bouton et y associer la méthode OnClick que nous définirons dans C#. Nous devons aussi faire le lien avec l'assembly via le tag Mapping, mais aussi associer notre class C# avec la fenêtre en complétant notre tag window. Il nous reste encore à encadrer notre panel contenant le bouton par le tag WindowsFormsHost.Controls.

Encore un peu de XAML

<?xml version="1.0"?>
<?Mapping
XmlNamespace="events"
ClrNamespace="Xamlon"
Assembly="Test Xamlon"?>

<Window xmlns:wfi="wfi"
xmlns:wf="wf"
xmlns:def="Definition"
def:Class="Xamlon.MyClass"
Width="200" Height="150" Text="Mon application">
<Canvas>
<wfi:WindowsFormsHost>
<wfi:WindowsFormsHost.Controls>
<wf:Panel>
<wf:Button Top="55" Left="60" Width="80" Height="18"
Text="Hello!" Click="OnClick" />
</wf:Panel>
</wfi:WindowsFormsHost.Controls>
</wfi:WindowsFormsHost>
</Canvas>
</Window>

Notre code C#

using System;
using System.Windows.Controls;
using System.Collections;
namespace Xamlon
{
/// <summary>
/// La classe associée à la fenêtre
/// </summary>
public class MyClass:System.Windows.Forms.Panel
, System.Windows.Serialization.IPageConnector
{
Hashtable lienXAML;

/// <summary>
/// Le constructeur
/// </summary>
public MyClass()
{
lienXAML = new Hashtable();
}

/// <summary>
/// La méthode Connect requise par IPageConnector
/// </summary>
public bool Connect(string id, object target)
{
this.lienXAML[id] = target;
return false;
}

/// <summary>
/// La méthode qui sera exécutée lors du clic sur le bouton.
/// </summary>
public void OnClick(object sender, EventArgs e)
{
System.Windows.Forms.Button bt = (System.Windows.Forms.Button)sender;
if (bt.Text == "Hello!")
{
bt.Text="Bye.";
}
else
{
bt.Text="Hello!";
}
}
}
}

Notre première fenêtre est terminée.

VII - Une autre approche de la liaison XAML C#

Il est possible d'intégrer le code C# directement dans le fichier XAML. Notre fichier XAML devient alors :

<?xml version="1.0"?>

<Window xmlns:wfi="wfi"
xmlns:wf="wf"
xmlns:def="Definition"
Width="200" Height="150" Text="Mon application">
<def:Code>
<![CDATA[

public void OnClick(object sender, EventArgs e)
{
System.Windows.Forms.Button bt = (System.Windows.Forms.Button)sender;
if (bt.Text == "Hello!")
{
bt.Text="Bye.";
}
else
{
bt.Text="Hello!";
}
}

]]>
</def:Code>
<Canvas>
<wfi:WindowsFormsHost>
<wfi:WindowsFormsHost.Controls>
<wf:Panel>
<wf:Button Top="55" Left="60" Width="80" Height="18"
Text="Hello!" Click="OnClick" />
</wf:Panel>
</wfi:WindowsFormsHost.Controls>
</wfi:WindowsFormsHost>
</Canvas>
</Window>

Le code est ainsi très simplifié, mais cette méthode mélange différents types de codes ce qui rend la lecture plus difficile.

Attention, pour que le programme fonctionne de cette façon, il faut que les fichiers Xamlon.dll et WindowsFormsIntegration.dll soient présents dans le répertoire.

VIII - Compiler une application XAML, C#

Microsoft a mis au point une nouvelle version du compilateur MSBuild qui permet de réaliser la compilation d'une application complète. Le compilateur compile aussi bien le code C# que le code XAML. Celui-ci est alors intégré dans l'exécutable. De cette manière, il est possible d'obtenir un exécutable qui peut être distribué sans les fichiers XAML.

Voici un exemple de fichier permettant la compilation.

<Project DefaultTargets="Build">
<PropertyGroup>

<Property Language="C#" />
<Property DefaultClrNameSpace="MonNameSpace" />
<Property TargetName="MonApplication" />

</PropertyGroup>

<Import Project="$(LAPI)\WindowsApplication.target" />

<Item Type="Reference" Include="WindowsFormsIntegration"
HintPath="C:\WINDOWS\Microsoft.NET\Windows\v6.0.4030\WindowsFormsIntegration.dll"
Name="WindowsFormsIntegration" />
<Item Type="Reference" Include="System" />
<Item Type="Reference" Include="System.Data" />
<Item Type="Reference" Include="System.Xml" />
<Item Type="Reference" Include="System.Windows.Forms" />

<ItemGroup>

<!-- Application markup -->
<Item Type="ApplicationDefinition" Include="MonApplication.xaml" />

<!-- Compiled Files list -->
<Item Type="Code" Include="MonApplication.cs"/>
<Item Type="Pages" Include="UneAutreClasse.cs"/>
<Item Type="Code" Include="MaSubForm.xaml"/>
<Item Type="Pages" Include="MaSubForm.cs"/>

</ItemGroup>

</Project>

On peut considérer ce code comme purement informatif, car il est pratiquement certain que Visual Studio ou tout autre IDE dédié prendra en charge la génération de ce fichier.

IX - XAML et Internet Explorer

Du code XAML pourra être directement interprété par la version d'Internet Explorer intégrée à Longhorn. Par contre ce code ne pourra intégrer directement du code C#. Toutefois, il sera possible d'exécuter des applications, préalablement compilées, directement dans le navigateur et non sous forme d'applications Windows. Pour cela, il faudra utiliser un mode de compilation particulier.

X - Conclusion

Nous avons maintenant une idée de ce qui nous attend à l'avenir. Bien que le concept soit fortement différent de ce qui se fait actuellement, je ne pense pas que l'introduction de XAML bouleverse nos habitudes de développeur. La plus grosse partie du travail d'adaptation sera réalisée par Visual studio Orca. Un certain nombre de nouvelles classes feront leurs apparitions alors que d'autres deviendront obsolètes. Pour un usage courant, il est peu probable que nous allions nous même modifier le fichier XAMl généré par Visual studio ou par un autre RAD.

La société Xamlon suit les développements XAML de Microsoft sans toutefois réaliser un clone d'Avalon. Par exemple, les espaces de noms sont différents. On ne peut être sûr de la manière dont vont évoluer les deux produits et les modifications qui vont encore y être apportées. On peut aussi s'interroger sur l'avenir de Xamlon après l'apparition des kits de développement produits par Microsoft. Pour ma part, je recommande la patience. Pour celui que l'investissement ne rebute pas et qui souhaite être à la pointe de la technologie, Xamlon est cependant une bonne rampe de lancement. Il fait par ailleurs partie de l'offre DVD partenaire de Microsoft.