// gzCombo.cpp : implementation file // #include "stdafx.h" #include "gzCombo.h" #include #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CgzCombo CgzCombo::CgzCombo() { m_bAutoComplete = TRUE; plist = new CStringList[2];//2 column string list //Color related code m_clrText = RGB( 0, 0, 0 ); //get the background color so it will not //look like an editable control and more like a //chooser control m_clrBkgnd = GetSysColor(COLOR_3DFACE); m_brBkgnd.CreateSolidBrush( GetSysColor(COLOR_3DFACE) ); //font related code ::GetObject((HFONT)GetStockObject(DEFAULT_GUI_FONT),sizeof(m_lf),&m_lf); m_lf.lfWeight = FW_SEMIBOLD ;//FW_MEDIUM,FW_SEMIBOLD,FW_BOLD,FW_BLACK m_font.DeleteObject(); BOOL bCreated = m_font.CreateFontIndirect(&m_lf); ASSERT(bCreated); } CgzCombo::~CgzCombo() { delete[] plist; } BEGIN_MESSAGE_MAP(CgzCombo, CComboBox) //{{AFX_MSG_MAP(CgzCombo) ON_CONTROL_REFLECT(CBN_EDITUPDATE, OnEditupdate) ON_CONTROL_REFLECT(CBN_KILLFOCUS, OnKillfocus) ON_CONTROL_REFLECT(CBN_SETFOCUS, OnSetfocus) ON_WM_CTLCOLOR() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CgzCombo message handlers //******************************************************************** //this function and the next are basically all the autoselect feature void CgzCombo::OnEditupdate() { // if we are not to auto update the text, get outta here if (!m_bAutoComplete) return; // Get the text in the edit box CString str; GetWindowText(str); int nLength = str.GetLength(); // Currently selected range DWORD dwCurSel = GetEditSel(); WORD dStart = LOWORD(dwCurSel); WORD dEnd = HIWORD(dwCurSel); // Search for, and select in, string in the combo box that is prefixed // by the text in the edit box if (SelectString(-1, str) == CB_ERR) { SetWindowText(str); // No text selected, so restore what was there before if (dwCurSel != CB_ERR) SetEditSel(dStart, dEnd); //restore cursor postion } else // Set the text selection as the additional text that we have added if (dEnd < nLength && dwCurSel != CB_ERR) SetEditSel(dStart, dEnd); else SetEditSel(nLength, -1); } //******************************************************************* BOOL CgzCombo::PreTranslateMessage(MSG* pMsg) { // Need to check for backspace/delete. These will modify the text in // the edit box, causing the auto complete to just add back the text // the user has just tried to delete. if (pMsg->message == WM_KEYDOWN) { m_bAutoComplete = TRUE; int nVirtKey = (int) pMsg->wParam; if (nVirtKey == VK_DELETE || nVirtKey == VK_BACK) { m_bAutoComplete = FALSE; } //if it's the escape key then return the selection //to it's previous value as in vb combo box if (nVirtKey == VK_ESCAPE) { m_bAutoComplete = FALSE; SetWindowText(m_InitialValue); return 1;//nonzero so app does not get the esc key and exit } } return CComboBox::PreTranslateMessage(pMsg); } //*********************** Add a row ****************** void CgzCombo::AddRow(CString text, CString ID) { SetFont(&m_font,FALSE); //add the visible text part and use the return value //to place the id number in the list of pointers //since were using the ID number returned by the combo //it's irrelevant whether the combo is in sort mode or not int x=AddString(text); if(x==0) { plist[0].AddHead(text); plist[1].AddHead(ID); } else if(x == (GetCount()-1)) { plist[0].AddTail(text); plist[1].AddTail(ID); } else { plist[0].InsertBefore(plist[0].FindIndex(x), text); plist[1].InsertBefore(plist[1].FindIndex(x), ID); } } //***************************************************** //returns the id string of the currently selected item //or blank on error, you might want to modify this //for more robust error handling if you change the //way the user typed text is validated from what it //is here. See OnKillFocus. CString CgzCombo::GetCurrentRowID() { int x=GetCurSel(); if(x!=CB_ERR) return plist[1].GetAt(plist[1].FindIndex(x)); return ""; } //***************************************************** //return the text string of the currently selected item CString CgzCombo::GetCurrentRowText() { int x=GetCurSel(); if(x!=CB_ERR) return plist[0].GetAt(plist[0].FindIndex(x)); return ""; } //****************************************************** //clear the combo box completely //remove all strings void CgzCombo::Clear() { ResetContent(); plist[0].RemoveAll(); plist[1].RemoveAll(); } //****************************************************** //override kill focus to see if user has made a valid //selection by typing in text, if so send a //CBN_CLOSEUP message to the parent window to simulate //the user having selected a string from the drop down //list. This way you only have to handle CBN_CLOSEUP //in the parent window to see if a user has made a change //either via drop list or typing in an entry. void CgzCombo::OnKillfocus() { //if the list box is empty then there //can never be a valid selection so just return //without this user is trapped in combo //if there aren't any items in the list box if( 0==GetCount() ) return; //See what's been selected from the list box? int cursel=GetCurSel(); //if cursel==CB_ERR then were here because //the user has typed some text and then left the control //and we want to process that // Get the text in the edit box CString CurrentText; GetWindowText(CurrentText); //otherwise, were here because the user selected something in //the list box so we should exit because we don't need to process it //however, we could also be here because the user tabbed to the control and //used the down or up arrow keys to make a list box selection without //dropping down the list box, so we also check to see if the text in the //edit box is the same as it was when we first came here, if so then we just exit //this is a workaround to a problem discovered by Chris Mancini //thank you Chris. if (cursel!=CB_ERR && CurrentText==m_InitialValue) return; //check to see if users entry in edit part of combo //matches a string in listbox part of combo int x=FindStringExact(-1, CurrentText); if ( x == CB_ERR ) //yes, you saw it! I used x as a variable and I dont care who knows. I might even use y later on. { //this is how I handle an invalid entry: //go back to the combo box and reset it to //whatever it was before the user modified it SetWindowText(m_InitialValue);//initialvalue set in OnSetFocus SetFocus(); } else//its a valid entry (in the list) { //synch the current listbox selection to the //typed text. If you don't do this your combo doesn't know that you have a selection SetCurSel(x); int y=GetDlgCtrlID(); //"Simulate" a list box selection by sending CBN_CLOSEUP //to the parent window. ::SendMessage(GetParent()->m_hWnd,WM_COMMAND,MAKEWPARAM(y,CBN_CLOSEUP),LPARAM(m_hWnd)); } } //************************************* //Removes the current (selected) row void CgzCombo::RemoveCurrentRow() { //you could easily add a function to //remove a row based on an ID value or //different combobox list value but I don't need that //so it's not here int x=GetCurSel(); DeleteString(x); if(x==0) { plist[0].RemoveHead(); plist[1].RemoveHead(); } else if(x ==GetCount()) { plist[0].RemoveTail(); plist[1].RemoveTail(); } else { plist[0].RemoveAt(plist[0].FindIndex(x)); plist[1].RemoveAt(plist[1].FindIndex(x)); } } //*************************************** void CgzCombo::OnSetfocus() { // Get the original text before user makes changes // so that it can be set back to it if necessary GetWindowText(m_InitialValue); } //******************************** //Select a string in combo based on id value bool CgzCombo::Select(CString ID) { //finds row that matches ID and selects it //as the displayed item //I could not find a simpler way to do this //because stringlist objects dont appear to have // a function that returns and index of a given // item. They do return POSITION types but according // to the docs that is not to be considered an index // value in any way. //this function does not generate an On_closeup message //as the other selection functions do because I use it //for displaying an initial record from a database //and no change has been made to the actual record so I //don't want to process it that way. //assert on an empty ID string if in debug mode _ASSERTE(!ID.IsEmpty()); //handle an empty ID string in release mode if(ID.IsEmpty()) return false; int x; POSITION pos; //if the list is empty, return if(plist[1].IsEmpty()==TRUE) return false; //set x to the number of items in the list x=plist[1].GetCount(); //bail if there is nothing in the list if(x==0) return false; //iterate through the list comparing the id //string at each y index location to the passed ID value //upon a match we know that y is the index value in the //combo box that matches the ID value passed to this function //CHANGE: changed ySetTextColor( m_clrText ); // text pDC->SetBkColor( m_clrBkgnd ); // text bkgnd return m_brBkgnd; /* if (nCtlColor == CTLCOLOR_EDIT) { pDC->SetTextColor( m_clrText ); // text pDC->SetBkColor( m_clrBkgnd ); // text bkgnd return m_brBkgnd; } else if (nCtlColor == CTLCOLOR_LISTBOX) { pDC->SetTextColor( m_clrText ); // text pDC->SetBkColor( m_clrBkgnd ); // text bkgnd return m_brBkgnd; } HBRUSH hbr = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor); return hbr; */ HBRUSH hbr = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor); return hbr; }