WINFORM自定義皮膚製作(上)

pamxy發表於2013-09-13

轉自:http://www.cnblogs.com/coding1016/archive/2010/01/22/1653777.html

最近要做個軟體正在做技術準備,由於WINFORM生成的窗體很醜陋,一個好的軟體除了功能性很重要外,UI的體驗也是不容忽視的。習慣性的在網上搜素了下,換膚控制元件也有好幾款,但是有些用起來不是很好用,好點的也要花很多銀子哦,而且畢竟是別人寫的,心裡總不是個滋味,所以決定自己嘗試著寫寫看,花了一個晚上終於做出來了個DEMO,貌似還不錯,貼圖如下(圖片是直接是用的暴風影音的,寒自己一個。。)image

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

下面和大家分享下。

首先分析下皮膚的製作原理,我的理解是把整個窗體(去邊框後)劃分為9個區域(如果有更復雜的介面,可以劃分更多),有圖有真相:

1

然後準備皮膚素材,切圖,我的切圖如下:

image

接著可以開工了:

1.初始化圖片資源變數

        protected int formMinX = 0;//最小化按鈕的X座標
        protected int formMaxX = 0;//最大化按鈕的X座標
        protected int formCloseX = 0;//關閉按鈕的X座標
        protected int formTitleMarginLeft = 0;//標題欄的左邊界
        protected int formTitleMarginRight = 0;//標題欄的右邊界
        Image imgTopLeft = (Image)Resources.topleft;//窗體頂部左上角圖片
        Image imgTopRight = (Image)Resources.topright;//窗體頂部右上角圖片
        Image imgTopMiddle = (Image)Resources.topstretch;//窗體頂部中間圖片
        Image imgBottomLeft = (Image)Resources.bottomLeft;//窗體底部左下角圖片
        Image imgBottonRight = (Image)Resources.bottomRight;//窗體底部右下角圖片
        Image imgBottonmMiddle = (Image)Resources.bottomstretch;//窗體底部中間圖片
        Image imgMiddleLeft = (Image)Resources.LeftDrag_Mid;//窗體中部左邊框圖片
        Image imgMiddleRight = (Image)Resources.RightDrag_Mid;//窗體中部右邊框圖片
        Image imgFormMin = (Image)Resources.skin_btn_min;//最小化按鈕
        Image imgFormMax = (Image)Resources.skin_btn_max;//最大化按鈕
        Image imgFormClose = (Image)Resources.skin_btn_close;//關閉按鈕
        Image imgFormRestore = (Image)Resources.skin_btn_restore;//還原按鈕

2.重寫OnPaint事件。程式碼直接貼上來(比較簡單,就是計算圖片要繪製到窗體的座標,然後把圖片繪到窗體上)

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            this.BackColor = Color.Black;
            //繪製皮膚
             Graphics g = e.Graphics;
            //繪製窗體頂部左上角圖片
              g.DrawImage(imgTopLeft, 0, 0, imgTopLeft.Width, imgTopLeft.Height);
            int topRightX = e.ClipRectangle.Width - imgTopRight.Width;
            //繪製窗體頂部右上角圖片
              g.DrawImage(imgTopRight, topRightX, 0, imgTopRight.Width, imgTopRight.Height);
            int topMiddleWidth= e.ClipRectangle.Width - (imgTopLeft.Width + imgTopRight.Width) + 4;
            //繪製窗體頂部中間圖片(標題欄)
            formTitleMarginLeft = imgTopLeft.Width;
            formTitleMarginRight = topRightX;
            g.DrawImage(imgTopMiddle, imgTopLeft.Width, 0, topMiddleWidth, imgTopMiddle.Height);
            //繪製窗體底部左下角圖片
              g.DrawImage(imgBottomLeft, 0, e.ClipRectangle.Height - imgBottomLeft.Height, imgBottomLeft.Width, imgBottomLeft.Height);
            //繪製窗體底部右下角圖片
              g.DrawImage(imgBottonRight, e.ClipRectangle.Width - imgBottomLeft.Width, e.ClipRectangle.Height - imgBottonRight.Height, imgBottonRight.Width, imgBottonRight.Height);
            //繪製窗體底部中間圖片
              g.DrawImage(imgBottonmMiddle, imgBottomLeft.Width, e.ClipRectangle.Height - imgBottonmMiddle.Height, e.ClipRectangle.Width - (imgBottomLeft.Width + imgBottomLeft.Width) + 4, imgBottonmMiddle.Height);
            //畫左右邊框
              g.DrawImage(imgMiddleLeft, 0, imgTopLeft.Height, imgMiddleLeft.Width, e.ClipRectangle.Height - (imgTopLeft.Height + imgBottomLeft.Height));
              g.DrawImage(imgMiddleRight, e.ClipRectangle.Width - imgMiddleRight.Width, imgTopRight.Height, imgMiddleRight.Width, e.ClipRectangle.Height - (imgTopLeft.Height + imgBottomLeft.Height));
            //畫右上角按鈕(最小化,最大化,關閉)
              formMinX = topRightX;
              g.DrawImage(imgFormMin, topRightX, 0, imgFormMin.Width, imgFormMin.Height);
              if (this.WindowState == FormWindowState.Maximized)
              {
                 imgFormMax = imgFormRestore;
              }
              else
                 imgFormMax = (Image)Resources.skin_btn_max;
              formMaxX = topRightX + imgFormMin.Width;
              g.DrawImage(imgFormMax, topRightX + imgFormMin.Width, 0, imgFormMax.Width, imgFormMax.Height);
              formCloseX = topRightX + imgFormMax.Width + imgFormMin.Width;
              g.DrawImage(imgFormClose, topRightX + imgFormMax.Width + imgFormMin.Width, 0, imgFormClose.Width, imgFormClose.Height);
         }

3.當窗體大小發生變化的時候同樣要重繪,所以重寫OnSizeChanged的事件

         protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            Invalidate();//強制窗體重繪
         }

OK,這樣就完成了皮膚的繪製。接下來我們解決的問題有:

1.如何用滑鼠拖拽改變無邊框的大小

2.如何用滑鼠移動無邊框窗體

3.如何讓繪製的最小化,最大化,關閉按鈕執行操作(響應事件)

對於第1個問題拖拽改變無邊框的大小,可以重寫訊息處理函式WndProc(除了這個我找不到其它好的方法了,如果哪個朋友知道請告訴我一聲)

        const int WM_NCHITTEST = 0x0084;
        const int HTLEFT = 10;
        const int HTRIGHT = 11;
        const int HTTOP = 12;
        const int HTTOPLEFT = 13;
        const int HTTOPRIGHT = 14;
        const int HTBOTTOM = 15;
        const int HTBOTTOMLEFT = 0x10;
        const int HTBOTTOMRIGHT = 17;
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            switch (m.Msg)
            {
                case WM_NCHITTEST:
                    Point vPoint = new Point((int)m.LParam & 0xFFFF,
                        (int)m.LParam >> 16 & 0xFFFF);
                    vPoint = PointToClient(vPoint);
                    if (vPoint.X <= 5)
                        if (vPoint.Y <= 5)
                            m.Result = (IntPtr)HTTOPLEFT;
                        else if (vPoint.Y >= ClientSize.Height - 5)
                            m.Result = (IntPtr)HTBOTTOMLEFT;
                        else m.Result = (IntPtr)HTLEFT;
                    else if (vPoint.X >= ClientSize.Width - 5)
                        if (vPoint.Y <= 5)
                            m.Result = (IntPtr)HTTOPRIGHT;
                        else if (vPoint.Y >= ClientSize.Height - 5)
                            m.Result = (IntPtr)HTBOTTOMRIGHT;
                        else m.Result = (IntPtr)HTRIGHT;
                    else if (vPoint.Y <= 5)
                        m.Result = (IntPtr)HTTOP;
                    else if (vPoint.Y >= ClientSize.Height - 5)
                        m.Result = (IntPtr)HTBOTTOM;
                    break;
            }
        } 

第2個問題滑鼠移動無邊框窗體網上一般有三種方法(詳見:[轉]C#無邊框窗體移動的三種方法

其中有兩種是用WINDOWS訊息機制來完成,但是我發現如果用訊息機制來處理會造成滑鼠的雙擊或者單擊事件不能使用,這一點讓我很糾結,所以就採用了最原始的處理方式。

        private Point mouseOffset; //記錄滑鼠指標的座標
        private bool isMouseDown = false; //記錄滑鼠按鍵是否按下
        private void Main_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                mouseOffset = new Point(-e.X, -e.Y);
                isMouseDown = true;
            }
        }
        private void Main_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                isMouseDown = false;
            }
        }
        private void Main_MouseMove(object sender, MouseEventArgs e)
        {
            if (isMouseDown)
            {
                Point mousePos = Control.MousePosition;
                mousePos.Offset(mouseOffset.X, mouseOffset.Y);
                Location = mousePos;
            }
        }
第3個問題我的思路是處理滑鼠單擊事件(這就是為什麼我放棄了用訊息處理機制來解決問題2),根據滑鼠的座標位置判斷是在點選哪個圖片來觸發對應的事件
        private void Main_MouseClick(object sender, MouseEventArgs e)
        {
            //判斷滑鼠是否點的是右上角按鈕區域
             if (e.X >= formMinX && e.X <= formMinX + Resources.skin_btn_min.Width && e.Y <= imgFormMin.Height)
            {
                this.WindowState = FormWindowState.Minimized;
            }
            if (e.X >= formMaxX && e.X <= formMaxX + Resources.skin_btn_max.Width && e.Y <= imgFormMax.Height)
            {
                if (this.WindowState != FormWindowState.Maximized)
                    this.WindowState = FormWindowState.Maximized;
                else
                    this.WindowState = FormWindowState.Normal;
            }
            if (e.X >= formCloseX && e.X <= formCloseX + Resources.skin_btn_close.Width && e.Y <= imgFormClose.Height)
            {
                Application.Exit();
            }
        }

然後最後的問題就是解決雙擊“標題欄”來最大化或者還原窗體了,我的思路是在繪製窗體的時候,就記錄標題欄的邊界座標值,然後在雙擊事件中,根據滑鼠的座標位置來判斷觸發最大化(還原)事件。

        private void Main_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (e.X >= formTitleMarginLeft && e.X <= formTitleMarginRight && e.Y <= imgTopMiddle.Height)
            {
                if (this.WindowState != FormWindowState.Maximized)
                    this.WindowState = FormWindowState.Maximized;
                else
                    this.WindowState = FormWindowState.Normal;
            }
        }

OK,整個皮膚的製作基本就完成了,乍一看貌似基本功能都實現了,但是如果想實現動態換膚,還是很麻煩的,下篇文章我會給出個動態換膚的解決方案和實現原始碼。

就這樣了,歡迎拍磚。(原始碼下載) 


 

相關文章