Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2014年01月10日] [更新 2019年10月26日] Delphi 一般・その他
   
Delphi のシェルコンポーネント
動作確認等 Delphi 2006 以降
サンプルのコードの動作確認は Windows 7 U64(SP1) + Delphi XE(UP1) Pro
[整理番号 - 0500]



  • 2019年10月24日 (これより前の履歴は省略)
  • Delphi XE7 以降の説明を [備考] ではなく別項目にして,ページのタイトルも変更
  • 2019年10月26日
  • [Delphi の [ source ] フォルダ内のファイルを使用する方法] を追加





Delphi のシェルコンポーネント


シェルコンポーネントは,下の図のように,Windows のシェルの機能を使用して,Delphi で,エクスプローラのような機能を実装するためのコンポーネント群です.次のようなコンポーネントで構成されていす.

  • TShellTreeView
  • TShellListView
  • TShellComboBox
  • TShellChangeNotifier

シエルコンポーネントは,Delphi 2005 まではインストール済みのパッケージとして提供されていました.Delphi 2006 からは,パッケージは提供されていますが,そのパッケージはインストールされていません.Delphi XE7 ではパッケージが提供されなくなりました.


図1
Delphi のシェルコンポーネント

このようなシェル関係のコンポーネントは,サードパーティからも提供されています.ネットで検索してみてください.





01_Delphi のシェルコンポーネントがある場所  -  Delphi XE6 以前


Delphi 2006 以降では,シェルコンポーネントは,[Samples] または [Demo], Delphi のバージョンよっては [サンプル] という名前のフォルダに格納されています.
このフォルダを開くには,下図のように,[スタート] メニューから該当する Delphi のバージョンのフォルダを開きます.

そのフォルダの階層下 (階層直下とは限らない) に [VCL] [ShellControls] があります.このフォルダにシェルコンポーネント関係のファイルがあります.



図2
ディスク上のサンプルコードの場所
  • [Samples], [Demo], [サンプル] 等,Delphi のバージョンによってフォルダ名が異なるので注意
  • フォルダの場所は [リンク先] で確認できる




02_Delphi XE7 以降で Delphi のシェルコンポーネントを使用する


Delphi XE7 では,シェルコンポーネントのパッケージは添付されなくなりました.Delphi XE6 以前の Delphi を所有している場合は,そのシェルコンポーネントのフォルダを適当な場所にコピーしてインストールできます.

Delphi XE6 以前を所有していない場合は,以下の最初のリンクから,Delphi XE6 のサンプルソースコードの [ShellControls] フォルダの分だけがダウンロードできます.[Download Snapshot] をクリックするとダウンロードできます.
ダウンロードしたら [ブロックの解除] をします.解凍したら,フォルダ名は [ShellControls] 等にしておいた方がいいでしょう.シェルコンポーネントのファイル類があるフォルダを適当な場所にコピーまたは移動してインストールに使用します.






03_Delphi の [ source ] フォルダ内のユニットを使用する方法
ShellControls_PKG_forXE7orLater.zip [1 KB] 2019年10月26日版
シェルコンポーネントをインストールするためのパッケージファイル類


Delphi XE7 では,シェルコンポーネントのパッケージは添付されなくなりましたが,シェルコンポーネントのユニットファイル Vcl.Shell.ShellConsts.pas と Vcl.Shell.ShellCtrls.pas が [source] ファルダ内にあります.また,コンポーネントのアイコンのリソースファイルも提供されています.

上のリンクからダウンロードしたファイルに [ShellControls] というフォルダがあります.このフォルダを適当な場所にコピーまたは移動します.このフォルダの中に,[source] フォルダ内のユニットファイルを使用してインストールするためのパッケージファイルがあります.

Delphi の [source] フォルダにはライブラリのパスは通っていません.したがつて,実際に使用されるのはコンパイル済みのユニットファイルです.それらも提供されています.

この方法でインストールしたシェルコンポーネントでは,プロパティエディタは使用できません.





04_Delphi のシェルコンポーネントのインストール


パッケージとしてインストールします.他のパッケージやコンポーネントのインストールと同様に,管理者権限で Delphi の IDE を起動してインストールします.

シェルコンポーネントのフォルダ内のパッケージファイル dclshlctrls.dpk を Delphi の IDE で開いてインストールします.必要であればこのフォルダにライブラリパスを通しておきます.
インストールしたコンポーネントは,コンポーネントパレットの [Samples] タブに追加されます.



図3
パッケージ名
  • dclshlctrls.dpk がシェルコンポーネントのパッケージ
    これをインストールして使用する
  • 先頭が vcl のパッケージファイルもあるが,これはインストール用ではない
  • この図は Delphi XE のフォルダ構成

パッケージのオプションで,下図のように [説明] のテキストを自分用に変更しておくと,パッケージの一覧から探す時に便利です.インストール済みのパッケージを変更しても反映されません.一度アンインストールしてから変更します.変更したらまたインストールします.この時,Delphi の IDE を閉じる必要はありません.

これは,[コンポーネント] [パッケージのインストール] で表示されるダイアログの,パッケージ一覧におけるパッケージの表示名となります.



図4
パッケージの説明




05_Delphi のシェルコンポーネントの不具合


Delphi 付属のシェルコンポーネントの TShellListView は,動作に不具合があるようです.
現在のところ,Delphi 7, 2007, XE, XE5 で,以下の現象を確認しています.Delphi の他のバージョンではテストしていません.
  • (1) ダブルクリックで,拡張子に関連付けしたアプリでファイルを表示できない場合がある
    この時,関連付けされているアプリは,非表示の状態で起動はしている
    (タスクマネージャーで確認可能)
  • (2) コンテキストメニューの [送る] で,複数のファイルを選択しても 1 つしか送れない
  • (3) TShellListView で,ファイルが未選択状態の場合,コンテキストメニューが表示されない
    (エクスプローラの場合と違うという意味で)

(1) については,ShellCtrls.pas があるフォルダにパスを通して,DblClick メソッドのコードを次のように修正すると回避できます.ExecuteDefault は,ファイルが選択されている時に [Enter] キーを押下した時と同じ操作を実行するものです.DblClick メソッド内での ShellExecute 関数の挙動については調査していません.



リスト1
procedure TCustomShellListView.DblClick;
begin
  if FAutoNavigate and (Selected <> nil) then
    with Folders[Selected.Index] do
      if IsFolder then
        SetPathFromID(AbsoluteID)
      else
        ExecuteDefault;                                      //追加
//        ShellExecute(Handle, nil, PChar(PathName), nil,    //削除
//          PChar(ExtractFilePath(PathName)), 0);            //削除
  inherited DblClick;
end;


with 文は便利なのですが,筆者の場合,どこに係っているか判断に迷うことがあります.そこで以下のように変更しました.上のコードの PathName は,LFolder.PathName のことになります.
この値が,選択されているファイルまたはフォルダの実フルパス名で,LFolder.AbsoluteID がその項目識別子 (PItemIDList) です.この場合は,絶対パス名に相当する完全項目識別子です.


リスト2
procedure TCustomShellListView.DblClick;
var
  LFolder : TShellFolder;
begin
  if FAutoNavigate and (Selected <> nil) then begin
    LFolder := Folders[Selected.Index];
    if LFolder.IsFolder then begin
      SetPathFromID(LFolder.AbsoluteID);
    end else begin
      LFolder.ExecuteDefault;
    end;
  end;

  //inherited DblClickはOnDblClickイベントを実行するだけ
  inherited DblClick;
end;


(2) と (3) については,ネット上で回避策のコードを見た記憶がありますが,URL は失念しました.
手前味噌ですが,拙作のコンテキストメニュー コンポーネントを使用することでも回避可能です.
ただし,Delphi 付属のシェルコンポーネントで,拙作のコンテキストメニューコンポーネントを利用する場合は,若干の注意が必要です.詳しくはコンポーネントの記事で確認してください.






06_TShellTreeView  -  デスクトップの選択フォルダで例外発生


Delphi 付属のシェルコンポーネントでデスクトップを表示したら,TShellTreeView の項目を選択します.この時,TShellListVeiw の項目を選択しない状態で,次のコードを実行します.このコードは選択中のフォルダ名を取得するものです.
すると,アプリの終了時に例外が発生して,下図のようなダイアログが現れます.
現在のところ,対処方法については調査中です.


図5
終了時に例外が発生する

リスト3
//=============================================================================
//  デスクトップを表示している状態で,TShellListViewの項目が未選択の場合
//  以下のコードを実行すると,終了時に例外が発生
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPath : String;
begin
  if ShellTreeView1.Selected <> nil then begin
    LPath := ShellTreeView1.Path;
    Memo1.Lines.Add(LPath);
  end;
end;




07_AutoRefresh 有効時のメモリリーク等


AutoRefresh プロパティを True にした状態で使用すると,メモリリークが発生するようです.
対処方法が下記の記事にあります.

AutoRefresh を True にしていると,ルートフォルダを表示する直前に表示したフォルダを,フォルダ削除の関数を使用して削除した場合,表示が更新されない現象が報告されています.この現象が発生すると,Refresh メソッドを実行しても表示は更新されません.
コンテキストメニューから削除を実行した場合は,この現象は発生しません.

AutoRefresh はある意味便利ですが,シェルコンポーネントで操作した内容は Refresh メソッドで表示の更新が可能です.他のアプリで操作した結果を表示に反映するには,例えば表示中のフォルダの変更を監視し,変更があったら Refresh メソッドを実行するという方法も考えられます.






08_TShellListView  -  指定した拡張子のファイルだけを表示


Delphi 付属のシェルコンポーネントの TShellListView で,指定した拡張子のファイルだけを表示するには,次のように,OnAddFolder イベントを処理することで可能です.


リスト4
//=============================================================================
//  TShellListViewのOnAddFolderイベント処理
//  このイベントは,現在のフォルダ内のファイル数だけ実行される
//  指定した拡張子のファイル名だけをリストする例
//
//  イベントの引数CanAddの値をFalseにすると非表示となる.デフォルトはTrue
//  MatchTextの使用にはusesにStrUtilsが必要
//=============================================================================
procedure TForm1.ShellListView1AddFolder(Sender: TObject; AFolder: TShellFolder;
  var CanAdd: Boolean);
var
  Ext : String;
begin
  //フォルダは表示
  if AFolder.IsFolder then exit;

  //拡張子が.jpgと.bmpのファイルのみ表示
  Ext    := ExtractFileExt(AFolder.PathName);
  CanAdd := MatchText(Ext, ['.jpg', '.bmp']);
end;




09_TShellListView  -  選択中のファイル名を取得


Delphi 付属のシェルコンポーネントの TShellListView で,選択中のフォルダ名とファイル名を取得する例です.次のコードは,Memo1 に, 選択中のフォルダ名とファイル名をリストします.


リスト5
//=============================================================================
//  現在選択中のフォルダとファイル名を取得する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LItem   : TListItem;
  LFolder : TShellFolder;
begin
  Memo1.Clear;

  LItem := ShellListView1.Selected;
  while LItem <> nil do begin
    LFolder := ShellListView1.Folders[LItem.Index];
    Memo1.Lines.Add(LFolder.PathName);
    LItem := ShellListView1.GetNextItem(LItem, sdAll, [isSelected]);
  end;
end;


上のコードでは,仮想フォルダの [ライブラリ] は,'ライブラリ' という名前を取得します.
これらの仮想フォルダの GUID 文字列を取得するには,以下のように,項目識別子から取得します.
GUID 文字列であれば,他のシェル関係の関数の引数として利用できます.


リスト6
//-----------------------------------------------------------------------------
//  項目識別子(完全項目識別子)からフルパスを取得する関数
//  この関数を使用するにはusesにShlObjが必要
//-----------------------------------------------------------------------------
function GetFullPathFromABSPItemID(pidlFull: PItemIDList): String;
var
  pShFolder : IShellFolder;
  pidl      : PItemIDList;
  StrRet    : TStrRet;
begin
  Result := '';

  //該当ファイルのフォルダオブジェクト(pShFolder)と,相対項目識別子をpidlに取得
  SHBindToParent(pidlFull, IShellFolder, Pointer(pShFolder), pidl);
  FillChar(StrRet, SizeOf(StrRet), 0);
  if pShFolder.GetDisplayNameOf(pidl, SHGDN_FORPARSING, StrRet) = S_OK then begin
    Result := StrRet.pOleStr;
  end;
end;

//=============================================================================
//  現在選択中のフォルダとファイル名を実パス名で取得する例
//  仮想フォルダ等はGUID文字列となる
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LItem     : TListItem;
  LFolder   : TShellFolder;
  LFullPath : String;
begin
  Memo1.Clear;

  LItem := ShellListView1.Selected;
  while LItem <> nil do begin
    LFolder   := ShellListView1.Folders[LItem.Index];
    LFullPath := GetFullPathFromABSPItemID(LFolder.AbsoluteID);
    Memo1.Lines.Add(LFullPath);
    LItem := ShellListView1.GetNextItem(LItem, sdAll, [isSelected]);
  end;
end;




10_仮想フォルダをルートフォルダにする例


上のコードで取得した GUID 文字列を,ルートフォルダに指定することができます.
次の Button2Click と Button3Click のコードは [ライブラリ] をルートフォルダにする例です.


リスト7
//=============================================================================
//  ルートフォルダを[Dドライブ]にする例
//  TShellTreeViewを使用していない場合は,そのフォルダ内のファイルをリスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ShellListView1.Root := 'D:\';
end;

//=============================================================================
//  ルートフォルダを仮想フォルダの[ライブラリ]にする例
//  TShellTreeViewを使用していない場合は,そのフォルダ内のファイルをリスト
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  ShellListView1.Root := '::{031E4825-7B94-4DC3-B131-E946B44C8DD5}';
end;

//=============================================================================
//  ルートフォルダを仮想フォルダの[ライブラリ]にする例
//  TShellTreeViewを使用していない場合は,そのフォルダ内のファイルをリスト
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
begin
  ShellListView1.Root := 'shell:UsersLibrariesFolder';
end;




11_TShellListView の表示形式変更


TShellListView では表示形式を [タイル] にできません.
次のコードは,LVM_SETVIEW メッセージを使用して TShellListView の表示形式を [詳細] または [タイル] に変更します.LVM_XXX のメッセージの値は CommCtrl.pas で定義されています.


リスト8
//=============================================================================
//  Delphiに付属のTShellListViewで表示形式を[タイル]にするコード
//  [詳細]と[タイル]のトグル
//  場合によってはTShellListView.Refreshが必要かも知れない
//=============================================================================
{$WRITEABLECONST ON}
procedure TForm1.Button2Click(Sender: TObject);
const
  ID_Value : Integer = 0;
var
  LiView : Cardinal;
begin
  if ID_Value = 0 then begin
    LiView := LV_VIEW_TILE;
    ID_Value := 1;
  end else begin
    LiView := LV_VIEW_DETAILS;
    ID_Value := 0;
  end;
  SendMessage(ShellListView1.Handle, LVM_SETVIEW, WPARAM(LiView), 0);
end;
{$WRITEABLECONST OFF}