繁体   English   中英

在 WPF 中将窗口置于最前面

[英]Bring a window to the front in WPF

如何将我的 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 ,它会完全按预期工作,所以我不确定为什么它在我原来的情况下不起作用。

编辑:我正在与全局热键一起执行此操作。

myWindow.Activate();

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

这应该可以解决问题,除非我误解了并且您希望始终处于最佳状态。 在这种情况下,您想要:

myWindow.TopMost = true;

我找到了一个将窗口置于顶部的解决方案,但它的行为与普通窗口相同:

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

如果您需要窗口在第一次加载时位于前面,那么您应该使用以下内容:

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;
}

我知道这个问题已经很老了,但我刚刚遇到了这个精确的场景,想分享我实施的解决方案。

正如本页的评论中提到的,提出的几个解决方案在 XP 上不起作用,我需要在我的场景中支持。 虽然我同意 @Matthew Xavier 的观点,即通常这是一种糟糕的 UX 实践,但有时它完全是一个合理的 UX。

将 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
    }
}

我希望此代码可以帮助遇到此问题的其他人。

为了使它成为一个快速复制粘贴 -
使用此类的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);
        }
    }
}

HTH

如果用户正在与另一个应用程序交互,则可能无法将您的应用程序放在最前面。 作为一般规则,如果该进程已经是前台进程,则该进程只能期望设置前台窗口。 (Microsoft 在SetForegroundWindow() MSDN 条目中记录了限制。)这是因为:

  1. 用户“拥有”前景。 例如,如果另一个程序在用户打字时窃取了前台,这将非常烦人,至少会中断她的工作流程,并且可能导致意想不到的后果,因为她针对一个应用程序的击键被冒犯者误解,直到她注意到变化.
  2. 想象一下,两个程序中的每一个都检查其窗口是否为前景,如果不是,则尝试将其设置为前景。 一旦第二个程序运行,计算机就会变得毫无用处,因为在每次任务切换时前景在两者之间跳来跳去。

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

  • 任何使用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 中,如果窗口不在屏幕上,如何将其移回屏幕上? .

我知道这是迟到的答案,也许对研究人员有帮助

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

我在 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();
}

好吧,既然这是一个如此热门的话题……这对我有用。 如果我不这样做,我会出错,因为如果您看不到窗口,Activate() 会出错。

Xml:

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

代码隐藏:

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

这是我让窗口显示在顶部的唯一方法。 然后激活它,这样您就可以在框中键入内容而无需使用鼠标设置焦点。 control.Focus() 不会工作,除非窗口是 Active();

好吧,我想出了一个解决方法。 我正在从用于实现热键的键盘挂钩进行调用。 如果我暂停将其放入 BackgroundWorker 中,该调用将按预期工作。 这是一个混乱,但我不知道为什么它最初不起作用。

void hotkey_execute()
{
    IntPtr handle = new WindowInteropHelper(Application.Current.MainWindow).Handle;
    BackgroundWorker bg = new BackgroundWorker();
    bg.DoWork += new DoWorkEventHandler(delegate
        {
            Thread.Sleep(10);
            SwitchToThisWindow(handle, true);
        });
    bg.RunWorkerAsync();
}

要显示任何当前打开的窗口,请导入这些 DLL:

public partial class Form1 : Form
{
    [DllImportAttribute("User32.dll")]
    private static extern int FindWindow(String ClassName, String WindowName);
    [DllImportAttribute("User32.dll")]
    private static extern int SetForegroundWindow(int hWnd);

并在程序中我们搜索具有指定标题的应用程序(写标题不带首字母(索引> 0))

  foreach (Process proc in Process.GetProcesses())
                {
                    tx = proc.MainWindowTitle.ToString();
                    if (tx.IndexOf("Title of Your app WITHOUT FIRST LETTER") > 0)
                    {
                        tx = proc.MainWindowTitle;
                        hWnd = proc.Handle.ToInt32(); break;
                    }
                }
                hWnd = FindWindow(null, tx);
                if (hWnd > 0)
                {
                    SetForegroundWindow(hWnd);
                }

这些代码在任何时候都可以正常工作。

首先在 XAML 中设置激活的事件处理程序:

Activated="Window_Activated"

将以下行添加到您的主窗口构造函数块:

public MainWindow()
{
    InitializeComponent();
    this.LocationChanged += (sender, e) => this.Window_Activated(sender, e);
}

在激活的事件处理程序中复制以下代码:

private void Window_Activated(object sender, EventArgs e)
{
    if (Application.Current.Windows.Count > 1)
    {
        foreach (Window win in Application.Current.Windows)
            try
            {
                if (!win.Equals(this))
                {
                    if (!win.IsVisible)
                    {
                        win.ShowDialog();
                    }

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

                    win.Activate();
                    win.Topmost = true;
                    win.Topmost = false;
                    win.Focus();
                }
            }
            catch { }
    }
    else
        this.Focus();
}

这些步骤将正常工作,并将所有其他窗口置于其父窗口的前面。

只是想为这个问题添加另一个解决方案。 此实现适用于我的场景,其中 CaliBurn 负责显示主窗口。

protected override void OnStartup(object sender, StartupEventArgs e)
{
    DisplayRootViewFor<IMainWindowViewModel>();

    Application.MainWindow.Topmost = true;
    Application.MainWindow.Activate();
    Application.MainWindow.Activated += OnMainWindowActivated;
}

private static void OnMainWindowActivated(object sender, EventArgs e)
{
    var window = sender as Window;
    if (window != null)
    {
        window.Activated -= OnMainWindowActivated;
        window.Topmost = false;
        window.Focus();
    }
}

问题可能是从钩子调用代码的线程没有被运行时初始化,所以调用运行时方法不起作用。

也许您可以尝试执行 Invoke 将代码编组到 UI 线程,以调用将窗口置于前台的代码。

如果您试图隐藏窗口,例如最小化窗口,我发现使用

    this.Hide();

将正确隐藏它,然后只需使用

    this.Show();

然后将再次将窗口显示为最顶部的项目。

请记住不要将显示该窗口的代码放在 PreviewMouseDoubleClick 处理程序中,因为活动窗口将切换回处理该事件的窗口。 只需将其放入 MouseDoubleClick 事件处理程序或通过将 e.Handled 设置为 True 来停止冒泡。

在我的情况下,我正在处理 Listview 上的 PreviewMouseDoubleClick 并且没有设置 e.Handled = true 然后它引发了 MouseDoubleClick 事件女巫坐焦点回到原始窗口。

这是上述一些建议的组合,效果很好而且很简单。 只有当这些事件触发时它才会出现在前面,所以在事件发生后弹出的任何窗口都会保持在顶部。

public partial class MainWindow : Window
{
    protected override void OnContentRendered(EventArgs e)
    {
        base.OnContentRendered(e);

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

        Topmost = true;
        Topmost = false;
    }

    ....
}

我构建了一个扩展方法,以便于重用。

using System.Windows.Forms;
    namespace YourNamespace{
        public static class WindowsFormExtensions {
            public static void PutOnTop(this Form form) {
                form.Show();
                form.Activate();
            }// END PutOnTop()       
        }// END class
    }// END namespace

调用表单构造函数

namespace YourNamespace{
       public partial class FormName : Form {
       public FormName(){
            this.PutOnTop();
            InitalizeComponents();
        }// END Constructor
    } // END Form            
}// END namespace

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM