起動中のアプリをリスト化して選択したアプリをアクティブにするアプリを作る （完成）

意見交換において起動しているアプリをリスト表示して切り替えたいが思うように動作するアプリが無い

https://qiita.com/itagagaki/questions/e30de068794084acfc3f

とのことで、2024年に似たようなアプリを作ったことがあったので応用して作れないか？と思ったが悪戦苦闘するのは目に見えているのでそれを記事にしておく

# どんなとき使うの？
Windowsで開発していると様々なアプリを切り替えながら使います
でもそのときってタスクバーのアイコンをマウスでクリックする操作が必要です
これってもっと簡単で便利に出来ないの？と思っていたので丁度良い意見交換でした
自分の場合このアプリの切り替え操作を1日1万回ぐらいしているので何とか出来ると効率が良くなりそうです
あとタスクバーだと起動させたくないアプリを誤クリックしてしまうのでそれも防げると思われます

# 0.5日目
似たようなアプリから必要最低限の部分を取り出し表示するときにアプリの一覧を表示する

参考にしたのは下記のサイトの3番目
http://mrxray.on.coocan.jp/Delphi/plSamples/320_AppList.htm

## 0.5日目の結果
タスクトレイに常駐し、クリックするとこのようになる※実際はホットキーによる起動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637460/2b1c927a-4607-2a59-ad81-2c129ebf616c.png)

一見うまくいっているように見えるがうまく言っていない
表示する必要がないものが表示されたり表示したいものが表示されていない

アイコンが16x16のものしか取得できず見た目が悪い
AIに大きいアイコンの取得を作らせたが取得できないのとサイズが指定出来ない

原因は古いAPIを古い方法で使っているためと思われる


# 1日目
Windows10用の起動中アプリを表示する仕組みに差し替える。
ついでにアイコンサイズが16ピクセルじゃ無くて大きいのを取得する※この処理が思いのほかめんどくさい

# 1日目の結果
起動しているアプリが正しく取得されているのとアイコンサイズが大きくなって見やすくなった
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637460/45f6747d-47c8-c3b2-658a-2c010de69213.png)

# 2日目
アプリ一覧の処理がジャマなのでフレームとして分離。
常駐型アプリとしてメニューからの終了以外は終了しない処理を追加。
キーボードのグローバルフックでキーを取得してキーを判断してアプリ一覧を表示

# 問題点
wParam に WM_KEYDOWNが入ってくるタイミングでlParam: LPARAMにキーコードが入ると思ったが入ってこないようだ
下記の方法でグローバルキーフックが出来たので採用
```sample.pas
RegisterHotKey(Handle, 1, MOD_CONTROL + MOD_SHIFT, Ord('G'));
```

# 設定画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637460/78b26e2c-a625-e6ca-0733-ce8c3c02a4b5.png)
必要最低限の機能を付けた

# 配布
下記リンクからDL可能
https://drive.google.com/uc?id=1jEf-jZDktQV3N0Yfc78r36hf47NwaPS5

# 使い方
実行すると右下のアイコントレイに常駐。
アイコントレイにあるアイコンをクリックするとアプリ一覧が表示される。
アイコントレイから右クリックメニューでアプリ一覧を表示するキーの設定が出来る。
有効にしてキーを入力するとマウスの位置を中心としてアプリ一覧が表示
使用するアプリを選ぶとそのアプリがアクティブに
そして本体は密かに隠れるので必要な時にまた呼び出す
アプリ一覧の右側のキーをダブルクリックするとショートカットキーが選べる
ショートカットキーを押すとアプリがアクティブになる
終了するときは右クリックからの終了で行う

# 最後に
なんだかんだで4日で完成
ついでにGeminiでアイコンを作ってもらった
キーのフックはもう少し学習する必要がありそうなのとAIに問い合わせると存在しない命令を使えと言われる。
キーの厳密チェックはしてないので変なキーを登録しないように
ショートカットキーの設定を標準機能に切り替えたので設定はやりなおし


# ソース
```main.pas
unit MainForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  Vcl.ComCtrls,DMStanderd, Vcl.Menus,AppListFrame,DataConfig;

type
  TFormMainPos = class(TDMStanderdWindow)
  private
  protected
    function GetFilename() : string;override;
  end;

type
  TFormMain = class(TForm)
    MenuPop: TPopupMenu;
    Menuconfig: TMenuItem;
    MenuClose: TMenuItem;
    N1: TMenuItem;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure MenuCloseClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure MenuconfigClick(Sender: TObject);
  private
    { Private 宣言 }
    FPos           : TFormMainPos;        // Window位置データ
    FTrayIconMain  : TTrayIcon;           // トレイアイコン操作クラス
    FFolder        : string;              // データ保存先フォルダ
    FHandleSelf    : HWND;                // 自分自身のハンドル
    FShowed        : Boolean;             // True:描画済み
    FToolHide      : Boolean;             // True : 手動で非表示中
    FFrameAppList  : TFrameAppList;
    FProcClosed    : Boolean;             // True : メニューから終了指示あり
    FConfig        : TDataConfig;
    FHotkeyUsed    : Boolean;

    procedure ShowList();

    procedure HotkeyStart();
    procedure HotkeyStop();

    //procedure WMNchiltTest(var Msg : TWMNchitTest);Message WM_NCHITTEST;
    procedure OnTrayIconClick(Sender: TObject);
    procedure OnAppSelect(Sender: TObject);
  protected
    //procedure CreateParams(var Params: TCreateParams); override;
  public
    { Public 宣言 }
    procedure WndProc(var Message: TMessage); override;
  end;

var
  FormMain: TFormMain;

  var
    WndProc       : Pointer;

implementation

uses FolderLib,
  Winapi.Dwmapi,
  Winapi.propSys,
  Winapi.PropKey,
  ConfigForm;


{$R *.dfm}


//--------------------------------------------------------------------------//
//  フォーム生成                                                            //
//--------------------------------------------------------------------------//
procedure TFormMain.FormCreate(Sender: TObject);
begin
  FHandleSelf := Self.Handle;

  SetFolder('WinReplace');                         // 設定値保存フォルダを指定
  FFolder := GetMyDocFolder();
  CheckFolderAndMake(FFolder);                // 設定値保存フォルダを作成

  FPos := TFormMainPos.Create;
  FPos.LoadFromFile;

  FConfig := TDataConfig.Create;
  FConfig.LoadFromFile(FFolder+'config.ini');

  FTrayIconMain := TTrayIcon.Create(Self);
  FTrayIconMain.Icon      := Application.Icon;
  FTrayIconMain.Visible   := True;
  FTrayIconMain.Hint      := Application.Title;
  FTrayIconMain.PopupMenu := MenuPop;
  FTrayIconMain.OnClick    := OnTrayIconClick;

  FFrameAppList := TFrameAppList.Create(Self);
  FFrameAppList.Parent := Self;
  FFrameAppList.Align := alClient;
  FFrameAppList.OnAppSelect := OnAppSelect;

  HotkeyStart();
  FPos.Pos.DataToForm(Self);

end;

//--------------------------------------------------------------------------//
//  フォーム破棄イベント                                                    //
//--------------------------------------------------------------------------//
procedure TFormMain.FormDestroy(Sender: TObject);
begin
  HotkeyStop();

  FFrameAppList.Free;

  FTrayIconMain.Visible   := False;
  FTrayIconMain.Free;

  FConfig.Free;

  FPos.Free;

end;

//--------------------------------------------------------------------------//
//  フォーム表示イベント                                                    //
//--------------------------------------------------------------------------//
procedure TFormMain.FormShow(Sender: TObject);
begin
  {
  ShowWindow(Application.Handle, SW_HIDE);
  SetWindowLong(Application.Handle, GWL_EXSTYLE,
  GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);
  }

  FShowed := True;
  ShowList();
end;




procedure TFormMain.HotkeyStart;
var
  ModKey : Cardinal;
begin
  if not FConfig.HotKeyed then exit;
  FHotkeyUsed := True;
  ModKey := 0;
  if FConfig.KeyShift then ModKey := ModKey + MOD_SHIFT;
  if FConfig.KeyCtrl  then ModKey := ModKey + MOD_CONTROL;
  if FConfig.KeyAlt   then ModKey := ModKey + MOD_ALT;

  RegisterHotKey(Handle, 1, ModKey, Ord(FConfig.HotKey));
  //RegisterHotKey(Handle, 1, MOD_CONTROL + MOD_SHIFT, Ord('G'));
end;

procedure TFormMain.HotkeyStop;
begin
  if not FHotkeyUsed then exit;
  FHotkeyUsed := False;
end;

//--------------------------------------------------------------------------//
//  フォーム閉じるイベント                                                  //
//--------------------------------------------------------------------------//
procedure TFormMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if not FShowed then exit;               // 描画していない場合未処理

  FPos.Pos.FormToData(Self);              // フォームサイズと位置を取得
  FPos.SaveToFile;                        // フォームサイズと位置を保存
  Action := caMinimize;
end;


procedure TFormMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if FProcClosed then begin
    exit;
  end;

  CanClose := False;
  Hide();
end;




//--------------------------------------------------------------------------//
//  終了メニュー                                                            //
//--------------------------------------------------------------------------//
procedure TFormMain.MenuCloseClick(Sender: TObject);
begin
  FProcClosed := True;
  Close;
  Application.ProcessMessages;
end;


procedure TFormMain.MenuconfigClick(Sender: TObject);
var
  df : TFormConfig;
begin
  df := TFormConfig.Create(Self);
  try
    df.Config.Assign(FConfig);
    if df.ShowModal <> mrOk then exit;
    FConfig.Assign(df.Config);
    FConfig.SaveToFile(FFolder+'config.ini');
    HotkeyStop();
    HotkeyStart();
  finally
    df.Free;
  end;
end;

procedure TFormMain.OnAppSelect(Sender: TObject);
begin
  Hide();
end;

//--------------------------------------------------------------------------//
//  トレイアイコンクリック操作イベント                                      //
//--------------------------------------------------------------------------//
procedure TFormMain.OnTrayIconClick(Sender: TObject);
begin
  if Self.Visible then exit;
  FToolHide := False;
  Self.Visible := True;
end;


procedure TFormMain.ShowList;
begin
  FFrameAppList.ShowList();
end;



procedure TFormMain.WndProc(var Message: TMessage);
var
  MousePos: TPoint;
  FormWidth, FormHeight: Integer;
//  Left, Top: Integer;
begin
 inherited WndProc(Message);
  if Message.Msg = WM_HOTKEY then
  begin
    if Message.WParam = 1 then
    begin

      // 1. マウス座標の取得
      GetCursorPos(MousePos);

      // 2. フォームの位置計算
      FormWidth := Width;
      FormHeight := Height;

      Left := MousePos.X - FormWidth div 2;
      Top := MousePos.Y - FormHeight div 2;

      // Ctrl+Shift+G が押されたときの処理
      ShowList();
      Show();
      // 3. フォームの位置設定
      //SetBounds(Left, Top, FormWidth, FormHeight);
      // または
       //Left := Left;
       //Top := Top;
    end;
  end;
end;

//--------------------------------------------------------------------------//
//  タイトルバーの無いフォームを移動させる処理                              //
//--------------------------------------------------------------------------//
{
procedure TFormMain.WMNchiltTest(var Msg: TWMNchitTest);
const
  h = 6;
begin
  Msg.Result := HTCAPTION;
end;
}

{ TFormMainPos }

function TFormMainPos.GetFilename: string;
begin
  result := GetMyDocFolder + 'WindowPos.ini';
end;

end.
```

```AppListFrame.pas
unit AppListFrame;

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,DMStanderd, Vcl.Menus;

type
  TFrameAppList = class(TFrame)
    ListView1: TListView;
    ImageList1: TImageList;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy;override;
    procedure ShowList();
  end;

  function QueryFullProcessImageNameW(Process: THandle; Flags: DWORD; Buffer: PChar;
    Size: PDWORD): DWORD; stdcall; external 'kernel32.dll';
  function EnumWindowProc(hWindow: HWND; lPara: TListView): Boolean; stdcall;
  function GetWindowTitle(hWindow: HWND): string;
  function GetWindowClassName(hWindow: HWND): string;


implementation

{$R *.dfm}

uses FolderLib,
  Winapi.Dwmapi,
  Winapi.propSys,
  Winapi.PropKey;


{ TFrameAppList }

constructor TFrameAppList.Create(AOwner: TComponent);
begin
  inherited;

  ImageList1.Width := 32;
  ImageList1.Height := 32;
  ImageList1.BkColor    := ListView1.Color;
  ListView1.ViewStyle   := vsReport;
  ListView1.SmallImages := ImageList1;

  ListView1.Columns.Add;
  ListView1.Columns[0].Caption :=' アプリケーション';
  ListView1.Columns[0].Width   := ListView1.ClientWidth;

end;

destructor TFrameAppList.Destroy;
begin

  inherited;
end;

procedure TFrameAppList.ShowList;
begin
  ListView1.Items.Clear;
  ListView1.Items.BeginUpdate;

  ListView1.Columns[0].Width   := ListView1.ClientWidth-32;

  ImageList1.Clear;

  EnumWindows(Addr(EnumWindowProc), LPARAM(ListView1));
  ListView1.Items.EndUpdate;

end;

function GetIconHandleFromWindow(hWindow: HWND): HICON;
begin
  Result := SendMessage(hWindow, WM_GETICON, ICON_SMALL2, 0);
  if Result = 0 then begin
    Result := SendMessage(hWindow, WM_GETICON, ICON_SMALL, 0);

    if Result = 0 then begin
      Result := GetClassLong(hWindow, GCL_HICONSM);
    end;
  end;
end;


// AIの提案 こっちはボツ
function GetWindowIcon(WindowHandle: HWND; Size: Integer): TIcon;
var
  IconHandle: HICON;
begin
  IconHandle := HICON(SendMessage(WindowHandle, WM_GETICON, WPARAM(Size), 0));

  if IconHandle <> 0 then
  begin
    Result := TIcon.Create;
    Result.Handle := IconHandle;
  end
  else
    Result := nil;
end;


//-----------------------------------------------------------------------------
//  SHGetImageList関数を使用してシステムのイメージリストを取得
//  取得するアイコンのサイズを引数で指定する
//  定数名はShellAPI.pasに定義されている
//
//  Delphi XEにはSHGetImageList関数が実装されているが，32ビットのEXEを64ビット
//  Windowsで実行すると，「特権命令」違反が発生する
//  本コードのようにLoadLivraryでDLLをロードし関数のアドレスを取得して実行する
//  と例外が発生しない
//  64ビットのEXEで実行すれば「特恵命令」は発生しない
//-----------------------------------------------------------------------------
function GetSystemImageList(ASHILValue: Cardinal): HIMAGELIST;
type
  TSHGetImageList = function(iImageList: integer;
                             const riid: TGUID;
                             var ppv: Pointer): HRESULT; stdcall;
const
  IID_IImageList: TGUID= '{46EB5926-582E-4017-9FDF-E8998DAA0950}';
var
  LDllHandle      : THandle;
  LSHGetImageList : TSHGetImageList;
begin
  Result := 0;

  LDllHandle := LoadLibrary('Shell32.dll');
  if LDllHandle <> 0 then begin
    try
      LSHGetImageList := GetProcAddress(LDllHandle, 'SHGetImageList');
      if @LSHGetImageList <> nil then begin
        LSHGetImageList(ASHILValue, IID_IImageList, Pointer(Result));
      end;
    finally
      FreeLibrary(LDllHandle);
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  EnumWindows のコールバック関数
//  このコールバック内で現在起動中のアプリの一覧を作成
//  必要なユニット類は uses 部の記述を参照
//-----------------------------------------------------------------------------
function EnumWindowProc(hWindow: HWND; lPara: TListView): Boolean; stdcall;
const
  UWP_FRAMEWND  = 'ApplicationFrameWindow';
  UWP_COREWND   = 'Windows.UI.Core.CoreWindow';
  PROCESS_QUERY_LIMITED_INFORMATION = $1000;
  DWMWA_CLOAKED = 14;
  CLASS_BRIDGE  = 'Windows.UI.Composition.DesktopWindowContentBridge';
var
  LIsUWPApp   : Boolean;
  LAppWindow  : HWND;
  LAppTitle   : string;
  LCloaked    : BOOL;
  LPropStore  : IPropertyStore;
  LPropVar    : TPropVariant;
  LProcessID  : DWORD;
  LhProcess   : THandle;
  LBuffer     : array [0..MAX_PATH - 1] of Char;
  LSTR_SIZE   : DWORD;
  LAppPath    : string;
  LITemIDPath : PItemIDList;
  LSHFileInfo : TSHFileInfo;
  LIcon       : TIcon;
  LItem       : TListItem;
  i : Integer;
  list    : HIMAGELIST;
begin
  Result := True;
  LAppWindow := hWindow;

  if GetWindow(LAppWindow, GW_OWNER) <> 0 then exit;
  if not IsWindowVisible(LAppWindow) then exit;

  // 待機中またはバックグラウンドのウィンドウは対象外
  // クラス名 Windows.UI.Core.CoreWindow のウインドウはバックグラウンド扱い
  DwmGetWindowAttribute(LAppWindow, DWMWA_CLOAKED, @LCloaked, SizeOf(LCloaked));
  if LCloaked then Exit;

  LAppTitle := GetWindowTitle(LAppWindow);
  if LAppTitle = '' then Exit;
  if SameText(LAppTitle, 'Program Manager') then Exit;
  if SameText(LAppTitle, 'Winreplace') then Exit;
  if FindWindowEx(LAppWindow, 0, PChar(CLASS_BRIDGE), nil) <> 0 then Exit;


  // クラス名 ApplicationFrameWindow のウインドウは UWP アプリ
  LIsUWPApp := GetWindowClassName(LAppWindow) = UWP_FRAMEWND;

  if LIsUWPApp then begin
    // AppUserModelId の値を取得
    if SHGetPropertyStoreForWindow(LAppWindow,
                                IID_IPropertyStore,
                                Pointer(LPropStore)) <> S_OK then Exit;
    if LPropStore.GetValue(PKEY_AppUserModel_ID, LPropVar) <> S_OK then Exit;
    LAppPath := LPropVar.bstrVal;
    LAppPath := 'shell:AppsFolder\' + LAppPath;
  end else begin
    // プロセスIDを取得
    // その値からプロセスのオープンハンドル取得
    GetWindowThreadProcessId(LAppWindow, Addr(LProcessID));
    LhProcess := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, LProcessID);
    try
      // EXE のフルパスを取得
      FillChar(LBuffer, SizeOf(LBuffer), #0);
      LSTR_SIZE := Length(LBuffer);
      LAppPath:='';
      if QueryFullProcessImageNameW(LhProcess,
                                    0,
                                    @LBuffer[0],
                                    @LSTR_SIZE) = 0 then Exit;
      LAppPath := LBuffer;
    finally
      CloseHandle(LhProcess);
    end;
  end;

  // ウィンドウのアイコンを取得して TListView に表示
  // TListView の Item の Data プロパティにウィンドウハンドルの値を代入
  LIcon := TIcon.Create;
  try
    if LIsUWPApp then begin
      LITemIDPath := ILCreateFromPath(PWideChar(LAppPath));
      SHGetFileInfo(Pointer(LITemIDPath),
                    0,
                    LSHFileInfo,
                    SizeOf(TSHFileInfo),
                    SHGFI_PIDL or SHGFI_ICON or SHGFI_SMALLICON);
      LIcon.Handle := LSHFILeInfo.hIcon;
      CoTaskMemFree(LITemIDPath);
    end else begin
      // ファイル名 (またはファイルの関連付け) から取得
      SHGetFileInfo(PChar(LAppPath),
                          0,
                          LSHFileInfo,
                          SizeOf(LSHFileInfo),
                          SHGFI_ICON or SHGFI_SMALLICON or SHGFI_SHELLICONSIZE);

     i := LSHFileInfo.iIcon;
     if i = -1 then i := 0;                                  // アイコンデータがない場合は未処理
     list := GetSystemImageList(SHIL_LARGE);                 // 指定サイズのアイコンを格納したシステムのイメージリストを取得
     LIcon.Handle := ImageList_GetIcon(list, i, ILD_NORMAL); // イメージリストから指定インデックスのアイコンのハンドルを取得

      //LIcon.Handle :=  LSHFileInfo.hIcon;
    end;

    LItem            := lPara.Items.Add;
    LItem.Caption    := GetWindowTitle(LAppWindow);
    LItem.Data       := PNativeUInt(LAppWindow);
    LItem.ImageIndex := lPara.SmallImages.AddIcon(LIcon);
  finally
    FreeAndNil(LIcon);
  end;
end;

//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitle(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetWindowText(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;

//-----------------------------------------------------------------------------
//  ウィンドウのクラス名を取得する関数
//-----------------------------------------------------------------------------
function GetWindowClassName(hWindow: HWND): string;
var
  LBuff : array [0..MAX_PATH - 1] of Char;
begin
  Result := '';
  FillChar(LBuff, SizeOf(LBuff), #0);
  if (GetClassName(hWindow, @LBuff, MAX_PATH) > 0) then begin
    Result := LBuff;
  end;
end;
end.
```

```ConfigForm.pas
unit ConfigForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.CheckLst,DataConfig;

type
  TFormConfig = class(TForm)
    btnOk: TButton;
    ChkKey: TCheckListBox;
    EdithotKey: TEdit;
    Label2: TLabel;
    ChkHotKey: TCheckBox;
    procedure EdithotKeyKeyPress(Sender: TObject; var Key: Char);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure ChkKeyClickCheck(Sender: TObject);
    procedure ChkHotKeyClick(Sender: TObject);
  private
    { Private 宣言 }
    FConfig: TDataConfig;
  public
    { Public 宣言 }
    property Config  : TDataConfig read FConfig;
  end;

var
  FormConfig: TFormConfig;

implementation

{$R *.dfm}

procedure TFormConfig.ChkHotKeyClick(Sender: TObject);
begin
  FConfig.HotKeyed := ChkHotKey.Checked;
end;

procedure TFormConfig.ChkKeyClickCheck(Sender: TObject);
begin
  FConfig.KeyShift := ChkKey.Checked[0];
  FConfig.KeyCtrl  := ChkKey.Checked[1];
  FConfig.KeyAlt   := ChkKey.Checked[2];
end;

procedure TFormConfig.EdithotKeyKeyPress(Sender: TObject; var Key: Char);
begin
  if (Key >= 'a') and (Key <= 'z') then begin
    Key := Char(Ord(Key) - Ord('a') + Ord('A'));
  end;
  EdithotKey.Text := Key;
  FConfig.HotKey := Key;
end;

procedure TFormConfig.FormCreate(Sender: TObject);
begin
  FConfig := TDataConfig.Create;
end;

procedure TFormConfig.FormDestroy(Sender: TObject);
begin
  FConfig.Free;
end;

procedure TFormConfig.FormShow(Sender: TObject);
begin
  ChkHotKey.Checked := FConfig.HotKeyed;
  ChkKey.Checked[0] := FConfig.KeyShift;
  ChkKey.Checked[1] := FConfig.KeyCtrl;
  ChkKey.Checked[2] := FConfig.KeyAlt;
  EdithotKey.Text := FConfig.HotKey;
end;

end.
```

```DataConfig.pas
unit DataConfig;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls,StringListEx,StringListKey,DMStanderd;

type
  TDataConfig = class(TDMPersistent)
  private
    { Private 宣言 }
    FHotKeyed : Boolean;
    FHotKey   : Char;          // 表示する操作キー
    FKeyShift : Boolean;       // True:重複禁止
    FKeyCtrl  : Boolean;
    FKeyAlt   : Boolean;
  public
    { Public 宣言 }
    constructor Create();virtual;
    destructor Destroy;override;

    //procedure Assign(Source : TPersistent);override;

  published
    function LoadFromFile(const Filename : string) : Boolean;
    function SaveToFile(const Filename : string) : Boolean;

    property HotKeyed : Boolean read FHotKeyed write FHotKeyed;

    property HotKey : Char  read FHotKey write FHotKey;
    //
    property KeyShift : Boolean read FKeyShift write FKeyShift;
    //
    property KeyAlt : Boolean read FKeyAlt write FKeyAlt;
    //
    property KeyCtrl : Boolean read FKeyCtrl write FKeyCtrl;
  end;

implementation

uses StringListRtti;

{ TDataConfig }

constructor TDataConfig.Create;
begin

end;

destructor TDataConfig.Destroy;
begin

  inherited;
end;

function TDataConfig.LoadFromFile(const Filename: string): Boolean;
var
  tss : TStringListRtti;
begin
  result := False;
  tss := TStringListRtti.Create;             // RTTI保存クラス生成
  try
    if not FileExists(FileName) then exit;   // ファイルが無ければ処理終了
    tss.LoadFromFile(FileName);               // ファイルを読み込む
    tss.LoadFromObject(Self);                 // オブジェクトに読み込み
    result := True;
  finally
    tss.Free;
  end;
end;

function TDataConfig.SaveToFile(const Filename: string): Boolean;
var
  tss : TStringListRtti;
begin
  tss := TStringListRtti.Create;         // RTTI保存クラス生成
  try
    tss.SaveToObject(Self);             // RTTI管理クラスにオブジェクトを保存
    tss.SaveToFile(FileName);             // ファイルに保存
    result := True;
  finally
    tss.Free;
  end;
end;

end.
```