0

I need to draw a PORTION of the graphics contents in a buffer to the screen during OnPaint override for a custom control, but only familiar with using .Render which draws the whole thing.

I use code like this to create the buffer.

private BufferedGraphicsContext context;
private BufferedGraphics grafx;

context = BufferedGraphicsManager.Current;
grafx = context.Allocate(CreateGraphics(), DisplayRectangle);

At various events things get drawn to buffer. Then...

protected override void OnPaint(PaintEventArgs e)
{
   grafx.Render(e.Graphics);
}

This fills the control using the entire buffer - as it should. Sometimes, I only want to draw a portion of the content of buffer grafx.Graphics to e.Graphics. Like dest Rect <- src Rect or something.
Any ideas?

------------ Following part added 4/4/2023 in response to comment

So my project has two custom controls: one is a track bar with some custom features and the other is an audio waveform viewer that shows a cursor (a vertical line across the horizontally oriented bitmap strip). it's called PictureZoomer because there will ultimately be a zoomed in version of the wave below. The track bar is owner-drawn entirely from geometric commands in its OnPaint(). The waveform viewer creates a graphics buffer that covers the entire surface of the control. When a song is opened, a waveform bitmap is created and drawn to the buffer. When music is playing, a timer periodically updates the song playback position including changing the track bar .Value and also the wave viewer .Position (which is where to draw the cursor). I also can change the playback position by selecting the track bar "thumb" and moving it around. The controls are drawn in such a way that the track bar thumb seems attached to the waveform cursor as one unit.

screen shot of waveform viewer +  track bar

tldr; The problem is: The cursor movement handling is too slow, and visually the cursor and tracking thumb get "disconnected" if I drag the thumb even moderately quickly. The OnPaint() of the viewer renders, with .Render(), the entire buffer to screen when the cursor position is changed, and then the cursor is drawn on top. I'd like OnPaint() only to redraw enough of the buffer to "erase" the cursor's former position, and then draw it again in a new, slightly shifted, spot.

The viewer class starts with...

    public partial class PictureZoomer : Control
    {   // PRIVATE VARIABLES
        BufferedGraphicsContext myContext = BufferedGraphicsManager.Current;
        BufferedGraphics myBuffer; // used to  self-manage double buffer graphics
        private Rectangle topFrame = new Rectangle();

In my properties section there is...

        private int _position = 0;
        public int Position
        {
            get => _position;
            set
            {
                if ((value != _position) && (value >= 0) && (value <= PictureWidth))
                {
                    /*if (value > _position) // moving right
                        Invalidate(new Rectangle(Gutter + _position - 2, topFrame.Top, value - _position + 2, topFrame.Height));
                    else // moving left
                        Invalidate(new Rectangle(Gutter + value - 2, topFrame.Top, _position - value + 2, topFrame.Height));*/
                    _position = value;
                    Invalidate();
                }
            }
        }

And also...

        protected override void OnPaint(PaintEventArgs pe)
        {
            myBuffer.Render();
            pe.Graphics.DrawLine(new Pen(SystemColors.Highlight, 2), Gutter + Position, topFrame.Top, Gutter + Position, topFrame.Bottom);
       }

Finally the track bar (slider1) emits an event when the thumb is moved that is handled in the Form...

        private void slider1_ValueChanging(object sender, EventArgs e)
        {
            toolTip1.SetToolTip(slider1, FormatTime(Convert.ToInt32(slider1.Value)));
            pzmWaveViewer.Position = Convert.ToInt32(slider1.Value * Convert.ToSingle(pzmWaveViewer.PictureWidth) / (slider1.ValueMax - slider1.ValueMin));
        }

In the _position property, I currently Invalidate() the whole thing. You can see my former attempt to invalidate just around the cursor - that "worked" but then I couldn't draw the cursor in a new place outside the clipping region. I tried .ResetClip() after the render but that seemed to fail (re: my 2nd comment below). In the code shown, I have extended the invalidate region to include the new cursor position, which works but isn't ideal - and still prevents me drawing outside the clipRegion so I can't add things like tab stop indicators, which I will need at some point.

So my latest thinking was to avoid the invalidate and just set some flag so that in OnPaint I would copy in from the buffer enough to erase the cursor and then draw a new one. That's the foundation for my original post.

Also - I don't know how to ever state these problems in a concise way that makes sense! Sorry for the lengthy addition.

Harald K
  • 26,314
  • 7
  • 65
  • 111
  • https://learn.microsoft.com/en-us/dotnet/api/system.drawing.graphics.setclip?view=windowsdesktop-8.0 – Hans Passant Apr 04 '23 at 19:44
  • Thanks. Leads me to another question though -tried to ask both questions at once before but it got yanked. That is to ask, is pasting large bitmap (or other) into a context with clipping essentially the same as pasting only part of it? I mean to say performance wise. If a) the pixel-by-pixel process of attempting to draw occurs over the whole image and rejected from visibility due to not testing inside a clip region downstream, then clipping would be slow; but if b) the region over which to even attempt copying is determined initially, then performance would be about like drawing a subset. – theDickChuck Apr 04 '23 at 21:01
  • Also, not to be a total numbskull, but I thought I can't change the clipping region of event.Graphics within OnPaint. https://stackoverflow.com/questions/7507602/best-practice-for-onpaint-invalidate-clipping-and-regions "You can't reset this clip or escape from these clip bounds, but you shouldn't need to. When painting, you generally shouldn't care about how it's being clipped unless you desperately need to maximize performance." Can I set the clipping on the source, or am I confused about your suggestion... and thanks. – theDickChuck Apr 04 '23 at 21:08
  • Let's not have to guess at this, post the code you have trouble with. – Hans Passant Apr 04 '23 at 21:27
  • Sorry for lengthy update. And if anyone can help me out of this whole of confusion I'd be forever in debt, and I meant "w"hole. My fate worsens... I can pretty much verify that the .Render() of the buffered graphics IGNORES the clip rect set by invalidate. I stripped down the code to where the waveform bitmap creator updates the buffer with waveform then invalidates a small rect in the top left corner. When it does paint, the waveform bitmap fills its frame but the cursor is chopped by clipping. I have to totally rethink things if this is true. – theDickChuck Apr 05 '23 at 05:25
  • Starting to regain sanity... I had begun this by altering a sample code I found that seemed very relevant. Apparently, the Render() needs to be Render(paintEventArgs.Graphics). Otherwise it goes to the "default device"... which I guess in this case is the parent form or something... Anyway, it will clip according to Invalidate() now, at least. – theDickChuck Apr 05 '23 at 05:46
  • Can confirm .Render is clipping correctly now... Performance better but still gets laggy. I want to try drawing just some of the buffer, instead of drawing it all and let Windows clip it. Is it wrong to think that would be faster? – theDickChuck Apr 06 '23 at 22:41

0 Answers0