2003/12/12
いわゆるUpdateLayeredWindow()の使用方法。なんでこれが標準で用意されてないのでしょう?
おそらくForm.OpacityはSetLayeredWindowAttributes()を使用していると思われます。この関数は非常に手軽にレイヤードウィンドウを実現できますが、小回りが効きません。対するUpdateLayeredWindow()はビットマップを元にレイヤードウィンドウを作ります。これはボタンを初めとするGUIを扱うことは出来ません(実際配置されておりアクセスすることも可能ですがWindowsが描画をしないため自分で実装する必要があります。Formのイメージをとる方法についてはFormをコピーを参照してください。)が単一イメージを表示するデスクトップアクセサリにはむしろ優れていると言えます。またパフォーマンスもこちらのほうが高いみたいです。
Form作成時にレイヤードウィンドウに設定します。Form.CreateParamsをオーバーライドしCreateParams.ExStyleにWS_EX_LAYEREDを指定するだけなのでお手軽。
class MyForm : System.Windows.Forms.Form {
protected override
System.Windows.Forms.CreateParams CreateParams
{
get {
System.Windows.Forms.CreateParams cp
= base.CreateParams;
cp.ExStyle |= WS_EX_LAYERED;
return cp;
}
}
}
特に難しいことはなく普通のSystem.Drawing.Bitmapで構いません。このピクセル単位の透過率がそのままFormの透過率となります。
これはフィールドとして定義しておきます。
class MyForm : System.Windows.Forms.Form {
/// <summary>
/// UpdateLayeredWindow サーフェイスビットマップ
/// </summary>
private System.Drawing.Bitmap m_surfaceBitmap;
}
UpdateLayeredWindow()は一つのビットマップに複数のサーフェイスを割り当てることができます。アニメーションさせる場合は複数のビットマップを作らず一つにまとめると便利です。
UpdateLayeredWindow()はPOINT,SIZE,BLENDFUNCTIONを使用します。Unmanagedなコードを呼び出す際の構造体にはSystem.Runtime.InteropServices.StructLayout属性が必要です。
構造体初期化はコード内で行うと煩雑になるので初期化用のメソッドを準備しておきます。(ビットマップにサーフェイスは一つと仮定)
class MyForm : System.Windows.Forms.Form {
/// <summary>
/// Formの位置を格納した
/// POINT 構造体を初期化
/// </summary>
private POINT Init_Update_FormPositon() {
POINT pos = new POINT();
pos.x = this.Left;
pos.y = this.Top;
return pos;
}
/// <summary>
/// BLENDFUNCTION を初期化
/// </summary>
private BLENDFUNCTION Init_Update_BlendFunction() {
BLENDFUNCTION bf = new BLENDFUNCTION();
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.AlphaFormat = AC_SRC_ALPHA;
// Form の全体の透過率(0〜255)255で非透過
bf.SourceConstantAlpha = 255;
return bf;
}
/// <summary>
/// サーフェイスのサイズを格納した
/// SIZE 構造体を初期化
/// </summary>
private SIZE Init_Update_SurfaceSize() {
SIZE size = new SIZE();
// 一つの Bitmap に複数のサーフェイスを持たせる場合
// 適宜改変してください。
size.cx = this.m_surfaceBitmap.Width;
size.cy = this.m_surfaceBitmap.Height;
return size;
}
/// <summary>
/// サーフェイスの位置を格納した
/// POINT 構造体を初期化
/// </summary>
private POINT Init_Update_SurfacePositon() {
POINT pos = new POINT();
// 一つの Bitmap に複数のサーフェイスを持たせる場合
// 適宜改変してください。
pos.x = pos.y = 0;
return pos;
}
}
長いです。はい
class MyForm : System.Windows.Forms.Form {
private void Update() {
if( this.m_surfaceBitmap == null) return;
if( this.m_surfaceBitmap.PixelFormat
!= System.Drawing.Imaging.PixelFormat.Format32bppArgb)
return;
lock( this ) {
IntPtr hPrevBitmap = IntPtr.Zero;
IntPtr hBitmap = IntPtr.Zero;
IntPtr hScreenDC = IntPtr.Zero;
IntPtr hWndDC = IntPtr.Zero;
BLENDFUNCTION bf;
POINT pt;
POINT surfacePos;
SIZE surfaceSize;
// カラーキー、指定した色は透過色になります。
// ULW_COLORKEY を指定した場合有効。
uint crKey = 0;
try {
// GDI オブジェクトの初期化
hScreenDC = GetDC( IntPtr.Zero );
hWndDC = CreateCompatibleDC( hScreenDC );
hBitmap = this.m_surfaceBitmap.GetHbitmap(
Color.FromArgb(0));
if( (hScreenDC == IntPtr.Zero)
|| (hWndDC == IntPtr.Zero)
|| (hBitmap == IntPtr.Zero) )
{
throw new System.ApplicationException(
"GDIオブジェクトの作成に失敗" );
}
// ビットマップをデバイスコンテキストに割り当て
hPrevBitmap = SelectObject( hWndDC, hBitmap );
// BLENDFUNCTION 構造体の設定
bf = this.Init_Update_BlendFunction();
// ウィンドウ位置の設定
pt =Init_Update_FormPositon();
// サーフェースサイズの設定
surfaceSize = this.Init_Update_SurfaceSize();
// サーフェースの位置の設定
surfacePos = this.Init_Update_SurfacePositon();
if( !UpdateLayeredWindow(
this.Handle, hScreenDC,
ref pt, ref surfaceSize,
hWndDC, ref surfacePos,
crKey, ref bf, (uint)ULW_ALPHA) )
{
throw new ApplicationException(
"レイヤードウィンドウの更新に失敗");
}
}
finally {
ReleaseDC( IntPtr.Zero, hScreenDC );
SelectObject(hWndDC, hPrevBitmap);
DeleteDC( hWndDC );
DeleteObject(hBitmap);
}
}
}
}
用意したBitmapからHBITMAPを作成しデバイスコンテキストに割り当てています。このHBITMAPはレイヤードウィンドウ更新後削除しちゃってかまいません。
透過率を変えるだけの場合サーフェイスの再設定は必要なくUpdateLayeredWindow( this.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, crKey, ref bf, (uint)ULW_ALPHA );
で行けます。
UpdateLayeredWindow()を使用した場合Formの持つレイヤードウィンドウ機能を使ってはいけません。全てUpdateLayeredWindow()で代用できるので実質問題はありません。
Copyright(C)方位記号