C#中窗體Form的美化


     VS足夠強大,強大到只需動動鼠標就可以寫出個基本的界面出來,但是其自帶的控件都是千篇一律的樣式,對於追求完美的我而言,實在是忍不下去了,只好自己親自動手對其進行改造----繼承已有的控件,再對其相關的消息或事件進行處理。窗體Form作為界面的主體部分,必先對其進行美化,在窗體自繪的過程中,需要使用到GDI+,如若對GDI+不是很了解的同學可移步我的CSDN博客或者搜索下相關的介紹。

這篇文章將要介紹到的內容:

 

實現效果演示:

 

 

代碼下載

 

一:窗體圓角的處理

    對於無邊框窗體圓角矩形的處理,我這里采用的是使用API函數CreateRoundRectRgn,相比於自己用GDI+寫的處理圓角的函數,效果要稍微好點,至少線條在圓角處過渡的比較平滑,為了便於復用,我把其封裝到窗體自繪輔助類RenderHlper的SetFormRoundRectRgn函數中:

View Code
        /// <summary>
        /// 設置窗體的圓角矩形
        /// </summary>
        /// <param name="form">需要設置的窗體</param>
        /// <param name="rgnRadius">圓角矩形的半徑</param>
        public static void SetFormRoundRectRgn(Form form, int rgnRadius)
        {
            int hRgn = 0;
            hRgn = Win32.CreateRoundRectRgn(0, 0, form.Width + 1, form.Height + 1, rgnRadius, rgnRadius);
            Win32.SetWindowRgn(form.Handle, hRgn, true);
            Win32.DeleteObject(hRgn);
        }

 

 此處需要把所需要的API函數引用到類Win32中,引用的時候注意添加 System.Runtime.InteropServices 命名空間:

View Code
        [DllImport("gdi32.dll")]
        public static extern int CreateRoundRectRgn(int x1, int y1, int x2, int y2, int x3, int y3);

        [DllImport("user32.dll")]
        public static extern int SetWindowRgn(IntPtr hwnd, int hRgn, Boolean bRedraw);

        [DllImport("gdi32.dll", EntryPoint = "DeleteObject", CharSet = CharSet.Ansi)]
        public static extern int DeleteObject(int hObject);

 

重寫窗體的OnSizeChanged事件,並在此事件中調用SetFormRoundRectRgn,此處的Radius參數為定義的窗體圓角半徑屬性:

View Code
       protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            RenderHelper.SetFormRoundRectRgn(this, Radius);
        }

 

二:無邊框窗體大小的改變與移動

      當把窗體的FormBorderStyle屬性調整為FormBorderStyle.None時,此時,窗體的大小改變不了,同時窗體不能移動。要想實現無邊框窗體大小的改變與移動,可采用如下方法:

(1)重寫窗體的過程WndProc:

 主要是對WM_NCHITTEST消息進行處理,根據事件的發生位置來進行不同方向箭頭的調整,窗體大小改變與移動的函數:

View Code
         //調整窗體大小
        private void WmNcHitTest(ref Message m)
        {
            int wparam = m.LParam.ToInt32();
            Point mouseLocation = new Point(RenderHelper.LOWORD(wparam),RenderHelper.HIWORD(wparam));
            mouseLocation = PointToClient(mouseLocation);

            if (WindowState != FormWindowState.Maximized )
            {
                if (CanResize == true)
                {
                    if (mouseLocation.X < 5 && mouseLocation.Y < 5)
                    {
                        m.Result = new IntPtr(Win32.HTTOPLEFT);
                        return;
                    }

                    if (mouseLocation.X > Width - 5 && mouseLocation.Y < 5)
                    {
                        m.Result = new IntPtr(Win32.HTTOPRIGHT);
                        return;
                    }

                    if (mouseLocation.X < 5 && mouseLocation.Y > Height - 5)
                    {
                        m.Result = new IntPtr(Win32.HTBOTTOMLEFT);
                        return;
                    }

                    if (mouseLocation.X > Width - 5 && mouseLocation.Y > Height - 5)
                    {
                        m.Result = new IntPtr(Win32.HTBOTTOMRIGHT);
                        return;
                    }

                    if (mouseLocation.Y < 3)
                    {
                        m.Result = new IntPtr(Win32.HTTOP);
                        return;
                    }

                    if (mouseLocation.Y > Height - 3)
                    {
                        m.Result = new IntPtr(Win32.HTBOTTOM);
                        return;
                    }

                    if (mouseLocation.X < 3)
                    {
                        m.Result = new IntPtr(Win32.HTLEFT);
                        return;
                    }

                    if (mouseLocation.X > Width - 3)
                    {
                        m.Result = new IntPtr(Win32.HTRIGHT);
                        return;
                    }
                }
            }
            m.Result = new IntPtr(Win32.HTCAPTION);
        }

 重寫窗體過程:

View Code
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case Win32.WM_NCHITTEST:
                    WmNcHitTest(ref m);
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

 (2)對於僅僅只想實現窗體的移動而不改變窗體的大小,可以重寫OnMouseDown事件中發送HTCAPTION消息來實現無邊框窗體的移動,具體的實現代碼如下:

View Code
        /// <summary>
        /// 移動窗體
      /// </summary>
        public static void MoveWindow(Form form)
        {
            Win32.ReleaseCapture();
            Win32.SendMessage(form.Handle, Win32.WM_NCLBUTTONDOWN, Win32.HTCAPTION, 0);
        }

調用窗體移動函數:

View Code
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            if (e.Button == MouseButtons.Left)
            {
                Render.MoveWindow(this);
            }
        }

 

三:窗體邊框的繪制與邊框陰影的實現

 邊框的繪制:邊框的繪制使用用PS制作好的圖片來進行貼圖操作,在貼圖的過程中使用九宮圖貼圖方法,保證此邊框圖片能滿足任何大小的窗體。

 窗體邊框的實現:此部分主要涉及到對CS_DropSHADOW的了解,只要在窗口的ClassStyle添加此樣式即可,關鍵代碼如下:

View Code
       protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                if (!DesignMode)
                {
                    cp.ClassStyle |= (int)  ClassStyle.CS_DropSHADOW;             
             }
                return cp;
            }
        }

 

四:系統按鈕的繪制與事件處理

 此部分是所有部分中最難的部分,在此部分中既要實現系統按鈕不同狀態下(鼠標操作改變按鈕狀態)的繪制,還有對其相應的事件進行處理,所以我創建了2個類:SystemButton類和SystemButtonManager類。SystemButton類表示系統按鈕類,而SystemButtonManager的功能是對系統按鈕各個狀態與事件的管理。類SystemButtonManager的類圖如下所示:

 

 屬性、方法、事件的功能介紹如下表:

 

     對於類SystemButtonManager,主要是管理三個系統按鈕的狀態與事件,其他特別要介紹的是定義的系統按鈕狀態索引器,根據提供的索引來獲取或者設置按鈕的當前狀態。

 

五:窗體標題欄的繪制

     標題欄的繪制主要涉及到窗體Icon圖標的繪制與窗體標題的繪制,繪制的過程中定義了2個屬性:IconRect,TextRect,分別對應着圖標的坐標矩形與窗體標題的坐標矩形,圖標與標題的繪制在這個矩形中繪制,需要提醒的時,圖標的繪制需要注意到是否窗體的ShowIcon屬性。

 

六:解決窗體閃爍的問題

     在窗體的自繪過程中,當調整窗體的大小等操作而觸發窗體重繪,此時窗體的閃爍現象更為明顯,相信大部分同學在自定義控件的過程中或多或多的出現這種問題,對於此問題,每個人又不同的解決方法,這里我提供四種解決方案類解決窗體的閃爍:

方法一:第一個容易想到的是采用雙緩沖機制來進行圖形的繪制,對雙緩沖不了解的同學可以參考下我的另外一篇文章《淺談C#中是雙緩沖》

方法二:當將CS_DropSHADOW樣式添加到窗體的ClassStyle樣式中可以明顯的解決窗體閃爍的現象。代碼見本文的第三部分--窗體邊框的繪制與邊框陰影的實現。

方法三:當窗體進行重繪時,對WM_ERASEBKGND消息進行忽略,應用代碼如下:

View Code
       protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case Win32.WM_ERASEBKGND:
                    m.Result = IntPtr.Zero;
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

方法四:將WS_CLIPCHILDREN樣式添加到窗體的ExStyle樣式中,此方法對解決窗體掛有很多子控件時窗體閃爍的現象特別明顯,應用代碼如下:

View Code
       protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                if (!DesignMode)
                {
                    cp.ExStyle |= (int)WindowStyle.WS_CLIPCHILDREN;                 
                }
                return cp;

            }
        }

 

 

注:本博客所有文章均為作者個人原創 ,如若轉載,請標記文章出處:http://www.cnblogs.com/Keep-Silence-/   苦笑。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM