VC.NET ~ フォームのアイコンをカスタマイズする

前置き

VC.NETでWindowsフォームアプリを作ると、デフォルトで、app.icoというアイコンが自動生成されます。このアイコンは、(同じく自動生成される)app.rc経由でアンマネージ・リソースとしてアセンブリに組み込まれ、exeファイルをエクスプローラで表示するときなどに使われます。

しかしapp.icoは、フォームのコントロールボックス(タイトルバーの左端)の小さなアイコンや、Windows7のタスクバーに表示される大きなアイコンとは連動してません。そこで、コントロールボックスやタスクバーにもapp.icoを表示する方法をまとめます。

Form::Iconプロパティ

コントロールボックスやタスクバーに表示されるアイコンは、Form::Iconプロパティで決まります。デフォルト値は、MSの(Windowsの?)イメージカラーをモチーフにしたアイコンです。

デフォルトアイコン

そこで、このIconプロパティ(型はSystem::Drawing::Icon^)にapp.icoをセットしたいのですが、app.icoはアンマネージ・リソースなので、そのままではセットできません。

アンマネージ・リソースからSystem::Drawing::Icon^の形でアイコンをロードすることは可能(別記事「アンマネージのリソースをマネージで使う」参照)ですが、私が知っている方法では、アイコンサイズが1種類に限定されてしまいます。これでは、マルチアイコン(1つのアイコンに複数種類のサイズのイメージが含まれている状態)のままでIconプロパティにセットすることができないので、コントロールボックス用とタスクバー用に別々のサイズのイメージを使い分けることができません。そこで、アンマネージ・リソースはあきらめて、マネージ・リソースを使います。

マネージ・リソース

まずは単純な方法から。上図に示した、フォームのプロパティウィンドウで、Icon欄の右にある参照(…)ボタンを押すとファイル選択画面が出るので、app.icoを選びます。これで終わり。この手順により、当該フォーム専用のマネージ・リソースとしてapp.icoがアセンブリに組み込まれます。今回の場合、MainForm.resXです。

フォームのリソース

この方法の欠点は、フォームの種類が増えた場合に、それぞれのフォームで同じ手順を繰り返す必要があるところです。また、同じアイコンイメージの複製が各フォームのresXに入ってしまうというのも芸がない気がします。

そこで、アプリ内で共有したいリソースを入れるためのresXを作りましょう。VB.NETやVC#.NETなら、この用途のためのresXが最初から用意されるようですが、VC.NETの場合はハンドで作成する必要があります。しかもVC.NETのExpress Editionでは、resXを新規作成するテンプレートが提供されてないので、既存のresXをコピペして作ります。

MainForm.resXをコピーしてapp.resXとし、app.icoを追加してみました。

アプリのリソース

あとは、このリソースファイルからアイコンをロードして、フォームのIconプロパティにセットすればOKです。

public ref class MainForm : public System::Windows::Forms::Form
{
public:
  MainForm(void)
  {
    InitializeComponent();
    Icon = loadAppIcon();
  }

private:
  System::Drawing::Icon^ loadAppIcon() {
    return dynamic_cast<System::Drawing::Icon^>(
      System::Resources::ResourceManager(
        GetType()->Namespace + ".app",
        System::Reflection::Assembly::GetExecutingAssembly()
      ).GetObject("app")
    );
  }

... 中略

  private: System::Void mListViewButton_Click(System::Object^  sender, System::EventArgs^  e) {
    ListViewForm^ f = gcnew ListViewForm();
    f->Icon = loadAppIcon();
    f->Show();
  }

ポイントは、MainForm::loadAppIcon()です。横着して1文で書いてますが、少し説明が必要でしょう。

VC.NETがresXファイルをビルドすると、<ルート名前空間> . <リソースファイル名> . "resources" という名前でリソースが出力されます(カルチャ別にリソースを分ける場合は更に複雑)。

リソースのコンパイル

ルート名前空間には、デフォルトではプロジェクト名が使われますが、プロジェクトのプロパティウィンドウから変更することも可能です。

ルート名前空間

コード上でルート名前空間を得るには、上記コードのようにGetType()->Namespaceを使います。

で、結局、app.resXの場合は、wiki.app.resourcesというリソース名になります(カルチャによっては、wiki.app.en-US.resourcesなど)。この前半部分"wiki.app"をリソースのルート名と呼びます。ResourceManagerのコンストラクタの第1引数には、このルート名を指定する必要があります。上記のコードでは、GetType()->Namespaceで得たルート名前空間に".app"を連結してリソースのルート名を組み立てています。

また、GetObject()の引数に渡しているのは、このリソースに含まれるアイコンの名前"app"です。

余談ですが、ルート名前空間を変更した場合、直後のビルド時にLNK1181エラーが発生するかもしれません(「xxx.yyy.resourcesを開けません」)。VC.NETのバグと思われますが、旧ルート名前空間でリソースを探してしまっているようです。この場合、VC.NETを再起動すれば回避できます。

もう一つ余談ですが、フォームを開いたときにMissingManifestResourceException例外が発生することがあります。

missingmanifestresource.png

発生箇所は、フォームがアイコンをロードする箇所です。

namespace wikiwiki {
  public ref class SubForm : public System::Windows::Forms::Form {

    private:
      void InitializeComponent(void) {
        System::ComponentModel::ComponentResourceManager^ resources =
          gcnew System::ComponentModel::ComponentResourceManager(SubForm::typeid);
        ...
        this->Icon = cli::safe_cast<System::Drawing::Icon^>(
          resources->GetObject(L"$this.Icon"));

これは、当該フォームクラスの名前空間(wikiwiki)と、プロジェクトのルート名前空間(wiki)が異なるケースです。ResourceManager::GetObject()が名前空間wikiwikiからリソースを取得しようとするのに対し、当該フォームのリソースはwiki.SubForm.resourcesとしてアセンブリに埋め込まれています。

回避するには、リソースのルート名がwikiwiki.SubForm.resourcesになるように、resXファイルのプロパティを変更する必要があります。

  • 構成プロパティ→マネージ リソース→全般→リソース ファイル名
$(IntDir)\wikiwiki.$(InputName).resources
Last modified:2012/05/08 17:11:01
Keyword(s):
References:[.NETアプリ開発]
This page is frozen.