Click here to Skip to main content
15,879,348 members
Articles / Desktop Programming / MFC

LedButton Status Control (Owner-draw)

Rate me:
Please Sign up or sign in to vote.
4.70/5 (35 votes)
17 Jan 2005CPOL10 min read 190.9K   15.9K   142  
Read-Only button control to display LED statuses (activity, multi-state, etc.).
//                                                                -*- C++ -*-
// ==========================================================================
//!
//! \file LedButton.cpp
//!
//! \brief Implementation of the CLedButton Class.
//!
//! \author 
//!    Ricky Marek <A HREF="mailto:ricky.marek@gmail.com">ricky.marek@gmail.com</A>
//!
//!	\par Disclaimer
//!    This code and the accompanying files are provided <b>"as is"</b> with
//!    no expressed  or  implied warranty.  No responsibilities  for possible
//!    damages, or side effects in its functionality.  The  user must assume 
//!    the entire risk of using this code.  The author accepts no  liability
//!    if it causes any damage to your computer, causes your pet to fall ill, 
//!    increases baldness or makes your car  start  emitting  strange noises 
//!    when you start it up.  <i>This code  has no bugs,  just  undocumented 
//!    features!.</i>
//!
//! \par Terms of use
//!    This code is <b>free</b> for personal use, or freeware applications as
//!    long as this comment-header  header remains like this.  If you plan to 
//!    use  this  code in  a commercial  or shareware  application,   you are 
//!    politely  asked  to  contact the author for his permission via e-mail. 
//!    From: <A HREF="mailto:ricky.marek@gmail.com">ricky.marek@gmail.com</A>
//!
//! \par Attributes
//!    \li \b Created       16/Aug/2002
//!    \li \b Last-Updated  16/Dec/2004
//!    \li \b Compiler      Visual C++
//!    \li \b Requirements  Win98/Win2k or later, MFC.
//!    \li \b Tested        with Visual C++ 6.0 and 7.1(.NET 2003)
//!
//!
// ==========================================================================


// ///////////////////////////////////////////////////////////////////////////
// Header Files
// ///////////////////////////////////////////////////////////////////////////
#include "StdAfx.h" // should be the 1st include ...
#include "LedButton.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif


#ifndef BS_TYPEMASK
# define BS_TYPEMASK  0x0000000F
#endif // BS_TYPEMASK

// ###########################################################################
// ##                                                                       ##
// ##                   C L e d B u t t o n   C l a s s                     ##
// ##                                                                       ##
// ###########################################################################

// ///////////////////////////////////////////////////////////////////////////
// Default Constructor (Public)
// ///////////////////////////////////////////////////////////////////////////
IMPLEMENT_DYNAMIC(CLedButton, CButton)
CLedButton::CLedButton()
{
    m_ledDataList.RemoveAll();

    m_listCount        = 0;
    m_ledState         = LED_BUTTON_STATE_OFF;
    m_isDisabled       = FALSE;
    m_buttonStyle      = BS_TYPEMASK;
    m_activityTimer    = NULL;
    m_activityDuration = LED_BUTTON_DEFAULT_ACTIVITY_DURATION;
    m_activityId       = 0;
    m_activityState    = LED_BUTTON_STATE_OFF;
    m_pCondition       = NULL;

    SetLedStatesNumber(LED_BUTTON_PREDEFINED_STATES_NUMBER, false);
}


// ///////////////////////////////////////////////////////////////////////////
// Default Destructor (Public)
// ///////////////////////////////////////////////////////////////////////////
CLedButton::~CLedButton()
{

    for(LedState ledState=0; ledState < m_listCount; ledState++)
    {
        RemoveIcon(ledState);
    }
    
    if (NULL != m_activityTimer)
    {
        KillTimer(m_activityTimer);
        m_activityTimer = NULL;
    }

    m_ledDataList.RemoveAll();
    m_listCount = 0;

}


// ///////////////////////////////////////////////////////////////////////////
// Message Map
// ///////////////////////////////////////////////////////////////////////////

BEGIN_MESSAGE_MAP(CLedButton, CButton)
    ON_WM_DESTROY()
    ON_WM_SYSCOLORCHANGE()
    ON_MESSAGE(BM_SETSTYLE, OnSetStyle)
    ON_WM_CTLCOLOR_REFLECT()
    ON_WM_TIMER()
END_MESSAGE_MAP()


// ###########################################################################
// ##                                                                       ##
// ##               L E D   S T A T E   A T T R I B U T E S                 ##
// ##                                                                       ##
// ###########################################################################

// ///////////////////////////////////////////////////////////////////////////
// SetLedStatesNumber (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::SetLedStatesNumber(int maxLedStatesNumber, bool isInvalidate /*=true*/)
{
    ASSERT(maxLedStatesNumber > 0);
	LedState ledState;

    for(ledState=0; ledState < m_listCount; ledState++)
    {
        RemoveIcon(ledState);
    }

    m_ledDataList.RemoveAll();
    m_listCount = maxLedStatesNumber;
    m_ledDataList.SetSize(maxLedStatesNumber);

    LedData *pData = m_ledDataList.GetData();
    
    for(ledState=0; ledState < m_listCount; ledState++)
    {
        ::ZeroMemory(&(pData[ledState]), sizeof(LedData));

    }

    RestoreDefaultColors(isInvalidate);

}



// ///////////////////////////////////////////////////////////////////////////
// SetLedState (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::SetLedState(LedState ledState, bool isForcedChange /*=false*/)
{
    if (NULL != m_pCondition)
    {
        ledState = m_pCondition->ChangeState(ledState, m_ledState, isForcedChange);
    }

    //
    // the following is added to remove flickering when the led is called
    // with the same led state as it is displaying.
    //
    if (ledState == m_ledState)
    {
        return;
    }

    if (ledState < m_listCount)
    {
        m_ledState = ledState;
    }

    if (0 != m_activityId)
    {
        //
        // Kill Timer if running.
        //
        if (NULL != m_activityTimer)
        {
            KillTimer(m_activityTimer);
            m_activityTimer = NULL;

        }
     
        if (m_ledState != m_activityState)
        {
            m_activityTimer = SetTimer(m_activityId, m_activityDuration, NULL);
        }
    }

    Invalidate();

}


// ///////////////////////////////////////////////////////////////////////////
// SetLedActivityTimer (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::SetLedActivityTimer(UINT timerId, 
                                     int duration /* =LED_BUTTON_DEFAULT_ACTIVITY_DURATION */, 
                                     LedState offState /*=LED_BUTTON_STATE_OFF*/)
{
    if (NULL != m_activityTimer)
    {
        KillTimer(m_activityTimer);
        m_activityTimer = NULL;
    }

    m_activityId       = timerId;
    m_activityDuration = duration;
    m_activityState    = offState;

}



// ///////////////////////////////////////////////////////////////////////////
// SetLedStateCondition (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::SetLedStateCondition(CLedStateCondition *pCondition)
{
    m_pCondition = pCondition;
    SetLedState(m_ledState, true);
}


// ###########################################################################
// ##                                                                       ##
// ##                    I C O N   O P E R A T I O N S                      ##
// ##                                                                       ##
// ###########################################################################

// ///////////////////////////////////////////////////////////////////////////
// SetIcon(1) (Public)
// ///////////////////////////////////////////////////////////////////////////
bool CLedButton::SetIcon(LedState ledState, UINT iconId, int width/*=0*/, int height/*=0*/)
{
    ASSERT(ledState < m_listCount);  // No room for this Led?.

    if (ledState < m_listCount)
    {
	    HINSTANCE hInstResource = AfxFindResourceHandle(MAKEINTRESOURCE(iconId), RT_GROUP_ICON);
        HICON     hIcon = (HICON)::LoadImage(hInstResource, MAKEINTRESOURCE(iconId), IMAGE_ICON, width,height,0);

        return(SetIcon(ledState, hIcon));

    }

    return(false);

}

// ///////////////////////////////////////////////////////////////////////////
// SetIcon(2) (Public)
// ///////////////////////////////////////////////////////////////////////////
bool CLedButton::SetIcon(LedState ledState, HICON hIcon)
{
    ASSERT(ledState < m_listCount);  // No room for this Led?

    if (ledState < m_listCount)
    {
        RemoveIcon(ledState);

        if (hIcon)
        {
      	    ICONINFO	iconInfo;

            m_ledDataList[ledState].hIcon = hIcon;

		    ::ZeroMemory(&iconInfo, sizeof(ICONINFO));

		    if (FALSE == ::GetIconInfo(hIcon, &iconInfo))
            {
                RemoveIcon(ledState);
                return(false);
            }

		    m_ledDataList[ledState].width  = iconInfo.xHotspot * 2;
		    m_ledDataList[ledState].height = iconInfo.yHotspot * 2;

		    ::DeleteObject(iconInfo.hbmMask);
		    ::DeleteObject(iconInfo.hbmColor);

        }
    }

    return(ledState < m_listCount);


}

// ///////////////////////////////////////////////////////////////////////////
// SetIcons (Public)
// ///////////////////////////////////////////////////////////////////////////
bool CLedButton::SetIcons(UINT offIconId, UINT onIconId)
{
    bool retVal = true;  // Optimistic..

    if ((offIconId > 0) && (m_listCount > 0))
    {
        retVal = SetIcon(LED_BUTTON_STATE_OFF, offIconId);
    }

    if ((retVal) && (onIconId > 0) && (m_listCount > 0))
    {
        retVal = SetIcon(LED_BUTTON_STATE_ON, onIconId);
    }

    return(retVal);

}

// ///////////////////////////////////////////////////////////////////////////
// RemoveIcon (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::RemoveIcon(LedState ledState)
{
    ASSERT(ledState < m_listCount);  // No room for this Led?

    if (ledState < m_listCount)
    {
        if (NULL != m_ledDataList[ledState].hIcon)
        {
            ::DestroyIcon(m_ledDataList[ledState].hIcon);
            m_ledDataList[ledState].hIcon = NULL;
        }

        m_ledDataList[ledState].height = 0;
        m_ledDataList[ledState].width = 0;
    
    }

}


// ###########################################################################
// ##                                                                       ##
// ##     T E X T   A T T R I B U T E S   A N D   O P E R A T I O N S       ##
// ##                                                                       ##
// ###########################################################################


// ///////////////////////////////////////////////////////////////////////////
// RestoreDefaultColors (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::RestoreDefaultColors(bool isInvalidate /* =true */)
{

    for(LedState ledState = 0; ledState < m_listCount; ledState++)
    {
        m_ledDataList[ledState].textForegroundColor = ::GetSysColor(COLOR_BTNTEXT);
        m_ledDataList[ledState].textBackgroundColor = ::GetSysColor(COLOR_BTNFACE);
    }

	if (isInvalidate)
    {
        Invalidate();
    }
}


// ///////////////////////////////////////////////////////////////////////////
// SetTextForeground (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::SetTextForeground(LedState ledState, COLORREF colorRef, bool isInvalidate /*=true*/)
{
    ASSERT(ledState < m_listCount);  // No room for this Led?

    if (ledState < m_listCount)
    {
        m_ledDataList[ledState].textForegroundColor = colorRef;
    }

    if (isInvalidate)
    {
        Invalidate();
    }

}


// ///////////////////////////////////////////////////////////////////////////
// SetTextBackground (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::SetTextBackground(LedState ledState, COLORREF colorRef, bool isInvalidate /*=true*/)
{
    ASSERT(ledState < m_listCount);  // No room for this Led?

    if (ledState < m_listCount)
    {
        m_ledDataList[ledState].textBackgroundColor = colorRef;
    }

    if (isInvalidate)
    {
        Invalidate();
    }

 
}


// ///////////////////////////////////////////////////////////////////////////
// SetTextColors (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::SetTextColors(LedState ledState, COLORREF fgColorRef, COLORREF bgColorRef, bool isInvalidate /*=true*/)
{
    ASSERT(ledState < m_listCount);  // No room for this Led?

    CLedButton::SetTextBackground(ledState, bgColorRef, false);
    CLedButton::SetTextForeground(ledState, fgColorRef, isInvalidate);
}


// ///////////////////////////////////////////////////////////////////////////
// GetTextForegroundColor (Public)
// ///////////////////////////////////////////////////////////////////////////
COLORREF CLedButton::GetTextForegroundColor(LedState ledState)
{
    ASSERT(ledState < m_listCount);  // No room for this Led?

    if (ledState < m_listCount)
    {
        return(m_ledDataList[ledState].textForegroundColor);
    }

    return(::GetSysColor(COLOR_BTNTEXT));
}


// ///////////////////////////////////////////////////////////////////////////
// GetTextBackgroundColor (Public)
// ///////////////////////////////////////////////////////////////////////////
COLORREF CLedButton::GetTextBackgroundColor(LedState ledState)
{
    ASSERT(ledState < m_listCount);  // No room for this Led?

    if (ledState < m_listCount)
    {
        return(m_ledDataList[ledState].textBackgroundColor);
    }

    return(::GetSysColor(COLOR_BTNFACE));
}


// ###########################################################################
// ##                                                                       ##
// ##                      T O O L T I P   S T U F F                        ##
// ##                                                                       ##
// ###########################################################################


// ///////////////////////////////////////////////////////////////////////////
// ToolTipInit (Private)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::ToolTipInit(void)
{
	if (NULL == m_toolTip.m_hWnd)
	{
		m_toolTip.Create(this);
		m_toolTip.Activate(FALSE);
		m_toolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, 400);
	}

}


// ///////////////////////////////////////////////////////////////////////////
// SetTooltipText(1) (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::SetTooltipText(UINT id, bool isActivate /*=true*/)
{
   	CString text;
	
    if (text.LoadString(id))
    {
        SetTooltipText(text, isActivate);
    }

}
// ///////////////////////////////////////////////////////////////////////////
// SetTooltipText(2) (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::SetTooltipText(LPCTSTR text, bool isActivate /*=true*/)
{
	if (NULL == text)
    {
        return;
    }

	ToolTipInit();

	if (0 == m_toolTip.GetToolCount())
	{
		CRect rect; 
		GetClientRect(rect);
		m_toolTip.AddTool(this, text, rect, 1);
	} 

	m_toolTip.UpdateTipText(text, this, 1);
	m_toolTip.Activate(isActivate);

}

// ///////////////////////////////////////////////////////////////////////////
// ActivateTooltip (Public)
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::ActivateTooltip(bool isActivate /*=true*/)
{
	if (0 != m_toolTip.GetToolCount())
    {
    	m_toolTip.Activate(isActivate);
    }

    return;

}


// ###########################################################################
// ##                                                                       ##
// ##                   M E S S A G E   H A N D L E R S                     ##
// ##                                                                       ##
// ###########################################################################

// ///////////////////////////////////////////////////////////////////////////
// OnDestroy
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::OnDestroy()
{
    CButton::OnDestroy();

    //
    // Kill timer
    //
    if (NULL != m_activityTimer)
    {
        KillTimer(m_activityTimer);
        m_activityTimer = NULL;
    }

}



// ///////////////////////////////////////////////////////////////////////////
// DrawItem
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
   	CDC* pDC = CDC::FromHandle(lpDIS->hDC);

	m_isDisabled = ((lpDIS->itemState & ODS_DISABLED) != 0);

	CRect itemRect = lpDIS->rcItem;

	pDC->SetBkMode(TRANSPARENT);

	OnDrawBackground(pDC, &itemRect);

    CRect captionRect = lpDIS->rcItem;
    CRect ledRect;
  
    //
    // Now the cases are:
    // - BS_LEFTTEXT: The text appears on the left side of the LED icon.( true/false)
    //
    // - BS_LEFT:   Text will be left aligned.
    // - BS_RIGHT:  Text will be right aligned.
    // - BS_CENTER: Text will be center
    //
    LedData ledData;
    ::ZeroMemory(&ledData, sizeof(LedData));
    ledData.textForegroundColor = ::GetSysColor(COLOR_BTNTEXT);
    ledData.textBackgroundColor = ::GetSysColor(COLOR_BTNFACE);

    if (m_ledState < m_listCount)
    {
        ledData = m_ledDataList[m_ledState];
    }

    if (m_buttonStyle & BS_LEFTTEXT)
    {
        ledRect.left      = captionRect.right - ledData.width;
        ledRect.right     = ledRect.left + ledData.width;
        captionRect.right = ledRect.left - 3;
    }
    else
    {
        ledRect.left     = captionRect.left;
        ledRect.right    = ledRect.left + ledData.width;
        captionRect.left = ledRect.right + 3;
    }

    ledRect.top    = (captionRect.Height() - ledData.height)/2;
    ledRect.bottom = ledRect.top + ledData.height;

    if (ledData.hIcon)
    {
        pDC->DrawState(ledRect.TopLeft(), 
                       ledRect.Size(),  
                       ledData.hIcon, 
                       (m_isDisabled ? DSS_DISABLED : DSS_NORMAL), 
                       (CBrush*)NULL);

    }

    //
    // Now deal with the text.(if any)
    //
	CString title;
	GetWindowText(title);

    if (FALSE == title.IsEmpty())
    {
        CRect centerRect = captionRect;
        UINT textFormat = DT_WORDBREAK | DT_VCENTER;

        if (BS_CENTER == (m_buttonStyle &  BS_CENTER))
        {
            textFormat |= DT_CENTER;
            pDC->DrawText(title, -1, &captionRect, (textFormat | DT_CALCRECT));
            captionRect.OffsetRect((centerRect.Width() - captionRect.Width())/2, 
                                   ((centerRect.Height() - captionRect.Height())/2));

        }
        else if (BS_RIGHT == (m_buttonStyle & BS_RIGHT))
        {
            textFormat |= DT_RIGHT;
            pDC->DrawText(title, -1, &captionRect, (textFormat | DT_CALCRECT));
            captionRect.OffsetRect((centerRect.Width() - captionRect.Width()), 
                                   ((centerRect.Height() - captionRect.Height())/2));
        }
        else
        {
            textFormat |= DT_LEFT;
            pDC->DrawText(title, -1, &captionRect, (textFormat | DT_CALCRECT));
            captionRect.OffsetRect(0, ((centerRect.Height() - captionRect.Height())/2));

        }

	    pDC->SetBkMode(TRANSPARENT);

        if (m_isDisabled)
        {
            captionRect.OffsetRect(1, 1);
            pDC->SetTextColor(::GetSysColor(COLOR_3DHILIGHT));
            pDC->DrawText(title, -1, &captionRect, textFormat);
            captionRect.OffsetRect(-1, -1);
            pDC->SetTextColor(::GetSysColor(COLOR_3DSHADOW));
            pDC->DrawText(title, -1, &captionRect, textFormat);
        }
        else
        {
            pDC->SetTextColor(ledData.textForegroundColor);
            pDC->SetBkColor(ledData.textForegroundColor);
            pDC->DrawText(title, -1, &captionRect, textFormat);
        }

    }

}



// ///////////////////////////////////////////////////////////////////////////
// OnTimer
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::OnTimer(UINT nIDEvent)
{
    if ((nIDEvent == m_activityId) && (0 != m_activityId))
    {
        SetLedState(m_activityState);
        return;
    }

    CButton::OnTimer(nIDEvent);
}

// ///////////////////////////////////////////////////////////////////////////
// OnSysColorChange
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::OnSysColorChange()
{
    CWnd::OnSysColorChange();
    
    RestoreDefaultColors();
}


// ///////////////////////////////////////////////////////////////////////////
// OnDrawBackground
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::OnDrawBackground(CDC* pDC, CRect* pRect)
{
    ASSERT(pDC);
    ASSERT(pRect);

    COLORREF color = GetTextBackgroundColor(m_ledState);
	CBrush brBackground(color);
	pDC->FillRect(pRect, &brBackground);
}


// ///////////////////////////////////////////////////////////////////////////
// OnSetStyle
// ///////////////////////////////////////////////////////////////////////////
LRESULT CLedButton::OnSetStyle(WPARAM wParam, LPARAM lParam)
{
	return(DefWindowProc(BM_SETSTYLE, (wParam & ~BS_TYPEMASK) | BS_OWNERDRAW, lParam));
}


// ///////////////////////////////////////////////////////////////////////////
// PreTranslateMessage
// ///////////////////////////////////////////////////////////////////////////
BOOL CLedButton::PreTranslateMessage(MSG* pMsg)
{
	ToolTipInit();
	m_toolTip.RelayEvent(pMsg);
	
    return(CButton::PreTranslateMessage(pMsg));
}

//
// To avoid a  warning C4189: 'style' : local variable is initialized but not referenced
// in the PreSubclassWindow method on Level4 Warnings/Release configuration.
// Reason: The ASSERT lines do not exist under Release. therefore the 'style' variable
// is never used.
#pragma warning( push )
#pragma warning( disable : 4189 )


// ///////////////////////////////////////////////////////////////////////////
// PreSubclassWindow
// ///////////////////////////////////////////////////////////////////////////
void CLedButton::PreSubclassWindow()
{
	UINT style = BS_TYPEMASK  & GetButtonStyle();
    
    ASSERT(style & BS_CHECKBOX);
	ASSERT(style != BS_OWNERDRAW);

	// Switch to owner-draw
	ModifyStyle(BS_TYPEMASK, BS_OWNERDRAW, SWP_FRAMECHANGED);

    m_buttonStyle = GetWindowLong(GetSafeHwnd(), GWL_STYLE);

    CButton::PreSubclassWindow();
}
//
// Undo the above warning disabling.
//
#pragma warning( pop )


// ///////////////////////////////////////////////////////////////////////////
// CtlColor
// ///////////////////////////////////////////////////////////////////////////
HBRUSH CLedButton::CtlColor(CDC* /*pDC*/, UINT /*CtlColor*/)
{
	return ((HBRUSH)::GetStockObject(NULL_BRUSH)); 
}


//
// --- No-code-allowed-beyond-this-line--------------------------------------
//

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)
Israel Israel
I was born in Tarija - Bolivia, and since 1981 I'm in Israel. Currently work for Entropic Communications, Inc. as Embedded Staff Software Engineer.

My software knowledge spans from sysadmin of Unix machines in the 90'(HPUX, Ultrix, SunOS, Solaris, Linux, Perl, XEmacs), Embedded programming for more than a decade (old pSOS, VxWorks, 68K, PPC, 960, ARM and others), Java with Network Management using SNMP, deep knowledge of Networking (I was the Network Architect for wireless systems using 802.16/802.11, in my previous workplace).

My interests are photography and travell to interesting places around our globe.

I really enjoy www.codeproject.com. It has saved me an incredible amount of time. I only hope my small contributions have given back some of what I'm using.

RBID stands for Ricky, Brenda, Irit and Daniel (My family), my real name is Ricky Marek.

Comments and Discussions