I'm drawing a lot of lines on a long canvas (think stripchart) and have it tuned fairly well for performance, using the low-level geometry classes and freezing them, etc. This improved performance dramatically, but it still takes a few seconds to load a few thousand items into the canvas. I ran a performance analysis on the application, and it looks like a big percentage of the time is taken by each call to canvas.children.add(). I've read that this should be a lightweight call, and since I'm calling it numerous times in one method, it shouldn't be trying to do anything heavy inbetween... Could there possibly be any other reason this might be taking so much time? And any way I might speed it up?
The performance is not terrible, but I fear it could become more of a problem later when I need to deal with larger sets of data.
Just for reference, it looks like it is called 1400 times in this sample, and it taking almost 3 seconds of CPU time on a modern/fast laptop.
The canvas is contained in a hierachy of other controls though, so I'm curious if they might be contributing to this.
Extra note: I'm also not setting a specific height on the canvas, as it is set to fill the grid parent container. Could this be a source of the problems?
Main problem is that Children.Add is always a slow operation, even if you use StreamGeometry objects. I faced the same problem recently and concluded the following:
If you put a bunch of objects into a new canvas and nest it into the main canvas, the performance of the addition operation will be increased dramatically.
So, instead of adding 1400 elements, put 200 elements in 7 canvases and add those 7 canvases to the main canvas.
Since all objects now belong to different canvases, you will need to adjust your app a bit, but this would be a less drastical solution than moving to an alternative solution like DrawingVisual
Just to add about the hierarchy of controls the canvas is within, and the height of the canvas:
the Canvas always takes as much space as its given, and no matter what children u add to it - it NEVER triggers a new Measuer/Arrange passes on its parents. therefor whatever u do inside a canvas could never affect the visual tree it is contained in.
To sum it up - the problem cannot come from there, and the suggestion about the StreamGeomatry is exactly right- this is what causing u the performance issues, and switching to streamgeormatry would solve it.
I would suggest that you draw your shapes directly into an image instead of adding them as children.
Rendering children has a HUGE overhead (as you can see).
There's a similar question with a reference to some helpful articles:
How to draw line of ten thousands of points with WPF within 0.5 second?
Related
My data structure has two fields:
* BackgroundImage (of type Bitmap/Image)
* Points (of type Point2D [])
My use case is as follows: a user can load an image into the app. After the image appears on user's screen, they might add points to it (by clicking a mouse button). The points should be visualized on the top of the image, but a user should be albe to reposition them if needed (e.g. drag'n'drop).
At the moment I solve the problem by doing the following every time the user adds / moves a point:
* clone the BackgroundImage
* draw all the points on the cloned image (using System.Drawing.Graphics)
* return the cloned image with the points (expose it as a property and bind to Image in WPF).
The time performance of this solution is ok, however it consumes a lot of memory, as everytime I end up copying whole image. I'm wondering if there's a better way of doing this (e.g. by using layers - then my BackgroundImage remains the same all the time while I keep modifying only the top layer).
My code is quite long, but if it's needed just let me know and I'll post it.
when it comes to memory consumption, there is nothing wrong with the aproach you described as long as you make sure the old instances of the image are not rooted anymore so that the GC can remove them.
However, during the timespans in which the cloned image is modified, occupation of memory might of course be up to double the lowest possible value when not cloning.
To reduce this memory consumption, the movable points can be implemented using UIElements. This could also help keeping the implementation simpler by using WPF hittesting for the "drag'n'drop" part.
Since UIElements require more memory than the points in the BitmapImage, the actual savings depend on the ratio of points in the BitmapImage to movable points.
To implement the points using UIElements, place the BitmapImage together with a Canvas in a Panel. Then use the Canvas as a container for the points and set their positions using the attached properties Canvas.Top and Canvas.Left.
To make the points appear in front of the BitmapImage, set the Panel.ZIndex of the Canvas.
But if you are seeing unreasonable memory consumption, you should use a memory profiler to take a closer look at what parts of the Process are actually taking up the space.
I am currently writing a program where I need to draw some graph's. I need to have a little bit specific layout in these graphs. For example I have three stages of a length in days defined by the user. a start stage of for example 30 days, a mid stage of 40 and an end stage of 20 days. These stages I want to have all a different backgroundcolor in the graph. I do that by drawing pictureboxes and adapting their widths to the stage lengths. Also for every day in the total length I want to draw a vertical line and for the amount of horizontal lines in the graph I take the maximum of y = f(x).
y = f(x) needs to be plotted on the graph. For I use many pictureboxes on the background I cannot use the graphics.DrawLine for it will be drawn behind the pictureboxes. So I decided to make the line with an array of pictureboxes ;) It works fine, but obviously it takes a lot of time to load the program now.
Is there another way to draw this graph using arrays of controls that require less effort from the computer? Or should I completely stop with the arrays?
(I wanted to post my picture here, but I don't have ten reputation yet because I'm a noobie :( )
Later on I will add more lines to this graph, but since I figured that my program is already slowing down I ceased programming those other lines and went to the all-knowing forum!
Any help will be much appreciated!
Greetz,
Arrie
The common form controls aren't really suitable for this purpose. I'd suggest taking a look at using libraries that give you more power and control over visuals and graphics.
#Kári is right:
If you want to stay with .NET only (no 3rd library dependence) you can use GDI. In .NET you can use by including System.Drawing.dll as an reference.
One simple yet correct approach would be:
create a target control (picturebox for example)
implement the OnPaintDraw Event which gives you an Graphics object
that contains many drawing methods. See MSDN for more information:
MSDN -> Graphics
The methods of Graphics will always draw above the control, so make sure your target control is visible an not behind any other control.
If GDI is not enough you can check out other libraries. (See .NET graph library around?)
I am creating a custom control for semiconductor wafermap
Each of those small rectangle need to satisfy following requirements;
1) Tooltip to show the index
2) clickable to include or exclude from the wafermap definition.
no of dies in the wafermap may cross 2 millions in the case of 1400 x 1450 dies.
at certain point i need to show all the dies in a window (most of the clicking will happen in zoomed view).
Currently I am adding each die separately using Rectangle shape and store the coordinate information (index like (10,10)) for the tooltip as an attached property.
I use different style to each die; depending on certain calculation and position of the die.
DieStyle1 = new Style { TargetType = typeof(Rectangle) };
DieStyle1.Setters.Add(new Setter(Shape.FillProperty, Brushes.MediumSlateBlue));
DieStyle1.Setters.Add(new Setter(Shape.StrokeProperty, Brushes.White));
DieStyle1.Setters.Add(new EventSetter(MouseDownEvent, new MouseButtonEventHandler(DieStyle1_MouseDown)));
this approach is slow and use high memory too. so suggest a better way to achieve this in WPF?
In creating a designer for christmas tree lights, I ran into the same problem. Using UIElement or Shapes is way too slow when you get to 100+ items. The best approach to handle a very large number of items entails using double-buffering with your own managed buffer of the image and a structure to handle the clicks. I have posted my project which should give you a good start. It can be obtained at:
http://sourceforge.net/projects/xlightsdesigner/
You are interested in the Controls\ChannelitemsCanvas.cs. It can be modified to suit your needs and uses a quad-tree to store the rectangles so that click events can be quickly determined.
I am doing some work for which I need to develop a control, it should be a simple graph that shows several points and two edges.
My problem is that I need to show up to 16k points, with an update rate of 30 Hz. Has anyone done something similar?, and has any advice?.
For example whether to inherit from FrameworkElement or Control (ItemsControl in this case). If the control inherits from FrameworkElememt it may have a better performance drawing the points in the OnRender method but I would miss the Templating feature that comes from inheriting from Control.
Or does there exist another control that can do this out there?
Thanks in advance for your time.
I ended up using InteropBitmap, it is the fatest bitmap rendering class from WPF.
It allows you to map the image that you want to paint (in memory) and then reder it as a Image. This was perfect as i needed to plot points on the screen.
I got great performance (almost 50Hz for 20k points), i also use PLINQ to update the points in memory.
check this article for more details...
Try and read about ZoomableCanvas. I believe it can solve your problem. You can render all the points as small rectangles/ellipses inside the ZoomableCanvas.
I have a C# .NET application with which I've created a custom image display control. Each image display represents its own display context and draws the image using glDrawPixels (Yes I know it would be better to use textures, I plan to in the futures but this app is already too far along and my time is limited).
I am now trying to have both images pan simultaneously. That is, when one image is moved down ten pixels, the second image moves down ten pixels. Like so:
imageOne.YPan -= 10;
imageTwo.YPan -= 10;
imageOne.Invalidate(); //This forces a redraw.
imageTwo.Invalidate(); //This forces a redraw.
Alright so here is the problem I am having. Only one of the images displays is redrawing. If I place a pause in between the two Invalidate calls and make the pause duration at least 110 milliseconds both will redraw, but not simultaneously. So it looks as if the second image is always trying to catch up to the first. Plus, a 110 millisecond pause slows down the motion too much.
I have tried placing the updating and invalidating of each image in its own thread but this did not help.
At the beginning of drawing I make the appropriate context is current, and at the end I am calling swapbuffers(). I tried adding a glFinish to the end of the draw function, but there was no change.
Could it be that its the graphics card that is the problem? I am stuck using an integrated gpu that only has openGL 1.4.
Hopefully, I have provided enough detail that the answer to my problem can be found.
Its difficult telling what's wrong with what you do since you give so little detail. Here are some pointers which may help.
- before doing something in a context, make sure you make it the current one. If you want to pan two contexts, make the first one current, pan it and then make the second one current and pan it. These is no real reason why this should not work.
- If it looks like there is a timing problem, adding glFinish() at strategic places may help weed the problem out
- As should always be done, on occasions call glError() and see that everything went well.
- I'm not sure how this is done in the framework you're talking about but you should make sure that both contexts get a swapBuffers() call for every frame.
Invalidate doesn't force an immediate redraw. It marks the window invalid, and when the message queue runs out of other messages, a paint message will be created and processed. But that won't happen until you finish processing the current message and return to the main message loop, and it may be delayed even more than that.
Generally OpenGL animation is an exception to the rule of doing all drawing inside Control.OnPaint (or in a handler for the Control.Paint event).