Jeff Fisher、Google Data APIs チーム
2008 年 1 月
はじめに: サンプルの範囲
ドキュメント リスト データ API の優れた点の 1 つは、Google ドキュメントにまだ慣れていないユーザー向けの移行ツールをデベロッパーが作成できることです。この API を使用するために、.NET クライアント ライブラリを使用して .NET 2.0 アップローダ アプリケーションを作成し、「DocList Uploader」という適切な名前を付けました。アップローダーのソースは、サブバージョンから取得できます。
このサンプルは、ユーザーがパソコンから Google ドキュメントにドキュメントを簡単に移行できるようにするためのものです。ユーザーは Google アカウントにログインして、サポートされているファイルをドラッグ&ドロップすると、ファイルが自動的にアップロードされます。このサンプルでは、ファイルをアップロードするために、Windows エクスプローラのシェルに右クリック メニュー オプションを追加するオプションも用意されています。このサンプルは Apache 2.0 ライセンスに基づいて提供されているため、独自のプログラムの出発点として自由に使用できます。
この記事では、.NET Framework を使用してサンプルの一部の動作を実現する方法について説明します。主に、関連するセクションの注釈付きコード スニペットで構成されています。この記事では、アプリケーション自体のフォームやその他の UI コンポーネントの構築方法については説明しません。これについては、多くの Visual Studio 記事で詳しく説明されています。UI コンポーネントがどのように構成されているかを知りたい場合は、クライアント ライブラリをダウンロードして、「clients\cs\samples\DocListUploader」サブディレクトリの中を確認することで、プロジェクト ファイルを自分で読み込むことができます。
システムトレイ アプリの作成
移行ツールは通常、オペレーティング システムで目立たないように実行され、ユーザーの作業をあまり妨げることなく OS の機能を拡張できます。Windows でこのようなツールを構成する方法の 1 つは、タスクバーを散らかすのではなく、システム トレイから実行することです。これにより、ユーザーは特定のタスクを実行する必要がある場合にのみプログラムを開くのではなく、プログラムを継続的に実行する可能性が高くなります。このサンプルでは、認証情報をディスクに保存する必要がないため、このアイデアは特に有用です。
システムトレイ アプリケーションとは、主にシステムトレイ(タスクバーの時計の近くの領域)の NotifyIcon のみで実行されるアプリケーションです。このようなアプリケーションを設計する際は、ユーザーが操作するメイン フォームがプロジェクトのメイン フォームにならないようにしてください。代わりに、アプリケーションの実行時に表示する別のフォームを作成します。その理由は後ほど説明します。
この例では、HiddenForm(アプリケーションのメイン フォームで、ほとんどのロジックが含まれています)と OptionsForm(ユーザーが一部のオプションをカスタマイズして Google アカウントにログインできるフォーム)の 2 つのフォームを作成しました。また、HiddenForm に DocListNotifyIcon という NotifyIcon を追加し、独自のアイコンでカスタマイズしました。HiddenForm がユーザーに表示されないように、Opacity を 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 に関連付けられたインスタンスを 1 つだけ保持できます。OptionsForm を再度表示する場合は、その Show メソッドを呼び出すだけです。
このアプリケーションのメイン フォームである HiddenForm はユーザーに表示されないため、ユーザーがアプリケーションを終了する方法を提供する必要があります。アプリケーションを閉じる ToolStripMenuItem を使用して、NotifyIcon に ContextMenu を追加することにしました。クリック ハンドラを記述するのは簡単です。HiddenForm の Close メソッドを呼び出すだけです。
バルーンのヒント
多くのシステムトレイ アプリケーションは、NotifyIcon から伸びる丸い吹き出しのようなバルーンチップを表示してユーザーと通信します。バブルは次のように表示できます。
DocListNotifyIcon.ShowBalloonTip(10000, "Title", "Example Text", ToolTipIcon.Info);
最初の引数は、バブルを表示する時間(ミリ秒単位)です。このフィールドで OS が許容する時間の最小値と最大値はそれぞれ 10 秒と 30 秒です。2 番目と 3 番目の引数では、バブルのタイトルとコンテンツを指定します。最後の引数では、バブルの目的を示すアイコンを選択できます。
ドキュメントのアップロード
ドキュメントのアップロードは簡単です。ほとんどの作業は、DocumentsService オブジェクトの UploadDocument メソッドによって行われます。このプロセスについては、Documents List API のデベロッパー ガイドで詳しく説明しています。
service = new DocumentsService("DocListUploader");
((GDataRequestFactory) service.RequestFactory).KeepAlive = false;
service.setUserCredentials(username, password);まず、DocumentsService オブジェクトを初期化し、ユーザーの認証情報を指定する必要があります。複数のファイルをアップロードする際に発生する問題を回避するため、.NET Framework で問題が発生することがわかっている「keep-alive」HTTP ヘッダーが無効になっています。
lastUploadEntry = service.UploadDocument(fileName, null);
このスニペットは、文字列 fileName に含まれるパスにあるファイルをアップロードします。第 2 引数が null の場合、Google ドキュメントのファイル名は元のファイル名と同じになります。
ドラッグ&ドロップの処理
アップロードを簡単にするために、ユーザーがフォルダからアプリケーションにファイルをドラッグ&ドロップしてアップロードできるようにするとよいでしょう。最初のステップは、ファイルからのドロップ操作を許可することです。次のコードは、ドロップが許可されていることを示すようにカーソルを変更します。
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 を使用する方法があります。OptionsForm に DocList という名前の ListView を追加しました。さらに、さまざまなドキュメント タイプを示すアイコンのカスタム 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 ドキュメントに保存されているため、ユーザーがブラウザでドキュメントを表示できるようにすると便利です。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 を使用すると、実行中のインスタンスが 1 つだけになるようにできます。
using System.Threading;
bool firstInstance;
Mutex mutex = new Mutex(true, "Local\\DocListUploader", out firstInstance);
if (!firstInstance)
{
return;
}上記のコードは、プログラムがすでに実行されている場合に早期に終了するように、アプリケーションの Main メソッドに配置できます。Mutex は「Local」名前空間内にあるため、マシン上の別のセッションでアプリケーションを個別に実行できます。ただし、グローバル レジストリを変更するため、注意が必要です。
プロセス間通信
ユーザーが先ほど追加したファイルのシェル コンテキスト メニュー項目をクリックすると、アプリケーションの新しいインスタンスが起動され、ディスク上のファイルの場所へのフルパスが渡されます。この情報を、すでに実行中のアプリケーションのインスタンスに伝える必要があります。これは、バージョン 2.0 で導入された .NET Framework の IPC メカニズムを使用して行うことができます。
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 オブジェクトのコピーを提供し、それ自体への参照で初期化しています。
プログラムの連続するインスタンスでは、args パラメータを介して Main に渡された文字列を元のインスタンスに渡す必要があります。次のコードを呼び出して、リスニング 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);
}
}リモート メッセージング システムは、プログラムの 1 つのインスタンスに属するオブジェクトをローカル IPC チャネルを介して他のインスタンスに表示できるため、非常に強力です。
まとめ
この記事では、DocList Uploader サンプルで使用されているさまざまな方法とテクニックについて、Google ドキュメントの使いやすい移行ユーティリティを提供するために使用されている方法とテクニックについて、概要を説明します。独自のアプリケーションに追加できる機能はまだたくさんあります。サンプルを自由に拡張して、独自の目的に合わせることができます。
Documents List Data API の使用に関心のあるデベロッパーや、他の Google Data API で .NET を使用したいデベロッパーに役立つリソースを以下に示します。