// GridListCtrl.cpp : implementation file // #include "stdafx.h" #include "GridListCtrl.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CGridListCtrl CGridListCtrl::CGridListCtrl() { m_CurSubItem = -1; m_pListEdit =NULL; } CGridListCtrl::~CGridListCtrl() { if( m_pListEdit) { delete m_pListEdit; } } BEGIN_MESSAGE_MAP(CGridListCtrl, CListCtrl) //{{AFX_MSG_MAP(CGridListCtrl) ON_WM_LBUTTONDOWN() ON_WM_HSCROLL() ON_WM_VSCROLL() ON_NOTIFY_REFLECT(LVN_BEGINLABELEDIT, OnBeginlabeledit) ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, OnEndlabeledit) //}}AFX_MSG_MAP ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CGridListCtrl message handlers BOOL CGridListCtrl::PrepareControl(WORD wStyle) { m_wStyle = wStyle; ASSERT( m_hWnd ); DWORD dwStyle = GetWindowLong(m_hWnd, GWL_STYLE); dwStyle &= ~(LVS_TYPEMASK); dwStyle &= ~(LVS_EDITLABELS); // Make sure we have report view and send edit label messages. SetWindowLong( m_hWnd, GWL_STYLE, dwStyle | LVS_REPORT ); // Enable the full row selection and the drag drop of headers. DWORD styles = LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP ; // Use macro since this is new and not in MFC. ListView_SetExtendedListViewStyleEx(m_hWnd, styles, styles ); return TRUE; } 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); NMLISTVIEW nmlv; nmlv.hdr.code = LVN_COLUMNCLICK; nmlv.hdr.hwndFrom = GetSafeHwnd(); nmlv.hdr.idFrom = (UINT)(::GetMenu( GetSafeHwnd() )); nmlv.iItem = ht.iItem; nmlv.iSubItem = ht.iSubItem; nmlv.uNewState = 0; nmlv.uOldState = 0; nmlv.uChanged = 0; GetParent()->SendMessage( WM_NOTIFY, (WPARAM)0, (LPARAM)(&nmlv) ); // 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 ) { RedrawItems( rval, rval ); 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; // check if focused column has editable attribute if( GLCT_NORMAL == GetColumnType(ht.iSubItem) ) { 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 ); } 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 ); int offset = 0; int i; for( i = 0; orderarray[i] != iSubItem; i++ ) offset += GetColumnWidth( orderarray[i] ); int colwidth = GetColumnWidth( iSubItem ); delete[] orderarray; CRect rect; GetItemRect( iItem, &rect, LVIR_BOUNDS ); // 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; } rect.left += offset+4; rect.right = rect.left + colwidth - 3 ; // The right end of the control should not go past the edge // of the grid control. if( rect.right > rcClient.right) rect.right = rcClient.right; pWnd->MoveWindow( &rect ); return 1; } void CGridListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult) { // This function is called by the control in different // stages during the control drawing process. NMLVCUSTOMDRAW *pCD = (NMLVCUSTOMDRAW*)pNMHDR; // By default set the return value to do the default behavior. *pResult = 0; switch( pCD->nmcd.dwDrawStage ) { case CDDS_PREPAINT: // First stage (for the whole control) // Tell the control we want to receive drawing messages // for drawing items. *pResult = CDRF_NOTIFYITEMDRAW; // The next stage is handled in the default: break; case CDDS_ITEMPREPAINT | CDDS_SUBITEM: // Stage three (called for each subitem of the focused item) { // We don't want to draw anything here, but we need to respond // of DODEFAULT will be the next stage. // Tell the control we want to handle drawing after the subitem // is drawn. *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NOTIFYPOSTPAINT; } break; case CDDS_ITEMPOSTPAINT | CDDS_SUBITEM: // Stage four (called for each subitem of the focused item) { // We do the drawing here (well maybe). // This is actually after the control has done its drawing // on the subitem. Since drawing a cell is near instantaneous // the user won't notice. int subitem = pCD->iSubItem; // Only do our own drawing if this subitem has focus at the item level. if( (pCD->nmcd.uItemState & CDIS_FOCUS) ) { // If this subitem is the subitem with the current focus, // draw it. Otherwise let the control draw it. CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); // We have to take into account the possibility that the // columns may be reordered. if( subitem == Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem ) ) { // POSTERASE CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc); // Calculate the offset of the text from the right and left of the cell. int offset = pDC->GetTextExtent(_T(" "), 1 ).cx*2; // The rect for the cell gives correct left and right values. CRect rect = pCD->nmcd.rc; CRect bounds; GetItemRect( pCD->nmcd.dwItemSpec, &bounds, LVIR_BOUNDS ); // Get the top and bottom from the item itself. rect.top = bounds.top; rect.bottom = bounds.bottom; // Adjust rectangle for horizontal scroll and first column label { if( subitem == 0 ) { CRect lrect; GetItemRect( pCD->nmcd.dwItemSpec, &lrect, LVIR_LABEL ); rect.left = lrect.left; rect.right = lrect.right; } else { rect.right += bounds.left; rect.left += bounds.left; } } // Clear the background with button face color pDC->FillRect(rect, &CBrush(::GetSysColor(COLOR_HIGHLIGHT))); // Draw column focus box rect.DeflateRect( 1, 1 ); pDC->FrameRect(rect, &CBrush(::GetSysColor(COLOR_BTNTEXT))); // Draw text CString str; str = GetItemText( pCD->nmcd.dwItemSpec, pCD->iSubItem ); rect.DeflateRect( 4, 1, 0, 0 ); pDC->SetTextColor( ::GetSysColor(COLOR_HIGHLIGHTTEXT) ); pDC->DrawText( str, rect, DT_SINGLELINE|DT_NOPREFIX|DT_LEFT|DT_VCENTER|DT_END_ELLIPSIS); // Tell the control that we handled the drawing for this subitem.*/ *pResult = CDRF_SKIPDEFAULT; } } } break; default: // Stage two handled here. (called for each item) if( !(pCD->nmcd.uItemState & CDIS_FOCUS) ) { // If this item does not have focus, let the // control draw the whole item. *pResult = CDRF_DODEFAULT; } else { // If this item has focus, tell the control we want // to handle subitem drawing. *pResult = CDRF_NOTIFYSUBITEMDRAW; } break; } } 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; } } 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 && GLCT_EDIT == GetColumnType(m_CurSubItem) ) { // 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); } 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. int nRet = -1; 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; iitem.pszText; int item = pDispInfo->item.iItem; int subitem = pDispInfo->item.iSubItem; // Construct and create the custom multiline edit control. // We could just as well have used a combobox, checkbox, // rich text control, etc. m_pListEdit = new CInPlaceEdit( item, subitem, str ); // Start with a small rectangle. We'll change it later. CRect rect( 0,0,1,1 ); DWORD dwStyle = ES_LEFT; dwStyle |= WS_BORDER|WS_CHILD|WS_VISIBLE;//|ES_MULTILINE|ES_AUTOVSCROLL; m_pListEdit->Create( dwStyle, rect, this, 103 ); // Have the Grid position and size the custom edit control this->PositionControl( m_pListEdit, item, subitem ); // Have the edit box size itself to its content. m_pListEdit->CalculateSize(); // Return TRUE so that the list control will hnadle NOT edit label itself. *pResult = 1; } void CGridListCtrl::OnEndlabeledit(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR; // TODO: Add your control notification handler code here int item = pDispInfo->item.iItem; int subitem = pDispInfo->item.iSubItem; // This is coming from the grid list control notification. if( m_pListEdit ) { CString str; if( pDispInfo->item.pszText ) { this->SetItemText( item, subitem, pDispInfo->item.pszText ); } delete m_pListEdit; m_pListEdit = 0; } *pResult = 0; } void CGridListCtrl::SetColumnType( int nCol, GLC_COLUMNTYPE columnType ) { if( nCol < 100 ) { m_aColumnType[nCol] = columnType; } } GLC_COLUMNTYPE CGridListCtrl::GetColumnType( int nCol ) { if( nCol < 100 ) { return m_aColumnType[nCol]; } return GLCT_EDIT; } ///////////////////////////////////////////////////////////////////////////// // CInPlaceEdit CInPlaceEdit::CInPlaceEdit(int iItem, int iSubItem, CString sInitText) :m_sInitText( sInitText ) { m_iItem = iItem; m_iSubItem = iSubItem; m_bESC = FALSE; } CInPlaceEdit::~CInPlaceEdit() { } BEGIN_MESSAGE_MAP(CInPlaceEdit, CEdit) //{{AFX_MSG_MAP(CInPlaceEdit) ON_WM_KILLFOCUS() ON_WM_CHAR() ON_WM_CREATE() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CInPlaceEdit message handlers BOOL CInPlaceEdit::PreTranslateMessage(MSG* pMsg) { if( pMsg->message == WM_KEYDOWN ) { SHORT sKey = GetKeyState( VK_CONTROL); if(pMsg->wParam == VK_RETURN || pMsg->wParam == VK_DELETE || pMsg->wParam == VK_ESCAPE || sKey ) { ::TranslateMessage(pMsg); /* Strange but true: If the edit control has ES_MULTILINE and ESC is pressed the parent is destroyed if the message is dispatched. In this case the parent is the list control. */ if( !(GetStyle() & ES_MULTILINE) || pMsg->wParam != VK_ESCAPE ) { ::DispatchMessage(pMsg); } return TRUE; // DO NOT process further } } return CEdit::PreTranslateMessage(pMsg); } void CInPlaceEdit::OnKillFocus(CWnd* pNewWnd) { CEdit::OnKillFocus(pNewWnd); CString str; GetWindowText(str); // Send Notification to parent of ListView ctrl LV_DISPINFO dispinfo; dispinfo.hdr.hwndFrom = GetParent()->m_hWnd; dispinfo.hdr.idFrom = GetDlgCtrlID(); dispinfo.hdr.code = LVN_ENDLABELEDIT; dispinfo.item.mask = LVIF_TEXT; dispinfo.item.iItem = m_iItem; dispinfo.item.iSubItem = m_iSubItem; dispinfo.item.pszText = m_bESC ? NULL : LPTSTR((LPCTSTR)str); dispinfo.item.cchTextMax = m_bESC ? 0 : str.GetLength(); GetParent()->GetParent()->SendMessage( WM_NOTIFY, GetParent()->GetDlgCtrlID(), (LPARAM)&dispinfo ); } void CInPlaceEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { if( nChar == VK_ESCAPE || nChar == VK_RETURN) { if( nChar == VK_ESCAPE ) m_bESC = TRUE; GetParent()->SetFocus(); return; } CEdit::OnChar(nChar, nRepCnt, nFlags); // Resize edit control if needed CalculateSize(); } int CInPlaceEdit::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CEdit::OnCreate(lpCreateStruct) == -1) return -1; // Set the proper font CFont* font = GetParent()->GetFont(); SetFont(font); SetWindowText( m_sInitText ); SetFocus(); CalculateSize(); SetSel( 0, -1 ); return 0; } void CInPlaceEdit::CalculateSize() { // Get text extent CString str; GetWindowText( str ); CWindowDC dc(this); CFont *pFont = GetParent()->GetFont(); CFont *pFontDC = dc.SelectObject( pFont ); CSize size; // Get client rect CRect rect, parentrect; GetClientRect( &rect ); GetParent()->GetClientRect( &parentrect ); // Transform rect to parent coordinates ClientToScreen( &rect ); GetParent()->ScreenToClient( &rect ); if( !(GetStyle() & ES_MULTILINE ) ) { size = dc.GetTextExtent( str ); dc.SelectObject( pFontDC ); size.cx += 5; // add some extra buffer } else { CRect thinrect( rect ); // To measure the skinniest text box CRect widerect( rect ); // To measure the wides text box widerect.right = parentrect.right; // Use the shortest of the two box sizes. int thinheight = dc.DrawText( str, &thinrect, DT_CALCRECT|DT_NOPREFIX|DT_LEFT|DT_EXPANDTABS|DT_WORDBREAK ); int wideheight = dc.DrawText( str, &widerect, DT_CALCRECT|DT_NOPREFIX|DT_LEFT|DT_EXPANDTABS|DT_WORDBREAK ); if( thinheight >= wideheight ) { size.cy = wideheight + 5; size.cx = widerect.right - widerect.left + 5; } else { size.cy = thinheight + 5; size.cx = thinrect.right - thinrect.left + 5; } } // Check whether control needs to be resized // and whether there is space to grow int changed = 0; if( size.cx > rect.Width() ) { if( size.cx + rect.left < parentrect.right-2 ) rect.right = rect.left + size.cx; else rect.right = parentrect.right-2; changed = 1; } if( size.cy > rect.Height() ) { if( size.cy + rect.top < parentrect.bottom-2 ) rect.bottom = rect.top + size.cy; else { rect.bottom = parentrect.bottom-2; ShowScrollBar( SB_VERT ); } changed = 1; } // If the size became larger rposition the window. if( changed ) MoveWindow( &rect ); }