Search code examples
c#panelinvalidation

How to call Invalidate not for the whole panel from another event/class


I have a paint event which looks like this:

private void panel1_Paint(object sender, PaintEventArgs e)
{
    Rectangle rec = new Rectangle(2, 2, 820, 620);
    Pen pi = new Pen(Color.Black, 2);
    e.Graphics.DrawRectangle(pi, rec);

    Rectangle rec2 = new Rectangle(Convert.ToInt32((410 + 2500 * GlobaleVariablen.IstWerte[0])), Convert.ToInt32(310 + 1875 * GlobaleVariablen.IstWerte[1]), 2, 2);
    e.Graphics.DrawRectangle(pi,rec2);
}

I have a datastream from a serialport, and everytime I receive data I want to invalidate rec2 but not the whole form. I was able to invalidate the whole form with within my Datareceived Event with:

panel1.Invalidate();

However I do not know how I can make it happen to only invalidate my rec2, because if you invalidate the whole form all the time with a datastream it blinks like crazy and it really does not look very good.


Solution

  • Invalidate() has an overload version with Rectangle you want to invalidate:

    panel1.Invalidate(GetRect2());
    

    Where GetRect2() (please pick a better name) is something like:

    static Rectangle GetRect2() {
        int x Convert.ToInt32((410 + 2500 * GlobaleVariablen.IstWerte[0]));
        int y = Convert.ToInt32(310 + 1875 * GlobaleVariablen.IstWerte[1]);
    
        return new Rectangle(x, y, 2, 2);
    }
    

    In your paint event handler you have first to check if invalidated region intersects each object you want to write (example is simple because you're working with rectangles and you do not have slow expansive filling).

    What will hurt performance more in your code is fact you're creating a new Pen for each paint operation. It's something you have to absolutely avoid: expansive native resources must be reused. Final code may be something similar to:

    private Pen _pen = new Pen(Color.Black, 2);
    private void panel1_Paint(object sender, PaintEventArgs e)
    {
        var rec = new Rectangle(2, 2, 820, 620);
        if (e.ClipRectangle.IntersectsWith(rec))
            e.Graphics.DrawRectangle(_pen, rec);
    
        var rec2 = GetRect2();
        if (e.ClipRectangle.IntersectsWith(rec2))
            e.Graphics.DrawRectangle(pi, rec2);
     }
    

    Now your code is slightly more optimized but it may still blink. To avoid that you have to enable double buffering for your panel. Derive your own class from Panel and add in its constructor:

    SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
    

    It may also be a good chance to refactor your code and move some paint logic in a separate class (but not panel itself). Please refer to MSDN for other flags you may need to use (such as AllPaintingInWmPaint).

    Final note: you hard-coded coordinates, it's not a good practice unless you have a fixed size panel (with or without scrolling) because it won't scale well with future changes and it may be broken in many circumstances (as soon as your code will become slightly more complex than a fictional example).