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▲
<?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 :
- Le fichier fait référence à la dll via Assembly=« Xolitaire » ;
- 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# ;
- Des événements sont associés à des méthodes de la classe définie ;
- 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.
<
Frame ID=
"root"
Source=
"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▲
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.
#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 :
#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.
#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.
#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.
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▲
<?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.
<?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.
<?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
<?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.
<?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>
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.