📄 multiline_edit_subitems.shtml.htm
字号:
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<META NAME="Author" CONTENT="Zafir Anjum">
<TITLE>CListCtrl - Multiline Editable Subitems</TITLE>
</HEAD>
<body background="../fancyhome/back.gif" tppabs="http://www.codeguru.com/fancyhome/back.gif" bgcolor="#FFFFFF" link="#B50029" vlink="#8E2323" alink="#FF0000" bgproperties="fixed">
<table WIDTH="100%">
<tr WIDTH="100%">
<td><td>
</tr>
</table>
<CENTER>
<H3>
<FONT COLOR="#AOAO99">Multiline Editable Subitems</FONT></H3></CENTER>
<HR>
This article was contributed by <A HREF="mailto:rex@surfutah.com">Rex Myer</A>.
<P>This article illustrates two points: another method of implementing
subitem editing and the multiline edit control used in place of the single
line edit control. The basis of this implementation comes from the
article, "<A HREF="edit_subitems.shtml.htm" tppabs="http://www.codeguru.com/listview/edit_subitems.shtml">Editable
Subitems</A>".
<BR>Usage Tip: To insert a return in the edit control without finishing the edit,
type Ctrl-Enter.
<P><IMG SRC="multiedit.gif" tppabs="http://www.codeguru.com/listview/multiedit.gif" BORDER=1>
<P>This is part of an implementation for a custom drawn <A HREF="grid_list_control.shtml.htm" tppabs="http://www.codeguru.com/listview/grid_list_control.shtml">grid list
control</A>. You
can download the full gridlist control implementation at the bottom of
the page. While this is based on the implementation of the Editable Subitems
article, I chose to implement the newest (v4.72) common control features.
In order to do the same, you will need to download the <A HREF="http://www.microsoft.com/msdn/downloads/files/40Comupd.htm">common
control redistributable</A> in order to run the sample. In order to build
the sample, you will also
need the <A HREF="http://www.microsoft.com/msdn/sdk/bldenv.htm">Platform
SDK build environment</A>. There is also <A HREF="http://www.microsoft.com/msdn/sdk/inetsdk/help/itt/CommCtls/CommCtls.htm#book_cc">documentation</A>
on the new common control features there as well. All you need from
the build environment are the CommCtrl.h and the ComCtl32.lib files. Make these available to
your VC++ build environment. I did this by backing up my old files
and copying these into my include and lib directories respectively.
<BR>In this explanation, I call out certain functions from the grid list control sample which
pertain to this article.
<H4>
Step 1: Derive a class from CListCtrl</H4>
You will need to first derive a class from CListCtrl. I derived CGridListCtrl.
You will need to add a member variable to keep track of the current column
which the user is interacting with. This variable is the column number
of the item also called the column order. Keep in mind that this may not be the same as the
subitem when dragging the columns and changing their order.
<PRE><TT><FONT COLOR="#990000">// Attributes
public:
// The current subitem or column number which is order based.
int m_CurSubItem;</FONT></TT></PRE>
<H4>
Step 2: Enable user to edit the control</H4>
In order to access the editing of the cell, the user can do it through the
mouse or keyboard. For more information on the cell navigation with
the keyboard see the article, "Grid List Control." For the mouse click,
we must first determine which subitem was clicked. The new common
controls have a message which is macro-ed as ListView_SubItemHitTest that
does the job nicely. The function below also uses the Header_OrderToIndex
macro to resolve the subitem clicked to an order variable. Also defined
for this control is the IndexToOrder which goes the other direction in
resolving the index to a column order. After we determine that we want
to edit, we send a message to the parent indicating the beginning of label
editing.
<PRE><TT><FONT COLOR="#990000">void CGridListCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
LVHITTESTINFO ht;
ht.pt = point;
// Test for which subitem was clicked.
// Use macro since this is new and not in MFC.
int rval = ListView_SubItemHitTest( m_hWnd, &ht );
// Store the old column number and set the new column value.
int oldsubitem = m_CurSubItem;
m_CurSubItem = IndexToOrder( ht.iSubItem );
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
// Make the column fully visible.
// We have to take into account that the columns may be reordered
MakeColumnVisible( Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem ) );
// Store old state of the item.
int state = GetItemState( ht.iItem, LVIS_FOCUSED );
// Call default left button click is here just before we might bail.
// Also updates the state of the item.
CListCtrl::OnLButtonDown(nFlags, point);
// Bail if the state from before was not focused or the
// user has not already clicked on this cell.
if( !state
|| m_CurSubItem == -1
|| oldsubitem != m_CurSubItem ) return;
int doedit = 0;
// If we are in column 0 make sure that the user clicked on
// the item label.
if( 0 == ht.iSubItem )
{
if( ht.flags & LVHT_ONITEMLABEL ) doedit = 1;
}
else
{
doedit = 1;
}
if( !doedit ) return;
// Send Notification to parent of ListView ctrl
CString str;
str = GetItemText( ht.iItem, ht.iSubItem );
LV_DISPINFO dispinfo;
dispinfo.hdr.hwndFrom = m_hWnd;
dispinfo.hdr.idFrom = GetDlgCtrlID();
dispinfo.hdr.code = LVN_BEGINLABELEDIT;
dispinfo.item.mask = LVIF_TEXT;
dispinfo.item.iItem = ht.iItem;
dispinfo.item.iSubItem = ht.iSubItem;
dispinfo.item.pszText = (LPTSTR)((LPCTSTR)str);
dispinfo.item.cchTextMax = str.GetLength();
GetParent()->SendMessage( WM_NOTIFY, GetDlgCtrlID(),
(LPARAM)&dispinfo );
}</FONT></TT></PRE>
<PRE><TT><FONT COLOR="#990000">int CGridListCtrl::IndexToOrder( int iIndex )
{
// Since the control only provide the OrderToIndex macro,
// we have to provide the IndexToOrder. This translates
// a column index value to a column order value.
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
int colcount = pHeader->GetItemCount();
int *orderarray = new int[ colcount ];
Header_GetOrderArray( pHeader->m_hWnd, colcount, orderarray );
int i;
for( i=0; i<colcount; i++ )
{
if( orderarray[i] == iIndex )
return i;
}
return -1;
}</FONT></TT></PRE>
The user should be able to start editing through the keyboard as well.
The code below overrides the PreTranslateMessage function. As in Explorer,
F2 commences nondestructive editing.
<PRE><TT><FONT COLOR="#990000">
BOOL CGridListCtrl::PreTranslateMessage(MSG* pMsg)
{
if(pMsg->message == WM_KEYDOWN)
{
// Handle the keystrokes for the left and right keys
// to move the cell selection left and right.
// Handle F2 to commence edit mode from the keyboard.
// Only handle these if the grid control has the focus.
// (Messages also come through here for the edit control
// and we don't want them.
if( this == GetFocus() )
{
switch( pMsg->wParam )
{
case VK_LEFT:
{
// Decrement the order number.
m_CurSubItem--;
if( m_CurSubItem < -1 )
{
// This indicates that the whole row is selected and F2 means nothing.
m_CurSubItem = -1;
}
else
{
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
// Make the column visible.
// We have to take into account that the header
// may be reordered.
MakeColumnVisible( Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem ) );
// Invalidate the item.
int iItem = GetNextItem( -1, LVNI_FOCUSED );
if( iItem != -1 )
{
CRect rcBounds;
GetItemRect(iItem, rcBounds, LVIR_BOUNDS);
InvalidateRect( &rcBounds );
}
}
}
return TRUE;
case VK_RIGHT:
{
// Increment the order number.
m_CurSubItem++;
CHeaderCtrl* pHeader = (CHeaderCtrl*) GetDlgItem(0);
int nColumnCount = pHeader->GetItemCount();
// Don't go beyond the last column.
if( m_CurSubItem > nColumnCount -1 )
{
m_CurSubItem = nColumnCount-1;
}
else
{
// We have to take into account that the header
// may be reordered.
MakeColumnVisible( Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem ) );
int iItem = GetNextItem( -1, LVNI_FOCUSED );
// Invalidate the item.
if( iItem != -1 )
{
CRect rcBounds;
GetItemRect(iItem, rcBounds, LVIR_BOUNDS);
InvalidateRect( &rcBounds );
}
}
}
return TRUE;
case VK_F2: // Enter nondestructive edit mode.
{
int iItem = GetNextItem( -1, LVNI_FOCUSED );
if( m_CurSubItem != -1 && iItem != -1 )
{
// Send Notification to parent of ListView ctrl
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
CString str;
// We have to take into account that the header
// may be reordered.
str = GetItemText( iItem, Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem ) );
LV_DISPINFO dispinfo;
dispinfo.hdr.hwndFrom = m_hWnd;
dispinfo.hdr.idFrom = GetDlgCtrlID();
dispinfo.hdr.code = LVN_BEGINLABELEDIT;
dispinfo.item.mask = LVIF_TEXT;
dispinfo.item.iItem = iItem;
// We have to take into account that the header
// may be reordered.
dispinfo.item.iSubItem = Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem );
dispinfo.item.pszText = (LPTSTR)((LPCTSTR)str);
dispinfo.item.cchTextMax = str.GetLength();
// Send message to the parent that we are ready to edit.
GetParent()->SendMessage( WM_NOTIFY, GetDlgCtrlID(),
(LPARAM)&dispinfo );
}
}
break;
default:
break;
}
}
}
return CListCtrl::PreTranslateMessage(pMsg);
}
</FONT></TT></PRE>
<H4>
Step 3: Add supporting function</H4>
If the user clicks on a column that is not fully visible, make it visible.
The function MakeColumnVisible makes the column visible by scrolling the control. It gets the current column ordering
to do this.
<PRE><TT><FONT COLOR="#990000">
void CGridListCtrl::MakeColumnVisible(int nCol)
{
if( nCol < 0 )
return;
// Get the order array to total the column offset.
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
int colcount = pHeader->GetItemCount();
ASSERT( nCol < colcount );
int *orderarray = new int[ colcount ];
Header_GetOrderArray( pHeader->m_hWnd, colcount, orderarray );
// Get the column offset
int offset = 0;
for( int i = 0; orderarray[i] != nCol; i++ )
offset += GetColumnWidth( orderarray[i] );
int colwidth = GetColumnWidth( nCol );
delete[] orderarray;
CRect rect;
GetItemRect( 0, &rect, LVIR_BOUNDS );
// Now scroll if we need to expose the column
CRect rcClient;
GetClientRect( &rcClient );
if( offset + rect.left < 0 || offset + colwidth + rect.left > rcClient.right )
{
CSize size;
size.cx = offset + rect.left;
size.cy = 0;
Scroll( size );
rect.left -= size.cx;
}
}
</FONT></TT></PRE>
<P>
<H4>
Step 4: Handle the scroll messages</H4>
The CInPlaceEdit class sends the end label edit message when it loses focus.
Clicking on the scrollbars of the ListView
control does not take away the focus from the edit control. We therefore
add handlers for the scrollbar messages which force focus away from the
edit control by setting the focus to the list view control itself.
<PRE><TT><FONT COLOR="#990000">void CMyListCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if( GetFocus() != this ) SetFocus();
CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
}
void CMyListCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if( GetFocus() != this ) SetFocus();
CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
}</FONT></TT></PRE>
<H4>
Step 5: Add positioning function for the editing control</H4>
In case you have not noticed by now, the list control does not handle
the beginning and ending label edit messages. This is unlike the "Edit Subitems" article.
The reason for having the list control's parent handle the edit control
instantiation and destruction is so that controls that take a long time to instantiate
can be instantiated once and reused by the parent control. This might
come in handy if a combobox was used and had 2,000 entries for instance.
Since the parent creates the editing control, the list control
still needs to position the control in the cell it is editing. That is
what this function does.
<PRE><TT><FONT COLOR="#990000">BOOL CGridListCtrl::PositionControl( CWnd * pWnd, int iItem, int iSubItem )
{
ASSERT( pWnd && pWnd->m_hWnd );
ASSERT( iItem >= 0 );
// Make sure that the item is visible
if( !EnsureVisible( iItem, TRUE ) ) return NULL;
// Make sure that nCol is valid
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
int nColumnCount = pHeader->GetItemCount();
ASSERT( iSubItem >= 0 && iSubItem < nColumnCount );
if( iSubItem >= nColumnCount ||
// We have to take into account that the header may be reordered
GetColumnWidth(Header_OrderToIndex( pHeader->m_hWnd,iSubItem)) < 5 )
{
return 0;
}
// Get the header order array to sum the column widths up to the selected cell.
int *orderarray = new int[ nColumnCount ];
Header_GetOrderArray( pHeader->m_hWnd, nColumnCount, orderarray );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -