YOU CAN CODE!

 

With The Case Of UCanCode.net  Release The Power OF  Visual C++ !   HomeProducts | PurchaseSupport | Downloads  
Download Evaluation
Pricing & Purchase?
E-XD++Visual C++/ MFC Products
Overview
Features Tour 
Electronic Form Solution
Visualization & HMI Solution
Power system HMI Solution
CAD Drawing and Printing Solution

Bar code labeling Solution
Workflow Solution

Coal industry HMI Solution
Instrumentation Gauge Solution

Report Printing Solution
Graphical modeling Solution
GIS mapping solution

Visio graphics solution
Industrial control SCADA &HMI Solution
BPM business process Solution

Industrial monitoring Solution
Flowchart and diagramming Solution
Organization Diagram Solution

Graphic editor Source Code
UML drawing editor Source Code
Map Diagramming Solution

Architectural Graphic Drawing Solution
Request Evaluation
Purchase
ActiveX COM Products
Overview
Download
Purchase
Technical Support
  General Q & A
Discussion Board
Contact Us

Links

Get Ready to Unleash the Power of UCanCode .NET

 


UCanCode Software focuses on general application software development. We provide complete solution for developers. No matter you want to develop a simple database workflow application, or an large flow/diagram based system, our product will provide a complete solution for you. Our product had been used by hundreds of top companies around the world!

"100% source code provided! Free you from not daring to use components because of unable to master the key technology of components!"


VC++ Example: EZSkin - A few skin library with source codes

By Lakshmi Vyas. 

A mini library to build
Bitmap based skinnable apps
 

Introduction

This is a framework for building Skinnable UIs for MFC apps. This is by no means complete and presently only supports Dialog based apps. But it is highly extensible. Well, a Screen Capture speaks more than a thousand lines of code and two of them should do better.

EZSkin1.gif (44507 bytes)

EZSkin2.gif (34070 bytes)

I have categorized the whole thing into three subjects.

The source code is not that well commented. But it is wordy enough to understand and follows standard coding rules for MFC. I hope this has been an effort worth making.

Important - Instructions for running the demo

Initially you will only get the <default> item in the list box when you run it first. Close the application, then find the HKEY_CURRENT_USER\Software\EZSuite\EZSkinDemo\Skins key in your registry and enter the path where you have extracted the skins as the value for the Dir key.

Interface

Introduction

This is just a neat & extensible architecture to build Winamp style skinnable apps rather than a full featured library. The protocol can be classed into four layers!

Manager->Skin->Component->Reader

Manager

In the sample code, CEZSkinManager is the class which performs the role of the manager. It is a simple class which is responsible for certain trivial tucancode.nets like loading the user preferences/settings from the registry or wherever you have them. There are four simple functions that basically help us manage the skins.

This is a non trivial class and all the lower layers are independent of this. So, you can implement it wherever and in whatever way you want. You can even have your app class exhibit this functionality.

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

And additionally there are two helpers which do exactly what they suggest.

// Makes a path out of a name
CString GetSkinPath(CString strName,BOOL bValidate =TRUE);
CString GetCurrentSkinPath() const;

One more example that says it hard & clear "Managers do the least!" :-)

Well, there is a not-so-difficult question of where this object should reside. It loads the preferences/settings so it should be a member of the App class with the read & save methods called during InitInstance & ExitInstance respectively. Right? I just took another route and derived my app class from this along with CWinApp.

Skin

The Backbone! This is, as the name suggests *The Skin*. CEZSkin represents this layer.

It is a Singleton. It sure does make sense as I cannot imagine n-skin objects hanging around taking a heavy toll on the resources with their bitmaps, fonts, icons & what not. Moreover it holds together all components and needs to be accessible from every skinned UI element and hence it is better to have a singleton with a static function returning its JIT instance rather than a global pointer polluting the ::.

CEZSkin& CEZSkin::Instance()
{
    static CEZSkin  Instance;//The one and only.
    return Instance;
}

Component

This is a skinlet. It is the skin of a particular UI element or a class of UI elements. The interface IEZComponent represents this.

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;}
};

Hey, why is it a stupid assert always virtual function rather than a pure VF? Well now at last comes some spicy implementation.

The reason for not having an abstract class in place of this pseudo is to make it creatable at runtime using the class name. See DECLARE_SERIAL(IEZSkinComponent).

The reason why I want it this way is to write code like this.

CEZSkin& ezs = CEZSkin::Instance();
ezs.AddComponent(_T("CEZDialogSkin"));
//class CEZDialogSkin:public IEZSkinComponent

Though it is perfectly possible to do this using the RUNTIME_CLASS way, I just thought it would be cool if I could have the class name as a part of the skin definition in an INI file/registry etc...

The CEZSkin class holds the components by using a CTypedPtrMap.

CTypedPtrMap<CMapStringToOb,CString,IEZSkinComponent*> m_mapComponents;

All the functions of the interface would be called by CEZSkin which does JIT instantiation of the component during CEZSkin::GetComponent. The code reads like this,

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!

Reader

This is again a pseudo abstract class for providing certain trivial *read from skin definition* functions.

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
};

Working

Step 1: Manager loads the settings during its read function.

Call CEZSkinManager::Read() during InitInstance.

Step 2: Manager introduces the Reader to the Skin with code like:

CEZSkin::Instance().SetIni(_T("CEZSkinIni"));
//class CEZSkinIni:public IEZSkinIni

Step 3: Manager loads the current skin or sets the skin to default.

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();
}

Step 4: The Skinned objects communicate with the CEZSkin to initialize and get the components.

Now let us see some CEZSkin functions that relate to the above tucancode.net.

virtual void SetIni(CString strClassName);
virtual void AddComponent(CString strClassName);
virtual IEZSkinComponent* GetComponent(CString strComponent);
virtual void LoadDefault();
virtual BOOL LoadSkin(CString strSkin);

The first function is called by the Manager in the above manner. The skinned UI element (Window) calls the next two functions like this.

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);
    .....
}

Step 5: and finally Manager writes the current settings to the storage.

Call CEZSkinManager::Save during ExitInstance.

AfxGetApp()->WriteProfileString(HKEY_SKINS,HKEY_DIR,m_strSkins);
AfxGetApp()->WriteProfileString(HKEY_SKINS,HKEY_SKIN,m_strCurrentSkin);

Implementation

Introduction

In the demo, I have implemented the EZSkin interface to create a skinned dialog. Strictly speaking, CEZSkinManager should have been discussed here but it would have been difficult for me to explain the interface without this class.

The following classes form the basis of this implementation.

CEZSkinIni

This gives a default implementation of IEZSkinIni. It implements the Reader layer as an INI file. I have used the CIni class by Iuri Apollonio and modified it to fit into the framework.

It uses a CStdioFile to read the INI file and stores every line in a CStringArray, then parses every line to get the required value. I have used a ; as the comment starter and , as the value separator.

It uses AfxExtractSubString to parse comma separated values.

A sample Skin INI;

[Skin]
Name = Black;
Author = V.Lakshmi Narasimhan;
Comment = Black Beauty;

[Main]
Bmp = back.bmp;
Draw = Tile;

[Caption]
Bmp = Caption.bmp;
DRAW = Tile;
TextFont = ARial Black,B,25;
TextColor = 200,200,200;
BtnsNormal = btns.bmp;
BtnsHilight  = btnsh.bmp;
TransColor = 192,224,64;
BtnPos = 7,27,47;
BtnWidth   = 20;

CEZGenericSkin

This gives a default implementation of IEZSkinComponent and is still pseudo abstract having a few assert always functions.

This class provides the interface for a window which requires the following skin attributes:

  • Background bitmap,
  • Background Color,
  • Text Color &
  • Text Font

This class holds the data using the following members:

BOOL m_bDefault;
BOOL m_bLoaded;
CEZDib m_Dib;//See the helpers section
CFont m_font;
COLORREF m_clrTxt;
COLORREF m_clrBk;

To use this class, one should derive from this and override the following functions:

//{Pseudo Pure virtual functions
virtual CString GetSection()
{ASSERT(FALSE);return _T("");}
virtual void LoadDefaultBmp(){ASSERT(FALSE);}
virtual void LoadDefaultFont(){ASSERT(FALSE);}
virtual void LoadDefaultBackColor(){ASSERT(FALSE);}
virtual void LoadDefaultTextColor(){ASSERT(FALSE);}
//}

It provides default implementation for all the functions exposed by the IEZSkinComponent interface. The reason why a derived class must override the above functions is this:

BOOL CEZGenericSkin::LoadDefault()
{
    LoadDefaultBmp();
    LoadDefaultBackColor();
    LoadDefaultTextColor();
    LoadDefaultFont();

    m_bDefault = TRUE;
    m_bLoaded = TRUE;
    return TRUE;
}

It also has a cool helper which loads a font into the m_font member given the Font face name style and width.

BOOL CEZGenericSkin::LoadFont(CString strFont, CString strStyle, int nHeight)

E.g. usage:

LoadFont(_T("Times New Roman"),_T("BI"),20);

To see how easy it is to implement IEZSkinComponent using CEZGenericSkin, take a look at the definition of CEZDialogSkin.

CEZDialogSkin

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

This is not quite as small as CEZDialogSkin.

It has additional members for Caption buttons - Rects, Highlighted & Normal bitmaps and transparent color of the bitmaps.

CEZDib m_DibBtnNormal;
CEZDib m_DibBtnHilight;
CRect m_rectBtns[3];
COLORREF m_clrTransparent;

Helpers

Introduction

Here we just take a look at the various helper classes that are used in the demo.

Rects

These are classes derived from CRect that encapsulate CWnd::GetxxxRect functions and CDC::GetClipBox so that it is possible to write code like:

CPaintDC dc(this);

//CEZDib dib;
dib.Draw(&dc,CEZClientRect(this));

//instead of 
//CRect rect;
//GetClientRect(&rect);
//dib.Draw(&dc,rect);

DCs

CEZMemDC, is CMemDC with additional bCopyOnDestruct parameter which prevents the DC from transferring its contents to the destination. CEZBmpDC selects a bitmap or portions of it to a compatible DC and can be used as a scratch pad.

The coolest one is the CEZMonoDC which takes in a DC and creates a DC with a monochrome bitmap of the source DC in place.

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

This is built on Jörg König's CDIBitmap class. I have included certain goodies from other DIB classes I found. The important change I have done to CDIBitmap is to make it passable as a CBitmap as suggested by Paul DiLascia in periodicals 97. I have also added four drawing functions that draw a normal bitmap, stretched bitmap, tiled bitmap and one that draws transparently.

Collapse
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),mucancode.netDC(pDC,&rcDC);

    DrawNormal(&imageDC,rcDC,rcBmp);

    COLORREF clrImageOld = imageDC.SetBkColor(clrTrans);
    mucancode.netDC.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(),&mucancode.netDC,rcDC.left,rcDC.top,NOTSRCCOPY);

    memDC.BitBlt(rcDC.left,rcDC.top,rcDC.Width(),rcDC.Height(),
      &mucancode.netDC,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

A class that encapsulates non client area functions of CWnd.

BOOL HasBorder();
BOOL HasSysMenu();
BOOL HasCaption();
CRect GetCaptionRect();
CRect GetLeftBorderRect();
CRect GetRightBorderRect();
CRect GetTopBorderRect();
CRect GetBottomBorderRect();

CEZDialog

This is the sample skinned UI element.

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));
}

Wow, doesn't the code look too small for a dialog with a skinned bitmap background?

CEZCaption

I have based this class on Dave Lorde's CCaption code. I have modified the original code to use CEZSkin and have also added code to paint and handle caption buttons. It uses CEZDib and CEZWindowNC extensively. I have also made changes to make it work for a dialog.

Though the caption does paint and handle the buttons well, I experienced problems with mouse tracking. I have simplified the tracking of the class at the cost of reducing the functionality. It would be nice if someone posts an article on how to do it.

 

 

Copyright ?1998-2009 UCanCode.Net Software , all rights reserved.
Other product and company names herein may be the trademarks of their respective owners.

Please direct your questions or comments to webmaster@ucancode.net