【问题标题】:Loading a WPF Window without showing it加载 WPF 窗口而不显示它
【发布时间】:2010-11-26 19:24:57
【问题描述】:

我通过 PInvoking RegisterHotKey() 创建了一个全局热键来显示一个窗口。但要做到这一点,我需要那个窗口的HWND,它在窗口加载之前不存在,这意味着第一次显示。但我不想在设置热键之前显示窗口。有没有办法为用户不可见的窗口创建HWND

【问题讨论】:

    标签: wpf window hwnd


    【解决方案1】:

    如果您的目标是 .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();
    

    一旦您想显示实际窗口,您就可以设置内容、大小并将样式改回普通窗口。

    【讨论】:

    • 是的,这行得通,谢谢。甚至没有必要设置 WindowState。另外,我在 XAML 中设置了 Window 的内容,但这并不重要。另一件事是 WindowStartupLocation=CenterScreen 不能以这种方式正常工作,但很容易修复。
    • 删除了 WindowState 设置器...感谢您告诉我。
    • 我也会添加ResizeMode = ResizeMode.NoResize,因为它会删除调整大小的窗口边框。
    • 如果你有这个设置,不要忘记设置 MinWidth/Height。
    • 可能还想添加“Visibility = Visibility.Hidden”(取自stackoverflow.com/a/6691090/997940
    【解决方案2】:

    您还可以将窗口更改为所谓的仅消息窗口。由于此窗口类型不支持图形元素,因此永远不会显示。基本上它归结为调用:

        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 上)。因此,我提供了这个替代选项。

    【讨论】:

    • 这似乎是唯一的 100% 解决方案。
    • 感谢这个伟大的提示。它真的对我有帮助,因为这里的所有其他解决方案都会引起一些不太好的副作用。但遗憾的是,你的也是如此。我有一个 MetroWindow(使用 Fluent Ribbon Suite)。之后窗口有一个典型的窗口边框,这些 MetroWindows 通常不可见......知道如何解决这个问题吗?
    • 完美。它只需要: ShowActivated = false;在可见性之后,因为没有它会闪烁。
    • 正如@SharpShade 提到的,这似乎改变了窗口样式。它看起来像一个 Win 95 窗口。
    【解决方案3】:

    这是一个肮脏的黑客,但它应该可以工作,并且没有改变不透明度的缺点:

    • WindowStartupLocation 设置为Manual
    • TopLeft 属性设置到屏幕外的某处
    • ShowInTaskbar设置为false,这样用户就不会意识到有一个新窗口
    • ShowHide 窗口

    您现在应该能够检索 HWND

    编辑:另一个选项,可能更好:将 ShowInTaskBar 设置为 false 并将 WindowState 设置为 Minimized,然后显示它:它根本不可见

    【讨论】:

    • 使用另一个选项,我可以在屏幕的左下角看到最小化的窗口。但第一个看起来很有希望。
    • @svick:您使用的是哪个操作系统?在 Windows 7 上,最小化的窗口不可见
    • 啊,是的,我记得这个行为......它似乎在 Win7(或者可能是 Vista)中发生了变化
    【解决方案4】:

    我已经发布了该问题的答案,但我刚刚找到了更好的解决方案。

    如果您只需要确保创建了 HWND,而不实际显示窗口,则可以这样做:

        public void InitHwnd()
        {
            var helper = new WindowInteropHelper(this);
            helper.EnsureHandle();
        }
    

    (实际上问题发布时EnsureHandle方法不可用,它是在.NET 4.0中引入的)

    【讨论】:

    • 这可能现在应该是公认的答案,还是我应该更新我的答案以包括这个?不确定框架版本差异的受人尊敬的做法是什么。
    • 你能告诉我如何调用这个功能吗?我需要调用 [Window w = new Window()] 来初始化对象,但在这一行本身它显示了窗口,甚至在调用 w.Show() 之前!
    【解决方案5】:

    我从来没有尝试过你正在做的事情,但是如果你需要显示窗口来获取 HWND,但不想显示它,请将 Window Opacity 设置为 0 . 这也将防止发生任何命中测试。然后,您可以在 Window 上使用公共方法将 Opacity 更改为 100,以使其可见。

    【讨论】:

    • 不幸的是,要使 Opacity 设置生效,必须将 AllowsTransparency 设置为 true,这反过来又会强制 WindowStyle 为 WindowStyle.None,这不是我想要的。另外,AllowsTransparency 在窗口显示后无法更改,因此我无法在之后将其重新设置。
    【解决方案6】:

    我对 WPF 一无所知,但是您能否使用其他方式(例如 PInvoke)创建一个message only window 来接收 WM_HOTKEY 消息?如果是,那么一旦您收到 WM_HOTKEY,您就可以从那里启动 WPF 窗口。

    【讨论】:

    • +1,如果你想这样做,你可以在另一个线程上使用 Winforms 窗口。
    【解决方案7】:

    我注意到初始化窗口时发生的最后一件事是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();
        }
    }
    

    你已经完成了。

    即使我错误地假设状态更改是在加载窗口时完成的最后一件事,您仍然可以更改为任何其他事件,这并不重要。

    【讨论】:

      【解决方案8】:

      隐藏模式下启动 Wpf 窗口:

      WpfWindow w = new WpfWindow() { Visibility = Visibility.Hidden };
      

      可见模式下启动 Wpf 窗口:

      WpfWindow w = new WpfWindow();
      w.Show();
      

      【讨论】:

        【解决方案9】:

        WindowInteropHelper 类应该允许您获取 WPF 窗口的 HWND。

        MyWindow win = new MyWindow();
        WindowInteropHelper helper = new WindowInteropHelper(win);
        
        IntPtr hwnd = helper.Handle;
        

        MSDN Documentation

        【讨论】:

        • 这就是我正在做的,但是这样一来,Window 还没有 HWND,所以 helper.Handle 为 0,这不是我需要的。
        【解决方案10】:

        与将不透明度设置为 0 类似的另一个选项是将大小设置为 0 并将位置设置为离开屏幕。这不需要 AllowsTransparency = True。

        还请记住,一旦您显示它一次,您就可以隐藏它并仍然获得 hwnd。

        【讨论】:

          【解决方案11】:

          使窗口大小为 0 x 0 px,将 ShowInTaskBar 设置为 false,显示它,然后在需要时调整它的大小。

          【讨论】:

            【解决方案12】:

            我已经创建了显示不可见窗口的扩展方法,接下来的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;
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-11-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多