功能:当你在编辑框中键入一个字符(或者一个字符串),该编辑框下方将自动弹出一个list列表,显示所有包含该字符的项。
使用方法:在你的应用程序中,包含以下两个文件;在你的UI中,假如用于搜索的那个编辑框是成员变量:CEdit m_Edit; 将CEdit替换为CTipEditBox。只需在初始化时调用
m_Edit.setString(str_vector);其中str_vector是你需要搜索的字符串的范围。在选中某一项或者回车时,你的主窗口将收到MyMsg_SEARCH_COMPLETE消息。可以响应该消息来处理搜索到得结果命令。
demo:
以下请看CTipEditBox.h:
#pragma once
// CTipEditBox
#include
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& strings); //get the lengthest strings width
//get the matched the strings, who will be showed in the list
vector match_strings(CString, vector& strings);
//create a list control
BOOL create_listCtrl();
public:
//you must call this function to initialize the edit-list control
void set_strings(vector & strings);
BOOL MsgOfComplete();
public:
afx_msg void OnEnChange();
vector 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 & 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& 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 CTipEditBox::match_strings(CString str, vector& strings)
{
vector 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 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(pNMHDR);
// TODO: Add your control notification handler code here
*pResult = 0;
}
void CMyTipListCtrl::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLVCUSTOMDRAW pNMCD = reinterpret_cast(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(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;
}