I'm developing a drawing application in Visual C++ by means of Direct2D.
I have a demo application where:
// create the ID2D1Factory
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
// create the main window
HWND m_hwnd = CreateWindow(...);
// set the render target of type ID2D1HwndRenderTarget
m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hwnd, size),
&m_pRenderTarget
);
And when I receive the WM_PAINT message I draw my shapes.
Now I need to develop a WPF control (a kind of Panel) that represents my new render target (therefore it would replace the main window m_hwnd), so that I can create a new (C#) WPF project with the main window that has as children my custom panel, while the rendering part remains in the native C++/CLI DLL project.
How can I do that? What I have to set as my new render target?
I thought to use the handle of my WPF window:
IntPtr windowHandle = new WindowInteropHelper(MyMainWindow).Handle;
But I need to draw on my panel and not on my window.
Please note that I don't want to use any WPF classes for the rendering part (Shapes, DrawingVisuals...)
You have to implement a class that hosts your Win32 Window m_hwnd. This class inherits from HwndHost.
Moreover, you have to override the HwndHost.BuildWindowCore and HwndHost.DestroyWindowCore methods:
HandleRef BuildWindowCore(HandleRef hwndParent)
{
HWND parent = reinterpret_cast<HWND>(hwndParent.Handle.ToPointer());
// here create your Window and set in the CreateWindow function
// its parent by passing the parent variable defined above
m_hwnd = CreateWindow(...,
parent,
...);
return HandleRef(this, IntPtr(m_hwnd));
}
void DestroyWindowCore(HandleRef hwnd)
{
DestroyWindow(m_hwnd); // hwnd.Handle
}
Please follow this tutorial: Walkthrough: Hosting a Win32 Control in WPF.
I'll try to answer the question as best I can based on WPF 4.5 Unleashed Chapter 19. If you want to look it up, you can find all information there in the sub-section "Mixing DirectX Content with WPF Content".
Your C++ DLL should have 3 exposed methods Initialize(), Cleanup() and Render().
The interesting methods are Initialize() and InitD3D(), which is called by Initialize():
extern "C" __declspec(dllexport) IDirect3DSurface9* WINAPI Initialize(HWND hwnd, int width, int height)
{
// Initialize Direct3D
if( SUCCEEDED( InitD3D( hwnd ) ) )
{
// Create the scene geometry
if( SUCCEEDED( InitGeometry() ) )
{
if (FAILED(g_pd3dDevice->CreateRenderTarget(width, height,
D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0,
true, // lockable (true for compatibility with Windows XP. False is preferred for Windows Vista or later)
&g_pd3dSurface, NULL)))
{
MessageBox(NULL, L"NULL!", L"Missing File", 0);
return NULL;
}
g_pd3dDevice->SetRenderTarget(0, g_pd3dSurface);
}
}
return g_pd3dSurface;
}
HRESULT InitD3D( HWND hWnd )
{
// For Windows Vista or later, this would be better if it used Direct3DCreate9Ex:
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
// Set up the structure used to create the D3DDevice. Since we are now
// using more complex geometry, we will create a device with a zbuffer.
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof( d3dpp ) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
// Create the D3DDevice
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
// Turn on the zbuffer
g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
// Turn on ambient lighting
g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff );
return S_OK;
}
Lets move on to the XAML code:
xmlns:interop="clr-namespace:System.Windows.Interop;assembly=PresentationCore"
<Button.Background>
<ImageBrush>
<ImageBrush.ImageSource>
<interop:D3DImage x:Name="d3dImage" />
</ImageBrush.ImageSource>
</ImageBrush>
</Button.Background>
I've set it as background of a button here, using an ImageBrush. I believe adding it as background is a good way to display the DirectX content. However, you can use the image in any way you like.
To initialize the rendering acquire a handle to the current window and call the Initialize() method of the DLL with it:
private void initialize()
{
IntPtr surface = DLL.Initialize(new WindowInteropHelper(this).Handle,
(int)button.ActualWidth, (int)button.ActualHeight);
if (surface != IntPtr.Zero)
{
d3dImage.Lock();
d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface);
d3dImage.Unlock();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
}
The CompositionTarget.Rendering event is fired just before the UI is rendered. You should render your DirectX content in there:
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (d3dImage.IsFrontBufferAvailable)
{
d3dImage.Lock();
DLL.Render();
// Invalidate the whole area:
d3dImage.AddDirtyRect(new Int32Rect(0, 0, d3dImage.PixelWidth, d3dImage.PixelHeight));
d3dImage.Unlock();
}
}
That was basically it, I hope it helps. Now just a few important sidenotes:
Always lock your image, to avoid that WPF draws frames partially
Dont call Present on the Direct 3D device. WPF presents its own backbuffer, based on the surface you passed to d3dImage.SetBackBuffer().
The event IsFrontBufferAvailableChanged should be handled because sometimes the frontbuffer can become unavailable (for example when the user enters the lock screen). You should free or acquire the resources based on the buffer availability.
private void d3dImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (d3dImage.IsFrontBufferAvailable)
{
initialize();
}
else
{
// Cleanup:
CompositionTarget.Rendering -= CompositionTarget_Rendering;
DLL.Cleanup();
}
}
It would be possible with WINFORMS probably, but not with WPF.
Here is a documentation talking about how WPF uses HWNDs:
How WPF Uses Hwnds
To make the most of WPF "HWND interop", you need to understand how WPF
uses HWNDs. For any HWND, you cannot mix WPF rendering with DirectX
rendering or GDI / GDI+ rendering. This has a number of implications.
Primarily, in order to mix these rendering models at all, you must
create an interoperation solution, and use designated segments of
interoperation for each rendering model that you choose to use. Also,
the rendering behavior creates an "airspace" restriction for what your
interoperation solution can accomplish. The "airspace" concept is
explained in greater detail in the topic Technology Regions Overview.
All WPF elements on the screen are ultimately backed by a HWND. When
you create a WPF Window, WPF creates a top-level HWND, and uses an
HwndSource to put the Window and its WPF content inside the HWND. The
rest of your WPF content in the application shares that singular HWND.
An exception is menus, combo box drop downs, and other pop-ups. These
elements create their own top-level window, which is why a WPF menu
can potentially go past the edge of the window HWND that contains it.
When you use HwndHost to put an HWND inside WPF, WPF informs Win32 how
to position the new child HWND relative to the WPF Window HWND. A
related concept to HWND is transparency within and between each HWND.
This is also discussed in the topic Technology Regions Overview.
Copied from https://msdn.microsoft.com/en-us/library/ms742522%28v=vs.110%29.aspx
I would recomend you to study a way to keep track of your render area and maybe create a "hideous" child window in front of it.
Other research you can maybe do is to try and find/get WPF graphics buffer and inject your rendered scene directly into it using pointers and some advanced memory programming.
https://github.com/SonyWWS/ATF/ might help
This is a level-editor with an direct2d view included
Related
I'm working on an WinForms replacement app in WPF 4.5.
The current WinForms app streams video from a C++ component. The C# WinForms control has this code:
public void StartVideoStream(int iCamera)
{
if (InvokeRequired)
{
delStartVideoStream del = new delStartVideoStream(StartVideoStream);
Invoke(del, new object[] { iCamera });
}
else
{
if (!VideoPlaying)
{
int iSuccess = ClientComm.StartVideoStream(iCamera, ucVideoPlayer.Handle,
(ClientComm.streaming_protocols)Properties.Settings.Default.VideoStreamProtocol,
Properties.Settings.Default.VideoStreamFrameRate);
if (iSuccess != 0)
{
Debug.WriteLine("[ucVideo] Could not play video.");
}
}
else
{
ClientComm.SelectVideoStream(iCamera);
}
VideoPlaying = true;
}
}
You can see that it passes its handle to the COM component which writes the video directly to it.
The problem is that in WPF controls do not have handles. How can I do this I WPF?
Thank you.
The only way I've seen this done is to host a WinForms control inside the WPF control using a HwndHost (https://msdn.microsoft.com/en-us/library/system.windows.interop.hwndhost%28v=vs.110%29.aspx). Unfortunately this lands you right back in WinForms territory.
HwndHost is a little involved to cover in a sensible answer, but there's a reasonable tutorial here: http://blogs.msdn.com/b/ivo_manolov/archive/2007/10/07/5354351.aspx
You should be able to get some useful pointers from the Vlc.DotNet library (https://github.com/ZeBobo5/Vlc.DotNet), which does something similar. Based on playing around with that, if you pass the handle of the WPF application window to a COM component the video is likely to overlay the entire window. If, on the other hand, you talk to the COM component from a WinForms component inside the WPF component, you can use the handle of the WinForms component and keep the rest of your window clear of obstruction.
I'm working on an application that should do the following on start-up:
Connect to an external application using COM (AutoCAD).
Send message to the application to run some DLL code (opens a window).
Hide AutoCAD's window, but keep the DLL's window visible.
I've successfully completed the first 2 steps, but the third is giving me some issues.
I do not know if it is possible to make a child window visible while it's parent is not visible. Every time that I make the child visible or make it the top most window, AutoCAD becomes visible as well.
My objective is to run my DLL code, but keep AutoCAD running in the background, completely invisible to my users. The DLL must be loaded, through AutoCAD, because it allows me to work with AutoCAD's .NET interface as opposed to COM.
In any case, I'm curious if what I'm trying to do is possible, perhaps through some Windows API calls or perhaps something in .NET.
PS: I'm unsure if this window relationship is really a parent-child one. I'm assuming it is though because my window belongs to the AutoCAD application instance due to the DLL loading.
Any help is greatly appreciated. Thanks! :)
EDIT:
DLL Code to create a window.
//CommandMethod is an AutoCAD attribute for entering into the DLL. This code is called
//when the user attempts the command "AUTOCADCOMMANDNAME" or can be done by simulating
//the command programmatically.
[CommandMethod("AUTOCADCOMMANDNAME", CommandFlags.Session)]
public void CommandEntry()
{
MainWindow mainWin = new MainWindow();
mainWin.ShowDialog();
}
Main Application Code
public static void Main()
{ //Use a utility class to create a connection to AutoCAD via COM
AcadApplication acadApp = ACUtil.GetAcadInstance(out createdNewInstance);
acadApp.Visible = false;
//Irrelevant code omitted...
acadApp.ActiveDocument.SendCommand("AUTOCADCOMMANDNAME");
acadApp.Quit(); //Exit AutoCAD application
//Note: doesn't quit until mainWin closes because of ShowDialog()
}
Can't be done. Parent windows control child window visibility.
Your best alternative is to make the DLL window a top-level window (but owned by the AutoCAD window).
Note that the DLL window will still be part of the AutoCAD thread.
What you want can be achieved, despite what others may think. You just need to think about the problem in a different way. Don't think about parent and child Windows... instead, think about a splash screen Window.
Typically, splash screens appear before the main application Window, but does that make them the parent? No, it doesn't. Normally, they'd be closed after the main Window has opened, but there is no reason why you couldn't hide it instead of closing it.
To find out how to do this in WPF, please refer to my answer from the How to open a child Window like a splash screen before MainWindow in WPF? question, here on Stack Overflow. Extending that answer a little bit, I should point out that you won't need to use a Timer. Instead of the code from the linked page, you could do something like this:
private void OpenMainWindow()
{
autoCadWindow.Visiblity = Visibility.Collapsed;
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
}
Haha! I found it!
So, I ended up calling the SetWindowPos function in the Windows API and supplied the handle for AutoCAD window. I did this inside my main application:
[DllImport("User32.dll")]
static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int x, int y, int w, int h, uint flags);
public const int SWP_HIDEWINDOW = 0x0080;
public static void Main()
{
//...Setup AutoCAD...
//Change window size and hide it before calling to open mainWin inside the DLL.
SetWindowPos(new IntPtr(acadApp.HWND), new IntPtr(1), 0, 0, 0, 0, SWP_HIDEWINDOW);
//Display mainWin by entering the DLL.
acadApp.ActiveDocument.SendCommand("AUTOCADCOMMANDNAME");
//Terminate application as before...
}
Basically I'm telling the AutoCAD window to hide by modifying the HWND directly. I also set the dimensions to width=0 and height=0 which causes the window to take up the minimum size possible. Unfortunately, the window will flicker once, but for my purposes, that is negligible. If anyone can find a way to remove the flicker, that would be great! :)
EDIT: When using SetWindowPos, Windows tends to remember the values entered for the next time that application window is shown. This means that if not restored properly, then the next time the user opens AutoCAD manually, it will have the coordinates of 0,0 and the minimum width/height.
To change that behavior, it is necessary to obtain the window information. For my program, I used GetWindowRect obtain the original settings. Before closing my program, I restored those settings using SetWindowPos. See the code below for details:
First, import necessary WINAPI functions and structs:
[DllImport("User32.dll")]
static extern bool GetWindowRect(IntPtr hwnd, out RECT rect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
Obtain original settings before modifying window:
RECT originalRect;
GetWindowRect(new IntPtr(acadApp.HWND), out originalRect);
Modify the window to hide (and resize):
SetWindowPos(new IntPtr(acadApp.HWND), new IntPtr(1), 0, 0, 0, 0, SWP_HIDEWINDOW);
Restore original settings before quitting:
SetWindowPos(new IntPtr(acadApp.HWND), new IntPtr(1),
originalRect.Left,
originalRect.Top,
originalRect.Right - originalRect.Left,
originalRect.Bottom - originalRect.Top, 0);
In C# WPF, I made a custom UI element that looks like a window so that I can have a mini-desktop environment in my WPF application. This "WindowControl" class has maximize, minimize, close, scaling, translation, etc...
This window UI contains a canvas, which is where other embedded UI elements goes. For example, I can embed a TreeView in the canvas of my WindowControl and move it around the screen just like as if you opened Windows Explorer in your OS.
Everything is working EXCEPT for the web browser. When I put the built-in web browser control into the canvas of my WindowControl class, it will NOT refresh. I DON'T mean refresh the url of the browser. I mean refresh the UI element itself. As I move my WindowControl class (with embedded web browser) around the screen, the web browser is leaving screen artifacts all over the place.
THE ACTUAL QUESTION IS: How do you force the built-in web browser UI element in C# WPF to re-draw itself so that it does not leave artifacts when being resized/translated?
I would include my code... but the WindowControl class alone is nearly 1000 lines, and that would not be pleasant on this forum.
So far I have tried the following (none worked):
webBrowser.Measure();
webBrowser.Arrange();
webBrowser.Dispatcher.Invoke(DispatcherPriority.Render, new Action(() => { }));
webBrowser.Width = XX;
webBrowser.Height = XX;
webBrowser.ActualWidth = XX;
webBrowser.ActualHeight = XX;
// As sugguested by Noseratio
IntPtr hwnd;
((IOleWindow)webBrowser.Document).GetWindow(out hwnd);
UpdateWindow(hwnd);
I'd try forcing the update with UpdateWindow (untested):
Get HWND of WebBrowser.Document via IOleWindow
IntPtr hwnd;
((IOleWindow)_webBrowser.Document).GetWindow(out hwnd);
Call UpdateWindow via p/invoke:
UpdateWindow(hwnd);
For WinForms:
Error can't convert ... 'System.Windows.Forms.HtmlDocument' in 'PInvoke.NativeMethods.IOleWindow'
Solution
public IntPtr GetHandle()
{
HtmlDocument doc = Document;
if (doc == null || doc.DomDocument == null) return IntPtr.Zero;
IntPtr hwnd;
((NativeMethods.IOleWindow)(doc.DomDocument)).GetWindow(out hwnd);
return hwnd;
}
I'm writing a simple "tutorial" library that will allow developers to easily add step-by-step tutorials to their existing WPF applications. The tutorials will help first time users of the application find their way around by adding an overlay that highlights a control and explains its purpose. The end result will look something like this:
The regular application:
The overlay explaining the purpose of a control:
My question is this: What's the most reliable and unobtrusive way to inject the overlay view into the current window? The best I've come up with so far is to require the developer to add an attached property to whatever window will be hosting the overlay, and then add the necessary elements on the window's Initialized callback:
public static void IsTutorialOverlayCompatibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((Boolean)e.NewValue == true)
{
if (sender as Window != null)
{
Window window = (Window)sender;
window.Loaded += new RoutedEventHandler((o, eargs) =>
{
Grid newRootElement = new Grid();
newRootElement.Name = "HelpOverlayRoot";
if (window.Content as UIElement != null)
{
UIElement currentContent = (UIElement)window.Content;
window.Content = null;
newRootElement.Children.Add(currentContent);
newRootElement.Children.Add(new HelpOverlayControl());
window.Content = newRootElement;
}
});
}
}
}
This feels like a hack, however, and I'm not sure that there isn't some edge case where this method will break the layout of the application. In addition, it requires that the window's Content property be an instance of type UIElement.
I'd like to avoid forcing developers to change their XAML (i.e, adding a custom overlay UserControl to every window) in order to use my library. What's the best way to add this kind of functionality to an existing WPF application?
I need, for my WPF app, to detect when the DWM is turned on/off or when the system theme changes.
There is such an event in WinForms, but I can't see any in WPF.
I haven't heard of a WinForms event that fires when a WinForms window receives messages from the system, however it has its own WndProc() method that you can override. You're probably confusing window messages for form events. Ah, so it's the StyleChanged event that gets invoked in WinForms windows. The rest of my answer still stands though.
WPF isn't closely tied to the Windows API either as it's a high-level technology that invests a lot of abstraction away from the internals. For one, it draws everything in a window by itself, and doesn't ask the system to do the drawing for it (EDIT: which is why WPF lacks such a StyleChanged event). That said, Windows sends messages to all windows when the DWM is toggled and when the theme changes, and you can still drill down into the low level from the WPF layer to access these messages and manipulate your WPF controls accordingly.
Attach a window procedure to your WPF window's HWND (window handle) as part of your window's SourceInitialized event. In your window procedure, handle the WM_DWMCOMPOSITIONCHANGED and WM_THEMECHANGED window messages respectively.
Here's a quick example (with boilerplate code adapted from this question of mine):
private IntPtr hwnd;
private HwndSource hsource;
private const int WM_DWMCOMPOSITIONCHANGED= 0x31E;
private const int WM_THEMECHANGED = 0x31A;
private void Window_SourceInitialized(object sender, EventArgs e)
{
if ((hwnd = new WindowInteropHelper(this).Handle) == IntPtr.Zero)
{
throw new InvalidOperationException("Could not get window handle.");
}
hsource = HwndSource.FromHwnd(hwnd);
hsource.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WM_DWMCOMPOSITIONCHANGED:
case WM_THEMECHANGED:
// Respond to DWM being enabled/disabled or system theme being changed
return IntPtr.Zero;
default:
return IntPtr.Zero;
}
}
Unfortunately the accepted solution does not work with Aero color theme changes, and the WM message hex numbers are mixed up - but I agree that it is very useful if you want to catch WM messages in WPF. I've been trying to find a solution to this problem for a while, and I think I've got it solved for all possible cases (for aero and classic themes).
The Aero color change triggers the WM_DWMCOLORIZATIONCOLORCHANGED message.
To detect when the color theme changes you have to use multiple methods. The Form.StyleChanged event is going to detect all theme change, except for Aero color changes. Here is an alternative solution to StyleChanged. (Ok, I know this is WinForms, but you've got the idea. The WPF equivalent is in the accepted answer anyway.)
private const int WM_DWMCOLORIZATIONCOLORCHANGED = 0x320;
private const int WM_DWMCOMPOSITIONCHANGED = 0x31E;
private const int WM_THEMECHANGED = 0x031A;
protected override void WndProc(ref Message m)
{
switch(m.Msg)
{
case WM_DWMCOLORIZATIONCOLORCHANGED:
case WM_DWMCOMPOSITIONCHANGED:
case WM_THEMECHANGED:
// you code here
break;
default:
break;
}
base.WndProc(ref m);
}
For Aero color themes, the SystemEvents.UserPreferenceChanged event works too (thanks sees!):
Microsoft.Win32.SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
private void SystemEvents_UserPreferenceChanged(object sender, Microsoft.Win32.UserPreferenceChangedEventArgs e)
{
if (e.Category == Microsoft.Win32.UserPreferenceCategory.General)
{
// your code here, compare saved theme color with current one
}
}
As you can see above, it is far from intuitive. Aero color change triggers a 'General' preference change event, even though there are many more suitable ones for this, like 'VisualStyle', etc...
If you want to be thorough, you should compare the saved DWM color to the current DWM color, to make sure it was indeed an Aero color theme that triggered this event (using the DwmGetColorizationParameters API call), and not something else. See these answers on how Aero colors can be retrieved:
Get the active color of Windows 8 automatic color theme
The event SystemEvents.UserPreferenceChanged also does the trick.
UserPreferenceChanged(in Japaense)