1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

最新でもモダンでもないVB.NET開発で、ChatGPTとCodexに手伝ってもらった話

1
Posted at

最新でもモダンでもないVB.NET開発で、ChatGPTとCodexに手伝ってもらった話

はじめに

Firefox 154で、Windows版Firefoxの設定画面から既定のブラウザーアイコンを変更できる機能が追加されました。
Mozillaの公式ブログによると、現時点ではWindows限定で、MSIX/Microsoft Store版は対象外とのことです。

またドコモSMTBネット銀行も、「ドコモの銀行」の新しいアイコンに加えて従来デザインなどから利用者がアプリアイコンを選べる機能を、2026年9月末ごろの提供開始を目指して開発していると発表しています。

こうした事例を続けて見かけて、

アプリアイコンをユーザーが選べるようにするのは、今後ちょっとしたUIカスタマイズの一つとして増えてくるのでは?

と思いました。

そこで、開発しているWindows向け業務アプリケーションでも、同じような利用体験を実現できないか試してみました。

今回の対象は次の構成です。

  • VB.NET
  • .NET Framework 4.8
  • Windows Forms
  • WPF
  • 旧形式のプロジェクトファイル

決して最新の開発環境ではありません。

普段の仕事も、新しいフレームワークを使った新規開発より、長年動いている業務アプリケーションを保守しながら、必要な機能を小さく追加していくことが中心です。

今回はそんな環境で、次の流れを試しました。

ChatGPTと要件・実装方針を壁打ち
        ↓
Markdownで実装計画を作る
        ↓
Codexに「問題を探す側」としてレビューさせる
        ↓
計画を修正する
        ↓
Codexに実装とテストを依頼する
        ↓
導入ガイドをMarkdownとして残す
        ↓
別プロジェクトへ横展開する

結果として、アイコン変更機能そのものだけでなく、

最新でもモダンでもないVB.NET開発でも、生成AIはかなり普通に使える

ことが印象に残りました。


今回実装したもの

利用者が設定画面からアプリアイコンを選び、その状態を次回起動後も維持できる機能を実装しました。

具体的には次の内容です。

  • 4種類の組み込みアイコンから選択できる
  • 正方形のPNGまたはICOをCustomアイコンとして取り込める
  • 起動中のWPF Window/Windows Forms Formへ反映できる
  • 後から開くWindow/Formにも現在のアイコンを適用できる
  • 自アプリを指す既存のDesktop/Start Menu/Taskbarショートカットのアイコンを更新できる
  • 選択したアイコンIDをLocalAppDataへ保存し、再起動時に復元できる
  • WPFとWindows Formsで設定、ICO生成、Shell Link処理を共有できる

WPF版

スクリーンショット 2026-08-31 112544.png

Windows Forms版

スクリーンショット 2026-08-31 112651.png

Firefoxのソースコードや内部実装を移植したわけではありません。

「設定画面から好みのアイコンを選べる」という体験を、従来型のWindowsデスクトップアプリ向けに独自設計しています。


まず「どのアイコンを変えるのか」を決める

Windowsでいう「アプリアイコン」は一つではありません。

  • EXEファイルに埋め込まれたアイコン
  • Window/Formのタイトルバー
  • Alt+Tab
  • 通常のタスクバーボタン
  • Desktopショートカット
  • Start Menuショートカット
  • Taskbarにピン留めされたショートカット

今回はEXE本体を書き換えません。

EXEを変更すると、Authenticode署名、更新処理、ウイルス対策ソフトの評価、障害時の復旧などへ影響するためです。

対応範囲は次のように整理しました。

表示場所 対応
WPF Window/Windows Forms Form 実行中に変更する
後から開くWindow/Form 現在の選択を適用する
Alt+Tab/通常のタスクバーボタン Window/Formのアイコンとして反映する
Desktop/Start Menu 自アプリを指す既存.lnkを更新する
Taskbarのピン留め 対応する.lnkの更新を試行するが、表示反映は保証しない
EXE本体 変更しない
Explorerのアイコンキャッシュ 削除しない

この整理を先にしておかないと、

Windowは変わったけど、ショートカットは変わらない

.lnkは更新できたけど、Taskbarの表示がすぐ変わらない

といった結果を、実装漏れなのかWindows側の制約なのか判断できなくなります。


最初からCodexに実装を頼まず、ChatGPTと壁打ちした

最初からCodexへ、

アプリアイコンを変更できるようにして

と依頼することもできます。

ただ今回は、いきなりコードを書いてもらわず、ChatGPTと次の論点を整理しました。

  • .NET Framework 4.8で実現可能か
  • WPFとWindows Formsで何が異なるか
  • EXEを変更せずにどこまで反映できるか
  • 組み込みアイコンとCustomアイコンをどこへ保存するか
  • 選択状態をどのように永続化するか
  • 既存ショートカットを安全に更新するにはどうするか
  • 不正な設定や壊れたICOがあった場合にどうするか
  • 既存プロジェクトへの変更を小さくするにはどうするか
  • 別のVB.NETアプリへ再利用できる構成にするにはどうするか

ここで欲しかったのは、コードそのものではなく、実装前の論点整理です。

既存業務アプリでは、機能が動くだけでなく、次の条件も重要です。

  • 既存のFrameworkとPlatformTargetを変えない
  • VB.NETから別言語へ移行しない
  • 既存のUI構成を無理にMVVM化しない
  • 既存の設定や配布方法を壊さない
  • 新しいNuGet依存を安易に増やさない
  • Designerで保守できる状態を維持する

これらも要件として実装計画へ含めました。


ChatGPTとCodexの間にMarkdownを置く

ChatGPTとの壁打ち結果は、会話の中だけで終わらせず、Markdownファイルへ保存しました。

実装計画_Markdownファイル

ChatGPTと実装する「Firefox 154風」アプリアイコン変更機能 実装計画

はじめに

Firefox Nightlyの公式ブログでは、Firefox 154からWindows版の設定画面で既定のブラウザーアイコンを変更できる機能が追加されたと紹介されています(Windows限定、MSIX/Microsoft Store版は対象外)。

本計画では、この「アプリの設定画面から好みのアイコンを選べる」という体験を、VB.NET/.NET Framework 4.8のWindowsデスクトップアプリで再現します。

Firefoxのソースコードや内部実装を移植するものではありません。WPF版とWindows Forms版で同じ機能とデータを共有できるよう、Windowsデスクトップアプリ向けに独自設計します。

1. 目的

次のアプリアイコン変更機能を、WPFアプリとWindows Formsアプリへ同時に実装します。

  • 設定画面で複数の組み込みアイコンから1つを選択できる。
  • 正方形のPNGまたはICOを、ユーザー独自のCustomアイコンとして取り込める。
  • 選択したアイコンを、起動中のWindow/Formへ即時反映できる。
  • 自アプリを指す既存のDesktop/Start Menu/Taskbarショートカットへ、選択アイコンを安全に設定できる。
  • 選択状態をユーザー領域へ保存し、次回起動時に復元できる。
  • WPFとWindows Formsで設定、ICO生成、Shell Link処理を共通化できる。

2. 実装方針

2.1 EXEは変更しない

配布済みEXEそのものを書き換えると、署名、ウイルス対策ソフトの評価、更新処理、復旧方法へ影響します。そのため、本機能ではEXEを変更しません。

変更対象は次の3点に限定します。

  1. 実行中のWPF WindowまたはWindows Forms Formのアイコン
  2. 自アプリを指すユーザー領域の既存ショートカットのIconLocation
  3. ユーザー領域へ保存する選択アイコンIDとCustom ICO

2.2 CoreとUIアダプターを分離する

WPF型とWindows Forms型を共通ライブラリへ持ち込まず、次の3アセンブリに分けます。

WPFアプリ
  └─ Appearance.WPF.dll
       └─ Appearance.Core.dll

Windows Formsアプリ
  └─ Appearance.WinForms.dll
       └─ Appearance.Core.dll
アセンブリ 責務
Appearance.Core.dll 設定保存、組み込みICO管理、PNG/ICO取込、ICO生成、Shell Link探索・更新、処理結果
Appearance.WPF.dll WPF設定画面、Window.Iconへの反映、Dispatcher制御
Appearance.WinForms.dll Forms設定画面、Form.Iconへの反映、UIスレッド制御、Iconの解放

ホストアプリは、自身のUI方式に対応するアダプターだけを参照します。Coreの内部サービスをホストへ公開しません。

2.3 WPF版とForms版で同じ仕様を使う

両UI版で次の仕様を共通にします。

  • 組み込みアイコン: Default、Blue、Red、Classic
  • Customアイコン: 固定1枠。新しい取込で置き換える。
  • PNG取込後は、Customを選択して即時適用する。
  • 設定保存先、ファイル名、アイコンID、入力検証を共通化する。
  • 一部のショートカット更新に失敗しても、Window/Formへの適用や設定保存は可能な範囲で続行する。

3. 保存先

設定とアイコンは、インストールフォルダーではなく現在のユーザーのLocalAppDataへ保存します。

%LOCALAPPDATA%\<ProductId>\Appearance
├─ settings.xml
└─ Icons
   ├─ custom.ico
   └─ BuiltIn
      ├─ default.ico
      ├─ blue.ico
      ├─ red.ico
      └─ classic.ico
  • ProductIdは公開後も変えない安定した識別子とする。
  • settings.xmlには少なくとも選択中のAppIconIdを保存する。
  • 設定ファイルがない初回起動ではdefaultを使用する。
  • 設定XMLが破損、不正バージョン、または未知のアイコンIDを含む場合は、安全にdefaultへフォールバックする。
  • Custom ICOが欠落または破損している場合もdefaultへフォールバックする。
  • 組み込みICOはCoreへ埋め込み、初期化時に物理ファイルとして展開する。

Shell LinkのIconLocationには埋め込みリソースを直接指定できないため、組み込みアイコンも物理ICOとして展開します。

4. データモデルと処理結果

4.1 アイコン定義

AppIconDefinitionは次の情報を持ちます。

Friend NotInheritable Class AppIconDefinition
    Public ReadOnly Property Id As String
    Public ReadOnly Property DisplayName As String
    Public ReadOnly Property IconPath As String
End Class

アイコンIDは保存データとして使用するため、表示名とは分離します。

default / blue / red / classic / custom

4.2 適用結果

適用処理は単純なBooleanではなく、次の結果を個別に保持します。

  • Window/Formへ反映できたか
  • 設定を保存できたか
  • 各ショートカットが対象EXEと一致したか
  • ショートカットファイルを更新できたか
  • Shellへ変更通知を送れたか
  • 利用者向けWarning/Error
  • ログへ出す診断情報とHRESULT

これにより、「画面には反映できたが、一部のショートカットだけ更新できなかった」という部分成功を正しく表示できます。

5. 共通Coreの実装

5.1 初期化

Core初期化時に次を行います。

  1. ProductIdと対象EXEの絶対パスを検証する。
  2. LocalAppData配下の保存先を解決する。
  3. 組み込みICOをIcons\BuiltInへ展開する。
  4. settings.xmlを読み込む。
  5. Custom ICOが存在する場合は検証して候補へ加える。
  6. 選択済みアイコンを復元できる状態にする。

異なるProductIdや対象EXEで、同じUIアダプターを実行中に再初期化することは許可しません。

5.2 組み込みアイコン

組み込みICOはアセンブリのEmbedded Resourceを正本とします。

  • 初回起動時はLocalAppDataへ展開する。
  • 展開先が欠落していた場合は再作成する。
  • 内容が組み込み資産と異なる場合は修復する。
  • アプリのインストール先へ書き込まない。

5.3 Customアイコンの取込

対応入力は.pngと.icoです。拡張子だけを信用せず、ファイル内容も検証します。

PNGの条件:

  • 20MiB以下
  • 正方形
  • 16~4096px
  • PNGシグネチャ、IHDR、デコード結果の寸法が一致する
  • 透過情報を保持する
  • 256px未満は拒否せず、品質警告を表示する

PNGからICOへ変換する場合は、System.Drawingで次の6サイズを生成します。

16 / 24 / 32 / 48 / 64 / 256 px

各フレームは32bit ARGBのPNGとして格納し、小さい表示から高DPI表示まで1つのICOで対応します。

ICO入力では、ヘッダー、エントリ数、各フレームの範囲、重複、寸法、デコード可否を検証します。不正なICOは保存しません。

取込後は、同一ディレクトリ内に一時ファイルを作り、検証完了後にFile.ReplaceまたはFile.Moveでcustom.icoを原子的に置き換えます。途中で失敗した場合は既存Customを維持し、一時ファイルを削除します。

5.4 選択アイコンの適用

適用処理は次の順で実行します。

  1. 選択IDが候補一覧に存在するか確認する。
  2. ICOファイルが存在し、デコードできるか確認する。
  3. UIアダプターを通じて、起動中のWindow/Formへ反映する。
  4. 自アプリを指す既存ショートカットのIconLocationを更新する。
  5. AppIconIdをsettings.xmlへ保存する。
  6. 成功、部分成功、失敗を集約して設定画面へ返す。

Custom取込が成功した場合は、候補一覧を再読込し、customをこの適用処理へ渡します。

5.5 起動時の復元

起動時は保存済みAppIconIdを読み、利用可能なら開いている画面へ適用します。

  • 保存IDが不明ならdefault
  • Custom ICOが欠落または破損していればdefault
  • 既定ICOも利用できない場合は、アプリ起動を失敗させずアイコン復元だけをスキップ

起動後に新しく開くWindow/Formには、各UIアダプターのApplyCurrentIconを呼び出して現在のアイコンを適用します。

6. WPFアダプター

6.1 公開入口

WPFホストには、初期化と画面呼出しに必要な最小APIだけを公開します。

Global.Appearance.WPF.AppearanceFeature.Initialize(options)
Global.Appearance.WPF.AppearanceFeature.RestoreWindowIcons()
Global.Appearance.WPF.AppearanceFeature.ApplyCurrentIcon(window)
Global.Appearance.WPF.AppearanceFeature.ShowIconSettings(owner)

6.2 アイコン読込

WPFでは、ICOをBitmapImageへ読み込みます。

  • BitmapCacheOption.OnLoadでストリームを開いている間にデコードする。
  • 読込後にストリームを閉じ、アイコンファイルをロックしない。
  • CanFreezeならFreeze()し、複数Windowで安全に利用する。
  • UIスレッド外から呼ばれた場合はDispatcherへマーシャリングする。
  • Application.Current.Windowsの全Windowへ適用する。

後から開くWindowにも適用するため、ホストのWindow用StyleまたはLoadedイベントからApplyCurrentIconを呼びます。

6.3 設定画面

XAMLの設定画面には次を配置します。

  • アイコン候補一覧
  • 選択アイコンのプレビュー
  • PNG/ICO取込ボタン
  • 適用ボタン
  • 処理中表示
  • 成功、部分成功、失敗を表示するステータス領域

適用中はボタンを無効化し、二重実行と画面終了を防止します。

7. Windows Formsアダプター

7.1 公開入口

Windows Formsホストにも、WPF版と対応するAPIを公開します。

Global.Appearance.WinForms.AppearanceFeature.Initialize(options)
Global.Appearance.WinForms.AppearanceFeature.RestoreFormIcons()
Global.Appearance.WinForms.AppearanceFeature.ApplyCurrentIcon(form)
Global.Appearance.WinForms.AppearanceFeature.ShowIconSettings(owner)

7.2 アイコン読込と解放

System.Drawing.Iconは元ストリームの寿命や破棄漏れに注意が必要です。

  • ICOをバイト配列へ読み込む。
  • Iconを生成後にClone()し、ストリームから独立させる。
  • Formへ設定したIconをアダプター側で所有する。
  • 再適用時は古い所有Iconを破棄する。
  • FormClosed時に所有Iconを破棄する。
  • InvokeRequiredを確認し、必要ならUIスレッドへマーシャリングする。
  • Application.OpenFormsの全Formへ適用する。

ホストまたはDesignerが所有する既存Iconを誤って破棄しないよう、アダプター自身が生成したIconだけを管理します。

7.3 設定画面

WPF版と同じ候補、プレビュー、取込、適用、結果表示をWindows Formsのコントロールで実装します。

  • Designerで開ける構成を維持する。
  • 長時間処理中は取込/適用ボタンを無効化する。
  • Async Subイベント内で例外を捕捉し、利用者向けメッセージと診断ログを分離する。
  • プレビュー用ImageとIconを適切に破棄する。

8. 既存ショートカットの安全な更新

8.1 対象

現在のユーザーに属する次の場所を検索します。

  • Desktop
  • Start Menu
  • Taskbarのピン留めショートカット保存場所

Start MenuとTaskbarはサブフォルダーを再帰検索しますが、reparse pointは辿りません。

8.2 更新条件

.lnkを見つけても無条件には変更しません。

  1. IShellLinkWとIPersistFileでリンクを読み込む。
  2. TargetPathを絶対パスへ正規化する。
  3. 現在の自アプリEXEとOrdinalIgnoreCaseで一致することを確認する。
  4. 一致したリンクだけSetIconLocation(iconPath, 0)を呼ぶ。
  5. 起動先、引数、作業フォルダー、説明などは変更しない。
  6. 保存成功後にSHCNE_UPDATEITEMを通知する。

Shell COM処理は専用STAスレッドで実行し、COMオブジェクトを確実に解放します。壊れたリンクやアクセス拒否が1件あっても、ほかの候補の処理を続けます。

8.3 表示上の制約

.lnkの保存とShell通知が成功しても、ExplorerやTaskbarのアイコンキャッシュにより表示反映が遅れる場合があります。

本機能ではExplorer再起動やキャッシュ削除を行わず、「ショートカットファイルを更新したこと」と「画面上で即時に変化したこと」を別の結果として扱います。

9. エラー処理とログ

  • 利用者には、操作を続けるために必要な短いメッセージを表示する。
  • ファイルパス、例外、スタックトレース、HRESULTは診断ログへ出す。
  • 例外を空のCatchで握り潰さない。
  • Window/Form適用、ショートカット更新、設定保存を個別に評価する。
  • Custom取込失敗時は、現在のアイコンと既存custom.icoを変更しない。
  • 設定保存失敗時も、反映済み画面や更新済みショートカットを成功として結果へ残す。
  • ログへ画像内容、個人情報、秘密情報を出力しない。

10. 実装手順

Step 1: 共通Coreを作る

  1. Appearance.Coreクラスライブラリを追加する。
  2. アイコン定義、設定DTO、適用結果、取込結果を追加する。
  3. LocalAppDataの保存先とsettings.xmlの読込・原子的保存を実装する。
  4. 4つの組み込みICOをEmbedded Resourceとして追加し、展開・修復処理を実装する。
  5. PNG/ICOの検証、6サイズICO生成、Customの原子的置換を実装する。
  6. Shell Linkの探索、対象EXE照合、アイコン更新、Shell通知を実装する。
  7. 起動時復元と明示適用をまとめるAppIconServiceを実装する。

Step 2: WPF版を接続する

  1. Appearance.WPFクラスライブラリを追加し、Coreを参照する。
  2. BitmapImageを使うWPFアイコンアダプターを実装する。
  3. XAMLでアイコン設定画面を作成する。
  4. ホストの起動処理で初期化と復元を呼ぶ。
  5. 後から開くWindowのLoadedで現在アイコンを適用する。

Step 3: Windows Forms版を接続する

  1. Appearance.WinFormsクラスライブラリを追加し、Coreを参照する。
  2. System.Drawing.Iconを使うFormsアイコンアダプターを実装する。
  3. Designer対応のアイコン設定Formを作成する。
  4. ホストの起動後に初期化と復元を呼ぶ。
  5. 後から開くFormへ現在アイコンを適用する。

Step 4: 両UI版を同じ条件で検証する

  1. 同じ組み込みICOとCustom ICOを両アダプターから利用する。
  2. WPFとFormsで選択、取込、適用、復元の結果が一致することを確認する。
  3. UI固有のファイルロック、スレッド、リソース解放を確認する。
  4. Debug/Releaseのビルドと自動テストを実行する。

11. テスト計画

11.1 Core単体テスト

  • 既定アイコン4件の列挙とID検索
  • 未知ID、空ID、Custom欠落時のdefaultフォールバック
  • 設定XMLの保存、再読込、破損、不正バージョン、Unicode
  • 組み込みICOの初回展開、欠落修復、改変修復
  • PNGの正常系、非正方形、最小/最大寸法、20MiB境界、拡張子偽装
  • ICOの正常系、不正ヘッダー、範囲外オフセット、重複フレーム、デコード不能
  • 16/24/32/48/64/256pxを含むICO生成
  • Customの原子的置換、同時取込、一時ファイル後始末
  • 入力ファイル、custom.ico、組み込みICOが処理後にロックされていないこと
  • 設定保存、UI適用、Shell Link更新の部分成功判定

11.2 Shell Link統合テスト

  • 自アプリを指すリンクだけを更新する。
  • 別EXEを指すリンクは変更しない。
  • TargetPathの大文字小文字差を同一として扱う。
  • 相対パス、空TargetPath、壊れたリンクを安全にスキップする。
  • 更新後もTargetPath、Arguments、WorkingDirectory、Descriptionが維持される。
  • 1件の失敗後も残りのリンクを処理する。
  • COM処理がSTAで実行され、終了後にリンクファイルをロックしない。

11.3 WPFテスト

  • 開いている全Windowへ適用できる。
  • 後から開くWindowへ適用できる。
  • ICO読込後にファイルを置換できる。
  • UIスレッド外からの呼出しをDispatcherへ渡せる。
  • 設定画面で候補選択、Custom取込、適用結果を表示できる。
  • XAMLとコードビハインドが一致し、Visual Studio Designerで開ける。

11.4 Windows Formsテスト

  • 開いている全Formへ適用できる。
  • 後から開くFormへ適用できる。
  • ICO読込後にファイルを置換できる。
  • 再適用時とForm終了時に、アダプター所有Iconを解放できる。
  • UIスレッド外からの呼出しをFormへマーシャリングできる。
  • 設定画面をVisual Studio Designerで開ける。

11.5 手動確認

  • Windows 10/11でWPF版とForms版を起動する。
  • 組み込み4アイコンを順番に適用する。
  • 透過PNGと既存ICOをCustomとして取り込む。
  • タイトルバー、タスクバー、Desktop、Start Menuの表示を確認する。
  • アプリ再起動後に選択アイコンが復元される。
  • Explorer表示が遅れた場合も、アプリが強制的なキャッシュ削除を行わない。
  • 適用前後でEXEのSHA-256が一致する。
  • 署名済み成果物がある場合は、Authenticode署名が有効なままである。

12. 完了条件

  • WPF版とWindows Forms版の両方に、同等のアイコン設定画面がある。
  • 組み込みアイコンを選択し、起動中の全Window/Formへ反映できる。
  • PNGまたはICOを固定Custom枠へ取り込み、直ちに適用できる。
  • 自アプリを指すユーザー領域の既存ショートカットだけを更新できる。
  • 選択IDを保存し、再起動後と後から開く画面へ復元できる。
  • WPF/Forms固有処理がアダプターへ分離され、Coreが両UIへ依存していない。
  • 入力ファイルとICOをロックせず、Stream、Image、Icon、COMオブジェクトを解放できる。
  • 部分成功を全面成功または全面失敗として隠さない。
  • EXE、署名、レジストリ、Explorerプロセス、アイコンキャッシュを変更しない。
  • Debug/Releaseビルド、自動テスト、Visual Studio Designer確認、Windows実機確認の結果を記録する。

13. 想定リスクと対策

リスク 対策
ExplorerやTaskbarの表示がすぐ変わらない .lnk更新とShell通知までを保証範囲とし、キャッシュ削除は行わない
誤って別アプリのショートカットを変更する 正規化したTargetPathが自アプリEXEと一致するリンクだけを更新する
Custom取込失敗で既存アイコンを失う 検証済み一時ファイルを原子的に置き換える
WPFがICOをロックする BitmapCacheOption.OnLoadで読込後にStreamを閉じる
FormsでGDIリソースがリークする Cloneした所有Iconを追跡し、再適用時とFormClosed時にDisposeする
Shell COMを不適切なApartmentで実行する 専用STAスレッドで処理し、RCWを確実に解放する
保存済みCustomが欠落して起動時に失敗する defaultへフォールバックし、アイコン復元失敗だけでアプリを停止しない
UI方式ごとに仕様が分岐する Coreに仕様と結果判定を集約し、UIアダプターは表示とUIスレッド制御に限定する

今回の流れは次のようなものです。

アイデア
  ↓
ChatGPTとの壁打ち
  ↓
実装計画.md
  ↓
Codexによるコードベースとの照合・レビュー
  ↓
計画の修正
  ↓
Codexによる実装・テスト
  ↓
人間による差分・実画面確認

ポイントは、ChatGPTからCodexへ会話を直接引き継ぐのではなく、人間も確認できる成果物を間に置いたことです。

Markdownにしておくと、次の利点があります。

  • コード作成前に自分で内容を確認できる
  • 実装前の仕様と制約が残る
  • Codex以外のAIにも渡せる
  • Gitで変更履歴を管理できる
  • 実装と計画を比較できる
  • 後から設計意図を確認できる

AI同士を自動的につなぐというより、

人間とAIの間に、確認可能なインターフェースを置く

という感覚に近いです。


「レビューして」ではなく、問題を探す立場を渡す

実装計画を作成した後、すぐに実装へは進みませんでした。

Codexには、次のような位置づけでレビューを依頼しました。

この計画を成立させる方向だけで考えず、前提の誤り、見落とし、不要な複雑化、既存コードへの悪影響、より単純な代替案を優先して探してください。

自分の中では「敵対的レビュー」と呼んでいます。

目的は成果物を否定することではなく、あえて疑う側から確認することです。

Codexには公式ドキュメントでもコードレビューのワークフローがありますが、ここでいう「敵対的レビュー」は製品の固有機能名ではなく、今回自分で与えたレビュー方針です。

主に確認した観点は次のとおりです。

  • 現在のコード構成と矛盾していないか
  • 要件に対して実装が大げさになっていないか
  • WPFとWindows Formsを無理に共通化していないか
  • 既存の設定と利用者データを壊さないか
  • 別アプリのショートカットを誤って変更しないか
  • ICO欠落や設定破損でアプリが起動不能にならないか
  • ファイルロックやGDIリソースの解放漏れがないか
  • Shell COMを正しいスレッドで扱えるか
  • 自動テストと手動確認が不足していないか

AIへは「何をするか」だけでなく、「どの立場で考えるか」を渡した方が結果を調整しやすいと感じました。

ChatGPT
    実現方法と選択肢を広げる

Codex(レビュー時)
    計画の穴、危険、過剰設計を探す

Codex(実装時)
    確定した計画と現在のコードを照合して変更する

人間
    方針、差分、テスト結果、実画面を判断する

これは独立した第三者レビューを保証するものではありません。同じAIでも役割とコンテキストを切り替え、見落としを減らすための使い方です。


レビューで、最初の案より実装が具体化した

最初の案では、各アプリがResourceからアイコンを読み、設定へ選択IDを保存する比較的単純な構成も考えていました。

しかし、実際のコードベースと照合すると、次の要件が見えてきました。

  • WPF版とWindows Forms版で同じ仕様を持ちたい
  • PNG/ICOをCustomアイコンとして取り込みたい
  • Shell Linkから参照できる物理ICOが必要
  • WPFとFormsでファイルロックとリソース所有が異なる
  • 一部のショートカット更新だけ失敗する可能性がある
  • 設定とCustom ICOを安全に置き換える必要がある

最終的には、共通CoreとUI別アダプターへ分離しました。

WPFアプリ
  └─ Appearance.WPF.dll
       └─ Appearance.Core.dll

Windows Formsアプリ
  └─ Appearance.WinForms.dll
       └─ Appearance.Core.dll
アセンブリ 主な責務
Appearance.Core.dll 設定保存、組み込みICO、Custom取込、ICO生成、Shell Link更新、処理結果
Appearance.WPF.dll WPF設定画面、Window.Icon、Dispatcher
Appearance.WinForms.dll Forms設定画面、Form.Icon、UIスレッド、Icon解放

CoreはWPFのWindowやWindows FormsのFormに依存しません。

ホストアプリは、自身のUI方式に対応するアダプターだけを参照します。


実装の要点

この記事の主題はAIを既存VB.NET開発へどう組み込んだかですが、実装内容も簡単に紹介します。

アイコンIDと保存先

設定には絶対パスではなく、安定したIDを保存します。

default / blue / red / classic / custom

設定とICOはLocalAppDataへ保存します。

%LOCALAPPDATA%\<ProductId>\Appearance
├─ settings.xml
└─ Icons
   ├─ custom.ico
   └─ BuiltIn
      ├─ default.ico
      ├─ blue.ico
      ├─ red.ico
      └─ classic.ico

未知のID、壊れた設定XML、欠落したCustom ICOがあっても、defaultへフォールバックします。

アイコン設定程度の問題で業務アプリ自体を起動不能にはしません。

Custom PNG/ICO

正方形PNGは、System.Drawingを使って次の6サイズを持つICOへ変換します。

16 / 24 / 32 / 48 / 64 / 256 px

入力は拡張子だけでなく内容も検証します。保存時は一時ファイルを検証した後、File.ReplaceまたはFile.Moveでcustom.icoを原子的に置き換えます。

WPF

WPFではBitmapImageをBitmapCacheOption.OnLoadで読み、Streamを閉じても利用できる状態にします。必要ならFreeze()し、Dispatcher経由で開いているWindowへ適用します。

Windows Forms

Windows Formsでは、元Streamから独立したIconをClone()します。アダプターが作成したIconだけを所有し、再適用時とFormClosed時にDisposeします。

既存ショートカット

Desktop、Start Menu、Taskbarの.lnkを探索しますが、無条件には変更しません。

正規化したTargetPathが現在の自アプリEXEと一致するリンクだけ、IconLocationを変更します。TargetPath、Arguments、WorkingDirectory、Descriptionは維持します。

Shell COM処理は専用STAスレッドで実行します。

部分成功

Windowへの適用、ショートカット更新、設定保存を個別に評価します。

例えばWindowと設定保存には成功し、一部のショートカットだけ更新できなかった場合は、全面失敗ではなく部分成功として画面へ返します。


実装とテスト

実装と自動確認は次の順番で進めました。

  1. 共通Coreへ設定、組み込みICO、Custom取込、Shell Link更新を実装
  2. WPFアダプターとXAML設定画面を実装
  3. Windows FormsアダプターとDesigner対応設定画面を実装
  4. Debug/Releaseで両UI版を自動テスト
  5. 出力DLLと依存関係を確認

既存分を含む64件の自動テストをDebug/Releaseで実行し、成功を確認しています。

テスト対象には次を含みます。

  • 設定XMLの保存、破損、不正バージョン
  • 組み込みICOの展開と修復
  • PNG/ICOの正常系、異常系、境界値
  • 6サイズICO生成
  • Customの原子的置換とファイルロック
  • 自アプリのShell Linkだけを更新すること
  • WPFのOnLoad読込
  • Windows FormsのIcon所有と解放
  • 個別失敗後の継続と部分成功

実画面、Visual Studio Designer、DPI、Desktop/Start Menu/Taskbar、Explorer上の反映は、自動テストだけでは判断できません。

Adobe Express - 画面録画 2026-08-31 104856 (1).gif


実際にかかった時間

この機能だけへ集中して開発していたわけではありません。

ほかの仕事の片手間で、

  • ChatGPTとの壁打ち
  • 実装計画作成
  • Codexによるレビュー
  • 実装と自動テスト
  • 動作確認
  • 導入文書の作成
  • 別プロジェクトへの展開

まで含め、延べで1日半ほどでした。

AIを使ったから数分で完成した、という話ではありません。また、同じ時間でほかの案件も完了できることを保証するものでもありません。

それでも、普段の仕事を続けながら、調査、設計、実装、確認、横展開まで一通り進められたため、自分にとっては現実的な使い方でした。

分からないことが出るたびに検索と試行を繰り返すより、最初に論点と制約を整理したことで、大きな手戻りを減らせた印象があります。


VB.NETでも生成AIは思っていた以上に使えた

以前は、生成AIによるコーディング支援は、Python、JavaScript、TypeScript、最近のC#など、より利用者の多い技術ほど強いのではないかと思っていました。

今回の対象はVB.NETだけでなく、.NET Framework 4.8、WPF、Windows Forms、旧形式プロジェクトです。

それでも、次の作業はかなり普通に進められました。

  • 既存コードとプロジェクト設定を読む
  • 実装方針を整理する
  • .NET Framework 4.8で利用可能なAPIへ限定する
  • WPFとWindows Formsの違いを踏まえる
  • 既存コードへの変更範囲を抑える
  • Designer生成コードを不用意に壊さない
  • 実装後の差分とテスト結果を確認する

重要だったのは、AIへ対象の制約を明示することでした。

例えば、

- VB.NETを維持する
- .NET Framework 4.8を維持する
- WPFとWindows Formsの既存構成を尊重する
- フレームワーク移行や全面リファクタリングは行わない
- 新しいNuGet依存を追加しない
- Debug/Releaseを確認する

といった条件です。

AIに最新の書き方を無条件に選ばせるのではなく、今ある制約の中で最小の変更を考えてもらいました。


最新技術へ移行するためだけのAIではない

生成AIの話題では、新しい言語、フレームワーク、新規開発、AIネイティブな開発環境が目立ちます。

一方、実際の業務には、今動いているシステムを安全に維持する仕事も多くあります。

VB.NETで長年動いている業務アプリを、AIを使いたいという理由だけで別言語へ書き直す必要はありません。

既存のVB.NETアプリ
        ↓
ChatGPTと実装方法を整理
        ↓
Markdownで計画を残す
        ↓
Codexに現在のコードを読ませる
        ↓
問題点をレビュー
        ↓
必要な部分だけ変更する

AIを使うために開発環境を新しくするのではなく、今ある開発環境へAIを連れてくる。

そのくらいの使い方でもよいのだと思いました。


分かっている言語でAIを使う意味

AIがコードを書けるからといって、自分がよく分からない言語へ無理に移る必要はありません。

最終的に、次を判断するのは人間です。

  • 生成されたコードが何をしているか
  • 既存コードと整合しているか
  • どのデータやファイルを変更するか
  • 障害時にどこを確認するか
  • AIが不要な変更を混ぜていないか
  • テスト結果をどこまで信用できるか

自分が読めるVB.NETでAIを使うことは、既存業務アプリの保守では合理的です。

十分理解できない新しい言語で大量のコードを書かせるより、生成された差分を自分でも確認できる方が安心できます。

生成AIがあるから分からない技術へ移るのではなく、分かっている技術をAIで補強する、という使い方も十分ありだと思います。


完成後に「次のプロジェクトへ導入するための文書」を残す

今回、特にやっておいてよかったのが、機能の完成後に導入手順をMarkdownとして整理したことです。

現在は、共通Core、WPF版、Windows Forms版それぞれのREADMEに、次の情報を残しています。

  • 必要なプロジェクト参照とDLL
  • 公開API
  • 起動時の初期化
  • 後から開くWindow/Formへの適用
  • 設定画面の呼出し
  • ProductIdと保存先
  • WPFとWindows Forms固有の注意点
  • ファイルロックとIconの所有
  • Shell LinkとExplorer表示の制約
  • ビルド、テスト、手動確認項目
appearance-core-app-icon-readme.md

Appearance.Core:アプリアイコン変更機能

Appearance.Core.dllは、VB.NET/.NET Framework 4.8のWPFアプリとWindows Formsアプリで共有する、アプリアイコン変更機能の共通ライブラリです。

この文書では、次の機能だけを説明します。

  • 組み込みアイコンの管理
  • Custom PNG/ICOの取込
  • 選択アイコンIDの保存と復元
  • 既存ショートカットのアイコン更新
  • WPF/Windows Formsアダプターへ共通処理を提供する境界

構成

WPFアプリ
  └─ Appearance.WPF.dll
       └─ Appearance.Core.dll

Windows Formsアプリ
  └─ Appearance.WinForms.dll
       └─ Appearance.Core.dll

ホストアプリはUI方式に対応するアダプターを参照します。Coreの内部サービスをホストから直接生成しません。

CoreはWPFのWindow、Windows FormsのFormやControlへ依存しません。画面へのアイコン適用とUIスレッド制御は各アダプターが担当します。

Coreの責務

初期化

  • アプリを識別するProductIdと対象EXEの検証
  • LocalAppData配下の保存先解決
  • 組み込みICOの展開と修復
  • 設定XMLの読込
  • Custom ICOの検証
  • 起動時に復元するアイコンIDの解決

ProductIdは、公開後も変更しない安定した識別子を使用します。空の場合はEntry Assemblyから補完できますが、保存先を安定させるため明示を推奨します。

設定保存

設定の正本はLocalAppDataのsettings.xmlです。

%LOCALAPPDATA%\<ProductId>\Appearance
├─ settings.xml
└─ Icons
   ├─ custom.ico
   └─ BuiltIn
      ├─ default.ico
      ├─ blue.ico
      ├─ red.ico
      └─ classic.ico

アイコンファイルの絶対パスではなく、次のようなアイコンIDを保存します。

default / blue / red / classic / custom
  • 設定ファイルがない初回起動ではdefaultを使用します。
  • XMLが破損している、不正なバージョンである、または未知のIDを含む場合はdefaultへフォールバックします。
  • 設定は同一ディレクトリの一時ファイルへ書き、File.ReplaceまたはFile.Moveで原子的に保存します。
  • 設定異常だけを理由に、ホストアプリの起動を失敗させません。

組み込みアイコン

default.ico、blue.ico、red.ico、classic.icoをCoreのEmbedded Resourceとして保持します。

  • 初期化時にIcons\BuiltInへ物理ICOとして展開します。
  • 展開先が欠落または改変されている場合は、組み込み資産から修復します。
  • インストールフォルダーへは書き込みません。

Shell LinkのIconLocationは埋め込みリソースを直接参照できないため、ショートカット更新には展開後の物理ICOを使用します。

Custom PNG/ICOの取込

Customアイコンは固定1枠です。新しい取込が成功すると、既存のcustom.icoを置き換えます。

PNG

  • 20MiB以下
  • 正方形
  • 16~4096px
  • PNGシグネチャ、IHDR、デコード結果の寸法が一致する
  • 透過情報を保持する
  • 256px未満は取り込みを許可し、品質警告を返す

PNGはSystem.Drawingを使用し、次の6サイズを含むICOへ変換します。

16 / 24 / 32 / 48 / 64 / 256 px

ICO

拡張子だけではなく、次の内容を検証します。

  • ICOヘッダーとエントリ数
  • 各フレームのオフセットと長さ
  • フレーム範囲の重複
  • 宣言寸法とデコード後寸法
  • 全フレームのデコード可否

保存

  1. 入力を検証する。
  2. 同じ保存ディレクトリへ一時ファイルを作る。
  3. 一時ファイルを再検証する。
  4. File.ReplaceまたはFile.Moveでcustom.icoを置き換える。
  5. 失敗時は一時ファイルを削除し、既存Customを維持する。

入力ファイル、一時ファイル、保存済みICOを処理後にロックしません。

アイコンの適用

適用処理は次の順番です。

選択IDを検証
    ↓
ICOをデコードできることを確認
    ↓
UIアダプターで開いているWindow/Formへ適用
    ↓
既存ショートカットを更新
    ↓
選択IDをsettings.xmlへ保存
    ↓
成功/部分成功/失敗を返す

Custom取込が成功した場合は候補一覧を再読込し、customを同じ適用処理へ渡します。

起動時の復元

  • 保存済みIDが利用可能なら、そのアイコンを復元します。
  • 保存済みIDが不明ならdefaultを使用します。
  • customが選択されていても、custom.icoが欠落または破損していればdefaultへフォールバックします。
  • default.icoも利用できない場合は、ホストアプリを停止せずアイコン復元だけをスキップします。

起動後に新しく開くWindow/Formには、UIアダプターのApplyCurrentIconを呼び出します。

既存ショートカットの更新

現在のユーザーに属する次の場所を検索します。

  • Desktop
  • Start Menu
  • Taskbarのピン留めショートカット保存場所

見つけた.lnkは無条件に変更しません。

  1. IShellLinkWとIPersistFileで読み込む。
  2. TargetPathを絶対パスへ正規化する。
  3. 対象EXEと大文字小文字を無視して比較する。
  4. 一致したリンクだけIconLocationを変更する。
  5. TargetPath、Arguments、WorkingDirectory、Descriptionは変更しない。
  6. 保存成功後にWindows ShellへSHCNE_UPDATEITEMを通知する。

Shell COM処理は専用STAスレッドで実行し、COMオブジェクトを確実に解放します。壊れたリンクやアクセス拒否があっても、残りの候補を可能な範囲で処理します。

部分成功とログ

次の結果を個別に保持します。

  • Window/Formへ適用できたか
  • 設定を保存できたか
  • ショートカットが対象EXEと一致したか
  • ショートカットファイルを更新できたか
  • Shellへ変更通知を送れたか
  • 利用者向けWarning/Error
  • 診断情報とHRESULT

一部のショートカットだけ更新できなかった場合も、成功した画面反映や設定保存を失敗として隠しません。

利用者向けメッセージと診断ログを分離し、例外を空のCatchで握り潰しません。

スレッドとリソース

  • Shell COM処理はSTAスレッドで実行します。
  • UIスレッドへのマーシャリングは各UIアダプターが担当します。
  • Stream、Image、Bitmap、Graphics、COMオブジェクトを確実に解放します。
  • CoreはWindow、Form、Control、Dispatcherを保持しません。

制約

  • Windows専用です。
  • .NET Framework 4.8を対象とします。
  • EXE埋め込みアイコン、署名、レジストリは変更しません。
  • Explorerを再起動せず、アイコンキャッシュも削除しません。
  • .lnkとShell通知に成功しても、ExplorerやTaskbarの表示反映が遅れる場合があります。
  • 全ユーザー領域は変更しません。

確認項目

  • 組み込み4 ICOの展開、欠落修復、改変修復
  • 設定XMLの保存、再読込、破損、不正バージョン、Unicode
  • PNG/ICOの正常系、異常系、境界値
  • 6サイズICOと透過情報
  • Customの原子的置換とファイルロック
  • 自アプリを指すShell Linkだけが更新されること
  • Shell Linkの起動情報が維持されること
  • 1件の失敗後も残りの処理を継続できること
  • WPF/Windows Formsの両アダプターから同じCoreを利用できること

UI別ガイド

appearance-wpf-app-icon-readme.md

Appearance.WPF:WPFアプリアイコン変更機能

Appearance.WPF.dllは、VB.NET/.NET Framework 4.8のWPFアプリへアプリアイコン変更機能を提供するUIアダプターです。

設定、組み込みICO、Custom取込、ICO生成、既存ショートカット更新はAppearance.Core.dllが担当します。

参照構成

WPFアプリ
  └─ Appearance.WPF.dll
       └─ Appearance.Core.dll

ホストからAppearance.WPFを参照します。Coreの内部サービスをホストから直接生成しません。

ホストが使用する主な公開入口は次のとおりです。

  • Global.Appearance.WPF.AppearanceOptions
  • Global.Appearance.WPF.AppearanceFeature

初期化と起動時復元

アプリ起動時に一度初期化し、開いているWindowへ保存済みアイコンを復元します。

Class Application

    Private Shared _appearanceInitialized As Boolean

    Protected Overrides Sub OnStartup(e As StartupEventArgs)
        MyBase.OnStartup(e)
        InitializeAppearanceFeature()
    End Sub

    Public Shared Sub InitializeAppearanceFeature()
        If Not _appearanceInitialized Then
            Dim options = New Global.Appearance.WPF.AppearanceOptions With {
                .ProductId = "Example.SampleApp",
                .Logger = Sub(message) Diagnostics.Debug.WriteLine(message)
            }

            Global.Appearance.WPF.AppearanceFeature.Initialize(options)
            _appearanceInitialized = True
        End If

        Global.Appearance.WPF.AppearanceFeature.RestoreWindowIcons()
    End Sub

End Class

ProductIdはアプリごとに一意で、公開後も変更しない値を使用します。

同じ初期化条件での再呼出しは許容します。異なるProductIdや対象EXEで、実行中に再初期化しません。

後から開くWindow

起動時点で存在しないWindowには、Loadedイベントで現在のアイコンを適用します。

Private Sub AppWindow_Loaded(sender As Object, e As RoutedEventArgs)
    Dim window = TryCast(sender, Window)
    If window IsNot Nothing Then
        Global.Appearance.WPF.AppearanceFeature.ApplyCurrentIcon(window)
    End If
End Sub

既存のWindow用暗黙Styleがある場合は、そのStyleへEventSetterを追加できます。

<EventSetter Event="Loaded" Handler="AppWindow_Loaded" />

既存Styleを同じキーで上書きせず、現在のSetterやBasedOnを維持します。共通Window基底クラスや独自の生成処理がある場合は、既存方式へ統合します。

アイコン設定画面

WindowまたはCommandから、Owner付きの設定画面を開きます。

Global.Appearance.WPF.AppearanceFeature.ShowIconSettings(owner)

設定画面は次を提供します。

  • Default、Blue、Red、Classicの選択
  • 選択アイコンのプレビュー
  • 正方形PNG/ICOのCustom取込
  • 選択アイコンの適用
  • 処理中の二重実行防止
  • 成功、部分成功、失敗の表示

Custom取込が成功すると固定のCustom枠を置き換え、そのまま適用します。

WPFでのICO読込

WPFではBitmapImageをBitmapCacheOption.OnLoadで読み込みます。

Using stream = New FileStream(
        iconPath,
        FileMode.Open,
        FileAccess.Read,
        FileShare.ReadWrite Or FileShare.Delete)

    Dim bitmap = New BitmapImage()
    bitmap.BeginInit()
    bitmap.CacheOption = BitmapCacheOption.OnLoad
    bitmap.StreamSource = stream
    bitmap.EndInit()

    If bitmap.CanFreeze Then
        bitmap.Freeze()
    End If

    Return bitmap
End Using
  • 読込後にStreamを閉じ、ICOファイルをロックしません。
  • Freeze()可能な場合は凍結し、複数Windowで利用します。
  • UIスレッド外から呼ばれた場合はDispatcherへマーシャリングします。
  • Application.Current.Windowsに含まれるWindowへ適用します。

保存とショートカット

保存先と処理仕様はCoreと共通です。

%LOCALAPPDATA%\<ProductId>\Appearance
├─ settings.xml
└─ Icons
   ├─ custom.ico
   └─ BuiltIn\*.ico

選択時には次を行います。

  1. 開いているWPF Windowへアイコンを適用する。
  2. 自アプリを指す既存Desktop/Start Menu/TaskbarショートカットのIconLocationを更新する。
  3. 選択アイコンIDをsettings.xmlへ保存する。

個別失敗は集約し、部分成功として設定画面へ返します。

制約

  • Windows/.NET Framework 4.8専用です。
  • EXE埋め込みアイコン、署名、レジストリを変更しません。
  • Explorerを再起動せず、アイコンキャッシュを削除しません。
  • TaskbarやExplorerの表示反映はWindows Shellに依存し、遅れる場合があります。
  • WPF UI構造を変更するためだけのMVVM化や全面リファクタリングは不要です。

確認項目

  • 設定画面を開けること
  • 組み込み4アイコンを選択・適用できること
  • PNG/ICOをCustomとして取り込めること
  • 開いている全Windowへ適用できること
  • 後から開くWindowへ適用できること
  • 再起動後に選択を復元できること
  • ICO読込後にファイルを置き換えられること
  • UIスレッド外からの呼出しをDispatcherへ渡せること
  • 既存ショートカット更新の成功と部分失敗を表示できること
  • XAMLとコードビハインドが一致すること
  • Visual Studio Designerで設定画面を開けること
  • Debug/Releaseでビルドできること

共通仕様

appearance-winforms-app-icon-readme.md

Appearance.WinForms:Windows Formsアプリアイコン変更機能

Appearance.WinForms.dllは、VB.NET/.NET Framework 4.8のWindows Formsアプリへアプリアイコン変更機能を提供するUIアダプターです。

設定、組み込みICO、Custom取込、ICO生成、既存ショートカット更新はAppearance.Core.dllが担当します。

参照構成

Windows Formsアプリ
  └─ Appearance.WinForms.dll
       └─ Appearance.Core.dll

ホストからAppearance.WinFormsを参照します。Coreの内部サービスやDLL内Formをホストから直接生成しません。

ホストが使用する主な公開入口は次のとおりです。

  • Global.Appearance.WinForms.AppearanceOptions
  • Global.Appearance.WinForms.AppearanceFeature

初期化と起動時復元

メインFormのUIスレッドで一度初期化し、開いているFormへ保存済みアイコンを復元します。

Public Class MainForm

    Private Shared _appearanceInitialized As Boolean

    Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        InitializeAppearanceFeature()
        Global.Appearance.WinForms.AppearanceFeature.ApplyCurrentIcon(Me)
    End Sub

    Private Shared Sub InitializeAppearanceFeature()
        If Not _appearanceInitialized Then
            Dim options = New Global.Appearance.WinForms.AppearanceOptions With {
                .ProductId = "Example.SampleApp",
                .Logger = Sub(message) Diagnostics.Debug.WriteLine(message)
            }

            Global.Appearance.WinForms.AppearanceFeature.Initialize(options)
            _appearanceInitialized = True
        End If

        Global.Appearance.WinForms.AppearanceFeature.RestoreFormIcons()
    End Sub

End Class

ProductIdはアプリごとに一意で、公開後も変更しない値を使用します。

同じ初期化条件での再呼出しは許容します。異なるProductIdや対象EXEで、実行中に再初期化しません。

後から開くForm

RestoreFormIconsは、呼出し時点のApplication.OpenFormsへ適用します。後から開く各Formでは、Load後に現在のアイコンを適用します。

Private Sub DetailForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Global.Appearance.WinForms.AppearanceFeature.ApplyCurrentIcon(Me)
End Sub

Formのハンドル作成前にUIスレッド外から呼び出しません。

アイコン設定画面

MenuItemまたはButtonのClickイベントから、Owner付きの設定画面を開きます。

Private Sub IconSettingsMenuItem_Click(sender As Object, e As EventArgs) _
    Handles IconSettingsMenuItem.Click

    Global.Appearance.WinForms.AppearanceFeature.ShowIconSettings(Me)
End Sub

設定画面は次を提供します。

  • Default、Blue、Red、Classicの選択
  • 選択アイコンのプレビュー
  • 正方形PNG/ICOのCustom取込
  • 選択アイコンの適用
  • 処理中の二重実行防止
  • 成功、部分成功、失敗の表示

Custom取込が成功すると固定のCustom枠を置き換え、そのまま適用します。

Windows FormsでのICO読込

元Streamから独立したSystem.Drawing.Iconを作成します。

Dim bytes = File.ReadAllBytes(iconPath)

Using stream = New MemoryStream(bytes, False)
    Using loaded = New Icon(stream)
        Return DirectCast(loaded.Clone(), Icon)
    End Using
End Using
  • ICOを読み終えた後はファイルをロックしません。
  • Formへ設定したClone済みIconをアダプターが所有します。
  • 再適用時は新しいIconへ置換してから、以前の所有IconをDisposeします。
  • FormClosed時に、そのForm用の所有IconをDisposeします。
  • ホストまたはDesignerが元から所有していたIconは破棄しません。
  • InvokeRequiredを確認し、必要ならFormのUIスレッドへマーシャリングします。

Designerとの境界

  • Form本体と.Designer.vbの関係を維持します。
  • Designer生成コードを不用意に直接編集しません。
  • 既存のコントロール名、Handles、AddHandlerを確認します。
  • プレビュー用Imageと、アダプターが所有するIconを確実に解放します。
  • 本機能だけを理由に、既存FormをWPFへ置き換えません。

保存とショートカット

保存先と処理仕様はCoreと共通です。

%LOCALAPPDATA%\<ProductId>\Appearance
├─ settings.xml
└─ Icons
   ├─ custom.ico
   └─ BuiltIn\*.ico

選択時には次を行います。

  1. 開いているWindows Forms Formへアイコンを適用する。
  2. 自アプリを指す既存Desktop/Start Menu/TaskbarショートカットのIconLocationを更新する。
  3. 選択アイコンIDをsettings.xmlへ保存する。

個別失敗は集約し、部分成功として設定画面へ返します。

制約

  • Windows/.NET Framework 4.8専用です。
  • EXE埋め込みアイコン、署名、レジストリを変更しません。
  • Explorerを再起動せず、アイコンキャッシュを削除しません。
  • TaskbarやExplorerの表示反映はWindows Shellに依存し、遅れる場合があります。
  • UIスレッド外からFormを直接操作しません。

確認項目

  • 設定画面を開けること
  • 組み込み4アイコンを選択・適用できること
  • PNG/ICOをCustomとして取り込めること
  • 開いている全Formへ適用できること
  • 後から開くFormへ適用できること
  • 再起動後に選択を復元できること
  • ICO読込後にファイルを置き換えられること
  • 再適用時とForm終了時に所有Iconを解放できること
  • UIスレッド外からの呼出しをFormへマーシャリングできること
  • 既存ショートカット更新の成功と部分失敗を表示できること
  • Visual Studio Designerで設定画面を開けること
  • 100%/125%/150% DPIで表示を確認すること
  • Debug/Releaseでビルドできること

共通仕様

導入先では、コード一式を単純にコピーするのではなく、対象プロジェクトのFramework、Root Namespace、起動処理、Window/Form生成方法、既存設定を先に確認させます。

適切な接続点を一意に決められない場合は、自動編集を止めて人間へ確認することも導入手順の一部です。


別プロジェクトへの横展開

導入文書を作成した後、実際に別プロジェクトへ同じ機能を組み込みました。

Codexには、

この導入文書を参照し、対象プロジェクトの実際の構成を確認したうえで、同じ機能を組み込んでください。FrameworkやUI方式を変更せず、接続点を確定できない場合は実装を止めて確認してください。

と依頼しました。

今回のケースでは、人間による修正をほとんど挟まずに横展開できました。

ただし、これはどのプロジェクトでもノータッチで移植できるという意味ではありません。

  • Root Namespace
  • Applicationクラス
  • Window/Formの共通基底や生成方法
  • 既存の暗黙Style
  • 設定管理
  • Option StrictなどのVBコンパイル設定
  • PlatformTarget
  • 配布方法

が異なれば、導入方法も変わります。

AIへコードをコピーさせるのではなく、導入文書を判断基準として渡し、対象コードに合わせて接続させる方法が有効でした。


「コード」だけを成果物にしない

AIを使う場合、ドキュメントは人間の備忘録だけではありません。

次の作業をAIへ渡すための入力にもなります。

実装計画
    これから何を作るか

README/導入ガイド
    できた機能をどう利用・再利用するか

テストと確認結果
    どこまで確認済みで、何が未確認か

今回、Markdownを間に置いたことで、AIへ会話履歴をすべて説明し直さなくても、次の作業を始めやすくなりました。

また、人間側も、コードだけを見て設計意図を推測する必要が減ります。

ドキュメントは完成コードのおまけではなく、次の人間とAIが作業するためのインターフェースとして扱えます。


人間側に残したこと

AIへ多くの作業を依頼しても、次の判断は人間側に残しました。

  • そもそもこの機能を入れるか
  • EXEを書き換えない方針を採用するか
  • WPFとWindows Formsをどこまで共通化するか
  • 設定や既存利用者データとの互換性をどうするか
  • 自動テスト結果と差分を受け入れるか
  • 実際のWindows上の表示を許容できるか
  • 横展開先ごとの差を受け入れられるか
  • 公開するスクリーンショットやアイコンの権利に問題がないか

特にWindows ShellやTaskbarは、ファイル更新に成功しても表示反映が遅れる場合があります。AIがコードとテストを確認できても、利用者から見える最終結果は実機で確認する必要があります。


まとめ

Firefox 154のアイコン変更機能をきっかけに、VB.NET/.NET Framework 4.8のWPFアプリとWindows Formsアプリへ、アプリアイコン変更機能を実装しました。

技術的には、次の内容を含みます。

  • 組み込みアイコン選択
  • Custom PNG/ICO取込
  • LocalAppDataへの設定保存
  • 起動時と後から開く画面への復元
  • 既存ショートカットの安全な更新
  • WPF/Windows Formsの共通Core化
  • ファイルロック、GDIリソース、STA COMへの対応
  • 成功、部分成功、失敗の区別

一方、今回特に印象に残ったのは、次の開発フローでした。

ChatGPTと壁打ち
        ↓
実装計画をMarkdown化
        ↓
Codexへ問題を探す立場を渡す
        ↓
計画を修正
        ↓
実装とテスト
        ↓
導入ガイドをMarkdown化
        ↓
別プロジェクトへ横展開

生成AIは、最新技術へ移行するためだけの道具ではありません。

今あるVB.NETのコードを読み、既存制約の中で変更を計画し、レビューし、必要な箇所だけを修正する用途にも使えます。

AIを使うために技術を新しくするのではなく、今ある技術へAIを連れてくる。

そして、コードだけでなく、実装計画や導入文書も次の作業へ渡せる成果物として残す。

既存業務アプリを保守する中では、この使い方がかなり実務的だと感じました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?