Introduction
Here's a
simple class to make it easy to edit
list box entries in place. It isn't derived
from CListBox
,
but instead from CEdit
and designed to work with CListBox
.
Design
Philosophy
First a
word about the design philosophy that lead to this
simple class. A new edit
window is created everytime you edit
and item and
destroyed when you're done. In the past, I'd probably
would have created the edit
window once, then move it, show it, hide it etc. as
needed. Why, I thought, take on the overhead of window
creation and destruction over and over instead of just
once?
Here's why:
because its simpler and the potential performance gains
you get just don't matter. CLBEditorWnd has 9 member
functions, many of them trivial and the longest one is
less than 20 lines of non-comment code. The whole class,
including header file and comments is under 300 lines.
Adding it to the sample project took <10 lines of
code. This simplicity makes code much more
understandable and is valuable.
Don't get
me wrong. Having a responsive UI is really important,
but you don't get that by caching windows and fonts and
other GDI resources and reusing them whenever possible.
In some cases, the OS even does the caching for you. But
even if it doesn't, other design decisions have a much
more substantial impact on responsiveness.
Are you
making numerous synchronous calls to a database server
inside the message loop? That's going to hurt
responsiveness. Are you doing a lot of complex rendering
in OnDraw()? Don't. Use on off-screen blit map and only
do the blitting in OnDraw(). Use worker threads
liberally.
I could
continue this digression to discuss how many threads is
too many, but I'm getting farther afield than I planned
already. Use common sense. Now, back to the business at
hand.
Using
CLBEditorWnd
CLBEditorWnd
can be used with a two-line sequence:
CLBEditorWnd* pEditor = new CLBEditorWnd( &list_control_to_edit ) ;
pEditor->Edit( index_of_item_to_edit ) ;
That's it. CLBEditorWnd
cleans up after itself, so even though you orphan the
pointer, there will be no memory leak. When its done, CLBEditorWnd
will update the listbox with whatever changes are made
unless the user hits the escape key.
How it
works
Most of the
work is done in two member functions Edit()
and EndEditing()
.
bool CLBEditorWnd::Edit(
int n
)
{
if ( n < 0 || n >= m_pLB->GetCount() )
{
delete this ;
return false ;
}
m_edit_index = n ;
CString s ;
m_pLB->GetText( n, s ) ;
CRect r ;
m_pLB->GetItemRect( n, &r ) ;
r.InflateRect( 1, 1 ) ;
Create( WS_BORDER | WS_CHILD | WS_VISIBLE | ES_WANTRETURN, r, m_pLB, 1 ) ;
SetFont( m_pLB->GetFont()) ;
SetWindowText( s ) ;
SetCapture() ;
SetFocus() ;
return true ;
}
First Edit()
grabs the text and rectangle of the listbox
item and uses that as the CLBEditorWnd
text
and rectangle. It creates the window, sets focus and
capture to itself and returns to let the standard
message pump do its stuff.
void CLBEditorWnd::EndEditing(
bool b
)
{
if ( m_edit_ended )
return ;
m_edit_ended = true ;
ReleaseCapture() ;
if (b )
{
CString s ;
GetWindowText( s ) ;
m_pLB->DeleteString( m_edit_index ) ;
m_pLB->InsertString( m_edit_index, s ) ;
m_pLB->SetCurSel( m_edit_index ) ;
}
MSG msg ;
while ( ::PeekMessage( &msg, m_hWnd, 0, 0, PM_REMOVE ) )
;
DestroyWindow() ;
}
At some
point, EndEditing()
is called by some
boring MFC message
handling function. It releases the mouse and (usually)
replaces the appropriate item in the list box.
Everything else is just housekeeping, including delete
this
in the WM_NCDESTROY
handler.
One quirk
that CLBEditorWnd
has to deal with involves
message routing. Because changes of focus to another app
with alt-Tab and the like give the window no other
notice than a kill focus message, I had to use that as a
reason to call EndEditing()
. But other
actions cause EndEditing()
too, and
indirectly cause loss of focus potentially causing EndEditing()
to be called twice, once after the object is deleted.
Obviously,
this won't do. So EndEditing()
includes a PeekMessage()
loop to remove any unprocessed messages for the window.
Doesn't matter, somewhere inside the PeekMessage()
call, the kill focus notification is generated and
processed before I have a chance to delete it. So
there's a recursion prevention flag added to the class
as well.
Any
drawbacks?
As written,
it works only with listboxes, not list controls, tree
controls or anything else. Modifying the code to deal
with those other types should be fairly easy. It also
doesn't handle edit commands from the menu bar -- you
can use the context menu to cut/copy/paste and the
accelerator keys seem to work, but unless you take steps
to enable the menu items in your app, and route the
commands appropriately, you can't use the menu bar.
It does
provide what I need at the moment, a simple and
effective way to edit list box
items in place.