...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

DFL: Clipboardのサンプルコード

Clipboardクラスを使ってコピー・アンド・ペーストをする処理のサンプルコードです。

簡単な使い方

長くなるのでここにサンプルコードを転記するのは止めておきますが、簡単に使い方を示します。 いつもどおり、基本的にWinFormsのそれと大差ありません。

// UTF-8文字列をクリップボードにコピー
string text = "hoge";
Clipboard.setString(text);

// クリップボードにUTF-8文字列がコピーされているか確認
if (Clipboard.containsString())
{
    // クリップボードからUTF-8文字列を取得
    string utf8Str = Clipboard.getString();
    if (utf8Str !is null)
        msgBox(utf8Str);
}

上記は、いくつかのデータ型に対してのみ定義されている簡単呼び出し版のメソッド群を使った方法です。 この方法では、DataクラスやDataObjectクラスを目にすることがありません。 WinFormsには同様のメソッドがもっとたくさん定義されていますが、現在DFLが対応しているのは、以下のとおりです。 括弧内は、その実体を表す代表的なデータ型です。

  • UTF-8文字列(string)
  • Unicode文字列(wstring)
  • Ansi文字列(ubyte[])
  • DDBビットマップ(HBITMAP)
  • DIBビットマップ(BITMAPINFO)
  • ファイルドロップリスト(HDROP)

データ型の自動変換

元々、Windowsではクリップボードにコピーされた特定のデータ型間は自動変換されるのですが、UTF-8文字列はDFLで定義した独自のデータ型なので、自動変換の対象外であるため、UTF-8/Unicode/Ansiの相互の自動変換を自前で実装しています。 自動変換されるので、UnicodeでセットされたデータをUTF-8でゲットすることが可能です。

また、ビットマップについては、システムがDDBビットマップ(HBITMAP)/DIB(BITMAPINFO)/DIBV5の相互変換をしてくれます。 なのですが、一体どういう形式のDDB/DIBに変換されたのかを解釈する必要があり、ビットマップのメモリ上のバイナリフォーマットを把握するのにかなり手こずりました。何とかDDB/DIBでセットされたデータをDDB/DIBのどちらでもゲットすることができるようになりました。

高度な使い方

簡単呼び出し版でない方法は、下記のとおりです。やっているのは上記コードと同じことです。

クリップボードフォーマットをDataFormats.stringFormat等で指定して、そのフォーマットに関連付けるデータをDataクラスのインスタンスに格納してセットします。

ゲットするときには、欲しいクリップボードフォーマットのデータがクリップボード上にあるかを確認したうえで、あった場合に、Dataクラスのインスタンス(data)を取得します。 dataに格納されている(はずの)データ型に対応したgetStringFormat()等のメソッドを呼び出して、データの実体を取り出します。

// UTF-8文字列をクリップボードにコピー
string text = "hoge";
Clipboard.setData(DataFormats.stringFormat, new Data(text));

// クリップボードにUTF-8文字列がコピーされているか確認
if (Clipboard.containsData(DataFormats.stringFormat))
{
    // クリップボードからUTF-8文字列を取得
    Data data = Clipboard.getData(DataFormats.stringFormat);
    string utf8str = data.getStringFormat();
    if (utf8Str !is null)
        msgBox(utf8Str);
}

さらに高度な使い方

Windowsクリップボードには複数のデータ型を同時にセットすることができます。 これにより、ユーザがビットマップデータを「コピー」して画像をクリップボードにセットしたように見えて、メモ帳で「ペースト」したらテキストデータがゲットされる、みたいなことができるようになっています。 メモ帳はクリップボードにテキストデータがあるか確認して、あった場合は、テキストデータの形式を要求するので、そのような挙動になります。

複数のデータ型を同時にセットするためには、DataObjectクラスのインスタンスを自分で作成し、それに複数セットします。 Clipboard.setData()を呼び出すとその度に新しいDataObjectが作られてしまうので、この用途では使えません。

// ビットマップとUTF-8文字列をクリップボードにコピー
Image bitmap = new Bitmap(r".\image\sample.bmp");
string text = "hoge";
dfl.data.IDataObject dataObj = new DataObject;
dataObj.setData(DataFormats.bitmap, new Data(bitmap));
dataObj.setData(DataFormats.stringFormat, new Data(text));
Clipboard.setDataObject(dataObj, false);
// クリップボードからビットマップとUTF-8文字列を取得
dfl.data.IDataObject dataObj = Clipboard.getDataObject();
if (dataObj.getDataPresent(DataFormats.bitmap))
{
    Data bmpData = dataObj.getData(DataFormats.bitmap);
    Image image = bmpData.getBitmap();
}
if (dataObj.getDataPresent(DataFormats.stringFormat))
{
    Data strData = dataObj.getData(DataFormats.stringFormat);
    string text = strData.getStringFormat();
}

GitHubにあるサンプルコードを実行し、コピーのボタンを押したあと、メモ帳やワードパッドでペーストすると、コピーしたデータ次第でちょっと面白い挙動が見られます。

ビットマップの解釈が大変だった

DataクラスとDataObjectクラスは、ドラッグアンドドロップの処理を見直したときにも大幅に修正しましたが、それに輪を掛けた大規模修正になりました。 DataObjectの実装も、DIBビットマップの構造を把握するのも大変でした。

特に、ビットマップのメモリ上のバイナリ形式には、カラーマスクがあったりなかったりするよ、カラーパレットもあったりなかったりするよ、という記事がいくつもありましたが、あったりなかったりする条件がよく分からず困りました。

そんな中でようやく下記の記事に出会うことができ、何とかDDB/DIBのバイナリを解釈できました。

クリップボードビューアがあってよかった

普段あまり使っていなかったので忘れていましたが、クリップボードの履歴を扱えるソフトCLCL (https://nakka.com/soft/clcl/) に、クリップボードビューア機能が付いていることに気がつきました。

これのおかげで、どのようなクリップボードフォーマットをセットしたときにどのようなフォーマットが自動変換されてセットされるのかが分かるようになり、また、そのバイナリデータも目視できるようになったことで、何が起こっているのかようやく分かるようになりました。

今後の課題

DFLのDataObjectはCOMのIDataObject実装です。

本来は、IDataObject実装が保持しているクリップボードフォーマットが何なのかを照会したとき、それ自身が提供できるフォーマットだけを列挙するのが作法のようですが、現在のDFLの実装ではそうなっていません。 システムが自動変換したフォーマットも一緒に列挙されるようになっています。

そのため、見かけ上は、DataObjectから取得しているのか、システムが自動変換したものを取得しているのか、区別が付かないようになっています。

それはそういう仕様と考えればいいのですが、WinFormsでは、自動変換を認めるか認めないかを選択できるようになっています。

DFLの各種メソッドは、WinFormsのメソッドのシグネチャを踏襲しているため、自動変換を認めるか認めないかを選択できるような引数があるものの、実際には機能していません。

また、その他の課題として、標準クリップボード形式はたくさんありますが、そのすべてに対応するのはかなり大変なので、無理そうです。

DFLのダウンロード

github.com