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

浙公网安备 33010602011771号