【Delphi】Explorer を開く

# はじめに

Delphi から Windows の Explorer を開いてみる記事です。

# Explorer を開く
何はともあれ Explorer を Delphi から開いてみます。

## ShellExecute() API
Explorer を開くには `ShellExecute()` API を使うのが簡単です。

 - [ShellExecuteW function (shellapi.h) (docs.microsoft.com)](https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew)

Delphi の場合、`Winapi.ShellAPI` を **uses** に加える事で使えるようになります。

```pascal
  ShellExecute(0, 'open', 'explorer.exe', nil, nil, SW_SHOWNORMAL);
```

定数 `SW_SHOWNORMAL` は `Winapi.Windows` で定義されています。

## Explorer の起動時オプション

Explorer には起動時オプションがあるのですが、もはや動作しなくなったものもあり、Microsoft のドキュメントからは消えています。

 - [Explorer.exe Command-Line Options for Windows XP (support.microsoft.com)](http://web.archive.org/web/20140823211915/http://support.microsoft.com/kb/314853)
 - [Windowsのエクスプローラで目的のフォルダだけを開く (@IT)](https://atmarkit.itmedia.co.jp/ait/articles/0404/03/news019.html)

| Option | Function |
|:---|:---|
| /n | Opens a new single-pane window for the default selection. This is usually the root of the drive that Windows is installed on. If the window is already open, a duplicate opens.  |
| /e | Opens Windows Explorer in its default view. |
| /root,\<object\> | Opens a window view of the specified object. |
| /select,\<object\> | Opens a window view with the specified folder, file, or program selected. |

## TExplorer レコード

Explorer の起動時オプションを指定して開ける TExprorer レコードを実装してみました。

```pascal:uExplorer.pas
unit uExplorer;

interface

uses
  Winapi.Windows, Winapi.ShellAPI;

type
  TExplorerOpenStyle = (eosNone, eosSinglePane, eosDefaultView);
  TExplorer = record
  public
    /// <summary>
    ///   Explorer を開く
    /// </summary>
    /// <param name="Path">
    ///   開く場所。
    /// </param>
    /// <param name="OpenStyle">
    ///   Explorer の表示スタイル。
    ///     eosNone
    ///       指定なし。
    ///     eosSinglePane
    ///       Windows XP だとシングルペインウィンドウで開く。
    ///     eosDefaultView
    ///       エクスプローラーバーの `フォルダ` が有効の状態で開く。
    /// </param>
    /// <param name="RootDir">
    ///   ルートフォルダ。Windows XP だとルートフォルダよりも上の階層に移動する事はできない。
    /// </param>
    /// <param name="SelectObject">
    ///   選択するオブジェクト。オブジェクトが選択された状態で Explorer が開く。
    /// </param>
    /// <returns>
    ///   ShellExecute() API の戻り値
    /// </returns>
    class function Open(Path: string = ''; OpenStyle: TExplorerOpenStyle = eosNone; RootDir: string = ''; SelectObject: string = ''): HINST; static;
  end;

implementation

{ TExplorer }

class function TExplorer.Open(Path: string; OpenStyle: TExplorerOpenStyle; RootDir,
  SelectObject: string): HINST;
var
  Param: string;
  procedure AddDelimiter;
  begin
    if Param <> '' then
      Param := Param + ',';
  end;
begin
  case OpenStyle of
    eosSinglePane:
      Param := '/n';
    eosDefaultView:
      Param := '/e';
  else
    Param := '';
  end;
  if (Path <> '') and (RootDir = '') and (SelectObject = '') then
    case OpenStyle of
      eosSinglePane,
      eosDefaultView:
        begin
          AddDelimiter;
          Param := Param + Path;
        end;
    else
      Param := Path;
    end;
  if RootDir <> '' then
    begin
      AddDelimiter;
      Param := Param + '/root';
      AddDelimiter;
      Param := Param + RootDir;
    end;
  if SelectObject <> '' then
    begin
      AddDelimiter;
      Param := Param + '/select';
      AddDelimiter;
      Param := Param + SelectObject;
    end;
  result := ShellExecute(0, 'open', 'explorer.exe', PChar(Param), nil, SW_SHOWNORMAL);
end;

end.
```

`Open()` メソッドで Explorer を開けます。

```pascal
class function TExplorer.Open(Path: string; OpenStyle: TExplorerOpenStyle; RootDir,
  SelectObject: string): HINST;
```

| コード |起動時オプション| 動作 |
|:---|:---|:---|
| TExplorer.Open;  | (なし)| Windows 11 だと `クイックアクセス` が開く。単純に `Explorer.exe` を起動した状態|
| TExplorer.Open('C:\WORK');  | C:\WORK | 'C:\WORK' が開く|
| TExplorer.Open('C:\WORK', eosNone);  | C:\WORK | 'TExplorer.Open('C:\WORK');' と同じ|
| TExplorer.Open('C:\WORK', eosSinglePane);  | /n,C:\WORK| Windows XP だと 'C:\WORK' がシングルペインウィンドウで開く|
| TExplorer.Open('C:\WORK', eosDefaultView);  |/e,C:\WORK| Windows XP だと 'C:\WORK' がエクスプローラーバーの `フォルダ` が有効の状態で開く|
| TExplorer.Open('', eosDefaultView, 'C:\WORK'); | /e,/root,C:\WORK| 'C:\WORK' をルートフォルダとして開く。 Windows XP だとルートフォルダよりも上の階層に移動する事はできない。 |
| TExplorer.Open('', eosDefaultView, '', 'C:\WORK\TEST.EXE'); | /e,/select,C:\WORK\TEST.EXE| 'C:\WORK' フォルダを開き、そこにある `TEST.EXE` を選択状態にする。 |

`Path` や `RootDir`、`SelectObject` には特殊フォルダを表す **CLSID** を指定する事もできます。CLSID を指定する場合には先頭にコロンを 2 つ並べます。

```
::<CLSID>
```

例えば次のコードはデスクトップフォルダが開き、ごみ箱が選択状態になります。

```pascal
TExplorer.Open('', eosDefaultView, '', '::{645FF040-5081-101B-9F08-00AA002F954E}');
```

:::note info
「長いパラメータの書式の方がよく使う！」という方はパラメータの並びを変更するか、オーバーロードメソッドを追加してください。
:::

## Shell() メソッドの実装
Explorer や `ファイル名を指定して実行` では **shell:** というプロトコルが使えます。

例えば `shell:C:\WORK` は Explorer で `C:\WORK` を開きますし、`shell:::{645FF040-5081-101B-9F08-00AA002F954E}` はゴミ箱を開きます。

`Shell()` メソッドの実装はこのようになりました。

```pascal:uExplorer.pas
    /// <summary>
    ///   Explorer を開く
    /// </summary>
    /// <param name="Param">
    ///   shell: コマンドに渡すパラメータ。
    /// </param>
    /// <returns>
    ///   ShellExecute() API の戻り値
    /// </returns>
    class function Shell(Param: string): HINST; static;

...

class function TExplorer.Shell(Param: string): HINST;
begin
  Param := 'shell:' + Param;
  result := ShellExecute(0, 'open', PChar(Param), nil, nil, SW_SHOWNORMAL);
end;
```

`Param` には**ロケーション名**を渡す事もできます。例えば次のコードはコントロールパネルを開きます。

```pascal
  TExplorer.Shell('ControlPanelFolder');
```

:::note info
ロケーション名はレジストリの `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\FolderDescriptions` から取得する事ができます。
:::

## Search() メソッドの実装
Explorer や ファイル名を指定して実行 では **search-ms:** (Vista 以降では **search:** でも可) というプロトコルでファイル検索を行う事ができます。

 - [検索プロトコルの使用 (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/windows/win32/shell/search-protocol)

```pascal:uExplorer.pas
uses
  ..., System.NetEncoding;

...

    /// <summary>
    ///   Explorer を開く
    /// </summary>
    /// <param name="Location">
    ///   検索する場所。
    /// </param>
    /// <param name="Query">
    ///   検索文字列。
    /// </param>
    /// <param name="DisplayName">
    ///   ［省略可能］ 検索結果に付ける表示名。
    /// </param>
    /// <returns>
    ///   ShellExecute() API の戻り値
    /// </returns>
    class function Search(Location: string; Query: string; DisplayName: string = ''): HINST; static;

 ...

class function TExplorer.Search(Location, Query, DisplayName: string): HINST;
var
  Param: string;
begin
  Param := 'search-ms:';
  Param := Param + 'query=' + Query + '&';
  Param := Param + 'location=' + TNetEncoding.URL.Encode(Location);
  if DisplayName <> '' then
    Param := Param + '&' + 'displayName=' + DisplayName;
  result := ShellExecute(0, 'open', PChar(Param), nil, nil, SW_SHOWNORMAL);
end;
```

`location=` に渡す文字列は **URL エンコード** しなくてはならないようなので、**uses** に `System.NetEncoding` を追加し、`TNetEncoding.URL.Encode()` メソッドを使って URL エンコードしています。

次のコードは `C:\WORK` の中から `*.pas` を検索し、検索結果に `ソースファイル` という名前を付けて表示します。

```pascal
  TExplorer.Search('C:\WORK', '*.pas', 'ソースファイル');
```

:::note warn
XP では正しく動作しない (環境がある) かもしれません。
:::

# おわりに

整理したソースコードを掲載しておきます。

```pascal:uExplorer.pas
unit uExplorer;

interface

uses
  Winapi.Windows, Winapi.ShellAPI, System.NetEncoding;

type
  TExplorerOpenStyle = (eosNone, eosSinglePane, eosDefaultView);
  TExplorer = record
  public
    /// <summary>
    ///   Explorer を開く
    /// </summary>
    /// <param name="Path">
    ///   開く場所。
    /// </param>
    /// <param name="OpenStyle">
    ///   Explorer の表示スタイル。
    ///     eosNone
    ///       指定なし。
    ///     eosSinglePane
    ///       Windows XP だとシングルペインウィンドウで開く。
    ///     eosDefaultView
    ///       エクスプローラーバーの `フォルダ` が有効の状態で開く。
    /// </param>
    /// <param name="RootDir">
    ///   ルートフォルダ。Windows XP だとルートフォルダよりも上の階層に移動する事はできない。
    /// </param>
    /// <param name="SelectObject">
    ///   選択するオブジェクト。オブジェクトが選択された状態で Explorer が開く。
    /// </param>
    /// <returns>
    ///   ShellExecute() API の戻り値
    /// </returns>
    class function Open(Path: string = ''; OpenStyle: TExplorerOpenStyle = eosNone; RootDir: string = ''; SelectObject: string = ''): HINST; static;
    /// <summary>
    ///   Explorer を開く
    /// </summary>
    /// <param name="Param">
    ///   shell: コマンドに渡すパラメータ。
    /// </param>
    /// <returns>
    ///   ShellExecute() API の戻り値
    /// </returns>
    class function Shell(Param: string): HINST; static;
    /// <summary>
    ///   Explorer を開く
    /// </summary>
    /// <param name="Location">
    ///   検索する場所。
    /// </param>
    /// <param name="Query">
    ///   検索文字列。
    /// </param>
    /// <param name="DisplayName">
    ///   ［省略可能］ 検索結果に付ける表示名。
    /// </param>
    /// <returns>
    ///   ShellExecute() API の戻り値
    /// </returns>
    class function Search(Location: string; Query: string; DisplayName: string = ''): HINST; static;
  end;

implementation

{ TExplorer }

class function TExplorer.Open(Path: string; OpenStyle: TExplorerOpenStyle; RootDir,
  SelectObject: string): HINST;
var
  Param: string;
  procedure AddDelimiter;
  begin
    if Param <> '' then
      Param := Param + ',';
  end;
begin
  case OpenStyle of
    eosSinglePane:
      Param := '/n';
    eosDefaultView:
      Param := '/e';
  else
    Param := '';
  end;
  if (Path <> '') and (RootDir = '') and (SelectObject = '') then
    case OpenStyle of
      eosSinglePane,
      eosDefaultView:
        begin
          AddDelimiter;
          Param := Param + Path;
        end;
    else
      Param := Path;
    end;
  if RootDir <> '' then
    begin
      AddDelimiter;
      Param := Param + '/root';
      AddDelimiter;
      Param := Param + RootDir;
    end;
  if SelectObject <> '' then
    begin
      AddDelimiter;
      Param := Param + '/select';
      AddDelimiter;
      Param := Param + SelectObject;
    end;
  result := ShellExecute(0, 'open', 'explorer.exe', PChar(Param), nil, SW_SHOWNORMAL);
end;

class function TExplorer.Search(Location, Query, DisplayName: string): HINST;
var
  Param: string;
begin
  Param := 'search-ms:';
  Param := Param + 'query=' + Query + '&';
  Param := Param + 'location=' + TNetEncoding.URL.Encode(Location);
  if DisplayName <> '' then
    Param := Param + '&' + 'displayName=' + DisplayName;
  result := ShellExecute(0, 'open', PChar(Param), nil, nil, SW_SHOWNORMAL);
end;

class function TExplorer.Shell(Param: string): HINST;
begin
  Param := 'shell:' + Param;
  result := ShellExecute(0, 'open', PChar(Param), nil, nil, SW_SHOWNORMAL);
end;

end.
```

アプリケーションから Explorer の検索結果を表示する事はあまりないかもしれませんが、アプリケーションが出力したファイルを選択状態にして Explorer を開く事はあるかもしれませんね。

**See also:**

 - [945_エクスプローラのテスト (Mr.XRAY)](http://mrxray.on.coocan.jp/Delphi/plSamples/945_ExplorerTest.htm)