DirectX 11/Windows Forms: Paint on Top of Rendered Scene - c#

I am having a difficult time finding details on the draw order of a DirectX 11 application when rendered in a Windows Form. I know that I can assign my SwapChain to render to the handler of a control (such as a panel) which I have done already. The question here is more focused on being able to paint over the content that is rendered via DirectX in the same control. I already know that I can add controls to the panel and they will paint over the rendered content. I attempted utilizing the Paint event for the panel and doing a simple draw:
private void panel1_Paint(object sender, PaintEventArgs e) {
using (Pen p = new Pen(Brushes.Red))
e.Graphics.FillEllipse(p, new Rectangle(0, 0, 250, 250));
}
However, I believe this is painting the requested content below the scene content rendered by DirectX. I haven't been able to find any details on the draw order and which events to utilize. If anyone has any ideas on which events to use to paint over the rendered scene, that would be helpful. If controls can be rendered on top of the scene, then I would like to believe custom painting could be achieved as well.
Maybe I'll have to go the long way around?
Update
I have found a post over on MSDN that doesn't fully answer the question at hand, but helps with my comprehension, and also sheds light on an alternate route which appears to require being done at the end of the render method for my engine (which makes sense seeing as rendering typically follows a FIFO system).
You can have GDI(+) calls on top of a DX render.  The d3d device can
return a Graphics g suitable to do gdi calls.  At least, I've done it
in windowed mode.
Microsoft.DirectX.Direct3D.Surface bb = device.GetBackBuffer(0, 0);
System.Drawing.Graphics g = bb.GetGraphics();
g.DrawRectangle(...); // or whatever
bb.ReleaseGraphics();
bb.Dispose();
Also, I don't see what's to stop somebody from adding controls to the
panel one uses as the DX panel.
Clarified Question
My original question however, still remains. Does anyone know what the draw order is between Windows Forms and DirectX 11? Does anyone know of an event that can be utilized as part of this order to draw on top of the rendered content?

Related

Background Panel Windows Form

I hope you can help me with this problem, attached videos to explain in a simpler way.
First example
Panel (has a textured background) with labels (the labels have a png image without background)
Events: MouseDown, MouseUp and MouseMove.
As you will notice in the video to drag the label the background turns white panel and regains its background image when I stop dragging the label
Panel controls have a transparent background as property, but changing the background with any color, let the problem occurred related to the substance, I do not understand why this happens and how to fix less.
Second Example
Contains the above, with the only difference that the panel controls instead of having transparent background, I chose black color for that property
You have to use double buffer and you don't have to stop using an image on the background, you can have everything running smoothly.
You have a couple of ways to do this, the fast way (not enough most of the time) is to enable doublebuffer of the panel.
The "slow" but better way is to do your own Double Buffer using a Bitmap object as a buffer.
This example creates a "side buffer" and accepts an image as parameter and draws it using created buffer.
public void DrawSomething(Graphics graphics, Bitmap yourimage)
{
Graphics g;
Bitmap buffer = new Bitmap(yourimage.Width, yourimage.Height, graphics);
g = Graphics.FromImage(buffer);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.DrawImage(yourimage, 0, 0);
graphics.DrawImage(buffer, 0, 0);
g.Dispose();
}
Call this on your OnPaint event.
BTW... this is just a double buffer example.
Cheers
Change DoubleBuffered to true for both form and panel. I think that should solve your problem.
this is totally normal, because System.Windows.Forms.Control based items were not designed to do this kind of advanced Graphics operations.
in fact the reason that this effect happens here, is that when you assign any value other than 255 to the alpha component of a control BackColor, the form does the following when you change the control size or position:
it sets the new control position
it redraws the parent control
it gets the background of the control's parent as an image
it draws acquired image into the control body to seem as if the control is transparent
the control body gets drawn on top of the previously drawn background
the control children are drawn
* this is is a simplified explanation for the sake of illustration to deliver the idea
steps 1, 2 are responsible for the flickering effect that you see.
but you have two ways to solve this,
-the first is some kinda advanced solution but it's very powerful, which is you would have to create a double buffered custom control that would be your viewport.
the second is to use WPF instead of windows forms, as WPF was designed exactly to do this kind of things.
if you can kindly provide some code, i can show you how to do both.

Redraw panel contents after ClientSizeChanged

So my application runs in fixed size window and in full screen. The problem I'm facing is how to properly scale the current contents of the panel (which depend on the application use) when the window is resized. This is my current code:
private void Form1_ClientSizeChanged(object sender, EventArgs e)
{
System.Drawing.Drawing2D.Matrix transformMatrix = new System.Drawing.Drawing2D.Matrix();
float px = panel2.Width;
float py = panel2.Height;
panel2.Width = this.Width / 2;
panel2.Height = panel2.Width;
panel2.Location = new Point(this.Width - panel2.Width - 30, 30);
transformMatrix.Scale(panel2.Width / px, panel2.Height / py);
panel2.Region.Transform(transformMatrix);
//Rest of the code
}
But the drawn content doesn't scale, and if I use Invalidate() or Refresh() the drawn content gets cleared (the panel is redrawn empty). What am I missing?
.NET doesn't remember what's drawn on the panel, as simple as that. As soon as anything invalidates the windows bitmap buffer (causing a WM_PAINT), it's going to be repainted again. So, you have to draw what you want to draw using the Paint event (or overriding OnPaint).
However, there is another way that might be easier to implement - don't paint into a Panel. Instead, paint into a PictureBox (or rather, a Bitmap assigned to the Image property of the PictureBox). The Bitmap will be reused when invalidating (and redrawing) the picture box, so nothing will be lost. By using PictureBox.ScaleMode, you can define how you want the picture box to scale the bitmap, and it will do so as well as it can.
In any case, transforming the Region property doesn't do anything useful - you're simply changing the region, not doing anything to the drawing itself. To use 2D transformation matrices, you want to apply them on a Graphics object during the drawing (in Paint handler or OnPaint override) - drawing anything on the Graphics object will then transform everything you're trying to draw, which in your case means scaling the painting.
So you have to decide: do you want to just scale a stored bitmap with the painted image, or do you want to redraw it all from scratch (which also means you can pick any level of detail you can provide)?
I think that you're mistaking what the Region property is meant for. According to the MSDN docs (empasis mine, replace 'window' with 'control' when reading):
The window region is a collection of pixels within the window where the operating system permits drawing. The operating system does not display any portion of a window that lies outside of the window region. The coordinates of a control's region are relative to the upper-left corner of the control, not the client area of the control.
All that you're doing is changing the region that the OS will allow painting, which explains why you're not seeing anything. I think that you should be resizing the control when the form is resized, either through Anchor, or through my preference of Dock with several controls, or a panel like TableLayoutPanel where it will handle scaling and relative sizing for you.
Thank you for your answers, but I wrote my own function and logic that serves the purpose for this application. Basically the function checks for the state of the application variables, and calls the appropriate function that originally drew the content, and since those functions use the panel width and height as arguments they properly scale the drawn content and retain the drawing composition.
P.S. I'll accept Luaan's answers since it offers a valid alternative and is complete.

rectangle drawn on desktop is not persistent in c# [duplicate]

I want to draw directly on the desktop in C#. From searching a bit, I ended up using a Graphics object from the Desktop HDC (null). Then, I painted normally using this Graphics object.
The problem is that my shapes get lost when any part of the screen is redrawn. I tried a While loop, but it actually ends up drawing as fast as the application can, which is not the update rate of the desktop.
Normally, I would need to put my drawing code in a "OnPaint" event, but such thing does not exist for the desktop.
How would I do it?
Example code: https://stackoverflow.com/questions/1536141/how-to-draw-directly-on-the-windows-desktop-c
I posted two solutions for a similar requirement here
Basically you have two options.
1- Get a graphics object for the desktop and start drawing to it. The problem is if you need to start clearing what you have previously drawn etc.
Point pt = Cursor.Position; // Get the mouse cursor in screen coordinates
using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
{
g.DrawEllipse(Pens.Black, pt.X - 10, pt.Y - 10, 20, 20);
}
2- The second option that I provide in the link above is to create a transparent top-most window and do all your drawing in that window. This basically provides a transparent overlay for the desktop which you can draw on. One possible downside to this, as I mention in the original answer, is that other windows which are also top-most and are created after your app starts will obscure your top most window. This can be solved if it is a problem though.
For option 2, making the form transparent is as simple as using a transparency key, this allows mouse clicks etc. to fall through to the underlying desktop.
BackColor = Color.LightGreen;
TransparencyKey = Color.LightGreen;
When you draw to HDC(NULL) you draw to the screen, in an unmanaged way. As you've discovered, as soon as windows refreshes that part of the screen, your changes are overwritten.
There are a couple of options, depending upon what you want to achieve:
create a borderless, possibly
non-rectangular window. (Use
SetWindowRgn to make a window
non-rectangular.) You can make this a child of the desktop window.
subclass the desktop window. This is not straightforward, and involves
injecting a DLL into the
Explorer.exe process.
To get an OnPaint for the desktop you would need to subclass the desktop window and use your drawing logic when it receives a WM_PAINT/WM_ERASEBKGND message.
As the thread you linked to says, you can only intercept messages sent to a window of an external process using a hook (SetWindowsHookEx from a DLL).
As mentioned a transparent window is another way to do it, or (depending on the update frequency) copying, drawing and setting a temporary wallpaper (as bginfo does).
This is difficult to do correctly.
It will be far easier, and more reliable, to make your own borderless form instead.

Drawing on Video within C#

I am making an application that will allow users to apply certain tools to analyse videos & images. I need help with how i actaully draw/write on the video loaded into windows media player within my form and being able to save it on. It needs to be able to lert the user draw freehand and shapes on it.
Thanks in Advance,
Chris :)
This is a non-trivial, if not impossible task to accomplish with the wmp control in winforms.
I don't know of any way to actually draw on the wmp but you could draw on a transparent panel overlaid over the wmp. This will not work will the video is playing but you can show the drawing while it is paused. I have used this technique to draw over a 3rd party video control that works similarly to wmp.(Edit - this does not seem to work with the wmp control)
However, as real transparent panels are also rather tricky in winforms, another way would be to grab an image from the video and draw on the overlaid image. Again, only when it is paused.
This commercial control does enable drawing over the video. It has an event that fires every frame that you can use to do the drawing. The big downside, though is that you can't really do anything too fancy as your drawing routine needs to finish before the next frame is drawn.
I would strongly encourage you to use WPF(even if its a wpf control hosted within a winforms app) to show your video. It is a whole lot easier to draw on video(including playing video) in wpf.
EDIT
I just tested drawing over the wmp using a transparent panel and its doesn't behave as my 3rd party control did,so I suggest you do the video playing bit in WPF and host that in your winforms app. (I just tested that too using #Callums inkcanvas suggestion and it works like a charm)
If you are using WPF, try placing an InkCanvas on top of your video and setting the Background to transparent. You can then save and load up the shapes the users draw on top of the video.
A little proof-of-concept with a picture instead of a video:
I suspect you may be using WinForms though, where this may be more difficult. If so, a good excuse to learn WPF!
EDIT: With WinForms, you would have to make your own custom control that acts as a transparent overlay and add brush strokes to it. It would be extremely hard to implement well (with transparent background, which doesn't play well with
WinForms). I would recommend using WPF if you are still at a stage you can change your application's UI. WPF works on XP and up.
EDIT2: After googling, there are some InkCanvas equivalents that people have made for WinForms, but I have no idea how good they are and may not support transparent backgrounds.
You could always have the video that you want annotated in a new WPF window and the rest of your application in WinForms.
I have found how to do this.
Here is one way in WPF using Canvas
private void buttonPlayVideo_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.Filter = "All Files|*.*";
Nullable<bool> result = dlg.ShowDialog();
if (result == true) {
MediaPlayer mp = new MediaPlayer();
mp.Open(new Uri(filename));
VideoDrawing vd = new VideoDrawing();
vd.Player = mp;
vd.Rect = new Rect(0, 0, 960, 540);
DrawingBrush db = new DrawingBrush(vd);
canvas.Background = db;
mp.Play();
}
}
then create mouse events for Canvas and draw with it
Point startPoint, endPoint;
private void canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(canvas);
}
private void canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
endPoint = e.GetPosition(canvas);
Line myLine = new Line();
myLine.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
myLine.X1 = startPoint.X;
myLine.Y1 = startPoint.Y;
myLine.X2 = endPoint.X;
myLine.Y2 = endPoint.Y;
myLine.HorizontalAlignment = HorizontalAlignment.Left;
myLine.VerticalAlignment = VerticalAlignment.Center;
myLine.StrokeThickness = 2;
canvas.Children.Add(myLine);
}
This can be done in WinForms but it is not easy. There is transparent form support with alpha blending in WinForms. Use the following CreateParams for the transparent overlay form: WS_EX_LAYERED, WS_EX_TRANSPARENT. Check the MSDN references for this type of window: http://msdn.microsoft.com/en-us/library/ms997507.aspx, http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#layered.
Put a transparent form above your video control and you can draw anything you want on it. Move and resize events need to be coordinated between your video window and the transparent form above it. Redrawing the overlay needs to use UpdateLayeredWindow() in user32.dll.
I learned quite a bit from this example: http://www.codeproject.com/Articles/13558/AlphaGradientPanel-an-extended-panel.
You might look at XNA (www.xna.com) from Microsoft. It is made for managed languages like c# and should support video.
I've only used it for drawing in c#, but it gets the job done.
I should also note that XNA will function as part of a regular Windows Forms app. For what it's worth, I have also prototyped something like this with Flash; Flash allows you to import each frame of the movie file into the editor and create a SWF that can respond to user interaction.
However, this approach is useless if you need to update the movie in real-time. Flash (last I checked) could only import the movie at design time.
Ok, by far and away the best way of doing this is to use Silverlight. Silverlight supports all of the major streaming formats and also provides complete access to the framebuffer.
Easy :-)

Resizing window causes black strips

I have a form, which sets these styles in constructor:
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
And I draw some rectangles in Paint event. There are no controls on the form. Hovewer, when I resize the form, there are black strips at right and bottom of the form. Is there any way to get rid of them? I've tried everything, listening for WM_ERASEBKGND in WndProc, manually drawing the form on WM_PAINT, implementing custom double buffer, etc. Is there anything else I could try?
I've found this:
https://connect.microsoft.com/VisualStudio/feedback/details/522441/custom-resizing-of-system-windows-window-flickers
and it looks like it is a bug in DWM, but I just hope I can do some workaround.
Please note that I must use double buffering, since I want to draw pretty intense graphic presentation in the Paint event. I develop in C# .NET 2.0, Win7.
Status Update 1
I've managed to get rid of most of the black stripes by implementing the resize functionality by myself. Hovewer there are still some minor glitches. Is there any way to do resize and paint operation at once? Here is a pseudo-code of what I need to do:
IntPtr hDC;
var size = new Size(250, 200);
IntPtr handle = API.PaintAndResizeBegin(this.Handle /* Form.Handle */,
size.Width, size.Height, out hDC);
using (var g = Graphics.FromHdc(hDC)) {
this.backBuffer.Render(g, size);
}
API.PaintAndResizeCommit(handle);
Is there any way to implement the above code?
The second solution could be to back-buffer whole form, including non-client area. But how to do that? I don't want to paint the non-client area by myself, as I want to keep the nice aero effect on Vista/7. Any help will be deeply appreciated.
Status Update 2
It looks like this problem is unsolvable, since it is omnipresent on Windows, in every application. We can just hope that MS will take some inspiration in Mac OS X and will provide appropriate APIs in new Windows.
I've found the function which can paint and resize window at the same time - UpdateLayeredWindow.
So now it should be possible to create resizable windows, which do not have any strips while being resized. However, you need to paint the window content yourself, so it is a little inconvenient. But I think that using WPF and UpdateLayeredWindow, there shouldn't be any problem.
Update
Found problems. :-) When using UpdateLayeredWindow, you must paint the window's border yourself. So, if you want standard window painted using UpdateLayeredWindow with nice glass effect in win7, you are screwed.
On Microsft Connect is even a thread about this problem, where Microsoft says it is a bug by design, and if it ever gets fixed, then probably in Win8 or some newer system. So there isn't much we could do about this.
I found that it is best not to do any custom rendering directly on the Form surface. Instead, put a docked PictureBox on the form, create Bitmap object that will be displayed in the PictureBox, draw everything onto that using the System.Drawing.Graphics.FromImage(Image) method.
I used that method with a game loop to make a simple shooter game (Crimsonland-style) and got pretty good performance (with anti-aliased lines), above 100 FPS.

Categories