491 lines
12 KiB
C++
491 lines
12 KiB
C++
// gzCombo.cpp : implementation file
|
|
//
|
|
|
|
#include "stdafx.h"
|
|
#include "gzCombo.h"
|
|
#include <crtdbg.h>
|
|
#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 y<x+1 to y<x 042500 2pm
|
|
for(int y=0;y<x;y++)
|
|
{
|
|
|
|
//will assert if invalid id attempted
|
|
|
|
pos=plist[1].FindIndex(y);
|
|
|
|
_ASSERTE(pos!=NULL);
|
|
|
|
if(pos==NULL)
|
|
return false;
|
|
|
|
if(ID==plist[1].GetAt(pos))
|
|
{
|
|
SetCurSel(y);//found a match, set the combo to it
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
//default to top of list
|
|
//Added 05/26/00 3.34 pm
|
|
SetCurSel(0);
|
|
return false;
|
|
|
|
}
|
|
|
|
//********************************
|
|
//Select a string in combo based on LONG id value
|
|
bool CgzCombo::Select(long lID)
|
|
{
|
|
//Long version of CString version above for convenience
|
|
//because 90% of the time I'm passing a long
|
|
//record ID value
|
|
CString ID;
|
|
ID.Format("%d",lID);
|
|
|
|
//assert on an empty ID string if in debug mode
|
|
ASSERT(!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 y<x+1 to y<x 042500 2pm
|
|
for(int y=0;y<x;y++)
|
|
{
|
|
|
|
//will assert if invalid id attempted
|
|
|
|
pos=plist[1].FindIndex(y);
|
|
|
|
_ASSERTE(pos!=NULL);
|
|
|
|
if(pos==NULL)
|
|
return false;
|
|
|
|
if(ID==plist[1].GetAt(pos))
|
|
{
|
|
SetCurSel(y);//found a match, set the combo to it
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
//default to top of list
|
|
//Added 05/26/00 3.34 pm
|
|
SetCurSel(0);
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
HBRUSH CgzCombo::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
|
|
{
|
|
|
|
pDC->SetTextColor( 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;
|
|
}
|
|
|
|
|
|
|
|
|