Джефф Фишер, команда Google Data API
Январь 2008 г.
Введение: область применения выборки

Загрузите пример исполняемого файла .
Одно из преимуществ API данных списка документов заключается в том, что он позволяет разработчикам создавать инструменты миграции для пользователей, которые только осваиваются с Google Docs . Для отработки этого API я использовал клиентскую библиотеку .NET для создания приложения-загрузчика .NET 2.0, получившего соответствующее название «DocList Uploader». Исходный код загрузчика можно получить в Subversion .
Этот пример предназначен для упрощения переноса документов с компьютера в Google Docs. Он позволяет пользователю войти в свою учётную запись Google и перетаскивать поддерживаемые файлы, которые затем автоматически загружаются. В примере также предусмотрена возможность добавления пункта контекстного меню в оболочку проводника Windows для загрузки файлов. Этот пример предоставляется по лицензии Apache 2.0, поэтому вы можете использовать его в качестве отправной точки для своих собственных программ.
В этой статье показано, как некоторые элементы поведения примера были реализованы с помощью фреймворка .NET. Статья в основном состоит из аннотированных фрагментов кода из соответствующих разделов. В статье не рассматривается создание форм и других компонентов пользовательского интерфейса самого приложения, поскольку существует множество статей по Visual Studio, подробно описывающих это. Если вам интересно узнать, как были настроены компоненты пользовательского интерфейса, вы можете загрузить файл проекта самостоятельно, загрузив клиентскую библиотеку и открыв подкаталог "clients\cs\samples\DocListUploader".
Создание приложения в системном трее

Инструменты миграции обычно могут работать незаметно в операционной системе, расширяя возможности ОС без чрезмерного отвлечения пользователя. Один из способов структурировать такой инструмент в Windows — разместить его в системном трее, чтобы он не загромождал панель задач. Это значительно повышает вероятность того, что пользователи оставят программу работающей постоянно, а не будут открывать её только для выполнения определённой задачи. Это особенно полезно для данного примера, поскольку не требуется сохранять учётные данные аутентификации на диске.
Приложение в системном трее — это приложение, которое работает преимущественно с иконкой NotifyIcon в системном трее (области рядом с часами на панели задач). При разработке такого приложения помните, что не следует, чтобы пользователь взаимодействовал с основной формой проекта. Вместо этого создайте отдельную форму, которая будет отображаться при запуске приложения. Причина этого будет объяснена ниже.
В моём примере я создал две формы: HiddenForm, главную форму приложения с большей частью логики, и OptionsForm, форму, позволяющую пользователю настраивать некоторые параметры и входить в учётную запись Google. Я также добавил к HiddenForm значок уведомления (NotifyIcon) с именем DocListNotifyIcon и настроил его с помощью собственного значка. Чтобы пользователь не видел HiddenForm, я установил для неё непрозрачность 0%, свойство WindowState — Minimized, а свойство ShowInTaskbar — False.
Обычно, когда программа закрывается в системном трее, закрытие приложения не должно останавливать её работу, а должно скрывать все активные формы, оставляя видимым только значок NotifyIcon. Для этого нам нужно переопределить событие FormClosing нашей формы следующим образом:
private void OptionsForm_FormClosing(object sender, FormClosingEventArgs e)
{
if(e.CloseReason == CloseReason.UserClosing) {
this.Hide();
e.Cancel = true;
}
}Мы также, вероятно, захотим скрыть форму, когда пользователь её сворачивает, поскольку нет смысла занимать место на панели задач, ведь у нас уже есть значок уведомления. Это можно сделать с помощью следующего фрагмента кода:
private void OptionsForm_Resize(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.Hide();
}
} Теперь, поскольку мы не позволяем пользователю закрывать OptionsForm, мы можем просто оставить один экземпляр, привязанный к нашей HiddenForm. Когда нам понадобится снова отобразить OptionsForm, мы можем просто вызвать его метод Show .
Поскольку основная форма этого приложения, HiddenForm, не видна пользователю, нам нужно предоставить ему возможность выйти из приложения. Я решил добавить ContextMenu к NotifyIcon с ToolStripMenuItem для закрытия приложения. Написать обработчик щелчков просто: достаточно вызвать метод Close объекта HiddenForm.
Советы по использованию воздушных шаров
Многие приложения в системном трее взаимодействуют с пользователем, показывая всплывающую подсказку, которая выглядит как округлый пузырь, выходящий из значка уведомления. Этот пузырь можно отобразить следующим образом:
DocListNotifyIcon.ShowBalloonTip(10000, "Title", "Example Text", ToolTipIcon.Info);
Первый аргумент — это время отображения всплывающей подсказки в миллисекундах. Обратите внимание, что ОС выделяет для этого поля минимальное и максимальное время — 10 и 30 секунд соответственно. Второй и третий аргументы задают заголовок и содержимое всплывающей подсказки. Последний аргумент позволяет выбрать значок, иллюстрирующий назначение всплывающей подсказки.
Загрузка документов
Загрузка документа проста. Большая часть работы выполняется методом UploadDocument объекта DocumentsService . Более подробно этот процесс описан в Руководстве разработчика по API списка документов .
service = new DocumentsService("DocListUploader");
((GDataRequestFactory) service.RequestFactory).KeepAlive = false;
service.setUserCredentials(username, password); Сначала необходимо инициализировать объект DocumentsService и предоставить учётные данные пользователя. Чтобы избежать проблем при загрузке нескольких файлов, HTTP-заголовок «keep-alive» был отключён, поскольку он, как известно, вызывает некоторые проблемы с .NET Framework.
lastUploadEntry = service.UploadDocument(fileName, null);
Этот фрагмент загружает файл по пути, указанному в строке fileName . Второй аргумент, равный null, указывает, что имя файла в Google Docs должно совпадать с именем исходного файла.
Обработка перетаскивания
Чтобы упростить загрузку, имеет смысл разрешить пользователю перетаскивать файлы из папок в приложение. Первый шаг — разрешить перетаскивание файлов из файла. Приведённый ниже код изменит курсор, указывая на то, что перетаскивание разрешено:
private void OptionsForm_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop, false))
{
e.Effect = DragDropEffects.Copy;
}
}После того как файл или группа файлов были удалены, мы должны обработать это событие, пройдя по каждому удаленному файлу и загрузив его:
private void OptionsForm_DragDrop(object sender, DragEventArgs e)
{
string[ fileList = (string[) e.Data.GetData(DataFormats.FileDrop);
foreach (string file in fileList)
{
mainForm.UploadFile(file);
}
}Листинговые документы
Получение списка документов с сервера — отличный способ напомнить пользователю о том, что он уже загрузил. В следующем фрагменте кода используется объект DocumentsService , который мы инициализировали ранее, для получения всех документов с сервера.
public DocumentsFeed GetDocs()
{
DocumentsListQuery query = new DocumentsListQuery();
DocumentsFeed feed = service.Query(query);
return feed;
} Удобный способ визуализации этих данных — использование ListView. Я добавил ListView с именем DocList в OptionsForm . Чтобы сделать его более привлекательным, я также создал собственный ImageList с иконками для иллюстрации различных типов документов. Следующий код заполняет ListView информацией, полученной из ленты выше:
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);
}
} Переменная imageKey определяет, какое изображение из соответствующего списка ImageList должно использоваться для каждой строки. Свойство Tag используется здесь для хранения исходной записи, что может быть полезно для последующих операций с документом. Наконец, метод AutoResize используется для автоматического форматирования ширины столбца в ListView.
Открытие документов в браузере
Поскольку эти документы хранятся в Google Docs, удобно предоставить пользователю возможность просматривать их в браузере. В Windows для этого есть встроенная функция:
using System.Diagnostics;
private void OpenSelectedDocument()
{
if (DocList.SelectedItems.Count > 0)
{
DocumentEntry entry = (DocumentEntry) DocList.SelectedItems[0].Tag;
Process.Start(entry.AlternateUri.ToString());
}
}
Здесь мы извлекаем исходную запись из свойства Tag , а затем используем AlternateUri выбранного документа для вызова Process.Start . Всё остальное делает магия .NET Framework.
Добавление контекстного меню оболочки
Самый простой способ добавить пункт в контекстное меню оболочки — это внести изменения в реестр. Для этого нужно создать запись в разделе HKEY_CLASSES_ROOT, указывающую на наше приложение. Обратите внимание, что при щелчке пользователя по пункту меню будет открываться новый экземпляр нашего приложения, что мы рассмотрим в следующих разделах.
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\"");
} Этот код создаёт раздел реестра, в котором находится текущее запущенное приложение. Обозначение «%1» используется для указания того, что выбранный в оболочке файл должен быть передан в этом параметре. KEY_NAME — это определённая строковая константа, определяющая текст записи в контекстном меню.
public void UnRegister()
{
RegistryKey key = Registry.ClassesRoot.OpenSubKey("*\\shell\\"+KEY_NAME);
if (key != null)
{
Registry.ClassesRoot.DeleteSubKeyTree("*\\shell\\"+KEY_NAME);
}
}Этот метод просто удаляет добавленный нами пользовательский ключ, если он существует.
Предотвращение множественных случаев
Поскольку наше приложение находится в системном трее, мы не хотим, чтобы несколько его экземпляров были запущены одновременно. Мы можем использовать Mutex , чтобы гарантировать, что запущен будет только один экземпляр.
using System.Threading;
bool firstInstance;
Mutex mutex = new Mutex(true, "Local\\DocListUploader", out firstInstance);
if (!firstInstance)
{
return;
} Приведённый выше код можно поместить в метод Main нашего приложения для досрочного завершения, если программа уже запущена. Поскольку Mutex находится в пространстве имён «Local», это позволяет запустить наше приложение в отдельном сеансе на компьютере. Однако следует соблюдать осторожность, поскольку мы изменяем глобальный реестр.
Межпроцессное взаимодействие
Когда пользователь щёлкает по пункту контекстного меню оболочки для файла, который мы добавили ранее, запускается новый экземпляр нашего приложения, которому предоставляется полный путь к файлу на диске. Теперь эту информацию необходимо передать уже запущенному экземпляру приложения. Это можно сделать, используя механизмы IPC .NET Framework, представленные в версии 2.0.
using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Ipc;
Передаваемое нами сообщение принимает форму пользовательского объекта. Здесь я создал объект, содержащий ссылку на HiddenForm , содержащую логику данного приложения. Поскольку этот объект будет размещен в исходном экземпляре, он предоставляет последующему экземпляру способ взаимодействия с основной формой исходного экземпляра.
class RemoteMessage : MarshalByRefObject
{
private HiddenForm mainForm;
public RemoteMessage(HiddenForm mainForm)
{
this.mainForm = mainForm;
}
public void SendMessage(string file)
{
mainForm.HandleUpload(file);
}
}
При инициализации первого экземпляра приложения следующий код позволяет ему прослушивать последующие экземпляры:
public void ListenForSuccessor()
{
IpcServerChannel serverChannel = new IpcServerChannel("DocListUploader");
ChannelServices.RegisterChannel(serverChannel, false);
RemoteMessage remoteMessage = new RemoteMessage(this);
RemotingServices.Marshal(remoteMessage,"FirstInstance");
} Обратите внимание, что выше он регистрирует именованный канал IPC и предоставляет копию объекта RemoteMessage , который мы определили, инициализируя его ссылкой на себя.
Для последующих экземпляров программы строка, предоставленная Main через параметр args, должна быть передана исходному экземпляру. Следующий код можно вызвать для подключения к прослушивающему IPC-каналу и извлечения объекта RemoteMessage из исходного экземпляра. Затем метод SendMessage используется для передачи имени файла исходному экземпляру.
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);
}
}Система удаленного обмена сообщениями очень мощна, поскольку с ее помощью мы можем сделать объекты, принадлежащие одному экземпляру нашей программы, видимыми по локальным каналам IPC для других экземпляров.
Заключение
В этой статье подробно объясняются некоторые методы и приёмы, используемые в примере DocList Uploader для создания удобной утилиты для миграции в Google Docs. Вы также можете добавить множество функций в свои собственные приложения, и вы можете свободно расширять пример в соответствии со своими задачами.
Вот несколько полезных ресурсов для разработчиков, заинтересованных в работе с API данных списка документов, а также для тех, кто хочет использовать .NET с другими API данных Google: