EZSkin——原始框架為構建面板功能的應用程式
介紹 這是一個為MFC應用程式構建可換膚ui的框架。這絕不是完全的,目前只支援基於對話方塊的應用程式。但是它是高度可擴充套件的。嗯,一個螢幕截圖說明了一千多行程式碼,其中兩行應該更好。 我把整個事情分成三個主題。 的介面 實現和 助手 原始碼的註釋不是很好。但它是冗長的足以理解和遵循MFC的標準編碼規則。我希望這是一項值得的努力。 重要資訊——執行演示程式的說明 最初,你將只會得到預設值。當您首先執行它時,列表框中的項。關閉應用程式,然後在登錄檔中找到HKEY_CURRENT_USER\Software\EZSuite\EZSkinDemo\ skin鍵,並輸入提取面板的路徑作為Dir鍵的值。 介面 介紹 這是一個簡潔的可擴充套件的架構,以構建Winamp風格的可skinnable應用程式,而不是一個完整的功能庫。協議可以分為四層! 面板經理→→元件→讀者 經理 在示例程式碼中,CEZSkinManager是執行管理器角色的類。它是一個簡單的類,負責一些瑣碎的任務,比如從登錄檔或其他地方載入使用者首選項/設定。有四個簡單的功能可以幫助我們管理面板。 這是一個非平凡類,所有較低的層都是獨立於它的。所以,你可以在任何地方,以任何你想要的方式實現它。你甚至可以讓你的app類展示這個功能。隱藏,複製Code
void LoadSkin(CString strSkin);//Loads the skin by name //For displaying a Skin browser kind of dialog int EnumerateSkins(CStringArray* pstrar); virtual void Save(); virtual void Read();//Registry, Ini or ur own save system
此外,還有兩個助手,他們完全按照自己的建議去做。隱藏,複製Code
//Makes a path out of a name CString GetSkinPath(CString strName,BOOL bValidate =TRUE); CString GetCurrentSkinPath() const;
再舉一個難的例子。“管理者做的最少!”: -) 好吧,有一個不那麼困難的問題,這個物件應該駐留在哪裡。它載入首選項/設定,所以它應該是App類的成員與read &儲存在初始化時呼叫的方法ExitInstance分別。對吧?我只是走了另一條路,從這個和CWinApp一起派生了我的app類。 面板 骨幹!顧名思義,這就是“面板”。CEZSkin表示這個層。 它是一個單元素。它確實是有意義的,因為我無法想象n-skin物件掛在周圍,用它們的點陣圖、字型、圖示和耗用大量資源。什麼不是。此外,它將所有元件結合在一起,並且需要從每個已蒙皮的UI元素中訪問,因此最好使用一個帶有返回JIT例項的靜態函式的單例,而不是使用一個汙染了::的全域性指標。隱藏,複製Code
CEZSkin& CEZSkin::Instance() { static CEZSkin Instance;//The one and only. return Instance; }
元件 這是一個小skinlet。它是特定UI元素或一類UI元素的面板。介面IEZComponent表示這一點。隱藏,複製Code
class IEZSkinComponent : public CObject { DECLARE_SERIAL(IEZSkinComponent) public: virtual BOOL Load(IEZSkinIni* pIni,BOOL bLoadDefaultOnFailure = TRUE) {ASSERT(FALSE); return FALSE;} virtual BOOL LoadDefault() {ASSERT(FALSE); return FALSE;} virtual void Destroy() {ASSERT(FALSE);} virtual BOOL IsLoaded() {ASSERT(FALSE); return FALSE;} virtual BOOL IsDefault() {ASSERT(FALSE); return TRUE;} };
嘿,為什麼它是一個愚蠢的assert總是虛擬函式,而不是一個純VF?現在終於有了一些辛辣的實施。 不使用抽象類來代替這個pseudo的原因是為了在執行時使用類名建立它。看到DECLARE_SERIAL (IEZSkinComponent)。 我想用這種方式編寫程式碼的原因是這樣的。隱藏,複製Code
CEZSkin& ezs = CEZSkin::Instance(); ezs.AddComponent(_T("CEZDialogSkin")); //class CEZDialogSkin:public IEZSkinComponent
雖然使用RUNTIME_CLASS的方式完全有可能做到這一點,但我只是認為如果我可以在INI檔案/登錄檔中將類名作為面板定義的一部分,這將是很酷的…… CEZSkin類使用CTypedPtrMap儲存元件。隱藏,複製Code
CTypedPtrMap<CMapStringToOb,CString,IEZSkinComponent*> m_mapComponents;
介面的所有函式都將由CEZSkin呼叫,它在CEZSkin::GetComponent期間對元件進行JIT例項化。程式碼讀起來是這樣的:複製Code
IEZSkinComponent* pComponent = NULL; if(!m_mapComponents.Lookup(strComponent,pComponent)) return NULL;//Not registered if(!pComponent)//Not yet created -do JIT Instantiation { pComponent = (IEZSkinComponent*)CEZRuntimeClass::CreateObject(strComponent); ASSERT(pComponent); m_mapComponents.SetAt(strComponent,pComponent); } if(m_bDefault)//Is the default skin loaded { if(!pComponent->IsDefault()) //Make the component default { pComponent->Destroy(); pComponent->LoadDefault(); } } else if(!pComponent->IsLoaded())// new? pComponent->Load(m_pIni); return pComponent;//Ok have it!
讀者 這也是一個偽抽象類,用於提供某些簡單的*從面板定義讀取*函式。隱藏,複製Code
class IEZSkinIni :public CObject { DECLARE_SERIAL(IEZSkinIni) public: virtual BOOL GetValue(CString strSection,CString strKey,COLORREF& clrValue) {ASSERT(FALSE); return FALSE;}//Read Triplet Value virtual BOOL GetValue(CString strSection, CString strKey, int& nValue) {ASSERT(FALSE); return FALSE;}//Read Integer Value virtual BOOL GetValue(CString strSection,CString strKey, CString& strValue) {ASSERT(FALSE); return FALSE;}//Read String Value virtual BOOL GetValue(CString strSection, CString strKey, CPoint& ptValue) {ASSERT(FALSE); return FALSE;}//Read Twin Value virtual BOOL Read(CString strCurrentSkinPath) {ASSERT(FALSE);return FALSE;}//Init };
工作 步驟1:管理器在讀取功能期間載入設定。 在InitInstance期間呼叫CEZSkinManager::Read()。 第二步:經理用程式碼向讀者介紹面板:複製Code
CEZSkin::Instance().SetIni(_T("CEZSkinIni")); //class CEZSkinIni:public IEZSkinIni
步驟3:管理器載入當前面板或設定面板為預設。隱藏,複製Code
void CEZSkinManager::Read() { m_strSkins = AfxGetApp()->GetProfileString(HKEY_SKINS,HKEY_DIR,_T("")); CEZSkin::Instance().SetIni(_T("CEZSkinIni")); CFileFind ff; BOOL bLoaded = ff.FindFile(m_strSkins); if(bLoaded) { CEZSkin::Instance().SetSkinsDir(m_strSkins); m_strCurrentSkin = AfxGetApp()->GetProfileString(HKEY_SKINS,HKEY_SKIN); ff.Close(); } LoadSkin(m_strCurrentSkin); } void CEZSkinManager::LoadSkin(CString strSkin) { CFileFind ff; BOOL bLoaded = ff.FindFile(GetSkinPath(strSkin)); if(bLoaded) { m_strCurrentSkin = strSkin; bLoaded = CEZSkin::Instance().LoadSkin(m_strCurrentSkin); } ff.Close(); }
步驟4:已蒙皮的物件與CEZSkin通訊,以初始化和獲取元件。 現在讓我們看看與上述任務相關的一些CEZSkin函式。隱藏,複製Code
virtual void SetIni(CString strClassName); virtual void AddComponent(CString strClassName); virtual IEZSkinComponent* GetComponent(CString strComponent); virtual void LoadDefault(); virtual BOOL LoadSkin(CString strSkin);
第一個函式由管理器按上述方式呼叫。已蒙皮的UI元素(Window)呼叫下面兩個函式,如下所示。隱藏,複製Code
void CSkinnedWindow::Init() { //class CMySkin:public IEZSkinComponent CEZSkin::Instance().AddComponent(_T("CMySkin")); .... } void CSkinnedWindow::OnPaint() { CPaintDC dc(this); CEZSkin& skin = CEZSkin::Instance(); CMySkin* pSkin = skin.GetComponent(_T("CMySkin")); //////Do Painting by getting the attributes of the component //say.. COLORREF clrBack = pSkin->GetBackgroundColor(); dc.FillSolidRect(CEZClientRect(this),clrBack); ..... }
步驟5:最後,管理器將當前設定寫入儲存。 在ExitInstance期間呼叫CEZSkinManager::Save。隱藏,複製Code
AfxGetApp()->WriteProfileString(HKEY_SKINS,HKEY_DIR,m_strSkins); AfxGetApp()->WriteProfileString(HKEY_SKINS,HKEY_SKIN,m_strCurrentSkin);
實現 介紹 在演示中,我實現了EZSkin介面來建立一個帶面板的對話方塊。嚴格地說,這裡應該討論CEZSkinManager,但是如果沒有這個介面,我就很難解釋這個介面s類。 下面的類構成了這個實現的基礎。 CEZSkinIni 這提供了IEZSkinIni的預設實現。它將閱讀器層實現為INI檔案。我使用了Iuri Apollonio的CIni類,並修改了它以適應框架。 它使用一個CStdioFile來讀取INI檔案,並將每一行儲存在一個CStringArray中,然後解析每一行以獲得所需的值。我用了a;作為註釋啟動器,作為值分隔符。 它使用AfxExtractSubString來解析逗號分隔的值。 樣本面板INI; 隱藏,複製程式碼(面板) Name =黑色; 作者= V。拉克希米納史木汗; 註釋=黑駿馬; (主要) Bmp = back.bmp; 畫=瓷磚; (標題) Bmp = Caption.bmp; 畫=瓷磚; TextFont = ARial Black,B,25; 輸入TextColor = 200200200; BtnsNormal = btns.bmp; BtnsHilight = btnsh.bmp; TransColor = 192224, 64; BtnPos = 7, 27歲,47歲; BtnWidth = 20; CEZGenericSkin 這提供了IEZSkinComponent的預設實現,並且仍然是偽抽象,擁有一些assert always函式。 這個類為需要以下面板屬性的視窗提供了介面: 背景點陣圖, 背景顏色, 文字顏色, 文字字型 這個類使用以下成員儲存資料:複製Code
BOOL m_bDefault; BOOL m_bLoaded; CEZDib m_Dib;//See the helpers section CFont m_font; COLORREF m_clrTxt; COLORREF m_clrBk;
要使用這個類,我們應該從這個派生並覆蓋下面的函式: 隱藏,複製程式碼//{偽純虛擬函式 因為虛擬字串GetSection () {斷言(假),返回_T (" ");} 虛擬空間LoadDefaultBmp(){斷言(假);} 虛擬空間LoadDefaultFont(){斷言(假);} 虛擬空間LoadDefaultBackColor(){斷言(假);} 虛擬空間LoadDefaultTextColor(){斷言(假);} / /} 它為IEZSkinComponent介面公開的所有函式提供了預設實現。派生類必須重寫上述函式的原因是:複製Code
BOOL CEZGenericSkin::LoadDefault() { LoadDefaultBmp(); LoadDefaultBackColor(); LoadDefaultTextColor(); LoadDefaultFont(); m_bDefault = TRUE; m_bLoaded = TRUE; return TRUE; }
它也有一個很酷的助手,載入字型到m_font成員給定的字型名稱樣式和寬度。隱藏,複製Code
BOOL CEZGenericSkin::LoadFont(CString strFont, CString strStyle, int nHeight)
如使用方法:隱藏,複製Code
LoadFont(_T("Times New Roman"),_T("BI"),20);
要了解使用CEZGenericSkin實現IEZSkinComponent有多容易,請檢視CEZDialogSkin的定義。 CEZDialogSkinHide,複製Code
IMPLEMENT_SERIAL(CEZDialogSkin,IEZSkinComponent,(UINT)-1) CString CEZDialogSkin::GetSection() {return _T("Main");} void CEZDialogSkin::LoadDefaultBackColor() {m_clrBk= RGB(0,0,255);} void CEZDialogSkin::LoadDefaultBmp() { m_Dib.Load(IDB_BACK); m_Dib.SetType(CEZDib::BMP_TILE); } void CEZDialogSkin::LoadDefaultFont() {LoadFont(_T("Times New Roman"),_T("B"),20);} void CEZDialogSkin::LoadDefaultTextColor() {m_clrTxt= RGB(255,0,0);}
CEZCaptionSkin 它沒有CEZDialogSkin那麼小。 它有額外的成員為標題按鈕- Rects,突出顯示&;正常點陣圖和透明顏色的點陣圖。隱藏,複製Code
CEZDib m_DibBtnNormal; CEZDib m_DibBtnHilight; CRect m_rectBtns[3]; COLORREF m_clrTransparent;
助手 介紹 這裡我們只看一下在演示中使用的各種helper類。 矩形 這些是來自CRect的類,它們封裝了CWnd::GetxxxRect函式和CDC::GetClipBox,這樣就可以編寫如下程式碼:Hide複製Code
CPaintDC dc(this); //CEZDib dib; dib.Draw(&dc,CEZClientRect(this)); //instead of //CRect rect; //GetClientRect(&rect); //dib.Draw(&dc,rect);
DCs CEZMemDC,是帶有附加bCopyOnDestruct引數的CMemDC,該引數阻止DC將其內容傳輸到目標。CEZBmpDC選擇一個位圖或它的一部分到一個相容的DC,並可以用作一個刮板。 最酷的一個是CEZMonoDC,它接收一個DC並建立一個帶有源DC的單色點陣圖的DC。隱藏,複製Code
CEZMonoDC(CDC* pDCSrc,LPRECT pRect=NULL):CDC() { ASSERT(pDCSrc != NULL); CreateCompatibleDC(pDCSrc); m_rect = pRect?*pRect:CEZClipRect(pDCSrc); m_bitmap.CreateBitmap(m_rect.Width(),m_rect.Height(),1,1,NULL); pDCSrc->SetBkColor(pDCSrc->GetPixel( 0, 0 ) ) ; m_pOldBitmap =(CBitmap*)SelectObject(&m_bitmap); SetWindowOrg(m_rect.left, m_rect.top); }
CEZDib 這是建立在Jorg Konig的CDIBitmap類。我已經包括了我發現的其他DIB類的某些好東西。我對CDIBitmap所做的重要改變是,按照Paul DiLascia在期刊97中建議的,使它可以作為CBitmap通過。我還添加了四個繪圖函式來繪製一個普通的點陣圖,拉伸的點陣圖,平鋪的點陣圖和一個透明的繪製。隱藏,收縮,複製Code
BOOL CEZDib::DrawTransparent(CDC* pDC,COLORREF clrTrans, const CRect& rcDest,const CRect& rcSrc) const { CRect rcDC(rcDest),rcBmp(rcSrc); if(rcDC.IsRectNull()) rcDC =CEZClipRect(pDC); if(rcBmp.IsRectNull()) rcBmp = CRect(0,0,GetWidth(),GetHeight()); CEZMemDC memDC(pDC,&rcDC,TRUE,TRUE ),imageDC(pDC,&rcDC,FALSE); CEZMonoDC backDC(pDC,&rcDC),maskDC(pDC,&rcDC); DrawNormal(&imageDC,rcDC,rcBmp); COLORREF clrImageOld = imageDC.SetBkColor(clrTrans); maskDC.BitBlt(rcDC.left,rcDC.top,rcDC.Width(), rcDC.Height(),&imageDC,rcDC.left,rcDC.top,SRCCOPY); imageDC.SetBkColor(clrImageOld); backDC.BitBlt(rcDC.left,rcDC.top,rcDC.Width(), rcDC.Height(),&maskDC,rcDC.left,rcDC.top,NOTSRCCOPY); memDC.BitBlt(rcDC.left,rcDC.top,rcDC.Width(),rcDC.Height(), &maskDC,rcDC.left,rcDC.top,SRCAND); imageDC.BitBlt(rcDC.left,rcDC.top,rcDC.Width(), rcDC.Height(),&backDC,rcDC.left,rcDC.top,SRCAND); memDC.BitBlt(rcDC.left,rcDC.top,rcDC.Width(),rcDC.Height(), &imageDC,rcDC.left,rcDC.top,SRCPAINT); return TRUE; }
CEZWindowNC 封裝CWnd的非客戶區域函式的類。隱藏,複製Code
BOOL HasBorder(); BOOL HasSysMenu(); BOOL HasCaption(); CRect GetCaptionRect(); CRect GetLeftBorderRect(); CRect GetRightBorderRect(); CRect GetTopBorderRect(); CRect GetBottomBorderRect();
CEZDialog 這是樣例蒙皮UI元素。隱藏,複製Code
BOOL CEZDialog::OnEraseBkgnd(CDC* pDC) { CEZSkin& ezs = CEZSkin::Instance(); CEZDialogSkin* pSkin = DYNAMIC_DOWNCAST(CEZDialogSkin, ezs.GetComponent(_T("CEZDialogSkin"))); ASSERT(pSkin); const CEZDib& bmp = pSkin->GetBackgroundBitmap(); CEZClientRect rcClient(this); bmp.Draw(pDC,rcClient); return TRUE; } void CEZDialog::Init() { CEZSkin& ezs = CEZSkin::Instance(); ezs.AddComponent(_T("CEZDialogSkin")); VERIFY(m_brushHollow.CreateStockObject(HOLLOW_BRUSH)); }
哇,這段程式碼對於帶面板點陣圖背景的對話方塊來說是不是太小了? CEZCaption 我已經基於Dave Lorde的CCaption程式碼建立了這個類。我修改了原始程式碼以使用CEZSkin,還添加了用於繪製和處理標題按鈕的程式碼。它廣泛使用CEZDib和CEZWindowNC。我還做了一些修改,使它在對話方塊中工作。 儘管標題可以很好地描繪和處理按鈕,但我在滑鼠跟蹤方面遇到了一些問題。我以減少功能為代價簡化了對類的跟蹤。如果有人寫一篇關於如何做到這一點的文章,那就太好了。 更新 2001年1月30日 固定靜態庫崩潰。 標題中滑鼠跟蹤的不一致。 新增一個CEZBorder類來繪製邊框。 本文轉載於:http://www.diyabc.com/frontweb/news12297.html