Search code examples
c#-4.0user-controlsonpaint

update just a portion of a usercontrol


I'm developing a timeline user control for video usage. here is a photo of my work: enter image description here

here i have problem. when currentTime changes, I have to update the UI. but when i use this.Invalidate(); it refreshes the whole control. but i want to just update the pointer (the white line in gray background). becuase it cause the control blink over and over on very tiny time change. how to just update the pointer? here is my onPaint method

 private void OnPaint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        int width = this.Width;
        int height = this.Height;
        int Floor = height - FloorDistance;
        Pen SecPen = new Pen(SecColor);
        Pen MinPen = new Pen(MinColor);
        SolidBrush TimeLineBrush = new SolidBrush(Color.Gray);
        StringFormat stringFormat = new StringFormat();
        SolidBrush TimesolidBrush = new SolidBrush(TimeColor);
        SolidBrush BackBrush = new SolidBrush(TimlineBackColor);
        SolidBrush PointerBrush = new SolidBrush(PointerColor);
        stringFormat.FormatFlags = StringFormatFlags.DirectionVertical;
        switch (Zoom)
        {
            case ZoomLEvel.Quarter:
                width = (int)Math.Ceiling((TotalDuration / 900)) * ThickDistance;
                break;
            case ZoomLEvel.Minute:
                width = (int)Math.Ceiling((TotalDuration / 60)) * ThickDistance;
                break;
            case ZoomLEvel.Secound:
                width = (int)Math.Ceiling(TotalDuration) * ThickDistance;
                break;
            case ZoomLEvel.MiliSecound:
                width = (int)Math.Ceiling(TotalDuration * 10) * ThickDistance;
                break;
        }
        width += 11;
        this.Width = width;

        e.Graphics.FillRectangle(TimeLineBrush, 0, Floor, width, 3);
        e.Graphics.FillRectangle(BackBrush, 0, 0, width, height - FloorDistance);

        int x = ThickDistance;
        int step = 0;
        while (x <= width - ThickDistance)
        {
            if (step % 5 == 0)
            {
                e.Graphics.DrawLine(MinPen, x, Floor, x, Floor - _MinHeight);
                // print time
                string time = "";
                double totalSecounds = 0;
                PointF pointF = new PointF(x - 8, Floor + 5);


                switch (Zoom)
                {
                    case ZoomLEvel.Quarter:
                        totalSecounds = step * 900;
                        break;
                    case ZoomLEvel.Minute:
                        totalSecounds = step * 60;
                        break;
                    case ZoomLEvel.Secound:
                        totalSecounds = step;
                        break;
                    case ZoomLEvel.MiliSecound:
                        totalSecounds = step / 10d;
                        break;
                }

                time = (new TimeSpan(0, 0, 0, (int)totalSecounds, (int)(step % 10) * 100)).ToString(@"hh\:mm\:ss\:fff");
                e.Graphics.DrawString(time, this.Font, TimesolidBrush, pointF, stringFormat);

            }
            else
                e.Graphics.DrawLine(SecPen, x, Floor, x, Floor - _SecHeight);
            x += ThickDistance;
            step++;
        }
        int PointerTime = 0;//(int)Math.Floor(CurrentTime);
        int pointerX = 0;
        switch (Zoom)
        {
            case ZoomLEvel.Quarter:

                PointerTime = (int)Math.Floor(CurrentTime / 900);
                pointerX = PointerTime * ThickDistance;
                break;
            case ZoomLEvel.Minute:
                PointerTime = (int)Math.Floor(CurrentTime / 60);
                pointerX = PointerTime * ThickDistance;
                break;
            case ZoomLEvel.Secound:
                PointerTime = (int)Math.Floor(CurrentTime);
                pointerX = PointerTime * ThickDistance;
                break;
            case ZoomLEvel.MiliSecound:
                PointerTime = (int)Math.Floor(CurrentTime * 10);
                pointerX = PointerTime * ThickDistance;
                break;
        }
        pointerX += 5;
        e.Graphics.FillRectangle(PointerBrush, pointerX, 0, 2, height - FloorDistance);
    }

Solution

  • An overload of the Invalidate() method takes a Rectangle type parameter that defines the region that you want to refresh, called the clipping rectangle. You should pass the bounding rectangle of your pointer (white line, with maybe 10 pixels padding on all sides). In your OnPaint() method, you should check e.ClipRectangle property for the area that needs to be redrawn. Now for all your drawing logic (e.Graphics.DrawX() calls), you should first confirm if that element intersects with the clipping region (can easily be done using Rectangle.IntersectsWith()). If it does, you should call the DrawX method, otherwise you shouldn't.

    To avoid flicker, you should turn on the DoubleBuffered property of your control to True. The goes a long way into achieving a truly smooth rendering.

    Additionally, I see that you're declaring a lot of brushes and pens in your OnPaint method and not disposing them after using. I assume that many of these will be required again and again, so you may want to declare them at class-level, and then dispose them off in the Dispose() method of your control. This will save you some processing, considering that this is a video-related application.

    Hope that helps.