Jump to content
  • Advertisement
Sign in to follow this  

Strange MFC problem that I can't figure out (CButton class-related).

This topic is 4348 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Ok. I'm trying to create a button that, when clicked, launches a simple color picker. The face of the button should just be a flat color, displaying the color that is currently chosen. Everything seems to be working great, for the most part. I have a COLORREF structure named m_CurrentColor that holds the value of the currently-chosen color. When the constructor for my CFormView-derived class is called, it initializes this variable to black (I used the RGB macro to do this). I created an "OnBnClicked...()" function for the button. It launches the color picker. The starting color is whatever m_CurrentColor is at the time that the button was clicked. The user can then choose a color, or click cancel. In the former case, m_CurrentColor is properly updated with the new color. In the latter case, no change is made. Now, in order to get the button to actually display the m_CurrentColor value, I grab the device context for the button, calculate the client rectangle of it, and then call FillSolidRect() to fill it in. All of it works fine, with two exceptions: 1) Because the code that fills the button client area with the m_CurrentColor color is found within the OnBnClicked message handler function, the button is just gray when the program loads (even though m_CurrentColor is set to black). So, I copied the code from the OnBnClicked function and placed it in the OnInitialUpdate() function, hoping that the button would be colored black when the CFormView-derived class is first shown. It doesn't, though. It still shows up gray. 2) If I click the button, choose a color from the color picker, and then click OK, it works perfectly: m_CurrentColor is updated with the new color value, and the button displays the right color, EXCEPT the first time that I do it. So in other words, when I load the program, the button is just gray (because of problem #1). Then, I click it and select another color. When I do this, m_CurrentColor IS updated with the proper value of the new color. However, the button is still gray. When I perform this a second time, though, it works fine. About 9 times out of 10, I have to do it twice in order to get it to work. Again, the m_CurrentColor variable is updated correctly every time, but the color of the button only changes on the second try (sometimes the first, but not usually). Here is the code that does the actual drawing of the color.
CRect cr;
CDC * buttonDC = ((CButton*)GetDlgItem(IDC_COLORBUTTON))->GetDC();
((CButton*)GetDlgItem(IDC_COLORBUTTON))->GetClientRect(&cr);
buttonDC->FillSolidRect(&cr, m_CurrentColor);
ReleaseDC(buttonDC);


Again, m_CurrentColor is initialized to black (0) in the class's constructor. The above code is placed in both the OnInitialUpdate() function and in the OnBnClicked function for the Color button. In the latter case, it is called after the color picker dialog executes. Here are some things I tried: * I tried overriding the OnPaint method, and I told it to basically execute the above code every time the window receives a WM_PAINT message. This worked, in that it made the button show up as black when the program first executes. However, this actually seems to make the program slow. It was hard to notice, but if I moved the window around a lot, it didn't seem to be as responsive as I like, soooooo.... * I made a bool variable called m_InitialDraw that is initialized to false in the constructor. Then, when OnDraw is called, it checks to see if the value is false. If so, it draws the black color onto the button once, then sets the bool to true. This ensures that the code only runs once, on the first WM_PAINT message. I had hoped that this would have the same effect as above, but solve the slowness problem. However, for some reason, it totally FUBAR'ed the program. Not wanting to touch this one with a 10-foot pole, I simply removed the OnDraw stuff from the program and message map. * I tried debugging the program to make sure that the code above is actually being executed. I also kept a watch on the m_CurrentColor and cr variables to make sure that, for instance, cr didn't equal {0,0,0,0} or that m_CurrentColor wasn't gray or some wierd NULL value. When I did this, I was able to confirm that the statements did indeed execute, and that the values of these variables were exactly as they were supposed to be at the time of their execution. So, I'm stumped. :( [Edited by - uncle_rico on June 26, 2006 4:33:30 PM]

Share this post


Link to post
Share on other sites
Advertisement
Can you tried to manage OnPaint instead of OnDraw?
Client area is repainted every time it needs, and WM_PAINT message is sent.

Here it is my ooold simple RGB slider custom control. If you find it usefull..

Header:
RGBSliderSimple.h

#if !defined(AFX_RGBSLIDER_H__BF97F412_279F_11D4_A2F6_0048543D92F7__INCLUDED_)
#define AFX_RGBSLIDER_H__BF97F412_279F_11D4_A2F6_0048543D92F7__INCLUDED_

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

#define SIMPLESLIDER_CLASSNAME _T("SimpleRGBSlider") // Window class name


enum { SL_RGB_MOVED = 1 };

struct NMRGBSLIDER : public NMHDR
{ // notification struct
long value; // how much is scrolled
};

/////////////////////////////////////////////////////////////////////////////
// CSimpleRGBSlider window

class CSimpleRGBSlider : public CWnd
{
// Construction
public:
CSimpleRGBSlider();

// Attributes
public:
//BOOL SetBitmap(UINT nIDResource);

// Operations
public:
BOOL Create(CWnd* pParentWnd, const RECT& rect, UINT nID, DWORD dwStyle = WS_VISIBLE);

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CSimpleRGBSlider)
protected:
virtual void PreSubclassWindow();
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CSimpleRGBSlider();
void SetRGB(INT r, INT g, INT b) { _R = r; _G = g; _B = b; }

INT GetRed() { return _R; }
INT GetGreen() { return _G; }
INT GetBlue() { return _B; }

INT GetValueFromMouseInput(CPoint);

protected:
BOOL RegisterWindowClass();

void DrawSliders(CDC *pDC);
void DrawRGBValues(CDC *pDC);
void DrawColorBox(CDC *pDC);

void SendMessage();

// Generated message map functions
protected:
CFont _Font;
INT _R, _G, _B;

BOOL _ClickedR, _ClickedG, _ClickedB;
//{{AFX_MSG(CSimpleRGBSlider)
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

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

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

#endif // !defined(AFX_RGBSLIDER_H__BF97F412_279F_11D4_A2F6_0048543D92F7__INCLUDED_)




RGBSliderSimple.cpp

//

#include "stdafx.h"
#include "resource.h"
#include "rgbslidersimple.h"

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

/////////////////////////////////////////////////////////////////////////////
// CSimpleRGBSlider

CSimpleRGBSlider::CSimpleRGBSlider()
{
RegisterWindowClass();

_R = _G = _B = 255;
_ClickedR = _ClickedG = _ClickedB = FALSE;

_Font.CreateFont(13, 0, 0, 0, FW_NORMAL,
0,0,0, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS,
CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|
FF_DONTCARE, "Arial");
}

CSimpleRGBSlider::~CSimpleRGBSlider()
{
}

// Register the window class if it has not already been registered.
BOOL CSimpleRGBSlider::RegisterWindowClass()
{
WNDCLASS wndcls;
HINSTANCE hInst = AfxGetInstanceHandle();

if (!(::GetClassInfo(hInst, SIMPLESLIDER_CLASSNAME, &wndcls)))
{
// otherwise we need to register a new class
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpfnWndProc = ::DefWindowProc;
wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
wndcls.hInstance = hInst;
wndcls.hIcon = NULL;
wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
wndcls.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
wndcls.lpszMenuName = NULL;
wndcls.lpszClassName = SIMPLESLIDER_CLASSNAME;

if (!AfxRegisterClass(&wndcls))
{
AfxThrowResourceException();
return FALSE;
}
}

return TRUE;
}


BEGIN_MESSAGE_MAP(CSimpleRGBSlider, CWnd)
//{{AFX_MSG_MAP(CSimpleRGBSlider)
ON_WM_PAINT()
ON_WM_ERASEBKGND()
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CSimpleRGBSlider message handlers
void CSimpleRGBSlider::OnPaint()
{
CRect rc;
CDC dcMemory;
CBitmap bmpMemory;

CBrush hbrBkGnd;

GetClientRect(&rc);

CPaintDC dc(this);
//----- init -----------

dcMemory .CreateCompatibleDC(&dc);
bmpMemory.CreateCompatibleBitmap(&dc, rc.right, rc.bottom);

CBitmap* pOldBitmap = dcMemory.SelectObject(&bmpMemory);

//-------end init ----------

// make background white
hbrBkGnd.CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
dcMemory.FillRect(&rc, &hbrBkGnd);
DeleteObject(hbrBkGnd);

dcMemory.DrawEdge(rc,BDR_SUNKENINNER,BF_RECT);

DrawSliders(&dcMemory);
DrawRGBValues(&dcMemory);
DrawColorBox(&dcMemory);

dc.BitBlt(rc.top,rc.left,rc.right, rc.bottom, &dcMemory, 0, 0, SRCCOPY);

dcMemory.SelectObject(pOldBitmap);
}
//
void CSimpleRGBSlider::DrawSliders(CDC *pDC)
{
CBrush *pOldBrush;
CRect rcBox, rcClient;

CBrush brSL1(GetSysColor(COLOR_3DSHADOW));
CBrush brSL2(GetSysColor(COLOR_3DLIGHT));

pOldBrush = pDC->SelectObject(&brSL2 );

GetClientRect(rcClient);

rcClient.top = 3;
rcClient.bottom = 13;

rcClient.left = 10;
rcClient.right -= 50;

rcBox = rcClient;

// slider area
for(int l = 0; l<3; l++)
{
pDC->Rectangle( rcBox );

rcBox.top +=13;
rcBox.bottom = rcBox.top +10;
}

pDC->SelectObject(&brSL1 );
// shadowed area
rcBox = rcClient;

//r
rcBox.left = (INT)((float)(rcClient.right - rcClient.left)* 0.01f * (float)(_R/ 2.55f))+10;
pDC->Rectangle( rcBox );

//g
rcBox.top +=13;
rcBox.bottom = rcBox.top +10;
rcBox.left = (INT)((float)(rcClient.right - rcClient.left)* 0.01f * (float)(_G/ 2.55f))+10;
pDC->Rectangle( rcBox );

//b
rcBox.top +=13;
rcBox.bottom = rcBox.top +10;
rcBox.left = (INT)((float)(rcClient.right - rcClient.left)* 0.01f * (float)(_B/ 2.55f))+10;
pDC->Rectangle( rcBox );
//end

pDC->SelectObject(pOldBrush);
}
//
void CSimpleRGBSlider::DrawRGBValues(CDC *pDC)
{
CRect rcBox;

GetClientRect(rcBox);

pDC->SetBkMode(TRANSPARENT);
CFont* def_font = pDC->SelectObject(&_Font);

pDC->TextOut(2,2,_T("R"));
pDC->TextOut(2,15,_T("G"));
pDC->TextOut(2,28,_T("B"));

CString sOut;

sOut.Format("%d",_R); pDC->TextOut(rcBox.right-48,2, sOut );
sOut.Format("%d",_G); pDC->TextOut(rcBox.right-48,15, sOut );
sOut.Format("%d",_B); pDC->TextOut(rcBox.right-48,28, sOut );

pDC->SelectObject(def_font);
}
//
void CSimpleRGBSlider::DrawColorBox(CDC *pDC)
{
CBrush *pOldBrush;
CRect rcBox;

GetClientRect(rcBox);

CBrush brSL2(RGB(_R,_G,_B));

pOldBrush = pDC->SelectObject(&brSL2 );


rcBox = CRect(
rcBox.right - 27, 3,
rcBox.right - 3, 3 + 12*3);

pDC->Rectangle( rcBox );

pDC->SelectObject(pOldBrush);
}

void CSimpleRGBSlider::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call the base class
// In our case this is not needed - yet - so just drop through to
// the base class
CWnd::PreSubclassWindow();
}

/////////////////////////////////////////////////////////////////////////////
// CSimpleRGBSlider methods

BOOL CSimpleRGBSlider::Create(CWnd* pParentWnd, const RECT& rect, UINT nID, DWORD dwStyle /*=WS_VISIBLE*/)
{
return CWnd::Create(SIMPLESLIDER_CLASSNAME, _T(""), dwStyle, rect, pParentWnd, nID);
}

//////////////////
// Left mouse button down function
//

void CSimpleRGBSlider::OnLButtonDown(UINT nFlags, CPoint pt)
{

CRect rcBox;

GetClientRect(rcBox);

rcBox = CRect(10,3,rcBox.right - 50,13);

if(rcBox.PtInRect(pt))
{
_ClickedR = TRUE;

_R = GetValueFromMouseInput(pt);
SendMessage();
Invalidate();
}
rcBox.top +=13; rcBox.bottom = rcBox.top +10;
//
if(rcBox.PtInRect(pt))
{
_ClickedG = TRUE;

_G = GetValueFromMouseInput(pt);
SendMessage();
Invalidate();
}
rcBox.top +=13; rcBox.bottom = rcBox.top +10;
//
if(rcBox.PtInRect(pt))
{
_ClickedB = TRUE;

_B = GetValueFromMouseInput(pt);
SendMessage();
Invalidate();
}


}
//////////////////
// Left mouse up function
//

void CSimpleRGBSlider::OnLButtonUp(UINT nFlags, CPoint pt)
{
_ClickedR = _ClickedG = _ClickedB = FALSE;
}
//////////////////
// Mouse moved
//
void CSimpleRGBSlider::OnMouseMove(UINT nFlags, CPoint pt)
{
//Invalidate();

if((nFlags & MK_LBUTTON) == MK_LBUTTON)
{
if(_ClickedR)
{
//r
_R = GetValueFromMouseInput(pt);
SendMessage();
Invalidate();
}
//
if(_ClickedG)
{
_G = GetValueFromMouseInput(pt);
SendMessage();
Invalidate();
}
//
if(_ClickedB)
{
_B = GetValueFromMouseInput(pt);
SendMessage();
Invalidate();
}
}
}
INT CSimpleRGBSlider::GetValueFromMouseInput(CPoint pt)
{
CRect rcBox;

GetClientRect(rcBox);

rcBox = CRect(10,3,rcBox.right - 50,13);

INT value = (INT)(((float)(pt.x-rcBox.left+1)/
((float)(rcBox.right - rcBox.left)* 0.01f)) * 2.55f);

if (value > 255 ) return 255;
if (value < 0 ) return 0;

return value;

}
BOOL CSimpleRGBSlider::OnEraseBkgnd(CDC* pDC)
{
return 1;
return CWnd::OnEraseBkgnd(pDC);
}

void CSimpleRGBSlider::SendMessage()
{
NMRGBSLIDER nm;
nm.hwndFrom = m_hWnd;
nm.idFrom = GetDlgCtrlID();
nm.code = SL_RGB_MOVED;
nm.value = 0;
CWnd* pParent = GetParent();
pParent->SendMessage(WM_NOTIFY, nm.idFrom, (LPARAM)&nm);

}




And don't forget to add 'SimpleRGBSlider' as Class value in property dialog of custom sontrol.

Share this post


Link to post
Share on other sites
Streamer, I'm very sorry for the mistake. I actually did use WM_PAINT/OnPaint instead of WM_DRAW/OnDraw. I use them interchangeably (in speach) as a very bad habit. I have no idea where I picked that habit up. I will edit my post.

Quote:
Original post by Nytegard
what are the dimensions of cr? (Not what they should be, but what does the debugger state they are?)


The debugger says:
Quote:

cr {top=0 bottom=19 left=0 right=45} CRect

Share this post


Link to post
Share on other sites
Alright, 2 alternatives you can try to get the desired result.

Either you want to create an image and attach it to the button, the image being a solid rectangle of the decided color, or you want to override the WM_CTLCOLOR message, and set the color there.

What I believe is happening is that since you're just drawing on top of the button, the initialization portion draws what you want, but later on, when the button receives more messages, it's told to redraw the grey. You can test this out and minimize your application. I'm willing to bet that when you restore it, the button you had changes back to grey.

Share this post


Link to post
Share on other sites
Thanks Nytgard. I think you were correct. If I minimized the window, it did become gray again. Basically, I just created a CButton-derived class (called CColorButton), and then overrid the OnPaint function for that class (that way it is called only when the button is invalidated). Then, I used DDX_Control to subclass the button from the dialog resource as a CColorButton, and then I simply invalidate it to force it to repaint the button. Works great!

Thanks for all of your help.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!