Taher
Taher

Reputation: 593

C# Check if control's parent lost focus

I have a custom control, a Hover button, that simply shows borders whenever a mouse moves over it or got focus.

Everything is fine but I am having problem handling a situation where I click the button and it shows another form (ShowDialog) or in case of (MessageBox), and then return back to the parent form of the control.

This is the control code:

namespace CustomControlTutorial
{
    using System;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Windows.Forms;

    public partial class HoverButton : Button
    {
        byte eventPaintMode = 0;
        //Color borderClr = Color.Transparent;

        public HoverButton()
        {
            InitializeComponent();
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            //base.OnPaint(pe);

            // render background
            Color clr = this.Parent.BackColor;
            Brush b = new SolidBrush(clr);
            pe.Graphics.FillRectangle(b, ClientRectangle);

            // Draw borders depending on event case
            Point p00 = new Point(0, 0);
            Point p01 = new Point(0, ClientSize.Height - 1);
            Point p10 = new Point(ClientSize.Width - 1, 0);
            Point p11 = new Point(ClientSize.Width - 1, ClientSize.Height - 1);

            Pen pen1;
            Pen pen2;

            switch (eventPaintMode)
            {
                case 1:
                    {
                        // draw borders (using pen)
                        pen1 = new Pen(new SolidBrush(Color.DimGray), 1);
                        pen2 = new Pen(new SolidBrush(Color.White), 1);

                        pe.Graphics.DrawLine(pen1, p10, p11);
                        pe.Graphics.DrawLine(pen2, p00, p10);
                        pe.Graphics.DrawLine(pen1, p01, p11);
                        pe.Graphics.DrawLine(pen2, p00, p01);

                        break;
                    }
                case 2:
                    {
                        pen2 = new Pen(new SolidBrush(Color.DimGray), 1);
                        pen1 = new Pen(new SolidBrush(Color.White), 1);

                        pe.Graphics.DrawLine(pen1, p10, p11);
                        pe.Graphics.DrawLine(pen2, p00, p10);
                        pe.Graphics.DrawLine(pen1, p01, p11);
                        pe.Graphics.DrawLine(pen2, p00, p01);

                        break;
                    }
                case 3:
                    {
                        // draw borders (using pen)
                        pen1 = new Pen(new SolidBrush(Color.DimGray), 1);
                        pen2 = new Pen(new SolidBrush(Color.White), 1);

                        pe.Graphics.DrawLine(pen1, p10, p11);
                        pe.Graphics.DrawLine(pen2, p00, p10);
                        pe.Graphics.DrawLine(pen1, p01, p11);
                        pe.Graphics.DrawLine(pen2, p00, p01);

                        // draw focus lines
                        pen1 = new Pen(new SolidBrush(Color.Black), 1);
                        pen1.DashCap = DashCap.Round;
                        pen1.DashPattern = new float[] { 1, 1, 1, 1 };
                        p00 = new Point(5, 5);
                        p01 = new Point(5, ClientSize.Height - 6);
                        p10 = new Point(ClientSize.Width - 6, 5);
                        p11 = new Point(ClientSize.Width - 6, ClientSize.Height - 6);

                        pe.Graphics.DrawLine(pen1, p10, p11);
                        pe.Graphics.DrawLine(pen1, p00, p10);
                        pe.Graphics.DrawLine(pen1, p01, p11);
                        pe.Graphics.DrawLine(pen1, p00, p01);

                        pe.Graphics.SmoothingMode = SmoothingMode.None;

                        break;
                    }

                default:
                    break;
            }

            // render text
            String drawString = this.Text;
            SizeF size = pe.Graphics.MeasureString(drawString, this.Font);
            float pointX = ((float)ClientSize.Width - size.Width) / 2;
            float pointY = ((float)ClientSize.Height - size.Height) / 2;

            pe.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), (int)pointX, (int)pointY);
            b.Dispose();
        }

        protected override void OnMouseEnter(EventArgs e)
        {
            base.OnMouseEnter(e);
            eventPaintMode = 1;
            Invalidate();

        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            eventPaintMode = 0;
            Invalidate();
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            if (e.Button == MouseButtons.Left)
            {
                eventPaintMode = 2;
                Invalidate();
            }

        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            if (e.Button == MouseButtons.Left)
            {
                eventPaintMode = 1;
                Invalidate();
            }
        }

        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);
            eventPaintMode = 3;
            Invalidate();
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);
            eventPaintMode = 0;
            Invalidate();
        }

    }
}

And this is where I show a test message box:

private void hoverButton1_Click(object sender, EventArgs e)
{
    MessageBox.Show("Test");
}

I can't find an event like, OnParentLostFocus or something like that.

Upvotes: 1

Views: 347

Answers (1)

dr.null
dr.null

Reputation: 4660

The problem is that when I set the button to show a message, the button's parent (Form) can't be accessed until the message is closed. So there is no connection between the 2 forms that I can use to make any changes to the button..

This is how it works. The parent From and its children won't receive the windows paint messages to redraw themselves as long as the modal dialog is shown.

Your code creates:

SOQ65313268A

As you can see, the button draws two different effects on tab stops. A hover and focus effects. While it draws the hover effect and ignores the focus one on the MouseEnter even if the control is currently has the focus. Another thing is, the button doesn't show or draw the normal/default state when the modal dialog is closed.

IMHO, the two things shouldn't combine, interact with the mouse events to draw the hover and down effects, and draw the focus rectangle whenever the control gains the focus and the mouse effects are not applied. Just like how the default System.Windows.Forms.Button works.

For example:

// Your namespace...
public enum MouseState : int
{
    None, Over, Down
}

[DesignerCategory("Code")]
public class HoverButton : Button
{
    private MouseState state = MouseState.None;

    public HoverButton() : base()
    {
        SetStyle(
            ControlStyles.AllPaintingInWmPaint |
            ControlStyles.UserPaint |
            ControlStyles.ResizeRedraw, true);
        UpdateStyles();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        var g = e.Graphics;
        var r = ClientRectangle;

        g.Clear(Parent.BackColor);

        var p00 = new Point(0, 0);
        var p01 = new Point(0, r.Height - 1);
        var p10 = new Point(r.Width - 1, 0);
        var p11 = new Point(r.Width - 1, r.Height - 1);

        switch (state)
        {
            case MouseState.Over:
                if (r.Contains(PointToClient(MousePosition)))
                {
                    g.DrawLine(Pens.DimGray, p10, p11);
                    g.DrawLine(Pens.White, p00, p10);
                    g.DrawLine(Pens.DimGray, p01, p11);
                    g.DrawLine(Pens.White, p00, p01);
                }
                break;
            case MouseState.Down:
                g.DrawLine(Pens.White, p10, p11);
                g.DrawLine(Pens.DimGray, p00, p10);
                g.DrawLine(Pens.White, p01, p11);
                g.DrawLine(Pens.DimGray, p00, p01);
                break;
            default:
                break;
        }

        TextRenderer.DrawText(g, Text, Font, r, ForeColor,
            TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);

        if (Focused && MouseButtons == MouseButtons.None &&
            !r.Contains(PointToClient(MousePosition)))
        {
            r.Inflate(-2, -2);
            ControlPaint.DrawFocusRectangle(g, r);
        }
    }

    protected override void OnMouseEnter(EventArgs e)
    {
        base.OnMouseEnter(e);
        state = MouseState.Over;
        Invalidate();
    }

    protected override void OnMouseLeave(EventArgs e)
    {
        base.OnMouseLeave(e);
        state = MouseState.None;
        Invalidate();
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        if (e.Button == MouseButtons.Left)
        {
            state = MouseState.Down;
            Invalidate();
        }
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        base.OnMouseUp(e);
        state = MouseState.Over;
        Invalidate();
    }

    protected override void OnEnter(EventArgs e)
    {
        base.OnEnter(e);
        state = MouseState.None;
        Invalidate();
    }

    protected override void OnLeave(EventArgs e)
    {
        base.OnLeave(e);
        state = MouseState.None;
        Invalidate();
    }
}

Which creates:

SOQ65313268B

Side Notes

  • Call the e.Graphics.Clear(..) method to clear the drawing canvas with the desired color.
  • Use the predefined Brushes and Pens whenever it's possible instead of creating new graphics objects.
  • The TextRenderer is better choice to draw strings over controls, while the e.Graphics.DrawString(..) is better one to draw over images.
  • To center the text in the control, draw it in a rectangle and use an overload where you can pass the TextFormatFlags (or the StringFormat with the Graphics.DrawString(..) method) to dictate that along with the other text layout information.
  • Don't forget to dispose the graphics objects (pen1 and pen2 in your code).

Upvotes: 1

Related Questions