Toolbar CSubClassWnd Tip 弹出窗口

背景:我用一个CListBox派生类实现宿主(owner-draw)列表框,这个列表框的项目宽度超过了列表框本身的宽度,因此当鼠标指针指向大宽度的列表框项时,我想显示一个类似Toolbar的提示窗口,在窗口中显示完整的列表框项目文本。
起初我想使用CToolTipCtrl::AddTool的第三个参数lpRectTool来实现这个功能,但没有成功。后来,我采用了自立更生的解决方案,创建了一个可重用窗口类:
CPopupText 类定义和实现 

// PupText.h  
// 
#pragma once

// Get NONCLIENTMETRICS info: ctor calls SystemParametersInfo.
//
class CNonClientMetrics : public NONCLIENTMETRICS {
public:
   CNonClientMetrics() {
      cbSize = sizeof(NONCLIENTMETRICS);
      SystemParametersInfo(SPI_GETNONCLIENTMETRICS,0,this,0);
   }
};

// Popup text window, like tooltip.
// Can be right or left justified relative to creation point.
//
class CPopupText : public CWnd {
public:
   CSize m_szMargins;      // extra space around text: change if you like
   enum {JUSTIFYLEFT=0, JUSTIFYRIGHT};
   CPopupText();
   virtual ~CPopupText();
   BOOL Create(CPoint pt, CWnd* pParentWnd, UINT nStyle=0, UINT nID=0);
   void ShowDelayed(UINT msec);
   void Cancel();

protected:
   CFont m_font;           // font to use (same as tooltips)
   UINT  m_nStyle;         // style (see enum below)

   virtual void PostNcDestroy();
   virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

   afx_msg void OnPaint();
   afx_msg void OnTimer(UINT nIDEvent);
   afx_msg LRESULT OnSetText(WPARAM wp, LPARAM lp);
   DECLARE_DYNAMIC(CPopupText);
   DECLARE_MESSAGE_MAP();
};

PupText.cpp 

// VCKBASE -- September 2000 
// Visual C++ 6.0 环境编译, Windows 98 和 NT 环境运行.
// 
#include "stdafx.h"
#include "puptext.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE

static char THIS_FILE[] = __FILE__;
#endif

IMPLEMENT_DYNAMIC(CPopupText,CWnd)
BEGIN_MESSAGE_MAP(CPopupText,CWnd)
   ON_WM_PAINT()
   ON_MESSAGE(WM_SETTEXT, OnSetText)
   ON_WM_TIMER()
END_MESSAGE_MAP()

CPopupText::CPopupText()
{
   m_szMargins = CSize(4,4);
   // create font ?use system tooltip font
   CNonClientMetrics ncm;
   m_font.CreateFontIndirect(&ncm.lfStatusFont);
}

CPopupText::~CPopupText()
{
}

// Create window. pt is upper-left or upper-right corner depending on 
// nStyle.
//
CPopupText::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);
}

// Someone changed the text: resize to fit new text
//
LRESULT CPopupText::OnSetText(WPARAM wp, LPARAM lp)
{
   CClientDC dc = this;
   CFont* pOldFont = dc.SelectObject(&m_font);
   CRect rc;
   GetWindowRect(&rc);
   int x = (m_nStyle & JUSTIFYRIGHT) ? rc.right : rc.left;
   int y = rc.top;
   dc.DrawText(CString((LPCTSTR)lp), &rc, DT_CALCRECT);
   rc.InflateRect(m_szMargins);
   if (m_nStyle & JUSTIFYRIGHT)
      x -= rc.Width();
   SetWindowPos(NULL,x,y,rc.Width(),rc.Height(), SWP_NOZORDER|SWP_NOACTIVATE);
   return Default();
}

// Paint the text. Use system colors
//
void CPopupText::OnPaint()
{
   CPaintDC dc(this);
   CRect rc;
   GetClientRect(&rc);
   CString s;
   GetWindowText(s);
   CBrush b(GetSysColor(COLOR_INFOBK)); // use tooltip bg color
   dc.FillRect(&rc, &b);

   // draw text
   dc.SetBkMode(TRANSPARENT);
   CFont* pOldFont = dc.SelectObject(&m_font);
   dc.SetTextColor(GetSysColor(COLOR_INFOTEXT)); // tooltip text color
   dc.DrawText(s, &rc, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
   dc.SelectObject(pOldFont);
}

// Register class if needed
//
BOOL CPopupText::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);
}

// CPopupText is intended to be used on the stack,
// not heap, so don't auto-delete.
//
void CPopupText::PostNcDestroy()
{
   // don't delete this
}

// Show window with delay. No delay means show now.
//
void CPopupText::ShowDelayed(UINT msec)
{
   if (msec==0)
      // no delay: show it now
      OnTimer(1);
   else
      // delay: set time
      SetTimer(1, msec, NULL);
}

// Cancel text梜ill timer and hide window
//
void CPopupText::Cancel()
{
   KillTimer(1);
   ShowWindow(SW_HIDE);
}

// Timer popped: display myself and kill timer
//
void CPopupText::OnTimer(UINT nIDEvent)
{
   ShowWindow(SW_SHOWNA);
   Invalidate();
   UpdateWindow();
   KillTimer(1);
}
CPopupText的基类是CWnd。用这个派生类不仅可以在列表框中实现类似Toolbar的提示窗口,还可以在其它的通用控件(如组合框、列表视图等)中实现类似Toolbar的提示窗口。
CPopupText实现了一个类似Toolbar的弹出式窗口-浅黄色的背景,黑色的文本。
CPopupText的使用方法是:实例化对象并创建窗口。
--------------------next---------------------
CPopupText类会根据提示文本的长度自己决定弹出窗口的大小。它的字体默认值与状态行显示字体相同(由SystemParametersInfo(SPI_GETNONCLIENTMETRICS)返回的 NONCLIENTMETRICS 结构成员 lfStatusFont 定义),设置 SW_SHOWNA是很重要的,因为你不想使提示窗口为活动窗口,而只是显示它。另外,CPopupText还提供一个专门的函数CPopupText::ShowDelayed,它的功能是在显示提示窗口之前设置一个毫秒级延时,如果延时为零,则立刻显示提示窗口,你可以使用这个特点来替代对ShowWindow的调用。如果要隐藏提示窗口或取消ShowDelayed,调用CPopupText::Cancel。

OK,前面讲了关于CPopupText类的创建以及使用方法。下面要讲一下对列表框所要做的事情。它是用CListBoxTipHandler类来实现的。这个类的功能是当列表框项目文本宽度超过列表框本身的宽度时,在一个提示窗口中显示鼠标指针所指的列表框项目的完整文本内容。图一是本文的例子程序,LCTest。它是一个对话框的例子。
--------------------next---------------------
CListBoxTipHandler类的设计原则是使它尽量易于使用,它的工作原理又是怎样的呢?如果你仔细研究一下它的代码就会明白。CListBoxTipHandler类的基类是CSubclassWnd,这个类在以前的VCKBASE文章中出现过很多次,CSubclassWnd类的作用是不用派生新类便能在MFC中子类化窗口。这一点很重要,如果你从CListBox派生一个新类,假如说是:CListBoxWithTips,那你就不能在自己已经派生的列表框类(如:CMyListBox)中直接使用它。而要作很多修改。这是不可取的。CSubclassWnd完全可以让CListBoxTipHandler通过实例化来子类你的列表框,而不是通过派生。

当你调用Init函数时,CListBoxTipHandler子类化列表框,然后,截获发送到列表框的所有消息。而只有一个消息是我们感兴趣的,那就是 WM_MOUSEMOVE。

当用户移动鼠标到列表框时,CListBoxTipHandler实例(提示处理器)便检查鼠标所指的列表框项目的文字是否比列表框本身的宽度要宽,从而决定是否启动CPopupText显示文本提示窗口。其中的处理有两个技巧:
CListBoxTipHandler 

// ListBoxTip.h 
// 
#pragma once

#include "subclass.h"
#include "puptext.h"

// Generic tip-handler to display tip for wide text in a listbox.
// To use:
// - instantiate one of these for each listbox
// - call Init
//
class CListBoxTipHandler : public CSubclassWnd {
protected:
   UINT  m_idMyControl;                 // id of listbox control
   UINT  m_nCurItem;                    // index of current item
   BOOL  m_bCapture;                    // whether mouse is captured
   static CPopupText 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:
   CListBoxTipHandler();
   ~CListBoxTipHandler();
   static UINT g_nTipTimeMsec;          // global: msec wait before showing 
                                        // tip
   void Init(CWnd* pListBox);           // initialize
};


// ListBoxTip.cpp  
// 
#include "stdafx.h"
#include "PupText.h"
#include "ListBoxTip.h"

// THE popup tip window
CPopupText CListBoxTipHandler::g_wndTip;

// Timeout before showing long listbox tip ?you can change
UINT CListBoxTipHandler::g_nTipTimeMsec = 100; // .1 sec

CListBoxTipHandler::CListBoxTipHandler()
{
   m_nCurItem=-1;
   m_bCapture = FALSE;
}

CListBoxTipHandler::~CListBoxTipHandler()
{
}

// Install hook. Initialize control ID from listbox and create
// (invisible) tip window.
//
void CListBoxTipHandler::Init(CWnd* pListBox)
{
   CSubclassWnd::HookWindow(pListBox);
   m_idMyControl = pListBox->GetDlgCtrlID();
   if (!g_wndTip) {
      // create scroll tip window
      g_wndTip.Create(CPoint(0,0), NULL, 0);
   }
}

// "Hook" function traps messages sent to listbox/control. 
//
LRESULT CListBoxTipHandler::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
   switch (msg) {
   case WM_MOUSEMOVE:
      OnMouseMove(CPoint(GET_X_LPARAM(lp),GET_Y_LPARAM(lp)));
      break;
   }
   return CSubclassWnd::WindowProc(msg, wp, lp);
}

// User moved the mouse.
// Get info for listbox item under mouse:
// If text is too wide to fit in listbox, prepare tip window and start
// timer to display it.
//
void CListBoxTipHandler::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()+6,
            rc.Height(), SWP_NOZORDER|SWP_NOACTIVATE);

         g_wndTip.ShowDelayed(g_nTipTimeMsec); // show popup text delayed
      }
   }
   m_nCurItem = nItem;

   if (nItem==-1) {
      ::ReleaseCapture();
      m_bCapture=FALSE;
   }
}

// Determine if given rectangle is completely visible within listbox
//
BOOL CListBoxTipHandler::IsRectCompletelyVisible(const CRect& rc)
{
   CListBox* pListBox = (CListBox*)CWnd::FromHandle(m_hWnd);
   CRect rcClient;
   pListBox->GetClientRect(&rcClient);
   return rcClient.Width() > rc.Width();
}

// Get info (rectangle and text) for item under point
//
UINT CListBoxTipHandler::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;
}
第一个技巧:如果用户把鼠标从文本提示窗口上移走,CListBoxTipHandler会调用CPopupText::Cancel 隐藏提示窗。当用户的鼠标在列表框项目间移动时不会有什么问题,但如果将鼠标完全移到列表框之外会发生什么呢?显然,你无法知道哪一个是最后一个WM_MOUSEMOVE消息。为了避免这种情况,CListBoxTipHandler代表列表框来捕获鼠标,所以全部的鼠标消息都到了CListBoxTipHandler,当鼠标移到列表框之外的情况发生时,CListBoxTipHandler释放鼠标。

第二个技巧:关于提示窗的激活与隐藏。为了正确定位提示窗口,CListBoxTipHandler要计算窗口矩形的大小,并调用SetWindowPos函数。这里使用 SWP_NOACTIVATE 很关键,否则提示窗口将是活动的,而且对话框不活动-对话框标题条变灰。在调用 CPopupText::ShowWindow 时之所以必须用SW_SHOWNA 也是一样的道理。

例子程序使用了一个最普通的列表框,但 CListBoxTipHandler 应该处理宿主(owner-draw)列表框,另外还要让 CListBoxTipHandler 获得列表框项目文本并决定文本宽度。CListBoxTipHandler::OnGetItemInfo 和 CListBoxTipHandler::IsRectCompletelyVisible 是两个重载的虚函数,如果你想让 CListBoxTipHandler 类也适用于其它类似列表框一样的控件(如组合框、列表视图等),这两个函数是你必须修改的。不要将CListBoxTipHandler 用于树型控件,因为树型控件本身就内建有toolbar功能。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值