头文件:CcomboCompletion.h #if !defined(AFX_COMBOCOMPLETION_H__E1EBAD20_8F3B_48E5_9D44_87410EF665A4__INCLUDED_) #define AFX_COMBOCOMPLETION_H__E1EBAD20_8F3B_48E5_9D44_87410EF665A4__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // ComboCompletion.h : header file // #include <afxtempl.h> #define WM_SHOWDROP WM_USER + 101 / // CComboCompletion window class CComboCompletion : public CComboBox { // Construction public: CComboCompletion(); // Attributes public: BOOL m_bEnableTool; private: static CWnd m_tipWnd; // tipwnd references static int m_nRef; // used to draw tooltip text static CFont m_font; static CMap<HWND, HWND, WNDPROC, WNDPROC&> m_mapWndProc; COMBOBOXINFO m_cbi; // is mouse in area static BOOL m_bEnter; // last tooltip_show_item of list static int m_nOriSel; BOOL m_bAutoComplete; // Operations public: void CreateTooltipWnd(); void DestroyTooltipWnd(); private: // specify the windowproc for edit and list void InstallEditAndListWndProc(); static LRESULT CALLBACK HookTooltipWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK HookEditboxWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK HookListboxWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // handler for paint tooltip window static void OnHandleTooltipPaint(); // handler for mouse move on listbox static void OnHandleListboxMousemove(CListBox *pList, CPoint pt); // handler for mouse hover on editbox static void OnHandleEditboxMousehover(CWnd *pWnd); // track mouse event static BOOL OnTrackMouseEvent(HWND hWnd, DWORD dwFlags); // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CComboCompletion) public: virtual BOOL PreTranslateMessage(MSG* pMsg); protected: virtual void PreSubclassWindow(); //}}AFX_VIRTUAL // Implementation public: virtual ~CComboCompletion(); // Generated message map functions protected: //{{AFX_MSG(CComboCompletion) afx_msg void OnDropdown(); afx_msg void OnEditupdate(); afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); //}}AFX_MSG afx_msg HRESULT OnShowDropDown(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() }; / //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_COMBOCOMPLETION_H__E1EBAD20_8F3B_48E5_9D44_87410EF665A4__INCLUDED_) 源文件:CComboCompletion.cpp // ComboCompletion.cpp : implementation file // #include "stdafx.h" #include "ComboCompletion.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif / // CComboCompletion CWnd CComboCompletion::m_tipWnd; int CComboCompletion::m_nRef; CMap<HWND, HWND, WNDPROC, WNDPROC&> CComboCompletion::m_mapWndProc; CFont CComboCompletion::m_font; BOOL CComboCompletion::m_bEnter = FALSE; int CComboCompletion::m_nOriSel = LB_ERR; CComboCompletion::CComboCompletion() { m_bEnableTool = TRUE; CreateTooltipWnd(); } CComboCompletion::~CComboCompletion() { // destroy tooltip window when only one reference if (m_nRef == 1) { DestroyTooltipWnd(); } else { m_nRef--; } } BEGIN_MESSAGE_MAP(CComboCompletion, CComboBox) //{{AFX_MSG_MAP(CComboCompletion) ON_CONTROL_REFLECT(CBN_DROPDOWN, OnDropdown) ON_CONTROL_REFLECT(CBN_EDITUPDATE, OnEditupdate) ON_WM_MOUSEWHEEL() //}}AFX_MSG_MAP END_MESSAGE_MAP() / // CComboCompletion message handlers void CComboCompletion::CreateTooltipWnd() { if (!::IsWindow(m_tipWnd.m_hWnd)) { m_tipWnd.CreateEx(WS_EX_TOOLWINDOW, AfxRegisterWndClass(0), NULL, WS_POPUP, 0, 0, 0, 0, NULL, NULL, NULL); WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(m_tipWnd.m_hWnd, GWL_WNDPROC, (LONG)HookTooltipWndProc); m_mapWndProc.SetAt(m_tipWnd.m_hWnd, oldWndProc); m_nRef = 1; } else { m_nRef++;// more than one reference } } void CComboCompletion::DestroyTooltipWnd() { // must destroy window m_tipWnd.DestroyWindow(); } void CComboCompletion::InstallEditAndListWndProc() { ZeroMemory(&m_cbi, sizeof(COMBOBOXINFO)); m_cbi.cbSize = sizeof(COMBOBOXINFO); ::GetComboBoxInfo(m_hWnd, &m_cbi); if (m_cbi.hwndItem) {// specify wndproc for editbox WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(m_cbi.hwndItem, GWL_WNDPROC, (LONG)HookEditboxWndProc); m_mapWndProc.SetAt(m_cbi.hwndItem, oldWndProc); } if (m_cbi.hwndList) {// specify wndproc for listbox WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(m_cbi.hwndList, GWL_WNDPROC, (LONG)HookListboxWndProc); m_mapWndProc.SetAt(m_cbi.hwndList, oldWndProc); } } LRESULT CALLBACK CComboCompletion::HookTooltipWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_PAINT) { CComboCompletion::OnHandleTooltipPaint(); } else if (uMsg == WM_SHOWWINDOW && wParam == FALSE) { ReleaseCapture(); } else if (uMsg == WM_NCDESTROY) { WNDPROC oldWndProc; m_mapWndProc.Lookup(hWnd, oldWndProc); m_mapWndProc.RemoveKey(hWnd); return ::CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam); } // default handle other message WNDPROC oldWndProc; m_mapWndProc.Lookup(hWnd, oldWndProc); return ::CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam); } LRESULT CALLBACK CComboCompletion::HookEditboxWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CWnd *pWnd = CWnd::FromHandle(hWnd); if (uMsg == WM_MOUSEMOVE) { // note,mouse-leave msg must be send manual CPoint pt; pt.x = LOWORD(lParam); pt.y = HIWORD(lParam); CRect rect; pWnd->GetClientRect(&rect); if (!rect.PtInRect(pt)) { ::SendMessage(hWnd, WM_MOUSELEAVE, wParam, lParam); } // when mouse enter edit-box,start tracking mouse event if (!m_bEnter) { OnTrackMouseEvent(hWnd, TME_HOVER|TME_LEAVE); m_bEnter = TRUE; } } else if (uMsg == WM_MOUSEHOVER) { OnHandleEditboxMousehover(pWnd); } else if (uMsg == WM_MOUSELEAVE) { m_bEnter = FALSE; m_tipWnd.ShowWindow(SW_HIDE); } else if (uMsg == WM_NCDESTROY) { // delete item from map WNDPROC oldWndProc; m_mapWndProc.Lookup(hWnd, oldWndProc); m_mapWndProc.RemoveKey(hWnd); return ::CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam); } // default handle other message WNDPROC oldWndProc; m_mapWndProc.Lookup(hWnd, oldWndProc); return ::CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam); } LRESULT CALLBACK CComboCompletion::HookListboxWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CListBox *pList = (CListBox *)CWnd::FromHandle(hWnd); if (uMsg == WM_MOUSEMOVE) { CPoint pt; pt.x = LOWORD(lParam); pt.y = HIWORD(lParam); CRect rect; pList->GetClientRect(&rect); if (rect.PtInRect(pt)) { CComboCompletion::OnHandleListboxMousemove(pList, pt); } else { ::SendMessage(hWnd, WM_MOUSELEAVE, wParam, lParam); } // track mouse-leave msg if (m_bEnter == FALSE) { OnTrackMouseEvent(hWnd, TME_LEAVE); m_bEnter = TRUE; } } else if (uMsg == WM_MOUSELEAVE) { m_nOriSel = LB_ERR; m_bEnter = FALSE; m_tipWnd.ShowWindow(SW_HIDE); } else if (uMsg == WM_CAPTURECHANGED) {// note, because capture msg is handled by us,so it need not default handle return 1; } else if (uMsg == WM_NCDESTROY) { // delete item from map WNDPROC oldWndProc; m_mapWndProc.Lookup(hWnd, oldWndProc); m_mapWndProc.RemoveKey(hWnd); return ::CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam); } // default handle other message WNDPROC oldWndProc; m_mapWndProc.Lookup(hWnd, oldWndProc); return ::CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam); } void CComboCompletion::OnHandleTooltipPaint() { // get dc CPaintDC dc(&m_tipWnd); // specify the rect CRect rect; m_tipWnd.GetClientRect(&rect); // draws a border around the specified rectangle CBrush border(RGB(0,0,0)); dc.FrameRect(&rect, &border); // fill the rectangle CBrush fill(GetSysColor(COLOR_INFOBK)); dc.FillRect(&rect, &fill); // specify the font CFont *pOldfont = dc.SelectObject(&m_font); // draw text CString strText; m_tipWnd.GetWindowText(strText); dc.SetBkMode(TRANSPARENT); dc.DrawText(strText, &rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER|DT_NOPREFIX); // recover the default font dc.SelectObject(pOldfont); } BOOL CComboCompletion::OnTrackMouseEvent(HWND hWnd, DWORD dwFlags) { TRACKMOUSEEVENT track; ZeroMemory(&track, sizeof(TRACKMOUSEEVENT)); track.cbSize = sizeof(TRACKMOUSEEVENT); track.hwndTrack = hWnd; track.dwFlags = dwFlags; track.dwHoverTime = HOVER_DEFAULT; return _TrackMouseEvent(&track); } void CComboCompletion::OnHandleEditboxMousehover(CWnd *pWnd)// pWnd is the editbox pointer { // get rect of editbox CRect rcEdit; pWnd->GetClientRect(&rcEdit); CString strText; pWnd->GetWindowText(strText); // get tooltip dc CDC *pDc = m_tipWnd.GetDC(); CFont *pOldfont = pDc->SelectObject(&m_font); // use drawtext to calculate the actual rect of text CRect rcDraw = rcEdit; pDc->DrawText(strText, &rcDraw, DT_CALCRECT|DT_SINGLELINE|DT_CENTER|DT_VCENTER|DT_NOPREFIX); // release tootip dc pDc->SelectObject(pOldfont); ::ReleaseDC(m_tipWnd.m_hWnd, pDc->m_hDC); if (rcDraw.Width() <= rcEdit.Width()) {// if text is shorter than edit, then don't show tooltip m_tipWnd.ShowWindow(SW_HIDE); } else {// if text is longer than edit, then show tooltip rcDraw.bottom = rcEdit.bottom; rcDraw.InflateRect(2, 2);// increase the width and height of draw rect pWnd->ClientToScreen(&rcDraw); m_tipWnd.SetWindowText(strText); ::SetCapture(pWnd->m_hWnd);// combobox has capture always m_tipWnd.SetWindowPos(&CWnd::wndTopMost, rcDraw.left, rcDraw.top, rcDraw.Width(), rcDraw.Height(), SWP_NOACTIVATE|SWP_SHOWWINDOW); } } void CComboCompletion::OnHandleListboxMousemove(CListBox *pList, CPoint pt) { BOOL bOut = TRUE; int nSel = pList->ItemFromPoint(pt, bOut); if (nSel == m_nOriSel) return; if (nSel != LB_ERR && bOut == FALSE) { m_nOriSel = nSel; CString strText; pList->GetText(nSel, strText); CRect rcItem; pList->GetItemRect(nSel, &rcItem); CRect rcDraw = rcItem; CDC *pDc = m_tipWnd.GetDC(); CFont *pOldfont = pDc->SelectObject(&m_font); // re-calculate the acutal rect of text pDc->DrawText(strText, &rcDraw, DT_CALCRECT|DT_SINGLELINE|DT_CENTER|DT_VCENTER|DT_NOPREFIX); pDc->SelectObject(pOldfont); ::ReleaseDC(m_tipWnd.m_hWnd, pDc->m_hDC); if (rcDraw.Width() <= rcItem.Width()) {// if text is shorter than list width,then don't show tooltip m_tipWnd.ShowWindow(SW_HIDE); } else { rcDraw.bottom = rcItem.bottom; rcDraw.InflateRect(2, 2);// increase the width and height of draw text pList->ClientToScreen(&rcDraw); m_tipWnd.ShowWindow(SW_HIDE); if (::GetCapture() != pList->m_hWnd) ::SetCapture(pList->m_hWnd); m_tipWnd.SetWindowText(strText); // note, in msdn, it say the SetWindowPos uses client coordinate,but in fact, it use screent coordinate m_tipWnd.SetWindowPos(&CWnd::wndTopMost, rcDraw.left, rcDraw.top, rcDraw.Width(), rcDraw.Height(), SWP_NOACTIVATE|SWP_SHOWWINDOW); } } } void CComboCompletion::PreSubclassWindow() { // TODO: Add your specialized code here and/or call the base class // use the same font to ctrl CFont *pFont = GetFont(); if (m_font.m_hObject != NULL) { m_font.DeleteObject(); } LOGFONT lf; pFont->GetLogFont(&lf); m_font.CreateFontIndirect(&lf); if (m_bEnableTool == TRUE) { InstallEditAndListWndProc(); } CComboBox::PreSubclassWindow(); } BOOL CComboCompletion::PreTranslateMessage(MSG* pMsg) { // TODO: Add your specialized code here and/or call the base class if (pMsg->message == WM_CHAR) { m_bAutoComplete = TRUE; int nVirKey = pMsg->wParam; switch (nVirKey) { case VK_RETURN: { // 关闭下拉框 ShowDropDown(FALSE); CString strLine; GetWindowText(strLine); // 回车即选中高亮项 SelectString(-1, strLine); // 给父窗口发送选项改变的消息 WPARAM wParam = MAKELPARAM(GetDlgCtrlID(), CBN_SELCHANGE); GetParent()->PostMessage(WM_COMMAND, wParam, (LPARAM)m_hWnd); break; } case VK_DELETE: case VK_BACK: m_bAutoComplete = FALSE; break; default: break; } } return CComboBox::PreTranslateMessage(pMsg); } void CComboCompletion::OnDropdown() { // TODO: Add your control notification handler code here SetCursor(LoadCursor(NULL, IDC_ARROW)); } void CComboCompletion::OnEditupdate() { // TODO: Add your control notification handler code here CString strLine; GetWindowText(strLine); int iHiLightStart = strLine.GetLength(); if(strLine.GetLength() == 0) { ShowDropDown(FALSE); SetWindowText(_T("")); m_bAutoComplete = TRUE; return; } // 处理删除操作 if(!m_bAutoComplete) { m_bAutoComplete = TRUE; return; } // 开始匹配用户输入 int iSelectedRow = FindString(-1, strLine); if(iSelectedRow >= 0) { // ShowDropDown(TRUE); PostMessage(WM_SHOWDROP, 0, 0); // 匹配的选项被选中 PostMessage(CB_SETCURSEL, iSelectedRow, 0); // 给父窗口发送选项改变的消息,这样可以保证当输入完整的匹配的部门时,不用回车也触发部门改变消息 WPARAM wParam = MAKELPARAM(GetDlgCtrlID(), CBN_SELCHANGE); GetParent()->PostMessage(WM_COMMAND, wParam, (LPARAM)m_hWnd); } else { // ShowDropDown(FALSE); // SetWindowText(strLine); } // 高亮自动完成的部分 PostMessage(CB_SETEDITSEL, 0, MAKELPARAM(iHiLightStart, -1)); } HRESULT CComboCompletion::OnShowDropDown(WPARAM wParam, LPARAM lParam) { ShowDropDown(TRUE); return 0; } BOOL CComboCompletion::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { // TODO: Add your message handler code here and/or call default if (::IsWindow(m_cbi.hwndList)) { CListBox *pList = (CListBox*)CWnd::FromHandle(m_cbi.hwndList); CRect rect; pList->GetClientRect(&rect); pList->ScreenToClient(&pt); if (rect.PtInRect(pt)) { CComboCompletion::OnHandleListboxMousemove(pList, pt); } } return CComboBox::OnMouseWheel(nFlags, zDelta, pt); }