Click here to Skip to main content
15,891,423 members
Articles / Desktop Programming / MFC

Control Message Bar

Rate me:
Please Sign up or sign in to vote.
5.00/5 (47 votes)
23 Oct 2008CPOL10 min read 90.4K   3.3K   140  
Code to add a message bar to virtually any existing Windows control.
////////////////////////////////////////////////////////////////////////////
// File:	CtrlMessageBar.cpp
// Version:	1.0
// Created:	08-Oct-2008
//
// Author:	Paul S. Vickery
// E-mail:	paul@vickeryhome.freeserve.co.uk
//
// Class to add a message-bar to (virtually) any control.
//
// You are free to use or modify this code, with no restrictions, other than
// you continue to acknowledge me as the original author in this source code,
// or any code derived from it.
//
// If you use this code, or use it as a base for your own code, it would be 
// nice to hear from you simply so I know it's not been a waste of time!
//
// Copyright (c) 2005-2008 Paul S. Vickery
//
////////////////////////////////////////////////////////////////////////////
// Version History:
//
// Version 1.0 - 08-Oct-2008
// =========================
// Initial version
// 
////////////////////////////////////////////////////////////////////////////
// PLEASE LEAVE THIS HEADER INTACT
////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "CtrlMessageBar.h"


#if _MFC_VER >= 0x0700
  #pragma warning( disable : 4312 ) // 'variable' : conversion from 'type' to 'type' of greater size
  #pragma warning( disable : 4311 )  // 'variable' : pointer truncation from 'type' to 'type'
#endif

#define ID_TOOLTIP 123

#ifndef WM_QUERYAFXWNDPROC
  #define WM_QUERYAFXWNDPROC  0x0360  
#endif

static const UINT s_nMsgGetMessageBarHeight = RegisterWindowMessage(_T("CtrlMessageBar_GetMessageBarHeight"));

static const TCHAR szInstancePropertyName[] = _T("CtrlMessageBarInstance");

IMPLEMENT_DYNAMIC(_CTempWndSubclass, CWnd)

static LRESULT CALLBACK _PostSubclassHeaderWndProc(
	CWnd* pWnd, HWND /*hWnd*/, UINT nMsg, WPARAM wParam, LPARAM lParam, LRESULT lResult)
{
  if (pWnd != NULL && pWnd->m_hWnd != NULL && ::IsWindow(pWnd->m_hWnd))
  {
    if (nMsg == HDM_LAYOUT)
    {
      int nExtraHeight = 0;
      // if the parent window is a CListCtrl, then this is the list's header
      // so we need to find out the height of the list's message bar
      // so we can adjust the header layout results
      CListCtrl* pListCtrl = DYNAMIC_DOWNCAST(CListCtrl, pWnd->GetParent());
      if (pListCtrl != NULL)
	nExtraHeight = (int)pListCtrl->SendMessage(s_nMsgGetMessageBarHeight);
      LPHDLAYOUT lphdl = (LPHDLAYOUT)lParam;
      lphdl->prc->bottom -= nExtraHeight;
    }

    // TODO: if the control that the message bar is added to has its own idea of what to put 
    // in the non-client area, then code may need to be added in here to tell it to use a 
    // different area from the one it has calculated for itself, as is the case above with 
    // list controls, which use the non-client area to position the header, and send an 
    // HDM_LAYOUT message which allows us to re-position the list's idea of where the header 
    // should be.

    CCtrlMessageBar* pBar = CCtrlMessageBar::GetMessageBarCtrl(pWnd);
    if (pBar != NULL)
      lResult = pBar->DoWindowProc(nMsg, wParam, lParam, lResult);
  }
  return lResult;
}

LRESULT AFXAPI OurCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,	WPARAM wParam, LPARAM lParam)
{
  LRESULT lResult = AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
  lResult = _PostSubclassHeaderWndProc(pWnd, hWnd, nMsg, wParam, lParam, lResult);
  return lResult;
}

LRESULT CALLBACK OurWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
  // special message which identifies the window as using AfxWndProc
  if (nMsg == WM_QUERYAFXWNDPROC)
    return 1;

  // all other messages route through message map
  CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

  ASSERT(pWnd != NULL);
  ASSERT(pWnd->m_hWnd == hWnd);

  return OurCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}

BOOL CCtrlMessageBarDblSubclassWnd::SubclassWindow(HWND hWnd, CCtrlMessageBar* pBar/*=NULL*/)
{
  if (hWnd == NULL)
    return FALSE;
  CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); // if not subclassed once, let us subclass.
  if (pWnd == NULL)
  {
    _CTempWndSubclass *pNewWnd = new _CTempWndSubclass;
    ASSERT(pNewWnd != NULL);
    VERIFY(pNewWnd->SubclassWindow(hWnd));
    ASSERT(CWnd::FromHandlePermanent(hWnd) != NULL);
  }
  if (IsSubclassed(hWnd)) // we've already subclassed it
    return TRUE;
  // attach the bar to the window, using properties
  SetProp(hWnd, szInstancePropertyName, (HANDLE)pBar);
#ifdef _DEBUG
  WNDPROC oldWndProc = (WNDPROC)
#endif // _DEBUG
      ::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)OurWndProc);
  ASSERT(oldWndProc == AfxGetAfxWndProc());
  ASSERT(oldWndProc != OurWndProc);
  return TRUE;
}

void CCtrlMessageBarDblSubclassWnd::UnSubclassWindow(HWND hWnd)
{
  if (hWnd == NULL)
    return;
  if (IsSubclassed(hWnd))
    SetWindowLong(hWnd, GWL_WNDPROC, (LONG)AfxGetAfxWndProc());
}

BOOL CCtrlMessageBarDblSubclassWnd::IsSubclassed(HWND hWnd)
{
  ASSERT(::IsWindow(hWnd));
  WNDPROC WndProc = (WNDPROC)::GetWindowLong(hWnd, GWL_WNDPROC);
  return (WndProc == OurWndProc); // subclassed by us
}

/////////////////////////////////////////////////////////////////////////////
// _CTempWndSubclass

long _CTempWndSubclass::_m_lObjectCount = 0;

BEGIN_MESSAGE_MAP(_CTempWndSubclass, CWnd)
	//{{AFX_MSG_MAP(_CTempWndSubclass)
	ON_WM_NCDESTROY()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

#if _MFC_VER >= 0x0700
  #pragma warning( default : 4312 )
  #pragma warning( default : 4311 )
#endif

/////////////////////////////////////////////////////////////////////////////
// CCtrlMessageBar

static LPCTSTR const CLOSE_BUTTON_FONT = _T("Marlett");
static UINT const CLOSE_BUTTON_FONT_SIZE = 90;	// 9 pt
static LPCTSTR const CLOSE_BUTTON_CHAR = _T("r"); // close button 'X' in Marlett font
static UINT const CLOSE_BUTTON_DRAW_FLAGS = (DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE);

static const DWORD s_dwSWPFlags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE/* | SWP_SHOWWINDOW*/;

CCtrlMessageBar::CCtrlMessageBar()
{
  m_pCtrlMessageBarThis = NULL;
  m_bShowMessageBar = FALSE;
  m_nMessageBarHeight = 0;
  m_nMessageBarImage = (UINT)-1;
  m_bMessageBarCloseButton = TRUE;
  m_bWrapMessageBarText = FALSE;
  m_bTextTruncated = FALSE;
  m_bHighlightOnMouseOver = FALSE;
  m_bMouseHover = FALSE;
  m_crBackgroundNormal = CLR_DEFAULT;
  m_crTextNormal = CLR_DEFAULT;
  m_crBackgroundHilite = CLR_DEFAULT;
  m_crTextHilite = CLR_DEFAULT;
  m_bResize = FALSE;
  m_pfnShowMenuCallback = NULL;
}

CCtrlMessageBar::~CCtrlMessageBar()
{
  m_imlMessageBar.DeleteImageList();
}

// CCtrlMessageBar member functions

BOOL CCtrlMessageBar::Attach(CWnd* pCtrl)
{
  m_pCtrlMessageBarThis = pCtrl;
  if (pCtrl != NULL && pCtrl->m_hWnd != NULL && ! CCtrlMessageBarDblSubclassWnd::IsSubclassed(pCtrl->m_hWnd))
    return CCtrlMessageBarDblSubclassWnd::SubclassWindow(pCtrl->m_hWnd, this);
  return FALSE;
}

BOOL CCtrlMessageBar::Attach(CWnd* pDlg, UINT nID)
{
  if (pDlg != NULL && pDlg->m_hWnd != NULL && ::IsWindow(pDlg->m_hWnd))
    return Attach(pDlg->GetDlgItem(nID));
  return FALSE;
}

CWnd* CCtrlMessageBar::Detach()
{
  CWnd* pCtrl = m_pCtrlMessageBarThis;
  if (m_pCtrlMessageBarThis != NULL && m_pCtrlMessageBarThis->m_hWnd != NULL)
  {
    // remove message bar from control, then un-subclass it
    Show(FALSE);
    CCtrlMessageBarDblSubclassWnd::UnSubclassWindow(m_pCtrlMessageBarThis->m_hWnd);
  }
  m_pCtrlMessageBarThis = NULL;
  return pCtrl;
}

IMPLEMENT_DYNAMIC(CCtrlMessageBar, CObject)

/*static*/CCtrlMessageBar* CCtrlMessageBar::GetMessageBarCtrl(CWnd* pCtrl)
{
  // get the message bar from the control, if it has one
  // if this wasn't set, or it's not a valid CCtrlMessageBar pointer, 
  // then it will return NULL
  CCtrlMessageBar* pBar = (CCtrlMessageBar*)::GetProp(pCtrl->m_hWnd, szInstancePropertyName);
  try {
    if (! AfxIsValidAddress(pBar, sizeof(CObject)) || 
	! pBar->IsKindOf(RUNTIME_CLASS(CCtrlMessageBar)))
      pBar = NULL;
  } catch(...) {
    pBar = NULL;
  }
  return pBar;
}

CRect CCtrlMessageBar::GetRect(BOOL bDrawing/*=FALSE*/) const
{
  // for drawing, the co-ords need to be relative to the 
  // top-left of the window. else get it in client co-ords
  CRect rc, rcClient;
  m_pCtrlMessageBarThis->GetWindowRect(rc);
  m_pCtrlMessageBarThis->ScreenToClient(rc);
  m_pCtrlMessageBarThis->GetClientRect(rcClient);
  if (bDrawing)  // we are drawing to the window dc, so adjust the co-ords
    rcClient.OffsetRect(-rc.left, -rc.top);
  rc.OffsetRect(-rc.left, -rc.top);
  CRect rcOffset;
  GetCtrlMessageBarOffset(rcOffset);
  rc.left = rcOffset.left + rcClient.left;  // take off left border
  rc.right -= rcClient.left;	// take off right border (assumes right and left are equal)
  rc.right += rcOffset.right;
  rc.top = rcClient.top - m_nMessageBarHeight;  // take off top border
  rc.top += rcOffset.top;
  rc.bottom = rc.top + m_nMessageBarHeight; // set the height to the message bar height
  rc.bottom += rcOffset.bottom;
  if (! bDrawing)
  {
    rc.right -= 2;
    rc.bottom -= 2;
  }
  return rc;
}

CRect CCtrlMessageBar::GetCloseButtonRect(CRect& rc) const
{
  // given the message bar rect, return the close button rect
  if (! m_bMessageBarCloseButton)
    return CRect(rc.right, rc.top, rc.right, rc.top);
  CRect rcClose(rc);
  CDC* pDC = m_pCtrlMessageBarThis->GetDC();
  CFont font;
  font.CreatePointFont(CLOSE_BUTTON_FONT_SIZE, CLOSE_BUTTON_FONT, pDC);
  CFont* pFontOld = pDC->SelectObject(&font);
  pDC->DrawText(CLOSE_BUTTON_CHAR, rcClose, DT_CALCRECT | CLOSE_BUTTON_DRAW_FLAGS);
  pDC->SelectObject(pFontOld);
  rcClose.bottom += 1 + 1;
  rcClose.right += 2 + 2;
  // line up the top-right of the close button with the top-right of the message bar
  rcClose.OffsetRect(rc.right - rcClose.right, 0);
  // adjust the message bar text area
  rc.right = rcClose.left - 2;
  return rcClose;
}

void CCtrlMessageBar::SetImageList(CImageList* piml)
{
  m_imlMessageBar.DeleteImageList();
  if (piml != NULL)
    m_imlMessageBar.Create(piml);
}

void CCtrlMessageBar::SetText(LPCTSTR lpszText, UINT nImage/*=-1*/, BOOL bShow/*=TRUE*/)
{
  m_sMessageBarText = lpszText;
  m_nMessageBarImage = nImage;
  Show(bShow);
}

void CCtrlMessageBar::SetImage(UINT nImage, BOOL bShow/*=TRUE*/)
{
  m_nMessageBarImage = nImage;
  Show(bShow);
}

void CCtrlMessageBar::SetColours(COLORREF crBackgroundNormal/*=CLR_DEFAULT*/, 
				 COLORREF crTextNormal/*=CLR_DEFAULT*/, 
				 COLORREF crBackgroundHilite/*=CLR_DEFAULT*/, 
				 COLORREF crTextHilite/*=CLR_DEFAULT*/)
{
  m_crBackgroundNormal = crBackgroundNormal;
  m_crTextNormal = crTextNormal;
  m_crBackgroundHilite = crBackgroundHilite;
  m_crTextHilite = crTextHilite;
  m_pCtrlMessageBarThis->SetWindowPos(NULL, 0, 0, 0, 0, s_dwSWPFlags);
}

void CCtrlMessageBar::GetColours(COLORREF& crBackgroundNormal, 
				 COLORREF& crTextNormal, 
				 COLORREF& crBackgroundHilite, 
				 COLORREF& crTextHilite) const
{
  crBackgroundNormal = m_crBackgroundNormal;
  crTextNormal = m_crTextNormal;
  crBackgroundHilite = m_crBackgroundHilite;
  crTextHilite = m_crTextHilite;
}

void CCtrlMessageBar::Show(BOOL bShow/*=TRUE*/)
{
  _ASSERTE(m_pCtrlMessageBarThis != NULL);
  if (m_pCtrlMessageBarThis != NULL)
  {
    if (m_bResize)
    {
      UINT nMsgHeight = 0;
      CRect rcCtrl;
      m_pCtrlMessageBarThis->GetWindowRect(&rcCtrl);
      // we need to resize the dialog to make it fit
      if (bShow && ! IsShown())
      {
	m_bShowMessageBar = bShow;
	// use SetWindowPos to make sure the frame gets re-calc'd
	m_pCtrlMessageBarThis->SetWindowPos(NULL, 0, 0, 0, 0, s_dwSWPFlags);
	nMsgHeight = GetMessageHeight();
	rcCtrl.bottom += nMsgHeight;
      }
      else if (! bShow && IsShown())
      {
	nMsgHeight = GetMessageHeight();
	m_bShowMessageBar = bShow;
	// use SetWindowPos to make sure the frame gets re-calc'd
	m_pCtrlMessageBarThis->SetWindowPos(NULL, 0, 0, 0, 0, s_dwSWPFlags);
	rcCtrl.bottom -= nMsgHeight;
      }
      if (m_pCtrlMessageBarThis->GetStyle() & WS_CHILD && m_pCtrlMessageBarThis->GetParent() != NULL)
	m_pCtrlMessageBarThis->GetParent()->ScreenToClient(&rcCtrl);
      m_pCtrlMessageBarThis->MoveWindow(rcCtrl);
    }
    m_bShowMessageBar = bShow;

    // use SetWindowPos to make sure the frame gets re-calc'd
    m_pCtrlMessageBarThis->SetWindowPos(NULL, 0, 0, 0, 0, s_dwSWPFlags);
    // ListView controls don't redraw properly when the bar is hidden
    // so update its items
    CListCtrl* pListCtrl = DYNAMIC_DOWNCAST(CListCtrl, m_pCtrlMessageBarThis);
    if (pListCtrl != NULL)
      pListCtrl->Update(-1);
    // make sure the client area gets re-painted
    m_pCtrlMessageBarThis->Invalidate();
  }
}

static const DWORD s_dwDrawTextFlags = DT_SINGLELINE | DT_NOPREFIX | DT_LEFT | DT_END_ELLIPSIS;
static const DWORD s_dwDrawTextFlagsWrap = DT_WORDBREAK | DT_NOPREFIX | DT_LEFT;

#ifndef DFCS_TRANSPARENT
#define DFCS_TRANSPARENT        0x0800
#define DFCS_HOT                0x1000
#endif // DFCS_TRANSPARENT

void CCtrlMessageBar::DoNcPaint()
{
  if (m_pCtrlMessageBarThis == NULL)
    return;
  if (! m_bShowMessageBar && m_nMessageBarHeight > 0)
    m_pCtrlMessageBarThis->SetWindowPos(NULL, 0, 0, 0, 0, s_dwSWPFlags);
  if (! m_bShowMessageBar || m_sMessageBarText.IsEmpty() || m_nMessageBarHeight == 0)
    return;

  CDC* pDC = m_pCtrlMessageBarThis->GetWindowDC();

  COLORREF crBackground = m_bMouseHover ? (m_crBackgroundHilite == CLR_DEFAULT ? 
				GetSysColor(COLOR_HIGHLIGHT) : m_crBackgroundHilite) : 
			    (m_crBackgroundNormal == CLR_DEFAULT ? 
				GetSysColor(COLOR_INFOBK) : m_crBackgroundNormal);
  COLORREF crText = m_bMouseHover ? (m_crTextHilite == CLR_DEFAULT ? 
				GetSysColor(COLOR_HIGHLIGHTTEXT) : m_crTextHilite) : 
			    (m_crTextNormal == CLR_DEFAULT ? 
				GetSysColor(COLOR_INFOTEXT) : m_crTextNormal);

  if (pDC != NULL)
  {
    CRect rc = GetRect(TRUE); // get rect in window co-ords
    pDC->FillSolidRect(rc, crBackground);
    COLORREF crBkOld = pDC->SetBkColor(crBackground);
    COLORREF crTextOld = pDC->SetTextColor(crText);
    CFont* pFontOld = pDC->SelectObject(m_pCtrlMessageBarThis->GetFont());
    rc.left += 2; // offset the text a little
    rc.right -= 1;

    if (m_nMessageBarImage != -1 && m_imlMessageBar.m_hImageList != NULL)
    {
      rc.top += 2;
      CPoint pt(rc.TopLeft());
      m_imlMessageBar.Draw(pDC, m_nMessageBarImage, pt, ILD_NORMAL);
      IMAGEINFO ii;
      m_imlMessageBar.GetImageInfo(m_nMessageBarImage, &ii);
      rc.left += abs(ii.rcImage.right - ii.rcImage.left) + 2;
    }

    if (m_bMessageBarCloseButton)
    {
      CRect rcClose = GetCloseButtonRect(rc);
      CFont font;
      font.CreatePointFont(CLOSE_BUTTON_FONT_SIZE, CLOSE_BUTTON_FONT, pDC);
      CFont* pFontOld2 = pDC->SelectObject(&font);
      pDC->DrawText(CLOSE_BUTTON_CHAR, rcClose, CLOSE_BUTTON_DRAW_FLAGS);
      pDC->SelectObject(pFontOld2);
    }

    CString sText(m_sMessageBarText);
    pDC->DrawText(sText.GetBuffer(sText.GetLength() + 4), -1, &rc, m_bWrapMessageBarText ? s_dwDrawTextFlagsWrap : s_dwDrawTextFlags | DT_MODIFYSTRING);
    sText.ReleaseBuffer();
    m_bTextTruncated = (sText != m_sMessageBarText);
    pDC->SetBkColor(crBkOld);
    pDC->SetTextColor(crTextOld);
    pDC->SelectObject(pFontOld);
    if (m_bTextTruncated)
    {
      // add tool-tip tool
      if (m_tipMessageBar.m_hWnd == NULL || ! ::IsWindow(m_tipMessageBar.m_hWnd))
	m_tipMessageBar.Create(m_pCtrlMessageBarThis);
      m_tipMessageBar.DelTool(m_pCtrlMessageBarThis, ID_TOOLTIP);
      m_tipMessageBar.AddTool(m_pCtrlMessageBarThis, m_sMessageBarText, GetRect(), ID_TOOLTIP);
    }
  }
}

void CCtrlMessageBar::DoNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp)
{
  m_nMessageBarHeight = 0;
  if (m_bShowMessageBar && bCalcValidRects && ! m_sMessageBarText.IsEmpty())
  {
    int nHeight = 15;
    int nHeightMin = 0;
    int nLeftOffset = 2;
    CFont* pFont = m_pCtrlMessageBarThis->GetFont();
    CDC* pDC = m_pCtrlMessageBarThis->GetDC();
    if (m_nMessageBarImage != -1 && m_imlMessageBar.m_hImageList != NULL)
    {
      IMAGEINFO ii;
      m_imlMessageBar.GetImageInfo(m_nMessageBarImage, &ii);
      nHeightMin = abs(ii.rcImage.bottom - ii.rcImage.top);
      nLeftOffset += abs(ii.rcImage.right - ii.rcImage.left) + 2 + 2;
      nHeightMin += 2;
    }
    if (m_bMessageBarCloseButton)
    {
      CFont font;
      font.CreatePointFont(CLOSE_BUTTON_FONT_SIZE, CLOSE_BUTTON_FONT, pDC);
      CFont* pFontOld2 = pDC->SelectObject(&font);
      CRect rcClose(0, 0, 1, 1);
      pDC->DrawText(CLOSE_BUTTON_CHAR, rcClose, DT_CALCRECT | CLOSE_BUTTON_DRAW_FLAGS);
      nHeightMin = max(nHeightMin, rcClose.Height() + 1 + 1);
      nLeftOffset += rcClose.Width() + 2 + 2;
      pDC->SelectObject(pFontOld2);
    }
    if (pDC != NULL)
    {
      CFont* pFontOld = pDC->SelectObject(pFont);
      CRect rc;
      m_pCtrlMessageBarThis->GetClientRect(&rc);
      rc.left += nLeftOffset;
      nHeight = pDC->DrawText(m_sMessageBarText, rc, DT_CALCRECT | (m_bWrapMessageBarText ? s_dwDrawTextFlagsWrap : s_dwDrawTextFlags));
      pDC->SelectObject(pFontOld);
    }
    nHeight += 2;
    nHeight = max(nHeight, nHeightMin);
    nHeight += 2;
    lpncsp->rgrc[0].top += nHeight;
    m_nMessageBarHeight = nHeight;
  }
}

UINT CCtrlMessageBar::DoNcHitTest(CPoint point, UINT nCode)
{
  if (nCode == HTNOWHERE || nCode == HTCAPTION)
  {
    // lets see if we really want to consider it as being in the message-bar
    CRect rc = GetRect(); // get rect in client co-ords
    CPoint pt(point);
    m_pCtrlMessageBarThis->ClientToScreen(&rc);
    CRect rcClose = GetCloseButtonRect(rc);
    if (rcClose.PtInRect(pt) && m_bMessageBarCloseButton)
      nCode = HTMESSAGEBARCLOSE;
    else if (rc.PtInRect(pt))
      nCode = HTMESSAGEBAR;
  }
  return nCode;
}

void CCtrlMessageBar::DoNcMouseButtonMsg(UINT nMsg, UINT nHitTest, CPoint point)
{
#if _DEBUG
  if (nHitTest == HTMESSAGEBAR)
    TRACE("Mouse down on HTMESSAGEBAR\n");
#endif // _DEBUG
  if (nHitTest == HTMESSAGEBARCLOSE && nMsg == WM_NCLBUTTONDOWN)
    Show(FALSE);
  else if ((nHitTest == HTMESSAGEBAR || nHitTest == HTMESSAGEBARCLOSE) && (nMsg == WM_NCRBUTTONDOWN || nMsg == WM_NCLBUTTONDOWN))
  {
    if (m_pfnShowMenuCallback == NULL || ! m_pfnShowMenuCallback(this, m_pCtrlMessageBarThis, nHitTest, point))
    {
      // show a menu to allow the bar to be hidden
      CMenu menu;
      menu.CreatePopupMenu();
      menu.AppendMenu(MF_STRING, 1, _T("Hide"));
      int nCmd = menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON | 
						  TPM_NONOTIFY | TPM_RETURNCMD, 
						  point.x, point.y, m_pCtrlMessageBarThis, NULL);
      if (nCmd == 1)
	Show(FALSE);
    }
  }
}

#ifndef WM_NCMOUSELEAVE
#define WM_NCMOUSELEAVE	0x02A2
#endif // WM_NCMOUSELEAVE
#ifndef TME_NONCLIENT
#define TME_NONCLIENT 0x10
#endif // TME_NONCLIENT
#ifndef TrackMouseEvent
#define TrackMouseEvent _TrackMouseEvent
#endif // TrackMouseEvent

LRESULT CCtrlMessageBar::DoWindowProc(UINT message, WPARAM wParam, LPARAM lParam, LRESULT lResult) 
{
  // respond to our special message to get the message bar height
  if (message == s_nMsgGetMessageBarHeight)
    return m_nMessageBarHeight;
  if (m_tipMessageBar.m_hWnd != NULL && ::IsWindow(m_tipMessageBar.m_hWnd))
  {
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));
    msg.hwnd = m_pCtrlMessageBarThis->m_hWnd;
    msg.lParam = lParam;
    msg.message = message;
    msg.wParam = wParam;
    m_tipMessageBar.RelayEvent(&msg);
  }
  switch (message)
  {
  case WM_CREATE:
    if (::IsWindow(m_pCtrlMessageBarThis->m_hWnd))
      m_tipMessageBar.Create(m_pCtrlMessageBarThis);
    break;

  case WM_NCLBUTTONDOWN:
  case WM_NCMBUTTONDOWN:
  case WM_NCRBUTTONDOWN:
  case WM_NCLBUTTONUP:
  case WM_NCMBUTTONUP:
  case WM_NCRBUTTONUP:
  case WM_NCLBUTTONDBLCLK:
  case WM_NCMBUTTONDBLCLK:
  case WM_NCRBUTTONDBLCLK:
    DoNcMouseButtonMsg(message, (UINT)wParam, CPoint(lParam));
    break;

  case WM_NCCALCSIZE:
    {
      // If the window is a CListCtrl, then we need to intercept the 
      // HDM_LAYOUT message that it sends to its header so that we can 
      // adjust the layout, so subclass its header.
      // The header may already be subclassed, so we need to use a double-
      // subclassing technique to make sure we don't break its existing subclassing
      CListCtrl* pListCtrl = DYNAMIC_DOWNCAST(CListCtrl, m_pCtrlMessageBarThis);
      if (pListCtrl != NULL && pListCtrl->m_hWnd != NULL && ! CCtrlMessageBarDblSubclassWnd::IsSubclassed(pListCtrl->m_hWnd))
	CCtrlMessageBarDblSubclassWnd::SubclassWindow(::GetDlgItem(pListCtrl->m_hWnd, 0));
    }
    DoNcCalcSize((BOOL)wParam, (NCCALCSIZE_PARAMS*)lParam);
    break;

  case WM_NCHITTEST:
    return (LRESULT)DoNcHitTest(CPoint(lParam), (UINT)lResult);
    break;

  case WM_NCPAINT:
    DoNcPaint();
    break;

  case WM_SIZE:
    m_pCtrlMessageBarThis->SetWindowPos(NULL, 0, 0, 0, 0, s_dwSWPFlags);
    break;

  case WM_VSCROLL:
    if (m_bShowMessageBar && m_pCtrlMessageBarThis->IsKindOf(RUNTIME_CLASS(CListCtrl)))
    {
      // ListView controls base their client areas on the window size, and 
      // so using WM_NCCALCSIZE to resize the client causes the scrolling 
      // to go wrong. There doesn't seem to be any way of influencing this, 
      // so the best we can do is to fiddle the scrolling to behave the 
      // way it should be. This, however, still isn't perfect and the 
      // window scrolls back up a little after scrolling to the very bottom.
      CListCtrl* pList = (CListCtrl*)m_pCtrlMessageBarThis;
      UINT nStyle = pList->GetStyle() & LVS_TYPEMASK;
      if (nStyle == LVS_ICON || nStyle == LVS_SMALLICON)
      {
	SCROLLINFO si = { sizeof(si), SIF_RANGE };
	pList->GetScrollInfo(SB_VERT, &si);
	CRect rcView;
	pList->GetViewRect(&rcView);
	si.nMax = rcView.Height() + m_nMessageBarHeight;
	pList->SetScrollInfo(SB_VERT, &si);
      }
    }
    break;

  case WM_NCMOUSEMOVE:
  case WM_NCMOUSELEAVE:
  case WM_MOUSELEAVE:
    // if we're over the bar, then set the bar to draw highlighted
    if (m_bHighlightOnMouseOver)
    {
      if (message == WM_NCMOUSEMOVE)
      {
	// make sure we get leave
	TRACKMOUSEEVENT tme;
	tme.cbSize = sizeof(TRACKMOUSEEVENT);
	tme.dwFlags = TME_LEAVE | TME_NONCLIENT;
	tme.dwHoverTime = HOVER_DEFAULT;
	tme.hwndTrack = m_pCtrlMessageBarThis->m_hWnd;
	TrackMouseEvent(&tme);
      }
      BOOL bOldMouseOver = m_bMouseHover;
      if (message == WM_NCMOUSEMOVE && DoNcHitTest(CPoint(lParam), (UINT)lResult) == HTMESSAGEBAR)
	m_bMouseHover = TRUE;
      else
	m_bMouseHover = FALSE;
      if (m_bMouseHover != bOldMouseOver)
	m_pCtrlMessageBarThis->SetWindowPos(NULL, 0, 0, 0, 0, s_dwSWPFlags);
    }
    break;
  }
  return lResult;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
Originally from an electronics background, I moved into software in 1996, partly as a result of being made redundant, and partly because I was very much enjoying the small amount of coding (in-at-the-deep-end-C) that I had been doing!

I swiftly moved from C to C++, and learned MFC, and then went on to real-time C on Unix. After this I moved to the company for which I currently work, which specialises in Configuration Management software, and currently program mainly in C/C++, for Windows. I have been gradually moving their legacy C code over to use C++ (with STL, MFC, ATL, and WTL). I have pulled in other technologies (Java, C#, VB, COM, SOAP) where appropriate, especially when integrating with third-party products.

In addition to that, I have overseen the technical side of the company website (ASP, VBScript, JavaScript, HTML, CSS), and have also worked closely with colleagues working on other products (Web-based, C#, ASP.NET, SQL, etc).

For developing, I mainly use Visual Studio 2010, along with an in-house-designed editor based on Andrei Stcherbatchenko's syntax parsing classes, and various (mostly freeware) tools. For website design, I use Dreaweaver CS3.

When not developing software, I enjoy listening to and playing music, playing electric and acoustic guitars and mandolin.

Comments and Discussions