EZSkin——原始框架为构建皮肤功能的应用程序

介绍 这是一个为MFC应用程序构建可换肤ui的框架。这绝不是完全的,目前只支持基于对话框的应用程序。但是它是高度可扩展的。嗯,一个屏幕截图说明了一千多行代码,其中两行应该更好。 我把整个事情分成三个主题。 的接口 实现和 助手 源代码的注释不是很好。但它是冗长的足以理解和遵循MFC的标准编码规则。我希望这是一项值得的努力。 重要信息——运行演示程序的说明 最初,你将只会得到默认值。当您首先运行它时,列表框中的项。关闭应用程序,然后在注册表中找到HKEY_CURRENT_USERSoftwareEZSuiteEZSkinDemo 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

原文地址:https://www.cnblogs.com/Dincat/p/13473899.html