Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Кнопка с плавной подсветкой при наведении мыши


Автор: mr.DUDA 28.12.2003, 19:01
Обновленный вариант исходного кода с поддержкой XP-тем -- внизу страницы !

Давно хотел поделиться способом, как реализовать кнопку с эффектом как в ICQ Lite (при наведении курсора мыши кнопка плавно изменяет свой цвет на более яркий; когда курсор "уходит" с кнопки - она также плавно меняет цвет на исходный).

На самом деле всё это делается очень просто.
1) добавляем в проект файлы FadeBtn.h/FadeBtn.cpp (приведены ниже)
2) создаём окно (пусть это будет окно диалога), кидаем туда обычную кнопку
3) в свойствах кнопки выставляем флажок "Owner Draw"
4) заходим в Class Wizard, открываем вкладку "Member Variables" и создаём переменную для кнопки (Add Variable), выбираем Category: "Control", и Variable Type: "CFadingButton", пишем какое-нибудь имя, типа "m_btnFade"

Начальный и конечный цвета перехода задаются переменными m_colorInactive и m_colorActive, плавность и скорость перехода цвета задаются через вызов функции ReinitFade(int nOneStepMillisecs, int nStepsColorChange). Эффект выглядит просто супер, особенно если прописать разные цвета (например красный и фиолетовый) в m_colorActive/m_colorInactive.

Исходный текст файлов FadeBtn.h/.cpp:

FadeBtn.h
Цитата
#if !defined(AFX_FADINGBUTTON_H__52AF83EF_3954_11D8_934D_444553540000__INCLUDED_)
#define AFX_FADINGBUTTON_H__52AF83EF_3954_11D8_934D_444553540000__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

/////////////////////////////////////////////////////////////////////////////
// CFadingButton window

class CFadingButton : public CButton
{
public:
COLORREF m_colorActive;
COLORREF m_colorInactive;
int   m_nStepsColorChange;
int   m_nOneStepMillicecs;

private:
int   m_iCurrStep;
bool  m_bActive;
bool  m_bInitialized;

// Construction
public:
CFadingButton();

// Operations
public:
void ReinitFade(int nOneStepMillisecs, int nStepsColorChange);

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CFadingButton)
public:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CFadingButton();

// Generated message map functions
protected:
//{{AFX_MSG(CFadingButton)
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
//}}AFX_MSG

DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_FADINGBUTTON_H__52AF83EF_3954_11D8_934D_444553540000__INCLUDED_)


FadeBtn.cpp
Цитата
#include "stdafx.h"
#include "FadingButton.h"

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

class CColor
{
public:
union
{
  struct
  {
   BYTE b0;
   BYTE b1;
   BYTE b2;
   BYTE b3;
  } bytes;
  COLORREF col;
} m_color;

CColor(COLORREF col)
{
  m_color.col = col;
}
};


/////////////////////////////////////////////////////////////////////////////
// CFadingButton

CFadingButton::CFadingButton()
{
m_colorInactive = GetSysColor(COLOR_BTNFACE);
m_colorActive = GetSysColor(COLOR_BTNFACE);

CColor colI(m_colorInactive);
CColor colA(m_colorActive);

colA.m_color.bytes.b0 = BYTE(float(colI.m_color.bytes.b0) * float(1.2));
colA.m_color.bytes.b1 = BYTE(float(colI.m_color.bytes.b1) * float(1.2));
colA.m_color.bytes.b2 = BYTE(float(colI.m_color.bytes.b2) * float(1.2));
colA.m_color.bytes.b3 = BYTE(float(colI.m_color.bytes.b3) * float(1.2));

m_colorActive = colA.m_color.col;
 
m_nStepsColorChange = 10;
m_nOneStepMillicecs = 5;
m_iCurrStep = 0;
m_bActive = false;
m_bInitialized = false;
}

CFadingButton::~CFadingButton()
{
}


BEGIN_MESSAGE_MAP(CFadingButton, CButton)
//{{AFX_MSG_MAP(CFadingButton)
ON_WM_TIMER()
ON_WM_MOUSEMOVE()
ON_WM_ERASEBKGND()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CFadingButton message handlers

void CFadingButton::OnTimer(UINT nIDEvent)
{
if(nIDEvent == 0)
{
  CPoint pt; CRect rect;
  GetCursorPos(&pt);
  GetWindowRect(&rect);
  if(!rect.PtInRect(pt))
   m_bActive = false;
  if(m_bActive && m_iCurrStep < m_nStepsColorChange)
  {
   m_iCurrStep++;
   RedrawWindow();
  }
  if(!m_bActive && m_iCurrStep)
  {
   m_iCurrStep--;
   RedrawWindow();
  }
}
CButton::OnTimer(nIDEvent);
}

void CFadingButton::OnMouseMove(UINT nFlags, CPoint point)
{
m_bActive = true;
CButton::OnMouseMove(nFlags, point);
}

void CFadingButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
if(!m_bInitialized)
{
  SetTimer(0, m_nOneStepMillicecs, NULL);
  m_bInitialized = true;
}

CDC dc;
dc.Attach(lpDrawItemStruct->hDC);

CColor colI(m_colorInactive);
CColor colA(m_colorActive);
CColor col(m_colorInactive);

BYTE &bI0 = colI.m_color.bytes.b0;
BYTE &bA0 = colA.m_color.bytes.b0;
BYTE &bI1 = colI.m_color.bytes.b1;
BYTE &bA1 = colA.m_color.bytes.b1;
BYTE &bI2 = colI.m_color.bytes.b2;
BYTE &bA2 = colA.m_color.bytes.b2;
BYTE &bI3 = colI.m_color.bytes.b3;
BYTE &bA3 = colA.m_color.bytes.b3;
col.m_color.bytes.b0 = BYTE(float(bI0) + float(bA0 - bI0) * (float(m_iCurrStep)/float(m_nStepsColorChange)));
col.m_color.bytes.b1 = BYTE(float(bI1) + float(bA1 - bI1) * (float(m_iCurrStep)/float(m_nStepsColorChange)));
col.m_color.bytes.b2 = BYTE(float(bI2) + float(bA2 - bI2) * (float(m_iCurrStep)/float(m_nStepsColorChange)));
col.m_color.bytes.b3 = BYTE(float(bI3) + float(bA3 - bI3) * (float(m_iCurrStep)/float(m_nStepsColorChange)));

dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
CRect rect = lpDrawItemStruct->rcItem;
CString text; GetWindowText(text);
dc.DrawFrameControl(&rect, DFC_BUTTON, (GetState() & 4)?DFCS_PUSHED|DFCS_BUTTONPUSH:DFCS_BUTTONPUSH);
rect.DeflateRect(2,2,2,2);
dc.FillSolidRect(&rect, (GetState() & 4)?m_colorActive:col.m_color.col);
dc.DrawText(text, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
dc.Detach();
}

void CFadingButton::ReinitFade(int nOneStepMillisecs, int nStepsColorChange)
{
KillTimer(0);
m_iCurrStep = 1;
m_nOneStepMillicecs = nOneStepMillisecs;
m_nStepsColorChange = nStepsColorChange;
SetTimer(0, m_nOneStepMillicecs, NULL);
RedrawWindow();
}

BOOL CFadingButton::OnEraseBkgnd(CDC* pDC)
{
return FALSE;
}

Автор: CaptainFlint 28.12.2003, 22:15
mr.DUDA
Здорово придумано, респект!
Ещё бы поддержку WinXP-стилей сюда впихнуть, а то на фоне закруглённых XP-шных кнопочек и тулбаров стандартный вид кнопки не вписывается... sad.gif

Автор: mr.DUDA 29.12.2003, 11:31
Угу, я тож об этом подумывал...
Обещаю сегодня-завтра закинуть вариант с поддержкой XP-тем.

Автор: Paradox 29.12.2003, 12:30
Немного попарился, с вставкой такой кнопки, но ниче вставил - прикольно !!!

Автор: mr.DUDA 29.12.2003, 13:51
Да, надо бы упростить вставку, но только как по-другому навесить свой класс на контрол ? Как вариант, можно было бы сделать как в FAQ (создание круглой кнопки) - там сам диалог обрабатывает рисование в OnDrawItem, и нужно только поставить "Owner draw" без всяких member variables.

Автор: CaptainFlint 29.12.2003, 13:54
Что-то я не пойму - куда уж проще? Добавил два файла в проект, вместо CButton у переменной кнопки прописал CFadingButton, плюс свои настройки добавил - цвет и скорость - и всё! Ну и Owner Draw поставить ещё не забыть. Куда ещё проще-то? И главное - метод универсальный!

Автор: Paradox 29.12.2003, 14:45
Цитата
Что-то я не пойму - куда уж проще?
Конечно просто, но как только вспоснишь как с VC общаться, я уже на нем месяцев 7 ниче не делал, а башка другим забивается, щас меня обратно на него сажают в конторе, так что буду вспоминать, для этого примерчик и пропробовал вставить

Автор: DENNN 29.12.2003, 14:51
Цитата
Что-то я не пойму - куда уж проще?

Если говорить о VC, то сделать студийный ocx, наподобии встроенного для добавления в проект ProgressWindow.

Автор: mr.DUDA 8.1.2004, 12:41
Как и было обещано -- улучшенное обновление первого варианта, с поддержкой XP-тем. Данный код подсвечивает кнопку путем умножения текущего цвета каждого пиксела на дробное число (гамма-коэффициент), определяемое для каждого канала своей переменной:
m_fMulGammaGreen - для канала зеленого (значения 0...0.5)
m_fMulGammaRed - для канала красного
m_fMulGammaBlue - синего
Отличия такого способа в том, что кнопка действительно подсвечивается, а не отрисовывается самостоятельно, как в первом варианте. Поэтому, чтобы сделать кнопку красной при подсветке, нужно выставить m_fMulGammaRed равным например 0.3f, а остальные коэффициенты -- 0.
Для управления скоростью и плавностью подсветки используются те же две переменные -- m_nStepsColorChange и m_nOneStepMillicecs.
Способ "навешивания" эффекта на любую кнопку остался тем же (описан в первом варианте). Заранее спасибо за ваши ответы, all.

(для тех, кому лень возиться со вставкой текста в файл - вот все исходники вместе с демонстрационной прогой, в одном архиве: http://www.logicsoftware.net/sos/downloads/FadeBtnDemo.rar)

FadeBtn.h
Цитата
#pragma warning(disable: 4786)
#include <map>

class CFadingButton : public CButton
{
public:
float  m_fMulGammaRed, m_fMulGammaGreen, m_fMulGammaBlue;
int   m_nStepsColorChange;
int   m_nOneStepMillicecs;

private:
int   m_iCurrStep;
bool  m_bActive;
bool  m_bInitialized;
bool  m_bRecursive;

typedef  std::map <UINT, HBITMAP> MapUintToHbmp;
typedef  MapUintToHbmp::value_type MapUintToHbmp_vt;
typedef  MapUintToHbmp::iterator  MapUintToHbmp_i;
MapUintToHbmp m_mapStateBitmaps;


// Construction
public:
CFadingButton();

// Operations
public:
void ReinitFade(int nOneStepMillisecs, int nStepsColorChange);

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CFadingButton)
public:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CFadingButton();

// Generated message map functions
protected:
//{{AFX_MSG(CFadingButton)
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
//}}AFX_MSG

DECLARE_MESSAGE_MAP()
};

FadeBtn.cpp
Цитата
#include "stdafx.h"
#include "FadingButton.h"

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

union CColor
{
public:
struct
{
  BYTE b0;
  BYTE b1;
  BYTE b2;
  BYTE b3;
} bytes;

COLORREF col;
};


/////////////////////////////////////////////////////////////////////////////
// CFadingButton

CFadingButton::CFadingButton()
{
m_nStepsColorChange = 20;
m_nOneStepMillicecs = 5;
m_iCurrStep = 0;
m_bActive = false;
m_bInitialized = false;
m_bRecursive = false;

// Normal values: 0 ... 0.5
m_fMulGammaBlue = 0.3f;
m_fMulGammaGreen = 0.3f;
m_fMulGammaRed = 0.3f;
}

CFadingButton::~CFadingButton()
{
}


BEGIN_MESSAGE_MAP(CFadingButton, CButton)
//{{AFX_MSG_MAP(CFadingButton)
ON_WM_TIMER()
ON_WM_MOUSEMOVE()
ON_WM_ERASEBKGND()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CFadingButton message handlers

void CFadingButton::OnTimer(UINT nIDEvent)
{
if(nIDEvent == 0)
{
  CPoint pt; CRect rect;
  GetCursorPos(&pt);
  GetWindowRect(&rect);
  if(!rect.PtInRect(pt))
   m_bActive = false;
  if(m_bActive && m_iCurrStep < m_nStepsColorChange)
  {
   m_iCurrStep++;
   RedrawWindow();
  }
  if(!m_bActive && m_iCurrStep)
  {
   m_iCurrStep--;
   RedrawWindow();
  }
}
CButton::OnTimer(nIDEvent);
}

void CFadingButton::OnMouseMove(UINT nFlags, CPoint point)
{
m_bActive = true;
CButton::OnMouseMove(nFlags, point);
}

void CFadingButton::ReinitFade(int nOneStepMillisecs, int nStepsColorChange)
{
KillTimer(0);
m_iCurrStep = 1;
m_nOneStepMillicecs = nOneStepMillisecs;
m_nStepsColorChange = nStepsColorChange;
SetTimer(0, m_nOneStepMillicecs, NULL);
RedrawWindow();
}

void CFadingButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
if(m_bRecursive) return;

if(!m_bInitialized)
{
  SetTimer(0, m_nOneStepMillicecs, NULL);
  m_bInitialized = true; 
}

CRect rect = lpDrawItemStruct->rcItem;
CRect screenRect = rect; ClientToScreen(&screenRect);
HDC hScreenDC = ::GetDC(NULL);
CDC ScreenDC; ScreenDC.Attach(hScreenDC);

int iCurrState = GetState();
MapUintToHbmp_i iter = m_mapStateBitmaps.find(iCurrState);
HBITMAP hBmpStateCurr;
if(iter == m_mapStateBitmaps.end())
{
  CButton btn;
  CString csText; GetWindowText(csText);
  btn.Create(csText, GetStyle() & (~BS_OWNERDRAW), rect, this, 0);
  btn.SetFont(GetFont());
  btn.SetState(iCurrState & 4 ? 1:0);
  m_bRecursive = true;
  btn.RedrawWindow();
  m_bRecursive = false;

  CDC dcTmp; dcTmp.CreateCompatibleDC(&ScreenDC);
  hBmpStateCurr = CreateCompatibleBitmap(hScreenDC, rect.Width(), rect.Height());
  CBitmap bmpTmp; bmpTmp.Attach(hBmpStateCurr);
  CBitmap *pBmpPrev = dcTmp.GetCurrentBitmap();
  dcTmp.SelectObject(&bmpTmp);
  dcTmp.BitBlt(0, 0, rect.Width(), rect.Height(), &ScreenDC, screenRect.left, screenRect.top, SRCCOPY);
  dcTmp.SelectObject(pBmpPrev);
  bmpTmp.Detach();
  m_mapStateBitmaps.insert(MapUintToHbmp_vt(iCurrState, hBmpStateCurr));
}
else hBmpStateCurr = (*iter).second;

CDC dcStateCurr; dcStateCurr.CreateCompatibleDC(&ScreenDC);
dcStateCurr.SelectObject(hBmpStateCurr);
CDC dcMem; dcMem.CreateCompatibleDC(&ScreenDC);
CBitmap bmpMem; bmpMem.CreateCompatibleBitmap(&ScreenDC, rect.Width(), rect.Height());
dcMem.SelectObject(&bmpMem);
dcMem.BitBlt(0, 0, rect.Width(), rect.Height(), &dcStateCurr, 0, 0, SRCCOPY);

if(!(iCurrState & 4))
{
  BITMAPINFO bmi;
  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmi.bmiHeader.biBitCount = 0; 
  HBITMAP hBmp = (HBITMAP)bmpMem.m_hObject;
  void *pBuf = new char[rect.Height()*rect.Width()*4];
  GetDIBits(dcMem.m_hDC, hBmp, 0, rect.Height(), NULL, &bmi, DIB_RGB_COLORS);
  GetDIBits(dcMem.m_hDC, hBmp, 0, rect.Height(), pBuf, &bmi, DIB_RGB_COLORS); 
 
  float fMulR = 1 + (float(m_iCurrStep) / float(m_nStepsColorChange)) * m_fMulGammaRed;
  float fMulG = 1 + (float(m_iCurrStep) / float(m_nStepsColorChange)) * m_fMulGammaGreen;
  float fMulB = 1 + (float(m_iCurrStep) / float(m_nStepsColorChange)) * m_fMulGammaBlue;
 
  CColor *pcolCurr = (CColor *)pBuf;
  for(int i=0; i<rect.Height()*rect.Width(); i++, pcolCurr++)
  {  
   CColor &col = *pcolCurr;
   float fCol = float(col.bytes.b0) * fMulB;
   col.bytes.b0 = fCol>255?255:BYTE(fCol);
   fCol = float(col.bytes.b1) * fMulG;
   col.bytes.b1 = fCol>255?255:BYTE(fCol);
   fCol = float(col.bytes.b2) * fMulR;
   col.bytes.b2 = fCol>255?255:BYTE(fCol);
  }
 
  SetDIBits(dcMem.m_hDC, hBmp, 0, rect.Height(), pBuf, &bmi, DIB_RGB_COLORS);
  delete pBuf;
}

CDC dcDst; dcDst.Attach(lpDrawItemStruct->hDC);
CBitmap bmpDst; bmpDst.CreateCompatibleBitmap(&ScreenDC, rect.Width(), rect.Height());
dcDst.SelectObject(&bmpDst);
dcDst.BitBlt(0, 0, rect.Width(), rect.Height(), &dcMem, 0, 0, SRCCOPY);
dcDst.Detach();
ScreenDC.Detach();
}

BOOL CFadingButton::OnEraseBkgnd(CDC* pDC)
{
return FALSE;
}

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)