A la découverte de XAML avec C#.

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 futures OS. Pour réaliser cet aperçu, j'ai utilisé le produit Xamlon.

N'hésitez pas à commenter cet article ! Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Avertissement

Il ne s'agit ni d'un test de Xamlon ni d'évaluer les performances ou les avantages de la technologie XAML (pronnoncez 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 pour 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 un nouvel interface graphique vectoriel nommé 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

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 quand à elle compilée sous forme d'une dll. Nous verrons en fin d'article qu'il ne s'agit pas d'une fatalité.

B. La procédure principale

Xolitaire.xaml
Sélectionnez
<?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 4 é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#.

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 jeux. 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
Sélectionnez
<Frame ID="root" Source="Playfield.xaml" />
Le code complet de Playfield.xaml
Sélectionnez
<?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.

D. La classe C# Xamlon.Xolitaire

Vue synthétique de la classe Xamlon.Xolitaire
Sélectionnez

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éfini 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
Sélectionnez
#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
Sélectionnez

#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 constructeurs 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
Sélectionnez
#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
Sélectionnez

#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
Sélectionnez

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 servit à 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.

A. Le code XAML

le minimum pour une fenêtre
Sélectionnez
<?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.

Image non disponible
Préparer la fenêtre à recevoir des contrôles
Sélectionnez
<?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 tag Canvas et WindowsFormHost, notre fenêtre est prête pour recevoir des contrôles.

Image non disponible
Ajout d'un bouton
Sélectionnez
<?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 !

Image non disponible

Il nous faut encore ajouter le tag Panel

le tag Panel
Sélectionnez
<?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.

Image non disponible

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
Sélectionnez
<?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#
Sélectionnez
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 clique 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:

 
Sélectionnez
<?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.

 
Sélectionnez
<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 le 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 dans l'avenir. Bien que le concept soit fortement différent de ce qui se fait actuellement, je ne pense pas que l'introduction d'XAML boulverse nos habitudes de développeur. La plus grosse partie du travail d'adaptation sera réalisé 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 partenaires de Microsoft.

Image non disponible

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Voir aussi :
 A la découverte d'Avalon.
 The "Longhorn" Application Model (MSDN)
 Longhorn SDK
 Xamlon
 MyXAML
 XAML.NET
 Longhorn Developer FAQ

  

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 © . 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.