// // CControlTreeListView.cpp // // (c)Copyright 2004 Steven J. Eschweiler. All Rights Reserved. // #define _WIN32_WINNT 0x0500 // for Mouse Wheel support #include <winsock2.h> // Always include before windows.h for SocketTools 5.x to work #include <windows.h> #include <assert.h> #include "CControlTreeListView.h" #include "resource.h" //#include <commctrl.h> // for _TrackMouseEvent // Base class is CBaseClassWindow CControlTreeListView::CControlTreeListView(void) { m_hwnd = NULL; m_nContentWidth = 0; m_nContentHeight = 0; m_nHotColumn = -1; m_nMinColumnWidth = 8; // Absolute safest minimum is 8 - otherwise it might crash... m_BufferRGB = NULL; m_nWhatsCaptured = m_enumWC_NothingOrScrollBar; m_pAnchorNode=NULL; m_xDragRectangleAnchor=0; m_yDragRectangleAnchor=0; m_xDragRectanglePos=0; m_yDragRectanglePos=0; m_bTimerSet[0] = false; m_bTimerSet[1] = false; m_bTimerSet[2] = false; m_bTimerSet[3] = false; pCControlTreeListView_DragAndDrop = NULL; nFindFirstNextSelectedIndex = -1; m_pPrevNodeClick = NULL; } CControlTreeListView::~CControlTreeListView(void) { if (m_BufferRGB!=NULL) delete [] m_BufferRGB; // Traverse the tree and delete all the nodes RecursivelyDeleteNodes(&m_Root); m_Root.vpChild.clear(); m_Root.Child.clear(); m_Root.strColumn.clear(); m_Root.strColumnSortValue.clear(); m_Root.smIcon.clear(); } void CControlTreeListView::RecursivelyDeleteNodes(CNode *pNode) { //for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++) for (std::vector<CNode *>::reverse_iterator iter=pNode->vpChild.rbegin(); iter!=pNode->vpChild.rend(); iter++) RecursivelyDeleteNodes(*iter); if (pNode->pNodeParent!=NULL) // This is not a root node so go ahead and delete it delete pNode; } void CControlTreeListView::OnCreate() { // We create our font here HDC hdc = GetDC(NULL); // We release it laster assert(hdc!=NULL); int nPointSize = 8; int nHeight = -MulDiv(nPointSize, GetDeviceCaps(hdc, LOGPIXELSY), 72); m_hFont = CreateFont( nHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH, "Tahoma"); if (m_hFont==NULL) m_hFont = CreateFont( nHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH, "Arial"); SetFont(m_hFont,true); m_hCursorHand = LoadCursor(NULL, IDC_HAND); m_hCursorSizeWE = LoadCursor(NULL, IDC_SIZEWE); m_hCursorArrow = LoadCursor(NULL, IDC_ARROW); m_hCursorArrowCopy = LoadCursor(GetModuleHandle(NULL), MAKEINTRESOURCE(IDC_ARROW_COPY)); m_bNeedToRestartMouseTrackEvent = true; // Determine scrollbar width and height ShowScrollBar(m_hwnd,SB_HORZ,TRUE); ShowScrollBar(m_hwnd,SB_VERT,TRUE); SCROLLBARINFO sbi; sbi.cbSize = sizeof(SCROLLBARINFO); GetScrollBarInfo(m_hwnd,OBJID_VSCROLL,&sbi); m_nVScrollWidth = sbi.rcScrollBar.right - sbi.rcScrollBar.left; GetScrollBarInfo(m_hwnd,OBJID_HSCROLL,&sbi); m_nHScrollHeight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top; // Now hide it initially ShowScrollBar(m_hwnd,SB_HORZ,FALSE); ShowScrollBar(m_hwnd,SB_VERT,FALSE); //Update(false); } bool CControlTreeListView::Init(HWND hwndParent,UINT nControlID,int x,int y,int width,int height,UINT ResourceIDExpandPlusBMP,UINT ResourceIDExpandMinusBMP, bool bWholeLineSelect, bool bWholeLineHighlight, bool bMultilineSelect, bool bShowRootExpandTabs, COLORREF clrBackground,UINT ResourceIDCheckedBMP,UINT ResourceIDUncheckedBMP,UINT ResourceIDPartiallyCheckedBMP) { // Make sure it's not already created assert(m_hwnd==NULL); if (ResourceIDCheckedBMP!=0 && ResourceIDUncheckedBMP!=0 && ResourceIDPartiallyCheckedBMP!=0) { m_bUsingCheckboxes = true; m_bmpChecked.Init(ResourceIDCheckedBMP); m_bmpUnchecked.Init(ResourceIDUncheckedBMP); m_bmpPartiallyChecked.Init(ResourceIDPartiallyCheckedBMP); } else { m_bUsingCheckboxes = false; } //m_bmpExpandPlus.Init(ResourceIDExpandPlusBMP); //m_bmpExpandMinus.Init(ResourceIDExpandMinusBMP); m_ResourceIDExpandPlusBMP = ResourceIDExpandPlusBMP; m_ResourceIDExpandMinusBMP = ResourceIDExpandMinusBMP; if (m_ResourceIDExpandPlusBMP!=0) m_bmpExpandPlus.Init(m_ResourceIDExpandPlusBMP); if (m_ResourceIDExpandMinusBMP!=0) m_bmpExpandMinus.Init(m_ResourceIDExpandMinusBMP); m_hwnd = NULL; m_hwndParent = NULL; m_bWholeLineSelect = bWholeLineSelect; m_bWholeLineHighlight = bWholeLineHighlight; m_bMultilineSelect = bMultilineSelect; m_bShowRootExpandTabs = bShowRootExpandTabs; // We determine node heights now CElementText cetTmp; cetTmp.Init("Tahoma",8,RGB(0,0,0),"Mg"); m_nNodeHeight = cetTmp.GetHeight(); if (m_nNodeHeight<16) m_nNodeHeight=16; if (m_nNodeHeight<GetSystemMetrics(SM_CYSMICON)) m_nNodeHeight=GetSystemMetrics(SM_CYSMICON); // Node indent amount m_nIndentMultiplier = 16; // By default, there is one column width, so we set the width to the window width bUsingColumns = false; m_vnColumnRightOffset.push_back(width); m_nColumnHeaderHeight = 0; // Initialize default root node m_Root.nIndentLevel = -1; m_Root.pNodeParent = NULL; m_Root.strNodeID = "ROOT"; //m_Root.Child.clear(); //m_Root.vpChild.clear(); m_Root.bTVExpandedAndVisible = true; return (CBaseClassWindow::Init( NULL, NULL, NULL, WS_HSCROLL|WS_VSCROLL| WS_TABSTOP|WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS, // WS_CLIPSIBLINGS avoid problems with moved by SplitterBar x,y,width,height, reinterpret_cast<HMENU>(nControlID), clrBackground ) ); } void CControlTreeListView::PaintDragRectangle(HDC hdc,int xDragRectangleAnchor,int yDragRectangleAnchor,int xDragRectanglePos,int yDragRectanglePos) { if (m_nWhatsCaptured==m_enumWC_DragRect) { BYTE nDstR, nDstG, nDstB; BYTE nNewR, nNewG, nNewB; BYTE nSrcR, nSrcG, nSrcB, nSrcA; int yStart,yEnd,xStart,xEnd; xStart = xDragRectangleAnchor; yStart = yDragRectangleAnchor; xEnd = xDragRectanglePos; yEnd = yDragRectanglePos; if (yDragRectangleAnchor>yDragRectanglePos) { yStart = yDragRectanglePos; yEnd = yDragRectangleAnchor; } if (xDragRectangleAnchor>xDragRectanglePos) { xStart = xDragRectanglePos; xEnd = xDragRectangleAnchor; } if (yStart<m_nColumnHeaderHeight) yStart=m_nColumnHeaderHeight; if (xStart<0) xStart=0; if (yEnd>(this->GetHeight()-0)) // -m_nHScrollHeight yEnd=(this->GetHeight()-0); // -m_nHScrollHeight if (xEnd>(this->GetWidth()-0)) // -m_nVScrollWidth xEnd=(this->GetWidth()-0); // -m_nVScrollWidth // OK, we got bugs regarding this so we do this for safety if (yEnd<m_nColumnHeaderHeight) yEnd=m_nColumnHeaderHeight; if (xEnd<0) xEnd=0; if (yStart>(this->GetHeight()-0)) // -m_nHScrollHeight yStart=(this->GetHeight()-0); // -m_nHScrollHeight if (xStart>(this->GetWidth()-0)) // -m_nVScrollWidth xStart=(this->GetWidth()-0); // -m_nVScrollWidth // No need to render and waste time... this could happen for a lot of reasons if (yStart==yEnd || xStart==xEnd) return; BITMAPINFO bi; bi.bmiHeader.biSize = sizeof(bi.bmiHeader); bi.bmiHeader.biWidth = 2000; bi.bmiHeader.biHeight = -2000; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; bi.bmiHeader.biSizeImage = 2000 * 4 * 2000; bi.bmiHeader.biClrUsed = 0; bi.bmiHeader.biClrImportant = 0; //GetDIBits(m_hdcBuffer, m_hBufferBitmap, 0, 2000, m_BufferRGB, &bi, DIB_RGB_COLORS); //GetBitmapBits(m_hBufferBitmap,2000*4*2000,m_BufferRGB); //GetDIBits(m_hdcBuffer, m_hBufferBitmap, 1900, 100, m_BufferRGB, &bi, DIB_RGB_COLORS); GetDIBits(m_hdcBuffer, m_hBufferBitmap, 2000-(yEnd+1), yEnd+1, m_BufferRGB, &bi, DIB_RGB_COLORS); nSrcB = 197; nSrcG = 106; nSrcR = 49; nSrcA = 64; int nOffset=0; for (int y=yStart; y<yEnd; y++) { for (int x=xStart; x<xEnd; x++) { nOffset = (((y*2000)+x)*4); // Get pixel color nDstB = m_BufferRGB[nOffset+0]; nDstG = m_BufferRGB[nOffset+1]; nDstR = m_BufferRGB[nOffset+2]; // Create alpha blended color nNewR = nDstR+(((nSrcR-nDstR)*nSrcA)/255); nNewG = nDstG+(((nSrcG-nDstG)*nSrcA)/255); nNewB = nDstB+(((nSrcB-nDstB)*nSrcA)/255); // Set new color m_BufferRGB[nOffset+0] = nNewB; m_BufferRGB[nOffset+1] = nNewG; m_BufferRGB[nOffset+2] = nNewR; } } for (int y=yStart; y<yEnd; y++) { nOffset = (((y*2000)+xStart)*4); m_BufferRGB[nOffset+0] = nSrcB; m_BufferRGB[nOffset+1] = nSrcG; m_BufferRGB[nOffset+2] = nSrcR; nOffset = (((y*2000)+(xEnd-1))*4); m_BufferRGB[nOffset+0] = nSrcB; m_BufferRGB[nOffset+1] = nSrcG; m_BufferRGB[nOffset+2] = nSrcR; } for (int x=xStart; x<xEnd; x++) { nOffset = (((yStart*2000)+x)*4); m_BufferRGB[nOffset+0] = nSrcB; m_BufferRGB[nOffset+1] = nSrcG; m_BufferRGB[nOffset+2] = nSrcR; nOffset = ((((yEnd-1)*2000)+x)*4); m_BufferRGB[nOffset+0] = nSrcB; m_BufferRGB[nOffset+1] = nSrcG; m_BufferRGB[nOffset+2] = nSrcR; } nOffset = ((((yEnd-1)*2000)+(xEnd-1))*4); m_BufferRGB[nOffset+0] = nSrcB; m_BufferRGB[nOffset+1] = nSrcG; m_BufferRGB[nOffset+2] = nSrcR; //SetDIBits(m_hdcBuffer, m_hBufferBitmap, 0, 2000, m_BufferRGB, &bi, DIB_RGB_COLORS); //SetBitmapBits(m_hBufferBitmap,2000*4*2000,m_BufferRGB); //SetDIBits(m_hdcBuffer, m_hBufferBitmap, 1900, 100, m_BufferRGB, &bi, DIB_RGB_COLORS); SetDIBits(m_hdcBuffer, m_hBufferBitmap, 2000-(yEnd+1), yEnd+1, m_BufferRGB, &bi, DIB_RGB_COLORS); } /* Slow: if (? { COLORREF clrDst; BYTE nDstR, nDstG, nDstB; BYTE nNewR, nNewG, nNewB; BYTE nSrcR, nSrcG, nSrcB, nSrcA; HPEN hNewPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); HPEN hOldPen = (HPEN) SelectObject(hdc, hNewPen); int yStart = yDragRectangleAnchor; int yEnd = yDragRectanglePos; int xStart = xDragRectangleAnchor; int xEnd = xDragRectanglePos; if (yDragRectangleAnchor>yDragRectanglePos) { yStart = yDragRectanglePos; yEnd = yDragRectangleAnchor; } if (xDragRectangleAnchor>xDragRectanglePos) { xStart = xDragRectanglePos; xEnd = xDragRectangleAnchor; } nSrcB = 255; nSrcG = 0; nSrcR = 0; nSrcA = 64; for (int y=yStart; y<yEnd; y++) { for (int x=xStart; x<xEnd; x++) { // Read pixel color at dst x,y... clrDst = GetPixel(hdc,x,y); nDstR = GetRValue(clrDst); nDstG = GetGValue(clrDst); nDstB = GetBValue(clrDst); // Create alpha blended color nNewR = nDstR+(((nSrcR-nDstR)*nSrcA)/255); nNewG = nDstG+(((nSrcG-nDstG)*nSrcA)/255); nNewB = nDstB+(((nSrcB-nDstB)*nSrcA)/255); // Set new color SetPixel(hdc,x,y,RGB(nNewR,nNewG,nNewB)); } } SelectObject(hdc, hOldPen); DeleteObject(hNewPen); } */ } void CControlTreeListView::PaintColumnHeader(int nColumnIndex,HDC hdc) { HPEN hNewPen; HPEN hOldPen; HBRUSH hNewBrush; HBRUSH hOldBrush; COLORREF clrColumnHeader = RGB(160,160,160); RECT rcColumn,rcTextBox; if (nColumnIndex==m_nHotColumn) clrColumnHeader = RGB(160,160,0); if (bUsingColumns) { // Render column headers hNewPen = CreatePen(PS_SOLID,1,this->GetBkColor()); hOldPen = (HPEN) SelectObject(hdc, hNewPen); hNewBrush = CreateSolidBrush(clrColumnHeader); hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush); this->GetColumnRect(nColumnIndex,&rcColumn); Rectangle(hdc, rcColumn.left, 0, rcColumn.right+1, m_nColumnHeaderHeight); SelectObject(hdc, hOldBrush); DeleteObject(hNewBrush); SelectObject(hdc, hOldPen); DeleteObject(hNewPen); SetBkColor(hdc, clrColumnHeader); SetTextColor(hdc, RGB(255,255,255)); //DrawText(hdc,m_vstrColumnHeader[nColumnIndex].c_str(),static_cast<UINT>(m_vstrColumnHeader[nColumnIndex].length()),&rcTextBox,DT_RIGHT|DT_VCENTER); rcTextBox = rcColumn; rcTextBox.top = 3; rcTextBox.left += 3; rcTextBox.bottom = m_nColumnHeaderHeight; rcTextBox.right -= 6; if (m_vbRightJustify[nColumnIndex]) DrawText(hdc,m_vstrColumnHeader[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_RIGHT); else DrawText(hdc,m_vstrColumnHeader[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT); //ExtTextOut(hdc, rcColumn.left+3, 3, ETO_OPAQUE, NULL, m_vstrColumnHeader[nColumnIndex].c_str(), static_cast<UINT>(m_vstrColumnHeader[nColumnIndex].length()), NULL); } } void CControlTreeListView::PaintFalseColumnAtEnd(HDC hdc) { HPEN hNewPen; HPEN hOldPen; HBRUSH hNewBrush; HBRUSH hOldBrush; COLORREF clrColumnHeader = RGB(160,160,160); int nLastColumnIndex = static_cast<int>(m_vnColumnRightOffset.size()) - 1; if (bUsingColumns) { // Render column headers hNewPen = CreatePen(PS_SOLID,1,this->GetBkColor()); hOldPen = (HPEN) SelectObject(hdc, hNewPen); hNewBrush = CreateSolidBrush(clrColumnHeader); hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush); // Paint blank column header Rectangle(hdc, m_nRenderX+m_vnColumnRightOffset[nLastColumnIndex], 0, this->GetWidth()+this->m_nVScrollWidth, m_nColumnHeaderHeight); // Erase Column Nodes area hNewBrush = CreateSolidBrush(this->GetBkColor()); SelectObject(hdc, hNewBrush); Rectangle(hdc, m_nRenderX+m_vnColumnRightOffset[nLastColumnIndex], m_nColumnHeaderHeight, this->GetWidth()+this->m_nVScrollWidth, this->GetHeight()); SelectObject(hdc, hOldBrush); DeleteObject(hNewBrush); SelectObject(hdc, hOldPen); DeleteObject(hNewPen); } } void CControlTreeListView::PaintNodesColumn(int nColumnIndex,HDC hdc,CNode *pNode) { HPEN hNewPen; HPEN hOldPen; HBRUSH hNewBrush; HBRUSH hOldBrush; COLORREF clrHighlighted = RGB(49,106,197); COLORREF clrColumnHeader = RGB(160,160,160); //RECT rcColumn; RECT rcTextBox; if (nColumnIndex==0) { PaintNodes(hdc,pNode); } else { if (pNode->pNodeParent!=NULL) // This is not the invisible root node so go ahead and display it { if (m_nRenderY>(-m_nNodeHeight)+m_nColumnHeaderHeight && m_nRenderY<this->GetHeight()+m_nNodeHeight) { rcTextBox.left = m_nRenderX; rcTextBox.top = m_nRenderY; rcTextBox.right = m_nRenderX + GetColumnWidth(nColumnIndex) + + 1; rcTextBox.bottom = m_nRenderY+m_nNodeHeight; if (pNode->bSelected && m_bWholeLineHighlight) { hNewPen = CreatePen(PS_SOLID,1,clrHighlighted); hOldPen = (HPEN) SelectObject(hdc, hNewPen); hNewBrush = CreateSolidBrush(clrHighlighted); hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush); Rectangle(hdc, rcTextBox.left, rcTextBox.top, rcTextBox.right, rcTextBox.bottom); SelectObject(hdc, hOldBrush); DeleteObject(hNewBrush); SelectObject(hdc, hOldPen); DeleteObject(hNewPen); SetBkColor(hdc, clrHighlighted); SetTextColor(hdc, RGB(255,255,255)); rcTextBox.top += 1; rcTextBox.left += 1; rcTextBox.right -= 6; if (m_vbRightJustify[nColumnIndex]) DrawText(hdc,pNode->strColumn[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_RIGHT); else DrawText(hdc,pNode->strColumn[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT); //ExtTextOut(hdc, m_nRenderX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[nColumnIndex].c_str(), static_cast<UINT>(pNode->strColumn[nColumnIndex].length()), NULL); } else { hNewPen = CreatePen(PS_SOLID,1,this->GetBkColor()); hOldPen = (HPEN) SelectObject(hdc, hNewPen); hNewBrush = CreateSolidBrush(this->GetBkColor()); hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush); Rectangle(hdc, rcTextBox.left, rcTextBox.top, rcTextBox.right, rcTextBox.bottom); SelectObject(hdc, hOldBrush); DeleteObject(hNewBrush); SelectObject(hdc, hOldPen); DeleteObject(hNewPen); SetBkColor(hdc, this->GetBkColor()); SetTextColor(hdc, RGB(0,0,0)); rcTextBox.top += 1; rcTextBox.left += 1; rcTextBox.right -= 6; if (m_vbRightJustify[nColumnIndex]) DrawText(hdc,pNode->strColumn[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_RIGHT); else DrawText(hdc,pNode->strColumn[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT); //ExtTextOut(hdc, m_nRenderX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[nColumnIndex].c_str(), static_cast<UINT>(pNode->strColumn[nColumnIndex].length()), NULL); } } // Always update this m_nRenderY += m_nNodeHeight; } if (pNode->bTVExpandedAndVisible) { for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++) { PaintNodesColumn(nColumnIndex,hdc,(*iter)); } } } } void CControlTreeListView::PaintNodes(HDC hdc,CNode *pNode) { COLORREF clrHighlighted = RGB(49,106,197); //COLORREF clrHighlighted = RGB(255,171,9); RECT rcTextBox,rcColumn; if (pNode->pNodeParent!=NULL) // This is not the invisible root node so go ahead and display it { // Optimize rendering performance by rendering only if within visible bounds if (m_nRenderY>(-m_nNodeHeight)+m_nColumnHeaderHeight && m_nRenderY<this->GetHeight()+m_nNodeHeight) { RECT rResult; HPEN hNewPen; HPEN hOldPen; HBRUSH hNewBrush; HBRUSH hOldBrush; int nX = m_nRenderX; if (pNode->bSelected && m_bWholeLineHighlight) { hNewPen = CreatePen(PS_SOLID,1,clrHighlighted); hOldPen = (HPEN) SelectObject(hdc, hNewPen); hNewBrush = CreateSolidBrush(clrHighlighted); hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush); Rectangle(hdc, 0, m_nRenderY, this->GetWidth()+1, m_nRenderY+m_nNodeHeight); SelectObject(hdc, hOldBrush); DeleteObject(hNewBrush); SelectObject(hdc, hOldPen); DeleteObject(hNewPen); } /* else { hNewPen = CreatePen(PS_SOLID,1,this->GetBkColor()); hOldPen = (HPEN) SelectObject(hdc, hNewPen); hNewBrush = CreateSolidBrush(this->GetBkColor()); hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush); Rectangle(hdc, 0, m_nRenderY, this->GetWidth()+1, m_nRenderY+m_nNodeHeight); SelectObject(hdc, hOldBrush); DeleteObject(hNewBrush); SelectObject(hdc, hOldPen); DeleteObject(hNewPen); } */ nX += (pNode->nIndentLevel*m_nIndentMultiplier); if (pNode->vpChild.size()) { if (pNode->bTVExpandedAndVisible) { if (m_ResourceIDExpandMinusBMP!=0) m_bmpExpandMinus.Render(hdc,nX+3,m_nRenderY+4); } else { if (m_ResourceIDExpandPlusBMP!=0) m_bmpExpandPlus.Render(hdc,nX+3,m_nRenderY+4); } } nX += (m_nIndentMultiplier); if (pNode->bCheckBoxVisible) { if ( pNode->nCheck==1 ) m_bmpChecked.Render(hdc,nX,m_nRenderY+2); else if ( pNode->nCheck==0 ) m_bmpUnchecked.Render(hdc,nX,m_nRenderY+2); else if ( pNode->nCheck==-1 ) m_bmpPartiallyChecked.Render(hdc,nX,m_nRenderY+2); nX += (m_nIndentMultiplier); } if (pNode->smIcon.size()) { if (!pNode->bCheckBoxVisible || pNode->nCheck==1 ) rResult = pNode->smIcon[0].Render(hdc,nX,m_nRenderY); else { if (pNode->bSelected && m_bWholeLineSelect) rResult = pNode->smIcon[0].RenderGrayed(hdc,nX,m_nRenderY,clrHighlighted); else rResult = pNode->smIcon[0].RenderGrayed(hdc,nX,m_nRenderY,this->GetBkColor()); } nX += 16; } // Now render Text nX += 4; this->GetColumnRect(0,&rcColumn); rcTextBox.left = nX-2; rcTextBox.top = m_nRenderY; rcTextBox.right = rcColumn.right; rcTextBox.bottom = m_nRenderY+m_nNodeHeight; if (!pNode->bCheckBoxVisible || pNode->nCheck==1 ) { if (pNode->bSelected) { if (m_bWholeLineHighlight==false) { hNewPen = CreatePen(PS_SOLID,1,clrHighlighted); hOldPen = (HPEN) SelectObject(hdc, hNewPen); hNewBrush = CreateSolidBrush(clrHighlighted); hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush); Rectangle(hdc, rcTextBox.left, rcTextBox.top, nX+pNode->nTextWidth+6, rcTextBox.bottom); SelectObject(hdc, hOldBrush); DeleteObject(hNewBrush); SelectObject(hdc, hOldPen); DeleteObject(hNewPen); } SetBkColor(hdc, clrHighlighted); SetTextColor(hdc, RGB(255,255,255)); rcTextBox.top += 1; rcTextBox.left += 3; rcTextBox.right -= 6; DrawText(hdc,pNode->strColumn[0].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT); //ExtTextOut(hdc, nX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[0].c_str(), static_cast<UINT>(pNode->strColumn[0].length()), NULL); } else { SetBkColor(hdc, this->GetBkColor()); SetTextColor(hdc, RGB(0,0,0)); rcTextBox.top += 1; rcTextBox.left += 3; rcTextBox.right -= 6; DrawText(hdc,pNode->strColumn[0].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT); //ExtTextOut(hdc, nX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[0].c_str(), static_cast<UINT>(pNode->strColumn[0].length()), NULL); } } else { if (pNode->bSelected) { if (m_bWholeLineHighlight==false) { hNewPen = CreatePen(PS_SOLID,1,clrHighlighted); hOldPen = (HPEN) SelectObject(hdc, hNewPen); hNewBrush = CreateSolidBrush(clrHighlighted); hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush); Rectangle(hdc, rcTextBox.left, rcTextBox.top, nX+pNode->nTextWidth+6, rcTextBox.bottom); SelectObject(hdc, hOldBrush); DeleteObject(hNewBrush); SelectObject(hdc, hOldPen); DeleteObject(hNewPen); } SetBkColor(hdc, clrHighlighted); SetTextColor(hdc, RGB(255,255,255)); rcTextBox.top += 1; rcTextBox.left += 3; rcTextBox.right -= 6; DrawText(hdc,pNode->strColumn[0].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT); //ExtTextOut(hdc, nX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[0].c_str(), static_cast<UINT>(pNode->strColumn[0].length()), NULL); } else { SetBkColor(hdc, this->GetBkColor()); SetTextColor(hdc, RGB(203,209,170)); rcTextBox.top += 1; rcTextBox.left += 3; rcTextBox.right -= 6; DrawText(hdc,pNode->strColumn[0].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT); //ExtTextOut(hdc, nX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[0].c_str(), static_cast<UINT>(pNode->strColumn[0].length()), NULL); } } } // Always update this m_nRenderY += m_nNodeHeight; } if (pNode->bTVExpandedAndVisible) { for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++) { PaintNodes(hdc,(*iter)); } } } LRESULT CControlTreeListView::WndProc(HWND hwnd, UINT mMsg, WPARAM wParam, LPARAM lParam) { TRACKMOUSEEVENT trackmouseevent; //int xPos; //int yPos; //int xPosContent,yPosContent; SCROLLINFO si; HFONT hFontOld; int nHorzPos; int nVertPos; HPEN hNewPen,hOldPen; HBRUSH hNewBrush,hOldBrush; RECT rcColumn; PAINTSTRUCT ps; LARGE_INTEGER currentTick; switch (mMsg) { case WM_CREATE: { HDC hdcDst; hdcDst = GetDC(hwnd); m_hdcBuffer = CreateCompatibleDC(hdcDst); // Maxiumum ***Window*** size (not content size) for this control is: m_hBufferBitmap = CreateCompatibleBitmap(hdcDst,2000,2000); SelectObject(m_hdcBuffer, m_hBufferBitmap); ReleaseDC(hwnd, hdcDst); m_BufferRGB = new BYTE[2000*2000*4]; // < 16 MB QueryPerformanceFrequency(&m_TickFrequency); QueryPerformanceCounter(&m_prevTick); //SetClassLongPtr(hwnd,GCL_HCURSOR,reinterpret_cast<LONG>(m_hCursor)); return 0; break; } case WM_DESTROY: { DeleteDC(m_hdcBuffer); DeleteObject(m_hBufferBitmap); //ReleaseDC(hwnd, m_hdcDst); break; } case WM_ERASEBKGND: { // We never erase the background, we paint everything to avoid flicker // and other problems with this control like when another app window is // moved around over this one... // // Besides, we are now using double buffering approach to avoid the // nightmare that is Windows return 1; break; } case WM_PAINT: { //RECT rcClient; //GetClientRect(hwnd, &rcClient); // Get and Save Scroll Positions si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(hwnd,SB_HORZ,&si); nHorzPos = si.nPos; si.fMask = SIF_POS; GetScrollInfo(hwnd,SB_VERT,&si); nVertPos = si.nPos; // Double buffer this to avoid flicker // http://www.gamedev.net/community/forums/topic.asp?topic_id=411559 // Erase Buffer First: hNewPen = CreatePen(PS_SOLID,1,this->GetBkColor()); hOldPen = (HPEN) SelectObject(m_hdcBuffer, hNewPen); hNewBrush = CreateSolidBrush(this->GetBkColor()); hOldBrush = (HBRUSH) SelectObject(m_hdcBuffer, hNewBrush); Rectangle(m_hdcBuffer, 0, 0, this->GetWidth(), this->GetHeight()); SelectObject(m_hdcBuffer, hOldBrush); DeleteObject(hNewBrush); SelectObject(m_hdcBuffer, hOldPen); DeleteObject(hNewPen); // Render columns for (int nColumnIndex=0; nColumnIndex<static_cast<int>(m_vnColumnRightOffset.size()); nColumnIndex++) { this->GetColumnRect(nColumnIndex,&rcColumn); // I don't know if we need to create a pen but it // seems like we had a problem once that was solved // by having a pen set. hNewPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); hOldPen = (HPEN) SelectObject(m_hdcBuffer, hNewPen); // Load up our font hFontOld = static_cast<HFONT>(SelectObject(m_hdcBuffer,static_cast<HGDIOBJ>(m_hFont))); // Let's setup our font first // Note: We render only from children of root m_nRenderX = 0; m_nRenderY = 0; // Adjust for scroll m_nRenderX -= nHorzPos; m_nRenderY -= (nVertPos*m_nNodeHeight); // Adjust for header height m_nRenderY += m_nColumnHeaderHeight; // Adjust m_nRenderX += (m_bShowRootExpandTabs?0:-16); if (nColumnIndex>0) m_nRenderX = rcColumn.left; // Unleash the Recursive Beast //PaintNodes(m_hdcBuffer,&m_Root); // Paint column here PaintNodesColumn(nColumnIndex,m_hdcBuffer,&m_Root); // Paint column header, if at all PaintColumnHeader(nColumnIndex,m_hdcBuffer); // Load up original font SelectObject(m_hdcBuffer, static_cast<HGDIOBJ>(hFontOld)); // Delete Pen SelectObject(m_hdcBuffer, hOldPen); DeleteObject(hNewPen); } // This will render only if needs to m_nRenderX = 0; m_nRenderX -= nHorzPos; PaintFalseColumnAtEnd(m_hdcBuffer); // Paint Drag Rectangle m_nRenderX = 0; m_nRenderY = 0; m_nRenderX -= nHorzPos; m_nRenderY -= (nVertPos*m_nNodeHeight); m_nRenderY += m_nColumnHeaderHeight; PaintDragRectangle(m_hdcBuffer, m_xDragRectangleAnchor+m_nRenderX, m_yDragRectangleAnchor+m_nRenderY, m_xDragRectanglePos+m_nRenderX, m_yDragRectanglePos+m_nRenderY); // Seems like BeginPaint()/EndPaint() is neccessary to keep // Windows happy... // bad: BitBlt(m_hdcDst, 0, 0, this->GetWidth()-m_nVScrollWidth, this->GetHeight()-m_nHScrollHeight, m_hdcBuffer, 0, 0, SRCCOPY); HDC hdc = BeginPaint(hwnd, &ps); // BTW, this is bad: BitBlt(hdc, 0, 0, this->GetWidth()-m_nVScrollWidth, this->GetHeight()-m_nHScrollHeight, m_hdcBuffer, 0, 0, SRCCOPY); BitBlt(hdc, 0, 0, this->GetWidth(), this->GetHeight(), m_hdcBuffer, 0, 0, SRCCOPY); EndPaint(hwnd, &ps); return 0; break; } case WM_HSCROLL: { int oldx=0,newx=0; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_ALL; GetScrollInfo(hwnd,SB_HORZ,&si); int xPos = oldx = si.nPos; switch(LOWORD(wParam)) { //case SB_LEFT: case SB_LINELEFT: { newx = oldx-5; if (newx<si.nMin) newx=si.nMin; si.nPos = newx; SetScrollInfo(hwnd,SB_HORZ,&si,TRUE); break; } //case SB_RIGHT: case SB_LINERIGHT: { newx = oldx+5; if (newx>(si.nMax-static_cast<int>(si.nPage))+1) newx=(si.nMax-static_cast<int>(si.nPage))+1; si.nPos = newx; SetScrollInfo(hwnd,SB_HORZ,&si,TRUE); break; } case SB_PAGELEFT: { newx = oldx-static_cast<int>(si.nPage); if (newx<si.nMin) newx=si.nMin; si.nPos = newx; SetScrollInfo(hwnd,SB_HORZ,&si,TRUE); break; } case SB_PAGERIGHT: { newx = oldx+static_cast<int>(si.nPage); if (newx>(si.nMax-static_cast<int>(si.nPage))+1) newx=(si.nMax-static_cast<int>(si.nPage))+1; si.nPos = newx; SetScrollInfo(hwnd,SB_HORZ,&si,TRUE); break; } case SB_THUMBTRACK: { newx = si.nTrackPos; si.nPos = newx; SetScrollInfo(hwnd,SB_HORZ,&si,TRUE); break; } } // Smooth scrolling // If the position has changed, scroll the window if (si.nPos != xPos) { if (m_nWhatsCaptured==m_enumWC_DragRect) { // m_xDragRectanglePos; // m_yDragRectanglePos; m_xDragRectanglePos -= (xPos - si.nPos); // Select nodes SelectDragRectangleNodes(); this->Invalidate(); } else { //ScrollWindowEx(m_hwnd,(xPos - si.nPos),0,NULL,NULL,NULL,NULL,SW_SCROLLCHILDREN); // // If you use ScrollWindowEx(), you need to set // up certain rectangles so that invalid portions of // the window get repainted. // // ScrollWindow() on the other hand, takes care of // invalidating the relevant portions of the window // for you. // //ScrollWindow(hwnd, (xPos - si.nPos), 0, NULL, NULL); RECT rcScroll; //GetColumnRect(0,&rcScroll); // Incorrect rcScroll.left = 0; rcScroll.right = this->GetWidth(); //rcScroll.top = this->m_nColumnHeaderHeight; rcScroll.top = 0; rcScroll.bottom = this->GetHeight(); ScrollWindow(hwnd, (xPos - si.nPos), 0, &rcScroll, &rcScroll); } } return 0; break; } case WM_KEYDOWN: { switch (wParam) { /* case VK_HOME : SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0); break ; case VK_END : SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0); break ; */ case VK_DELETE: SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_DELETE),(LPARAM)this->GetHwnd()); break; /* if (m_pAnchorNode!=NULL) { m_pAnchorNode->pNodeParent->RemoveChild(m_pAnchorNode->strNodeID); this->Update(); this->Invalidate(); m_pAnchorNode = NULL; } break; */ case VK_PRIOR : SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0); break ; case VK_NEXT : SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0); break ; case VK_UP : SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0); break ; case VK_DOWN : SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0); break ; case VK_LEFT : SendMessage (hwnd, WM_HSCROLL, SB_PAGELEFT, 0); break ; case VK_RIGHT : SendMessage (hwnd, WM_HSCROLL, SB_PAGERIGHT, 0); break ; } return 0; break; } case WM_MOUSEWHEEL: { short nDelta = HIWORD(wParam); if (nDelta>0) { PostMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0) ; PostMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0) ; PostMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0) ; } else { PostMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0) ; PostMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0) ; PostMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0) ; } return 0; break; } case WM_VSCROLL: { int oldy=0,newy=0; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_ALL; GetScrollInfo(hwnd,SB_VERT,&si); // Save the position for comparison later on int yPos = oldy = si.nPos; switch(LOWORD(wParam)) { //case SB_TOP: case SB_LINEUP: { newy = oldy-1; if (newy<si.nMin) newy=si.nMin; si.nPos = newy; SetScrollInfo(hwnd,SB_VERT,&si,TRUE); break; } //case SB_BOTTOM: case SB_LINEDOWN: { newy = oldy+1; if (newy>(si.nMax-static_cast<int>(si.nPage))+1) newy=(si.nMax-static_cast<int>(si.nPage))+1; si.nPos = newy; SetScrollInfo(hwnd,SB_VERT,&si,TRUE); break; } case SB_PAGEUP: { newy = oldy-static_cast<int>(si.nPage); if (newy<si.nMin) newy=si.nMin; si.nPos = newy; SetScrollInfo(hwnd,SB_VERT,&si,TRUE); break; } case SB_PAGEDOWN: { newy = oldy+static_cast<int>(si.nPage); if (newy>(si.nMax-static_cast<int>(si.nPage))+1) newy=(si.nMax-static_cast<int>(si.nPage))+1; si.nPos = newy; SetScrollInfo(hwnd,SB_VERT,&si,TRUE); break; } case SB_THUMBTRACK: { newy = si.nTrackPos; si.nPos = newy; SetScrollInfo(hwnd,SB_VERT,&si,TRUE); break; } } // Smooth scrolling if (si.nPos != yPos) { if (m_nWhatsCaptured==m_enumWC_DragRect) { // m_xDragRectanglePos; // m_yDragRectanglePos; m_yDragRectanglePos -= (m_nNodeHeight * (yPos - si.nPos)); // Select nodes SelectDragRectangleNodes(); this->Invalidate(); } else { //ScrollWindowEx(m_hwnd,0,m_nNodeHeight * (yPos - si.nPos),NULL,NULL,NULL,NULL,SW_SCROLLCHILDREN); // // If you use ScrollWindowEx(), you need to set // up certain rectangles so that invalid portions of // the window get repainted. // // ScrollWindow() on the other hand, takes care of // invalidating the relevant portions of the window // for you. // //ScrollWindow(hwnd, 0, m_nNodeHeight * (yPos - si.nPos), NULL, NULL); RECT rcScroll; //GetColumnRect(0,&rcScroll); // Incorrect rcScroll.left = 0; rcScroll.right = this->GetWidth(); rcScroll.top = this->m_nColumnHeaderHeight; rcScroll.bottom = this->GetHeight(); ScrollWindow(hwnd, 0, m_nNodeHeight * (yPos - si.nPos), &rcScroll, &rcScroll); } } return 0; break; } case WM_TIMER: { // The wParam parameter of the WM_TIMER message contains the // value of the nIDEvent parameter. switch (wParam) { case 0: { SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0); SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0); //SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0); break; } case 1: { SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0); SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0); //SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0); break; } case 2: { SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0); SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0); //SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0); break; } case 3: { SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0); SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0); //SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0); break; } } return 0; break; } // This is rather complex. We determine a double click using a different method /* case WM_LBUTTONDBLCLK: { // CS_DBLCLKS SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_DOUBLECLICKED),(LPARAM)this->GetHwnd()); return 0; break; } */ case WM_LBUTTONUP: { QueryPerformanceCounter(¤tTick); double dElapsedTime = static_cast<double>((currentTick.LowPart - m_prevTick.LowPart)) / static_cast<double>(m_TickFrequency.LowPart); bool bDoubleClick = false; if (dElapsedTime<0.25) bDoubleClick = true; m_prevTick = currentTick; // Two step approach required to get negative mouse values: signed short ssXPosWindow = static_cast<signed short>(LOWORD(lParam)); signed short ssYPosWindow = static_cast<signed short>(HIWORD(lParam)); int nXPosWindow = static_cast<int>(ssXPosWindow); int nYPosWindow = static_cast<int>(ssYPosWindow); InitMouseAndCursorInfo(nXPosWindow,nYPosWindow); // Get and Save Scroll Positions si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(hwnd,SB_HORZ,&si); nHorzPos = si.nPos; si.fMask = SIF_POS; GetScrollInfo(hwnd,SB_VERT,&si); nVertPos = si.nPos; // Get mouse click in node content coordinates int nXPosContent = nXPosWindow + nHorzPos; int nYPosContent = (nYPosWindow + (nVertPos*m_nNodeHeight)) - m_nColumnHeaderHeight; // Avoid problems with SetCapture and ScrollBars: RECT rcNodeWindow; GetNodeClientWindow(&rcNodeWindow); if (GetCursor()==m_hCursorArrowCopy) SetCursor(m_hCursorArrow); // Nodes already selected via WM_MOUSEMOVE. It's safe to send this message. if (m_nWhatsCaptured == m_enumWC_DragRect && m_xDragRectangleAnchor!=nXPosContent && m_yDragRectangleAnchor!=nYPosContent) SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd()); if (m_nWhatsCaptured==m_enumWC_DragNodes) { int nItemIndex = nYPosContent/m_nNodeHeight; // NOTE: Be very careful here. User can click somewhere like // on the lower horz scroll bar making nItemIndex out of // range. Always ensure that nItemIndex is in range if you // want to continue. if (nItemIndex<0 || nItemIndex>=static_cast<int>(m_mapRenderedNodeLine.size())) { break; } // Help for determining what was clicked int nXExpandButtonLeft = (m_mapRenderedNodeLine[nItemIndex]->nIndentLevel*m_nIndentMultiplier) + 1; nXExpandButtonLeft += (m_bShowRootExpandTabs?0:-16); int nXExpandButtonRight = nXExpandButtonLeft + 12; int nXCheckBoxLeft = (m_mapRenderedNodeLine[nItemIndex]->nIndentLevel*m_nIndentMultiplier) + 16; nXCheckBoxLeft += (m_bShowRootExpandTabs?0:-16); int nXCheckBoxRight = nXCheckBoxLeft + (m_mapRenderedNodeLine[nItemIndex]->bCheckBoxVisible?16:0); int nXIconLeft = nXCheckBoxRight; int nXIconRight = nXIconLeft + ((m_mapRenderedNodeLine[nItemIndex]->smIcon.size()?16:0)); int nXTextLeft = nXIconRight; int nXTextRight = 10 + nXTextLeft + m_mapRenderedNodeLine[nItemIndex]->nTextWidth; // Some setup for potential Node Selection int nxRight = nXTextRight; if (m_bWholeLineSelect==false && bUsingColumns==true) { if (m_vnColumnRightOffset[0]>nXTextRight) nxRight = nXTextRight; else nxRight = m_vnColumnRightOffset[0]; } // Node Selection (mouse up only for drag purposes) if ( (m_bWholeLineSelect && (nXPosContent<nXExpandButtonLeft || nXPosContent>nXExpandButtonRight)) || (false==m_bWholeLineSelect && nXPosContent>nXIconLeft && nXPosContent<nxRight) ) { if (wParam&MK_CONTROL) { //if (m_mapRenderedNodeLine[nItemIndex]->bSelected) //m_mapRenderedNodeLine[nItemIndex]->bSelected = false; //m_mapRenderedNodeLine[nItemIndex]->bSelected =! m_mapRenderedNodeLine[nItemIndex]->bSelected; //SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode); //SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd()); } else { // Save this, then select if needed bool bSelectedSaved = m_mapRenderedNodeLine[nItemIndex]->bSelected; int nNumSelectedSaved = GetNumSelected(); // Select if (wParam&MK_SHIFT) SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode); else m_pAnchorNode = SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode); if (bDoubleClick && m_pPrevNodeClick==m_mapRenderedNodeLine[nItemIndex]) { // A single node was double clicked SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_DOUBLECLICKED),(LPARAM)m_mapRenderedNodeLine[nItemIndex]); } else { // Send NOTIFY_SELECTIONCHANGED only if selection has actually changed // Note: We send this message after we select the nodes because // our parent window which processes this message will // want to know which nodes are selected after the selection // is made. if (nNumSelectedSaved>1) SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd()); else if (bSelectedSaved == false) SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd()); } m_pPrevNodeClick = m_mapRenderedNodeLine[nItemIndex]; } } //ReleaseCapture(); m_nWhatsCaptured = m_enumWC_NothingOrScrollBar; pCControlTreeListView_DragAndDrop = NULL; } else if (pCControlTreeListView_DragAndDrop!=NULL) { // Nodes Dropped Here from a different window... /* //MessageBox(NULL,"Nodes Dropped","Hello",MB_OK); pCControlTreeListView_DragAndDrop->m_nWhatsCaptured = m_enumWC_NothingOrScrollBar; // Cycle through other Control and add selected nodes here for (int i=0; i<static_cast<int>(pCControlTreeListView_DragAndDrop->m_mapRenderedNodeLine.size()); i++) { if (pCControlTreeListView_DragAndDrop->m_mapRenderedNodeLine[i]->bSelected) { this->m_Root.AddChild( &pCControlTreeListView_DragAndDrop->m_mapRenderedNodeLine[i]->smIcon[0], pCControlTreeListView_DragAndDrop->m_mapRenderedNodeLine[i]->strColumn[0]); } } this->Update(); pCControlTreeListView_DragAndDrop = NULL; */ SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_NODESDRAGGEDANDDROPPEDHERE),(LPARAM)pCControlTreeListView_DragAndDrop); //this->Update(); pCControlTreeListView_DragAndDrop = NULL; } KillTimer(hwnd,0); KillTimer(hwnd,1); KillTimer(hwnd,2); KillTimer(hwnd,3); m_bTimerSet[0] = false; m_bTimerSet[1] = false; m_bTimerSet[2] = false; m_bTimerSet[3] = false; ReleaseCapture(); m_nWhatsCaptured = m_enumWC_NothingOrScrollBar; this->Invalidate(); return 0; break; } case WM_LBUTTONDOWN: { // Leave this for good measure. Seems to solve potential problems. this->SetFocus(); // Two step approach required to get negative mouse values: signed short ssXPosWindow = static_cast<signed short>(LOWORD(lParam)); signed short ssYPosWindow = static_cast<signed short>(HIWORD(lParam)); int nXPosWindow = static_cast<int>(ssXPosWindow); int nYPosWindow = static_cast<int>(ssYPosWindow); InitMouseAndCursorInfo(nXPosWindow,nYPosWindow); // Get and Save Scroll Positions si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(hwnd,SB_HORZ,&si); nHorzPos = si.nPos; si.fMask = SIF_POS; GetScrollInfo(hwnd,SB_VERT,&si); nVertPos = si.nPos; // Get mouse click in node content coordinates int nXPosContent = nXPosWindow + nHorzPos; int nYPosContent = (nYPosWindow + (nVertPos*m_nNodeHeight)) - m_nColumnHeaderHeight; // Avoid problems with SetCapture and ScrollBars: RECT rcNodeWindow; GetNodeClientWindow(&rcNodeWindow); if (bUsingColumns && nYPosWindow<m_nColumnHeaderHeight) { // This is a column header click if (m_nWhatsHot==m_enumColumnResize) { if (nXPosWindow<rcNodeWindow.right) { // Not a scroll bar click SetCapture(this->GetHwnd()); m_nWhatsCaptured = m_enumWC_ColumnResize; } } else if (m_nWhatsHot==m_enumColumnSort) { //MessageBox(hwnd,m_SM.Format(m_nHotColumn).c_str(),"Hot Column:",MB_OK); m_Root.SortChildren(m_nHotColumn); this->Update(); } } else { int nItemIndex = nYPosContent/m_nNodeHeight; // NOTE: Be very careful here. User can click somewhere like // on the lower horz scroll bar making nItemIndex out of // range. Always ensure that nItemIndex is in range if you // want to continue. if (nItemIndex<0 || nItemIndex>=static_cast<int>(m_mapRenderedNodeLine.size())) { if (nItemIndex>=static_cast<int>(m_mapRenderedNodeLine.size())) { // Possibly a selection drag rectangle if (m_nWhatsHot==m_enumNodes && m_bWholeLineSelect==false && m_bMultilineSelect==true) { if (nXPosWindow<rcNodeWindow.right && nYPosWindow<rcNodeWindow.bottom ) { m_xDragRectangleAnchor = nXPosContent; m_yDragRectangleAnchor = nYPosContent; SetCapture(this->GetHwnd()); m_nWhatsCaptured = m_enumWC_DragRect; m_pAnchorNode = NULL; } } } break; } // Help for determining what was clicked int nXExpandButtonLeft = (m_mapRenderedNodeLine[nItemIndex]->nIndentLevel*m_nIndentMultiplier) + 1; nXExpandButtonLeft += (m_bShowRootExpandTabs?0:-16); int nXExpandButtonRight = nXExpandButtonLeft + 12; int nXCheckBoxLeft = (m_mapRenderedNodeLine[nItemIndex]->nIndentLevel*m_nIndentMultiplier) + 16; nXCheckBoxLeft += (m_bShowRootExpandTabs?0:-16); int nXCheckBoxRight = nXCheckBoxLeft + (m_mapRenderedNodeLine[nItemIndex]->bCheckBoxVisible?16:0); int nXIconLeft = nXCheckBoxRight; int nXIconRight = nXIconLeft + ((m_mapRenderedNodeLine[nItemIndex]->smIcon.size()?16:0)); int nXTextLeft = nXIconRight; int nXTextRight = 10 + nXTextLeft + m_mapRenderedNodeLine[nItemIndex]->nTextWidth; // Some setup for potential Node Selection int nxRight = nXTextRight; if (m_bWholeLineSelect==false && bUsingColumns==true) { if (m_vnColumnRightOffset[0]>nXTextRight) nxRight = nXTextRight; else nxRight = m_vnColumnRightOffset[0]; } // Expand/Collapse Selection if (m_mapRenderedNodeLine[nItemIndex]->vpChild.size() && nXPosContent>nXExpandButtonLeft && nXPosContent<nXExpandButtonRight ) { // Toggle m_mapRenderedNodeLine[nItemIndex]->bTVExpandedAndVisible = !m_mapRenderedNodeLine[nItemIndex]->bTVExpandedAndVisible; // This won't always work when collapsing a small tree so leave it X'd out //RECT rcInvalidate; //rcInvalidate.left = 0; //rcInvalidate.right = this->GetWidth(); //rcInvalidate.top = static_cast<int>(HIWORD(lParam))-m_nNodeHeight; //rcInvalidate.bottom = this->GetHeight(); //this->Invalidate(&rcInvalidate); // Now handle special case where a selected node collapses. // In other words, select (highlight) parent (this) node // if a child node was selected. if (AreAnyChildrenSelected(m_mapRenderedNodeLine[nItemIndex])) { m_pAnchorNode = SelectNode(m_mapRenderedNodeLine[nItemIndex]); // Always send this because children are being collapsed and // unselected. SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd()); } this->Update(); //this->Invalidate(); // This will keep it clean looking if it takes a long time // for parent to process expand or collapse message UpdateWindow(hwnd); if (m_mapRenderedNodeLine[nItemIndex]->bTVExpandedAndVisible) SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_EXPANDED),(LPARAM)m_mapRenderedNodeLine[nItemIndex]); else SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_COLLAPSED),(LPARAM)m_mapRenderedNodeLine[nItemIndex]); } // Checkbox Selection else if (m_mapRenderedNodeLine[nItemIndex]->bCheckBoxVisible && nXPosContent>nXCheckBoxLeft && nXPosContent<nXCheckBoxRight ) { // Save this, then select if needed bool bSelectedSaved = m_mapRenderedNodeLine[nItemIndex]->bSelected; int nNumSelectedSaved = GetNumSelected(); // Select if (wParam&MK_SHIFT) SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode); else m_pAnchorNode = SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode); // Send NOTIFY_SELECTIONCHANGED only if selection has actually changed // Note: We send this message after we select the nodes because // our parent window which processes this message will // want to know which nodes are selected after the selection // is made. if (nNumSelectedSaved>1) SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd()); else if (bSelectedSaved == false) SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd()); // Toggle checkbox if (m_mapRenderedNodeLine[nItemIndex]->nCheck==1 ) { SetCheckState(m_mapRenderedNodeLine[nItemIndex],false); UpdateWindow(hwnd); SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_UNCHECKED),(LPARAM)m_mapRenderedNodeLine[nItemIndex]); } else // if (m_mapRenderedNodeLine[nItemIndex]->nCheck==0 || -1 ) { SetCheckState(m_mapRenderedNodeLine[nItemIndex],true); UpdateWindow(hwnd); SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_CHECKED),(LPARAM)m_mapRenderedNodeLine[nItemIndex]); } } // Node Selection else if ( (m_bWholeLineSelect && (nXPosContent<nXExpandButtonLeft || nXPosContent>nXExpandButtonRight)) || (false==m_bWholeLineSelect && nXPosContent>nXIconLeft && nXPosContent<nxRight) ) { if (m_mapRenderedNodeLine[nItemIndex]->bSelected==false) { if (wParam&MK_SHIFT) { SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode); } else { m_pAnchorNode = SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode); } SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd()); m_nWhatsCaptured = m_enumWC_DragNodes; pCControlTreeListView_DragAndDrop = this; } else { if (wParam&MK_SHIFT) { SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode); SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd()); } else if (wParam&MK_CONTROL) { m_pAnchorNode = SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode); SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd()); } else if (nXPosWindow<rcNodeWindow.right && nYPosWindow<rcNodeWindow.bottom ) { // Begin drag operation //if (GetCapture()==NULL) //SetCapture(this->GetHwnd()); // This is not a normal drag operation m_nWhatsCaptured = m_enumWC_DragNodes; pCControlTreeListView_DragAndDrop = this; } } } // Possibly a selection drag rectangle else if (m_nWhatsHot==m_enumNodes && m_bWholeLineSelect==false && m_bMultilineSelect==true) { if (nXPosWindow<rcNodeWindow.right && nYPosWindow<rcNodeWindow.bottom ) { m_xDragRectangleAnchor = nXPosContent; m_yDragRectangleAnchor = nYPosContent; SetCapture(this->GetHwnd()); m_nWhatsCaptured = m_enumWC_DragRect; m_pAnchorNode = NULL; } } } return 0; break; } case WM_MOUSEMOVE: { //if (GetCursor()==m_hCursorArrowCopy && (wParam&MK_LBUTTON)==0 ) if ((wParam&MK_LBUTTON)==0) { SetCursor(m_hCursorArrow); pCControlTreeListView_DragAndDrop = NULL; m_nWhatsCaptured = m_enumWC_NothingOrScrollBar; } /* if (GetCapture()==this->GetHwnd()) m_vstrColumnHeader[0] = "Captured"; else m_vstrColumnHeader[0] = "NOT Captured"; */ int nSavedHotColumn = m_nHotColumn; // Two step approach required to get negative mouse values: signed short ssXPosWindow = static_cast<signed short>(LOWORD(lParam)); signed short ssYPosWindow = static_cast<signed short>(HIWORD(lParam)); int nXPosWindow = static_cast<int>(ssXPosWindow); int nYPosWindow = static_cast<int>(ssYPosWindow); InitMouseAndCursorInfo(nXPosWindow,nYPosWindow); // Get and Save Scroll Positions si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(hwnd,SB_HORZ,&si); nHorzPos = si.nPos; si.fMask = SIF_POS; GetScrollInfo(hwnd,SB_VERT,&si); nVertPos = si.nPos; // Get mouse click in node content coordinates int nXPosContent = nXPosWindow + nHorzPos; int nYPosContent = (nYPosWindow + (nVertPos*m_nNodeHeight)) - m_nColumnHeaderHeight; // Avoid problems with SetCapture and ScrollBars: RECT rcNodeWindow; GetNodeClientWindow(&rcNodeWindow); if (m_nHotColumn != nSavedHotColumn) { // Invalidate header to redraw hot header RECT rcInvalidate; rcInvalidate.left = 0; rcInvalidate.right = this->GetWidth(); rcInvalidate.top = 0; rcInvalidate.bottom = m_nColumnHeaderHeight; this->Invalidate(&rcInvalidate); } if (m_bNeedToRestartMouseTrackEvent) { // Set the event for next movement trackmouseevent.cbSize = sizeof(trackmouseevent); trackmouseevent.dwFlags = TME_LEAVE; trackmouseevent.hwndTrack = hwnd; trackmouseevent.dwHoverTime = HOVER_DEFAULT; TrackMouseEvent(&trackmouseevent); m_bNeedToRestartMouseTrackEvent = false; } if (m_nWhatsCaptured!=m_enumWC_NothingOrScrollBar) { if (m_nWhatsCaptured == m_enumWC_DragNodes) { // TO-DO: Drag nodes (change cursor) //MessageBox(this->GetHwnd(),"Dragging Nodes","Note",MB_OK); } else if (m_nWhatsCaptured==m_enumWC_ColumnResize) { // Resizing column here... int nNewPos = nXPosContent; if (nNewPos < (m_nMinColumnWidth*(m_nHotColumn+1))) nNewPos = (m_nMinColumnWidth*(m_nHotColumn+1)); int nDelta = nNewPos - m_vnColumnRightOffset[m_nHotColumn]; for (int i=m_nHotColumn; i<static_cast<int>(m_vnColumnRightOffset.size()); i++) { m_vnColumnRightOffset[i] += nDelta; } // Loop through and ensure other columns offsets are correct for (int i=static_cast<int>(m_vnColumnRightOffset.size())-2; i>=0; i--) { if (m_vnColumnRightOffset[i] > (m_vnColumnRightOffset[i+1]-m_nMinColumnWidth)) m_vnColumnRightOffset[i] = (m_vnColumnRightOffset[i+1]-m_nMinColumnWidth); } this->m_nContentWidth = m_vnColumnRightOffset[m_vnColumnRightOffset.size()-1]; this->UpdateScrollBars(); this->Invalidate(); } else if (m_nWhatsCaptured==m_enumWC_DragRect) { // Determine new drag rectangle position m_xDragRectanglePos = nXPosContent; m_yDragRectanglePos = nYPosContent; // Select nodes SelectDragRectangleNodes(); SCROLLBARINFO sbi; sbi.cbSize = sizeof(SCROLLBARINFO); GetScrollBarInfo(m_hwnd,OBJID_VSCROLL,&sbi); bool bVInvisible = (sbi.rgstate[0]&STATE_SYSTEM_INVISIBLE)?true:false; GetScrollBarInfo(m_hwnd,OBJID_HSCROLL,&sbi); bool bHInvisible = (sbi.rgstate[0]&STATE_SYSTEM_INVISIBLE)?true:false; // Scroll window if needed if (nXPosWindow<0) { // Scroll Left if (!m_bTimerSet[0]) { SetTimer(hwnd,0,10,NULL); m_bTimerSet[0] = true; if (m_bTimerSet[1]) KillTimer(hwnd,1); m_bTimerSet[1] = false; } //SendMessage (hwnd, WM_HSCROLL, SB_PAGELEFT, 0); //SendMessage (hwnd, WM_HSCROLL, SB_LINELEFT, 0); } else if (nXPosWindow>rcNodeWindow.right && !bHInvisible) { // Scroll Right if (!m_bTimerSet[1]) { SetTimer(hwnd,1,10,NULL); m_bTimerSet[1] = true; if (m_bTimerSet[0]) KillTimer(hwnd,0); m_bTimerSet[0] = false; } //SendMessage (hwnd, WM_HSCROLL, SB_PAGERIGHT, 0); //SendMessage (hwnd, WM_HSCROLL, SB_LINERIGHT, 0); } else { if (m_bTimerSet[0]) KillTimer(hwnd,0); m_bTimerSet[0] = false; if (m_bTimerSet[1]) KillTimer(hwnd,1); m_bTimerSet[1] = false; } if (nYPosWindow<m_nColumnHeaderHeight) { // Scroll Up if (!m_bTimerSet[2]) { SetTimer(hwnd,2,10,NULL); m_bTimerSet[2] = true; if (m_bTimerSet[3]) KillTimer(hwnd,3); m_bTimerSet[3] = false; } //SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0); //SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0); } else if (nYPosWindow>rcNodeWindow.bottom && !bVInvisible) { // Scroll Down if (!m_bTimerSet[3]) { SetTimer(hwnd,3,10,NULL); m_bTimerSet[3] = true; if (m_bTimerSet[2]) KillTimer(hwnd,2); m_bTimerSet[2] = false; } //SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0); //SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0); } else { if (m_bTimerSet[2]) KillTimer(hwnd,2); m_bTimerSet[2] = false; if (m_bTimerSet[3]) KillTimer(hwnd,3); m_bTimerSet[3] = false; } this->Invalidate(); } } return 0; break; } case WM_MOUSELEAVE: { //if (m_nHotColumn!=-1) { m_nHotColumn = -1; // Invalidate header to redraw hot header RECT rcInvalidate; rcInvalidate.left = 0; rcInvalidate.right = this->GetWidth(); rcInvalidate.top = 0; rcInvalidate.bottom = m_nColumnHeaderHeight; this->Invalidate(&rcInvalidate); UpdateWindow(hwnd); m_bNeedToRestartMouseTrackEvent = true; } return 0; break; } /* // ms-help://MS.VSCC/MS.MSDNVS/winui/mousinpt_2jas.htm case WM_MOUSELEAVE: if (m_state!=down && m_state!=disabled) { // Restore COLD button image if (m_state!=cold) { m_state = cold; InvalidateRect(hwnd, NULL, FALSE); } } break; case WM_MOUSEMOVE: if (m_state!=down && m_state!=disabled) { if (m_state!=hot) { m_state = hot; InvalidateRect(hwnd, NULL, FALSE); } // Set the event for next movement trackmouseevent.cbSize = sizeof(trackmouseevent); trackmouseevent.dwFlags = TME_LEAVE; trackmouseevent.hwndTrack = hwnd; trackmouseevent.dwHoverTime = HOVER_DEFAULT; _TrackMouseEvent(&trackmouseevent); } break; case WM_LBUTTONDOWN: if (m_state!=down && m_state!=disabled) { m_state = down; InvalidateRect(hwnd, NULL, FALSE); SetCapture(this->GetHwnd()); } break; case WM_LBUTTONUP: if (m_bMouseCaptured) { ReleaseCapture(); if (m_state!=disabled) { // If button is released in the window in which it was pressed, // it is a legitimate button press or push signed short nxPos = static_cast<signed short>(LOWORD(lParam)); signed short nyPos = static_cast<signed short>(HIWORD(lParam)); xPos = static_cast<int>(nxPos); yPos = static_cast<int>(nyPos); bool bLegit=false; //RECT rcButton; //GetClientRect(hwnd, &rcButton); if (xPos<=m_Width && yPos<=m_Height) bLegit=true; // (m_state!=down && m_bPushButton)) = send message only if not previously pushed if (m_state!=hot ||(m_state!=down && m_bPushButton)) //if (m_state!=hot) { if (m_bPushButton && bLegit) m_state = down; else m_state = hot; InvalidateRect(hwnd, NULL, FALSE); //ReleaseCapture(); if (bLegit) SendMessage(m_hwndParent,WM_COMMAND,MAKEWPARAM(m_nControlID,412),(LPARAM)m_hwnd); } } } break; case WM_KEYUP: if (wParam==VK_RETURN) { m_state = hot; InvalidateRect(hwnd, NULL, FALSE); SendMessage(m_hwndParent,WM_COMMAND,MAKEWPARAM(m_nControlID,412),(LPARAM)m_hwnd); break; } case WM_KEYDOWN: if (wParam==VK_RETURN) { if (m_state!=down && m_state!=disabled) { m_state = down; InvalidateRect(hwnd, NULL, FALSE); } break; } if (this->m_dwStyle&WS_TABSTOP) { // wParam could == VK_TAB, etc. // lParam could be WM_KEYDOWN, etc. PostMessage(this->GetHwndParent(),this->GetControlId(),wParam,static_cast<LPARAM>(mMsg)); } break; case WM_SETFOCUS: if (m_state!=down && m_state!=disabled) { if (m_state!=hot) { m_state = hot; InvalidateRect(hwnd, NULL, FALSE); } } break; */ case WM_SIZE: { // This causes problems when the control initializes!!!!!!!! //Update(); // This causes errors with expanding a section creating a wider width for some reason... //int test=static_cast<int>(m_Root.Child.size()); if (m_Root.Child.size()) { UpdateScrollBars(); this->Invalidate(); return 0; } break; } /* case WM_SETFOCUS: { // Just leave this here for safety in case the wm_paint goes haywire. // Seems to be an issue sometimes when another app window // is moved over this one. //***** UPDATE FIXED! this->Invalidate(); break; } */ } return CBaseClassWindow::WndProc(hwnd, mMsg, wParam, lParam); } // This is the client area excluding scroll bars and header columns, if any void CControlTreeListView::GetNodeClientWindow(RECT *lpRect) { SCROLLBARINFO sbi; sbi.cbSize = sizeof(SCROLLBARINFO); GetScrollBarInfo(m_hwnd,OBJID_VSCROLL,&sbi); bool bVInvisible = (sbi.rgstate[0]&STATE_SYSTEM_INVISIBLE)?true:false; GetScrollBarInfo(m_hwnd,OBJID_HSCROLL,&sbi); bool bHInvisible = (sbi.rgstate[0]&STATE_SYSTEM_INVISIBLE)?true:false; lpRect->left = 0; lpRect->top = m_nColumnHeaderHeight; if (bVInvisible) lpRect->right = this->GetWidth(); else lpRect->right = this->GetWidth()-this->m_nVScrollWidth; if (bHInvisible) lpRect->bottom = this->GetHeight(); else lpRect->bottom = this->GetHeight()-this->m_nHScrollHeight; lpRect->right -= 1; lpRect->bottom -= 1; } void CControlTreeListView::InitMouseAndCursorInfo(int xPos,int yPos) { SCROLLINFO si; int nHorzPos,nVertPos; if (m_nWhatsCaptured==m_enumWC_NothingOrScrollBar) { // Get and Save Scroll Positions si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(this->GetHwnd(),SB_HORZ,&si); nHorzPos = si.nPos; si.fMask = SIF_POS; GetScrollInfo(this->GetHwnd(),SB_VERT,&si); nVertPos = si.nPos; xPos += nHorzPos; //yPos += (nVertPos*m_nNodeHeight); //enum __M_ENUM_WHATSHOT__ {m_enumNodes,m_enumColumnSort,m_enumColumnResize}; //int m_nWhatsHot; HCURSOR hCursorCurrent = m_hCursorArrow; m_nWhatsHot = m_enumNodes; m_nHotColumn = -1; if (bUsingColumns && yPos<m_nColumnHeaderHeight && xPos<m_vnColumnRightOffset[m_vnColumnRightOffset.size()-1]) { // This is a column header hCursorCurrent = m_hCursorHand; m_nWhatsHot = m_enumColumnSort; int xTmp = 0; for (int nColumnIndex=0; nColumnIndex<static_cast<int>(m_vnColumnRightOffset.size()); nColumnIndex++) { if (xPos>=xTmp+4) m_nHotColumn = nColumnIndex; xTmp = m_vnColumnRightOffset[nColumnIndex]; if (xPos>xTmp-4 && xPos<xTmp+4) { hCursorCurrent = m_hCursorSizeWE; m_nWhatsHot = m_enumColumnResize; } } } if (pCControlTreeListView_DragAndDrop!=NULL) hCursorCurrent = m_hCursorArrowCopy; SetCursor(hCursorCurrent); } else if (m_nWhatsCaptured==m_enumWC_DragNodes || pCControlTreeListView_DragAndDrop!=NULL) SetCursor(m_hCursorArrowCopy); } void CControlTreeListView::Update() { // Determine content widths and heights DetermineContentWidthAndHeight(); UpdateScrollBars(); this->Invalidate(); } void CControlTreeListView::UpdateScrollBars() { int nUsableClientWidth; int nUsableClientHeight; SCROLLINFO si; nUsableClientWidth = this->GetWidth()-m_nVScrollWidth; nUsableClientHeight = this->GetHeight()-m_nHScrollHeight; if ( m_nContentHeight > nUsableClientHeight ) { si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_RANGE | SIF_PAGE; si.nMin = 0; si.nMax = m_nContentHeight/m_nNodeHeight; si.nMax -= 1; // <--- I'm rather certain this is correct si.nPage = static_cast<UINT>(nUsableClientHeight/m_nNodeHeight); si.nPage -= 1; // <--- I'm rather certain this is correct SetScrollInfo(m_hwnd,SB_VERT,&si,TRUE); ShowScrollBar(m_hwnd,SB_VERT,TRUE); } else { si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; si.nPos = 0; SetScrollInfo(m_hwnd,SB_VERT,&si,FALSE); ShowScrollBar(m_hwnd,SB_VERT,FALSE); } if ( m_nContentWidth > nUsableClientWidth ) { si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_RANGE | SIF_PAGE; si.nMin = 0; si.nMax = m_nContentWidth; si.nMax -= 1; // <--- I'm rather certain this is correct si.nPage = static_cast<UINT>(nUsableClientWidth); si.nPage -= 1; // <--- I'm rather certain this is correct SetScrollInfo(m_hwnd,SB_HORZ,&si,TRUE); ShowScrollBar(m_hwnd,SB_HORZ,TRUE); } else { si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; si.nPos = 0; SetScrollInfo(m_hwnd,SB_HORZ,&si,FALSE); ShowScrollBar(m_hwnd,SB_HORZ,FALSE); } // Causes a WM_PAINT //this->Invalidate(); //CBaseClassWindow::Update(); // <-- This causes additional processing time, is not neccessary, and causes a very noticiable delay when resizing the window } CControlTreeListView::CNode *CControlTreeListView::CNode::AddCheckboxChild_Worker(CElementIcon *pIcon,std::string strNodeID,std::string strDisplayName,bool bChecked,bool bCheckBoxVisible) { CControlTreeListView::CNode *ChildNode; ChildNode = new CControlTreeListView::CNode(); // NodeID's MUST be unique in order for certain functions to work correctly. // For example, FindFirstSelection will return incorrect results // if NodeIDs are not unique. // Moral: Each child must have a unique strNodeID for (std::vector<CNode *>::iterator iter=this->vpChild.begin(); iter!=this->vpChild.end(); iter++) { assert((*iter)->strNodeID != strNodeID); // strNodeID is not unique!!! if ((*iter)->strNodeID == strNodeID) { //assert((*iter)->strNodeID != strNodeID); // strNodeID is not unique!!! return NULL; } } ChildNode->strNodeID = strNodeID; if (pIcon!=NULL) ChildNode->smIcon[0].Init(pIcon); ChildNode->bSelected = false; ChildNode->bTVExpandedAndVisible = true; ChildNode->bCheckBoxVisible = bCheckBoxVisible; if (bChecked) ChildNode->nCheck = 1; else ChildNode->nCheck = 0; ChildNode->pNodeParent = this; ChildNode->nIndentLevel = this->nIndentLevel + 1; ChildNode->strColumn[0] = strDisplayName; ChildNode->nWidth = 0; // Calculated later // All cap version of strDisplayName is default sort value. // This mimicks Windows Explorer filename sort std::string strSort = strDisplayName; std::transform(strSort.begin(),strSort.end(),strSort.begin(),(int(*)(int))toupper); //toupper(strSort[0]); ChildNode->EditColumnData(0,strDisplayName,strSort); this->vpChild.push_back(ChildNode); this->Child[strNodeID] = ChildNode; if (this->Child[strNodeID]->nCheck==1) { //SetCheckState(this->Child[strNodeID],true); CNode *pParentNode = this->Child[strNodeID]; while (pParentNode!=NULL) { if (pParentNode->bCheckBoxVisible) { if (pParentNode->nCheck == 0 || pParentNode->nCheck == -1 ) { if (bChecked) // A little optimization here { // If we are here, we don't need to look for children that are // checked because we know at least one of them is. We just // need to set nocheck on all parents to partial check. if (pParentNode->nCheck==0) { pParentNode->nCheck = -1; } } else { if (AreAnyChildrenFullyChecked(pParentNode)) pParentNode->nCheck = -1; else pParentNode->nCheck = 0; } } } pParentNode = pParentNode->pNodeParent; } } return this->Child[strNodeID]; } // This gets called when we add, delete, expand, or collapse a node // In other words, when we update the treeview control void CControlTreeListView::DetermineContentWidthAndHeight() { // Some setup m_nContentWidth = 0; m_nContentHeight = 0; m_mapRenderedNodeLine.clear(); if (m_Root.Child.size()<1) return; HDC hdc = GetDC(NULL); assert(hdc!=NULL); HFONT hFontOld = static_cast<HFONT>(SelectObject(hdc,static_cast<HGDIOBJ>(m_hFont))); // Unleash the Recursive Beast DetermineContentWidthAndHeightRecursiveHelper(hdc,&m_Root); SelectObject(hdc, static_cast<HGDIOBJ>(hFontOld)); ReleaseDC(NULL, hdc); // Now adjust m_nContentHeight for m_nNodeHeight m_nContentHeight *= m_nNodeHeight; // Adjust for any potential column headers m_nContentHeight += m_nColumnHeaderHeight; // Adjust if more than one column... (HORZ Scroll is not based on width of nodes anymore) //std::map<int,int>::iterator iter; if (bUsingColumns) { m_nContentWidth = m_vnColumnRightOffset[static_cast<int>(m_vnColumnRightOffset.size())-1]; } else { m_vnColumnRightOffset[0] = m_nContentWidth + this->m_nVScrollWidth; } } void CControlTreeListView::DetermineContentWidthAndHeightRecursiveHelper(HDC hdc,CNode *pNode) { if (pNode->pNodeParent!=NULL) // This is not the invisible root node so go ahead and process it { //m_nContentHeight += m_nNodeHeight; // We don't do this. We assume m_nContentHeight is for lines now m_mapRenderedNodeLine[m_nContentHeight] = pNode; m_nContentHeight++; // Determine text width SIZE TextSize; GetTextExtentPoint32(hdc, pNode->strColumn[0].c_str(), static_cast<UINT>(pNode->strColumn[0].length()), &TextSize); pNode->nTextWidth = TextSize.cx; // Calculate the total indented width of column one for HORZ scroll purposes. // Expand = 16 // Checkbox = 16 if visible, 0 otherwise // Icon = 16 if visible, 0 otherwise // 4 pixel space, then text: // text = text width pNode->nWidth = (m_bShowRootExpandTabs?0:-16) + (pNode->nIndentLevel*m_nIndentMultiplier) + 16 + // expand tab, whether visible or not (pNode->bCheckBoxVisible?16:0) + (pNode->smIcon.size()?16:0) + 4 + pNode->nTextWidth; if (pNode->nWidth > m_nContentWidth) m_nContentWidth = pNode->nWidth; } if (pNode->bTVExpandedAndVisible) { for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++) { DetermineContentWidthAndHeightRecursiveHelper(hdc,*iter); } } } CControlTreeListView::CNode *CControlTreeListView::SelectNode(CNode *pNode,bool bControlKeyDown,bool bShiftKeyDown,CNode *pNodeShiftAnchor) { if (bShiftKeyDown && m_bMultilineSelect) { RecursivelyDeselectNodes(&m_Root,pNodeShiftAnchor); // Now select nodes based on m_mapRenderedNodeLine int nMin,nMax; int nIndexCurrent = nMin = DetermineNodeLineIndex(pNode); int nIndexAnchor = nMax = DetermineNodeLineIndex(pNodeShiftAnchor); if (nIndexCurrent==-1 || nIndexAnchor==-1) return pNodeShiftAnchor; if (nIndexCurrent>nIndexAnchor) { nMin = nIndexAnchor; nMax = nIndexCurrent; } for (int i=nMin; i<=nMax; i++) { m_mapRenderedNodeLine[i]->bSelected = true; if (m_bWholeLineHighlight) Redraw(i,false,false,false,false,true); else Redraw(i,false,false,true,true); } return pNodeShiftAnchor; } if ( m_bMultilineSelect==false || bControlKeyDown==false ) { RecursivelyDeselectNodes(&m_Root,pNode); } if (pNode->bSelected==false && bControlKeyDown==false) { pNode->bSelected = true; if (m_bWholeLineHighlight) Redraw(pNode,false,false,false,false,true); else Redraw(pNode,false,false,true,true); } else if (bControlKeyDown) { // control key is down... toggle this selection on/off pNode->bSelected = !pNode->bSelected; if (m_bWholeLineHighlight) Redraw(pNode,false,false,false,false,true); else Redraw(pNode,false,false,true,true); } return pNode; } void CControlTreeListView::RecursivelyDeselectNodes(CNode *pNode,CNode *pIgnoreMeNode) { if (pNode->bSelected && pNode!=pIgnoreMeNode) { pNode->bSelected = false; if (m_bWholeLineHighlight) Redraw(pNode,false,false,false,false,true); else Redraw(pNode,false,false,false,true); } for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++) RecursivelyDeselectNodes(*iter,pIgnoreMeNode); } bool CControlTreeListView::AreAnyChildrenSelected(CNode *pNode) { bool bAnyChildrenSelected = false; RecursivelyDetermineIfAnyChildrenSelected(pNode,&bAnyChildrenSelected,pNode); return bAnyChildrenSelected; } void CControlTreeListView::RecursivelyDetermineIfAnyChildrenSelected(CNode *pNode,bool *pbAnyChildrenSelected,CNode *pNodeIgnore) { if (pNode->bSelected && pNode!=pNodeIgnore) { *pbAnyChildrenSelected = true; return; // Our goal is complete. We have a true value. We return to gain performance. } for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++) RecursivelyDetermineIfAnyChildrenSelected(*iter,pbAnyChildrenSelected,pNodeIgnore); } bool CControlTreeListView::CNode::AreAnyChildrenFullyChecked(CNode *pNode) { bool bAnyChildrenFullyChecked = false; RecursivelyDetermineIfAnyChildrenFullyChecked(pNode,&bAnyChildrenFullyChecked); return bAnyChildrenFullyChecked; } void CControlTreeListView::CNode::RecursivelyDetermineIfAnyChildrenFullyChecked(CNode *pNode,bool *pbAnyChildrenFullyChecked) { if (pNode->nCheck==1) { *pbAnyChildrenFullyChecked = true; return; // Our goal is complete. We have a true value. We return to gain performance. } for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++) RecursivelyDetermineIfAnyChildrenFullyChecked(*iter,pbAnyChildrenFullyChecked); } void CControlTreeListView::SetCheckState(CNode *pNode,bool bChecked) { //int nScrollX,nScrollY; if (bChecked) { if (pNode->nCheck!=1) { pNode->nCheck = 1; Redraw(pNode,false,true,true,false); } } else { if (pNode->nCheck!=0) { pNode->nCheck = 0; Redraw(pNode,false,true,true,false); } } // Go up to the chain to each parent. If the parent is not checked, // then check for checked children. If any children are checked, the // parent becomes partialy checked. CNode *pParentNode = pNode; bool bInvalidateCheckBox; while (pParentNode!=NULL) { if (pParentNode->bCheckBoxVisible) { if (pParentNode->nCheck == 0 || pParentNode->nCheck == -1 ) { bInvalidateCheckBox = false; if (bChecked) // A little optimization here { // If we are here, we don't need to look for children that are // checked because we know at least one of them is. We just // need to set nocheck on all parents to partial check. if (pParentNode->nCheck==0) { bInvalidateCheckBox = true; pParentNode->nCheck = -1; } } else { if (m_Root.AreAnyChildrenFullyChecked(pParentNode)) { if (pParentNode->nCheck!=-1) bInvalidateCheckBox = true; pParentNode->nCheck = -1; } else { if (pParentNode->nCheck!=0) bInvalidateCheckBox = true; pParentNode->nCheck = 0; } } if (bInvalidateCheckBox) { Redraw(pParentNode,false,true,false,false); } } } pParentNode = pParentNode->pNodeParent; } //this->Invalidate(); } void CControlTreeListView::Redraw(int nLineIndex,bool bExpandTab,bool bCheckBox,bool bIcon,bool bText,bool bWholeLine) { int nScrollX, nScrollY; if (nLineIndex==-1) return; // Get index of top line SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(this->GetHwnd(),SB_VERT,&si); nScrollY = si.nPos; // Get left edge GetScrollInfo(this->GetHwnd(),SB_HORZ,&si); nScrollX = si.nPos; // Calculate top line for optimization //int nTop = nLineIndex-nScrollY; int nTop = ((nLineIndex*m_nNodeHeight))-(nScrollY*m_nNodeHeight); nTop += m_nColumnHeaderHeight; if (nTop<0) return; // Since our trees are top down, we don't need to check for bottom // because doing this won't optimize anything anyway // Determine a few important items int nXExpandButtonLeft = ( -nScrollX + (m_mapRenderedNodeLine[nLineIndex]->nIndentLevel*m_nIndentMultiplier)) + 1; nXExpandButtonLeft += (m_bShowRootExpandTabs?0:-16); int nXExpandButtonRight = nXExpandButtonLeft + 12; int nXCheckBoxLeft = ( -nScrollX + (m_mapRenderedNodeLine[nLineIndex]->nIndentLevel*m_nIndentMultiplier)) + 16; nXCheckBoxLeft += (m_bShowRootExpandTabs?0:-16); int nXCheckBoxRight = nXCheckBoxLeft + (m_mapRenderedNodeLine[nLineIndex]->bCheckBoxVisible?16:0); int nXIconLeft = nXCheckBoxRight; int nXIconRight = nXIconLeft + ((m_mapRenderedNodeLine[nLineIndex]->smIcon.size()?16:0)); int nXTextLeft = nXIconRight; int nXTextRight = nXTextLeft + m_mapRenderedNodeLine[nLineIndex]->nTextWidth; nXTextRight += 10; // Now determine invalidate area for removal of selection highlight RECT rcInvalidate; // Top and bottom are always like this: //rcInvalidate.top = ((nLineIndex*m_nNodeHeight))-(nScrollY*m_nNodeHeight); //rcInvalidate.top += m_nColumnHeaderHeight; rcInvalidate.top = nTop; rcInvalidate.bottom = rcInvalidate.top + m_nNodeHeight; if (bWholeLine) { rcInvalidate.left = 0; rcInvalidate.right = this->GetWidth(); this->Invalidate(&rcInvalidate); } else { if (bExpandTab && m_mapRenderedNodeLine[nLineIndex]->vpChild.size()) { rcInvalidate.left = nXExpandButtonLeft; rcInvalidate.right = nXExpandButtonRight; this->Invalidate(&rcInvalidate); } if (bCheckBox && m_mapRenderedNodeLine[nLineIndex]->bCheckBoxVisible) { rcInvalidate.left = nXCheckBoxLeft; rcInvalidate.right = nXCheckBoxRight; this->Invalidate(&rcInvalidate); } if (bIcon && m_mapRenderedNodeLine[nLineIndex]->smIcon.size()) { rcInvalidate.left = nXIconLeft; rcInvalidate.right = nXIconRight; this->Invalidate(&rcInvalidate); } if (bText && m_mapRenderedNodeLine[nLineIndex]->strColumn[0].length()>0) { rcInvalidate.left = nXTextLeft; rcInvalidate.right = nXTextRight; this->Invalidate(&rcInvalidate); } } } void CControlTreeListView::Redraw(CNode *pNode,bool bExpandTab,bool bCheckBox,bool bIcon,bool bText,bool bWholeLine) { Redraw(DetermineNodeLineIndex(pNode),bExpandTab,bCheckBox,bIcon,bText,bWholeLine); } int CControlTreeListView::DetermineNodeLineIndex(CNode *pNode) { // Determine index of current node int nLineIndex = -1; for (int i=0; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++) { if (m_mapRenderedNodeLine[i]==pNode) { nLineIndex = i; break; } } /* // Yes, this could potentially happen and can cause // a crash. Leave this be!! if (nLineIndex==-1) return; */ return nLineIndex; } void CControlTreeListView::AddColumn(int nColumn,int nColumnWidth,std::string strColumnHeader,bool bRightJustify) { bUsingColumns = true; /* if (nColumn<m_vnColumnRightOffset.size() && nColumn!=0) return; */ //int nColumnNumber = static_cast<int>(m_nColumnWidth.size()) + 1; //if (nColumnNumber==2) { // Since we now have more than one column, make column // header visible. Note, it is invisible if only one column. //CElementText cetTmp; //cetTmp.Init("Tahoma",8,RGB(0,0,0),"Mg"); //m_nNodeHeight = cetTmp.GetHeight(); int nPadding = 4; m_nColumnHeaderHeight = m_nNodeHeight + nPadding; } if (nColumn==0) m_vnColumnRightOffset[nColumn] = nColumnWidth; // Should already exist //m_vnColumnRightOffset.push_back(nColumnWidth); else m_vnColumnRightOffset.push_back(m_vnColumnRightOffset[nColumn-1] + nColumnWidth); //m_vnColumnRightOffset[nColumn] = m_vnColumnRightOffset[nColumn-1] + nColumnWidth; //m_vstrColumnHeader[nColumn] = strColumnHeader; m_vstrColumnHeader.push_back(strColumnHeader); m_vbRightJustify.push_back(bRightJustify); } void CControlTreeListView::GetColumnRect(int nColumn,RECT *rcColumn) { SCROLLINFO si; // Get and Save Scroll Positions si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; GetScrollInfo(this->GetHwnd(),SB_HORZ,&si); int nHorzPos = si.nPos; //si.fMask = SIF_POS; //GetScrollInfo(hwnd,SB_VERT,&si); //int nVertPos = si.nPos; rcColumn->top = m_nColumnHeaderHeight; rcColumn->bottom = this->GetHeight(); rcColumn->left = 0; if (nColumn==0) rcColumn->left = 0; else rcColumn->left = m_vnColumnRightOffset[nColumn-1]; rcColumn->left -= nHorzPos; rcColumn->right = rcColumn->left + GetColumnWidth(nColumn); /* if (rcColumn->left<0) rcColumn->left = 0; if (rcColumn->right>this->GetWidth()) rcColumn->right = this->GetWidth(); */ //rcColumn->bottom += 1; //rcColumn->right += 1; } int CControlTreeListView::GetColumnWidth(int nColumn) { if (nColumn==0) return m_vnColumnRightOffset[0]; else if (nColumn>=1 && nColumn<static_cast<int>(m_vnColumnRightOffset.size())) { return (m_vnColumnRightOffset[nColumn]-m_vnColumnRightOffset[nColumn-1]); } else return 0; return 0; } void CControlTreeListView::SelectDragRectangleNodes() { if (m_nWhatsCaptured!=m_enumWC_DragRect) return; // Select those within range int xLeft = (m_xDragRectanglePos<m_xDragRectangleAnchor)?m_xDragRectanglePos:m_xDragRectangleAnchor; //int xRight = (m_xDragRectanglePos>m_xDragRectangleAnchor)?m_xDragRectanglePos:m_xDragRectangleAnchor; int yTop = (m_yDragRectanglePos<m_yDragRectangleAnchor)?m_yDragRectanglePos:m_yDragRectangleAnchor; int yBottom = (m_yDragRectanglePos>m_yDragRectangleAnchor)?m_yDragRectanglePos:m_yDragRectangleAnchor; int yPosTmp = yTop; //yPosTmp -= m_nColumnHeaderHeight; int nItemIndexTop = yPosTmp/m_nNodeHeight; yPosTmp = yBottom; //yPosTmp -= m_nColumnHeaderHeight; int nItemIndexBottom = yPosTmp/m_nNodeHeight; if (bUsingColumns==false) { for (int i=0; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++) { if (i>=nItemIndexTop && i<=nItemIndexBottom && xLeft<m_mapRenderedNodeLine[i]->nWidth) { m_mapRenderedNodeLine[i]->bSelected = true; m_pAnchorNode = m_mapRenderedNodeLine[i]; } else m_mapRenderedNodeLine[i]->bSelected = false; } } else { int nxRight; for (int i=0; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++) { if (m_vnColumnRightOffset[0]>m_mapRenderedNodeLine[i]->nWidth) nxRight = m_mapRenderedNodeLine[i]->nWidth; else nxRight = m_vnColumnRightOffset[0]; //nxRight = (m_mapRenderedNodeLine[i]->nWidth>m_vnColumnRightOffset[0])?m_vnColumnRightOffset[0]:m_mapRenderedNodeLine[i]; if (i>=nItemIndexTop && i<=nItemIndexBottom && xLeft<nxRight) { m_mapRenderedNodeLine[i]->bSelected = true; m_pAnchorNode = m_mapRenderedNodeLine[i]; } else m_mapRenderedNodeLine[i]->bSelected = false; } } } void CControlTreeListView::CNode::RemoveChildren() { RemoveChildrenRecursively(this); this->vpChild.clear(); } void CControlTreeListView::CNode::RemoveChild(std::string strNodeID) { CNode *pNode = this->Child[strNodeID]; if (pNode==NULL) return; // strNodeID Not found // Erase this child std::vector<CNode *>::iterator vpChildIter; for (vpChildIter=this->vpChild.begin(); vpChildIter!=this->vpChild.end(); vpChildIter++) { if (pNode == *vpChildIter) { this->vpChild.erase(vpChildIter); break; } } this->Child.erase(strNodeID); // Cycle through and recursively delete the children of this child, // as well as this child itself RemoveChildrenRecursively(pNode); } void CControlTreeListView::CNode::RemoveChildrenRecursively(CNode *pNode) { for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++) RemoveChildrenRecursively(*iter); if (pNode->pNodeParent!=NULL) // This is not the root node so go ahead and delete it { delete pNode; } } CControlTreeListView::CNode * CControlTreeListView::FindFirstSelected(void) { nFindFirstNextSelectedIndex = -1; for (int i=0; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++) { if (m_mapRenderedNodeLine[i]->bSelected) { nFindFirstNextSelectedIndex = i; break; } } if (nFindFirstNextSelectedIndex == -1) return NULL; return m_mapRenderedNodeLine[nFindFirstNextSelectedIndex]; } CControlTreeListView::CNode * CControlTreeListView::FindNextSelected(void) { int nNext = -1; for (int i=nFindFirstNextSelectedIndex+1; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++) { if (m_mapRenderedNodeLine[i]->bSelected) { nNext = i; break; } } if (nNext == -1) return NULL; nFindFirstNextSelectedIndex = nNext; return m_mapRenderedNodeLine[nFindFirstNextSelectedIndex]; } int CControlTreeListView::GetNumSelected(void) { int nNumSelected = 0; for (int i=0; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++) { if (m_mapRenderedNodeLine[i]->bSelected) nNumSelected++; } return nNumSelected; } void CControlTreeListView::CNode::EditColumnData(int nColumn,std::string strText,std::string strSortValue) { this->strColumn[nColumn] = strText; //this->ColumnSortType[nColumn] = CCTLV_SORT_STRING; if (strSortValue=="") this->strColumnSortValue[nColumn].clear(); else { this->strColumnSortValue[nColumn] = strSortValue; /* std::string::iterator Iter; bool bIsNumber = true; for (Iter=strSortValue.begin(); Iter!=strSortValue.end(); Iter++) { if ( (*Iter)<'0' && (*Iter)>'9') bIsNumber = false; } if (bIsNumber) { this->dColumnSortValue[nColumn] = static_cast<double>(strtod(strSortValue.c_str(),NULL)); } */ } } /* void CControlTreeListView::CNode::EditColumnData(int nColumn,std::string strText,double dSortValue) { this->strColumn[nColumn] = strText; this->strColumnSortValue[nColumn] = " "; this->ColumnSortType[nColumn] = CCTLV_SORT_DOUBLE; this->dColumnSortValue[nColumn] = dSortValue; } */ void CControlTreeListView::CNode::SortChildren(int nColumn,bool bToggleSortDirection) { std::string::iterator strIter; double dValA = 0.0; double dValB = 0.0; std::string strLeadingNumberA; std::string strLeadingNumberB; std::string strSortA; std::string strSortB; int nASortType, nBSortType; if (bToggleSortDirection) bSortReverseToggle = !bSortReverseToggle; CNode *pNodeTmp; // We sort in three ways. These can be reversed. // // 1. Empty sort value strings get sorted by column[0] (display name) // 2. Number sort values get sorted // 3. String sort values get sorted (case-insensitive) // // This makes 9 possible sort scenarios plus reverse = 18. for (int i=static_cast<int>(vpChild.size())-1; i>=0; i--) { for (int j=1; j<=i; j++) { // If sort value is empty, sort by value of column 0 if (vpChild[j-1]->strColumnSortValue[nColumn].empty() || vpChild[j-1]->strColumnSortValue[nColumn]=="" ) { strSortA = vpChild[j-1]->strColumnSortValue[0]; nASortType = 0; // Empty } else { strSortA = vpChild[j-1]->strColumnSortValue[nColumn]; strLeadingNumberA = ""; dValA = 0.0; for (strIter=strSortA.begin(); strIter!=strSortA.end(); strIter++) { if ( (*strIter)>='0' && (*strIter)<='9') strLeadingNumberA += (*strIter); else break; } if (strLeadingNumberA.size()>0) { dValA = static_cast<double>(strtod(strLeadingNumberA.c_str(),NULL)); nASortType = 1; // Number } else { nASortType = 2; // Regular string sort } } if (vpChild[j]->strColumnSortValue[nColumn].empty() || vpChild[j]->strColumnSortValue[nColumn]=="" ) { strSortB = vpChild[j]->strColumnSortValue[0]; nBSortType = 0; // Empty } else { strSortB = vpChild[j]->strColumnSortValue[nColumn]; strLeadingNumberB = ""; dValB = 0.0; for (strIter=strSortB.begin(); strIter!=strSortB.end(); strIter++) { if ( (*strIter)>='0' && (*strIter)<='9') strLeadingNumberB += (*strIter); else break; } if (strLeadingNumberB.size()>0) { dValB = static_cast<double>(strtod(strLeadingNumberB.c_str(),NULL)); nBSortType = 1; // Number } else { nBSortType = 2; // Regular string sort } } if (nASortType==1 && nBSortType==1) { if ( dValA > dValB ) { pNodeTmp = vpChild[j-1]; vpChild[j-1] = vpChild[j]; vpChild[j] = pNodeTmp; } } else if (nASortType==0 && nBSortType==0) { if ( strSortA > strSortB ) { pNodeTmp = vpChild[j-1]; vpChild[j-1] = vpChild[j]; vpChild[j] = pNodeTmp; } } else { if ( strSortA > strSortB ) { pNodeTmp = vpChild[j-1]; vpChild[j-1] = vpChild[j]; vpChild[j] = pNodeTmp; } } if (bSortReverseToggle) { pNodeTmp = vpChild[j-1]; vpChild[j-1] = vpChild[j]; vpChild[j] = pNodeTmp; } } } }