Search code examples
c#.netwinformspaint

Update a Control’s Appearance in OnPaint Before it is Made Visible


I have a form with controls (Buttons, Panels, and UserControls) that have modifications to their appearances such as rounded corners. I change the visibility property of these controls depending on inputs from the user.

The problem is when I make one of these controls visible the default version of the control is shown first, then OnPaint is called and the appearance is updated. For example, if I have a panel with rounded corners when I first set the panel’s visibility property to true the panel is shown with sharp corners then it is updated with the rounded corners I programmed. The default appearance of the control is only shown for a moment but the result feels unprofessional.

When I debug the code I see the default appearance is shown as soon as the control’s visibility property is set to true. Next the remaining code in the current method is executed. Then OnPaint is called and the custom appearance I programmed is displayed.

I have created some example code to demonstrates this:

private void Form1_Load(object sender, EventArgs e)
{
    this.BackColor = Color.LightGray;
    myPanel1.BackColor = Color.Black;
}

private void btnChangeVisibility_Click(object sender, EventArgs e)
{
    if (myPanel1.Visible)
    {
        myPanel1.Visible = false;
    }
    else
    {
        myPanel1.Visible = true;

        // Thread sleep to simulate other work being done.
        Thread.Sleep(100);
    }
}
public class MyPanel : Panel
{
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        int cornerRadius = 10;

        Rectangle rctTopLeft = new Rectangle(0, 0, cornerRadius, cornerRadius);
        Rectangle rctTopRight = new Rectangle(e.ClipRectangle.Size.Width - cornerRadius, 0, cornerRadius, cornerRadius);
        Rectangle rctBottomLeft = new Rectangle(0, e.ClipRectangle.Size.Height - cornerRadius, cornerRadius, cornerRadius);
        Rectangle rctBottomRight = new Rectangle(e.ClipRectangle.Size.Width - cornerRadius, e.ClipRectangle.Size.Height - cornerRadius, cornerRadius, cornerRadius);

        e.Graphics.FillRectangle(new SolidBrush(Color.LightGray), rctTopLeft);
        e.Graphics.FillRectangle(new SolidBrush(Color.LightGray), rctTopRight);
        e.Graphics.FillRectangle(new SolidBrush(Color.LightGray), rctBottomLeft);
        e.Graphics.FillRectangle(new SolidBrush(Color.LightGray), rctBottomRight);

        rctTopLeft = new Rectangle(rctTopLeft.Location.X, rctTopLeft.Location.Y, 2 * cornerRadius, 2 * cornerRadius);
        rctTopRight = new Rectangle(rctTopRight.Location.X - cornerRadius - 1, rctTopRight.Location.Y, 2 * cornerRadius, 2 * cornerRadius);
        rctBottomLeft = new Rectangle(rctBottomLeft.Location.X, rctBottomLeft.Location.Y - cornerRadius - 1, 2 * cornerRadius, 2 * cornerRadius);
        rctBottomRight = new Rectangle(rctBottomRight.Location.X - cornerRadius - 1, rctBottomRight.Location.Y - cornerRadius - 1, 2 * cornerRadius, 2 * cornerRadius);

        Color clr;
        clr = Color.Black;

        e.Graphics.FillPie(new SolidBrush(clr), rctTopLeft, 180, 90);
        e.Graphics.FillPie(new SolidBrush(clr), rctTopRight, 270, 90);
        e.Graphics.FillPie(new SolidBrush(clr), rctBottomLeft, 90, 90);
        e.Graphics.FillPie(new SolidBrush(clr), rctBottomRight, 0, 90);
    }
}

Is there anyway to prevent this behavior? I’ve looked into trying to update the control’s appearance before I set the visibility property to true but I can’t figure out a way to get OnPaint to be called while the control’s visibility property is false.

I also tried following this answer to a similar question https://stackoverflow.com/a/487757 but I can’t figure out how to make this work because I can’t get OnPaint to be called while drawing updates are suspended.

Any help would be greatly appreciated.


Solution

  • Turn on double buffering for your control in its constructor:

    public class MyPanel : Panel
    {
    
        public MyPanel()
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.DoubleBuffer, true);
        } 
    
        // ... other code ...
    
    }