ツールボタンの画像を動的に変更する

メニューバーの階層化されたコマンドをショートカット実行するのにツールボタンは便利な機能です。PCに詳しくないユーザーに「このボタンを押してね」というのにも使えます。

話はトビますが、工場にある生産装置には照光式ボタンというのものが使われている場合が多く、装置の準備ができたらボタンが光って、それを人間が押す、という作業が行われています。

まさにこれと同じことがしたい場合は、ツールボタンの画像を動的に変更する必要があります。ソフトウェア起動時には Fig. 1 の状態であり、なんらかの準備ができたら Fig. 2 のようになると、ユーザには親切だと思います。

Fig. 1
Fig. 2

これを実現するには、ひとつのボタンに対して2つのビットマップファイルを用意して、動的に入れ替えてやるようにします。3つの状態を示すのならば3つの画像を用意してください。本記事では2つのボタンに2つの状態をもたせるために 2×2 の4つのビットマップファイルを用意しました。

ビットマップファイルの作成時から注意しておくことは、透明色というものを意識することです。VisualStudio2019 のデフォルト設定ではマゼンタ色が透明色ですので、それを意識してビットマップファイルを作成します。

そもそもマゼンタ色をボタンの柄に使いたい場合は、透明色を別の色に設定する方法があるので、ご安心ください。後で解説します。

Fig. 3
Fig. 4
Fig. 5
Fig. 6

おおまかな動きとしては下記のような流れです。

(0) ビットマップファイルを画像リソースとしてプロジェクトに登録する.
(1) 画像リソースを起動時に Bitmap クラスに関連づける.
(2) ウインドウの描画イベントハンドラ ( Form1_Paint ) で、条件に応じてツールボタンにどの Bitmap を関連付ける決定する.

たったこれだけです。上記の作業(0)は最初に1回やるだけでかまいません。プログラムコード上で実施するのは (1)(2) です。

ビットマップファイルを画像リソースとしてプロジェクトに登録する

Fig. 7、まずは、[Ctrl] + [Alt] + [L] のショートカットキーでソリューションエクスプローラを表示させてください。そして aaa の Properties の Resources.resx をダブルクリックしてください。

デフォルトでは文字列リソースの編集画面が表示されます。登録したいのは文字列リソースではなくて、画像リソースですから、この画面を切り替えなくてはなりません。

Fig. 7

Fig. 8、ツールボタンに画像を割り当てるのはビットマップファイル画像ですので「イメージ」を選択して、画像リソース登録用の画面に切り替えます。

Fig. 8

Fig. 9、最初はまったく画像リソースを設定していないので、真っ白のペインしか表示されません。

Fig. 9

Fig. 10、あらかじめ作成しておいたビットマップ画像ファイルを、ペインにドラッグ&ドロップします。

Fig. 10

Fig. 11、この時点でリソース化されて、プロジェクト内で画像リソースとして使えるようになります。

Fig. 11

Fig. 12、ソリューションエクスプローラに注目してください。この例では4つの画像が登録されたことがわかります。

ここに登録された名称が、そのまま画像リソースの名称になります。
ビットマップ画像ファイルを作成して保存するときに、すでにプログラムの変数名を意識したものにしておいてください。

日本語のファイル名や、ハイフンをはじめ特殊な文字を使用したファイル名を使うことは良くないことは予想できると思います。

Fig. 12

いったん VisualStudio を最小化して、ソリューションのディレクトリを確認してみてください。/Resources というディレクトリが新しく作成されているはずです。その中に先ほどドラッグ&ドロップした画像ファイルと同じものがコピー配置されています。

ソースコード解説

ここからはソースコードに従って解説します。

16,17行目、ツールボタンAに使う Bitmap クラスを宣言します。
19,20行目、ツールボタンBに使う Bitmap クラスを宣言します。

29,30行目、画像リソースをツールボタンA用の Bitmap に流し込みます。
32,33行目、画像リソースをツールボタンB用の Bitmap に流し込みます。

36,37行目、画像リソースを構成する色のなかで透明にしたい色を指定します。仮にここで Color.Blue とすれば、'A' や 'B' の柄が透明になります。

39,40行目、ツールボタンに表示する画像を縮小表示(SizeToFit)するか、画像サイズの原寸表示(None)するかを設定します。大きなサイズのボタンを表示する場合は ToolStripItemImageScaling.None にしてください。

53~71行目、これは定石ですが、Bitmap を使わなくなったら Dispose() します。

79~88行目、グローバルフラグに応じてどちらの Bitmap をツールボタンの画像に関連付けるか決定します。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace aaa
{
	public partial class Form1 : Form
	{

		Bitmap BmpTbnA0;
		Bitmap BmpTbnA1;

		Bitmap BmpTbnB0;
		Bitmap BmpTbnB1;

		bool FlagForToolButton = false;

		public Form1()
		{
			InitializeComponent();

			// リソースのイメージをビットマップに割り当てる.
			BmpTbnA0 = Properties.Resources.btn_a_0;
			BmpTbnA1 = Properties.Resources.btn_a_1;

			BmpTbnB0 = Properties.Resources.btn_b_0;
			BmpTbnB1 = Properties.Resources.btn_b_1;

			// ツールボタン画像の透明キー色を指定する.
			tbnA.ImageTransparentColor = Color.Magenta;
			tbnB.ImageTransparentColor = Color.Magenta;

			// ツールボタンの画像表示は元画像サイズと同じにする.
			tbnA.ImageScaling = ToolStripItemImageScaling.None;
			tbnB.ImageScaling = ToolStripItemImageScaling.None;

			// ツールボタンのさかいめがわかりにくいのでウインドウを暗い色にする.
			this.BackColor = SystemColors.ControlDark;

			this.Text = "GazoYaro";

		}

		private void Form1_FormClosing( object sender, FormClosingEventArgs e )
		{

			if ( BmpTbnA0 != null )
			{
				BmpTbnA0.Dispose();
			}

			if ( BmpTbnB0 != null )
			{
				BmpTbnB0.Dispose();
			}

			if ( BmpTbnA1 != null )
			{
				BmpTbnA1.Dispose();
			}

			if ( BmpTbnB1 != null )
			{
				BmpTbnB1.Dispose();
			}

		}

		private void Form1_Paint( object sender, PaintEventArgs e )
		{

			// ツールボタンの画像にビットマップを割り当てる.
			if ( FlagForToolButton )
			{
				tbnA.Image = BmpTbnA1;
				tbnB.Image = BmpTbnB1;
			}
			else
			{
				tbnA.Image = BmpTbnA0;
				tbnB.Image = BmpTbnB0;
			}

			// ステータスバーに現在のフラグの状態を表示する.
			toolStripStatusLabel1.Text = FlagForToolButton.ToString();

		}

		private void menuSet1_Click( object sender, EventArgs e )
		{

			FlagForToolButton = true;

			// 再描画要求, Form1_Paint がコールされる.
			this.Invalidate( true );

		}

		private void menuSet0_Click( object sender, EventArgs e )
		{

			FlagForToolButton = false;

			// 再描画要求, Form1_Paint がコールされる.
			this.Invalidate( true );

		}

		private void tbnA_Click( object sender, EventArgs e )
		{
			String str = String.Format( "You pushed A, flag is {0}", FlagForToolButton );
			MessageBox.Show( str );
		}

		private void tbnB_Click( object sender, EventArgs e )
		{
			String str = String.Format( "You pushed B, flag is {0}", FlagForToolButton );
			MessageBox.Show( str );
		}

		private void menuApplicationQuit_Click( object sender, EventArgs e )
		{
			this.Close();
		}

	}

}

サンプルのプロジェクトを用意しました、下記からダウンロードできます。よろしければご活用ください。ツールボタン用のビットマップファイル4個もまとめてZIPアーカイブしてあります。

プロジェクトは画像リソース登録前の状態になっていますので、そのままではビルドできません。本記事の要領でビットマップファイルを登録してお試しください。

ビルド時に VisualStudio で下記のようなメッセージがでてビルドができない可能性があります。

Fig. 13

リビルドを開始しました…
------ すべてのリビルド開始: プロジェクト:aaa, 構成: Release Any CPU ------
C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\Microsoft.Common.CurrentVersion.targets(3162,5): error MSB3821: ファイル Form1.resx を処理できませんでした。インターネットまたは制限付きゾーン内にあるか、ファイルに Web のマークがあるためです。これらのファイルを処理するには、Web のマークを削除してください。
------ すべてのリビルド開始: プロジェクト:aaa, 構成: Debug Any CPU ------
C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\Microsoft.Common.CurrentVersion.targets(3162,5): error MSB3821: ファイル Form1.resx を処理できませんでした。インターネットまたは制限付きゾーン内にあるか、ファイルに Web のマークがあるためです。これらのファイルを処理するには、Web のマークを削除してください。
========== すべてリビルド: 0 正常終了、2 失敗、0 スキップ ==========

よく読むと Form1.resx に問題があるようです。VisualStudio から報告されたエラー番号は MSB3821 です。ネット検索をかけたら山ほどヒットしますので本記事では簡単に対処方法を紹介します。

Fig. 14、エクスプローラで Form1.resx を選んで右クリックしてプロパティを表示させます。
Fig. 15、プロパティのダイアログでセキュリティを許可するためにチェックを入れます。

Fig. 14
Fig. 15

これでビルドができるようになります。(なんでこんなことになるのか、私にはよくわかりません....)

重ねての注意喚起ですが、本記事の内容をビルドするにはビットマップファイルを画像リソースとして登録してからビルドしてください。