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.
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?)
For my software, I am using a Timer from Systems.timer library, every time my timer ticks, it calls a method for repainting my screen. I do not want to clear the screen, then to repaint on it. I just want to paint the new areas on it directly.
At the beginning, I did this:
Constructor{
...
this.timer = new Timer
{
Interval = 10,
};
this.timer.Elapsed += OnPaint;
this.timer.start();
}
public void OnPaint(Object sender, EventArgs e)
{
This.Parent.OnPaintLoadingCircle();
This.Parent.OnPaintReadyToBePaintedAreas();
}
Then I noticed it was much faster for painting when the OnPaint method contains this:
public void OnPaint(Object sender, EventArgs e)
{
This.Parent.Invalidate();
}
So I have two questions:
QUESTION 1 :
Why is it faster???
Because when I call invalidate():
The UI thread clears the screen.
Then UI thread redraws the old areas
Then UI thread draws the loading circle
Then UI thread draws the new areas.
And when I call my two methods OnPaintLoadingCircle() and OnPaintReadyToBePaintedArea():
The timer thread draws the loading circle
Then the timer thread draws the new areas
QUESTION 2 :
I would like to know if it exists a way for asking a controller to draw it surface without clearing it. ( I tried this.Parent.Update(), this.Parent.Refresh(), both of them first clear the screen as well).
Thank you very much for helping me.
Why is it faster???
For the simplest of reasons: because when you call Invalidate() in the OnPaint() method, it forces re-painting of the window immediately, which is much more quickly than a timer could.
The timers in .NET are not suited for high-frequency operations. They only guarantee the time between intervals will be at least what you specify. The actual interval can and often is longer than what you specify, especially if you are using a very short interval (e.g. on the order of less than 10-20ms). This necessarily limits how often you can re-paint the window when using a timer, to a much greater degree than just re-painting the window as fast as you can.
I would like to know if it exists a way for asking a controller to draw it surface without clearing it.
Not easily, no. At the most basic level, you can override OnPaintBackground() and not call the base implementation. But this approach only works if you are prepared to redraw everything, because the system counts on you covering up stale pixels with the correct pixels when you draw.
In fact, a much more common approach is to use double-buffering. The most basic form is to just set the DoubleBuffered property in the control constructor. But you can also combine not clearing the window with maintaining your own offscreen Bitmap object into which you draw your content. Then when a Paint event happens, you just copy the Bitmap to the window.
A much more complicated approach involves hosting a Direct2D surface in your window. Not for the faint of heart, but should offer the best possible performance in a Winforms program.
I am currently developing a GUI for an Ising model (german wikipedia because only the picture on the right really matters) which should consist of approx 200x200 spin elements. I implemented this in the following way:
<UniformGrid Name="grid" .... />
and added a rectangle for every spin in the code behind which I update if the value of the spin changes. This somehow was very slow and I changed it so it uses Binding
<ItemsControl Name="IsingLattice" ItemsSource="{Binding Spins}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Name="grid" ...
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Fill={Binding Color} ...
but this is again - very slow. I tried to debug and improve it for 3 days now but no success so far.
Now the question is: Is my approach wrong? What should I use instead if so? If not - how could I improve the performance?
If it's relevant I will update this post with some details of the implementation of my model.
Edit: It should be possible to change single spins by interacting with the elements. This could be done with a transparent layer on top of the actual graphics though so maybe not that hard anyway.
You could write a single custom element (derived from FrameworkElement) that stores the spin data internally then renders the data in one pass by overriding the OnRender method:
public sealed class IsingModel : FrameworkElement
{
readonly bool[] _spinData = new bool[200 * 200];
protected override void OnRender(DrawingContext dc)
{
// use methods of DrawingContext to draw appropriate
// filled squares based on stored spin data
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
// work out which "cell" was clicked on and change
// appropriate spin state value in Boolean array
InvalidateVisual(); // force OnRender() call
}
}
This approach should be faster than having several thousand individual elements. How much faster I don't know.
ItemsControl is meant to repeat UI controls based on datasource. UI control must be customizable, responsive when layout changes, and interactive. Neither of this is your case.
You actualy want to render just a picture.
This - seems to be a Bitmap, so you should threat it as a Bitmap. Instead of ItemsControl use Image and instead of ItemsSource use WritableBitmap as a Source of Image.
When it comes to your original code, it could have couple of performance bottlenecks:
Generation of your classes you used as a ItemsSource can take some time
UniformGrid needs to measure size and calculate position of each element. This can take a while. With Canvas you could achieve better results
It can take some time to ItemsControl to generate 40 000 items from DataTemplate. You could create those rectangles manualy and add it to canvas in code behind
Binding have performance cost. If each of your item is databound, then your binding needs to be evaluated 40 000 times. Instead of binding you could set the properties manualy in code behind.
Using canvas and no binding I was able to render the grid in just 500miliseconds. However, using WritableBitmap or other "pixel based" approach you could display much larger grids.
A GUI, any kind of GUI technology, being it WPF or Windows Forms or anything else, is not meant to handle heavy graphics. It's meant to be easy to develop simple graphics.
If you need graphic power (like dynamically updating 40.000 cells) then you need a framework for graphics. Most likely, a gaming framework will do, pick one of your choice.
Alternatively, you could try to emulate it yourself by only binding to a single picture and drawing that picture yourself when a cell changes. Maybe that's enough, you will have to test that.
Have you considered using the BitmapCache to improve rendering speed?
My understanding is that this can significantly improve rendering speed when drawing complex controls, or when having many instances of the control on screen at the same time. You would want to enable the cache at the grid level, not on each individual spinner.
I'm reading a lot about C# drawing and reading MSDN tutorial on GDI+ using Graphics handlers.
I want to be able to paint a graph, which nodes I have in a list but I can't use auto-placing, I need the nodes to be in a specified place and have specific appearance and so on, changing over time, that's why I stopped looking for graph libraries.
It all works great when painted for the first time but when I want something painted after something else happens in the code (not after clicking the control), I can't do it. For example:
if (somethingHappens) {
// repaint the panel adding some things
}
All I got is either nothing new happens or my earlier painting disappears.
I found some examples on OnPaint overriding and drawings disappearing when minimalized. When I need to paint something additional when something happens in an application, do I have to override it or is it completely different?
I looked for another Q&A that would include the information you need. Frankly, it's a fundamental question, how to properly deal with drawing a Forms control. MSDN topics like Overriding the OnPaint Method and Custom Control Painting and Rendering provide some detail on the right way to do this. But I'm surprised I wasn't able to find any StackOverflow Q&A that at least addresses this basic question directly (there are many which address it tangentially of course).
Anyway…
This is the basic sequence for drawing in Forms code:
Generate some data to be drawn
Invalidate the control where the data will be drawn
Handle the Paint event by actually drawing that data
Repeat the above as necessary, i.e. any time the data changes (such as "something happens", as in your question) you move back to step #1, adding whatever new data you want to your existing collection of data, then invalidating the control, and finally responding to the Paint event in your event handler.
In the case of drawing a graph, this might look something like this:
private List<Point> _points = new List<Point>();
void AddPoint(Point point)
{
_points.Add(point);
panel1.Invalidate();
}
void panel1_Paint(object sender, PaintEventArgs e)
{
if (_points.Count < 2)
{
return;
}
Point previousPoint = _points[0];
for (int i = 1; i < _points.Count; i++)
{
currentPoint = _points[i];
e.Graphics.DrawLine(Pens.Black, previousPoint, currentPoint);
previousPoint = currentPoint;
}
}
Note that the panel1_Paint() event is an event handler. Normally you would create this via the Designer by selecting the Panel object, switching to the "Events" list in the "Properties" window for the control, and double-clicking in the edit field for the Paint event.
That is of course the simplest example. You can add things like updating the data in a batched manner (i.e. don't invalidate the control until you've added a group of points), use different colors or line styles to draw, draw other elements of the graph like axes, ticks, legend, etc. The above is simply meant to illustrate the basic technique.
One final note: depending on how many points in your graph you need to draw, the above may or may not be fast enough. It should be fine up to thousands of points or so, but if you start getting to tens or hundreds of thousands or more, you'll probably find that it's useful to cache the drawing into a bitmap and draw just the bitmap. But that's a whole separate question. First, you need to make sure you understand the Forms drawing model and are using it correctly.
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.