2

I need to add a close "x" button in the upper left corner of a UserControl. I'd like that button have similar style as a usual Winforms close button (Blue in XP style, etc).

Is it possible to do such a thing?

serhio
  • 28,010
  • 62
  • 221
  • 374

3 Answers3

5

Here's an example of how to make a simulated button. It does so on the form itself, but works the same on a custom control.

It also shows how to get the "fancy" button style using a VisualStyleRenderer in case the ControlPaint style is too "old".

Updated After six edits, I think I'm happy with it.

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

class Form1 : Form
{
    [STAThread]
    static void Main()
    {
        //Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    public Form1()
    {
        SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.Opaque | ControlStyles.AllPaintingInWmPaint, true);
    }

    Rectangle buttonRect = new Rectangle(10, 10, 50, 20);
    ButtonState buttonState = ButtonState.Normal;

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (buttonRect.Contains(e.Location))
            buttonState = Capture ? ButtonState.Pushed : ButtonState.Checked;
        else
            buttonState = ButtonState.Normal;
        Invalidate(buttonRect);

        base.OnMouseMove(e);
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left && buttonRect.Contains(e.Location))
        {
            Capture = true;
            buttonState = ButtonState.Pushed;
            Invalidate(buttonRect);
        }

        base.OnMouseDown(e);
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        if (Capture)
        {
            Capture = false;
            Invalidate(buttonRect);
            if (buttonRect.Contains(e.Location))
            {
                // The button was clicked

                MessageBox.Show("You clicked the button.");
            }
        }

        base.OnMouseUp(e);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.FillRectangle(SystemBrushes.Window, e.ClipRectangle);

        VisualStyleRenderer renderer = null;
        if (Application.RenderWithVisualStyles)
        {
            switch (buttonState)
            {
                case ButtonState.Checked:
                    if (VisualStyleRenderer.IsElementDefined(VisualStyleElement.Window.CloseButton.Hot))
                        renderer = new VisualStyleRenderer(VisualStyleElement.Window.CloseButton.Hot);
                    break;
                case ButtonState.Pushed:
                    if (VisualStyleRenderer.IsElementDefined(VisualStyleElement.Window.CloseButton.Pressed))
                        renderer = new VisualStyleRenderer(VisualStyleElement.Window.CloseButton.Pressed);
                    break;
                default:
                    if (VisualStyleRenderer.IsElementDefined(VisualStyleElement.Window.CloseButton.Normal))
                        renderer = new VisualStyleRenderer(VisualStyleElement.Window.CloseButton.Normal);
                    break;
            }
        }

        if (renderer != null)
            renderer.DrawBackground(e.Graphics, buttonRect);
        else
            ControlPaint.DrawCaptionButton(e.Graphics, buttonRect, CaptionButton.Close, buttonState);

        base.OnPaint(e);
    }
}

Extended Comment

An extended operation is one that begins with the user pressing a mouse button, (optionally) moving the mouse, and then releasing that button. Clicking on a UI button is always an extended operation. The purpose of capturing the mouse is to prevent it from interacting with other windows during that operation.

You can see this demonstrated right now:

First notice how when you move the mouse over elements on this page (like comments), they change background color.

Now open the Windows Run dialog (Win+R).

Click on the Cancel or Browse... button and keep holding the button.

Move the mouse around to the browser window and notice that it no longer registers your mouse movement.

This is how all buttons work by design. While hardly anyone notices it because you generally click and release without moving the mouse (much).

Moving the mouse off of the button and releasing is a mechanism provided to the user to allow them to change their mind and not "click" the button. That's why you test again in the button up event to make sure they are still on the button before you determine that the user has performed a click.

So there are three reasons to capture the mouse:

First you don't want other windows performing things like highlighting because the user is busy interacting with your window, having other windows doing things during the operation is distracting and potentially confusing.

Second you don't want the user's release of the mouse button to be sent to another window on the off chance that they move the mouse all the way onto another window. A spurious mouse release without an accompanying press can confuse poorly written applications.

Third, for certain extended operations like painting or "wrangling", you want to know the coordinates of the mouse even when they are outside the boundaries of your window. You can see an example of wrangling by clicking and holding on the desktop and moving the mouse around. Notice how the rectangle changes even when you move the mouse over other windows.

Tergiver
  • 14,171
  • 3
  • 41
  • 68
  • I try to implement this code on a custom buttom... and `MessageBox.Show("You clicked the button.");`? is a little confusing – serhio May 16 '11 at 16:07
  • @Tergiver: This code has been very useful for me, thanks. Can you please explain why you are using Capture to track "mousedown" state? Isn't Capture a property of the Button class itself? – Sabuncu Mar 16 '14 at 19:20
  • @Sabuncu Capture is a state of the associated system window object. Since we are calling `this.Capture` the window in question is the form window. Capture allows you to get WM_MOUSEMOVE messages (OnMouseMove events) even when the mouse moves outside the boundary of the window, for as long as the window has capture. – Tergiver Mar 19 '14 at 20:22
  • @Sabuncu Here we are simulating one button, but you would likely have more simulated buttons. These buttons don't have associated system windows, they are simulated. – Tergiver Mar 19 '14 at 20:27
  • @Tergiver Believe me, at this point, I am very familiar w/ your code, I have it running, and it works great, and I know the button is simulated through a rectangle. What I don't understand is why one can set Capture? Shouldn't this property be read-only? That is, I am having problem understanding certain basics of Windows Forms. – Sabuncu Mar 19 '14 at 22:16
  • @Tergiver In fact, I have extended your code to implement dimming as well! ;-) – Sabuncu Mar 19 '14 at 22:18
  • @Sabuncu see the Extended Comment above. – Tergiver Mar 20 '14 at 17:51
  • @Tergiver Now I understand, THANK YOU so much. They should have named this property "ExtendedOp" instead of "Capture". Many thanks. – Sabuncu Mar 20 '14 at 19:03
3

Updated Answer
I just found out there the ControlPaint class in .net, which does the same thing as the DrawFrameControl function. Use that instead!

You could use the ControlPaint class to implement a button control that paints itself to look like the Windows "Close" button (override OnPaint). Depending on the required level of complexity, you might implement the mouse-over-events and mouse-down-events, too, to provide visual feedback for the current state. If you place that control on your UserControl you should be on the right track.

Original, obsolete Answer
You might want to look at the DrawFrameControl API function in windows.

The combination of DFC_CAPTION and DFCS_CAPTIONCLOSE should do what you want.

Thorsten Dittmar
  • 55,956
  • 8
  • 91
  • 139
  • not very clear, however... Should I override OnPaint method... how should I manage the click on it... – serhio May 16 '11 at 15:12
  • Well, you didn't say you wanted to click the button, too. This method just draws something that looks like the button. Of course you must call `ControlPaint` somewhere where painting is done! Easiest would be to implement a control that paints itself using `ControlPaint` and reacts to clicks (`ControlPaint` is also able to draw pressed buttons). That way you can just place your button on your user control and make it act like a normal button. – Thorsten Dittmar May 16 '11 at 15:14
  • Further clarification: Implement a clickable control that paints itself using `ControlPaint` (override `OnPaint` for that). Place that on your `UserControl` and react when it is clicked. – Thorsten Dittmar May 16 '11 at 15:16
  • Huh? Getting downvoted for providing the correct answer? That's new. The OP is asking how to draw a close button, I'm telling him how to draw a close button, additionally even providing information on how to turn it into an actual button - if that isn't a valid answer I don't know... – Thorsten Dittmar May 16 '11 at 15:19
  • in other words, I should build my own button, paint the button when is pressed, paint the button when is not pressed, paint the button when is mouseOver, mouse out, etc etc... – serhio May 16 '11 at 15:20
  • @Thorsten: I downvoted, because it was (not yet) useful 4 me. Sorry. Please modify your answer with the precisions from the comments then I will be able to upvote it. – serhio May 16 '11 at 15:23
  • Well, it depends: If you do not actually NEED to display these states, then don't paint them (for example if you just want to close your control when the button is clicked without further visual feedback)! If you, however, want to totally clone the close button with all its visual feedback then yes, this would be the way. – Thorsten Dittmar May 16 '11 at 15:24
0

not sure it is possible but that would definately confuse the users if your main Control that will host this UserContorl will have these 3 buttons (min,Max/close) and your UserControl as well. what's the rational behind this?

Bek Raupov
  • 3,782
  • 3
  • 24
  • 42
  • He might want to implement some docking functionality or a "close button on a tab"-thing. – Thorsten Dittmar May 16 '11 at 14:51
  • i have used infragistics dock manager in the past, never thought of this scenario. tnx – Bek Raupov May 16 '11 at 14:55
  • This is rather a comment that an answer. Will it confuse, or not, this is an other problem. I need a floating usercontrol like a toolbar in Photoshop that you can move over a picture, or close it if you don't need it anymore. – serhio May 16 '11 at 15:16