1. 程式人生 > 實用技巧 >EZSkin——原始框架為構建面板功能的應用程式

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