ZIPフォルダーからのドラッグ・アンド・ドロップをWPFアプリで受け取る
WPFで他のアプリからファイルのドラッグ・アンド・ドロップを受け取るときにはDropイベントハンドラーでDataFormats.FileDropを使う。でも、ときどき、これでは受け取れないドラッグ・アンド・ドロップがある。その代表が、Windows標準のZIPフォルダーというもの。ZIPを扱うアーカイバーの類をインストールしていない状態で、エクスプローラーでzipファイルを (「展開」せずに) ダブルクリックすると開くウインドウがそれだ。そのウインドウでZIPの中のファイルをいくつか選択してWPFアプリにドラッグ・アンド・ドロップすると、DROPイベントは発生するものの、GetDataPresent(DataFormats.FileDrop)が真にならない。無理やりGetData(DataFormats.FileDrop)しても結果はnull。これではファイルを受け取ることはできない。
実は、Windowsでファイルをドラッグ・アンド・ドロップする「方法」はいくつかあって、そのことはWindows SDKのドキュメント (https://learn.microsoft.com/windows/win32/shell/clipboard#formats-for-transferring-file-system-objects) に記載がある。C#/WPFのDataFormats.FileDropはWindows SDKではCF_HDROPと呼ばれているが、その他に重要なものにCFSTR_FILEDESCRIPTORがある。ZIPフォルダーはCFSTR_FILEDESCRIPTORを使うので、普通にWPFアプリを作るとファイルを受け取ることができないのだ。
CFSTR_FILEDESCRIPTORの文字列は"FileGroupDescriptorW"なので、テストアプリを書いて試すと、ZIPフォルダーからのドラッグ・アンド・ドロップでは確かにGetDataPresent("FileGroupDescriptorW")が真になる。それはいいのだが、GetData("FileGroupDescriptorW")しても、なんだかよく分からないMemoryStreamが得られるだけで、ファイルの中身は得られない。先述したWindows SDKのドキュメントは、COMを熟知していることを前提にC++用に書かれていて、C#からのアクセス方法は自明ではない。
ということで、正解はこちら (https://gist.github.com/AlissaSabre/074416480bac8408548fdcadc48ea460)。このクラスを使うと、CFSTR_FILEDESCRIPTORのドラッグ・アンド・ドロップをWPFアプリで受け取ることができる。使い方はdocumentation commentに書いたが、結論だけ言うと、次の例のような感じで使えばOK。
async void MyDragEventHandler(object sender, DragEventArgs e)
{
OutlookDataObject data = new OutlookDataObject(e.Data);
if (data.GetDataPresent("FileGroupDescriptorW"))
{
string[] names = (string[])data.GetData("FileGroupDescriptorW");
for (int i = 0; i < names.Length; i++)
{
using (Stream outstream = File.Create(names[i]))
{
MemoryStream content = data.GetData("FileContents", i);
await content.CopyToAsync(outstream);
}
}
}
}
ここで急にOutlookなんていう名前が出てくるが、Outlookに特有のことはやっていない。このコードはもともとMattyBoy4444氏のGist (https://gist.github.com/MattyBoy4444/521547) で、それがそういう名前だったのでそのままにしてある。ZIPフォルダーではなくてOutlookのメールの添付ファイルを受け取ることが目的だったので、名前にOutlookが入っているようだ。(たぶん、OutlookもCFSTR_FILEDESCRIPTORなんでしょう。知らんけど。) MattyBoy4444氏のコードは.NET 8までは動くのだが、.NET 10では動かなかった。原因は、WPFの実装内部にあるprivateなメソッドを、リフレクション経由で無理やり呼んでいること。.NET 10では、そのメソッド (GetDataFromHGLOBAL) がなくなってしまったらしいのだ。そこで、代わりのコードに書き換えたのがAlissa版。これは.NET Framework 4.8でも、.NET の 6~10でも動く。privateメソッドをゴニョゴニョしていないので、将来も動く可能性は高いと思う。
Alissa版は、先日紹介したaiimetaでドラッグ・アンド・ドロップをサポートするために書いた。この記事は答えが分かった後で書いているのだが、最初はドロップの受け取り方が分からなくて大変だった。ChatGPTに聞いたら「こうすればできます」としてコード例を教えてくれたが、そのコードは全く動作しなかった。でも、その情報の出典として教えてくれたURLの中にMattyBoy4444氏のGistが含まれていて、これがビンゴだったのだ。コード例はダメだったが、正解にたどり着けたのはChatGPTのおかげ。このgistは、Outlookの添付ファイルという体になっているので、おそらく普通の検索では自力で見つけられなかっただろうと思う。ChatGPTはウソも言うが役に立つ。そういうことだった。



最近のコメント