For example, let's say I want to create something complex. like a zoommable/pannable graph. like google maps, or a stock market graph or something.
XAML and that whole hierarchy doesn't really work. What I'm trying to do is kind of more like back in the day with GDI+/Winforms. Where I could override paint
i.e., "protected override void OnPaint(PaintEventArgs e)" and then I'd draw whatever the heck I wanted. Where I'd do double buffering. Like draw to a buffer and blip it to the screen.
But how do I go about this in WPF?
A fundamental difference between WPF and GDI/GDI+/WinForms is that WPF uses retained mode rendering (as opposed to the direct rendering of GDI). In a nutshell, this means that the system (actually, the hardware) is taking care of the double buffering for you. Instead of procedurally drawing to the screen / buffer, you rather declaratively provide a tree of vector objects and leave all the rendering to WPF.
The vector objects come in different levels of complexity / abstraction - the most low-level ones you might ever want to deal with are Visuals. The Shapes (Ellipse, Rectangle etc.) mentioned by David are already higher-level objects which can also handle user interaction like hit-testing etc.
You can use any of the WPF shapes, like Ellipse, Rectangle and then using the Canvas class you can move them:
var rect = new Rectangle();
//...set width, height...
Canvas.SetTop(rect, 10);
Canvas.SetLeft(rect, 15);
This should get you started. Keep in mind that zooming, stretching content, traslating and rotations can all be achieved using math functions, but don't panic! WPF's got some cut things about it too:
var rotateTransform1 = new RotateTransform(45);
rect.RenderTransform = rotateTransform1;
If you really, really want it, you can derive your control from UIElement or FrameworkElement and override OnRender where you will get a DrawingContext object which provides methods for drawing shapes, text, images.
But if you want to work in the WPF's philosophy and spirit, probably 99% of the times you don't need to override OnRender. WPF offers a lot (and I really mean A LOT) of ways to develop new controls by styling, templating and if these two don't do the job, then subclassing the appropriate control in the WPF's controls hierarchy.
As gstercken very good pointed before, WPF is not WinForms, you must think in WPF in order to do a good work.
Related
I am creating a tool which relies heavily on graph-node trees. The current implementation is done in Java and I'm porting it to a generic code-base on C#, so it can be used by various rendering implementations and also because I want to use the power of WPF for a user-friendly interface.
After browsing around for a day, I came across various methods to draw Vector-graphics through WPF.
This guy speaks about different layers within WPF developers can choose from. As I want to use WPF PURELY for his rendering at first, I want to work on the "Visual Layer".
I then came across things like:
DrawingVisual,
GeometryDrawing,
FrameworkElement / UIElement / Shapes
So, I'm a bit overwhelmed by all the different implementations that do eventually the same in totally different ways.
The Graph-Node library has been ported to C# already with all it's logic (including collision detection and dragging with mouse). As it is made with graphic-renderers in mind (like XNA, SlimDX, OpenTK, etc.), what would be the best way in terms of performance to implement a WPF renderer (as in, it will draw whatever the graph library tells it to draw?
Basically, the resulting WPF control acts as a canvas, but it has to be SUPER lightweight and not have any neat WPF features besides providing me a way to draw my circles, lines and other shapes :)
EDIT:
I basically want to know: What is the way to go? Do I extend Canvas as "Host" for my graphics and then add my custom implementation of a UIElement? Or can I have one class which can draw EVERYTHING (as in, one mega super ultra graphic). Much like overriding OnPaint in GDI or Paint-method in Java (which gives a Graphics object to do everything with).
I'd recommend reading Optimizing Performance: 2D Graphics and Imaging.
Basically, Drawing objects will be lighter weight than Shapes, in general. This is probably what you want to use.
Generally, better performance is obtained with lower-level services. In WPF, this means the Drawing family of objects. All you get are: Drawing, DrawingGroup, GeometryDrawing, GlyphRunDrawing, ImageDrawing, and VideoDrawing. However, they are sufficient for all needs. Using these types is very friendly with WPF because Drawing is the conceptual unit that WPF exchanges with your GPU accelerator, possibly retaining and managing it there if possible. This works because the Drawing is expressed in terms of portable vector drawing primitives.
Once you start re-architecting your app around Drawings however, you might need some interop with your higher-level code which is still based on UIElement, FrameworkElement, etc. One thing that I haven't found built-in to WPF is a simple way to wrap a Drawing as a FrameworkElement in the lowest-overhead way possible. DrawingVisual isn't a complete solution, because it only derives from Visual--meaning it still requires a hosting element.
The following class will host any WPF Drawing directly without using an intermediate DrawingVisual. I added support for FrameworkElement's Margin property (with no performance penalty if unused) but little else. Because of WPF's single rendering thread it's safe and easy to cache a single TranslateTransform object to implement the margin. I'd recommend that you supply only drawings which have been Frozen; in fact, in the version that I use, I have an assert to that effect in the constructor.
public class DrawingElement : FrameworkElement
{
static readonly TranslateTransform tt_cache = new TranslateTransform();
public DrawingElement(Drawing drawing)
{
this.drawing = drawing;
}
readonly Drawing drawing;
TranslateTransform get_transform()
{
if (Margin.Left == 0 && Margin.Top == 0)
return null;
tt_cache.X = Margin.Left;
tt_cache.Y = Margin.Top;
return tt_cache;
}
protected override Size MeasureOverride(Size _)
{
var sz = drawing.Bounds.Size;
return new Size
{
Width = sz.Width + Margin.Left + Margin.Right,
Height = sz.Height + Margin.Top + Margin.Bottom,
};
}
protected override void OnRender(DrawingContext dc)
{
var tt = get_transform();
if (tt != null)
dc.PushTransform(tt);
dc.DrawDrawing(drawing);
if (tt != null)
dc.Pop();
}
};
[edit:] This is also useful for inserting a WPF Drawing into the InlineUIContainer.Child property (i.e. using TextBlock.InlinesCollection to format the contents of the TextBlock more richly).
the DrawingVisual seems to be a valid choice:
The DrawingVisual is a lightweight drawing class that is used to
render shapes, images, or text. This class is considered lightweight
because it does not provide layout or event handling, which improves
its performance. For this reason, drawings are ideal for backgrounds
and clip art.
source: Using DrawingVisual Objects
so this seems to be absolutely what you ask, a Canvas SUPER lightweight.
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.
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?
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);
}
}
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.
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
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.
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.
Just wondering if anyone knows how I would implement additional/custom EndLineCap, StartLineCap & LineJoin attributes?
In short, LineJoin="Round" is what I want but I need both sides of the angle are smooth - instead of just the wider angled side.
Also, instead of just Rounded EndLineCap and StartLineCap I want a ball on each end (for instance, a circle at each end who's diameter is double that of the with of the line).
Instead of adding additional custom items, I might be able to achieve this through attached properties, but I don't know where to start.
I don't believe that it is possible to do what you're asking. Line Caps are implemented at a very low level in WPF where there seems to be little possibility of customisation.
The lowest level Graphics API that WPF exposes is the DrawingContext. This provides methods like DrawLine which take a Pen object to specify how LineCaps should be drawn. Your choice of LineCaps is limited to those exposed in the PenLineCap enum - it doesn't provide a "Custom" option.
Having said all that, it may be possible to emulate custom line caps by modifying the path data in a geometry. This answer may give you some hints.
I need more precision then integer based locations when puttng controls on a form.
It seems control.location only supports Point.
Is there a work around for this?
Point p = new Point(100, 200);
this.Location = p;// this works of course
PointF pF = new PointF(100.04f, 200.08f);
this.Location = pF;// this does not work of course because Location expects a Point not PointF
Is there some setting on the base form, or base control I can set to have more location precision?
You could use Windows Presentation Foundation (WPF) together with XAML (both work nicely within Visual Studio), which allows subpixel positioning of controls.
However, assuming that you are using Windows Forms and not WPF, why would you ever need to put a control on a non-integer (subpixel) location?
Although GDI+ is capable of floating point coordinate systems, Win32 (upon which Winforms is based) is not. I second the reccomendation to move to WPF which has a ubiquitous floating point coordinate system based on device independent virtual pixels.
No, control rendering in WinForms is all fundamentally pixel-based. There is a way you could achieve sub-pixel positioning (without using WPF), but it would be work on your part, and I'm really not sure why you would need this anyway.
The gist of the approach is to create a user control with a hidden instance of the control you're trying place funkily (sub-pixelly, maybe). You wire up all the user control's events so that they pass through to the hidden control, and after each event is passed you call the hidden control's DrawToBitmap method to get a snapshot of the control. You then use Graphics.DrawImage to copy the snapshot to the surface of the user control. DrawImage is not restricted to pixels, so you can offset the drawing by less than a pixel to achieve the precise positioning you're looking for.
Warning: please do not actually do this, as there is no reason for it when you can just use WPF. This would be a lot of work, since "passing the control's events through" is not as simple matter as it sounds. There's also a problem with rendering the focus correctly in this manner, as the invisible control cannot be given the focus (I'm not even going to tell you what the grisly hack solution is to that problem).
Update: It's worth revisiting that decision about WPF - it is ideal for what you're doing and would make your life much simpler. I have been generally underwhelmed by WPF, because I think that while it's very powerful it is essentially overpowered for the uses to which it is most often put (namely, boring-ass business apps). In your case, though, it provides a granularity that you actually require in your app.
If you're stuck in WinForms, however, your best approach is to write your own UserControl versions of the text-editing controls that your application requires. At its core, a TextBox is just a square that you draw a border around and some text on. The .Net Graphics methods for this (DrawRectangle and DrawString) can have the drawing coordinates specified in floating point.
There are tons of StackOverflow questions about owner-drawn user controls and GDI+ graphics.
1 point is 1 pixel and therefore the maximum resolution required.
This is the reason why 0,0 maps to the left corner, and 1024 maps to the right, in a resolution of 1024x768
I'm attempting to write a custom button user control. I have run into a challenge when drawing the image.
Is there a simple way to draw the image accounting for the ImageAlign and TextImageRelation? (Kind of like StringFormat makes text aligning a breeze)
Or do I have to do all the aligning logic and stuff manually?
Thanks
What functionality are you trying to achieve? Perhaps it should instead inherit from the Button class (asssuming WinForms), and override appropiate methods. Depending on what you need to do, you will propably get much of the lower levels of functionality in the button for free, if you do this.
To get back to your question; No, if you need to draw stuff yourself, there is no magic easy way to determine where the individual pixels should go :-) One great helper in doing this, that you should be aware of, is the Graphics.DrawString method. It lets you measure the dimensions of a given text string when drawn on the control with the selected font and size.
I do not know of anything that does this stuff for you, but be aware of the ControlPaint class as that has a bunch of handy utility methods for painting controls.