分享一个智能提示搜索控件的实现

功能:当你在编辑框中键入一个字符(或者一个字符串),该编辑框下方将自动弹出一个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;
}


 

你可能感兴趣的:(windows)