Introduction
This article concerns those MFC programmers who use the ProfUIS
library in their projects; also, it contains some general tips on how to use custom UI libraries. I think most programmers who use custom UI control classes in their dialogs don't like the simple way to do it - create new member of the desired control class and do something like DDX_Control()
. The more dialogs you have in your program - the more you become bothered by this "simple way". So, I solved this problem, and now I can give a simple and "right" way to make it work. In addition, I will give two interesting tips on how to replace the standard MessageBox
and standard Print Preview bar with skinned ones.
Background
If you want to check this sample in real work, I recommend you install the free version of the professional user interface solutions by FOSS software by simply downloading it from the company site: http://www.prof-uis.com/download.aspx.
So let's start.
General concept
The article will give an example of three classes and some hints on the external code. The main classes are: CGeneralSubclassersContainer
, CProfUISDialog
, and CProuisDialogBar
.
The general concept is to dynamically create and subclass all usual skinned controls. Since I used not only the dialogs to deal with, but also the dialog bars, I created the standalone class to manipulate all dynamic controls - the CGeneralSubclassersContainer
class. The CProfUISDialog
replaces the standard CDialog
class, and the CProuisDialogBar
class replaces the standard CDialogBar
class, so to use them - simply replace all entries of CDialog
(CDialogBar
) in your derived classes with CProfUISDialog
(CProuisDialogBar
), and have fun.
Class code
Header file
#pragma once
#include < vector >
BOOL CALLBACK EnumContainerChildProc( HWND hwnd, LPARAM lParam );
struct CGeneralSubclasserContainer {
CGeneralSubclasserContainer()
{
m_ActLikeMessageBox = false;
}
virtual ~CGeneralSubclasserContainer()
{
for( long i = 0; i < m_GeneralSubclassers.size(); ++i )
{
delete m_GeneralSubclassers[i];
}
}
bool m_ActLikeMessageBox;
std::vector< CWnd* > m_GeneralSubclassers;
long addWindow(CWnd* newWnd)
{
m_GeneralSubclassers.push_back( newWnd );
return m_GeneralSubclassers.size() - 1;
}
};
class CProuisDialogBar : public CExtWA < CExtWS < CDialogBar > >,
public CGeneralSubclasserContainer
{
public:
CProuisDialogBar() {}
virtual ~CProuisDialogBar() {};
void StandardPrepare();
};
class CExtProgressCtrl : public CProgressCtrl {
public:
CExtProgressCtrl(){}
virtual ~CExtProgressCtrl(){}
public:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnPaint();
};
class CProfUISDlg : public CExtResizableDialog,
public CGeneralSubclasserContainer {
DECLARE_DYNAMIC(CProfUISDlg)
public:
CProfUISDlg() {}
CProfUISDlg(UINT nID, CWnd* pParent = NULL); CProfUISDlg( __EXT_MFC_SAFE_LPCTSTR lpszTemplateName,
CWnd * pParentWnd = NULL );
virtual ~CProfUISDlg();
void StandardPrepare();
protected:
virtual void DoDataExchange(CDataExchange* pDX);
DECLARE_MESSAGE_MAP()
public:
virtual BOOL OnInitDialog();
afx_msg void OnShowWindow(BOOL bShow, UINT nStatus);
};
The ProfUIS
library needs the next class to be declared, so you will get a skinned look for the non-client area as well.
template < >
class CExtNCW < CProfUISDlg >
: public CProfUISDlg
, public CExtNcFrameImpl
{
public:
CExtNCW()
{
}
CExtNCW(
UINT nIDTemplate,
CWnd * pParentWnd = NULL
)
: CProfUISDlg( nIDTemplate, pParentWnd )
{
}
CExtNCW(
__EXT_MFC_SAFE_LPCTSTR lpszTemplateName,
CWnd * pParentWnd = NULL
)
: CProfUISDlg( lpszTemplateName, pParentWnd )
{
}
virtual ~CExtNCW()
{
}
virtual HWND NcFrameImpl_OnQueryHWND()
{
return GetSafeHwnd();
}
protected:
virtual void PreSubclassWindow()
{
CProfUISDlg::PreSubclassWindow();
CExtNcFrameImpl::PreSubclassWindow();
}
virtual void PostNcDestroy()
{
CExtNcFrameImpl::PostNcDestroy();
CProfUISDlg::PostNcDestroy();
}
virtual LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam )
{
if( ! NcFrameImpl_IsSupported() )
return CProfUISDlg::WindowProc( message, wParam, lParam );
HWND hWndOwn = m_hWnd;
LRESULT lResult = 0;
if( NcFrameImpl_PreWindowProc( lResult, message, wParam, lParam ) )
return lResult;
lResult = CProfUISDlg::WindowProc( message, wParam, lParam );
if( ! ::IsWindow( hWndOwn ) )
return lResult;
if( CWnd::FromHandlePermanent(hWndOwn) == NULL )
return lResult;
NcFrameImpl_PostWindowProc( lResult, message, wParam, lParam );
return lResult;
}
};
Implementation file
#include < stdafx.h >
#pragma hdrstop
#include "ProfUISDlgApp.h"
#include "ProfuisDlg.h"
IMPLEMENT_DYNAMIC(CProfUISDlg, CExtResizableDialog)
CProfUISDlg::CProfUISDlg(UINT nID, CWnd* pParent )
: CExtResizableDialog( nID, pParent)
{
}
CProfUISDlg::CProfUISDlg( __EXT_MFC_SAFE_LPCTSTR lpszTemplateName,
CWnd * pParentWnd )
: CExtResizableDialog( lpszTemplateName, pParentWnd)
{
}
CProfUISDlg::~CProfUISDlg()
{
}
void CProfUISDlg::DoDataExchange(CDataExchange* pDX)
{
CExtResizableDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CProfUISDlg, CExtResizableDialog )
ON_WM_SHOWWINDOW()
END_MESSAGE_MAP()
BOOL CALLBACK EnumContainerChildProc( HWND hwnd, LPARAM lParam )
{
CGeneralSubclasserContainer* dialogPtr =
(CGeneralSubclasserContainer*)lParam;
if( CWnd::FromHandlePermanent( hwnd ) == 0 ) {
CWnd* tmp = CWnd::FromHandle( hwnd );
CString className;
GetClassName( hwnd, className.GetBufferSetLength( 200 ), 200 );
className.ReleaseBuffer();
if( className.CompareNoCase( _T("edit") ) == 0 )
{
dialogPtr->m_GeneralSubclassers[dialogPtr->addWindow
( new CExtEdit )]->
SubclassWindow( hwnd );;
}
else if( className.CompareNoCase( _T("Static") ) == 0 )
{
dialogPtr->m_GeneralSubclassers[dialogPtr->addWindow(
new CExtLabel )]->SubclassWindow( hwnd );
}
else if( className.CompareNoCase( _T("BUTTON") ) == 0 )
{
CButton* tmpButton = (CButton*)CWnd::FromHandle( hwnd );
if( tmpButton->GetButtonStyle() == BS_CHECKBOX ||
tmpButton->GetButtonStyle() == BS_AUTOCHECKBOX )
{
dialogPtr->m_GeneralSubclassers[dialogPtr->addWindow
( new CExtCheckBox )]->
SubclassWindow( hwnd );
}
else if( tmpButton->GetButtonStyle() == BS_GROUPBOX )
{
dialogPtr->m_GeneralSubclassers[dialogPtr->addWindow
( new CExtGroupBox )]->
SubclassWindow( hwnd );
((CExtGroupBox*)dialogPtr->m_GeneralSubclassers[dialogPtr->
m_GeneralSubclassers.size() - 1])->SetStyle
( CExtGroupBox::STYLE_ROUNDED );
((CExtGroupBox*)dialogPtr->m_GeneralSubclassers[dialogPtr->
m_GeneralSubclassers.size() - 1])->SetWindowPos
( &CWnd::wndBottom, 0,0,0,0, SWP_NOSIZE| SWP_NOMOVE );
}
else if( tmpButton->GetButtonStyle() == BS_AUTORADIOBUTTON ||
tmpButton->GetButtonStyle() == BS_RADIOBUTTON )
{
dialogPtr->m_GeneralSubclassers[dialogPtr->addWindow
( new CExtRadioButton )]->
SubclassWindow( hwnd );
}
else
{
dialogPtr->m_GeneralSubclassers[dialogPtr->addWindow
( new CExtButton )]->
SubclassWindow( hwnd );
}
}
else if( className.CompareNoCase( _T("msctls_progress32") ) == 0 )
{
dialogPtr->m_GeneralSubclassers[dialogPtr->
addWindow( new CExtProgressCtrl )]->SubclassWindow( hwnd );
}
else if( className.CompareNoCase( _T("ComboBox") ) == 0 )
{
dialogPtr->m_GeneralSubclassers[dialogPtr->
addWindow( new CExtComboBox )]->SubclassWindow( hwnd );
}
}
return TRUE;
}
CProuisDialogBar::StandardPrepare()
{
EnumChildWindows( GetSafeHwnd(), EnumContainerChildProc, LPARAM(
static_cast< CGeneralSubclasserContainer* >( this ) ) );
}
void CProfUISDlg::StandardPrepare()
{
EnumChildWindows( GetSafeHwnd(), EnumContainerChildProc, LPARAM(
static_cast< CGeneralSubclasserContainer*>( this ) ) );
ShowSizeGrip( FALSE );
}
BOOL CProfUISDlg::OnInitDialog()
{
CExtResizableDialog::OnInitDialog();
StandardPrepare();
return TRUE;
}
BEGIN_MESSAGE_MAP(CExtProgressCtrl, CProgressCtrl)
ON_WM_PAINT()
END_MESSAGE_MAP()
void CExtProgressCtrl::OnPaint()
{
CRect rcClient;
GetClientRect( &rcClient );
CPaintDC dcPaint( this );
CExtMemoryDC dc( &dcPaint, &rcClient );
if( g_PaintManager->GetCb2DbTransparentMode(this) )
{
CExtPaintManager::stat_ExcludeChildAreas(
dc,
GetSafeHwnd(),
CExtPaintManager::stat_DefExcludeChildAreaCallback
);
g_PaintManager->PaintDockerBkgnd( true, dc, this );
} else
dc.FillSolidRect( &rcClient, g_PaintManager->
GetColor( CExtPaintManager::CLR_3DFACE_OUT, this ) );
DefWindowProc( WM_PAINT, WPARAM(dc.GetSafeHdc()), 0L );
g_PaintManager->OnPaintSessionComplete( this );
}
void CProfUISDlg::OnShowWindow(BOOL bShow, UINT nStatus)
{
CExtResizableDialog::OnShowWindow(bShow, nStatus);
if( m_ActLikeMessageBox && bShow ) {
EnumChildWindows( GetSafeHwnd(), EnumContainerChildProc,
LPARAM( static_cast< CGeneralSubclasserContainer*>( this ) ) );
ShowSizeGrip( FALSE );
}
}
Using the code
To use these classes in your applications, first you must copy the declarations to your header file and the implementation to the implementation file. After including this code to your project, use the desired class CProfUISDlg
or CProuisDialogBar
as usual CDialog
or CDialogBar
!
Example
class CImportDialog : public CExtNCW< CProfUISDlg >
{
....
public:
virtual BOOL OnInitDialog();
}
Important tip: if you want to subclass some control in an unusual way, subclass it before calling the parent CProfUISDlg::OnInitDialog
!!
BOOL CImportDialog::OnInitDialog()
{
BOOL result = CProfUISDlg::OnInitDialog();
return result;
}
Following the given instructions, you can obtain something that looks like this:
The red/green indicator is a simple static-derived control I coded for a nice look in large input forms. It can switch on/off the next control after it by mouse click, and also indicates the control state. Maybe in my next short article, I will put it on the top of the discussion.
Useful hints
Now, some useful code hints.
First hint: How to replace the standard MessageBox with a brand-new one.
You must replace the standard CWinApp::DoMessageBox
with your own CMyApp::DoMessageBox
. The new version will look like this:
int CMyApp::DoMessageBox(LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt)
{
CExtNCW<CProfuisDlg> dlg;
dlg.m_ActLikeMessageBox = true;
AfxHookWindowCreate( &dlg );
return CWinApp::DoMessageBox(lpszPrompt, nType, nIDPrompt);
}
AfxMessageBox( _T("Hi! Now you'll see the result!");
And here is the Mr. NiceLooking MessageBox
:
Second hint: How to replace the standard print preview bar with a new one.
If you already know how to work with custom print preview, then add to your custom DoPrintPreview
the given code:
BOOL CWrapperView::DoPrintPreview(UINT nIDResource, CView* pPrintView,
CRuntimeClass* pPreviewViewClass, CPrintPreviewState* pState)
{
CMyPreviewView* pView = (CMyPreviewView*)pPreviewViewClass->CreateObject();
if (pView == NULL)
{
TRACE0("Error: Failed to create preview view.\n");
return FALSE;
}
pView->m_pPreviewState = pState;
pView->m_pToolBar = new CProuisDialogBar;
if (!pView->m_pToolBar->Create(pParent, MAKEINTRESOURCE(nIDResource),
CBRS_TOP, AFX_IDW_PREVIEW_BAR))
{
return FALSE;
}
((CProuisDialogBar*) pView->m_pToolBar)->StandardPrepare();
pView->m_pToolBar->m_bAutoDelete = TRUE;
addOptionsButtonToPreviewBar( pView->m_pToolBar );
}
After all, we get what we wanted to:
Conclusion
You can test the code in a working sample project. You can download the project at the top of this article.
I hope you find this article really useful, see ya.
Special thanks
Special thanks to my friend SloNN for the message box hint.
History
- 20.06.2007 - sample project added and code samples changed to be stable and tested.