I am creating a VSTO Plugin for Excel and my first attempt works, but I am not happy with the design. As standard VSTO only handles Windows Forms. I am getting in to WPF now and have found the options for layout and animations make for a much better user experience.
I have now found that I can add a WPF Project to the VSTO Solution and call the forms that way... Excellent!
The problem is when I load a form I do this:
Dim NewForm as New NewForm
NewForm.Show()
This works fine, and the form opens, however if I try to type in a textbox, the form drops behind excel and the text goes in to the active cell in Excel.
If I do:
Dim NewForm as New NewForm
NewForm.ShowDialog()
it works fine. Unfortunately I cannot have the form being modal for my application. How can I get around this?
I use the following class:
https://dl.dropboxusercontent.com/u/62538279/Help/OfficeDialog.cs
You'll notice that the ShowDialog() method is replaced
The class also makes the dialog look like a Word VBA form (something my clients often want)
My dialog.xaml.cs class looks like (and the xaml matches):
public partial class myDialog : OfficeDialog
-- Edit --
Here's the source code. I've been having trouble with it. It occasionally slips behind the application (very rarely)
http://stackoverflow.com/questions/40374059/why-does-my-modal-wpf-dialog-slip-behind-ms-word/40401198#40401198
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
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;
/// <summary>
/// Sometimes get System.ComponentModel.Win32Exception: Invalid window handle
/// I'm pretty sure that this is because Word is shit at handling windows and has an internal memory leak
/// http://stackoverflow.com/questions/222649/winforms-issue-error-creating-window-handle
/// I'm not sure why this error isn't trapped and logged by the try catch below. Somehow it bubbles up to the calling routine..
/// </summary>
public OfficeDialog()
{
this.ShowInTaskbar = false;
//this.Topmost = true;
//Uri uri = new Uri("PresentationFramework.Aero;V3.0.0.0;31bf3856ad364e35;component\\themes/aero.normalcolor.xaml", UriKind.Relative);
//Uri uri = new Uri("PresentationFramework.Classic;V3.0.0.0;31bf3856ad364e35;component\\themes/classic.xaml", UriKind.Relative);
//Resources.MergedDictionaries.Add(Application.LoadComponent(uri) as ResourceDictionary);
//var helper = new WindowInteropHelper(this);
//using (Process currentProcess = Process.GetCurrentProcess())
// helper.Owner = currentProcess.MainWindowHandle;
}
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);
//this.Topmost = true;
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);
//using (Process currentProcess = Process.GetCurrentProcess())
// SetCentering(this, currentProcess.MainWindowHandle);
}
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);
}
Related
Disclaimer: This is not a duplicated post. I googled about the issue. Also read this, this and this SO questions. I tried some of those things but nothing seemed to help.
Consider the following simple example code. It's just an empty ElementHost inside a WinForm (no WPF control inside):
using System.Windows.Forms;
using System.Windows.Forms.Integration;
namespace WindowsFormsApplication15
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ElementHost host = new ElementHost();
host.Dock = DockStyle.Fill;
this.Controls.Add(host);
}
}
}
When you resize the form, you can see two black edges at the form border:
Please, ¿someone could give a working solution over my example to fix this issue?
Try this (same idea as the first link you provided, but better performance):
public class ElementHost2 : ElementHost {
public ElementHost2() {
this.AutoSize = true;
}
public override Size GetPreferredSize(Size proposedSize) {
Form f = this.FindForm();
Size s = f.ClientSize;
return s;
}
private const uint WM_SETREDRAW = 0xB;
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
private const uint NOSIZE = 0x0001;
private const uint NOMOVE = 0x0002;
private const uint NOZORDER = 0x0004;
private const uint NOREDRAW = 0x0008;
private const uint NOACTIVATE = 0x0010;
private const uint DRAWFRAME = 0x0020;
private const uint FRAMECHANGED = 0x0020;
private const uint SHOWWINDOW = 0x0040;
private const uint HIDEWINDOW = 0x0080;
private const uint NOCOPYBITS = 0x0100;
private const uint NOOWNERZORDER = 0x0200;
private const uint NOREPOSITION = 0x0200;
private const uint NOSENDCHANGING = 0x0400;
private const uint DEFERERASE = 0x2000;
private const uint ASYNCWINDOWPOS = 0x4000;
protected override void OnResize(EventArgs e) {
base.OnResize(e);
SendMessage(this.Handle, WM_SETREDRAW, 0, 0);
SendMessage(this.Handle, WM_SETREDRAW, 1, 0);
// forces window to redraw:
SetWindowPos(this.Handle, IntPtr.Zero, 0, 0, 0, 0, NOSIZE | NOMOVE| NOZORDER | NOACTIVATE | SHOWWINDOW);
}
// better performance?
protected override void OnPaintBackground(PaintEventArgs pevent) {
//base.OnPaintBackground(pevent);
}
protected override void OnPaint(PaintEventArgs e) {
//base.OnPaint(e);
}
}
class Form2 : Form {
ElementHost host = new ElementHost2();
public Form2() {
Controls.Add(host);
this.BackColor = Color.Red;
var p = new System.Windows.Controls.DockPanel();
p.Background = System.Windows.Media.Brushes.Red;
host.Child = p;
p.Children.Add(new System.Windows.Controls.TextBox { Width = 100, Height = 20 });
}
}
The issue is not related to ElementHost and Winforms. It's just a WPF issue, and I found the answer in the following SO question:
How to fix the WPF form resize - controls lagging behind and black background?
I have my winform app where I want its main window to be attached to google chrome window.
It would always be on the left side of chrome window. The width is 300px, height is always the same as chrome window. I think I can do this by watching chrome window but I am wondering if there is easier way to do this.
When chrome is maximized, the first 300px of the screen should be occupied by my app and then the rest display chrome window. It is as if there is main window wrapping my app's window and chrome window
Seems like WinEvents is the easiest solution. BrendanMcK's answer on this thread served as the basis: Setting up Hook on Windows messages
MSDN Event Constants
This code will track an open Notepad window. As the Notepad window is moved/resized, the Form will stay pinned to the right side of the Notepad window.
using System;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Text;
namespace WindowTracker {
public class WindowTracker {
delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll")]
static extern bool UnhookWinEvent(IntPtr hWinEventHook);
[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
const uint WINEVENT_OUTOFCONTEXT = 0;
static WinEventDelegate procDelegate = new WinEventDelegate(WinEventProc);
private static IntPtr hhook;
private static Form f = null;
static Process p = null;
public static void HookNotepad(Form f) {
WindowTracker.f = f;
p = Process.GetProcessesByName("Notepad").FirstOrDefault();
if (p == null)
throw new Exception("Notepad is not running.");
f.Show(new SimpleWindow(p.MainWindowHandle));
hhook = SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, IntPtr.Zero, procDelegate, (uint) p.Id, 0, WINEVENT_OUTOFCONTEXT);
}
private class SimpleWindow : System.Windows.Forms.IWin32Window {
IntPtr h = IntPtr.Zero;
public SimpleWindow(IntPtr ptr) {
h = ptr;
}
public IntPtr Handle {
get { return h; }
}
}
public static void UnhookNotepad() {
UnhookWinEvent(hhook);
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int Left;
public int Top;
public int Right;
public int Bottom;
}
static void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) {
if (idObject != 0 || idChild != 0)
return;
RECT r = new RECT();
GetWindowRect(p.MainWindowHandle, out r);
int h = r.Bottom - r.Top;
int x = r.Right - f.Width;
int y = r.Top + ((h - f.Height) / 2);
f.Location = new System.Drawing.Point(x, y);
}
[STAThread]
static void Main() {
Form f = new Form();
WindowTracker.HookNotepad(f);
System.Windows.Forms.Application.Run(f);
WindowTracker.UnhookNotepad();
}
}
}
How can add this button to the title bar in WPF, by it being so used in a lot of applications I thought it would be built in or something, but looks like it isn't. Anyway let me know if you know anything about this.
Thanks.
Edit:
Isn't there anything equivalent to this?
Basically, to have the ? icon in win forms, all you need to do is this:
public Form1()
{
InitializeComponent();
this.HelpButton = true;
this.MaximizeBox = false;
this.MinimizeBox = false;
}
Doesn't WPF have anything like that?
It's simple, just inset this code into your Window class.
This code uses interop to remove the WS_MINIMIZEBOX and WS_MAXIMIZEBOX styles and add the WS_EX_CONTEXTHELP extended style (the question mark will only show up if you remove the minimize and maximize buttons).
EDIT: added click detection on the help button, this is done by hooking into the WndProc using HwndSource.AddHook and listening for a WM_SYSCOMMAND message with wParam of SC_CONTEXTHELP.
When a click is detected this code will show a message box, changing this into an event, routed event or even a command (for MVVM apps) is left as an exercise for the reader.
private const uint WS_EX_CONTEXTHELP = 0x00000400;
private const uint WS_MINIMIZEBOX = 0x00020000;
private const uint WS_MAXIMIZEBOX = 0x00010000;
private const int GWL_STYLE = -16;
private const int GWL_EXSTYLE = -20;
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOMOVE = 0x0002;
private const int SWP_NOZORDER = 0x0004;
private const int SWP_FRAMECHANGED = 0x0020;
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_CONTEXTHELP = 0xF180;
[DllImport("user32.dll")]
private static extern uint GetWindowLong(IntPtr hwnd, int index);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hwnd, int index, uint newStyle);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int x, int y, int width, int height, uint flags);
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
uint styles = GetWindowLong(hwnd, GWL_STYLE);
styles &= 0xFFFFFFFF ^ (WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
SetWindowLong(hwnd, GWL_STYLE, styles);
styles = GetWindowLong(hwnd, GWL_EXSTYLE);
styles |= WS_EX_CONTEXTHELP;
SetWindowLong(hwnd, GWL_EXSTYLE, styles);
SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
((HwndSource)PresentationSource.FromVisual(this)).AddHook(HelpHook);
}
private IntPtr HelpHook(IntPtr hwnd,
int msg,
IntPtr wParam,
IntPtr lParam,
ref bool handled)
{
if (msg == WM_SYSCOMMAND &&
((int)wParam & 0xFFF0) == SC_CONTEXTHELP)
{
MessageBox.Show("help");
handled = true;
}
return IntPtr.Zero;
}
No help buttons come out of the box with WPF. Should'nt be a push to roll your own however.
Following MS guidelines, my WPF application's App constructor includes the following code for proper focus behavior:
HwndSource.DefaultAcquireHwndFocusInMenuMode = false;
Keyboard.DefaultRestoreFocusMode = RestoreFocusMode.None;
As explained in this article, these settings prevent focus stealing.
However, setting DefaultRestoreFocusMode to None has a bad side effect. When using Alt+Tab to leave a WPF application and then return to it, the WPF application doesn't get focus. However, if I don't set DefaultRestoreFocusMode to none, it does get focus as expected. Is there a way to prevent focus stealing but have focus still set when returning to a WPF application via Alt+Tab?
-Craig
I prevent my wpf window from getting focus by doing the below and i can still activate it by using ALT-TAB or clicking on it's taskbar item.
Here you change the window styles on your window so that it has no activate.
var yourWindow = new YourWindowType();
//set the windowstyle to noactivate so the window doesn't get focus
yourWindow.SourceInitialized += (s, e) =>
{
var interopHelper = new WindowInteropHelper(yourWindow);
int exStyle = User32.GetWindowLong(interopHelper.Handle, (int)WindowLongFlags.GWL_EXSTYLE);
User32.SetWindowLong(interopHelper.Handle, (int)WindowLongFlags.GWL_EXSTYLE, exStyle | (int)WindowStylesEx.WS_EX_NOACTIVATE);
//If you have trouble typing into your form's textboxes then do this
ElementHost.EnableModelessKeyboardInterop(yourWindow);
};
This is something i added as an extra precaution, plus it lets you drag your window around if it is borderless:
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
//don't activate the window when you click on it.
case WindowMessage.WM_MOUSEACTIVATE:
handled = true;
return (IntPtr)MouseActivate.MA_NOACTIVATE;
//For Borderless Windows: occurs while dragging. it reports new position before it has been finalized.
//otherwise you wont see the window moving while you're dragging it
case WindowMessage.WM_MOVING:
RECT rect = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
User32.SetWindowPos(new WindowInteropHelper(this).Handle, Hwnd.HWND_TOPMOST,
rect.Left, rect.Top, rect.Width, rect.Height,
SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOSIZE);
break;
}
return IntPtr.Zero;
}
These add a hook so that WndProc is actually called in WPF:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
if (source == null) return;
source.AddHook(WndProc);
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
if (source == null) return;
source.RemoveHook(WndProc);
}
Just an FYI.. this still works even though you don't get focus:
private void WpfPillForm_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.DragMove();
}
Here's the Win32 API declarations so you don't have to look them up:
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left, top, right, bottom;
}
public static class MouseActivate
{
public const int MA_ACTIVATE = 1;
public const int MA_ACTIVATEANDEAT = 2;
public const int MA_NOACTIVATE = 3;
public const int MA_NOACTIVATEANDEAT = 4;
}
public enum WindowLongFlags : int
{
GWL_EXSTYLE = -20,
GWLP_HINSTANCE = -6,
GWLP_HWNDPARENT = -8,
GWL_ID = -12,
GWL_STYLE = -16,
GWL_USERDATA = -21,
GWL_WNDPROC = -4,
DWLP_USER = 0x8,
DWLP_MSGRESULT = 0x0,
DWLP_DLGPROC = 0x4
}
public const int WM_MOVING = 0x0216;
public const uint WS_EX_NOACTIVATE = 0x08000000,
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowLong(IntPtr hwnd, int index);
I have the following code, which adds an 'Always on Top' item to the system context menu as displayed on the window chrome. It works correctly, but I'd like it to display a check mark or similar to indicate if it's been toggled on/off.
Any idea how I can do this?
public RibbonShell()
{
InitializeComponent();
Loaded += (s,e) =>
{
// Get the Handle for the Forms System Menu
var systemMenuHandle = GetSystemMenu(Handle, false);
// Create our new System Menu items just before the Close menu item
InsertMenu(systemMenuHandle, 5, MfByposition | MfSeparator, 0, string.Empty); // <-- Add a menu seperator
InsertMenu(systemMenuHandle, 6, MfByposition, SettingsSysMenuId, "Always on Top");
// Attach our WindowCommandHandler handler to this Window
var source = HwndSource.FromHwnd(Handle);
source.AddHook(WindowCommandHandler);
};
}
#region Win32 API Stuff
// Define the Win32 API methods we are going to use
[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
private static extern bool InsertMenu(IntPtr hMenu, Int32 wPosition, Int32 wFlags, Int32 wIDNewItem, string lpNewItem);
/// Define our Constants we will use
private const int WmSyscommand = 0x112;
private const int MfSeparator = 0x800;
private const int MfByposition = 0x400;
#endregion
// The constants we'll use to identify our custom system menu items
private const int SettingsSysMenuId = 1000;
/// <summary>
/// This is the Win32 Interop Handle for this Window
/// </summary>
public IntPtr Handle
{
get { return new WindowInteropHelper(this).Handle; }
}
private IntPtr WindowCommandHandler(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Check if a System Command has been executed
if (msg == WmSyscommand && wParam.ToInt32() == SettingsSysMenuId)
{
Topmost = !Topmost;
handled = true;
}
return IntPtr.Zero;
}
You need to call CheckMenuItem whenever you change Topmost. See the CheckMenuItem documentaton for details. Here's the P/Invoke signature and constants you'll need:
[DllImport("user32.dll")]
private static extern bool CheckMenuItem(IntPtr hMenu, Int32 uIDCheckItem, Int32 uCheck);
private const int MfChecked = 8;
private const int MfUnchecked = 0;
Now to check the item, just:
CheckMenuItem(systemMenuHandle, SettingsSysMenuId, MfChecked);
and to uncheck:
CheckMenuItem(systemMenuHandle, SettingsSysMenuId, MfUnchecked);