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:

visio2010.png

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 » apparaït vous permettant d'accéder aux options de publication.

saveas.png


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.

outputformat.png

La visualisation du résultat dans un navigateur:

image


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.

xamlfile.png


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.

newprojectwcf.png

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.

Contrat du service
Sélectionnez
[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ée à 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.

Contrat de rappel
Sélectionnez
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.

Contrats de données
Sélectionnez
[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.

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

Méthode Subscribe du service
Sélectionnez
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 rappeller 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.

Rappelle du client
Sélectionnez
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.

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

newprojectwpf.png


Une fois le projet WPF créé, nous ajoutons une référence à notre Web service :

image


Nous utiliserons le pattern MVVM pour structurer l'application. La vue (le diagramme réseau) sera « bindée » à la classe NetworkMonitorViewModel :

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

Implémentation de l'interface MonitorServiceCallback
Sélectionnez
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 :

Convertisseur Statut vers Couleur
Sélectionnez
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 :

blenderror1.png

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 :

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

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

blendreplace.png

Au final, la propriété FontUri ressemblera à ceci :

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

blendview.png

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é).

blendprop1.png


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.

blendbinding.png

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 :

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

image

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 tutorial, dans les sources, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter via le forum.