// XTPTreeBase.cpp : implementation file // // This file is a part of the XTREME CONTROLS MFC class library. // (c)1998-2012 Codejock Software, All Rights Reserved. // // THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE // RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN // CONSENT OF CODEJOCK SOFTWARE. // // THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED // IN THE XTREME TOOLKIT PRO LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO // YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A // SINGLE COMPUTER. // // CONTACT INFORMATION: // support@codejock.com // http://www.codejock.com // ///////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "Common/XTPColorManager.h" #include "Common/XTPDrawHelpers.h" #include "Common/XTPSystemHelpers.h" #include "XTPTreeBase.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #ifndef SCROLL_TIMER_PERIOD #define SCROLL_TIMER_PERIOD 75 #endif #ifndef HOVER_TIMER_PERIOD #define HOVER_TIMER_PERIOD 85 #endif #ifndef COLOR_HOTLIGHT #define COLOR_HOTLIGHT 26 #endif // COLOR_HOTLIGHT ///////////////////////////////////////////////////////////////////////////// // CXTPTreeBase CXTPTreeBase::CXTPTreeBase() : m_hSelect(NULL) , m_bMultiSelect(false) , m_bBandLabel(true) , m_bActionDone(false) , m_bOkToEdit(true) , m_htiEdit(NULL) , m_htiLast(NULL) { m_pTreeCtrl = NULL; } CXTPTreeBase::~CXTPTreeBase() { } ///////////////////////////////////////////////////////////////////////////// // CXTPTreeBase message handlers void CXTPTreeBase::OnLButtonDown(UINT nFlags, CPoint point) { // If multiselect control, process possible left // click drag selection. if (m_bMultiSelect) { UINT nHitFlags = 0; HTREEITEM hItemHit = m_pTreeCtrl->HitTest(point, &nHitFlags); HTREEITEM hItemSel = m_pTreeCtrl->GetSelectedItem(); // if expanding/contracting call base class. if ((nHitFlags & (TVHT_ONITEMBUTTON | TVHT_ONITEMSTATEICON)) != 0) { m_pTreeCtrl->Default(); return; } if (HasEditLabels()) { if (!(nFlags & (MK_CONTROL | MK_SHIFT)) && (nHitFlags & TVHT_ONITEMLABEL)) { if (m_bOkToEdit && hItemHit == hItemSel) { SelectAll(FALSE); SelectItem(hItemHit); m_pTreeCtrl->Default(); return; } } if ((nHitFlags & TVHT_ONITEM) == 0) { m_bOkToEdit = false; } else if (GetFocusedItem() == hItemSel) { m_bOkToEdit = true; } } OnButtonDown(TRUE, nFlags, point); } else { m_pTreeCtrl->Default(); } } void CXTPTreeBase::OnRButtonDown(UINT /*nFlags*/, CPoint point) { if (m_pTreeCtrl->GetStyle() & TVS_SINGLEEXPAND) { m_pTreeCtrl->Default(); return; } // hittest to get the tree item under the cursor // and select it. UINT uFlags = 0; HTREEITEM hItem = m_pTreeCtrl->HitTest(point, &uFlags); if (hItem != NULL && (uFlags & TVHT_ONITEM) != 0) { // if the item is not selected, clear previous // selections and select the item under cursor. if (!IsSelected(hItem)) { SelectAll(FALSE); SelectItem(hItem); } } else { // clear previous selections. SelectAll(FALSE); } // call Default() so correct notification messages are // sent such as TVN_BEGINRDRAG. m_pTreeCtrl->Default(); // get the owner of the tree control. //HWND hWnd = m_pTreeCtrl->GetOwner()->m_hWnd; //if (::IsWindow(hWnd)) //{ // construct a NMHDR struct... //NMHDR mHDR; //mHDR.hwndFrom = m_pTreeCtrl->m_hWnd; //mHDR.code = NM_RCLICK; //mHDR.idFrom = m_pTreeCtrl->GetDlgCtrlID(); // and send a WM_NOTIFY message to our owner. //SendNotify(&mHDR); //} } void CXTPTreeBase::OnSetFocus(CWnd* /*pOldWnd*/) { if (m_bMultiSelect) { //'emulated' selected items will remain greyed // if application gets covered HTREEITEM hItem = GetFirstSelectedItem(); while (hItem) { RECT rect; m_pTreeCtrl->GetItemRect(hItem, &rect, TRUE); m_pTreeCtrl->InvalidateRect(&rect); hItem = GetNextSelectedItem(hItem); } } m_pTreeCtrl->Default(); } void CXTPTreeBase::OnKillFocus(CWnd* /*pNewWnd*/) { m_pTreeCtrl->Default(); if (m_bMultiSelect) { //'emulated' selected items may not get // refreshed to grey HTREEITEM hItem = GetFirstSelectedItem(); while (hItem) { RECT rect; m_pTreeCtrl->GetItemRect(hItem, &rect, TRUE); m_pTreeCtrl->InvalidateRect(&rect); hItem = GetNextSelectedItem(hItem); } } } AFX_INLINE BOOL IsArrowKey(UINT nChar) { return (nChar == VK_UP) || (nChar == VK_DOWN) || (nChar == VK_PRIOR) || (nChar == VK_NEXT) || (nChar == VK_HOME) || (nChar == VK_END) || (nChar == VK_LEFT) || (nChar == VK_RIGHT) || (nChar == VK_BACK) || (nChar == VK_ADD) || (nChar == VK_MULTIPLY) || (nChar == VK_SUBTRACT); } void CXTPTreeBase::OnKeyDown(UINT nChar, UINT /*nRepCnt*/, UINT /*nFlags*/) { if (!m_bMultiSelect) { m_pTreeCtrl->Default(); return; } BOOL bCtrl = (::GetKeyState(VK_CONTROL) < 0); BOOL bShift = (::GetKeyState(VK_SHIFT) < 0); if (!bCtrl && !bShift && IsArrowKey(nChar)) { UINT uCount = GetSelectedCount(); if ((uCount > 1) || (uCount == 1 && !IsSelected(GetFocusedItem()))) SelectAll(FALSE); } HTREEITEM hSel = NULL; switch (nChar) { case VK_UP: case VK_DOWN: case VK_PRIOR: case VK_NEXT: hSel = m_pTreeCtrl->GetSelectedItem(); if (!m_hSelect) { m_hSelect = hSel; } if (!bCtrl && !bShift) { m_hSelect = NULL; //reset } break; } BOOL bDir = (nChar == VK_UP) || (nChar == VK_PRIOR); m_pTreeCtrl->Default(); if (!hSel || (!bCtrl && !bShift)) { return; } HTREEITEM hNext = (nChar == VK_NEXT) || (nChar == VK_PRIOR) ? GetFocusedItem() : bDir ? m_pTreeCtrl->GetPrevVisibleItem(hSel) : m_pTreeCtrl->GetNextVisibleItem(hSel); if (!hNext) { hNext = hSel; } if (bShift) { SelectItems(m_hSelect, hNext, TRUE); } else if (bCtrl) { SetItemState(hNext, TVIS_FOCUSED, TVIS_FOCUSED); } } BOOL CXTPTreeBase::OnBeginLabelEdit(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR; *pResult = 0; // set m_htiEdit equal to pTVDispInfo->item.hItem. m_htiEdit = pTVDispInfo->item.hItem; return FALSE; //pass to parent } BOOL CXTPTreeBase::OnEndLabelEdit(NMHDR* /*pNMHDR*/, LRESULT* pResult) { *pResult = 0; // set m_htiEdit equal to NULL. m_htiEdit = NULL; return FALSE; //pass to parent } BOOL CXTPTreeBase::OnItemExpanding(NMHDR* pNMHDR, LRESULT* pResult) { if (!m_bMultiSelect) return FALSE; NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; *pResult = 0; if ((pNMTreeView->action == TVE_COLLAPSE) || (pNMTreeView->action == TVE_COLLAPSERESET)) { // clear selection of children, it would be confusing otherwise // - the notifications can be over-ridden to stop the de-selection // if required. // Unfortunately, the parent window can't over-ride this functionality // because MFC gives this class first crack. So if changes are required // a derived class will have to be used.. ASSERT(pNMTreeView->itemNew.hItem); // if a descendent item has focus the parent will get selected as a // consequence of collapsing the tree, so preserve // (if the parent was already selected it will gain focus, but // that's acceptable) BOOL bWasSel = IsSelected(pNMTreeView->itemNew.hItem); BOOL bWasFocus = SelectChildren(pNMTreeView->itemNew.hItem, FALSE, TRUE); if (bWasFocus && !bWasSel) { FocusItem(pNMTreeView->itemNew.hItem); // give parent focus. } } return FALSE; //pass to parent } HTREEITEM CXTPTreeBase::GetNextItem(HTREEITEM hItem) const { HTREEITEM hti = NULL; if (m_pTreeCtrl->ItemHasChildren(hItem)) { hti = m_pTreeCtrl->GetChildItem(hItem); } if (hti == NULL) { while ((hti = m_pTreeCtrl->GetNextSiblingItem(hItem)) == NULL) { if ((hItem = m_pTreeCtrl->GetParentItem(hItem)) == NULL) return NULL; } } return hti; } HTREEITEM CXTPTreeBase::GetPrevItem(HTREEITEM hItem) const { HTREEITEM hti = NULL; hti = m_pTreeCtrl->GetPrevSiblingItem(hItem); if (hti == NULL) { hti = m_pTreeCtrl->GetParentItem(hItem); } else { hti = GetLastItem(hti); } return hti; } HTREEITEM CXTPTreeBase::GetLastItem(HTREEITEM hItem) const { // Temporary used variable HTREEITEM htiNext; // Get the last item at the top level if (hItem == NULL) { hItem = m_pTreeCtrl->GetRootItem(); htiNext = m_pTreeCtrl->GetNextSiblingItem(hItem); while (htiNext != NULL) { hItem = htiNext; htiNext = m_pTreeCtrl->GetNextSiblingItem(htiNext); } } while (m_pTreeCtrl->ItemHasChildren(hItem) != NULL) { // Find the last child of hItem htiNext = m_pTreeCtrl->GetChildItem(hItem); while (htiNext != NULL) { hItem = htiNext; htiNext = m_pTreeCtrl->GetNextSiblingItem(htiNext); } } return hItem; } HTREEITEM CXTPTreeBase::FindItemInBranch(LPCTSTR lpszSearch, BOOL bCaseSensitive /*= FALSE*/, BOOL bWholeWord /*= FALSE*/, HTREEITEM htiItem /*= NULL*/) { HTREEITEM htiFound = NULL; if (!m_pTreeCtrl->ItemHasChildren(htiItem)) return NULL; CString strSearch = lpszSearch; int iLen = strSearch.GetLength(); if (iLen == 0) { return NULL; } if (!bCaseSensitive) { strSearch.MakeLower(); } HTREEITEM htiChild = m_pTreeCtrl->GetChildItem(htiItem); while (htiChild != NULL) { if (m_pTreeCtrl->ItemHasChildren(htiChild)) { htiFound = FindItemInBranch(lpszSearch, bCaseSensitive, bWholeWord, htiChild); if (htiFound != NULL) return htiFound; } CString strItemText = m_pTreeCtrl->GetItemText(htiChild); if (!bCaseSensitive) { strItemText.MakeLower(); } int iIndex; while ((iIndex = strItemText.Find(strSearch)) != -1) { // Search string found if (bWholeWord) { // Check preceding char if (iIndex != 0) { if (_istalpha(strItemText[iIndex-1]) || strItemText[iIndex-1] == '_') { // Not whole word strItemText = strItemText.Right(strItemText.GetLength() - iIndex - iLen); continue; } } // Check succeeding char if (strItemText.GetLength() > iIndex + iLen && (_istalpha(strItemText[iIndex + iLen]) || (strItemText[iIndex + iLen] == '_'))) { // Not whole word strItemText = strItemText.Right(strItemText.GetLength() - iIndex - strSearch.GetLength()); continue; } } if (IsFindValid(htiChild)) { return htiChild; } else { break; } } htiChild = m_pTreeCtrl->GetNextSiblingItem(htiChild); } return NULL; } HTREEITEM CXTPTreeBase::FindItem(LPCTSTR lpszSearch, BOOL bCaseSensitive /*= FALSE*/, BOOL bDownDir /*= TRUE*/, BOOL bWholeWord /*= FALSE*/, HTREEITEM hItem /*= NULL*/) { CString str = lpszSearch; int lenSearchStr = str.GetLength(); if (lenSearchStr == 0) { return NULL; } // For the first pass, set the current item equal to the selection HTREEITEM htiSel = hItem ? hItem : m_pTreeCtrl->GetSelectedItem(); // If NULL, use root item. if (htiSel == NULL) htiSel = m_pTreeCtrl->GetRootItem(); HTREEITEM htiCur = htiSel; CString strSearch = str; // make sure it ends if we started with no selection if ((htiCur == NULL) && (htiSel != NULL)) { if (bDownDir) { htiCur = m_pTreeCtrl->GetRootItem(); } else { htiCur = GetLastItem(NULL); } } if (!bCaseSensitive) { strSearch.MakeLower(); } // For the first pass only, we check to see if it // is the item we're looking for. BOOL bFirstPass = TRUE; BOOL bFoundRoot = FALSE; while (htiCur && (htiCur != htiSel || bFirstPass)) { bFirstPass = FALSE; CString strItemText = m_pTreeCtrl->GetItemText(htiCur); if (!bCaseSensitive) { strItemText.MakeLower(); } int iIndex; while ((iIndex = strItemText.Find(strSearch)) != -1) { // Search string found if (bWholeWord) { // Check preceding char if (iIndex != 0) { if (_istalpha(strItemText[iIndex-1]) || strItemText[iIndex-1] == '_') { // Not whole word strItemText = strItemText.Right(strItemText.GetLength() - iIndex - lenSearchStr); continue; } } // Check succeeding char if (strItemText.GetLength() > iIndex + lenSearchStr && (_istalpha(strItemText[iIndex + lenSearchStr]) || (strItemText[iIndex + lenSearchStr] == '_'))) { // Not whole word strItemText = strItemText.Right(strItemText.GetLength() - iIndex - strSearch.GetLength()); continue; } } if (IsFindValid(htiCur)) { return htiCur; } else { break; } } htiCur = bDownDir ? GetNextItem(htiCur) : GetPrevItem(htiCur); if (htiCur == NULL) { if (bFoundRoot) return NULL; if (bDownDir) { htiCur = m_pTreeCtrl->GetRootItem(); } else { htiCur = GetLastItem(NULL); } bFoundRoot = TRUE; } } return NULL; } BOOL CXTPTreeBase::IsFindValid(HTREEITEM) { return TRUE; } void CXTPTreeBase::SetItemFont(HTREEITEM hItem, LOGFONT& logfont) { CLRFONT cf; if (!m_mapColorFont.Lookup(hItem, cf)) { cf.color = COLORREF_NULL; } cf.logfont = logfont; m_mapColorFont[hItem] = cf; } BOOL CXTPTreeBase::GetItemFont(HTREEITEM hItem, LOGFONT* plogfont) { CLRFONT cf; if (!m_mapColorFont.Lookup(hItem, cf)) { return FALSE; } if (cf.logfont.lfFaceName[0] == _T('\0')) { return FALSE; } *plogfont = cf.logfont; return TRUE; } void CXTPTreeBase::SetItemBold(HTREEITEM hItem, BOOL bBold) { SetItemState(hItem, bBold ? TVIS_BOLD : 0, TVIS_BOLD); m_pTreeCtrl->InvalidateRect(NULL); } BOOL CXTPTreeBase::GetItemBold(HTREEITEM hItem) { return GetItemState(hItem, TVIS_BOLD) & TVIS_BOLD; } void CXTPTreeBase::SetItemColor(HTREEITEM hItem, COLORREF color) { CLRFONT cf; m_mapColorFont.Lookup(hItem, cf); cf.color = color; m_mapColorFont[hItem] = cf; m_pTreeCtrl->InvalidateRect(NULL); } COLORREF CXTPTreeBase::GetItemColor(HTREEITEM hItem) { CLRFONT cf; if (m_mapColorFont.Lookup(hItem, cf)) return cf.color; return COLORREF_NULL; } void CXTPTreeBase::SetItemBackColor(HTREEITEM hItem, COLORREF color) { CLRFONT cf; m_mapColorFont.Lookup(hItem, cf); cf.colorBack = color; m_mapColorFont[hItem] = cf; m_pTreeCtrl->InvalidateRect(NULL); } COLORREF CXTPTreeBase::GetItemBackColor(HTREEITEM hItem) { CLRFONT cf; if (m_mapColorFont.Lookup(hItem, cf)) return cf.colorBack; return COLORREF_NULL; } COLORREF CXTPTreeBase::GetTreeBackColor() const { // if the tree is disabled return disabled back color. if (!m_pTreeCtrl->IsWindowEnabled()) return GetXtremeColor(COLOR_3DFACE); #if _MSC_VER >= 1200 // MFC 6.0 if (XTPSystemVersion()->GetComCtlVersion() >= MAKELONG(71, 4)) { // is there a user defined color available ? COLORREF crBack = m_pTreeCtrl->GetBkColor(); if (crBack != COLORREF_NULL) return crBack; } #endif // return the system window color. return GetXtremeColor(COLOR_WINDOW); } COLORREF CXTPTreeBase::GetTreeTextColor() const { // if the tree is disabled return disabled text color. if (!m_pTreeCtrl->IsWindowEnabled()) return GetXtremeColor(COLOR_GRAYTEXT); #if _MSC_VER >= 1200 // MFC 6.0 if (XTPSystemVersion()->GetComCtlVersion() >= MAKELONG(71, 4)) { // is ther a user defined color available ? COLORREF crText = m_pTreeCtrl->GetTextColor(); if (crText != COLORREF_NULL) return crText; } #endif // return the system window text color. return GetXtremeColor(COLOR_WINDOWTEXT); } COLORREF CXTPTreeBase::GetItemBackColor(UINT uState, bool bTreeHasFocus, DWORD dwStyle, COLORREF crBack) const { // if the tree item is selected or drop highlighted. if (uState & TVIS_SELECTED || uState & TVIS_DROPHILITED) { // if the tree has focus and draw the text background hilite color. if (bTreeHasFocus || uState & TVIS_DROPHILITED) { if (m_pTreeCtrl->IsWindowEnabled()) return GetXtremeColor(COLOR_HIGHLIGHT); else return GetXtremeColor(COLOR_GRAYTEXT); } // if the tree does not have focus and the TVS_SHOWSELALWAYS flag is // set draw the text background hilite color. else if (dwStyle & TVS_SHOWSELALWAYS) { if (m_pTreeCtrl->IsWindowEnabled()) return GetXtremeColor(COLOR_3DFACE); else return GetXtremeColor(COLOR_GRAYTEXT); } } return crBack; } COLORREF CXTPTreeBase::GetItemTextColor(UINT uState, bool bTreeHasFocus, DWORD dwStyle, COLORREF crText) const { // if the tree item is selected or drop highlighted. if (uState & TVIS_SELECTED || uState & TVIS_DROPHILITED) { // if the tree has focus and draw the text hilite color. if (bTreeHasFocus || uState & TVIS_DROPHILITED) { if (m_pTreeCtrl->IsWindowEnabled()) return GetXtremeColor(COLOR_HIGHLIGHTTEXT); else return GetXtremeColor(COLOR_3DFACE); } // if the tree does not have focus and the TVS_SHOWSELALWAYS flag is // set draw the text hilite color. else if (dwStyle & TVS_SHOWSELALWAYS) { if (m_pTreeCtrl->IsWindowEnabled()) return GetXtremeColor(COLOR_WINDOWTEXT); else return GetXtremeColor(COLOR_3DFACE); } } return crText; } void CXTPTreeBase::DoPaint(CDC& dc, BOOL bInternal) { // Get the client rect. CRect rcClient; m_pTreeCtrl->GetClientRect(&rcClient); const COLORREF crTreeBack = GetTreeBackColor(); const COLORREF crTreeText = GetTreeTextColor(); // Paint to a memory device context to help // eliminate screen flicker. CXTPBufferDC memDC(dc, rcClient); memDC.FillSolidRect(rcClient, crTreeBack); // Now let the window do its default painting... m_pTreeCtrl->DefWindowProc(WM_PAINT, (WPARAM)memDC.m_hDC, 0); if (!bInternal) return; int nSavedDC = memDC.SaveDC(); // check to see if a tree label is getting edited. BOOL bEditing = (HasEditLabels() && m_pTreeCtrl->GetEditControl()); // if not editing, make sure the edit item is set to NULL. if (!bEditing) m_htiEdit = NULL; // check to see if the tree is enabled. BOOL bIsEnabled = m_pTreeCtrl->IsWindowEnabled(); // check to see if the tree has focus. bool bTreeHasFocus = (CWnd::GetFocus() == m_pTreeCtrl); // get the style for the tree. DWORD dwStyle = m_pTreeCtrl->GetStyle(); // get the visible count. HTREEITEM hItem = m_pTreeCtrl->GetFirstVisibleItem(); int iVisibleCount = m_pTreeCtrl->GetVisibleCount()+1; LOGFONT treefont; if (!m_pTreeCtrl->GetFont()->GetLogFont(&treefont)) { ::GetObject(::GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT), &treefont); } while (hItem && iVisibleCount--) { // get the tree item rect. CRect rcItem; m_pTreeCtrl->GetItemRect(hItem, &rcItem, TRUE); // refresh the background. CRect rcFill = rcItem; rcFill.right = rcClient.right; memDC.FillSolidRect(&rcFill, crTreeBack); // get the state of the tree item. UINT uState = GetItemState(hItem, TVIS_SELECTED | TVIS_FOCUSED | TVIS_DROPHILITED); if (m_pTreeCtrl->GetDropHilightItem() && uState & TVIS_SELECTED && hItem != m_pTreeCtrl->GetDropHilightItem()) uState ^= TVIS_SELECTED; BOOL bSelected = ((uState & (TVIS_SELECTED | TVIS_DROPHILITED)) != 0); bool bUnderline = m_htiLast == hItem; // define the background and text colors. COLORREF crItemBack = GetItemBackColor( uState, bTreeHasFocus, dwStyle, crTreeBack); COLORREF crItemText = GetItemTextColor( uState, bTreeHasFocus, dwStyle, crTreeText); // if the item is getting edited paint white. if (m_htiEdit == hItem) { crItemBack = crTreeBack; crItemText = crTreeBack; } // create the LOGFONT used by the tree item. LOGFONT logfont; logfont = treefont; CLRFONT cf; if (m_mapColorFont.Lookup(hItem, cf)) { if (!bSelected && bIsEnabled) { if (cf.color != COLORREF_NULL) crItemText = cf.color; if (cf.colorBack != COLORREF_NULL) crItemBack = cf.colorBack; } if (cf.logfont.lfFaceName[0] != _T('\0')) { logfont = cf.logfont; } } if (GetItemBold(hItem)) { logfont.lfWeight = FW_BOLD; } if (dwStyle & TVS_TRACKSELECT) { logfont.lfUnderline = bUnderline ? (BYTE)1 : (BYTE)0; if (logfont.lfUnderline && !bSelected) { if (XTPSystemVersion()->IsWin95() || XTPSystemVersion()->IsWinNT4()) crItemText = GetXtremeColor(COLOR_HIGHLIGHT); else crItemText = GetXtremeColor(COLOR_HOTLIGHT); } } // create a CFont object from the LOGFONT structure and // select it into the current device context. CFont fontDC; fontDC.CreateFontIndirect(&logfont); CFont* pOldFont = memDC.SelectObject(&fontDC); // get the text for the tree item and determine its size. CString strItem = m_pTreeCtrl->GetItemText(hItem); CSize sizeText = memDC.GetTextExtent(strItem); // if the text is wider than the tree item label, adjust accordingly. if (rcItem.Width() < sizeText.cx) { rcItem.right = rcItem.left + sizeText.cx + 2; } // set the font foreground and background colors. memDC.SetBkColor(crItemBack); memDC.SetTextColor(crItemText); // draw the label background if selected. if (bSelected) { memDC.FillSolidRect(&rcItem, crItemBack); } CRect rcText(rcItem); rcText.OffsetRect(3, 0); // draw the text and restore the device context. memDC.DrawText(strItem, &rcText, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_NOPREFIX); // draw the focus rect. if ((uState & TVIS_FOCUSED)) { if (!bEditing && bIsEnabled && CWnd::GetFocus() == m_pTreeCtrl) { memDC.SetTextColor(crTreeText); memDC.DrawFocusRect(&rcItem); } } memDC.SelectObject(pOldFont); fontDC.DeleteObject(); // move to the next visible item. hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); } memDC.RestoreDC(nSavedDC); } HTREEITEM CXTPTreeBase::GetPrevSelectedItem(HTREEITEM hItem) const { for (hItem = m_pTreeCtrl->GetPrevVisibleItem(hItem); hItem != NULL; hItem = m_pTreeCtrl->GetPrevVisibleItem(hItem)) { if (GetItemState(hItem, TVIS_SELECTED) & TVIS_SELECTED) { return hItem; } } return NULL; } UINT CXTPTreeBase::GetSelectedCount() const { UINT nCount = 0; HTREEITEM hItem = GetFirstSelectedItem(); while (hItem) { nCount++; hItem = GetNextSelectedItem(hItem); } return nCount; } BOOL CXTPTreeBase::EnableMultiSelect(BOOL bMultiSelect) { BOOL bReturn = m_bMultiSelect; m_bMultiSelect = bMultiSelect; if (!m_bMultiSelect) { HTREEITEM hItem = m_pTreeCtrl->GetSelectedItem(); if (hItem && !IsSelected(hItem)) { hItem = NULL; } SelectAllIgnore(FALSE, hItem); if (hItem) { SelectItem(hItem); } } return bReturn; } BOOL CXTPTreeBase::SetItemState(HTREEITEM hItem, UINT nState, UINT nStateMask) { ASSERT(hItem); if (!hItem) return FALSE; if (!m_bMultiSelect) { return m_pTreeCtrl->SetItemState(hItem, nState, nStateMask); } HTREEITEM hFocus = m_pTreeCtrl->GetSelectedItem(); // current focus BOOL bWasFocus = (hFocus == hItem); BOOL bFocusWasSel = hFocus && IsSelected(hFocus); // selection state of current focus BOOL bWasSel = IsSelected(hItem); // select state of acting item UINT nS = (nState & ~TVIS_FOCUSED); UINT nSM = (nStateMask & ~TVIS_FOCUSED); BOOL bVista = XTPSystemVersion()->IsWinVistaOrGreater(); if (nStateMask & TVIS_FOCUSED) { //wanted to affect focus if (nState & TVIS_FOCUSED) { if (bVista) { //wanted to set focus if (!bWasFocus && hFocus) { //because SelectItem would de-select the current 'real' selection // (the one with focus), need to make the tree ctrl think there is // no 'real' selection but still keep the the old item selected //it has to be done before the SelectItem call because // otherwise the TVN_SELCHANGING/ED notification handlers // wouldn't be able to get the proper list of selected items m_pTreeCtrl->SelectItem(NULL); //will cause notify, but can be taken as focus change m_pTreeCtrl->SetItemState(hFocus, TVIS_SELECTED, TVIS_SELECTED); } if (!m_pTreeCtrl->SelectItem(hItem)) //set focus (will consequently select, if not already focused) { return FALSE; } if (!bWasFocus && hFocus) { m_pTreeCtrl->SetItemState(hFocus, bFocusWasSel ? TVIS_SELECTED : 0, TVIS_SELECTED); } } else { //wanted to set focus if (!bWasFocus && bFocusWasSel) { //because SelectItem would de-select the current 'real' selection // (the one with focus), need to make the tree ctrl think there is // no 'real' selection but still keep the the old item selected //it has to be done before the SelectItem call because // otherwise the TVN_SELCHANGING/ED notification handlers // wouldn't be able to get the proper list of selected items m_pTreeCtrl->SelectItem(NULL); //will cause notify, but can be taken as focus change m_pTreeCtrl->SetItemState(hFocus, TVIS_SELECTED, TVIS_SELECTED); m_pTreeCtrl->UpdateWindow(); } if (!m_pTreeCtrl->SelectItem(hItem)) //set focus (will consequently select, if not already focused) { return FALSE; } } if (nStateMask & TVIS_SELECTED) { //wanted to affect select state if (nState & TVIS_SELECTED) { //wanted to select, already done if wasn't focused if (!bVista && (!bWasFocus || bFocusWasSel)) { nS &= ~TVIS_SELECTED; nSM &= ~TVIS_SELECTED; } } //else wanted to clear, base call will do that } else { //didn't want to affect select state if (!bWasSel) { //it wasn't previously selected, let base clear (correct) nS &= ~TVIS_SELECTED; nSM |= TVIS_SELECTED; } //else was already selected, no harm done } } else { //wanted to clear focus if (bWasFocus) { //it had the focus m_pTreeCtrl->SelectItem(NULL); //clear focus if (!(nStateMask & TVIS_SELECTED)) { //didn't want to affect select state if (bWasSel) { //it was selected, so restore ASSERT(!(nS & TVIS_SELECTED)); ASSERT(!(nSM & TVIS_SELECTED)); //set state here, to avoid double-notify m_pTreeCtrl->SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED); //let base do other states } } else if (nState & TVIS_SELECTED) { //wanted to select (but clear focus) if (bWasSel) { //if was selected, restore m_pTreeCtrl->SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED); } //don't want to notify, default did it nS &= ~TVIS_SELECTED; nSM &= ~TVIS_SELECTED; } } } } if (!nSM) { return TRUE; //no other states to alter } if (nSM & TVIS_SELECTED) { //still need to alter selection state NMTREEVIEW nmtv; ZeroMemory(&nmtv, sizeof(NMTREEVIEW)); nmtv.hdr.hwndFrom = m_pTreeCtrl->m_hWnd; nmtv.hdr.idFrom = ::GetDlgCtrlID(m_pTreeCtrl->m_hWnd); nmtv.hdr.code = TVN_SELCHANGING; nmtv.itemOld.mask = nmtv.itemNew.mask = 0; nmtv.itemOld.hItem = nmtv.itemNew.hItem = NULL; TVITEM& item = (nS & TVIS_SELECTED) ? nmtv.itemNew : nmtv.itemOld; item.mask = TVIF_HANDLE | TVIF_PARAM; item.hItem = hItem; item.lParam = m_pTreeCtrl->GetItemData(hItem); item.state = nS; item.stateMask = nSM; if (SendNotify(&nmtv.hdr)) { return FALSE; //sel-changing stopped } VERIFY(m_pTreeCtrl->SetItemState(hItem, nS, nSM)); nmtv.hdr.code = TVN_SELCHANGED; SendNotify(&nmtv.hdr); nS &= ~TVIS_SELECTED; nSM &= ~TVIS_SELECTED; } if (!nSM) { return TRUE; } return m_pTreeCtrl->SetItemState(hItem, nS, nSM); } UINT CXTPTreeBase::GetItemState(HTREEITEM hItem, UINT nStateMask) const { UINT n = m_pTreeCtrl->GetItemState(hItem, nStateMask & ~TVIS_FOCUSED); if (nStateMask & TVIS_FOCUSED) { if (m_pTreeCtrl->GetSelectedItem() == hItem) { n |= TVIS_FOCUSED; } } return n; } BOOL CXTPTreeBase::SelectItem(HTREEITEM hItem) { if (m_bMultiSelect) { return SetItemState(hItem, TVIS_SELECTED | TVIS_FOCUSED, TVIS_SELECTED | TVIS_FOCUSED); } else { return m_pTreeCtrl->SelectItem(hItem); } } BOOL CXTPTreeBase::FocusItem(HTREEITEM hItem) { ASSERT(m_bMultiSelect); BOOL bRet = FALSE; if (hItem) { bRet = SetItemState(hItem, TVIS_FOCUSED, TVIS_FOCUSED); } else { hItem = m_pTreeCtrl->GetSelectedItem(); if (hItem) { bRet = SetItemState(hItem, 0, TVIS_FOCUSED); } } return bRet; } LRESULT CXTPTreeBase::SendNotify(LPNMHDR pNMHDR) { CWnd* pWndOwner = m_pTreeCtrl->GetOwner(); if (pWndOwner->GetSafeHwnd()) { return ::SendMessage(pWndOwner->m_hWnd, WM_NOTIFY, (WPARAM)pNMHDR->idFrom, (LPARAM)pNMHDR); } return NULL; } HTREEITEM CXTPTreeBase::GetFirstSelectedItem() const { HTREEITEM hItem = m_pTreeCtrl->GetRootItem(); while (hItem) { if (IsSelected(hItem)) { break; } hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); } return hItem; } HTREEITEM CXTPTreeBase::GetNextSelectedItem(HTREEITEM hItem) const { hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); while (hItem) { if (IsSelected(hItem)) { break; } hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); } return hItem; } void CXTPTreeBase::SelectAll(BOOL bSelect /*= TRUE*/, HTREEITEM htItem/*= NULL*/) { bSelect = !!bSelect; //ensure 0 or 1 UINT nState = bSelect ? TVIS_SELECTED : 0; if (htItem == NULL) htItem = m_pTreeCtrl->GetRootItem(); while (htItem) { if (IsSelected(htItem) != bSelect) { SetItemState(htItem, nState, TVIS_SELECTED); } htItem = m_pTreeCtrl->GetNextVisibleItem(htItem); } } void CXTPTreeBase::SelectAllIgnore(BOOL bSelect, HTREEITEM hIgnore) { //special case to avoid multiple notifications for // the same item bSelect = !!bSelect; //ensure 0 or 1 UINT nState = bSelect ? TVIS_SELECTED : 0; HTREEITEM hItem = m_pTreeCtrl->GetRootItem(); while (hItem) { if (hItem != hIgnore) { if (IsSelected(hItem) != bSelect) { SetItemState(hItem, nState, TVIS_SELECTED); } } hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); } } void CXTPTreeBase::SelectItems(HTREEITEM hFirst, HTREEITEM hLast, BOOL bOnly /*= TRUE*/) { //locate (and select) either first or last // (so order is arbitrary) HTREEITEM hItem = m_pTreeCtrl->GetRootItem(); while (hItem) { if ((hItem == hFirst) || (hItem == hLast)) { if (hFirst != hLast) { if (!IsSelected(hItem)) { SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED); } hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); } break; } if (bOnly && IsSelected(hItem)) { SetItemState(hItem, 0, TVIS_SELECTED); } hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); } //select rest of range while (hItem) { if (!IsSelected(hItem)) { SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED); } if ((hItem == hFirst) || (hItem == hLast)) { hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); break; } hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); } if (!bOnly) { return; } while (hItem) { if (IsSelected(hItem)) { SetItemState(hItem, 0, TVIS_SELECTED); } hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); } } BOOL CXTPTreeBase::OnButtonDown(BOOL bLeft, UINT nFlags, CPoint point) { UINT nHF = 0; HTREEITEM hItem = NULL; if (!m_bMultiSelect) return TRUE; BOOL bBase = FALSE; hItem = m_pTreeCtrl->HitTest(point, &nHF); if (hItem) { //base always handles expanding items //(doesn't really mean much to right button, but pass anyway) bBase = (nHF & (TVHT_ONITEMBUTTON)); if (!bBase && bLeft && (m_pTreeCtrl->GetStyle() & TVS_CHECKBOXES)) { //when the tree has check-boxes, the default handler makes // a quick selection of the clicked item, then re-selects // the previously selected item - to cause a sel-changed // notification. Fortunately it doesn't affect the multi- // selection, so can pass on. bBase = (nHF & TVHT_ONITEMSTATEICON); #ifdef _MST_MULTI_CHECK //Use the above define if you want all selected items to // be checked the same when any one of them is checked // - Interestingly this doesn't happen in the listview control // (LVS_EX_CHECKBOXES) if (bBase) { //the default selection notification would mess // the multi-selection up, so generate the notification // manually // (anyway, this is smoother than the selection flicker // the default gives) NMTREEVIEW nmtv; ZeroMemory(&nmtv, sizeof(NMTREEVIEW)); #ifdef TVN_CHKCHANGE nmtv.hdr.code = TVN_CHKCHANGE; #else nmtv.hdr.code = TVN_SELCHANGED; #endif nmtv.hdr.hwndFrom = m_hWnd; nmtv.hdr.idFrom = ::GetDlgCtrlID(m_hWnd); nmtv.itemOld.hItem = NULL; nmtv.itemNew.mask = TVIF_HANDLE | TVIF_PARAM; BOOL bChk = !GetCheck(hItem); if (IsSelected(hItem)) { HTREEITEM h = GetFirstSelectedItem(); while (h) { if (!GetCheck(h) == bChk) //! to ensure 0 or 1 { SetCheck(h, bChk); #ifdef TVN_CHKCHANGE //only send multiple check-change // notifications (not sel-changed) if (h != hItem) //clicked item will be done last { nmtv.itemNew.hItem = h; nmtv.itemNew.lParam = m_pTreeCtrl->GetItemData(h); SendNotify(&nmtv.hdr); } #endif } h = GetNextSelectedItem(h); } } else { SetCheck(hItem, bChk); } //notify clicked item nmtv.itemNew.hItem = hItem; nmtv.itemNew.lParam = m_pTreeCtrl->GetItemData(hItem); SendNotify(&nmtv.hdr); return TRUE; } #endif } } if (bBase) return TRUE; if (!hItem || (nHF & (TVHT_ONITEMRIGHT | TVHT_NOWHERE | TVHT_ONITEMINDENT))) { //clicked in space, do rubber-banding DoBanding(nFlags, point); return TRUE; } ASSERT(nHF & (TVHT_ONITEM | TVHT_ONITEMSTATEICON)); //nothing else left DoPreSelection(hItem, bLeft, nFlags); DoAction(hItem, bLeft, nFlags, point); return m_bActionDone; // as set in doaction } void CXTPTreeBase::DoPreSelection(HTREEITEM hItem, BOOL bLeft, UINT nFlags) { if (bLeft) { //if shift key down, select immediately if ((nFlags & MK_SHIFT)) { if (!m_hSelect) { m_hSelect = m_pTreeCtrl->GetSelectedItem(); //focus } SetItemState(hItem, TVIS_FOCUSED, TVIS_FOCUSED); //focus changes to last clicked SelectItems(m_hSelect, hItem, !(nFlags & MK_CONTROL)); } else { if (!(nFlags & MK_CONTROL)) { //if ctrl was down, then the selection is delayed until // mouse up, otherwise select the one item if (!IsSelected(hItem)) { SelectAllIgnore(FALSE, hItem); } SetItemState(hItem, TVIS_SELECTED | TVIS_FOCUSED, TVIS_SELECTED | TVIS_FOCUSED); } m_hSelect = NULL; //reset when a non-shift operation occurs } return; } //right mouse if (nFlags & (MK_CONTROL | MK_SHIFT)) { if (!(nFlags & MK_SHIFT)) { m_hSelect = hItem; } return; //do nothing if shift or ctrl } if (!IsSelected(hItem)) { SelectAllIgnore(FALSE, hItem); } SetItemState(hItem, TVIS_SELECTED | TVIS_FOCUSED, TVIS_SELECTED | TVIS_FOCUSED); } void CXTPTreeBase::DoAction(HTREEITEM hItem, BOOL bLeft, UINT nFlags, CPoint point) { m_bActionDone = false; ::SetCapture(m_pTreeCtrl->m_hWnd); ASSERT(::GetCapture() == m_pTreeCtrl->m_hWnd); MSG msg; UINT nDone = 0; CPoint pt; CSize sizeDrag(::GetSystemMetrics(SM_CXDRAG), ::GetSystemMetrics(SM_CYDRAG)); while (!nDone && ::GetMessage(&msg, NULL, 0, 0)) { if (::GetCapture() != m_pTreeCtrl->m_hWnd) { break; } switch (msg.message) { case WM_MOUSEMOVE: pt.x = LOWORD(msg.lParam); pt.y = HIWORD(msg.lParam); if ((abs(pt.x - point.x) > sizeDrag.cx) || ((abs(pt.y - point.y) > sizeDrag.cy))) { nDone = 2; } //because we exit loop, button up will still be dispatched // which means WM_CONTEXTMENU will be sent after TVN_BEGINRDRAG // - this is the same behavior as original tree break; case WM_LBUTTONUP: case WM_RBUTTONUP: nDone = 1; break; default: ::DispatchMessage(&msg); break; } } ::ReleaseCapture(); ASSERT(::GetCapture() != m_pTreeCtrl->m_hWnd); //construct tree notification info NMTREEVIEW nmtv; ZeroMemory(&nmtv, sizeof(NMTREEVIEW)); nmtv.hdr.hwndFrom = m_pTreeCtrl->m_hWnd; nmtv.hdr.idFrom = ::GetDlgCtrlID(m_pTreeCtrl->m_hWnd); nmtv.itemNew.mask = TVIF_HANDLE | TVIF_PARAM; nmtv.itemNew.hItem = hItem; nmtv.itemNew.lParam = m_pTreeCtrl->GetItemData(hItem); DWORD dwStyle = m_pTreeCtrl->GetStyle(); if (nDone == 1) { //click if (!(nFlags & MK_SHIFT) && bLeft) { if ((nFlags & MK_CONTROL)) { UINT nState = (GetItemState(hItem, TVIS_SELECTED) & TVIS_SELECTED) ? 0 : TVIS_SELECTED; SetItemState(hItem, TVIS_FOCUSED | nState, TVIS_FOCUSED | TVIS_SELECTED); } else { SelectAllIgnore(FALSE, hItem); SetItemState(hItem, TVIS_FOCUSED | TVIS_SELECTED, TVIS_FOCUSED | TVIS_SELECTED); } } if (::GetFocus() != m_pTreeCtrl->m_hWnd) { ::SetFocus(m_pTreeCtrl->m_hWnd); } nmtv.hdr.code = bLeft ? NM_CLICK : NM_RCLICK; SendNotify(&nmtv.hdr); m_bActionDone = true; } else if (nDone == 2) { //drag SetItemState(hItem, TVIS_FOCUSED | TVIS_SELECTED, TVIS_FOCUSED | TVIS_SELECTED); if (!(dwStyle & TVS_DISABLEDRAGDROP)) { nmtv.hdr.code = bLeft ? TVN_BEGINDRAG : TVN_BEGINRDRAG; nmtv.ptDrag = point; SendNotify(&nmtv.hdr); } m_bActionDone = true; } } void CXTPTreeBase::DoBanding(UINT nFlags, CPoint point) { if (::GetFocus() != m_pTreeCtrl->m_hWnd) { ::SetFocus(m_pTreeCtrl->m_hWnd); } ::SetCapture(m_pTreeCtrl->m_hWnd); CTypedPtrList list; if (nFlags & (MK_SHIFT | MK_CONTROL)) { GetSelectedList(list); } CClientDC dc(m_pTreeCtrl); CRect rectCli; m_pTreeCtrl->GetClientRect(&rectCli); MSG msg; BOOL bDone = FALSE; CPoint pt; CSize sizeDrag(::GetSystemMetrics(SM_CXDRAG), ::GetSystemMetrics(SM_CYDRAG)); BOOL bDrag = FALSE; CSize sizeEdge(1, 1); UINT_PTR nTimer = m_pTreeCtrl->SetTimer(1, SCROLL_TIMER_PERIOD, NULL); //for scroll CPoint ptScr(m_pTreeCtrl->GetScrollPos(SB_HORZ), m_pTreeCtrl->GetScrollPos(SB_VERT)); CRect rect(0, 0, 0, 0); UINT h = 0; HTREEITEM hItem = m_pTreeCtrl->GetRootItem(); if (hItem) { m_pTreeCtrl->GetItemRect(hItem, &rect, FALSE); ptScr.y *= (h = rect.Height()); //this assumes equal height items } point += ptScr; while (!bDone && ::GetMessage(&msg, NULL, 0, 0)) { if (::GetCapture() != m_pTreeCtrl->m_hWnd) { break; } switch (msg.message) { case WM_TIMER: { if (msg.wParam == SCROLL_TIMER_PERIOD) { pt = msg.pt; m_pTreeCtrl->ScreenToClient(&pt); if (rectCli.PtInRect(pt)) { ::DispatchMessage(&msg); break; } msg.lParam = MAKELPARAM(pt.x, pt.y); } else { break; } } //fall through to mouse move case WM_MOUSEMOVE: pt.x = LOWORD(msg.lParam); pt.y = HIWORD(msg.lParam); if (!bDrag) { if ((abs(pt.x - point.x) <= sizeDrag.cx) && ((abs(pt.y - point.y) <= sizeDrag.cy))) { break; } bDrag = TRUE; if (!(nFlags & (MK_CONTROL | MK_SHIFT))) { SelectAll(FALSE); } m_pTreeCtrl->UpdateWindow(); rect.SetRect(point, point); dc.DrawDragRect(rect, sizeEdge, NULL, sizeEdge); } dc.DrawDragRect(rect, sizeEdge, NULL, sizeEdge); //delete if (pt.y < rectCli.top) { ::SendMessage(m_pTreeCtrl->m_hWnd, WM_VSCROLL, SB_LINEUP, 0); } else if (pt.y >= rectCli.bottom) { ::SendMessage(m_pTreeCtrl->m_hWnd, WM_VSCROLL, SB_LINEDOWN, 0); } if (pt.x < rectCli.left) { ::SendMessage(m_pTreeCtrl->m_hWnd, WM_HSCROLL, SB_LINELEFT, 0); } else if (pt.x >= rectCli.right) { ::SendMessage(m_pTreeCtrl->m_hWnd, WM_HSCROLL, SB_LINERIGHT, 0); } ptScr = point; ptScr.x -= m_pTreeCtrl->GetScrollPos(SB_HORZ); ptScr.y -= m_pTreeCtrl->GetScrollPos(SB_VERT) * h; rect.SetRect(ptScr, pt); rect.NormalizeRect(); UpdateSelectionForRect(rect, nFlags, list); dc.DrawDragRect(rect, sizeEdge, NULL, sizeEdge); //draw break; case WM_LBUTTONUP: case WM_RBUTTONUP: bDone = TRUE; break; case WM_KEYDOWN: if (LOWORD(msg.wParam) == VK_ESCAPE) { SelectAll(FALSE); bDone = TRUE; break; } //dispatch default: ::DispatchMessage(&msg); break; } } m_pTreeCtrl->KillTimer(nTimer); ::ReleaseCapture(); if (bDrag) { dc.DrawDragRect(rect, sizeEdge, NULL, sizeEdge); } else { if (!(nFlags & (MK_CONTROL | MK_SHIFT))) { SelectAll(FALSE); } } } void CXTPTreeBase::UpdateSelectionForRect(LPCRECT pRect, UINT nFlags, CTypedPtrList& list) { CRect rect; BOOL bSel; POSITION pos; HTREEITEM hItem = m_pTreeCtrl->GetRootItem(); while (hItem) { bSel = IsSelected(hItem); m_pTreeCtrl->GetItemRect(hItem, &rect, m_bBandLabel); if (rect.IntersectRect(rect, pRect)) { //item in rect pos = list.Find(hItem); if (!bSel && !pos) { SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED); } else if ((nFlags & MK_CONTROL) && pos) { SetItemState(hItem, 0, TVIS_SELECTED); } else if ((nFlags & MK_SHIFT) && pos) { list.RemoveAt(pos); //if shift and in rect, don't lock anymore } } else { //item not in rect pos = list.Find(hItem); if (bSel && !pos) { SetItemState(hItem, 0, TVIS_SELECTED); } else if (pos) { SetItemState(hItem, TVIS_SELECTED, TVIS_SELECTED); } } hItem = m_pTreeCtrl->GetNextVisibleItem(hItem); } m_pTreeCtrl->UpdateWindow(); } BOOL CXTPTreeBase::SelectChildren(HTREEITEM hParent, BOOL bSelect /*= TRUE*/, BOOL bRecurse /*= TRUE*/) { UINT nS = bSelect ? TVIS_SELECTED : 0; BOOL bFocusWasInHere = FALSE; HTREEITEM hItem = GetNextItem(hParent, TVGN_CHILD); while (hItem) { UINT nState = GetItemState(hItem, TVIS_SELECTED | TVIS_EXPANDED | TVIS_FOCUSED); if ((nState & TVIS_SELECTED) != nS) { SetItemState(hItem, nS, TVIS_SELECTED); } bFocusWasInHere |= (nState & TVIS_FOCUSED); if (bRecurse && (nState & TVIS_EXPANDED)) { bFocusWasInHere |= SelectChildren(hItem, bSelect, bRecurse); } hItem = m_pTreeCtrl->GetNextSiblingItem(hItem); } return bFocusWasInHere; } void CXTPTreeBase::GetSelectedList(CTypedPtrList& list) const { list.RemoveAll(); HTREEITEM hItem = GetFirstSelectedItem(); while (hItem) { list.AddTail(hItem); hItem = GetNextSelectedItem(hItem); } } BOOL CXTPTreeBase::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) { NMHDR* pNMHDR = (NMHDR*)lParam; switch (pNMHDR->code) { case TTN_SHOW: { // get a pointer to the tooltip control. HWND hWnd = TreeView_GetToolTips(m_pTreeCtrl->m_hWnd); if (hWnd != NULL) { // make sure the tooltip is at the top of the "Z" order, otherwise // it will appear behind popup windows.... ::SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); } } break; } return m_pTreeCtrl->CTreeCtrl::OnNotify(wParam, lParam, pResult); } BOOL CXTPTreeBase::PreTranslateMessage(MSG* pMsg) { if (pMsg->message == WM_KEYDOWN) { // ensure that keystrokes are handled by the edit control. if (HasEditLabels() && m_pTreeCtrl->GetEditControl()) { ::TranslateMessage(pMsg); ::DispatchMessage(pMsg); return TRUE; } // toggle expand / contract when return key is hit. if (pMsg->wParam == VK_RETURN) { HTREEITEM htItem = m_pTreeCtrl->GetSelectedItem(); if (htItem != NULL) { m_pTreeCtrl->Expand(htItem, TVE_TOGGLE); return TRUE; } } } return m_pTreeCtrl->CTreeCtrl::PreTranslateMessage(pMsg); } BOOL CXTPTreeBase::OnDeleteItem(NMHDR* pNMHDR, LRESULT* pResult) { NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; // Remove the tree item from the map. m_mapColorFont.RemoveKey(pNMTreeView->itemOld.hItem); *pResult = 0; return FALSE; } bool CXTPTreeBase::HasEditLabels() const { return ((m_pTreeCtrl->GetStyle() & TVS_EDITLABELS) == TVS_EDITLABELS); } void CXTPTreeBase::OnSize(UINT /*nType*/, int /*cx*/, int /*cy*/) { m_pTreeCtrl->Default(); // MFCBUG: During label editing if the tree is resized while // there is an edit box open the edit box is not moved to the // new location of the tree item, but is left in it previous // location. if (HasEditLabels() && m_htiEdit) { CEdit* pEdit = m_pTreeCtrl->GetEditControl(); if (pEdit && ::IsWindow(pEdit->m_hWnd)) { CRect rcEdit; pEdit->GetWindowRect(&rcEdit); CRect rcItem; m_pTreeCtrl->GetItemRect(m_htiEdit, &rcItem, TRUE); rcItem.top -= 1; rcItem.left -= 3; rcItem.right = rcItem.left + rcEdit.Width(); rcItem.bottom = rcItem.top + rcEdit.Height(); pEdit->MoveWindow(&rcItem); } } } void CXTPTreeBase::OnMouseMove(UINT /*nFlags*/, CPoint point) { if (m_pTreeCtrl->GetStyle() & TVS_TRACKSELECT) { TV_HITTESTINFO hit; ::ZeroMemory(&hit, sizeof(hit)); hit.pt = point; m_pTreeCtrl->HitTest(&hit); HTREEITEM hItem = hit.hItem && hit.flags & TVHT_ONITEM ? hit.hItem : NULL; if (m_htiLast != hItem) { m_htiLast = hItem; if (m_htiLast) { UINT_PTR uID = m_pTreeCtrl->SetTimer(HOVER_TIMER_PERIOD, 55, NULL); ASSERT(uID == HOVER_TIMER_PERIOD); } m_pTreeCtrl->Invalidate(FALSE); } } m_pTreeCtrl->Default(); } void CXTPTreeBase::OnTimer(UINT_PTR nIDEvent) { if (m_pTreeCtrl->GetStyle() & TVS_TRACKSELECT) { if (nIDEvent == HOVER_TIMER_PERIOD) { if (m_htiLast != NULL) { CPoint pt(::GetMessagePos()); CRect rc; m_pTreeCtrl->GetWindowRect(&rc); if (rc.PtInRect(pt) == FALSE) { m_pTreeCtrl->KillTimer(HOVER_TIMER_PERIOD); m_htiLast = NULL; m_pTreeCtrl->Invalidate(FALSE); } } else { m_pTreeCtrl->KillTimer(HOVER_TIMER_PERIOD); } return; } } m_pTreeCtrl->Default(); } void CXTPTreeBase::OnNcMouseMove(UINT /*nHitTest*/, CPoint /*point*/) { if (m_pTreeCtrl->GetStyle() & TVS_TRACKSELECT) { if (m_htiLast) { m_htiLast = NULL; m_pTreeCtrl->Invalidate(FALSE); } } m_pTreeCtrl->Default(); } bool CXTPTreeBase::Init() { if (!::IsWindow(m_pTreeCtrl->GetSafeHwnd())) return false; return true; }