|
Delphi Programming / Object Pascal
[掲載 2018年10月15日] [更新 2021年11月07日] Delphi サンプルプログラム集 |
356_インストールされているアプリと Delphi のバージョン |
動作確認等 |
Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
後半は Windows 10 ビルド 19042 + Delphi XE5(UP2) Pro VCL-32 |
|
356_InstalledAppList.zip [14.9 MB] 2021年11月07日版 (EXE 同梱) |
インストールされているアプリと Delphi のバージョン |
システムにインストールされているアプリケーションと,システムにインストールされている Delphi のパーションを,レジストリから取得するサンプルです.
後半は,Windows 10 にインストールされているアプリの一覧を取得するサンプルです. |
システムにインストール済みのアプリは,レジストリの [HKEY_LOCAL_MACHINE] の以下の階層下のキーに情報があります.本サンプルはこのキーの階層下のキー名の一覧を取得して,[UninstallString] 項目値が有効なアプリだけを一覧表示します.
以下のキーは 32 ビットの Windows の場合です. |
SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall |
64 ビットの Windows では [
HKEY_LOCAL_MACHINE] の以下の階層下のキーが対象です.
|
64 ビットのアプリ
SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
32 ビットのアプリ
Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
以下のサンプルで取得するアプリの一覧は [コントロールパネル] の [プログラムのアンインストールまたは変更] で表示されるリストとぼぼ同じです.これらのレジストリエントリーには,Windows に付属している電卓やメモ帳の情報は格納されていません.
アプリケーションの中には,[HKEY_CURRENT_USER] [Software]
や [HKEY_LOCAL_MACHINE] [Software]
の階層直下のキーに情報を格納していることがあります.また,アンインストールの機能も独自で実装しているアプリもあります.
|
|
図1
実行結果
|
リスト1
インストールされているアプリの一覧を取得 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Generics.Collections, Generics.Defaults;
type
TAppInfo = record
DisplayName : string;
InstallLocation : string;
UninstallString : string;
end;
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
function Is64bitWindows: Boolean;
public
{ Public 宣言 }
FAppList : TList<TAppInfo>;
end;
var
Form1: TForm1;
implementation
uses
Registry,
StrUtils;
{$R *.dfm}
//=============================================================================
// フォーム生成時の処理
// ジェネリックスのTListを生成
// リストに格納するのは各アプリの情報を格納するレコード型
//
// ジェネリックスの使用にはusesにGenerics.Collectionsが必要
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
FAppList := TList<TAppInfo>.Create;
end;
//=============================================================================
// フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FAppList);
end;
//=============================================================================
// インストール済みのアプリの一覧をレコード型に格納して一覧表示
// 正確にはHKEY_LOCAL_MACHINEの以下のキーの階層下のキーの項目を検索
// (それらのキーのサブキーは検索しない - 再帰的な検索はしない)
//
// 64ビットWindowsの場合は以下の2つの場所
// SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
// SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
//
// 32ビットWindowsの場合は以下の場所
// SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
//
// レコード型に格納するのは各キーの以下の項目の値
// Windowsのセキュリティ用の更新ソフト(KBXXXX)は除外
// DisplayName
// InstallLocation
// UninstallString
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
//---------------------------------------------------------------------------
// 引数のARegKey内のサブキー内の項目を検索
// ジェネリックのTListに各アプリの情報をレコード型として格納
//---------------------------------------------------------------------------
procedure GetAppInfo(AReg: TRegistry; ARegKey: string; AList: TList<TAppInfo>);
var
LKeyNameList : TStringList;
LKeyEachApp : string;
LDisplayName : string;
LInstLocate : string;
LUninstStr : string;
LLoopCnt : Integer;
LAppInfo : TAppInfo;
begin
LKeyNameList := TStringList.Create;
try
if not AReg.OpenKey(ARegKey, False) then exit;
// ARegKey階層下のキー名の一覧を取得
AReg.GetKeyNames(LKeyNameList);
AReg.CloseKey;
// キー名の一覧の各キーの項目を調べる
for LLoopCnt := 0 to LKeyNameList.Count - 1 do begin
try
LKeyEachApp := ARegKey + PathDelim + LKeyNameList[LLoopCnt];
if AReg.OpenKey(LKeyEachApp, False) then begin
if not AReg.ValueExists('DisplayName') then Continue;
if not AReg.ValueExists('UninstallString') then Continue;
LDisplayName := AReg.ReadString('DisplayName');
if ContainsText(LDisplayName, '(KB') then Continue;
LUninstStr := AReg.ReadString('UninstallString');
if not AReg.ValueExists('InstallLocation') then begin
LInstLocate := '';
end else begin
LInstLocate := AReg.ReadString('InstallLocation');
end;
if (LDisplayName <> '') and (LUninstStr <> '') then begin
LAppInfo.DisplayName := LDisplayName;
LAppInfo.InstallLocation := LInstLocate;
LAppInfo.UninstallString := LUninstStr;
AList.Add(LAppInfo);
end;
end;
finally
AReg.CloseKey;
end;
end;
finally
FreeAndNil(LKeyNameList);
end;
end;
//---------------------------------------------------------------------------
var
LRegistry : TRegistry;
LKeyUnInst : String;
LAppInfo : TAppInfo;
LDisplayName : string;
LInstLocate : string;
LLoopCnt : Integer;
begin
Memo1.Lines.Clear;
LRegistry := TRegistry.Create;
try
if Is64bitWindows then begin
// 64ビットWindowsの場合
LRegistry.Access := KEY_READ or KEY_WOW64_32KEY;
LRegistry.RootKey := HKEY_LOCAL_MACHINE;
LKeyUnInst := 'Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall';
GetAppInfo(LRegistry, LKeyUnInst, FAppList);
LRegistry.CloseKey;
LRegistry.Access := KEY_READ or KEY_WOW64_64KEY;
LKeyUnInst := 'Software\Microsoft\Windows\CurrentVersion\Uninstall';
GetAppInfo(LRegistry, LKeyUnInst, FAppList);
end else begin
// 32ビットWindowsの場合
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_LOCAL_MACHINE;
LKeyUnInst := 'Software\Microsoft\Windows\CurrentVersion\Uninstall';
GetAppInfo(LRegistry, LKeyUnInst, FAppList);
end;
LRegistry.CloseKey;
//-------------------------------------------------------------------------
// リストのソート
// TComparer関係の使用にはusesにGenerics.Defaultsが必要
//-------------------------------------------------------------------------
FAppList.Sort(TComparer<TAppInfo>.Construct(
function(const Left, Right: TAppInfo): Integer
begin
//DisplayNameの値でソート
Result := SysUtils.CompareText(Left.DisplayName, Right.DisplayName);
end)
);
//-------------------------------------------------------------------------
// 重複項目を削除
for LLoopCnt := FAppList.Count - 2 downto 0 do begin
LDisplayName := FAppList[LLoopCnt].DisplayName;
LInstLocate := FAppList[LLoopCnt].InstallLocation;
if (LDisplayName = FAppList[LLoopCnt + 1].DisplayName)
and (LInstLocate = FAppList[LLoopCnt + 1].InstallLocation) then begin
FAppList.Delete(LLoopCnt + 1);
end;
end;
// DisplayNameだけを一覧表示
// [プログラムのアンインストールまたは変更]のリストとほぼ同じ
Memo1.Lines.BeginUpdate;
for LAppInfo in FAppList do begin
Memo1.Lines.Add(LAppInfo.DisplayName);
end;
Memo1.Lines.EndUpdate;
Label1.Caption := IntToStr(FAppList.Count);
finally
FreeAndNil(LRegistry);
end;
end;
//-----------------------------------------------------------------------------
// 32ビットのWindowsか64ビットのWindowsかを調べる関数
// 64ビット版のWindowsの場合はTrueを返す
//-----------------------------------------------------------------------------
function TForm1.Is64bitWindows: Boolean;
var
Wow64Proc : function(hProcess: THandle; var Wow64: BOOL): BOOL stdcall;
RetFlag : LongBool;
begin
@Wow64Proc := GetProcAddress(GetModuleHandle('Kernel32.dll'), 'IsWow64Process');
if @Wow64Proc <> nil then begin
Wow64Proc(GetCurrentProcess, RetFlag);
if SizeOf(THandle) = 4 then begin
Result := RetFlag;
end else
if SizeOf(THandle) = 8 then begin
Result := True;
end;
end else begin
//Windows XPの64ビット版か
Result := CheckWin32Version(5, 2);
end;
end;
end.
02_インストールされている Delphi のバージョンを取得 |
下の表は各バージョンの Delphi の情報が格納されているレジストリの場所です.
これらのキーの項目値,あるいはこれらの階層下のキーの項目値から Delphi の各バージョンの情報を取得して表示するサンプルです.
|
Delphi のバージョン |
HKEY_CURRENT_USER\Software |
HKEY_LOCAL_MACHINE\Software |
Delphi 1 (Delphi) |
- |
- |
Delphi 2 |
Borland\Delphi\2.0 |
Borland\Delphi\2.0 |
Delphi 3 / 3.1 |
Borland\Delphi\3.0 |
Borland\Delphi\3.0 |
Delphi 4 |
Borland\Delphi\4.0 |
Borland\Delphi\4.0 |
Delphi 5 |
Borland\Delphi\5.0 |
Borland\Delphi\5.0 |
Delphi 6 |
Borland\Delphi\6.0 |
Borland\Delphi\6.0 |
Delphi 7 / 7.1 |
Borland\Delphi\7.0 |
Borland\Delphi\7.0 |
Delphi 2005 |
Borland\BDS\3.0 |
Borland\BDS\3.0 |
BDS 2006 |
Borland\BDS\4.0 |
Borland\BDS\4.0 |
Delphi 2007 / 2007-R2 |
Borland\BDS\5.0 |
Borland\BDS\5.0 |
Delphi 2009 |
CodeGear\BDS\6.0 |
CodeGear\BDS\6.0 |
Delphi 2010 |
CodeGear\BDS\7.0 |
CodeGear\BDS\7.0 |
Delphi XE |
Embarcadero\BDS\8.0 |
Embarcadero\BDS\8.0 |
Delphi XE2 |
Embarcadero\BDS\9.0 |
Embarcadero\BDS\9.0 |
Delphi XE3 |
Embarcadero\BDS\10.0 |
Embarcadero\BDS\10.0 |
Delphi XE4 |
Embarcadero\BDS\11.0 |
Embarcadero\BDS\11.0 |
Delphi XE5 |
Embarcadero\BDS\12.0 |
Embarcadero\BDS\12.0 |
Delphi XE6 |
Embarcadero\BDS\14.0 |
Embarcadero\BDS\14.0 |
Delphi XE7 |
Embarcadero\BDS\15.0 |
Embarcadero\BDS\15.0 |
Delphi XE8 |
Embarcadero\BDS\16.0 |
Embarcadero\BDS\16.0 |
Delphi 10 Seattle |
Embarcadero\BDS\17.0 |
Embarcadero\BDS\17.0 |
Delphi 10.1 Berlin |
Embarcadero\BDS\18.0 |
Embarcadero\BDS\18.0 |
Delphi 10.2 Tokyo |
Embarcadero\BDS\19.0 |
Embarcadero\BDS\19.0 |
上の表を見ると,ある程度の規則性があることが確認できます.この規則性を利用します.
レジストリから取得した Delphi の各バージョンの情報は,レコード型のメンバの変数に代入していきます.そのレコード型の変数をジェネリックスの TList<T> の要素として格納します.
取得結果はプロダクトバージョンの値でソートして,古いバージョンの Delphi の情報から順番に表示するようにしています.
|
[備考 1]
筆者の開発環境には,Delphi 2005 / 2006 / 2007 / 2007-R2 / 2009 / 2010 がインストールされていません.したがって,これらのバージョンでの情報取得は確認していません.
Delphi 2005 で情報が取得できれば,それ以降のバージョンでも取得可能と思われます.
Delphi 1 (Delphi) はレジストリを使用していません.
|
|
図2
実行結果
|
|
図3
実行結果
|
リスト2
インストールされている Delphi のバージョンを取得 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Generics.Collections, Generics.Defaults;
type
TDelphiVerInfo = record
ProductVer : Double;
AppFullPath : string;
RegKeyBase : string;
VersionStr : string;
Enable64bit : Boolean;
end;
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
FAppList : TList<TDelphiVerInfo>;
end;
procedure GetDelphiVerList(AList: TList<TDelphiVerInfo>);
var
Form1: TForm1;
implementation
uses
Registry,
StrUtils;
{$R *.dfm}
//=============================================================================
// フォーム生成時の処理
// ジェネリックスのリストを生成
// リストに格納するのはDephiの各バージョンの情報を格納するレコード型
//
// ジェネリックスの使用にはusesにGenerics.Collectionsが必要
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
FAppList := TList<TDelphiVerInfo>.Create;
end;
//=============================================================================
// フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FAppList);
end;
//=============================================================================
// インストール済みのDelphiの各バージョンの情報を取得して表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LAppInfo : TDelphiVerInfo;
begin
Memo1.Lines.Clear;
FAppList.Clear;
GetDelphiVerList(FAppList);
Memo1.Lines.BeginUpdate;
for LAppInfo in FAppList do begin
Memo1.Lines.Add(LAppInfo.VersionStr);
Memo1.Lines.Add('ProductVersion ' + FloatToStr(LAppInfo.ProductVer));
Memo1.Lines.Add(LAppInfo.AppFullPath);
Memo1.Lines.Add(LAppInfo.RegKeyBase);
if LAppInfo.Enable64bit then begin
Memo1.Lines.Add('Enable 64 bit Application');
end;
Memo1.Lines.Add('');
end;
Memo1.Lines.EndUpdate;
Label1.Caption := IntToStr(FAppList.Count);
end;
//-----------------------------------------------------------------------------
// インストール済みDelphiの各バージョンの情報をジェネリックスのリストに格納
// リストに格納するのはDephiの各バージョンの情報を格納するレコード型
//-----------------------------------------------------------------------------
procedure GetDelphiVerList(AList: TList<TDelphiVerInfo>);
procedure InitializeRecord(var AInfo: TDelphiVerInfo);
begin
AInfo.ProductVer := 0.0;
AInfo.AppFullPath := 'Empty';
AInfo.RegKeyBase := 'Empty';
AInfo.VersionStr := 'Empty';
AInfo.Enable64bit := False;
end;
const
LArrayBDS : array [0..2] of string
= ('\Borland\BDS', '\CodeGear\BDS', '\Embarcadero\BDS');
var
LRegistry : TRegistry;
LKeyBDS : string;
LKeyEeachVer : string;
LKeyNameList : TStringList;
LKeyItemList : TStringList;
LAppInfo : TDelphiVerInfo;
LTempValue : string;
LLoopCnt : Integer;
LLoopBDS : Integer;
begin
AList.Clear;
LRegistry := TRegistry.Create;
LKeyNameList := TStringList.Create;
LKeyItemList := TStringList.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_LOCAL_MACHINE;
//-------------------------------------------------------------------------
// Delphi 2 - 7
// Delphi 4 までは最初の項目がDelphi のバージョン文字列
// Delphi 5 からは項目名の App がEXEのフルパス
//-------------------------------------------------------------------------
LKeyBDS := 'Software\Borland\Delphi';
if LRegistry.OpenKey(LKeyBDS, False) then begin
// 現在のキーの階層下のキー名の一覧を取得
// このキーがDelphiの各バージョンごとのキー
LRegistry.GetKeyNames(LKeyNameList);
for LLoopCnt := 0 to LKeyNameList.Count - 1 do begin
LKeyEeachVer := LKeyBDS + PathDelim + LKeyNameList[LLoopCnt];
LRegistry.CloseKey;
InitializeRecord(LAppInfo);
if LRegistry.OpenKey(LKeyEeachVer, False) then begin
LAppInfo.ProductVer := StrToFloat(LKeyNameList[LLoopCnt]);
if LAppInfo.ProductVer < 5.0 then begin
// 現在のDelphiのキー名の各項目名(名前:レジストリエントリ)一覧を取得
LRegistry.GetValueNames(LKeyItemList);
if LKeyItemList.Count < 1 then Continue;
LAppInfo.AppFullPath := LRegistry.ReadString(LKeyItemList[0]);
end else begin
if not LRegistry.ValueExists('App') then Continue;
LAppInfo.AppFullPath := LRegistry.ReadString('App');
end;
// Delphi のバージョン文字列の作成
// 小数点以下が 0 の時は整数部だけとする
LTempValue := Trim(LKeyNameList[LLoopCnt]);
if RightStr(LTempValue, 2) = '.0' then begin
LTempValue := LeftStr(LTempValue, 1);
end;
LAppInfo.VersionStr := 'Delphi ' + LTempValue;
LAppInfo.RegKeyBase := LKeyEeachVer;
// ジェネリックスのリストにレコード型のデータを追加
AList.Add(LAppInfo);
end;
end;
end;
LRegistry.CloseKey;
//-------------------------------------------------------------------------
// Delphi 2005 以降
//-------------------------------------------------------------------------
for LLoopBDS := 0 to Length(LArrayBDS) - 1 do begin
LKeyBDS := 'Software' + LArrayBDS[LLoopBDS];
if LRegistry.OpenKey(LKeyBDS, False) then begin
// 現在のキーの階層下のキー名の一覧を取得
// このキーがDelphiの各バージョンごとのキー
LRegistry.GetKeyNames(LKeyNameList);
for LLoopCnt := 0 to LKeyNameList.Count - 1 do begin
LKeyEeachVer := LKeyBDS + PathDelim + LKeyNameList[LLoopCnt];
LRegistry.CloseKey;
InitializeRecord(LAppInfo);
if LRegistry.OpenKey(LKeyEeachVer, False) then begin
if not LRegistry.ValueExists('App') then Continue;
LAppInfo.AppFullPath := LRegistry.ReadString('App');
if LRegistry.ValueExists('ProductVersion') then begin
LAppInfo.ProductVer := StrToFloat(LRegistry.ReadString('ProductVersion'));
end;
LRegistry.CloseKey;
// Delphi XE,Delphi XE5,..等の文字列を取得
if LRegistry.KeyExists(LKeyEeachVer + '\Personalities') then begin
if LRegistry.OpenKey(LKeyEeachVer + '\Personalities', False) then begin
if LRegistry.ValueExists('Delphi.Win32') then begin
LAppInfo.VersionStr := LRegistry.ReadString('Delphi.Win32');
end;
end;
end;
LAppInfo.RegKeyBase := LKeyEeachVer;
// 64 ビットの開発が可能かを調査
if ContainsText(LKeyBDS, 'Embarcadero\BDS') then begin
LRegistry.CloseKey;
if LRegistry.KeyExists(LKeyEeachVer + '\Library\Win64') then begin
// 64ビット用のWindowsアプリが開発可能な場合はTrue
LAppInfo.Enable64bit := True;
end else begin
LAppInfo.Enable64bit := False;
end;
end;
AList.Add(LAppInfo);
end;
end;
end;
LRegistry.CloseKey;
end;
//---------------------------------------------------------
// プロダクトバージョンの値でリストをソート
// TComparer関係の使用にはusesにGenerics.Defaultsが必要
//---------------------------------------------------------
AList.Sort(TComparer<TDelphiVerInfo>.Construct(
function(const Left, Right: TDelphiVerInfo): Integer
begin
Result := Trunc((Left.ProductVer - Right.ProductVer) * 10);
end)
);
finally
FreeAndNil(LRegistry);
FreeAndNil(LKeyNameList);
FreeAndNil(LKeyItemList);
end;
end;
end.
[備考 2]
デバッグ中であれば,環境変数 $(DELPHI) または $(BDS) でインストール先のフォルダのパス名を取得することができます.
|
[備考 3]
IDE の機能拡張の実装やコンポーネントのプロパティエディタの実装の場合は,OpenToolsAPI (OTA) の機能を利用して IDE 関係の情報を取得することができます. |
[備考 4]
Delphi のインストール先のフォルダ名は Delphi のバージョン番号,コンパイラバージョン等の値ではありません.これは別名 (Other names) とも呼ばれていますが,Delphi 2005 が BDS 3 です.そこからの連番になっています.
Delphi XE7 は 15.0, Delphi XE8 は 16.0 というようになっています (13.0 は欠番).
|
03_インストールされているアプリ一覧 - Windows 10 |
Windows 10 では,インストールされているアプリのリストが,仮想フォルダ AppsFolder にあります.そのフォルダ内のリストを取得して表示するサンプルです.
仮想フォルダ AppsFolder のリストには,下図のような [スタート] メニューの展開部分も含まれています.仮想フォルダ AppsFolder のリストには,[スタート] メニューに登録してないアプリはありません. |
|
図4
[スタート] メニューのアプリのリスト
- 展開リストもインストールされているアプリの一覧に含まれる
|
スタート] メニューに登録していないデスクトップアプリも,そのアプリの EXE ファイルのコンテキストメニューから [スタートメニューにピン留めする] を選択すれば一覧に表示できます.スタートメニューにピン留めすると,例えば以下の場所にその EXE のショートカットが作成されます.このフォルダ内にショートカットを作成すると,スタートメニューに登録されて本サンプルのアプリの一覧に表示されます.
このショートカットの名前 (表示名) を変更すれば,アプリの一覧に表示するアプリ名も変わります. |
%APPDATA%\Microsoft\Windows\Start Menu\Programs |
追加したショートカットがあるフォルダは,AppUserModelId のコンテキストメニューの [ファイルの場所を開く] で表示できます. |
|
図5
スタートメニュー
- TEAD というアプリの例
- 左はショートカット作成
- 右はスタートメニューにピン留めした結果
|
サンプルの実行結果です.左側がアプリ名,右側の AppUserModelId は,アプリケーション識別子と呼ばれる値です.[全コピー] は,それらの値をタブ区切りの文字列としてでクリップボードにコピーします.タブ区切りの文字列は,エクセルのセルにそのまま貼り付けすることができます.選択項目をダブルクリックすると,そのアプリが起動します. |
|
図6
実行結果
|
リスト3
インストールされているアプリの一覧を取得 - Windows 10 用 |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
Winapi.ShellAPI, Winapi.ActiveX, Winapi.ShlObj, Winapi.CommCtrl, Vcl.ImgList,
Vcl.ComCtrls;
type
TForm1 = class(TForm)
ImageList1: TImageList;
ListView1: TListView;
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure ListView1DblClick(Sender: TObject);
procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
function GetDispNameFromPItemID(ShFolder: IShellFolder; ItemID:
PItemIDList; Flag: DWORD=SHGDN_NORMAL): string;
var
Form1: TForm1;
implementation
uses
Vcl.Clipbrd;
{$R *.DFM}
//=============================================================================
// フォーム生成時の処理
// 一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
ImageList1.BkColor := ListView1.Color;
ListView1.ViewStyle := vsReport;
ListView1.SmallImages := ImageList1;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' 名前';
ListView1.Columns[0].Width := 200;
ListView1.Columns.Add;
ListView1.Columns[1].Caption := 'AppUserModelId';
end;
//=============================================================================
// インストールされているアプリをリスト - Windows 10
// 仮想フォルダ AppsFolder 内のリストを取得
// ( アプリ名と AppUserModelId の値を列挙 )
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LPath : WideString;
LSHDesktop : IShellFolder;
LSHAppFolder : IShellFolder;
LpidlRoot : PItemIDList;
LAppIdList : IEnumIDList;
LFetched : Cardinal;
LpidlChild : PItemIDList;
LAppName : string;
LAppIDPath : string;
LpidlFull : PItemIDList;
LSHFileInfo : TSHFileInfo;
LIcon : TIcon;
LListItem : TListItem;
begin
Screen.Cursor := crHourGlass;
ListView1.Items.Clear;
ListView1.RowSelect := True;
ListView1.Items.BeginUpdate;
LpidlRoot := nil;
LpidlChild := nil;
LPath := 'shell:AppsFolder';
try
// ファイルを項目識別子に変換
LpidlRoot := ILCreateFromPath(PChar(LPath));
if ILIsEmpty(LpidlRoot) then Exit;
// 対象の項目識別子を[デスクトップ]のシェルフォルダオブジェクトにバインド
// 対象のフォルダの IShellFolder オブジェクトが取得できる
SHGetDesktopFolder(LSHDesktop);
LSHDesktop.BindToObject(LpidlRoot, nil, IShellFolder, Pointer(LSHAppFolder));
// ファイル (項目識別子) のリストを取得
LSHAppFolder.EnumObjects(Handle, SHCONTF_NONFOLDERS, LAppIdList);
// リストの各項目の表示名を調べる
// ここで取得した項目識別子は解放しないこと
LAppIdList.Reset;
while (LAppIdList.Next(1, LpidlChild, LFetched) = S_OK) do begin
LAppName := GetDispNameFromPItemID(LSHAppFolder, LpidlChild, SHGDN_NORMAL);
if LAppName = '' then Continue;
LAppIDPath := GetDispNameFromPItemID(LSHAppFolder, LpidlChild, SHGDN_FORPARSING);
if LAppIDPath = '' then Continue;
LListItem := ListView1.Items.Add;
LListItem.Caption := LAppName;
LListItem.SubItems.Add(LAppIDPath);
// ファイルの項目識別子の値からアイコンを取得
LpidlFull := ILCombine(LpidlRoot, LpidlChild);
SHGetFileInfo(Pointer(LpidlFull),
0,
LSHFileInfo,
SizeOf(TSHFileInfo),
SHGFI_PIDL or SHGFI_ICON or SHGFI_SMALLICON);
LIcon := TIcon.Create;
try
LIcon.Handle := LSHFileInfo.hIcon;
LListItem.ImageIndex := ListView1.SmallImages.AddIcon(LIcon);
finally
FreeAndNil(LIcon);
CoTaskMemFree(LpidlFull);
end;
end;
finally
LSHDesktop := nil;
LSHAppFolder := nil;
CoTaskMemFree(LpidlRoot);
Screen.Cursor := crDefault;
end;
ListView1.SortType := stText;
ListView1.Items.EndUpdate;
FormResize(nil);
end;
//=============================================================================
// 表示しているアプリ名と AppUserModelId の値をクリップボードにコピー
// TAB 区切りテキスト.エクセルに貼り付け可能
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LStrList : TStringList;
LText : string;
Lindex : Integer;
begin
LStrList := TStringList.Create;
try
for Lindex := 0 to ListView1.Items.Count -1 do begin
LText := ListView1.Items[Lindex].Caption + Char(VK_TAB)
+ ListView1.Items[Lindex].SubItems[0];
LStrList.Add(LText);
end;
Clipboard.AsText := LStrList.Text;
finally
FreeAndNil(LStrList);
end;
end;
//=============================================================================
// TListView の OnDblClick イベント
// ダブルクリックしたら選択しているアプリを起動
// 本 EXE が 32 ビットの場合,起動しない 64 ビットのアプリがある
//=============================================================================
procedure TForm1.ListView1DblClick(Sender: TObject);
var
LItem : TListItem;
LAppId : string;
begin
LItem := ListView1.Selected;
if LItem = nil then Exit;
LAppId := 'shell:AppsFolder' + PathDelim + Litem.SubItems[0];
ShellExecute(0, '', PChar(LAppId), nil, nil, SW_SHOW);
end;
//=============================================================================
// TListView の OnSelectItem イベント
// TListView の Item を選択したら AppUserModelId の値を TEdit に表示
//=============================================================================
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
begin
if Item = nil then Exit;
Edit1.Text := Item.SubItems[0];
end;
//=============================================================================
// フォームの幅を変更したらカラムの幅も変更
//=============================================================================
procedure TForm1.FormResize(Sender: TObject);
begin
ListView1.Columns[1].Width := ListView1.ClientWidth - ListView1.Columns[0].Width;
end;
//-----------------------------------------------------------------------------
// 相対項目識別子からフォルダまたはファイル名を求める
// 仮想フォルダは GUID 文字列として求まる
//
// 第 1 引数 対象の項目識別子を取得する際に使用したシェルフォルダオブジェクト
// 第 2 引数 対象の項目識別子.ShFolder 階層下の相対値
// 第 3 引数 フルパスを取得する場合は,SHGDN_FORPARSING
// ファイル名だけ取得する場合は,SHGDN_NORMAL また はSHGDN_INFOLDER
//-----------------------------------------------------------------------------
function GetDispNameFromPItemID(ShFolder: IShellFolder; ItemID:
PItemIDList; Flag: DWORD=SHGDN_NORMAL): string;
var
StrRet : TStrRet;
begin
Result := '';
if ShFolder = nil then exit;
if ItemID = nil then exit;
FillChar(StrRet, SizeOf(StrRet), 0);
if NOERROR = ShFolder.GetDisplayNameOf(ItemID, Flag, StrRet) then begin
Result := StrRet.pOleStr;
end;
end;
end.
筆者の環境には 64 ビット版の Google Chrome ブラウザがインストールされています.その AppUserModelId の値は Chorme だけとなっています.
添付のサンプルの EXE は 32 ビットです.この Google Chrome を起動できません.EXE が 32 ビットなので 32 ビットの EXE を探しに行きます.そのため,下図のようなエラーとなります.本サンプルを 64 ビットの EXE としてコンパイルして実行すれば,64 ビット版の Google Chrpme は起動します.
AppUserModelId の値を指定しない場合,AppUserModelId の値は,そのアプリケーションのインストールの際にシステムが自動的に設定します.おそらく Google hrome は Chrome という値を指定しているものと思われます. |
|
図7
32 ビットの EXE を探す例 |
Google Chrome が何故そのような設定にしているのか,筆者に知る術はありません.以下のように,Shell コマンドではなく,普通に Chrome というコマンドを実行すれば起動します. |
if Litem.SubItems[0] = 'Chrome' then begin
LAppId := Litem.SubItems[0];
end else begin
LAppId := 'shell:AppsFolder' + PathDelim + Litem.SubItems[0];
end;
ShellExecute(0, '', PChar(LAppId), nil, nil, SW_SHOW);
Windows が 64 ビット版で,本サンプルが 32 ビットの EXE の場合,スクリーンキーボードも起動しません.64 ビット版の Windows には,32 ビット版のスクリーンキーボードはありません.メモ帳や電卓は 32 ビット版と 64 ビット版がありますから,本サンプルが 32 ビットの EXE でも 64 ビットの EXE でも起動します.
32 ビットの EXE から 64 ビットのスクリーンキーボードが起動できるようにするには,ファイルシステムのリダイレクト機能を無効にします.後の方に具体的なサンプルがあります.
Delphi 6 / Delphi 7 等の旧いバージョンの Delphi を IDE から起動すると,下図のようなモジュールの読み込みエラーが発生します.この場合は [はい] のボタンをクリックします.そうしないと次回の起動時に問題が発生する可能性があります.EXE を直接起動して実行すればエラーは発生しません. |
|
図8
エラー |
//=============================================================================
// Delphi 6 / Delphi 7 を起動するとエラーが発生する
// このコードの実行には uses に Winapi.ShellAPI が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFilepath : string;
begin
LFilepath := 'C:\Program Files (x86)\Borland\Delphi6\Bin\delphi32.exe';
ShellExecute(0, '', PChar(LFilepath), nil, nil, SW_SHOW);
end;
ソートの結果が,後の方の仮想リストビューを使用するサンプルと異なっています.仮想リストビューを使用したサンプルと同じ結果にするには,以下のように OnCompare イベントを使用して,Unocode の文字列としてソートします. |
//=============================================================================
// TListView の OnCompare イベント
// Unicode の文字列としてソートする
//
// TListView.SortType := stBoth にして,このイベントで
// CompareText (System.SysUtils) 関数を使用してソートする
//=============================================================================
procedure TForm1.ListView1Compare(Sender: TObject; Item1, Item2: TListItem;
Data: Integer; var Compare: Integer);
begin
Compare := CompareText(Item1.Caption, Item2.Caption)
end;
[備考 1]
リストが表示されるまで少し時間がかかりますが,続けて実行すると,キャッシュが働いて少しだけ処理が速くなります.アイコン画像を取得する部分のコードを削除すれば高速になります. |
[備考 2]
ShellExecuteEx 関数を使用すると,項目識別子の値でファイルを [開く] ことができます. |
[備考 3]
UWP アプリは,Windows のランタイムライブラリである WinRT を使用する COM の拡張オブジェクトとしての位置づけとなっています.Delphi 10.2 では,WinRT の機能を利用するためのユニット類が実装されています.それらのユニットは,主に source\rtl\win\winrt 内にあります.例えば WinAPI.ApplicationModel.pas というユニットがあります. |
04_インストールされているアプリ一覧 ( IShellItem ) - Windows 10 |
エクスプローラでフォルダ内のファイルを [詳細] で表示すると,一般的には複数の列があります.それらは [名前] [更新日時 ] [作成日時] [サイズ] [種類] 等です.前項のサンプルでは列方向の処理はしていません.[名前] に相当する列の表示名を変更しているだけです.列の処理が必要ない場合,IShellItem インターフェイスのオブジェクトが使用できます.
本サンプルは IShellItem を使用する例です.実行結果は,前項のサンプルと同じです. |
リスト4
インストールされているアプリの一覧を取得 - Windows 10 用 |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
Winapi.ShellAPI, Winapi.ActiveX, Winapi.ShlObj, Winapi.CommCtrl, Vcl.ImgList,
Vcl.ComCtrls;
type
TForm1 = class(TForm)
ImageList1: TImageList;
ListView1: TListView;
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure ListView1DblClick(Sender: TObject);
procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
Vcl.Clipbrd;
{$R *.DFM}
//=============================================================================
// フォーム生成時の処理
// 一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
ImageList1.BkColor := ListView1.Color;
ListView1.ViewStyle := vsReport;
ListView1.SmallImages := ImageList1;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' 名前';
ListView1.Columns[0].Width := 200;
ListView1.Columns.Add;
ListView1.Columns[1].Caption := 'AppUserModelId';
end;
//=============================================================================
// インストールされているアプリをリスト - Windows 10
// 仮想フォルダ AppsFolder 内のリストを取得
// ( アプリ名と AppUserModelId の値を列挙 )
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LPath : WideString;
LpidlRoot : PItemIDList;
LShItemRoot : IShellItem;
LShItemChild : IShellItem;
LEnumItems : IEnumShellItems;
LFetched : LongInt;
LAppName : PWideChar;
LAppIDPath : PWideChar;
LpidlFull : PItemIDList;
LSHFileInfo : TSHFileInfo;
LIcon : TIcon;
LListItem : TListItem;
begin
Screen.Cursor := crHourGlass;
ListView1.Items.Clear;
ListView1.RowSelect := True;
ListView1.Items.BeginUpdate;
LpidlRoot := nil;
LShItemRoot := nil;
LShItemChild := nil;
LPath := 'shell:AppsFolder';
try
// ファイルを項目識別子に変換
LpidlRoot := ILCreateFromPath(PChar(LPath));
if ILIsEmpty(LpidlRoot) then Exit;
// フォルダの IShellItem オブジェクトを取得
SHCreateItemFromIDList(LpidlRoot, IShellItem, LShItemRoot);
// ファイル (IShellItem のリストを LEnumItems として取得
LShItemRoot.BindToHandler(nil, BHID_EnumItems, IEnumShellItems, LEnumItems);
// リストの各項目の表示名を調べる
while (LEnumItems.Next(1, LShItemChild, @LFetched) = S_OK) do begin
LShItemChild.GetDisplayName(SIGDN_NORMALDISPLAY, LAppName);
if StrLen(LAppName) = 0 then Continue;
LShItemChild.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, LAppIDPath);
if StrLen(LAppIDPath) = 0 then Continue;
LListItem := ListView1.Items.Add;
LListItem.Caption := LAppName;
LListItem.SubItems.Add(LAppIDPath);
// ファイルの項目識別子の値からアイコンを取得
LPath := LAppIDPath;
LPath := 'shell:AppsFolder' + PathDelim + LPath;
LpidlFull := ILCreateFromPath(PWideChar(LPath));
SHGetFileInfo(Pointer(LpidlFull),
0,
LSHFileInfo,
SizeOf(TSHFileInfo),
SHGFI_PIDL or SHGFI_ICON or SHGFI_SMALLICON);
LIcon := TIcon.Create;
try
LIcon.Handle := LSHFileInfo.hIcon;
LListItem.ImageIndex := ListView1.SmallImages.AddIcon(LIcon);
finally
FreeAndNil(LIcon);
LShItemChild := nil;
CoTaskMemFree(LpidlFull);
end;
end;
finally
LEnumItems := nil;
LShItemRoot := nil;
CoTaskMemFree(LpidlRoot);
Screen.Cursor := crDefault;
end;
ListView1.SortType := stText;
ListView1.Items.EndUpdate;
FormResize(nil);
end;
//=============================================================================
// 表示しているアプリ名と AppUserModelId の値をクリップボードにコピー
// TAB 区切りテキスト.エクセルに貼り付け可能
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LStrList : TStringList;
LText : string;
Lindex : Integer;
begin
LStrList := TStringList.Create;
try
for Lindex := 0 to ListView1.Items.Count -1 do begin
LText := ListView1.Items[Lindex].Caption + Char(VK_TAB)
+ ListView1.Items[Lindex].SubItems[0];
LStrList.Add(LText);
end;
Clipboard.AsText := LStrList.Text;
finally
FreeAndNil(LStrList);
end;
end;
//=============================================================================
// TListView の OnDblClick イベント
// ダブルクリックしたら選択しているアプリを起動
// 本 EXE が 32 ビットの場合,起動しない 64 ビットのアプリがある
//=============================================================================
procedure TForm1.ListView1DblClick(Sender: TObject);
var
LItem : TListItem;
LAppId : string;
begin
LItem := ListView1.Selected;
if LItem = nil then Exit;
LAppId := 'shell:AppsFolder' + PathDelim + Litem.SubItems[0];
ShellExecute(0, '', PChar(LAppId), nil, nil, SW_SHOW);
end;
//=============================================================================
// TListView の OnSelectItem イベント
// TListView の Item を選択したら AppUserModelId の値を TEdit に表示
//=============================================================================
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
begin
if Item = nil then Exit;
Edit1.Text := Item.SubItems[0];
end;
//=============================================================================
// フォームの幅を変更したらカラムの幅も変更
//=============================================================================
procedure TForm1.FormResize(Sender: TObject);
begin
ListView1.Columns[1].Width := ListView1.ClientWidth - ListView1.Columns[0].Width;
end;
end.
05_TShell を使用して AppUserModelId の一覧を取得 - Windows 10 |
当サイトには,Windows のシェルの機能を利用するために,タイプライブラリの取り込みで作成して Shell32_TLB.pas を使用するサンプルがいくつかあります.参考として,この Shell32_TLB.pas を使用する例を掲載しておきます.
このサンプルでは,TShell コンポーネントを実行時に生成して使用しています.
このサンプルは,処理が非常に遅いコードです. |
リスト5
TShell を使用して AppUserModelId の一覧を取得.処理に非常に時間がかかる例 |
implementation
uses
Shell32_TLB;
{$R *.DFM}
//=============================================================================
// フォーム生成時の処理
// 一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
Self.Caption := 'AppUserModelId 一覧';
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' 名前';
ListView1.Columns[0].Width := 230;
ListView1.Columns.Add;
ListView1.Columns[1].Caption := 'AppUserModelId';
ListView1.Columns[1].Width := ListView1.ClientWidth - ListView1.Columns[0].Width;
end;
//=============================================================================
// インストールされているアプリをリスト - Windows 10
// TShell を使用して仮想フォルダ AppsFolder 内のリストを取得
// ( アプリ名と AppUserModelId の値を列挙 )
//
// 処理が非常に遅いコード
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LShell : Shell32_TLB.TShell;
LShellFolder : Shell32_TLB.Folder;
LFolderItem : Shell32_TLB.FolderItem;
LListItem : TListItem;
LIndex : Integer;
begin
Screen.Cursor := crHourGlass;
ListView1.Items.BeginUpdate;
ListView1.Items.Clear;
ListView1.RowSelect := True;
LShell := TShell.Create(Self);
try
LShellFolder := LShell.NameSpace('shell:AppsFolder');
for LIndex := 0 to LShellFolder.Items.Count - 1 do begin
LFolderItem := LShellFolder.Items.Item(LIndex);
LListItem := ListView1.Items.Add;
LListItem.Caption := LFolderItem.Name;
LListItem.SubItems.Add(LFolderItem.Path);
end;
finally
FreeAndNil(LShell);
end;
ListView1.SortType := stText;
ListView1.Items.EndUpdate;
Screen.Cursor := crDefault;
end;
LShell.NameSpace('shell:AppsFolder').Items; を実行して,あらかじめリストを取得しておくと処理が速くなります. |
リスト6
TShell を使用して AppUserModelId の一覧を取得.処理を高速化した例 |
implementation
uses
Shell32_TLB;
{$R *.DFM}
//=============================================================================
// フォーム生成時の処理
// 一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
Self.Caption := 'AppUserModelId 一覧';
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' 名前';
ListView1.Columns[0].Width := 230;
ListView1.Columns.Add;
ListView1.Columns[1].Width := ListView1.ClientWidth - ListView1.Columns[0].Width;
ListView1.Columns[1].Caption := 'AppUserModelId';
end;
//=============================================================================
// インストールされているアプリをリスト - Windows 10
// 仮想フォルダ AppsFolder 内のリストを取得
// ( TShell を使用してアプリ名と AppUserModelId の値を列挙 )
//
// 処理が速いコード
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LShell : Shell32_TLB.TShell;
LFolderItems : Shell32_TLB.FolderItems;
LFolderItem : Shell32_TLB.FolderItem;
LListItem : TListItem;
LIndex : Integer;
begin
Screen.Cursor := crHourGlass;
ListView1.Items.BeginUpdate;
ListView1.Items.Clear;
ListView1.RowSelect := True;
LShell := TShell.Create(Self);
try
// あらかじめリストを作成しておく
LFolderItems := LShell.NameSpace('shell:AppsFolder').Items;
for LIndex := 0 to LFolderItems.Count - 1 do begin
LFolderItem := LFolderItems.Item(LIndex);
LListItem := ListView1.Items.Add;
LListItem.Caption := LFolderItem.Name;
LListItem.SubItems.Add(LFolderItem.Path);
end;
finally
FreeAndNil(LShell);
end;
ListView1.SortType := stText;
ListView1.Items.EndUpdate;
Screen.Cursor := crDefault;
end;
仮想リストビューの機能を利用すると更に高速処理になります. |
リスト7
TShell を使用して AppUserModelId の一覧を取得.仮想リストビューを使用して処理高速化する例 |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
Winapi.ShellAPI, Winapi.ActiveX, Winapi.ShlObj, Winapi.CommCtrl, Vcl.ComCtrls;
type
TForm1 = class(TForm)
ListView1: TListView;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure ListView1Data(Sender: TObject; Item: TListItem);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
Generics.Collections,
Generics.Defaults,
Shell32_TLB;
{$R *.DFM}
type
PAppIdItem = ^TAppIdItem;
TAppIdItem = Record
AppName : string;
AppID : string;
IconIdx : Integer;
end;
var
FAppIDList : TList<TAppIdItem>;
//=============================================================================
// フォーム生成時の処理
// 一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
LSHFileInfo : TSHFileInfo ;
LhImgList : THandle;
begin
Self.Caption := 'AppUserModelId 一覧';
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' 名前';
ListView1.Columns[0].Width := 230;
ListView1.Columns.Add;
ListView1.Columns[1].Caption := 'AppUserModelId';
FAppIDList := TList<TAppIdItem>.Create;
// システムのイメージリストのハンドルを取得
LhImgList := SHGetFileInfo('',
0,
LSHFileInfo,
SizeOf(LSHFileInfo),
SHGFI_ICON or SHGFI_SMALLICON or SHGFI_SYSICONINDEX);
ListView_SetImageList(ListView1.Handle, LhImgList, LVSIL_SMALL);
end;
//=============================================================================
// フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FAppIDList);
end;
//=============================================================================
// インストールされているアプリをリスト - Windows 10
// 仮想フォルダ AppsFolder 内のリストを取得
// ( アプリ名と AppUserModelId の値を列挙 )
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LPath : WideString;
LAppItem : TAppIdItem;
LShell : Shell32_TLB.TShell;
LFolderItems : Shell32_TLB.FolderItems;
LFolderItem : Shell32_TLB.FolderItem;
LIndex : Integer;
begin
ListView1.Items.BeginUpdate;
ListView1.Items.Clear;
ListView1.RowSelect := True;
FAppIDList.Clear;
LPath := 'shell:AppsFolder';
LShell := TShell.Create(Self);
try
LFolderItems := LShell.NameSpace(LPath).Items;
for LIndex := 0 to LFolderItems.Count - 1 do begin
LFolderItem := LFolderItems.Item(LIndex);
LAppItem.AppName := LFolderItem.Name;
LAppItem.AppID := LFolderItem.Path;
LAppItem.IconIdx := -1;
FAppIDList.Add(LAppItem);
end;
finally
FreeAndNil(LShell);
end;
//----------------------------------------------------
// リストのソート
// TComparer関係の使用には Generics.Defaults が必要
//----------------------------------------------------
FAppIDList.Sort(TComparer<TAppIdItem>.Construct(
function(const Left, Right: TAppIdItem): Integer
begin
// AppName の値でソート
Result := CompareText(Left.AppName, Right.AppName);
end)
);
ListView1.Items.Count := FAppIDList.Count;
ListView1.Items.EndUpdate;
FormResize(nil);
end;
//=============================================================================
// ListView1 の OnData イベント (仮想リストビューのイベント)
// ListView の各 Item の描画が必要となった場合に発生
// ListView の Item と SubItem に値をセットする
// このイベントは ListView1.OwnerData := True; でないと機能しない
//=============================================================================
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
var
LAppId : string;
LSHFileInfo : TSHFileInfo;
LpidlFull : PItemIDList;
begin
if Item.Index > FAppIDList.Count then Exit;
Item.Caption := FAppIDList[Item.Index].AppName;
Item.SubItems.Add(FAppIDList[Item.Index].AppID);
if Item.ImageIndex > 0 then Exit;
// アイコンの取得と設定
LAppId := 'shell:AppsFolder' + PathDelim + Item.SubItems[0];
LpidlFull := ILCreateFromPath(PChar(LAppId));
SHGetFileInfo(Pointer(LpidlFull),
0,
LSHFileInfo,
SizeOf(LSHFileInfo),
SHGFI_PIDL or SHGFI_SYSICONINDEX);
Item.ImageIndex := LSHFileInfo.iIcon;
CoTaskMemFree(LpidlFull);
end;
//=============================================================================
// フォームの幅を変更したらカラムの幅も変更
//=============================================================================
procedure TForm1.FormResize(Sender: TObject);
begin
ListView1.Columns[1].Width := ListView1.ClientWidth - ListView1.Columns[0].Width;
end;
end.
06_AppUserModelId の値から UWP アプリかを判定 |
IShellItem を使用したサンプルに UWP アプリであるかの判定機能を追加したサンプルです.UWP アプリの判定用に以下の関数を作成して使用しています.
このコードは,システムにインストールされている UWP アプリのフルパッケージ名を列挙して検索しています.システムにインストールされている UWP アプリの全てが AppUserModelId として登録されているわけではありません. |
//-----------------------------------------------------------------------------
// 引数の AppUserModelId の値から UWP アプリかを判定する関数
//
// 以下の値を比較して判定
// (1) HKEY_CURRENT_USER\Software\Classes\ActivatableClasses\Package
// 階層下のキー名からバージョン関係文字列を除いた値
// (2) 引数の AppUserModelId の値
//
// 完全には判定できない.UWP アプリによっては誤認識の可能性あり
//-----------------------------------------------------------------------------
function IsUWPApp(AppID: string): Boolean;
var
LRegistry : TRegistry;
LPkgNameList : TStringList;
LKeyPkgRoot : string;
LPkgName : string;
LIndex : Integer;
begin
Result := False;
LPkgNameList := TStringList.Create;
LRegistry := TRegistry.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CURRENT_USER;
LKeyPkgRoot := 'Software\Classes\ActivatableClasses\Package';
if not LRegistry.OpenKey(LKeyPkgRoot, False) then Exit;
LRegistry.GetKeyNames(LPkgNameList);
LRegistry.CloseKey;
// バージョン関係文字列を削除して検索
for LIndex := 0 to LPkgNameList.Count - 1 do begin
LPkgName := TRegEx.Replace(LPkgNameList[LIndex], '_(.+)_', '_');
if SameText(LPkgName, LeftStr(AppID, Length(LPkgName)))
or (Pos('!', AppID) > 0) then begin
Result := True;
Break;
end;
end;
finally
FreeAndNil(LRegistry);
FreeAndNil(LPkgNameList);
end;
end;
下図は,筆者の環境における実行結果です.フォームに配置した TLabel に UWP アプリであるかを表示します.UWP アプリでない場合は not UWP と表示します. |
|
図9
UWP アプリかを判定 |
起動中のアプリの場合,UWP アプリであるかは以下のどちらかで判定できます. |
- ウィンドウのクラス名が ApplicationFrameWindow である
- プロセスのハンドルから取得した EXE 名が ApplicationFrameHost.exe である
|
07_AppUserModelId のコンテキストメニュー |
前項のサンプルにコンテキストメニューを表示する機能を追加したサンプルです.拙作の TplContextMenu コンポーネントを使用しています.
表示するメニューの項目は,[スタート] メニューの項目におけるコンテキストメニューとは少し違います.本サンプルで表示するコンテキストメニューの内容は,以下のコマンドで AppUserModelId のリストを,エクスプローラで表示した時のコンテキストメニューとほぼ同じです. |
複数のバージョンの Delphi をインストールしている場合,[サンプル] の項目が複数ありますが,AppUserModelId の末尾の方の値で判定できます.[ファイルの場所を開く] でサンプルのフォルダ内を表示できます.フォルダなので [開く] でも動作は同じです.ショートカットは強制的にデスクトップに作成となります. |
|
図10
デスクトップアプリの例 |
|
図11
UWP アプリかの例
- UWP アプリには [ファイルの場所を開く] がない
|
本サンプルのコードです.このサンプルは,仮想リストビューを使用して体感的な表示速度を高速にしています. |
リスト8
AppUserModelId の一覧.UWP アプリかの判定とコンテキストメニュー表示機能付き |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
Winapi.ShellAPI, Winapi.ActiveX, Winapi.ShlObj, Winapi.CommCtrl, Vcl.ComCtrls,
Vcl.Menus, plContextMenu;
type
TForm1 = class(TForm)
ListView1: TListView;
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
Label1: TLabel;
plContextMenu1: TplContextMenu;
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure ListView1DblClick(Sender: TObject);
procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
procedure plContextMenu1Popup(Sender: TObject);
procedure ListView1Data(Sender: TObject; Item: TListItem);
procedure FormDestroy(Sender: TObject);
procedure plContextMenu1BeforeContextShowMenu(Sender: TObject;
MenuHandle: HWND);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
function IsUWPApp(AppID: string): Boolean;
var
Form1: TForm1;
implementation
uses
Vcl.Clipbrd,
System.Win.Registry,
System.StrUtils,
Generics.Collections,
Generics.Defaults,
System.RegularExpressions;
{$R *.DFM}
type
PAppIdItem = ^TAppIdItem;
TAppIdItem = Record
AppName : string;
AppID : string;
IconIdx : Integer;
end;
var
FAppIDList : TList<TAppIdItem>;
//=============================================================================
// フォーム生成時の処理
// 一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
LSHFileInfo : TSHFileInfo ;
LhImgList : THandle;
begin
Self.Caption := 'AppUserModelId 一覧';
Label1.Caption := '';
Button2.Enabled := False;
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' 名前';
ListView1.Columns[0].Width := 230;
ListView1.Columns.Add;
ListView1.Columns[1].Caption := 'AppUserModelId';
FAppIDList := TList<TAppIdItem>.Create;
// システムのイメージリストのハンドルを取得
LhImgList := SHGetFileInfo('',
0,
LSHFileInfo,
SizeOf(LSHFileInfo),
SHGFI_ICON or SHGFI_SMALLICON or SHGFI_SYSICONINDEX);
ListView_SetImageList(ListView1.Handle, LhImgList, LVSIL_SMALL);
end;
//=============================================================================
// フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FAppIDList);
end;
//=============================================================================
// インストールされているアプリをリスト - Windows 10
// 仮想フォルダ AppsFolder 内のリストを取得
// ( アプリ名と AppUserModelId の値を列挙 )
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LPath : WideString;
LpidlRoot : PItemIDList;
LShItemRoot : IShellItem;
LShItemChild : IShellItem;
LEnumItems : IEnumShellItems;
LFetched : LongInt;
LAppName : PWideChar;
LAppIDPath : PWideChar;
LAppItem : TAppIdItem;
begin
ListView1.Items.BeginUpdate;
ListView1.Items.Clear;
ListView1.RowSelect := True;
FAppIDList.Clear;
LpidlRoot := nil;
LShItemRoot := nil;
LShItemChild := nil;
LPath := 'shell:AppsFolder';
try
// ファイルを項目識別子に変換
LpidlRoot := ILCreateFromPath(PChar(LPath));
if ILIsEmpty(LpidlRoot) then Exit;
// フォルダの IShellItem オブジェクトを取得
SHCreateItemFromIDList(LpidlRoot, IShellItem, LShItemRoot);
// ファイル (IShellItem のリストを LEnumItems として取得
LShItemRoot.BindToHandler(nil, BHID_EnumItems, IEnumShellItems, LEnumItems);
// リストの各項目の表示名を調べる
while (LEnumItems.Next(1, LShItemChild, @LFetched) = S_OK) do begin
LShItemChild.GetDisplayName(SIGDN_NORMALDISPLAY, LAppName);
if StrLen(LAppName) = 0 then Continue;
LShItemChild.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, LAppIDPath);
if StrLen(LAppIDPath) = 0 then Continue;
LAppItem.AppName := LAppName;
LAppItem.AppID := LAppIDPath;
LAppItem.IconIdx := -1;
FAppIDList.Add(LAppItem);
end;
//----------------------------------------------------
// リストのソート
// TComparer関係の使用には Generics.Defaults が必要
//----------------------------------------------------
FAppIDList.Sort(TComparer<TAppIdItem>.Construct(
function(const Left, Right: TAppIdItem): Integer
begin
// AppName の値でソート
Result := CompareText(Left.AppName, Right.AppName);
end)
);
ListView1.Items.Count := FAppIDList.Count;
finally
LEnumItems := nil;
LShItemRoot := nil;
CoTaskMemFree(LpidlRoot);
end;
ListView1.Items.EndUpdate;
Button2.Enabled := FAppIDList.Count > 0;
FormResize(nil);
end;
//=============================================================================
// 表示しているアプリ名と AppUserModelId の値をクリップボードにコピー
// TAB 区切りテキスト.エクセルに貼り付け可能
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LStrList : TStringList;
LText : string;
Lindex : Integer;
begin
LStrList := TStringList.Create;
try
for Lindex := 0 to FAppIDList.Count -1 do begin
LText := FAppIDList[Lindex].AppName + Char(VK_TAB)
+ FAppIDList[Lindex].AppID;
LStrList.Add(LText);
end;
Clipboard.AsText := LStrList.Text;
finally
FreeAndNil(LStrList);
end;
end;
//=============================================================================
// TListView の OnDblClick イベント
// ダブルクリックしたら選択しているアプリを起動
// 本 EXE が 32 ビットの場合,起動しない 64 ビットのアプリがある
//=============================================================================
procedure TForm1.ListView1DblClick(Sender: TObject);
var
LItem : TListItem;
LAppId : string;
begin
LItem := ListView1.Selected;
if LItem = nil then Exit;
LAppId := 'shell:AppsFolder' + PathDelim + Litem.SubItems[0];
ShellExecute(0, '', PChar(LAppId), nil, nil, SW_SHOW);
end;
//=============================================================================
// TListView の OnSelectItem イベント
// TListView の Item を選択したら AppUserModelId の値を TEdit に表示
// UWP アプリか非 UWP アプリかの判定結果も表示
//=============================================================================
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
var
LAppId : string;
begin
if Item = nil then Exit;
LAppId := Item.SubItems[0];
Edit1.Text := LAppId;
if IsUWPApp(LAppId) then begin
Label1.Caption := 'UWP';
end else begin
Label1.Caption := 'not UWP';
end;
end;
//=============================================================================
// ListView1 の OnData イベント (仮想リストビューのイベント)
// ListView の各 Item の描画が必要となった場合に発生
// ListView の Item と SubItem に値をセットする
// このイベントは ListView1.OwnerData := True; でないと機能しない
//=============================================================================
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
var
LAppId : string;
LSHFileInfo : TSHFileInfo;
LpidlFull : PItemIDList;
begin
if Item.Index > FAppIDList.Count then Exit;
Item.Caption := FAppIDList[Item.Index].AppName;
Item.SubItems.Add(FAppIDList[Item.Index].AppID);
if Item.ImageIndex > 0 then Exit;
// アイコンの取得と設定
LAppId := 'shell:AppsFolder' + PathDelim + Item.SubItems[0];
LpidlFull := ILCreateFromPath(PChar(LAppId));
SHGetFileInfo(Pointer(LpidlFull),
0,
LSHFileInfo,
SizeOf(LSHFileInfo),
SHGFI_PIDL or SHGFI_SYSICONINDEX);
Item.ImageIndex := LSHFileInfo.iIcon;
CoTaskMemFree(LpidlFull);
end;
//=============================================================================
// フォームの幅を変更したらカラムの幅も変更
//=============================================================================
procedure TForm1.FormResize(Sender: TObject);
begin
ListView1.Columns[1].Width := ListView1.ClientWidth - ListView1.Columns[0].Width;
end;
//=============================================================================
// plContextMenu1 の OnPopup イベント
// コンテキストメニューの表示
//
// 拙作の TplContextMenu コンポーネントをフォームに配置
// フォームの PopupMenu プロパティを plContextMenu1 にしておく
//
// 表示項目は UWP アプリと非 UWP とでは異なる
// [スタート] メニューの項目におけるコンテキストのメニューの項目とは異なる
//=============================================================================
procedure TForm1.plContextMenu1Popup(Sender: TObject);
var
LItem : TListItem;
LAppId : string;
begin
LItem := ListView1.Selected;
if LItem = nil then Exit;
LAppId := Litem.SubItems[0];
plContextMenu1.Directory := 'shell:AppsFolder';
plContextMenu1.Files.Clear;
plContextMenu1.Files.Add(LAppId);
end;
//=============================================================================
// plContextMenu1 の OnBeforeContextShowMenu イベント
// [ピン留めする] 項目を削除
//=============================================================================
procedure TForm1.plContextMenu1BeforeContextShowMenu(Sender: TObject;
MenuHandle: HWND);
var
LCount : Integer;
LCmdIdx : Cardinal;
LBuffer : array [0..MAX_COLUMN_NAME_LEN - 1] of Char;
LIndex : Integer;
begin
LCount := GetMenuItemCount(MenuHandle);
for LIndex := LCount - 1 downto 0 do begin
LCmdIdx := GetMenuItemID(MenuHandle, LIndex);
FillChar(LBuffer, SizeOf(LBuffer), #0);
GetMenuString(MenuHandle, LCmdIdx, @LBuffer, Length(LBuffer), MF_BYCOMMAND);
if ContainsText(LBuffer, 'ピン留めする') then begin
RemoveMenu(MenuHandle, LCmdIdx, MF_BYCOMMAND);
end;
end;
end;
//-----------------------------------------------------------------------------
// 引数の AppUserModelId の値から UWP アプリかを判定する関数
//
// 以下の値を比較して判定
// (1) HKEY_CURRENT_USER\Software\Classes\ActivatableClasses\Package
// 階層下のキー名からバージョン関係文字列を除いた値
// (2) 引数の AppUserModelId の値
//
// 完全には判定できない.UWP アプリによっては誤認識の可能性あり
//-----------------------------------------------------------------------------
function IsUWPApp(AppID: string): Boolean;
var
LRegistry : TRegistry;
LPkgNameList : TStringList;
LKeyPkgRoot : string;
LPkgName : string;
LIndex : Integer;
begin
Result := False;
LPkgNameList := TStringList.Create;
LRegistry := TRegistry.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CURRENT_USER;
LKeyPkgRoot := 'Software\Classes\ActivatableClasses\Package';
if not LRegistry.OpenKey(LKeyPkgRoot, False) then Exit;
LRegistry.GetKeyNames(LPkgNameList);
LRegistry.CloseKey;
// バージョン関係文字列を削除して検索
for LIndex := 0 to LPkgNameList.Count - 1 do begin
LPkgName := TRegEx.Replace(LPkgNameList[LIndex], '_(.+)_', '_');
if SameText(LPkgName, LeftStr(AppID, Length(LPkgName)))
or (Pos('!', AppID) > 0) then begin
Result := True;
Break;
end;
end;
finally
FreeAndNil(LRegistry);
FreeAndNil(LPkgNameList);
end;
end;
end.
サンプルで使用しているコンテキストメニューコンポーネントのイベントを利用すると,ショートカットを任意の場所に作成できます. |
//-----------------------------------------------------------------------------
// 指定場所にショートカットを作成
//
// AszFileName : リンク元のファイルのフルパス名
// ADirectory : ショートカットを作成する場所(ディレクトリ名)
// AWorkDie : 作業ディレクトリ
// AswFileName : ショートカットに付ける名前
// 空文字の場合はリンク先のファイル名+'へのショートカット.lnk'
//
// CreateComObject の使用には uses に System.Win.ComObj が必要
// IShellLink の 使用には uses に Winapi.ShlObj が 必要
// IPersistFile の使用には uses に Winapi.ActiveX が必要
//-----------------------------------------------------------------------------
function CreateShortCutLink(AszFileName: string; ADirectory: string;
AWorkDir: string; AswFileName: string): Boolean;
var
LUnknownObject : IUnknown;
LIShellLink : IShellLink;
LIPersistFile : IPersistFile;
LwFileName : string;
begin
Result := False;
// IShellLink,IPersistFileインターフェイスのオブジェクトを生成
LUnknownObject := CreateComObject(CLSID_SHELLLINK);
LIShellLink := LUnknownObject as IShellLink;
LIPersistFile := LUnknownObject as IPersistFile;
if not Assigned(LIShellLink) or not Assigned(LIPersistFile) then exit;
// リンク元の指定
LIShellLink.SetPath(PChar(AszFileName));
LIShellLink.SetWorkingDirectory(PChar(AWorkDir));
// シュートカットに付ける名前を作成
if AswFileName <> '' then begin
// 本関数の引数に名前の指定があればそれを使用
LwFileName := IncludeTrailingPathDelimiter(ADirectory) +
AswFileName + '.lnk';
end else begin
// 名前の指定がなければデフォルトで以下にする
LwFileName := IncludeTrailingPathDelimiter(ADirectory) +
ExtractFileName(AszFileName) + 'へのショートカット.lnk';
end;
// リンク作成
// IPersistFile.Save メソッドを実行すると結果が反映される
if LIPersistFile.Save(PChar(LwFileName), False) = S_OK then begin
Result := True;
end;
end;
//-----------------------------------------------------------------------------
// 特殊フォルダのフルパスを CSIDL 値から取得する関数
//-----------------------------------------------------------------------------
function GetDirectoryFromCSIDL(AnFolder: Cardinal): string;
var
LBuff : array [0..MAX_PATH-1] of Char;
LiRet : HRESULT;
LDir : string;
begin
Result := '';
FillChar(LBuff, SizeOf(LBuff), #0);
LiRet := SHGetFolderPath(0, AnFolder, 0, SHGFP_TYPE_CURRENT, @LBuff);
if LiRet = S_OK then begin
Result := LBuff;
end;
end;
//=============================================================================
// plContextMenu1 の OnBeforeContextInvoke イベント
// ショートカットを別の場所に作成
//=============================================================================
procedure TForm1.plContextMenu1BeforeContextInvoke(Sender: TObject;
CmdIndex: Integer; VerbNameStr: string; var InvokeFlag: Boolean);
var
LAppIdPath : string;
LDestDir : string;
begin
// 17 はショートカットの作成
if CmdIndex = 17 then begin
LAppIdPath := 'shell:AppsFolder\' + ListView1.Selected.SubItems[0];
LDestDir := ExtractFilePath(Application.ExeName) + '000';
ForceDirectories(LDestDir);
CreateShortCutLink(LAppIdPath, LDestDir, '', ListView1.Selected.Caption);
InvokeFlag := False;
end;
end;
デスクトップにショートカットを作成する場合でも,上のようにコンポーネントのイベントで設定すれば,ショートカットに任意の名前を付けることができます.そのためにはテスクトップのパス名を取得します. |
LDestDir := GetDirectoryFromCSIDL(CSIDL_DESKTOP);
上の方のサンプルで,リストのソートに TListView.SortType := stText; を使用しています.そのソートの結果と本サンプルのソートの結果が異なっています.以下のように,AnsiCompareText 関数を使用して AnsiString 型の文字列としてソートすれば,上のサンプルと同じ結果になります.
AnsiString 型の文字列は,日本語版の Windows では Shift_JIS の文字コードの文字列を意味します.
|
//----------------------------------------------------
// リストのソート
// TComparer関係の使用には Generics.Defaults が必要
// AnsiCompareText を使用すると
// TListView.SortType := stText; と同じになる
//----------------------------------------------------
FAppIDList.Sort(TComparer<TAppIdItem>.Construct(
function(const Left, Right: TAppIdItem): Integer
begin
// AppName の値でソート
Result := AnsiCompareText(Left.AppName, Right.AppName);
end)
);
08_AppUserModelId の UWP アプリだけを列挙 - Windows 10 |
仮想フォルダ AppsFolder のリストにある AppUserModelId の内,UWP アプリだけを列挙するサンプルです.これは,システムにインストールされている UWP アプリの列挙ではありません.アプリケーション識別子 AppUserModelId に User という文字列があるように,現在のユーザがスタートメニューに登録している UWP アプリの列挙です.
フォーム生成時に,システムにインストールされている UWP アプリのフルパッケージ名のリストを作成しておきます.そのリストの中から AppUserModelId の値があるかを検索していきます. |
|
図12
UWP アプリだけを列挙 |
リスト9
AppUserModelId の UWP アプリのみを列挙 |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
Winapi.ShellAPI, Winapi.ActiveX, Winapi.ShlObj, Winapi.CommCtrl, Vcl.ComCtrls;
type
TForm1 = class(TForm)
ListView1: TListView;
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure ListView1DblClick(Sender: TObject);
procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
procedure ListView1Data(Sender: TObject; Item: TListItem);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
Vcl.Clipbrd,
System.Win.Registry,
System.StrUtils,
Generics.Collections,
Generics.Defaults,
System.RegularExpressions;
{$R *.DFM}
type
PAppIdItem = ^TAppIdItem;
TAppIdItem = Record
AppName : string;
AppID : string;
IconIdx : Integer;
end;
var
FAppIDList : TList<TAppIdItem>;
FPkgNameList : TStringList;
//=============================================================================
// フォーム生成時の処理
// 一覧表示用の TListView の設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
LSHFileInfo : TSHFileInfo ;
LhImgList : THandle;
LRegistry : TRegistry;
LKeyPkgRoot : string;
LIndex : Integer;
begin
Self.Caption := 'AppUserModelId 一覧 ( UWP アプリ )';
Button2.Enabled := False;
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' 名前';
ListView1.Columns[0].Width := 230;
ListView1.Columns.Add;
ListView1.Columns[1].Caption := 'AppUserModelId';
FAppIDList := TList<TAppIdItem>.Create;
// システムのイメージリストのハンドルを取得
LhImgList := SHGetFileInfo('',
0,
LSHFileInfo,
SizeOf(LSHFileInfo),
SHGFI_ICON or SHGFI_SMALLICON or SHGFI_SYSICONINDEX);
ListView_SetImageList(ListView1.Handle, LhImgList, LVSIL_SMALL);
// レジストリの以下のキー名のリストを作成
// これが UWP アプリのフルパッケージ名
// このパッケージの全てが AppUserModelId のリストにあるわけではない
// HKEY_CURRENT_USER\Software\Classes\ActivatableClasses\Package
FPkgNameList := TStringList.Create;
LRegistry := TRegistry.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CURRENT_USER;
LKeyPkgRoot := 'Software\Classes\ActivatableClasses\Package';
if not LRegistry.OpenKey(LKeyPkgRoot, False) then Exit;
LRegistry.GetKeyNames(FPkgNameList);
LRegistry.CloseKey;
// バージョン関係文字列を削除しておく
for LIndex := 0 to FPkgNameList.Count - 1 do begin
FPkgNameList[LIndex] := TRegEx.Replace(FPkgNameList[LIndex], '_(.+)_', '_');
end;
finally
FreeAndNil(LRegistry);
end;
end;
//=============================================================================
// フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FAppIDList);
FreeAndNil(FPkgNameList);
end;
//=============================================================================
// インストールされているアプリをリスト - Windows 10
// 仮想フォルダ AppsFolder 内の UWP アプリだけをリスト
// UWP アプリの判定は一部誤認識あり
// ( アプリ名と AppUserModelId の値を列挙 )
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LPath : WideString;
LpidlRoot : PItemIDList;
LShItemRoot : IShellItem;
LShItemChild : IShellItem;
LEnumItems : IEnumShellItems;
LFetched : LongInt;
LAppName : PWideChar;
LAppIDPath : PWideChar;
LAppItem : TAppIdItem;
LPkgName : string;
LUWPFlag : Boolean;
LIndex : Integer;
begin
ListView1.Items.BeginUpdate;
ListView1.Items.Clear;
ListView1.RowSelect := True;
FAppIDList.Clear;
LpidlRoot := nil;
LShItemRoot := nil;
LShItemChild := nil;
LPath := 'shell:AppsFolder';
try
// ファイルを項目識別子に変換
LpidlRoot := ILCreateFromPath(PChar(LPath));
if ILIsEmpty(LpidlRoot) then Exit;
// フォルダの IShellItem オブジェクトを取得
SHCreateItemFromIDList(LpidlRoot, IShellItem, LShItemRoot);
// ファイル (IShellItem のリストを LEnumItems として取得
LShItemRoot.BindToHandler(nil, BHID_EnumItems, IEnumShellItems, LEnumItems);
// リストの各項目の表示名を調べる
while (LEnumItems.Next(1, LShItemChild, @LFetched) = S_OK) do begin
LShItemChild.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, LAppIDPath);
if StrLen(LAppIDPath) = 0 then Continue;
LShItemChild.GetDisplayName(SIGDN_NORMALDISPLAY, LAppName);
if StrLen(LAppName) = 0 then Continue;
// レジストリにパッケージ名が存在するアプリ (UWP アプリ) のみリストに追加
for LIndex := 0 to FPkgNameList.Count - 1 do begin
LPkgName := FPkgNameList[LIndex];
LUWPFlag := (Pos('!', LAppIDPath) > 0)
or SameText(LPkgName, LeftStr(LAppIDPath, Length(LPkgName)));
if LUWPFlag then begin
LAppItem.AppName := LAppName;
LAppItem.AppID := LAppIDPath;
LAppItem.IconIdx := -1;
FAppIDList.Add(LAppItem);
Break;
end;
end;
end;
//----------------------------------------------------
// リストのソート
// TComparer関係の使用には Generics.Defaults が必要
//----------------------------------------------------
FAppIDList.Sort(TComparer<TAppIdItem>.Construct(
function(const Left, Right: TAppIdItem): Integer
begin
// AppName の値でソート
Result := CompareText(Left.AppName, Right.AppName);
end)
);
ListView1.Items.Count := FAppIDList.Count;
finally
LEnumItems := nil;
LShItemRoot := nil;
CoTaskMemFree(LpidlRoot);
end;
ListView1.Items.EndUpdate;
Button2.Enabled := FAppIDList.Count > 0;
FormResize(nil);
end;
//=============================================================================
// 表示しているアプリ名と AppUserModelId の値をクリップボードにコピー
// TAB 区切りテキスト.エクセルに貼り付け可能
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LStrList : TStringList;
LText : string;
Lindex : Integer;
begin
LStrList := TStringList.Create;
try
for Lindex := 0 to FAppIDList.Count -1 do begin
LText := FAppIDList[Lindex].AppName + Char(VK_TAB)
+ FAppIDList[Lindex].AppID;
LStrList.Add(LText);
end;
Clipboard.AsText := LStrList.Text;
finally
FreeAndNil(LStrList);
end;
end;
//=============================================================================
// TListView の OnDblClick イベント
// ダブルクリックしたら選択しているアプリを起動
// 本 EXE が 32 ビットの場合,起動しない 64 ビットのアプリがある
//=============================================================================
procedure TForm1.ListView1DblClick(Sender: TObject);
var
LItem : TListItem;
LAppId : string;
begin
LItem := ListView1.Selected;
if LItem = nil then Exit;
LAppId := 'shell:AppsFolder' + PathDelim + Litem.SubItems[0];
ShellExecute(0, '', PChar(LAppId), nil, nil, SW_SHOW);
end;
//=============================================================================
// TListView の OnSelectItem イベント
// TListView の Item を選択したら AppUserModelId の値を TEdit に表示
//=============================================================================
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
var
LAppId : string;
begin
if Item = nil then Exit;
LAppId := Item.SubItems[0];
Edit1.Text := LAppId;
end;
//=============================================================================
// ListView1 の OnData イベント (仮想リストビューのイベント)
// ListView の各 Item の描画が必要となった場合に発生
// ListView の Item と SubItem に値をセットする
// このイベントは ListView1.OwnerData := True; でないと機能しない
//=============================================================================
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
var
LAppId : string;
LSHFileInfo : TSHFileInfo;
LpidlFull : PItemIDList;
begin
if Item.Index > FAppIDList.Count then Exit;
Item.Caption := FAppIDList[Item.Index].AppName;
Item.SubItems.Add(FAppIDList[Item.Index].AppID);
if Item.ImageIndex > 0 then Exit;
// アイコンの取得と設定
LAppId := 'shell:AppsFolder' + PathDelim + Item.SubItems[0];
LpidlFull := ILCreateFromPath(PChar(LAppId));
SHGetFileInfo(Pointer(LpidlFull),
0,
LSHFileInfo,
SizeOf(LSHFileInfo),
SHGFI_PIDL or SHGFI_SYSICONINDEX);
Item.ImageIndex := LSHFileInfo.iIcon;
CoTaskMemFree(LpidlFull);
end;
//=============================================================================
// フォームの幅を変更したらカラムの幅も変更
//=============================================================================
procedure TForm1.FormResize(Sender: TObject);
begin
ListView1.Columns[1].Width := ListView1.ClientWidth - ListView1.Columns[0].Width;
end;
end.
09_デスクトップ
アプリの EXE 等のフルパスを取得 |
仮想フォルダ AppsFolder のリストにある AppUserModelId の内,デスクトップアプリの実行形式ファイル (EXE ファイル等) のフルパスを取得するサンプルです.
仮想フォルダ AppsFolder をエクスプローラで表示すると,名前と AppUserModelId の値があります.したがって,「shell:AppsFolder\AppUserModelId の値」が各々のアプリのシェル名前空間におけるフルパスということになりますが,ここでは,その AppUserModelId のアプリを起動した時,あるいは [ファイルの場所を開く] で表示するファイルのフルパスを取得します.
デスクトップアプリの AppUserModelId の値に GUID の文字列がありますが,それらがレジストリのキーとして登録されているわけではありません.システムが自動的に処理します.そのため,AppUserModelId の値から EXE 等のフルパスを直接知ることはできません. |
// アプリケーション識別子としてのメモ帳のシェル名前空間における絶対パス
// 通常のファイルシステムにおけるパスは存在しない
shell:AppsFolder\{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\notepad.exe
仮想フォルダ AppsFolder のリストにあるアプリは,スタートメニューに登録されています.その内,非 UWP アプリ,つまりテスクトップアプリは以下の場所にショートカットファイルがあります.本サンプルは,それらのショートカットから AppUserModelId と同じ名前のファイルを探して,そのショートカットのリンク先を取得します.取得した値をレコード型の各メンバーにセットし,そのレコード型をリストに追加していきます.
以下のフォルダ内には,UWP アプリのショートカットはありません.
|
// 現在のユーザ固有
%APPDATA%\Microsoft\Windows\Start Menu\Programs
// ユーザ共通
%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs
以下は全て
64 版の Windows 10 における実行結果です.
選択したアプリの実行ファイルのフルパスを TEdit に表示します.ダブルクリックすると,ShellExecut 関数でその実行ファイルを起動するようになっています.
筆者はフォルトビューアをスタートメニューに追加しています (ピン留めではない).そのフォートビューアの EXE 名を取得したのが下図です.コマンドラインの引数も取得するようにしているので,フォトビューアを起動できます. |
|
図13
フォトビューア
|
64 ビット版の Google Chrome の例です.EXE のフルパスに Program Files (x86) があります.これは 32 ビットの EXE がある場所です.したがって,この Google Chrome は起動しません.本サンプルを 64 ビットの EXE にすれば起動します.
この仕様では寂しいので,2021年10月26日の更新で,ファイルシステムのリダイレクト機能を無効にして,システム関係のファイルの場合はフォルダ名を変更する処理を追加しました.その結果,本サンプルが 32 ビットの EXE でも 64 ビット版の Google Chrome が起動するようになっています.ただし,TEdit には,取得した EXE のフルパスをそのまま表示するようにしています. |
|
図14
Google Chrome
- 64 ビットの Google Chrome
- 本サンプルは 32 ビット
- 起動できない
- 64 ビットの Windows10 での実行結果
|
|
図15
Google Chrome
- 64 ビットの Google Chrome
- 本サンプルは 64 ビット
- 起動可能
- 64 ビットの Windows10 での実行結果
|
メモ帳の例です.取得した EXE のフルパスに System32 があります.System32 階層下には 64 ビットのアプリの EXE があります.2021年10月26日の更新でリダイレクトの機能を無効したので,本サンプルが 32 ビットの EXE でも 64 ビットの EXE であっても,64 ビットの版のメモ帳が起動します.32 ビットのアプリの EXE は SysWOW64 フォルダ階層下にあります.
ファイルシステムのリダイレクトを無効ににしない場合は,本サンプルが 32 ビットの EXE の時は 32 ビット版メモ帳が,本サンプルが 64 ビットの EXE の時は 64 ビット版のメモ帳が起動します. |
C:\windows\system32\notepad.exe // 64 ビット版のメモ帳
C:\windows\sysWOW64\notepad.exe // 32 ビット版のメモ帳
|
図16
メモ帳 |
下図は筆者の Windows 10 のスタートメニューの一部です.[設定] が 2 つあります.左の [設定] アイコンは UWP アプリの [設定] です.右の [設定] は従来のコントロールパネルが起動します.コントロールパネルは,確かに各種の設定用のダイアログですが,AppUerModelId の [設定] は UWP アプリです.この状態だと AppUserModelId のコントロールパネルが検出できません.そこで本サンプルでは,その対策用の処理をしています.
コントロールパネルと設定アプリは,以下のコマンドでも起動できます. |
control // コントロールパネル
shell:ControlPanelFolder // コントロールパネル
ms-settings: // 設定
|
図17
2 つの [設定] とコントロールパネル
- 左の [設定] アイコンは UWP アプリ
- 右は従来の [コントロールパネル]
|
[ファイル名を指定して実行] の実行可能なファイルは取得できません.
[ファイル名を指定して実行] のダイアログは以下のコマンドで起動できます.このコマンドは,shell32.dll 内の 61 番目の関数を実行します.この 61 番目の関数が [ファイル名を指定して実行] のダイアログを表示する関数です.何番目かを示す値を序数と言います. [ファイル名を指定して実行] というプログラムのファイルは存在しません.
フォトビューアも rundll32.exe を使用して起動しますが,フォトビューアには具体的なファイルがあります. |
rundll32.exe shell32.dll,#61 |
[ファイル名を指定して実行] のダイアログを ShellExecute 関数で起動する場合,以下のようなコードでも起動できます. |
//=============================================================================
// [ファイル名を指定して実行] のダイアログを ShellExecute 関数で起動
// ダイアログの表示位置は画面の左下ではない
// uses に Winapi.ShellAPI が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LCmd : string;
LArg : string;
begin
LCmd := 'rundll32.exe';
LArg := 'shell32.dll,#61';
ShellExecute(0, '', PChar(LCmd), PChar(LArg), nil, SW_SHOW);
end;
//=============================================================================
// [ファイル名を指定して実行] のダイアログを ShellExecute 関数で起動
// Shell コマンドを使用する例
// ダイアログの表示位置は画面の左下
// uses に Winapi.ShellAPI が必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LCmd : string;
begin
LCmd := 'shell:::{2559a1f3-21d7-11d4-bdaf-00c04f60b9f0}';
ShellExecute(0, '', PChar(LCmd), nil, nil, SW_SHOW);
end;
リスト10
AppUserModelId の非 UWP アプリの EXE のフルパスを取得.スタートメニューのリストから検索 |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
Winapi.ShellAPI, Winapi.ActiveX, Winapi.ShlObj, Winapi.CommCtrl, Vcl.ComCtrls,
System.StrUtils, System.IOUtils, System.Types, System.Win.ComObj,
Generics.Collections, Generics.Defaults;
type
TForm1 = class(TForm)
ListView1: TListView;
Button1: TButton;
Edit1: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure ListView1DblClick(Sender: TObject);
procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
procedure ListView1Data(Sender: TObject; Item: TListItem);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
PStartMenuItem = ^TStartMenuItem;
TStartMenuItem = Record
Name : string;
FullPath : string;
end;
function Wow64DisableWow64FsRedirection(var flgRedirec: LongBool): LongBool; stdcall;
external 'Kernel32.dll' Name 'Wow64DisableWow64FsRedirection';
function GetStartMenuShortCutList(AShortCutList: TList<TStartMenuItem>): Integer;
function GetLinkPathName(ALinkFilePath: string; var ArgStr: string;
var WorkDir: string): string;
function ExpandEnvPath(AEnvPath: string): string;
var
Form1: TForm1;
implementation
uses
System.Win.Registry,
System.RegularExpressions;
{$R *.DFM}
type
PAppIdItem = ^TAppIdItem;
TAppIdItem = Record
AppName : string;
AppID : string;
LinkPath : string;
ExePath : string;
ArgStr : string;
WorkDir : string;
IconIdx : Integer;
end;
var
FAppIDList : TList<TAppIdItem>;
//=============================================================================
// フォーム生成時の処理
// 一覧表示用の TListView の設定等
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
LSHFileInfo : TSHFileInfo ;
LhImgList : THandle;
LDirect : LongBool;
begin
// ファイルシステムのリダイレクト機能を無効化
Wow64DisableWow64FsRedirection(LDirect);
Self.Caption := 'AppUserModelId 一覧 ( デスクトップアプリの EXE )';
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' 名前';
ListView1.Columns[0].Width := 230;
ListView1.Columns.Add;
ListView1.Columns[1].Caption := 'AppUserModelId';
FAppIDList := TList<TAppIdItem>.Create;
// システムのイメージリストのハンドルを取得
LhImgList := SHGetFileInfo('',
0,
LSHFileInfo,
SizeOf(LSHFileInfo),
SHGFI_ICON or SHGFI_SMALLICON or SHGFI_SYSICONINDEX);
ListView_SetImageList(ListView1.Handle, LhImgList, LVSIL_SMALL);
end;
//=============================================================================
// フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FAppIDList);
end;
//=============================================================================
// インストールされているアプリをリスト - Windows 10
// 仮想フォルダ AppsFolder 内のリスト作成
// アプリ名と AppUserModelId の値を列挙
// スタートメニューのリストからアプリのリンク先も取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LShortCutList : TList<TStartMenuItem>;
LPath : WideString;
LpidlRoot : PItemIDList;
LShItemRoot : IShellItem;
LShItemChild : IShellItem;
LEnumItems : IEnumShellItems;
LFetched : LongInt;
LAppName : PWideChar;
LAppIDPath : PWideChar;
LAppItem : TAppIdItem;
LShortName : string;
LExePath : string;
LArgStr : string;
LWorkDir : string;
LIndex : Integer;
LListIndex : Integer;
begin
ListView1.Items.BeginUpdate;
ListView1.Items.Clear;
ListView1.RowSelect := True;
// スタートメニューのショートカットファイルのリストを作成
LShortCutList := TList<TStartMenuItem>.Create;
GetStartMenuShortCutList(LShortCutList);
FAppIDList.Clear;
LpidlRoot := nil;
LShItemRoot := nil;
LShItemChild := nil;
LPath := 'shell:AppsFolder';
try
// ファイルを項目識別子に変換
LpidlRoot := ILCreateFromPath(PChar(LPath));
if ILIsEmpty(LpidlRoot) then Exit;
// フォルダの IShellItem オブジェクトを取得
SHCreateItemFromIDList(LpidlRoot, IShellItem, LShItemRoot);
// ファイル (IShellItem のリストを LEnumItems として取得
LShItemRoot.BindToHandler(nil, BHID_EnumItems, IEnumShellItems, LEnumItems);
// リストの各項目の表示名を調べる
while (LEnumItems.Next(1, LShItemChild, @LFetched) = S_OK) do begin
LShItemChild.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, LAppIDPath);
if StrLen(LAppIDPath) = 0 then Continue;
LShItemChild.GetDisplayName(SIGDN_NORMALDISPLAY, LAppName);
if StrLen(LAppName) = 0 then Continue;
// レコード型の初期化と AppUserModelId とその表示名の代入
LAppItem := Default(TAppIdItem);
LAppItem.AppName := LAppName;
LAppItem.AppID := LAppIDPath;
LAppItem.IconIdx := -1;
// ショートカットのリストから AppUserModelId と同じ名前を検出
// 重複名を検出しないように,検出したショートカットの項目を削除
for LIndex := LShortCutList.Count - 1 downto 0 do begin
LListIndex := LShortCutList.Count - LIndex - 1;
LShortName := LShortCutList[LListIndex].Name;
// 筆者の環境ではショートカットの「設定」がコントロールパネルになっている
// 「設定」は本来 UWP のアプリ名
// その対策用の処理コード
if (LShortName = 'コントロール パネル') or (LShortName = '設定') then begin
LExePath := GetLinkPathName(LShortCutList[LListIndex].FullPath,
LArgStr,
LWorkDir);
if LExePath = '' then Continue;
LShortName := 'コントロール パネル';
end;
if SameText(LAppName, LShortName) then begin
LAppItem.LinkPath := LShortCutList[LListIndex].FullPath;
LExePath := GetLinkPathName(LAppItem.LinkPath, LArgStr, LWorkDir);
LAppItem.ExePath := LExePath;
LAppItem.ArgStr := LArgStr;
LAppItem.WorkDir := LWorkDir;
LShortCutList.Delete(LListIndex);
Break;
end;
end;
FAppIDList.Add(LAppItem);
end;
//----------------------------------------------------
// リストのソート
// TComparer関係の使用には Generics.Defaults が必要
//----------------------------------------------------
FAppIDList.Sort(TComparer<TAppIdItem>.Construct(
function(const Left, Right: TAppIdItem): Integer
begin
// AppName の値でソート
Result := CompareText(Left.AppName, Right.AppName);
end)
);
ListView1.Items.Count := FAppIDList.Count;
finally
LEnumItems := nil;
LShItemRoot := nil;
CoTaskMemFree(LpidlRoot);
FreeAndNil(LShortCutList);
end;
ListView1.Items.EndUpdate;
FormResize(nil);
end;
//=============================================================================
// TListView の OnDblClick イベント
// ダブルクリックしたら選択しているアプリを起動
// TEdit に表示している EXE または実行可能ファイルを実行
//
// 筆者の 64 ビット版の Windows 10 では
// Delhi 6 / 7 起動時に rtl60.bpl 等の読み込みエラーが発生
// IDE からではなく EXE から起動すればエラーは発生しない
// ショートカットを実行する場合も現象は同じ
//=============================================================================
procedure TForm1.ListView1DblClick(Sender: TObject);
var
LItem : TListItem;
LExePath : string;
LArgStr : string;
LRes : HRESULT;
LText : string;
begin
LItem := ListView1.Selected;
if LItem = nil then Exit;
LExePath := ExpandEnvPath(FAppIDList[LItem.Index].ExePath);
LArgStr := ExpandEnvPath(FAppIDList[LItem.Index].ArgStr);
if LExePath = '' then begin
LText := '実行可能なファイルがありません。';
MessageBox(Handle, PChar(LText), '情報', MB_ICONINFORMATION);
end else begin
// システム関係フォルダ内のファイルが存在しない場合の処理
// フォルダ名を決め打ちにしているので将来の保証なし
if not FileExists(LExePath) then begin
if SizeOf(THandle) = 4 then begin
LExePath := ReplaceText(LExePath, 'Program Files (x86)', 'Program Files');
LExePath := ReplaceText(LExePath, 'SysWOW64', 'System32');
end else begin
LExePath := ReplaceText(LExePath, 'Program Files', 'Program Files (x86)');
LExePath := ReplaceText(LExePath, 'System32', 'SysWOW64');
end;
end;
LRes := ShellExecute(0, '', PChar(LExePath), PChar(LArgStr), nil, SW_SHOW);
if LRes <= 32 then begin
LText := SysErrorMessage(GetLastError);
MessageBox(Handle, PChar(LText), '情報', MB_ICONWARNING);
end;
end;
end;
//=============================================================================
// TListView の OnSelectItem イベント
// TListView の Item を選択したら EXE のフルパスを取得して TEdit に表示
// UWP アプリは空白とする
//=============================================================================
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
var
LExePath : string;
LArgStr : string;
begin
if Item = nil then Exit;
LExePath := FAppIDList[Item.Index].ExePath;
LArgStr := FAppIDList[Item.Index].ArgStr;
Edit1.Text := LExePath;
if LArgStr <> '' then Edit1.Text := LExePath + ' ' + LArgStr;
end;
//=============================================================================
// ListView1 の OnData イベント (仮想リストビューのイベント)
// ListView の各 Item の描画が必要となった場合に発生
// ListView の Item と SubItem に値をセットする
// このイベントは ListView1.OwnerData := True; でないと機能しない
//=============================================================================
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
var
LAppId : string;
LSHFileInfo : TSHFileInfo;
LpidlFull : PItemIDList;
begin
if Item.Index > FAppIDList.Count then Exit;
Item.Caption := FAppIDList[Item.Index].AppName;
Item.SubItems.Add(FAppIDList[Item.Index].AppID);
if Item.ImageIndex > 0 then Exit;
// アイコンの取得と設定
LAppId := 'shell:AppsFolder' + PathDelim + Item.SubItems[0];
LpidlFull := ILCreateFromPath(PChar(LAppId));
SHGetFileInfo(Pointer(LpidlFull),
0,
LSHFileInfo,
SizeOf(LSHFileInfo),
SHGFI_PIDL or SHGFI_SYSICONINDEX);
Item.ImageIndex := LSHFileInfo.iIcon;
CoTaskMemFree(LpidlFull);
end;
//=============================================================================
// フォームの幅を変更したらカラムの幅も変更
//=============================================================================
procedure TForm1.FormResize(Sender: TObject);
begin
ListView1.Columns[1].Width := ListView1.ClientWidth - ListView1.Columns[0].Width;
end;
//-----------------------------------------------------------------------------
// スタートメニューのショートカットファイルのリストを作成
// TStartMenuItem はショートカットの名前とパス名格納用のレコード型
//
// このリストには UWP アプリはない
//-----------------------------------------------------------------------------
function GetStartMenuShortCutList(AShortCutList: TList<TStartMenuItem>): Integer;
const
LINK_SUB_DIR = 'Microsoft\Windows\Start Menu\Programs';
var
LRootDir : string;
LListItem : TStartMenuItem;
LOption : TSearchOption;
LFilesList : TStringDynArray;
LpidlFull : PItemIDList;
LShelItem : IShellItem;
LFileName : PWideChar;
LDirIndex : Integer;
LIndex : Integer;
begin
AShortCutList.Clear;
for LDirIndex := 0 to 1 do begin
if LDirIndex = 0 then begin
// 現在のユーザ固有
LRootDir := GetEnvironmentVariable('APPDATA') + PathDelim + LINK_SUB_DIR;
end else begin
// ユーザ共通
LRootDir := GetEnvironmentVariable('PROGRAMDATA') + PathDelim + LINK_SUB_DIR;
end;
LOption := TSearchOption.soAllDirectories;
LFilesList := TDirectory.GetFiles(LRootDir, '*.lnk', LOption);
// ショートカットのエクスプローラにおける表示名を取得
// 例 : notepad.lnk --> メモ帳
for LIndex := 0 to Length(LFilesList) - 1 do begin
LpidlFull := ILCreateFromPath(PWideChar(LFilesList[LIndex]));
SHCreateItemFromIDList(LpidlFull, IShellItem, LShelItem);
LShelItem.GetDisplayName(SIGDN_NORMALDISPLAY, LFileName);
CoTaskMemFree(LpidlFull);
// レコード型のメンバに代入して,そのレコードをリストに追加
LListItem.Name := LFileName;
LListItem.FullPath := LFilesList[LIndex];
AShortCutList.Add(LListItem);
end;
end;
//----------------------------------------------------
// リストのソート
// TComparer関係の使用には Generics.Defaults が必要
//----------------------------------------------------
AShortCutList.Sort(TComparer<TStartMenuItem>.Construct(
function(const Left, Right: TStartMenuItem): Integer
begin
// Name の値でソート
Result := CompareText(Left.Name, Right.Name);
end)
);
Result := AShortCutList.Count;
end;
//-----------------------------------------------------------------------------
// ショートカット (拡張子.lnk) のリンク先のフルパスとコマンドライン引数を取得
// 引数の ArgStr はコマンドラインの引数
// 引数の WorkDir は作業ディレクトリ
//
// 64 版の Windows の場合,32 ビットの EXE と 64 ビットの EXE から取得した場合,
// 以下の値が含まれる時の結果に違い (リダイレクト) があることに注意
// %ProgramFiles% (Program Files と Program Files (x86)
// System32 と SysWOW64
//-----------------------------------------------------------------------------
function GetLinkPathName(ALinkFilePath: string; var ArgStr: string;
var WorkDir: string): string;
var
LIShellLink : IShellLink;
LIPersistFile : IPersistFile;
LFileData : TWin32FindData;
LFilePath : array [0..MAX_PATH - 1] of Char;
LArgPath : array [0..MAX_PATH - 1] of Char;
LWorkDir : array [0..MAX_PATH - 1] of Char;
begin
Result := '';
ArgStr := '';
WorkDir := '';
// IShellLink, IPersistFile インターフェイスのオブジェクトを生成
LIShellLink := CreateComObject(CLSID_ShellLink) as IShellLink;
LIPersistFile := LIShellLink as IPersistFile;
if Succeeded(LIPersistFile.Load(PChar(ALinkFilePath), STGM_READ)) then begin
// ショートカットファイルのリンク先を取得
FillChar(LFilePath, SizeOf(LFilePath), #0);
LIShellLink.GetPath(@LFilePath, MAX_PATH, LFileData, SLGP_UNCPRIORITY);
// コマンドラインの引数の値を取得
FillChar(LArgPath, SizeOf(LArgPath), #0);
LIShellLink.GetArguments(@LArgPath, MAX_PATH);
// 作業ディレクトリの値を取得
FillChar(LWorkDir, SizeOf(LWorkDir), #0);
LIShellLink.GetWorkingDirectory(@LWorkDir, MAX_PATH);
// 関数の戻り値にセット
ArgStr := LArgPath;
WorkDir := LWorkDir;
Result := LFilePath;
end;
end;
//-----------------------------------------------------------------------------
// 環境変数を含むパス名を実パスに変換する関数
//-----------------------------------------------------------------------------
function ExpandEnvPath(AEnvPath: string): string;
var
LBuff : array [0..MAX_PATH - 1] of Char;
LLen : Integer;
begin
FillChar(LBuff, SizeOf(LBuff), #0);
LLen := Length(LBuff);
ExpandEnvironmentStrings(PChar(AEnvPath), @LBuff, LLen);
Result := LBuff;
end;
end.
上のコードでは項目識別子を使用して表示名を取得しています.実ディレクトリであるスタートメニューフォルダ内のリンクファイルの表示名は,SHGetFileInfo 関数で取得できます. |
|
図18
SHGetFileInfo 関数で表示名を取得
- 「表示名」は,エクスプローラの「名前」欄のテキストのこと
|
//=============================================================================
// ファイルの表示名を取得
// SHGetFileInfo 関数 (Winapi.ShellAPI) を使用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LLinkPath : string;
LFileInfo : TSHFileInfo;
LDispName : string;
begin
LLinkPath := 'C:\ProgramData\Microsoft\Windows\Start Menu'
+ '\Programs\Accessories\Notepad.lnk';
//表示名の取得
SHGetFileInfo(PChar(LLinkPath),
0,
LFileInfo,
SizeOf(LFileInfo),
SHGFI_DISPLAYNAME);
LDispName := LFileInfo.szDisplayName;
MessageBox(Handle, Pchar(LDispName), '結果', MB_ICONINFORMATION);
end;
デスクトップアプリは,EXE 名で起動しても AppUserMOdelId の値で起動しても起動するのは同じ EXE です.デスクトップアプリを ShellExecuteEx 関数で起動すると,プロセス ID の値が取得できます.プロセス ID の値がプロセスのハンドルが取得できます.プロセスのハンドルがら,起動した EXE のフルパスが取得できます. |
[備考 1]
ファイルシステムのリダイレクトが問題となるのは 64 ビット版の Windows です.リダイレクトによる現象を回避するには,64 ビット版の Windows では 64 ビットの EXE を作成して,リダイレクトの機能を無効にします. |
[備考 2]
ZIP 書庫には,特定の AppUserModelId の値から,デスクトップアプリの EXE のフルパスを取得するサンプルを同梱しています. |
UWP アプリの EXE のフルパスを取得するサンプルです.UWP アプリ関係ファイルはインストール先のパッケージ (フォルダ) 内にあります.以下のリンクの記事のサンプルは,パッケージ内のアイコン画像の場所を調べる例です.本サンプルは,アイコン画像がある場所ではなく,EXE のパス名を取得するサンプルとなっています. |
手順は上のリンクの記事のサンプルと同じです.
電卓の AppUserModelId の値とパッケージ名は以下のようになっています.赤の下線部分を削除すると両者は同じ値になります.本サンプルはこのことを利用して AppUserModelId の値からパッケージ名を検索します.したがって,この規則に従っていない UWP アプリは検出できません.パッケージ名の下線部分はバージョン関係の値です.アプリのバージョンが変わると,この部分も変わる可能性があります. |
|
図19
AppUserModelId の値とパッケージ名 |
下図が実行結果です.TEdit に別の AppUserModelId の値を入力して実行すれば,その UWP アプリのパッケージ名と EXE 名が取得できます.取得した EXE を起動しようとしても起動できません.UWP アプリは一部のアプリを除いて EXE からの起動はできません.
デスクトップアプリを識別するのは EXE 名です.プログラムからデスクトップアプリを起動するには EXE のパス名を使用します.UWP アプリをプログラムから起動する場合,一般的にはアプリケーション識別子である AppUserModelId を使用しますが,電卓の場合,以下のコマンドでも起動できます. |
|
図20
UWP アプリの EXE 名 |
|
図21
EXE は起動できない |
リスト11
UWP アプリのパッケージ名と EXE のパス名を,AppUserModelId の値から取得 |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Memo1: TMemo;
Button2: TButton;
Button3: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses
System.Win.Registry,
Generics.Collections,
Generics.Defaults,
System.StrUtils,
System.IOUtils,
System.Types,
Winapi.ShellAPI,
System.RegularExpressions;
{$R *.DFM}
type
PUwpPkgItem = ^TUwpPkgItem;
TUwpPkgItem = record
RegKeyName : string;
PgkFullPath : string;
end;
var
FUwpPkgList : TList<TUwpPkgItem>;
FPkgExeFullPath : string;
FPkgFullPath : string;
//=============================================================================
// フォーム生成時の処理
//
// レジストリのパッケージのキー名のリストを作成
// インストール先のフルパッケージ名 (ディレクトリ名) も取得しておく
// 結果はレコード型のデータとして格納
// そのレコード型のデータをパッケージ情報格納用のリストに追加していく
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
LRegistry : TRegistry;
LKeyPkgRoot : string;
LKeyPkg : string;
LKeyList : TStringList;
LPkgPath : string;
LPkgItem : TUwpPkgItem;
LIndex : Integer;
begin
Button2.Enabled := False;
Button3.Enabled := False;
// UWP アプリ 電卓の AppUserModelId の値
Edit1.Text:= 'Microsoft.WindowsCalculator_8wekyb3d8bbwe!App';
FUwpPkgList := TList<TUwpPkgItem>.Create;
LRegistry := TRegistry.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CLASSES_ROOT;
LKeyPkgRoot := 'Local Settings\Software\Microsoft\Windows\CurrentVersion'
+ '\AppModel\Repository\Packages';
LKeyList := TStringList.Create;
try
if not LRegistry.OpenKey(LKeyPkgRoot, False) then Exit;
LRegistry.GetKeyNames(LKeyList);
LRegistry.CloseKey;
for LIndex := 0 to LKeyList.Count - 1 do begin
LKeyPkg := LKeyPkgRoot + PathDelim + LKeyList[LIndex];
if LRegistry.OpenKey(LKeyPkg, False) then begin
LPkgPath := LRegistry.ReadString('PackageRootFolder');
if LPkgPath <> '' then begin
LPkgItem.RegKeyName := LKeyList[LIndex];
LPkgItem.PgkFullPath := LPkgPath;
FUwpPkgList.Add(LPkgItem);
end;
end;
LRegistry.CloseKey;
end;
finally
FreeAndNil(LKeyList);
end;
finally
FreeAndNil(LRegistry);
end;
end;
//=============================================================================
// フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FUwpPkgList);
end;
//=============================================================================
// 指定した AppUserModelId のインストールのフルパッケージ名を調べる
// UWP アプリによってはこのコードで取得できない場合あり
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LAppId : string;
LKeyPkg : string;
LFilesList : TStringDynArray;
LpkgPath : string;
LOption : TSearchOption;
LIndex : Integer;
LExeIndex : Integer;
LText : string;
begin
Memo1.Lines.Clear;
// アプリケーション識別子 AppUserModelId の値
LAppId := Trim(Edit1.Text);
for LIndex := 0 to FUwpPkgList.Count - 1 do begin
// バージョン関係文字列を削除して検索
LKeyPkg := TRegEx.Replace(FUwpPkgList[LIndex].RegKeyName, '_(.+)_', '_');
if not SameText(LKeyPkg, LeftStr(LAppId, Length(LKeyPkg))) then Continue;
// EXE のフルパスを取得
// EXE は 2 つ以上ある場合がある
LpkgPath := FUwpPkgList[LIndex].PgkFullPath;
LOption := TSearchOption.soAllDirectories;
LFilesList := TDirectory.GetFiles(LpkgPath, '*.exe', LOption);
Memo1.Lines.Add(FUwpPkgList[LIndex].PgkFullPath);
Memo1.Lines.Add('');
for LExeIndex := 0 to Length(LFilesList) - 1 do begin
Memo1.Lines.Add(ExtractFileName(LFilesList[LExeIndex]));
end;
FPkgExeFullPath := LFilesList[0];
FPkgFullPath := LpkgPath;
Break;
end;
Button2.Enabled := Length(LFilesList) > 0;
Button3.Enabled := Length(LFilesList) > 0;
if Length(LFilesList) = 0 then begin
LText := 'パッケージが見つかりませんでした。';
MessageBox(Handle, PChar(LText), '情報', MB_ICONWARNING);
end;
end;
//=============================================================================
// パッケージ内の EXE を起動してみる
// 起動不可を確認
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LRes : HRESULT;
LText : string;
begin
if FPkgExeFullPath = '' then Exit;
LRes := ShellExecute(0, '', PChar(FPkgExeFullPath), nil, nil, SW_SHOW);
if LRes <= 32 then begin
LText := SysErrorMessage(GetLastError);
MessageBox(Handle, PChar(LText), '情報', MB_ICONWARNING);
end;
end;
//=============================================================================
// パッケージ内をエクスプローラで表示してみる
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
LRes : HRESULT;
LText : string;
begin
if FPkgFullPath = '' then Exit;
LRes := ShellExecute(0, '', PChar(FPkgFullPath), nil, nil, SW_SHOW);
if LRes <= 32 then begin
LText := SysErrorMessage(GetLastError);
MessageBox(Handle, PChar(LText), '情報', MB_ICONWARNING);
end;
end;
end.
UWP アプリは,ShellExecuteEx 関数で起動しても,UWP アプリ自身のプロセス ID の値が取得できません.
EnumWindos 関数で起動を監視すれば,そのコールバック関数内で,起動したアプリの AppUerModelId の値やパッケージ名,EXE 名を取得することができます. |
[備考]
UWP アプリの開発者は,エイリアスとしての EXE を作成,設定したり,URL スキームを設定できます.そのような UWP アプリの場合は,電卓のように,それらのコマンドでアプリを起動できます.
URL スキームは,デスクトップアプリにも実装することができます. |
特定の UWP アプリの URL スキームの値を調べるサンプルです.
UWP アプリのインストール先のパッケージ内に AppxManifest.xml という設定ファイルがあります.このファイルの XML コードから URL スキームの設定を読み取ります.本サンプルでは, AppUserModelId の値からインストール先のパッケージ名 (パス名) を取得する部分を関数化して使用しています.
URL スキームは,どのアプリにも存在するとは限りません.テストに使用した「マップ」アプリには,以下の URL スキームがあります. |
|
図22
「マップ」の URL スキームの値
|
リスト12
パッケージの AppxManifest.xml から,UWP アプリの URL スキームの値を取得 |
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure ListBox1DblClick(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
function GetPackagePathFromAppId(AppId: string): string;
var
Form1: TForm1;
implementation
uses
System.Win.Registry,
Generics.Collections,
System.StrUtils,
System.RegularExpressions,
Winapi.ShellAPI,
Winapi.msxml,
System.Win.ComObj;
{$R *.DFM}
// =============================================================================
// フォーム生成時の処理
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
// UWP アプリ マップの AppUserModelId の値
Edit1.Text := 'Microsoft.WindowsMaps_8wekyb3d8bbwe!App';
end;
// ===========================================================================
// UWP アプリのパッケージから URL スキームの値を取得
// URL スキームは全ての UWP アプリにあるわけではない
//
// uses に Winapi.msxml, System.Win.ComObj が必要
// ===========================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LPkgPath : string;
LFilePath : string;
LStrList : TStringList;
LXMLDomDoc : IXMLDOMDocument;
LDOMNodeList : IXMLDOMNodeList;
LIndex : Integer;
begin
ListBox1.Items.Clear;
// IXMLDOMDocument のインスタンスを生成
LXMLDomDoc := CreateComObject(CLASS_DOMDocument) as IXMLDOMDocument;
LXMLDomDoc.async := False;
LXMLDomDoc.preserveWhiteSpace := True;
// AppUserModelId の値がら UWP アプリのインストール先のパッケージを取得
// 設定関係の XML ファイルを読み込む
// その XML コードを IXMLDOMDocument の XML コードにする
LPkgPath := GetPackagePathFromAppId(Trim(Edit1.Text));
if LPkgPath = '' then Exit;
LFilePath := LPkgPath + PathDelim + 'AppxManifest.xml';
LStrList := TStringList.Create;
try
LStrList.LoadFromFile(LFilePath, TEncoding.UTF8);
LXMLDomDoc.loadXML(LStrList.Text);
LStrList.SaveToFile('AppxManifest.xml', TEncoding.UTF8);
finally
FreeAndNil(LStrList);
end;
// URL スキームの値があるノードを取得
LDOMNodeList := LXMLDomDoc.getElementsByTagName('uap:Protocol/@Name');
if LDOMNodeList <> nil then begin
// URL スキームの値を列挙
for LIndex := 0 to LDOMNodeList.length - 1 do begin
// 末尾に : (コロン) を追加
ListBox1.Items.Add(LDOMNodeList[LIndex].nodeValue + ':');
end;
end;
ListBox1.Items.SaveToFile('URLScheme.txt');
LDOMNodeList := nil;
LXMLDomDoc := nil;
end;
// ===========================================================================
// ListBox1 の OnDblClick イベント
// URL スキームで UWP アプリを起動
// ===========================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
LURLScheme : string;
begin
if ListBox1.ItemIndex < 0 then Exit;
LURLScheme := ListBox1.Items[ListBox1.ItemIndex];
ShellExecute(0, '', PChar(LURLScheme), nil, nil, SW_SHOW);
end;
// -----------------------------------------------------------------------------
// AppUserModelId の値からインストール先のフルパスを取得する関数
// レジストリのキー名のリストから探す
// -----------------------------------------------------------------------------
function GetPackagePathFromAppId(AppId: string): string;
var
LRegistry : TRegistry;
LKeyPkgRoot : string;
LPkgName : string;
LKeyPkg : string;
LKeyList : TStringList;
LPkgPath : string;
LIndex : Integer;
begin
Result := '';
LRegistry := TRegistry.Create;
try
LRegistry.Access := KEY_READ;
LRegistry.RootKey := HKEY_CLASSES_ROOT;
LKeyPkgRoot := 'Local Settings\Software\Microsoft\Windows\CurrentVersion'
+ '\AppModel\Repository\Packages';
LKeyList := TStringList.Create;
try
if not LRegistry.OpenKey(LKeyPkgRoot, False) then Exit;
LRegistry.GetKeyNames(LKeyList);
LRegistry.CloseKey;
for LIndex := 0 to LKeyList.Count - 1 do begin
LKeyPkg := LKeyPkgRoot + PathDelim + LKeyList[LIndex];
if not LRegistry.OpenKey(LKeyPkg, False) then Continue;
LPkgName := LKeyList[LIndex];
LPkgPath := LRegistry.ReadString('PackageRootFolder');
LRegistry.CloseKey;
if LPkgPath = '' then Continue;
LPkgName := TRegEx.Replace(LPkgName, '_(.+)_', '_');
if not SameText(LPkgName, LeftStr(AppId, Length(LPkgName))) then Continue;
Result := LPkgPath;
Break;
end;
finally
FreeAndNil(LKeyList);
end;
finally
FreeAndNil(LRegistry);
end;
end;
end.
上のコードではタグ名を検出していますが,XPATH の機能を使用した方が確実です.XPATH を使用する場合は,以下のように,XPATH の文字列の先頭に / を追加して // とします.
このコードは,現在のドキュメント内の指定階層下の,Name 属性を持つ全ての <uap:Protocol> タグを,IXMLDOMNode オブジェクトとして取得します. |
// URL スキームの値があるノードを取得
// documentElement.selectNodes を使用する場合は先頭に / を追加する
LXPath := '//Applications/Application'
+ '/Extensions/uap:Extension/uap:Protocol/@Name';
LDOMNodeList := LXMLDomDoc.documentElement.selectNodes(LXPath);
[備考]
「設定」アプリの URL スキームは ms-settings: ですが,設定アプリの AppxManifest.xml には,この URL スキームの記述はありません.上のサンプルでは AppxManifest.xml ファイルを保存するようにしています.それをテキストエディタで表示して確認できます. |
|