Control within a control & Subclassing with a cool example






4.85/5 (24 votes)
A list control displaying directories and files as on typing the path in your Rich Edit control and a subclassed Color256 dialog.
Introduction
Controls within a control with a cool example – “Do remember, controls are nothing but child windows. They can get all messages that a window can get.” This article will go along with a RichEdit control example. In the example, a rich edit control is subclassed and a list box is used as a child control, i.e., a ListBox within a RichEdit control.
Subclassing a control:
Subclassing a control is nothing but having your own class derived from an existing control class (like CButton
, CRichEditCtrl
, CEdit
…). By subclassing a control, you can add your own functionality to that control, as you need. For example, you can change the background color of a control as you paint the background on OnEraseBackground
handler and return true
. Only, simple thing to make it happen is, let the control variable (object) be created from your class (subclassed). For example, instead of CEdit m_eEdit
, let it be CMyEdit m_eEdit
, here CMyEdit
is derived from CEdit
. Or you can use the SubclassDlgItem
method of the CWnd
class like m_eEdit.SubclassDlgItem(IDC_EDIT1,this)
. (Refer MSDN for more info on subclassing).
Example Details:
In our example, CRichEditCtrl
has been subclassed into my own class CMyRichEditCtrl
. The theme of the work is to have a rich edit control that has to identify and display the directory/file path in a list box within it, if I am typing any drive letters in the system. For example, if I am typing “c:\” it has to list all the files and directories in that path in a listbox. It is just like the list we are having in our Visual C++ IDE – putting a period (dot) displays all the methods and variables of an object, or typing scope resolution operator (::
) to make a list of available functions, APIs and variables in that scope in a list box.
I used some string parsing functions inside the class. I am not sure that they are all well defined. May be they are. But, as the core is different let us keep it as a second thing.
Why RichEditCtrl?
No special reasons. I have done a sample Editor project using CRichEditCtrl
, and taken the code snap from there for the article, thatzaal. You can have the CEdit
control instead.
Detecting a drive letter
While typing in the edit control, we have to check whether any drive letter is typed or not in the format “driveletter:\”, if yes, then create a list box, show it with a list of available directories and files in that path, or if it is not a valid path/drive letter then do nothing. To do so, add OnKeyDown
or OnKeyUp
message handlers to your control class. Check each character on key down and the previous characters typed as follows:
void CMyRichEditCtrl::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { CString str; GetWindowText(str); // get rich control text long sl,el; GetSel(sl,el); long rpos=str.Find("\r\n",0); if(sl>rpos && rpos!=-1) SetSel(rpos,rpos); if(str.Mid(sl-1,1)=="\\" && nChar!=VK_ESCAPE && nChar!=VK_BACK) { long spos=ReverseFind(str,":",sl); // Find a : in back long bpos=ReverseFind(str,"\r\n",sl); if(spos!=-1 && spos>bpos) { long pos=ReverseFind(str," ",spos); // Find a space in back if(pos==-1 || pos<bpos) pos=bpos; CString path=str.Mid(spos-2,sl-(spos-2)); if(path.Find(":\\",0)!=-1) // if drive letter { ShowDirList(path); // show directory list } } } CRichEditCtrl::OnKeyUp(nChar, nRepCnt, nFlags); }
Creating a control inside a control:
void CMyRichEditCtrl::ShowDirList(CString sDir) { CPoint point=GetCaretPos(); // To position the list control if(!m_dirlist) // Check whether already created or not { m_dirlist= new CListBox(); m_dirlist->Create(WS_CHILD|WS_THICKFRAME| WS_VISIBLE|WS_HSCROLL| WS_VSCROLL|LBS_SORT| WS_BORDER|LBS_STANDARD, CRect(CPoint(point.x+5,point.y), CPoint(point.x+200,point.y+100)),this,1000); } else m_dirlist->SetWindowPos(&wndTop,point.x+5,point.y, 200,100,SWP_SHOWWINDOW); m_dirlist->ShowWindow(SW_SHOW); m_dirlist->SetFocus(); m_dirlist->ResetContent(); m_dirlist->Dir(DDL_READWRITE|DDL_DIRECTORY, _T(sDir+"*.*")); if(m_dirlist->GetCount()==0) m_dirlist->ShowWindow(SW_HIDE); else m_dirlist->SetCurSel(0); }
The following code will create a child control inside the rich edit control:
m_dirlist= new CListBox();
m_dirlist->Create(WS_CHILD|WS_THICKFRAME|....................);
If we want to customize the listbox, subclass the CListBox
control class and make your own CMyListBox
class and do whatever you want (like, you can add icons to the list items as in VC++ IDE listboxes).
To make a list box to list the directories and files in a specified path:
m_dirlist->Dir(DDL_READWRITE|DDL_DIRECTORY, _T(sDir+"*.*"));
Subclassing Example:
The above dialog will be shown if you click the ShowDlg button in the example. This dialog is just repainted with colors and is used to pick a color from 256 colors in it. This dialog is customized and used in a project of mine. CColorDlg_256
is derived from the CDialog
class. OnEraseBackground
handler is used to repaint colors. OnMouseMove
handler can detect which color has been selected.
The following code is the 256 color generator:
COLORREF CColorDlg256::GetRGBColor(int nColor256) { switch(nColor256) { case 0:return RGB(255,0,0); //black case 1:return RGB(255,0,0); //Red case 2:return RGB(255,255,0); //Yellow case 3:return RGB(0,255,0); //Green case 4:return RGB(0,255,255); //Cyan case 5:return RGB(0,0,255); //Blue case 6:return RGB(255,0,255); //Pink case 7:return RGB(255,255,255); //White case 8:return RGB(125,125,125); //Dark Gray case 9:return RGB(192,192,192); //Light Gray case 250:return RGB(85,85,85); // Dark Gray case 251:return RGB(125,125,125); case 252:return RGB(155,155,155); case 253:return RGB(192,192,192); case 254:return RGB(220,220,220); case 255:return RGB(255,255,255); // White } int red=240,green=0,blue=0; // Start with red for(int i=10;i<250;i++) { if(i==nColor256) return RGB(red,green,blue); if(red==240 && green<240 && blue==0 ) // Green incrementaion towards Yellow green+=6; else if(red>0 && green==240 && blue==0) // Red decrementation towards Green red-=6; else if(red==0 && green==240 && blue<240) // Blue incrementation towards Cyan blue+=6; else if(red==0 && green>0 && blue==240) // Green decrementation towards Blue green-=6; else if(red<240 && green==0 && blue==240) // Red incrementation towards Pink red+=6; else if(red==240 && green==0 && blue>0) // Blue decrementation towards Red blue-=6; } return RGB(0,0,0); // Return Black }
Everything is drawn... the color rects, frame like borders, frame captions. All are drawn in the OnEraseBackground
handler. To prevent the handler itself from drawing the original background again, its return
statement has been commented and TRUE
returned.
BOOL CColorDlg256::OnEraseBkgnd(CDC* pDC) { CRect rect; GetClientRect(rect); CBrush bkbr;bkbr.CreateSolidBrush(GetSysColor(COLOR_BTNFACE)); pDC->FillRect(rect,&bkbr); CRect sqrRect(rect.left+8,rect.top+10,0,0); CFont font;font.CreateFont(13,0,0,0,0,0,0,0,0,0,0,0,0,"small font"); pDC->SelectObject(&font); for(int i=1;i<=255;i++) { CRect frect(rect.left+20,rect.top+20,rect.left+30,rect.top+30); COLORREF rgb=GetRGBColor(i); CBrush br;br.CreateSolidBrush(rgb); CRect border(frect.left-1,frect.top-1,frect.right+1,frect.bottom+1); CPen pen; if(rgb==m_cSelectedColor) { pen.CreatePen(PS_DOT,3,RGB(0,0,0)); pDC->SelectObject(&pen); } else { pen.CreatePen(0,0,RGB(0,0,0)); pDC->SelectObject(&pen); } pDC->Rectangle(border); pDC->FillRect(frect,&br); rect.left+=13; if(i==249) { sqrRect.right= frect.right+10; sqrRect.bottom =frect.bottom+10; CBrush *brush=CBrush::FromHandle((HBRUSH)GetStockObject(HOLLOW_BRUSH)); pDC->SelectObject(brush); pDC->Rectangle(sqrRect); pDC->SetBkColor(GetSysColor(COLOR_BTNFACE)); CString ts ="Pallette"; pDC->TextOut(sqrRect.left+10,sqrRect.top-7,ts); GetClientRect(rect); rect.left=(9*20)+55; sqrRect=CRect(rect.left+8,rect.top+10,0,0); } else if(i==255) { sqrRect.right= frect.right+10; sqrRect.bottom =frect.bottom+5; CBrush *brush=CBrush::FromHandle((HBRUSH)GetStockObject(HOLLOW_BRUSH)); pDC->SelectObject(brush); pDC->Rectangle(sqrRect); pDC->SetBkColor(GetSysColor(COLOR_BTNFACE)); CString ts ="Gray Scale"; pDC->TextOut(sqrRect.left+10,sqrRect.top-7,ts); } else if(i==9) { sqrRect.right= frect.right+10; sqrRect.bottom =frect.bottom+5; CBrush *brush=CBrush::FromHandle((HBRUSH)GetStockObject(HOLLOW_BRUSH)); pDC->SelectObject(brush); pDC->Rectangle(sqrRect); pDC->SetBkColor(GetSysColor(COLOR_BTNFACE)); CString ts ="Basic Colors"; pDC->TextOut(sqrRect.left+10,sqrRect.top-7,ts); rect.top+=35; rect.left=0; sqrRect.top=frect.bottom+15; } else if((i-9)%24==0 && i>9) { rect.top+=13; rect.left=0; } } return 1; //return CDialog::OnEraseBkgnd(pDC); }
Hence subclassing is nothing but extending the functionality of a class, that is what we are calling as Inheritance.
CRichEditCtrl --------Subclassed -----> CMyRichEditCtrl
CDialog --------Subclassed -----> CColorDlg_256
And every control can have its own child control and can get all the messages that we are getting for a parent window.
Conclusion
This article may not be that much detailed. None of the articles can satisfy one's expectations. But, each article should be a seed for your technical growth. Thus, I believe that this would be a seed. Thank you all.