2011年04月23日

半透明のフォームの作り方(2つのフォームを重ねる)

レイヤードウィンドウを使った半透明のフォームには、コントロールが描画されない、描画が重たい、などの問題がありました。それに、せっかくのC#なのに、WindowsAPIを呼び出して実行するのも良くありません(個人的に)
フォームのOpacityを設定すれば良いのですが、それではフォームに配置したコントロールも半透明になります。コントロールは不透明のまま、背景のみ半透明にするにはどうしたら良いでしょうか。
多少強引ですが、半透明のフォームの上に背景のみ透明にしたフォームを重ね合わせることで、解決を試みます。

まず、半透明のフォームを作ります。テキストボックスとボタンを配置しておきます。

public partial class Form1 : Form
{
    // テキスト
    private TextBox text1;

    // ボタン
    private Button button1;

    public Form1()
    {
        InitializeComponent();

        // テキストを生成して配置
        text1 = new TextBox();
        text1.Text = "テキスト1";
        text1.Left = 10;
        text1.Top = 10;
        this.Controls.Add(text1);

        // ボタンを生成して配置
        button1 = new Button();
        button1.Text = "ボタン2";
        button1.Left = 10;
        button1.Top = text1.Bottom + 10;
        this.Controls.Add(button1);

        // キャプション
        this.Text = "半透明フォーム";

        // 半透明指定
        this.Opacity = 0.75;
    }
}

もう一つ、上に重ねるフォームを作ります。重ねたときに分かるように、コントロールは右側に配置します。

public partial class Form2 : Form
{
    // テキスト
    private TextBox text2;

    // ボタン
    private Button button2;

    public Form2()
    {
        InitializeComponent();

        // テキストを生成して配置
        text2 = new TextBox();
        text2.Text = "テキスト2";
        text2.Left = 150;
        text2.Top = 10;
        this.Controls.Add(text2);

        // ボタンを生成して配置
        button2 = new Button();
        button2.Text = "ボタン2";
        button2.Left = 150;
        button2.Top = text2.Bottom + 10;
        this.Controls.Add(button2);

        // キャプション
        this.Text = "上重ねフォーム";
    }
}

この時点で両方のフォームを表示すると、このような感じになります。まだ上に重ねるフォームは不透明です。

private void Form1_Load(object sender, EventArgs e)
{
    Form2 f2 = new Form2();
    f2.Show();
}

dual-form-1.png

続きます

良かったらクリックしてください
にほんブログ村 IT技術ブログ プログラム・プログラマーへ  人気ブログランキングへ

posted by among at 20:01 | Comment(0) | TrackBack(0) | C#

2011年04月21日

Colorの反転方法あれこれ(C#)

Colorを反転させるメソッドが標準に無さそうだったので、その方法を幾つか紹介します。

方法1

// カラー定義
Color color = Color.Blue;
// 反転処理
color = Color.FromArgb(color.ToArgb() ^ 0xFFFFFF);

ToArgbにより、Alpha、Red、Green、Blueの色要素を32bitの数値に変換し、そのうち下位24bitをビット演算で反転させています。上位8bitはAlphaなので反転させません。

方法2

// カラー定義
Color color = Color.Blue;
// 反転処理
color = Color.FromArgb(
                color.R ^ 0xFF, 
                color.G ^ 0xFF, 
                color.B ^ 0xFF);

Red、Green、Blueの色要素を、R/G/Bのメンバ変数により取得し、それぞれを反転させて、FromArgbでColorに設定しています。特定の色要素だけ反転させたいときは、こちらの方がやりやすいです。

方法3

// カラー定義
Color color = Color.Blue;
// 反転処理
color = Color.FromArgb(
                (byte)~color.R, 
                (byte)~color.G, 
                (byte)~color.B);

方法2と同じですが、反転のさせ方が違います。どちらを使うかはお好みで。

良かったらクリックしてください
にほんブログ村 IT技術ブログ プログラム・プログラマーへ  人気ブログランキングへ

posted by among at 22:35 | Comment(0) | TrackBack(0) | C#

2011年04月17日

半透明のフォームの作り方(レイヤードウィンドウ・周期描画)(C#)

前回の記事でレイヤードウィンドウを使った半透明のフォームを作りましたが、今回はそのフォームに描画する方法を紹介します。
レイヤードウィンドウでは通常のウィンドウとは描画の仕組みがことなるようで、コントロールが描画されません。つまりラベルなどを貼り付けても表示されません。
そこで、レイヤードウィンドウを作成するときに使ったBITMAPを直接編集し、もう一度UpdateLayerdWindowを呼び出し、フォームを更新するという手順を行います。

描画はタイマーを使って定周期で画面を更新するだけの簡単なものにします。
タイマースレッドから安全にUpdateLayerdWindowを呼び出すために、Invokeを使った排他制御を行います。SetLayeredWindowDelegateを参照してください。

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        // サーフェースの画像
        private Bitmap surfaceBitmap;

        // 背景の画像
        private Bitmap bgBitmap;

        // フォームをD&Dで移動させるクラス
        // レイヤードウィンドウではタイトルバーが表示されないため
        // このクラスの機能で移動させるようにする
        Com.Mirano.Forms.FormDragMover formMover;

        // ☆描画テストのためのタイマー
        System.Threading.TimerCallback drawTimerDelegate;
        System.Threading.Timer drawTimer;

        // ☆描画テストのためのカウンター
        private int drawCounter = 0;

        // ☆UpdateLayeredWindowのコールバック関数(排他制御)
        delegate void SetLayeredWindowCallback(Bitmap image);

        // ☆UpdateLayeredWindowのデリゲート(排他制御)
        private void SetLayeredWindowDelegate(Bitmap image)
        {
            // スレッドセーフ
            if (this.InvokeRequired)
            {
                SetLayeredWindowCallback d = new SetLayeredWindowCallback(SetLayeredWindowDelegate);
                this.Invoke(d, new object[] { image });
            }
            else
            {
                SetLayeredWindow(image);
            }
        }

        public Form1()
        {
            InitializeComponent();

            // フォームの境界線を無くす
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;

            // フォームをD&Dで移動させるクラス
            formMover = new Com.Mirano.Forms.FormDragMover(this, 8, true, new Size(16, 16));

            // フォームの境界線を無くしたことで、メニューも閉じるボタンも表示されないので
            // 右クリック時コンテキストメニューにより終了できるようにする
            ContextMenuStrip cntmenu = new ContextMenuStrip();
            {
                ToolStripMenuItem newcontitem = new ToolStripMenuItem();
                newcontitem.Text = "終了(&Q)";
                newcontitem.Click += delegate
                {
                    // 閉じる
                    this.Close();
                };
                cntmenu.Items.Add(newcontitem);
            }
            this.ContextMenuStrip = cntmenu;  //フォームの右クリック

            // ☆描画タイマースタート
            drawTimerDelegate = new System.Threading.TimerCallback(this.drawTimerCallback);
            drawTimer = new System.Threading.Timer(drawTimerDelegate, null, 100, 100);
        }

        // Form作成時にCreateParams.ExStyleにWS_EX_LAYEREDを指定するため
        // Form.CreateParamsをオーバーライドする
        protected override System.Windows.Forms.CreateParams CreateParams
        {
            get
            {
                const int WS_EX_LAYERED = 0x00080000;

                System.Windows.Forms.CreateParams cp = base.CreateParams;
                cp.ExStyle = cp.ExStyle | WS_EX_LAYERED;

                return cp;
            }
        }

        // UpdateLayeredWindow関連のAPI定義
        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int DeleteObject(IntPtr hobject);

        public const byte AC_SRC_OVER = 0;
        public const byte AC_SRC_ALPHA = 1;
        public const int ULW_ALPHA = 2;

        // UpdateLayeredWindowで使うBLENDFUNCTION構造体の定義
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct BLENDFUNCTION
        {
            public byte BlendOp;
            public byte BlendFlags;
            public byte SourceConstantAlpha;
            public byte AlphaFormat;
        }

        // UpdateLayeredWindowを使うための定義
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int UpdateLayeredWindow(
            IntPtr hwnd,
            IntPtr hdcDst,
            [System.Runtime.InteropServices.In()]
            ref Point pptDst,
            [System.Runtime.InteropServices.In()]
            ref Size psize,
            IntPtr hdcSrc,
            [System.Runtime.InteropServices.In()]
            ref Point pptSrc,
            int crKey,
            [System.Runtime.InteropServices.In()]
            ref BLENDFUNCTION pblend,
            int dwFlags);

        /// <summary>
        /// レイヤードウィンドウを設定する
        /// </summary>
        /// <param name="srcBitmap">サーフェイスのBITMAP</param>
        public void SetLayeredWindow(Bitmap srcBitmap)
        {
            // スクリーンのGraphicsと、hdcを取得
            Graphics g_sc = Graphics.FromHwnd(IntPtr.Zero);
            IntPtr hdc_sc = g_sc.GetHdc();

            // BITMAPのGraphicsと、hdcを取得
            Graphics g_bmp = Graphics.FromImage(srcBitmap);
            IntPtr hdc_bmp = g_bmp.GetHdc();

            // BITMAPのhdcで、サーフェイスのBITMAPを選択する
            // このとき背景を無色透明にしておく
            IntPtr oldhbmp = SelectObject(hdc_bmp, srcBitmap.GetHbitmap(Color.FromArgb(0)));

            // BLENDFUNCTION を初期化
            BLENDFUNCTION blend = new BLENDFUNCTION();
            blend.BlendOp = AC_SRC_OVER;
            blend.BlendFlags = 0;
            blend.SourceConstantAlpha = 255;
            blend.AlphaFormat = AC_SRC_ALPHA;

            // ウィンドウ位置の設定
            Point pos = new Point(this.Left, this.Top);

            // サーフェースサイズの設定
            Size surfaceSize = new Size(this.Width, this.Height);

            // サーフェース位置の設定
            Point surfacePos = new Point(0, 0);

            // レイヤードウィンドウの設定
            UpdateLayeredWindow(
                this.Handle, hdc_sc, ref pos, ref surfaceSize, 
                hdc_bmp, ref surfacePos, 0, ref blend, ULW_ALPHA);

            // 後始末
            DeleteObject(SelectObject(hdc_bmp, oldhbmp));
            g_sc.ReleaseHdc(hdc_sc);
            g_sc.Dispose();
            g_bmp.ReleaseHdc(hdc_bmp);
            g_bmp.Dispose();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // ☆サーフェイスのBITMAPを生成
            surfaceBitmap = new Bitmap(this.Width, this.Height);

            // ☆背景の画像を生成
            {
                bgBitmap = new Bitmap(this.Width, this.Height);
                Graphics g = Graphics.FromImage(bgBitmap);
                // グラデーション描画(半透明)
                {
                    // 透明から紫色へのグラデーションを描画
                    LinearGradientBrush lgb = new LinearGradientBrush(
                            this.ClientRectangle, 
                            Color.Transparent, Color.Violet, 
                            LinearGradientMode.Horizontal);
                    lgb.GammaCorrection = true; //ガンマ補正
                    g.FillRectangle(lgb, this.ClientRectangle);
                    lgb.Dispose();
                }
                g.Dispose();
            }
        }

        //
        // ☆描画テストのためのタイマーからのコールバック
        //
        private void drawTimerCallback(object o)
        {
            // カウンターの値を更新
            drawCounter++;
            // 描画するメッセージの作成
            string text = "COUNT:" + drawCounter.ToString("D6");

            // 描画処理
            drawPerform(text);
        }

        //
        // ☆描画処理
        //
        private void drawPerform(string text)
        {
            // BITMAPに描画するためのGraphicsを取得
            Graphics g = Graphics.FromImage(surfaceBitmap);

            // いったん透明色でクリアする
            g.Clear(Color.Transparent);

            // 背景画像を描画
            g.DrawImage(bgBitmap, 0, 0);

            // グラデーションの上に文字を重ねる(不透明)
            {
                // アンチエリアス
                g.SmoothingMode = SmoothingMode.HighQuality;

                // レンダリング品質
                g.CompositingQuality = CompositingQuality.HighQuality;

                // メイリオでの描画
                {
                    Font font = new Font("Meiryo UI", 24, FontStyle.Bold);
                    Point pos = new Point(20, 20);

                    // GraphicsPathの生成
                    GraphicsPath gp = new GraphicsPath();

                    // テキストをPathに変換
                    gp.AddString(text, font.FontFamily, (int)font.Style, font.SizeInPoints,
                        pos, StringFormat.GenericDefault);

                    // パスを移動させる
                    {
                        Matrix translateMatrix = new Matrix();
                        translateMatrix.Translate(pos.X - gp.GetBounds().X, pos.Y - gp.GetBounds().Y);
                        gp.Transform(translateMatrix);
                        translateMatrix.Dispose();
                    }

                    // 文字の縁を描画
                    g.DrawPath(new Pen(Brushes.Blue, 3.0f), gp);

                    // 文字を塗る
                    g.FillPath(Brushes.White, gp);

                    // 終了
                    gp.Dispose();
                }
            }
            // レイヤードウィンドウ指定
            SetLayeredWindowDelegate(surfaceBitmap);
        }
    }

実行結果は下記のようになります。
UpdateLayeredWindow-2.png

良かったらクリックしてください
にほんブログ村 IT技術ブログ プログラム・プログラマーへ  人気ブログランキングへ

posted by among at 20:23 | Comment(0) | TrackBack(0) | C#