Search code examples
c#animationtransparencypictureboxpanels

C# Transparent Panels Refresh Problems


I'm currently working on a project that requires animation on a transparent panel. I have been able to create a transparent panel and draw on it, but when I refresh the panel, where I have drawn with the pen tool is not being redrawn as transparent. This is leaves the last position of what I drew previously stained on the panel.

I assume there is a simple way to override OnPaint or Refresh to redraw all of the pixels on the panel as transparent, but I can't find a solution online or figure it out myself.

Here is the code I used to make the background transparent:

public class TransparentPanel : Panel
{
    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x20;
            return cp;
        }
    }

    protected override void OnPaintBackground(PaintEventArgs e)
    {
    }
 }

This was my best failed attempt to redraw the background as transparent:

protected override void OnPaint(PaintEventArgs pe)
{
        pe.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(100,255,255,255)), this.ClientRectangle);
}

The problem with this solution is that the background becomes opaque white after a couple refreshes.

Can anyone help me figure out how to do this? I'm super new to graphics and animation, and I assume this has a fairly simple answer. Thanks in advance.


***EDIT*** As per Dyppl's response I have changed the way I draw the graphics to the panel. Here is my current code:

    public TransparentPanel fighterPanel;
...
    fighterPanel.Paint +=new PaintEventHandler(fighterPanel_Paint);
...
    fighterPanel = new TransparentPanel();
    fighterPanel.Location = new System.Drawing.Point(600, 300);
    fighterPanel.Size = new System.Drawing.Size(400, 400);
    gameArenaForm.Controls.Add(fighterPanel);
    fighterPanel.BringToFront();        
...
    private void fighterPanel_Paint(object sender, PaintEventArgs e)
    {
        using (Pen blackPen = new Pen(Color.Black, 3), redPen = new Pen(Color.Red, 3), bluePen = new Pen(Color.Blue, 3))
        {
            //head     
            e.Graphics.DrawEllipse
                (blackPen,
                head.DrawPoint(torso.GetTorsoAngle(), torso.neck.getLocationX(), torso.neck.getLocationY()).X,
                head.DrawPoint(torso.GetTorsoAngle(), torso.neck.getLocationX(), torso.neck.getLocationY()).Y,
                head.radius * 2,
                head.radius * 2
                );
            //torso
            e.Graphics.DrawLine(blackPen, torso.neck.getLocationX(), torso.neck.getLocationY(), torso.shoulders.getLocationX(), torso.shoulders.getLocationY());
            e.Graphics.DrawLine(blackPen, torso.shoulders.getLocationX(), torso.shoulders.getLocationY(), torso.hips.getLocationX(), torso.hips.getLocationY());
            //right arm
            e.Graphics.DrawLine(blackPen, torso.shoulders.getLocationX(), torso.shoulders.getLocationY(), rightArm.elbow.getLocationX(), rightArm.elbow.getLocationY());
            e.Graphics.DrawLine(redPen, rightArm.elbow.getLocationX(), rightArm.elbow.getLocationY(), rightArm.attachHand.getLocationX(), rightArm.attachHand.getLocationY());
            //left arm
            e.Graphics.DrawLine(blackPen, torso.shoulders.getLocationX(), torso.shoulders.getLocationY(), leftArm.elbow.getLocationX(), leftArm.elbow.getLocationY());
            e.Graphics.DrawLine(bluePen, leftArm.elbow.getLocationX(), leftArm.elbow.getLocationY(), leftArm.attachHand.getLocationX(), leftArm.attachHand.getLocationY());
            //right leg
            e.Graphics.DrawLine(blackPen, torso.hips.getLocationX(), torso.hips.getLocationY(), rightLeg.knee.getLocationX(), rightLeg.knee.getLocationY());
            e.Graphics.DrawLine(redPen, rightLeg.knee.getLocationX(), rightLeg.knee.getLocationY(), rightLeg.attachFoot.getLocationX(), rightLeg.attachFoot.getLocationY());
            //left leg
            e.Graphics.DrawLine(blackPen, torso.hips.getLocationX(), torso.hips.getLocationY(), leftLeg.knee.getLocationX(), leftLeg.knee.getLocationY());
            e.Graphics.DrawLine(bluePen, leftLeg.knee.getLocationX(), leftLeg.knee.getLocationY(), leftLeg.attachFoot.getLocationX(), leftLeg.attachFoot.getLocationY());
        }
    }

Here are some pictures of before and after the object moves and refreshes a few times:

Before

After

Sorry, it won't let me embed the images unless I have 10 Rep.


Solution

  • You shouldn't use CreateGraphics for your task. Subscribe to the Paint event of your panel and do all your drawing in the event handler using PaintEventArgs.Graphics as a Graphics object instead.

    private void transparentPanel1_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.DrawRectangle(new Pen(Color.Red,2), 4,4,64,64);
    }
    

    Here's what I got using your TransparentPanel and my event handler:

    Result image

    UPDATE: The problem is that you have to call Graphics.Clear method in the beginning of every draw cycle in order to get rid of all previous drawings. It will mess up your transparent background. This link gives you some advices regarding the problem. The Key point is that you should invalidate the parent control of your panel as well to get your "transparent" background.

    So, I left the code of TransparentPanel untouched but changed the code of main form:

    readonly Random _rand =new Random();
    
    private void transparentPanel1_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.DrawRectangle(new Pen(Color.Red,2), _rand.Next(60),_rand.Next(30),64,64);
    }
    
    private void button1_Click(object sender, EventArgs e)
    {
       Invalidate(new Rectangle(transparentPanel1.Location, transparentPanel1.Size),true);
    }
    

    Now, whenever I click the button on my form the panel gets redrawn and shows one rectangle in random location. If you invalidate the transparantPanel1 itself, it won't clear and you'll see a mess of red rectangles.


    UPDATED (By Alex): This is the code provided in the link from Dyppl that solved the problem. It just needs to be placed in the transparent panel class:

    public void InvalidateEx()
        {
            if (Parent == null)
                return;
            Rectangle rc = new Rectangle(this.Location, this.Size);
            Parent.Invalidate(rc, true);
        } 
    

    I call this code at every refresh on the panel and it keeps the background transparent.