Delphi Programming / Object Pascal

[掲載 2014年01月10日] [更新 2019年10月26日] Delphi 一般・その他 |
| Delphi のシェルコンポーネント |
| 動作確認等 |
Delphi 2006 以降
サンプルのコードの動作確認は Windows 7 U64(SP1) + Delphi XE(UP1) Pro |
- 2019年10月24日 (これより前の履歴は省略)
- Delphi XE7 以降の説明を [備考] ではなく別項目にして、ページのタイトルも変更
|
- 2019年10月26日
- [Delphi の [ source ] フォルダ内のファイルを使用する方法] を追加
|
| シェルコンポーネントは、下の図のように、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 関数の挙動については調査していません。 |
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) です。この場合は、絶対パス名に相当する完全項目識別子です。 |
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
終了時に例外が発生する |
//=============================================================================
// デスクトップを表示している状態で、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 イベントを処理することで可能です。 |
//=============================================================================
// 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 に、
選択中のフォルダ名とファイル名をリストします。 |
//=============================================================================
// 現在選択中のフォルダとファイル名を取得する例
//=============================================================================
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 文字列であれば、他のシェル関係の関数の引数として利用できます。 |
//-----------------------------------------------------------------------------
// 項目識別子(完全項目識別子)からフルパスを取得する関数
// この関数を使用するには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;
上のコードで取得した GUID 文字列を、ルートフォルダに指定することができます。
次の Button2Click と Button3Click のコードは [ライブラリ] をルートフォルダにする例です。 |
//=============================================================================
// ルートフォルダを[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 で定義されています。 |
//=============================================================================
// 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}
|