Attach window to window of another process - c#

My WPF application has more than one window, I want to attach some of these windows to a window of another process. My problem is that once I attach my window it becomes invisible.
I'm trying this with the following code:
public static bool setParentWindow(IntPtr hWndChild, IntPtr hWndNewParent)
{
IntPtr previousParent = SetParent(hWndChild, hWndNewParent);
return (previousParent == null ? false : true);
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
setParentWindow(myWindowHwnd, newParentHwnd);
So, the above code successfully attaches the window, but unfortunately makes it invisible.
My reason for doing this is that I'm trying to extend an application by building "Widgets" for it, my widgets will hook in and show the user extra information.
Both windows have the following styles: WS_OVERLAPPEDWINDOW, WS_OVERLAPPED, WS_VISIBLE, WS_CLIPSIBLINGS, WS_CLIPCHILDREN.

I found that I could do this without even using the setParent call. I used HwndSource class as follows:
MyWindow window = new MyWindow();
window.ShowActivated = true;
HwndSourceParameters parameters = new HwndSourceParameters();
parameters.WindowStyle = 0x10000000 | 0x40000000;
parameters.SetPosition(0, 0);
parameters.SetSize((int)window.Width, (int)window.Height);
parameters.ParentWindow = newParent;
parameters.UsesPerPixelOpacity = true;
HwndSource src = new HwndSource(parameters);
src.CompositionTarget.BackgroundColor = Colors.Transparent;
src.RootVisual = (Visual)window.Content;
This is working great now without any problems.

I'm not sure what you need to do with overlapped windows, but from MSDN:
For compatibility reasons, SetParent does not modify the WS_CHILD or WS_POPUP window styles of the window whose parent is being changed. Therefore, if hWndNewParent is NULL, you should also clear the WS_CHILD bit and set the WS_POPUP style after calling SetParent. Conversely, if hWndNewParent is not NULL and the window was previously a child of the desktop, you should clear the WS_POPUP style and set the WS_CHILD style before calling SetParent.

private void TryToAttach(IntPtr ownerHandle)
{
if(ownerHandle == IntPtr.Zero)
{
return;
}
try
{
var helper = new WindowInteropHelper(window) { Owner = ownerHandle };
}
catch(Exception e)
{
Logger.Error(e, "Could not attach window.");
}
}

Related

Set foreground of a child window (?) in Visual Studio

I want to set the foreground of a window in Visual Studio.
What i've tried:
When i have 2 windows "docked" side by side i can't set the foreground of a window under the mouse in VS.
I'm using GetCursorPos and WindowFromPoint which usually works for standard windows. I also tried to use EnumChildWindows from pinvoke.net (sample code 2 -https://www.pinvoke.net/default.aspx/user32.enumchildwindows) but it returns 0 when i pass the WindowFromPoint or MainWindowHandle of the process.
I assume it's not a typical child window technically or something else that i don't understand?
public static IntPtr GetProcessMainWindowHandle()
{
Process process = null;
if (GetCursorPos(out Point point))
{
IntPtr hWnd = WindowFromPoint(point);
GetWindowThreadProcessId(hWnd, out uint pid);
process = Process.GetProcessById((int)pid);
}
return process.MainWindowHandle;
}
public static IntPtr GetHandle()
{
IntPtr hWnd = IntPtr.Zero;
if (GetCursorPos(out Point point))
{
hWnd = WindowFromPoint(point);
}
return hWnd;
}
As mentioned in the comments from Lars, it could be a WPF window.
I can see that i can only get the handle for the parent window as described for WPF behaviour but i get still null with the following code. Maybe i have something wrong? (Any hint would be great.)
public static void GetWPFObject(IntPtr handle)
{
HwndSource source = HwndSource.FromHwnd(handle) as HwndSource;
}

Cannot force my window to be on top

I need my window to be on top of another window. That "other" window (application) is from different developer. I do not have source codes for it. I can only use Spy++ to get information about it.
I am using Windows 7.
I tryed several things but they did not work.
This is what I tryed so far:
1) Timer + BringWindowToTop
2) I changed Owner of my window
IntPtr handle = User32.FindWindow("Vega Prime", "Vega Prime");
NativeWindow win = new NativeWindow();
win.AssignHandle(handle);
ChildForm form = new ChildForm();
form.Show(win);
When I am saying that it does not work I mean this:
1) at first everything looks alright: my window is on top
2) then I click on window (Vega Prime) which is below mine
3) my window disappears
4) I click on place where my window should be and it reappears (!!!!!!)
What is that? How is it possible at all?
UPDATE:
I spent some time trying to find solution to my problem.
Here is what I found:
TopMost window going behind non-TopMost fullscreen window sometimes
https://social.msdn.microsoft.com/Forums/vstudio/en-US/92e66584-6cb8-4976-9531-eab3b9a129e3/mfc-window-with-wsextopmost-hidden-by-full-screen-window?forum=vcgeneral
I am pretty sure that my problem has something to do with "Full Screen Issue" at Windows 7 (sometimes, when not top most window becomes full screen it forces top most windows to become hidden). That explains described above weird behaviour, right?
I realized this example that seems to do the trick:
public partial class Form1 : Form
{
IntPtr hWndToStayOver = User32.FindWindow("Vega Prime", "Vega Prime");
private System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
public Form1()
{
InitializeComponent();
this.Load += new EventHandler(this.Form1_Load);
this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
}
private void Form1_Load(object sender, EventArgs e)
{
timer1.Start(); // Routine starts now that I'm sure my Form exists
}
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
timer1.Stop(); // Stop routine (my Form doesn't exist anymore)
}
private bool AmIAboveOtherWindow()
{
IntPtr tmpHwnd = User32.GetNextWindow(hWndToStayOver, User32.GetNextWindowCmd.GW_HWNDPREV);
while (tmpHwnd != (IntPtr)0)
{
if (tmpHwnd == this.Handle)
return true;
tmpHwnd = User32.GetNextWindow(tmpHwnd, User32.GetNextWindowCmd.GW_HWNDPREV);
}
return false;
}
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Stop();
if (!AmIAboveOtherWindow()) // Check if I am behind the target window
{
User32.SetWindowPos(this.Handle, hWndToStayOver, 0, 0, 0, 0, // Move my Form behind the target
User32.SetWindowPosFlags.SWP_NOMOVE |
User32.SetWindowPosFlags.SWP_NOSIZE |
User32.SetWindowPosFlags.SWP_SHOWWINDOW |
User32.SetWindowPosFlags.SWP_NOACTIVATE |
User32.SetWindowPosFlags.SWP_ASYNCWINDOWPOS);
User32.SetWindowPos(hWndToStayOver, this.Handle, 0, 0, 0, 0, // Move target behind my Form
User32.SetWindowPosFlags.SWP_NOMOVE |
User32.SetWindowPosFlags.SWP_NOSIZE |
User32.SetWindowPosFlags.SWP_SHOWWINDOW |
User32.SetWindowPosFlags.SWP_NOACTIVATE |
User32.SetWindowPosFlags.SWP_ASYNCWINDOWPOS);
}
timer1.Start();
}
}
User32 class
public class User32
{
[Flags]
public 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,
}
public enum GetNextWindowCmd : uint
{
GW_HWNDNEXT = 2,
GW_HWNDPREV = 3,
}
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindow")]
public static extern IntPtr GetNextWindow(IntPtr hWnd, GetNextWindowCmd uCmd);
}
Clearly, you have to add to this code some check on the target handle: this example works only if the target window exists when the Form is loaded and if it isn't closed until the end
Make sure you set the owner of your window to the other window.
That will make sure that your window is always on top of the owner.
Here's how to import the appr. native functions, just change the 'Get' to 'Set' everywhere.
Then you can invoke it like this:
SetWindowLong(handle_of_owned_window, -8, handle_of_owner_window);
Hint #1: it's easy to set the ownership b/w 2 Form instances via the Form.Owner property, however you don't have access to one of them Forms.
Hint #2: in order to access the handle of a window, it needs to be shown at least once first.
It turned out that "other" application (which I was trying to merge with my application) is using Full-Screen Exclusive Mode. That explains why my topmost window disappeared from view and never reappeared (unless I switch off fullscreen mode with mouse click).
Details about Full-Screen Exclusive Mode are here:
https://docs.oracle.com/javase/tutorial/extra/fullscreen/exclusivemode.html
Basic idea is that Full-Screen Exclusive Mode "allows the programmer to suspend the windowing system so that drawing can be done directly to the screen". I believe that means that during Full-Screen Exclusive Mode (some experts call it "real full screen") OS ignores different windows (to save resources).
The only solution in situation like that is to configure "other" application to disable full screen mode.
It helped in my case - I studied documentation and found a place in configuration file where to set full screen mode to false.

HwndHost for Windows Form - Win32 / WinForm Interoperability

I need to host a Win32 window in a Windows Form control. I had the same problem with WPF and I solved this problem by using the HwndHost control.
I followed this tutorial:
Walkthrough: Hosting a Win32 Control in WPF
Is there any equivalent control in Windows Form?
I have a Panel and its Handle property, I use this handle as parent of my Direct2D render target window:
// Register the window class.
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
// Redraws the entire window if a movement or size adjustment changes the height
// or the width of the client area.
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = Core::WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(LONG_PTR);
wcex.hInstance = HINST_THISCOMPONENT;
wcex.hbrBackground = nullptr;
wcex.lpszMenuName = nullptr;
wcex.hCursor = LoadCursor(nullptr, IDI_APPLICATION);
wcex.lpszClassName = L"SVGCoreClassName";
RegisterClassEx(&wcex);
hwnd = CreateWindow(
L"SVGCoreClassName", // class name
L"", // window name
WS_CHILD | WS_VISIBLE, // style
CW_USEDEFAULT, // x
CW_USEDEFAULT, // y
CW_USEDEFAULT, // width
CW_USEDEFAULT, // height
parent, // parent window
nullptr, // window menu
HINST_THISCOMPONENT, // instance of the module to be associated with the window
this); // pointer passed to the WM_CREATE message
...
hr = d2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hwnd, size, D2D1_PRESENT_OPTIONS_IMMEDIATELY),
&renderTarget);
The code works if I use the HwndHost parent handle with WPF. But it does not render anything if I use the System.Windows.Forms.Panel Handle.
What you had to do in WPF to create a valid D2D1 target window is not necessary in Winforms. It was necessary in WPF because controls are not windows themselves and don't have a Handle property, the one that CreateHwndRenderTarget() needs.
In Winforms the Panel class is already a perfectly good render target, you can use its Handle property to tell D2D1 where to render. You just need to tell it to stop painting itself. Add a new class to your project and paste this code:
using System;
using System.Windows.Forms;
class D2D1RenderTarget : Control {
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
if (!this.DesignMode) {
this.SetStyle(ControlStyles.UserPaint, false);
// Initialize D2D1 here, use this.Handle
//...
}
}
}
Compile. Drop the new control onto your form, replacing the existing panel.
Sounds like an MDI application. Something like this?
[DllImport("user32.dll")]
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
Form f = new Form();
Button b1 = new Button { Text = "Notepad" };
b1.Click += delegate {
using (var p = Process.Start("notepad.exe")) {
p.WaitForInputIdle();
SetParent(p.MainWindowHandle, f.Handle);
MoveWindow(p.MainWindowHandle, 50, 50, 300, 300, true);
}
};
f.Controls.Add(b1);
Application.Run(f);

Given a window, how can I determine if it is part of a winforms application?

I have handles to the main form of the Winforms application, and the window that I'm trying to check (which may or may not be part of the application). I've tried iterating using GetParent, but it doesn't seem to work.
What I'm essentially trying to do is detect a modal window (such as a MsgBox), get it's controls, and send a button click message if the controls fulfill some requirements (like be a Button).
Now, while I can detect if a modal window is open, and can find the currently focused window, I have no idea if the currently focused window is the modal window that was detected. Essentially, if I open a model window and then open up a completely different program, it tries to find the controls of that external program.
The code is below:
if (pF.Visible && !pF.CanFocus) //Is a Modal Window
{
///TODO: Check if currently active window is a child of the main window
///Gets information of currently active window
string currentActiveWindow = GetActiveWindowTitle();
IntPtr currentActiveHandle = GetActiveWindowHandle();
///Gets 'children' or controls of currently active window
var hwndChild = EnumAllWindows(currentActiveHandle);
///Iterate over all child windows
foreach (IntPtr element in hwndChild) {
const int nChars = 256;
StringBuilder Buff = new StringBuilder(nChars);
IntPtr handle = GetForegroundWindow();
string windowElementType = GetWindowClassName(element);
///Check if the "windows" are buttons
if (GetWindowText(element, Buff, nChars) > 0 && windowElementType=="Button")
{
string windowElement = Buff.ToString();
if (windowElement.ToLower()=="ok")
{
///Send Button click message
const int BM_CLICK = 0x00F5;
SendMessage(element, BM_CLICK, IntPtr.Zero, IntPtr.Zero);
}
}
}
}
A convenience function (C++) to determine whether two windows identified by their HWND belong to the same process would look like this:
bool OwnedBySameProcess(HWND hWnd1, HWND hWnd2) {
if ( ::IsWindow(hWnd1) && ::IsWindow(hWnd2) ) {
DWORD procId1 = 0x0;
DWORD procId2 = 0x0;
::GetWindowThreadProcessId(hWnd1, &procId1);
::GetWindowThreadProcessId(hWnd2, &procId2);
return ( procId1 == procId2 );
}
return false;
}
The GetWindowThreadProcessId is not subject to UIPI (User Interface Privilege Isolation) and will always succeed given valid input. The return values are IDs and do not need to be cleaned up.
Translated to C#:
public class Helper
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd,
out uint lpdwProcessId);
public static bool OwnedBySameProcess(IntPtr hWnd1, IntPtr hWnd2)
{
if ( !IsWindow(hWnd1) )
throw new ArgumentException("hWnd1");
if ( !IsWindow(hWnd2) )
throw new ArgumentException("hWnd2");
uint procId1 = 0;
GetWindowThreadProcessId(hWnd1, out procId1);
uint procId2 = 0;
GetWindowThreadProcessId(hWnd2, out procId2);
return ( procId1 == procId2 );
}
}

How to initially hide the main GUI window with C#

I have a GUI app written with C# that must be hidden off the screen when it starts (instead, it displays a tray icon.) In C++/MFC I'd hide it as such:
void OnWindowPosChanging(WINDOWPOS* lpwndpos)
{
CDialog::OnWindowPosChanging(lpwndpos);
//Prevent dialog from showing
lpwndpos->flags &= ~SWP_SHOWWINDOW;
}
But is there an easier way in C#?
You will need to set the form's Visible and ShowInTaskbar properties to false and then simply use the NotifyIcon class to show an icon in the tray area.
Alternative if this will be the first form your application will open you will need to edit the Application.Run() in your Program.cs file.
Simply replace
Application.Run(new MyForm());
with
MyForm myForm = new MyForm();
Application.Run();
Then in your form's constructor initialize the NotifyIcon object.
NotifyIcon nIcon = new NotifyIcon();
nIcon.Icon = new Icon(#"...");
nIcon.Visible = true;
You do this in Winforms by overriding the SetVisibleCore() method in your form. Some extra work is required, the native window gets created in traditional .NET lazy fashion. The trigger is the first Show() call. So you still have to ensure that this is taken care of. Paste this code into your form:
protected override void SetVisibleCore(bool value) {
if (!this.IsHandleCreated) {
value = false;
this.CreateHandle();
}
base.SetVisibleCore(value);
}
You can now make it visible whenever you are ready to by calling Show() or setting the Visible property to true. Do beware that the Load event doesn't fire until then so be sure to move all initialization code into the constructor, where it belongs.
You can use the ShowInTaskBar and Visible property of the startup form.
I used this to hide a console window, (eventually).
private static class NativeMethods
{
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
public static void SetConsoleWindowVisibility(Boolean argShow)
{
IntPtr hWnd = NativeMethods.FindWindow(null, Console.Title);
if (hWnd != IntPtr.Zero)
{
if (!argShow)
//Hide the window
ShowWindow(hWnd, 0); // 0 = SW_HIDE
else
//Show window again
ShowWindow(hWnd, 1); //1 = SW_SHOWNORMAL
}
}
}
All looks a bit complicated, but's it's basically get a handle to the window and then call ShowWindow with it. As it's a console app, I pass in a command line argument to not hide the window, for debugging and such.
I put this in program.cs, decode the command line args and then just call NativeMethods.SetConsoleWindowVisiblity.
Never did find out why just setting visibility and showintaskbar didn't work. But they definitely didn't

Categories