在WPF中为窗口打开一个窗口

时间:2008-11-02 23:43:00

标签: c# .net wpf winapi pinvoke

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

编辑:我正在与全球热键一起完成​​此操作。

18 个答案:

答案 0 :(得分:282)

myWindow.Activate();

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

除非我误解了你想要永远在线的行为,否则应该这样做。在这种情况下,你想要:

myWindow.TopMost = true;

答案 1 :(得分:152)

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

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 :(得分:25)

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

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

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

答案 3 :(得分:20)

要快速复制粘贴一个 -
使用此类'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

答案 4 :(得分:17)

我知道这个问题相当陈旧,但我只是遇到了这个精确的场景,并希望分享我已实施的解决方案。

正如本页评论中所提到的,提出的一些解决方案不适用于XP,我需要在我的场景中支持。虽然我同意@Matthew Xavier的观点,即一般来说这是一个糟糕的用户体验,但有时它完全是一个可信的用户体验。

将WPF窗口置于顶端的解决方案实际上是通过我用来提供全局热键的相同代码提供给我的。 A blog article by Joseph Cooney包含link to his code samples,其中包含原始代码。

我已经清理并修改了一些代码,并将其作为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
    }
}

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

答案 5 :(得分:13)

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

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

答案 6 :(得分:6)

我遇到了类似的问题,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();
}

答案 7 :(得分:6)

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

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

答案 8 :(得分:6)

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

  • 使用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);
    }
}

其他链接

有关如何确保通知窗口始终返回可见屏幕的提示,请参阅我的回答:In WPF, how to shift a window onto the screen if it is off the screen?

答案 9 :(得分:4)

嗯,因为这是一个热门话题......这对我有用。如果我没有这样做,我会收到错误,因为如果你看不到窗口,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()不会工作,除非窗口是Active();

答案 10 :(得分:2)

要显示任何当前打开的窗口,请导入这些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);

在程序中我们搜索具有指定标题的应用程序(没有第一个字母的书写标题(索引&gt; 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);
                }

答案 11 :(得分:2)

好吧,我想出了一个解决方法。我正在通过用于实现热键的键盘钩子进行呼叫。如果我将其置于具有暂停的BackgroundWorker中,则调用将按预期工作。这是一个kludge,但我不知道为什么它最初没有工作。

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

答案 12 :(得分:1)

问题可能是,从钩子调用代码的线程尚未被运行时初始化,因此调用运行时方法不起作用。

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

答案 13 :(得分:1)

这些代码一直都能正常运行。

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

这些步骤可以正常运行,并将所有其他窗口放到父窗口中。

答案 14 :(得分:0)

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

    this.Hide();

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

    this.Show();
然后,

将再次将窗口显示为最重要的项目。

答案 15 :(得分:0)

只是想为这个问题添加另一个解决方案。此实现适用于我的场景,其中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();
    }
}

答案 16 :(得分:0)

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

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

答案 17 :(得分:0)

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

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