Jeff Fisher, team delle API Google Data
Gennaio 2008
Introduzione: l'ambito del campione
Scarica il file eseguibile di esempio.
Uno dei vantaggi dell'API Data List di Documenti è che consente agli sviluppatori di creare strumenti di migrazione per gli utenti che stanno ancora prendendo confidenza con Documenti Google. Ai fini dell'esercizio di questa API, ho utilizzato la libreria client.NET per creare un'applicazione di caricamento .NET 2.0, denominata in modo appropriato "DocList Uploader". Puoi ottenere l'origine del caricatore da Subversion.
Questo esempio ha lo scopo di semplificare la migrazione dei documenti di un utente dal computer a Documenti Google. Consente agli utenti di accedere al proprio Account Google e quindi trascinare e rilasciare i file supportati, che vengono caricati automaticamente. Il campione offre anche la possibilità di aggiungere un'opzione di menu con il tasto destro del mouse alla shell di Esplora risorse di Windows per caricare i file. Questo esempio è fornito ai sensi della licenza Apache 2.0, pertanto puoi utilizzarlo come punto di partenza per i tuoi programmi.
Questo articolo ha lo scopo di mostrare come è stato ottenuto parte del comportamento dell'esempio utilizzando .NET Framework. Consiste principalmente in snippet di codice annotati delle sezioni pertinenti. Questo articolo non tratta la creazione dei moduli e di altri componenti dell'interfaccia utente dell'applicazione, in quanto esistono molti articoli di Visual Studio che descrivono in dettaglio questo argomento. Se vuoi scoprire come sono stati configurati i componenti dell'interfaccia utente, puoi caricare il file di progetto autonomamente scaricando la libreria client e consultando la sottodirectory "clients\cs\samples\DocListUploader".
Creare un'app per la barra delle applicazioni
Gli strumenti di migrazione in genere sono in grado di essere eseguiti in modo non intrusivo nel sistema operativo, estendendo le funzionalità del sistema operativo senza distrarre troppo l'utente. Un modo per strutturare uno strumento di questo tipo in Windows è farlo eseguire dalla barra delle notifiche anziché ingombrare la barra delle applicazioni. In questo modo, è molto più probabile che gli utenti lascino il programma in esecuzione continua anziché aprirlo solo quando devono eseguire un'attività specifica. Si tratta di un'idea particolarmente utile per questo esempio, in quanto non è necessario memorizzare le credenziali di autenticazione sul disco.
Un'applicazione della barra delle applicazioni è un'applicazione che viene eseguita principalmente con una sola NotifyIcon nella barra delle applicazioni (la regione vicino all'orologio sulla barra delle applicazioni). Quando progetti un'applicazione di questo tipo, tieni presente che il modulo principale del progetto non deve essere quello con cui interagisce l'utente. Crea invece un modulo separato da visualizzare quando viene eseguita l'applicazione. Il motivo di questa scelta verrà chiarito a breve.
Nel mio esempio ho creato due moduli: HiddenForm, il modulo principale dell'applicazione con la maggior parte della logica, e OptionsForm, un modulo che consente all'utente di personalizzare alcune opzioni e accedere al proprio Account Google. Ho anche aggiunto un NotifyIcon chiamato DocListNotifyIcon a HiddenForm e l'ho personalizzato con la mia icona. Per assicurarmi che l'utente non veda HiddenForm, ho impostato la sua opacità su 0%, WindowState su Minimized e la proprietà ShowInTaskbar su False.
In genere, quando un programma viene eseguito dalla barra delle applicazioni, la chiusura dell'applicazione non dovrebbe interrompere il programma, ma nascondere tutti i moduli attivi e lasciare visibile solo NotifyIcon. Per farlo, dobbiamo eseguire l'override dell'evento "FormClosing" del nostro modulo come segue:
private void OptionsForm_FormClosing(object sender, FormClosingEventArgs e)
{
if(e.CloseReason == CloseReason.UserClosing) {
this.Hide();
e.Cancel = true;
}
}Probabilmente vogliamo anche nascondere il modulo quando l'utente lo riduce a icona, poiché non c'è motivo di occupare spazio sulla barra delle app dato che abbiamo già un'icona di notifica. Puoi farlo con il seguente snippet di codice:
private void OptionsForm_Resize(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.Hide();
}
}Ora, poiché non consentiamo all'utente di chiudere OptionsForm, possiamo mantenere una sola istanza associata a HiddenForm. Quando vogliamo visualizzare di nuovo OptionsForm, possiamo semplicemente chiamare il suo metodo Show.
Poiché il modulo principale di questa applicazione, HiddenForm, non è visibile all'utente, dobbiamo fornirgli un modo per uscire effettivamente dalla nostra applicazione. Ho scelto di aggiungere un ContextMenu a NotifyIcon con un ToolStripMenuItem per chiudere l'applicazione. Scrivere il gestore dei clic è semplice, basta chiamare il metodo Close di HiddenForm.
Suggerimenti per i palloncini
Molte applicazioni della barra delle applicazioni comunicano con l'utente mostrando un suggerimento a forma di fumetto, che appare come una bolla arrotondata che proviene da NotifyIcon. Il fumetto può essere visualizzato come segue:
DocListNotifyIcon.ShowBalloonTip(10000, "Title", "Example Text", ToolTipIcon.Info);
Il primo argomento è il periodo di tempo in millisecondi in cui visualizzare la bolla. Tieni presente che il sistema operativo consente un intervallo di tempo minimo e massimo per questo campo, ovvero 10 e 30 secondi rispettivamente. Il secondo e il terzo argomento specificano un titolo e alcuni contenuti per la bolla. L'ultimo argomento ti consente di scegliere un'icona per illustrare lo scopo della bolla.
Caricamento dei documenti
Caricare un documento è semplice. La maggior parte del lavoro viene svolta dal metodo UploadDocument dell'oggetto DocumentsService. Questa procedura è spiegata più chiaramente nella Guida per gli sviluppatori dell'API Documents List.
service = new DocumentsService("DocListUploader");
((GDataRequestFactory) service.RequestFactory).KeepAlive = false;
service.setUserCredentials(username, password);Innanzitutto, l'oggetto DocumentsService deve essere inizializzato e devono essere fornite le credenziali dell'utente. Per evitare alcuni problemi durante il caricamento di più file, l'intestazione HTTP "keep-alive" è stata disattivata perché è noto che causa alcuni problemi con .NET Framework.
lastUploadEntry = service.UploadDocument(fileName, null);
Questo snippet carica il file nel percorso contenuto nella stringa fileName. Il secondo argomento nullo indica che il nome del file Google Docs deve essere uguale al nome del file originale.
Gestione del trascinamento
Per semplificare il caricamento, è opportuno consentire all'utente di trascinare i file dalle cartelle nell'applicazione per caricarli. Il primo passaggio consiste nel consentire l'operazione di trascinamento da un file. Il codice riportato di seguito modificherà il cursore per indicare che il trascinamento è consentito:
private void OptionsForm_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop, false))
{
e.Effect = DragDropEffects.Copy;
}
}Una volta rilasciato il file o il gruppo di file, dobbiamo gestire l'evento esaminando ogni file rilasciato e caricandolo:
private void OptionsForm_DragDrop(object sender, DragEventArgs e)
{
string[ fileList = (string[) e.Data.GetData(DataFormats.FileDrop);
foreach (string file in fileList)
{
mainForm.UploadFile(file);
}
}Elenco dei documenti
Ottenere un elenco di documenti dal server è un buon modo per ricordare all'utente ciò che ha già caricato. Lo snippet riportato di seguito utilizza l'oggetto DocumentsService inizializzato in precedenza per recuperare tutti i documenti dal server.
public DocumentsFeed GetDocs()
{
DocumentsListQuery query = new DocumentsListQuery();
DocumentsFeed feed = service.Query(query);
return feed;
}Un modo pratico per visualizzare questi dati è utilizzare una ListView. Ho aggiunto una ListView denominata DocList a OptionsForm. Per renderlo più bello, ho anche creato un ImageList personalizzato di icone per illustrare i vari tipi di documenti. Il seguente codice compila ListView con le informazioni recuperate dal feed precedente:
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 variabile imageKey seleziona l'immagine da utilizzare per ogni riga nell'elenco di immagini associato. La proprietà Tag viene utilizzata qui per memorizzare la voce originale, che può essere utile per eseguire operazioni sul documento in un secondo momento. Infine, il metodo AutoResize viene utilizzato per formattare automaticamente la larghezza della colonna in ListView.
Apertura dei documenti nel browser
Poiché questi documenti sono archiviati in Google Documenti, è consigliabile consentire all'utente di visualizzarli nel browser. Windows offre una funzionalità integrata per farlo:
using System.Diagnostics;
private void OpenSelectedDocument()
{
if (DocList.SelectedItems.Count > 0)
{
DocumentEntry entry = (DocumentEntry) DocList.SelectedItems[0].Tag;
Process.Start(entry.AlternateUri.ToString());
}
}
Qui recuperiamo la voce originale dalla proprietà Tag, quindi utilizziamo AlternateUri del documento selezionato per chiamare Process.Start. Il resto viene gestito dalla magia di .NET Framework.
Aggiunta di un menu contestuale della shell
Il modo più semplice per aggiungere un elemento al menu contestuale della shell è modificare il registro. Dobbiamo creare una voce in HKEY_CLASSES_ROOT che rimandi alla nostra applicazione. Tieni presente che quando l'utente fa clic sulla voce di menu, si aprirà una nuova istanza della nostra applicazione, un aspetto che dovremo affrontare nelle sezioni successive.
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\"");
}Questo codice crea la chiave del Registro di sistema in cui si trova l'applicazione attualmente in esecuzione. La notazione "%1" viene utilizzata per indicare che il file selezionato nella shell deve essere passato all'interno di questo parametro. KEY_NAME è una costante stringa definita che determina il testo della voce nel menu contestuale.
public void UnRegister()
{
RegistryKey key = Registry.ClassesRoot.OpenSubKey("*\\shell\\"+KEY_NAME);
if (key != null)
{
Registry.ClassesRoot.DeleteSubKeyTree("*\\shell\\"+KEY_NAME);
}
}Questo metodo rimuove semplicemente la chiave personalizzata che abbiamo aggiunto, se esiste.
Impedire più istanze
Poiché la nostra applicazione si trova nella barra delle applicazioni, non vogliamo che vengano eseguite più istanze del programma contemporaneamente. Possiamo utilizzare un Mutex per assicurarci che rimanga in esecuzione una sola istanza.
using System.Threading;
bool firstInstance;
Mutex mutex = new Mutex(true, "Local\\DocListUploader", out firstInstance);
if (!firstInstance)
{
return;
}Il codice riportato sopra può essere inserito nel metodo Main della nostra applicazione per uscire in anticipo se il nostro programma è già in esecuzione. Poiché Mutex si trova nello spazio dei nomi "Locale", ciò consente di eseguire la nostra applicazione separatamente in una sessione diversa sulla macchina. Tuttavia, è necessario prestare particolare attenzione, poiché stiamo modificando il registro globale.
comunicazione tra processi
Quando un utente fa clic sulla voce del menu contestuale della shell per un file che abbiamo aggiunto in precedenza, viene avviata una nuova istanza della nostra applicazione e viene fornito il percorso completo della posizione del file sul disco. Queste informazioni devono ora essere comunicate all'istanza dell'applicazione già in esecuzione. Questa operazione può essere eseguita utilizzando i meccanismi IPC di .NET Framework introdotti nella versione 2.0.
using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Ipc;
Il messaggio che stiamo trasmettendo assume la forma di un oggetto personalizzato. Qui ho creato un oggetto che contiene un riferimento a HiddenForm, che contiene la logica di questa applicazione. Poiché questo oggetto verrà ospitato nell'istanza originale, fornisce a un'istanza successiva un modo per comunicare con il modulo principale dell'istanza originale.
class RemoteMessage : MarshalByRefObject
{
private HiddenForm mainForm;
public RemoteMessage(HiddenForm mainForm)
{
this.mainForm = mainForm;
}
public void SendMessage(string file)
{
mainForm.HandleUpload(file);
}
}
Quando viene inizializzata la prima istanza dell'applicazione, il seguente codice consente di ascoltare le istanze successive:
public void ListenForSuccessor()
{
IpcServerChannel serverChannel = new IpcServerChannel("DocListUploader");
ChannelServices.RegisterChannel(serverChannel, false);
RemoteMessage remoteMessage = new RemoteMessage(this);
RemotingServices.Marshal(remoteMessage,"FirstInstance");
}Nota che registra un canale IPC denominato e fornisce una copia dell'oggetto RemoteMessage che abbiamo definito, inizializzandolo con un riferimento a se stesso.
Per le istanze successive del programma, la stringa fornita a Main tramite il parametro args deve essere passata all'istanza originale. È possibile chiamare il seguente codice per connettersi al canale IPC in ascolto e recuperare l'oggetto RemoteMessage dall'istanza originale. Il metodo SendMessage viene quindi utilizzato per passare il nome del file all'istanza originale.
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);
}
}Il sistema di messaggistica remota è molto potente perché ci consente di rendere visibili gli oggetti appartenenti a un'istanza del nostro programma su altri canali IPC locali.
Conclusione
Questo articolo spiega a livello generale alcuni dei vari metodi e trucchi utilizzati nell'esempio di programma di caricamento di elenchi di documenti per fornire un'utilità di migrazione intuitiva per Google Docs. Esistono ancora molte funzionalità che possono essere aggiunte alle tue applicazioni e puoi estendere l'esempio per adattarlo ai tuoi scopi.
Ecco alcune risorse utili per gli sviluppatori interessati a lavorare con l'API Data di Elenco documenti, nonché per coloro che vogliono utilizzare .NET con altre API Google Data: