/*++ Module Name: Connect.cpp Abstract: This module contains the implementation for CConnectToDialog. This is used to display the Connect To Dfs Root dialog box --*/ #include "stdafx.h" #include #include "DfsGUI.h" #include "Utils.h" // For the LoadStringFromResource and SetStandardCursor method #include "Connect.h" #include "dfshelp.h" static const int iFOLDER_IMAGE = 0; static const int iFOLDER_SELECTED_IMAGE = 1; static const int iDOMAIN_IMAGE = 2; static const int iDOMAIN_SELECTED_IMAGE = 2; static const int iSTANDALONE_DFSROOT_IMAGE = 3; static const int iFT_DFSROOT_IMAGE = 3; static const int iOVERLAY_BUSY_IMAGE = 4; static const int iOVERLAY_ERROR_IMAGE = 5; static const int OV_BUSY = 1; static const int OV_ERROR = 2; CConnectToDialog::CConnectToDialog() { CWaitCursor WaitCursor; // Display the wait cursor m_pBufferManager = NULL; m_hImageList = NULL; (void)Get50Domains(&m_50DomainList); LoadStringFromResource(IDS_DOMAIN_DFSROOTS_LABEL, &m_bstrDomainDfsRootsLabel); LoadStringFromResource(IDS_ALL_DFSROOTS_LABEL, &m_bstrAllDfsRootsLabel); } CConnectToDialog::~CConnectToDialog() { CWaitCursor WaitCursor; // An object to set\reset the cursor to wait cursor if(NULL != m_hImageList) { ImageList_Destroy(m_hImageList); m_hImageList = NULL; } // Free Domain List FreeNetNameList(&m_50DomainList); if (m_pBufferManager) { // // signal all related running threads to terminate // m_pBufferManager->SignalExit(); // // decrement the reference count on the CBufferManager instance // m_pBufferManager->Release(); } } LRESULT CConnectToDialog::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { // // create instance of CBufferManager // m_pBufferManager will be set to NULL if CreateInstance() failed. // (void) CBufferManager::CreateInstance(m_hWnd, &m_pBufferManager); ::SendMessage(GetDlgItem(IDC_EditDfsRoot), EM_LIMITTEXT, DNSNAMELIMIT, 0); InitTVImageList(); // Get the image list for the TV FillupTheTreeView(); // Fill up the Tree View return TRUE; // let the dialog box set the focus to any control it wants. } /*++ This function is called when a user clicks the ? in the top right of a property sheet and then clciks a control, or when they hit F1 in a control. --*/ LRESULT CConnectToDialog::OnCtxHelp( IN UINT i_uMsg, IN WPARAM i_wParam, IN LPARAM i_lParam, IN OUT BOOL& io_bHandled ) { LPHELPINFO lphi = (LPHELPINFO) i_lParam; if (!lphi || lphi->iContextType != HELPINFO_WINDOW || lphi->iCtrlId < 0) return FALSE; ::WinHelp((HWND)(lphi->hItemHandle), DFS_CTX_HELP_FILE, HELP_WM_HELP, (DWORD_PTR)(PVOID)g_aHelpIDs_IDD_DLGCONNECTTO); return TRUE; } /*++ This function handles "What's This" help when a user right clicks the control --*/ LRESULT CConnectToDialog::OnCtxMenuHelp( IN UINT i_uMsg, IN WPARAM i_wParam, IN LPARAM i_lParam, IN OUT BOOL& io_bHandled ) { ::WinHelp((HWND)i_wParam, DFS_CTX_HELP_FILE, HELP_CONTEXTMENU, (DWORD_PTR)(PVOID)g_aHelpIDs_IDD_DLGCONNECTTO); return TRUE; } LRESULT CConnectToDialog::OnGetDataThreadDone( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { _ASSERT(m_pBufferManager); bHandled = TRUE; CEntryData* pEntry = reinterpret_cast(wParam); HRESULT hr = (HRESULT)lParam; _ASSERT(pEntry); CComBSTR bstrNode = pEntry->GetNodeName(); HTREEITEM hItem = pEntry->GetTreeItem(); switch (pEntry->GetEntryType()) { case BUFFER_ENTRY_TYPE_VALID: (void)InsertData(pEntry, hItem); ChangeIcon(hItem, ICONTYPE_NORMAL); break; case BUFFER_ENTRY_TYPE_ERROR: ExpandNodeErrorReport(hItem, bstrNode, pEntry->GetEntryHRESULT()); break; default: _ASSERT(FALSE); break; } bHandled = TRUE; return TRUE; } void CConnectToDialog::ChangeIcon( IN HTREEITEM hItem, IN ICONTYPE IconType ) { TVITEM TVItem; ZeroMemory(&TVItem, sizeof(TVItem)); TVItem.hItem = hItem; TVItem.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE; switch (IconType) { case ICONTYPE_BUSY: TVItem.iImage = iOVERLAY_BUSY_IMAGE; TVItem.iSelectedImage = iOVERLAY_BUSY_IMAGE; break; case ICONTYPE_ERROR: TVItem.iImage = iOVERLAY_ERROR_IMAGE; TVItem.iSelectedImage = iOVERLAY_ERROR_IMAGE; break; default: // ICONTYPE_NORMAL { NODETYPE NodeType = UNASSIGNED; HRESULT hr = GetNodeInfo(hItem, NULL, &NodeType); if (FAILED(hr)) return; switch (NodeType) { case TRUSTED_DOMAIN: TVItem.iImage = iDOMAIN_IMAGE; TVItem.iSelectedImage = iDOMAIN_SELECTED_IMAGE; break; case DOMAIN_DFSROOTS: case ALL_DFSROOTS: TVItem.iImage = iFOLDER_IMAGE; TVItem.iSelectedImage = iFOLDER_SELECTED_IMAGE; break; case FTDFS: TVItem.iImage = iFT_DFSROOT_IMAGE; TVItem.iSelectedImage = iFT_DFSROOT_IMAGE; break; case SADFS: TVItem.iImage = iSTANDALONE_DFSROOT_IMAGE; TVItem.iSelectedImage = iSTANDALONE_DFSROOT_IMAGE; break; default: return; } } } SendDlgItemMessage(IDC_TV, TVM_SETITEM, 0, (LPARAM)&TVItem); UpdateWindow(); } /* void CConnectToDialog::ChangeIcon( IN HTREEITEM hItem, IN ICONTYPE IconType ) { TVITEM TVItem; ZeroMemory(&TVItem, sizeof(TVItem)); TVItem.hItem = hItem; TVItem.mask = TVIF_STATE; TVItem.stateMask = TVIS_OVERLAYMASK; switch (IconType) { case ICONTYPE_BUSY: TVItem.state = INDEXTOOVERLAYMASK(OV_BUSY); break; case ICONTYPE_ERROR: TVItem.state = INDEXTOOVERLAYMASK(OV_ERROR); break; default: TVItem.state = 0; break; } SendDlgItemMessage(IDC_TV, TVM_SETITEM, 0, (LPARAM)&TVItem); UpdateWindow(); } */ void CConnectToDialog::ExpandNodeErrorReport( IN HTREEITEM hItem, IN PCTSTR pszNodeName, IN HRESULT hr ) { // change the icon to "X" dfsDebugOut((_T("Failed to expand: %s, hr=%x\n"), pszNodeName, hr)); SetChildrenToZero(hItem); ChangeIcon(hItem, ICONTYPE_ERROR); } void CConnectToDialog::ExpandNode( IN PCTSTR pszNodeName, IN NODETYPE nNodeType, IN HTREEITEM hParentItem ) { HRESULT hr = S_OK; dfsDebugOut((_T("CConnectToDialog::ExpandNode for %s\n"), pszNodeName)); if (m_pBufferManager) { // // change icon to wait // ChangeIcon(hParentItem, ICONTYPE_BUSY); UpdateWindow(); // // start the thread to calculate a list of servers in the current selected domain // CEntryData *pEntry = NULL; hr = m_pBufferManager->LoadInfo(pszNodeName, nNodeType, hParentItem, &pEntry); if (SUCCEEDED(hr)) { // // Either we get a valid ptr back (ie. data is ready), insert it; // or, a thread is alreay in progress, wait until a THREAD_DONE message. // if (pEntry) { _ASSERT(pEntry->GetEntryType() == BUFFER_ENTRY_TYPE_VALID); (void)InsertData(pEntry, hParentItem); } } else { ExpandNodeErrorReport(hParentItem, pszNodeName, hr); } } return; } HRESULT CConnectToDialog::InsertData( IN CEntryData* pEntry, IN HTREEITEM hParentItem ) { _ASSERT(pEntry); CComBSTR bstrNode = pEntry->GetNodeName(); NODETYPE nNodeType = pEntry->GetNodeType(); NETNAMELIST* pList = pEntry->GetList(); _ASSERT(pList); HRESULT hr = S_OK; if (0 == pList->size()) { SetChildrenToZero(hParentItem); return hr; } int nImageIndex; int nSelectedImageIndex; bool bChildren; nImageIndex = iSTANDALONE_DFSROOT_IMAGE; nSelectedImageIndex = iSTANDALONE_DFSROOT_IMAGE; bChildren = false; for (NETNAMELIST::iterator i = pList->begin(); i != pList->end(); i++) { hr = AddSingleItemtoTV( (*i)->bstrNetName, nImageIndex, nSelectedImageIndex, bChildren, nNodeType, hParentItem); RETURN_IF_FAILED(hr); } // make the child items visible HTREEITEM hChildItem = (HTREEITEM)SendDlgItemMessage( IDC_TV, TVM_GETNEXTITEM, TVGN_CHILD, (LPARAM)hParentItem); if (hChildItem) SendDlgItemMessage(IDC_TV, TVM_ENSUREVISIBLE, 0, (LPARAM)hChildItem); // sort all its child items SendDlgItemMessage(IDC_TV, TVM_SORTCHILDREN, 0, (LPARAM)hParentItem); return S_OK; } LRESULT CConnectToDialog :: OnNotify( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) /*++ Routine Description: Called on WM_NOTIFY. Used to set the Edit box depending on the current selection in the TV. Arguments: uMsg - The windows message being sent. This is WM_NOTIFY. lParam - Info about the message like control for which the message is being sent, what sub type of message, etc Return value: TRUE, if we have handled the message FALSE, if we ignore it. The system handles the message then. --*/ { _ASSERTE(WM_NOTIFY == uMsg); _ASSERTE(lParam != NULL); LRESULT lr = FALSE; // Set it to true if we handle this message. LPNM_TREEVIEW pNMTreeView = (NM_TREEVIEW *) lParam; bHandled = FALSE; // Check if the message is for our tree control if (pNMTreeView && IDC_TV == pNMTreeView->hdr.idFrom) { // Check if the message is for selection change. if (TVN_SELCHANGED == pNMTreeView->hdr.code) { lr = DoNotifySelectionChanged(pNMTreeView); } else if (TVN_ITEMEXPANDING == pNMTreeView->hdr.code) { lr = DoNotifyItemExpanding(pNMTreeView); } else if (NM_DBLCLK == pNMTreeView->hdr.code) { lr = DoNotifyDoubleClick(); } else { lr = FALSE; } } return (lr); } LRESULT CConnectToDialog::DoNotifyDoubleClick( ) /*++ Routine Description: Handles the WM_NOTIFY for NM_DBLCLK. This acts like a click on OK, if the current item is a dfsroot. Arguments: None Return value: TRUE, if we have handled the message FALSE, if we ignore it. The system handles the message then. --*/ { HRESULT hr = E_FAIL; HTREEITEM hCurrentItem = NULL; NODETYPE NodeType = UNASSIGNED; hCurrentItem = TreeView_GetSelection(GetDlgItem(IDC_TV)); if (NULL == hCurrentItem) // Unable to get the current selection { return FALSE; } hr = GetNodeInfo(hCurrentItem, NULL, &NodeType); if(FAILED(hr)) return FALSE; // Take action only on a dfs root if (FTDFS == NodeType || SADFS == NodeType) { int iHandled = TRUE; // A variable used for communication with OnOK OnOK(NULL, 1, 0, iHandled); // On a double click, we simulate a click on OK. _ASSERTE(TRUE == iHandled); return TRUE; } return FALSE; } LRESULT CConnectToDialog::DoNotifyItemExpanding( IN LPNM_TREEVIEW i_pNMTreeView ) /*++ Routine Description: Handles the WM_NOTIFY for TVN_ITEMEXPANDING. If the expand is for "Standalone label", we create another thread to fill it up. Else, we get the Fault Tolerant Dfs Roots for the domain name. Also we removes the '+' sign, if the tree node is empty. Arguments: i_pNMTreeView - Information related to the tree and the node for which the message occurred Return value: TRUE, if we have handled the message FALSE, if we ignore it. The system handles the message then. --*/ { HTREEITEM hCurrentItem = (i_pNMTreeView->itemNew).hItem; _ASSERT(hCurrentItem); // If children actually exist, we have nothing to do. It is a normal expand HTREEITEM hItemChild = (HTREEITEM)SendDlgItemMessage(IDC_TV, TVM_GETNEXTITEM, TVGN_CHILD, (LPARAM)hCurrentItem); if (hItemChild) return FALSE; NODETYPE NodeType = UNASSIGNED; HRESULT hr = GetNodeInfo(hCurrentItem, NULL, &NodeType); if(FAILED(hr)) { SetChildrenToZero(hCurrentItem); return TRUE; } switch (NodeType) { case TRUSTED_DOMAIN: { AddSingleItemtoTV( m_bstrDomainDfsRootsLabel, iFOLDER_IMAGE, iFOLDER_SELECTED_IMAGE, true, DOMAIN_DFSROOTS, hCurrentItem); /* AddSingleItemtoTV( m_bstrAllDfsRootsLabel, iFOLDER_IMAGE, iFOLDER_SELECTED_IMAGE, true, ALL_DFSROOTS, hCurrentItem); */ return TRUE; } case DOMAIN_DFSROOTS: // case ALL_DFSROOTS: { CWaitCursor WaitCursor; // get the domain name HTREEITEM hParentItem = (HTREEITEM)SendDlgItemMessage(IDC_TV, TVM_GETNEXTITEM, TVGN_PARENT, (LPARAM)hCurrentItem); _ASSERT(hParentItem); CComBSTR bstrDomainName; hr = GetNodeInfo(hParentItem, &bstrDomainName, NULL); if(FAILED(hr)) SetChildrenToZero(hCurrentItem); ExpandNode(bstrDomainName, ((NodeType == DOMAIN_DFSROOTS) ? FTDFS : SADFS), hCurrentItem); return TRUE; } default: break; } return FALSE; } LRESULT CConnectToDialog::DoNotifySelectionChanged( IN LPNM_TREEVIEW i_pNMTreeView ) /*++ Routine Description: Handles the WM_NOTIFY for TVN_SELCHANGED. The text in the edit box is set here to the dfs root path. Arguments: i_pNMTreeView - Information related to the tree and the node for which the message occurred Return value: TRUE, if we have handled the message FALSE, if we ignore it. The system handles the message then. --*/ { HRESULT hr = S_OK; CComBSTR bstrNameForEditBox; CComBSTR bstrDisplayName; NODETYPE NodeType; HTREEITEM hItem = (i_pNMTreeView->itemNew).hItem; hr = GetNodeInfo(hItem, &bstrDisplayName, &NodeType); if(FAILED(hr)) return FALSE; switch (NodeType) { case FTDFS: { // get its parent's display name HTREEITEM hParentItem = (HTREEITEM)SendDlgItemMessage(IDC_TV, TVM_GETNEXTITEM, TVGN_PARENT, (LPARAM)hItem); _ASSERT(hParentItem); HTREEITEM hGrandParentItem = (HTREEITEM)SendDlgItemMessage(IDC_TV, TVM_GETNEXTITEM, TVGN_PARENT, (LPARAM)hParentItem); _ASSERT(hGrandParentItem); CComBSTR bstrDomainName; hr = GetNodeInfo(hGrandParentItem, &bstrDomainName, NULL); if(FAILED(hr)) return FALSE; bstrNameForEditBox = _T("\\\\"); bstrNameForEditBox += bstrDomainName; bstrNameForEditBox += _T("\\"); bstrNameForEditBox += bstrDisplayName; } break; /* case SADFS: bstrNameForEditBox = bstrDisplayName; break; */ default: bstrNameForEditBox = _T(""); break; } return SetDlgItemText(IDC_DLG_EDIT, bstrNameForEditBox); } LRESULT CConnectToDialog::OnOK( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled ) /*++ Routine Description: Called when the OK button is pressed. Arguments: None used. Return value: 0. As it is a command handler Calls EndDialog(S_OK). S_OK is passed back as return value of DoModal. This indicates that the dialog ended on OK being pressed --*/ { DWORD dwTextLength = 0; HRESULT hr = S_OK; m_bstrDfsRoot.Empty(); hr = GetInputText(GetDlgItem(IDC_DLG_EDIT), &m_bstrDfsRoot, &dwTextLength); if (FAILED(hr)) { DisplayMessageBoxForHR(hr); ::SetFocus(GetDlgItem(IDC_DLG_EDIT)); return FALSE; } else if (0 == dwTextLength) { DisplayMessageBoxWithOK(IDS_MSG_EMPTY_DFSROOT); ::SetFocus(GetDlgItem(IDC_DLG_EDIT)); return FALSE; } EndDialog(S_OK); return 0; } LRESULT CConnectToDialog::OnCancel( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled ) /*++ Routine Description: Called when the Cancel button is pressed. Arguments: None used. Return value: 0. As it is a command handler Calls EndDialog(S_FALSE). S_FALSE is passed back as return value of DoModal. This indicates that the dialog ended on Cancel being pressed --*/ { EndDialog(S_FALSE); return 0; } BOOL CConnectToDialog :: EndDialog( IN int i_RetCode ) /*++ Routine Description: Overridden method that calls the parent method after some internal processing. This includes deleting the objects stored in the lparams of the TV items. Arguments: None used. Return value: The return value of the parent method. --*/ { ::ShowCursor(FALSE); SetCursor(::LoadCursor(NULL, IDC_WAIT)); ::ShowCursor(TRUE); // Remove the Imagelist from the tree. We destroy it in the dtor SendDlgItemMessage(IDC_TV, TVM_SETIMAGELIST, TVSIL_NORMAL, (LPARAM)NULL); return CDialogImpl::EndDialog(i_RetCode); } STDMETHODIMP CConnectToDialog::get_DfsRoot( OUT BSTR* pVal ) /*++ Routine Description: Return the selected DfsRoot name. Part of the interface IConnectToDialog. Arguments: pVal - Return the BSTR in this. Return value: S_OK, if successful E_FAIL, if the value is unavailable E_INVALIDARG, if the pointer is invalid(NULL) E_OUTOFMEMORY if we run out of memory --*/ { RETURN_INVALIDARG_IF_NULL(pVal); if ((!m_bstrDfsRoot) || (0 == m_bstrDfsRoot.Length())) { return E_FAIL; } *pVal = SysAllocString(m_bstrDfsRoot); RETURN_OUTOFMEMORY_IF_NULL(*pVal); return S_OK; } void CConnectToDialog::SetChildrenToZero( IN HTREEITEM i_hItem ) { TV_ITEM TVItem; ZeroMemory(&TVItem, sizeof TVItem); TVItem.mask = TVIF_CHILDREN; TVItem.cChildren = 0; TVItem.hItem = i_hItem; SendDlgItemMessage( IDC_TV, TVM_SETITEM, 0, (LPARAM)&TVItem); } HRESULT CConnectToDialog::InitTVImageList() { m_hImageList = ImageList_LoadBitmap( _Module.GetModuleInstance(), MAKEINTRESOURCE(IDB_CONNECT_16x16), 16, 8, CLR_DEFAULT); if (NULL == m_hImageList) return E_FAIL; ImageList_SetOverlayImage( m_hImageList, iOVERLAY_BUSY_IMAGE, OV_BUSY); ImageList_SetOverlayImage( m_hImageList, iOVERLAY_ERROR_IMAGE, OV_ERROR); SendDlgItemMessage( IDC_TV, TVM_SETIMAGELIST, TVSIL_NORMAL, (LPARAM)m_hImageList); return S_OK; } HRESULT CConnectToDialog::FillupTheTreeView( ) /*++ Routine Description: This routine does 2 things, adds the NT 5.0 domain names and the Standalone subtree label. Also makes the text over the TV invisible. Arguments: None. Return value: S_OK, On success HRESULT sent by methods called, if it is not S_OK. E_FAIL, on other errors. --*/ { HRESULT hr = S_OK; // // add trusted domains DNS names // FT dfs roots will be added under these nodes // if (m_50DomainList.empty()) return hr; for(NETNAMELIST::iterator i = m_50DomainList.begin(); i != m_50DomainList.end(); i++) { _ASSERTE((*i)->bstrNetName); hr = AddSingleItemtoTV( (*i)->bstrNetName, iDOMAIN_IMAGE, iDOMAIN_SELECTED_IMAGE, true, // Children = true TRUSTED_DOMAIN); if (FAILED(hr)) break; } if (SUCCEEDED(hr)) { // sort the trusted domains only SendDlgItemMessage(IDC_TV, TVM_SORTCHILDREN, 0, 0); } return hr; } HRESULT CConnectToDialog::AddFaultTolerantDfsRoots( IN HTREEITEM i_hCurrentItem, IN BSTR i_bstrDomainName ) { RETURN_INVALIDARG_IF_NULL(i_hCurrentItem); RETURN_INVALIDARG_IF_NULL(i_bstrDomainName); NETNAMELIST DfsRootList; HRESULT hr = GetDomainDfsRoots(&DfsRootList, i_bstrDomainName); if (S_OK != hr) return hr; for (NETNAMELIST::iterator i = DfsRootList.begin(); i != DfsRootList.end(); i++) { hr = AddSingleItemtoTV( (*i)->bstrNetName, iFT_DFSROOT_IMAGE, iFT_DFSROOT_IMAGE, false, FTDFS, // Children = false i_hCurrentItem); BREAK_IF_FAILED(hr); } FreeNetNameList(&DfsRootList); HTREEITEM hChildItem = (HTREEITEM)SendDlgItemMessage( IDC_TV, TVM_GETNEXTITEM, TVGN_CHILD, (LPARAM)i_hCurrentItem); if (hChildItem) SendDlgItemMessage(IDC_TV, TVM_ENSUREVISIBLE, 0, (LPARAM)hChildItem); SendDlgItemMessage(IDC_TV, TVM_SORTCHILDREN, 0, (LPARAM)i_hCurrentItem); return hr; } HRESULT CConnectToDialog::AddSingleItemtoTV( IN const BSTR i_bstrItemLabel, IN const int i_iImageIndex, IN const int i_iImageSelectedIndex, IN const bool i_bChildren, IN const NODETYPE i_NodeType, IN HTREEITEM i_hItemParent /* = NULL */ ) { RETURN_INVALIDARG_IF_NULL(i_bstrItemLabel); HRESULT hr = S_OK; TV_INSERTSTRUCT TVInsertData; TV_ITEM TVItem; HTREEITEM hCurrentItem = NULL; ZeroMemory(&TVItem, sizeof(TVItem)); ZeroMemory(&TVInsertData, sizeof(TVInsertData)); TVItem.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; if (true == i_bChildren) // To decide whether we add the '+' or not { TVItem.mask |= TVIF_CHILDREN; TVItem.cChildren = 1; } TVItem.pszText = i_bstrItemLabel; TVItem.cchTextMax = _tcslen(i_bstrItemLabel); TVItem.iImage = i_iImageIndex; TVItem.iSelectedImage = i_iImageSelectedIndex; TVItem.lParam = (LPARAM)i_NodeType; TVInsertData.hParent = i_hItemParent; TVInsertData.hInsertAfter = TVI_LAST; // No sorting to improve performance TVInsertData.item = TVItem; hCurrentItem = (HTREEITEM) SendDlgItemMessage(IDC_TV, TVM_INSERTITEM, 0, (LPARAM) (LPTV_INSERTSTRUCT) &TVInsertData); if (NULL == hCurrentItem) return E_FAIL; return S_OK; } HRESULT CConnectToDialog::GetNodeInfo( IN HTREEITEM hItem, OUT BSTR* o_bstrName, OUT NODETYPE* pNodeType ) { _ASSERT(o_bstrName || pNodeType); HRESULT hr = S_OK; TCHAR szName[MAX_PATH]; TVITEM TVItem; ZeroMemory(&TVItem, sizeof(TVItem)); TVItem.hItem = hItem; if (o_bstrName) { TVItem.mask |= TVIF_TEXT; TVItem.pszText = szName; TVItem.cchTextMax = MAX_PATH; } if (pNodeType) TVItem.mask |= TVIF_PARAM; if ( SendDlgItemMessage(IDC_TV, TVM_GETITEM, 0, (LPARAM)&TVItem) ) { if (o_bstrName) { *o_bstrName = SysAllocString(szName); if (!*o_bstrName) hr = E_OUTOFMEMORY; } if (pNodeType) { *pNodeType = (NODETYPE)TVItem.lParam; } } else { hr = E_FAIL; } return hr; }