Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年04月23日] [更新 2016年08月26日] Delphi サンプルプログラム集
   
470_システム関係ダイアログの .cpl による表示
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
470_SomeDialogs.zip [1,139 KB] 2016年08月26日版(EXE同梱)



  • 2011年11月01日
  • 動作確認環境変更と,それに伴う修正・編集
  • 2016年08月26日
  • cpl の操作と直接関係ないサンプルを削除して,別のページに移動




01_.cpl を使用した [ コントロールパネル ] 関係の設定ダイアログ


システムフォルダ内にある,拡張子が .cpl のコントロールパネルファイルを起動して,各種ダイアログを表示するサンプルです.Windows 2000 以降で動作しますが,表示されるダイアログのデザインや機能が違うことがあります.


図1
System32 フォルダ内の .cplファイル
  • 拡張子が .cpl のファイルをダブルクリックするとダイアログが表示される
図2
テストしたのはこれだけ
  • 項目のダブルクリックでダイアログを標示
  • 表示項目は Windows 7 のもの
  • ダイアログのタイトルや機能は,Windows のバージョンによって異なる
  • ShellExecute, CreateProcess, WinExec の 3 つの関数でテストできるようにした

リスト1
コントロールパネル関係の設定ダイアログを表示
unit SomeDialogsUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ShellAPI, ExtCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    RadioGroup1: TRadioGroup;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure ListBox1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//-----------------------------------------------------------------------------
//  指定したファイルを実行する(Halbow資料館のWinExecAndWait32V2を修正)
//  ここでは表示位置変更のためのコードを追加.また終了を待つのではなく,起動を
//  待つように修正している.
//-----------------------------------------------------------------------------
function WinExecAndWait32V2(FileName: string; Visibility: Integer): DWORD;
var
  StartupInfo : TStartupInfo;
  ProcessInfo : TProcessInformation;
  Ret         : DWORD;
begin
  FillChar(StartupInfo, SizeOf(StartupInfo), #0);
  StartupInfo.cb          := SizeOf(StartupInfo);
  //以下の3行とdwFlagsにSTARTF_USERPOSITIONを追加
  StartupInfo.dwX         := 0;
  StartupInfo.dwY         := Form1.Top;
  StartupInfo.dwFlags     := STARTF_USESHOWWINDOW or
                             STARTF_USEPOSITION;
  StartupInfo.wShowWindow := Visibility;

  //参照カウンタ対策
  UniqueString(FileName);

  if not CreateProcess(nil,
    PChar(FileName),      // pointer to command line string
    nil,                  // pointer to process security attributes
    nil,                  // pointer to thread security attributes
    False,                // handle inheritance flag
    CREATE_NEW_CONSOLE or // creation flags
    NORMAL_PRIORITY_CLASS,
    nil,                  //pointer to new environment block
    nil,                  // pointer to current directory name
    StartupInfo,          // pointer to STARTUPINFO
    ProcessInfo)          // pointer to PROCESS_INF
  then Result := WAIT_FAILED
  else
  begin
    repeat
      Ret := WaitForInputIdle(ProcessInfo.hProcess, 100);
      Application.ProcessMessages;
    until Ret <> WAIT_TIMEOUT;
    Sleep(100);

    CloseHandle(ProcessInfo.hProcess);
    CloseHandle(ProcessInfo.hThread);
    Result:=WAIT_TIMEOUT
  end;
end; { WinExecAndWait32 }

//=============================================================================
//  ListBox1のオブジェクトを破棄
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
var
  i : Integer;
begin
  for i := 0 to ListBox1.Items.Count-1 do begin
    Dispose(PString(ListBox1.Items.Objects[i]));
  end;
end;

//=============================================================================
//  ListBox1に動作を示す文字列と起動するcplファイル名をセット
//  cplには起動オプションを指定できるものがある.ここでは例として[画面のプロパ
//  ティ]の起動時に,[スクリーンセーバ]タブを選択状態にするオプションを追加し
//  たItemsを追加してみた
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);

  //---------------------------------------------------------------------------
  //  Delphiにも同名の関数があるが,AnsiStringなのでString型で再定義
  //---------------------------------------------------------------------------
  function NewStr(S: String): PString;
  begin
    New(Result);
    Result^ := S;
  end;

  //---------------------------------------------------------------------------
  //  ListBox1に文字列と実行用のパス名をセット
  //---------------------------------------------------------------------------
  procedure AddItem(ACaption, APath: String);
  begin
    ListBox1.AddItem(ACaption, TObject(NewStr(APath)));
  end;

//-----------------------------------------------------------------------------
begin
  AddItem('コンピューターの簡単操作センター',   'access.cpl');
  AddItem('プログラムと機能',                   'appwiz.cpl');
  AddItem('BDE Administrator',                  'bdeadmin.cpl');
  AddItem('画面の解像度',                       'desk.cpl');
  AddItem('画面のプロパティ[スクリーンセーバ]', 'desk.cpl,,1');
  AddItem('Windowsファイアーウォール',          'firewall.cpl');
  AddItem('Windows Update',                     'wuaucpl.cpl');
  AddItem('インターネットのプロパティ',         'inetcpl.cpl');
  AddItem('地域と言語',                         'intl.cpl');
  AddItem('ゲームコントローラ',                 'joy.cpl');
  AddItem('マウスのプロパティ',                 'main.cpl');
  AddItem('キーボードのプロパティ',             'main.cpl,@1');
  AddItem('サウンド',                           'mmsys.cpl');
  AddItem('電話とモデム',                       'telephon.cpl');
  AddItem('ネットワーク接続',                   'ncpa.cpl');
  AddItem('ネットワークのセットアップ',         'netsetup.cpl');
  AddItem('ユーザアカウント',                   'nusrmgr.cpl');
  AddItem('電源オプション',                     'powercfg.cpl');
  AddItem('システムのプロパティ',               'sysdm.cpl');
  AddItem('日付と時刻',                         'timedate.cpl');

  RadioGroup1.ItemIndex := 0;
  ListBox1.ItemIndex    := 0;
  ListBox1Click(nil);
end;

//=============================================================================
//  ListBox1のOnDblClickイベント処理
//  ListBox1のアイテムをクリックしたら該当ダイアログを表示
//
//  参考のために以下の関数を選択して実行できるようにしている
//    WinExec
//    CreateProcess
//    ShellExecute
//
//  CreateProcessは表示位置を指定しているが,指定を無視するダイアログもある
//  OSや環境によっても起動しないダイアログもある
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  LItemStr     : string;
  LAnsiItemStr : AnsiString;
begin
  if ListBox1.ItemIndex < 0 then exit;

  //選択中のItemsの.cplファイル名を取得して起動名文字列を作成
  LItemStr     := PString(ListBox1.Items.Objects[ListBox1.ItemIndex])^;
  LAnsiItemStr := AnsiString(LItemStr);

  case RadioGroup1.ItemIndex of
    0: ShellExecute(Handle, '', 'control.exe', PChar(LItemStr), nil, SW_SHOW);
    1: WinExecAndWait32V2('control.exe ' + LItemStr, SW_SHOWNORMAL);
    2: WinExec(PAnsiChar('control.exe ' + LAnsiItemStr), SW_SHOWNORMAL);
  end;
end;

//=============================================================================
//  ListBox1のOnDblClickイベント処理
//  実行ファイル名を表示
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
  LItemStr : string;
begin
  if ListBox1.ItemIndex < 0 then exit;

  //選択中のItemsの.cplファイル名を取得して起動名文字列を作成
  LItemStr := PString(ListBox1.Items.Objects[ListBox1.ItemIndex])^;
  Edit1.Text := LItemStr;
end;

//=============================================================================
//  ListBox1のOnKeyDownイベント処理
//=============================================================================
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if ListBox1.ItemIndex < 0 then exit;

  if Key = VK_RETURN then begin
    ListBox1DblClick(nil);
    Key := 0;
  end;
end;

end.


CPL ファイルは Rundll32.exe を使用して,以下のようにも実行できます.


procedure TForm1.ListBox1Click(Sender: TObject);
var
  LItemStr   : string;
  LParameter : string;
begin
  if ListBox1.ItemIndex < 0 then exit;

  //選択中のItemsの.cplファイル名を取得して起動名文字列を作成
  LItemStr   := PString(ListBox1.Items.Objects[ListBox1.ItemIndex])^;
  LParameter := 'Shell32.dll, Control_RunDLL ' + LItemStr;

  ShellExecute(Handle, '', 'Rundll32.exe', PChar(LParameter), nil, SW_SHOW);
end;




02_システム関係ダイアログの .cpl による表示例


以下は,Windows 7 での実行例です.
各々の図の上のコマンドは,[ファイル名を指定して実行] のダイアログでも実行できます.cpl ファイルによっては,control.exe がなくてもダイアログを表示しますが,control.exe があった方が確実です.control.exe の .exe は省略可能です.

control.exe access.cpl


図3
コンピューターの簡単操作センター
  • XPでは[ユーザ補助のオプション]

control.exe appwiz.cpl

図4
プログラムと機能
  • XP では [プログラムの追加と削除]

control.exe bdeadmin.cpl

図5
BDE Administrator
  • 64 ビットの EXE からは起動できない
  • 管理者権限で実行する必要がある

control.exe desk.cpl

図6
画面の解像度
  • XP では [画面のプロパティ] の [テーマ] タブを表示

control.exe desk.cpl,,1

図7
画面のプロパティ
スクリーンセーバ
  • XP では [画面のプロパティ] の [スクリーンセーバ] タブを表示

control.exe firewall.cpl

図8
Windows ファイアーウォール

control.exe wuaucpl.cpl

図9
Windows Update
  • XP では [自動更新]

control.exe inetcpl.cpl

図10
インターネットのプロパティ

control.exe intl.cpl

図11
地域と言語
  • XP では [地域と言語のオプション]

control.exe joy.cpl

図12
ゲームコントローラ

control.exe main.cpl

図13
マウスのプロパティ

control.exe main.cpl,@1

図14
キーボードのプロパティ

control.exe mmsys.cpl

図15
サウンド
  • XP では [サウンドとオーディオデバイスのプロパティ]

control.exe telephon.cpl

図16
電話とモデム
  • XP では [電話とモデムのオプション]

control.exe ncpa.cpl

図17
ネットワーク接続

control.exe netsetup.cpl

図18
ネットワークのセットアップ
  • XP では [ネットワークセットアップウィザードの開始]

control.exe nusrmgr.cpl

図19
ユーザアカウント

control.exe powercfg.cpl

図20
電源オプション
  • XP では [電源オプションのプロパティ]

control.exe sysdm.cpl

図21
システムのプロパティ

control.exe timedate.cpl

図22
日付と時刻
  • XP では [日付と時刻のプロパティ]




03_CPL の引数


画面のプロパティ[スクリーンセーバ]では,.cpl の最後の引数を 1 としていました.この値を 0 にすると [デスクトップのアイコンの設定] ダイアログを表示します (Windows 7 の場合).
このように,.cpl によるダイアログの表示では,最後の引数値を変えることで,ダイアログ起動時に表示するタブ等を変更することができる場合があります.


図23
'control.exe desk.cpl,,1' の場合
図24
'control.exe desk.cpl,,0' の場合




04_CPL ファイルの一覧と CPLApplet 関数による実行


本サンプルはシステムフォルダ内にある,拡張子が .cpl のファイルを列挙します.
ダイアログの表示は,各々の CPL ファイル内の CPlApplet という関数を使用します.cpl ファイル内にこの関数がない場合は ShellExecute 関数でダイアログを表示します.

列挙した cpl ファイルの中には,64 ビット Windows では 64 ビットの EXE でないとダイアログを表示しないものがあります.また,全くダイアログを表示しないものもあります.



図25
設計時画面
  • 結果の表示は TListView
図26
列挙の結果
  • 32 ビットと 64 ビット Windows とでは列挙数が異なる
  • 実際には実行不可の CPL もある

本ページの最初のサンプルで,access.cpl でダイアログを表示しているのがあります.
本サンプルの列挙の結果には,この CPL ファイルは表示されていません.

下図は筆者の 64 ビット Windows 7 Ultimate エディションで,エクスプローラからシステムフォルダ内の .cpl を検索した結果です.全部で 44 ありました.
末尾が .cpl と .cpl.mui というのがあります..mui は拡張子としては認識されていません.mui は複数の言語の切り換え機能を提供している Multilingual User Interface に関係しているものと思われますが,詳細については不明です.
EXE もそうですが,CPL も単独では機能しないものがあります.本サンプルでの実行結果とエクスプローラでの検索結果の関係,あるいは仕様についての知識は筆者にはありません.



図27
システムフォルダ内の CPL ファイル

リスト2
CPL ファイルの一覧と CPLApplet 関数による実行
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls, ImgList;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ListView1: TListView;
    ImageList1: TImageList;
    Label1: TLabel;
    procedure ListView1DblClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure ListView1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Types,
  IOUtils,
  cpl,
  ShellAPI,
  CommCtrl;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Button1.Visible := False;
  Button1Click(nil);
end;

//=============================================================================
//  cplファイルを列挙
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSysDir        : array [0..MAX_PATH - 1] of Char;
  LInfoName      : array [0..MAX_PATH - 1] of Char;
  LInfoComment   : array [0..MAX_PATH - 1] of Char;
  LFilesList     : TStringDynArray;
  LFilePath      : String;
  LFileName      : String;
  LListItem      : TListItem;
  LhLib          : THandle;
  LCplAppletProc : TCPLApplet;
  LCplItemCnt    : Integer;
  LCplInfo       : TCPLInfo;
  LNewCPLInfo    : TNewCPLInfo;
  LIcon          : TIcon;
  i              : Integer;
begin
  //システムディレクトリを取得
  //ここにcplファイルがある
  GetSystemDirectory(LSysDir, MAX_PATH);

  //テキスト表示を補完するツールチップを非表示にする
  ListView_SetToolTips(ListView1.Handle, 0);
  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;
  ListView1.SmallImages.Clear;

  ListView1.Column[0].Width := 180;
  ListView1.Column[1].Width := 140;
  ListView1.Column[2].Width := -1;


  //指定ディレクトリのサブディレクトリ内も検索
  LFilesList := TDirectory.GetFileSystemEntries(LSysDir, '*.cpl');
  for LFilePath in LFilesList do begin
    LFileName := TPath.GetFileName(LFilePath);
    LListItem := ListView1.Items.Add;
    LListItem.Caption := LFileName;
    LListItem.ImageIndex := -1;

    //DLLとしてのcplファイルのロード
    LhLib := LoadLibrary(PChar(LFileName));
    if LhLib = 0 then Continue;

    try
      //関数のアドレスを取得
      LCplAppletProc := TCPLApplet(GetProcAddress(LhLib, 'CPlApplet'));
      if @LCplAppletProc = nil then Continue;

      LCplAppletProc(Handle, CPL_INIT, 0, 0);
      //同じcpl内に複数のダイアログが存在する場合がある
      //その数を取得
      LCplItemCnt := LCplAppletProc(Handle, CPL_GETCOUNT, 0, 0);
      for i := 0 to LCplItemCnt - 1 do begin
        if i > 0 then begin
          LListItem := ListView1.Items.Add;
          LListItem.Caption := LFileName;
        end;

        //TCPLInfo構造体の情報を取得
        FillChar(LCplInfo, Sizeof(TCPLInfo), 0);
        LCplAppletProc(Handle, CPL_INQUIRE, i, LPARAM(@LCplInfo));

        //ダイアログの種類名と説明文を取り出す
        LoadString(LhLib, LCplInfo.idName, @LInfoName, MAX_PATH);
        LoadString(LhLib, LCplInfo.idInfo, @LInfoComment, MAX_PATH);

        //アイコンの生成
        LIcon := TIcon.Create;
        try
          //TNewCPLInfoでの設定がある場合を検出
          FillChar(LNewCPLInfo, Sizeof(TNewCPLInfo), 0);
          LCplAppletProc(Handle, CPL_NEWINQUIRE, i, LPARAM(@LNewCPLInfo));

          if LCplInfo.idName = CPL_DYNAMIC_RES then begin
            StrCopy(LInfoName, LNewCPLInfo.szName);
          end;
          if LCplInfo.idInfo = CPL_DYNAMIC_RES then begin
            StrCopy(LInfoComment, LNewCPLInfo.szInfo);
          end;

          //アイコンの取得
          if LCplInfo.idIcon = CPL_DYNAMIC_RES then begin
            LIcon.Handle := LNewCPLInfo.hIcon;
          end else begin
            LIcon.Handle := LoadIcon(LhLib, MAKEINTRESOURCE(LCplInfo.idIcon));
            ListView1.SmallImages.AddIcon(LIcon);
          end;
          LListItem.ImageIndex := ListView1.SmallImages.Count - 1;
        finally
          FreeAndNil(LIcon);
        end;

        LListItem.SubItems.Add(LInfoName);
        LListItem.SubItems.Add(LInfoComment);

        //同名のcplファイルで異なるダイアログの時はその番号を保持
        //TListView.TListItemのDataプロパティに代入しておく
        LListItem.Data := PInteger(i);

        LCplAppletProc(Handle, CPL_STOP, i, LCplInfo.lData);
        LCplAppletProc(Handle, CPL_EXIT, 0, 0);
      end;
    finally
      FreeLibrary(LhLib);
    end;
  end;
  ListView1.Items.EndUpdate;

  Label1.Caption := IntToStr(ListView1.Items.Count);
end;

//=============================================================================
//  ListView1のOnDblClickイベント処理
//  選択中のcplを実行する
//
//  64ビットWindowsでは,32ビットEXEで起動しないダイアログがある
//  リストにあっても起動しないダイアログがある(詳細な動作仕様については不明)
//=============================================================================
procedure TForm1.ListView1DblClick(Sender: TObject);
var
  LInfoName      : array [0..MAX_PATH - 1] of Char;
  LIndex         : Integer;
  LhLib          : THandle;
  LCplAppletProc : TCPLApplet;
  LCplInfo       : TCPLInfo;
  LCplItemIndex  : Integer;
  LCmdText       : string;
  LiRet          : Integer;
begin
  LIndex := ListView1.ItemIndex;
  if LIndex < 0 then exit;

  //DLLとしてのcplファイルのロード
  LhLib := LoadLibrary(PChar(ListView1.Items[LIndex].Caption));
  if LhLib = 0 then exit;

  //同名のcplファイルで異なるダイアログの時はその番号を取得
  //この値はTListView.TListItemのDataプロパティに格納している
  LCplItemIndex := Integer(ListView1.Items[LIndex].Data);

  LiRet := 0;
  //関数のアドレスを取得
  LCplAppletProc := TCPLApplet(GetProcAddress(LhLib, 'CPlApplet'));
  if @LCplAppletProc <> nil then begin
    try
      //cplを初期化してダブルクリックの動作を実行して後処理を行う
      LCplAppletProc(Handle, CPL_INIT, 0, 0);
      FillChar(LCplInfo, Sizeof(TCPLInfo), 0);
      LCplAppletProc(Handle, CPL_INQUIRE, LCplItemIndex, LongInt(@LCplInfo));
      LoadString(LhLib, LCplInfo.idName, @LInfoName, MAX_PATH);
      if string(LInfoName) <> '' then begin
        LiRet := 1;
        LCplAppletProc(Handle, CPL_DBLCLK, LCplItemIndex, LCplInfo.lData);
        LCplAppletProc(Handle, CPL_STOP, LCplItemIndex, LCplInfo.lData);
      end;
      LCplAppletProc(Handle, CPL_EXIT, 0, 0);
    finally
      FreeLibrary(LhLib);
    end;
  end;

  //関数のアドレスが取得できなかった場合はShellExecuteで実行
  //TCPLInfo.idNameが取得できなかった場合もShellExecuteで実行
  if LiRet = 0 then begin
    LCmdText := ListView1.Items[LIndex].Caption;
    ShellExecute(Handle, '', 'control.exe', PChar(LCmdText), nil, SW_SHOW);
  end;
end;

//=============================================================================
//  ListView1のOnKeyDownイベント処理
//  [Enter]キー押下でOnDblClickと同じ動作とする
//=============================================================================
procedure TForm1.ListView1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    ListView1DblClick(nil);
    Key := 0;
  end;
end;

end.




05_CPL ファイルの作成 - コントロールパネルアプリケーションの作成


CPL ファイル (コントロールパネルファイル) の実体は DLL です.
したがって,DLL を作成するわけですが,Delphi 5 からは,コントロールパネルアプリケーション作成用のテンプレートが用意されています.CPL の操作用として CtlPanel.pas に各種のクラス類が実装されています.



図28
コントロールパネルアプリケーションのテンプレート