自动完成是个很酷也很实用的功能,比如在浏览器地址栏输入几个字母,相关的记录就会在下拉框中陈列出来。
最近在做公司产品UI部分的改善,原版本是MFC做的,我决定用WTL,于是就遇到自动完成控件的问题。遍寻Internet,WTL版的只找到一个用IEnumString+IE组件实现的,但是其个性化修改比较困难。so我决定自己用WTL改写该控件,在此向原作者 Andreas Kapust 致谢!
该控件包含四个文件:ACEdit.h, ACEdit.cpp, ACListWnd.h 和 ACListWnd.cpp。使用时用CACEdit 声明一个变量并将其与Edit控件或ComboBox控件建立关联(用DDX_CONTROL或者SubClassWindow)不可使用Attach,因为窗口过程函数必须要被替换。具体使用见代码。
因为我本人也是个VC菜鸟,望有同学发现错误或不足之处能不吝指点。感谢!另外在WTL动态创建的窗口销毁上我还是不太明了,请大虾赐教!
此为 _MODE_FIND_ALL_ 模式的效果
此为 _MODE_FILESYSTEM_ 模式的效果
原来不能发附件?贴下代码算了,要demo工程的邮件我 mforestlaw@163.com
ACEdit.h
/************************************************************************* Author : Andreas Kapust WTL Edition : Forest.Law Contact me at : mforestlaw@163.com **************************************************************************/ #pragma once #include "ACListWnd.h" class CACEdit : public CWindowImpl<CACEdit, CEdit>, public CWinDataExchange<CACEdit>, public CMessageFilter { public: CACEdit(); virtual ~CACEdit(); virtual BOOL PreTranslateMessage(MSG* pMsg); void SetMode(int nMode = _MODE_STANDARD_); void SetSeparator(LPCTSTR lpszString, TCHAR lpszPrefixChar = 0); int AddString(LPCTSTR lpszString); int GetLBText(int nIndex, LPTSTR lpszText); void GetLBText(int nIndex, CString& rString); int SetDroppedWidth(UINT nWidth); int FindString(int nStartAfter, LPCTSTR lpszString); int SelectString(int nStartAfter, LPCTSTR lpszString); void ShowDropDown(BOOL bShow = TRUE); void ResetContent(); int GetCurSel(); void Init(); void AddSearchString(LPCTSTR lpszString); void AddSearchStrings(LPCTSTR lpszStrings[]); void RemoveSearchAll(); void SetStartDirectory(LPCTSTR lpszString); void SetFontHeight(long lHeight); void SetClearKillFocus(); CACListWnd m_Liste; protected: BEGIN_MSG_MAP_EX(CACEdit) REFLECTED_COMMAND_CODE_HANDLER(EN_KILLFOCUS, OnKillFocus) REFLECTED_COMMAND_CODE_HANDLER(CBN_KILLFOCUS, OnKillFocus) MSG_WM_KEYDOWN(OnKeyDown) REFLECTED_COMMAND_CODE_HANDLER(EN_CHANGE, OnChange) REFLECTED_COMMAND_CODE_HANDLER(CBN_EDITCHANGE, OnChange) REFLECTED_COMMAND_CODE_HANDLER(CBN_DROPDOWN, OnCloseList) MSG_WM_ERASEBKGND(OnEraseBkgnd) MESSAGE_HANDLER(ENAC_UPDATE, OnUpdateFromList) MSG_WM_DESTROY(OnDestroy) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() LRESULT OnKillFocus(UINT wNotifyCode, UINT wID, HWND hWndCtl, BOOL& bHandled); void OnKeyDown(TCHAR vkey, UINT repeats, UINT code); LRESULT OnChange(UINT wNotifyCode, UINT wID, HWND hWndCtl, BOOL& bHandled); LRESULT OnCloseList(UINT wNotifyCode, UINT wID, HWND hWndCtl, BOOL& bHandled); LRESULT OnEraseBkgnd(HDC hDC); LRESULT OnUpdateFromList(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); void OnDestroy(); void ReadDirectory(CString strDir); int FindSepLeftPos(int nPos, bool bFindSepLeftPos = false); int FindSepLeftPos2(int nPos); int FindSepRightPos(int nPos); bool HandleKey(UINT nChar, bool bFromChild); CString m_strEditText; CString m_strSeparator; CString m_strLastDirectory; TCHAR m_prefixChar; int m_nMode; bool m_bCursorMode; int m_nType; CEdit* m_pEdit; TCHAR m_szDrive[_MAX_DRIVE]; TCHAR m_szDir[_MAX_DIR]; TCHAR m_szFname[_MAX_FNAME]; TCHAR m_szExt[_MAX_EXT]; CBitmap m_backBmp; BOOL m_bClearTextKillFocus; };
ACEdit.cpp
#include "stdafx.h" #include "ACEdit.h" #include <io.h> #define _EDIT_ 1 #define _COMBOBOX_ 2 CACEdit::CACEdit() { m_nMode = _MODE_STANDARD_; m_nType = -1; m_pEdit = NULL; m_bCursorMode = false; m_prefixChar = 0; m_bClearTextKillFocus = FALSE; } CACEdit::~CACEdit() { } BOOL CACEdit::PreTranslateMessage(MSG* pMsg) { if(pMsg->message == WM_KEYDOWN) { if(m_Liste.IsWindowVisible()) { if(m_nType == _COMBOBOX_) { if(pMsg->wParam == VK_DOWN || pMsg->wParam == VK_UP) { if(HandleKey(pMsg->wParam, false)) { return TRUE; } } } if(pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_RETURN) { if(HandleKey(pMsg->wParam, false)) { return TRUE; } } } } return FALSE; } void CACEdit::SetMode(int nMode) { if(m_nType == -1) { Init(); } m_nMode = nMode; if(nMode == _MODE_CURSOR_O_LIST_) { m_nMode |= _MODE_STANDARD_; } if(nMode & _MODE_FILESYSTEM_) { m_strSeparator = _T("\"); } if(nMode & _MODE_FIND_ALL_) { m_Liste.m_lMode |= _MODE_FIND_ALL_; SetSeparator(_T(" ")); } } void CACEdit::SetSeparator(LPCTSTR lpszString,TCHAR lpszPrefixChar) { m_strSeparator = lpszString; m_Liste.m_prefixChar = m_prefixChar = lpszPrefixChar; SetMode(_MODE_SEPARATION_); } int CACEdit::AddString(LPCTSTR lpszString) { if(m_nType == _COMBOBOX_) { return ((CComboBox*)this)->AddString(lpszString); } return CB_ERR; } int CACEdit::GetLBText(int nIndex, LPTSTR lpszText) { if(m_nType == _COMBOBOX_) { return ((CComboBox*)this)->GetLBText(nIndex, lpszText); } return CB_ERR; } void CACEdit::GetLBText(int nIndex, CString& rString) { if(m_nType == _COMBOBOX_) { ((CComboBox*)this)->GetLBText(nIndex, rString); } } int CACEdit::SetDroppedWidth(UINT nWidth) { if(m_nType == _COMBOBOX_) { return ((CComboBox*)this)->SetDroppedWidth(nWidth); } return CB_ERR; } int CACEdit::FindString(int nStartAfter, LPCTSTR lpszString) { if(m_nType == _COMBOBOX_) { return ((CComboBox*)this)->FindString(nStartAfter, lpszString); } return CB_ERR; } int CACEdit::SelectString(int nStartAfter, LPCTSTR lpszString) { if(m_nType == _COMBOBOX_) { return ((CComboBox*)this)->SelectString(nStartAfter, lpszString); } return CB_ERR; } void CACEdit::ShowDropDown(BOOL bShow) { if(m_nType == _COMBOBOX_) { ((CComboBox*)this)->ShowDropDown(bShow); } } void CACEdit::ResetContent() { if(m_nType == _COMBOBOX_) { ((CComboBox*)this)->ResetContent(); } } int CACEdit::GetCurSel() { if(m_nType == _COMBOBOX_) { return ((CComboBox*)this)->GetCurSel(); } return CB_ERR; } void CACEdit::Init() { // m_backBmp.LoadBitmap(IDB_QUREY); CMessageLoop* pLoop = _Module.GetMessageLoop(); ATLASSERT(pLoop != NULL); pLoop->AddMessageFilter(this); WNDCLASS wndcls; wndcls.style = CS_CLASSDC | CS_SAVEBITS | CS_HREDRAW | CS_VREDRAW; wndcls.lpfnWndProc = ::DefWindowProc; wndcls.cbClsExtra = wndcls.cbWndExtra = 0; wndcls.hInstance = _Module.GetModuleInstance(); wndcls.hIcon = NULL; wndcls.hCursor = LoadCursor(NULL, IDC_ARROW); wndcls.hbrBackground = (HBRUSH)COLOR_WINDOW; wndcls.lpszMenuName = NULL; wndcls.lpszClassName = _T("ACListWnd"); RegisterClass(&wndcls); CRect rcWnd; CRect rcWnd1; GetWindowRect(rcWnd); HWND hListe = CreateWindowEx(WS_EX_TOOLWINDOW, _T("ACListWnd"), NULL, WS_CHILD | WS_BORDER | WS_CLIPSIBLINGS | WS_OVERLAPPED, rcWnd.left, rcWnd.top + rcWnd.Height(), rcWnd.Width(), rcWnd.Height(), GetDesktopWindow(), NULL, _Module.GetModuleInstance(), NULL); ::SetWindowPos(hListe, HWND_TOPMOST, rcWnd.left, rcWnd.top + rcWnd.Height(), rcWnd.Width(), rcWnd.Height(), NULL); m_Liste.SubclassWindow(hListe); CString strClassName; ::GetClassName(this->m_hWnd, strClassName.GetBuffer(32), 32); strClassName.ReleaseBuffer(); if (strClassName.Compare(_T("Edit")) == 0) { m_nType = _EDIT_; } else { if (strClassName.Compare(_T("ComboBox")) == 0) { m_nType = _COMBOBOX_; m_pEdit = (CEdit*)&GetWindow(GW_CHILD); ATLASSERT(m_pEdit); ::GetClassName(m_pEdit->m_hWnd, strClassName.GetBuffer(32), 32); strClassName.ReleaseBuffer(); ATLASSERT(strClassName.Compare(_T("Edit")) == 0); } } if(m_nType == -1) { ATLASSERT(0); return; } m_Liste.Init(this); } void CACEdit::AddSearchString(LPCTSTR lpszString) { if(m_nType == -1) { ATLASSERT(0); return; } m_Liste.AddSearchString(lpszString); } void CACEdit::AddSearchStrings(LPCTSTR lpszStrings[]) { int i = 0; LPCTSTR str = NULL; if(m_nType == -1) { ATLASSERT(0); return; } m_Liste.RemoveAll(); do { str = lpszStrings[i]; if(str) { m_Liste.AddSearchString(str); } i++; } while(str); m_Liste.SortSearchList(); } void CACEdit::RemoveSearchAll() { if(m_nType == -1) { ATLASSERT(0); return; } m_Liste.RemoveAll(); } void CACEdit::SetStartDirectory(LPCTSTR lpszString) { if(m_nType == -1) { ATLASSERT(0); return; } if(m_nMode & _MODE_FS_START_DIR_) { ReadDirectory(lpszString); } } void CACEdit::SetFontHeight(long lHeight) { m_Liste.SetFontHeight(lHeight); } void CACEdit::SetClearKillFocus() { m_bClearTextKillFocus = TRUE; } LRESULT CACEdit::OnKillFocus(UINT wNotifyCode, UINT wID, HWND hWndCtl, BOOL& bHandled) { if(m_Liste.m_hWnd) { m_Liste.ShowWindow(false); } if (m_bClearTextKillFocus) { SetWindowText(_T("")); Invalidate(); } return 0; } void CACEdit::OnKeyDown(TCHAR vkey, UINT repeats, UINT code) { if(!HandleKey(vkey, false)) { DefWindowProc(); } } LRESULT CACEdit::OnChange(UINT wNotifyCode, UINT wID, HWND hWndCtl, BOOL& bHandled) { CString strText; int nPos=0; if(m_nType == -1) { ATLASSERT(0); return 0; } GetWindowText(m_strEditText); int nLen = m_strEditText.GetLength(); if(m_nMode & _MODE_FILESYSTEM_ || m_nMode & _MODE_FS_START_DIR_) { if(!m_bCursorMode) { CPoint point; if(m_nType == _EDIT_) { GetCaretPos(&point); nPos = LOWORD(((CEdit*)this)->CharFromPos(point)); } if(m_nType == _COMBOBOX_) { GetCaretPos(&point); nPos = m_pEdit->CharFromPos(point); } if(m_nMode & _MODE_FS_START_DIR_) { if(nLen) { m_Liste.FindString(-1, m_strEditText); } else { m_Liste.ShowWindow(false); } } else { if(nLen > 2 && nPos == nLen) { if(_taccess(m_strEditText, 0) == 0) { ReadDirectory(m_strEditText); } m_Liste.FindString(-1, m_strEditText); } else { m_Liste.ShowWindow(false); } } } } if(m_nMode & _MODE_SEPARATION_) { if(!m_bCursorMode) { CPoint point; if(m_nType == _EDIT_) { GetCaretPos(&point); nPos = LOWORD(((CEdit*)this)->CharFromPos(point)); } if(m_nType == _COMBOBOX_) { GetCaretPos(&point); nPos = m_pEdit->CharFromPos(point); } int nLeft = FindSepLeftPos(nPos - 1); int nRight = FindSepRightPos(nPos); strText = m_strEditText.Mid(nLeft, nRight - nLeft); m_Liste.FindString(-1, strText); } } if(m_nMode & _MODE_STANDARD_) { if(!m_bCursorMode) { m_Liste.FindString(-1, m_strEditText); } } SendMessage(ENAC_UPDATE, EN_UPDATE, GetDlgCtrlID()); return 0; } LRESULT CACEdit::OnCloseList(UINT wNotifyCode, UINT wID, HWND hWndCtl, BOOL& bHandled) { m_Liste.ShowWindow(false); return 0; } LRESULT CACEdit::OnEraseBkgnd(HDC hDC) { CDC dc(hDC); RECT updatarect; GetClientRect(&updatarect); CBrush BackBrush = CreatePatternBrush(m_backBmp); CBrush oldBrush = dc.SelectBrush(BackBrush); CPen penBlack = CreatePen(PS_NULL, 1, RGB(0, 0, 0)); CPen oldPen = dc.SelectPen(penBlack); dc.Rectangle(&updatarect); dc.SelectBrush(oldBrush); dc.SelectPen(oldPen); return TRUE; } LRESULT CACEdit::OnUpdateFromList(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { if(lParam == WM_KEYDOWN) { HandleKey(VK_DOWN, true); } return 0; } void CACEdit::OnDestroy() { m_Liste.SendMessage(WM_CLOSE); } void CACEdit::ReadDirectory(CString strDir) { if(strDir.Right(1) != _T('\')) { _tsplitpath_s(strDir, m_szDrive, m_szDir, m_szFname, m_szExt); strDir.Format(_T("%s%s"), m_szDrive, m_szDir); } TCHAR ch = (TCHAR)towupper(strDir.GetAt(0)); strDir.SetAt(0, ch); CString m_Name, m_File, strDir1 = strDir; if(strDir.Right(1) != _T('\')) { strDir += _T("\"); } if(m_strLastDirectory.CompareNoCase(strDir) == 0 && m_Liste.m_searchList.GetSize()) { return; } m_strLastDirectory = strDir; strDir += _T("*.*"); WIN32_FIND_DATA findData; HANDLE findHandle = FindFirstFile(strDir, &findData); if(INVALID_HANDLE_VALUE == findHandle) { return; } while(FindNextFile(findHandle, &findData)) { m_File = findData.cFileName; if(findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN || findData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) { continue; } if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if(_MODE_ONLY_FILES & m_nMode) { continue; } if(m_File.CompareNoCase(_T(".")) == 0 || m_File.CompareNoCase(_T("..")) == 0) { continue; } if(m_File.Right(1) != _T('\')) { m_File += _T("\"); } } else { if(_MODE_ONLY_DIRS & m_nMode) { continue; } } if(m_nMode & _MODE_FS_START_DIR_) { m_Name = m_File; } else { m_Name = strDir1; if(m_Name.Right(1) != _T('\')) { m_Name += _T("\"); } m_Name += m_File; } AddSearchString(m_Name); } FindClose(findHandle); } int CACEdit::FindSepLeftPos(int nPos, bool bFindSepLeftPos) { int nLen = m_strEditText.GetLength(); TCHAR ch; if(nPos >= nLen && nLen != 1) { nPos = nLen -1; } int i = nPos; for(; i>=0; i--) { ch = m_strEditText.GetAt(i); if(m_prefixChar == ch) { return i + (bFindSepLeftPos ? 1 : 0); } if(m_strSeparator.Find(ch) != -1) { break; } } return i + 1; } int CACEdit::FindSepLeftPos2(int nPos) { int nLen = m_strEditText.GetLength(); TCHAR ch; if(nPos >= nLen && nLen != 1) { nPos = nLen - 1; } if(nLen == 1) { return 0; } for(int i=nPos; i>=0; i--) { ch = m_strEditText.GetAt(i); if(m_prefixChar == ch) { return 1; } } return 0; } int CACEdit::FindSepRightPos(int nPos) { int nLen = m_strEditText.GetLength(); TCHAR ch; int i = nPos; for(; i<nLen; i++) { ch = m_strEditText.GetAt(i); if(m_strSeparator.Find(ch) != -1) { break; } } return i; } bool CACEdit::HandleKey(UINT nChar, bool bFromChild) { if (nChar == VK_ESCAPE || nChar == VK_RETURN) { m_Liste.ShowWindow(false); return true; } if (nChar == VK_DOWN || nChar == VK_UP || nChar == VK_PRIOR || nChar == VK_NEXT || nChar == VK_HOME || nChar == VK_END) { if(!m_Liste.IsWindowVisible() && (m_nMode & _MODE_CURSOR_O_LIST_)) { GetWindowText(m_strEditText); if(m_strEditText.IsEmpty()) { m_Liste.CopyList(); return true; } } if(m_Liste.IsWindowVisible()) { int nPos; if(m_nMode & _MODE_STANDARD_ || m_nMode & _MODE_FILESYSTEM_ || m_nMode & _MODE_FS_START_DIR_) { m_bCursorMode = true; if(!bFromChild) { m_strEditText = m_Liste.GetNextString(nChar); } else { m_strEditText = m_Liste.GetString(); } if(m_nMode & _MODE_FILESYSTEM_) { if(m_strEditText.Right(1) == _T('\')) { m_strEditText = m_strEditText.Mid(0, m_strEditText.GetLength() - 1); } } m_Liste.SelectItem(-1); SetWindowText(m_strEditText); nPos = m_strEditText.GetLength(); if(m_nType == _COMBOBOX_) { m_pEdit->SetSel(nPos, nPos, true); m_pEdit->SetModify(true); } if(m_nType == _EDIT_) { this->SetSel(0, nPos, true); this->SetModify(true); } SendMessage(ENAC_UPDATE, WM_KEYDOWN, GetDlgCtrlID()); m_bCursorMode = false; return true; } if(m_nMode & _MODE_SEPARATION_) { CString strText; CString strLeft; CString strRight; int nLeft; int nRight; int nPos = 0; int nLen; m_bCursorMode = true; GetWindowText(m_strEditText); CPoint point; if(m_nType == _EDIT_) { GetCaretPos(&point); nPos = LOWORD(((CEdit*)this)->CharFromPos(point)); } if(m_nType == _COMBOBOX_) { GetCaretPos(&point); nPos = m_pEdit->CharFromPos(point); } nLeft = FindSepLeftPos(nPos - 1, true); nRight = FindSepRightPos(nPos); strText = m_strEditText.Left(nLeft); if(!bFromChild) { strText += m_Liste.GetNextString(nChar); } else { strText += m_Liste.GetString(); } m_Liste.SelectItem(-1); strText += m_strEditText.Mid(nRight); nLen = m_Liste.GetString().GetLength(); SetWindowText(strText); SendMessage(ENAC_UPDATE, WM_KEYDOWN, GetDlgCtrlID()); nRight = FindSepLeftPos2(nPos - 1); nLeft -= nRight; nLen += nRight; if(m_nType == _EDIT_) { ((CEdit*)this)->SetModify(true); ((CEdit*)this)->SetSel(nLeft, nLeft + nLen, false); } if(m_nType == _COMBOBOX_) { m_pEdit->SetModify(true); m_pEdit->SetSel(nLeft, nLeft + nLen, true); } m_bCursorMode = false; return true; } } } return false; }
ACListWnd.h
#pragma once #define ENAC_UPDATE WM_USER + 1200 #define IDTimerInstall 10 #define _MAX_ENTRYS_ 5 #define _MODE_ONLY_FILES (1L << 16) #define _MODE_ONLY_DIRS (1L << 17) #define _MODE_STANDARD_ (1L << 0) #define _MODE_SEPARATION_ (1L << 1) #define _MODE_FILESYSTEM_ (1L << 2) #define _MODE_FS_START_DIR_ (1L << 3) #define _MODE_CURSOR_O_LIST_ (1L << 4) #define _MODE_FIND_ALL_ (1L << 5) #define _MODE_FS_ONLY_FILE_ (_MODE_FILESYSTEM_ | _MODE_ONLY_FILES) #define _MODE_FS_ONLY_DIR_ (_MODE_FILESYSTEM_ | _MODE_ONLY_DIRS) #define _MODE_SD_ONLY_FILE_ (_MODE_FS_START_DIR_ | _MODE_ONLY_FILES) #define _MODE_SD_ONLY_DIR_ (_MODE_FS_START_DIR_ | _MODE_ONLY_DIRS) class CACListWnd : public CWindowImpl<CACListWnd> { public: CACListWnd(); virtual ~CACListWnd(); void Init(CWindow* pWindow); bool EnsureVisible(int nItem, bool bWait); bool SelectItem(int nItem); int FindString(int nStartAfter, LPCTSTR lpszString, bool bDisplayOnly = false); int FindStringExact(int nIndexStart, LPCTSTR lpszFind); int SelectString(LPCTSTR lpszString); bool GetText(int nItem, CString& strText); void AddSearchString(LPCTSTR lpszString); void RemoveAll(); CString GetString(); CString GetNextString(int nChar); void CopyList(); void SortSearchList(); void DrawItem(CDC* pDC, long lItem, long lWidth); void SetFontHeight(long lHeight); CString m_strDisplay; TCHAR m_prefixChar; long m_lMode; long m_lFontHeight; CSimpleArray<CString> m_searchList; protected: BEGIN_MSG_MAP_EX(CACListWnd) MSG_WM_PAINT(OnPaint) MSG_WM_SIZE(OnSize) MSG_WM_ERASEBKGND(OnEraseBkgnd) MSG_WM_NCPAINT(OnNcPaint) MSG_WM_KEYDOWN(OnKeyDown) MSG_WM_NCCALCSIZE(OnNcCalcSize) MSG_WM_VSCROLL(OnVScroll) MSG_WM_ACTIVATEAPP(OnActivateApp) MSG_WM_NCHITTEST(OnNcHitTest) MSG_WM_LBUTTONDOWN(OnLButtonDown) MSG_WM_RBUTTONDOWN(OnRButtonDown) MSG_WM_SETCURSOR(OnSetCursor) MSG_WM_SHOWWINDOW(OnShowWindow) MSG_WM_NCLBUTTONDOWN(OnNcLButtonDown) MSG_WM_MOUSEMOVE(OnMouseMove) MSG_WM_TIMER(OnTimer) MSG_WM_GETMINMAXINFO(OnGetMinMaxInfo) END_MSG_MAP() void OnPaint(HDC hDC); void OnSize(UINT nType, CSize size); LRESULT OnEraseBkgnd(HDC hDC); void OnNcPaint(HRGN hRgn); void OnKeyDown(TCHAR vkey, UINT repeats, UINT code); LRESULT OnNcCalcSize(BOOL bCalcValidRects, LPARAM lParam); void OnVScroll(int nSBCode, short nPos, HWND hWnd); void OnActivateApp(BOOL bActive, DWORD dwThreadID); LRESULT OnNcHitTest(CPoint point); void OnLButtonDown(UINT nFlags, CPoint point); void OnRButtonDown(UINT nFlags, CPoint point); LRESULT OnSetCursor(HWND hWnd, UINT nHitTest, UINT nMessage); void OnShowWindow(BOOL bShow, int nStatus); void OnNcLButtonDown(UINT nHitTest, CPoint point); void OnMouseMove(UINT nFlags, CPoint point); void OnTimer(UINT nIDEvent); void OnGetMinMaxInfo(LPMINMAXINFO lpMMI); int HitTest(CPoint point); void SetScroller(); void SetProp(); long ScrollBarWidth(); void InvalidateAndScroll(); void SortList(CSimpleArray<CString>& List); static int CompareString(const void* p1, const void* p2); CScrollBar m_vertBar; CScrollBar m_horzBar; CRect m_lastSize; CRect m_parentRect; CEdit* m_pEdit; int m_nIDTimer; long m_lTopIndex; long m_lCount; long m_lItemHeight; long m_lVisibleItems; long m_lSelItem; CSimpleArray<CString> m_displayList; };
ACListWnd.cpp
#include "stdafx.h" #include "ACListWnd.h" void DoPaintMessageLoop() { MSG msg; while(::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } } CACListWnd::CACListWnd() { m_lTopIndex = 0; m_lCount = 0; m_lItemHeight = 16; m_lSelItem = -1; m_lVisibleItems = 0; m_pEdit = NULL; m_lastSize.SetRectEmpty(); m_prefixChar = 0; m_lMode = 0; m_lFontHeight = 0; } CACListWnd::~CACListWnd() { m_searchList.RemoveAll(); m_displayList.RemoveAll(); } void CACListWnd::Init(CWindow* pWindow) { ATLASSERT(m_vertBar.Create(this->m_hWnd, CRect(0, 0, GetSystemMetrics(SM_CYVSCROLL), 100), NULL, WS_CHILD | WS_VISIBLE|SBS_VERT|SBS_LEFTALIGN, NULL)); SetScroller(); m_pEdit = (CEdit*)pWindow; m_lCount = m_displayList.GetSize(); m_vertBar.SetScrollPos(0, false); SetProp(); CDC dc(GetDC()); LOGFONT logFont = {0}; CFont((HFONT)GetStockObject(DEFAULT_GUI_FONT)).GetLogFont(&logFont); if (m_lFontHeight > 0) { logFont.lfHeight = m_lFontHeight; logFont.lfWeight = FW_BOLD; dc.SelectFont(CreateFontIndirect(&logFont)); } m_lItemHeight = abs(logFont.lfHeight); } bool CACListWnd::EnsureVisible(int nItem, bool bWait) { if(nItem > m_lTopIndex && nItem < m_lTopIndex + m_lVisibleItems) { return false; } if(nItem > m_lTopIndex) { int nLen = nItem; for(int i=m_lTopIndex; i<nLen; i++) { if(i >= m_lCount - m_lVisibleItems || i + m_lVisibleItems > nItem) { break; } m_lTopIndex++; if(bWait) { InvalidateAndScroll(); Sleep(10); DoPaintMessageLoop(); } } InvalidateAndScroll(); return true; } if(nItem < m_lTopIndex) { while(nItem < m_lTopIndex) { if(m_lTopIndex > 0) { m_lTopIndex--; } else { break; } if(bWait) { InvalidateAndScroll(); Sleep(10); DoPaintMessageLoop(); } } InvalidateAndScroll(); return true; } return false; } bool CACListWnd::SelectItem(int nItem) { if(nItem > m_lCount) { return false; } if(nItem == -1) { EnsureVisible(m_lSelItem, false); Invalidate(); return false; } m_lSelItem = nItem; if(!EnsureVisible(nItem,true)) { Invalidate(); } return true; } int CACListWnd::FindString(int nStartAfter, LPCTSTR lpszString, bool bDisplayOnly) { long lCount = m_displayList.GetSize(); if(!bDisplayOnly) { CString str1; CString str2(lpszString); if(!m_pEdit) { ShowWindow(false); return -1; } if(nStartAfter > m_searchList.GetSize()) { ShowWindow(false); return -1; } if(str2.IsEmpty()) { ShowWindow(false); return -1; } m_displayList.RemoveAll(); str2.MakeUpper(); for(int i=nStartAfter + 1; i<m_searchList.GetSize(); i++) { if(m_prefixChar) { str1 = m_prefixChar; } else { str1 = _T(""); } str1 += m_searchList[i]; str1.MakeUpper(); if(m_lMode & _MODE_FIND_ALL_) { if(str1.Find(str2) >= 0) { m_displayList.Add(m_searchList[i]); } } else { if(str1.Find(str2) == 0) { m_displayList.Add(m_searchList[i]); } } } } m_lCount = m_displayList.GetSize(); if(m_lCount) { CRect rcWnd; int nWidth; m_pEdit->GetWindowRect(rcWnd); SetScroller(); SetProp(); ShowWindow(true); Invalidate(); int nHeight = m_lCount * m_lItemHeight + 2 * GetSystemMetrics(SM_CYBORDER); if(m_lCount > _MAX_ENTRYS_) { nHeight = _MAX_ENTRYS_ * m_lItemHeight + 2 * GetSystemMetrics(SM_CYBORDER); } if(!m_lastSize.IsRectEmpty()) { nWidth = m_lastSize.Width(); nHeight = m_lastSize.Height(); rcWnd.top += rcWnd.Height(); rcWnd.right = rcWnd.left + nWidth; rcWnd.bottom = rcWnd.top + nHeight; MoveWindow(rcWnd.left, rcWnd.top, rcWnd.Width(), rcWnd.Height()); } else { MoveWindow(rcWnd.left, rcWnd.top + rcWnd.Height(), rcWnd.Width(), nHeight); } if(lCount != m_displayList.GetSize()) { m_lSelItem = -1; } SortList(m_displayList); } else { ShowWindow(false); } return 1; } int CACListWnd::FindStringExact(int nIndexStart, LPCTSTR lpszFind) { if(nIndexStart > m_searchList.GetSize()) { return -1; } for(int i=nIndexStart + 1; i<m_searchList.GetSize(); i++) { if(m_searchList[i].Compare(lpszFind) == 0) { return i; } } return -1; } int CACListWnd::SelectString(LPCTSTR lpszString) { int nItem = FindString(-1, lpszString); SelectItem(nItem); return nItem; } bool CACListWnd::GetText(int nItem, CString& strText) { if(nItem < 0 || nItem > m_searchList.GetSize()) { return false; } strText = m_searchList[nItem]; return true; } void CACListWnd::AddSearchString(LPCTSTR lpszString) { m_searchList.Add(lpszString); } void CACListWnd::RemoveAll() { m_searchList.RemoveAll(); m_displayList.RemoveAll(); } CString CACListWnd::GetString() { int i = m_displayList.GetSize(); if(i == 0) { return _T(""); } if(i <= m_lSelItem || m_lSelItem == -1) { i = 0; } else { i = m_lSelItem; } return m_displayList[i]; } CString CACListWnd::GetNextString(int nChar) { switch(nChar) { case VK_DOWN: m_lSelItem++; break; case VK_UP: m_lSelItem--; break; case VK_PRIOR: m_lSelItem -= m_lVisibleItems; if(m_lSelItem < 0) { m_lSelItem = 0; } break; case VK_NEXT: m_lSelItem += m_lVisibleItems; if(m_lSelItem >= m_lCount - 1) { m_lSelItem = m_lCount - 1; } break; case VK_HOME: m_lSelItem = 0; break; case VK_END: m_lSelItem = m_lCount - 1; break; } if(m_lSelItem < 0) { m_lSelItem = m_lCount - 1; } if(m_lSelItem >= m_lCount) { m_lSelItem = 0; } if(EnsureVisible(m_lSelItem, m_lCount > 50 ? false : true)) { InvalidateAndScroll(); } return GetString(); } void CACListWnd::CopyList() { m_displayList = m_searchList; m_lCount = m_displayList.GetSize(); if(m_lCount) { FindString(0, _T(""), true); } } void CACListWnd::SortSearchList() { SortList(m_searchList); } void CACListWnd::DrawItem(CDC* pDC, long lItem, long lWidth) { long y = lItem - m_lTopIndex; CRect rcLabel(2, y * m_lItemHeight, lWidth, (y + 1) * m_lItemHeight); pDC->SetTextColor(::GetSysColor(COLOR_WINDOWTEXT)); if(lItem == m_lSelItem) { rcLabel.left = 0; pDC->FillSolidRect(rcLabel, ::GetSysColor(COLOR_HIGHLIGHT)); pDC->SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT)); rcLabel.left = 2; } if(m_prefixChar) { m_strDisplay = m_prefixChar + m_displayList[lItem]; } else { m_strDisplay = m_displayList[lItem]; } pDC->DrawText(m_strDisplay, -1, rcLabel, DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS); } void CACListWnd::SetFontHeight(long lHeight) { m_lFontHeight = lHeight; } void CACListWnd::OnPaint(HDC hDC) { CRect rcWnd; CRect rect; CRect rc; CDC* pDC = NULL; GetClientRect(rc); rcWnd = rect = rc; rc.left = rc.right - GetSystemMetrics(SM_CXHSCROLL); rc.top = rc.bottom - GetSystemMetrics(SM_CYVSCROLL); rect.right -= ScrollBarWidth(); CPaintDC dc(this->m_hWnd); CDC MemDC = CreateCompatibleDC(dc); CBitmap bitmap = CreateCompatibleBitmap(dc, rect.Width(), rect.Height()); CBitmap oldBitmap = MemDC.SelectBitmap(bitmap); MemDC.SetWindowOrg(rect.left, rect.top); long lWidth = rcWnd.Width() - ScrollBarWidth(); MemDC.FillSolidRect(rcWnd, ::GetSysColor(COLOR_WINDOW)); int i = 0; if (m_lFontHeight <= 0) { MemDC.SelectFont((HFONT)GetStockObject(DEFAULT_GUI_FONT)); MemDC.SetBkMode(TRANSPARENT); for(i=m_lTopIndex; i<m_lCount; i++) { DrawItem(&MemDC, i, lWidth); } } else { LOGFONT logFont = {0}; CFont((HFONT)GetStockObject(DEFAULT_GUI_FONT)).GetLogFont(&logFont); logFont.lfHeight = m_lFontHeight; logFont.lfWeight = FW_BOLD; logFont.lfItalic = TRUE; CFont font = CreateFontIndirect(&logFont); CFont oldFont = MemDC.SelectFont(font); MemDC.SetBkMode(TRANSPARENT); for(i=m_lTopIndex; i<m_lCount; i++) { DrawItem(&MemDC, i, lWidth); } MemDC.SelectFont(oldFont); } CPen pen1 = CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_WINDOW)); CPen pen2 = CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_BTNFACE)); CPen pen3 = CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW)); pDC = &dc; if(m_vertBar.IsWindowVisible()) { dc.FillSolidRect(rc, ::GetSysColor(COLOR_BTNFACE)); } else { pDC = &MemDC; } CPen oldPen = pDC->SelectPen(pen1); int nBottom= rcWnd.bottom - GetSystemMetrics(SM_CXHSCROLL) - 1; lWidth = GetSystemMetrics(SM_CXHSCROLL); int a = 1; for(i=0; i<20; i++, a++) { if(a == 1) { pDC->SelectPen(pen1); } else if(a == 2) { pDC->SelectPen(pen2); } else if(a == 3) { pDC->SelectPen(pen3); } else { a = 0; } pDC->MoveTo(rc.left + i - 1, rcWnd.bottom); pDC->LineTo(rc.left + i + lWidth, nBottom); } dc.BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), MemDC, rect.left, rect.top, SRCCOPY); pDC->SelectPen(oldPen); MemDC.SelectBitmap(oldBitmap); } void CACListWnd::OnSize(UINT nType, CSize size) { SetScroller(); SetProp(); if(!m_lastSize.IsRectEmpty()) { GetWindowRect(m_lastSize); } } LRESULT CACListWnd::OnEraseBkgnd(HDC hDC) { return 0; } void CACListWnd::OnNcPaint(HRGN hRgn) { CWindowDC dc(this->m_hWnd); CRect rectClient; CRect rectWindow; CRect rcWnd; GetClientRect(rectClient); GetWindowRect(rectWindow); ScreenToClient(rectWindow); rectClient.OffsetRect(-(rectWindow.left), -(rectWindow.top)); dc.ExcludeClipRect(rectClient); rectWindow.OffsetRect(-rectWindow.left, -rectWindow.top); dc.FillSolidRect(rectWindow, ::GetSysColor(COLOR_WINDOWTEXT)); } void CACListWnd::OnKeyDown(TCHAR vkey, UINT repeats, UINT code) { if (vkey == VK_ESCAPE) { ShowWindow(false); } } LRESULT CACListWnd::OnNcCalcSize(BOOL bCalcValidRects, LPARAM lParam) { NCCALCSIZE_PARAMS* lpncsp = (NCCALCSIZE_PARAMS*)lParam; return ::InflateRect(lpncsp->rgrc, -GetSystemMetrics(SM_CXBORDER), -GetSystemMetrics(SM_CYBORDER)); } void CACListWnd::OnVScroll(int nSBCode, short nPos, HWND hWnd) { long lOldTopIndex = m_lTopIndex; switch(nSBCode) { case SB_ENDSCROLL: break; case SB_PAGEUP: m_lTopIndex -= m_lVisibleItems; if(m_lTopIndex < 0) { m_lTopIndex = 0; } break; case SB_PAGEDOWN: m_lTopIndex += m_lVisibleItems; if(m_lTopIndex >= m_lCount - m_lVisibleItems) { m_lTopIndex = m_lCount-m_lVisibleItems; } break; case SB_LINEUP: m_lTopIndex--; if(m_lTopIndex < 0) { m_lTopIndex = 0; } break; case SB_LINEDOWN: m_lTopIndex++; if(m_lTopIndex >= m_lCount - m_lVisibleItems) { m_lTopIndex = m_lCount - m_lVisibleItems; } break; case SB_THUMBTRACK: m_lTopIndex = nPos; break; } m_vertBar.SetScrollPos(m_lTopIndex, true); if(lOldTopIndex != m_lTopIndex) { Invalidate(); } } void CACListWnd::OnActivateApp(BOOL bActive, DWORD dwThreadID) { ShowWindow(false); } LRESULT CACListWnd::OnNcHitTest(CPoint point) { CRect rectClient; GetWindowRect(rectClient); rectClient.left = rectClient.right - GetSystemMetrics(SM_CYVSCROLL); rectClient.top = rectClient.bottom - GetSystemMetrics(SM_CXVSCROLL); if(rectClient.PtInRect(point)) { return HTBOTTOMRIGHT; } else { return HTCLIENT; } } void CACListWnd::OnLButtonDown(UINT nFlags, CPoint point) { int nSel = HitTest(point); if(nSel >= 0) { if(!EnsureVisible(nSel, true)) { Invalidate(); } m_lSelItem = nSel; m_pEdit->SendMessage(ENAC_UPDATE, GetDlgCtrlID(), WM_KEYDOWN); DoPaintMessageLoop(); Sleep(100); ShowWindow(false); } else { CRect rect; GetClientRect(rect); if(!rect.PtInRect(point)) { ShowWindow(false); } } } void CACListWnd::OnRButtonDown(UINT nFlags, CPoint point) { ShowWindow(false); } LRESULT CACListWnd::OnSetCursor(HWND hWnd, UINT nHitTest, UINT nMessage) { CRect rectClient; GetWindowRect(rectClient); ScreenToClient(&rectClient); rectClient.left = rectClient.right - GetSystemMetrics(SM_CYVSCROLL); rectClient.top = rectClient.bottom - GetSystemMetrics(SM_CXVSCROLL); CPoint ptCursor; GetCursorPos(&ptCursor); ScreenToClient(&ptCursor); SetCursor(LoadCursor(NULL, IDC_ARROW)); return TRUE; } void CACListWnd::OnShowWindow(BOOL bShow, int nStatus) { if(bShow) { m_nIDTimer = SetTimer(IDTimerInstall, 200, NULL); m_pEdit->GetParent().GetWindowRect(m_parentRect); } else { if(m_nIDTimer) { KillTimer(IDTimerInstall); } m_nIDTimer = 0; m_lSelItem = -1; m_lTopIndex = 0; } } void CACListWnd::OnNcLButtonDown(UINT nHitTest, CPoint point) { if(OnNcHitTest(point) == HTBOTTOMRIGHT) { GetWindowRect(m_lastSize); } } void CACListWnd::OnMouseMove(UINT nFlags, CPoint point) { int nSel = HitTest(point); if(nSel >= 0) { Invalidate(); } } void CACListWnd::OnTimer(UINT nIDEvent) { CRect parentRect; switch(nIDEvent) { case IDTimerInstall: m_pEdit->GetParent().GetWindowRect(parentRect); if(!parentRect.EqualRect(m_parentRect)) { ShowWindow(false); } break; default: break; } } void CACListWnd::OnGetMinMaxInfo(LPMINMAXINFO lpMMI) { if(this->m_hWnd) { long lMinY1 = 2 * GetSystemMetrics(SM_CYHSCROLL) + GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CXHTHUMB); long lMinY2 = m_lCount * m_lItemHeight + 2 * GetSystemMetrics(SM_CYBORDER); if(m_lVisibleItems > m_lCount - 1 && lMinY2 < lMinY1) { lpMMI->ptMinTrackSize.y = lMinY2; } else { lpMMI->ptMinTrackSize.y = lMinY1; } lpMMI->ptMinTrackSize.x = 4 * GetSystemMetrics(SM_CXHSCROLL); if(m_pEdit != NULL) { RECT rect; m_pEdit->GetWindowRect(&rect); lpMMI->ptMinTrackSize.x = rect.right - rect.left; } } } int CACListWnd::HitTest(CPoint point) { CRect rcItem; CRect rcWnd; GetClientRect(rcWnd); long lWidth = rcWnd.Width() - ScrollBarWidth(); for(int i=m_lTopIndex; i<m_lCount; i++) { long y = i - m_lTopIndex; rcItem.SetRect(2, y * m_lItemHeight, lWidth, (y + 1) * m_lItemHeight); if(PtInRect(&rcItem, point)) { return m_lSelItem = (y + m_lTopIndex); } } return -1; } void CACListWnd::SetScroller() { CRect rcWnd; CRect rcBar; GetClientRect(rcWnd); if(m_vertBar.m_hWnd) { rcBar = rcWnd; rcBar.top = -1; rcBar.left = rcWnd.Width() - GetSystemMetrics(SM_CYVSCROLL); rcBar.bottom -= GetSystemMetrics(SM_CYHSCROLL); m_vertBar.MoveWindow(rcBar); rcBar.top = rcWnd.bottom - 20; rcBar.bottom = rcWnd.bottom; m_vertBar.SetScrollPos(m_lTopIndex, true); } } void CACListWnd::SetProp() { CRect rcWnd; CRect rcBar; if(!m_lCount) { return; } GetWindowRect(rcWnd); ScreenToClient(rcWnd); SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_PAGE | SIF_RANGE; si.nMin = 0; si.nMax = m_lCount - 1; m_lVisibleItems = si.nPage = rcWnd.Height() / m_lItemHeight; si.nTrackPos = 2; m_vertBar.SetScrollRange(0, m_lCount - 1); m_vertBar.SetScrollInfo(&si); if(m_lVisibleItems > m_lCount - 1) { m_vertBar.ShowWindow(false); } else { m_vertBar.ShowWindow(true); } if(m_lTopIndex + m_lVisibleItems > m_lCount) { m_lTopIndex = m_lCount - m_lVisibleItems; if(m_lTopIndex < 0) { m_lTopIndex = 0; } m_vertBar.SetScrollPos(m_lTopIndex, true); } } long CACListWnd::ScrollBarWidth() { if(m_vertBar.IsWindowVisible()) { return GetSystemMetrics(SM_CYVSCROLL); } else { return 0; } } void CACListWnd::InvalidateAndScroll() { m_vertBar.SetScrollPos(m_lTopIndex, true); Invalidate(); DoPaintMessageLoop(); } void CACListWnd::SortList(CSimpleArray<CString>& List) { int nCount = List.GetSize(); if (nCount > 1) { CSimpleArray<CString> List2 = List; LPCTSTR* ppSortArray = new LPCTSTR[nCount + 1]; int i=0; for(; i<nCount; i++) { ppSortArray[i] = (LPCTSTR)List2[i]; } List.RemoveAll(); qsort(ppSortArray, nCount, sizeof(LPCTSTR), CompareString); for(i=0; i<nCount; i++) { List.Add((LPCTSTR)ppSortArray[i]); } List2.RemoveAll(); delete[] ppSortArray; } } int CACListWnd::CompareString(const void* p1, const void* p2) { return _stricmp(*(char**)p1, *(char**)p2); }