Introduction▲
« Qu'ils sont beaux mes diagrammes Visio, si seulement je pouvais les exporter en XAML et ainsi les réutiliser dans mon application WPF ou Silverlight ». Pour répondre à ce besoin, il existait jusqu'à présent un add-in pour Visio 2007 disponible sur Codeplex à cette adresse : http://www.codeplex.com/VisioExportToXAML. Visio 2010 permet désormais d'exporter ses schémas en XAML, plus précisément au format Silverlight. Nous allons voir comment récupérer le fichier XAML généré et le réutiliser dans Expression Blend 3 au sein d'une application WPF.
I. Création et export du schéma Visio▲
Dans Visio 2010 nous créons tout d'abord un nouveau schéma à partir du modèle de diagramme réseau.
Après quelques drag and drop et un peu d'habillage, notre schéma ressemble à ceci :
Pour chaque élément du réseau, nous avons associé une pastille (de couleur verte sur l'image) qui servira à refléter son état (en ligne ou hors ligne). Le but est qu'au sein d'une l'application WPF la couleur de ces pastilles change en temps réel en fonction des informations renvoyées par un service Web.
La fonctionnalité d'export en XAML de Visio 2010 n'est pas triviale à trouver. Pour la découvrir, il faut utiliser le menu « Enregistrer sous » et sélectionner le type « Page Web ». Un bouton « Publier » apparait vous permettant d'accéder aux options de publication.
Enfin, dans l'onglet « Avancé » de la fenêtre d'options, il faut sélectionner le format XAML dans la liste déroulante. Ce format est normalement déjà sélectionné par défaut.
La visualisation du résultat dans un navigateur :
Une fois l'export effectué, vous trouverez dans le dossier contenant les fichiers associés à la page Web, un fichier XAML. Il s'agit du fichier que nous allons exploiter.
Vous trouverez aussi un (ou plusieurs) fichier d'extension « .odttf ». Il s'agit d'un fichier contenant la fonte de caractères utilisée dans le diagramme Visio (dans notre cas, la fonte Calibri) et référencé dans le fichier XAML. Malheureusement, ce type de fichier n'est pas des plus exploitables au sein de Blend ou de Visual Studio. Nous verrons par la suite comment nous en débarrasser.
II. Création du service Web▲
Pour le développement du service Web nous utiliserons Visual Studio 2010 et le Framework 4.0. Toutes les opérations effectuées ici sont cependant reproductibles avec Visual Studio 2008 et le Framework 3.5.
Nous utiliserons le template WCF Service Application pour créer le projet hébergeant le Web service.
II-A. Contrat du service▲
Définissons tout d'abord le contrat de notre service. L'attribut CallbackContract permet d'associer un contrat de rappel pour des communications bidirectionnelles. Ce contrat de rappel est lui aussi un contrat de service; nous en parlerons plus en détail juste après. L'attribut SessionMode indique que les sessions doivent être prises en compte.
[ServiceContract(Namespace =
"http://developpez.com/MonitorService/2010/01"
,
Name =
"MonitorService"
,
CallbackContract =
typeof
(
IMonitorCallback),
SessionMode =
SessionMode.
Required)]
public
interface
IMonitorService
{
[OperationContract(IsOneWay = true, IsInitiating = true, IsTerminating = false)]
void
Subscribe
(
);
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)]
void
Unsubscribe
(
);
}
Le service est composé de deux méthodes Subscribe et Unsubscribe. L'attribut Initiating positionné à true sur la méthode Subscribe indique qu'un appel à cette méthode créera une session. Positionné à false sur la méthode Unsubscribe, il indique que la méthode ne pourra être appelée que si une session existe déjà (dans le cas contraire, une exception sera déclenchée). Ainsi, le client aura l'obligation d'appeler la fonction Subscribe en premier. L'attribut IsTerminating à true pour la méthode Unsubscribe, signifie que celle-ci terminera la session en cours.
Le contrat de rappel est un contrat de service que devra implémenter le client afin de pouvoir être rappelé par le serveur.
public
interface
IMonitorCallback
{
[OperationContract(IsOneWay = true)]
void
ReceiveNetworkInfo
(
NetworkElementInfo networkElementInfo);
}
Dans notre cas, le serveur rappellera le client afin de lui envoyer les données concernant un élément du réseau sous la forme d'un objet de type NetworkElementInfo.
La classe NetworkElementInfo est déclarée en tant que DataContract. Elle possède trois propriétés contenant le statut (sous forme d'énumération), le nom et le code d'un élément réseau.
[DataContract]
public
class
NetworkElementInfo
{
[DataMember]
public
NetworkStatus Status {
get
;
set
;}
[DataMember]
public
string
Name {
get
;
set
;}
[DataMember]
public
string
Code {
get
;
set
;
}
}
[DataContract]
public
enum
NetworkStatus
{
[EnumMember]
Online,
[EnumMember]
Offline
}
II-B. Implémentation du service▲
Dans l'implémentation du service, nous déclarons un tableau de NetworkElementInfo qui servira de référentiel de données.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public
class
MonitorService :
IMonitorService
{
IMonitorCallback _client;
Timer _timer;
bool
_closing;
NetworkElementInfo[]
Machines =
{
new
NetworkElementInfo {
Code =
"FW"
,
Name =
"FIREWALL"
,
Status =
NetworkStatus.
Online },
new
NetworkElementInfo {
Code =
"RO"
,
Name =
"ROUTER"
,
Status =
NetworkStatus.
Online },
new
NetworkElementInfo {
Code =
"FS"
,
Name =
"FILES_SERVER"
,
Status =
NetworkStatus.
Online },
new
NetworkElementInfo {
Code =
"WS"
,
Name =
"WEB_SERVER"
,
Status =
NetworkStatus.
Online },
new
NetworkElementInfo {
Code =
"DS"
,
Name =
"DATA_SERVER"
,
Status =
NetworkStatus.
Online },
new
NetworkElementInfo {
Code =
"SW"
,
Name =
"SWITCH"
,
Status =
NetworkStatus.
Online }
};
//reste du code
}
Lorsque le client appelle la méthode Subscibe nous conservons une référence vers le canal de rappel et initialisons un timer qui appellera la méthode CallClient toutes les cinq secondes.
public
void
Subscribe
(
)
{
// récupération du channel de rappel client
_client =
OperationContext.
Current.
GetCallbackChannel<
IMonitorCallback>(
);
OperationContext.
Current.
Channel.
Closing +=
new
EventHandler
(
Channel_Closing);
//on simule le fait que le service va rappeler le client toutes les 5 secondes
_timer =
new
Timer
(
new
TimerCallback
(
CallClient),
null
,
5000
,
5000
);
Thread.
Sleep
(
11000
);
}
La méthode CallClient choisit au hasard un élément du tableau de NetworkElementInfo et inverse son statut. On rappelle ensuite le client en lui envoyant les nouvelles informations concernant cet élément réseau.
private
void
CallClient
(
object
o)
{
Random rElement =
new
Random
(
);
NetworkElementInfo info =
Machines[
rElement.
Next
(
5
)];
info.
Status =
info.
Status ==
NetworkStatus.
Online ?
NetworkStatus.
Offline :
NetworkStatus.
Online;
try
{
//rappel du client
_client.
ReceiveNetworkInfo
(
info);
}
catch
{
//TODO: log, arrêt du service, etc.
}
La méthode Unsubscribe ne fait que supprimer le timer en cours. Nous nous abonnons aussi à l'événement Closing du canal de communication pour aussi supprimer le timer au cas où le client se serait déconnecté (coupure réseau par exemple) sans appeler la méthode Unsubscribe avant.
public
void
Unsubscribe
(
)
{
_closing =
true
;
_timer.
Dispose
(
);
}
//le client ferme le canal
private
void
Channel_Closing
(
object
sender,
EventArgs e)
{
//si on n'est pas passé par Unsubscribe avant
if
(!
_closing)
Unsubscribe
(
);
}
III. Création du client WPF▲
Le client sera une application WPF 4.0 développée avec Visual Studio 2010 et Blend 3 Preview for .Net 4. Toutes les manipulations sont cependant reproductibles sous Visual Studio 2008 et le Framework 3.5.
III-A. Un peu de code dans VS 2010▲
Une fois le projet WPF créé, nous ajoutons une référence à notre Web service :
Nous utiliserons le pattern MVVM pour structurer l'application. La vue (le diagramme réseau) sera « bindée » à la classe NetworkMonitorViewModel :
public
class
NetworkMonitorViewModel :
ViewModelBase,
MonitorServiceCallback
{
public
NetworkMonitorViewModel
(
)
{
InitializeData
(
);
MonitorNetwork
(
);
}
private
void
InitializeData
(
)
{
Logs =
new
ObservableCollection<
string
>(
);
FirewallInfos =
new
NetworkElementInfo {
Code =
"FW"
,
Name =
"FIREWALL"
,
Status =
NetworkStatus.
Online };
RouterInfos =
new
NetworkElementInfo {
Code =
"RO"
,
Name =
"ROUTER"
,
Status =
NetworkStatus.
Online };
FileServerInfos =
new
NetworkElementInfo {
Code =
"FS"
,
Name =
"FILES_SERVER"
,
Status =
NetworkStatus.
Online };
WebServerInfos =
new
NetworkElementInfo {
Code =
"WS"
,
Name =
"WEB_SERVER"
,
Status =
NetworkStatus.
Online };
DataServerInfos =
new
NetworkElementInfo {
Code =
"DS"
,
Name =
"DATA_SERVER"
,
Status =
NetworkStatus.
Online };
SwitchInfos =
new
NetworkElementInfo {
Code =
"SW"
,
Name =
"SWITCH"
,
Status =
NetworkStatus.
Online };
}
private
void
MonitorNetwork
(
)
{
MonitorServiceClient client =
new
MonitorServiceClient
(
new
InstanceContext
(
this
));
client.
Subscribe
(
);
}
public
ObservableCollection<
String>
Logs {
get
;
private
set
;
}
private
NetworkElementInfo _firewallInfos;
public
NetworkElementInfo FirewallInfos
{
get
{
return
_firewallInfos;
}
private
set
{
_firewallInfos =
value
;
OnPropertyChanged
(
"FirewallInfos"
);
}
}
//reste du code
}
La classe ViewModelBase permet de gérer l'implémentation de l'interface INotifyPropertyChanged.
Pour chaque élément réseau, nous aurons une référence vers un objet NetworkElementInfo le représentant (dans l'extrait de code ci-dessus il n'y a que l'élément firewall qui est visible). La collection Logs servira à afficher à l'écran les logs des messages reçus du service.
Il y a deux éléments importants à noter ici. Le premier est que la classe implémente l'interface MonitorServiceCallback (il s'agit bien d'une interface, même si elle n'est pas préfixée par « I ») qui a été créée par Visual Studio lorsque l'on a référencé le Web service. Il s'agit en fait de l'interface IMonitorCallback créée dans notre Web service et que le client doit implémenter afin d'être rappelé par le service. Le deuxième élément à noter est la construction de l'objet MonitorServiceClient (le proxy vers le Web service). Nous lui passons en paramètre une référence vers un objet implémentant l'interface de rappel (c'est-à-dire le ViewModel lui-même).
L'interface MonitorServiceCallback nous oblige à implémenter la méthode ReceiveNetworkInfo :
public
void
ReceiveNetworkInfo
(
NetworkElementInfo networkElementInfo)
{
if
(
networkElementInfo !=
null
)
{
switch
(
networkElementInfo.
Code)
{
case
"FW"
:
FirewallInfos =
networkElementInfo;
break
;
case
"RO"
:
RouterInfos =
networkElementInfo;
break
;
case
"FS"
:
FileServerInfos =
networkElementInfo;
break
;
case
"WS"
:
WebServerInfos =
networkElementInfo;
break
;
case
"DS"
:
DataServerInfos =
networkElementInfo;
break
;
case
"SW"
:
SwitchInfos =
networkElementInfo;
break
;
}
Logs.
Add
(
String.
Format
(
"{0} - Name: {1} - Status: {2}"
,
DateTime.
Now.
ToLongTimeString
(
),
networkElementInfo.
Name,
networkElementInfo.
Status));
}
else
{
Logs.
Add
(
"Erreur"
);
}
}
Le code est assez simple. Nous mettons simplement à jour les informations concernant l'élément réseau que le Web service vient de nous envoyer. Nous ajoutons aussi un élément à la collection de logs avec l'heure de réception du message, le nom et le statut de l'élément.
Afin de représenter graphiquement (couleur verte ou rouge) les statuts en ligne/hors ligne, nous aurons besoin d'un convertisseur entre les types NetworkStatus et SolidColorBrush :
public
class
StatusToColorConverter :
IValueConverter
{
public
object
Convert
(
object
value
,
Type targetType,
object
parameter,
System.
Globalization.
CultureInfo culture)
{
if
(
value
!=
null
)
{
NetworkStatus status =
(
NetworkStatus)value
;
switch
(
status)
{
case
NetworkStatus.
Online:
return
new
SolidColorBrush
(
Colors.
Green);
case
NetworkStatus.
Offline:
return
new
SolidColorBrush
(
Colors.
Red);
}
}
return
new
SolidColorBrush
(
Colors.
White);
}
public
object
ConvertBack
(
object
value
,
Type targetType,
object
parameter,
System.
Globalization.
CultureInfo culture)
{
throw
new
NotImplementedException
(
);
}
}
III-B. Un peu de design dans Expression Blend▲
Il est maintenant temps d'utiliser l'export XAML venant de Visio 2010. Pour cela, il suffit de copier/coller le XAML de l'export dans le code XAML de la fenêtre principale de l'application WPF. On repasse ensuite en mode design afin d'admirer le résultat :
Aïe ! On a comme qui dirait un léger problème. Si l'on regarde le message, on s'aperçoit que cela vient du fichier « .odttf » contenant la fonte de caractères utilisée dans le diagramme Visio.
Si l'on regarde de plus près le XAML, on s'aperçoit que les éléments Glyphs référencent ce fichier dans leur propriété FontUri :
<Glyphs
RenderTransform
=
"105.6, 0, 0, 96, 21.377739, 765.354331"
FontRenderingEmSize
=
"0.13889"
FontUri
=
"48230029-18BE-6784-E14A-6C3DD62CAE72.odttf"
Indices
=
"87"
>
</Glyphs>
Nous allons tout d'abord déclarer une source de données qui nous renverra le chemin complet vers la fonte Calibri se trouvant sur le système d'exploitation courant :
<ObjectDataProvider
x
:
Key
=
"Font_Calibri"
ObjectType
=
"{x:Type s:Environment}"
MethodName
=
"ExpandEnvironmentVariables"
>
<ObjectDataProvider.MethodParameters>
<
s
:
String>
%WINDIR%\Fonts\Calibri.ttf</
s
:
String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
Puis, remplaçons la chaine 48230029-18BE-6784-E14A-6C3DD62CAE72.odttf par {Binding Source={StaticResource Font_Calibri}} dans tout le fichier :
Finalement, la propriété FontUri ressemblera à ceci :
FontUri="{Binding Source={StaticResource Font_Calibri}}"
La propriété n'est plus liée au fichier « .odttf », mais à la font du système.
Si l'on repasse maintenant en mode design, nous obtenons quelque chose de nettement plus intéressant :
Le diagramme a été placé au sein d'un Viewbox pour répondre au redimensionnement de la fenêtre et un ItemsControl a été ajouté afin d'afficher les logs définis dans le ViewModel.
Nous pouvons maintenant passer à la partie « binding » pour donner vie à notre interface. Pour cela il suffit de sélectionner les différentes pastilles associées à chaque élément du réseau et dans le panneau de propriétés de lier la couleur de remplissage à une source de données (via le petit carré blanc à droite de la propriété).
Dans la fenêtre de création d'une liaison de données, nous allons ajouter une nouvelle source de données de type CLR Object et choisir notre ViewModel(NetworkMonitorViewModel ).
Dans le panneau de droite, nous choisissons la propriété à laquelle nous voulons lier la couleur de fond (dans notre cas, la propriété Status) et sélectionnons le convertisseur StatusToColorConverter que nous avions créé précédemment.
Cela aura notamment pour effet de créer deux nouvelles ressources dans la fenêtre ; une pour le ViewModel qui servira de DataContext à la fenêtre et une pour le convertisseur :
<Window.Resources>
<
NetworkMonitorClient_ViewModels
:
NetworkMonitorViewModel
x
:
Key
=
"NetworkMonitorViewModelDataSource"
d
:
IsDataSource
=
"True"
/>
<
local
:
StatusToColorConverter
x
:
Key
=
"statusToColorConverter"
/>
<ObjectDataProvider
x
:
Key
=
"Font_Calibri"
ObjectType
=
"{x:Type s:Environment}"
MethodName
=
"ExpandEnvironmentVariables"
>
<ObjectDataProvider.MethodParameters>
<
s
:
String>
%WINDIR%\Fonts\Calibri.ttf</
s
:
String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
Il ne reste maintenant plus qu'à lancer l'application (et le Web service) et admirer le résultat :
Les logs sont affichés dans la liste de gauche et les différents éléments réseau deviennent rouges ou verts en fonction de leur état.
Conclusion▲
Au travers de cet article, nous avons vu comment exporter un diagramme en XAML sous Visio 2010 et le réutiliser dans Visual Studio et Blend afin d'en faire une application connectée à un Web service.
La nouvelle fonctionnalité d'export en XAML de Visio 2010 ouvre des perspectives très intéressantes et permet de nouveaux usages pour les diagrammes Visio.
Sources▲
Téléchargez les sources de la solution donnée en exemple.
Liens▲
Remerciements▲
J'adresse ici tous mes remerciements à l'équipe de rédaction de « Developpez.com » pour le temps qu'ils ont bien voulu passer à la correction et à l'amélioration de cet article.
Contact▲
Si vous constatez une erreur dans le tutoriel, dans les sources, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter via le forum.