⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 balloontip.h

📁 这是一本学习 window编程的很好的参考教材
💻 H
字号:
if !defined(AFX_BALLOONTIP_H__20020228_7248_7790_4F55_0080AD509054__INCLUDED_)
#define AFX_BALLOONTIP_H__20020228_7248_7790_4F55_0080AD509054__INCLUDED_

#pragma once

/////////////////////////////////////////////////////////////////////////////
// CBalloonDialog - A balloon-like dialog
//
// Written by Bjarke Viksoe (bjarke@viksoe.dk)
// Copyright (c) 2002 Bjarke Viksoe.
// Thanks to Ramon Casellas for suggestions.
//
// To use this dialog extension, create a normal dialog class,
// derive from CBalloonDialog as well and chain it to 
// the dialog's message map...
//   BEGIN_MSG_MAP(CMyDialog)
//      MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
//      ...
//      CHAIN_MSG_MAP( CBalloonDialog<CMyDialog> )
//   ALT_MSG_MAP(1)
//      CHAIN_MSG_MAP_ALT( CBalloonDialog<CMyDialog>, 1 )
//   END_MSG_MAP()
//
// You can handle OnInitDialog() but make sure to return
// "bHandled = FALSE" to let this class have a shot too...
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed by any means PROVIDING it is 
// not sold for profit without the authors written consent, and 
// providing that this notice and the authors name is included. 
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage to you or your
// computer whatsoever. It's free, so don't hassle me about it.
//
// Beware of bugs.
//

#ifndef __cplusplus
  #error WTL requires C++ compilation (use a .cpp suffix)
#endif

#ifndef __ATLAPP_H__
  #error BalloonTip.h requires atlapp.h to be included first
#endif

#ifndef __ATLCTRLS_H__
  #error BalloonTip.h requires atlctrls.h to be included first
#endif


template< class T >
class CBalloonDialog
{
public:
   enum 
   { 
      s_cxArrow = 20,
      s_cyArrow = 16,
      s_cxArc = 10
   };

   CContainedWindowT<CStatic> m_wndArrow;
   CRgn m_rgnArrow;
   POINT m_ptArrow[3];
   RECT m_rcCollision;

   CRgn m_rgnFrame;
   COLORREF m_clrBack;
   COLORREF m_clrText;
   CBrush m_brBack;
   CBrush m_brText;
   RECT m_rcBuddy;
   int m_iPreferredSide;
   int m_iAttachedSide;

   CBalloonDialog()
   {
      SetFrameColors(::GetSysColor(COLOR_INFOTEXT), ::GetSysColor(COLOR_INFOBK));
      m_iAttachedSide = m_iPreferredSide = HTERROR;
      ::SetRectEmpty(&m_rcBuddy);
   }

   // Operations

   void SetBuddy(HWND hWndBuddy)
   {
      ATLASSERT(::IsWindow(hWndBuddy));
      ATLASSERT(::IsWindowVisible(hWndBuddy));
      ::GetWindowRect(hWndBuddy, &m_rcBuddy);
   }
   void SetBuddy(RECT rcBuddy)
   {
      // Rectangle should be in Screen coordinates
      ATLASSERT(!::IsRectEmpty(&rcBuddy));
      m_rcBuddy = rcBuddy;
   }
   void SetFrameColors(COLORREF clrText, COLORREF clrBack)
   {
      // Set colours
      m_clrText = clrText;
      m_clrBack = clrBack;
      // Create brushes
      if( !m_brBack.IsNull() ) m_brBack.DeleteObject();
      m_brBack.CreateSolidBrush(clrBack);
      if( !m_brText.IsNull() ) m_brText.DeleteObject();
      m_brText.CreateSolidBrush(clrText);
      // Repaint?
      T* pT = static_cast<T*>(this);
      if( pT->IsWindow() ) pT->Invalidate();
   }
   void SetPreferredSide(int iSide)
   {
      m_iPreferredSide = iSide;
   }

   // Implementation

   void _CreateDialogFrame()
   {
      T* pT = static_cast<T*>(this);
      RECT rc;
      pT->GetClientRect(&rc);
      CRgnHandle rgn;
      rgn.CreateRoundRectRgn(0, 0, rc.right-rc.left, rc.bottom-rc.top, s_cxArc, s_cxArc);
      m_rgnFrame.CreateRectRgn(0,0,1,1); // Stupid region must exist...
      m_rgnFrame.CopyRgn(rgn);
      pT->SetWindowRgn(rgn, FALSE);
   }

   void _PositionDialog()
   {
      // Figure out where the dialog should be placed.
      // First we need some heuristics...
      T* pT = static_cast<T*>(this);
      RECT rcBuddy = m_rcBuddy;
      RECT rcDialog;
      pT->GetWindowRect(&rcDialog);
      POINT pt = { 0 };
      // The possible user's preffered anchor is examined first
      // if one is defined...
      int nRes = _DoesDialogFit(m_iPreferredSide, rcBuddy, rcDialog, pt);
      // Then search for a location at the top, left, bottom and to
      // the right of the buddy...
      if( nRes==HTNOWHERE ) nRes = _DoesDialogFit(HTTOP, rcBuddy, rcDialog, pt);
      if( nRes==HTNOWHERE ) nRes = _DoesDialogFit(HTLEFT, rcBuddy, rcDialog, pt);
      if( nRes==HTNOWHERE ) nRes = _DoesDialogFit(HTBOTTOM, rcBuddy, rcDialog, pt);
      if( nRes==HTNOWHERE ) nRes = _DoesDialogFit(HTRIGHT, rcBuddy, rcDialog, pt);
      ATLASSERT(nRes!=HTNOWHERE); // Should never happen
      // Uhm, in case of mis-calculations...
      if( nRes==HTNOWHERE ) {
         pt.x = rcBuddy.left;
         pt.y = rcBuddy.bottom + s_cxArrow;
         m_iAttachedSide = HTBOTTOMLEFT;
      }
      m_iAttachedSide = nRes;
      pT->SetWindowPos(HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE);

      // Create arrow window as well...
      _CreateArrowHead();
   }

   void _CreateArrowHead()
   {
      T* pT = static_cast<T*>(this);
      RECT rcDialog;
      pT->GetWindowRect(&rcDialog);

      // Figure out the shape of the arrow.
      // This is a bit clumsy code. Probably could do it
      // better, but I was hacking this at 2 in the night :(
      RECT rcArrow = { 0 };
      switch( m_iAttachedSide ) {
      case HTTOPRIGHT:
         rcArrow.left = rcDialog.left + s_cxArrow;
         rcArrow.top = rcDialog.bottom - 2;
         m_ptArrow[0].x = 0;
         m_ptArrow[0].y = 0;
         m_ptArrow[1].x = s_cxArrow;
         m_ptArrow[1].y = s_cyArrow;
         m_ptArrow[2].x = s_cxArrow;
         m_ptArrow[2].y = 0;
         ::SetRect(&m_rcCollision, m_ptArrow[0].x, m_ptArrow[0].y, m_ptArrow[2].x, m_ptArrow[2].y);
         break;
      case HTTOPLEFT:
         rcArrow.left = rcDialog.right - (s_cxArrow*2);
         rcArrow.top = rcDialog.bottom - 2;
         m_ptArrow[0].x = 0;
         m_ptArrow[0].y = 0;
         m_ptArrow[1].x = 0;
         m_ptArrow[1].y = s_cyArrow;
         m_ptArrow[2].x = s_cxArrow;
         m_ptArrow[2].y = 0;
         ::SetRect(&m_rcCollision, m_ptArrow[0].x, m_ptArrow[0].y, m_ptArrow[2].x, m_ptArrow[2].y);
         break;
      case HTBOTTOMLEFT:
         rcArrow.left = rcDialog.left + s_cxArrow;
         rcArrow.top = rcDialog.top - s_cyArrow + 1;
         m_ptArrow[0].x = 0;
         m_ptArrow[0].y = s_cyArrow;
         m_ptArrow[2].x = s_cxArrow;
         m_ptArrow[2].y = s_cyArrow;
         m_ptArrow[1].x = s_cxArrow;
         m_ptArrow[1].y = 0;
         ::SetRect(&m_rcCollision, m_ptArrow[0].x, m_ptArrow[0].y-1, m_ptArrow[2].x, m_ptArrow[2].y-1);
         break;
      case HTBOTTOMRIGHT:
         rcArrow.left = rcDialog.right - (s_cxArrow*2);
         rcArrow.top = rcDialog.top - s_cyArrow + 1;
         m_ptArrow[0].x = 0;
         m_ptArrow[0].y = s_cyArrow;
         m_ptArrow[1].x = 0;
         m_ptArrow[1].y = 0;
         m_ptArrow[2].x = s_cxArrow;
         m_ptArrow[2].y = s_cyArrow;
         ::SetRect(&m_rcCollision, m_ptArrow[0].x, m_ptArrow[0].y-1, m_ptArrow[2].x, m_ptArrow[2].y-1);
         break;
      case HTLEFT:
         rcArrow.left = rcDialog.right - 2;
         rcArrow.top = rcDialog.top + s_cxArrow;
         m_ptArrow[0].x = 0;
         m_ptArrow[0].y = 0;
         m_ptArrow[1].x = s_cyArrow;
         m_ptArrow[1].y = 0;
         m_ptArrow[2].x = 0;
         m_ptArrow[2].y = s_cxArrow;
         ::SetRect(&m_rcCollision, m_ptArrow[0].x, m_ptArrow[0].y, m_ptArrow[2].x, m_ptArrow[2].y);
         break;
      case HTRIGHT:
         rcArrow.left = rcDialog.left - s_cyArrow + 1;
         rcArrow.top = rcDialog.top + s_cxArrow;
         m_ptArrow[0].x = s_cyArrow;
         m_ptArrow[0].y = 0;
         m_ptArrow[1].x = 0;
         m_ptArrow[1].y = 0;
         m_ptArrow[2].x = s_cyArrow;
         m_ptArrow[2].y = s_cxArrow;
         ::SetRect(&m_rcCollision, m_ptArrow[0].x-1, m_ptArrow[0].y, m_ptArrow[2].x-1, m_ptArrow[2].y);
         break;
      }
      rcArrow.right = rcArrow.left + s_cxArrow;
      rcArrow.bottom = rcArrow.top + s_cxArrow;

      ATLASSERT(!m_wndArrow.IsWindow());
      m_wndArrow.Create(pT, 1, pT->m_hWnd, &rcArrow, NULL, WS_VISIBLE|WS_POPUP, WS_EX_TOOLWINDOW);
      ATLASSERT(m_wndArrow.IsWindow());
   }

   int _DoesDialogFit(int pos, RECT &rcBuddy, RECT &rcDialog, POINT &pt) const
   {
      int cxScreen = ::GetSystemMetrics(SM_CXSCREEN);
      int cyScreen = ::GetSystemMetrics(SM_CYSCREEN);
      int cxDlg = rcDialog.right-rcDialog.left;
      int cyDlg = rcDialog.bottom-rcDialog.top;
      switch( pos ) {
      case HTTOP:
         if( rcBuddy.top - s_cxArrow - cyDlg >= 0 ) {
            if( rcBuddy.left + ((rcBuddy.right - rcBuddy.left)/2) <= (cxScreen/2)) {
               if( rcBuddy.left + cxDlg <= cxScreen ) {
                  if( rcBuddy.left < 0 ) pt.x = 0; else pt.x = rcBuddy.left;
                  pt.y = rcBuddy.top - s_cxArrow - cyDlg;
                  return HTTOPRIGHT;
               }
            }
            else {
               if( rcBuddy.right - cxDlg >= 0 ) {
                  if( rcBuddy.right > cxScreen ) pt.x = cxScreen - cxDlg; else pt.x = rcBuddy.right - cxDlg;
                  pt.y = rcBuddy.top - s_cxArrow - cyDlg;
                  return HTTOPLEFT;
               }
            }
         }
         break;
      case HTBOTTOM:
         if( rcBuddy.bottom + s_cxArrow + cyDlg < cyScreen ) {
            if( rcBuddy.left + ((rcBuddy.right - rcBuddy.left)/2) <= (cxScreen/2)) {
               if( rcBuddy.left + cxDlg <= cxScreen ) {
                  if( rcBuddy.left < 0 ) pt.x = 0; else pt.x = rcBuddy.left;
                  pt.y = rcBuddy.bottom + s_cxArrow;
                  return HTBOTTOMLEFT;
               }
            }
            else {
               if( rcBuddy.right - cxDlg >= 0 ) {
                  if( rcBuddy.right > cxScreen ) pt.x = cxScreen - cxDlg; else pt.x = rcBuddy.right - cxDlg;
                  pt.y = rcBuddy.bottom + s_cxArrow;
                  return HTBOTTOMRIGHT;
               }
            }
         }
         break;
      case HTLEFT:
         if( ((rcBuddy.left - s_cxArrow - cxDlg) >= 0) && ((rcBuddy.top + cyDlg) <= cxScreen) ) {
            pt.x = rcBuddy.left - s_cxArrow - cxDlg;
            pt.y = rcBuddy.top;
            return HTLEFT;
         }
         break;
      case HTRIGHT:
         if( ((rcBuddy.right + s_cxArrow + cxDlg) <= cxScreen) && ((rcBuddy.top + cyDlg) <= cyScreen) ) {
            pt.x = rcBuddy.right + s_cxArrow;
            pt.y = rcBuddy.top;
            return HTRIGHT;
         }
      }
      return HTNOWHERE;
   }

   // Message map and handlers

   BEGIN_MSG_MAP(CMainDlg)
      MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
      MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
      MESSAGE_HANDLER(WM_CTLCOLORSTATIC, OnCtlColorStatic)
   ALT_MSG_MAP(1) // Arrow
      MESSAGE_HANDLER(WM_CREATE, OnArrowCreate)
      MESSAGE_HANDLER(WM_PAINT, OnArrowPaint)
   END_MSG_MAP()

   LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
   {
      T* pT = static_cast<T*>(this);
      pT;

      // Make sure to remove these styles in the dialog resource editor
      ATLASSERT((pT->GetStyle() & (WS_CAPTION|WS_BORDER|WS_DLGFRAME|WS_THICKFRAME|WS_MINIMIZE|WS_MAXIMIZE|WS_SYSMENU))==0);
      // It must be a popup dialog
      ATLASSERT((pT->GetStyle() & (WS_POPUP))==(WS_POPUP));
      // And set this one, so we don't get a task-bar icon
      ATLASSERT((pT->GetExStyle() & (WS_EX_TOOLWINDOW))==(WS_EX_TOOLWINDOW));

      // Create frame (round region)
      _CreateDialogFrame();

      if( !::IsRectEmpty(&m_rcBuddy) ) {
         // Move the dialog and create the little arrow window too
         _PositionDialog();
      }
      else {
         // Assume a MessageBox-like dialog
         T* pT = static_cast<T*>(this);
         pT->CenterWindow();
      }

      bHandled = FALSE;
      return 0;
   }
   LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      CDCHandle dc( (HDC) wParam );
      // Fill the nice balloon...
      dc.FillRgn(m_rgnFrame, m_brBack );
      dc.FrameRgn(m_rgnFrame, m_brText, 1, 1);
      // Prepare some DC settings
      dc.SetBkMode(TRANSPARENT);
      dc.SetTextColor(m_clrText);
      return 1; // handled, no background painting needed
   }
   LRESULT OnCtlColorStatic(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      // To allow static controls to have the correct background
      // colour we need this...
      CDCHandle dc( (HDC) wParam );
      dc.SetTextColor(m_clrText);
      dc.SetBkColor(m_clrBack),
      dc.SetBkMode(TRANSPARENT);
      return (LRESULT) (HBRUSH) m_brBack;
   }

   // The arrow

   LRESULT OnArrowCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      T* pT = static_cast<T*>(this);
      CRgnHandle rgn;
      rgn.CreatePolygonRgn(pT->m_ptArrow, 3, WINDING);
      if( !m_rgnArrow.IsNull() ) m_rgnArrow.DeleteObject();
      m_rgnArrow.CreateRectRgn(0,0,1,1); // Must be created before use!
      m_rgnArrow.CopyRgn(rgn);
      m_wndArrow.SetWindowRgn(rgn, FALSE);
      return TRUE;
   }
   LRESULT OnArrowPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      if( wParam!=NULL ) {
         DoArrowPaint( (HDC)wParam );
      }
      else {
         CPaintDC dc( m_wndArrow );
         DoArrowPaint( dc.m_hDC );
      }
      return 0;
   }
   void DoArrowPaint(CDCHandle dc)
   {
      // Paint the arrow
      dc.FillRgn(m_rgnArrow, m_brBack );
      dc.FrameRgn(m_rgnArrow, m_brText, 1, 1 );
      // Remove the "black" border-line between the arrow and the
      // balloon. Clearly this is a nasty hack...
      CPen pen;
      pen.CreatePen(PS_SOLID, 1, m_clrBack);
      HPEN hOldPen = dc.SelectPen(pen);
      dc.MoveTo(m_rcCollision.left, m_rcCollision.top);
      dc.LineTo(m_rcCollision.right, m_rcCollision.bottom);
      dc.SelectPen(hOldPen);
   }
};


#endif // !defined(AFX_BALLOONTIP_H__20020228_7248_7790_4F55_0080AD509054__INCLUDED_)

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -