コンテンツ

Activity

リンク

ピクセル単位で透過設定を持つForm

2003/12/12

いわゆるUpdateLayeredWindow()の使用方法。なんでこれが標準で用意されてないのでしょう?

Formが持つOpacityとの違い

おそらくForm.OpacitySetLayeredWindowAttributes()を使用していると思われます。この関数は非常に手軽にレイヤードウィンドウを実現できますが、小回りが効きません。対するUpdateLayeredWindow()はビットマップを元にレイヤードウィンドウを作ります。これはボタンを初めとするGUIを扱うことは出来ません(実際配置されておりアクセスすることも可能ですがWindowsが描画をしないため自分で実装する必要があります。Formのイメージをとる方法についてはFormをコピーを参照してください。)が単一イメージを表示するデスクトップアクセサリにはむしろ優れていると言えます。またパフォーマンスもこちらのほうが高いみたいです。

初期化

Form作成時にレイヤードウィンドウに設定します。Form.CreateParamsをオーバーライドしCreateParams.ExStyleWS_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()で代用できるので実質問題はありません。

履歴

2006/12/04
UpdateLayeredWindow()に渡す引数の名前が間違っていた問題を修正
2003/12/12
SetLayeredWindowAttributes()がSetLayeredWindowAttribute()になっていた問題を修正
2003/11/19
初出

goto Top

Copyright(C)方位記号

テレワークならECナビ Yahoo 楽天 LINEがデータ消費ゼロで月額500円〜!
無料ホームページ 無料のクレジットカード 海外格安航空券 海外旅行保険が無料! 海外ホテル