Public Group

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

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

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 on other sites
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..

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 nameenum { SL_RGB_MOVED = 1 };struct NMRGBSLIDER : public NMHDR {									// notification struct	long value;				 	// how much is scrolled};/////////////////////////////////////////////////////////////////////////////// CSimpleRGBSlider windowclass CSimpleRGBSlider : public CWnd{// Constructionpublic:	CSimpleRGBSlider();// Attributespublic:    //BOOL SetBitmap(UINT nIDResource);// Operationspublic:	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// Implementationpublic:	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 functionsprotected:	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_FILEstatic char THIS_FILE[] = __FILE__;#endif/////////////////////////////////////////////////////////////////////////////// CSimpleRGBSliderCSimpleRGBSlider::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_MAPEND_MESSAGE_MAP()/////////////////////////////////////////////////////////////////////////////// CSimpleRGBSlider message handlersvoid 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 methodsBOOL 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 on other sites
Quick question.

On the line

((CButton*)GetDlgItem(IDC_COLORBUTTON))->GetClientRect(&cr);

what are the dimensions of cr? (Not what they should be, but what does the debugger state they are?)

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 Nytegardwhat 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 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 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.

1. 1
2. 2
Rutin
19
3. 3
4. 4
5. 5

• 9
• 9
• 9
• 14
• 12
• Forum Statistics

• Total Topics
633298
• Total Posts
3011256
• Who's Online (See full list)

There are no registered users currently online

×