功能:当你在编辑框中键入一个字符(或者一个字符串),该编辑框下方将自动弹出一个list列表,显示所有包含该字符的项。
使用方法:在你的应用程序中,包含以下两个文件;在你的UI中,假如用于搜索的那个编辑框是成员变量:CEdit m_Edit; 将CEdit替换为CTipEditBox。只需在初始化时调用
m_Edit.setString(str_vector);其中str_vector是你需要搜索的字符串的范围。在选中某一项或者回车时,你的主窗口将收到MyMsg_SEARCH_COMPLETE消息。可以响应该消息来处理搜索到得结果命令。
demo:
以下请看CTipEditBox.h:
#pragma once // CTipEditBox #include <vector> using std::vector; class CMyTipListCtrl : public CListCtrl { DECLARE_DYNAMIC(CMyTipListCtrl) public: CMyTipListCtrl(CWnd* p_editBox); virtual ~CMyTipListCtrl(); bool FromNoSel; bool ToNoSel; bool isEnter; protected: DECLARE_MESSAGE_MAP() CWnd* m_pEditBox; int pre_selItem; int m_selItem; static int m_counter; public: afx_msg void OnNMClick(NMHDR *pNMHDR, LRESULT *pResult); public: afx_msg void OnLvnItemchanged(NMHDR *pNMHDR, LRESULT *pResult); public: afx_msg void OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult); public: afx_msg void OnLvnItemchanging(NMHDR *pNMHDR, LRESULT *pResult); }; class CTipEditBox : public CEdit { DECLARE_DYNAMIC(CTipEditBox) public: CTipEditBox(); virtual ~CTipEditBox(); protected: DECLARE_MESSAGE_MAP() void release_listCtrl(); //release the list control if user is created at the outside of class void change_current_select(bool isDown); //select the next or previews item int get_strings_max_width(CDC* pDC, vector<CString>& strings); //get the lengthest strings width //get the matched the strings, who will be showed in the list vector<CString> match_strings(CString, vector<CString>& strings); //create a list control BOOL create_listCtrl(); public: //you must call this function to initialize the edit-list control void set_strings(vector<CString> & strings); BOOL MsgOfComplete(); public: afx_msg void OnEnChange(); vector<CString> m_tipstrings; CMyTipListCtrl * m_pTipList; public: afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); public: virtual BOOL PreTranslateMessage(MSG* pMsg); };
下面是实现文件 TipEditBox.cpp:
// TipEditBox.cpp : implementation file // #include "stdafx.h" #include "TipEditBox.h" #define MyMsg_SEARCH_COMPLETE WM_USER + 100 #define SELCOLOR RGB(0,128,255) #define UNSELCOLOR RGB(255,255,255) // CTipEditBox IMPLEMENT_DYNAMIC(CTipEditBox, CEdit) CTipEditBox::CTipEditBox() { m_pTipList = NULL; } CTipEditBox::~CTipEditBox() { if(m_pTipList != NULL) release_listCtrl(); } BEGIN_MESSAGE_MAP(CTipEditBox, CEdit) ON_CONTROL_REFLECT(EN_CHANGE, &CTipEditBox::OnEnChange) ON_WM_KEYDOWN() END_MESSAGE_MAP() void CTipEditBox::set_strings(vector<CString> & strings) { m_tipstrings.clear(); if(strings.empty()) return; for(int i = 0; i < strings.size(); i++) m_tipstrings.push_back(strings[i]); create_listCtrl(); } BOOL CTipEditBox::create_listCtrl() { m_pTipList = new CMyTipListCtrl(this); if(!m_pTipList) return FALSE; BOOL ret = m_pTipList->Create(WS_CHILD|LVS_REPORT|LVS_SINGLESEL/*|LVS_SHOWSELALWAYS*/ |LVS_ALIGNTOP|LVS_NOCOLUMNHEADER, CRect(0,0,0,0), AfxGetMainWnd(),2); m_pTipList->SetExtendedStyle(LVS_EX_FULLROWSELECT); m_pTipList->InsertColumn(0,_T("")); return ret; } void CTipEditBox::release_listCtrl() { if(m_pTipList) delete m_pTipList; m_pTipList = NULL; } void CTipEditBox::change_current_select(bool isDown) { //get the next item need to select int selIdx = m_pTipList->GetNextItem(-1,LVNI_ALL|LVNI_SELECTED); int count = m_pTipList->GetItemCount(); int idxNext; if(isDown) //down idxNext = (selIdx + 1) % count; else //up { if(selIdx == 0 || selIdx == -1) idxNext = count - 1; else idxNext = selIdx - 1; } //set the item selected and set window text as this item text /*CString result = m_pTipList->GetItemText(idxNext,0); this->SetWindowText(result);*/ if(selIdx != idxNext){ m_pTipList->SetItemState(idxNext,LVIS_SELECTED,LVIS_SELECTED); //enable the scroll bar m_pTipList->EnsureVisible(idxNext,FALSE); m_pTipList->RedrawItems(idxNext,idxNext); } } int CTipEditBox::get_strings_max_width(CDC* pDC, vector<CString>& strings) { int max_length = 0; for(int i = 0; i < strings.size(); i++) { CSize size = pDC->GetTextExtent(strings[i],strings[i].GetLength()); if(max_length < size.cx) max_length = size.cx; } return max_length; } vector<CString> CTipEditBox::match_strings(CString str, vector<CString>& strings) { vector<CString> result; for(int i = 0; i < strings.size(); i++) { if(strings[i].Find(str) != -1) result.push_back(strings[i]); } return result; } BOOL CTipEditBox::MsgOfComplete() { CWnd * pWnd = this->GetParent(); if(pWnd == NULL || pWnd->m_hWnd == NULL) pWnd = ::AfxGetMainWnd(); if(pWnd == NULL || pWnd->m_hWnd == NULL) return FALSE; return pWnd->PostMessage(MyMsg_SEARCH_COMPLETE,0,0); } // CTipEditBox message handlers void CTipEditBox::OnEnChange() { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CEdit::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. // TODO: Add your control notification handler code here //get editor box's string CString editStr; this->GetWindowText(editStr); //using editStr to match the result vector<CString> search_result = match_strings(editStr,m_tipstrings); if(search_result.empty()){ if(m_pTipList && m_pTipList->IsWindowVisible()) m_pTipList->ShowWindow(SW_HIDE); return; } //get the list control's rect if(m_pTipList && m_pTipList->IsWindowVisible()) { m_pTipList->ToNoSel = TRUE; m_pTipList->ShowWindow(SW_HIDE); int selIdx = m_pTipList->GetNextItem(-1,LVNI_ALL|LVNI_SELECTED); if(selIdx >= 0) m_pTipList->SetItemState(selIdx,0,LVNI_SELECTED); } //prepare CDC *pDC = m_pTipList->GetDC(); int max_w = get_strings_max_width(pDC,search_result); m_pTipList->ReleaseDC(pDC); CWnd* pWnd = AfxGetMainWnd(); CRect rc,rc1,rc2; this->GetWindowRect(rc); pWnd->ScreenToClient(rc); pWnd->GetWindowRect(rc2); int list_h, list_w,min_w; bool hasSBar = FALSE; //calculate width of the list control min_w = rc.Width(); if(max_w < min_w) max_w = min_w; list_w = max_w < (rc2.Width() - rc.left) ? max_w : (rc2.Width() - rc.left); //calculate height of the list control #define ITEMHEIGHT 22 #define MAXCOUNT 6 int count = search_result.size(); if(count > MAXCOUNT){ hasSBar = TRUE; count = MAXCOUNT; } list_h = (rc2.Height() - rc.bottom) < count * ITEMHEIGHT ? (rc2.Height() - rc.bottom) : count * ITEMHEIGHT; #undef ITEMHEIGHT #undef MAXCOUNT rc1.left = rc.left; rc1.top = rc.bottom; rc1.right = rc1.left + list_w; rc1.bottom = rc1.top + list_h; //if editStr is not empty,display the list, and insert the items matched if(!editStr.IsEmpty()){ m_pTipList->MoveWindow(rc1,TRUE); m_pTipList->DeleteAllItems(); int Bar_w = hasSBar ? 18 : 0; m_pTipList->SetColumnWidth(0,rc1.Width()-Bar_w); for(int i = 0; i < search_result.size(); i++) m_pTipList->InsertItem(i,search_result[i]); m_pTipList->FromNoSel = TRUE; m_pTipList->ShowWindow(SW_SHOW); m_pTipList->BringWindowToTop();
} else{ m_pTipList->ToNoSel = TRUE; m_pTipList->ShowWindow(SW_HIDE); int selIdx = m_pTipList->GetNextItem(-1,LVNI_ALL|LVNI_SELECTED); if(selIdx >= 0) m_pTipList->SetItemState(selIdx,0,LVNI_SELECTED); } } void CTipEditBox::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default CString text; this->GetWindowText(text); if(!text.IsEmpty() && m_pTipList && m_pTipList->IsWindowVisible() && (!m_tipstrings.empty())) { switch(nChar) { case VK_DOWN: change_current_select(true); return; case VK_UP: change_current_select(false); return; default: break; } } CEdit::OnKeyDown(nChar, nRepCnt, nFlags); } BOOL CTipEditBox::PreTranslateMessage(MSG* pMsg) { // TODO: Add your specialized code here and/or call the base class if(pMsg->message == WM_KEYDOWN) { switch(pMsg->wParam) { case VK_RETURN: if(m_pTipList && m_pTipList->IsWindowVisible()){ int idx = m_pTipList->GetNextItem(-1,LVNI_ALL|LVNI_SELECTED); CString str; if(idx >= 0){ str = m_pTipList->GetItemText(idx,0); m_pTipList->isEnter = TRUE; m_pTipList->ToNoSel = TRUE; m_pTipList->SetItemState(idx,0,LVNI_SELECTED); } m_pTipList->ShowWindow(SW_HIDE); this->SetWindowText(str); this->SetSel(str.GetLength(),str.GetLength()); } this->MsgOfComplete(); return TRUE; case VK_ESCAPE: if(m_pTipList && m_pTipList->IsWindowVisible()){ int idx = m_pTipList->GetNextItem(-1,LVNI_ALL|LVNI_SELECTED); if(idx >= 0){ m_pTipList->isEnter = TRUE; m_pTipList->ToNoSel = TRUE; m_pTipList->SetItemState(idx,0,LVNI_SELECTED); } m_pTipList->ShowWindow(SW_HIDE); } return TRUE; default: break; } } return CEdit::PreTranslateMessage(pMsg); } // CMyTipListCtrl IMPLEMENT_DYNAMIC(CMyTipListCtrl, CListCtrl) int CMyTipListCtrl::m_counter = 0; CMyTipListCtrl::CMyTipListCtrl(CWnd* p_editBox) { m_pEditBox = p_editBox; pre_selItem = -1; m_selItem = -1; FromNoSel = FALSE; ToNoSel = FALSE; isEnter = FALSE; } CMyTipListCtrl::~CMyTipListCtrl() { } BEGIN_MESSAGE_MAP(CMyTipListCtrl, CListCtrl) ON_NOTIFY_REFLECT(NM_CLICK, &CMyTipListCtrl::OnNMClick) ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, &CMyTipListCtrl::OnLvnItemchanged) ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CMyTipListCtrl::OnNMCustomdraw) ON_NOTIFY_REFLECT(LVN_ITEMCHANGING, &CMyTipListCtrl::OnLvnItemchanging) END_MESSAGE_MAP() // CMyTipListCtrl message handlers void CMyTipListCtrl::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult) { // TODO: Add your control notification handler code here NM_LISTVIEW * pNMList = (NM_LISTVIEW*) pNMHDR; CString str = this->GetItemText(pNMList->iItem,0); isEnter = TRUE; ToNoSel = TRUE; SetItemState(pNMList->iItem,0,LVNI_SELECTED); m_pEditBox->SetWindowText(str); this->ShowWindow(SW_HIDE); ((CTipEditBox*)m_pEditBox)->MsgOfComplete(); *pResult = 0; } void CMyTipListCtrl::OnLvnItemchanged(NMHDR *pNMHDR, LRESULT *pResult) { LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR); // TODO: Add your control notification handler code here *pResult = 0; } void CMyTipListCtrl::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult) { LPNMLVCUSTOMDRAW pNMCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR); *pResult = 0; // TODO: Add your control notification handler code here if(CDDS_PREPAINT == pNMCD->nmcd.dwDrawStage) *pResult = CDRF_NOTIFYITEMDRAW; else if(CDDS_ITEMPREPAINT == pNMCD->nmcd.dwDrawStage) { if(pre_selItem == pNMCD->nmcd.dwItemSpec) { pNMCD->clrTextBk = UNSELCOLOR; } else if(m_selItem == pNMCD->nmcd.dwItemSpec) pNMCD->clrTextBk = SELCOLOR; *pResult = 0; } m_counter = 0; } void CMyTipListCtrl::OnLvnItemchanging(NMHDR *pNMHDR, LRESULT *pResult) { LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR); // TODO: Add your control notification handler code here m_counter++; if(FromNoSel && !isEnter){ pre_selItem = -1; m_selItem = pNMLV->iItem; FromNoSel = FALSE; } else if(ToNoSel) { pre_selItem = pNMLV->iItem; m_selItem = -1; ToNoSel = FALSE; isEnter = FALSE; } else { if(m_counter == 1) pre_selItem = pNMLV->iItem; else if(m_counter == 2){ m_selItem = pNMLV->iItem; this->RedrawItems(pre_selItem,pNMLV->iItem); } } *pResult = 0; }