.NET-Beispiel für das Hochladen von Dokumentlisten

Jeff Fisher, Google Data APIs-Team
Januar 2008

Einführung: Umfang der Stichprobe

Screenshot der Programmoberfläche

Beispiel-Executable herunterladen

Ein Vorteil der Documents List Data API ist, dass Entwickler damit Migrationstools für Nutzer erstellen können, die sich noch nicht an Google Docs gewöhnt haben. Für die Verwendung dieser API habe ich die .NET-Clientbibliothek verwendet, um eine .NET 2.0-Uploaderanwendung mit dem Namen „DocList Uploader“ zu erstellen. Sie können die Quelle des Uploaders aus Subversion abrufen.

Dieses Beispiel soll es Nutzern erleichtern, ihre Dokumente von ihrem Computer zu Google Docs zu migrieren. Nutzer können sich in ihrem Google-Konto anmelden und unterstützte Dateien per Drag-and-drop hochladen. Das Beispiel bietet auch die Möglichkeit, dem Windows Explorer-Shell eine Kontextmenüoption zum Hochladen von Dateien hinzuzufügen. Dieses Beispiel wird unter der Apache 2.0-Lizenz bereitgestellt. Sie können es also als Ausgangspunkt für Ihre eigenen Programme verwenden.

In diesem Artikel wird gezeigt, wie einige Verhaltensweisen des Beispiels mit dem .NET Framework erreicht wurden. Sie besteht hauptsächlich aus kommentierten Code-Snippets aus den relevanten Abschnitten. In diesem Artikel wird nicht beschrieben, wie die Formulare und anderen UI-Komponenten der Anwendung selbst erstellt werden, da es viele Visual Studio-Artikel gibt, in denen dies ausführlich beschrieben wird. Wenn Sie wissen möchten, wie die UI-Komponenten konfiguriert wurden, können Sie die Projektdatei selbst laden. Dazu müssen Sie die Clientbibliothek herunterladen und sich das Unterverzeichnis „clients\cs\samples\DocListUploader“ ansehen.

Taskleisten-App erstellen

Beispiel für eine Tray-App

Migrationstools können in der Regel unauffällig im Betriebssystem ausgeführt werden und erweitern die Funktionen des Betriebssystems, ohne den Nutzer zu sehr abzulenken. Eine Möglichkeit, ein solches Tool in Windows zu strukturieren, besteht darin, es in der Taskleiste auszuführen, anstatt die Taskleiste zu überladen. So ist es viel wahrscheinlicher, dass Nutzer das Programm kontinuierlich geöffnet lassen, anstatt es nur zu öffnen, wenn sie eine bestimmte Aufgabe ausführen müssen. Das ist eine besonders nützliche Idee für dieses Beispiel, da keine Anmeldedaten auf der Festplatte gespeichert werden müssen.

Eine Taskleistenanwendung wird hauptsächlich mit einem NotifyIcon in der Taskleiste (dem Bereich in der Nähe der Uhr in der Taskleiste) ausgeführt. Beim Entwerfen einer solchen Anwendung sollten Sie bedenken, dass das Hauptformular des Projekts nicht das Formular sein sollte, mit dem der Nutzer interagiert. Erstellen Sie stattdessen ein separates Formular, das angezeigt wird, wenn die Anwendung ausgeführt wird. Der Grund dafür wird gleich erläutert.

In meinem Beispiel habe ich zwei Formulare erstellt: „HiddenForm“, das Hauptformular der Anwendung mit dem Großteil der Logik, und „OptionsForm“, ein Formular, mit dem der Nutzer einige Optionen anpassen und sich in seinem Google-Konto anmelden kann. Außerdem habe ich dem HiddenForm ein NotifyIcon namens DocListNotifyIcon hinzugefügt und es mit einem eigenen Symbol angepasst. Damit der Nutzer das HiddenForm nicht sieht, habe ich die Deckkraft auf 0%, den WindowState auf Minimized und die ShowInTaskbar-Eigenschaft auf False gesetzt.

Wenn ein Programm in der Taskleiste ausgeführt wird, sollte das Schließen der Anwendung normalerweise nicht das Programm beenden, sondern alle aktiven Formulare ausblenden und nur das NotifyIcon sichtbar lassen. Dazu müssen wir das „FormClosing“-Ereignis unseres Formulars so überschreiben:

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

Außerdem möchten wir das Formular wahrscheinlich ausblenden, wenn der Nutzer es minimiert, da es keinen Grund gibt, Platz in der Taskleiste zu belegen, da wir bereits ein Benachrichtigungssymbol haben. Dazu können Sie den folgenden Code verwenden:

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

Da wir dem Nutzer nicht erlauben, das OptionsForm zu schließen, können wir einfach eine Instanz beibehalten, die an unser HiddenForm gebunden ist. Wenn wir das OptionsForm wieder anzeigen möchten, können wir einfach die zugehörige Show-Methode aufrufen.

Da das Hauptformular dieser Anwendung, das HiddenForm, für den Nutzer nicht sichtbar ist, müssen wir ihm eine Möglichkeit geben, die Anwendung zu beenden. Ich habe mich entschieden, dem NotifyIcon ein ContextMenu mit einem ToolStripMenuItem zum Schließen der Anwendung hinzuzufügen. Das Schreiben des Klick-Handlers ist einfach. Rufen Sie einfach die Close-Methode des HiddenForm auf.

Tipps für Ballons

Viele Systemtray-Anwendungen kommunizieren mit dem Nutzer, indem sie eine QuickInfo anzeigen, die wie eine abgerundete Blase aussieht, die vom NotifyIcon ausgeht. Die Blase kann so angezeigt werden:

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

Das erste Argument ist die Zeit in Millisekunden, die die Sprechblase angezeigt werden soll. Das Betriebssystem lässt für dieses Feld nur bestimmte Mindest- und Höchstzeiten zu, nämlich 10 Sekunden bzw. 30 Sekunden. Mit dem zweiten und dritten Argument werden ein Titel und einige Inhalte für das Infofeld angegeben. Mit dem letzten Argument können Sie ein Symbol auswählen, um den Zweck der Blase zu veranschaulichen.

Dokumente hochladen

Das Hochladen eines Dokuments ist ganz einfach. Der Großteil der Arbeit wird von der UploadDocument-Methode des DocumentsService-Objekts erledigt. Dieser Vorgang wird im Entwicklerhandbuch für die Documents List API genauer beschrieben.

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

Zuerst muss das DocumentsService-Objekt initialisiert und die Anmeldedaten des Nutzers müssen angegeben werden. Um Probleme beim Hochladen mehrerer Dateien zu vermeiden, wurde der HTTP-Header „keep-alive“ deaktiviert, da er bekanntermaßen Probleme mit dem .NET Framework verursacht.

lastUploadEntry = service.UploadDocument(fileName, null);

Mit diesem Snippet wird die Datei unter dem Pfad hochgeladen, der im String fileName enthalten ist. Wenn das zweite Argument „null“ ist, soll der Google-Dokumente-Dateiname mit dem Originaldateinamen übereinstimmen.

Drag-and-drop-Vorgänge verarbeiten

Um das Hochladen zu erleichtern, ist es sinnvoll, dem Nutzer zu ermöglichen, Dateien aus seinen Ordnern per Drag-and-drop in die Anwendung zu ziehen, um sie hochzuladen. Im ersten Schritt muss das Ablegen von Dateien erlaubt werden. Mit dem folgenden Code wird der Cursor so geändert, dass angezeigt wird, dass das Ablegen erlaubt ist:

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

Sobald die Datei oder Dateigruppe abgelegt wurde, müssen wir dieses Ereignis verarbeiten, indem wir jede abgelegte Datei durchgehen und hochladen:

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

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

Dokumente auflisten

Eine Liste der Dokumente vom Server abzurufen ist eine gute Möglichkeit, den Nutzer daran zu erinnern, was er bereits hochgeladen hat. Im folgenden Snippet wird das zuvor initialisierte DocumentsService-Objekt verwendet, um alle Dokumente vom Server abzurufen.

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

Eine praktische Möglichkeit, diese Daten zu visualisieren, ist die Verwendung einer ListView. Ich habe der OptionsForm eine ListView mit dem Namen DocList hinzugefügt. Um das Ganze ansprechender zu gestalten, habe ich auch eine benutzerdefinierte ImageList mit Symbolen erstellt, um die verschiedenen Dokumenttypen zu veranschaulichen. Mit dem folgenden Code wird die ListView mit den Informationen aus dem oben genannten Feed gefüllt:

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);
    }
}

Mit der Variablen imageKey wird ausgewählt, welches Bild in der zugehörigen ImageList für jede Zeile verwendet werden soll. Die Eigenschaft Tag wird hier verwendet, um den ursprünglichen Eintrag zu speichern. Das kann nützlich sein, um später Vorgänge für das Dokument auszuführen. Schließlich wird die Methode AutoResize verwendet, um die Spaltenbreite in der ListView automatisch zu formatieren.

Dokumente im Browser öffnen

Da diese Dokumente in Google Docs gespeichert sind, ist es sinnvoll, dem Nutzer das Dokument in seinem Browser anzeigen zu lassen. Windows bietet eine integrierte Funktion dafür:

using System.Diagnostics;

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

Hier rufen wir den ursprünglichen Eintrag aus der Property Tag ab und verwenden dann die AlternateUri des ausgewählten Dokuments, um Process.Start aufzurufen. Der Rest wird vom .NET Framework erledigt.

Hinzufügen eines Shell-Kontextmenüs

Die einfachste Möglichkeit, dem Kontextmenü der Shell ein Element hinzuzufügen, besteht darin, die Registrierung zu ändern. Wir müssen einen Eintrag unter HKEY_CLASSES_ROOT erstellen, der auf unsere Anwendung verweist. Wenn der Nutzer auf das Menüelement klickt, wird eine neue Instanz unserer Anwendung geöffnet. Das ist etwas, womit wir uns in den folgenden Abschnitten befassen müssen.

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\"");
}

Mit diesem Code wird der Registrierungsschlüssel für den Speicherort der aktuell ausgeführten Anwendung erstellt. Die Notation „%1“ gibt an, dass die ausgewählte Datei in der Shell in diesem Parameter übergeben werden soll. KEY_NAME ist eine definierte Stringkonstante, die den Text des Eintrags im Kontextmenü bestimmt.

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

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

Mit dieser Methode wird einfach der benutzerdefinierte Schlüssel entfernt, den wir hinzugefügt haben, sofern er vorhanden ist.

Mehrere Instanzen verhindern

Da sich unsere Anwendung in der Taskleiste befindet, möchten wir nicht, dass mehrere Instanzen des Programms gleichzeitig ausgeführt werden. Mit einem Mutex können wir dafür sorgen, dass nur eine Instanz ausgeführt wird.

using System.Threading;

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

Der obige Code kann in der Main-Methode unserer Anwendung platziert werden, um frühzeitig zu beenden, wenn unser Programm bereits ausgeführt wird. Da sich Mutex im Namespace „Lokal“ befindet, kann unsere Anwendung in einer anderen Sitzung auf dem Computer separat ausgeführt werden. Da wir die globale Registrierung ändern, ist jedoch Vorsicht geboten.

IPC-Verfahren (Inter-Process Communication)

Wenn ein Nutzer auf das Kontextmenüelement für eine Datei klickt, die wir zuvor hinzugefügt haben, wird eine neue Instanz unserer Anwendung gestartet und erhält den vollständigen Pfad zum Speicherort der Datei auf der Festplatte. Diese Informationen müssen nun an die bereits ausgeführte Instanz der Anwendung weitergegeben werden. Dazu können Sie die IPC-Mechanismen des .NET Frameworks verwenden, die in Version 2.0 eingeführt wurden.

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

Die Nachricht, die wir übergeben, hat die Form eines benutzerdefinierten Objekts. Hier habe ich ein Objekt erstellt, das einen Rückverweis auf die HiddenForm enthält, die die Logik dieser Anwendung enthält. Da dieses Objekt auf der ursprünglichen Instanz gehostet wird, kann eine spätere Instanz damit mit dem Hauptformular der ursprünglichen Instanz kommunizieren.

class RemoteMessage : MarshalByRefObject
{
    private HiddenForm mainForm;

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

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

Wenn die erste Instanz der Anwendung initialisiert wird, wird mit dem folgenden Code dafür gesorgt, dass sie auf nachfolgende Instanzen wartet:

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

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

Beachten Sie, dass oben ein benannter IPC-Kanal registriert und eine Kopie des von uns definierten RemoteMessage-Objekts bereitgestellt wird, das mit einem Verweis auf sich selbst initialisiert wird.

Für die nachfolgenden Instanzen des Programms muss der String, der Main über den Parameter „args“ bereitgestellt wird, an die ursprüngliche Instanz weitergegeben werden. Der folgende Code kann aufgerufen werden, um eine Verbindung zum überwachten IPC-Channel herzustellen und das RemoteMessage-Objekt aus der ursprünglichen Instanz abzurufen. Die Methode SendMessage wird dann verwendet, um den Dateinamen an die ursprüngliche Instanz zu übergeben.

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);
    }
}

Das Remote-Messaging-System ist sehr leistungsstark, da wir damit Objekte, die zu einer Instanz unseres Programms gehören, über lokale IPC-Channels für andere Instanzen sichtbar machen können.

Fazit

In diesem Artikel werden einige der verschiedenen Methoden und Tricks beschrieben, die im DocList Uploader-Beispiel verwendet werden, um ein benutzerfreundliches Migrationsdienstprogramm für Google Docs bereitzustellen. Es gibt noch viele Funktionen, die Sie in Ihren eigenen Anwendungen hinzufügen können. Sie können das Beispiel auch an Ihre eigenen Zwecke anpassen.

Hier finden Sie einige nützliche Ressourcen für Entwickler, die mit der Documents List Data API arbeiten möchten, sowie für Entwickler, die .NET mit anderen Google Data APIs verwenden möchten: