#include "ctlspriv.h" #include "treeview.h" void TV_ScrollItems(PTREE pTree, int nItems, int iTopShownIndex, BOOL fDown); // in: // hItem item to delete // flags controls how/what to delete // TVDI_NORMAL delete this node and all children // TVDI_NONOTIFY don't send notify messages // TVDI_CHILDRENONLY just delete the kids (not the item) void TV_DeleteItemRecurse(PTREE pTree, TREEITEM * hItem, UINT flags) { TREEITEM *hKid; TREEITEM *hNext; TREEITEM *hParent; int i; DBG_ValidateTreeItem(hItem, 0); // // We do this from DeleteItemRecurse(), kind of like how USER sends // Destroy notifications from its FreeWindow() code, so that we get // deletes for parent and children both. // NotifyWinEvent(EVENT_OBJECT_DESTROY, pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hItem)); // // While the item is still valid, clean up if it's the insertion point. // The item needs to be valid because we're going to call other // functions that validate their parameters... // if (hItem == pTree->htiInsert) { TV_SetInsertMark(pTree, NULL, FALSE); ASSERT(pTree->htiInsert == NULL); } // remove all kids (and their kids) for (hKid = hItem->hKids; hKid; hKid = hNext) { hNext = hKid->hNext; // recurse on each child TV_DeleteItemRecurse(pTree, hKid, flags & ~TVDI_CHILDRENONLY); } if ((flags & TVDI_CHILDRENONLY) || !hItem->hParent) return; if (!(flags & TVDI_NONOTIFY)) { NM_TREEVIEW nm; // Let the app clean up after itself nm.itemOld.hItem = hItem; nm.itemOld.lParam = hItem->lParam; nm.itemNew.mask = 0; nm.itemOld.mask = (TVIF_HANDLE | TVIF_PARAM); CCSendNotify(&pTree->ci, TVN_DELETEITEM, &nm.hdr); } // // If anybody has a watch on our item, let him know that it's gone. // i = DPA_GetPtrCount(pTree->hdpaWatch); while (--i >= 0) { PTVWATCHEDITEM pwi = DPA_FastGetPtr(pTree->hdpaWatch, i); ASSERT(pwi); if (pwi->hti == hItem) { pwi->hti = hItem->hNext; pwi->fStale = TRUE; } } hParent = hItem->hParent; ASSERT(hParent); // unlink ourselves from the parent child chain if (hParent->hKids == hItem) { hParent->hKids = hItem->hNext; hKid = NULL; } else { // not the first child, find our previous item (linear search!) hKid = TV_GetNextItem(pTree, hItem, TVGN_PREVIOUS); ASSERT(hKid); hKid->hNext = hItem->hNext; } pTree->cItems--; TV_ScrollBarsAfterRemove(pTree, hItem); // reset tooltip after unlink from the parent child chain if (pTree->hToolTip == hItem) TV_SetToolTipTarget(pTree, NULL); Str_Set(&hItem->lpstr, NULL); TV_MarkAsDead(hItem); // be careful from here down. hItem is unlinked but // still has some valid fields // Check to see if the user has deleted one of the // special items that is stored in the main tree structure. if (hItem == pTree->htiEdit) pTree->htiEdit = NULL; if (hItem == pTree->hDropTarget) pTree->hDropTarget = NULL; if (hItem == pTree->hOldDrop) pTree->hOldDrop = NULL; if (hItem == pTree->hHot ) pTree->hHot = NULL; if (hItem == pTree->htiSearch ) pTree->htiSearch = NULL; // if the caret escaped the collapsed area and landed on us, push it away if (pTree->hCaret == hItem) { HTREEITEM hTemp; if (hItem->hNext) hTemp = hItem->hNext; else { hTemp = VISIBLE_PARENT(hItem); if (!hTemp) hTemp = hKid; // set above when we unlinked from the previous item } // Reset the caret to NULL as to not try to reference our // invalidated item. pTree->hCaret = NULL; TV_SelectItem(pTree, TVGN_CARET, hTemp, (flags & TVDI_NOSELCHANGE) ? 0 : TVSIFI_NOTIFY, 0); ASSERT(pTree->hCaret != hItem); } ASSERT(pTree->hItemPainting != hItem); ControlFree(pTree->hheap, hItem); } // ---------------------------------------------------------------------------- // // Removes the given item and all children from the tree. // Special case: if the given item is the hidden root, all children are // removed, but the hidden root is NOT removed. // // sets cItems // // ---------------------------------------------------------------------------- BOOL TV_DeleteItem(PTREE pTree, TREEITEM * hItem, UINT flags) { if (hItem == TVI_ROOT || !hItem) hItem = pTree->hRoot; // BUGUBG: send TVN_DELETEALLITEMS and TVDI_NONOTIFY if they respond // if (hItem == pTree->hRoot) // etc. if (!ValidateTreeItem(hItem, 0)) return FALSE; // Collapse first to speed things up (not as much scroll bar recalcs) and // to set the top index correctly after the remove. if (hItem != pTree->hRoot) TV_Expand(pTree, TVE_COLLAPSE, hItem, FALSE); else { // TV_Expand punts on the root item, so manually iterate through it's kids TREEITEM *hKid = hItem->hKids; while (hKid) { TV_Expand(pTree, TVE_COLLAPSE, hKid, FALSE); if (!ValidateTreeItem(hKid, 0)) break; // callback during collapse could delete hKid = hKid->hNext; } } // Invalidate everything below this item; must be done AFTER setting the // selection if (hItem->hParent == pTree->hRoot || hItem == pTree->hRoot || ITEM_VISIBLE(hItem->hParent)) { if (pTree->fRedraw) { InvalidateRect(pTree->ci.hwnd, NULL, TRUE); } } else { TV_ScrollBelow(pTree, hItem->hParent, FALSE, FALSE); } // We can pass in the root to clear all items if (hItem == pTree->hRoot) flags |= TVDI_CHILDRENONLY; TV_DeleteItemRecurse(pTree, hItem, flags); ASSERT(pTree->hRoot); // didn't go too far, did we? // maybe everything's gone... // check out our cleanup job if (!pTree->hRoot->hKids) { // the tree itself ASSERT(pTree->cItems == 0); pTree->cItems = 0; // just removed it all, didn't we? pTree->hTop = NULL; AssertMsg(pTree->hCaret == NULL, TEXT("hCaret not NULL, but empty tree")); pTree->hCaret = NULL; pTree->fNameEditPending = FALSE; pTree->cxMax = 0; pTree->xPos = 0; // the invisible root ASSERT(pTree->hRoot->hNext == NULL); pTree->hRoot->hNext = NULL; ASSERT(pTree->hRoot->hParent == NULL); pTree->hRoot->hParent = NULL; ASSERT(pTree->hRoot->hKids == NULL); pTree->hRoot->hKids = NULL; ASSERT(pTree->hRoot->state & TVIS_EXPANDED); pTree->hRoot->state |= (TVIS_EXPANDED | TVIS_EXPANDEDONCE); ASSERT(pTree->hRoot->iLevel == (BYTE)-1); pTree->hRoot->iLevel = (BYTE) -1; ASSERT(pTree->hRoot->iShownIndex == (WORD)-1); pTree->hRoot->iShownIndex = (WORD) -1; } return TRUE; } // ---------------------------------------------------------------------------- // // Creates the hidden root node for the tree -- all items will trace up to // this root, and the first child of the root is the first item in the tree. // // sets hRoot // // ---------------------------------------------------------------------------- BOOL TV_CreateRoot(PTREE pTree) { TREEITEM * hRoot = ControlAlloc(pTree->hheap, sizeof(TREEITEM)); if (!hRoot) return FALSE; // hRoot->hNext = NULL; // hRoot->hKids = NULL; // hRoot->hParent = NULL; hRoot->iLevel = (BYTE) -1; hRoot->state = (TVIS_EXPANDED | TVIS_EXPANDEDONCE); hRoot->iShownIndex = (WORD)-1; hRoot->wSignature = TV_SIG; hRoot->dwAccId = pTree->dwLastAccId++; pTree->hRoot = hRoot; // OLEACC asks for the text of the root item (d'oh!) Str_Set(&hRoot->lpstr, c_szNULL); return TRUE; } #ifdef DEBUG void DumpItem(TREEITEM *hItem) { LPTSTR p; if (hItem->lpstr == LPSTR_TEXTCALLBACK) p = TEXT("(callback)"); else if (hItem->lpstr == NULL) p = TEXT("(null)"); else p = hItem->lpstr; TraceMsg(TF_TREEVIEW, "%s", p); TraceMsg(TF_TREEVIEW, "\tstate:%4.4x show index:%3d level:%2d kids:%ld lparam:%4.4x", hItem->state, hItem->iShownIndex, hItem->iLevel, hItem->fKids, hItem->lParam); } #else #define DumpItem(hItem) #endif // ---------------------------------------------------------------------------- // // Adds the item described by the given arguments to the tree. // // sets hTop, cItems // // ---------------------------------------------------------------------------- TREEITEM * TV_InsertItemA(PTREE pTree, LPTV_INSERTSTRUCTA lpis) { LPSTR pszA = NULL; TREEITEM *ptvi; //HACK Alert! This code assumes that TV_INSERTSTRUCTA is exactly the same // as TV_INSERTSTRUCTW except for the text pointer in the TVITEM COMPILETIME_ASSERT(sizeof(TV_INSERTSTRUCTA) == sizeof(TV_INSERTSTRUCTW)); if (!IsFlagPtr(lpis) && (lpis->DUMMYUNION_MEMBER(item).mask & TVIF_TEXT) && !IsFlagPtr(lpis->DUMMYUNION_MEMBER(item).pszText)) { pszA = lpis->DUMMYUNION_MEMBER(item).pszText; lpis->DUMMYUNION_MEMBER(item).pszText = (LPSTR)ProduceWFromA(pTree->ci.uiCodePage, lpis->DUMMYUNION_MEMBER(item).pszText); if (lpis->DUMMYUNION_MEMBER(item).pszText == NULL) { lpis->DUMMYUNION_MEMBER(item).pszText = pszA; return NULL; } } ptvi = TV_InsertItem( pTree, (LPTV_INSERTSTRUCTW)lpis ); if (pszA) { FreeProducedString(lpis->DUMMYUNION_MEMBER(item).pszText); lpis->DUMMYUNION_MEMBER(item).pszText = pszA; } return ptvi; } TREEITEM * TV_InsertItem(PTREE pTree, LPTV_INSERTSTRUCT lpis) { TREEITEM *hNewItem, *hItem; TREEITEM *hParent; TREEITEM *hInsertAfter; UINT mask; if (!lpis) return NULL; //Bug#94345: Validate LPTV_INSERTSTRUCT // initialize _after_ the check for NULL! hParent = lpis->hParent; hInsertAfter = lpis->hInsertAfter; mask = lpis->DUMMYUNION_MEMBER(item).mask; // don't allow undefined bits AssertMsg((lpis->DUMMYUNION_MEMBER(item).mask & ~TVIF_ALL) == 0, TEXT("Invalid TVIF mask specified")); if (mask & ~TVIF_ALL) { // if they used bogus bits, // restrict to win95 bits only // I'd like to fail completely, but for win95 compat, we can't // // this fixes QuaterDesk's CleanSweep which has bogus garbage on the stack for a mask mask = (TVIF_WIN95 & mask); } TV_DismissEdit(pTree, FALSE); // // Zillions of apps pass garbage for hInsertAfter, so don't fail if // it's invalid. Fortunately, we never dereference hInsertAfter, so // garbage is okay. if (!ValidateTreeItem(hParent, VTI_NULLOK)) // NULL means TVI_ROOT return NULL; DBG_ValidateTreeItem(hInsertAfter, 0); hNewItem = ControlAlloc(pTree->hheap, sizeof(TREEITEM)); if (!hNewItem) { TraceMsg(TF_ERROR, "TreeView: Out of memory"); return NULL; } hNewItem->wSignature = TV_SIG; if (mask & TVIF_TEXT) { // // We will setup the text string next, before we link our self in // as to handle the case where we run out of memory and need to // destroy ourself without having to unlink. // if (!lpis->DUMMYUNION_MEMBER(item).pszText) { hNewItem->lpstr = LPSTR_TEXTCALLBACK; } else { if (!Str_Set(&hNewItem->lpstr, lpis->DUMMYUNION_MEMBER(item).pszText)) { // Memory allocation failure... TraceMsg(TF_ERROR, "TreeView: Out of memory"); TV_MarkAsDead(hNewItem); ControlFree(pTree->hheap, hNewItem); return NULL; } } } else { Str_Set(&hNewItem->lpstr, c_szNULL); } AssertMsg(hNewItem->lpstr != NULL, TEXT("Item added with NULL text")); if ((hParent == NULL) || (hParent == TVI_ROOT)) { hParent = pTree->hRoot; if (!pTree->hTop) pTree->hTop = hNewItem; } else if (!pTree->hRoot->hKids) { TV_MarkAsDead(hNewItem); ControlFree(pTree->hheap, hNewItem); return NULL; } // We will do the sort later, so we can handle TEXTCALLBACK things if ((hInsertAfter == TVI_FIRST || hInsertAfter == TVI_SORT) || !hParent->hKids) { hNewItem->hNext = hParent->hKids; hParent->hKids = hNewItem; } else { // Bug#94348: we should cache the last insert after pointer to try to // catch the case of consecutive adds to the end of a node if (hInsertAfter == TVI_LAST) for (hItem = hParent->hKids; hItem->hNext; hItem = hItem->hNext) ; else { for (hItem = hParent->hKids; hItem->hNext; hItem = hItem->hNext) if (hItem == hInsertAfter) break; } hNewItem->hNext = hItem->hNext; hItem->hNext = hNewItem; } // hNewItem->hKids = NULL; hNewItem->hParent = hParent; hNewItem->iLevel = hParent->iLevel + 1; // hNewItem->iWidth = 0; // hNewItem->state = 0; if ((mask & TVIF_INTEGRAL) && LOWORD(lpis->DUMMYUNION_MEMBER(itemex).iIntegral) > 0) { hNewItem->iIntegral = LOWORD(lpis->DUMMYUNION_MEMBER(itemex).iIntegral); } else { hNewItem->iIntegral = 1; } if (pTree->hTop == hNewItem) hNewItem->iShownIndex = 0; // calc me please! else hNewItem->iShownIndex = (WORD)-1; // calc me please! if (mask & TVIF_IMAGE) hNewItem->iImage = (WORD) lpis->DUMMYUNION_MEMBER(item).iImage; if (mask & TVIF_SELECTEDIMAGE) hNewItem->iSelectedImage = (WORD) lpis->DUMMYUNION_MEMBER(item).iSelectedImage; if (mask & TVIF_PARAM) hNewItem->lParam = lpis->DUMMYUNION_MEMBER(item).lParam; if (mask & TVIF_STATE) hNewItem->state = lpis->DUMMYUNION_MEMBER(item).state & lpis->DUMMYUNION_MEMBER(item).stateMask; // if we're in check box mode, inforce that it has a check box if (pTree->ci.style & TVS_CHECKBOXES) { if ((hNewItem->state & TVIS_STATEIMAGEMASK) == 0) { hNewItem->state |= INDEXTOSTATEIMAGEMASK(1); } } if ((hNewItem->state & TVIS_BOLD) && !pTree->hFontBold) //$BOLD TV_CreateBoldFont(pTree); //$BOLD // TraceMsg(TF_TRACE, "Tree: Inserting i = %d state = %d", TV_StateIndex(&lpis->item), lpis->item.state); if (mask & TVIF_CHILDREN) { switch (lpis->DUMMYUNION_MEMBER(item).cChildren) { case I_CHILDRENCALLBACK: hNewItem->fKids = KIDS_CALLBACK; break; case I_CHILDRENAUTO: hNewItem->fKids = KIDS_COMPUTE; break; case 0: hNewItem->fKids = KIDS_FORCE_NO; break; default: hNewItem->fKids = KIDS_FORCE_YES; break; } } hNewItem->dwAccId = pTree->dwLastAccId++; // accept state bits on create? // mask & TVIF_STATE pTree->cItems++; // I don't want to do any callbacks until the item is completed // so sorting waits until the end // special case an only child for speed // (hKids && hKids->hNext means more than one child) if ((hInsertAfter == TVI_SORT) && hParent->hKids && hParent->hKids->hNext) { TVITEMEX sThisItem, sNextItem; TCHAR szThis[64], szNext[64]; sThisItem.pszText = szThis; sThisItem.cchTextMax = ARRAYSIZE(szThis); TV_GetItem(pTree, hNewItem, TVIF_TEXT, &sThisItem); // We know that the first kid of hParent is hNewItem for (hItem = hNewItem->hNext; hItem; hItem = hItem->hNext) { sNextItem.pszText = szNext; sNextItem.cchTextMax = ARRAYSIZE(szNext); TV_GetItem(pTree, hItem, TVIF_TEXT, &sNextItem); if (lstrcmpi(sThisItem.pszText, sNextItem.pszText) < 0) break; hInsertAfter = hItem; } // Check if this is still the first item if (hInsertAfter != TVI_SORT) { // Move this item from the beginning to where it // should be hParent->hKids = hNewItem->hNext; hNewItem->hNext = hInsertAfter->hNext; hInsertAfter->hNext = hNewItem; } } if ((hNewItem->hNext == pTree->hTop) && !pTree->fVert) { // there's no scrollbars and we got added before the top // item. we're now the top. hNewItem->iShownIndex = 0; pTree->hTop = hNewItem; } if (pTree->fRedraw) { BOOL fVert = pTree->fVert; RECT rc; RECT rc2; if (TV_ScrollBarsAfterAdd(pTree, hNewItem)) { // scroll everything down one if (ITEM_VISIBLE(hNewItem)) { int iTop = hNewItem->iShownIndex - pTree->hTop->iShownIndex; // if there wasn't a scrollbar and we're the 0th item, // TV_ScrollBarsAfterAdd already scrolled us if (iTop > 0 || !fVert) TV_ScrollItems(pTree, hNewItem->iIntegral, iTop + hNewItem->iIntegral - 1, TRUE); } } // connect the lines, add the buttons, etc. on the item above // TV_GetPrevVisItem only works after TV_Scroll* stuff is done if (TV_GetItemRect(pTree, hNewItem, &rc, FALSE)) { // find the previous sibling or the parent if no prev sib. if (hParent->hKids == hNewItem) { hItem = hParent; } else { hItem = hParent->hKids; while ( hItem->hNext != hNewItem ) { ASSERT(hItem->hNext); hItem = hItem->hNext; } } // invalidate from there to the new one if (TV_GetItemRect(pTree, hItem, &rc2, FALSE)) { rc.top = rc2.top; } RedrawWindow(pTree->ci.hwnd, &rc, NULL, RDW_INVALIDATE | RDW_ERASE); } } // DumpItem(hNewItem); NotifyWinEvent(EVENT_OBJECT_CREATE, pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hNewItem)); if (pTree->hToolTip) { TV_PopBubble(pTree); } return hNewItem; } void TV_DeleteHotFonts(PTREE pTree) { if (pTree->hFontHot) DeleteObject(pTree->hFontHot); if (pTree->hFontBoldHot) DeleteObject(pTree->hFontBoldHot); pTree->hFontHot = pTree->hFontBoldHot = NULL; } // ---------------------------------------------------------------------------- // // Frees all allocated memory and objects associated with the tree. // // ---------------------------------------------------------------------------- void TV_DestroyTree(PTREE pTree) { HWND hwnd = pTree->ci.hwnd; ASSERT(pTree->hRoot); pTree->fRedraw = FALSE; TV_OnSetBkColor(pTree, (COLORREF)-1); if (pTree->hbrLine) { DeleteObject(pTree->hbrLine); } if (pTree->hbrText) { DeleteObject(pTree->hbrText); } if (pTree->hCurHot) { DestroyCursor(pTree->hCurHot); } if (IsWindow(pTree->hwndToolTips)) { DestroyWindow(pTree->hwndToolTips); } pTree->hwndToolTips = NULL; if (IsWindow(pTree->hwndEdit)) { DestroyWindow(pTree->hwndEdit); } pTree->hwndEdit = NULL; TV_DeleteItem(pTree, pTree->hRoot, TVDI_CHILDRENONLY | TVDI_NOSELCHANGE); if (pTree->hRoot) { Str_Set(&pTree->hRoot->lpstr, NULL); // No point in marking dead since the entire control is going away ControlFree(pTree->hheap, pTree->hRoot); } if (pTree->hdcBits) { if (pTree->hBmp) { SelectObject(pTree->hdcBits, pTree->hStartBmp); DeleteObject(pTree->hBmp); } DeleteDC(pTree->hdcBits); } if (pTree->fCreatedFont && pTree->hFont) { DeleteObject(pTree->hFont); } if (pTree->hFontBold) { DeleteObject(pTree->hFontBold); } Str_Set(&pTree->pszTip, NULL); if (pTree->pszTipA) { LocalFree(pTree->pszTipA); } TV_DeleteHotFonts(pTree); if (pTree->hdpaWatch) { DPA_Destroy(pTree->hdpaWatch); } IncrementSearchFree(&pTree->is); if (pTree->hTheme) { CloseThemeData(pTree->hTheme); } NearFree(pTree); // Don't try to use this var when window is destroyed... SetWindowInt(hwnd, 0, 0); } void TV_CreateToolTips(PTREE pTree); void TV_InitThemeMetrics(PTREE pTree, HTHEME hTheme) { COLORREF cr; HRESULT hr = GetThemeColor(hTheme, 0, 0, TMT_COLOR, &cr); if (SUCCEEDED(hr)) SendMessage(pTree->ci.hwnd, TVM_SETBKCOLOR, 0, cr); // Line color hr = GetThemeColor(hTheme, TVP_BRANCH, 0, TMT_COLOR, &cr); if (SUCCEEDED(hr)) SendMessage(pTree->ci.hwnd, TVM_SETLINECOLOR, 0, cr); } // ---------------------------------------------------------------------------- // // Allocates space for the tree and initializes the tree's data // // ---------------------------------------------------------------------------- LRESULT TV_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreate) { HTHEME hTheme; HRESULT hr = E_FAIL; PTREE pTree = NearAlloc(sizeof(TREE)); if (!pTree) return -1; // fail the create window pTree->hheap = GetProcessHeap(); if (!TV_CreateRoot(pTree)) { NearFree((HLOCAL)pTree); return -1; // fail the create window } pTree->hdpaWatch = DPA_Create(8); if (!pTree->hdpaWatch) { // No point in marking dead since the entire control is going away ControlFree(pTree->hheap, pTree->hRoot); NearFree((HLOCAL)pTree); return -1; // fail the create window } SetWindowPtr(hwnd, 0, pTree); CIInitialize(&pTree->ci, hwnd, lpCreate); if (lpCreate->dwExStyle & WS_EX_RTLREADING) pTree->ci.style |= TVS_RTLREADING; pTree->fRedraw = TRUE; pTree->clrim = CLR_DEFAULT; pTree->clrText = (COLORREF)-1; pTree->clrBkNonTheme = pTree->clrBk = (COLORREF)-1; pTree->clrLineNonTheme = pTree->clrLine = CLR_DEFAULT; pTree->hbrLine = g_hbrGrayText; pTree->cxBorder = 3; hTheme = OpenThemeData(pTree->ci.hwnd, L"TreeView"); if (hTheme) { TV_InitThemeMetrics(pTree, hTheme); } pTree->hTheme = hTheme; pTree->hbrText = g_hbrWindowText; // pTree->fHorz = FALSE; // pTree->fVert = FALSE; // pTree->fFocus = FALSE; // pTree->fNameEditPending = FALSE; // pTree->cxMax = 0; // pTree->cxWnd = 0; // pTree->cyWnd = 0; // pTree->hTop = NULL; // pTree->hCaret = NULL; // pTree->hDropTarget = NULL; // pTree->hOldDrop = NULL; // pTree->cItems = 0; // pTree->cShowing = 0; pTree->cFullVisible = 1; // pTree->hdcBits = NULL; // pTree->hBmp = NULL; // pTree->hbrBk = NULL; // pTree->xPos = 0; // pTree->cxIndent = 0; // init this for real in TV_OnSetFont() // pTree->dwCDDepth = 0; pTree->uMaxScrollTime = SSI_DEFAULT; pTree->dwLastAccId = 1; // Start at 1 because 0 means self // pTree->dwExStyle = 0; // pTree->fInTextCallback = FALSE; TV_OnSetFont(pTree, NULL, TRUE); // You cannot combine TVS_HASLINES and TVS_FULLROWSELECT // because it doesn't work if (pTree->ci.style & TVS_HASLINES) { if (pTree->ci.style & TVS_FULLROWSELECT) { DebugMsg(DM_ERROR, TEXT("Cannot combine TVS_HASLINES and TVS_FULLROWSELECT")); } pTree->ci.style &= ~TVS_FULLROWSELECT; } if (!(pTree->ci.style & TVS_NOTOOLTIPS)) { TV_CreateToolTips(pTree); } SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE); SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE); return 0; // success } void TV_CreateToolTips(PTREE pTree) { DWORD exStyle = 0; if(pTree->ci.style & TVS_RTLREADING) { exStyle |= WS_EX_RTLREADING; } pTree->hwndToolTips = CreateWindowEx(WS_EX_TRANSPARENT | exStyle, c_szSToolTipsClass, NULL, WS_POPUP | TTS_NOPREFIX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, pTree->ci.hwnd, NULL, HINST_THISDLL, NULL); if (pTree->hwndToolTips) { TOOLINFO ti; ti.cbSize = sizeof(ti); ti.uFlags = TTF_IDISHWND | TTF_TRANSPARENT; ti.hwnd = pTree->ci.hwnd; ti.uId = (UINT_PTR)pTree->ci.hwnd; ti.lpszText = LPSTR_TEXTCALLBACK; ti.lParam = 0; SendMessage(pTree->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); SendMessage(pTree->hwndToolTips, WM_SETFONT, (WPARAM)pTree->hFont, (LPARAM)TRUE); SendMessage(pTree->hwndToolTips, TTM_SETDELAYTIME, TTDT_INITIAL, (LPARAM)500); } else pTree->ci.style |= (TVS_NOTOOLTIPS); }