1. 程式人生 > >C# Winform 窗體美化(三、不規則窗體)

C# Winform 窗體美化(三、不規則窗體)

三、不規則窗體

概況

之前學習的 LayeredSkin 看到裡面有個異形視窗,比較感興趣,所以就找一下資料研究一下。不規則窗體學習有一個比較好的例子,叫 GoldFishProject,是一條魚金魚在螢幕上游。

現學習了兩種實現方式:
1. UpdateLayeredWindow
2. GraphicsPath

1.UpdateLayeredWindow

這種方式實現的不規則視窗很平滑,沒有鋸齒,可以帶半透明的效果,但是不在響應 paint 方法,繪製不了窗體上的控制元件,效果圖:
不規則視窗

程式碼如下:

窗體程式碼:

public partial class UpdateLayeredWindowForm : Form
{
    bool
haveHandle = false;//窗體控制代碼建立完成 public UpdateLayeredWindowForm() { InitializeComponent(); } private void UpdateLayeredWindowForm_Load(object sender, EventArgs e) { FormBorderStyle = FormBorderStyle.None;//取消視窗邊框 SetBits(new Bitmap(BackgroundImage));//設定不規則窗體 FormMovableEvent();//設定拖動窗體移動
} #region 防止窗體閃屏 private void InitializeStyles() { SetStyle( ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.SupportsTransparentBackColor, true
); SetStyle(ControlStyles.Selectable, false); UpdateStyles(); } #endregion #region 控制代碼建立事件 protected override void OnHandleCreated(EventArgs e) { InitializeStyles();//設定視窗樣式、雙緩衝等 base.OnHandleCreated(e); haveHandle = true; } #endregion #region 設定窗體樣式 protected override CreateParams CreateParams { get { CreateParams cParms = base.CreateParams; cParms.ExStyle |= 0x00080000; // WS_EX_LAYERED return cParms; } } #endregion #region 設定不規則窗體 public void SetBits(Bitmap bitmap) { if (!haveHandle) return; if (!Bitmap.IsCanonicalPixelFormat(bitmap.PixelFormat) || !Bitmap.IsAlphaPixelFormat(bitmap.PixelFormat)) throw new ApplicationException("The picture must be 32bit picture with alpha channel."); IntPtr oldBits = IntPtr.Zero; IntPtr screenDC = Win32.GetDC(IntPtr.Zero); IntPtr hBitmap = IntPtr.Zero; IntPtr memDc = Win32.CreateCompatibleDC(screenDC); try { Win32.Point topLoc = new Win32.Point(Left, Top); Win32.Size bitMapSize = new Win32.Size(bitmap.Width, bitmap.Height); Win32.BLENDFUNCTION blendFunc = new Win32.BLENDFUNCTION(); Win32.Point srcLoc = new Win32.Point(0, 0); hBitmap = bitmap.GetHbitmap(Color.FromArgb(0)); oldBits = Win32.SelectObject(memDc, hBitmap); blendFunc.BlendOp = Win32.AC_SRC_OVER; blendFunc.SourceConstantAlpha = 255;//這裡設定窗體繪製的透明度 blendFunc.AlphaFormat = Win32.AC_SRC_ALPHA; blendFunc.BlendFlags = 0; Win32.UpdateLayeredWindow(Handle, screenDC, ref topLoc, ref bitMapSize, memDc, ref srcLoc, 0, ref blendFunc, Win32.ULW_ALPHA); } finally { if (hBitmap != IntPtr.Zero) { Win32.SelectObject(memDc, oldBits); Win32.DeleteObject(hBitmap); } Win32.ReleaseDC(IntPtr.Zero, screenDC); Win32.DeleteDC(memDc); } } #endregion #region 無標題欄的視窗移動 private Point mouseOffset; //記錄滑鼠指標的座標 private bool isMouseDown = false; //記錄滑鼠按鍵是否按下 /// <summary> /// 窗體移動監聽繫結 /// </summary> private void FormMovableEvent() { //窗體移動 this.MouseDown += new MouseEventHandler(Frm_MouseDown); this.MouseMove += new MouseEventHandler(Frm_MouseMove); this.MouseUp += new MouseEventHandler(Frm_MouseUp); } /// <summary> /// 窗體按下時 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Frm_MouseDown(object sender, MouseEventArgs e) { int xOffset; int yOffset; //點選窗體時,記錄滑鼠位置,啟動移動 if (e.Button == MouseButtons.Left) { xOffset = -e.X; yOffset = -e.Y; mouseOffset = new Point(xOffset, yOffset); isMouseDown = true; } } /// <summary> /// 窗體移動時 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Frm_MouseMove(object sender, MouseEventArgs e) { if (isMouseDown) { //移動的位置計算 Point mousePos = Control.MousePosition; mousePos.Offset(mouseOffset.X, mouseOffset.Y); Location = mousePos; } } /// <summary> /// 窗體按下並釋放按鈕時 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Frm_MouseUp(object sender, MouseEventArgs e) { // 修改滑鼠狀態isMouseDown的值 // 確保只有滑鼠左鍵按下並移動時,才移動窗體 if (e.Button == MouseButtons.Left) { //鬆開滑鼠時,停止移動 isMouseDown = false; //Top高度小於0的時候,等於0 if (this.Top < 0) { this.Top = 0; } } } #endregion }

Win32API程式碼:(從金魚的例子中引用的程式碼)

//##########################################################################
//★★★★★★★           http://www.cnpopsoft.com           ★★★★★★★
//★★          VB & C# source code and articles for free !!!           ★★
//★★★★★★★                Davidwu                       ★★★★★★★
//##########################################################################

using System;
using System.Runtime.InteropServices;

/// <summary>
/// Wind32API
/// </summary>
internal class Win32
{
    #region 訊息
    public const int MF_REMOVE = 0x1000;

    public const int SC_RESTORE = 0xF120; //還原
    public const int SC_MOVE = 0xF010; //移動
    public const int SC_SIZE = 0xF000; //大小
    public const int SC_MINIMIZE = 0xF020; //最小化
    public const int SC_MAXIMIZE = 0xF030; //最大化
    public const int SC_CLOSE = 0xF060; //關閉 

    public const int WM_SYSCOMMAND = 0x0112;
    public const int WM_COMMAND = 0x0111;

    public const int GW_HWNDFIRST = 0;
    public const int GW_HWNDLAST = 1;
    public const int GW_HWNDNEXT = 2;
    public const int GW_HWNDPREV = 3;
    public const int GW_OWNER = 4;
    public const int GW_CHILD = 5;

    public const int WM_NCCALCSIZE = 0x83;
    public const int WM_WINDOWPOSCHANGING = 0x46;
    public const int WM_PAINT = 0xF;
    public const int WM_CREATE = 0x1;
    public const int WM_NCCREATE = 0x81;
    public const int WM_NCPAINT = 0x85;
    public const int WM_PRINT = 0x317;
    public const int WM_DESTROY = 0x2;
    public const int WM_SHOWWINDOW = 0x18;
    public const int WM_SHARED_MENU = 0x1E2;
    public const int HC_ACTION = 0;
    public const int WH_CALLWNDPROC = 4;
    public const int GWL_WNDPROC = -4;

    public const int WS_SYSMENU = 0x80000;
    public const int WS_SIZEBOX = 0x40000;

    public const int WS_MAXIMIZEBOX = 0x10000;

    public const int WS_MINIMIZEBOX = 0x20000;
    #endregion
    [StructLayout(LayoutKind.Sequential)]
    public struct Size
    {
        public Int32 cx;
        public Int32 cy;

        public Size(Int32 x, Int32 y)
        {
            cx = x;
            cy = y;
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct BLENDFUNCTION
    {
        public byte BlendOp;
        public byte BlendFlags;
        public byte SourceConstantAlpha;
        public byte AlphaFormat;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct Point
    {
        public Int32 x;
        public Int32 y;

        public Point(Int32 x, Int32 y)
        {
            this.x = x;
            this.y = y;
        }
    }

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

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern IntPtr CreateCompatibleDC(IntPtr hDC);

    [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern IntPtr GetDC(IntPtr hWnd);

    [DllImport("gdi32.dll", ExactSpelling = true)]
    public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObj);

    [DllImport("user32.dll", ExactSpelling = true)]
    public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern int DeleteDC(IntPtr hDC);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern int DeleteObject(IntPtr hObj);

    [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern int UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pptSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern IntPtr ExtCreateRegion(IntPtr lpXform, uint nCount, IntPtr rgnData);

    [DllImport("user32")]
    public static extern int SendMessage(IntPtr hwnd, int msg, int wp, int lp);
}

2.GraphicsPath

這種方式不能實現半透明效果,有鋸齒,好處是能顯示出控制元件,貼個效果圖感受下:

刪除完全透明區域
只保留不透明區域

程式碼如下:

public partial class GraphicsPathForm : Form
{
    public GraphicsPathForm()
    {
        InitializeComponent();
    }
    private void GraphicsPathForm_Load(object sender, EventArgs e)
    {
        TopMost = true;//設定為最頂層
        FormBorderStyle = FormBorderStyle.None;//取消視窗邊框
        this.Region = new Region(GetWindowRegion(new Bitmap(BackgroundImage)));//設定不規則窗體
        FormMovableEvent();//設定拖動窗體移動
    }
    #region 設定不規則窗體
    private GraphicsPath GetWindowRegion(Bitmap bitmap)
    {
        Color TempColor;
        GraphicsPath gp = new GraphicsPath();
        if (bitmap == null) return null;

        for (int nX = 0; nX < bitmap.Width; nX++)
        {
            for (int nY = 0; nY < bitmap.Height; nY++)
            {
                TempColor = bitmap.GetPixel(nX, nY);
                //if (TempColor.A != 0)//去掉完全透明區域
                if (TempColor.A == 255)//保留完全不透明的區域
                {
                    gp.AddRectangle(new Rectangle(nX, nY, 1, 1));
                }
            }
        }
        return gp;
    } 
    #endregion
    #region 無標題欄的視窗移動
    private Point mouseOffset; //記錄滑鼠指標的座標
    private bool isMouseDown = false; //記錄滑鼠按鍵是否按下

    /// <summary>
    /// 窗體移動監聽繫結
    /// </summary>
    private void FormMovableEvent()
    {
        //窗體移動
        this.MouseDown += new MouseEventHandler(Frm_MouseDown);
        this.MouseMove += new MouseEventHandler(Frm_MouseMove);
        this.MouseUp += new MouseEventHandler(Frm_MouseUp);
    }

    /// <summary>
    /// 窗體按下時
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Frm_MouseDown(object sender, MouseEventArgs e)
    {
        int xOffset;
        int yOffset;
        //點選窗體時,記錄滑鼠位置,啟動移動
        if (e.Button == MouseButtons.Left)
        {
            xOffset = -e.X;
            yOffset = -e.Y;
            mouseOffset = new Point(xOffset, yOffset);
            isMouseDown = true;
        }
    }

    /// <summary>
    /// 窗體移動時
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Frm_MouseMove(object sender, MouseEventArgs e)
    {
        if (isMouseDown)
        {
            //移動的位置計算
            Point mousePos = Control.MousePosition;
            mousePos.Offset(mouseOffset.X, mouseOffset.Y);
            Location = mousePos;
        }
    }

    /// <summary>
    /// 窗體按下並釋放按鈕時
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Frm_MouseUp(object sender, MouseEventArgs e)
    {
        // 修改滑鼠狀態isMouseDown的值
        // 確保只有滑鼠左鍵按下並移動時,才移動窗體
        if (e.Button == MouseButtons.Left)
        {
            //鬆開滑鼠時,停止移動
            isMouseDown = false;
            //Top高度小於0的時候,等於0
            if (this.Top < 0)
            {
                this.Top = 0;
            }
        }
    }
    #endregion
}

像第一種不能新增控制元件的方法要想實現顯示控制元件的話,下一個關鍵詞就是“雙層窗體”,使用兩層窗體來實現一個不規則窗體的效果,大致步驟如下:

底層是面板層,使用第一種方法;
上層是控制元件層,使用第二種方法。