Adsense_top

2009年9月19日土曜日

C# 変形ウインドウにマウス操作を追加した。

先日書いた、「画像を使った変形ウインドウを作ってみた。」の続きです。

前回は画像を読み込んでウインドウを作っていたため タイトルバーないためにCloseBoxの代わりに、フォームのクリックイベットで閉じるようにして逃げていました。
また、同じくタイトルバーがないために、ドラッグしてのフォームの移動が出来ませんでした

今回は上記の点を変更し、独自に画像ファイルを使用してCloseBoxを表示し、フォームのドラッグも出来るように変更してみます。
前回のソースを変更して対応していますので、前回説明した部分ははしょってるかもしれませんので、前回分をみられていない方は前回のエントリーも参照してください

以下のような画像ファイルを用意しました。
Skin.bmp
Close.bmp

20 X 60 の中に、20 X 20で、左から、「通常状態」「マウスがポイントしている状態」「Boxを押下している状態」の3つをまとめて描いてあります。

クラス変数は、画像を格納する変数と、マウスの状態を格納する変数、背景画像上の CloseBoxの描画位置とサイズ情報などを格納した変数を用意しました。
// 背景画像とCloseBox画像を格納する変数
private Bitmap backBmp = null;
private Bitmap closeBmp = null;
// マウスの左ボタンが押下されているかの状態を格納
private bool isMouseDown = false;
// マウスポインタがCloseBox上に位置しているかを格納
private bool isMouseOverBox = false;
// マウスがCloseBox上で押下されたかを格納
private bool isMouseDownOnClose = false;
// マウスポインタの位置を格納
private Point mousePoint;
// CloseBoxの位置とサイズを格納した、
// readonly修飾しているので実質的な実行時定数
private readonly Point boxLocation = new Point(323, 43);
private readonly Size boxSize = new Size(20, 20);
// 「arayan's」と書かれた背景画像上ボタンの位置とサイズを格納した、
// readonly修飾しているので実質的な実行時定数
private readonly Point titleLocation = new Point(16, 15);
private readonly Size titleSize = new Size(137, 30);

フォームのコンストラクタには、CloseBox画像の読み込みを追加しています。
//
// コンストラクタ(初期化処理)
//
public Form1()
{
InitializeComponent();
// 今回はこのイベントハンドラの登録およびハンドラは削除
//this.Click += new System.EventHandler(this.Form1_Click);

// 以下3つのイベントハンドラ登録部今回追加
this.MouseDown += new System.Windows.Forms.
MouseEventHandler(this.Form1_MouseDown);
this.MouseUp += new System.Windows.Forms.
MouseEventHandler(this.Form1_MouseUp);
this.MouseMove += new System.Windows.Forms.
MouseEventHandler(this.Form1_MouseMove);

// 境界線なしにする。
this.FormBorderStyle = FormBorderStyle.None;
// 。ダブル バッファリングを使用するように動作を設定する。
this.SetStyle(ControlStyles.DoubleBuffer
| ControlStyles.UserPaint
| ControlStyles.AllPaintingInWmPaint, true);
// 背景画像ファイルを読み込む
bmp =(Bitmap) Bitmap.FromFile("skin.bmp");

// CloseBox画像を読み込む(今回追加部)
closeBmp = (Bitmap)Bitmap.FromFile("Close.bmp");

// ウインドウサイズを調整(前回分とは少し変更しました。)
this.Width = bmp.Width;
this.Height = bmp.Height;
// ウインドウ領域を設定する。
this.Region = BitmapToRegion(bmp, Color.FromArgb(255, 0, 255));
// 原点の色を使う場合は以下のようにする
// this.Region = BitmapToRegion(bmp, bmp.GetPixel(0, 0));
}
背景描画の部分にCloseBoxの描画メソッドのコール追加しています。
BitmapToRegionメソッドは前回のエントリーに書いたコードを参照してください。
//
// コントロールの背景を描画。
// (OnPaintBackground をオーバーライド)
//
protected override void OnPaintBackground(PaintEventArgs e)
{
// 座標単位を設定
e.Graphics.PageUnit = GraphicsUnit.Pixel;
// 背景描画(画像を描画)
e.Graphics.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height);

// CloseBoxの描画メソッドを呼ぶ(今回追加部)
DrawCloaseBox(e.Graphics);
}
//
// CloseBoxを状態に合わせて異なる画像を描画する(今回追加部)
//
private void DrawCloseBox(Graphics g)
{
// closeBmp中の今回使用するCloseBox画像の矩形を格納する変数
Rectangle rectSrc;
if (isMouseOverBox)
{
if (isMouseDown)
{
// CloseBoxが押下された画像
rectSrc =
new Rectangle(new Point(boxSize.Width * 2, 0), boxSize);
}
else
{
// CloseBoxがアクティブ状態の画像
rectSrc =
new Rectangle(new Point(boxSize.Width, 0), boxSize);
}
}
else
{
// 通常時の画像
rectSrc =
new Rectangle(new Point(0, 0), boxSize);
}
// 状態にあった画像でCloseBoxを描画
g.DrawImage(this.closeBmp, new Rectangle(boxLocation,boxSize),
rectSrc, GraphicsUnit.Pixel);
}
「OnPaintBackground」では、前回分に追加してCloseBoxを描画するメソッド「DrawCloseBox」をGraphicsオブジェクトを渡して読んでいます。
「DrawCloseBox」では、マウスイベントハンドラで設定したフラグで状態を判断し、状態にあったCloseBox画像を描画しています。
下図のような表示になります。
  

以下は今回追加したイベントハンドラ部でです。
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
// マウス左クリック時位置を記録
if (e.Button == MouseButtons.Left)
mousePoint = new Point(e.X, e.Y);
// CloseBox上の押下か?を記録
isMouseDownOnClose = isMouseOverButton;
// マウスの押下状態を記録
isMouseDown = true;
// 再描画
Refresh();
}
ここでは、マウス左ボタンの押下状態を記録。
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
// マウス押下記録を消去
isMouseDown = false;
// CloseBox上にマウスがあり且、CloseBox上で
// 押下さたのであればウインドウを閉じる
if (isMouseOverButton && isMouseDownOnClose)
this.Close();
else
isMouseDownOnClose = false;
}
ここでは、変数の値を確認判定して、条件を満たしていればウインドウを閉じています。
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
// マウス左ボタンか確認
if ( e.Button == MouseButtons.Left)
{
// ポインタ位置が「arayan's」ボタンの中にある場合
if (e.X > titleLocation.X)
&& (e.X < titleLocation.X + titleSize.Width)
&& (e.Y > titleLocation.Y)
&& (e.Y < titleLocation.Y + titleSize.Height))
{
// フォームの位置をMouseDown時からの移動分調節(移動)する。
this.SetDesktopLocation(this.Left - mousePoint.X + e.X,
this.Top - mousePoint.Y + e.Y);
}
// ポインタ位置がCloseBoxの中にある場合
if ((e.X > buttonLocation.X)
&& (e.X < buttonLocation.X + buttonSize.Width)
&& (e.Y > buttonLocation.Y)
&& (e.Y < buttonLocation.Y + buttonSize.Height))
{
// 変数にフラグを立てる
isMouseOverButton = true;
}
else
{
isMouseOverButton = false; ;
}
// 再描画
Refresh();
}
}
ここで、フォームのドラッグを処理しています。また、マウスがCloseBox上に位置するかも記録しています。

以上で「arayan's」ボタンをタイトルバーのようにドラッグで移動できるようになり、右上のボタンで閉じることが出来るようになりました。
画像を使ったウインドウは大体出来たので、着せ替えが出来るようにし本当の意味でのスキンを考えてみようかと思います。

2009年9月8日火曜日

C# 画像を使った変形ウインドウを作ってみた。

前回の変形ウインドウは丸いだけで面白くもないので、画像を読み込んで、その画像形状のウインドウを表示してみました。

以下のような画像を用意しました。画像の上手下手やセンスは目を瞑ってください。
マゼンタ(ピンクっぽい色)のところが透過する部分になります。

使用するクラス変数は Bitmap画像を格納する bmp だけです。
// 背景画像格納する変数
private Bitmap bmp = null;

以下が今回のメインのウインドウの形状を切り抜く部分です。
コードとしては、ひたすら透過色以外の部分の点を抜き出しているだけです
//
// 画像からウインドウの領域を作成し返します。
//
// 引数 bmp:対象画像
// 引数 transparencyKey:透過色
private Region BitmapToRegion(Bitmap bmp,
Color transparencyKey)
{
int height = bmp.Height;
int width = bmp.Width;
int xStart = 0;
System.Drawing.Drawing2D.GraphicsPath path =
new System.Drawing.Drawing2D.GraphicsPath();
// 縦方向の走査
for (int i = 0; i < height; i++)
{
// 横方向の走査
for (int j = 0; j < width; j++)
{
if (bmp.GetPixel(j, i) != transparencyKey)
{
// 透過色でない部分の連続を1ピクセル高の矩形にまとめ、
// GraphicsPathに追加していく
// (点ごとに登録処理するより効率がよいので)
xStart = j;
while ((j < width) &&
(bmp.GetPixel(j, i) != transparencyKey))
j++;
path.AddRectangle(
new Rectangle(xStart, i, j - xStart, 1));
}
}
}
// GraphicsPathからRegionを作成し返す。
Region region = new Region(path);
path.Dispose();
return region;
}

以下の部分は、まんま、画像を書いているだけです。
//
// コントロールの背景を描画。
// (OnPaintBackground をオーバーライド)
//
protected override void OnPaintBackground(PaintEventArgs e)
{
// 座標単位を設定
e.Graphics.PageUnit = GraphicsUnit.Pixel;
// 背景描画(画像を描画)
e.Graphics.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height);
}

次のイベントハンドラは、枠なしのウインドウにしたので、閉じることが出来にので,クリックでとりあえず閉じられるようしているだけです。
//
// マウスクリックでウインドウを閉じる
//
private void Form1_Click(object sender, EventArgs e)
{
this.Close();
}

コンストラクタでは、ダブルバッファを使えるようにするなどの設定と、ウインドウの形状を設定しています。
クリックイベントは見て分かり易いようにココに書いているだけです。
VisualStudioのWindows フォーム デザイナに任せて勿論OKです。
//
// コンストラクタ(初期化処理)
//
public Form1()
{
InitializeComponent();
// ウインドウを閉じるためのイベントを登録
this.Click += new System.EventHandler(this.Form1_Click);

// 境界線なしにする。
this.FormBorderStyle = FormBorderStyle.None;
// 。ダブル バッファリングを使用するように動作を設定する。
this.SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint, true);
// 画像ファイルを読み込む
bmp =(Bitmap) Bitmap.FromFile("skin.bmp");
// ウインドウサイズを調整
if (this.Width < bmp.Width)
this.Width = bmp.Width;
if (this.Height < bmp.Height)
this.Height = bmp.Height;
// ウインドウ領域を設定する。
this.Region =
BitmapToRegion(bmp, Color.FromArgb(255, 0, 255));

// 原点の色を使う場合は以下のようにする
// this.Region = BitmapToRegion(bmp, bmp.GetPixel(0, 0));
}

一番下のコメントアウトしている部分は、よく原点の色を透過色に使う方おられるので書いておきました。
で、結果以下のように表示することが出来ます。

前回の丸よりはスキンぽくなりました。画像の造りが悪くエッジが白く出ていますが...
面白くなって来たので、このシリーズ続けるかも知れません


上記メソッド「BitmapToRegion」を改良し高速化したものを「C# 画像を使った変形ウインドウの改良」としてエントリーしました。そちらも合わせてご覧ください。