WPF hosting a WindowsFormHost what are my drawing options? - wpf

I have a WPF app which hosts a WindowsFormHost. The WindowsFormHost loads ESRI's ArcEngine. The ArcEngine has some drawing functions available but I'm looking for more power and control. Assuming I want to draw some images & text on the screen over the image created by the ArcEngine, what are my options, if any?

A WPF window cant render anything over any winforms elements that it contains. You either need to do the drawing within the winforms element or do a nasty hack of creating a separate WPF window with a transparent background & no border that you programatically move around to keep on top of the winforms element - then you can draw into this overlay window using WPF mechanisms. it's ugly but effective.

You can implement your Custom Layer. Inside the layer you can implement your drawing method with GDI+ or OpenGL (in Dynamic Display mode).
Create class that implements ILayer interface. In this case you can to manage your drawing as you wish.
There is an exmaple of simple implementation with GDI+
[Guid("0c745c09-a67a-4736-ba8c-23238582f78f")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("CustomLayerPan.customLayer")]
public class customLayer : ESRI.ArcGIS.ADF.BaseClasses
{
public customLayer(List<IGeometry> p_objItems)
{
}
public override void Draw(ESRI.ArcGIS.esriSystem.esriDrawPhase drawPhase, ESRI.ArcGIS.Display.IDisplay Display, ESRI.ArcGIS.esriSystem.ITrackCancel trackCancel)
{
Graphics objGraphics = Graphics.FromHdc(new IntPtr(((IScreenDisplay)Display).hDC)) as Graphics;
//Draw here using GDI+
}
}

Related

Overlay WinForms controls on DirectX content

I'm trying to create a graphics library with graphics control. The control enables adding overlay controls. The graphics on the control uses DirectX and is part of the library, while the overlay controls are provided by end users of the library which are not graphics experts. Thus stability and convenient API are the most important issues.
For technical reasons I need to show the graphics by DirectX directly on the control and cannot host the DirectX scene using another framework as WPF (For more information see my previous question: Stereoscopic 3D on WPF).
I think the most problematic issues are:
Transparency (and semi-transparency...) within the area of the control itself.
Animations on the overlay control: That's why control rasterization (e.g. by WPF) is not an option.
My tries until now:
Following the lot of posts discussed similar issues I decided to use WS_EX_TRANSPARENT. I was surprised to see that although the Airspace issue, I can see the DirectX content under the transparent regions of the overlay control. However the overlay control was not shown except for when I resized the window - then it blinks and disappears again. Here is the code of the overlay control:
class GDIGraphicsControl : UserControl
{
private const int WS_EX_TRANSPARENT = 0x20;
protected override CreateParams CreateParams
{
get
{
CreateParams p = base.CreateParams;
p.ExStyle |= WS_EX_TRANSPARENT;
return p;
}
}
public GDIGraphicsControl()
{
this.Dock = DockStyle.Fill;
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// Do nothing
}
protected override void OnPaint(PaintEventArgs e)
{
for (int i = 0; i < 10; ++i)
{
int alpha = 25 * i;
int yPos = 10 * i;
e.Graphics.FillRectangle(
new SolidBrush(Color.FromArgb(alpha, Color.Green)),
5, 5 + yPos, 100, 10);
}
e.Graphics.FillEllipse(new SolidBrush(Color.Red), 110, 5, 100, 100);
}
}
To analyze the blinking problem I tried to make things simpler. First for debugging purpose I tried to use GDI rendering instead of DirectX. I implemented it such that it will be very similar to the DirectX rendering - especially the Form.SetStyle and the Invalidate() call in every OnPaint() which I thought to be the cause to the problem. The problem do exist also in GDI-only rendering. Here is the GDI-rendering parent control code:
public partial class RenderingForm : Form
{
public RenderingForm()
{
InitializeComponent();
ControlStyles styles =
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint |
ControlStyles.Opaque;
this.SetStyle(styles, true);
Button button = new Button()
{
Text = "Just a button",
Left = 5,
Top = 210,
Width = 200
};
this.Controls.Add(button);
this.Controls.Add(new GDIGraphicsControl());
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// Do nothing
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Color.Blue);
this.Invalidate();
base.OnPaint(e);
}
}
I tried to add a simple overlay Button to check if the problem exist when no transparency is used (see in the above code). The button doesn't blink, but instead of the button I see garbage on the screen until resizing the control first time - then the button appears correctly.
I tried to invalidate the child overlay controls but it has no effect. I tried to invalidate it both in parent's OnPaint and in the child OnPaint to create the message-pump rendering effect and it has no effect. Setting the control's style as in parent rendering control didn't resolve the problem and caused the background to blink in black.
I also performed some tries using WPF but the question is long enough for now without hard Airspace issue...
Now for the questions:
Can someone explain how does GDI overlay over DirectX graphics work? Especially I don't understand it because I know that WinForms transparency is working by that the children controls render on the parent's Device Context - and DirectX has a hardware rendering context. Does it mean that the DirectX texture is copied back to software?
Why does overriding the CreateParams causes the Control to blink and how to prevent it?
What are performance cost do those transparency methods (overriding CreateParams, BackColorTransparency = True, Control.SetStyle+override OnPainBackground)? It is of high importance that the underlying graphics will be rendered efficiently but I don't care about the overlay performance (except for lightweight animation).
I've done a LOT of research in this area and I've come to the conclusion that Airspace is the way to go. I've collected sources from numerous projects around the web and put them into a single project that allows you to integrate WPF with XNA. I've also done a version of this that was pure DirectX, but to be honest the XNA version is a lot more straightforward and better suited to C#. I don't have enough room to post all the details here but you can download the demo from my website and probably figure out where to go from there. Good luck!

How to improve Canvas rendering performance?

I have to draw a lot of Shape (about 1/2 hundred thousand) as [Canvas][2]'s childrens. I make this in my WPF application dividing work in two parts: first thing I create shapes by setting the properties of each of them (like Margin, Fill, Width, etc...), after I add shapes as Canvas's children.
MyCanvas.Children.Add(MyShape)
Now i want to improve the performance of the second part, because when i draw the shapes my application is blocked for a long period of time. So i tried to use the Dispatcher and its method [BeginInvoke][4] with different [priorities][5]: only if I use the Background priority the main application does not block, otherwise the application remains blocked and the "picture" is not displayed until all shapes are added to my Canvas, but if I use the Background priority obviously everything is slower. I also tried to create a new thread instead of using the Dispatcher, but there was no significant change.
How can I fix this problem, and generally improve the performance of my application when I add my shapes to Canvas?
Thanks.
Need to use Visual objects instead of Shape; in particular, as suggested, DrawingVisual: a visual object that can be used to render vector graphics. In fact, as written in the MSDN library:
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, input, focus, or event handling, which improves its performance. For this reason, drawings are ideal for backgrounds and clip art.
So, for example, to create a DrawingVisual that contains a rectangle:
private DrawingVisual CreateDrawingVisualRectangle()
{
DrawingVisual drawingVisual = new DrawingVisual();
// Retrieve the DrawingContext in order to create new drawing content.
DrawingContext drawingContext = drawingVisual.RenderOpen();
// Create a rectangle and draw it in the DrawingContext.
Rect rect = new Rect(new System.Windows.Point(160, 100), new System.Windows.Size(320, 80));
drawingContext.DrawRectangle(System.Windows.Media.Brushes.LightBlue, (System.Windows.Media.Pen)null, rect);
// Persist the drawing content.
drawingContext.Close();
return drawingVisual;
}
In order to use DrawingVisual objects, you need to create a host container for the objects. The host container object must derive from the FrameworkElement class, which provides the layout and event handling support that the DrawingVisual class lacks. When you create a host container object for visual objects, you need to store the visual object references in a VisualCollection.
public class MyVisualHost : FrameworkElement
{
// Create a collection of child visual objects.
private VisualCollection _children;
public MyVisualHost()
{
_children = new VisualCollection(this);
_children.Add(CreateDrawingVisualRectangle());
// Add the event handler for MouseLeftButtonUp.
this.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(MyVisualHost_MouseLeftButtonUp);
}
}
The event handling routine can then implement hit testing by invoking the HitTest method. The method's HitTestResultCallback parameter refers to a user-defined procedure that you can use to determine the resulting action of a hit test.
Agreed that if you want to draw millions of elements, you simply can't do it in WPF. WriteableBitmapEx as mentioned is a good alternative.
See this related question which goes into depth on high performance graphics in WPF and the alternatives available.
If you simply must use Canvas, check out this ZoomableApplication2 - A million items. This is a Canvas based demo which makes heavy use of Virtualization to get reasonable performance with 1,000,000 UIElements on a Canvas.
That's a lot of UIElements and probably isn't going to give the kind of performance you're looking for. Do you need to be able to interact with each of the elements you're rendering? If not, I would highly recommend looking into using WriteableBitmap instead. If you need to draw shapes and don't want to create all that logic yourself (who would want to?), check out the WriteableBitmapEx project over on CodePlex.
This may be somewhat unrelated, and I apologize if you feel this way, but in the hopes that it can shed some light for other users, I'll share this tidbit.
We had some performance issues with a Canvas control used for capturing signatures. The capture was very jagged, and we couldn't draw curved lines as a result. It turned out to be related to a style was was generating drop-shadows on the UI elements. Disabling the drop-shadow effect solved our problem.

WPF canvas drawing with Graphics

I'd like to ask if there is any possibility to draw on WPF Canvas with some kind of a Graphics type providing methods like: DrawLine, DrawPath etc.. (as it was in .NET 2).
I know there's a lot of stuff like storyboards etc.. but I'm planning to do all the drawing in code behind and to have just 1 Canvas in WPF without any child elements.
Do you think it is a good idea? will it be smooth ?
I'd like to ask if there is any possibility to draw on WPF Canvas with some kind of a Graphics type providing methods like: DrawLine, DrawPath etc.. (as it was in .NET 2).
Yes, you need to use the DrawingContext class
Do you think it is a good idea? will it be smooth ?
That's hard to tell, depending on your exact needs... If the canvas doesn't have any child items, I think a better solution would be to create a custom control and override the OnRender method. Regarding smoothness, it all depends on how you implement it...

Erasing in a WPF program

How can I make a MS Paint clone in WPF?
I use Canvas and Shapes, but I don't know how to implement erasing. Should I use different controls, image control for example, or other drawing technology?
You can simply remove the object from the Canvas using canvas.Children.Remove() methods.

Rotating a .NET panel in Windows Forms

We use Windows Forms and custom user controls, and I would like to be able to rotate the panel hosting the userControl in a particular form. I have seen similar functionnalities with WPF, but I can't use it for the moment. Is it possible to achieve the rotation of a panel and its children using possibly built-in .NET methods or GDI+?
I have seen some pretty cool visual effect with menus that are displayed in game development, so I was wondering if it would be possible to create similar effects using Windows Forms.
Rotating a panel and its children in Windows Forms is not something directly supported, and I think it will end up being a buggy headache that could easily suck up lots of time. It's especially painful to think about when you could do this in WPF with zero lines of C# code and only a tiny bit of XAML.
You can use rotations in GDI+ by calling the RotateTransform method on a Graphics object.
However, rotating an entire control is not so simple, and will depend heavily on how the control is implemented.
If it's a composite UserControl that has other controls inside of it, you're out of luck.
If it's a sinlge control that paints itself, try inheriting the control, overriding the OnPaint method, and calling RotateTransform on the Graphics object. However, you will probably have trouble with it. In particular, you will probably need to override all of the mouse events and call the base control's events with rotated coordinates.
You can get halfway there by calling the DrawToBitmap method on your panel, then rotating the bitmap and displaying it e.g. in a PictureBox:
var bitmap = new Bitmap(panel.Width, panel.Height);
panel.DrawToBitmap(bitmap, new Rectangle(Point.Empty, panel.Size));
bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
var pictureBox = new PictureBox();
pictureBox.Location = panel.Location;
pictureBox.SizeMode = PictureBoxSizeMode.AutoSize;
pictureBox.Image = bitmap;
Controls.Remove(panel);
Controls.Add(pictureBox);
Rotation angles other than 90-degree increments are also possible, if you draw the bitmap into another bitmap using GDI:
var bitmap2 = new Bitmap(bmp.Width + 75, bmp.Height + 100);
var graphics = Graphics.FromImage(bmp2);
graphics.TranslateTransform(bitmap2.Width / 2, bitmap2.Height / 2);
graphics.RotateTransform(-15f);
graphics.TranslateTransform(-bitmap.Width / 2, -bitmap.Height / 2);
graphics.DrawImageUnscaled(bitmap, Point.Empty);
graphics.Dispose();
The problem of course is that you're only displaying an image of your panel, and not the panel itself, so it's no longer possible to interact with the controls inside.
That could probably be done as well, but you would have to mess with window messages, which gets quite a bit more complicated. Depending on your needs you might also be able to get away with handling click and key events on the PictureBox, manipulating the controls in the panel, and then updating the image.

Resources