Drawing a moving line in a transparent panel in C#

2020-02-16 03:43发布

问题:

There seems to be a million questions out there on this, yet I can't find one that will work. So, I guess it's time for question 1,000,001.

I have a custom control with a PictureBox and a Panel. The Panel is the child of PictureBox with a transparent background. This allows me tp draw on top of whatever image is loaded in the PictureBox.

The drawing part works, but the erasing part does not. And if I use Invalidate() I just get a bunch of flickering, and the line never even shows.

If the end goal isn't obvious, it should work like any decent drawing application, where you click in one spot, drag around, and the line moves with the mouse until you let go.

Code:

private void drawLine(Point pt) {
    // Erase the last line
    if (m_lastPoints != null) {
        m_graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
        m_graphics.DrawLine(m_transPen, m_lastPoints[0], m_lastPoints[1]);
    }

    // Set the last points
    m_lastPoints = new Point[] { m_mouseStartPoint, pt };

    m_graphics.DrawLine(new Pen(m_color), m_mouseStartPoint, pt);
}

m_transPen is defined as new Pen(Color.FromArgb(0, 0, 0, 0));

And the result:

Now, if I change it to:

m_graphics.DrawLine(Pens.White, m_lastPoints[0], m_lastPoints[1]);

I get this, which shows what it should be doing, only instead of with white lines, they should be transparent.

回答1:

No need to erase the old line! Just invalidate the Panel and draw the fresh one, best in the Paint event.

But for this to work, the Panel must not overlay the PictureBox. It must be inside it! Put this in the load or constructor event :

yourPanel.Parent  = yourPictureBox;
yourPanel.Size = yourPictureBox.Size; 
yourPanel.Location = Point.Empty;

(I know you got that one right already, but maybe the next person only looks at the answer ;-)

To avoid flicker use a double-buffered Panel..:

class DrawPanel : Panel
{
    public DrawPanel()
    {
        DoubleBuffered =  true;
    }
}

..or, better yes, a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.

Actually, if you only want to draw something on top of the loaded Image, you don't even need a separate Panel. Just draw on the PictureBox itself! It has three independent layers: BackgroundImage, Image and the Control surface..

Here is the minimal code to draw a Cursor controlled line:

pictureBox1.MouseDown += pictureBox1_MouseDown;
pictureBox1.MouseMove += pictureBox1_MouseMove;
pictureBox1.MouseUp   += pictureBox1_MouseUp;
pictureBox1.Paint += pictureBox1_Paint;

// class level
Point mDown   = Point.Empty;
Point mCurrent = Point.Empty;

void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    if (mDown != Point.Empty) e.Graphics.DrawLine(Pens.White, mDown, mCurrent);
}

void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
    mDown = Point.Empty;
}

void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
        mCurrent = e.Location;
        pictureBox1.Invalidate();
    }
}

void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    mDown = e.Location;
}

The line disappears when you release the mouse button.

To make it permanent you need to store its two points in a list of data needed to draw them and work through that list in the Paint event.

That list should probably also include the color, pen width and then some, so designing a class 'drawAction' will help..