Scénarios d’interopérabilité entre une application Moderne et une application de Bureau Part I.

Pour développer des applications sur Windows 8 et 8.1, aujourd’hui vous avez deux paradigmes de développement comme illustré sur la figure suivante :

Image2

Des applications de bureaux traditionnelles, et des applications pour le Windows store ou dites Modernes, vous remarquerez qu’il existe une frontière entre les deux.

En effet, les applications Modernes sont pour la plus part développées pour un déploiement dans le Windows Store, elles s’exécutent essentiellement dans un conteneur “hyper protecteur”, ou il n’est pas possible de dialoguer aussi facilement qu’on le souhaiterait avec des applications de Bureau via les canaux Interprocessus traditionnels (IPC).

Image1

 

Avec Windows 8, seul deux mécanismes sont mis à notre disposition.

Avec l’arrivée de Windows 8.1 Update 1 deux autres mécanismes sont désormais disponibles.

Partant de ces mécanismes, il est possible d’imaginer différents scénarios afin de faire passer des informations entre les deux types d’applications ou entre une application Moderne et un composant qui ne serait pas chargé dans ce container “Hyper protecteur”.

Par exemple, nous pourrions imaginer les scénarios suivants :

Comment lancer votre application Moderne (Windows Store) au chargement de Windows 8.1 ?

Avec Windows il est possible de définir une application qui se lancera dés que l’utilisateur se connecte, il suffit de placer votre application dans le répertoire suivant :

C:\Users\[VOUS]\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

Le hic, c’est que vous ne pouvez-pas y placer votre application Moderne.

Une des solutions consiste à :

Définir un protocole personnalisé pour votre application moderne, comme illustré sur la figure suivante :
image

Créer une petite application de bureau qui invoquera ce protocole dans sa méthode Main(), comme illustré dans le code suivant :

static void Main()
        {
            ProcessStartInfo ppi = new ProcessStartInfo("alsdkcs:");
            ppi.WindowStyle = ProcessWindowStyle.Maximized;
            Process pp = new Process();
            pp.StartInfo = ppi;
            pp.Start();              
            
        }

Enfin il faut copier l’application de bureau dans le répertoire de démarrage et le tour est joué.

Lorsque l’utilisateur se connectera, l’application de bureau démarre qui elle même invoque l’application moderne via son protocole.

Comment lancer votre application de bureau à partir d’une application Moderne ?

Si l’application manipule des fichiers, le plus simple sera donc d’utiliser la méthode LaunchFileAsync, le fichier sera passé comme paramètre, à la méthode Main de l’application desktop. Il suffira alors de charger le fichier.
Néanmoins, plusieurs restrictions seront à prendre en compte.

Ce fichier à charger doit être présent :

Soit dans le répertoire courant de l’application

ApplicationData.Current.LocalFolder.GetFileAsync("presentation2.ppsx");

Soit dans un répertoire connu comme Images par exemple.

KnownFolders.PicturesLibrary.GetFileAsync("presentation2.ppsx");

Il ne sera pas possible de passer directement des paramètres (sauf à y être placés dans le fichier lui même).

L’autre méthode, consiste à utiliser un protocole personnalisé pour lancer l’application de bureau.

Tout d’abord il faut enregistrer un protocole personnalisé pour votre application de bureau. C’est très simple, ce n’est que des clés de registre à créer, dont voici un bout de code qui vous simplifiera la tâche.

   public static class CustomProtocol
    {
        const string URL_PROTOCOL_STRING =    "URL:{0} Protocol"           ;
        const string URL_PROTOCOL ="URL Protocol"                 ;
        const string URL_PROTOCOL_DEFAULTICON ="DefaultIcon"                 ;
        const string URL_PROTOCOL_COMMAND ="Shell\\Open\\command"         ;
        const string URL_PROTOCOL_OPEN ="Shell\\Open"                  ;
        const string URL_PROTOCOL_SHELL ="Shell"                         ;
        public static int Register(string protocolname,string exepath)
        {
            RegistryKey HKCR = null;
            RegistryKey SubKeyProtocolName=null;
            RegistryKey SubKeyDefaultIcone = null;
            RegistryKey SubKeyCommand = null;
            int Status = 0;
            try
            {
                HKCR = Registry.ClassesRoot;               
                SubKeyProtocolName = HKCR.CreateSubKey(protocolname,
                                                       RegistryKeyPermissionCheck.ReadWriteSubTree,
                                                       RegistryOptions.None);

                SubKeyProtocolName.SetValue("", String.Format(URL_PROTOCOL_STRING,protocolname));
                SubKeyProtocolName.SetValue(URL_PROTOCOL, "");
                SubKeyDefaultIcone=SubKeyProtocolName.CreateSubKey(URL_PROTOCOL_DEFAULTICON,
                                                                   RegistryKeyPermissionCheck.ReadWriteSubTree,
                                                                   RegistryOptions.None);

                SubKeyDefaultIcone.SetValue("", exepath);
                SubKeyCommand = SubKeyProtocolName.CreateSubKey(URL_PROTOCOL_COMMAND,
                                                                   RegistryKeyPermissionCheck.ReadWriteSubTree,
                                                                   RegistryOptions.None);
                string command = String.Format("{0} %1", exepath);

                SubKeyCommand.SetValue("", command);
            }
            catch(Exception ex)
            {
                Status = 1;
            }
            finally
            {

                if (SubKeyCommand != null) SubKeyCommand.Close();
                if (SubKeyDefaultIcone != null) SubKeyDefaultIcone.Close();
                if (SubKeyProtocolName!=null) SubKeyProtocolName.Close();
                if (HKCR != null) HKCR.Close();
            }

            return Status;
        }
         public static int UnRegister(string protocolname)
        {
            int Status = 0;
            RegistryKey HKCR = null;           
             try
             {
                 HKCR = Registry.ClassesRoot;
                 HKCR.DeleteSubKeyTree(protocolname, false);                                 
             }
             catch(Exception)
             {
                 Status = 1;
             }
             finally
             {
                 if (HKCR != null) HKCR.Close();
             }
            return Status;
        }
    }

A partir de l’application Moderne, il suffit d’appeler la méthode suivante :

await Windows.System.Launcher.LaunchUriAsync(new Uri(“LauncherProtocol:”));

Lorsque l’application de bureau se lance, Windows passe à la méthode Main la chaine LauncherProtocol qu’il faut intercepter.

static void Main(string[] args)
        {
            string protocol = args[0];
            if (args[0]=="LauncherProtocol:")
            {
                //code omis
            }
            else
            {
                //code omis
            }
        }

A la différence de l’association de fichier, vous pouvez également passer des paramètres via le protocole. En partant de ce principe on peu alors imaginer une foultitude de scénarios, comme dans la section suivante.

Comment formater un e-mail dans une application moderne et l’envoyer dans Outlook ?

L’idée ici est de pouvoir ce substituer au contrat de partage de Windows 8.1, et formater un e-mail avec une liste de distribution, des fichiers attachés, et un corps de message au format HTML.

On va donc utiliser la méthode à base de protocole personnalisé et procéder par étapes.

L’application Moderne va sauvegarder dans son répertoire courant un fichier au format XML qui contiendra les éléments du message, comme illustrer sur la figure suivante :
clip_image002
Dans ce fichier on retrouve en gros les destinataires du message, les fichiers attachés, ainsi que le corps du message au format HTML.

Cette application au lieu d’invoquer directement Outlook via le protocole Mailto, invoquera une application de bureau via le protocole personnalisé nommé LauncherProtocol : qui ne prend qu’un seul paramètre, c’est à dire le chemin d’accès au fichier xml au préalablement crée.

string filePath = await CreateMailAsync();
            Uri uri = new Uri(String.Format("LauncherProtocol:{0}", filePath));
            await Windows.System.Launcher.LaunchUriAsync(uri);

L’application de bureau est lancée via son protocole, va traiter la demande lancer Outlook si il n’est pas déjà chargé, puis demander à Outlook de créer un nouveau message factice à l’aide cette fois-ci du protocole MailTo.

static void Main(string[] args)
        {
                        
            int IndexOf= args[0].IndexOf(':');
            var file = args[0].Remove(0, IndexOf+1);            
            InvokeAppByProtocol(file);            
        }
        public static void InvokeAppByProtocol(string file)
        {
            Process[] Processes= Process.GetProcessesByName("outlook");            
            if (Processes.Count() > 1)
            {
                StartNewMessage(file);
                return; //no need to restart outlook;
            }                
            ProcessStartInfo pi = new ProcessStartInfo("Outlook.Exe");            
            pi.CreateNoWindow = false;
            pi.UseShellExecute = true;
            pi.WindowStyle = ProcessWindowStyle.Maximized;
            Process p = new Process();
            p.StartInfo = pi;
            
            if (p.Start())
            {
                
                StartNewMessage(file);
            }            
        }
        static void StartNewMessage(string file)
        {

            ProcessStartInfo ppi = new ProcessStartInfo(String.Format("mailto:fake@contoso.com?subject={0}", file));
            ppi.WindowStyle = ProcessWindowStyle.Maximized;
            Process pp = new Process();
            pp.StartInfo = ppi;
            pp.Start();
            Program.ShowWindow(pp.MainWindowHandle, 3);
        }

Enfin, l’astuce consiste à développer un Add-In Outlook qui va :

  • Intercepter le nouveau message factice.
  • Récupérer le chemin d’accès au fichier dans le champ Subject.
  • De sérialiser le vrai message à partir du fichier xml.
  • Et enfin, créer un nouveau message, comme illustré dans le listing suivant :

     void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
        {
            try
            {

                Outlook.MailItem currentMailItem = Inspector.CurrentItem as Outlook.MailItem;
                if (currentMailItem != null)
                {

                    if (currentMailItem.Subject == null) return;
                    StreamReader reader = new StreamReader(currentMailItem.Subject);
                    Mail mail = new Mail(reader.BaseStream);
                    
                    if (currentMailItem.EntryID == null)
                    {
                        Outlook.Attachments mailAttachments = currentMailItem.Attachments;
                        Outlook.Attachment newAttachment = null;
                        foreach (var attachment in mail.Attachments)
                        {
                            newAttachment = mailAttachments.Add(attachment);

                        }
                        currentMailItem.Save();
                        if (newAttachment != null) Marshal.ReleaseComObject(newAttachment);
                        if (mailAttachments != null) Marshal.ReleaseComObject(mailAttachments);

                        currentMailItem.Subject = mail.Subject;
                        currentMailItem.HTMLBody = mail.HTMLBody;
                        currentMailItem.Importance = (Outlook.OlImportance)mail.Importance;
                        currentMailItem.CC = mail.Cc;
                        currentMailItem.To = mail.To;
                        currentMailItem.BCC = mail.Bcc;
                        Marshal.ReleaseComObject(currentMailItem);
                    }
                }
            }
            catch (Exception ex)
            {
                var Message = ex.Message;
            }
        }

ce qui donne un message Outlook du style de la figure suivante :

image

Bon ok, c’est un peu tordu, mais c’est une idée…

Comme je le disais au début de ce billet, avec Windows 8.1 update 1, Microsoft a ouvert un peu les vannes en proposant deux autres mécanismes les Brokered Components et l’utilisation du LoopBack réseau
qui permet à des applications Modernes de dialoguer plus largement avec des applications de bureau, voir d’utiliser des librairies qui ne sont disponibles que pour le modèle de développement de bureau, mais si et seulement si ces applications modernes sont “sideloadées, c’est à dire qu’elles ne soient PAS installées via le Windows Store.

Dans la deuxième partie de cet article nous aborderons la manière d’utiliser le Loopback réseau via un mécanisme Interprocessus comme les Sockets ainsi que la manière d’autoriser l’application de bureau à y accéder.

Eric