工作需要做一个自动完成combobox,就是用户在编辑框输入的时候,下拉框能自动选择用户输入最相近的一项,同时编辑框给出自动完成功能。
途中遇到很多小麻烦,网上下了一大堆源码,总结了不少经验,不敢独享,以飨读者。
首先继承一个CCombobox的类,响应以下消息处理:
BEGIN_MESSAGE_MAP(CComboCompletion, CComboBox) //{{AFX_MSG_MAP(CComboCompletion) //ON_CONTROL_REFLECT(CBN_SELCHANGE, OnSelChange) ON_CONTROL_REFLECT(CBN_DROPDOWN, OnDropdown) ON_CONTROL_REFLECT(CBN_EDITUPDATE, OnEditUpdate) //}}AFX_MSG_MAP END_MESSAGE_MAP()
注意,不宜响应CBN_SELCHANGE,而是应该响应CBN_EDITUPDATE,前者让组合框的爸爸去解决就好了。。。
在处理消息之前,我们应该对消息进行一下判断,并不是用户的每次按键都是有意义的。
同时,有些消息如果不想传给父亲,就要中断消息链。在我的例子里,回车键会发给父窗口的编辑器导致换行,所以我这里阻止了回车消息的传递。
// 重写PreTranslateMessage函数以预处理消息 BOOL CComboCompletion::PreTranslateMessage(MSG* pMsg) { if (pMsg->message == WM_CHAR) { m_bAutoComplete = TRUE; int nVirtKey = (int) pMsg->wParam; switch(nVirtKey) { case VK_RETURN: { // 回车按下,收起下拉列表 ShowDropDown(FALSE); // 设置当前选择列 CString str; GetWindowText(str); SelectString(-1, str); // 这里直接返回,于是组合框的爸爸也无法响应回车了 return TRUE; } // 如果是删除操作,则不进行自动完成 case VK_DELETE: case VK_BACK: m_bAutoComplete = FALSE; default: break; } } return CComboBox::PreTranslateMessage(pMsg); }
接下来处理编辑事件响应。
注意PostMessage是自动选择的关键!否则下拉框虽然能滚动,但是不会高亮选择项!
原因不是很明确,不过肯定是消息同步和异步处理之间的原因。
void CComboCompletion::OnEditUpdate() { CString line; // partial line entered by user CString sMatchedText; // holds full line from list // get the text from the user input GetWindowText(line); int iHiLightStart = line.GetLength(); // if the line is empty if(line.GetLength() == 0) { // 关闭下拉框 ShowDropDown(FALSE); // empty the selection SetWindowText(_T("")); m_bAutoComplete = true; return; } // 这里处理删除操作的逻辑 if(!m_bAutoComplete) { m_bAutoComplete = true; return; } // 开始匹配用户输入 int m_iSelectedRow = FindString(-1, line); if(m_iSelectedRow >= 0) { // 打开下拉框 ShowDropDown(TRUE); // 注意这里一定要使用postmessage以保证下拉框的选项被选中! PostMessage(CB_SETCURSEL, m_iSelectedRow, 0); } // 接下来处理匹配失败 else { ShowDropDown(FALSE); SetWindowText(line); } // 最后我们要高亮自动匹配的部分,以方便用户继续编辑,同样的要用异步消息发送 PostMessage(CB_SETEDITSEL, 0, MAKELPARAM(iHiLightStart, -1)); }
基本就差不多了,你会发现一个bug,就是自动提示的时候打开下拉框鼠标不见了!
嘿嘿。。。这就是我最开始之所以响应CBN_DROPDOWN消息的原因,这里得做个补救措施:
void CComboCompletion::OnDropdown() { SetCursor(LoadCursor(NULL, IDC_ARROW)); }
现在效果出来了,貌似比较专业...下图:
如果是CComboBoxEx则麻烦一点,你需要自己实现排序,插入,搜索字符串的操作。
然而原理则是一样的。