Delphi Programming / Object Pascal
[掲載 2010年12月11日] [更新 2014年04月07日] Delphi サンプルプログラム集
819_シェル名前空間とフォルダビュー ( ファイルリスト)
動作確認等
Windows 7 U64(SP1) + Delphi XE Pro
Windows XP 以降,Delphi 2010 以降専用
819_ShellNameSpace_FileList.zip [1,772 KB] 2014年04月07日版 (EXE 同梱)
[818_項目識別子(シェル名前空間)] で説明した,ウィンドウズのシェル機能を使用した,フォルダビュー,つまり指定フォルダ内のファイルリストを,TListView に表示するサンプルです.
このサンプルでは,Windows XP 以降,Delphi 2010 以降で使用可能となったシェル関係の関数類を使用しています.Delphi は Windows 3.1 の時代に登場しました.その後,Windows 2000 までの時代までに,多くの方が貴重なサンプルを提供してきました.その頃は,このサンプルで使用しているような便利な関数類がありませんでした.他の Windows API 関数類を使用して,同様な機能の関数を作成していました.
新しい API 関数類を使用すると,古いバージョンの Windows や Delphi では修正しないと利用できないサンプルコードとなってしまいますが,これも時代の 1 つの流れでしょう.
これらの Delphi 2010 以降で使用可能になったシェル関数類は,IL から始まっています.これらの関数類は,フォルダやファイル名に使用できない文字が含まれていると,取得に失敗するか,関数の実行が終了しないで無限ループになってしまうことがあるようです.
例えば,IE のインターネットキャッシャ等もそれに該当します.キャッシュのファイル名には ? が含まれていることがあります.この ? はファイル名には使用できません.そこで,本サンプルでは,このような場合,ファイルの取得をしないようにしてしまっています.
基本的な機能だけを実装したサンプルです.下表は,コードで使用しているクラス内のグローバル変数のリストです.参考として,ListView の Item からファイルシステムのフルパスを取得する関数を追加しておきました.
実用的にするには,仮想リストビュー,あるいはスレッドを使用する等,表示速度の改善などが必要でしょう.特にリンク切れのファイル等は,検出に時間がかかり,表示が非常に遅くなります.
変数名
型
内容
FImageHandle
THandle
イメージリストのハンドル
FshFolderDesktop
IShellFolder
デスクトップのフォルダオブジェクト
FpidlDesktop
PItemIDList
デスクトップの項目識別子
FshFolderExpand
IShellFolder
表示中のフォルダのフォルダオブジェクト
FpidlExpand
PItemIDList
表示中のフォルダの項目識別子 (完全 PIDL)
FBaseFolder
String
テスト用の選択フォルダ
図1
設計時画面
図2
デスクトップ
図3
プリンタのフォルダ内を表示
図4
タイル表示
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, ShellAPI, ComObj, ActiveX, ExtCtrls, ShlObj,
CommCtrl;
type
TForm1 = class (TForm)
Panel2: TPanel;
Panel3: TPanel;
Panel1: TPanel;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button10: TButton;
Button11: TButton;
Button12: TButton;
Button13: TButton;
Edit1: TEdit;
ListView1: TListView;
procedure FormShow(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button10Click(Sender: TObject);
procedure Button11Click(Sender: TObject);
procedure Button12Click(Sender: TObject);
procedure Button13Click(Sender: TObject);
procedure ListView1Deletion(Sender: TObject; Item: TListItem);
procedure ListView1Edited(Sender: TObject; Item: TListItem; var S: string );
procedure ListView1DblClick(Sender: TObject);
procedure ListView1AdvancedCustomDrawItem(Sender: TCustomListView;
Item: TListItem; State: TCustomDrawState; Stage: TCustomDrawStage;
var DefaultDraw: Boolean);
procedure ListView1ColumnClick(Sender: TObject; Column: TListColumn);
procedure ListView1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
private
{ Private 宣言 }
FImageHandle : THandle;
FshFolderDesktop : IShellFolder;
FshFolderExpand : IShellFolder;
FpidlDesktop : PItemIDList;
FpidlExpand : PItemIDList;
FBaseFolder : String ;
function SetFilesListFromPItemIDList(ItemID: PItemIDList): Boolean;
procedure ResetListViewColumn(pShellFolder: IShellFolder);
function SetListViewItemSubItem(pShellFolder: IShellFolder;
pidlChild: PItemIDList): Boolean;
public
{ Public 宣言 }
procedure ChangeViewStyle(iView: Integer);
function GetFullPathFromListItem(Item: TListItem): String ;
function GetFullPathFromListItemEx(Item: TListItem): String ;
end ;
var
Form1: TForm1;
implementation
uses FileCtrl, CSIDL_ListFormUnit;
{$R *.dfm}
const
PID_STG_CLASSID = 3 ;
PID_STG_STORAGETYPE = 4 ;
PID_STG_VOLUME_ID = 5 ;
PID_STG_PARENT_WORKID = 6 ;
PID_STG_SECONDARYSTORE = 7 ;
PID_STG_FILEINDEX = 8 ;
PID_STG_LASTCHANGEUSN = 9 ;
PID_STG_NAME = $0A ;
PID_STG_PATH = $0B ;
PID_STG_SIZE = $0C ;
PID_STG_ATTRIBUTES = $0D ;
PID_STG_WRITETIME = $0E ;
PID_STG_CREATETIME = $0F ;
PID_STG_ACCESSTIME = $10 ;
PID_STG_CHANGETIME = $11 ;
PID_STG_ALLOCSIZE = $12 ;
PID_STG_CONTENTS = $13 ;
PID_STG_SHORTNAME = $14 ;
var
g_shFolderExpand : IShellFolder; // 展開中のフォルダオブジェクト
g_SortColum : array [0 ..3 ] of Integer; // ソートのカラム番号用配列
g_SortColumnIndex : Integer; // ソートカラム番号
// -----------------------------------------------------------------------------
// フルパス相当の項目識別子(完全PIDL)からフルパス名を取得する関数
// 仮想フォルダは空文字出力
// -----------------------------------------------------------------------------
function GetFullPathFromABSPItemID(pidl: PItemIDList): String ;
var
Buff : array [0 ..MAX_PATH - 1 ] of Char;
begin
FillChar(Buff, SizeOf(Buff), 0 );
SHGetPathFromIDList(pidl, Buff);
Result := Buff;
end ;
// -----------------------------------------------------------------------------
// ソートのコールバック関数
// 項目識別子でソートする
// 実ファイル名でソートしたのですエクスプローラと同じ表示順とならない
// -----------------------------------------------------------------------------
function SortListViewData(Item1, Item2: TListItem;
ParamSort: integer): Integer; stdcall ;
begin
Result := g_SortColum[g_SortColumnIndex] *
SHORT($FFFF and g_shFolderExpand.CompareIDs(
g_SortColumnIndex,
Item1.Data,
Item2.Data));
end ;
// =============================================================================
// テスト用ボタン
// [デスクトップ]のファイルをリスト
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
SetFilesListFromPItemIDList(FpidlDesktop);
end ;
// =============================================================================
// テスト用ボタン
// [特殊フォルダ]のファイルをリスト
// CSIDLの一覧フォームを表示して,そこから選択する
// =============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
AIndex : Integer;
CSIDL_Value : Integer;
pidl : PItemIDList;
begin
pidl := nil ;
// CSIDL値リストを表示するフォームを作成
CSIDL_ListForm := TCSIDL_ListForm.Create(Self);
try
if CSIDL_ListForm.ShowModal = mrOk then begin
Update;
AIndex := CSIDL_ListForm.ListBox1.ItemIndex;
CSIDL_Value := Integer(CSIDL_ListForm.ListBox1.Items.Objects[AIndex]);
// CSIDL_XXXの値から項目識別子を取得
SHGetSpecialFolderLocation(0 , CSIDL_Value, pidl);
if pidl <> nil then begin
SetFilesListFromPItemIDList(pidl);
CoTaskMemFree(pidl);
end else begin
MessageBox(0 , '取得できませんでした.' , '結果' , MB_OK);
end ;
end ;
finally
FreeAndNil(CSIDL_ListForm);
end ;
end ;
// =============================================================================
// テスト用ボタン
// [一般フォルダ]のファイルリスト
// フォルダの選択ダイアログを表示して選択する
// =============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
pidl : PItemIDList;
begin
pidl := nil ;
if FBaseFolder = '' then FBaseFolder := ExtractFileDir(ParamStr(0 ));
if SelectDirectory('フォルダ指定' , '' , FBaseFolder) then begin
Update;
// ファイル名を項目識別子に変換
pidl := ILCreateFromPath(PChar(FBaseFolder));
if (pidl = nil ) or (FBaseFolder = '' ) then begin
MessageBox(0 , '取得できませんでした.' , '結果' , MB_OK);
end else begin
SetFilesListFromPItemIDList(pidl);
end ;
end ;
if pidl <> nil then CoTaskMemFree(pidl);
end ;
// =============================================================================
// テスト用ボタン
// [アイコン]表示
// =============================================================================
procedure TForm1.Button10Click(Sender: TObject);
begin
ChangeViewStyle(LV_VIEW_ICON);
end ;
// =============================================================================
// テスト用ボタン
// [リスト]表示 [並べて表示]に相当
// =============================================================================
procedure TForm1.Button11Click(Sender: TObject);
begin
ChangeViewStyle(LV_VIEW_LIST);
end ;
// =============================================================================
// テスト用ボタン
// [タイル] [一覧]表示に相当
// =============================================================================
procedure TForm1.Button12Click(Sender: TObject);
begin
ChangeViewStyle(LV_VIEW_TILE);
end ;
// =============================================================================
// テスト用ボタン
// [詳細表示]
// =============================================================================
procedure TForm1.Button13Click(Sender: TObject);
begin
ChangeViewStyle(LV_VIEW_DETAILS);
end ;
// =============================================================================
// テスト用ボタン
// [戻る]
// FpidlExpandが新しいフォルダの項目識別子となる
// 最後はデスクトップになる
// 必要であれば,そこから上の階層には移動できないように,特定のフォルダを指定
// することができる.
// =============================================================================
procedure TForm1.Button4Click(Sender: TObject);
begin
ILRemoveLastID(FpidlExpand);
SetFilesListFromPItemIDList(FpidlExpand);
end ;
// -------------------- 以下,シェルによるファイルリスト本体 ------------------
// =============================================================================
// フォーム生成時の処理
// 各種初期化
// 項目識別子にはnilを代入しておくと,後処理がしやすい
// nilを代入しないと,不定値となっている
// =============================================================================
procedure TForm1.FormShow(Sender: TObject);
var
i : Integer;
FileInfo : TSHFileInfo;
begin
FpidlDesktop := nil ;
FpidlExpand := nil ;
// ソートで使用する配列値に代入
// ソート対象カラムの初期値は0(名前のカラム)
g_SortColumnIndex := 0 ;
for i := 0 to Length(g_SortColum) - 1 do g_SortColum[i] := 1 ;
ListView1.ViewStyle := vsReport;
ListView1.IconOptions.Arrangement := iaTop;
ListView1.IconOptions.AutoArrange := True;
ListView1.IconOptions.WrapText := True;
ListView1.DoubleBuffered := True;
// [デスクトップ]のシェルフォルダオブジェクトを取得
SHGetDesktopFolder(FshFolderDesktop);
// デスクトップの項目識別子を取得
SHGetSpecialFolderLocation(0 , CSIDL_DESKTOP, FpidlDesktop);
// イメージリストの取得と設定
FImageHandle := SHGetFileInfo(Pointer(FpidlDesktop),
0 ,
FileInfo,
SizeOf(FileInfo),
SHGFI_PIDL or
SHGFI_SYSICONINDEX or
SHGFI_SMALLICON);
ListView_SetImageList(ListView1.Handle, FImageHandle, LVSIL_SMALL);
end ;
// =============================================================================
// フォーム破棄時の処理
// 後処理
// =============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
if FpidlDesktop <> nil then CoTaskMemFree(FpidlDesktop);
if FpidlExpand <> nil then CoTaskMemFree(FpidlExpand);
FshFolderDesktop := nil ;
FshFolderExpand := nil ;
end ;
// =============================================================================
// 引数のフルパス相当の項目識別子のフォルダ以下のファイルリストを作成
//
// BindToObjectは対象がデスクトップの場合は失敗するので注意
// フォルダオブジェクトと対象の項目識別子(このコードではItemID)が同じフォルダ
// でも失敗する
//
// FshFolderDesktop デスクトップのシェル・フォルダオブジェクト
// FpidlDesktop デスクトップのフルパス相当項目識別子(完全PIDL)
// FshFolderExpand 展開フォルダのシェル・フォルダオブジェクト
// FpidlExpand 展開フォルダのフルパス相当項目識別子(完全PIDL)
// ListViewの各ItemのDataプロパティに,ファイルの相対項目識別子を代入
// =============================================================================
function TForm1.SetFilesListFromPItemIDList(ItemID: PItemIDList): Boolean;
var
Flags : Cardinal;
pidlChild : PItemIDList;
pEnumIdList : IEnumIDList;
Fetched : Cardinal;
begin
Result := False;
pidlChild := nil ;
// 対象となるフォルダの項目識別子とデスクトップの項目識別子を比較
// 同じなら対象フォルダは[デスクトップ]フォルダ
if FshFolderDesktop.CompareIDs(0 , ItemID, FpidlDesktop) = 0 then begin
FshFolderExpand := FshFolderDesktop;
FpidlExpand := ILClone(FpidlDesktop);
end else begin
// 対象の項目識別子を[デスクトップ]のシェルフォルダオブジェクトにバインド
// 対象のシェルフォルダオブジェクトが求まる
FshFolderDesktop.BindToObject(ItemID, nil , IShellFolder, Pointer(FshFolderExpand));
FpidlExpand := ILClone(ItemID);
end ;
// ソートで使用するフォルダオブジェクトに代入しておく
g_shFolderExpand := FshFolderExpand;
// このFlagsで検索フォルダの属性を指定できる
// pEnumIDListに情報が格納される
Flags := SHCONTF_INCLUDEHIDDEN or SHCONTF_NONFOLDERS or SHCONTF_FOLDERS;
Screen.Cursor := crHourGlass;
if (FshFolderExpand.EnumObjects(Handle, Flags, pEnumIDList)) <> S_OK then begin
Screen.Cursor := crDefault;
exit;
end ;
// ListViewのItemにファイル情報を追加していく
LockWindowUpdate(ListView1.Handle);
ListView1.Items.BeginUpdate;
ListView1.Items.Clear;
ResetListViewColumn(FshFolderExpand);
// 取得したpEnumIDListから1つづつファイル情報を取り出す
// ここで取得したpidlChildは解放しないこと
while (pEnumIDList.Next(1 , pidlChild, Fetched) = S_OK) do begin
SetListViewItemSubItem(FshFolderExpand, pidlChild);
end ;
if ListView1.Items.count > 1 then ListView1.CustomSort(@SortListViewData, 0 );
ListView1.Items.EndUpdate;
LockWindowUpdate(0 );
Screen.Cursor := crDefault;
Edit1.Text := GetFullPathFromABSPItemID(FpidlExpand);
Result := True;
end ;
// =============================================================================
// 表示するカラムの作成と設定
// シェルのファイルリストは,表示フォルダによって表示項目(カラム)が異なるため,
// 表示フォルダが変わる度に,一度カラムを全て削除して,新規作成する
//
// PKEY_Size(TPropertyKey型-PropKey.pas)等でもカラムの形式の判定ができる
// =============================================================================
procedure TForm1.ResetListViewColumn(pShellFolder: IShellFolder);
var
pShellFolder2 : IShellFolder2;
ColumnID : SHCOLUMNID;
StrRet : TStrRet;
i : Integer;
Column : TListColumn;
details : SHELLDETAILS;
begin
for i := ListView1.Columns.Count - 1 downto 0 do begin
ListView1.Columns.Delete(i);
end ;
if (pShellFolder.QueryInterface(IID_IShellFolder2, pShellFolder2)) <> S_OK then
exit;
for i := 0 to 3 do begin
if (pShellFolder2.GetDetailsOf(nil , i, details)) <> S_OK then break;
// 特定のカラムを非表示にするのであれば,先にカラムの種類を調べてからAddする
Column := ListView1.Columns.Add;
StrRet := details.str;
// カラムの情報をColumnIDに取得
pShellFolder2.MapColumnToSCID(i, ColumnID);
if ColumnID.pid = PID_STG_NAME then begin
Column.Width := 200 ;
end else
if ColumnID.pid = PID_STG_SIZE then begin
Column.Width := 100 ;
end else
if ColumnID.pid = PID_STG_STORAGETYPE then begin
Column.Width := 120 ;
end else
if ColumnID.pid = PID_STG_WRITETIME then begin
Column.Width := 120 ;
end else begin
Column.Width := -1 ;
end ;
if details.fmt = LVCFMT_RIGHT then begin
Column.Alignment := taRightJustify;
end ;
Column.Caption := StrRet.pOleStr;
end ;
end ;
// =============================================================================
// ListViewにItemを追加して,各カラムに表示する値をSubItemに追加していく
// 各ファイルの項目識別子をItemのDateプロパティに代入しておく
// このメソッドではアイコンはセットしない
// ここでセットすると非常に時間がかかる(アイコンの取得操作に時間がかかる)
//
// ここでは最初にItemを追加しているが,最初にファイルの種類や拡張子,日付等を
// 調査して,必要がなければ追加しないようにする
//
// このコードでItemを追加していく方式は,展開に時間がかかる.特に[ゴミ箱]は時
// 間がかかる.仮想リストビュー等を使用する等の工夫が必要
// 各Itemのデータの型はTPropVariantで取得しているが,TVarDataでも取得可能
// =============================================================================
function TForm1.SetListViewItemSubItem(pShellFolder: IShellFolder;
pidlChild: PItemIDList): Boolean;
// ---------------------------------------------------------------------------
// UTC(世界協定時刻)とローカルとの時刻差を求める関数
//
// GetTimeZoneInformationでシステムから現在のタイムゾーンパラメータを取得
// Bias値は世界協定時刻(UTC)時間とローカル時間との分差,つまり分なので,これ
// をTDateTime型に変換して返す.日本国内の場合は常に9:00:00
// ---------------------------------------------------------------------------
function UTCTimeDateTimeOffset: TDateTime;
var
TZ : TTimeZoneInformation;
Hour : Integer;
Min : Integer;
begin
GetTimeZoneInformation(TZ);
Hour := ABS(TZ.Bias) div 60 ;
Min := ABS(TZ.Bias) mod 60 ;
Result := EncodeTime(Hour,Min,0 ,0 );
end ;
// ---------------------------------------------------------------------------
var
pShellFolder2 : IShellFolder2;
i : Integer;
details : SHELLDETAILS;
ColumnID : SHCOLUMNID;
varResult : TPropVariant;
AItem : TListItem;
StrText : string ;
StrFormat : String ;
ASystemTime : SYSTEMTIME;
ADateTime : TDateTime;
begin
Result := False;
// IShellFolder2インターフェイスのフォルダオブジェクトを取得
if (pShellFolder.QueryInterface(IID_IShellFolder2, pShellFolder2)) <> S_OK then
exit;
AItem := nil ;
// このテストプログラムでは,カラムは最初の4つのみ取得して表示
for i := 0 to 3 do begin
// 初期化
VariantInit(OleVariant(varResult));
details.str.pOleStr := nil ;
// カラム構造体を取得(日付型の処理用)
pShellFolder2.MapColumnToSCID(i, ColumnID);
VariantInit(OleVariant(varResult));
// カラム構造体から値をvarResultに取得
// 情報がなければ終了
if (pShellFolder2.GetDetailsEx(pidlChild, ColumnID, @varResult) <> S_OK) then
break;
// SHELLDETAILS構造体にカラムの情報を取得
if (pShellFolder2.GetDetailsOf(pidlChild, i, details)) <> S_OK then
break;
StrText := details.str.pOleStr;
if i = 0 then begin
AItem := ListView1.Items.Add;
AItem.Caption := StrText;
AItem.ImageIndex := -1 ;
end else begin
// ロケールの違いによって変換できない場合があるのでシステム時間から変換
// もし更新日時以外の日付も表示するのであれば,例えば
// if TPropVariant(varResult).vt = VT_DATE then begin
// のように,日付型であるかを調査して処理することも可能
if ColumnID.pid = PID_STG_WRITETIME then begin
VariantTimeToSystemTime(TPropVariant(varResult).date, ASystemTime);
ADateTime := SystemTimeToDateTime(ASystemTime) + UTCTimeDateTimeOffset;
StrFormat := FormatDateTime('yyyy/mm/dd hh:mm' , ADateTime);
AItem.SubItems.Add(StrFormat);
end else begin
// 更新日時以外は文字列で取得
// ゴミ箱等はこの処理に時間がかかる(文字数が多いのが原因か?)
AItem.SubItems.Add(StrText);
end ;
end ;
end ;
if AItem <> nil then begin
AItem.Data := pidlChild;
end ;
Result := True;
end ;
// =============================================================================
// TListViewのOnAdvancedCustomDrawItemイベント
// ここで各Itemにアイコンを取得してセットする
// 各ファイルの情報(名前等)を取得する際にアイコンも取得すると非常に時間がかか
// る.そのためここで行っている.仮想リストビューの表示速度には及ばないが,そ
// こそこの表示速度となる
// =============================================================================
procedure TForm1.ListView1AdvancedCustomDrawItem(Sender: TCustomListView;
Item: TListItem; State: TCustomDrawState; Stage: TCustomDrawStage;
var DefaultDraw: Boolean);
var
SHFileInfo : TSHFileInfo;
pidl : PItemIDList;
pidlFull : PItemIDList;
Attrib : Cardinal;
begin
if FImageHandle = 0 then exit;
if (Stage = cdPrePaint) and (Item.ImageIndex = -1 ) then begin
pidl := ILClone(Item.Data);
if pidl = nil then exit;
// アイコンの取得と設定
// SHGetFileInfoの引数にはフルパス相当の項目識別子(完全PIDL)が必要
// リンク切れファイルやネットワークの検出には非常に時間がかかる.工夫が必要
pidlFull := ILCombine(FpidlExpand, pidl);
SHGetFileInfo(Pointer(pidlFull),
0 ,
SHFileInfo,
SizeOf(TSHFileInfo),
SHGFI_PIDL or SHGFI_SYSICONINDEX);
Item.ImageIndex := SHFileInfo.iIcon;
// リンクのオーバレイアイコン
Attrib := SFGAO_LINK;
FshFolderExpand.GetAttributesOf(1 , pidl, Attrib);
if (SFGAO_LINK and Attrib) <> 0 then Item.OverlayIndex := 1 ;
// 共有フォルダのオーバレイアイコン
Attrib := SFGAO_SHARE;
FshFolderExpand.GetAttributesOf(1 , pidl, Attrib);
if (SFGAO_SHARE and Attrib) <> 0 then
Item.OverlayIndex := INDEXTOOVERLAYMASK(3 );
// 隠し属性のアイコン
Attrib := SFGAO_GHOSTED;
FshFolderExpand.GetAttributesOf(1 , pidl, Attrib);
if (SFGAO_GHOSTED and Attrib) <> 0 then Item.Cut := True;
if pidl <> nil then CoTaskMemFree(pidl);
if pidlFull <> nil then CoTaskMemFree(pidlFull);
end ;
end ;
// =============================================================================
// TListViewのOnDeletionイベント
// ファイルを削除したらListViewのItemも削除することになるが,そのItemを削除し
// たら,Dataプロパティのメモリも開放
// =============================================================================
procedure TForm1.ListView1Deletion(Sender: TObject; Item: TListItem);
begin
if Item.Data <> nil then CoTaskMemFree(Item.Data);
Item.Data := nil ;
end ;
// =============================================================================
// TListViewのOnEditedイベント
// 名前を変更したら,実際のファイルも項目識別子を使用して更新する
// 更新に失敗したら,ListViewのItemのCaptionを元に戻す
// DlgHandleを0にすると,エラー発生時のダイアログを表示しない
//
// 拡張子を非表示にしている場合には,拡張子関係の処理コードを追加する必要あり
// =============================================================================
procedure TForm1.ListView1Edited(Sender: TObject; Item: TListItem;
var S: string );
var
pidlOrigin : PItemIDList;
pidlDest : PItemIDList;
DlgHandle : HWND;
hr : HRESULT;
StrDest : String ;
begin
if Item.Data = nil then exit;
PIDLDest := nil ;
pidlOrigin := ILClone(Item.Data);
DlgHandle := Application.Handle;
StrDest := S;
hr := FshFolderExpand.SetNameOf(DlgHandle,
pidlOrigin,
PChar(StrDest),
SHGDN_NORMAL,
pidlDest);
if Succeeded(hr) then begin
Item.Data := ILClone(pidlDest);
end else begin
S := Item.Caption;
end ;
if pidlOrigin <> nil then CoTaskMemFree(pidlOrigin);
if pidlDest <> nil then CoTaskMemFree(pidlDest);
end ;
// =============================================================================
// TListViewのOnDblClickイベント
// 選択項目のダブルクリック動作
// =============================================================================
procedure TForm1.ListView1DblClick(Sender: TObject);
var
AItem : TListItem;
Attrib : Cardinal;
pidl : PItemIDList;
pidlFull : PItemIDList;
ShExeInfo : TShellExecuteInfo;
begin
if ListView1.Selected = nil then exit;
AItem := ListView1.Selected;
if AItem.Data = nil then exit;
// ファイルの属性を調査
pidl := ILClone(AItem.Data);
pidlFull := ILCombine(FpidlExpand, pidl);
Attrib := SFGAO_FOLDER or SFGAO_FILESYSANCESTOR;
// 属性がファイルシステムのフォルダの場合は,このプログラムで表示
// それ以外は,デフォルトの動作とする
FshFolderExpand.GetAttributesOf(1 , pidl, Attrib);
if (Attrib and (SFGAO_FOLDER)) <> 0 then begin
SetFilesListFromPItemIDList(pidlFull);
end else begin
// 必要があれば,lpParametersやlpDirectory等も設定する
try
ShExeInfo.cbSize := sizeof(TShellExecuteInfo);
ShExeInfo.fMask := SEE_MASK_INVOKEIDLIST;
ShExeInfo.wnd := Handle;
ShExeInfo.lpVerb := '' ;
ShExeInfo.lpFile := nil ;
ShExeInfo.lpParameters := '' ;
ShExeInfo.lpDirectory := '' ;
ShExeInfo.nShow := SW_SHOWNORMAL;
ShExeInfo.hInstApp := 0 ;
ShExeInfo.lpIDList := pidlFull;
ShExeInfo.lpClass := nil ;
ShExeInfo.hkeyClass := 0 ;
ShExeInfo.dwHotKey := 0 ;
ShExeInfo.hIcon := 0 ;
ShellExecuteEx(@ShExeInfo);
finally
end ;
end ;
if pidl <> nil then CoTaskMemFree(pidl);
if pidlFull <> nil then CoTaskMemFree(pidlFull);
end ;
// =============================================================================
// TListViewのOnKeDownイベント
// 選択項目での[Enter]キー操作もダブルクリックと同じ動作とする
// 実用的には[F2]等の操作も追加した方がいいだろう
// =============================================================================
procedure TForm1.ListView1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
// 名前の編集中は実行しない
if not ListView1.IsEditing then begin
if Key = VK_RETURN then begin
if ListView1.Selected <> nil then begin
ListView1DblClick(nil );
Key:=0 ;
end ;
end ;
end ;
end ;
// =============================================================================
// TListViewのOnColumnClickイベント
// カラムをクリックしたらソート
// =============================================================================
procedure TForm1.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
begin
if ListView1.Items.count > 1 then begin
// カラムをクリックする度にソート順を逆にする(トグル操作)
g_SortColum[g_SortColumnIndex] := -g_SortColum[g_SortColumnIndex];
g_SortColumnIndex := Column.Index;
ListView1.CustomSort(@SortListViewData, 0 );
end ;
end ;
// =============================================================================
// TListViewのOnSelectItemイベント
// Itemを選択したらフルパスを表示
// =============================================================================
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
begin
if Item = nil then exit;
Edit1.Text := GetFullPathFromListItemEx(Item);
end ;
// =============================================================================
// 表示スタイルの変更
//
// iViewe : 表示のスタイルで,以下のいづれかの値(CommCtrl.pas内の定数値)
// LV_VIEW_DETAILS 詳細
// LV_VIEW_ICON アイコン
// LV_VIEW_LIST リスト
// LV_VIEW_TILE タイル
// ランタイムテーマを有効しないと効果なし
// =============================================================================
procedure TForm1.ChangeViewStyle(iView: Integer);
var
FileInfo : TSHFileInfo;
OldStyle : Integer;
NewStyle : Integer;
uFlags : Cardinal;
iImageList : Cardinal;
begin
if FpidlDesktop = nil then exit;
// 詳細表示の時だけカラムヘッダを表示
OldStyle := GetWindowLong(ListView1.Handle, GWL_STYLE);
if iView = LV_VIEW_DETAILS then begin
if (OldStyle and LVS_NOCOLUMNHEADER) > 0 then begin
NewStyle := OldStyle and not LVS_NOCOLUMNHEADER;
SetWindowLong(ListView1.Handle, GWL_STYLE, NewStyle);
end ;
end else begin
if (OldStyle and LVS_NOCOLUMNHEADER) = 0 then begin
NewStyle := OldStyle or LVS_NOCOLUMNHEADER;
SetWindowLong(ListView1.Handle, GWL_STYLE, NewStyle);
end ;
end ;
if iView = LV_VIEW_DETAILS then begin
iImageList := LVSIL_SMALL;
uFlags := SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_SMALLICON;
end else
if (iView = LV_VIEW_ICON) or
(iView = LV_VIEW_LIST) or
(iView = LV_VIEW_TILE) then begin
iImageList := LVSIL_NORMAL;
uFlags := SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_LARGEICON;
end else begin
exit;
end ;
// イメージリストの取得と設定を実行して,ListViewのStyleを更新
FImageHandle := SHGetFileInfo(Pointer(FpidlDesktop),
0 ,
FileInfo,
SizeOf(FileInfo),
uFlags);
ListView_SetImageList(ListView1.Handle, FImageHandle, iImageList);
SendMessage(ListView1.Handle, LVM_SETVIEW, WPARAM(DWORD(iView)), 0 );
end ;
// =============================================================================
// ListVeiwのItemからフルパスを求める関数
// 仮想フォルダ等は空文字出力
// 仮想フォルダを扱わない場合はこれで十分と思われる
//
// ILCombine(FpidlExpand, Item.Data)はフルパス相当の項目識別子(完全PIDL)の取得
// =============================================================================
function TForm1.GetFullPathFromListItem(Item: TListItem): String ;
var
Buff : array [0 ..MAX_PATH - 1 ] of Char;
begin
Result := '' ;
if (FpidlExpand = nil ) or (Item.Data = nil ) then exit;
FillChar(Buff, SizeOf(Buff), 0 );
SHGetPathFromIDList(ILCombine(FpidlExpand, Item.Data), Buff);
Result := Buff;
end ;
// =============================================================================
// ListVeiwのItemからフルパスを求める関数
// 仮想フォルダはGUID文字列となる
//
// FshFolderExpandは展開フォルダオブジェクト.Item.Dataは相対項目識別子
// =============================================================================
function TForm1.GetFullPathFromListItemEx(Item: TListItem): String ;
var
StrRet : TStrRet;
begin
Result := '' ;
if (FshFolderExpand = nil ) or (Item.Data = nil ) then exit;
FillChar(StrRet, SizeOf(StrRet), 0 );
if FshFolderExpand.GetDisplayNameOf(Item.Data,
SHGDN_FORPARSING,
StrRet) = S_OK then begin
Result := StrRet.pOleStr;
end ;
end ;
end .
上のコードでは,各カラムのタイトル名を取り出す関数と,各カラムの実際の値を取得する関数を作成して使用しています.
各カラムのタイトルは,そのフォルダ,ファイルのプロパティに相当し,各カラムの実際の値がそのプロパティの値ということになります.上のサンプルコードでは,最初から4つ目までのカラムの情報だけ取り出していますが,以下のコードでは,カラム情報がなくなるまでループして取得しています.カラムの数,内容は Windows のバージョンと,ファイルの種類によって異なることになります.
実際に表示してみると,ファイルには様々な情報の項目があることが確認できます.
この中には個人情報が書き込まれている場合があります.他人にファイルを提供する際には注意が必要でしょう.これらの一部は,ファイルのプロパティのダイアログの [詳細] タブを表示して,一番下にある [プロパティや個人情報を削除] から削除するができます.
図5
設計時画面
図6
デジカメで撮影した画像のプロパティ
リスト2
フォルダ・ファイルのプロパティを取得して表示するサンプルプログラム
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, ShlObj, ExtCtrls;
type
TForm1 = class (TForm)
ListView1: TListView;
Button1: TButton;
OpenDialog1: TOpenDialog;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
procedure GetDisplayFilleProperties(pShellFolder: IShellFolder;
pidlChild: PItemIDList);
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses ComObj, ActiveX;
{$R *.dfm}
// -----------------------------------------------------------------------------
// (ディレクトリ名を除いた)ファイル名を相対項目識別子に変換する関数
//
// ShFolderにディレクトリ部分に相当するフォルダオブジェクト,Pathにはディレク
// トリ部分を除いたファイル名を指定する
// -----------------------------------------------------------------------------
function GetPItemIDFromFileName(ShFolder: IShellFolder2; Path:String ): PItemIDList;
var
Len : Cardinal;
Flags : Cardinal;
begin
Flags := 0 ;
Len := Length(Path);
ShFolder.ParseDisplayName(Application.Handle,
nil ,
PWideChar(StringToOleStr(Path)),
Len,
Result,
Flags);
end ;
// =============================================================================
// [開く]ダイアログを表示して,プロパティを調べるファイルを選択
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
ADir : String ;
AFileName : String ;
shFolderDesktop : IShellFolder2;
shFolderDir : IShellFolder2;
pidlDesktop : PItemIDList;
pidlDir : PItemIDList;
pidlFileName : PItemIDList;
begin
pidlDesktop := nil ;
pidlDir := nil ;
pidlFileName := nil ;
// [デスクトップ]のシェルフォルダオブジェクトと項目識別子を取得
SHGetDesktopFolder(IShellFolder(shFolderDesktop));
SHGetSpecialFolderLocation(0 , CSIDL_DESKTOP, pidlDesktop);
if OpenDialog1.Execute then begin
// ディレクトリとファイル名に分る
ADir := ExtractFileDir(OpenDialog1.FileName);
AFileName := ExtractFileName(OpenDialog1.FileName);
// ディレクトリの項目識別子(完全PIDL)を求める
pidlDir := ILCreateFromPath(PChar(ADir));
// フォルダの項目識別子を[デスクトップ]のシェルフォルダオブジェクトにバインド
// 対象のシェルフォルダオブジェクトshFolderDirが求まる
shFolderDesktop.BindToObject(pidlDir, nil , IShellFolder, shFolderDir);
pidlFileName := GetPItemIDFromFileName(shFolderDir, AFileName);
// プロパティ(各カラム情報)をListViewに表示
ListView1.Items.Clear;
ListView1.Items.BeginUpdate;
GetDisplayFilleProperties(shFolderDir, pidlFileName);
ListView1.Items.EndUpdate;
end ;
CoTaskMemFree(pidlDesktop);
CoTaskMemFree(pidlDir);
CoTaskMemFree(pidlFileName);
end ;
// =============================================================================
// ListViewにItemを追加して,各カラムに表示する値をSubItemに追加していく
// =============================================================================
procedure TForm1.GetDisplayFilleProperties(pShellFolder: IShellFolder;
pidlChild: PItemIDList);
// ---------------------------------------------------------------------------
// UTC(世界協定時刻)とローカルとの時刻差を求める関数
//
// GetTimeZoneInformationでシステムから現在のタイムゾーンパラメータを取得
// Bias値は世界協定時刻(UTC)時間とローカル時間との分差,つまり分なので,これ
// をTDateTime型に変換して返す.日本国内の場合は常に9:00:00
// ---------------------------------------------------------------------------
function UTCTimeDateTimeOffset: TDateTime;
var
TZ : TTimeZoneInformation;
Hour : Integer;
Min : Integer;
begin
GetTimeZoneInformation(TZ);
Hour := ABS(TZ.Bias) div 60 ;
Min := ABS(TZ.Bias) mod 60 ;
Result := EncodeTime(Hour,Min,0 ,0 );
end ;
// ---------------------------------------------------------------------------
var
pShellFolder2 : IShellFolder2;
i : Integer;
details : SHELLDETAILS;
ColumnID : SHCOLUMNID;
varResult : TPropVariant;
AItem : TListItem;
StrText : String ;
StrFormat : String ;
ASystemTime : SYSTEMTIME;
ADateTime : TDateTime;
begin
// IShellFolder2インターフェイルのオブジェクトを取得
if (pShellFolder.QueryInterface(IID_IShellFolder2, pShellFolder2)) <> S_OK then
exit;
i := 0 ;
while True do begin
// 初期化
VariantInit(OleVariant(varResult));
details.str.pOleStr := nil ;
// カラム構造体を取得(日付型の処理用)
pShellFolder2.MapColumnToSCID(i, ColumnID);
// SHELLDETAILS構造体にカラム(タイトル-プロパティ)を取得
if (pShellFolder2.GetDetailsOf(nil , i, details)) <> S_OK then break;
StrText := details.str.pOleStr;
// ListViewの行追加
AItem := ListView1.Items.Add;
AItem.Caption := StrText;
// 初期化(これを実行しないと情報がない項目が文字化けする場合あり)
details.str.pOleStr := nil ;
// 実際に項目識別子pidlChildのファイルの,i番目のカラムの値をdetailsに取出す
pShellFolder2.GetDetailsOf(pidlChild, i, details);
StrText := details.str.pOleStr;
// カラム構造体から値をvarResultに取得
// これは日付型かどうかの判定のみに使用
pShellFolder2.GetDetailsEx(pidlChild, ColumnID, @varResult);
// 日付はロケールの違いによって変換できない場合があるのでシステム時間から変換
if varResult.vt = VT_DATE then begin
VariantTimeToSystemTime(TPropVariant(varResult).date, ASystemTime);
ADateTime := SystemTimeToDateTime(ASystemTime) + UTCTimeDateTimeOffset;
StrFormat := FormatDateTime('yyyy/mm/dd hh:mm' , ADateTime);
AItem.SubItems.Add(StrFormat);
end else begin
// 日付型以外は文字列で取得
AItem.SubItems.Add(StrText);
end ;
i := i + 1 ;
end ;
end ;
end .
下のリストは,上のコードで取得したプロパティの項目名のリストです.
これは筆者の Windows 7 U64(SP1) での結果です.最後の Masters Keywords (debug) の意味は分かりません.実質,全部で 287 項目あります.
このプロパティの項目名と項目の数は,表示するフォルダによっても違ってきます.
000 名前
001 サイズ
002 項目の種類
003 更新日時
004 作成日時
005 アクセス日時
006 属性
007 オフラインの状態
008 オフラインで利用可能
009 認識された種類
010 所有者
011 分類
012 撮影日時
013 参加アーティスト
014 アルバム
015 年
016 ジャンル
017 指揮者
018 タグ
019 評価
020 作成者
021 タイトル
022 件名
023 分類項目
024 コメント
025 著作権
026 トラック番号
027 長さ
028 ビット レート
029 保護
030 カメラのモデル
031 大きさ
032 カメラの製造元
033 会社
034 ファイルの説明
035 プログラム名
036 継続時間
037 オンライン
038 再帰
039 場所
040 任意出席者アドレス
041 任意出席者
042 開催者住所
043 開催者名
044 アラーム時刻
045 必須出席者アドレス
046 必須出席者
047 リソース
048 会議の状態
049 空き時間情報
050 合計サイズ
051 アカウント名
052 進捗状況
053 コンピューター
054 記念日
055 秘書の名前
056 秘書の電話
057 誕生日
058 住所 (会社)
059 市 (会社)
060 国/地域 (会社)
061 私書箱 (会社)
062 郵便番号 (会社)
063 都道府県 (会社)
064 番地 (会社)
065 FAX 番号 (会社)
066 会社のホーム ページ
067 会社電話
068 コールバック番号
069 自動車電話
070 子供
071 会社代表電話
072 部署
073 電子メール アドレス
074 電子メール2
075 電子メール3
076 電子メールの一覧
077 電子メール表示名
078 表題2
079 名
080 氏名
081 性別
082 名2
083 趣味
084 住所 (自宅)
085 市 (自宅)
086 国/地域 (自宅)
087 私書箱 (自宅)
088 郵便番号 (自宅)
089 都道府県 (自宅)
090 番地 (自宅)
091 自宅 FAX
092 自宅電話
093 IM アドレス
094 イニシャル
095 役職
096 ラベル
097 姓
098 住所 (郵送先)
099 ミドル ネーム
100 携帯電話
101 ニックネーム
102 勤務先所在地
103 住所 (その他)
104 ほかの市区町村
105 他の国/地域
106 他の私書箱
107 他の郵便番号
108 他の都道府県
109 他の番地
110 ポケットベル
111 肩書き
112 市区町村 (郵送先)
113 国/地域
114 私書箱 (郵送先)
115 郵便番号 (郵送先)
116 都道府県
117 番地 (郵送先)
118 プライマリ電子メール
119 通常の電話
120 職業
121 配偶者
122 サフィックス
123 TTY/TTD 電話
124 テレックス
125 Web ページ
126 内容の状態
127 内容の種類
128 取得日時
129 アーカイブ日時
130 完了日
131 デバイス カテゴリ
132 接続済み
133 探索方法
134 フレンドリ名
135 ローカル コンピューター
136 製造元
137 モデル
138 ペアリング済み
139 クラス
140 状態
141 クライアント ID
142 貢献者
143 コンテンツの作成日時
144 前回印刷日
145 前回保存日時
146 事業部
147 ドキュメント ID
148 ページ数
149 スライド
150 総編集時間
151 単語数
152 期限
153 終了日
154 ファイル数
155 ファイル名
156 ファイル バージョン
157 フラグの色
158 フラグの状態
159 空き領域
160 ビットの深さ
161 水平方向の解像度
162 幅
163 垂直方向の解像度
164 高さ
165 重要度
166 添付
167 削除
168 暗号化の状態
169 フラグの有無
170 終了済
171 未完了
172 開封の状態
173 共有
174 製作者
175 日付時刻
176 フォルダー名
177 フォルダーのパス
178 フォルダー
179 参加者
180 パス
181 場所ごと
182 種類
183 連絡先の名
184 履歴の種類
185 言語
186 最終表示日
187 説明
188 リンクの状態
189 リンク先
190 URL
191 メディアの作成日時
192 リリース日
193 エンコード方式
194 プロデューサー
195 発行元
196 サブタイトル
197 ユーザー Web URL
198 作者
199 添付ファイル
200 BCC アドレス
201 BCC
202 CC アドレス
203 CC
204 会話 ID
205 受信日時
206 送信日時
207 送信元アドレス
208 差出人
209 添付ファイルの有無
210 送信者アドレス
211 送信者名
212 ストア
213 送信先アドレス
214 To do タイトル
215 宛先
216 経費情報
217 アルバムのアーティスト
218 アルバム ID
219 ビート数/分
220 作曲者
221 イニシャル キー
222 コンパイルの一部
223 雰囲気
224 セットのパート
225 期間
226 色
227 保護者による制限
228 保護者による制限の理由
229 使用領域
230 EXIF バージョン
231 イベント
232 露出補正
233 露出プログラム
234 露出時間
235 絞り値
236 フラッシュ モード
237 焦点距離
238 35mm 焦点距離
239 ISO 速度
240 レンズ メーカー
241 レンズ モデル
242 光源
243 最大絞り
244 測光モード
245 向き
246 人物
247 プログラムのモード
248 彩度
249 対象の距離
250 ホワイト バランス
251 優先度
252 プロジェクト
253 チャンネル番号
254 この回のタイトル
255 字幕
256 再放映
257 SAP
258 放送日
259 プログラムの説明
260 記録時間
261 ステーション コール サイン
262 局名
263 概要
264 抜粋
265 自動要約
266 検索のランキング
267 秘密度
268 共有ユーザー
269 共有状態
270 製品名
271 製品バージョン
272 サポートのリンク
273 ソース
274 開始日
275 課金情報
276 完了
277 仕事の所有者
278 総ファイル サイズ
279 商標
280 ビデオ圧縮
281 ディレクター
282 データ速度
283 フレーム高
284 フレーム率
285 フレーム幅
286 総ビット レート
287
288
289
290
291 Masters Keywords (debug)
292 Masters Keywords (debug)
ゴミ箱を空にするサンプルです.つまり,ゴミ箱の中のファイルを全て削除します.
上のサンプルでゴミ箱を表示し,表示したファイルを全て削除すれば,ゴミ箱は空になりますが,専用の関数もあります.ゴミ箱に限らず,このような専用の関数が用意されていることがあります.必要に応じて利用できます.参考リンクの MSDN の記事の左側の関数一覧の内,先頭が SH の名前の関数がシェル操作用の関数です.
図7
設計時画面
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class (TForm)
Button1: TButton;
CheckBox1: TCheckBox;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
uses ShellAPI;
{$R *.dfm}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
CheckBox1.Checked := True;
end ;
// =============================================================================
// ゴミ箱を空にする
// SHEmptyRecycleBinの使用には,usesにShellAPIが必要
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
// 確認ダイアログを表示する
if CheckBox1.Checked then begin
SHEmptyRecycleBin(0 , nil , 0 );
end else begin
// 確認ダイアログを表示しない
SHEmptyRecycleBin(0 , nil , SHERB_NOCONFIRMATION);
end ;
end ;
end .