2006 年 6 月 24 日 22 時 4 分

ColumnProvider を作る


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


今日は、IColumnProvider のプロトコルを確認するため、
IColumnProvider の実装系を作ってみよう。

まず、プロジェクトのルートに Column フォルダを追加する。
フォルダは自動的に名前空間として使用されるので、
Column の中は、LoaferShellEx.Column 名前空間となる。

この中にクラスを追加し、ColumnProvider クラスを作る。
このクラスには IColumnProvider を実装するので、
using LoaferShellEx.Interop で空間をインポートしておく。

「public class ColumnProvider」 の後に、
「: IColumnProvider」を追加して、インタフェース実装宣言をする。

この際、コピーなどで入力せずに、Ctrl+Space の入力支援機能を使い、
IColumnProvider を一覧から選択すれば、
確定後に Tab を押せば自動的にコードを挿入してくれる。

ここまでの作業で、以下のようになっているはずだ。
(コメント等は除いている)

    using System;
    using LoaferShellEx.Interop;

    namespace LoaferShellEx.Column {

        public class ColumnProvider : IColumnProvider {

            public ColumnProvider() {
            }

            public void Initialize(ref SHCOLUMNINIT psci) {
            }

            public void GetColumnInfo(
                    uint dwIndex, out SHCOLUMNINFO psci) {
                psci = new SHCOLUMNINFO ();
            }

            public void GetItemData(
                    ref SHCOLUMNID pscid, ref SHCOLUMNDATA pscd,
                    out object pvarData) {
                pvarData = null;
            }

        }

    }

さて、各メソッドを実装していこう。

まず、COM オブジェクトとして公開する際の制約として、
引数なしのコンストラクタが必要である。

別に引数のあるコンストラクタを用意してもいいが、
そのコンストラクタは COM から呼ばれることはない。

COM では、オブジェクトの生成と初期化は別であり、
初期化は、オブジェクトの実装している
特定のメソッドを呼ぶことで行われる。

逆に言えば、必ず初期化用のメソッドが呼ばれるので、
コンストラクタでは、特に処理は必要ない。
そのため、コンストラクタは空のままにしておく。

ここからは、IColumnProvider のプロトコルを調べながらやる。

Initialize はフォルダを表示する際に呼び出される。
表示されるフォルダが変更された場合も呼び出され、
引数の psci.wszFolder にフォルダのパスが格納されている。

「検索結果」など、特定のフォルダではない場合、
つまり、ディレクトリではない場合は空文字列が入っている。

このメソッドは、事前にディレクトリ内を検索する、
フォルダによって有効無効を切り替える、
キャッシュのヒントとして利用などの使い方があるが、
今回は特に利用しないので空のままにしておこう。

そして、GetColumnInfo メソッドは、
カラムの情報を問い合わせる際に何度も呼び出される。
メソッド内では、引数にカラムの情報を設定して返す。

IColumnProvider が複数のカラムを提供できるように
GetColumnInfo は繰り返し呼び出される。
まず引数の dwIndex が 0 で呼び出され、
メソッドが psci にカラムの情報を入れて戻す。

そうすると次に dwIndex が 1 で呼び出されるので、
また別のカラムの情報を入れて戻す。
これを繰り返し、これ以上カラムがない場合は、
戻り値を使ってそれを示す。

ここで、戻り値が問題となる。
COM では、メソッドは HRESULT という定数値を返す。
GetColumnInfo は、成功した際に S_OK を、
カラムが存在しない場合 S_FALSE を返す定義である。

戻り値は、.NET においては例外に変換されており、
メソッドが例外を発生させなければ、
自動的に S_OK が返却させる仕組みだ。

今回は、S_FALSE を返却したいので、
それに対応した例外を発生させればいいのだが、
S_XXX 定数は、メソッドの成功を意味するため、
.NET に対応する例外クラスが用意されていない。

そこで、S_FALSE に対応した例外を
自分で定義することにしよう。

Interop フォルダ内に、
ComFalseException クラスを追加する。

    using System;

    namespace LoaferShellEx.Interop {

        public class ComFalseException : SystemException {

            public const int S_FALSE = 0x00000001;

            public ComFalseException() {
                HResult = S_FALSE;
            }

        }

    }

S_FALSE は、winerror.h に定義されている。
COM 例外は SystemException から派生させよう。

また、例外クラスの基底である Exception には、
HResult プロパティが定義されており、
継承クラスでこの値を上書きしておけば、
COM ⇔ .NET 間の変換に使われるのだ。

では、GetColumnInfo を実装する。

    // カラムセットの ID
    private static readonly Guid FORMAT_ID
            = new Guid("09f98e7c-f7ef-4e5f-b84f-fa5bf75aad07");

    // カラムの ID
    private const uint PROPERTY_ID = 12345;

    // カラムの情報の列挙
    public void GetColumnInfo(uint dwIndex, out SHCOLUMNINFO psci) {

        // 範囲外なら、S_FALSE を返す
        if (dwIndex != 0) {
            throw new ComFalseException();
        }

        // カラムの情報を設定する
        psci.scid.fmtid = FORMAT_ID;
        psci.scid.pid = PROPERTY_ID;
        psci.vt = VARTYPE.BSTR;
        psci.fmt = LVCFMT.LEFT;
        psci.cChars = 5;
        psci.csFlags = SHCOLSTATE.TYPE_STR;
        psci.wszTitle = "テストカラム";
        psci.wszDescription = "カラムのテストです";

    }

テストのためにカラムは 1 つだけとしよう。
dwIndex が 0 でなければ ComFalseException を返す。
0 ならば構造体の中身を埋める。

psci.scid は、カラムの識別子であり、
任意の Guid と uint の組み合わせだ。
今回は適当な Guid と uint 値を用意し、
FORMAT_ID、PROPERTY_ID として定義しておいて使う。

カラムは、文字列(VARTYPE.BSTR)、
左寄せ表示(LVCFMT.LEFT)、標準の幅 5 文字、
ソート基準は文字列(SHCOLSTATE.TYPE_STR)、
カラム名が「テストカラム」、
そして、カラムの説明が「カラムのテストです」だ。

最後に、GetItemData メソッド。
これは、ファイルの情報を表示する際に、
ファイル毎に、そしてカラム毎に呼ばれる。

引数の pscid はカラムの識別子だ。
カラムの識別子が自分のカラムであることを確認し、
カラムの値として「テスト値」を返却することにしよう。

このメソッドは、対応する値がなければ S_FALSE を返す。
対応するカラムがない場合の挙動は書いていないが、
S_FALSE を返しておくか。

    // ファイルに対するカラムの値の問い合わせ
    public void GetItemData(
            ref SHCOLUMNID pscid, ref SHCOLUMNDATA pscd,
            out object pvarData) {

        // 識別子を調べる
        if (pscid.fmtid != FORMAT_ID || pscid.pid != PROPERTY_ID) {
            throw new ComFalseException();
        }

        // 値の設定
        pvarData = "テスト値";
    }

テスト用の実装はこれでよしだ。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.