I have a Windows Forms application with a button that's supposed to open an exe file.
The default Windows behavior is that the exe file, once opened, will have its own icon appear in the taskbar, however the requirement is for it not to have the icon, and for the window to appear "nested" inside the icon of the Windows Form application from which it originates, so to hide the location of the exe file from users.
I have looked around for a solution, and my understanding is that in order to achieve this, I would have to use the user32.dll library.
I have found some code online that attempts something similar (no window, no taskbar icon), and I am trying to tweak it so that it fits my needs, but so far I am stuck.
This is what I have:
private int GWL_STYLE = -16;
private int GWL_EXSTYLE = -20;
private int WS_EX_APPWINDOW = 262144;
private UInt32 WS_POPUP = 0x80000000;
private UInt32 WS_CHILD = 0x40000000;
[DllImport("user32.dll")]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
public Process HideProcess(string executablesPath, System.Windows.Forms.Form parentForm) {
Process valueToReturn = null;
if (executablesPath != null && executablesPath.Equals(String.Empty) == false && File.Exists(executablesPath) == true) {
ProcessStartInfo startInfo = new ProcessStartInfo(executablesPath);
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
valueToReturn = Process.Start(startInfo);
int style = GetWindowLong(valueToReturn.MainWindowHandle, GWL_EXSTYLE) & ~WS_EX_APPWINDOW;
SetWindowLong(valueToReturn.MainWindowHandle, GWL_EXSTYLE, style);
SetParent(valueToReturn.MainWindowHandle, parentForm.Handle);
}
return valueToReturn;
}
I have tried several different variations of this method, but I always get close but not exactly where I'd like to be.
The documentation I have found seems a little bit confusing, so I am a little bit stuck and would appreciate some help.
I am using this code (on windows 2003) to remove and resize window:
Process process = Process.GetProcessById(12121);
IntPtr mwh = process.MainWindowHandle;
SetWindowLong(mwh, GWL_STYLE, WS_VISIBLE);
ShowWindowAsync(mwh, 3);
SetWindowPos(mwh, new IntPtr(0), 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
And declarations:
[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("USER32.DLL")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
static readonly int GWL_STYLE = -16;
static readonly int SWP_NOMOVE = 0x2;
static readonly int SWP_NOSIZE = 0x1;
static readonly int SWP_FRAMECHANGED = 0x20;
static readonly int WS_VISIBLE = 0x10000000;
Everything works correct, when I am resizing window associated with process started by me. But when I want to do this with other users windows, then it does nothing.
How to make it works for other users windows?
The behavior is by design in Windows Vista and later, due to the User Interface Privilege Isolation (UIPI). You could solve it if you have access to the source code of the controlled applications.
Read this answer for more: https://stackoverflow.com/a/15445510.
process.MainWindowHandle is a .NET concept, there is no such thing as a main window in native desktop apps and therefore might not always work correctly on other processes. You should check if mwh is IntPtr.Zero, if it is then you need to use EnumWindows + GetWindowThreadProcessId + IsWindowVisible to find the applications window.
Calling SetWindowLong(mwh, GWL_STYLE, WS_VISIBLE); on a window you did not create is not OK. You need to call GetWindowLong first the get the existing style, remove all the styles you don't want and add WS_POPUP. You might want to remove some of the extended styles as well.
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.
Believe me, I have Googled it and expected it to be a fairly easy find - turns out it isn't.
I have my window handle, but no form. How do I do it?
Thanks!
Declare these:
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
private const int GWL_EX_STYLE = -20;
private const int WS_EX_APPWINDOW = 0x00040000, WS_EX_TOOLWINDOW = 0x00000080;
And then use this before the form is shown:
SetWindowLong(handle, GWL_EX_STYLE, (GetWindowLong(handle, GWL_EX_STYLE) | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW);
(change handle to whatever your window handle is stored in)
Set Form's ShowInTaskbar property to false.
I'm trying to launch another application from C# application, is there a way to display this application inside the mainform of my application?
Thanks,
You can start other applications using Process.Start(...):
Process.Start(#"C:\Path\OtherApp.exe");
To embed the application within your form, check out this CodeProject article that demos a technique for hosting other application's windows within your form.
You can try do this via reparenting. See my post on MSDN where I describe this for WPF: Composite "shell" application.
The technique itself would be the same for WinForms. Have a host area in your app. Change the top-level window's style of the target application to WS_CHILD. Call SetParent(), changing the parent of the target window to your host area.
Note that in Win32, only a top-level window has a menu. Thus, changing to WS_CHILD removes the menu.
You can do it in that way :
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);
[DllImport("user32.dll", SetLastError = true)]
static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
[DllImport("user32.dll")]
static extern IntPtr SetActiveWindow(IntPtr hWnd);
private const int GWL_STYLE = (-16);
private const int WS_VISIBLE = 0x10000000;
private const int WS_MAXIMIZE = 0x01000000;
private void Form1_Load(object sender, EventArgs e)
{
this.SuspendLayout();
Process notepad = new Process();
ProcessStartInfo psi = new ProcessStartInfo("notepad.exe");
psi.WindowStyle = ProcessWindowStyle.Normal;
notepad.StartInfo = psi;
notepad.Start();
this.ResumeLayout();
notepad.WaitForInputIdle(3000);
IntPtr old = SetParent(notepad.MainWindowHandle, this.Handle);
SetWindowLong(notepad.MainWindowHandle, GWL_STYLE, WS_VISIBLE + WS_MAXIMIZE);
MoveWindow(notepad.MainWindowHandle, 100, 100, 400, 400, true);
SetActiveWindow(notepad.MainWindowHandle);
SwitchToThisWindow(notepad.MainWindowHandle, true); }
In this way you have Notepad app in your form ;)
In general, it's next to impossible to display any kind of 3rd party application inside of yours.
If target app supports console interface, I would create my own interface for this app that will translate GUI commands to console commands of target app.