c# exe encapsulation in wpf not work - c#

I need to encapsulate exe on my wpf application.
My wpf application is very large and with many UserControls.
To do this, i've start the exe from my code, then get the handle and used the "setParent" to "bind" the exe to my application, but the only effect is to show the drop down menu of the exe, but not the main page. For example: i've tried to embedded notepad, but appear only the drop down menu when I click in the area (note that not appear the main menu bar).
var procInfo = new System.Diagnostics.ProcessStartInfo(this.exeName);
procInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(this.exeName);
// Start the process
_childp = System.Diagnostics.Process.Start(procInfo);
// Wait for process to be created and enter idle condition
_childp.WaitForInputIdle();
// Get the main handle
_appWin = _childp.MainWindowHandle;
// Get main window handle
var helper = new WindowInteropHelper(Window.GetWindow(this.AppContainer));
// Incapsulate
SetWindowLongA(_appWin, -20, 0x00000040 | 0x00000008);
SetParent(_appWin, helper.Handle);
Note that I've tried this piece of code in other c# application and work fine!
I think there is a problem of redraw/update the viewport.
In which way can i force this redraw of the external exe inside my application?
Can you help me, even to found an alternative solution to embedded the exe? Thanks
I've tried the solution to run the exe in a separate tab (here), but even this solution not work.
Can I resolve this with a "SendMessage" ???
Can you suggest me a test to do?
I ask you one thing: help me!!!

The below works for me, can provide you with example project if necessary. The missing piece seems to be that either you have a z index issue OR your initial window placement in your desktop co-oridinates is such that is it outside your 'outer window'.
this will bring it the the from and make it FILL your window:
SetWindowPos(_appWin, default(IntPtr), 0, 0, (int)Application.Current.MainWindow.Width, (int)Application.Current.MainWindow.Height, SetWindowPosFlags.FrameChanged);
The default(IntPtr) is for the ZIndex and says 'bring to front'
You can then make that smaller by passing in the offsets from your containing control, ie if this.grid was what you wanted notepad to appear over:
var desiredPos = this.grid.TranslatePoint(new Point(0, 0), Window.GetWindow(this.grid));
SetWindowPos(_appWin, default(IntPtr),
(int)desiredPos.X, (int)desiredPos.Y,
(int)this.grid.ActualWidth, (int)this.grid.ActualHeight, SetWindowPosFlags.FrameChanged);

Using AllowsTransparency="False" instead AllowsTransparency="True" inside the WPF of the Window I've been able to solve partially the problem
Now I've embedded the external exe (for example: "notepad.exe") using this approach (WindowsFormHost approach):
System.Windows.Forms.Panel _pnlSched = new System.Windows.Forms.Panel();
System.Windows.Forms.Integration.WindowsFormsHost windowsFormsHost1 =
new System.Windows.Forms.Integration.WindowsFormsHost();
windowsFormsHost1.Child = _pnlSched;
_grid.Children.Add(windowsFormsHost1);
ProcessStartInfo psi = new ProcessStartInfo(#"notepad.exe");
psi.WindowStyle = ProcessWindowStyle.Normal;
Process PR = Process.Start(psi);
PR.WaitForInputIdle();
SetParent(PR.MainWindowHandle, _pnlSched.Handle);
Now the new problem may be the Z-order of the user control. In fact, when another user contol move above the "notepad", it is below and not above...
enter image description here
Note that also the background of the WindowsFormHost not respect the 'z-order':
enter image description here
any suggestion is welcome
Thanks

Related

How can I force a redraw of a process whoʼs parent is a panel-control?

I've been tasked with creating an application launcher that hosts both Winforms and WPF applications and via a slightly different methodology Web applications. The user is presented with a list of applications they can launch and these are "captured" upon launch and are lodged inside a panel on a form by using SetParent to make the panel the parent of the processes MainWindowHandle. This bit appears to work well and the applications when launched are captured and displayed in the given panel.
What Iʼm having a particular issue with is that not all applications that are captured are happy to initially draw themselves in the panel. It seems to be isolated to those applications that are WPF based, but thatʼs not guaranteed.
Effectively what happens is that if a WPF application is launched, it is captured and moved to the panel, and the panel will remain blank until I click the panel at which point the application will happily repaint itself. From this point forward the application seems to be happy enough repainting itself as required without intervention.
Basically Iʼm now at my wits ends and have tried the following User32 native and .NET methods;
Invalidate (on the form, on the panel, on the tab control that hosts both of these)
Update
Refresh
SendMessage with a plethora of parameters, including attempting the, "you shouldnʼt do this" WM_PAINT.
RedrawWindow with the UpdateNow and Invalidate flags.
None of the above makes any visible difference and itʼs only when I physically click the panel or move the window that the contained application will behave and repaint itself.
Has anyone else produced anything similar and has a solution to the redraw/repaint issue? Iʼve scoured the entire Google/Bing/Duck Duck Go spheres trying to hunt down an answer but to no avail.
Hopefully one of you out there has an answer.
The following code represents the bulk of the feature in that it starts a process and captures the handle for the main window of the process and sets itsʼ parent to a panel control on a bog-standard WinForms window. I should probably point out that the bog-standard WinForms window is itself “hosted” inside an application using the (EasyTabs) Library. Which, I believe, is not causing any problems.
Example:
// Try to acquire a the process.
ProcessStartInfo startInfo = new ProcessStartInfo(path);
try
{
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.WindowStyle = ProcessWindowStyle.Normal;
Process.StartInfo = startInfo;
Process.EnableRaisingEvents = true;
Process.Exited += TabManagerContainerForm_ProcessExited;
Process.Start();
if (Process != null)
{
// Wait until the process has created a main window or exited.
while (Process.MainWindowHandle == IntPtr.Zero && !Process.HasExited)
{
Thread.Sleep(100);
Process.Refresh();
}
if (!Process.HasExited) // We have acquired a MainWindowHandle
{
// Capture the Process's main window and show it inside the applicationPanel panel control.
SetParent(Process.MainWindowHandle, applicationPanel.Handle);
// Change the captured Process's window to one without the standard chrome. Itʼs provided by our tabbed application.
SetWindowLong(Process.MainWindowHandle, (int)WindowLongFlags.GWL_STYLE, (int)WindowStyles.WS_VISIBLE);
}
else // Process has exited.
{
if (Process.MainWindowHandle == IntPtr.Zero) Log.Information("{0} failed to execute.", Process.ProcessName);
throw new FailedProcessException(string.Format("{0} failed to execute.", path));
}
}
else
{
throw new Exception(string.Format("Invalid path: {0}", path));
}
}
catch (Exception ex) when (!(ex is FailedProcessException)) // Catch everything but FailedProcessExceptions. FPEs are simply passed up the chain.
{
Log.Error(ex.Message);
throw;
}
Summary:
The problem seems to happen when attaching an application to a Panel in a tab that is not the currently visible tab.
Solution:
Add WS_CHILD flag to the call to SetWindowLong:
SetWindowLong(Process.MainWindowHandle, (int)WindowLongFlags.GWL_STYLE, (int)(WindowStyles.WS_VISIBLE | WindowStyles.WS_CHILD));
Details:
I tried reproducing the example in the question (without using EasyTabs). I used a simple Form with a single Panel. With a button press I call a simple WPF app and attach it to the Panel. It works OK, it renders immediately. The only problem I found was the position of the WPF window which was random. I fixed it with calling SetWindowPos (using pinvoke) like this:
SetWindowPos(Process.MainWindowHandle, IntPtr.Zero, 0, 0, 0, 0, SetWindowPosFlags.IgnoreResize);
Than, I tried using a TabControl with two tabs, each containing a Panel and two buttons, each one when pressed, launches a different WPF app to one of the Panels. I found that when a tab (TabPage) is not the visible tab, the problem occurs - the launched application is not visible until the Panel is clicked. I solved this problem by adding WS_CHILD flag to the call to SetWindowLong. I'm not sure why but it works...
My code:
// Capture the Process's main window and show it inside the applicationPanel panel control.
SetParent(Process.MainWindowHandle, applicationPanel.Handle);
// Change the captured Process's window to one without the standard chrome. Itʼs provided by our tabbed application.
SetWindowLong(Process.MainWindowHandle, (int)WindowLongFlags.GWL_STYLE, (int)(WindowStyles.WS_VISIBLE | WindowStyles.WS_CHILD));
// Change the Process's window position to the top-left corner of the panel
SetWindowPos(Process.MainWindowHandle, IntPtr.Zero, 0, 0, 0, 0, SetWindowPosFlags.IgnoreResize);

Is this a way to show a modeless dialog from a dockable pane using WPF?

I'm building a Revit plugin. It consists of a dockable pane that (among other elements) has a button. I want to open a new, separate window when a user clicks this button.
At the moment, i create a new Window, but i don't know if that's the right way to go, because now i see two Revit icons on a task bar. I do not have experience as Revit user, i'm new to Revit development, so i'm not sure if this should be the case (two icons) and as silly as it sounds, i do not have admin rights to install random addins and get a feeling of expected user experience.
I create a Window using the following code:
ParametersMissingValueWindow parametersMissingValueWindow = new ParametersMissingValueWindow();
parametersMissingValueWindow.Show();
Based on the understanding of a dockable pane that i have, i think i do not want to create another dockable pane, but just a simple modeless dialog. I wasn't able to find any examples using WPF. Hence, any information whether this is the way to go or help on how to achieve this is highly appreciated.
The Show method takes an optional parent window argument. Specify the Revit main window as the parent window, and your modeless dialogue will be recognised as belonging to the running Revit process. It is accessible from the MainWindowHandle property.
var MyWindow = new MyWindow();
HwndSource hwndSource = HwndSource.FromHwnd(UIApplication.MainWindowHandle);
Window wnd = hwndSource.RootVisual as Window;
if (wnd != null)
{
MyWindow.Owner = wnd;
//MyWindow.ShowInTaskbar = false;
MyWindow.Show();
}
It's not necessary to assign a value to ShowInTaskbar property, but it actually achieves what i wanted to do from the beginning (have only one program open in taskbar), so i left it as part of the solution, but commentted out.
Big thanks to Jeremy Tammik for pointing out the parent property.
You can use WPF to setup a window to use in revit.
MyWPF menu = new menu();
System.Windows.Window wind = new System.Windows.Window();
wind.ShowDialog(); //--> the window shows up and make stuff for revit
if you need the menu to be a dockable one check this source.
Perhaps is not up to date and you will need to adapt the code to the new api.

Coded UI tests not correctly finding my control

I'm busy running a proof of concept on what should be a very basic coded UI test.
My application is Winforms, I have a form that allows you to log in to the application.
Here 2 controls exist called _textUsername and _textPassword respectively.
To simplify the whole thing, I want playback to be able to double click the username text field (_textUsername).
However, during playback the _textPassword is selected.
I've tried to adjust the search criteria to include the control name , but then it fails to find the control at all and fails.
My question is simple: I have 2 controls on my form : _textUsername and _textPassword, UI coded tests seems to always find the _textPassword, how can I get it to find the other text box instead?
Try manually coding the controls. You can use the UI Test Builder to find the search properties. inspect.exe is also useful. Sometimes the properties aren't what you expect.
// Controls
WinWindow logonWindow = new WinWindow();
WinEdit _textPassword = new WinEdit(logonWindow);
WinEdit _textUsername = new WinEdit(logonWindow);
// Add search properties and configurations
logonWindow.SearchProperties[WinWindow.PropertyNames.Name] = "Main Window Name";
logonWindow.SearchConfigurations.Add(SearchConfiguration.AlwaysSearch);
_textPassword.SearchProperties[WinEdit.PropertyNames.Name] = "Password";
_textPassword.SearchConfigurations.Add(SearchConfiguration.AlwaysSearch);
_textUsername.SearchProperties[WinEdit.PropertyNames.Name] = "Username";
_textUsername.SearchConfigurations.Add(SearchConfiguration.AlwaysSearch);
// Identify each control
logonWindow.DrawHighlight();
_textPassword.DrawHighlight();
_textUsername.DrawHighlight();
This turned out to be wrong versions in DevExpress between the client application and the test runner code.

How do I make a tray-icon-only C# application in MonoMac (no dock icon)?

I am trying to create an application that will have a tray icon only, and not appear in the taskbar. (similar to Dropbox) I need to create both Windows and Mac version of the application, so I tried using MonoMac to create the Mac front-end.
What is the best way to create a tray-only application in MonoMac?
All the resources I have found say to do one of two things:
Add <key>LSUIElement</key><string>1</string> to the Info.plist file.
Add the following code to the FinishedLaunching event in the AppDelegate class: NSApplication.SharedApplication.ActivationPolicy = NSApplicationActivationPolicy.Accessory;
I have tried all combinations of these two, but it seems that as soon as I try to instantiate a C# System.Timers.Timer, the icon reappears in the dock at the bottom of the screen. Am I missing something about how OSX handles background applications?
What am I doing wrong? Is there a better way to make a background application that has an upper tray icon but no bottom dock icon in OSX?
(This is very similar to this SO question, but that question was from a couple years ago and was never fully answered, so I'm hoping there might be a more complete answer out there.)
Here's the code I have so far:
public partial class AppDelegate : NSApplicationDelegate
{
MyServiceObject currentServiceObject;
public AppDelegate () { }
public override void FinishedLaunching (NSObject notification)
{
// Construct menu that will be displayed when tray icon is clicked
var notifyMenu = new NSMenu();
var exitMenuItem = new NSMenuItem("Quit My Application",
(a,b) => { System.Environment.Exit(0); }); // Just add 'Quit' command
notifyMenu.AddItem(exitMenuItem);
// Display tray icon in upper-right-hand corner of the screen
var sItem = NSStatusBar.SystemStatusBar.CreateStatusItem(30);
sItem.Menu = notifyMenu;
sItem.Image = NSImage.FromStream(System.IO.File.OpenRead(
NSBundle.MainBundle.ResourcePath + #"/notify-icon.icns"));
sItem.HighlightMode = true;
// Remove the system tray icon from upper-right hand corner of the screen
// (works without adjusting the LSUIElement setting in Info.plist)
NSApplication.SharedApplication.ActivationPolicy =
NSApplicationActivationPolicy.Accessory;
// Start running the program -- If I comment out then no dock icon appears
currentServiceObject = new MyServiceObject();
}
}
I found the problem, and it wasn't related to the application settings at all. Evidently, there are some operations that MacOS does not allow an 'Agent applications' to perform. As soon as one of those methods is called, the application is forced to appear in the dock. The code that was tripping up my application was a call to:
System.Windows.Forms.Cursor.Position.ToString()
Removing that line, and replacing it with the following MonoMac method allowed the application to remain hidden:
NSEvent.CurrentMouseLocation.ToString()
I was able to get this working by setting the value of "Application is agent (UIElement)" key to 1 in the info.plist file. Even though it should be a BOOL value, MonoDevelop makes it a string, but setting it to 1 seems to work. You can also set an empty string the for the "Icon file" but it's not necessary.

How do i set this window to topmost?

How do i set my window above all other? I need a bad but noticeable msg box that closes on its own.
Msg is a dummy form which is empty. All i want is its title.
The problems with the code is the window isnt created 0,0 (its just whereever windows feels like putting it). The width is correct but i notice if i click firefox or another app window my app doesnt pop up. I know it is being shown bc i can see it in the taskbar at the bottom for a brief second. So the bugs so far
Doesnt go topmost if i click another app
Isnt 0,0
How do i fix this?
{
var msg = new Msg();
msg.Text = (has ? "*" : "+") + args[0];
msg.TopMost = true;
msg.Width = 2000;
msg.Top = 0;
msg.Left = 0;
msg.Show();
System.Threading.Thread.Sleep(1000);
msg.Close();
}
Sounds like TopMost doesn't always do it; here's an answer to a similar question showing how to hook into Win32 for the call: Form top most?
Update: just read the rest of the answer; it might only fail running in Debug mode within Visual Studio (where your app is actually executed with vshost.exe, rather than running independently).

Categories