// ========================================================================== // Class Implementation : COXMultiComboBox // ========================================================================== // Source file : OXMultiComboBox.cpp // Version: 9.3 // This software along with its related components, documentation and files ("The Libraries") // is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is // governed by a software license agreement ("Agreement"). Copies of the Agreement are // available at The Code Project (www.codeproject.com), as part of the package you downloaded // to obtain this file, or directly from our office. For a copy of the license governing // this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900. // ////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "OXMultiComboBox.h" #include "UTBStrOp.h" #include "UTB64Bit.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif IMPLEMENT_DYNAMIC(COXMultiComboBox, CComboBox) BEGIN_MESSAGE_MAP(COXMultiComboBox, CComboBox) //{{AFX_MSG_MAP(COXMultiComboBox) ON_WM_CTLCOLOR() ON_WM_DELETEITEM() //}}AFX_MSG_MAP ON_MESSAGE(WM_OX_LISTHSCROLL,OnListHScroll) ON_MESSAGE(CB_GETITEMDATA,OnGetItemData) ON_MESSAGE(CB_SETITEMDATA,OnSetItemData) ON_MESSAGE(WM_COMPAREITEM,OnCompareItem) ON_MESSAGE(WM_MEASUREITEM,OnMeasureItem) ON_MESSAGE(WM_DRAWITEM,OnDrawItem) ON_MESSAGE(CB_INSERTSTRING,OnInsertString) ON_MESSAGE(CB_ADDSTRING,OnAddString) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // Definition of static members // Data members------------------------------------------------------------- //Protected // int m_nColumnCount; // --- Numbers of columns // int m_nMasterColumn // --- Master column number (zero based index) // CArray m_aColumnWidth; // --- Array used to store the width of each column // enum {DEFAULT_COLUMN_WIDTH=40}; // --- Default column width // CPen m_GridPen // --- Pen to Draw the grid // class COXComboLBox : public CWnd // --- This CWnd class is used to subclass the listbox hadle of the combobox // to handle its WM_HSCROLL message, which will be send to combobox. // COXComboLBox m_ComboLBox; // --- This Window object is used to subclass the window handle of listbox of // the combobox. // class COXRowData // --- This private class is used to store the each items column strings and // its data. All the members in this are self explainatory //Private // BOOL m_fSizeChanged // --- This will be true when a column width is set and accordingly // the scrollbasr are not adjusted. // BOOL m_fMasterColumnChanging // --- Flag used to avoid from deleting the itemdata when master column is // changing. // int m_nPageWidth; // --- Amount of Width to scroll on SB_PAGELEFT and SB_PAGERIGHT // int m_nLineWidth; // --- Line width to scroll, on SB_LINELEFT and SB_LINERIGHT // Member functions --------------------------------------------------------- // public: COXMultiComboBox::COXMultiComboBox() { // ... By default m_nColumnCount = 1; // ... By default m_nMasterColumn = 0; m_aColumnWidth.SetSize(1); m_aColumnWidth[0] = DEFAULT_COLUMN_WIDTH; m_fSizeChanged = TRUE; m_ComboLBox.Init(this); m_fMasterColumnChanging = FALSE; m_bFitToSize=FALSE; // Create pen with alternating dot - space // (PS_DOT does not give a good result, so we use // PS_COSMETIC | PS_ALTERNATE) LOGBRUSH logBrush; logBrush.lbColor = RGB(0,0,0); logBrush.lbStyle = BS_SOLID; logBrush.lbHatch = HS_HORIZONTAL; if (!m_GridPen.CreatePen(PS_COSMETIC | PS_ALTERNATE, 1, &logBrush, 0,NULL)) if (!m_GridPen.CreatePen(PS_COSMETIC, 1, &logBrush, 0, NULL)) TRACE0("COXMultiComboBox::COXMultiComboBox : Error creating the grid lines Pen\n"); } BOOL COXMultiComboBox::SetColumnCount(int nCount) { if(nCount == m_nColumnCount) return TRUE; // ... nCount should be more than or equal to Master column and -1 if ((nCount <= m_nMasterColumn) || (nCount < 0)) return FALSE; m_aColumnWidth.SetSize(nCount); // If size grows initailize the new columns width for(int nColIndex = m_nColumnCount; nColIndex < nCount; nColIndex++) m_aColumnWidth[nColIndex] = DEFAULT_COLUMN_WIDTH; // Resizes the number of columns in each rowdata object int nCurCount = GetCount(); for(int nIndex = 0; nIndex < nCurCount; nIndex++) { COXRowData* pRowData = GetRowData(nIndex); if (pRowData != NULL) pRowData->SetColumnCount(nCount); else TRACE0("In COXMultiComboBox::SetColumnCount : GetRowData() returned NULL.\n"); } m_nColumnCount = nCount; AdjustToFitSize(); return TRUE; } BOOL COXMultiComboBox::SetMasterColumn(int nCol) { if(nCol == m_nMasterColumn) return TRUE; // Master Column index should be in between 0 and m_nColumnCount-1 if ((m_nColumnCount <= nCol) || (nCol < 0)) return FALSE; int nPrevMasterColumn = m_nMasterColumn; CString sPrevMasterColumnString; int nCurSel = GetCurSel(); if(nCurSel!=-1) GetLBText(GetCurSel(),sPrevMasterColumnString); m_nMasterColumn = nCol; // Resorts based on newly set master column if(!ChangeMasterColumn(nCol)) { m_nMasterColumn = nPrevMasterColumn; return FALSE; } // Selects the string in newly set master column into editbox of combo if(nCurSel!=-1) if((SelectString(-1,nPrevMasterColumn,sPrevMasterColumnString.GetBuffer(0)))==CB_ERR) return FALSE; return TRUE; } BOOL COXMultiComboBox::SetColumnWidth(int nColIndex, int nWidth) { if(nColIndex >= m_nColumnCount || nColIndex < -1) return FALSE; if(nColIndex == -1) { // Set the width of all the columns for(nColIndex =0; nColIndex= GetCount() || nColIndex >= m_nColumnCount || nColIndex < 0 || nIndex < 0) return CB_ERR; // ... copies the string into buffer COXRowData* pRowData = GetRowData(nIndex); if (pRowData != NULL) { // ... returns the length UTBStr::tcscpy(lpszText, pRowData->GetColumnString(nColIndex).GetLength()+1, pRowData->GetColumnString(nColIndex).GetBuffer(0)); return pRowData->GetColumnString(nColIndex).GetLength(); } else { TRACE0("In COXMultiComboBox::GetLBText : GetRowData() returned NULL.\n"); return CB_ERR; } } void COXMultiComboBox::GetLBText(int nIndex, int nColIndex, CString& rString) const { if(nIndex < GetCount() && nColIndex < m_nColumnCount && nIndex >= 0 && nColIndex >=0 ) { COXRowData* pRowData = GetRowData(nIndex); if (pRowData != NULL) { rString = pRowData->GetColumnString(nColIndex); return; } else TRACE0("In COXMultiComboBox::GetLBText : GetRowData() returned NULL.\n"); } rString.Empty(); } int COXMultiComboBox::GetLBTextLen(int nIndex,int nColIndex) const { if(nIndex >= GetCount() || nColIndex >= m_nColumnCount || nColIndex < 0 || nIndex < 0) return CB_ERR; COXRowData* pRowData = GetRowData(nIndex); if (pRowData != NULL) return pRowData->GetColumnString(nColIndex).GetLength(); else { TRACE0("In COXMultiComboBox::GetLBTextLen : GetRowData() returned NULL.\n"); return CB_ERR; } } int COXMultiComboBox::FindStringExact(int nIndexStart, int nColIndex, LPCTSTR lpszFind, BOOL bCaseSensitive/*=FALSE*/) const { if(nColIndex >= m_nColumnCount || nColIndex < 0) return CB_ERR; if(nIndexStart <= -1) nIndexStart = 0; int nItemCount = GetCount(); for(int nIndex = nIndexStart; nIndex < nItemCount; nIndex++) { COXRowData* pRowData = GetRowData(nIndex); if (pRowData != NULL) { if(bCaseSensitive) { if(pRowData->GetColumnString(nColIndex) == lpszFind) return nIndex; } else { if(pRowData->GetColumnString(nColIndex).CompareNoCase(lpszFind) == 0) return nIndex; } } else TRACE0("In COXMultiComboBox::FindStringExact : GetRowData() returned NULL.\n"); } return CB_ERR; } int COXMultiComboBox::FindString(int nStartAfter, int nColIndex, LPCTSTR lpszString, BOOL bCaseSensitive/*=FALSE*/) const { if(nColIndex >= m_nColumnCount || nColIndex < 0) return CB_ERR; if(nStartAfter < -1) nStartAfter = -1; int nItemCount = GetCount(); int nStrLen = (int)_tcslen(lpszString); if(nStrLen==0) { TRACE(_T("COXMultiComboBox::FindString : zero-length string was specified\n")); return CB_ERR; } for(int nIndex = nStartAfter+1; nIndex < nItemCount; nIndex++) { COXRowData* pRowData = GetRowData(nIndex); if (pRowData != NULL) { if(bCaseSensitive) { if(pRowData->GetColumnString(nColIndex).Left(nStrLen) == lpszString) return nIndex; } else { if(pRowData->GetColumnString(nColIndex).Left(nStrLen). CompareNoCase(lpszString) == 0) return nIndex; } } else TRACE0("In COXMultiComboBox::FindString : GetRowData() returned NULL.\n"); } return CB_ERR; } int COXMultiComboBox::InsertString(int nIndex, LPCTSTR* lpszString, int nNumStrings) { int nRetVal; // return CB_ERR if invalid number of strings if(nNumStrings > m_nColumnCount || nNumStrings < 1) return CB_ERR; // Actually Inserts the item with master column text if((nRetVal = CComboBox::InsertString(nIndex,lpszString[m_nMasterColumn])) != CB_ERR) { COXRowData* pRowData = new COXRowData(m_nColumnCount); // Initialize the column strings for(int nColIndex=0; nColIndex < nNumStrings; nColIndex++) pRowData->SetColumnString(nColIndex,lpszString[nColIndex]); SetRowData(nRetVal,pRowData); AdjustToFitSize(); } return nRetVal; } int COXMultiComboBox::AddString(LPCTSTR* lpszString, int nNumStrings) { int nRetVal; // return CB_ERR if invalid number of strings if(nNumStrings > m_nColumnCount || nNumStrings < 1) return CB_ERR; // Adds the item with master column text if((nRetVal = CComboBox::AddString(lpszString[m_nMasterColumn])) != CB_ERR) { COXRowData* pRowData = new COXRowData(m_nColumnCount); // Initialize the column strings for(int nColIndex=0; nColIndex < nNumStrings; nColIndex++) pRowData->SetColumnString(nColIndex,lpszString[nColIndex]); SetRowData(nRetVal,pRowData); AdjustToFitSize(); } return nRetVal; } int COXMultiComboBox::SelectString(int nStartAfter, int nColIndex, LPCTSTR lpszString) { if(nColIndex >= m_nColumnCount || nColIndex < 0) return CB_ERR; int nIndex = FindStringExact(nStartAfter,nColIndex,lpszString); if(nIndex != CB_ERR) { COXRowData* pRowData = GetRowData(nIndex); if (pRowData != NULL) nIndex = CComboBox::SelectString(nStartAfter,pRowData->GetColumnString(m_nMasterColumn)); else { TRACE0("In COXMultiComboBox::SelectString : GetRowData() returned NULL.\n"); nIndex = CB_ERR; } } return nIndex; } int COXMultiComboBox::AddString(LPCTSTR lpszString) { int nRetVal; if((nRetVal = CComboBox::AddString(lpszString)) != CB_ERR) { COXRowData* pRowData = new COXRowData(m_nColumnCount); pRowData->SetColumnString(m_nMasterColumn,lpszString); SetRowData(nRetVal,pRowData); AdjustToFitSize(); } return nRetVal; } int COXMultiComboBox::InsertString(int nIndex, LPCTSTR lpszString) { int nRetVal; if((nRetVal = CComboBox::InsertString(nIndex,lpszString)) != CB_ERR) { COXRowData* pRowData = new COXRowData(m_nColumnCount); pRowData->SetColumnString(m_nMasterColumn,lpszString); SetRowData(nRetVal,pRowData); AdjustToFitSize(); } return nRetVal; } //Protected // This is handled to divide the drawing into individual columns. // Advised to handle 'DrawColumnItem' than handling this itself in derived // classes unless you need to customize the lists more than just dividing // into column drawings void COXMultiComboBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) // effect : Number of calls to DrawColumnItems are made { ASSERT(lpDrawItemStruct->CtlType==ODT_COMBOBOX); // In case of drawing the static Text area of dropdown-list combobox if(lpDrawItemStruct->itemState & ODS_COMBOBOXEDIT) { DrawColumnItem(lpDrawItemStruct->hDC,GetCurSel(),m_nMasterColumn,lpDrawItemStruct->rcItem,lpDrawItemStruct->itemState); return; } if(lpDrawItemStruct->itemID==-1 && GetCount() == 0) { DrawFocusRect(lpDrawItemStruct->hDC,&(lpDrawItemStruct->rcItem)); return; } // Draw columns one by one until last but one CRect rectColumn = lpDrawItemStruct->rcItem; int nColIndex=0; for(nColIndex=0; nColIndex< m_nColumnCount-1; nColIndex++) { rectColumn.right = rectColumn.left + m_aColumnWidth[nColIndex]-1; DrawColumnItem(lpDrawItemStruct->hDC,lpDrawItemStruct->itemID, nColIndex,rectColumn,lpDrawItemStruct->itemState); rectColumn.left = rectColumn.right+1; } // draws last column rectColumn.right = __max(lpDrawItemStruct->rcItem.right, rectColumn.left + m_aColumnWidth[nColIndex]-1); DrawColumnItem(lpDrawItemStruct->hDC,lpDrawItemStruct->itemID, nColIndex,rectColumn,lpDrawItemStruct->itemState); } //This virtual function is called to draw the individual columns. In the case of // non-CBS_HASSTRINGS this has to be implimented in derived classes void COXMultiComboBox::DrawColumnItem(HDC hDC, int nIndex, int nColIndex, const CRect& rectColumn, int ItemState) // --- In : hDC : the DC into which to draw the column // nIndex : The row index // nColIndex : The Column Index // rectColumn : The rectangle into which we can draw // ItemState : Gives the state of the item(Selected or not) // --- Out : // --- Returns : // --- Effect : The column text is drawn in the case of CBS_HASSTRINGS { ASSERT(GetStyle()&CBS_HASSTRINGS); CDC dc; dc.Attach(hDC); // Sets the foreground color of the text based on its selection state COLORREF clrOldForeground = dc.SetTextColor(::GetSysColor(IsWindowEnabled() ? (ItemState&ODS_SELECTED ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT) : COLOR_GRAYTEXT)); COLORREF clrBackground = ::GetSysColor(IsWindowEnabled() ? (ItemState & ODS_SELECTED ? COLOR_HIGHLIGHT : COLOR_WINDOW) : COLOR_BTNFACE); // Sets the background color of text based on selection state COLORREF clrOldBackground = dc.SetBkColor( clrBackground); // Gets the column text to draw CString strText; if(nIndex!=-1) GetLBText(nIndex,nColIndex,strText); else strText.Empty(); CRect cellRect(rectColumn); dc.FillSolidRect(cellRect, clrBackground); // give left margin cellRect.left += LOWORD(GetDialogBaseUnits()) / 2; // Draw text dc.DrawText(strText, cellRect, DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS); // Seperators are drawn in list boxe between columns and rows DrawColumnBorder(&dc,rectColumn,ItemState); // Reset the actual DC colors dc.SetTextColor(clrOldForeground); dc.SetBkColor(clrOldBackground); dc.Detach(); } //override this function to draw you own border void COXMultiComboBox::DrawColumnBorder(CDC* pDC,const CRect& rectColumn,int ItemState) // --- In : pDC : the pointer to the DC into which to draw the column border // rectColumn : The rectangle into which we can draw // ItemState : Gives the state of the item(Selected or not) // --- Out : // --- Returns : // --- Effect : The column borderis drawn like ' _______| ' { if(!(ItemState & ODS_COMBOBOXEDIT)) { CPen* pOldPen = pDC->SelectObject(&m_GridPen); CPoint ptOld = pDC->MoveTo(rectColumn.right,rectColumn.top); pDC->LineTo(rectColumn.right,rectColumn.bottom-1); pDC->LineTo(rectColumn.left,rectColumn.bottom-1); pDC->MoveTo(ptOld); pDC->SelectObject(pOldPen); } } void COXMultiComboBox::MeasureItem(LPMEASUREITEMSTRUCT /* lpMeasureItemStruct */) { // In case of CBS_OWNERDRAWNVARIABLE,to be handle in derived classes // to get the size of items // The documentation of CComboBox::MeasureItem() states // "If the combo box is created with the CBS_OWNERDRAWVARIABLE style, // the framework calls this member function for each item in the list box. // Otherwise, this member is called only once." // So even with CBS_OWNERDRAWFIXED this function is called once. // In this case we do nothing return; } int COXMultiComboBox::CompareItem(LPCOMPAREITEMSTRUCT /* lpCompareItemStruct */) { // In case of non-CBS_HASSTRINGS, to be handle in derived classes // to compare the items ASSERT(FALSE); return 0; } ///////////////////////////////////////////////////////////////////////////// // COXMultiComboBox message handlers //This is handle to delete rowdata. void COXMultiComboBox::OnDeleteItem(int nIDCtl, LPDELETEITEMSTRUCT lpDeleteItemStruct) { if(!m_fMasterColumnChanging && lpDeleteItemStruct->CtlType == ODT_LISTBOX) { COXRowData* pRowData = GetRowData(lpDeleteItemStruct->itemID); if (pRowData != NULL) { pRowData->SetColumnCount(0); delete pRowData; } else TRACE0("In COXMultiComboBox::OnDeleteItem : GetRowData() returned NULL.\n"); } CComboBox::OnDeleteItem(nIDCtl, lpDeleteItemStruct); } // This message is handle to get the HWND of the listbox of the combobox // as there is no otherway to get it(refer to Q65881, Q131845 in Knowledge Base) // The listbox handle is needed to set the scrollbar to it if needed, and to // handle its WM_HSCROLL message. HBRUSH COXMultiComboBox::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) // --- In : pDC : device context (See standard help for detaled info) // pWnd : Pointer Cwnd obejct of the window whose background // has to be painted. // nCtlColor : Type of the control // --- Out : // --- Returns : Brush to supply to paint the background // --- Effect : The listbox's HWND is got and sunclassed to COXComboLBox // window object. The listbox style is modified to // have WS_HSCROLL. The horizontal extent of the listbox is // set and scrollbars are shown are hided depending on the // width and height. Scroll range for horizantal scrollbar // is set and also m_NumCharsPerPage is set. { HBRUSH hbr = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor); // Handle only if the type of control is Listbox only if ((nCtlColor == CTLCOLOR_LISTBOX) && m_fSizeChanged) { // ... To stop this code run unneccessorily m_fSizeChanged = FALSE; if(m_ComboLBox.GetSafeHwnd()==NULL) { m_ComboLBox.SubclassWindow(pWnd->GetSafeHwnd()); m_ComboLBox.ModifyStyle(0,WS_HSCROLL); TEXTMETRIC tm; GetTextMetrics(pDC->m_hDC,&tm); m_nLineWidth = tm.tmAveCharWidth; } int nTotalWidth = GetTotalWidth(); // ... Sets horizontal extent to total width m_ComboLBox.SetHorizontalExtent(nTotalWidth); CRect rcDrop; GetDroppedControlRect(&rcDrop); int iTotalItemHeight = 0; for (int i = 0; i < m_ComboLBox.GetCount(); i++) iTotalItemHeight += m_ComboLBox.GetItemHeight(i); if (rcDrop.Height() < iTotalItemHeight || m_bFitToSize) m_ComboLBox.AdjustHScroll(); if(GetStyle()&CBS_DROPDOWN) { CRect rcWnd; m_ComboLBox.GetWindowRect(&rcWnd); m_ComboLBox.MoveWindow(&rcWnd,TRUE); } if(m_ComboLBox.m_fHorzScrollVisible) { CRect rcClient; m_ComboLBox.GetClientRect(&rcClient); m_nPageWidth = rcClient.Width(); // setting sroll info SCROLLINFO si; si.cbSize = sizeof(si); si.nPos = 0; si.nMin = 0; si.nMax = nTotalWidth; si.nPage = m_nPageWidth; si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS ; m_ComboLBox.SetScrollInfo(SB_HORZ,&si,TRUE); m_ComboLBox.SendMessage(WM_HSCROLL,SB_TOP,0L); } m_ComboLBox.RedrawWindow(NULL,NULL,RDW_INVALIDATE|RDW_FRAME); } return hbr; } BEGIN_MESSAGE_MAP(COXMultiComboBox::COXComboLBox, CListBox) //{{AFX_MSG_MAP(COXMultiComboBox::COXComboLBox) ON_MESSAGE(WM_HSCROLL,COXMultiComboBox::COXComboLBox::OnHScroll) ON_MESSAGE(WM_WINDOWPOSCHANGING,COXMultiComboBox::COXComboLBox::OnWindowPosChanging) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // COXComboLBox message handlers LRESULT COXMultiComboBox::COXComboLBox::OnHScroll(WPARAM wParam, LPARAM lParam) // --- In : wParam : Hiword contains scroll position and loward scroll code. // lParam : the handle of scrollbar control // --- Out : // --- Returns : Has to return 0 to stop from processing further // --- Effect : A WM_OX_LISTHSCROLL message is sent to Combobox Window { //Sends a message to combo box to notify it to change scroll position //If the message is not processes in Combo it will be sent to window //proc of listbox if(!m_pWndCombo->SendMessage(WM_OX_LISTHSCROLL,wParam,lParam)) return DefWindowProc(WM_HSCROLL,wParam,lParam); else // ... to stop further processing return 0l; } LRESULT COXMultiComboBox::OnListHScroll(WPARAM wParam,LPARAM /* lParam */) // --- In : wParam : Hiword contains scroll position and loward scroll code. // lParam : the handle of scrollbar control // --- Out : // --- Returns : Has to return 0 to stop from processing further // --- Effect : The scrollbar thumb positions are set { int nSBCode = (int) LOWORD(wParam); int nPos = (short int) HIWORD(wParam); CRect Rect; switch(nSBCode) { case SB_LINELEFT : // Decrease the position by one char m_ComboLBox.SetScrollPos(SB_HORZ,m_ComboLBox.GetScrollPos(SB_HORZ)-m_nLineWidth,TRUE); return 0l; case SB_LINERIGHT : // Increase the position by one char m_ComboLBox.SetScrollPos(SB_HORZ,m_ComboLBox.GetScrollPos(SB_HORZ)+m_nLineWidth,TRUE); return 0l; case SB_PAGELEFT : // Decrease the position by 2/3rds of Number of chasr per page m_ComboLBox.SetScrollPos(SB_HORZ,m_ComboLBox.GetScrollPos(SB_HORZ)-((2*m_nPageWidth)/3),TRUE); return 0l; case SB_PAGERIGHT : // Increase the position by 2/3rds of Number of chasr per page m_ComboLBox.SetScrollPos(SB_HORZ,m_ComboLBox.GetScrollPos(SB_HORZ)+((2*m_nPageWidth)/3),TRUE); return 0l; case SB_THUMBTRACK : m_ComboLBox.SetScrollPos(SB_HORZ,nPos,TRUE); return 0l; } return 0l; } //this message to handle to control the height of the window depending on //the visibility of the horizontal scrollbar when ever it is shown //Horz Scrollbar Visibility is decided by the difference between //the WindowRect and ClentRect LRESULT COXMultiComboBox::COXComboLBox::OnWindowPosChanging(WPARAM wParam, LPARAM lParam) { LPWINDOWPOS lpWP = (LPWINDOWPOS) lParam; if( (!(lpWP->flags & SWP_NOSIZE))) { int nTotalHeight = ((COXMultiComboBox*)m_pWndCombo)->GetTotalHeight(); int nBorderHeight = GetSystemMetrics(SM_CYBORDER); int nScrollbarHeight = GetSystemMetrics(SM_CYHSCROLL); CRect rcDrop; ((COXMultiComboBox*)m_pWndCombo)->GetDroppedControlRect(&rcDrop); //Check and see if listbox appears //in a random position and if so, fix it - Nish, Feb 24, 2005 int orig_cy = lpWP->cy; if(m_fHorzScrollVisible) { if(nTotalHeight+nScrollbarHeight+2*nBorderHeight<=rcDrop.Height()) lpWP->cy = nTotalHeight+nScrollbarHeight + 2*nBorderHeight; else lpWP->cy = GetMaxHeight(); } else { if(nTotalHeight + 2*nBorderHeight <= rcDrop.Height()) lpWP->cy = nTotalHeight + 2*nBorderHeight; else lpWP->cy = GetMaxHeight(); } //Fixing listbox position - Nish if(orig_cy != lpWP->cy) { CRect rCbEdit; m_pWndCombo->GetWindowRect(&rCbEdit); lpWP->y = lpWP->y + (orig_cy - lpWP->cy); if(lpWP->y > rCbEdit.bottom) { lpWP->y = rCbEdit.bottom; } else { if(lpWP->y < rCbEdit.bottom) { lpWP->y = rCbEdit.bottom; } } } } // check if the right side of the combo list box is out of screen coordinates int nScreenWidth=::GetSystemMetrics(SM_CXSCREEN); if(lpWP->x+lpWP->cx>nScreenWidth) { lpWP->x=nScreenWidth-lpWP->cx; lpWP->x=lpWP->x>0 ? lpWP->x : 0; // remove SWP_NOMOVE flag lpWP->flags&=~SWP_NOMOVE; } return DefWindowProc(WM_WINDOWPOSCHANGING,wParam,lParam); } //To on and off the scrollbars of the listbox void COXMultiComboBox::COXComboLBox::AdjustHScroll() { int nTotalHeight = ((COXMultiComboBox*)m_pWndCombo)->GetTotalHeight(); int nTotalWidth = ((COXMultiComboBox*)m_pWndCombo)->GetTotalWidth(); int nBorderWidth = GetSystemMetrics(SM_CXBORDER); int nBorderHeight = GetSystemMetrics(SM_CYBORDER); int nScrollbarWidth = GetSystemMetrics(SM_CXVSCROLL); int nScrollbarHeight = GetSystemMetrics(SM_CYHSCROLL); CRect rcDrop; ((COXMultiComboBox*)m_pWndCombo)->GetDroppedControlRect(&rcDrop); if( (nTotalHeight > rcDrop.Height()-2*nBorderHeight) || ((nTotalWidth > rcDrop.Width() - 2*nBorderWidth) && (nTotalHeight > rcDrop.Height()-nScrollbarHeight - 2*nBorderHeight)) ) m_fVertScrollVisible = TRUE; else m_fVertScrollVisible = FALSE; int nMargin=nTotalWidth-rcDrop.Width()+2*nBorderWidth; if(m_fVertScrollVisible) nMargin+=nScrollbarWidth; if( nMargin>0 ) m_fHorzScrollVisible = TRUE; else m_fHorzScrollVisible = FALSE; if(m_fHorzScrollVisible) ShowScrollBar(SB_BOTH,TRUE); else ShowScrollBar(SB_HORZ,FALSE); ShowScrollBar(SB_VERT,m_fVertScrollVisible); } // The following five message handlers are defined to replace the Itemdata which we are using // to store the COXRowData with the actual item data the user set LRESULT COXMultiComboBox::OnGetItemData(WPARAM wParam,LPARAM lParam) { LRESULT result = DefWindowProc(CB_GETITEMDATA, wParam, lParam); // Replace the value by the correct one (own data structure) if (result != CB_ERR) { COXRowData* pRowData = (COXRowData*)result; ASSERT(AfxIsValidAddress(pRowData, sizeof(COXRowData))); result = pRowData->GetItemData(); } return result; } LRESULT COXMultiComboBox::OnSetItemData(WPARAM wParam,LPARAM lParam) { LRESULT result = DefWindowProc(CB_GETITEMDATA, wParam, lParam); // Replace the value by the correct one (own data structure) if (result != CB_ERR) { COXRowData* pRowData = (COXRowData*)result; ASSERT(AfxIsValidAddress(pRowData, sizeof(COXRowData))); pRowData->SetItemData((DWORD)lParam); } return result; } LRESULT COXMultiComboBox::OnCompareItem(WPARAM wParam,LPARAM lParam) { LPCOMPAREITEMSTRUCT pCompareItemStruct = (LPCOMPAREITEMSTRUCT)lParam; COXRowData* pRowData; if(pCompareItemStruct->itemID1!=-1) { pRowData = (COXRowData*)pCompareItemStruct->itemData1; ASSERT(AfxIsValidAddress(pRowData, sizeof(COXRowData))); pCompareItemStruct->itemData1 = pRowData->GetItemData(); } if(pCompareItemStruct->itemID2!=-1) { pRowData = (COXRowData*)pCompareItemStruct->itemData2; ASSERT(AfxIsValidAddress(pRowData, sizeof(COXRowData))); pCompareItemStruct->itemData2 = pRowData->GetItemData(); } return DefWindowProc(WM_COMPAREITEM,wParam,lParam); } LRESULT COXMultiComboBox::OnMeasureItem(WPARAM wParam,LPARAM lParam) { LPMEASUREITEMSTRUCT pMeasureItemStruct = (LPMEASUREITEMSTRUCT)lParam; // ... Ignore if CBS_OWNERDRAWFIXED if( (pMeasureItemStruct->itemID!=-1) && ((GetStyle() & CBS_OWNERDRAWFIXED) != CBS_OWNERDRAWFIXED) ) { COXRowData* pRowData = (COXRowData*)(pMeasureItemStruct->itemData); ASSERT(AfxIsValidAddress(pRowData, sizeof(COXRowData))); pMeasureItemStruct->itemData = pRowData->GetItemData(); } return DefWindowProc(WM_MEASUREITEM,wParam,lParam); } LRESULT COXMultiComboBox::OnDrawItem(WPARAM wParam,LPARAM lParam) { LPDRAWITEMSTRUCT pDrawItemStruct = (LPDRAWITEMSTRUCT)lParam; if(pDrawItemStruct->itemID!=-1) { COXRowData* pRowData = (COXRowData*)pDrawItemStruct->itemData; ASSERT(AfxIsValidAddress(pRowData, sizeof(COXRowData))); pDrawItemStruct->itemData = pRowData->GetItemData(); } return DefWindowProc(WM_DRAWITEM,wParam,lParam); } //CB_ADDSTRING and CB_INSERTSTRING are handled to notify that Size is changed LRESULT COXMultiComboBox::OnAddString(WPARAM wParam,LPARAM lParam) { m_fSizeChanged = TRUE; //if(::IsWindow(m_ComboLBox.m_hWnd)) // m_ComboLBox.InvalidateRect(NULL); return DefWindowProc(CB_ADDSTRING,wParam,lParam); } LRESULT COXMultiComboBox::OnInsertString(WPARAM wParam,LPARAM lParam) { m_fSizeChanged = TRUE; // if(::IsWindow(m_ComboLBox.m_hWnd)) // m_ComboLBox.InvalidateRect(NULL); return DefWindowProc(CB_INSERTSTRING,wParam,lParam); } int COXMultiComboBox::GetTotalHeight() // --- In : // --- Out : // --- Returns : returns the sum of hieghts of all the items in the combobox // --- Effect : { int nTotalHeight =0; int nCount = GetCount(); if(!(GetStyle()&CBS_OWNERDRAWVARIABLE)) nTotalHeight = nCount * GetItemHeight(0); else for(int nIndex=0; nIndex < nCount; nIndex++) nTotalHeight+=GetItemHeight(nIndex); return nTotalHeight; } int COXMultiComboBox::GetTotalWidth() // --- In : // --- Out : // --- Returns : Returns the sum of widths of all columns // --- Effect : { int nTotalWidth =0; int nColumnCount = GetColumnCount(); for(int nColIndex=0; nColIndex < nColumnCount; nColIndex++) nTotalWidth += GetColumnWidth(nColIndex); return nTotalWidth; } BOOL COXMultiComboBox::SetRowData(int nIndex, COXRowData* pRowData) // --- In : Index whose Rowdata has to be get // pRowData : RowData to set to the item // --- Out : // --- Returns : TRUE if successfull // --- Effect : to set the RowData to an Item { if(DefWindowProc(CB_SETITEMDATA,nIndex,(DWORD)PtrToUint(pRowData))==CB_ERR) return FALSE; else return TRUE; } COXMultiComboBox::COXRowData* COXMultiComboBox::GetRowData(int nIndex) const // --- In : Index whose Rowdata has to be get // --- Out : // --- Returns : Returns row data if pointer to sucessfull, otherwise NULL. // --- Effect : { LRESULT lResult; // casting is to convert this from 'const COXMultiComboBox*' to // 'COXMultiComboBox*' lResult = ((COXMultiComboBox*)this)->DefWindowProc(CB_GETITEMDATA, nIndex, 0l); if(lResult!=CB_ERR) return (COXRowData*)lResult; else { TRACE0("In COXMultiComboBox::GetRowData : GetItemData() returned CB_ERR. Returning NULL.\n"); return NULL; } } //Private BOOL COXMultiComboBox::ChangeMasterColumn(int /* nCol */) { int nNumItems = GetCount(); CArray aPtrsRowData; aPtrsRowData.SetSize(nNumItems); int nIndex=0; for(nIndex=0; nIndex < nNumItems; nIndex++) { COXRowData* pRowData = GetRowData(nIndex); aPtrsRowData[nIndex] = pRowData; if (pRowData == NULL) TRACE0("In COXMultiComboBox::ChangeMasterColumn : GetRowData() returned NULL.\n"); } // ... To avoid deleting the Rowdata in DeleteItem m_fMasterColumnChanging = TRUE; // ... Delete all the Items ResetContent(); // ... Resets the flag m_fMasterColumnChanging = FALSE; int nRetVal; // Again add all the items. This deletion and addition is to effect // the sorting order based on current master column for(nIndex=0; nIndex < nNumItems; nIndex++) { if((nRetVal = CComboBox::AddString((aPtrsRowData[nIndex])->GetColumnString(m_nMasterColumn))) != CB_ERR) SetRowData(nRetVal,aPtrsRowData[nIndex]); else return FALSE; } return TRUE; } int COXMultiComboBox::COXComboLBox::GetMaxHeight() { CRect rcDrop; ((COXMultiComboBox*)m_pWndCombo)->GetDroppedControlRect(&rcDrop); int nScrollHeight = m_fHorzScrollVisible ? GetSystemMetrics(SM_CYHSCROLL) : 0; int nBorderHeight = GetSystemMetrics(SM_CYBORDER); int nTmpHeight = rcDrop.Height()-2*nBorderHeight-nScrollHeight; int nAbsHeight; if(((COXMultiComboBox*)m_pWndCombo)->GetStyle() & CBS_OWNERDRAWVARIABLE) { nAbsHeight = 0; int nCount = ((COXMultiComboBox*)m_pWndCombo)->GetCount(); int nItemHeight; for(int nIndex=0; nIndex < nCount; nIndex++) { nItemHeight = ((COXMultiComboBox*)m_pWndCombo)->GetItemHeight(nIndex); if(nAbsHeight + nItemHeight <= nTmpHeight) nAbsHeight += nItemHeight; else break; } } else nAbsHeight = nTmpHeight - nTmpHeight%((COXMultiComboBox*)m_pWndCombo)->GetItemHeight(0); return nAbsHeight + 2*nBorderHeight + nScrollHeight; } void COXMultiComboBox::SetFitToSize(BOOL bFitToSize) { m_bFitToSize=bFitToSize; AdjustToFitSize(); } int COXMultiComboBox::AdjustToFitSize() { int nResult; if(m_bFitToSize) { int nTotalWidth=GetTotalWidth()+2*GetSystemMetrics(SM_CXBORDER); int nTotalHeight=GetTotalHeight(); int nBorderHeight=GetSystemMetrics(SM_CYBORDER); int nScrollbarHeight=GetSystemMetrics(SM_CYHSCROLL); CRect rcDrop; GetDroppedControlRect(&rcDrop); if((nTotalHeight > rcDrop.Height()-2*nBorderHeight) || ((nTotalWidth > rcDrop.Width()) && (nTotalHeight > rcDrop.Height()-nScrollbarHeight-2*nBorderHeight))) { nTotalWidth+=::GetSystemMetrics(SM_CXVSCROLL); } int nScreenWidth=::GetSystemMetrics(SM_CXSCREEN); nTotalWidth=nTotalWidth>nScreenWidth ? nScreenWidth : nTotalWidth; nResult=SetDroppedWidth(nTotalWidth); } else nResult=SetDroppedWidth(1); if(nResult!=CB_ERR) { // ... To indicate that the scrollbars has to be re-adjusted m_fSizeChanged = TRUE; // Redraws the listbox if(::IsWindow(m_ComboLBox.m_hWnd)) { m_ComboLBox.RedrawWindow(NULL,NULL, RDW_ERASE|RDW_INVALIDATE|RDW_ERASENOW|RDW_UPDATENOW|RDW_FRAME); } } return nResult; }