如何使用MFC編寫自定義UI界面【附高仿QQ 2014登陸界面範例程序】

地址: http://blog.csdn.net/hujkay
作者:Jekkay Hu([email protected])
關鍵詞:MFC, 編寫異行窗體,自定義UI控件,VC++,異形控件,高仿QQ登陸界面, 截取QQ密碼,QQ釣魚
時間: 2014/4/12


1. 概述

    在開發客戶端程序的時候,稍微有點正常審美觀的人都會對微軟的默認UI界面表示深惡痛絕,連最簡單的默認程序ICON都用那麼低的像素的ICON,質量差的令人髮指啊。所以,但凡好一點的軟件,都會去找一些界面庫,或者乾脆自己寫個界面庫來美化一下用戶交互界面,免得用戶吐血暴斃。
    網絡上有許多界面庫,其中不少是免費的,這裏推薦一款我經常用的界面庫:Skinsharp。這個界面庫,動態編譯版本是免費的,靜態編譯版本是收費的,當然網上肯定有不少破解版本,但是爲了中國軟件的前途,大家還是稍微掏點錢買正版吧,有點扯遠了,拽回來!使用第三方提供的界面庫固然能美化程序界面,但有的程序需要一些比較特殊的異形界面,那麼這些界面庫就很難滿足需求了,這樣我們就必須自己去編寫UI插件了。


2. 編寫自定義UI界面

     編寫自定義UI插件,涉及到三方面:形狀,界面繪製和事件響應。下面分別對着三個方面進行詳細闡述一下。

    2.1 形狀

        我們先隨便找個軟件,比如金山毒霸吧,如下:
      
      界面是不是很酷,相比Windows自身的界面是不是感覺是天上天下之別。而我們看到這些好看的界面形狀都不是方方正正,全是不規則的形狀,所以我們第一步要做的就是設定窗口的形狀。微軟提供了一個API函數,可以設定窗體的形狀:
int SetWindowRgn(HWND hWnd, HRGN hRgn, BOOL bRedraw);
 其中HRGN是一個用於表示形狀的對象,我們可以用另外一個封裝好的類CRGN來進行操作。該CRGN提供了許多非常友好的接口函數,比如添加一塊區域,減少一塊區域等,具體的可以查看一下官網的MSDN,下面列舉一些常用的操作。    
    a. 創建一個矩形的區域
          CRgn	rgn;
          rgn.CreateRectRgn(0, 0, nWidth,nHeight);
    b. 在當前的區域中添加一塊的區域
        rcXor.SetRect(0, 0, 1, 2);
        rgn_xor.CreateRectRgn(0, y, border_offset[y], y + 1);
        rgn.CombineRgn(&rgn, &rgn_xor, RGN_OR);
    c. 在當前的區域中只取共同區域
        rcXor.SetRect(0, 0, 1, 2);
        rgn_xor.CreateRectRgn(0, y, border_offset[y], y + 1);
        rgn.CombineRgn(&rgn, &rgn_xor, RGN_AND);
    d. 在當前的區域中異或一塊的區域
        rcXor.SetRect(0, 0, 1, 2);
        rgn_xor.CreateRectRgn(0, y, border_offset[y], y + 1);
        rgn.CombineRgn(&rgn, &rgn_xor, RGN_XOR);
      
   區域創建了好了之後,就設定一下窗體的區域:      
SetWindowRgn((HRGN)rgn, TRUE);

下面一段完整的代碼,供大家參考一下:  

  CDC* pDC = GetDC();


 CRect	rc;
 GetWindowRect(rc);
 rc.OffsetRect(-rc.left, -rc.top);


 CRgn	rgn;
 rgn.CreateRectRgn(0, 0, rc.Width(), rc.Height());
 CRgn	rgn_xor;
 CRect	rcXor;
 
 for (int y = 0; y < nSize; ++y)
 {
  rcXor.SetRect(0, y, border_offset[y], y + 1);
  rgn_xor.CreateRectRgn(0, y, border_offset[y], y + 1);
  rgn.CombineRgn(&rgn, &rgn_xor, RGN_XOR);
  rgn_xor.DeleteObject();
 }


 for (int y = 0; y < nSize; ++y)
 {
  rcXor.SetRect(rc.right - border_offset[y], y, rc.right, y + 1);
  rgn_xor.CreateRectRgn(rc.right - border_offset[y], y, rc.right, y + 1);
  rgn.CombineRgn(&rgn, &rgn_xor, RGN_XOR);
  rgn_xor.DeleteObject();
 }


 for (int y = 0; y < nSize; ++y)
 {
  rcXor.SetRect(0, rc.bottom - y - 1, border_offset[y], rc.bottom - y);
  rgn_xor.CreateRectRgn(0, rc.bottom - y - 1, border_offset[y], rc.bottom - y);
  rgn.CombineRgn(&rgn, &rgn_xor, RGN_XOR);
  rgn_xor.DeleteObject();
 }


 for (int y = 0; y < nSize; ++y)
 {
  rcXor.SetRect(rc.right - border_offset[y], rc.bottom - y - 1, rc.right, rc.bottom - y);
  rgn_xor.CreateRectRgn(rc.right - border_offset[y], rc.bottom - y - 1, rc.right,rc.bottom - y);
  rgn.CombineRgn(&rgn, &rgn_xor, RGN_XOR);
  rgn_xor.DeleteObject();
 }


 SetWindowRgn((HRGN)rgn, TRUE);
 m_Rgn.DeleteObject();
 m_Rgn.Attach(rgn.Detach());
 ReleaseDC(pDC);

   2.2 界面繪製

        界面繪製是繁瑣的工作,推薦使用Gdiplus庫來支持一下,在stdafx.h加入如下的代碼,將其包含進來即可:
#include <gdiplus.h> 
using namespace Gdiplus; 

#pragma comment(lib, "gdiplus.lib")
    自定義繪製界面需要重載一下主要下面的幾個函數:
     
繪製客戶區域的函數:ON_WM_PAINT()
繪製非客戶區域的函數:ON_WM_NCPAINT()
背景擦除函數:ON_WM_ERASEBKGND()
限定窗體大小的函數:ON_WM_GETMINMAXINFO()
   一般來說,自繪窗體控件最主要的就是ON_WM_PAINT()方法,大部分時間都耗在就是在裏面,這個活比較細緻,像素一個一個地調,沒有耐心的人估計會砸電腦。
    在這裏有技巧需要提一下,如果繪製的界面插件老一閃一閃的,可以先在內存裏面創建一塊畫板,在裏面畫好了之後然後在顯示到界面上,可參考如下的代碼:
     
   CRect	rcClient;
   GetClientRect(&rcClient);


  CPaintDC	dc(this);
  CDC MemDC;
  MemDC.CreateCompatibleDC(&dc);
  CBitmap memBmp;
  memBmp.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height());
  CBitmap *pOldmap = MemDC.SelectObject(&memBmp);

  DrawImageStyle(MemDC, rcClient);

  dc.BitBlt(0, 0, rcClient.Width(), rcClient.Height(), &MemDC, 0, 0, SRCCOPY);

  MemDC.SelectObject(pOldmap);
  MemDC.DeleteDC();

  2.3 事件

     事件是指根據用戶的交互動作而觸發某些動作,主要是鼠標和鍵盤兩種。我這裏主要介紹下鼠標事件,鼠標事件可分爲:移動,按下,彈起,移入,移出,單擊,雙擊和滾動。在創建自定義創建窗口的時候,千萬不要重載單擊和雙擊的事件,這是非常不好的習慣,而且容易引發各種問題,最好的辦法是自己在按下按鈕的時候模擬單擊或者雙擊的信息,所以一般來說,重載下面幾個方法即可:
 ON_WM_MOUSEMOVE()
 ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
 ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover)
 ON_WM_LBUTTONDOWN()
 ON_WM_LBUTTONUP()
 ON_WM_LBUTTONDBLCLK()
     事件和窗體繪製有着緊密的關係,比如一個按鈕,用戶鼠標移入時是一種狀態,按下時又是一種狀態,彈起式又是一狀態。因此,在繪製窗體時,要根據當前的用於的交互狀態繪製不同的形態,非常考驗一個人的變成能力。

3. 小結

    繪製自定義UI插件是個細活,必須如加工藝術品一樣有耐心,所以我但凡見到做UI插件的同仁,都心生佩服。週末閒來無事,根據其他高手的一些代碼,高仿了一下QQ的登陸界面,很酷吧,呵呵。




    高仿的QQ 2014登陸程序的下載地址:http://download.csdn.net/user/hujkay

    別幹壞事啊~~~


胡楊, Jekkay Hu

2014/4/12










發佈了52 篇原創文章 · 獲贊 16 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章