簡體   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