Exemple d'outil d'importation de liste de documents .NET

Jeff Fisher, équipe Google Data APIs
Janvier 2008

Introduction : champ d'application de l'échantillon

Capture d'écran de l'interface du programme

Téléchargez l'exemple d'exécutable.

L'un des avantages de l'API Data de la liste des documents est qu'elle permet aux développeurs de créer des outils de migration pour les utilisateurs qui ne sont pas encore habitués à Google Docs. Pour utiliser cette API, j'ai créé une application d'importation.NET 2 .0 à l'aide de la bibliothèque cliente.NET, que j'ai appelée "DocList Uploader" (Importateur DocList). Vous pouvez obtenir la source de l'outil d'importation à partir de Subversion.

Cet exemple est conçu pour permettre à un utilisateur de migrer facilement ses documents de son ordinateur vers Google Docs. Il permet aux utilisateurs de se connecter à leur compte Google, puis de glisser-déposer les fichiers compatibles, qui sont ensuite importés automatiquement. L'exemple fournit également une option permettant d'ajouter une option de menu contextuel à l'explorateur Windows pour importer des fichiers. Cet exemple est fourni sous la licence Apache 2.0. Vous pouvez donc l'utiliser comme point de départ pour vos propres programmes.

Cet article explique comment certains comportements de l'échantillon ont été obtenus à l'aide du framework .NET. Il se compose principalement d'extraits de code annotés provenant des sections concernées. Cet article ne traite pas de la façon de créer les formulaires et autres composants d'interface utilisateur de l'application elle-même, car de nombreux articles Visual Studio détaillent ce processus. Si vous souhaitez savoir comment les composants d'UI ont été configurés, vous pouvez charger vous-même le fichier du projet en téléchargeant la bibliothèque cliente et en examinant le sous-répertoire "clients\cs\samples\DocListUploader".

Créer une application de barre d'état système

exemple d'application de la barre d'état système

Les outils de migration peuvent généralement s'exécuter discrètement dans le système d'exploitation, ce qui étend les capacités de l'OS sans trop distraire l'utilisateur. Une façon de structurer un tel outil dans Windows consiste à l'exécuter à partir de la barre d'état système plutôt que d'encombrer la barre des tâches. Les utilisateurs sont ainsi beaucoup plus susceptibles de laisser le programme s'exécuter en continu au lieu de ne l'ouvrir que lorsqu'ils doivent effectuer une tâche spécifique. C'est une idée particulièrement utile pour cet exemple, car il n'a pas besoin de stocker les identifiants d'authentification sur le disque.

Une application de barre d'état système est une application qui s'exécute principalement avec une icône NotifyIcon dans la barre d'état système (la zone située près de l'horloge dans la barre des tâches). Lorsque vous concevez une telle application, gardez à l'esprit que vous ne voulez pas que le formulaire principal du projet soit celui avec lequel l'utilisateur interagit. Créez plutôt un formulaire distinct à afficher lorsque l'application est exécutée. Vous comprendrez pourquoi dans un instant.

Dans mon exemple, j'ai créé deux formulaires : HiddenForm, le formulaire principal de l'application avec la majeure partie de la logique, et OptionsForm, un formulaire qui permet à l'utilisateur de personnaliser certaines options et de se connecter à son compte Google. J'ai également ajouté un NotifyIcon appelé DocListNotifyIcon au HiddenForm et l'ai personnalisé avec ma propre icône. Pour m'assurer que l'utilisateur ne voit pas HiddenForm, j'ai défini son opacité sur 0 %, son WindowState sur Minimized et sa propriété ShowInTaskbar sur False.

En règle générale, lorsqu'un programme s'exécute à partir de la barre d'état système, la fermeture de l'application n'est pas censée arrêter le programme, mais plutôt masquer les formulaires actifs et ne laisser visible que NotifyIcon. Pour ce faire, nous devons remplacer l'événement "FormClosing" de notre formulaire comme suit :

private void OptionsForm_FormClosing(object sender, FormClosingEventArgs e)
{
    if(e.CloseReason == CloseReason.UserClosing) {
        this.Hide();
        e.Cancel = true;
    }
}

Nous souhaitons probablement également masquer le formulaire lorsque l'utilisateur le réduit, car il n'y a aucune raison de prendre de la place dans la barre des tâches puisque nous avons déjà une icône de notification. Pour ce faire, utilisez le code suivant :

private void OptionsForm_Resize(object sender, EventArgs e)
{
    if (this.WindowState == FormWindowState.Minimized)
    {
        this.Hide();
    }
}

Maintenant que nous n'autorisons pas l'utilisateur à fermer OptionsForm, nous pouvons simplement conserver une instance liée à HiddenForm. Lorsque nous voulons afficher à nouveau OptionsForm, nous pouvons simplement appeler sa méthode Show.

Étant donné que le formulaire principal de cette application, HiddenForm, n'est pas visible par l'utilisateur, nous devons lui donner un moyen de quitter l'application. J'ai choisi d'ajouter un ContextMenu à NotifyIcon avec un ToolStripMenuItem pour fermer l'application. L'écriture du gestionnaire de clics est simple : il suffit d'appeler la méthode Close de HiddenForm.

Conseils sur les ballons

De nombreuses applications de la barre d'état système communiquent avec l'utilisateur en affichant une info-bulle, qui ressemble à une bulle arrondie provenant de NotifyIcon. La bulle peut s'afficher comme suit :

DocListNotifyIcon.ShowBalloonTip(10000, "Title", "Example Text", ToolTipIcon.Info);

Le premier argument correspond à la durée d'affichage de la bulle en millisecondes. Notez que le système d'exploitation autorise une durée minimale de 10 secondes et une durée maximale de 30 secondes pour ce champ. Les deuxième et troisième arguments spécifient un titre et du contenu pour la bulle. Le dernier argument vous permet de choisir une icône pour illustrer l'objectif de la bulle.

Importer des documents

Importer un document est simple. La majeure partie du travail est effectuée par la méthode UploadDocument de l'objet DocumentsService. Ce processus est expliqué plus clairement dans le guide du développeur pour l'API Documents List.

service = new DocumentsService("DocListUploader");
((GDataRequestFactory) service.RequestFactory).KeepAlive = false;
service.setUserCredentials(username, password);

Tout d'abord, l'objet DocumentsService doit être initialisé et les identifiants de l'utilisateur doivent être fournis. Afin d'éviter certains problèmes lors de l'importation de plusieurs fichiers, l'en-tête HTTP "keep-alive" a été désactivé, car il est connu pour causer des problèmes avec le framework .NET.

lastUploadEntry = service.UploadDocument(fileName, null);

Cet extrait importe le fichier dont le chemin d'accès est contenu dans la chaîne fileName. Le deuxième argument étant nul, cela indique que le nom du fichier Google Docs doit être identique à celui du fichier d'origine.

Gérer le glisser-déposer

Pour faciliter l'importation, il est judicieux de permettre à l'utilisateur de glisser-déposer des fichiers depuis ses dossiers vers l'application pour les importer. La première étape consiste à autoriser l'opération de dépôt à partir d'un fichier. Le code ci-dessous modifie le curseur pour indiquer que le dépôt est autorisé :

private void OptionsForm_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop, false))
    {
        e.Effect = DragDropEffects.Copy;
    }
}

Une fois le fichier ou le groupe de fichiers déposé, nous devons gérer cet événement en parcourant chaque fichier déposé et en l'important :

private void OptionsForm_DragDrop(object sender, DragEventArgs e)
{
    string[ fileList = (string[) e.Data.GetData(DataFormats.FileDrop);

    foreach (string file in fileList)
    {
      mainForm.UploadFile(file);
    }
}

Lister les documents

Obtenir une liste de documents depuis le serveur est un bon moyen de rappeler à l'utilisateur ce qu'il a déjà importé. L'extrait ci-dessous utilise l'objet DocumentsService que nous avons initialisé précédemment pour récupérer tous les documents du serveur.

public DocumentsFeed GetDocs()
{
    DocumentsListQuery query = new DocumentsListQuery();
    DocumentsFeed feed = service.Query(query);
    return feed;
}

Un moyen pratique de visualiser ces données consiste à utiliser une ListView. J'ai ajouté une ListView nommée DocList à OptionsForm. Pour le rendre plus agréable, j'ai également créé une ImageList personnalisée d'icônes pour illustrer les différents types de documents. Le code suivant remplit la ListView avec les informations récupérées à partir du flux ci-dessus :

public void UpdateDocList()
{

    DocList.Items.Clear();


    DocumentsFeed feed = mainForm.GetDocs();


    foreach (DocumentEntry entry in feed.Entries)
    {
        string imageKey = "";
        if (entry.IsDocument)
        {
            imageKey = "Document.gif";
            
        }
        else if (entry.IsSpreadsheet)
        {
            imageKey = "Spreadsheet.gif";
        }
        else
        {
            imageKey = "Presentation.gif";
        }

        ListViewItem item = new ListViewItem(entry.Title.Text, imageKey);
        item.SubItems.Add(entry.Updated.ToString());
        item.Tag = entry;
        DocList.Items.Add(item); 
    }

    foreach (ColumnHeader column in DocList.Columns)
    {
        column.AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
    }
}

La variable imageKey sélectionne l'image à utiliser pour chaque ligne dans la liste d'images associée. La propriété Tag est utilisée ici pour stocker l'entrée d'origine, ce qui peut être utile pour effectuer des opérations sur le document ultérieurement. Enfin, la méthode AutoResize est utilisée pour mettre en forme automatiquement la largeur des colonnes dans la ListView.

Ouvrir des documents dans le navigateur

Étant donné que ces documents sont stockés dans Google Docs, il est préférable de permettre à l'utilisateur de les consulter dans son navigateur. Windows propose une fonctionnalité intégrée pour ce faire :

using System.Diagnostics;

private void OpenSelectedDocument()
{
    if (DocList.SelectedItems.Count > 0)
    {
        DocumentEntry entry = (DocumentEntry) DocList.SelectedItems[0].Tag;
        Process.Start(entry.AlternateUri.ToString());
    }
}

Ici, nous récupérons l'entrée d'origine à partir de la propriété Tag, puis nous utilisons le AlternateUri du document sélectionné pour appeler Process.Start. Le reste est géré par la magie du .NET Framework.

Ajouter un menu contextuel Shell

Le moyen le plus simple d'ajouter un élément au menu contextuel du shell consiste à modifier le registre. Nous devons créer une entrée sous HKEY_CLASSES_ROOT qui pointe vers notre application. Notez que cela ouvrira une nouvelle instance de notre application lorsque l'utilisateur cliquera sur l'élément de menu, ce que nous devrons traiter dans les sections suivantes.

using Microsoft.Win32;

public void Register()
{
    RegistryKey key = Registry.ClassesRoot.OpenSubKey("*\\shell\\"+KEY_NAME+"\\command");

    if (key == null)
    {
        key = Registry.ClassesRoot.CreateSubKey("*\\shell\\" + KEY_NAME + "\\command");
    }

    key.SetValue("", Application.ExecutablePath + " \"%1\"");
}

Ce code crée la clé de registre où se trouve l'application en cours d'exécution. La notation "%1" est utilisée pour indiquer que le fichier sélectionné dans le shell doit être transmis dans ce paramètre. KEY_NAME est une constante de chaîne définie qui détermine le texte de l'entrée dans le menu contextuel.

public void UnRegister()
{
    RegistryKey key = Registry.ClassesRoot.OpenSubKey("*\\shell\\"+KEY_NAME);

    if (key != null)
    {
        Registry.ClassesRoot.DeleteSubKeyTree("*\\shell\\"+KEY_NAME);
    }
}

Cette méthode supprime simplement la clé personnalisée que nous avons ajoutée, si elle existe.

Empêcher plusieurs instances

Étant donné que notre application réside dans la barre d'état système, nous ne voulons pas que plusieurs instances du programme s'exécutent en même temps. Nous pouvons utiliser un Mutex pour nous assurer qu'une seule instance reste en cours d'exécution.

using System.Threading;

bool firstInstance;
Mutex mutex = new Mutex(true, "Local\\DocListUploader", out firstInstance);
if (!firstInstance)
{
  return;
}

Le code ci-dessus peut être placé dans la méthode Main de notre application pour quitter rapidement si notre programme est déjà en cours d'exécution. Comme Mutex se trouve dans l'espace de noms "Local", cela permet à une autre session sur la machine d'exécuter notre application séparément. Toutefois, vous devez faire preuve de prudence, car vous modifiez le registre global.

communication interprocessus

Lorsqu'un utilisateur clique sur l'élément de menu contextuel du shell pour un fichier que nous avons ajouté précédemment, une nouvelle instance de notre application est lancée et reçoit le chemin d'accès complet à l'emplacement du fichier sur le disque. Ces informations doivent maintenant être communiquées à l'instance de l'application déjà en cours d'exécution. Pour ce faire, utilisez les mécanismes d'IPC du .NET Framework introduits dans la version 2.0.

using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;

Le message que nous transmettons prend la forme d'un objet personnalisé. Ici, j'ai créé un objet qui contient une référence à HiddenForm, qui contient la logique de cette application. Comme cet objet sera hébergé sur l'instance d'origine, il permet à une instance ultérieure de communiquer avec le formulaire principal de l'instance d'origine.

class RemoteMessage : MarshalByRefObject
{
    private HiddenForm mainForm;

    public RemoteMessage(HiddenForm mainForm)
    {
        this.mainForm = mainForm;
    }

    public void SendMessage(string file)
    {
        mainForm.HandleUpload(file);
    }
}

Lorsque la première instance de l'application est initialisée, le code suivant lui permet d'écouter les instances successives :

public void ListenForSuccessor()
{
    IpcServerChannel serverChannel = new IpcServerChannel("DocListUploader");
    ChannelServices.RegisterChannel(serverChannel, false);

    RemoteMessage remoteMessage = new RemoteMessage(this);
    RemotingServices.Marshal(remoteMessage,"FirstInstance");
    
}

Notez que l'exemple ci-dessus enregistre un canal IPC nommé et fournit une copie de l'objet RemoteMessage que nous avons défini, en l'initialisant avec une référence à lui-même.

Pour les instances successives du programme, la chaîne fournie à Main via le paramètre args doit être transmise à l'instance d'origine. Le code suivant peut être appelé pour se connecter au canal IPC d'écoute et récupérer l'objet RemoteMessage à partir de l'instance d'origine. La méthode SendMessage est ensuite utilisée pour transmettre le nom de fichier à l'instance d'origine.

public static void NotifyPredecessor(string file)
{
    IpcClientChannel clientChannel = new IpcClientChannel();
    ChannelServices.RegisterChannel(clientChannel, false);

    RemoteMessage message = (RemoteMessage) Activator.GetObject(typeof(RemoteMessage), 
      "ipc://DocListUploader/FirstInstance");

    if (!message.Equals(null))
    {
        message.SendMessage(file);
    }
}

Le système de messagerie à distance est très puissant, car il nous permet de rendre les objets appartenant à une instance de notre programme visibles sur les canaux IPC locaux pour d'autres instances.

Conclusion

Cet article explique de manière générale certaines des méthodes et astuces utilisées dans l'exemple DocList Uploader pour fournir un utilitaire de migration convivial pour Google Docs. Vous pouvez ajouter de nombreuses fonctionnalités dans vos propres applications et vous êtes libre d'étendre l'échantillon pour l'adapter à vos propres besoins.

Voici quelques ressources utiles pour les développeurs qui souhaitent utiliser l'API Documents List Data, ainsi que ceux qui souhaitent utiliser .NET avec d'autres API Google Data :