Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2016年09月14日] [更新 2018年03月06日] Delphi サンプルプログラム集
909_既存 EXE のアプリケーションアイコンを変更
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
909_ChangeExeIcon.zip [3,521 KB] 2018年03月06日版 (EXE 同梱)







EXE ファイルのアプリケーションアイコン


自分自身ではない EXE ファイルのアプリケーションアイコンを変更するサンプルです。
アプリケーションアイコンは、リソースとして EXE に埋め込まれています。実行中の EXE のリソースは変更できまぜん。したがって、実行中の EXE のアイコンは変更できません。

アプリケーションアイコンとは、エクスプローラのファイルの左端や、ショートカットを作成した時、あるいはウィンドウのキャプションバーの左端に表示されるアイコンのことです。
アプリケーションが内部で使用するアイコン (画像類) のことではありません。

リソースの追加、削除、置き換えを行う Windows API の関数は UpdateResource ですが、筆者の確認では、マルチアイコンを設定しても登録されず、既存のアイコンが削除されてしまいました。そこで、本ページのサンプルでは madRes というユニットを使用しています。

madshi.net から madCollection.exe をダウンロードして実行します。機能を選択してインストールすると、インストール済みの Delphi を検出して設定してくれます。ライブラリのパスの設定もしてくれます。アンインストーラも添付されています。後は uses に madRes を追加して利用するだけです。
関数類の使用方法は Windows API の同じような名前の関数とほぼ同じです。

madRes.pas は madBasic に含まれているユニットです。したがって、madBasic をインストールする必要があります。



図1
ダウンロードした madCollection.exe を起動してインストール
  • 今回はこの状態でインストールした

インストールが終了すると下図のダイアログが表示されます。[コンポーネント] [パッケージのインストール] のダイアログで確認すると、パッケージが 2 つ追加されているのが確認できます。



図2
インストール終了
図3
パッケージが 2 つ追加されている

[備考 1]
システム関係フォルダ内の EXE のリソースは変更できない場合があります。これは該当ファイルのアクセス権 (セキュリティ) によります。また、Program Files (Program Files (x86)) 内の EXE ファイルのリソースの変更には管理者権限での実行が必要です。

エクスプローラ等でコピーして、システムフォルダに貼り付ける際、許可を求めるダイアログが現れます。この時に許可してシステムフォルダ内に配置すれば、そのファイルは、操作したユーザに対してアクセス権が発生します。

[備考 2]
アプリケーションアイコンのリソースの型名は Icon Group ですが、このリソースに外部からアクセスするには、RT_GROUP_ICON という識別子を使用します。

[備考 3]
本ページの記事を書く際に使用したリソースエディタは、EXE のアイコンが変更しても表示は変化しません。アイコンを変更した後に EXE を再度読み込んで表示しています (実際にはドラッグ・アンド・ドロップ操作)。




01_Delphi で作成した EXE ファイルのアイコンを変更


Delphi で作成した EXE で、言語識別子が日本語の EXE のみを対象としたサンプルです。

Delphi の場合、設計時であれば [プロジェクト] [オプション] でアプリケーションアイコンを設定できます。下図はその設定のダイアログです。
言語識別子が日本語限定ですが、本サンプルを使用すると、作成済みの EXE のアプリケーションアイコンを、IDE でプロジェクトを開かなくても変更することができます。



図4
設計時におけるアプリケーションアイコンの設定

下図が本サンプルの設計時の画面です。
アイコンを変更する EXE と、変更するアイコンのファイルの入力欄があります。入力欄の右端のボタンをクリックするとダイアログでファイルを選択できます。

Delphi で作成した EXE のアプリケーションアイコンは、MAINICON というリソース識別子で格納されています。日本語版の場合、デフォルトでは言語識別子は日本語しかありません。一般的に、リソースの操作にはリソース識別子と言語識別子が必要ですが、これを固定値として操作します。

[Button1] をクリックするとアイコンを変更します。この時、元のアプリケーションアイコンをディスクに保存します。保存したこのアイコンファイルを使用すれば、元のアイコンに戻せます。ただし、保存名は MAINICON.ico の決め打ちにして、上書き保存としています。

言語識別子は MAKELANGID マクロを使用しなくても直接数値を指定しても認識しますが、リソース識別子が数字の場合は MAKEINTRESOURCE マクロを使用して作成しないと認識してくれません。Delphi のように文字列の場合は MAKEINTRESOURCE を使用する必要はありません。



[備考]
コンソールアプリケーションには MAINICON というリソース識別子はありません。したがって、コンソールアプリのアプリケーションアイコンは、本項のサンプルでは変更できません。


図5
設計時の画面
  • プロジェクトを開かなくても、作成済みの EXE のアプリケーションアイコンの変更が可能

下図は添付の Target.exe と Multi.ico ファイルを使用した実行結果です。
Target.exe は Delphi XE Pro で作成したフォームだけのプログラムです。Multi.ico ファイルはフリーのアイコン画像で、マルチアイコンファイルです。



図6
アイコンを変更する前
図7
[Button1] クリックでアイコン変更
  • エクスプローラのアイコンが変更されている
  • デスクトップのショートカットのアイコンも変更されている
  • 元のアイコンは MAINICON.ico の名前で自動保存してある
  • ただし、上書き保存なので注意

リソースエディタで EXE 内のリソースの変更を確認したのが下図です。Icon Group にある MAINICON の画像が入れ換わっているのが確認できます。



図8
アイコンを変更する前
  • 1041 は日本語の言語識別子の値
図9
アイコン変更後
  • MAINICON のアイコンが全て削除されて、新しいアイコンになっている

リスト1
Delphi で作成した EXE のアプリケーションアイコンを変更
ただし、言語識別子が日本語の場合の専用コード
言語識別子の設定コード (1 行) を変更すれば他の言語でも使用可能
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    SpeedButton2: TSpeedButton;
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    OpenDialog2: TOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure SpeedButton2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  madRes,
  ShlObj;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//
//  Program Files内のEXEは管理者権限でないとアイコンの変更はできない
//  システム関係フォルダ内のEXEは管理者権限でも変更できない(アクセス権による)
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  LabeledEdit1.AutoSelect := False;
  LabeledEdit2.AutoSelect := False;
  ActiveControl := LabeledEdit1;

  LabeledEdit1.Text := ExpandFileName('..\Target.exe');
  LabeledEdit2.Text := ExpandFileName('..\Multi.ico');
end;

//=============================================================================
//  アプリケーションアイコンを変更するEXEファイル名を選択
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  OpenDialog1.Filter := 'exe ファイル (*.exe)|*.exe';
  if OpenDialog1.Execute then begin
    //.lnkファイルは"(ダブルクォーテーション・引用符)が付いてくることがある
    //それを取り除いておく
    LabeledEdit1.Text := AnsiDequotedStr(PChar(OpenDialog1.FileName), '"');
    OpenDialog1.InitialDir := ExtractFileDir(OpenDialog1.FileName);
  end;
end;

//=============================================================================
//  アプリケーションアイコンとして設定するアイコンファイルを選択
//=============================================================================
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
  OpenDialog2.Filter := 'アイコンファイル (*.ico)|*.ico';
  if OpenDialog2.Execute then begin
    LabeledEdit2.Text := OpenDialog2.FileName;
    OpenDialog2.InitialDir := ExtractFileDir(OpenDialog2.FileName);
  end;
end;

//=============================================================================
//  EXEのアプリケーションアイコンを変更
//  Delphiで作成したEXEで,リソース言語が日本語の場合
//  madResの関数類を使用したコード
//
//  アイコン変更と同時に現在のEXEのアプリケーションアイコンを保存する
//  保存場所と保存名はコード内で決め打ち
//
//  SHChangeNotifyの使用にはusesにShlObjが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LExePath      : string;
  LIconPath     : string;
  LResName      : string;
  LLanguage     : Word;
  LResHandle    : DWORD;
  LSaveIconPath : string;
  LNewIconPath  : string;
  LText         : string;
begin
  LExePath  := Trim(LabeledEdit1.Text);
  LIconPath := Trim(LabeledEdit2.Text);

  //LLanguageの値は日本の場合1041
  LResName  := 'MAINICON';
  LLanguage := MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT);

  LResHandle := madRes.BeginUpdateResourceW(PChar(LExePath), False);
  if LResHandle = 0 then  begin
    LText := 'アイコンの変更はできませんでした。' ;
    MessageBox(Handle, PChar(LText), '情報', MB_ICONINFORMATION);
  end else begin
    //現在のアイコンをファイルに保存
    //保存名は固定
    LSaveIconPath := ExpandFileName('..\MAINICON.ico');
    if LSaveIconPath = LIconPath then begin
      LNewIconPath := ChangeFileExt(LIconPath, '') + '_01.ico';
      RenameFile(LIconPath, LNewIconPath);
      LIconPath := LNewIconPath;
    end;

    if madRes.SaveIconGroupResourceW(LResHandle,
                                  PChar(LResName),
                                  LLanguage,
                                  PChar(LSaveIconPath)) then begin
      //新しいアイコンを設定する
      madRes.LoadIconGroupResourceW(LResHandle,
                                    PChar(LResName),
                                    LLanguage,
                                    PChar(LIconPath));
      DeleteFile(LNewIconPath);
    end else begin
      //アイコンが保存できない時は,該当するアイコンがない場合なので中止する
      LText := '識別子が MAINICON のリソースが見つかりませんでした。' + sLineBreak
             + 'アイコンの変更を中止します。';
      MessageBox(Handle, PChar(LText), '情報', MB_ICONINFORMATION);
    end;
    madRes.EndUpdateResourceW(LResHandle, False);
    //システムにアイコンの変更を通知
    //EndUpdateResourceWの後に実行すること
    SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, nil, nil);
  end;
end;

end.




02_LANG_NEUTRAL を使用した汎用的な方法


前項のサンプルは Delphi で作成されていて、しかも言語識別子が日本語限定でした。以下のコードは、Delphi 以外で作成された EXE のアプリケーションアイコンも変更可能にしたサンプルです。

言語識別子が LANG_NEUTRAL のリソースのアイコンが優先的に使われるようです。そこで、本サンプルでは、Icon Gruop (RT_GROUP_ICON) の言語識別子が LANG_NEUTRAL の項目を追加してそこにアイコンを格納する方式としてみました。つまり、リソースの追加です。この状態で別のアイコンを設定すると、この項目内のアイコンが入れ換わります。
この項目を削除すれば元のアプリケーションアイコンに戻すことができます。この項目がない状態で削除のコードを実行しても何も変化しません。

下図は前項と同じく、添付の Target.exe と Multi.ico ファイルを使用した実行結果をリソースエディタで確認した結果です。



図10
アイコンの変更前
  • MAINICON は Delphi で作成した EXE のアプリケーションアイコンのリソース識別子
  • 1041 は日本語の言語識別子
図11
アイコンの変更後
  • リソース識別子 MAINICON に言語識別子が 0 の項目が追加されている
  • [Restore (Delete)] ボタンクリックはこの項目 (リソース) を削除する

下図はエクセルの実行ファイルである EXCEL.EXE のアプリケーションアイコンを変更する例です。
確かに変更されていることが確認できます。筆者のエクセルは Program Files (x86) 内にあるため、本サンプルを管理者権限 ([管理者として実行]) で起動して実行しています。



図12
アイコンを変更する前
図13
アイコン変更後
  • 変更は管理者権限 ([管理者として実行]) で実行
  • エクスプローラのアイコンが変更されている
  • デスクトップのショートカットのアイコンも変更されている

これもリソースエディタで確認してみます。


図14
アイコンを変更する前
  • 257 が Icon Group の最初のリソース識別子
  • 1033 は英語 (米国) の言語識別子
図15
アイコン変更後
  • 変更は管理者権限 ([管理者として実行]) で実行
  • 最初のリソース識別子である 257 に言語識別子が 0 の項目が追加されている
  • [Restore (Delete)] ボタンクリックはこの項目 (リソース) を削除する

リスト2
EXE 内のアプリケーションアイコンを変更する汎用的なサンプル
実際にはリソースの追加なので、追加したリソースを削除すれば元に戻せる
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    SpeedButton2: TSpeedButton;
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    OpenDialog2: TOpenDialog;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure SpeedButton2Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  madRes,
  ShlObj;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  EnumResourceNamesのコールバック関数
//  RT_GROUP_ICON(icon group)の最初のリソースの識別子(文字列)を取得
//-----------------------------------------------------------------------------
function EnumResNames(hModule: HWND; lpszType: LPCTSTR; lpszName: LPTSTR;
  lParam: PString): LongBool; stdcall;
var
  LResIDStr : string;
begin
  Result := True;

  //リソース名のチェック
  //上位WORD(2バイト)が 0ならIDは数値.それ以外は文字列のポインタ
  if HiWord(Cardinal(lpszName)) = 0 then begin
    LResIDStr := IntToStr(Cardinal(lpszName));
  end else begin
    LResIDStr := lpszName;
  end;
  lParam^ := LResIDStr;

  if lParam^ <> '' then Result := False;
end;

//=============================================================================
//  フォーム生成時の処理
//
//  Program Files内のEXEは管理者権限でないとアイコンの変更はできない
//  システム関係フォルダ内のEXEは管理者権限でも変更できない
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  LabeledEdit1.AutoSelect := False;
  LabeledEdit2.AutoSelect := False;
  ActiveControl := LabeledEdit1;

  LabeledEdit1.Text := ExpandFileName('..\Target.exe');
  LabeledEdit2.Text := ExpandFileName('..\Multi.ico');
end;

//=============================================================================
//  アプリケーションアイコンを変更するEXEファイル名を選択
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  OpenDialog1.Filter := 'exe ファイル (*.exe)|*.exe';
  if OpenDialog1.Execute then begin
    //.lnkファイルは"(ダブルクォーテーション・引用符)が付いてくることがある
    //それを取り除いておく
    LabeledEdit1.Text := AnsiDequotedStr(PChar(OpenDialog1.FileName), '"');
    OpenDialog1.InitialDir := ExtractFileDir(OpenDialog1.FileName);
  end;
end;

//=============================================================================
//  アプリケーションアイコンとして設定するアイコンファイルを選択
//=============================================================================
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
  OpenDialog2.Filter := 'アイコンファイル (*.ico)|*.ico';
  if OpenDialog2.Execute then begin
    LabeledEdit2.Text := OpenDialog2.FileName;
    OpenDialog2.InitialDir := ExtractFileDir(OpenDialog2.FileName);
  end;
end;

//=============================================================================
// アプリケーションアイコンを変更
//  実際にはリソースの追加
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LExePath    : string;
  LIconPath   : string;
  LhModule    : THandle;
  LResIDName  : string;
  LpResIDName : PChar;
  LResIDInt   : Integer;
  LLanguage   : Word;
  LResHandle  : DWORD;
  LText       : string;
begin
  LExePath  := Trim(LabeledEdit1.Text);
  LIconPath := Trim(LabeledEdit2.Text);

  //RT_GROUP_ICON(icon group)の最初のリソースの識別子(文字列)を検索
  LhModule := LoadLibraryEx(PChar(LExePath),
                            0,
                            LOAD_LIBRARY_AS_DATAFILE or
                            LOAD_WITH_ALTERED_SEARCH_PATH);
  if LhModule <> 0 then begin
    EnumResourceNames(LhModule, RT_GROUP_ICON, @EnumResNames, LPARAM(@LResIDName));
    FreeLibrary(LhModule);
  end;
  if LResIDName = '' then begin
    LText := 'アイコンの変更はできませんでした。' ;
    MessageBox(Handle, PChar(LText), '情報', MB_ICONINFORMATION);
    exit;
  end;


  //リソースIDの文字列からリソースIDを作成
  LResIDInt := StrToIntDef(LResIDName, -1);
  if LResIDInt < 0 then begin
    LpResIDName := PChar(LResIDName);
  end else begin
    LpResIDName := PChar(MakeIntResource(IntToStr(LResIDInt)));
  end;

  LLanguage := MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);

  //指定リソース識別と言語識別子でアプリケーションアイコンのリソースを追加
  LResHandle := madRes.BeginUpdateResourceW(PChar(LExePath), False);
  if LResHandle = 0 then begin
    LText := 'アイコンの変更はできませんでした。' ;
    MessageBox(Handle, PChar(LText), '情報', MB_ICONINFORMATION);
  end else begin
    madRes.LoadIconGroupResourceW(LResHandle,
                                  LpResIDName,
                                  LLanguage,
                                  PChar(LIconPath));
    madRes.EndUpdateResourceW(LResHandle, False);

    //システムにアイコンの変更を通知
    //EndUpdateResourceWの後に実行しないと無効
    Button3Click(nil);
  end;
end;

//=============================================================================
// アプリケーションアイコンを元に戻す
//  実際には追加したリソースの削除
//  最初のリソース識別子の言語識別子が0のリソースを削除する
//  ない場合は削除しない
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LExePath    : string;
  LhModule    : THandle;
  LLanguage   : Word;
  LResHandle  : DWORD;
  LResIDName  : string;
  LpResIDName : PChar;
  LResIDInt   : Integer;
begin
  //.lnkファイルをTOpenDialogで選択する"(ダブルクォーテーション・引用符)が付いて
  //くることがある。それを取り除く
  LExePath  := AnsiDequotedStr(PChar(LabeledEdit1.Text), '"');

  //RT_GROUP_ICON(icon group)の最初のリソース識別子(文字列)を検索
  LhModule := LoadLibraryEx(PChar(LExePath),
                            0,
                            LOAD_LIBRARY_AS_DATAFILE or
                            LOAD_WITH_ALTERED_SEARCH_PATH);
  if LhModule <> 0 then begin
    EnumResourceNames(LhModule, RT_GROUP_ICON, @EnumResNames, LPARAM(@LResIDName));
    FreeLibrary(LhModule);
  end;
  if LResIDName = '' then exit;

  //リソースIDの文字列からリソースIDを作成
  LResIDInt := StrToIntDef(LResIDName, -1);
  if LResIDInt < 0 then begin
    LpResIDName := PChar(LResIDName);
  end else begin
    LpResIDName := PChar(MakeIntResource(IntToStr(LResIDInt)));
  end;


  //言語識別子がこの値のアイコンリソースを削除する
  //ない場合は削除しない
  LLanguage := MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);

  //追加したアプリケーションアイコンのリソースを削除
  LResHandle := madRes.BeginUpdateResourceW(PChar(LExePath), False);
  if LResHandle <> 0 then begin
    madRes.DeleteIconGroupResourceW(LResHandle, LpResIDName, LLanguage);
    madRes.EndUpdateResourceW(LResHandle, False);

    //システムにアイコンの変更を通知
    //EndUpdateResourceWの後に実行しないと無効
    Button3Click(nil);
  end;
end;

//=============================================================================
//  システムにアイコンの変更を通知
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
begin
  SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, nil, nil);
end;

end.




03_EXE のショートカットファイルを選択した時の動作


上の 2 つのサンプルでは、TOpenDialog.Options に ofNoDereferenceLinks を指定していません。したがって、デスクトップ等に作成した .lnk のショートカットファイルも選択できます。
ショートカットファイルを選択した場合は、入力欄にセットされるのは、そのショートカットのリンク元の EXE のフルパスです。その EXE のアプリケーションアイコンを変更します。EXE のアプリケーションアイコンを変更すれば、そのショートカットのアイコンも変わります。

上のサンプルでは、ショートカットのアイコンだけの変更はできません。ショートカットのアイコンだけを変更するには、IShellLink, IPersistFile インターフェイスを使用します。






04_実行時におけるアプリケーションアイコンの設定


アプリケーションアイコンは、以下のようにプロジェクトのソースコード、フォームの OnCreate イベントで設定あるいは変更できます。



program Project1;

uses
  Forms,
  SysUtils,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.Icon.LoadFromFile(ExpandFileName('..\Multi.ico'));
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.


procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.Icon.LoadFromFile(ExpandFileName('..\Multi.ico'));
end;


ただし、この設定を実行しても、エクスプローラのアイコンやショートカットのアイコンは変更されません。実行中の EXE のリソースの変更はできません。これは上のサンプルでも同じです。

このコードは Application.icon プロパティの値を設定していますが、実際にはフォームのアイコンを設定するのと同じ結果になります。
TForm.Icon を設定しなければ、フォームのアイコンには Application.Icon が使われます。




図16
実行時にアプリケーションアイコンを設定した結果
  • エクスプローラのアイコンは変わらない
  • ショートカットを作成しても、そのアイコンは変わらない




05_アイコン更新通知メッセージとエクスプローラの表示アイコン更新


本ページのサンプルでは、アイコンを変更した後に SHChangeNotify 関数を実行して、システムにアイコンの変更を通知しています。これを実行しないと変更が反映されない場合があります。

次のコードは指定ディレクトリ内だけに変更を通知するものですが、このコードでは、アイコンを変更した EXE のショートカットを作成してもアイコンは元のままのことがあります。また、既に作成しているショートカットのアイコンも変更されない場合があります。


  SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, PChar(DirPath), nil);


本サンプルの場合に限らず、アイコンの変更が反映されない現象は Windows 7 になって多く発生するようになったようです。
ファイルのアイコンを変更したり、拡張子の関連付けを変更しても、アイコンが更新されないこともあります。そのような場合も以下の Button1Click のコードを実行すると効果があるようです。
このコードは、エクスプローラでファイルのアイコン表示が更新されない場合にも有効なようですが、筆者が確認しているのは Windows 7 (SP1) (64 ビット版) だけです。

このコードを実行しても、Windwos 2000 以前の古い形式の .cur (カーソル) ファイルの画像は正常に表示できません。かえって表示がおかしくなります。



図17
実行時の画面


[備考 1]
異なるバージョンの Delphi で作成されたプロジェクトをコンパイルした時に、アプリケーションアイコンが変化しないのは別の原因が考えられます。例えば、以前のアプリケーションアイコンのリソースが使われていることがあります。


[備考 2]
28931 というコマンドの値は、以下のサンプルのように、グローバルフックでメッセージを捕捉 (フック) することで調べることができます。



リスト3
システムへのアイコンの更新通知とエクスプローラの表示アイコンの更新
更新には多少のタイムラグがある
uses
  ShlObj,
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  拡張子の関連付けを変更してもファイルのアイコン画像が変更されない場合の処理
//  エクスプローラのアイコン表示が更新されない場合も利用可能
//  [F5] ([表示][最新の情報に更新] を実行して関連付けの変更を通知
//
//  SHChangeNotifyの使用にはusesにShlObjが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  SendMessage(HWND_BROADCAST, WM_COMMAND, 28931, 0);
  //実際には以下だけでよい
  SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
end;

//=============================================================================
//  エクスプローラの表示アイコンを更新する例
//  ie4uinit.exeを使用
//  ie4uinit.exeはIE,エクスプローラの初期化用ユーティリティ
//  64ビットのWindowsには64ビットのie4uinit.exeしかない
//
//  このコードは64ビットのWindowsで32ビットのEXEから実行する例
//  Windows10では-ClearIconCacheの代わりに-showを指定する
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LCmdExe   : string;
  LCmdParam : string;
begin
  LCmdExe   := 'C:\windows\Sysnative\ie4uinit.exe';
  LCmdParam := '-ClearIconCache';
  ShellExecute(Handle, '', PChar(LCmdExe), PChar(LCmdParam), nil, SW_HIDE);
end;




06_エクスプローラでのカーソルファイルのアイコン表示


前項のコードを実行すると、ファイルのショートカットのアイコン、あるいはエクスプローラでのファイルのアイコンを更新することができますが、一部のカーソルファイルのアイコンが正しく表示されなくなりります。正しく表示されないのは、古い Windows の時代に作成された 2 色や 16 色のカーソルファイルのようです。
下図はこのことを確認した結果です。Windows XP(SP3) ではこの現象は発生しません。

これらのカーソルファイルは、TOpenPicture ダイアログで表示する場合は、本来のマウスカーソルの画像として正常に表示されます。



図18
正常なアイコン表示
  • [Windows] [Cursors] フォルダ内の一部のカーソルファイルの例
図19
前項のコードを実行した結果
  • 前項のコードを実行する度に変化する
  • 変化するのは 2 色や 16 色のカーソルファイル
  • 32 ビット色のカーソルファイルは正常表示
  • 何故か Delphi 関係のアイコンがいくつも登場している

コンピュータの再起動が必要ですが、以下のウェブサイトで提供しているバッチファイル施用すると、前項のコードを実行して表示が不正になったカーソルファイルの表示を元に戻すことができます。
当然ですが、以下の方法で正しく表示されたとしても、再度前項のコード実行すればまた該当カーソルの画像でない表示になります。何か手段があるのかも知れませんが、この「文字化け」ならぬ「アイコン化け」は避けられない現象ではないかと思われます。

以下のページから [Rebuild_Icon_Cache.bat] をダウンロードします。ダウンロードしたらブロックの解除をしてデスクトップに置きます。このバッチファイルはデスクトップにあることを前提にして動作するようになっています。
実行したら、エクスプローラ等を表示しないでコンピュータを直ちに再起動します。このバッチファイルはエクスプローラのキャッシュをクリアしますが、キャッシュは、エクスプローラのプロセスを終了させてコンピュータを再起動しないと正常に削除されません。完全に削除する前にエクスプローラを表示してしまうとキャッシャの削除が正常に行われません。



上記のバッチファイルでも元に戻らない場合は、同じサイトの以下のページからレジストリ修正用のファイルをダウンロードして実行します。このレジストリ修正用のスクリプトは、ファイルの関連付けの設定を修正するものです。

[Default Windows 7 File Extension Types:] の下にダウンロードのリンクがあります。ダウンロードしたらブロックの解除をして zip ファイルを解凍します。その中の Default_CUR.reg ファイルをデスクトップに置いて実行します。終了したら上記のバッチファイルを実行してコンピュータを直ちに再起動します。



[備考]
バッチファイルで実行している ie4uinit.exe は IE の初期化用のユーティリティです。IE, エクスプローラのアイコンキャッシュデータである .db 形式ファイルの操作用です。
以下のサイトにも、このユーティリティを使用した、上記と同様の VBS のスクリブが提供されています。この VBS (cleariconcache.vbs) には Windows 10 用のコードも含まれています。

記事によるとコンピュータの再起動は必要ないようですが、古い形式のカーソルファイルの表示は、スクリプトの実行後に直ちにコンピュータの再起動が必要なようです。