#endif
-// this global variable is used on vista systems for preventing unwanted
-// item state changes in the vista tree control. It is only used in
+// this helper class is used on vista systems for preventing unwanted
+// item state changes in the vista tree control. It is only effective in
// multi-select mode on vista systems.
-static HTREEITEM gs_unlockItem = NULL;
+// The vista tree control includes some new code that originally broke the
+// multi-selection tree, causing seemingly spurious item selection state changes
+// during Shift or Ctrl-click item selection. (To witness the original broken
+// behavior, simply make IsLocked() below always return false). This problem was
+// solved by using the following class to 'unlock' an item's selection state.
class TreeItemUnlocker
{
public:
- TreeItemUnlocker(HTREEITEM item) { gs_unlockItem = item; }
- ~TreeItemUnlocker() { gs_unlockItem = NULL; }
+ // unlock a single item
+ TreeItemUnlocker(HTREEITEM item) { ms_unlockedItem = item; }
+
+ // unlock all items, don't use unless absolutely necessary
+ TreeItemUnlocker() { ms_unlockedItem = (HTREEITEM)-1; }
+
+ // lock everything back
+ ~TreeItemUnlocker() { ms_unlockedItem = NULL; }
+
+
+ // check if the item state is currently locked
+ static bool IsLocked(HTREEITEM item)
+ { return ms_unlockedItem != (HTREEITEM)-1 && item != ms_unlockedItem; }
+
+private:
+ static HTREEITEM ms_unlockedItem;
};
+HTREEITEM TreeItemUnlocker::ms_unlockedItem = NULL;
// ----------------------------------------------------------------------------
// private functions
SetForegroundColour(wxWindow::GetParent()->GetForegroundColour());
#endif
-
- // VZ: this is some experimental code which may be used to get the
- // TVS_CHECKBOXES style functionality for comctl32.dll < 4.71.
- // AFAIK, the standard DLL does about the same thing anyhow.
-#if 0
- if ( m_windowStyle & wxTR_MULTIPLE )
- {
- wxBitmap bmp;
-
- // create the DC compatible with the current screen
- HDC hdcMem = CreateCompatibleDC(NULL);
-
- // create a mono bitmap of the standard size
- int x = ::GetSystemMetrics(SM_CXMENUCHECK);
- int y = ::GetSystemMetrics(SM_CYMENUCHECK);
- wxImageList imagelistCheckboxes(x, y, false, 2);
- HBITMAP hbmpCheck = CreateBitmap(x, y, // bitmap size
- 1, // # of color planes
- 1, // # bits needed for one pixel
- 0); // array containing colour data
- SelectObject(hdcMem, hbmpCheck);
-
- // then draw a check mark into it
- RECT rect = { 0, 0, x, y };
- if ( !::DrawFrameControl(hdcMem, &rect,
- DFC_BUTTON,
- DFCS_BUTTONCHECK | DFCS_CHECKED) )
- {
- wxLogLastError(wxT("DrawFrameControl(check)"));
- }
-
- bmp.SetHBITMAP((WXHBITMAP)hbmpCheck);
- imagelistCheckboxes.Add(bmp);
-
- if ( !::DrawFrameControl(hdcMem, &rect,
- DFC_BUTTON,
- DFCS_BUTTONCHECK) )
- {
- wxLogLastError(wxT("DrawFrameControl(uncheck)"));
- }
-
- bmp.SetHBITMAP((WXHBITMAP)hbmpCheck);
- imagelistCheckboxes.Add(bmp);
-
- // clean up
- ::DeleteDC(hdcMem);
-
- // set the imagelist
- SetStateImageList(&imagelistCheckboxes);
- }
-#endif // 0
-
wxSetCCUnicodeFormat(GetHwnd());
return true;
// multiple selections emulation
// ----------------------------------------------------------------------------
-bool wxTreeCtrl::IsItemChecked(const wxTreeItemId& item) const
-{
- wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
-
- // receive the desired information.
- wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_STATEIMAGEMASK);
- DoGetItem(&tvItem);
-
- // state image indices are 1 based
- return ((tvItem.state >> 12) - 1) == 1;
-}
-
-void wxTreeCtrl::SetItemCheck(const wxTreeItemId& item, bool check)
-{
- wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
-
- // receive the desired information.
- wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_STATEIMAGEMASK);
-
- DoGetItem(&tvItem);
-
- // state images are one-based
- tvItem.state = (check ? 2 : 1) << 12;
-
- DoSetItem(&tvItem);
-}
-
size_t wxTreeCtrl::GetSelections(wxArrayTreeItemIds& selections) const
{
TraverseSelections selector(this, selections);
tvIns.item.lParam = (LPARAM)param;
tvIns.item.mask = mask;
+ // don't use the hack below for the children of hidden root: this results
+ // in a crash inside comctl32.dll when we call TreeView_GetItemRect()
+ const bool firstChild = !IsHiddenRoot(parent) &&
+ !TreeView_GetChild(GetHwnd(), HITEM(parent));
+
HTREEITEM id = TreeView_InsertItem(GetHwnd(), &tvIns);
if ( id == 0 )
{
wxLogLastError(wxT("TreeView_InsertItem"));
}
+ // apparently some Windows versions (2000 and XP are reported to do this)
+ // sometimes don't refresh the tree after adding the first child and so we
+ // need this to make the "[+]" appear
+ if ( firstChild )
+ {
+ RECT rect;
+ TreeView_GetItemRect(GetHwnd(), HITEM(parent), &rect, FALSE);
+ ::InvalidateRect(GetHwnd(), &rect, FALSE);
+ }
+
// associate the application tree item with Win32 tree item handle
param->SetItem(id);
void wxTreeCtrl::Delete(const wxTreeItemId& item)
{
+ // unlock tree selections on vista, without this the
+ // tree ctrl will eventually crash after item deletion
+ TreeItemUnlocker unlock_all;
+
if ( !TreeView_DeleteItem(GetHwnd(), HITEM(item)) )
{
wxLogLastError(wxT("TreeView_DeleteItem"));
// delete all children (but don't delete the item itself)
void wxTreeCtrl::DeleteChildren(const wxTreeItemId& item)
{
+ // unlock tree selections on vista for the duration of this call
+ TreeItemUnlocker unlock_all;
+
wxTreeItemIdValue cookie;
wxArrayTreeItemIds children;
void wxTreeCtrl::DeleteAllItems()
{
+ // unlock tree selections on vista for the duration of this call
+ TreeItemUnlocker unlock_all;
+
// delete the "virtual" root item.
if ( GET_VIRTUAL_ROOT() )
{
// that can be used to regulate this incorrect behavior. The
// following messages will allow only the unlocked item's selection
// state to change
+
case TVN_ITEMCHANGINGA:
case TVN_ITEMCHANGINGW:
{
{
// get info about the item about to be changed
NMTVITEMCHANGE* info = (NMTVITEMCHANGE*)lParam;
- if (info->hItem != gs_unlockItem)
+ if (TreeItemUnlocker::IsLocked(info->hItem))
{
// item's state is locked, don't allow the change
// returning 1 will disallow the change
// notify us before painting each item
*result = m_hasAnyAttr ? CDRF_NOTIFYITEMDRAW
: CDRF_DODEFAULT;
+
+ // windows in TreeCtrl use one-based index for item state images,
+ // 0 indexed image is not being used, we're using zero-based index,
+ // so we have to add temp image (of zero index) to state image list
+ // before we draw any item, then after items are drawn we have to
+ // delete it (in POSTPAINT notify)
+ if (m_imageListState && m_imageListState->GetImageCount() > 0)
+ {
+ #define hImageList (HIMAGELIST)m_imageListState->GetHIMAGELIST()
+
+ // add temporary image
+ int width, height;
+ m_imageListState->GetSize(0, width, height);
+
+ HBITMAP hbmpTemp = ::CreateBitmap(width, height, 1, 1, NULL);
+ int index = ::ImageList_Add(hImageList, hbmpTemp, hbmpTemp);
+ ::DeleteObject(hbmpTemp);
+
+ if ( index != -1 )
+ {
+ // move images to right
+ for ( int i = index; i > 0; i-- )
+ ::ImageList_Copy(hImageList, i, hImageList, i-1, 0);
+
+ // we must remove the image in POSTPAINT notify
+ *result |= CDRF_NOTIFYPOSTPAINT;
+ }
+
+ #undef hImageList
+ }
+ break;
+
+ case CDDS_POSTPAINT:
+ // we are deleting temp image of 0 index, which was
+ // added before items were drawn (in PREPAINT notify)
+ if (m_imageListState && m_imageListState->GetImageCount() > 0)
+ m_imageListState->Remove(0);
break;
case CDDS_ITEMPREPAINT:
switch ( hdr->code )
{
case NM_DBLCLK:
- // we translate NM_DBLCLK into ACTIVATED event, so don't interpret
- // the return code of this event handler as the return value for
- // NM_DBLCLK - otherwise, double clicking the item to toggle its
- // expanded status would never work
- *result = false;
+ // we translate NM_DBLCLK into ACTIVATED event and if the user
+ // handled the activation of the item we shouldn't proceed with
+ // also using the same double click for toggling the item expanded
+ // state -- but OTOH do let the user to expand/collapse the item by
+ // double clicking on it if the activation is not handled specially
+ *result = processed;
break;
case NM_RCLICK:
// why do they define INDEXTOSTATEIMAGEMASK but not the inverse?
#define STATEIMAGEMASKTOINDEX(state) (((state) & TVIS_STATEIMAGEMASK) >> 12)
-void wxTreeCtrl::SetState(const wxTreeItemId& node, int state)
+int wxTreeCtrl::DoGetItemState(const wxTreeItemId& item) const
{
- TV_ITEM tvi;
- tvi.hItem = (HTREEITEM)node.m_pItem;
- tvi.mask = TVIF_STATE;
- tvi.stateMask = TVIS_STATEIMAGEMASK;
-
- // Select the specified state, or -1 == cycle to the next one.
- if ( state == -1 )
- {
- TreeView_GetItem(GetHwnd(), &tvi);
-
- state = STATEIMAGEMASKTOINDEX(tvi.state) + 1;
- if ( state == m_imageListState->GetImageCount() )
- state = 1;
- }
+ wxCHECK_MSG( item.IsOk(), wxTREE_ITEMSTATE_NONE, wxT("invalid tree item") );
- wxCHECK_RET( state < m_imageListState->GetImageCount(),
- _T("wxTreeCtrl::SetState(): item index out of bounds") );
-
- tvi.state = INDEXTOSTATEIMAGEMASK(state);
+ // receive the desired information
+ wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_STATEIMAGEMASK);
+ DoGetItem(&tvItem);
- TreeView_SetItem(GetHwnd(), &tvi);
+ // state images are one-based
+ return STATEIMAGEMASKTOINDEX(tvItem.state) - 1;
}
-int wxTreeCtrl::GetState(const wxTreeItemId& node)
+void wxTreeCtrl::DoSetItemState(const wxTreeItemId& item, int state)
{
- TV_ITEM tvi;
- tvi.hItem = (HTREEITEM)node.m_pItem;
- tvi.mask = TVIF_STATE;
- tvi.stateMask = TVIS_STATEIMAGEMASK;
- TreeView_GetItem(GetHwnd(), &tvi);
+ wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
- return STATEIMAGEMASKTOINDEX(tvi.state);
+ wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_STATEIMAGEMASK);
+
+ // state images are one-based
+ // 0 if no state image display (wxTREE_ITEMSTATE_NONE = -1)
+ tvItem.state = INDEXTOSTATEIMAGEMASK(state + 1);
+
+ DoSetItem(&tvItem);
}
#endif // wxUSE_TREECTRL