给CListBox增加个性化的ToolTip
转载请注明来源:http://www.cnblogs.com/xuesongshu/
【步骤】
1、新建一个MFC应用程序项目,无项目设置要求
2、添加一个对话框,添加一个ListBox,无属性设置要求,ListBox宽度最好设小点,以突出效果。
3、添加CShellExcuteString类,ShellExcuteString.h代码如下:
#ifndef _HYPRILNK_H #define _HYPRILNK_H class CShellExcuteString:public CString { public: CShellExcuteString(LPCTSTR lpLink=NULL):CString(lpLink) {} ~CShellExcuteString() {} const CShellExcuteString& operator=(LPCTSTR lpsz) { CString::operator=(lpsz); return*this; } operator LPCTSTR() { return CString::operator LPCTSTR(); } virtual HINSTANCE Navigate() { return IsEmpty()?NULL:ShellExecute(0,_T("open"),*this,0,0,SW_SHOWNORMAL); } }; #endif
4、添加CHookWindowObject类,HookWindowObject.h代码如下:
#ifndef _SUBCLASSW_H #define _SUBCLASSW_H class CHookWindowObject:public CObject { public: CHookWindowObject(); ~CHookWindowObject(); BOOL HookWindow(HWND hwnd); BOOL HookWindow(CWnd *pWnd) { return HookWindow(pWnd->GetSafeHwnd()); } void Unhook() { HookWindow((HWND)NULL); } BOOL IsHooked() { return m_hWnd!=NULL; } friend LRESULT CALLBACK HookWndProc(HWND,UINT,WPARAM,LPARAM); friend class CHookWindowMap; virtual LRESULT WindowProc(UINT msg,WPARAM wp,LPARAM lp); LRESULT Default();//call this at the end of handler fns #ifdef _DEBUG virtual void AssertValid()const; virtual void Dump(CDumpContext&dc)const; #endif protected: HWND m_hWnd;//the window hooked WNDPROC m_pOldWndProc;//..and original window proc CHookWindowObject *m_pNext;//next in chain of hooks for this window DECLARE_DYNAMIC(CHookWindowObject); }; #endif//_SUBCLASSW_H
HookWindowObject.cpp代码如下:
IMPLEMENT_DYNAMIC(CHookWindowObject,CWnd); CHookWindowObject::CHookWindowObject() { m_pNext=NULL; m_pOldWndProc=NULL; m_hWnd=NULL; } CHookWindowObject::~CHookWindowObject() { if(m_hWnd) HookWindow((HWND)NULL);//unhook window } BOOL CHookWindowObject::HookWindow(HWND hwnd) { ASSERT_VALID(this); if(hwnd) { //Hook the window ASSERT(m_hWnd==NULL); ASSERT(::IsWindow(hwnd)); theHookMap.Add(hwnd,this);//Add to map of hooks } else if(m_hWnd) { //Unhook the window theHookMap.Remove(this);//Remove from map m_pOldWndProc=NULL; } m_hWnd=hwnd; return TRUE; } LRESULT CHookWindowObject::WindowProc(UINT msg,WPARAM wp,LPARAM lp) { //ASSERT_VALID(this);//removed for speed ASSERT(m_pOldWndProc); return m_pNext?m_pNext->WindowProc(msg,wp,lp): ::CallWindowProc(m_pOldWndProc,m_hWnd,msg,wp,lp); } LRESULT CHookWindowObject::Default() { //MFC stores current MSG in thread state MSG &curMsg=AfxGetThreadState()->m_lastSentMsg; //Note:must explicitly call CHookWindowObject::WindowProc to avoid infinte //recursion on virtual function return CHookWindowObject::WindowProc(curMsg.message,curMsg.wParam,curMsg.lParam); } #ifdef _DEBUG void CHookWindowObject::AssertValid() const { CObject::AssertValid(); ASSERT(m_hWnd==NULL||::IsWindow(m_hWnd)); if(m_hWnd) { for(CHookWindowObject *p=theHookMap.Lookup(m_hWnd); p; p=p->m_pNext) { if(p==this) break; } ASSERT(p);//should have found it! } } void CHookWindowObject::Dump(CDumpContext&dc)const { CObject::Dump(dc); } #endif ///////////////// //Find first hook associate with window // CHookWindowObject *CHookWindowMap::Lookup(HWND hwnd) { CHookWindowObject *pFound=NULL; if(!CMapPtrToPtr::Lookup(hwnd,(void*&)pFound)) return NULL; ASSERT_KINDOF(CHookWindowObject,pFound); return pFound; }
5、添加CHookWindowMap类,代码放在HookWindowObject.cpp头部,代码如下:
class CHookWindowMap:private CMapPtrToPtr { public: CHookWindowMap(); ~CHookWindowMap(); static CHookWindowMap &GetHookMap(); void Add(HWND hwnd,CHookWindowObject *pNewHook); void Remove(CHookWindowObject *pOldHook); void RemoveAll(HWND hwnd); CHookWindowObject*Lookup(HWND hwnd); }; #define theHookMap (CHookWindowMap::GetHookMap()) CHookWindowMap::CHookWindowMap() { } CHookWindowMap::~CHookWindowMap() { //This assert bombs when posting WM_QUIT,so I've deleted it. //ASSERT(IsEmpty());//all hooks should be removed! } CHookWindowMap &CHookWindowMap::GetHookMap() { //By creating theMap here,C++doesn't instantiate it until/unless //it's ever used!This is a good trick to use in C++,to //instantiate/initialize a static object the first time it's used. // static CHookWindowMap theMap; return theMap; } ///////////////// //Add hook to map;i.e.,associate hook with window // void CHookWindowMap::Add(HWND hwnd,CHookWindowObject *pNewHook) { ASSERT(hwnd&&::IsWindow(hwnd)); //Add to front of list pNewHook->m_pNext=Lookup(hwnd); SetAt(hwnd,pNewHook); if(pNewHook->m_pNext==NULL) { //If this is the first hook added,subclass the window pNewHook->m_pOldWndProc=(WNDPROC)SetWindowLong(hwnd,GWL_WNDPROC,(DWORD)HookWndProc); } else { //just copy wndproc from next hook pNewHook->m_pOldWndProc=pNewHook->m_pNext->m_pOldWndProc; } ASSERT(pNewHook->m_pOldWndProc); } ////////////////// //Remove hook from map // void CHookWindowMap::Remove(CHookWindowObject *pUnHook) { HWND hwnd=pUnHook->m_hWnd; ASSERT(hwnd&&::IsWindow(hwnd)); CHookWindowObject *pHook=Lookup(hwnd); ASSERT(pHook); if(pHook==pUnHook) { //hook to remove is the one in the hash table:replace w/next if(pHook->m_pNext) SetAt(hwnd,pHook->m_pNext); else { //This is the last hook for this window:restore wnd proc RemoveKey(hwnd); SetWindowLong(hwnd,GWL_WNDPROC,(DWORD)pHook->m_pOldWndProc); } } else { //Hook to remove is in the middle:just remove from linked list while(pHook->m_pNext!=pUnHook) pHook=pHook->m_pNext; ASSERT(pHook&&pHook->m_pNext==pUnHook); pHook->m_pNext=pUnHook->m_pNext; } } ////////////////// //Remove all the hooks for a window // void CHookWindowMap::RemoveAll(HWND hwnd) { CHookWindowObject*pSubclassWnd; while((pSubclassWnd=Lookup(hwnd))!=NULL) pSubclassWnd->HookWindow((HWND)NULL);//(unhook) }
6、添加全局函数HookWindowProc,代码如下:
LRESULT CALLBACK HookWndProc(HWND hwnd,UINT msg,WPARAM wp,LPARAM lp) { #ifdef _USRDLL //If this is a DLL,need to set up MFC state AFX_MANAGE_STATE(AfxGetStaticModuleState()); #endif //Set up MFC message state just in case anyone wants it //This is just like AfxCallWindowProc,but we can't use that because //a CHookWindowObject is not a CWnd. // MSG &curMsg=AfxGetThreadState()->m_lastSentMsg; MSG oldMsg=curMsg;//save for nesting curMsg.hwnd=hwnd; curMsg.message=msg; curMsg.wParam=wp; curMsg.lParam=lp; //Get hook object for this window.Get from hook map CHookWindowObject *pSubclassWnd=theHookMap.Lookup(hwnd); ASSERT(pSubclassWnd); LRESULT lr; if(msg==WM_NCDESTROY) { //Window is being destroyed:unhook all hooks(for this window) //and pass msg to orginal window proc // WNDPROC wndproc=pSubclassWnd->m_pOldWndProc; theHookMap.RemoveAll(hwnd); lr=::CallWindowProc(wndproc,hwnd,msg,wp,lp); } else { //pass to msg hook lr=pSubclassWnd->WindowProc(msg,wp,lp); } curMsg=oldMsg;//pop state return lr; }
7、添加CHookWindowTipHandler类,HookWindowTipHandler.h代码如下:
#pragma once #include "HookWindowObject.h" #include "ToolTipWnd.H" class CHookWindowTipHandler:public CHookWindowObject { protected: UINT m_idMyControl;//id of list box control UINT m_nCurItem;//index of current item BOOL m_bCapture;//whether mouse is captured static CToolTipWnd g_wndTip;//THE tip window //subclass window proc virtual LRESULT WindowProc(UINT msg,WPARAM wp,LPARAM lp); //virtual fns you can override virtual void OnMouseMove(CPoint p); virtual BOOL IsRectCompletelyVisible(const CRect&rc); virtual UINT OnGetItemInfo(CPoint p,CRect&rc,CString&s); public: CHookWindowTipHandler(); ~CHookWindowTipHandler(); static UINT g_nTipTimeMsec;//global:msec wait before showing tip void Init(CWnd*pListBox);//initialize };
HookWindowTipHandler.cpp代码如下:
#include "stdafx.h" #include "ToolTipWnd.H" #include "HookWindowTipHandler.h" #include <afxcoll.h> #include <windowsx.h> //THE popup tip window CToolTipWnd CHookWindowTipHandler::g_wndTip; //Timeout before showing long listbox tip--you can change UINT CHookWindowTipHandler::g_nTipTimeMsec=100;//.1 sec CHookWindowTipHandler::CHookWindowTipHandler() { m_nCurItem=-1; m_bCapture=FALSE; } CHookWindowTipHandler::~CHookWindowTipHandler() { } ////////////////// //Install hook.Initialize control ID from list box and create //(invisible)tip window. // void CHookWindowTipHandler::Init(CWnd *pListBox) { CHookWindowObject::HookWindow(pListBox); m_idMyControl=pListBox->GetDlgCtrlID(); if(!g_wndTip) { //create scroll tip window g_wndTip.Create(CPoint(0,0),pListBox,PTS_TRANSPARENT); } } LRESULT CHookWindowTipHandler::WindowProc(UINT msg,WPARAM wp,LPARAM lp) { switch(msg) { case WM_MOUSEMOVE: OnMouseMove(CPoint(GET_X_LPARAM(lp),GET_Y_LPARAM(lp))); break; case WM_LBUTTONDOWN: g_wndTip.Cancel();//cancel popup text if any break; } return CHookWindowObject::WindowProc(msg,wp,lp); } void CHookWindowTipHandler::OnMouseMove(CPoint pt) { CListBox *pListBox=(CListBox*)CWnd::FromHandle(m_hWnd); if(!m_bCapture) { ::SetCapture(m_hWnd); m_bCapture=TRUE; } //Get text and text rectangle for item under mouse CString sText;//item text CRect rcText;//item text rect UINT nItem=OnGetItemInfo(pt,rcText,sText); if(nItem==-1||nItem!=m_nCurItem) { g_wndTip.Cancel();//new item,or no item:cancel popup text if(nItem>=0&&!IsRectCompletelyVisible(rcText)) { //new item,and not wholly visible:prepare popup tip CRect rc=rcText; pListBox->ClientToScreen(&rc);//text rect in screen coords g_wndTip.SetWindowText(sText);//set tip text to that of item //move tip window over list text g_wndTip.SetWindowPos(NULL,rc.left-3,rc.top,rc.Width()*2,rc.Height()*2,SWP_NOZORDER|SWP_NOACTIVATE); g_wndTip.ShowDelayed(g_nTipTimeMsec);//show popup text delayed } } m_nCurItem=nItem; if(nItem==-1) { ::ReleaseCapture(); m_bCapture=FALSE; } } BOOL CHookWindowTipHandler::IsRectCompletelyVisible(const CRect&rc) { CListBox *pListBox=(CListBox*)CWnd::FromHandle(m_hWnd); CRect rcClient; pListBox->GetClientRect(&rcClient); return rcClient.Width()>rc.Width(); } UINT CHookWindowTipHandler::OnGetItemInfo(CPoint p,CRect&rc,CString&s) { CListBox *pListBox=(CListBox*)CWnd::FromHandle(m_hWnd); ASSERT_VALID(pListBox); BOOL bOutside; UINT nItem=pListBox->ItemFromPoint(p,bOutside); s.Empty(); if(!bOutside) { pListBox->GetText(nItem,s); pListBox->GetItemRect(nItem,&rc); CFont *pFont=pListBox->GetFont(); CClientDC dc(pListBox); CFont *pOldFont=dc.SelectObject(pFont); dc.DrawText(s,&rc,DT_CALCRECT); dc.SelectObject(pOldFont); return nItem; } return -1; }
8、添加CLinkStatic类,LinkStatic.h代码如下:
#ifndef _STATLINK_H #define _STATLINK_H #include "ShellExcuteString.H" class CLinkStatic:public CStatic { public: DECLARE_DYNAMIC(CLinkStatic) CLinkStatic(LPCTSTR lpText=NULL,BOOL bDeleteOnDestroy=FALSE); ~CLinkStatic() {} CShellExcuteString m_link; COLORREF m_color; static COLORREF g_colorUnvisited; static COLORREF g_colorVisited; static HCURSOR g_hCursorLink; protected: CFont m_font;//underline font for text control BOOL m_bDeleteOnDestroy;//delete object when window destroyed? virtual void PostNcDestroy(); //message handlers DECLARE_MESSAGE_MAP() afx_msg UINT OnNcHitTest(CPoint point); afx_msg HBRUSH CtlColor(CDC*pDC,UINT nCtlColor); afx_msg void OnLButtonDown(UINT nFlags,CPoint point); afx_msg BOOL OnSetCursor(CWnd*pWnd,UINT nHitTest,UINT message); }; #endif _STATLINK_H
LinkStatic.cpp代码如下:
#include "StdAfx.h" #include "LinkStatic.H" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif COLORREF CLinkStatic::g_colorUnvisited=RGB(0,0,255);//blue COLORREF CLinkStatic::g_colorVisited=RGB(128,0,128);//purple HCURSOR CLinkStatic::g_hCursorLink=NULL; IMPLEMENT_DYNAMIC(CLinkStatic,CStatic) BEGIN_MESSAGE_MAP(CLinkStatic,CStatic) ON_WM_NCHITTEST() ON_WM_CTLCOLOR_REFLECT() ON_WM_LBUTTONDOWN() ON_WM_SETCURSOR() END_MESSAGE_MAP() CLinkStatic::CLinkStatic(LPCTSTR lpText,BOOL bDeleteOnDestroy) { m_link=lpText;//link text(NULL==>window text) m_color=g_colorUnvisited;//not visited yet m_bDeleteOnDestroy=bDeleteOnDestroy;//delete object with window? } UINT CLinkStatic::OnNcHitTest(CPoint point) { return HTCLIENT; } HBRUSH CLinkStatic::CtlColor(CDC *pDC,UINT nCtlColor) { ASSERT(nCtlColor==CTLCOLOR_STATIC); DWORD dwStyle=GetStyle(); HBRUSH hbr=NULL; if((dwStyle&0xFF)<=SS_RIGHT) { //this is a text control:set up font and colors if(!(HFONT)m_font) { //first time init:create font LOGFONT lf; GetFont()->GetObject(sizeof(lf),&lf); lf.lfUnderline=TRUE; m_font.CreateFontIndirect(&lf); } //use underline font and visited/unvisited colors pDC->SelectObject(&m_font); pDC->SetTextColor(m_color); pDC->SetBkMode(TRANSPARENT); //return hollow brush to preserve parent background color hbr=(HBRUSH)::GetStockObject(HOLLOW_BRUSH); } return hbr; } void CLinkStatic::OnLButtonDown(UINT nFlags,CPoint point) { if(m_link.IsEmpty()) { //no link:try to load from resource string or window text m_link.LoadString(GetDlgCtrlID())||(GetWindowText(m_link),1); if(m_link.IsEmpty()) return; } HINSTANCE h=m_link.Navigate(); if((UINT)h>32) //success! { m_color=g_colorVisited;//change color Invalidate();//repaint } else { MessageBeep(0);//unable to execute file! TRACE(_T("***WARNING:CLinkStatic:unable to navigate link%s\n"),(LPCTSTR)m_link); } } BOOL CLinkStatic::OnSetCursor(CWnd *pWnd,UINT nHitTest,UINT message) { if(g_hCursorLink==NULL) { static bTriedOnce=FALSE; if(!bTriedOnce) { CString windir; GetWindowsDirectory(windir.GetBuffer(MAX_PATH),MAX_PATH); windir.ReleaseBuffer(); windir+=_T("\\winhlp32.exe"); HMODULE hModule=LoadLibrary(windir); if(hModule) { g_hCursorLink=CopyCursor(::LoadCursor(hModule,MAKEINTRESOURCE(106))); } FreeLibrary(hModule); bTriedOnce=TRUE; } } if(g_hCursorLink) { ::SetCursor(g_hCursorLink); return TRUE; } return FALSE; } void CLinkStatic::PostNcDestroy() { if(m_bDeleteOnDestroy) delete this; }
9、添加CToolTipWnd类,ToolTipWnd.h代码如下:
class CToolTipWnd:public CWnd { public: CSize m_szMargins;//extra space around text:change if you like CFont m_font; CToolTipWnd(); virtual ~CToolTipWnd(); BOOL Create(CPoint pt,CWnd *pParentWnd,UINT nStyle=0,UINT nID=0); void ShowDelayed(UINT msec); void Cancel(); protected: UINT m_nStyle;//style(see below) virtual void PostNcDestroy(); virtual BOOL PreCreateWindow(CREATESTRUCT &cs); void DrawText(CDC &dc,LPCTSTR lpText,CRect &rc,UINT flags); afx_msg UINT OnNcHitTest(CPoint pt); afx_msg void OnPaint(); afx_msg void OnTimer(UINT nIDEvent); afx_msg LRESULT OnSetText(WPARAM wp,LPARAM lp); DECLARE_DYNAMIC(CToolTipWnd); DECLARE_MESSAGE_MAP(); }; #define PTS_JUSTIFYLEFT 0x0000 #define PTS_JUSTIFYRIGHT 0x0001 #define PTS_TRANSPARENT 0x0002
ToolTipWnd.cpp代码如下:
#include "stdafx.h" #include "ToolTipWnd.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[]=__FILE__; #endif IMPLEMENT_DYNAMIC(CToolTipWnd,CWnd) BEGIN_MESSAGE_MAP(CToolTipWnd,CWnd) ON_WM_NCHITTEST() ON_WM_PAINT() ON_MESSAGE(WM_SETTEXT,OnSetText) ON_WM_TIMER() END_MESSAGE_MAP() CToolTipWnd::CToolTipWnd() { m_szMargins=CSize(4,4); m_font.CreateFont(24,0,0,0,FW_NORMAL,0,0,0,1,0,0,0,0,"微软雅黑"); } CToolTipWnd::~CToolTipWnd() { } int CToolTipWnd::Create(CPoint pt,CWnd *pParentWnd,UINT nStyle,UINT nID) { m_nStyle=nStyle; return CreateEx(0, NULL, NULL, WS_POPUP|WS_VISIBLE, CRect(pt,CSize(0,0)), pParentWnd, nID); } UINT CToolTipWnd::OnNcHitTest(CPoint pt) { if(m_nStyle&PTS_TRANSPARENT)//transparent? return HTTRANSPARENT;//..make it so return CWnd::OnNcHitTest(pt);//otherwise return default } LRESULT CToolTipWnd::OnSetText(WPARAM wp,LPARAM lp) { CRect rc; GetWindowRect(&rc); int x=(m_nStyle&PTS_JUSTIFYRIGHT)?rc.right:rc.left; int y=rc.top; CClientDC dc=this; DrawText(dc,CString((LPCTSTR)lp),rc,DT_CALCRECT); rc.InflateRect(m_szMargins); //if(m_nStyle&PTS_JUSTIFYRIGHT) //{ // x-=rc.Width(); //} //SetWindowPos(NULL,x,y,rc.Width(),rc.Height(),SWP_NOZORDER|SWP_NOACTIVATE); return Default(); } void CToolTipWnd::DrawText(CDC &dc,LPCTSTR lpText,CRect &rc,UINT flags) { CBitmap bmp; bmp.LoadBitmap(IDB_BITMAP1); //CBrush b(GetSysColor(COLOR_INFOBK));//use tooltip bg color CBrush br; br.CreatePatternBrush(&bmp); dc.FillRect(&rc,&br); dc.SetBkMode(TRANSPARENT); //dc.SetTextColor(GetSysColor(COLOR_INFOTEXT));//tooltip text color dc.SetTextColor(RGB(0xFF,0,0)); //CFont *pOldFont=dc.SelectObject(GetParent()->GetFont()); CFont *pOldFont=dc.SelectObject(&m_font); dc.DrawText(lpText,&rc,flags); dc.SelectObject(pOldFont); } void CToolTipWnd::OnPaint() { CRect rc; GetClientRect(&rc); CString s; GetWindowText(s); CPaintDC dc(this); DrawText(dc,s,rc,DT_SINGLELINE|DT_VCENTER|DT_CENTER); } BOOL CToolTipWnd::PreCreateWindow(CREATESTRUCT&cs) { static CString sClassName; if(sClassName.IsEmpty()) sClassName=AfxRegisterWndClass(0); cs.lpszClass=sClassName; cs.style=WS_POPUP|WS_BORDER; cs.dwExStyle|=WS_EX_TOOLWINDOW; return CWnd::PreCreateWindow(cs); } void CToolTipWnd::PostNcDestroy() { //don't delete this } void CToolTipWnd::ShowDelayed(UINT msec) { if(msec==0) { //no delay:show it now OnTimer(1); } else { //delay:set time SetTimer(1,msec,NULL); } } void CToolTipWnd::Cancel() { KillTimer(1); ShowWindow(SW_HIDE); } void CToolTipWnd::OnTimer(UINT nIDEvent) { ShowWindow(SW_SHOWNA); Invalidate(); UpdateWindow(); KillTimer(1); }
10、添加CNonClientMetrics,代码写在ToolTipWnd.h头部,代码如下:
class CNonClientMetrics:public NONCLIENTMETRICS { public: CNonClientMetrics() { cbSize=sizeof(NONCLIENTMETRICS); SystemParametersInfo(SPI_GETNONCLIENTMETRICS,0,this,0); } };
11、添加一张位图,资源代号是:IDB_BITMAP1。
12、在对话框的InitDialog里添加初始化ListBox的代码:
static LPCTSTR STRINGS[] = { "来是空言去绝踪,", "月斜楼上五更钟。梦为远别啼难唤,", "书被催成墨未浓。蜡照半笼金翡翠,麝熏微度绣芙蓉。", "刘郎已恨蓬山远,", "更隔蓬山一万重。昨夜星辰昨夜风,", "画楼西畔桂堂东。身无彩凤双飞翼,心有灵犀一点通。", "隔座送钩春酒暖," "分曹射覆蜡灯红。嗟余听鼓应官去,走马兰台类转蓬。", NULL }; for (int i=0; STRINGS[i]; i++) { m_wndListBox.AddString(STRINGS[i]); } m_tipHandler.Init(&m_wndListBox); m_wndListBox.SetFocus(); m_wndLink1.m_link = _T("http://www.vckbase.com"); m_wndLink2.m_link = _T("mailto:jiayuxiaosheng@yeah.net"); m_wndLink1.SubclassDlgItem(IDC_VCKBASE, this); m_wndLink2.SubclassDlgItem(IDC_MAIL, this);
【效果】
好了,大功告成,运行效果截图: