9

So, I've used winForms .CreateGraphics to draw a variety of different things, from lines to boxes to images. It was very snappy and responsive.

I am trying to learn WPF in C#

I found that WPF allows me to "add" rectangle objects to a canvas which will display them properly. HOWEVER, I am drawing hundreds of thousands of rectangles at times, and the draw rate can become exceedingly slow, and the UI becomes less snappy when I move even 1 of the rectangles.

Painting directly onto an element in winForms was not very fast, but it was consistent regardless of how much I painted.

Is there a similar solution to doing this in WPF?

I tried adding a linq to System.Drawing, which gave me a Graphics object, but none of the wpf elements i tried have the .CreateGraphics() method.

ohmusama
  • 4,159
  • 5
  • 24
  • 44
  • 4
    Holy @#%( .CreateGraphics doesn't exist in WPF either ?????? every single day i find new reasons to regret using WPF as a form choice; It's like use win forms and do everything easy as pie or use WPF and gl searching 10 hours how to do something that use to be simple. –  Apr 27 '12 at 13:44

4 Answers4

6

WPF uses a different model for graphics manipulation than WinForms.

With WinForms, you are able to directly edit the pixels on the screen. The concept of your rectangle is lost after the pixels are drawn. Drawing pixels is a very fast operation.

With WPF, you are not controlling the pixels on the screen. DirectDraw is. DirectDraw is a vector-based compositing engine. You do not draw pixels. You define vector shapes (or visuals). The concept of a shape, or a rectangle, is RETAINED, even after the image is rendered to the screen. When you add a new rectangle which overlaps the others, ALL OTHER RECTANGLES NEED TO BE REDRAWN. This is likely where your performance is slowing down. This does not happen when using WinForms.

You can improve the performance of WPF a bit by overriding OnRender. You can cut out the overhead of the Rectangle object and directly provide the visuals. However, you are still not drawing pixels to the screen. You are defining shapes that DirectDraw uses to render the image. In this regard, the OnRender name may be a bit misleading.

I am sure you can find plenty of tricks to improve performance of your application in WPF. There are ways to still paint pixels - but that is kinda defeating the point of WPF.

What are you doing that requires thousands of rectangles?

Scott
  • 1,876
  • 17
  • 13
3

You would need to create a control that overrides OnRender and do your drawing in there. There isn't a way for you to draw onto another control, but a control can draw itself.

Also, keep in mind that WPF uses retained graphics, so if you change something you need to invalidate the visual as needed.

EDIT:

Something like:

public class MyControl : Control {

    public MyControl() {
       this.Rects = new ObservableCollection<Rect>();
       // TODO: attach to CollectionChanged to know when to invalidate visual
    }

    public ObservableCollection<Rect> Rects { get; private set; }

    protected override void OnRender(DrawingContext dc) {
        SolidColorBrush mySolidColorBrush  = new SolidColorBrush();
        mySolidColorBrush.Color = Colors.LimeGreen;
        Pen myPen = new Pen(Brushes.Blue, 10);

        foreach (Rect rect in this.Rects)
            dc.DrawRectangle(mySolidColorBrush, myPen, rect);
    }
}
CodeNaked
  • 40,753
  • 6
  • 122
  • 148
  • I figured out the invalidate, and how to force a redraw. Would you be willing to provide a short snippet if I had a Canvas object named `canvas`, and how to override the `OnRender()` method for just that object? – ohmusama May 11 '11 at 17:57
  • @ohmusama - You wouldn't need to use something like Canvas, in general anyway. Your control would simply render rectangles, but you wouldn't actually have any UIElement rectangles (just the Rect). The OnRender link shows an example control. – CodeNaked May 11 '11 at 18:03
  • Does this require me to extend the existing canvas class, to override it? if my new canvas class was `class MyCanvas : Canvas` in the yaml I would create a `` instead of `` If I understand everything correctly? – ohmusama May 11 '11 at 18:13
  • @ohmusama - You probably don't need Canvas, as you don't need to add Rectangle objects if you are doing the drawing. I'll update my answer with an example. – CodeNaked May 11 '11 at 18:22
  • I suspected I wouldn't need the rectangle objects, it was just how I got it to work. – ohmusama May 11 '11 at 18:24
  • and I use `` in the yaml just to verify? – ohmusama May 11 '11 at 18:27
  • @ohmusama - That's correct, but it depends on your setup. You'd need to use something like local:MyControl, where the local xmlns points to the namespace you have this control defined. – CodeNaked May 11 '11 at 18:31
  • say my namespace for the project was `namespace maze` and this was defined in that namespace, I would then do `` or neglect the namespace all together. – ohmusama May 11 '11 at 18:36
  • @ohmusama - Please see this [link](http://msdn.microsoft.com/en-us/library/ms747086.aspx), but you'd need to add something like **xmlns:local="clr-namespace:maze;assembly=YourAssemblyName"**. Where maze is the CLR namespace, local is how you will refer to it in XAML, and YourAssemblyName is the name of your assembly. You can leave off the **;assembly=YourAssemblyName"** if the XAML file and class are in the same assembly. – CodeNaked May 11 '11 at 18:41
3

As was said, WPF uses a retained graphics methodology so your actually creating 100,000 Rectangle objects in memory and then drawing all of them. The slowdowns are probably due to garbage collection and general memory issues.

Aside from override the OnRender method, here's a couple of things you could look into though.

  1. Drawing the rectangles to an image in a background thread using the GDI methods your familiar and then write the result to a WPF WriteableBitmap

  2. Use the D3DImage and take advantage of hardware acceleration. This requires you to know the DirectX (or Direct2D) libraries. If your interested in this approach, I'd suggest looking into SlimDx.

mdm20
  • 4,475
  • 2
  • 22
  • 24
3

The problem is most likeley not that WPF can't render 1000s of graphic objects, but that your creating and adding items too far up the WPF object hierachy. It does after all use the GPU for all the graphical grunt work.

You should add objects as close to the "Visual" class as possible, as soon as you start adding objects based on the latter "UIElement" you are asking WPF to track user clicks, hovers and so on for each object, not just draw it.

Pete Stensønes
  • 5,595
  • 2
  • 39
  • 62
  • I was adding `Rectangle`s to a `Canvas` which was the child of `MainWindow` where/how would you add those rectangles. I was using 'myCanvas.Add(myRectangle)` – ohmusama May 11 '11 at 20:08
  • Actually the WPF Rectangle would seem to be a good bet; it is the frameworks lowest level shape drawing thing, but as its inherited from FrameworkElement it has got some hit test/mouse over/etc baggage. You might want to consider creating your own custom control (based on Visual if you can) to remove as much run time UI control baggage as you can. – Pete Stensønes May 11 '11 at 20:48
  • Is there anything special I would need to do to make my own custom control inheriting Visual to make it useable, or maybe a link would suffice. – ohmusama May 11 '11 at 21:06
  • have a look at this from MSDN: http://msdn.microsoft.com/en-us/library/ms748373.aspx – Pete Stensønes May 11 '11 at 21:33
  • there is a lot of info there, and that should help learn the otherway of doing things, thanks! – ohmusama May 11 '11 at 22:06