I have a small problem. I have method which register global hotkey, when I press hotkey, program call Action. In Action method I would like Show window, but don't work it.
This is my code:
MainWindow.cs:
_hotKeyRegistrator.Add(Modifier.Ctrl, Keys.A, () => {Show();}
HotkeyRegistrator.cs:
public class HotkeyRegistrator
{
private HwndSource _source;
private readonly WindowInteropHelper _windowInteropHelper;
private const int HotkeyId = 9000;
private const int WmHotkey = 0x0312;
private List<HotKey> _hotKeys;
public HotkeyRegistrator(Window window)
{
_windowInteropHelper = new WindowInteropHelper(window);
_source = HwndSource.FromHwnd(_windowInteropHelper.Handle);
_source?.AddHook(HwndHook);
_hotKeys = new List<HotKey>();
}
[DllImport("User32.dll")]
private static extern bool RegisterHotKey([In] IntPtr hWnd, [In] int id, [In] uint fsModifiers, [In] uint vk);
[DllImport("User32.dll")]
private static extern bool UnregisterHotKey([In] IntPtr hWnd, [In] int id);
public void Add(Modifiers modifier, Keys key, Action action)
=> _hotKeys.Add(new HotKey(HotkeyId + _hotKeys.Count, modifier, key, action));
public void Register()
{
foreach (var hotKey in _hotKeys)
{
if (!RegisterHotKey(_windowInteropHelper.Handle, hotKey.Id, hotKey.Modifier, hotKey.Key))
{
throw new Exception("Cannot register hotkey");
}
}
}
public void UnRegisterAll()
{
_source.RemoveHook(HwndHook);
_source = null;
foreach (var hotKey in _hotKeys)
{
UnregisterHotKey(_windowInteropHelper.Handle, hotKey.Id);
}
_hotKeys = null;
}
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WmHotkey)
_hotKeys.FirstOrDefault(x => x.Id == wParam.ToInt32())?.Action.Invoke();
return IntPtr.Zero;
}
}
Hotkey.cs:
public class HotKey
{
public int Id { get; set; }
public uint Modifier { get; }
public uint Key { get; }
public Action Action { get; }
public HotKey(int id, Modifiers modifier, Keys key, Action action)
{
Id = id;
Modifier = (uint)modifier;
Key = (uint)key;
Action = action;
}
}
Action method is called using Invoke method. When I press CTRL + A, call action and Show method, but window dont open.
Thanks for advices.
Problem solved. My window has set WindowState="Minimized" attribute on startup. When I would like call Show() after press global shortcut, code must be this:
_hotKeyRegistrator.Add(Modifier.Ctrl, Keys.A, () => {
WindowState = WindowState.Normal;
Show();
});
Related
I have a SaveFileDialog in my WPF Project.
If the SaveFileDialog is closed via the red 'x' in the corner, I want the dialog to close and the user to be returned to the main application.
However, if the SaveFileDialog is 'canceled', I want the entire application to close.
The only issue I am having is knowing if the user has pressed the red x or the cancel button. They both evaluate to false in the code below.
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Text file (*.txt)|*.txt";
var dlg = saveFileDialog.ShowDialog();
if (dlg == true)
{
//When the user presses save
File.WriteAllLines(saveFileDialog.FileName, rowList);
ExitApp();
}
else if (dlg == false)
{
//This occurs when red x or cancel is pressed.
}
Is there any way to distinguish between the two in WPF?
The answer is short and simple:
"no"
See Microsoft documentation here
It is not so simple, but you can do it by using hooks and the SetWindowsHookEx method.
First of all we need a class for registering and unregistering our hook:
public class WinHooker
{
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);
private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
private HookProc hookDelegate;
private int hookHandle;
private bool wasClosedButtonPressed;
private static WinHooker instance;
private WinHooker()
{
}
public static WinHooker Instance
{
get
{
if (instance == null)
{
instance = new WinHooker();
}
return instance;
}
}
public bool WasClosedButtonPressed
{
get
{
return wasClosedButtonPressed;
}
}
public void Register()
{
wasClosedButtonPressed = false;
hookDelegate = this.HookProcHandler;
hookHandle = SetWindowsHookEx(5,
hookDelegate,
IntPtr.Zero,
AppDomain.GetCurrentThreadId());
}
public void Unregister()
{
UnhookWindowsHookEx(hookHandle);
hookHandle = 0;
hookDelegate = null;
}
private int HookProcHandler(int nCode, int wParam, IntPtr lParam)
{
if (nCode >= 0)
{
if (nCode == 8 && wParam == 0xF060)
{
wasClosedButtonPressed = true;
}
}
return CallNextHookEx(hookHandle, nCode, wParam, lParam);
}
}
As you can see I used 5 as the first parameter for the SetWindowsHookEx, since it correspond to the WH_CBT value (refer to the SetWindowsHookEx page). Moreover the values in the method HookProcHandler (i.e. nCode == 8 && wParam == 0xF060) can be retrived from here.
Now let's use our class in the code:
SaveFileDialog saveFileDialog = new SaveFileDialog();
WinHooker.Instance.Register();
saveFileDialog.ShowDialog();
WinHooker.Instance.Unregister();
if (WinHooker.Instance.WasClosedButtonPressed)
{
MessageBox.Show("Oh my God! What have you done??");
}
As you can understand WasClosedButtonPressed is set to true just if the user closes the dialog by clicking on the red 'x' in the corner. Otherwise it is false.
I hope it can help you.
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 want to use in my wpf aplication notify icon (with .dll library in project http://www.codeproject.com/Articles/36468/WPF-NotifyIcon).
But I don't know, how to show my window (after minimize to tray) by double click in tray icon.
I declared new command
namespace MyBasicFlyffKeystroke
{
class ShowWindowCommand : ICommand
{
public void Execute(object parameter)
{
Window1 window = new Window1();
window.Show();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
}
And I used it in my window1.xaml file:
<tb:TaskbarIcon x:Name="notifyIcon" IconSource="icon.ico" ToolTipText="MyBasicFlyffKeystroke"
DoubleClickCommand="{StaticResource ShowWindow}">
</tb:TaskbarIcon>
and
<Grid.Resources>
<my:ShowWindowCommand x:Key="ShowWindow" />
</Grid.Resources>
But after double clicking open new instance with Window1... Is any metod here?
Best regards,
Dagna
Try add an event handler for window messages
Command
namespace MyBasicFlyffKeystroke
{
class ShowWindowCommand : ICommand
{
public void Execute(object parameter)
{
// Broadcast isn't a good idea but work...
NativeMethods.PostMessage((IntPtr)NativeMethods.HWND_BROADCAST, NativeMethods.WM_SHOWME, IntPtr.Zero, IntPtr.Zero);
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
}
In Window1
protected override void OnSourceInitialized(EventArgs e) {
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
if (msg == NativeMethods.WM_SHOWME) {
WindowState = WindowState.Normal;
}
return IntPtr.Zero;
}
And in NativeMethods (UPDATED)
public static readonly int HWND_BROADCAST = 0xffff;
public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
[DllImport("user32.dll")]
public static extern int RegisterWindowMessage(string message);
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
Application.Current.Window1.Show();
this worked for me
I'm using WPF's OpenFileDialog, and I'm looking for a way to make sure it is centered in the parent window when shown. It seems to be missing obvious properties like StartupPosition that might enable this.
Does anybody know the secret?
Update: It seems that the first time I open it, it does appear in the center of the parent, but if I move it, it then remembers its position, and doesn't open centered on subsequent occassions.
here is the code of a generic class that allows to play with "sub dialogs" like this one:
public class SubDialogManager : IDisposable
{
public SubDialogManager(Window window, Action<IntPtr> enterIdleAction)
:this(new WindowInteropHelper(window).Handle, enterIdleAction)
{
}
public SubDialogManager(IntPtr hwnd, Action<IntPtr> enterIdleAction)
{
if (enterIdleAction == null)
throw new ArgumentNullException("enterIdleAction");
EnterIdleAction = enterIdleAction;
Source = HwndSource.FromHwnd(hwnd);
Source.AddHook(WindowMessageHandler);
}
protected HwndSource Source { get; private set; }
protected Action<IntPtr> EnterIdleAction { get; private set; }
void IDisposable.Dispose()
{
if (Source != null)
{
Source.RemoveHook(WindowMessageHandler);
Source = null;
}
}
private const int WM_ENTERIDLE = 0x0121;
protected virtual IntPtr WindowMessageHandler(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_ENTERIDLE)
{
EnterIdleAction(lParam);
}
return IntPtr.Zero;
}
}
And this is how you would use it in a standard WPF app. Here I just copy the parent window size, but I'll let you do the center math :-)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
bool computed = false; // do this only once
int x = (int)Left;
int y = (int)Top;
int w = (int)Width;
int h = (int)Height;
using (SubDialogManager center = new SubDialogManager(this, ptr => { if (!computed) { SetWindowPos(ptr, IntPtr.Zero, x, y, w, h, 0); computed= true; } }))
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.ShowDialog(this);
}
}
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int flags);
}
CommonDialog in WPF does not inherit from window class, so it does not have StartupPosition property.
Check this blog post for one solution: OpenFileDialog in .NET on Vista
In short, it wraps dialog in a window and then shows it.
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);