Inplace edit control

Have you ever wanted to allow users of your MFC application to rename strings contained in various controls? If so, you probably included a "Rename" button next to your control. Take a listbox for example. When "Rename" is pressed you retrieve the current selection from the listbox, display it in a dialog for the user to modify, and then modify the string in the listbox to the new string. Not a very aesthetic or efficient method.

A better solution is to dynamically create an edit control on top of the area where the displayed string is, preferably following a double-click of the string in the listbox. You could re-use this edit class throughout your application, thus removing the need for "Rename" buttons. This is common technique.

One method our SmartEdit can use to notify its parent that the user is done with it (<Enter> was pressed for example) is to send its parent a custom message. But how does the parent (in our example, a dialog with a list control) know what new string the user typed? We could pass "this" to our SmartEdit, which in turn could call a dialog function passing it the new string. Although convenient, this method would not make our SmartEdit very re-usable, since it would have to know a) the type of CDialog our parent is and b) every different parent dialog would need to implement this same function to accept the new string.

The method I chose was to post a message to the parent, essentially telling it "Hay, the user possibly modified this string you sent me. Here it is, do as you please." But right before posting this message to its parent, SmartEdit places the text on the clipboard. This way, SmartEdit doesn't care what kind of window its parent is, and the parent can handle it as it seems fit.

||||| Figure 1 "myDialog.cpp LBN_DBLCLK handler"

void myDialog::OnDblclkStringInListBox()
{
	const INT nIndex = m_Ctl.GetCurSel();
	if(nIndex == LB_ERR) return;
	CString string;
	m_Ctl.GetText(nIndex, string);
	RECT rect;
	INT result = m_Ctl.GetItemRect(nIndex, &rect);
	if(result==LB_ERR) return;
	SmartEdit* pEdit = new SmartEdit;
	rect.bottom += 4;
	pEdit->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
		rect, &m_Ctl, (UINT)-1);
	pEdit->SetWindowText(string);
	pEdit->SetFocus();
	pEdit->LimitText(MYMAX_LEN); // user defined maximum length of string
}

Figure 1 shows the ON_LBN_DBLCLK handler from the myDialog class. This is the handler called when the user double-clicks a string in the listbox. We first determine which, if any, of the listbox strings is currently selected. Note GetCurSel() can only be used with a single-selection listbox. The actual string is then retrieved from the listbox. The dimension of the item's rectangle is determined since this will be the size and position of our SmartEdit. The SmartEdit is created on the heap, and its Create() function called which creates the Windows edit control and attaches it to the CEdit object.

||||| Figure 2 "SmartEdit.cpp implementation"

SmartEdit::SmartEdit()  : bEscapeKey(FALSE)
{
}

BEGIN_MESSAGE_MAP(SmartEdit, CEdit)
//{{AFX_MSG_MAP(SmartEdit)
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()


void SmartEdit::OnKillFocus(CWnd*)
{
	PostMessage(WM_CLOSE, 0, 0);
	if(!bEscapeKey){
		CString str;
		GetWindowText(str);
		COleDataSource *pds = new COleDataSource;
		PTCHAR cp = (PTCHAR)GlobalAlloc(GMEM_FIXED, (str.GetLength() *
			sizeof(TCHAR)) + sizeof(TCHAR));
		_tcscpy(cp, str);
		pds->CacheGlobalData(CF_TEXT, cp);
		pds->SetClipboard();
		GetOwner()->PostMessage(EDITCLASSMSG);
		TRACE1("SmartEdit::OnKillFocus  Posting message to my owner, I have put
			[%s] on the clipboard for him\n", cp);
	}
}

// "override" base class member function
void SmartEdit::PostNcDestroy()
{
	delete this;
}

// "augment" base class member function
BOOL SmartEdit::PreTranslateMessage(MSG* pMsg)
{
	if(pMsg->wParam == VK_RETURN){
		PostMessage(WM_CLOSE, 0, 0);
		return TRUE;
	}else if(pMsg->wParam == VK_ESCAPE){
		PostMessage(WM_CLOSE, 0, 0);
		return bEscapeKey = TRUE;
	}
	
	return CEdit::PreTranslateMessage(pMsg);
}

Figure 2 shows SmartEdit's implementation. It is straightforward, but perhaps the PostNcDestroy function needs further explanation. Remember this object was created on the heap with "new" in myDialog. That means someone must "delete" it. Since PostNcDestroy() is called after the window has been destroyed, it is a perfect time to delete the object. Modeless dialogs use this method of self-destruction.

The most important action occurs in its OnKillFocus() handler. When the SmartEdit loses focus by a means other than the key, it retrieves the new string with GetWindowText(). The string is then placed on the clipboard using a COleDataSource. COleDataSource is a source actor in OLE data transfer. Notice I used PTCHAR, and _tcscpy() to allow the code to be transparantly compiled in both ANSI and UNICODE applications. After the string is placed on the clipboard, it sends EDITCLASSMSG (simply #defined as WM_APP + 100) to its parent.

||||| Figure 3 "myDialog.cpp EDITCLASSMSG handling code"

BOOL myDialog::PreTranslateMessage(MSG* pMsg)
{
	if(EDITCLASSMSG == pMsg->message){
		COleDataObject data;
		
		hClipboard()){           
			if(data.IsDataAvailable(CF_TEXT)){ 
				HGLOBAL hg;
				if(hg = data.GetGlobalData(CF_TEXT)){
					CString str = (LPCTSTR)GlobalLock(hg);
					TRACE1("myDialog::PreTrans(EDITCLASSMSG)  This is on the clipboard [%s]\n", str);
					GlobalUnlock(hg);
					NewString(str); // Appropriate action
				}
			}
			data.Release();
		}
		return TRUE;
	}
	
	return CDialog::PreTranslateMessage(pMsg);
}

Fogure 3 shows what myDialog does when handling a EDITCLASSMSG message. It knows there's a string on the clipboard, so uses COleDataObject to retrieve it. COleDataObject is the destination actor in OLE data transfer. The string from the clipboard is simply passed to a helper function, New String() in this case, which will decide a course of action. To replace the string that was 'underneath' the string double-clicked in the listbox, you would keep the item index obtained in OnDblclkStringInListBox(). For example, "const INT nIndex = m_Ctl.GetCurSel();" could be replaced by "m_nIndex = m_Ctl.GetCurSel();" after which NewString() would use myDialog's member variable 'm_nIdex' as the index for the new string.

We now have a fully functional SmartEdit capable of notifying its parent (using OLE) that it has accepted a string. One problem still remains. Can you spot it? Since the SmartEdit object 'kills' itself using 'delete' in PostNcDestroy(), it *must* be created on the heap using 'new'. If we don't enforce this, someone will inevitably use our SmartEdit in a DDX_Control(). This will cause serious havoc when the "delete this;" line is executed. So what can we do?

The answer is simple. make the ~SmartEdit destructor protected. This still allows the class to be used in inheritance, but prevents its usage on the stack.

Posted: March, 8, 98



Comments

  • 新作 ヴィトン

    Posted by pletcherzgk on 03/18/2013 06:02am

    [url=http://www.lvbagssaleonlinejapan.com]ルイヴィトン財布[/url]http://www.lvbagssaleonlinejapan.com [url=http://www.lvbagscheapsalejp.com]ルイヴィトン バッグ[/url]ルイヴィトン バッグ,ルイビトン ハンドバック,ルイヴィトン財布本物保証,さらに全品送料,おすすめ人気アイテムや新作の入荷情報をいち早くお届けしております.送料無料! [url=http://www.lvbagscheapsalejp.com]lvバッグ[/url]http://www.lvbagscheapsalejp.com

    Reply
  • lamichael james jersey

    Posted by Jimmyrc8nz on 11/03/2012 09:51am

    wvbzv brian urlacher jersey wzddf clay matthews jersey hkeez ryan grant jersey vvnsm michael turner jersey deuyt greg jennings jersey

    Reply
  • code around hClipboard

    Posted by rodrickhales on 04/13/2005 10:18am

    is the code along the hClipboard correct? if not, what is the correct code.

    Reply
  • it would be better if V..........

    Posted by Legacy on 09/02/2002 12:00am

    Originally posted by: deepjot

    It would b better if V use a staic function say GetText which returns a global variable of type CString defined in SmartEdit.cpp
    
    n the code would look like

    void SmartEdit::OnKillFocus(CWnd* pNewWnd)
    {
    PostMessage(WM_CLOSE, 0, 0);
    if(!bEscapeKey)
    GetWindowText(string);

    CEdit::OnKillFocus(pNewWnd);
    }
    'n'JOY

    Reply
  • How to Change the color of this edit control

    Posted by Legacy on 06/06/2002 12:00am

    Originally posted by: Maria

    How do I change the color of this edit control.
    if i use it in an application and want to change its color to blue if its noneditable.

    Reply
  • ExEditors Library

    Posted by Legacy on 03/19/2002 12:00am

    Originally posted by: Mike Philips

    A complete collection of editors in a single file:
    
    

    http://www.exontrol.com/sg.jsp?content=products/exeditors

    Regards,
    Mike

    Reply
  • why COleDataSource..

    Posted by Legacy on 04/25/2000 12:00am

    Originally posted by: Ape

    Its really good..
    but y using COledataSoure.. y not win32 API for clip board handling.. which is more simple right ?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds