I need to print a screenshot of a silverlight UserControl, which I have tried doing the usual way with PrintDocument, but unfortunately it takes too long and because it has to take place on the UI thread the whole application is locked up for too long. It takes so long because the control contains a grid that needs to be measured and printed over many pages.
So I'm looking for a way to do this without locking up the UI thread. Is there any way I can render these controls again (separately from the originally rendered visible controls) on a background thread? The plan would be to then send those to the PrintDocument, or if that isn't possible to use a WritableBitmap to take screenshots of them.
I've had a quick go already but of course I always get thread affinity issues. Given that I don't want these controls to be visible to the user though I'm hoping there is a way to get around that?
Cheers
You can use WriteableBitmap
Get a reference to the root element that you want to reference. You can use XamlReader if you want to load up the XAML dynamically from an external source if you want. Call it ScreenshotRoot
WriteableBitmap bmp = new WriteableBitmap(ScreenshotRoot.RenderSize.Width, ScreenshotRoot.RenderSize.Height)
bmp.Render(ScreenshotRoot, new MatrixTransform());
bmp.Invalidate();
You should be able to do this in another thread.
Related
I am writing an application in .NET that has a plugin interface. That plugin interface provides several ways to draw information (controls) onto the surface of the application window. While there are several reasons why I am doing this, the main reason is to provide custom colorization to text, either through the use of a graphic or directly manipulating the color of the text based on the background color. I do this through the use of a "text mask" which is a black and white bitmap that works as an "alpha" map to let the Paint method know where to apply the texture/color changes.
The plugin developer has the option of using regular text (such as with a label), mask text (which is drawn to the mask rather than as a regular control), OR letting the user decide. To go along with this, I have provided a modified label class that can either be drawn "normally' (when the text mask is not set for the control), or to the text mask when the User OR Developer decides (depending on what the plugin developer wishes to offer to the user). Here is the class's code so that you understand how this is being done:
public class MaskingLabel : Label
{
private static readonly SolidBrush maskBrush = new SolidBrush(Color.White);
public Bitmap Mask { get; set; }
public MaskingLabel() : base() { }
protected override void OnPaint(PaintEventArgs e)
{
if (Mask == null)
base.OnPaint(e);
else
{
Graphics g = Graphics.FromImage(Mask);
g.DrawString(Text, Font, maskBrush, Location);
}
}
}
The problem I am running into is that this approach requires that I handle controls in a very specific order so that the form is drawn correctly. I need to find the most efficient approach to get the tasks listed below done in the order given. I have thought of three possibilities discussed further down. For reference, this is the order in which tasks must be done:
All "MaskingLabel" controls that have the bitmap object set to the mask must be drawn first so that the mask is created before the next step.
The mask is applied to the background picture.
The resulting Bitmap is drawn in a way similar to the way a background would be drawn (except that it is modified first).
The rest of the controls are drawn as normal.
Is there a way for me to insure this happens without separating the controls manually? My first guess is no. As such, I have a few guesses below about how I should go about this. I was hoping someone with more in depth knowledge of GDI+ could offer some insight.
One idea that has occurred to me is to draw the masked controls during the OnPaintBackground method. However, I don't want to waste time by painting the controls twice. This means I would need to filter out which controls are drawn during the main Paint method which effectively leads us to option 2 (FAIK):
I can manually filter out the controls which draw to the mask so that they don't get added to the control. My question here though is would they get drawn at all? Can I manually force them to invoke the OnPaint method?
If doing that wouldn't work, then perhaps I can create a separate derived panel control to serve as a "backdrop" child control that acts as the background picture which can be forced to be drawn first?
EDIT (With Part of the answer):
I realized after posting this that I already have part of the solution built into my project. Still, I think it is a legitimate question to ask, so if anyone can add insight beyond what I have done in my description below, it is welcome.
Specifically, my project has only two controls that are added to the "root" form: a bar that goes to the top (docked at the top when it is shown), and a transparent panel that occupies the rest of the space (with a dock style set to fill). So my solution would be to add the mask controls to the main form and add all the rest to the panel. This only leaves one remaining issue to be resolved: How do I make sure that the panel and the bar are drawn last? (As part of step 4 in the first list?)
I have a custom WPF control that I made a couple of days ago:
public class MapContext : FrameworkElement{
//....
protected override void OnRender(DrawingContext dc) {
// Draw the map
if (mapDrawing != null) dc.DrawDrawing(mapDrawing);
}
The mapDrawing drawing is updated in another thread where all the geometries to display are computed, the thread then updates the UI by calling InvalidateVisual():
Application.Current.Dispatcher.Invoke(DispatcherPriority.Render, new Action(delegate { InvalidateVisual(); }));
On InvalidateVisual, MSDN documentation says:
Invalidates the rendering of the element, and forces a complete new layout pass. OnRender is called after the layout cycle is completed.
This is not the behaviour I want as the MapContext control layout did not change. Only the drawing inside has changed.
Question
Is there a proper way of forcing OnRender method to be called without doing a complete layout pass?
Thanks in advance
No, there is no way to force a re-render without a layout pass.
Since WPF uses a retained mode system, OnRender() does not work like the old WinAPI days. OnRender() simply stores a collection of drawing instructions, and WPF determines how and when to do the actual rendering.
If you need to change the look of your control independantly of sizing, I'd suggest you use something like a DrawingVisual, and using RenderOpen() to add your mapDrawing when you want it to change.
Maybe instead of using your own control, you can draw image with your map and set this image as source to standard control? Such operations like drawing are usually taking some time so its better to prepare image in background and then switch it with currently displayed.
I am loading 640x480 Bitmaps into a picture box one after the other. When I do that my UI gets blocked. For example, if I was typing something in a text box which is on the same form where my picture box is, I would not be able to see the key that pressed right away, because the bitmap loading makes the UI very slow ..
How would one handle that ? is there any way around it ?
Any sample code would be great.
Thanks
A Picturebox is actually a fairly 'heavy' control in what it provides; it may not be the appropriate thing to use here. You might consider a much simpler container control, or drawing on the surface of the form itself.
If you want to consider BackgroundWorker or any other threaded technique here, keep in mind that the drawing itself must happen on the UI thread; there's no way around that.
If the loading of the images from disk is the source of the latency, you might consider loading the images into an in-memory bitmap on another thread, then signaling somehow to indicate that a new item is ready to be drawn. You would then invalidate the drawing surface, and add the new item as appropriate.
Also; if you are doing any scaling to the images, doing this in the background thread would be appropriate - that way, the drawing code itself only needs to draw an unscaled rect; Using the GDI+ DrawUnscaled functionality to copy a bitmap to an area of the exact same size is actually quite fast.
To get into anything more specific, like actual code, I would want to see code for how you are doing it now. I'm not even sure you are 'drawing' the images in the first place, rather than simply setting Picture/Image properties.
Use background worker so the gui is not freezed (async invoke the image display).
http://www.dreamincode.net/forums/topic/112547-using-the-backgroundworker-in-c%23/
Your second request is a bit more tricky, I guess short answer would be not to rely on gdi+ if that's what you are doing be cause it's known to be slow. How exactly do you load the image into the imagebox?
So we have this legacy code that displays data in a tree format. They pad each node of the tree using spacer images (...yup..ugh)
Unfortunately, the use of these images is controlled by an in-house UserControl that we're forced to use (basically just derives from Web.UI.WebControls.Image, though).
Well, turns out we have a HUGE tree with thousands of nodes, each one four levels deep or more. This means we're creating about 10,000 padding images each time we draw the page, which is taking up quite a bit of time.
My solution right now is to statically pre-allocate a large number of these Images and use those. I'm hoping there isn't any nastiness that will crop up when multiple users access the page at the same time.
However...is there any way to reuse a UserControl such that we could create just a SINGLE instance of the Image and somehow add it multiple times to the Controls collections? I tried this naively and it didn't work. The image only gets drawn a single time, for the first control it's added to the first time (probably something to do with INamingConainer stuff...?)
Just an idea, but can you not just replace the Padding Image User Control with a different user control, such as:
public DivPadder : HtmlGenericControl
{
public DivPadder() : base("div")
{
this.Style.Add("padding:10px");
}
}
Have you considered loading the contents of the tree in a backgound thread?
In normal C# it is easy to draw to a bitmap using the Grpahics.DrawString() method. Silverlight seems to have done away with Bitmap objects and Graphics is no longer available either. So...How am I meant to manipulate/create a bitmap when using Silverlight? If it helps, I am using Silverlight 3.
Let me tell you what I am doing. I am being given a template, basically a pre-rendered image. The user is then able to select from multiple images and enter the deisred text. I then render it to the image, adjusting size etc... within bounds and centering it in the pre-defined area of the image. If I can calculate the size (as in the MeasureString method) and then draw the string (as in the Graphics.DrawString method) that would be fine. The real question, no matter why I want to be able to do this, is can it be done?
The question is: why do you want to? Why not just use a TextBlock?
If you are trying to dynamically generate an image, use standard Silverlight/WPF controls (including TextBlock) and render them to a WritableBitmap.
Edit:
Ok, you've updated and expanded, which gives me more to go on. Unfortunately, you're not going to like the answer. First, keep in mind that Silverlight and WPF in general are vector based, and intended to be used as such. Although the Canvas allows you to do pseudo-pixel manipulations, you cannot be nearly as pixel-accurate as old-school GDI. This is a factor of your medium. If you absolutely have to measure things the way you want to measure them, I suggest you build your images on a remote server and transmit them to your Silverlight app.
You can calculate the size on-screen of the text rendered via a TextBlock using the ActualWidth and ActualHeight properties. But it only works on an already rendered control. Something like MeasureString is simply not available in Silverlight. Based on your description of your app, some user interaction could accomplish what you want. The user selects the image, enters the text, and is shown a preview. The user can then adjust the width and height of the various text areas until satisfied, at which point you can take a snapshot using the render method I liked to above.
The following may work, its a bit nebulous because I haven't tried yet myself.
The object you are looking for is the WritableBitmap.
You create a Visual tree, for example create your self a Grid or Canvas (you're not adding this to the UI). Add to it the selected image and a TextBlock positioned and sized as you prefer.
Create a new WritableBitmap either of a specific size or using the selected image to initialize it.
Use the WritableBitmap Render method passing the above root Grid or Canvas to it.
Now you have a bitmap which you should able to use to do whatever its you needed to do that required all this hoop jumping in the first place.