|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Introduction
The library promises to achieve (with minor modifications - to make it very generic) a non-windows UI look-n-feel for those who want to develop a customized UI (curves et al), by leveraging the power of images, GDI, composition, and multiple inheritance.
Inspired by...
Few years back, when I first saw the cool skins on Winamp (MP3 player), I felt excited and challenged to write a library to use in all my future developments that would shock people with belief that slick UI is only feasible in web applications and Flash applications!
What's inside the library?
The library is composed of following classes:
CSkinControl (Parent class of all controls, containing common functionality)
CSkinnedStatic - Custom class to act as a static control or label
- Inherits from: Cwnd, CSkinControl
CSkinnedButton - Custom class to act as a button control
- Inherits from: Cwnd, CSkinControl
CSkinnedEdit - Custom class to act as an edit control
- Inherits from: Cwnd, CSkinControl
CSkinnedComboBox - Custom class to act as a combo box control
- Inherits from: Cwnd, CSkinControl
- Composed of: CSkinnedEdit, CSkinnedButton, and CSkinnedListBox
CSkinnedListBox - Custom class to act as a list box control
- Inherits from: Cwnd, CSkinControl
- Composed of: CSkinnedButton
CSkinnedScrollBar - Custom class to act as a scroll bar control
- Inherits from: Cwnd, CSkinControl
- Composed of: CSkinnedButton
CSkinnedSliderCtrl - Custom class to act as a slider control
- Inherits from: Cwnd, CSkinControl
- Composed of: CSkinnedButton
Architectural details...
The idea was to store as much common functionalities as possible in one class (CSkinControl) and then consume the same, through inheritance, in concrete control classes. The base class holds references to 4 different images (ids), one for normal state, one for disabled state, one for hover state, and one for pressed state. The function that stores the same is SetImageResources(normal, hover, pressed, disabled). The base class also contains functionality for:
Location and Size:
SetCoordinates(left, top)
SetDimensions(width, height)
GetLeft()
GetTop()
GetWidth()
GetHeight()
Colors and Fonts:
GetCurrentBackgroundColor()
GetTextColor()
GetBackgroundColor(state)
SetBackgroundColor(state, color)
SetForegroundColor(color)
SetTextColor(color)
SetFontName(name)
SetFontStyle(style)
SetFontSize(size)
GetFontName()
GetFontStyle()
GetFontSize()
Most important is UpdateMemoryDC() which takes care of drawing and updating the visual of each control on screen, whether in default state or triggered by some user-action (mouse events).
int CSkinControl::UpdateMemoryDC()
{
HBITMAP hBitmap = NULL;
BITMAP bmpTemp;
#ifdef USE_GIF_IMAGES
hBitmap = LoadGIF(GetDllInstance((LPCTSTR)m_csDLLFileName),MAKEINTRESOURCE(GetID()));
#else
hBitmap = LoadBitmap(GetDllInstance((LPTSTR)(LPCTSTR)m_csDLLFileName), MAKEINTRESOURCE(GetID()));
#endif
if(hBitmap != NULL)
{
::GetObject(hBitmap, sizeof(BITMAP), &bmpTemp);
m_lImageWidth = bmpTemp.bmWidth;
m_lImageHeight = bmpTemp.bmHeight;
::SelectObject(m_dcMemory.GetSafeHdc(),hBitmap);
}
else if(m_nPressedID == -1 && m_nUnPressedID == -1 && m_nHoverID == -1)
{
m_dcMemory.SetTextColor(m_crTextColor);
m_dcMemory.DrawText(m_csText, CRect(0, 0, m_nWidth, m_nHeight), DT_CENTER);
}
return 0;
}
Concrete classes provide functionalities that are required by their standard counterparts. For example, CSkinnedEdit supports text selection, insertion, deletion (no copy-paste implemented - sorry!!), and other customized features like "read-only", "decimal point validation", etc. Similarly, CSkinnedScrollBar provides functionality to set minimum range, maximum range, retrieve position of scroll bar button, and so on. The code and function names are quite self-explanatory. I apologize for not providing many inline code comments, for which you can always contact me.
All the controls are created dynamically. Each one of them has a function CreateSkinControl(name, rect, parent, id, flags) that takes parameters as mentioned. The last one (flags) is an interesting parameter that holds any "extra" information required (as you'll see in different controls) for creation. As an example, mentioned below is the creation code for CSkinnedButton control.
BOOL CSkinnedButton::CreateSkinControl(LPCTSTR lpszWindowName, LPRECT lpRect, CWnd *pParentWnd, UINT nControlID, long lFlags)
{
m_csText = lpszWindowName;
m_nLeft = lpRect->left;
m_nTop = lpRect->top;
m_nWidth = lpRect->right - lpRect->left;
m_nHeight = lpRect->bottom - lpRect->top;
m_pParentWnd = pParentWnd;
m_nControlID = nControlID;
m_csFontName = "Arial";
m_nFontSize = 16;
m_nFontStyle = FONT_NORMAL;
m_crBackgroundColorHover = RGB(255,255,255);
m_crBackgroundColorPressed = RGB(255,255,255);
m_crBackgroundColorUnPressed = RGB(255,255,255);
m_crForegroundColor = RGB(0,0,0);
m_lButtonType = lFlags;
if(m_hWnd != NULL)
{
return FALSE;
}
if(CWnd::Create(NULL, m_csText, WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS, *lpRect, pParentWnd, nControlID, NULL))
{
CWnd::BringWindowToTop();
return TRUE;
}
return FALSE;
}
Steps to implement...
In a window/dialog that you want to use a control (button, for example), define a member variable that is a pointer to the control.
CSkinnedButton* m_pOkButton;
Inside creation logic of the dialog OnCreate() or OnInitDialog(), insert the creation logic of the button, after few initializations of creating a memory DC for background painting.
int CMyDialog::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if(CDialog::OnCreate(lpCreateStruct) == -1)
{
return -1;
}
CClientDC dc(this);
m_memDC.CreateCompatibleDC(&dc);
m_memBmp.CreateCompatibleBitmap(&dc, 1024, 768);
m_memDC.SelectObject(&m_memBmp);
...
m_pOkButton = new CSkinnedButton;
m_pOkButton.SetImageResource(ID_NORMAL, ID_HOVER, ID_PRESSED, ID_DISABLED);
m_pOkButton.SetShapedFlag(TRUE);
...
}
Custom code for button creation and button rendering is implemented in the CSkinnedButton class as shown:
int CSkinnedButton::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;
CClientDC dc(this);
CBitmap bmpTemp;
m_dcMemory.CreateCompatibleDC(&dc);
if(bmpTemp.CreateCompatibleBitmap(&dc, m_nWidth, m_nHeight) != 0)
{
m_dcMemory.SelectObject(&bmpTemp);
if(PrepareFont())
{
}
UpdateMemoryDC();
if(m_bShape)
{
m_hRgn = CreateRectRgn(0,0,0,0);
if(m_hRgn != NULL)
{
if(GetWindowRgn(m_hRgn) == ERROR)
{
m_hRgn = NULL;
return -1;
}
}
else
{
return -1;
}
}
}
return 0;
}
int CSkinnedButton::UpdateMemoryDC()
{
BITMAP bmpTemp;
memset(&bmpTemp, 0, sizeof(BITMAP));
if(m_dcMemory == NULL)
{
return -1;
}
#ifdef USE_GIF_IMAGES
if(m_hBitmap != NULL && m_hBitmap == GetCurrentStateBitmap())
{
return -1;
}
m_hBitmap = GetCurrentStateBitmap();
#else
hBitmap = GetCurrentStateBitmap();
#endif
if(m_hBitmap != NULL)
{
::GetObject(m_hBitmap, sizeof(BITMAP), &bmpTemp);
m_lImageWidth = bmpTemp.bmWidth;
m_lImageHeight = bmpTemp.bmHeight;
::SelectObject(m_dcMemory.GetSafeHdc(),m_hBitmap);
}
else if(m_nPressedID == -1 && m_nUnPressedID == -1 && m_nHoverID == -1)
{
CClientDC dc(this);
m_dcMemory.SetMapMode(dc.GetMapMode());
m_dcMemory.SetWindowExt(dc.GetWindowExt());
m_dcMemory.SetViewportExt(dc.GetViewportExt());
m_dcMemory.SetWindowOrg(0, 0);
CBitmap cbmpTemp;
cbmpTemp.CreateCompatibleBitmap(&dc, m_nWidth, m_nHeight);
if(m_dcMemory.SelectObject(&cbmpTemp) != NULL)
{
m_dcMemory.FillSolidRect(0, 0, m_nWidth, m_nHeight, GetCurrentBackgroundColor());
}
}
if(m_bShape != -1 && m_bFindEdges)
{
m_bFindEdges = FALSE;
FindControlEdge(this, &m_dcMemory, COLOR_MAGENTA, m_hRgnWindow);
}
return 0;
}
FindControlEdge() (not a very intuitive name!) implements the transparency algorithm, using a Magenta color mask, traversing through the image, and cutting out a region. You might argue that why not use the GDI function TransparentBlt() to achieve the same. Good point! However, when I tried to implement using TransparentBlt it failed to run in Windows 98 SE (although MS claims to have supported in that version of Windows!). Anyways, may be I didnt have the correct patch of Windows or SDK at the time. I decided to write my own. You have a choice of using TransparentBlt which would promise an optimized performance over my technique for sure ;)
Also, my technique introduces a strick requirement of having all images bounded by a 4 pixel magenta background!!!!!!!
Example:
For those who might face a similar problem of TransparentBlt() are free to use the algorithm placed here or of your own.
BOOL FindControlEdge(CWnd* pWnd, CDC *dcControl, COLORREF colToSkip, HRGN &hRgn)
{
int nCurrentX = 0;
int nCurrentY = 0;
int nTempX = 0;
int nTempY = 0;
BOOL bStop = FALSE;
int nDirection = 0;
int nCurDirection = 0;
int nFirstX = 0;
int nFirstY = 0;
int nXMap = 0;
int nYMap = 0;
int nIterate = 0;
POINT ptTempCoord;
CList ptCoord;
CRect rcWindow(0,0,0,0);
CRect rcClient(0,0,0,0);
pWnd->GetWindowRect(&rcWindow);
pWnd->GetClientRect(&rcClient);
pWnd->ClientToScreen(&rcClient);
nXMap = rcClient.left - rcWindow.left;
nYMap = rcClient.top - rcWindow.top;
nIterate = 0;
bStop = FALSE;
nCurrentX = 0;
nCurrentY = 0;
nDirection = SOUTHEAST;
nFirstX = 0;
nFirstY = 0;
while(!bStop)
{
if((dcControl->GetPixel(nCurrentX+1, nCurrentY+1)) != colToSkip)
{
bStop = TRUE;
if(nCurrentX == 0 && nCurrentY == 0)
{
return FALSE;
}
}
else
{
nCurrentX++;
nCurrentY++;
}
}
bStop = FALSE;
while(!bStop)
{
nIterate++;
switch(nDirection)
{
case SOUTHEAST:
if((dcControl->GetPixel(nCurrentX+1, nCurrentY+1)) != colToSkip)
{
nDirection = EAST;
continue;
}
else
{
nCurrentX++;
nCurrentY++;
}
break;
case EAST:
if((dcControl->GetPixel(nCurrentX+1, nCurrentY)) != colToSkip)
{
nDirection = NORTHEAST;
continue;
}
else
{
nCurrentX++;
}
break;
case NORTHEAST:
if((dcControl->GetPixel(nCurrentX+1, nCurrentY-1)) != colToSkip)
{
nDirection = NORTH;
continue;
}
else
{
nCurrentX++;
nCurrentY--;
}
break;
case NORTH:
if((dcControl->GetPixel(nCurrentX, nCurrentY-1)) != colToSkip)
{
nDirection = NORTHWEST;
continue;
}
else
{
nCurrentY--;
}
break;
case NORTHWEST:
if((dcControl->GetPixel(nCurrentX-1, nCurrentY-1)) != colToSkip)
{
nDirection = WEST;
continue;
}
else
{
nCurrentX--;
nCurrentY--;
}
break;
case WEST:
if((dcControl->GetPixel(nCurrentX-1, nCurrentY)) != colToSkip)
{
nDirection = SOUTHWEST;
continue;
}
else
{
nCurrentX--;
}
break;
case SOUTHWEST:
if((dcControl->GetPixel(nCurrentX-1, nCurrentY+1)) != colToSkip)
{
nDirection = SOUTH;
continue;
}
else
{
nCurrentX--;
nCurrentY++;
}
break;
case SOUTH:
if((dcControl->GetPixel(nCurrentX, nCurrentY+1)) != colToSkip)
{
nDirection = SOUTHEAST;
continue;
}
else
{
nCurrentY++;
}
break;
}
nCurDirection = nDirection;
if((dcControl->GetPixel(nCurrentX+1, nCurrentY+1)) != colToSkip)
{
nDirection = SOUTHEAST;
}
if((dcControl->GetPixel(nCurrentX+1, nCurrentY)) != colToSkip)
{
nDirection = EAST;
}
if((dcControl->GetPixel(nCurrentX+1, nCurrentY-1)) != colToSkip)
{
nDirection = NORTHEAST;
}
if((dcControl->GetPixel(nCurrentX, nCurrentY-1)) != colToSkip)
{
nDirection = NORTH;
}
if((dcControl->GetPixel(nCurrentX-1, nCurrentY-1)) != colToSkip)
{
nDirection = NORTHWEST;
}
if((dcControl->GetPixel(nCurrentX-1, nCurrentY)) != colToSkip)
{
nDirection = WEST;
}
if((dcControl->GetPixel(nCurrentX-1, nCurrentY+1)) != colToSkip)
{
nDirection = SOUTHWEST;
}
if((dcControl->GetPixel(nCurrentX, nCurrentY+1)) != colToSkip)
{
nDirection = SOUTH;
}
POINT ptTemp;
if(ptCoord.GetCount() > 0)
{
ptTemp = ptCoord.GetTail();
}
else
{
ptTemp.x = 0;
ptTemp.y = 0;
}
if(nCurrentX != ptTemp.x || nCurrentY != ptTemp.y)
{
nTempX = nCurrentX;
nTempY = nCurrentY;
switch (nCurDirection)
{
case NORTH:
case NORTHWEST:
nTempX++;
break;
case NORTHEAST:
case EAST:
nTempY++;
break;
}
ptTempCoord.x = nTempX;
ptTempCoord.y = nTempY;
ptCoord.AddTail(ptTempCoord);
}
if(nFirstX == 0 && nFirstY == 0)
{
nFirstX = nCurrentX;
nFirstY = nCurrentY;
}
else if(nCurrentX == nFirstX && nCurrentY == nFirstY)
{
break;
}
}
POINT *ptAll;
ptAll = new POINT[ptCoord.GetCount()];
int nLen = ptCoord.GetCount();
for(int idx=0; idx<nLen; idx++)
{
ptAll[idx] = ptCoord.GetHead();
ptCoord.RemoveHead();
}
hRgn = CreatePolygonRgn(ptAll, nLen, ALTERNATE);
delete []ptAll;
if(hRgn != NULL)
{
if(pWnd->SetWindowRgn(hRgn, TRUE) != 0)
{
return TRUE;
}
}
return FALSE;
}
Finally, you implement message handlers to react to control events and messages (LButtonDown/Up for Button, OnChar for Edit, and so on), and appropriately play with different control states (normal, hover, disabled, etc.) and updation of corresponding "look" by calling UpdateMemoryDC()
Some benefits...
If you design properly, you can come up with parallel "themes" for your application; basically different set of images to super-impose on your application and controls within, and switch easily, using configuration files.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 27 (Total in Forum: 27) (Refresh) | FirstPrevNext |
|
 |
|
|
 |
|
|
 |
|
|
bigb_602,
This library is incredible. I cannot believe those sample images were from a compiled desktop application! A+, exceptional work.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
It seems like a nice idea, equally well implemented I'm sure. But I'd bet that you'd get a much warmer reception if readers could see an example skinned app, or even a dialog. Why not add one today?
Jerry
|
| Sign In·View Thread·PermaLink | 5.00/5 (3 votes) |
|
|
|
 |
|
|
 |
|
|
 |
|
|
 |
|
|
 |
|
|
Hi,
I've used a code which converts GIFs into BITMAP handle. I believe if you tweak that portion of code and write another method to do the conversion, you can surely use PNGs or any other image format. You may also easily find ready-to-use code for the conversion.
Please let me know if I should do some research and let you know.
Regards.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I was not able to find code which I could accomadate to ur specification. could u please help me out here?
Thanks, Reji
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Use GdiPlus its simple here's a snippet of code I made to show how its done. Modify it so it works for your needs.
#pragma comment(lib,"gdiplus.lib")
#include <gdiplus.h>
using namespace Gdiplus;
ULONG_PTR GdiPlusToken = 0; Bitmap* GdiPlusBitmap = 0; HBITMAP GdiBitmapHandle = 0;
bool LoadPNG( const wstring& filename ) { GdiPlusBitmap = new GdiPlusImage( (WCHAR*) filename.c_str() );
if ( GdiPlusBitmap && GdiPlusBitmap->GetLastStatus() == Gdiplus::Ok ) { GdiBitmapHandle = GdiPlusBitmap->GetHBITMAP( Gdiplus::Color( 0 , 0 , 0 ) , &hbmNew ); return GdiBitmapHandle != NULL; } else { if ( GdiPlusBitmap ) delete GdiPlusBitmap; return false; } }
bool InitGdiPlus() { GdiplusStartupInput gdiplusStartupInput;
if ( GdiplusStartup( &GdiPlusToken , &gdiplusStartupInput , NULL ) != Ok ) return false;
return true; }
----------------------------- I am out of scope
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
I've tried building thease source fiels. but I couldn't some files below.
#include "..\CImage\src\ImgObj.h" (skincontrol.cpp) #include "..\tools\StringEx.h" (skinnedSliderCtrl.cpp) #include "..\tools\GlobalFunctions.h" (skinnedSliderCtrl.cpp) #include "..\GlobalConstants.h" (skinnedSliderCtrl.cpp)
How can I skip these header files?
Thanx.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
Apologies for missing header files. I'll upload another zip file containing the missing ones. Thanks much for pointing out the error.
Regards.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
I had to compile "GlobalFunctions.cpp". but, that file needs some headers below.
#include "..\..\PukamSoft\resource.h" #include "..\..\PukamSoft\MessageBoxDlg.h"
Maybe, the MessageBoxDlg Class seems to be defined.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I've skipped these files. I didn't use the MessageBoxEng function. Anyway, I've finished compiling all the source files which you shared.
Now, I'll test these skinned controls to my sample app.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
Again, please accept my sincere apologies for not cleaning up and providing a ready-to-use library. I've simply pulled the relevant files from an application that I wrote and it unfortunately contains few application-specific header files as well (bad coding, i know!)
Let me know if your experiment worked.
Regards.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Not working yet.
I've made "SkinRes.dll" for skine resource of GIF type. I've inserted GIF resource below to that dll file.
#define ID_DISABLED 5000 #define ID_NORMAL 5001 #define ID_HOVER 5002 #define ID_PRESSED 5003
and, in the project that included those skinned control library, I inserted below codes at OnCreate function.
OnCreate() { ... .... CClientDC dc(this); m_memDC.CreateCompatibleDC(&dc); m_memBmp.CreateCompatibleBitmap(&dc, 1024, 768); m_memDC.SelectObject(&m_memBmp); // Create button m_pOkButton = new CSkinnedButton; // Assign 4 image ids m_pOkButton->SetImageResource(ID_NORMAL, ID_HOVER, ID_PRESSED, ID_DISABLED); // This flag (true) suggests that the button is an irregular shaped, // which will be drawn using a transparency algorithm to achieve the desired result m_pOkButton->SetShapedFlag(TRUE); ... ... }
but, Nothing happed. What's going on?
Can you point out my fault?
Best regards
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
I've uploaded my entire demo project, where you will be able to see how and where to call the correct methods in order. Please let me know if you have any problems.
Regards.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks, That's no problem. very nice. I'll check out my fault yesterday. These codes seem to be complicated. 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi,
Sorry for the delayed response. I'll uploaded a sample application. Please let me know if this is what you expected.
Regards.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I think he means not the sample application but the sample project.
|
| Sign In· | | | | | |