【发布时间】:2010-11-26 19:24:57
【问题描述】:
我通过 PInvoking RegisterHotKey() 创建了一个全局热键来显示一个窗口。但要做到这一点,我需要那个窗口的HWND,它在窗口加载之前不存在,这意味着第一次显示。但我不想在设置热键之前显示窗口。有没有办法为用户不可见的窗口创建HWND?
【问题讨论】:
我通过 PInvoking RegisterHotKey() 创建了一个全局热键来显示一个窗口。但要做到这一点,我需要那个窗口的HWND,它在窗口加载之前不存在,这意味着第一次显示。但我不想在设置热键之前显示窗口。有没有办法为用户不可见的窗口创建HWND?
【问题讨论】:
如果您的目标是 .NET 4.0,您可以使用 WindowInteropHelper 上提供的新 EnsureHandle 方法:
public void InitHwnd()
{
var helper = new WindowInteropHelper(this);
helper.EnsureHandle();
}
(感谢 Thomas Levesque 提供pointing this out.)
如果您的目标是旧版本的 .NET Framework,最简单的方法是显示窗口以到达 HWND,同时设置一些属性以确保窗口不可见并且不会窃取焦点:
var window = new Window() //make sure the window is invisible
{
Width = 0,
Height = 0,
WindowStyle = WindowStyle.None,
ShowInTaskbar = false,
ShowActivated = false
};
window.Show();
一旦您想显示实际窗口,您就可以设置内容、大小并将样式改回普通窗口。
【讨论】:
ResizeMode = ResizeMode.NoResize,因为它会删除调整大小的窗口边框。
您还可以将窗口更改为所谓的仅消息窗口。由于此窗口类型不支持图形元素,因此永远不会显示。基本上它归结为调用:
SetParent(hwnd, (IntPtr)HWND_MESSAGE);
要么创建一个始终隐藏的专用消息窗口,要么使用真正的 GUI 窗口并在您想要显示它时将其更改回普通窗口。有关更完整的示例,请参见下面的代码。
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hwnd, IntPtr hwndNewParent);
private const int HWND_MESSAGE = -3;
private IntPtr hwnd;
private IntPtr oldParent;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
if (hwndSource != null)
{
hwnd = hwndSource.Handle;
oldParent = SetParent(hwnd, (IntPtr)HWND_MESSAGE);
Visibility = Visibility.Hidden;
}
}
private void OpenWindowMenuItem_Click(object sender, RoutedEventArgs e)
{
SetParent(hwnd, oldParent);
Show();
Activate();
}
对我来说,将宽度、高度设置为零和样式设置为无的解决方案没有奏效,因为它仍然显示了一个小窗口,并且在 0x0 窗口周围似乎有一个令人讨厌的边框阴影(经过测试在 Windows 7 上)。因此,我提供了这个替代选项。
【讨论】:
这是一个肮脏的黑客,但它应该可以工作,并且没有改变不透明度的缺点:
WindowStartupLocation 设置为Manual
Top 和Left 属性设置到屏幕外的某处ShowInTaskbar设置为false,这样用户就不会意识到有一个新窗口Show 和 Hide 窗口您现在应该能够检索 HWND
编辑:另一个选项,可能更好:将 ShowInTaskBar 设置为 false 并将 WindowState 设置为 Minimized,然后显示它:它根本不可见
【讨论】:
我已经发布了该问题的答案,但我刚刚找到了更好的解决方案。
如果您只需要确保创建了 HWND,而不实际显示窗口,则可以这样做:
public void InitHwnd()
{
var helper = new WindowInteropHelper(this);
helper.EnsureHandle();
}
(实际上问题发布时EnsureHandle方法不可用,它是在.NET 4.0中引入的)
【讨论】:
我从来没有尝试过你正在做的事情,但是如果你需要显示窗口来获取 HWND,但不想显示它,请将 Window Opacity 设置为 0 . 这也将防止发生任何命中测试。然后,您可以在 Window 上使用公共方法将 Opacity 更改为 100,以使其可见。
【讨论】:
我对 WPF 一无所知,但是您能否使用其他方式(例如 PInvoke)创建一个message only window 来接收 WM_HOTKEY 消息?如果是,那么一旦您收到 WM_HOTKEY,您就可以从那里启动 WPF 窗口。
【讨论】:
我注意到初始化窗口时发生的最后一件事是WindowState 的更改,如果它与正常情况不同的话。因此,您实际上可以使用它:
public void InitializeWindow(Window window) {
window.Top = Int32.MinValue;
window.Left = Int32.MinValue;
window.Width = 0;
window.Height = 0;
window.ShowActivated = false;
window.ShowInTaskbar = false;
window.Opacity = 0;
window.StateChanged += OnBackgroundStateChanged;
window.WindowStyle = WindowStyle.None;
}
public void ShowWindow(Window window) {
window.Show();
window.WindowState = WindowState.Maximized;
}
protected bool isStateChangeFirst = true;
protected void OnBackgroundStateChanged(object sender, EventArgs e) {
if (isStateChangeFirst) {
isStateChangeFirst = false;
window.Top = 300;
window.Left = 200;
window.Width = 760;
window.Height = 400;
window.WindowState = WindowState.Normal;
window.ShowInTaskbar = true;
window.Opacity = 1;
window.Activate();
}
}
这对我来说很公平。它不需要使用任何句柄和东西,更重要的是,不需要为窗口提供自定义类。这对于动态加载的 XAML 非常有用。如果您正在制作全屏应用程序,这也是一个很好的方法。您甚至不需要将其状态更改回正常或设置适当的宽度和高度。就去吧
protected bool isStateChangeFirst = true;
protected void OnBackgroundStateChanged(object sender, EventArgs e) {
if (isStateChangeFirst) {
isStateChangeFirst = false;
window.ShowInTaskbar = true;
window.Opacity = 1;
window.Activate();
}
}
你已经完成了。
即使我错误地假设状态更改是在加载窗口时完成的最后一件事,您仍然可以更改为任何其他事件,这并不重要。
【讨论】:
在隐藏模式下启动 Wpf 窗口:
WpfWindow w = new WpfWindow() { Visibility = Visibility.Hidden };
在可见模式下启动 Wpf 窗口:
WpfWindow w = new WpfWindow();
w.Show();
【讨论】:
WindowInteropHelper 类应该允许您获取 WPF 窗口的 HWND。
MyWindow win = new MyWindow();
WindowInteropHelper helper = new WindowInteropHelper(win);
IntPtr hwnd = helper.Handle;
【讨论】:
与将不透明度设置为 0 类似的另一个选项是将大小设置为 0 并将位置设置为离开屏幕。这不需要 AllowsTransparency = True。
还请记住,一旦您显示它一次,您就可以隐藏它并仍然获得 hwnd。
【讨论】:
使窗口大小为 0 x 0 px,将 ShowInTaskBar 设置为 false,显示它,然后在需要时调整它的大小。
【讨论】:
我已经创建了显示不可见窗口的扩展方法,接下来的Show 调用会正常运行。
public static class WindowHelper
{
public static void ShowInvisible(this Window window)
{
// saving original settings
bool needToShowInTaskbar = window.ShowInTaskbar;
WindowState initialWindowState = window.WindowState;
// making window invisible
window.ShowInTaskbar = false;
window.WindowState = WindowState.Minimized;
// showing and hiding window
window.Show();
window.Hide();
// restoring original settings
window.ShowInTaskbar = needToShowInTaskbar;
window.WindowState = initialWindowState;
}
}
【讨论】: