在WPF中将窗口置于最前面

242

如何将我的WPF应用程序置于桌面前台?目前我尝试过以下方法:

SwitchToThisWindow(new WindowInteropHelper(Application.Current.MainWindow).Handle, true);

SetWindowPos(new WindowInteropHelper(Application.Current.MainWindow).Handle, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

SetForegroundWindow(new WindowInteropHelper(Application.Current.MainWindow).Handle);

没有完成工作(Marshal.GetLastWin32Error() 显示这些操作成功完成,每个定义的P/Invoke属性都设置了 SetLastError=true)。

如果我创建一个新的空白WPF应用程序,并使用计时器调用 SwitchToThisWindow,它会按预期工作,所以我不确定为什么在我的原始情况下它没有工作。

编辑:我与全局热键一起使用此功能。


你确认了MainWindow是你想要的窗口吗?根据MSDN:MainWindow会自动设置为在AppDomain中实例化的第一个Window对象的引用。 - Todd White
好的想法,但它是应用程序中唯一的窗口。 - Factor Mystic
你能多给一点上下文代码吗? - Todd White
20个回答

346
myWindow.Activate();

尝试将窗口置于前景并激活它。

除非我误解了,否则这应该可以解决问题,除非您希望实现始终置顶的行为。在那种情况下,您需要:

myWindow.TopMost = true;

17
我只是使用了myWindow.Show(),有时窗口没有在最上面。我立即调用了myWindow.Activate(),然后它正常了。 - Bermo
4
有时候在Windows XP上Activate无法正常工作。我建议参考@Matthew Xavier的答案。 - Lex Li
1
第一个答案很好,谢谢!但是使用 Topmost 属性的第二行代码是不良实践,因为它可能会遮盖其他弹出式对话框并产生意外行为。 - Jonathan Perry
3
实际上,可以通过以下代码实现:if (myWindow.WindowState == WindowState.Minimized) myWindow.WindowState = WindowState.Normal; 奇怪的是,它还会保留任何最大化的窗口,并不会将它们恢复到正常状态。 - r41n
2
只在VS调试模式下工作! - CodingNinja
显示剩余5条评论

192

我找到了一个将窗口置于顶部的解决方案,但它表现得像普通窗口:

if (!Window.IsVisible)
{
    Window.Show();
}

if (Window.WindowState == WindowState.Minimized)
{
    Window.WindowState = WindowState.Normal;
}

Window.Activate();
Window.Topmost = true;  // important
Window.Topmost = false; // important
Window.Focus();         // important

2
太好了!如果窗口已经打开但在其他窗口下面,TopMost可以在Windows 7上实现这一神奇功能。 - gsb
1
谢谢 - 修复很简短而且很甜美。 - code4life
3
在我的情况下,Window.Activate() 和 Window.Focus() 就足够了。设置 Window.TopMost 是不必要的。 - virious
9
不要使用Window.Focus()。这会将焦点从用户当前正在输入的文本框中抓取走,这对最终用户来说非常令人沮丧。上述代码在没有它的情况下完全正常工作。 - Contango
Topmost = true; 是我所需要的。谢谢。 - demo.b
显示剩余6条评论

39

如果你需要窗口在第一次加载时就在前台显示,那么你应该使用以下代码:

private void Window_ContentRendered(object sender, EventArgs e)
{
    this.Topmost = false;
}

private void Window_Initialized(object sender, EventArgs e)
{
    this.Topmost = true;
}

或者通过重写这些方法:

protected override void OnContentRendered(EventArgs e)
{
    base.OnContentRendered(e);
    Topmost = false;
}

protected override void OnInitialized(EventArgs e)
{
    base.OnInitialized(e);
    Topmost = true;
}

1
如果你用C#开发类似于Launchy(http://launchy.net)的东西,你应该注意到这个答案几乎没有用处。 - Lex Li

28

我知道这个问题已经很老了,但我刚遇到这种情况并想分享我实现的解决方案。

正如在本页面上的评论中提到的,一些提出的解决方案在我需要支持XP的情况下不起作用。虽然我同意@Matthew Xavier所表达的观点,通常这是一个糟糕的用户体验实践,但有时候它完全是一个合理的用户体验。

将WPF窗口置于顶层的解决方案实际上是由提供全局热键的相同代码提供给我的。Joseph Cooney的博客文章包含指向他的代码示例链接的原始代码。

我已经清理和修改了一些代码,并将其实现为System.Windows.Window的扩展方法。 我已在XP 32位和Win7 64位上进行了测试,两者都能正常工作。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Interop;
using System.Runtime.InteropServices;

namespace System.Windows
{
    public static class SystemWindows
    {
        #region Constants

        const UInt32 SWP_NOSIZE = 0x0001;
        const UInt32 SWP_NOMOVE = 0x0002;
        const UInt32 SWP_SHOWWINDOW = 0x0040;

        #endregion

        /// <summary>
        /// Activate a window from anywhere by attaching to the foreground window
        /// </summary>
        public static void GlobalActivate(this Window w)
        {
            //Get the process ID for this window's thread
            var interopHelper = new WindowInteropHelper(w);
            var thisWindowThreadId = GetWindowThreadProcessId(interopHelper.Handle, IntPtr.Zero);

            //Get the process ID for the foreground window's thread
            var currentForegroundWindow = GetForegroundWindow();
            var currentForegroundWindowThreadId = GetWindowThreadProcessId(currentForegroundWindow, IntPtr.Zero);

            //Attach this window's thread to the current window's thread
            AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, true);

            //Set the window position
            SetWindowPos(interopHelper.Handle, new IntPtr(0), 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW);

            //Detach this window's thread from the current window's thread
            AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, false);

            //Show and activate the window
            if (w.WindowState == WindowState.Minimized) w.WindowState = WindowState.Normal;
            w.Show();
            w.Activate();
        }

        #region Imports

        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll")]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);

        [DllImport("user32.dll")]
        private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);

        [DllImport("user32.dll")]
        public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

        #endregion
    }
}

我希望这段代码能够帮助其他遇到这个问题的人。


嘿,看这里!我已经为此苦苦挣扎了几个月!这对我的两种情况都有效。太棒了!(Windows 7 x64) - mdiehl13
实际上,只有在我这样做时它才有效:App.mainWindow.Show(); SystemWindows.GlobalActivate(App.mainwindow); // 当我删除第一个 .show() 时,它不会置于前台。 - mdiehl13
+1 用于 SetWindowPos(),我正在寻找一种方法,只将我的窗口置于顶层而不打扰其他应用程序或窃取焦点。this.Activate() 会窃取焦点。 - prettyvoid
这对我很有帮助,而且在我的情况下,整个重点是窃取焦点,因为当用户与某些元素交互时会发生这种情况。所以非常感谢,这似乎一直有效!只调用this.Activate()有时候似乎不起作用。 - Peter
谢谢。这个方法在我尝试了其他大多数方法失败时起作用了(我没有尝试这个问题中的每个建议)。 - Cogent
1
几年前,我发现排名第一的答案(Activate/TopMost)可行并对其进行了点赞。但是今天在Windows 10中,我有可重复的例子表明它不起作用。幸运的是,这个答案在这些边缘情况下确实有效。 - Gabe Halsmer

25
为了使这段内容便于复制粘贴,
使用该类的DoOnProcess方法将进程的主窗口移动到前台(但不从其他窗口中夺取焦点)。
public class MoveToForeground
{
    [DllImportAttribute("User32.dll")]
    private static extern int FindWindow(String ClassName, String WindowName);

    const int SWP_NOMOVE        = 0x0002;
    const int SWP_NOSIZE        = 0x0001;            
    const int SWP_SHOWWINDOW    = 0x0040;
    const int SWP_NOACTIVATE    = 0x0010;
    [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
    public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);

    public static void DoOnProcess(string processName)
    {
        var allProcs = Process.GetProcessesByName(processName);
        if (allProcs.Length > 0)
        {
            Process proc = allProcs[0];
            int hWnd = FindWindow(null, proc.MainWindowTitle.ToString());
            // Change behavior by settings the wFlags params. See http://msdn.microsoft.com/en-us/library/ms633545(VS.85).aspx
            SetWindowPos(new IntPtr(hWnd), 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
        }
    }
}

希望有所帮助。


6
+1 这是唯一对我有用的答案。我有一个应用程序,其中包括一个主窗口和几个浮动的从窗口。激活任何一个窗口时,所有其他窗口也应该置于前台。但不要像大多数答案建议的那样被激活/获得焦点:这是灾难性的,因为它会使当前点击的窗口无法点击,因为另一个窗口突然获得了焦点。 - stijn
不使用 process.MainWindowHandle 的原因是什么? - Sriram Sakthivel
在我的情况下,我不想要主窗口,但同意,有其他获取hWnd的方法。顺便说一句,HwndSource对象效果很好。 - tobriand

14

为什么这个页面上的一些答案是错误的!

  • 任何使用 window.Focus() 的答案都是错误的。

    • 为什么?如果弹出通知消息,window.Focus() 将从用户正在输入的任何内容中夺走焦点。这对最终用户来说非常令人沮丧,特别是如果弹出窗口非常频繁。
  • 任何使用 window.Activate() 的答案都是错误的。

    • 为什么?它会使任何父窗口也可见。
  • 任何省略 window.ShowActivated = false 的答案都是错误的。
    • 为什么?当消息弹出时,它将从另一个窗口夺走焦点,这非常令人恼火!
  • 任何不使用 Visibility.Visible 来隐藏/显示窗口的答案都是错误的。
    • 为什么?如果我们在使用 Citrix,如果关闭窗口时窗口没有折叠,它将在屏幕上留下奇怪的黑色矩形区域。因此,我们不能使用 window.Show()window.Hide()

基本上:

  • 当激活窗口时,该窗口不应从任何其他窗口夺走焦点;
  • 显示时,该窗口不应激活其父窗口;
  • 该窗口应与 Citrix 兼容。

MVVM 解决方案

此代码与 Citrix 100%兼容(屏幕没有任何空白区域)。它已在普通 WPF 和 DevExpress 中进行了测试。

此答案适用于任何使用情况,其中我们希望拥有一个始终在其他窗口前面的小型通知窗口(如果用户在首选项中选择此选项)。

如果此答案似乎比其他答案更复杂,则是因为它是健壮的企业级代码。此页面上的其他一些答案很简单,但实际上不起作用。

XAML - 附加属性

将此附加属性添加到窗口内的任何 UserControl 上。该附加属性将:

  • 等待 Loaded 事件被触发(否则它无法查找可视树以查找父窗口)。
  • 添加事件处理程序,以确保窗口可见或不可见。

在任何时候,您都可以通过翻转附加属性的值来将窗口置于前台或后台。

<UserControl x:Class="..."
         ...
         attachedProperties:EnsureWindowInForeground.EnsureWindowInForeground=
             "{Binding EnsureWindowInForeground, Mode=OneWay}">

C# - 帮助方法

public static class HideAndShowWindowHelper
{
    /// <summary>
    ///     Intent: Ensure that small notification window is on top of other windows.
    /// </summary>
    /// <param name="window"></param>
    public static void ShiftWindowIntoForeground(Window window)
    {
        try
        {
            // Prevent the window from grabbing focus away from other windows the first time is created.
            window.ShowActivated = false;

            // Do not use .Show() and .Hide() - not compatible with Citrix!
            if (window.Visibility != Visibility.Visible)
            {
                window.Visibility = Visibility.Visible;
            }

            // We can't allow the window to be maximized, as there is no de-maximize button!
            if (window.WindowState == WindowState.Maximized)
            {
                window.WindowState = WindowState.Normal;
            }

            window.Topmost = true;
        }
        catch (Exception)
        {
            // Gulp. Avoids "Cannot set visibility while window is closing".
        }
    }

    /// <summary>
    ///     Intent: Ensure that small notification window can be hidden by other windows.
    /// </summary>
    /// <param name="window"></param>
    public static void ShiftWindowIntoBackground(Window window)
    {
        try
        {
            // Prevent the window from grabbing focus away from other windows the first time is created.
            window.ShowActivated = false;

            // Do not use .Show() and .Hide() - not compatible with Citrix!
            if (window.Visibility != Visibility.Collapsed)
            {
                window.Visibility = Visibility.Collapsed;
            }

            // We can't allow the window to be maximized, as there is no de-maximize button!
            if (window.WindowState == WindowState.Maximized)
            {
                window.WindowState = WindowState.Normal;
            }

            window.Topmost = false;
        }
        catch (Exception)
        {
            // Gulp. Avoids "Cannot set visibility while window is closing".
        }
    }
}

使用方法

为了使用这个功能,您需要在ViewModel中创建窗口:

private ToastView _toastViewWindow;
private void ShowWindow()
{
    if (_toastViewWindow == null)
    {
        _toastViewWindow = new ToastView();
        _dialogService.Show<ToastView>(this, this, _toastViewWindow, true);
    }
    ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(_toastViewWindow);
    HideAndShowWindowHelper.ShiftWindowIntoForeground(_toastViewWindow);
}

private void HideWindow()
{
    if (_toastViewWindow != null)
    {
        HideAndShowWindowHelper.ShiftWindowIntoBackground(_toastViewWindow);
    }
}

其他链接

关于如何确保通知窗口始终移回可见屏幕的技巧,请参见我的答案:在 WPF 中,如果窗口超出屏幕,如何将其移回屏幕?


7
“Enterprise level code” 几行后面出现了 catch (Exception) { }。是的,它使用了一些没有在答案中显示的代码,比如 _dialogService 或者 ShiftWindowOntoScreenHelper。此外还要求在 ViewModel 中创建窗口(这基本上破坏了整个 MVVM 模式)…… - Kryptos
@Kryptos 这是企业级代码。我从记忆中打出来的,这种精确技术在一家大型FTSE100公司中得到了应用。现实生活与我们所追求的完美设计模式相比略显不够完美。 - Contango
我不喜欢我们在视图模型中保留窗口实例的事实,正如Kryptos所提到的那样,这破坏了mvvm的整个要点,也许可以在代码后台完成它? - Igor Meszaros
1
@Igor Meszaros 同意。现在我有更多的经验,如果我再做一次,我会添加一个行为,并使用绑定到 ViewModel 的 Func<> 控制它。 - Contango
1
我想问一下,活动窗口是否意味着它必须在顶部? - CodingNinja

13
如果用户正在与另一个应用程序交互,可能无法将您的应用程序置于前台。一般规则是,只有当进程已经成为前台进程时,该进程才能期望设置前景窗口。(Microsoft在SetForegroundWindow() MSDN条目中记录了限制。)这是因为:
  1. 用户“拥有”前景。例如,如果用户正在打字,另一个程序突然抢占前景,这将极其令人恼火,至少会打断用户的工作流程,并可能导致她的击键被错误地解释,直到她注意到变化。
  2. 想象一下,两个程序都检查自己的窗口是否为前景,并尝试将其设置为前景(如果不是)。一旦第二个程序运行起来,计算机就变得无用了,因为前景在每次任务切换时在两者之间反弹。

好的观点。代码的目的是与全局热键配合使用,而其他应用程序也以某种方式实现了这一点。 - Factor Mystic
必须在C#中使用PInvoke来模拟此文章中所描述的内容:http://www.codeproject.com/Tips/76427/How-to-bring-window-to-top-with-SetForegroundWindo.aspx - Lex Li
为什么有时候我切换到 Visual Studio,表达式混合错误弹出对话框仍然可见呢? :-/ - Simon_Weaver
Simon,我怀疑你看到的错误弹出窗口是“最顶层”的窗口(这是我不赞成的设计决策)。前景窗口(接收用户输入)和Z序列中的“最顶层”窗口之间存在差异。任何窗口都可以使自己成为“最顶层”,这将使其置于所有非最顶层窗口之上,但不会像成为前景窗口那样给予窗口键盘焦点等。 - Matthew Xavier
这个技巧对于一些特定的窗口会失效。 Visual Studio 和命令提示符窗口必须有某些东西来防止其他窗口成为前景窗口。 - Lex Li

8

我知道这是晚回答,也许对研究人员有所帮助。

 if (!WindowName.IsVisible)
 {
     WindowName.Show();
     WindowName.Activate();
 }

7

我曾经遇到过一个类似的问题,我有一个WPF应用程序,通过Shell对象从Access应用程序中调用。

我的解决方案如下 - 在XP和Win7 x64上使用x86目标编译的应用程序可以正常工作。

我更喜欢这种方法而不是模拟alt-tab。

void Window_Loaded(object sender, RoutedEventArgs e)
{
    // make sure the window is normal or maximised
    // this was the core of the problem for me;
    // even though the default was "Normal", starting it via shell minimised it
    this.WindowState = WindowState.Normal;

    // only required for some scenarios
    this.Activate();
}

5

好的,既然这是一个如此热门的话题...这是对我有用的方法。如果您无法看到窗口,Activate()将会出错,因此如果不按照以下方式操作,我就会收到错误信息。

Xaml:

<Window .... 
        Topmost="True" 
        .... 
        ContentRendered="mainWindow_ContentRendered"> .... </Window>

代码后置文件:

private void mainWindow_ContentRendered(object sender, EventArgs e)
{
    this.Topmost = false;
    this.Activate();
    _UsernameTextBox.Focus();
}

这是我唯一能让窗口置顶的方法。然后激活它,这样你就可以在不用鼠标设置焦点的情况下在框中输入内容。除非窗口处于活动状态,否则control.Focus()无法工作。

在我这里可以正常工作,启动画面也能显示。谢谢。 - SiL3NC3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接