views:

161

answers:

3

I'm implementing a custom control that inherits from Control. I want it to be focusable (it's a kind of list box).

In the constructor, I do

SetStyle(ControlStyles.Selectable, true);

I can now use Tab to navigate to the control.

However, when the control receives a mouse click, it does not automatically claim focus. I can work around this, of course:

protected override void OnMouseDown(MouseEventArgs e)
{
    Focus();
    base.OnMouseDown(e);
}

But this feels like a kludge that should not be necessary. Is this really the way to go? Or is there some way to tell Control to claim focus automatically when it receives a mouse click?

+1  A: 

Disassembly to the rescue! It turns out that

SetStyle(ControlStyles.UserMouse, true);

does the trick.

Ironically, I had read in the documentation:

UserMouse: If true, the control does its own mouse processing, and mouse events are not handled by the operating system.

That seemed the exact opposite of what I wanted, so I had only tried setting it to false... Way to go, WinForms documentation.

Thomas
This isn't correct. Turning the UserMouse style off should only be done for Control class wrappers around native controls that implement their own mouse message processing. You allow a control to take the focus because you're interested in input events. Intentionally turning input event processing off doesn't make sense.
Hans Passant
The sentence "the control does its own mouse processing" is extra confusing in the light of your clarification: does the *WinForms* control do its own mouse processing (overriding `OnMouseDown` etc.) or does the *native* control do its own mouse processing (meaning that the WinForms control should stay away from it)? Anyway, apparently `UserMouse` was `false` by default. And since I'm not wrapping a native control, it should be `true`, right?
Thomas
Yes it does, it calls the OnMouseDown method. Which you would override to give your own control its mouse behavior. Like giving it the focus. Your control inherits from Control, there is no native Windows control being wrapped (like ListBox or TreeView). It is just a plain window.
Hans Passant
+1  A: 

Yes, that what you should do. There are many controls that don't have a meaningful way to take the focus. PictureBox, Panel are good examples. Anything that derives from ContainerControl. Control.OnMouseDown() therefore doesn't automatically call Focus() in OnMouseDown().

Just overriding the OnMouseDown method isn't enough, you should also make it clear to the user that your control has the focus. So she'll have an idea where the keyboard strokes go. That requires overriding OnPaint() so you can draw a focus rectangle. ControlPaint.DrawFocusRectangle() is a boilerplate implementation for that.

But taking the focus is really only useful if you do something meaningful with keyboard messages. So you'll have to override OnKeyDown and/or OnKeyPressed as well. And show feedback to the user so she can see what she typed. If you don't have a useful implementation for that, you shouldn't take the focus. Which is why PictureBox doesn't.

Hans Passant
Good points, but why did you think I wanted the control to be focusable in the first place? I already had the focus rectangle in place, and am now working on the keyboard handlers.
Thomas
Always good to mention these kind of details in the question.
Hans Passant
I mentioned in parentheses that it's a kind of listbox, which sort-of implied that it should handle keypresses; I didn't want to distract from the issue by mentioning all these details. But you're right: it is good to give information about the "why would you want that" part, because sometimes it turns out that you actually want to solve or circumvent the underlying problem in a completely different way!
Thomas
A: 
  1. compile in a WinForms project for FrameWork 3.5

  2. drag an instance of Control1 from the ToolBox to a Form Surface ... make sure its TabStop property is set to 'true

  3. put some other controls on the form.

  4. verify that when the instance of Control1 is tabbed to: it shows a selection rectangle which disappears as you "tab away" from it.

  5. verify if you click on the instance of Control1 that it shows the selection rectangle, and if you click on some other control it disappears.

    namespace testFocusableControl
    {
        //  VS Studio 2010 RC1 : Tested against FrameWork 3.5 Full (not 'Client)
    
    
    
    public class Control1 : Control
    {
        public Control1()
        {
            SetStyle(ControlStyles.UserMouse, true);
        }
    
    
        protected override void OnLostFocus(EventArgs e)
        {
            this.Invalidate();
            base.OnLostFocus(e);
        }
    
    
        protected override void OnGotFocus(EventArgs e)
        {
            this.Invalidate();
            base.OnGotFocus(e);
        }
    
    
        protected override void OnPaint(PaintEventArgs e)
        {
            if (this.Focused)
            {
                ControlPaint.DrawFocusRectangle(e.Graphics, this.ClientRectangle, Color.Red, Color.Blue);
            }
            base.OnPaint(e);
        }
    }
    
    }

The only "loose end" here for me is that this solution will show the selection rectangle on a mouse-click, but I did not implement any MouseDown code as Thomas suggested.

Note that if you make the Control above a 'ContainerControl via 'SetStyle(ControlStyles.ContainerControl, true); and add some other control to it, even if you set the TabStop property of the added control to 'false : ... if it clicked ... it will get focus, and you will lose the focus rectangle shown on the ContainerControl.

BillW
The methods you marked as `// no effect` do not work because you declared them `virtual`, not `override`. Apparently `UserPaint` and `Selectable` default to `true`. As to #6: not understanding your code is always bad, regardless of whether it works. But I think we now understand what does and doesn't work *and* why, right? :)
Thomas
+1 Many thanks, Thomas for your insights. I have revised the code to incorporate your informatin. !
BillW