//////////////////////////////////////////////////////////////// // VCKBASE -- August 2000 // Compiles with Visual C++ 6.0, runs on Windows 98 and probably NT too. // #include "stdafx.h" #include "autocompl.h" #include "LYFZIPManage.h" ////////////////// // ctor: initialize stuff to zero // CAutoComplete::CAutoComplete() { m_bIgnoreChangeMsg=0; m_iType = 0; m_idMyControl = 0; m_iCurString = 0; } CAutoComplete::~CAutoComplete() { } ////////////////// // Install hook. Initialize control ID and type of control based on // classname // void CAutoComplete::Init(CWnd* pWnd) { CSubclassWnd::HookWindow(pWnd->GetParent()); CString sClassName; ::GetClassName(pWnd->GetSafeHwnd(), sClassName.GetBuffer(32), 32); sClassName.ReleaseBuffer(); if (sClassName=="Edit") { m_iType = Edit; } else if (sClassName=="ComboBox") { m_iType = ComboBox; } m_idMyControl = pWnd->GetDlgCtrlID(); } ////////////////// // Scan string array for strings that match text, and add the matches // to a new array. Returns number of matches. For edit controls, only // need to find the first match, so to be efficient BOOL arg tells me // that. // UINT CAutoComplete::GetMatches(LPCTSTR pszText, CStringArray& arMatches, BOOL bFirstOnly) { arMatches.RemoveAll(); int nMatch = 0; CString s=pszText; if (s.GetLength()>0) { OnFirstString(); CString sMatch; while (OnNextString(sMatch)) { if (OnMatchString(s, sMatch)) { TRACE("Add %s\n",(LPCTSTR)sMatch); arMatches.Add(sMatch); nMatch++; if (bFirstOnly) break; } } } return nMatch; } ////////////////// // This virtual function takes the string entered and a potential match // and returns TRUE if the strings match. Default implementation does a // normal prefix compare--but you could override, for example to ignore // 'www' at the start of either string. // BOOL CAutoComplete::OnMatchString(const CString& s, const CString& sMatch) { return s==sMatch.Left(s.GetLength()); } void CAutoComplete::OnFirstString() { m_iCurString=0; } BOOL CAutoComplete::OnNextString(CString& sNext) { if (m_iCurString < m_arStrings.GetSize()) { sNext = m_arStrings[m_iCurString++]; return TRUE; } sNext = (LPCTSTR)NULL; return FALSE; } ////////////////// // "hook" function traps messages sent to edit control/combobox. I am // interested in EN_CHANGE or CBN_EDITCHANGE: contents of edit control // have changed. // LRESULT CAutoComplete::WindowProc(UINT msg, WPARAM wp, LPARAM lp) { if ((msg==WM_COMMAND && LOWORD(wp)==m_idMyControl) && ((m_iType==Edit && HIWORD(wp)==EN_CHANGE) || (m_iType==ComboBox && HIWORD(wp)==CBN_EDITCHANGE))) { // since I will be changing the contents of the control, which // will trigger more EN_CHANGE messages, turn off processing // while I have control, using m_bIgnoreChangeMsg. if (!m_bIgnoreChangeMsg++) { CString s; CWnd* pWnd = CWnd::FromHandle((HWND)lp); pWnd->GetWindowText(s); OnComplete(pWnd, s); } m_bIgnoreChangeMsg--; } return CSubclassWnd::WindowProc(msg, wp, lp); } ////////////////// // This is the main function that does the completion. // void CAutoComplete::OnComplete(CWnd* pWnd, CString m_filter) { CStringArray arMatches; // strings that match // if (s.GetLength()>0 && GetMatches(s, arMatches, m_iType==Edit)>0) { // DoCompletion(pWnd, s, arMatches); // } m_filter.TrimLeft (); int ii; if (m_filter.GetLength()>0) { m_posarray.RemoveAll (); int type=GetType(m_filter); if(type==1)//µç»° { for(ii=0; iiGetSize (); ii++) { if(m_pArray->ElementAt (ii).ElementAt (12).Find (m_filter)!=-1 || \ m_pArray->ElementAt (ii).ElementAt (13).Find (m_filter)!=-1) { m_posarray.Add (ii); arMatches.Add (m_pArray->ElementAt (ii).ElementAt (1)+m_pArray->ElementAt (ii).ElementAt (2)); } } } else if(type==2)//Æ´Òô { m_filter.MakeUpper (); for(ii=0; iiGetSize (); ii++) { if(m_pArray->ElementAt (ii).ElementAt (14).Find (m_filter)!=-1 || \ m_pArray->ElementAt (ii).ElementAt (15).Find (m_filter)!=-1) { m_posarray.Add (ii); arMatches.Add (m_pArray->ElementAt (ii).ElementAt (1)+m_pArray->ElementAt (ii).ElementAt (2)); } } } else { for(ii=0; iiGetSize (); ii++) { if(m_pArray->ElementAt (ii).ElementAt (0).Find (m_filter)!=-1 ||\ m_pArray->ElementAt (ii).ElementAt (1).Find (m_filter)!=-1 || \ m_pArray->ElementAt (ii).ElementAt (2).Find (m_filter)!=-1 || \ m_pArray->ElementAt (ii).ElementAt (7).Find (m_filter)!=-1 || \ m_pArray->ElementAt (ii).ElementAt (8).Find (m_filter)!=-1 || \ m_pArray->ElementAt (ii).ElementAt (9).Find (m_filter)!=-1|| \ m_pArray->ElementAt (ii).ElementAt (10).Find (m_filter)!=-1|| \ m_pArray->ElementAt (ii).ElementAt (12).Find (m_filter)!=-1 || \ m_pArray->ElementAt (ii).ElementAt (13).Find (m_filter)!=-1 ) { m_posarray.Add (ii); arMatches.Add (m_pArray->ElementAt (ii).ElementAt (1)+m_pArray->ElementAt (ii).ElementAt (2)); } } } DoCompletion(pWnd, m_filter, arMatches); } m_sPrevious=m_filter; // remember current string } void CAutoComplete::DoCompletion(CWnd* pWnd, CString s, //m_pArray const CStringArray& arMatches) { if (m_iType==ComboBox) { // This cast is technically incorrect, but a standard MFC trick. CComboBox* pComboBox = (CComboBox*)pWnd; // update dropdown to reflect possible matches pComboBox->ResetContent(); for (int i=0; iAddString(arMatches[i]); } // user arrow cursor so user can select ::SetCursor(LoadCursor(NULL,MAKEINTRESOURCE(IDC_ARROW))); // show dropdown pComboBox->ShowDropDown(); // pComboBox->SetWindowText(IgnoreCompletion(s) ? s : arMatches[0]); pComboBox->SetWindowText(s); pComboBox->SetEditSel(s.GetLength(),-1); } else if (m_iType==Edit && !IgnoreCompletion(s)) { // This cast is technically incorrect, but a standard MFC trick. CEdit* pEdit = (CEdit*)pWnd; pEdit->SetWindowText(arMatches[0]); pEdit->SetSel(s.GetLength(),-1); } } ////////////////// // This function is used to turn off the completion feature when the // user presses Backspace to delete a character typed. In that case, // the current string (s) will match the last (previously) entered // string. If this is the case, don't do the completion. For example, // if user types 'foo' and this causes me to complete to 'foobar', with // 'bar' highlighted, I don't want to complete to foobar again if the // user presses Backspace or Delete to delete 'bar'. Instead, 'foo' // should remain--and likewise if the user keeps pressing backspace. // This is one of the only functions I have ever written where the // explanation is longer than the code itself! // BOOL CAutoComplete::IgnoreCompletion(CString s) { return s==m_sPrevious.Left(s.GetLength()); }