I have created a custom action for my setup project and have successfully implemented a form that displays a progress bar for a download step in my install (I'm using a WebClient in my custom action code). So I have two questions that relate to each other.
Is there any way to show a download progress bar in the main setup window rather than creating a separate form that I display as I have done? I would prefer this.
If not, then what can I do to cause my form to display in front of the actual setup window when I call form.ShowDialog()? I've also called BringToFront() on it which doesn't work either. It's there, but it's always behind the main setup window. Seems there has to be some way to get the correct z-order.
Thanks for your help.
So I gave up on the idea of integrating the progress bar into the actual installer screen, but it's just plain ridiculous what it takes to get the Windows Form to display on top. I have to get a handle to the installer Window and send it to the background because bringing the progress bar window forward simply won't work. I've moved to Mac development now so coming back to this is just frustrating. I remember thinking C# .NET was pretty cool. It's got NOTHING on Cocoa/Objective-C.
It's infuriating having a method called BringToFront() that simply ignores you. Why do I have to drop down to Windows API code to do something as fundamental to a GUI as managing the the Z-Order? Z-Order? Seriously?
In case you're wondering, here's what I ended up doing (via google):
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern bool SetWindowPos(
IntPtr hWnd, // window handle
IntPtr hWndInsertAfter, // placement-order handle
int X, // horizontal position
int Y, // vertical position
int cx, // width
int cy, // height
uint uFlags); // window positioning flags
public const uint SWP_NOSIZE = 0x1;
public const uint SWP_NOMOVE = 0x2;
public const uint SWP_SHOWWINDOW = 0x40;
public const uint SWP_NOACTIVATE = 0x10;
[DllImport("user32.dll", EntryPoint = "GetWindow")]
public static extern IntPtr GetWindow(
IntPtr hWnd,
uint wCmd);
public const uint GW_HWNDFIRST = 0;
public const uint GW_HWNDLAST = 1;
public const uint GW_HWNDNEXT = 2;
public const uint GW_HWNDPREV = 3;
public static void ControlSendToBack(IntPtr control)
{
bool s = SetWindowPos(
control,
GetWindow(control, GW_HWNDLAST),
0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}
I get a handle to the installer window and then call ControlSendToBack() on it. It works, but it sends it to the very back. I tried another method that would just send it back one position, but this wouldn't work either. Windows programming--as good as it was in 1995. Cool.
Another way of doing this is to use a BackgroundWorker. You let the Background Worker handle the downloading of the file so it doesn't prevent the UI being updated.
See this link on donnetperls
Related
Essentially cannot find and answer to this question, or if it is even possible.
I have a game I am creating for a class, and it simply looks better when forced full screen and when the zoom is set to a particular size. I was wonder if I could recreate this without the player being necessary to change it themselves.
ALT + ENTER Full screen
And
CTRL + Scroll wheel zoom
For a literal answer to your question on how to:
Send keys to go Fullscreen, and
Send a Ctrl+MouseWheel
You want some help from the Win32 interop to send keyboard & mouse messages to your console window.
using System.Runtime.InteropServices;
public class Win32
{
public const int VK_F11 = 0x7A;
public const int SW_MAXIMIZE = 3;
public const uint WM_KEYDOWN = 0x100;
public const uint WM_MOUSEWHEEL = 0x20A;
public const uint WHEEL_DELTA = 120;
public const uint MK_CONTROL = 0x00008 << 16;
[DllImport("kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}
As reference the magic numbers are from:
Virtual Keys (VK_*)
Window input messages (WM_*)
Mousewheel params (WHEEL_DELTA & MK_*)
and the ShowWindow params (SW_*)
You could then simply send your keypress and mousewheel like so:
using static Win32;
// Get window handle of the console
var hwnd = GetConsoleWindow();
// Go fullscreen by sending the F11 keydown message.
PostMessage(hwnd, WM_KEYDOWN, (IntPtr)VK_F11, IntPtr.Zero);
// Or maximize the window instead. Your users may not know how to get out of fullscreen...
/// ShowWindow(hwnd, SW_MAXIMIZE);
// Send mouse wheel message.
// MK_CONTROL: Holds the Ctrl key. WHEEL_DELTA: Positive=Up, Negative=Down.
PostMessage(hwnd, WM_MOUSEWHEEL, (IntPtr)(MK_CONTROL | WHEEL_DELTA), IntPtr.Zero);
Alternatively, as #JeremyLakerman mentioned in a comment to your question, you could set the console font to a larger size; which is a lot better, but also a bit more involved than sending Ctrl+MouseWheel.
Some background
One of my current clients runs a chain of Internet points where customers an access the net through PC:s set up as "kiosks" (a custom-built application "locks" the computer until a user has signed in, and the running account is heavily restricted through the Windows group policy). Currently, each computer is running Windows XP and uses Active Desktop to display advertisements in the background. However, since my client has got problems with Active Desktop crashing on a daily basis (in addition to generally slowing down the computer) I have been asked to develop an application that replaces it.
The problem
I am trying to investigate whether it is possible to build a Windows forms application (using C#) that always stays in the background. The application should lie above the desktop (so that it covers any icons, files etc) but always behind all other running applications. I guess I'm really looking for a BottomMost property of the Form class (which doesn't exist, of course).
Any tips or pointers on how to achieve this would be highly appreciated.
This isn't directly supported by the .NET Form class, so you have two options:
1) Use the Win32 API SetWindowPos function.
pinvoke.net shows how to declare this for use in C#:
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;
So in your code, call:
SetWindowPos(Handle, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
As you commented, this moves the form to the bottom of the z-order but doesn't keep it there. The only workaround I can see for this is to call SetWindowPos from the Form_Load and Form_Activate events. If your application is maximized and the user is unable to move or minimise the form then you might get away with this approach, but it's still something of a hack. Also the user might see a slight "flicker" if the form gets brought to the front of the z-order before the SetWindowPos call gets made.
2) subclass the form, override the WndProc function and intercept the WM_WINDOWPOSCHANGING Windows message, setting the SWP_NOZORDER flag (taken from this page).
I think the best way to do so is using the activated event handler and SendToBack method, like so:
private void Form1_Activated(object sender, EventArgs e)
{
this.SendToBack();
}
Set your window to be a child window of the desktop (the "Program Manager" or "progman" process). I've succeeded with this method in Windows XP (x86) and Windows Vista (x64).
I stumbled on this method while searching for a way to make a screensaver display as if it were wallpaper. It turns out, this is sort of built in to the system's .scr handler. You use screensaver.scr /p PID, where PID is the process id of another program to attach to. So write a program to find progman's handle, then invoke the .scr with that as the /p argument, and you have screensaver wallpaper!
The project I'm playing with now is desktop status display (shows the time, some tasks, mounted disks, etc), and it's built on Strawberry Perl and plain Win32 APIS (mainly the Win32::GUI and Win32::API modules), so the code is easy to port to or understand any dynamic language with similar Win32 API bindings or access to Windows' Scripting Host (eg, ActivePerl, Python, JScript, VBScript). Here's a relevant portion of the class that produces the window:
do { Win32::API->Import(#$_) or die "Win32::API can't import #$_ ($^E)" } for
[user32 => 'HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName)'],
[user32 => 'HWND SetParent(HWND hWndChild, HWND hWndNewParent)'],
sub __screen_x {
Win32::GUI::GetSystemMetrics(SM_CXSCREEN)
}
sub __screen_y {
Win32::GUI::GetSystemMetrics(SM_CYSCREEN)
}
sub _create_window { # create window that covers desktop
my $self = shift;
my $wnd = $$self{_wnd} = Win32::GUI::Window->new(
-width => __screen_x(), -left => 0,
-height => __screen_y(), -top => 0,
) or die "can't create window ($^E)";
$wnd->SetWindowLong(GWL_STYLE,
WS_VISIBLE
| WS_POPUP # popup: no caption or border
);
$wnd->SetWindowLong(GWL_EXSTYLE,
WS_EX_NOACTIVATE # noactivate: doesn't activate when clicked
| WS_EX_NOPARENTNOTIFY # noparentnotify: doesn't notify parent window when created or destroyed
| WS_EX_TOOLWINDOW # toolwindow: hide from taskbar
);
SetParent($$wnd{-handle}, # pin window to desktop (bottommost)
(FindWindow('Progman', 'Program Manager') or die "can't find desktop window ($^E)")
) or die "can't pin to desktop ($^E)";
Win32::GUI::DoEvents; # allow sizing and styling to take effect (otherwise DC bitmaps are the wrong size)
}
This program buffers output to prevent flickering, which you'll probably want to do as well. I create a DC (device context) and PaintDesktop to it (you could use any bitmap with only a couple more lines -- CreateCompatibleBitmap, read in a file, and select the bitmap's handle as a brush), then create a holding buffer to keep a clean copy of that background and a working buffer to assemble the pieces -- on each loop, copy in background, then draw lines and brush bitmaps and use TextOut -- which is then copied to the original DC, at which time it appears on screen.
Yes, function SetWindowPos with flag HWND_BOTTOM should help you. But, from my experience: even after calling SetWindowPos as result of some user operations your window may bring to front.
subclass the form, override the WndProc function and intercept the Windows message(s) that are responsible for moving it up the z-order when it gets activated.
Create a Panel that cover your form, but what ever you want on that Panel, then in the Panel's Click-Event write this.sendback .
I've managed to get rid of the flickering when using setwindowpos...
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;
const UInt32 SWP_NOZORDER = 0x0004;
const int WM_ACTIVATEAPP = 0x001C;
const int WM_ACTIVATE = 0x0006;
const int WM_SETFOCUS = 0x0007;
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
const int WM_WINDOWPOSCHANGING = 0x0046;
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr DeferWindowPos(IntPtr hWinPosInfo, IntPtr hWnd,
IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr BeginDeferWindowPos(int nNumWindows);
[DllImport("user32.dll")]
static extern bool EndDeferWindowPos(IntPtr hWinPosInfo);
private void Window_Loaded(object sender, RoutedEventArgs e)
{
IntPtr hWnd = new WindowInteropHelper(this).Handle;
SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
HwndSource src = HwndSource.FromHwnd(windowHandle);
src.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SETFOCUS)
{
IntPtr hWnd = new WindowInteropHelper(this).Handle;
SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
handled = true;
}
return IntPtr.Zero;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
HwndSource src = HwndSource.FromHwnd(windowHandle);
src.RemoveHook(new HwndSourceHook(this.WndProc));
}
Winforms App. .Net 3.5.
I need to set the focus from my C# application to the user's desktop (almost like simulating a mouse click on the desktop).
Can someone please show me how to do this with C#? I just want to set focus on the desktop so the focus is no longer on my application but I want to do this from within my application.
Edit: An answer below works by setting the focus to the desktop, but it minimizes all the open windows on the user's desktop.
Is there a way I can maybe set the focus to the next open window on the desktop instead? I just want to get the focus off of my application (without minimizing my application or hiding it). I just want to move focus to somewhere else. Maybe the desktop was not the best choice if it will minimize all the user's open windows/applications.
This should do it for you.
using System;
using System.Runtime.InteropServices;
namespace ConsoleApplication1 {
class Program {
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true)]
static extern IntPtr SendMessage(IntPtr hWnd, Int32 Msg, IntPtr wParam, IntPtr lParam);
const int WM_COMMAND = 0x111;
const int MIN_ALL = 419;
const int MIN_ALL_UNDO = 416;
static void Main(string[] args) {
IntPtr lHwnd = FindWindow("Shell_TrayWnd", null);
SendMessage(lHwnd, WM_COMMAND, (IntPtr)MIN_ALL, IntPtr.Zero);
System.Threading.Thread.Sleep(2000);
SendMessage(lHwnd, WM_COMMAND, (IntPtr)MIN_ALL_UNDO, IntPtr.Zero);
}
}
}
Get Next Window
I don't have a code example ready for these two but I'm going to give you the links to both. The first think you need to do is call GetWindow. After doing that you'll want to call SwitchToThisWindow passing in the pointer you received from GetWindow.
You can add this COM object in your project:
Microsoft Shell Controls And Automation
And then just call:
Shell32.ShellClass shell = new Shell32.ShellClass();
shell.MinimizeAll();
This will minimize all the windows and then focus the desktop. Otherwise, if you have your window non-full screen then you can simulate the mouse click using:
//This is a replacement for Cursor.Position in WinForms
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern bool SetCursorPos(int x, int y);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
public const int MOUSEEVENTF_LEFTDOWN = 0x02;
public const int MOUSEEVENTF_LEFTUP = 0x04;
//This simulates a left mouse click
public static void LeftMouseClick(int xpos, int ypos)
{
SetCursorPos(xpos, ypos);
mouse_event(MOUSEEVENTF_LEFTDOWN, xpos, ypos, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, xpos, ypos, 0, 0);
}
You can calculate coordinates by looking at your window startup location plus height/width and select a available space (that will be the desktop indeed).
The MSV-Studio description for Locked is "The Locked property determines if we can move or resize the control" so I set the winforms Locked property to true but the form is still movable.
What is the correct way to prevent the form from moving?
Maximize it. Thanks, JackN. ;-)
I use the following code to display a form dialog window for a corporate security application written in-house - one of the requirements was that the form could not be moved, resized or live under any other form. Anyway, see below for a start...
/// <seealso href="http://msdn.microsoft.com/en-us/library/ms633548(v=vs.85).aspx"/>
/// <seealso href="http://msdn.microsoft.com/en-us/library/ms633545(v=vs.85).aspx"/>
public class ShowMessage
{
const int SW_SHOWMAXIMIZED = 3; //for maximising (if desired)
const int SW_SHOW = 5; //for simply activating the form (not needed)
const int SW_SHOWNORMAL = 1; //displays form at original size and position (what we use here)
const UInt32 SWP_NOSIZE = 0x0001; //cannot be resized
const UInt32 SWP_NOMOVE = 0x0002; //cannot be moved
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); //always lives at the top
const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE; //sets the flags for no resize / no move
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
/// <summary>
/// Displays the passed form using the parameters set in the base ShowMessage class
/// </summary>
/// <param name="frm">A Windows Form object</param>
/// <example><code>ShowMessage.ShowTopmost(new myForm());</code></example>
public static void ShowTopmost(Form frm)
{
ShowWindow(frm.Handle, SW_SHOWNORMAL); //shows the form
SetWindowPos(frm.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS); //sets the form position as topmost, centered
}
}
Then I simply call
ShowMessage.ShowTopmost(new frmMessage());
I'm not saying it's the only way or the right way, but it a way to do it.
It's generally bad form to prevent the user from moving the window. The user should be able to have the window wherever he wants. Preventing resizing is one thing, preventing moving is another. I'm not aware of any C# native way of doing this, but you can probably hook down into Win32 to prevent the window from moving.
You might be able to use the Move event of the form and set the form back to the starting position. You would have to capture and store (in memory) the starting position.
We are developing a layout manager in WPF that has viewports which can be moved/resized/etc by a user. Viewports are normally filled with data (pictures/movies/etc) via providers that are under our control in the layout manager. My job is to examine if its also possible to host any external Windows app (i.e. notepad, calc, adobe reader, etc) in a viewport. I encounter a number of problems.
Most resources point to using the HwndHost class. I am experimenting with this walkthrough from Microsoft itself: http://msdn.microsoft.com/en-us/library/ms752055.aspx
I've adapted this so the list box is replaced with the windows handle from the external application. Can anybody help me out with these questions:
The walkthrough adds an extra static sub window in which the ListBox is placed. I don't think I need that for external apps. If I ommit it, I have to make the external app a child window (using Get/SetWindowLong from user32.dll to set GWL_STYLE as WS_CHILD). But if I do that, the menu bar of the app dissapears (because of the WS_CHILD style) and it no longer receives input.
If I do use the sub window, and make the external app a child of that things work reasonably, but sometimes the external app does not paint ok.
Also, I need the child window to resize to the viewport. Is this possible?
When the exernal app spawns a child window (i.e. Notepad->Help->About), this window is not hosted by the HwndHost (and thus can be moved outside the viewport). Is there any way I can prevent that?
Since I need no further interaction between the external application and the layout manager, am I right in assuming I do not need to catch and forward messages? (the walkthrough adds a HwndSourceHook to the sub window to catch selection changes in the listbox).
When you run the (unmodified) example VS2010 and close the window, VS2010 does not see that the program ended. If you break-all, you end up in assembly without source. Something smelly is going on, but I cannot find it.
The walkthrough itself seems to be very sloppy coded, but I have not found any better documentation on this subject. Any other examples?
Another approach is not to use HwndHost but WindowsFormHost as discussed here. It works (and is much simpler!) but I do not have control over the size of the application? Also, WinFormHost is not really meant for this?
Thanks for any pointers in the right direction.
Well... if the question had been posed like 20 years ago, one would have answer, "Sure, look at 'OLE'!", here is a link to what is "Object Linking and Embedding":
http://en.wikipedia.org/wiki/Object_Linking_and_Embedding
If you read this article, you will see the number of interfaces this spec defined, not because its author thought it was fun, but because it's technically difficult to achieve in the general cases
It's actually still supported by some apps (mostly Microsoft ones, as Microsoft was almost the only sponsor of OLE...)
You can embed these apps using something called DSOFramer (see links here on SO: MS KB311765 and DsoFramer are missing from MS site), a component that allows you to host OLE server (ie: external apps running as another process)
visually inside an application. It's some kind of a big hack Microsoft let out a few years ago, that is not supported anymore to the point that the binaries are quite difficult to find!
It (may) still works for simple OLE servers, but I think I read somewhere it does not even work for new Microsoft applications such as Word 2010.
So, you can use DSOFramer for application that support it. You can try it.
For others applications, well, today, in the modern world we live in, you don't host applications, ran in external process, you host components, and they are in general supposed to run inprocess.
That's why you will have great difficulties to do what you want to do in general. One problem you will face (and not the least with recent versions of Windows) is security: how can your process I don't trust can legitimately handle my windows and menus created by my process :-) ?
Still, you can do quite a lot application by application, using various Windows hack.
SetParent is basically the mother of all hacks :-)
Here is a piece of code that extends the sample you point, adding automatic resize, and the removal of the caption box.
It demonstrates how to implicitely remove the control box, the system menu, as an example:
public partial class Window1 : Window
{
private System.Windows.Forms.Panel _panel;
private Process _process;
public Window1()
{
InitializeComponent();
_panel = new System.Windows.Forms.Panel();
windowsFormsHost1.Child = _panel;
}
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32")]
private static extern IntPtr SetParent(IntPtr hWnd, IntPtr hWndParent);
[DllImport("user32")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
private const int SWP_NOZORDER = 0x0004;
private const int SWP_NOACTIVATE = 0x0010;
private const int GWL_STYLE = -16;
private const int WS_CAPTION = 0x00C00000;
private const int WS_THICKFRAME = 0x00040000;
private void button1_Click(object sender, RoutedEventArgs e)
{
button1.Visibility = Visibility.Hidden;
ProcessStartInfo psi = new ProcessStartInfo("notepad.exe");
_process = Process.Start(psi);
_process.WaitForInputIdle();
SetParent(_process.MainWindowHandle, _panel.Handle);
// remove control box
int style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE);
style = style & ~WS_CAPTION & ~WS_THICKFRAME;
SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style);
// resize embedded application & refresh
ResizeEmbeddedApp();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
if (_process != null)
{
_process.Refresh();
_process.Close();
}
}
private void ResizeEmbeddedApp()
{
if (_process == null)
return;
SetWindowPos(_process.MainWindowHandle, IntPtr.Zero, 0, 0, (int)_panel.ClientSize.Width, (int)_panel.ClientSize.Height, SWP_NOZORDER | SWP_NOACTIVATE);
}
protected override Size MeasureOverride(Size availableSize)
{
Size size = base.MeasureOverride(availableSize);
ResizeEmbeddedApp();
return size;
}
}
This is basically all Windows "traditional" hacks. You could also remove item menus you don't like, as explained here: http://support.microsoft.com/kb/110393/en-us (How to Remove Menu Items from a Form's Control-Menu Box).
You can also replace "notepad.exe" by "winword.exe" and it seems to work. But there are limitations to this (keyboard, mouse, focus, etc.).
Good luck!
Simon Mourier's answer is extremely well written. However, when I tried it with a winform app made by myself, it failed.
_process.WaitForInputIdle();
can be replaced by
while (_process.MainWindowHandle==IntPtr.Zero)
{
Thread.Sleep(1);
}
and everything goes smoothly.
Thank you for the great question and all of you for your answers.
After reading the answers in this thread and doing some trial and error myself I ended up with something that works pretty well, but of course some things will need your attention for special cases.
I used the HwndHostEx as base class for my host class, you can find it here: http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/69631#1034035
Example code:
public class NotepadHwndHost : HwndHostEx
{
private Process _process;
protected override HWND BuildWindowOverride(HWND hwndParent)
{
ProcessStartInfo psi = new ProcessStartInfo("notepad.exe");
_process = Process.Start(psi);
_process.WaitForInputIdle();
// The main window handle may be unavailable for a while, just wait for it
while (_process.MainWindowHandle == IntPtr.Zero)
{
Thread.Yield();
}
HWND hwnd = new HWND(_process.MainWindowHandle);
const int GWL_STYLE = -16;
const int BORDER = 0x00800000;
const int DLGFRAME = 0x00400000;
const int WS_CAPTION = BORDER | DLGFRAME;
const int WS_THICKFRAME = 0x00040000;
const int WS_CHILD = 0x40000000;
int style = GetWindowLong(notepadHandle, GWL_STYLE);
style = style & ~WS_CAPTION & ~WS_THICKFRAME; // Removes Caption bar and the sizing border
style |= WS_CHILD; // Must be a child window to be hosted
NativeMethods.SetWindowLong(hwnd, GWL.STYLE, style);
return hwnd;
}
protected override void DestroyWindowOverride(HWND hwnd)
{
_process.CloseMainWindow();
_process.WaitForExit(5000);
if (_process.HasExited == false)
{
_process.Kill();
}
_process.Close();
_process.Dispose();
_process = null;
hwnd.Dispose();
hwnd = null;
}
}
The HWND, NativeMethods and enums comes from the DwayneNeed library as well (Microsoft.DwayneNeed.User32).
Just add the NotepadHwndHost as a child in a WPF window and you should see the notepad window hosted there.
The solution is incredibly involved. Lots of code. Here's a few hints.
First, you are on the right track.
You do have to use the HwndHost and HwndSource thing. If you don't, you'll get visual artifacts. Like flicker. A warning, if you don't use the Host and Source, it will seem like it will work, but it won't in the end -- it will have random little stupid bugs.
Take a look at this for some hints. It's not complete, but it will help you go in the right direction.
http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346
You have to get into Win32 to control a lot of what you are asking about. You do need to catch and forward messages. You do need to control which windows "own" the child windows.
Use Spy++ alot.
I have this running in production and so far so good in a WPF application. Make sure you call SetNativeWindowInWPFWindowAsChild() from UI thread that owns window.
public static bool SetNativeWindowInWPFWindowAsChild(IntPtr hWndNative, Window window)
{
UInt32 dwSyleToRemove = WS_POPUP | WS_CAPTION | WS_THICKFRAME;
UInt32 dwExStyleToRemove = WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE;
UInt32 dwStyle = GetWindowLong(hWndNative, GWL_STYLE);
UInt32 dwExStyle = GetWindowLong(hWndNative, GWL_EXSTYLE);
dwStyle &= ~dwSyleToRemove;
dwExStyle &= ~dwExStyleToRemove;
SetWindowLong(hWndNative, GWL_STYLE, dwStyle | WS_CHILD);
SetWindowLong(hWndNative, GWL_EXSTYLE, dwExStyle);
IntPtr hWndOld = SetParent(hWndNative, new WindowInteropHelper(window).Handle);
if (hWndOld == IntPtr.Zero)
{
System.Diagnostics.Debug.WriteLine("SetParent() Failed -> LAST ERROR: " + Marshal.GetLastWin32Error() + "\n");
}
return hWndOld != IntPtr.Zero;
}
Here is the Native Win32 API I used. (There are extras in here because I size/focus the window after it's set)
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public Int32 left;
public Int32 top;
public Int32 right;
public Int32 bottom;
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
private static extern UInt32 SetWindowLong(IntPtr hWnd, int nIndex, UInt32 dwNewLong);
[DllImport("user32.dll")]
private static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
private static int GWL_STYLE = -16;
private static int GWL_EXSTYLE = -20;
private static UInt32 WS_CHILD = 0x40000000;
private static UInt32 WS_POPUP = 0x80000000;
private static UInt32 WS_CAPTION = 0x00C00000;
private static UInt32 WS_THICKFRAME = 0x00040000;
private static UInt32 WS_EX_DLGMODALFRAME = 0x00000001;
private static UInt32 WS_EX_WINDOWEDGE = 0x00000100;
private static UInt32 WS_EX_CLIENTEDGE = 0x00000200;
private static UInt32 WS_EX_STATICEDGE = 0x00020000;
[Flags]
private enum SetWindowPosFlags : uint
{
SWP_ASYNCWINDOWPOS = 0x4000,
SWP_DEFERERASE = 0x2000,
SWP_DRAWFRAME = 0x0020,
SWP_FRAMECHANGED = 0x0020,
SWP_HIDEWINDOW = 0x0080,
SWP_NOACTIVATE = 0x0010,
SWP_NOCOPYBITS = 0x0100,
SWP_NOMOVE = 0x0002,
SWP_NOOWNERZORDER = 0x0200,
SWP_NOREDRAW = 0x0008,
SWP_NOREPOSITION = 0x0200,
SWP_NOSENDCHANGING = 0x0400,
SWP_NOSIZE = 0x0001,
SWP_NOZORDER = 0x0004,
SWP_SHOWWINDOW = 0x0040
}
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
private static readonly IntPtr HWND_TOP = new IntPtr(0);
private static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
Check out my answer to: How to run an application inside wpf application?
I managed to get the notepad example working without DwayneNeed jiggery. I just added SetParent() and boom... she works just like the DwayneNeed example.