Why does my Modal WPF dialog slip behind MS Word - c#

I have a MS Word Application Add-in written with VSTO. It contains a button used to create new Letter documents. When pressed a document is instantiated, a WPF dialog is displayed to capture information and then the information is inserted into the document.
On rare occasions, the WPF dialog slips behind MS Word. I then have to kill the Winword.exe process because the dialog is Modal.
I use the following code for my WPF dialog. The OfficeDialog sub class is used to make the dialog look like a MS-Word dialog.
var view = new LetterDetailsView(ViewModel);
view.ShowDialog();
public class OfficeDialog : Window
{
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hwnd, int index);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int x, int y, int width, int height, uint flags);
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);
const int GWL_EXSTYLE = -20;
const int WS_EX_DLGMODALFRAME = 0x0001;
const int SWP_NOSIZE = 0x0001;
const int SWP_NOMOVE = 0x0002;
const int SWP_NOZORDER = 0x0004;
const int SWP_FRAMECHANGED = 0x0020;
const uint WM_SETICON = 0x0080;
const int ICON_SMALL = 0;
const int ICON_BIG = 1;
public OfficeDialog()
{
this.ShowInTaskbar = false;
}
public new void ShowDialog()
{
try
{
var helper = new WindowInteropHelper(this);
using (Process currentProcess = Process.GetCurrentProcess())
helper.Owner = currentProcess.MainWindowHandle;
base.ShowDialog();
}
catch (System.ComponentModel.Win32Exception ex)
{
Message.LogWarning(ex);
var helper = new WindowInteropHelper(this);
using (Process currentProcess = Process.GetCurrentProcess())
helper.Owner = currentProcess.MainWindowHandle;
base.ShowDialog();
}
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
RemoveIcon(this);
HideMinimizeAndMaximizeButtons(this);
}
public static void HideMinimizeAndMaximizeButtons(Window window)
{
const int GWL_STYLE = -16;
IntPtr hwnd = new WindowInteropHelper(window).Handle;
long value = GetWindowLong(hwnd, GWL_STYLE);
SetWindowLong(hwnd, GWL_STYLE, (int)(value & -131073 & -65537));
}
public static void RemoveIcon(Window w)
{
// Get this window's handle
IntPtr hwnd = new WindowInteropHelper(w).Handle;
// Change the extended window style to not show a window icon
int extendedStyle = OfficeDialog.GetWindowLong(hwnd, GWL_EXSTYLE);
OfficeDialog.SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_DLGMODALFRAME);
// reset the icon, both calls important
OfficeDialog.SendMessage(hwnd, WM_SETICON, (IntPtr)ICON_SMALL, IntPtr.Zero);
OfficeDialog.SendMessage(hwnd, WM_SETICON, (IntPtr)ICON_BIG, IntPtr.Zero);
// Update the window's non-client area to reflect the changes
OfficeDialog.SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
static void SetCentering(Window win, IntPtr ownerHandle)
{
bool isWindow = IsWindow(ownerHandle);
if (!isWindow) //Don't try and centre the window if the ownerHandle is invalid. To resolve issue with invalid window handle error
{
//Message.LogInfo(string.Format("ownerHandle IsWindow: {0}", isWindow));
return;
}
//Show in center of owner if win form.
if (ownerHandle.ToInt32() != 0)
{
var helper = new WindowInteropHelper(win);
helper.Owner = ownerHandle;
win.WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
else
win.WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsWindow(IntPtr hWnd);
}

A modal dialog not being on top is the result of an incorrectly set owner. You already set the owner to the MainWindowHandle of the current process; however, in particular with multiple Word documents open, this might not be what you want.
I'd suggest to rely on the following property (introduced with Word 2013):
document.ActiveWindow.HWnd;
Apart from that there should not be the need to kill the Word process. It should be sufficient to minimize all windows (e.g. by pressing Windows Key + M)

Related

Start a program on different screen

I've already checked out:
SetWindowPos not working on Form.Show()
Launch an application and send it to second monitor?
However, none of these solutions seem to work for me. I want to open an external program on a different monitor.
This is my current code:
public const int SWP_NOSIZE = 0x0001;
public const int SWP_NOZORDER = 0x0004;
public const int SWP_SHOWWINDOW = 0x0040;
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
[DllImport("user32.dll")]
public static extern bool UpdateWindow(IntPtr hWnd);
Process application = new Process();
application.StartInfo.UseShellExecute = false;
application.StartInfo.FileName = ".......";
if (application.Start())
{
Rectangle monitor = Screen.AllScreens[1].Bounds; // for monitor no 2
SetWindowPos(
application.MainWindowHandle,
IntPtr.Zero,
monitor.Left,
monitor.Top,
monitor.Width,
monitor.Height,
SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
UpdateWindow(application.MainWindowHandle); // tried even with application.Handle
}
First of all, you don't need UpdateWindow, calling SetWindowPos is probably enough. You just need to make sure that the window handle is created (because the process is being started). Simply add the following line before calling SetWindowPos:
application.WaitForInputIdle();
If WaitForInputIdle() doesn't work for you, you might try something like:
while (application.MainWindowHandle == IntPtr.Zero)
{
await Task.Delay(100);
}
The following code works fine for me:
Process application = new Process();
application.StartInfo.UseShellExecute = false;
application.StartInfo.FileName = "notepad.exe";
if (application.Start())
{
application.WaitForInputIdle();
/* Optional
while (application.MainWindowHandle == IntPtr.Zero)
{
await Task.Delay(100);
} */
Rectangle monitor = Screen.AllScreens[1].Bounds; // for monitor no 2
SetWindowPos(
application.MainWindowHandle,
IntPtr.Zero,
monitor.Left,
monitor.Top,
monitor.Width,
monitor.Height,
SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
}
Note that this will only set the position of the window, not its size though. If you want the size to be changed as well, you'll need to remove the SWP_NOSIZE flag.

Autoresize called process within a panel C#

I have the following code that opens notepad within a tabControl panel, this works when I start the form maximized.
this.WindowState = FormWindowState.Maximized
Tab Opening:
TabPage tp = new TabPage("notepad");
Panel tb = new Panel();
tb.Dock = DockStyle.Fill;
tp.Controls.Add(tb);
myTab.TabPages.Add(tp);
The problem is when I launch the form not maximized, opens Notepad then resize the form, the following happens (see picture link) --- notepad doesn't stretch out.
After Opening Notepad within Panel the form is maximized
Any suggestions? Thanks!
[DllImport("user32.dll", SetLastError = true)]
private static extern uint SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("USER32.DLL")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("USER32.DLL")]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
private const int GWL_STYLE = (-16);
public static int WS_BORDER = 0x00800000;
public static int WS_CAPTION = WS_BORDER;
public static void loadProcess()
{
Process p = Process.Start("Notepad");
p.WaitForInputIdle();
p.Refresh();
int WS_VISIBLE = GetWindowLong(p.MainWindowHandle, GWL_STYLE);
SetWindowLong(p.MainWindowHandle, GWL_STYLE, (WS_VISIBLE & ~WS_CAPTION));
SetParent(p.MainWindowHandle, panel1.Handle);
ShowWindow(p, SW_SHOWMAXIMIZED);
}
[DllImport("user32.dll")]
private static extern
bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
private const int SW_SHOWMAXIMIZED = 3;
private static bool ShowWindow(Process _Process, int nCmdShow)
{
return ShowWindowAsync(_Process.MainWindowHandle, nCmdShow);
}
After few trials, I was able to resize it automatically.
On my loadProcess method, I have stored the process in a hashtable for reference later.
Process p = Process.Start("Notepad");
p.WaitForInputIdle();
sessions.Add(tabName, p);
on tabControl1_Selecting event:
String name = myProcess.SelectedTab.Name;
Process pHandle = (Process) sessions[name];
SetForegroundWindow(pHandle.MainWindowHandle);
SetFocus(pHandle.MainWindowHandle);
MoveWindow(pHandle.MainWindowHandle, 0, 0, this.Width, this.Height, true);
I've added a Form1_Resize event so it will resize accordingly.
String name = myProcess.SelectedTab.Name;
Process pHandle = (Process) sessions[name];
MoveWindow(pHandle.MainWindowHandle, 0, 0, this.Width, this.Height, true);

How do I embed tabtip.exe inside windows

I'm trying to embed the osk in a wpf window or a user control and I've found the code below and it's working for notepad but for tabtip.exe, it's saying that it doesn't have a graphical interface??
WaitForInputIdle failed. This could be because the process does not have a graphical interface.
I tried letting it sleep for awhile instead of calling waitForInputIdle method but it throws another exception:
Process has exited, so the requested information is not available.
But in my task manager, I can still see TabTip.exe running.
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private System.Windows.Forms.Panel _panel;
private Process _process;
public MainWindow()
{
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;
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;
}
private void button1_Click_1(object sender, RoutedEventArgs e)
{
button1.Visibility = Visibility.Hidden;
ProcessStartInfo psi = new ProcessStartInfo("C:\\Program Files\\Common Files\\microsoft shared\\ink\\TabTip.exe");
_process = Process.Start(psi);
Thread.Sleep(500);
//_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();
}
}
}
Edit: Inspired by rene's comment, I've tried to obtain the window ptr as below and used spy++ to verify that the address that FindWindow gives is pointing to the correct window, but it's still not moving:
IntPtr KeyboardWnd = FindWindow("IPTip_Main_Window", null);
int style = GetWindowLong(KeyboardWnd, GWL_STYLE);
style = style & ~WS_CAPTION & ~WS_THICKFRAME;
SetWindowLong(KeyboardWnd, GWL_STYLE, style);
SetWindowPos(KeyboardWnd, IntPtr.Zero, 0, 0, (int)_panel.ClientSize.Width, (int)_panel.ClientSize.Height, SWP_NOZORDER | SWP_NOACTIVATE);
Edit 2: My first thought was that tab tip couldn't be resized, but then I noticed a behavior when I try to move the window across two different screen, it'll resize to fit the screen size, so I'm sure there must be a way to resize, so I started spy++(x64) to check :
Edit 3: after tinkering abit with user32 api and no progress, I've tried to use a memory scanner to scan for the x and y position of tabtip and change it, however, it's not refreshing until a repaint is triggered, I'm wondering the feasibility going down that path.
Can you try to run your handle code in STA thread? I had a similar issue with native window, which I had resolved using STA thread.
var thread = new Thread(() => {
// Your code here
});
thread.TrySetApartmentState(ApartmentState.STA);
thread.Start();
I had a similar problem, and the reason I had it was that I started a program that needed to be run by an administrator with a non-administrative program, and it would pop up with WaitForInputIdle failed. This could be because the process does not have a graphical interface, so I assume you try starting your program with an administrator

Scrolling Notepad using C# and WIN32

I'm trying to scroll a Notepad window using a C# application. The relevant code block is below, the call to move/size the window works so I know the handle is valid
please can you see what I am missing nothing happens when this is run.
[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,
}
private const int WM_SCROLL = 276; // Horizontal scroll
private const int WM_VSCROLL = 277; // Vertical scroll
private const int SB_LINEUP = 0; // Scrolls one line up
private const int SB_LINELEFT = 0;// Scrolls one cell left
private const int SB_LINEDOWN = 1; // Scrolls one line down
private const int SB_LINERIGHT = 1;// Scrolls one cell right
private const int SB_PAGEUP = 2; // Scrolls one page up
private const int SB_PAGELEFT = 2;// Scrolls one page left
private const int SB_PAGEDOWN = 3; // Scrolls one page down
private const int SB_PAGERIGTH = 3; // Scrolls one page right
private const int SB_PAGETOP = 6; // Scrolls to the upper left
private const int SB_LEFT = 6; // Scrolls to the left
private const int SB_PAGEBOTTOM = 7; // Scrolls to the upper right
private const int SB_RIGHT = 7; // Scrolls to the right
private const int SB_ENDSCROLL = 8; // Ends scroll
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
public void scroll()
{
IntPtr handle = IntPtr.Zero;
Process[] processes = Process.GetProcessesByName("Notepad");
foreach (Process p in processes)
{
handle = p.MainWindowHandle;
Console.WriteLine("Got Handle: " + p.MainWindowTitle);
break;
}
//this is to test I have a valid handle
SetWindowPos(handle, new IntPtr(0), 10, 10, 1024, 350, SetWindowPosFlags.SWP_DRAWFRAME);
SendMessage(handle, WM_VSCROLL, (IntPtr)SB_LINEDOWN, IntPtr.Zero);
SendMessage(handle, WM_VSCROLL, (IntPtr)SB_PAGEDOWN, IntPtr.Zero);
}
This fails because you are sending the WM_VSCROLL message to the main window. You need to send the message to Notepad's edit control, which is the window with the scrollbar.
You can enumerate Notepad's child windows using EnumChildWindows. The child with class "Edit" is the one you want.

How to remove 3d border (sunken) from MDIClient component in MDI parent form?

I am developing WinForms MDI app in VS2010 (.NET 4.0) and I just hate 3D border in MDI parent form.
So any ideas on how to remove it (make it flat or just no border it all) ?
I know this is an old post but I have spent some time and pain working the 3D border stuff out (because I needed it too) from fragments across the internet including:
Elements from Jacob Slusser's page at codeproject.com (Accessed 1st Aug'12)
So here goes:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace MDITest
{
public static class MDIClientSupport
{
[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", ExactSpelling = true)]
private static extern int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
private const int GWL_EXSTYLE = -20;
private const int WS_EX_CLIENTEDGE = 0x200;
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOZORDER = 0x0004;
private const uint SWP_NOREDRAW = 0x0008;
private const uint SWP_NOACTIVATE = 0x0010;
private const uint SWP_FRAMECHANGED = 0x0020;
private const uint SWP_SHOWWINDOW = 0x0040;
private const uint SWP_HIDEWINDOW = 0x0080;
private const uint SWP_NOCOPYBITS = 0x0100;
private const uint SWP_NOOWNERZORDER = 0x0200;
private const uint SWP_NOSENDCHANGING = 0x0400;
public static bool SetBevel(this Form form, bool show)
{
foreach (Control c in form.Controls)
{
MdiClient client = c as MdiClient;
if (client != null)
{
int windowLong = GetWindowLong(c.Handle, GWL_EXSTYLE);
if (show)
{
windowLong |= WS_EX_CLIENTEDGE;
}
else
{
windowLong &= ~WS_EX_CLIENTEDGE;
}
SetWindowLong(c.Handle, GWL_EXSTYLE, windowLong);
// Update the non-client area.
SetWindowPos(client.Handle, IntPtr.Zero, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
return true;
}
}
return false;
}
}
}
In the form load event call:
form.SetBevel(false);
Don't forget to change the namespace and remember this is an extension method but it could be changed to be just a method call in another class or in you MDI parent form.
If you would prefer not to import external libraries there is also following cheat which repositions/resizes the mdi container control.
protected override void OnLoad(EventArgs e)
{
var mdiclient = this.Controls.OfType<MdiClient>().Single();
this.SuspendLayout();
mdiclient.SuspendLayout();
var hdiff = mdiclient.Size.Width - mdiclient.ClientSize.Width;
var vdiff = mdiclient.Size.Height - mdiclient.ClientSize.Height;
var size = new Size(mdiclient.Width + hdiff, mdiclient.Height + vdiff);
var location = new Point(mdiclient.Left - (hdiff / 2), mdiclient.Top - (vdiff / 2));
mdiclient.Dock = DockStyle.None;
mdiclient.Size = size;
mdiclient.Location = location;
mdiclient.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom;
mdiclient.ResumeLayout(true);
this.ResumeLayout(true);
base.OnLoad(e);
}
Try changing the FormBorderStyle property to FixedSingle

Categories