Back to Amir Israeli Homepage

[Subclassed Controls : Several useful Subclassed controls]

This article was contributed by Amir Israeli Amir Israeli.
Amir Israeli Web Site
Environment: Win 95/98 VC6 SP4

Subclassed Controls described in this article :
CMemFileEx (Memory mapped file)
CMultiLineEditCtrl (persistence and string conversions)
CPictureMgr (wrapper for IPicture)
CStaticAnim (CStatic that does animation with dib-section)
CStaticLink (CStatic that implements links)
CStaticLogo (CStatic that draws some text ...)
CStaticPicture (CStatic that implemets CPictureMgr)
CToolTipEx (Extended Tooltip that does everything , recommended !)
CWindowTip (CWnd that imitates data tips)
CHistoryMgr (CStringArray derived to deal with history files+resources)

About:
CStaticLink is based on CStaticLink of Paul DiLascia MSJ 1998 Paul DiLascia
CWindowTip is based on CStaticLink of Paul DiLascia MSJ 1998
CPictureMgr is based on ImageViewer of Chesnu Chesnu

This is a just a partial documentation , This documentation will help you using these controls and discusses some of the features , For full documentation and updates look at the source code or Go here Your questions mail here I'll answer them as soon as possible in my site.

CMemFileEx
This class containds only 5 additinal functions (to those in CMemFile): the most importent ones are:

BOOL CMemFileEx::LoadResource( LPCTSTR lpszName, LPCTSTR lpszType);
BOOL LoadResource( UINT uResourceID, LPCTSTR lpszType);
This function allows It allows easy load of resources , for example loading a bitmap named IDB_BITMAP1 :
LoadResource(IDN_BITMAP1,RT_BITMAP)
Another importent function is CopyResourceToHGLOBAL()
It allows coping of the resource data (any CMemFile data bits ) to HGLOBAL . In this case you are responsible to call ::GlobalFree(HGLOBAL) (This is usefull if you want to create IStream object to be used somehow ... IPersistStream ... IPicture -see next in CPictureMgr)
The class is self-contained therefore you dont have to delete memory yourself and some other time-consuming resource functions . (CPictureMgr does have 2 functions for loading custom pictures : every one uses a ddifferent method : 1 function uses this object to load a resource and the second uses resource functions (a much longer function)
A good use to this class is set an object of that type in some function and load a Custom resource (or any other) , An example for that is "CRegistrySerialize" class that you can find in my site : this class allows you to create a "Map" of registry entries . and use as a resource , It uses this class to load the map and then it does its' actions (persistence of data).

CMultiLineEditCtrl
This class subclasses CEdit , it adds features of multilined control: the next functions allow you to set text in an easy form :

BOOL operator=(LPCTSTR lpszText);
BOOL operator=(CString &lpszText);
2 functions allow you to set a string from a different location :
BOOL ReadFromFile( LPCTSTR lpszFileName); // get string from text file
BOOL LoadFromResource( UINT uResourceName); // get from resource
Get the string ...
BOOL SaveToFile( LPCTSTR lpszFileName, BOOL bOverriteExisting = TRUE); // save string to text file
CString GetMultiText();
In general all work is done in 2 protected functions :
BOOL SetMultiLineText( LPCTSTR lpszText);
BOOL GetMultiLineText( CString &rString);
The first is called whenever you set a new string in the control . it replaces '\n' (CR) characters with '\r'(LF)+''\n' whenever the CR isn't preceeded with LF; : each CR (13)=> 10+13.
The second function does the oposite : it removes the '\r' character from sequences of "\r\n" , and then returns the text.
It supports serialization , to save code and time the functions "SaveToFile" + "ReadFromFile" use Serialize to do the job.

CPictureMgr
This class allows loading of pictures from resources and external files . The target is using jpeg pictures in your aopplication instead of bitmaps . by that your modules will become smaller with better load time and memory consumption . especially when 256 + color bitmaps are involved . (Its gread to use in Win32 wizards insead of bitmap picture) A person loads a picture with the help of these 3 functions : as you see the default location to place any jpg/jpeg image is under JPEG resource type . (I didnt use the RT_HTML : used by CHtmlView)

BOOL LoadFromResource( LPCTSTR lpResourceName, LPCTSTR lpResourceType = _T("JPEG"), BOOL bUseMemFile = TRUE);
BOOL LoadFromResource( UINT uID, LPCTSTR lpResourceType = _T("JPEG"), BOOL bUseMemFile = TRUE);
BOOL LoadPicture( LPCTSTR lpszFileName);
Loading of picture resources is done by 2 methods : the third parameter in both the 2 first functions "LoadFromResource(..., ,BOOL bUseMemFile = TRUE) tells which method to use : selected at :
BOOL CPictureMgr::LoadFromResource( LPCTSTR lpResourceName, LPCTSTR lpResourceType, BOOL bUseMemFile)
{
	ResetContent(); // previous image is deleted
	// The previous image should be unloaded.
	ASSERT(m_pPicture == NULL);

	HGLOBAL hGlobal = (bUseMemFile) ? 
			GetGlobalFromMemoryFile( lpResourceName, lpResourceType) :  //the first method
			GetGlobalFromResourceFunctions( lpResourceName, lpResourceType); //resource functions (I this\nk this method is quicker)

	return LoadFromGlobal(hGlobal);
}
. The default method first loads the resource to a memory mapped file of type CMemFileEx then it copies the data to HGLOBAL , when the function return , it returns HGLOBAL , that holds a copy of the memory , and the memory mapped file is destroyed . Notice : for a short while there are 2 copies of the data (I dont use detach because CMemFile doesn't use GlobalAlloc (I didn't override the behaviour of Alloc ...)
The second method is using directly resource functions : it loads the resource , and Allocates a global memory (with GlobalAlloc) then , copies the resource data to the new handle . (the resource can't be use because returned handle is NOT HGLOBAL (it is Win16 handle cast to HGLOBAL). Both functions return HGLOBAL handle of picture.
The function
BOOL CPictureMgr::LoadFromGlobal(HGLOBAL hGlobal)
creates a stream object from this HGLOBAL handle. then loads the picture form IStream. (OleLoadPicture).

You need to set the parent window as the target of all operations :
CWnd* SetWindow( CWnd *pNewWnd, BOOL bWindowIsView = FALSE);
if window is a view you allow the use of some more drawing functions ,

void OnPrepareDC(CDC* pDC, CPrintInfo* pInfo);
BOOL OnEraseBkgnd(CDC *pDC);
void OnUpdate( CView *pSender, LPARAM lHint, CObject *pHint);
There are several more functions:
GetType(short* ptype);
BOOL GetType(short* ptype);
BOOL GetAttributes(DWORD* pdwAttr);
CSize GetSize()
History support will soon be full .

Drawing is done by an internal function "InternalDraw" and can be called from "OnDraw" , "OnPaint" : both functions exist to make your call intuitive.

void OnDraw(CDC *pDC) {	InternalDraw(pDC); }
void OnPaint(CDC *pDC) { InternalDraw(pDC); }

How to use?
You need to declare CPictureMgr object as a member variable of a class contains a window (any CWnd derived class) (the most common is CStatic and CView(CScrollView)) then you call SetWindow(this,TRUE/FALSE) : TRUE = when it is a view :it will enable some functions , and allow you to use them . If you look into the code of CStaticPicture you see that it Creates CPictureMgr object as a member variable . It calls SetWindow(this) /* + FALSE = default parameter */ in 2 places : in SubclassDlgItem(...) for subclassed control and in OnCreate (in cas you create it programmaticly)

int CStaticPicture::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CStatic::OnCreate(lpCreateStruct) == -1)
		return -1;
	::DragAcceptFiles( GetSafeHwnd(), TRUE); //it allows a primitive form of drag and drop
	mgr.SetWindow(this);                //this feature is still not fully implemented
	return 0;
}
A feature such as a history support should be generic to include support to file-paths and resources. It will be useful for views to have some already-built history support , and less for CStatic. "OnContextMenu" will allow you to create a Menu list of files (from history) and choose a new one. Currently this handler is disabled.
for more documentation read source code.

CStaticAnim
This control create animation within static control , by painting a series of bitmaps in that control , in a predefined frequency. All values and behaviours of that control have default values. (General values that are quite acceptable by any application . example : default frequency is 60 milli-seconds : ~15 pictures will be presented every second ... )
How to use?
1) you create the control or subclasses it (within a dialog template)
2) Sets a bitmap to be used.

BOOL SetBitmap( LPCTSTR lpszResourceName, int cx, int cy, BOOL bAdjustWindowSize = TRUE);
BOOL SetBitmap( UINT nIDResource, int cx, int cy, BOOL bAdjustWindowSize = TRUE);
The image must be a bitmap , it is treated as a dib-section , loaded with :
HBITMAP hbm = (HBITMAP)::LoadImage( AfxGetInstanceHandle(),
		lpszResourceName, IMAGE_BITMAP,	0, 0, LR_CREATEDIBSECTION);
The variables : cx,cy (int values) tell the width and height of every image , You must specify the correct values : the object save these values and uses them to calculate the boundries of the area that will be copied to client DC on each step. (a step is a call for a new paint of the client area : called from OnTimer() ).
BOOL bAdjustWindowSize allows you to call MoveWindow(...) and reset the size of window to fit exactly the size of image . This is importent to have nice animations especially if control resides in a rebar control .
2 public member functions allow you to start or end animation :
Play(); //creates a timer , stores it's id and a member variable to: bPlaying = TRUE;
Pause() //kills the timer , reset the id and status (0,FALSE)
This is usefull if you want to control programmaticly the appearence and behaviour of object. for example : If you application submits(recieves) data over a network , while submitting , animation work , when it stops , animation stop . (just like a browser).

Functions such as "SetDirection" ... are part of the mechanism that does the animation : a very simple one : if there are 20 pictures (count of images is calculated in the end of SetBitmap()) if walks along : 0, 1, 2, 3, ... 18, 19, 18, 17, 16, 1, 0, 1, ... by that there is continuious affect. (The example applications contains 4 bitmap resources imported from a dll : msoleres.dll (I think that Microsoft Internet applications (also Microsoft Outlook Express) uses these bitmaps to do efficient animations. (In the VB examples you can also find an example of animation that is done with imagelist so I this this method is somewhat prefered to that of using an animation control. (and it takes MUCH less space).

DIRECTION CStaticAnim::SetDirection( DIRECTION iNewDirection)
int CStaticAnim::SetPosition( int iNewPosition) 
The application first creates a member variable of type CStaticAnim within the dialog class (also calls #include...), then creates the animation in WM_INITDIALOG handler (OnInitDialog())
m_Anim1.SubclassDlgItem( IDC_ANIM1, this);
m_Anim1.SetBitmap( IDB_BITMAP1, 22, 22); //sizes of each image : you know it by dividing : (max(width,length)/(number of images))
m_Anim2.SubclassDlgItem( IDC_ANIM2, this);
m_Anim2.SetBitmap( IDB_BITMAP2, 38, 38);
m_Anim3.SubclassDlgItem( IDC_ANIM3, this);
Play is NOT automatic you need an additional call to start it . you may add it at the end of OnInitDialog or OnCreate().

CStaticLink
CStaticLink objects imalements Link connections , and is based on another (Great) control created by Paul Dilascia. This control implements 2 more features :
1) Underlined font is used when the mouse is hovering over the control.
2) Internal Tip support CWindowTip(tracking data tips) that can be disabled is you want to use external tooltip support (use CToolTipEx object)
Tracking mouse position isn't done with a timer but by tracking the WM_MOUSEMOVE messages and corresponding status variable (enum MOUSE_STATE : MOUSE_OUT = 0 , MOUSE_OVER) and the given point. the object state is initialized to MOUSE_OUT . and then mouse movements are tracked :

void CStaticLink::OnMouseMove(UINT nFlags, CPoint point) 
{
	CStatic::OnMouseMove(nFlags, point);
	// 1. Mouse has moved and we are not tracking this button, or 
	// 2. mouse has moved and the cursor was not above this window
	// == Is equivalent to WM_MOUSEENTER (for which there is no message)
	if ((GetCapture()!=this) && (GetState() == MOUSE_OUT))
		OnMouseEnter();
	else {
		if (GetState() == MOUSE_OVER) {
			CRect rc;
			GetClientRect(&rc);
			if (!rc.PtInRect(point)) // The mouse cursor is no longer above this button
				OnMouseLeave();
		}
	}
}
When you use this object you need to set an ID for the internal object CWindowTip. Its ID is shered (static) , you MUST set it BEFORE you use tips .
CStaticLink::SetTipID(ID_WINDOW_TIP); // setting tip ID
m_Link1.SubclassDlgItem( IDC_LINK1, this, _T("Amir Israeli Homepage"), _T("https://amir-israeli.tripod.com/default.html"));
m_Link2.SubclassDlgItem( IDC_LINK2, this, _T("Amir Israeli Email"), _T("mailto:israelaq@walla.co.il"));
The parameters are ID, parent window , caption , link(+protocol) CWindowTip support is automaticly Enabled , an additional function call will allow you disable it.

CStaticLogo
This is a very simple control : it just draws text in a non standard way I saw in several commercial applications . you set the text dtrectly from
BOOL SubclassDlgItem(UINT nID, CWnd* pParent, LPCTSTR lpszText = NULL);//
or call explicitly (after creation) SetLogoText(LPCTSTR strText); A nice feature to add (if someone wants) is pallete "games" when mouse hovers, a person can add very cheap affects in terms of memory and cpu.

CStaticPicture
This object id derived from CStatic , it allows pictures of various formats be loaded from external files or (more recommended) from resources. and be presented in the client area. All functionality of resources is contained in a member variable , that is part of this object .Object CPictureMgr is declared as a member variable . You must set the owner that holds CPictureMgr with the function SetWindow(...). This object also support context menus , and History support . Still these features are'nt fully implemented. The object also serves as a drop target.
How to use?
Create an object by using Create(...) or using a dialog-resource (in this case you call SubclassDlgItem(...) , then call SetWindow(this,FALSE); you dont have to specify the second parameter.

BOOL CStaticPicture::SubclassDlgItem( UINT nID, CWnd* pParent)
{
	if (CStatic::SubclassDlgItem(nID, pParent)) {
		::DragAcceptFiles( GetSafeHwnd(), TRUE); //sets as a drop target
		mgr.SetWindow(this); //Most Importent !!! CPictureMgr uses that window!
		return TRUE;
	}
	return FALSE;
}
Since this control supports pictures of various formats : jpeg, wmf , You can use these formats instead of bitmaps , You will be able to insert great true-color pictures and you're exe (or library) will still be small.

CToolTipEx
CToolTipEx object allows you to do anything in a single line : it supports Data-tips , Tracking-tips (and non Tracking-tips) it also supports Rect tips (tips that are activated on part of a window and not a full window.
Create it by calling :

BOOL Create( CWnd* pParentWnd, BOOL bTrackState = FALSE, DWORD dwInitial = 500, DWORD dwAutoPop = 2000, BOOL bActivate = TRUE);

the second parameter defines whether the tip is tracking (TRUE) or not;

You add tips by calling :

BOOL AddTool( CWnd *pWnd, LPCTSTR lpszTip);
BOOL AddTool( CWnd *pWnd, UINT uToolID);
These functions check the type of tooltip control (tracking or non tracking) and addition of tip changes accordingly. The first parameter is the window to which you assign a tip , the second is text (or string id) The parent Window (In this application is the dialog need to call RelayEvent in its' PreTranslateMessage() , you needn't check the message (in a switch) CToolTipEx::RelayEvent(LPMSG lpMsg) checks the message and then sends it to
if (bTrackingState) { //
	CheckForTrack(lpMsg); //checks if needs to display a new tip , remove
} //the current one , change tip position
else
	CToolTipCtrl::RelayEvent(lpMsg); // non tracking tooltip
	break;
}
The function "CheckForTrack(LPMSG lpMsg)" needs to work very fast : it checks whether it tracked a tip , if it did (it saved the previous tracked handle of window) it checks whether the current position causes to track a different window : if TRUE the key is cancel tracking of previous window , and track a new one :
SendMessage( TTM_TRACKACTIVATE, (WPARAM)TRUE/FALSE /* state */, (LPARAM)&ti); //ti = CToolInfo staructure

The Application creates a tracking tip control in OnInitDialog() and then adds tips to the window :

if (tip.Create( this, TRUE)) { // 
	tip.AddTool( GetDlgItem(IDC_CHECK1), _T("IDC_CHECK1"));
	tip.AddTool( GetDlgItem(IDC_CHECK2), _T("IDC_CHECK2"));
	...
	tip.AddTool( GetDlgItem(IDC_CHECK7), _T("IDC_CHECK7"));
	tip.AddTool( GetDlgItem(IDC_CHECK8), _T("IDC_CHECK8"));
	tip.AddTool( GetDlgItem(IDC_LOGO), _T("CStaticLogo"));
	tip.AddTool( GetDlgItem(IDC_OLEPICTURE), _T("CStaticPicture"));
}

For more documentation and examples enter my site .

CWindowTip
This is a CWnd derived class that mimics the windows that are created by the tooltip control. It creats a window , Shows it , Hides .
It counts the time that tooltip is seen : after it passes the time it should be seen it is hide.

4 functions are available :

BOOL Create( CWnd* pParentWnd, UINT nID, int iTimeOfTip = 2000 , BOOL bTrackingMode = FALSE);
void SetText( LPCTSTR lpszText = NULL);
BOOL StartWindow();
BOOL EndWindow();
Create() : Creates the window sets the time that it will appear on screen (in case of non-tracking mode) if this is a tracking-mode tooltip then iTimeOfTip value is invalid.
SetText() : Sets a new text to the tip , tip is automaticly shown on screen. (StartWindow() is called)
StartWindow() + EndWindow() : functios to be used when showing or hiding a tip;
BOOL CWindowTip::StartWindow() //start a new window
{
	CreateTimer(); //create a timer (create does nothing in case of tracking-mode)
	MoveWindow(); //set the proper position (cursor position)
	return TRUE;
}
BOOL CWindowTip::EndWindow()
{
	if (!::IsWindow(m_hWnd))
		return TRUE;
	if (IsWindowVisible())
		ShowWindow(SW_HIDE); // the tooltip is now hidden
	StopTimer(); //stops the timer
	return TRUE;
}

The window tip functions are called from within the context of CStaticLink the only function that is called (Create() is called in the beginning) is SetText(LPCTSTR) whenever the mouse cursor enters a control. (or moves within it = in tracking mode tips)

This control isn't responsive as CToolTipEx tips , because it doesn't use call SetText() (that also updates position) from PreTranslateMessage() (Events aren't relayed) therefore the frequency of updates is much less . I used a timer with a frequency of 60 milliseconds (minimum frequency)

UINT WindowTip::uTimerInterval = 60; //frequency 

Read source for more ...

CHistoryMgr
This is a CStrinArray derived class that allows you to have some, history support. In general it allows you to insert resources (by type and name) , files . it keeps them as a complex string. (for ex. adds some prefixes ).
When you want to get an item , check first the count of items (GetCount()) then ask whether this item is file or resource . (IsFile(int nIndex)) if it is file/resource you need to call proper functions :

BOOL CHistoryMgr::GetFile(int nIndex, CString &strFileName)
{
	ASSERT(IsValid(nIndex));
	if (!IsFile(nIndex))  return FALSE;//not a file

	CString strFull = GetAt(nIndex);
	int iFind = strFull.ReverseFind(_T('/'));
	strFileName = strFull.Mid(iFind+1);
	return TRUE;
}
BOOL CHistoryMgr::GetResource(int nIndex, CString &strResourceName, CString &strResourceType)
{
	ASSERT(IsValid(nIndex));
	if (IsFile(nIndex))  return FALSE;//not a resource
	
	CString strFull = GetAt(nIndex);
	if (!strFull.GetLength())
		return FALSE;
	int iFind = strFull.ReverseFind(_T('.'));
	strResourceName	= strFull.Mid(iFind+1);

	iFind = strFull.ReverseFind(_T('/'));
	strResourceType = strFull.Mid(iFind+1);
	iFind = strResourceType.ReverseFind(_T('.'));
	strResourceType = strResourceType.Left(iFind);

	return TRUE;
}
You can also delete some string : by index or name. function RemoveAt is overloaded to allow delete of resource/string.

Read source for more ...

Notice
Documentation isn't complete for more , read source code and comments , There is some more ...

Downloads

Download demo project - 214 Kb
Download source - 31.5 Kb

History

Date Posted: August 4, 2000

Home  |  Links  |  About