Skip to main content

Windows Home Server : créez des présentations Open XML à partir de vos photos


Par Julien Chable– MVP - Wygwam

Après une Beta des plus réussie, le premier système d’exploitation serveur de Microsoft destiné aux particuliers - Windows Home Server – devrait arriver massivement sur le marché européen en tout début d’année prochaine. A peine le temps pour les développeurs d’anticiper l’arrivée de ce nouveau produit grand public et de découvrir les possibilités offertes par cette plateforme et son SDK

Windows Home Server est le point central offrant à votre réseau domestique un partage de vos photos, vidéos ou autres, une gestion des sauvegardes ou encore la prise en main à distance d’un ordinateur de votre réseau. WHS est un produit grand public, entendez par là que c’est une version de Windows Server destinée au grand public et que par conséquent quelques unes des fonctionnalités destinées aux entreprises se voient bridées à cette occasion (WHS est issu de Windows Server 2003 SBS). Son objectif de séduire le grand public signifie également que toute l’interface d’administration du serveur a été considérablement améliorée et concentrée dans une unique console graphique permettant de quasiment tout contrôler.

Un serveur certes destiné aux particuliers mais dont l’extensibilité n’enlève rien à l’intérêt des développeurs et des entreprises qui désireraient développer des applications et des produits autour et pour la plateforme Windows Home Server. En effet, WHS et son SDK vous offre la possibilité de créer des extensions permettant d’étendre l’expérience de vos utilisateurs. Vous pourrez par exemple développer des extensions pour réaliser une surveillance par caméra, une application de gestion ou de tri automatique de vos documents, un service d’upload de document depuis un mobile, … ou même exploiter vos photos stockées sur le serveur pour générer des présentations Office 2007 pour les envoyer à vos amis, cas pratique qui comme vous l’aurez compris, sera l’exemple de cet article.

Cet article se compose de deux parties bien distinctes : la première est tout simplement une explication des prérequis et de la configuration nécessaire au développement d’une extension pour WHS, la seconde est l’implémentation de l’extension et du générateur de présentations PowerPoint 2007 (format Open XML) en utilisant les photos stockées dans le répertoire ‘Photos’ de votre WHS.

Remarque :cet article et l’exemple associé ont été réalisés avec une version d’évaluation Française de Windows Home Server.

 

Développer sur Windows Home Server

Le développement sur WHS est une tâche relativement simple et à portée de tout développeur pourvu que le développement .NET ne soit pas une discipline complètement inconnue. Notez que quelques connaissances des APIs de Windows Server 2003 représente tout de même un plus si vous souhaitez développer des extensions complexes. Cependant dans le cadre de cet article vous n’en aurez nullement besoin.

Le SDK de WHS est simple et seulement composé de deux éléments :

Vous avez deux façons d’étendre WHS : étendre la console WHS ou étendre la plateforme WHS en elle-même.

Dans le premier cas, vous allez pouvoir rajouter des onglets à ceux déjà existants dans la console WHS et/ou dans le panneau de configuration de la console. Dans le second cas, vous avez la possibilité de créer des applications de toute sorte en vous aidant des APIs WHS vous donnant accès à différentes informations cruciales : les ordinateurs clients connectés, les partages, les disques du serveur, les sauvegardes, les notifications, etc. Ainsi vous pourrez développer vos propres solutions web, Web Services ou applications WSS si vous le souhaitez.

Cet article va vous introduire au développement d’un onglet de la console administrative de WHS.

 

La solution type d’une extension WHS

Nous allons utiliser Visual Studio 2005 pour créer une solution qui sera composée de deux projets distincts :

  • Un projet de bibliothèque de classes (une extension WHS est une DLL) qui vous permettra de créer l’onglet pour la console ainsi que tout le code d’implémentation de notre extension.
  • Un projet de déploiement WiX qui vous permettra de créer un exécutable d’installation MSI pour déployer l’extension WHS sur le serveur.
  •  

Création du projet d’addin

Dans Visual Studio 2005, créez un nouveau projet en sélectionnant un projet de typebibliothèque de classes que vous pouvez nommerWHSOpenXMLAddin (garder l’option de création d’une solution).

Afin de déployer l’extension sur le serveur, WHS demande à la DLL d’extension de suivre la convention de nommage suivante : HomeServerConsoleTab.LeNomDeVotreOnglet.dll. Pour cela, il vous suffit de changer dans les propriétés du projet, le nom de l’assembly de la bibliothèque et de la nommer HomeServerConsoleTab.OpenXMLDemo. En plus de cette convention portant sur le nom de la DLL, tous vos fichiers source doivent utiliser un espace de noms respectant cette forme : Microsoft.HomeServer.HomerServerConsole.LeNomDeVotreOnglet. Afin de vous faciliter la tâche par la suite, il est d’une bonne pratique de changer l’espace de nom par défaut dans les propriétés du projet en spécifiant Microsoft.HomeServer.HomerServerConsole.OpenXMLDemo :

 

Création du projet d’addin

 

Un autre élément nécessaire à un onglet est l’image 32x32 qui lui sera associée. Toujours dans le panneau de propriétés de votre projet, sélectionner l’onglet Ressources puis cliquer sur Ajouter ressource puis Ressource existantepour ajouter l’image qui fera office d’icône de l’onglet :

 

Création du projet d’addin

 

La dernière chose qu’il reste à faire pour configurer votre projet d’extension est d’ajouter les dépendances vers les assembly de Windows Home Server, soit les DLLs HomeServerExt.dlletMicrosoft.HomeServer.SDK.Interop.v1.dll se trouvant dans le répertoire %ProgramFiles%\Windows Home Server de votre serveur.

Récupérez donc ces DLLs sur votre WHS et référencez leur copie dans le projet de façon à avoir une vue similaire à :

 

Création du projet d’addin

 

Pour résumer les différentes étapes de configuration du projet, il vous faudra :

  • Configurer le nom de l’assembly de votre bibliothèque de classe ainsi que l’espace de nom par défaut (ou ne pas oublier de le changer par la suite dans vos sources) selon les conventions de nommage indiquées plus haut.
  • Ajouter une image 32x32 qui sera associée à votre onglet.
  • Ajouter les dépendances vers les assembly de Windows Home Server et dans notre contexte, l’assemblyWindowsBase.dll de .NET 3.

 

Création du projet de déploiement

Il y a deux manières de réaliser le MSI nécessaire au déploiement de l’addin dans WHS : utiliser WiX pour créer directement le MSI désiré, ou alors utiliser un projet de déploiement dans Visual Studio 2005 puis l’outil ORCA.exe pour modifier les propriétés du MSI (mettre la valeur de la propriété WHSLogo à1)pour que Windows Home Server l’identifie comme un exécutable de déploiement d’extension WHS.

Dans cet article nous allons traiter uniquement de la méthode avec WiX, méthode qui vous servira sûrement dans l’avenir pour créer des installeurs d’extensions plus complexes. Le déploiement de l’addin se faisant à l’aide de WiX (Windows Installer XML, l’un des premiers projets Open Source de Microsoft) et ce dernier n’étant pas livré en standard dans Visual Studio 2005, vous allez devoir procéder à une légère installation supplémentaire s’il n’est pas installé sur votre machine de développement.

En effet, pour pouvoir créer un projet WiX, vous devez avoir installé un addin pour Visual Studio 2005 nommé Votive ( http://wix.sourceforge.net/votive.html) afin d’avoir à votre disposition les modèles de projet WiX :

 

Création du projet de déploiement

 

Remarque :dans le cadre de cet article nous utilisons la dernière version (version 3.0.2925.0).

Ajoutez un projet à votre solution existante en choisissant un projet de type WiX Project. Voici les différentes tâches que devra effectuer le script XML fourni à WiX pour générer l’installeur :

  • Vérifier que l’ordinateur cible est bien un Windows Server 2003 ou Windows Home Server.
  • Ajouter une propriété WHSLogo avec une valeur à1.
  • Copier les fichiers nécessaires à votre application dans le répertoire%systemdrive%\Program Files\Windows Home Server\.
  •  

Pour spécifier toutes ces tâches à WiX, ouvrez ou créer un fichier de script .wxs et modifiez-le de façon à ce qu’il ressemble au script suivant :

 

<?xmlversion="1.0" encoding="UTF-8"?><Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" >      <Product Id="3896a0e5-c7ff-4566-97e0-9ee83ad685e2"                  Name="WHSOpenXMLAddin"                  Language="1036"                  Codepage="1252"                  Version="1.0.0"                  Manufacturer="DemoFactory"                  UpgradeCode="86cbe6d8-6e6e-4ad9-a3cc-7be4ff15a3af" >            <Package                         InstallerVersion="200"                         Compressed="yes"                         Platforms="Intel"                         Languages="1036"                         SummaryCodepage="1252"/>            <Media Id="1" Cabinet="InstallWHSOpenXMLAddin.cab" EmbedCab="yes" />            <Property Id="WHSLogo" Value="1"/>            <Condition Message="[ProductName] requiert Windows Home Server FR.">VersionNT = 502</Condition>            <Directory Id="TARGETDIR" Name="SourceDir">                  <Directory Id="ProgramFilesFolder" Name="PFiles">                        <Directory Id="WHS" Name="Windows Home Server">                             <Component Id="HomeServerConsoleTab.OpenXMLDemo.dll" Guid="f56d6ad3-beb6-4577-8b5f-9b05931fc7a8" >                                   <File Id="HomeServerConsoleTab.OpenXMLDemo.dll" Name="HomeServerConsoleTab.OpenXMLDemo.dll"                                          Source="..\WHSOpenXMLAddin\bin\release\HomeServerConsoleTab.OpenXMLDemo.dll"                                          Vital="yes" KeyPath="yes" DiskId="1"/>                             </Component>                        </Directory>                  </Directory>            </Directory>            <Feature Id="DefaultFeature" Level="1">                  <ComponentRef Id="HomeServerConsoleTab.OpenXMLDemo.dll" />            </ Feature >      </ Product ></ Wix >

 

Pour commenter ce script qui pourrait vous paraître complexe, nous avons dans l’ordre :

  • Informations sur le package de déploiement (version de l’application, le fournisseur, la langue, etc) à noter que 1036 représente le français.
  • L’ajout de la propriété WHSLogo au paquet de déploiement.
  • La condition de vérification du système d’exploitation (doit être WHS ou Windows Server 2003).
  • Copie de la DLL dans le paquet de déploiement (qui ne sera au final qu’une archive Zip contenant la DLL développée).

 

Comme vous avez pu le remarquer dans le script, c’est la version ‘Release’ de l’assembly qui est utilisé, par conséquent il vous faudra configurer votre projet d’extension en mode ‘Release’ en appelant le gestionnaire de configuration du menu ‘Construire’.

 

Création du projet de déploiement

 

Une fois configuré et compilé le projet d’addin, vous allez pouvoir exécuter le script WiX en lançant la construction du projet. Le MSI ainsi créé devrait se retrouver dans le répertoire de sortie du projet WiX :

 

Création du projet de déploiement

 

Déploiement d’une extension Windows Home Server

Pour déployer l’addin dans Windows Home Server, il vous suffit de copier le fichier de déploiement MSI généré par WiX dans le répertoire\\NomServeyr\Logiciels\Add-ins\  de votre serveur WHS:

 

Déploiement d’une extension Windows Home Server

 

Une fois le fichier MSI copié dans le répertoire Logiciels/Add-Ins de votre serveur, il vous suffira de vous connecter à la console et de déployer l’addin en allant dans le panneau des paramètres de Windows Home Server àCompléments àEspace disponible puis de cliquer sur ‘Installer’ :

 

Déploiement d’une extension Windows Home Server

 

Implémenter une extension Windows Home Server : le générateur de diaporama

Le projet que nous allons réaliser dans le cadre de cet article est un générateur de diaporamas au format Open XML (nécessite PowerPoint 2007 ou PowerPoint 2003 avec le pack de compatibilité installé pour les visionner). Imaginez une extension Windows Home Server permettant de sélectionner les meilleures photos de vos vacances (par exemple, celles situées dans le répertoire ‘Photos’ de votre serveur Windows Home Server) puis de faire un clic sur un bouton pour générer un diaporama PowerPoint pour l’envoyer à vos amis : c’est tout simplement ce que nous allons faire !

 

Implémenter une extension Windows Home Server : le générateur de diaporama

 

Vous l’aurez compris, créer un générateur de diaporama directement dans la console d’administration de WHS n’est sûrement pas la meilleure idée pour mettre à disposition cette fonctionnalité. En effet, il aurait été plus judicieux de développer une interface web pour rendre cette fonctionnalité plus accessible à vos utilisateurs (et non pas uniquement à l’administrateur). Néanmoins dans le cadre de cet article et dans un but didacticiel, nous allons mettre l’interface de cette application dans un onglet de la console de WHS comme le montre la capture précédente.

 

Dernière ligne droite pour la configuration

Dans le cadre de ce projet nous allons modifier des documents Open XML. Par conséquent, nous avons besoin d’utiliser le framework .NET 3 pour avoir accès à la DLL WindowsBase.dll qui fourni l’espace de nom System.IO.Packaging permettant de manipuler les documents Open XML.

Suite à cela, la première chose à faire par conséquent est d’installer le framework .NET 3 sur le serveur Windows Home Server (cela vous permettra également d’utiliser WCF et/ou WF pour vos futures extensions) et sur votre poste de développement si cela n’est pas déjà chose faite.

Une fois .NET 3 installé sur la machine de développement et le serveur, rajoutez la référence vers l’assembly WindowsBase.dll dans votre projet en effectuant un clic droit sur votre projet puis ‘Ajouter référence’ :

 

Dernière ligne droite pour la configuration

 

Les onglets WHS

Un onglet WHS se compose au minimum de trois éléments principaux :

  • Une image associée au titre de l’onglet.
  • Une classe implémentant l’interfaceIConsoleTab.
  • Un contrôle utilisateur qui représente l’interface graphique de l’onglet.

 

Implémenter l’onglet de l’extension

De la théorie …

Pour ajouter un onglet à WHS, vous devez nécessairement implémenter une classe nommée HomeServerTabExtender qui implémente l’interface Microsoft.HomeServer.Extensibility.IConsoleTab :

 


using System.Drawing;
using System.Windows.Forms;
using Microsoft.HomeServer.Extensibility;
using Microsoft.HomeServer.SDK.Interop.v1;
namespace Microsoft.HomeServer.HomeServerConsoleTab.OpenXMLDemo
{
  publicclass HomeServerTabExtender : Microsoft.HomeServer.Extensibility.IConsoleTab
...
}

 

QuandHomeServerConsole.exe trouve la classeHomeServerTabExtender dans votre DLL, il va l’instancier en se servant d’un constructeur possédant la signature suivante :

 


public HomeServerTabExtender(int width, int height, IConsoleServices consoleServices)

 

Cette signature spécifie la taille de la zone graphique ainsi que les services à disposition (notamment concernant le panneau des paramètres). L’interface IConsoleTab expose plusieurs méthodes permettant à WHS de pouvoir récupérer les informations indispensables concernant l’onglet à ajouter :

 


public bool GetHelp() { ... }
public Guid SettingsGuid { get { ... } }
public string TabText { get { ... } }
public Bitmap TabImage { get { ... } }
public Control TabControl { get { ... } }

 

Voici un descriptif rapide des différentes méthodes de l’interface :

  • La méthode GetHelp() spécifie si vous fournissez ou non une aide à l’utilisateur pour cet onglet.
  • La propriété SettingsGuid spécifie l’identifiant unique du panneau de paramètres associé à cet onglet. Vous pouvez par exemple vouloir paramétrer certaines propriétés de votre onglet en proposant une interface de configuration. Cet identifiant sera utilisé pour afficher le panneau des paramètres associé à cet onglet. Si vous ne souhaitez pas mettre de panneau de configuration – comme dans le cadre de cet article – vous pouvez renvoyer un Guid vide.
  • La propriété TabText retourne le texte qui sera affiché dans l’onglet de la console administrative de WHS en dessous de l’image.
  • La propriété TabImage retourne l’image qui sera affichée dans l’onglet au dessus du texte deTabText.
  • La propriété TabControl retourne le contrôle utilisateur qui sera affiché dans l’onglet.

 

… à la pratique

Maintenant que la convention de nommage et le détail de l’interfaceIConsoleTab ont été présentés, il est grand temps de remplir la classe d’extension avec un peu de code. Voici le code complet de la classe HomeServerTabExtender de notre extension :

 


using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.HomeServer.Extensibility;
using Microsoft.HomeServer.SDK.Interop.v1;
namespace Microsoft.HomeServer.HomeServerConsoleTab.OpenXMLDemo
{
    public classHomeServerTabExtender : Microsoft.HomeServer.Extensibility.IConsoleTab
    {
        private PictureSelectorPanel nPanel;
        public HomeServerTabExtender(int width, int height, IConsoleServices consoleServices)
        {
           nPanel = new PictureSelectorPanel();
            nPanel.Size = new Size(width, height);

        }
        #region IConsoleTab Members
        public bool GetHelp() { returnfalse; }
        public Guid SettingsGuid { get { return Guid.Empty; } }
        public string TabText { get { return "Photos diaporama"; } }
        public Bitmap TabImage { get { return Properties.Resources.Thumbnail; } }
        public Control TabControl { get { return nPanel; } }
       #endregion
    }
}

 

L’instance du contrôle utilisateur utilisé pour l’interface de l’onglet est stockée dans une variable d’instance, de façon à ce que l’appel répété à cette propriété n’entraine pas l’instanciation d’instance multiple.

Remarque : le code précédent comporte déjà une instance du contrôle utilisateur (classe PictureSelectorPanel), nous n’en avons toujours pas parlé, et c’est  d’ailleurs le sujet du prochain paragraphe.

 

L’interface utilisateur de l’onglet

L’interface d’un onglet est l’expression d’un unique contrôle utilisateur :

 

L’interface utilisateur de l’onglet

 

L’idée est ici de proposer à l’utilisateur de parcourir des photos (au format JPEG et situées dans le répertoire ‘Photos’ des répertoires partagées) dans le TreeView du haut afin d’en sélectionner quelques unes qui seront ajoutées à la liste du bas. Une fois que l’utilisateur à choisi ses photos, il ne lui suffit plus que d’appuyer sur le bouton en bas à droite pour générer sa présentation.

Notez également la présence de deux PictureBox à droite afin de proposer un aperçu rapide de l’image sélectionnée dans les listes respectives.

Voici les étapes de création de cette interface :

  1. Ajoutez un contrôle utilisateur à votre solution nommé PictureSelectorPanel.
  2. Création du TreeView :
    1. Ajoutez un contrôle ImageList à l’interface, ce contrôle contiendra les images permettant à l’utilisateur de savoir si l’entrée est un répertoire ou une image :
      1. Spécifiez la valeur imageListTv à la propriété Name du contrôle.
      2. Ajoutez des images en faisant un clic droit sur le contrôle puis sélectionner ‘Choisir images’ pour configurer le contrôle de cette façon puis valider :
      3.  

        L’interface utilisateur de l’onglet

         

    2. Ajoutez sur votre contrôle un TreeView avec les propriétés suivantes :
      1. Name: tvFolderPictures
      2. ImageList: imageListTV
      3.  

  3. Ajoutez un contrôle de type ListBox et spécifiez les propriétés suivantes :
    1. Name : lstDest

     

  4. Ajoutez un contrôle SaveFileDialog nommésaveFileDialog avec les propriétés suivantes :
    1. DefaultExt : pptx
    2. Filter : Open XML Presentation|*.pptx
    3. Title : Enregistrer la presentation
    4.  

  5. Ajoutez les deux PictureBox à droite du TreeView et à droite de la ListBox avec respectivement les noms previewPictureet previewPictureDiapo.
  6.  

  7. Ajoutez un bouton en bas à droite nommébtnGeneratePresentation avec la propriété Textà Générer la présentation PowerPoint
  8.  

 

La logique derrière l’interface

Implémentons maintenant le comportement derrière l’interface du contrôle utilisateur que vous avez créé dans la partie précédente.

Tout d’abord assurez-vous bien d’avoir les imports nécessaire :

 


using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Microsoft.HomeServer.Extensibility;
using Microsoft.HomeServer.SDK.Interop.v1;

 

La première chose à faire est d’appeler une fonction de rafraichissement du TreeView dans le constructeur proposant la hiérarchie des photos:

 


publicPictureSelectorPanel()
        {
            InitializeComponent();
            // Mise à jour du tree view
            UpdatePictureFolderTV(null);
           ...
        }

 

Voici l’implémentation de la méthode de rafraichissement du Tree View qui recherche le répertoire partagé ‘Photos’ à l’aide des API de Windows Home Server si aucun nœud ne lui est spécifié, puis liste les répertoires et fichiers correspondant :

 


void UpdatePictureFolderTV(TreeNode selectedNode)
{
      TreeNode currentNode = selectedNode;
      string currentPath = null;
      if (selectedNode == null) {
           // Recherche du répertoire 'Photos' de WHS
            WHSInfoClass whsInfo = new WHSInfoClass();
            Array shares = whsInfo.GetShareInfo();
            foreach (IShareInfo info in shares)
{
                 if (info.Name.Equals("Photos")) {
                        photosFolder = info.Path;
                        currentPath = photosFolder;
                        currentNode = new TreeNode(
                       Path.GetDirectoryName(currentPath + Path.DirectorySeparatorChar), 0, 0);
                        currentNode.Tag = currentPath;
                        tvFolderPictures.Nodes.Add(currentNode);
                       break;
                  }
            }
           if (string.IsNullOrEmpty(photosFolder)) {
                 MessageBox.Show("Impossible de trouver le répertoire 'Photos'");
                  return ;
            }
      }
      else
      {
            currentPath = (string)selectedNode.Tag;
           if (File.Exists(currentPath)) {
                  previewPicture.ImageLocation = currentPath;
                 return;
            }
            else
            selectedNode.Nodes.Clear(); // On supprime tous les noeuds fils
      }
     // Mise à jour des fichiers
      string [] files = Directory.GetFiles(currentPath, "*.jpg");
      foreach (string file in files)
      {
           TreeNode nodeToAdd = new TreeNode(Path.GetFileName(file), 1, 1);
            nodeToAdd.Tag = file;
            currentNode.Nodes.Add(nodeToAdd);
      }
     // Mise à jour des fichiers
     string[] dirs = Directory.GetDirectories(currentPath);
      foreach (string dir in dirs) {
           TreeNode nodeToAdd = new TreeNode(
           Path.GetDirectoryName(dir + Path.DirectorySeparatorChar), 0, 0);
            nodeToAdd.Tag = dir;
            currentNode.Nodes.Add(nodeToAdd);
      }
}

 

Continuons maintenant avec les évènements pouvant être lancés par l’interface, et notamment ceux auxquels nous nous abonnons dans le constructeur :

 


public PictureSelectorPanel()
{
      ...
      tvFolderPictures.NodeMouseClick += new TreeNodeMouseClickEventHandler(tvFolderPictures_NodeMouseClick);
      tvFolderPictures.NodeMouseDoubleClick += new TreeNodeMouseClickEventHandler(tvFolderPictures_NodeMouseDoubleClick);
      lstDest.SelectedIndexChanged += newEventHandler(lstDest_SelectedIndexChanged);

 

Ces évènements permettent de spécifier plusieurs comportement : afficher un aperçu lorsque l’utilisateur sélectionne une image dans le TreeView ou la liste, et ajouter la sélection dans la ListBox quand l’utilisateur double clic une image dans le TreeView :

 


/// <summary>
/// Si l'utilisateur clique sur une image différente, on change l'affichage de la preview.
/// </summary>
void lstDest_SelectedIndexChanged(object sender, EventArgs e)
{
      int selectedIndex = ((ListBox)sender).SelectedIndex;
      if (selectedIndex >= 0)
            previewPictureDiapo.ImageLocation = ((ListBox)sender).SelectedItem.ToString();
}
/// <summary>
/// Si l'utilisateur effectue un double clic sur un noeud et que le noeud est une image
/// alors on l'ajoute à la collection d'image destiné au diaporama.
/// </summary>
void tvFolderPictures_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
      if (File.Exists((string)e.Node.Tag))
            lstDest.Items.Add((string)e.Node.Tag);
}
/// <summary>
/// A chaque clic d'un noeud on met à jour le treeview
/// </summary>
void tvFolderPictures_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
      UpdatePictureFolderTV(e.Node);
}

 

Maintenant que les différents contrôles fonctionnent de façon à permettre à l’utilisateur de pouvoir naviguer dans les répertoires de ses photos et d’ajouter des éléments du TreeView dans la ListBox inférieure, il reste encore à coder le comportement du bouton de génération de présentations Open XML. Pour cela, double cliquez sur le bouton dans le designer de Visual Studio pour générer automatiquement le code d’abonnement à l’évènement Click et placez le code suivant :

 


void btnGeneratePresentation_Click(object sender, EventArgs e)
{
      if(lstDest.Items.Count == 0) {
            MessageBox.Show("Vous devez au moins avoir une image pour générer une présentation !");
            return ;
      }
      WHSInfoClass whsInfo = new WHSInfoClass();
      saveFileDialog.InitialDirectory = Directory.GetParent(photosFolder).Name;
      saveFileDialog.FileOk += delegate {
            bool needCancelation = string.IsNullOrEmpty(saveFileDialog.FileName);
           if(needCancelation)
                  MessageBox.Show("Vous devez saisir un nom de fichier !");
            else {
                  if (OpenXMLPicturePresentationGen.Generate(GetPhotos(), saveFileDialog.FileName))
                       MessageBox.Show("Votre présentation a été générée avec SUCCES !");
            }
      };
      saveFileDialog.ShowDialog();
}

 

La méthode Generate de la classe OpenXMLPicturePresentationGen (que nous implémenterons dans la partie suivante) est appelée en spécifiant le chemin de toutes les images à inclure dans la présentation finale ainsi que le chemin de la présentation qui sera générée. La méthode suivante permet justement de récupérer sous forme de tableau de chaines, l’ensemble des chemins des images sélectionnées par l’utilisateur (celles listées dans la ListBox) dans l’objectif d’appeler la méthode de génération :

 


/// <summary>
/// Retourne sous forme de tableau de chaine, l'ensemble des chemins des images
/// sélectionnées par l'utilisateur.
/// </summary>
private string[] GetPhotos()
{
   string [] retArray = new string[lstDest.Items.Count];
   for (int i = 0; i < retArray.Length; ++i)
      retArray[i] = lstDest.Items[i] as string;
   returnretArray;
}

 

Générer la présentation Open XML pour PowerPoint 2007

Nous avons créé la classe d’extension et l’interface de l’onglet, il nous reste encore une dernière tâche pour terminer notre extension WHS : implémenter la génération de présentations Open XML !

Le format Open XML permet de décrire des documents bureautiques de type traitement de texte, tableur et présentation sous forme XML dans une archive Zip. Si vous ne connaissez pas encore le format Open XML, vous pouvez consulter les spécifications de ce standard en vous rendant sur le site de l’ECMA.

Remarque : Si vous ne vous sentez pas à l’aise avec les explications et le code suivant, vous pouvez passer cette partie et copier le code source en l’état. Vous pouvez également consulter la partie Open XML sur le site MSDN France : http://msdn2.microsoft.com/fr-fr/office/bb409619.aspxpour plus d’informations sur le format.

Tout d’abord ajoutez/créez une classe nomméeOpenXMLPicturePresentationGen dans le projet de l’extension, Visual Studio devrait vous créer un fichier source suivant :

 


using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.HomeServer.HomeServerConsoleTab.OpenXMLDemo
{
   public class OpenXMLPicturePresentationGen
    {
    }
}

 

Rajoutez les espaces de noms suivants :

 


...
using System.Xml;
using System.IO;
using System.IO.Packaging;
using System.Windows.Forms;

 

Pour rendre plus simple la tâche de génération de documents de présentation, nous allons utiliser un modèle de présentation ainsi qu’un modèle de structure XML de diapositive. Ces deux éléments sont placés en ressource dans l’assembly. Dans les propriétés du projet, ajoutez le fichier modèle PowerPoint et le modèle XML de diapositive qui se trouvent avec les sources de cet article :

 

Générer la présentation Open XML pour PowerPoint 2007



Nous allons manipuler du contenu XML avec des espaces de nom ainsi que la structure interne OPC (Open Packaging Convention) de la présentation Open XML. Ces manipulations nécessitent la déclaration des constantes suivante :

 


public class OpenXMLPicturePresentationGen
    {
       const string typeRelationDocument = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
       const string typeRelationDiapositive = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
        const string typeRelationDiapositiveLayout = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout";
        const string typeRelationImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
        const string namespacePresentationML = "http://schemas.openxmlformats.org/presentationml/2006/main";
        const string namespaceDrawingML = "http://schemas.openxmlformats.org/drawingml/2006/main";
        conststring namespaceRelationships = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
        const string contentTypeSlide = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
...

 

Attaquons maintenant à la partie la plus difficile de cet article, à savoir la manipulation du contenu et de la structure OPC du document. Voici les manipulations que nous allons effectuer pour modifier le modèle de présentation afin d’ajouter autant de diapositives que l’utilisateur a spécifié d’images :

 

  1.  Extraire la présentation modèle de l’assembly et en placer une copie sur le disque dur.
  2.  Ouvrir la présentation copiée.
  3.  Modifier les propriétés (créateur, date de création).
  4.  Rechercher la partie principale du document (contient l’enchainement des diapositives de la présentation), charger son contenu XML et ajouter un élément p:sldIdLst après l’élément p:sldMasterIdLst.
  5.  Pour chaque diapositive :
        
    1. Copier dans le structure OPC du document les données binaire de l’image.
    2. Créer la partie -PackagePart - représentant la diapositive et les relations vers le modèle de disposition de la diapositive et l’image qui sera affichée.
    3. Charger le modèle XML de diapositive et modifier l’identifiant de relation du conteneur d’image afin que PowerPoint retrouve la bonne image dans la structure du document Open XML.
    4. Créer la relation entre la partie principale du document et la diapositive de façon à ce que la partie principale puisse retrouver la diapositive à partir de son identifiant.
    5. Ajout dans la liste des diapositives de la partie principale d’un élémentp:
    6.  

  6. Enregistrement de la présentation.

 

Ces tâches sont dévolues à la méthode Generate(string[] photos, string destFilename) qui prend en arguments le chemin physique des photos à inclure dans la présentation finale et le chemin du fichier d’enregistrement :

 


...
/// <summary>
/// Génère les présentations Open XML à partir du chemin des images à insérer dans
/// la présentation.
/// </summary>
/// <param name="pictures">Le chemin des images à insérer.</param>
/// <param name="destFilename">Chemin de destination de la présentation.</param>
/// <returns>True si la génération à réussie, sinon False.</returns>
public static bool Generate(string[] photos, string destFilename)
{
      FileStream destStream = new FileStream(destFilename, FileMode.Create);
      // On extrait la présentation modèle vers un fichier
      CopyStream(new MemoryStream(Properties.Resources.Template), destStream);
      using (Package package = Package.Open(destStream, FileMode.Open, FileAccess.ReadWrite))
      {
            /** Définition des propriétés du document **/
            PackageProperties packProps = package.PackageProperties;
            packProps.Creator = "WHS Open XML Presentation Generator";
            packProps.Created = DateTime.Now;
            packProps.LastModifiedBy = null;
           /** Modification de la description de la présentation : ppt/presentation.xml; **/
            PackagePartpartiePresentation = null;
            // On obtient la partie principale du document (ppt/presentation.xml).
            foreach (System.IO.Packaging.PackageRelationship relation in package.GetRelationshipsByType(typeRelationDocument))
            {
                  Uri uriDocument = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), relation.TargetUri);
                 partiePresentation = package.GetPart(uriDocument);
            break;
            }
            // Extraction du contenu de la présentation
            //  Gère les espaces de nom pour réaliser des requêtes XPath.
            NameTablent = new NameTable();
            XmlNamespaceManagergestionnaireNS = newXmlNamespaceManager(nt);
            gestionnaireNS.AddNamespace("p", namespacePresentationML);
            gestionnaireNS.AddNamespace("a", namespaceDrawingML);
            gestionnaireNS.AddNamespace("r", namespaceRelationships);
            // Charge le XML de la présentation
            XmlDocumentdocPresentation = newXmlDocument(nt);
            docPresentation.Load(partiePresentation.GetStream());
            // Insertion de l'élément 'p:sldIdLst' après 'p:sldMasterIdLst'
            XmlNodenoeudPresentation = docPresentation.SelectSingleNode("//p:presentation", gestionnaireNS);
            XmlNode noeudSldMasterIdLst = docPresentation.SelectSingleNode("//p:sldMasterIdLst", gestionnaireNS);
            XmlNode noeudSldIdLst = docPresentation.CreateElement("p:sldIdLst", namespacePresentationML);
           noeudPresentation.InsertAfter(noeudSldIdLst, noeudSldMasterIdLst);
            // Ajout de la diapositive pour chaque photo
            uint diapoId = 1;
            foreach (string photo in photos)
           {
                 // Création de la partie contenant la photo
                  PackagePart partieImage = package.CreatePart(new Uri("/ppt/media/image" + diapoId + ".jpg", UriKind.Relative), "image/jpeg");
                 // Copie des données de l'image
                 using (FileStream photoStream = new FileStream(photo, FileMode.Open))
                  {
                        CopyStream(photoStream, partieImage.GetStream());
                  }
                 // Création de la diapositive
                 PackagePart partieDiapositive = package.CreatePart(new Uri("/ppt/slides/slide" + diapoId + ".xml", UriKind.Relative), contentTypeSlide);
                 PackageRelationship relationDiapo = partiePresentation.CreateRelationship(PackUriHelper.GetRelativeUri(partiePresentation.Uri, partieDiapositive.Uri),TargetMode.Internal, typeRelationDiapositive);
                  partieDiapositive.CreateRelationship( PackUriHelper.GetRelativeUri(partieDiapositive.Uri, new Uri("/ppt/slideLayouts/slideLayout1.xml", UriKind.Relative)), TargetMode.Internal, typeRelationDiapositiveLayout);
                  PackageRelationship relationImage = partieDiapositive.CreateRelationship(PackUriHelper.GetRelativeUri(partieDiapositive.Uri, partieImage.Uri), TargetMode.Internal, typeRelationImage);
                 XmlDocument docDiapositive = new XmlDocument(nt);
                  docDiapositive.Load(newStringReader(Properties. Resources.slide));
                 XmlNode noeudCNvPr = docDiapositive.SelectSingleNode(string.Format("//p:cNvPr[@name='{0}']", "Picture Placeholder 2"), gestionnaireNS);
                  noeudCNvPr.Attributes["descr"].Value = Path.GetFileName(photo);
                 XmlNode noeudBlip = docDiapositive.SelectSingleNode("//p:blipFill/a:blip",gestionnaireNS);
                  noeudBlip.Attributes["r:embed"].Value = relationImage.Id;
                 docDiapositive.Save(partieDiapositive.GetStream());
                 // Ajout de la diapositive dans la liste des diapositives de la présentation
                  XmlNode noeudSldId = docPresentation.CreateElement("p:sldId", namespacePresentationML);
                 XmlAttribute idSldAtt = docPresentation.CreateAttribute("id");
                 idSldAtt.Value = diapoId + 255 + "";    // Les id de PowerPoint 2007 commence à 256, faisons de même
                  XmlAttribute idAtt = docPresentation.CreateAttribute("r:id", namespaceRelationships);
                  idAtt.Value = relationDiapo.Id;
                  noeudSldId.Attributes.Append(idSldAtt);
                  noeudSldId.Attributes.Append(idAtt);
                  noeudSldIdLst.AppendChild(noeudSldId);
                 diapoId++;
            }
            // On enregistre les modifications du contenu de la présentation.
            docPresentation.Save(partiePresentation.GetStream());
            }
      destStream.Close();
      return true;
}
private static void CopyStream(Stream source, Stream target)
{
      constint bufSize = 0x1000;
      byte [] buf = new byte[bufSize];
      int bytesRead = 0;
      while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
            target.Write(buf, 0, (int)bytesRead);
      source.Close();
}

 

Déploiement

Dans les parties précédentes nous avons eu l’occasion de créer une classe représentant un onglet WHS, un contrôle utilisateur et une classe de génération de présentation PowerPoint 2007. La dernière étape consiste simplement à compiler la solution puis à lancer le script WiX (‘Construire’ sur le projet de déploiement) créé en début d’article afin de générer l’exécutable de déploiement pour Windows Home Server.

Suivez les étapes de déploiement tel que présentées précédemment et vous devriez être capable, une fois l’extension installée dans WHS, de pouvoir utiliser l’extension directement dans la console de Windows Home Server :

 

Déploiment

 

Déploiment

 


Conclusion

Cet article s’est voulu le plus simple possible pour créer une extension à Windows Home Server, cependant nous n’avons pas abordé en profondeur les APIs misent à notre disposition, ni la création d’un onglet dans le panneau des paramètres, etc.

La création d’extension à WHS n’est pas une tâche difficile du moment que vous possédez une certaine méthodologie et discipline quant aux noms des classes, des espaces de nom et des assembly. Sachez également que vous n’êtes pas cantonner à créer seulement des extensions pour la console, vous avez aussi un serveur IIS à votre disposition pour réaliser des solutions web (attention WHS n’est pas un serveur web de production pour autant !) et des APIs pour enrichir la plateforme Windows.

Windows Home Server est le premier serveur de Microsoft pour les particuliers, et même si certaines personnes autour de vous peuvent penser que les APIs et les fonctions de la console Windows Home Server sont limitées, il ne tient qu’à vous d’étendre la plateforme pour proposer à vos utilisateurs une expérience encore plus riche et innovante.



Références