Is there a preferred, fast method of scrolling within a user control in GDI+?
I've created a control that graphically renders a horizontally scrollable data plot.
Currently, my control operates by converting the position of a horizontal scroll bar into an offset into the data. The control then renders the data that exists between this starting point and an end point calculated based on the width of the control.
This method works, but is very slow. I do not wish to have to manually redraw the entire control surface upon each scroll event. Rather, I'd like to initialize the control by painting the entirety (or some portion) of the graphical data to an offscreen surface, and then virtually scroll the control surface by causing it to read the pre-rendered graphic data starting at the offset calculated by the position of the horizontal scroll bar.
Is BitBlt the only way to do this? Do I really have to manually copy graphics data from one surface to another? Can't I just take over the Paint event and cause it to read the data from the offscreen surface as it renders? This way, the copy and render action are one in the same.
Or, should I do something hokey like paint directly to a Panel control and then just literally scroll the panel itself from left to right?
CLARIFICATION:
Essentially, I want to know the correct way to scroll pre-rendered data. How does one scroll graphical data within a control? Redrawing the pre-rendered graphic is NOT a correct option.
Yes, I would try double-buffering. If you render to an off-screen bitmap, you can just scroll the bitmap around.
You can try rendering the entire graph to one big bitmap and let the scroll bars move it around. However, if your graph is extremely large, then you'll need to limit the size of the bitmap to the visible area, paint just what is visible to it, and handle scrolling virtually as you are now.
Testing on 32-bit Windows XP, I found the limitation is somewhere around 237.9 million pixels. That is certainly plenty for most controls, but it may not be enough for your application.
For the curious, here is the maximum Windows bitmap sizes we were able to create and use:
Width Height Area (pixels)
====== ====== ===========
32,767 7,261 237,921,187
25,000 9,517 237,925,000
23,792 10,000 237,920,000
20,000 11,896 237,920,000
15,861 15,000 237,915,000
15,000 15,861 237,915,000
11,896 20,000 237,920,000
10,000 23,792 237,920,000
9,517 25,000 237,925,000
7,261 32,767 237,921,187
Related
I have a scrollable control whose content sometimes requires the scrollbars to be operational depending on what display the user is using to see the View. So, for example, if the View is being displayed on a laptop screen it will most likely have scroll bars in operation. Whereas, on a 27" monitor all will be visible with no scrollbars present.
What I would like to be able to do is calculate a scale factor that I can use in a ScaleTransform on the ScrollableControl so that the contents are completely visible on screen (what ever that screen happens to be). But I don't know how to do this?
I am aware that I could simply wrap the ScrollableControl with a ViewBox, but my users want to be able to vary the scale factor, so I am using a slider and a ScaleTranform to give them this effect. However, I would like to initialise the slider with the ScaleFactor that I am seeking help with so the View starts off with everything on screen.
I have discovered the System Parameters type which looks to have some useful screen dimension properties, but I'm still confused about how to apply them to work out the scale factor.
I'm faced with a problem: I am trying to automate a control with UI Automation. The control is a viewer in a client application, which hooks into a service hosted remotely. As a result of some legacy design decisions, this viewer simply displays a bitmap on a canvas. When interactions occur (e.g. clicks), the position of the click is sent to the service, which uses the co-ordinates to work out where the click occurred, and react correspondingly. The result of this is a nightmare for UI test automation. There is no way of hooking directly into sub-controls, because they are simply painted on to the bitmap. I have found a back-end way of accessing information about what is in this canvas, but now I need to work out where, in this scrolling bitmap, those items appear, so I can interact with them. I use positional information based on the upper part of the control, but since I don't have access to the bounding rects of these sub-controls, as soon as I scroll, all this information becomes invalid. My main problem is that I can't work out how far the scroll bar moves the canvas. Since scroll bars in UIA only have values from 0-100 (despite the actual magnitude of the scrolling effect), I can't work out how far down the canvas I've moved from a known position (it would depend on how far the scroll bar can move at that given time - i.e. how many sub-controls have been rendered in the bitmap). Is there any way of working out the magnitude of the scroll event on this canvas? I know that this must be done internally - the scroll bar has to know how far to move the canvas, based on the actual size of the canvas. However, the bounding rect of the canvas only gives it's visible on-screen position - it doesn't indicate how big the underlying image is. I either need to get the full size of the bitmap (as if it was rendered fully on screen, without scrolling), or to know how much adjusting the scroll value effects the visible position of the image. Is there any way of working this out?
Specifically: I need to capture as a bitmap a specific region of what a picturebox is actually displaying. The coordinates of the region are specified by the bounds of a control that I have overlayed on top of the picturebox (but that belongs to the picturebox). The control is hidden when I make the "snapshot" of the region.
I tried using normal screen capture methods (CopyFromScreen), but you can't really control the timing there. So it was capturing "interstitial" states, like transitions between photos in my picturebox. Frequently it was only capturing purely black images (the background color of the picture box).
So I tried just converting the image (picturebox.image property) being displayed to a bitmap. The problem there is that the picture box is rarely showing exactly the image. It's displaying some PORTION of the image, scaled and clipped as appropriate to it's sizemode (which is zoom). So the I can't just take my control coordinates and clip them from the image as a whole.
So I tried to estimate what portion of the image was being displayed, and correcting my rectangle based on that. Turns out that I was basically re-creating the "zoom" code of the picturebox to do this (using aspect ratio of the picturebox, the aspect ratio of the image, guessing at what level of scaling is currently happening to the image if it's larger or smaller than the picturebox, etc). It was not pretty.
So: now I need a method of just capturing only the bitmap currently being displayed in the client area of the picturebox, including the photo and any black "letterboxing" currently being displayed around it. Anybody got one?
Remember that I can't rely on using CopyFromScreen. It's not reliable enough for my purposes. I think I need a method of getting picturebox to TELL me the bits it is displaying.
This will copy and save the currently shown content of the PictureBox including a BackgroundImage (if there is one and if it shines through) and also all Controls that belong to the PictureBox, like Labels etc.. Also included are elements drawn in the Paint event. Things drawn outside the Paint event are non-persistent and will not be included.
using (Bitmap bmp = new Bitmap(pictureBox1.ClientSize.Width,
pictureBox1.ClientSize.Height))
{
pictureBox1.DrawToBitmap(bmp, pictureBox1.ClientRectangle);
bmp.Save(yourfilename, ImageFormat.Png);
}
Note: On my test Form the PicureBox is sitting inside an AutoScroll Panel pan_PBscroll. The PictureBox is displaying pixels 1:1 and is therefore, with a photograph loaded, much bigger than the Panel, the Form or even the Screen. So to clip to the actually visible parts I could not use the pictureBox1.ClientSize and pictureBox1.ClientRectangle but used the dimensions of that Panel. This may well apply to you, too.
I'm not sure about your timing issues. But since you mentioned CopyFromScreen here are a few differences:
CopyFromScreen makes a 1:1 copy of each screen pixel
This includes non-persistent drawings and excludes anything covered or hidden
Control.DrawToBitmap makes the Control draw itself onto a Bitmap, just as it draws itself during Paint
This excludes anything that doesn't belong to the Control but includes all members of its Controls collection
This also excludes non-persistent drawings but includes the full Size of the Control, whether it fits on the Form or Screen or not and whether it is hidden or covered or not.
For Controls with active Scrollbars only the visible parts are copied. To copy all you need to resize it temporarily. Then you can get a complete image of a listbox even if it has a thousand items..
Since you're using a PictureBox I would say to take a look PictureBox.Image where you can get the Bitmap object.
Hope it helps.
I have a Form whose main panel is a FlowLayoutPanel that is exactly what I want to print (it is designed to look like a nicely formatted document... no buttons, etc. ... you right-click for commands).
So, I take advantage of DrawToBitmap to implement printing for almost free... I simply resize the Form such that the main panel is the size of the sheet of paper the user wants to print on (minus margins), which causes it to automatically flow its contents properly for that size. I then just use DrawToBitmap to render that FlowLayoutPanel and all its contents to the printed page. I can even scale to fit by growing the window larger than the page size (same aspect ratio), and then scaling down the Bitmap I get from DrawToBitmap. Works great...
BUT it only works if your screen is larger than the page size! Because any attempt to resize a Form or Control larger than the size of the screen + 12 pixels in either direction gets thwarted.
Ugh!! Why tease me with the simplicity and utility of DrawToBitmap if you're going to kill its applicability by refusing to resize larger than the screen? (rhetorical)
My questions:
Is there a way to circumvent that limit and get the Form or the FlowLayoutPanel to resize larger than the screen?
If not, is there a way to get to the FlowLayoutPanel's scrolled surface (not just the portion scrolled into view) such that I can size it to match the printed page and such that I can call DrawToBitmap on it?
If not, is there some other way to leverage my existing layout functionality to print? Or am I stuck essentially rewriting all that WinForms code just to generate the same Bitmap it'll already generate if my screen is bigger than my printed page??
I'm not sure, but would it be possible to do this all virtually. IE:
Dim FLP as FlowLayoutPanel = Form1.FlowLayoutPanel1
For Each flp_subobject as Object in FlowLayoutPanel.Children
FLP.Children.Add(flp_subobject)
Next
FLP.Height = MASSIVEHEIGHT
FLP.Width = MASSIVEWIDTH
DrawToBitmapFunction(FLP)
That is how I would do it, not that I have much experience with regards to exceeding screen limits etc. Let me know how this goes, as it pretty much just came straight out of my head on the spot :D
How can I speed up the scrolling of UserControls in a WinForms app.?
My main form has trouble scrolling quickly on slow machines--painting for each of the small scroll increments is CPU intensive.
My form has roughly fifty UserControls (with multiple fields) positioned one below the other.
I’ve tried intercepting OnScroll and UserPaint in order to eliminate some of the unnecessary re-paints for very small scroll events, but the underlying Paint gets called anyway.
How can I streamline scrolling on slower machines?
The tried-and-true method is to use an offscreen bitmap which is updated only when the data represented by your control actually changes; then, all OnPaint needs to do is render that bitmap to the screen.
If your paint process is intensive, and since you have so many controls, you'll find this makes a massive difference to the performance of your application.
Note that using the DoubleBuffering control property won't help in your case--it does tell WinForms to render to an offscreen bitmap before rendering to the screen, but that still happens at every paint cycle since WinForms doesn't keep track of when the representation has changed.
So, you'd have to roll your own. It's not that difficult. Here's what looks like a reasonably good article on the subject.
You can also increase the size of the scroll step. For example
panel1.VerticalScroll.SmallChange = 100;
Will cause the panel to scroll it's content 100 units vertically per click of the scrollbar button. So you take bigger steps each time, that might make the experience feel better at least. And you can do the same for the horizontal scroll bar of course.
I have used tabs to eliminate scrolling.