Click to See Complete Forum and Search --> : How to control WM_COMMAND message in owner-draw button


Tom G
May 11th, 1999, 09:51 PM
I have a group of four owner-draw stand-alone child window buttons. The group functions as a group of radio buttons would. In my handlers for the buttons I set the state accordingly, setting the state of the selected button to TRUE and the other three to FALSE. As the message is not sent until I release the mouse button, the program draws the buttons "up" state before drawing the "down" state. Occasionally this causes noticible flicker in the button. Is there any way around this? Also, I am not familiar with applying a bitmask. Exactly how do I apply the bitmask to obtain the state of a button using CButton::GetState()?

TIA

Thomas

Jason Teagle
May 12th, 1999, 03:09 AM
You need to trap the WM_LBUTTONDOWN message to get the button state correct. It would be better to make the button class a class derived from CButton, that way you can put the handler directly into that new class; but if you trap WM_LBUTTONDOWN in the same way you have trapped WM_COMMAND, and then check the co-ordinates of the mouse at the time the message was sent (since for the window that owns these buttons, the mouse-down message can appear if you click ANYWHERE in the window), you can then redraw for that situation. Remember that you will have to get the window rect of the button(s) (which comes in screen co-ordinates), then convert to the owning window's client co-ordinates before comparing with the co-ordinates delivered by WM_LBUTTONDOWN. You will have to do the same for WM_LBUTTONUP, and you will have to capture the mouse in the mouse-down message so that if the user clicks in the button, then moves out of the button before releasing, you can redraw the button in the up state (as per normal buttons).

Did you follow all that?

To use GetState(), use:

---
UINT uState ;

uState = m_btnSimRadio.GetState();

// If this was a check button or radio button, this could check
// the 'checked' state (doesn't work on push buttons):
if (uState & 0x0003 == 0)
// Unchecked (= not selected for a radio button).
else if (uState & 0x0003 == 1)
// Checked (= selected for a radio button).
else if (uState & 0x0003 == 2)
// Neither checked nor unchecked (3-state check button, N/A for radio).

// This checks whether the button is highlighted (mouse is clicked
// over it, but not released yet):
if (uState & 0x0004)
// Highlighted (you can also say 'if (uState & 0x0004 == 4)').

// This checks if the button has the focus (focus rectangle).
if (uState & 0x0008)
// Has the focus.



---

Does that help?

Tom G
May 12th, 1999, 07:00 AM
Jason,

I think I follow you. Let's find out. In my original code I create the buttons like this:
m_bmpBut.Create("",BS_OWNERDRAW|BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE,
CRect(8, 4, 55, 45), this, IDC_BMPBUT);
I then load the bmp's.
Now, if I remove "BS_PUSHBUTTON", the button stops responding to WM_COMMAND messages, correct? Then, can I simply trap the LBUTTONDOWN/UP messages in the CRect and go from there? The buttons are ever-present members of MyView, so I see no need to derive a seperate class. Thanks for the info on GetState().

Thomas

Jason Teagle
May 12th, 1999, 07:26 AM
You should NOT remove the BS_PUSHBUTTON style unless you specifically want to stop WM_COMMAND messages from getting to the parent - it would still be useful to get notified when the button is clicked - you may want to act on the current selection after it is chosen / clicked. If you don't want to know after the button has been clicked, don't add a handler for the ON_BN_CLICKED situation.

The reason I suggested deriving a class from CButton is that if you do not, the mouse-down or -up messages that go to the parent window need to have the mouse position checked to make sure it is actually over a button, and which button - otherwise you wouldn't know which was being clicked, if any at all. If you derive a class, then the mouse handlers go into the DERIVED button class, not the parent's window class, and so you do NOT need to check the mouse position - each button will ONLY get mouse messages if they apply to it. So, it would be less effort in the mouse handlers.

However, if you are comfortable with checking the mouse position, then no problem.

If you want more information on how to derive your button class and associate it with the buttons (this requires subclassing - which will cause a conflict if you have member variables for the buttons, but which is easily sorted out), let me know.

Tom G
May 12th, 1999, 01:35 PM
Thanks for the help Jason. Subclassing is something I'm unfamiliar with, so I would appreciate your advice. The buttons are used to set member int variables, but these variables are not tied to the buttons. The vars are members of the view class and must remain that way. The buttons themselves are however CBitmapButton members of the view. I have no problem checking the mouse input, but if deriving a button class is advantageous, I'd certainly like to investigate. Interestingly, (or not) before I untangled the procedure for creating non-dialog owner-draw buttons, I was simply checking mouse input, drawing the appropriate bitmap, and handling the messages in WM_LBUTTONDOWN/UP and WM_MOUSEMOVE. Is there any drawback to this approach?

Jason Teagle
May 13th, 1999, 03:24 AM
In your situation, the pros and cons of subclassing versus a little extra work in the parent window class are roughly about even. Therefore, if you are unfamiliar with the concept, we should leave it as it is. You sound confident at checking the mouse position, so don't worry about it. There are no drawbacks that I can think of, since if the button size or position changes, the code is still OK (presumably you are getting the rectangle that the button occupies and not hard-coding positions and size, aren't you? A good answer would be 'Yes'!)

The time when subclassing becomes useful is when your controls or windows do not behave the way you want them to - you need to subclass so that (1) you retain most of the original functionality, and (2) you can tweak the behaviour a little. An example of this is if you wanted an edit control which processed <Ctrl>-character combos for special purposes (beyond cut, copy and paste, of course), or a button which responded to right-mouse clicks, etc.

Tom G
May 14th, 1999, 04:58 PM
OK Jason, I'm experiencing some difficulty. The main problem is the parent window IS NOT responding to WM_LBUTTONDOWN within the buttons. I attempted implementing a couple methods that send child messages to the parent. OnParentNotify was successful after repeated clicks in the button only. I've been researching subclassing and it appears pretty straightforward, but I'd appreciate some pointers. For example, can I use SubclassDlgItem even though the buttons are not part of a dlg template? SubclassWindow is a little vague to me. The samples I've looked at don't deal with the View/Child button relationship.

TIA

Tom

Jason Teagle
May 17th, 1999, 04:05 AM
****. I owe you an apology.

I realise, now you have said it, that the mouse messages go to the child controls, and NOT to the parent; when the button detects it has been pressed, it sends the WM_COMMAND (BN_CLICKED) to its parent, which is why you can trap THAT.

Mouse messages always go to the child control that the mouse sits over, or the control which has captured the mouse. If the mouse is not over a control or the main window has captured the mouse, THEN it goes to the window that owns those controls.

Keyboard messages always go to the control that has the focus; if the menu has been activated, then they go to the menu system instead. As most main windows cannot get the focus (a control on them must always have the focus), they do not get normal keyboard messages.

However, SYSTEM keyboard messages (those with the <Alt> key down) DO go to the window; normally the system deals with these and dispatches them to the appropriate place for you.

Accelerator keys only work if the window's message pump deals with them - which is the case for the MFC framework; even here, though, they go either to controls or menus (for activating mnemonics).

Now you can see what is happening. This means that you WILL have to subclass the buttons.

SubclassWindow() is not the right one; SubclassDlgItem() is correct. Yes, it works on ANY child controls; but as the MFC does not expect normal windows (other than CFormView, which is effectively a dialogue template) to have child controls, it is named with Dlg in it. Yes, the help is not all that good; when I first tried to do it, it went horribly wrong. I don't know how I finally got it right, I must have found a code sample; but here is what to do (assume we have a button which has an ID of IDC_BUTTON1):

1) Using ClassWizard, create a new class based on CButton (or the control you wish to subclass). The code that is generated for this new class at the moment contains the same functionality as the standard control (or rather, it does not add or change anything). For example, call this CMyButton. There should be one new class for every TYPE of control you want to subclass, or if you want controls of the same type to have largely different behaviour, then they should have their own class.

2) In your main window's (or dialogue box's) header file, add a private member variable of your new class (CMyButton), one per control to subclass.

---
.
.
.
private:
CMyButton m_btnExtra ;
.
.
.


---

3) In your main window's startup override, which is

OnInitDialog() (CDialog-derived)
OnInitialUpdate() (CView-derived)
OnCreate() (other CWnd- or CFrameWnd-derived, generated by adding a handler to the WM_CREATE message)



AFTER the call to the base class, add the following line, one per control to subclass:

m_btnExtra.SubclassDlgItem(IDC_BUTTON1, this);



The control that you call the SubclassDlgItem() method on must have been declared BUT NOT CREATED YET (i.e., has the wrapping MFC object but not the actual Windows component). The first parameter is the ID of the control which will be attached to the object, and the second parameter is the pointer to the parent window of the control.

Now, the button (or other control) is attached to your new object, just like an HWND is attached to a CWnd when you call CWnd::Create(). This is exactly what member variables of type 'control' are doing.

NOTE: If you have already assigned a member variable to this control, you MUST remove the association; m_btnExtra is now THE member variable, and can be used as such - if you try to have both, you will get an assertion, because the MFC does not like you trying to subclass the control twice.

You do not need to unsubclass the control, this is automatic.

4) Now you can add extra functionality to the button or control simply by adding the code to your new class. In your case, you can add mouse-down and -up handlers to accomplish your task. If necessary, you can send messages to the parent window as if they had gone there by themselves.

I hope this helps. I can give you a code sample if you wish.