...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

DFL: やることリスト

TODO

  • [-] application.d
  • [-] base.d
  • [x] button.d
  • [x] chart.d
    • [x] 表を描画するTableRendererクラスを作る
    • [x] 折れ線グラフを描画するLineGraphRendererクラスを作る
    • [x] タイムチャートを描画するTimeChartRendererクラスを作る
  • [x] clipboard.d
  • [x] clippingform.d
  • [-] collections.d
  • [x] colordialog.d
  • [-] com.d
  • [x] combobox.d
  • [-] commondialog.d
  • [-] control.d
  • [x] data.d
  • [x] drawing.d
  • [x] environment.d
  • [x] event.d
  • [x] filedialog.d
  • [x] folderdialog.d
  • [x] fontdialog.d
  • [ ] form.d
    • [ ] MessageFilter 周りが中途半端なので TextBox と一緒に見直す
  • [x] groupbox.d
  • [x] imagelist.d
  • [x] label.d
  • [x] listbox.d
  • [x] listview.d
  • [x] menu.d
  • [x] messagebox.d
  • [x] notifyicon.d
  • [x] panel.d
  • [x] picturebox.d
  • [x] printing.d
  • [x] progressbar.d
  • [ ] registry.d
    • [ ] サンプルコード追加する
  • [ ] resources.d
    • [ ] サンプルコード追加する
  • [x] richtextbox.d
  • [ ] sharedcontrol.d
    • [ ] どういうときに使うのかよく分からないので調べる
  • [ ] socket.d
    • [ ] サンプルコード追加する?(そもそもDFLにsocketライブラリいる?)
  • [x] splitter.d
  • [x] statusbar.d
  • [x] tabcontrol.d
  • [ ] textbox.d
    • [ ] キー入力イベント処理の見直し
  • [ ] textboxbase.d
    • [ ] キー入力イベント処理の見直し
  • [x] timer.d
  • [x] toolbar.d
  • [x] tooltip.d
  • [x] trackbar.d
  • [x] treeview.d
  • [-] usercontrol.d

その他

  • [ ] internal/ の windows ヘッダー関係を DFL 独自のものから core.sys.windows.* に置き換える
  • [ ] Stream を廃止して undeaD ライブラリへの依存をなくす
    • COM の IStream から Picture コントロールを作成するコードだけ削除すればよさそうなので deprecated にした
  • [ ] ReBar コントロール(ToolStrip コントロール)を実装する
    • Panel を使った非フローティングウィンドウタイプならすぐできそうだが、フローティングウィンドウタイプだと難しそう
  • [x] PrintDialog とかの印刷関係の実装
  • [x] フォーム備付けのスクロールバーのサンプルコードを書く
  • [-] getter を const メンバにする
    • Windows APIを使っているメソッドでは無理そうなので諦め

更新履歴

  • 2023/3/17 更新
  • 2023/3/18 更新
  • 2023/3/19 更新
  • 2023/3/20 更新
  • 2023/3/29 更新
  • 2023/5/5 更新
  • 2024/1/14 更新
  • 2024/4/3 更新
  • 2024/4/13 更新
  • 2024/4/14 更新

DFL: ProgressBar のサンプルコード

DFL の ProgressBar クラスを更新しました。 表示形式を動的に設定できるようにしたり、マーキー形式の描画間隔を設定できるようにしたり。

さて、Windows の仕様で、ブロック形式(BLOCKS)の表示方法は、クラシックスタイルでしか使えません。こちらがクラシックスタイル。

下がビジュアルスタイルです。ビジュアルスタイルでは、スムーズ形式(CONTINUOUS)と区別が付かないようになってしまいます。

右下図のマーキー形式(MARQUEE)は、段階が数えられないような場合に使う表示形式で、ずっとバーが動き続けます。 こちらはビジュアルスタイルでしか使えません。

import dfl;

version(Have_dfl) // For DUB.
{
}
else
{
    pragma(lib, "dfl.lib");
}

class MainForm : Form
{
    private ProgressBar _progress;
    private Button _incre;
    private Button _reset;
    private ComboBox _mode;

    public this()
    {
        this.text = "ProgressBar example";
        this.size = Size(300, 300);

        _progress = new ProgressBar();
        _progress.parent = this;
        _progress.location = Point(20, 100);
        _progress.size = Size(200, 30);
        _progress.minimum = 0;
        _progress.maximum = 100;
        _progress.step = 5;
        _progress.value = 50;
        _progress.marqueeAnimationSpeed = 0; // Default; 30 ms.

        _mode = new ComboBox();
        _mode.parent =  this;
        _mode.location = Point(20, 150);
        _mode.dropDownStyle = ComboBoxStyle.DROP_DOWN_LIST;
        _mode.items.add("BLOCKS");
        _mode.items.add("CONTINUOUS");
        _mode.items.add("MARQUEE");
        _mode.selectedIndex = 0;
        _mode.selectedValueChanged ~= (Control c, EventArgs e)
        {
            if (_mode.selectedItem.toString() == "BLOCKS")
            {
                _progress.style = ProgressBarStyle.BLOCKS; // ビジュアルスタイルではCONTINUOUSと同じ
            }
            else if (_mode.selectedItem.toString() == "CONTINUOUS")
            {
                _progress.style = ProgressBarStyle.CONTINUOUS;
            }
            else if (_mode.selectedItem.toString() == "MARQUEE")
            {
                _progress.style = ProgressBarStyle.MARQUEE; // ビジュアルスタイルのみ
            }
        };

        _incre = new Button();
        _incre.parent = this;
        _incre.location = Point(20, 20);
        _incre.text = "Increment";
        _incre.click ~= (Control c, EventArgs e)
        {
            if (_progress.style == ProgressBarStyle.MARQUEE)
                msgBox("Can't increment.");
            else
            {
                // 事前に設定した固定値分だけ進める
                _progress.performStep();
                // 任意の値分だけ進める
                _progress.increment(5);
            }
        };

        _reset = new Button();
        _reset.parent = this;
        _reset.location = Point(120, 20);
        _reset.text = "Reset";
        _reset.click ~= (Control c, EventArgs e)
        {
            if (_progress.style == ProgressBarStyle.MARQUEE)
                msgBox("Can't reset.");
            else
                _progress.value = 50;
        };
    }
}

static this()
{
    Application.enableVisualStyles(); // ビジュアルスタイルにする(コメントアウトして実験)
}

void main()
{
    Application.run(new MainForm());
}

DFL のダウンロード

github.com

参考文献

DFL: ToolTip の機能とサンプルコードの追加

ToolTip のサンプルコードを作成するついでに WinForms にあってこちらにない機能をいくつか追加しました。

  • バルーンタイプのツールチップ
  • アイコンとタイトルの表示
  • アニメーション表示(フェードイン)なし
  • フェードアウトなし
  • 複数行の表示
  • &記号がアクセラレータ形式になる表示

こういうのって結局 Windows API で定義されているのを呼び出すだけなんですよね。

DFL のダウンロード

github.com

DFL: GetOpenFileName と GetSaveFileName

DFL の OpenFileDialog では GetOpenFileName が使われていて、SaveFileDialog では GetSaveFileName が使われています。それぞれ、表示されるダイアログがプレースバーがないタイプでした。

プレースバーがあるタイプのダイアログにするには OPENFILENAME 構造体の Flags メンバに OFN_EXPLORER を指定するのだと勘違いしてしばらくはまっていましたが、そうではありませんでした。 OFN_EXPLORERエクスプローラスタイルにするためのもので、上記のスタイルが既にエクスプローラスタイルでした。

オールドスタイルはこちら。

エクスプローラスタイルのダイアログには、さらに2種類あって、XP スタイルと Vista スタイルがあるようです。プレースバーがないのが XP スタイルです。

Vista スタイルにしたダイアログがこちら。

Vista スタイルにするためには、OPENFILENAME 構造体の Flags メンバに OFN_EXPLORER を指定するとともに、OFN_ENABLEHOOK を指定してはいけません。 フックプロシージャを有効にすると、強制的に XP スタイルになってしまいます。

互換性の理由から、 FlagsがOFN_ENABLEHOOKに設定され、lStructSizeがOPENFILENAME_SIZE_VERSION_400に設定されている場合、Places Bar は非表示になります。

公式には上記のように書いてありますが、Flags と lStructSize のいずれかがこのとおりになっている場合、プレースバーが表示されないようです。最初は AND 条件なのかと思いましたが、OFN_ENABLEHOOK を指定するだけで、プレースバーは非表示になりました。

さて、そうすると、プレースバーを表示した場合、フックプロシージャが使えませんので、ヘルプボタンを表示してもクリックしたことを検知できないのですが、ヘルプボタンの押下に限っては救済があります。

意味
OFN_SHOWHELP
0x00000010
ダイアログ ボックスに [ヘルプ] ボタンを表示します。hwndOwnerメンバーは、ユーザーが [ヘルプ] ボタンをクリックしたときにダイアログ ボックスが送信するHELPMSGSTRING登録メッセージを受信するウィンドウを指定する必要があります。ユーザーが [ヘルプ] ボタンを クリックすると、 Explorer スタイルのダイアログ ボックスがCDN_HELP通知メッセージをフック プロシージャに送信します。

ユーザーが [ヘルプ] ボタンをクリックしたときに、エクスプローラースタイルの [開く] または [名前を付けて保存] ダイアログ ボックスによって送信されます。
OFNHookProc フック プロシージャは、WM_NOTIFY メッセージの形式でこのメッセージを受信します。

これにより、OpenFileDialog と SaveFileDialog が備える helpRequest イベントは実装できます。多分、 GetDlgItem とかを駆使すれば、どのダイアログのヘルプボタンがクリックされたか突き止めることができるでしょう。

ただ、ファイルを選択するボタンを押下しても、CDN_FILEOK 通知メッセージをトラップすることができなかったので、どうやら fileOk イベントはこの方法では実装できないようです。

2つのイベントのうちヘルプボタンのクリックだけ対応しても中途半端なので、とりあえずエクスプローラスタイルのときはイベントハンドラが使えないことにしました。

条件付きではありますが、これで Vista スタイルのファイル選択ダイアログが使えるようになりました。

DFL のダウンロード

github.com

参考資料

DFL: コモンダイアログのサンプルコード

ボタンの配置が微妙ですが、コモンダイアログのサンプルコードを追加しました。

以下のクラスのサンプルを含みます。

  • OpenFileDialog
  • SaveFileDialog
  • FolderBrowserDialog
  • FontDialog
  • ColorDialog

あと PrintDialog が存在しますが、DFL には実装されていません。

実装済みのクラスには、未実装のメソッドがいくつかありました。 あまり使いそうにないものは後回しにされていたようです。 普通に使う分には、今の状態でも十分使えました。

Stream を deprecated に

なんとか最新の dmd で DFL をビルドするために undeaD ライブラリを使って Stream を使えるようにしましたが、Stream は追々使わないようにした方がいいと思うので、OpenFIleDialog クラスと SaveFileDialog クラスの openFile() を変更しました。

  • openFile() が Stream を返すのを止めて、std.stdio.File を返すように変更
  • Stream を返す旧 openFile() は openFileStream() に改名し、deprecated を付与

いつ削除するかは特に決めていません。

DFL のダウンロード

github.com

参考文献

DFL: ImageListのサンプルコード(ComboBoxも)

ここにコードを貼るのは省略しますが、ImageListのサンプルコードを追加しました。 下のリンクから参照してください。

ついでにComboBoxも使っているので、こちらのサンプルとしても使えます。 挙動が不便だったので少し直してあります。

ちなみにイメージリストというのは、下のように1つのビットマップファイルに複数の画像を含めておいて、使うときに切り分けて使えるものです。

DFL のダウンロード

github.com