// 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);
}
本文为原创文章,转载请注明出处!