仮想フォルダを実装するために最低限必要となるインターフェースは、IShellFolderとIPersistFolderです。IPersistFolderは仮想フォルダにPIDLを設定する場合に使用されます。今回は、これらを継承したCShellFolderというクラスを定義することにします。IPersistFolderではなくIPersistFolder2を継承していますが、これは問題ありません。理由は、上位(数字が2)のインターフェースが下位のインターフェースを継承しているからです。デフォルトのフォルダビューを使用する場合は上位のインターフェースも実装することになります。それでは、IShellFolderのメソッドから順に見ていくことにします。EnumObjectsとGetUIObjectOfについては、後の節で取り上げます。今回のサンプルでは呼び出しを確認することができませんでした。よって、実装しないことを示すE_NOTIMPLを返しています。hr;}BindToObjectは、主に表示されたフォルダを切り替える際に呼ばれます。要求されたインターフェースを実装しているかを確認するためにQueryInterfaceを呼び出します。Releaseの実行によってオブジェクトは開放されることになります。成功した場合は、新しいオブジェクトにPIDLを設定するためにInitializeを呼び出すことになります。ここで指定するPIDLは、現在のフォルダのPIDL(m_pidl)とバインド先の新しいフォルダのPIDLを連結したものでなければなりません。新しく作成されたフォルダのm_levelは現在の階層より1つ大きくなっていなければなりません。自作メソッドであるSetLevelを呼び出すと、第1引数の値がm_levelに設定されます。オブジェクトは破棄されることになります。ppvOut);}BindToStorageは、BindToObjectを呼び出すだけで問題ありません。このメソッドが呼ばれることはないと思われます。nCode);}CompareIDsは、2つのPIDLを比較する際に呼ばれます。この中の「昇順」や「降順」を選択するとCompareIDsが呼ばれます。この調べ方についてはPIDLの中身によって異なるでしょう。この構造体のszNameにはアイテムの名前が格納されています。CompareIDsの望む形で戻り値を得ることができます。hr;}CreateViewObjectは、フォルダビューを作成する際に呼ばれます。デフォルトのフォルダビューを使用する場合は、SHCreateShellFolderViewを呼び出すだけで構いません。S_OK;}GetAttributesOfは、アイテムの属性を取得する際に呼ばれます。メソッド内ではこの属性をアイテムがサポートしているかどうかを返す必要があります。これとrgfInOutの論理積をとります。S_OK;}GetDisplayNameOfは、アイテムに関連する名前を取得する際に呼ばれます。STRRET_WSTRを指定した場合はpOleStrメンバにUNICODE文字列へのポインタを格納することになります。そのメモリへのアドレスが第2引数に格納されます。今回のサンプルでは呼び出しを確認することができませんでした。よって、実装しないことを示すE_NOTIMPLを返しています。続いて、IShellFolder2のメソッドを順に見ていきます。E_NOINTERFACE;}GetDefaultSearchGUIDとEnumSearchesは、呼ばれることがないようなのでE_NOTIMPLを返すだけで構いません。S_OK;}GetDefaultColumnは、デフォルト表示のカラムのインデックスと、ソート時に参照するカラムのインデックスを取得する際に呼ばれます。今回はカラムを1つしか追加しないため、どちらの変数にも0を指定することになります。ちなみに、ソート時に参照するカラムというのは、既に示したCompareIDsの第1引数に大きく関係します。CompareIDsは名前ではなくサイズを基にアイテムの比較を行うことになります。複数のカラムが存在する場合は、その数だけ比較のアルゴリズムが必要になります。E_INVALIDARG;}GetDefaultColumnStateは、第1引数のカラムのデフォルト状態を取得する際に呼ばれます。インデックスが0であるカラムの場合のみ処理を行います。SHCOLSTATE_TYPE_STRはカラムに設定される値が文字列であることを意味します。E_NOTIMPL;}GetDetailsExは、第1引数のアイテムから第2引数のカラムに相当するデータを取得する際に呼ばれます。GetDetailsOfを実装している場合は、特に実装する必要がないためE_NOTIMPLを返すようにしています。hr;}GetDetailsOfは、第1引数のアイテムから第2引数のカラムに相当するデータを取得する際に呼ばれます。このメソッドはカラムをインデックスで識別するので比較的分かりやすいといえます。カラムのフォーマットを設定してバッファに名前をコピーします。カラムを複数個追加したい場合は、iColumnが1以上であるときにも同じような処理を行うことになります。pidlがNULLでない場合は、カラムに関連するアイテムのデータを返すことになります。ITEMDATA構造体のszNameメンバをバッファにコピーしています。SHELLDETAILS構造体のstr.pOleStrがそのアドレスを受け取ることができます。E_NOTIMPL;}MapColumnToSCIDは、第1引数のインデックスで識別されるカラムから関連するキーを取得する際に呼ばれます。GetDetailsExを実装していない場合は、E_NOTIMPLを返すだけで構いません。CShellFolderが継承しているIPersistFolder2は、内部的にIPersistとIPersistFolderを継承しています。よって、これら3つのインターフェースのメソッドを実装する必要があります。S_OK;}GetClassIDはIPersistのメソッドであり、CLSIDを返すだけで問題ありません。InitializeはIPersistFolderのメソッドであり、このフォルダのPIDLが与えられることになります。このPIDLは後で使用することになるため、メンバ変数であるm_pidlに保存しておきます。pidlはメソッドが制御を返した後に開放される可能性があるので、保存の際はILCloneで複製するようにします。GetCurFolderはIPersistFolder2のメソッドであり、このフォルダのPIDLを返すことになります。これは、保存しておいたm_pidlを複製して格納すれば問題ありません。