WPF窗口永远置顶

前言

窗口置顶有两种情况

  • 应用内的窗口置顶
  • 应用外的窗口置顶

一般我们这样设置窗口置顶

1
<Window Topmost="True"></Window>

但是如果其他程序也置顶,后来的置顶就会覆盖之前的置顶,所以我们要保证我们的窗口永远置顶就要做如下设置。

窗口永远置顶

窗口置顶只设置Topmost = true,有时并不能生效,比如有多个置顶的窗口,比如系统底部的任务栏,当有多个置顶的窗口,谁激活了,谁就在最顶部。

一个应用中不要设置多个窗口永远置顶。

Deactivated

既然谁激活谁在顶部,那我们可以在失去激活的时候再调用一下置顶即可。

您可以Topmost = trueWindow.Deactivated事件的处理程序中设置:

Window.Deactivated在窗口成为后台窗口时发生。

XAML:

1
2
3
4
5
6
7
<Window 
Title="工具条"
Width="276"
Height="728"
Deactivated="Window_Deactivated"
>
</Window>

或者

1
Deactivated += Window_Deactivated;

代码

1
2
3
4
5
private void Window_Deactivated(object sender, EventArgs e)
{
Topmost = false;
Topmost = true;
}

Deactivated只要您的应用程序失去焦点(通常在另一个应用程序请求时Topmost),就会调用该事件,因此这将在此之后重置您的应用程序.

这里为什么先取消置顶再置顶是因为

已经在置顶状态的窗口再次设置置顶是无效的。

这种方案不能保证窗口永远置顶,是因为:

假如我们先点击了其他窗口,再点击任务栏,就会发现置顶又失效了,这是因为,第一次失去焦点确实置顶生效了,但是失去焦点后另一个窗口再获取焦点,原来失去焦点的窗口事件就不会再触发了,原来的这个窗口就不再置顶了。

既然如此,我们在非激活的时候重新激活就行了,但是一定要添加延迟。

1
2
3
4
5
6
7
8
9
10
11
12
13
private void Window_Deactivated(object sender, EventArgs e)
{
Topmost = false;
Topmost = true;
Task.Run
(
() =>
{
Thread.Sleep(500);
Dispatcher.Invoke(Activate);
}
);
}

但是

这样虽然可以保证窗口一直置顶,但是影响别的窗口的操作,特别是输入文字。

定时检测+WindowAPI

注意

这种方式还是比较完美的。不但能保证窗口置顶,还不影响其他窗口的输入。

WPF窗体的句柄然后调用WindowAPI使窗口置顶。

搞个线程,每隔500ms设置一下窗体置顶

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public SelectAreaWin()
{
InitializeComponent();
Loaded += Window_Loaded;
Unloaded += Window_Unloaded;
}

private readonly WinTopUtils _winTopUtils = new WinTopUtils();

private void Window_Loaded(object sender, RoutedEventArgs e)
{
WindowInteropHelper wndHelper = new WindowInteropHelper(this);
_winTopUtils.Start(Dispatcher, wndHelper.Handle);
}

private void Window_Unloaded(object sender, RoutedEventArgs e)
{
_winTopUtils.Stop();
}

窗口置顶工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
namespace z_screen_recorder.Utils
{
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Threading;

public class WinTopUtils
{

[DllImport("user32.dll")]
private static extern int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int uFlags);
private static readonly IntPtr HwndTopmost = new IntPtr(-1);
private const int SwpNosize = 0x0001;
private const int SwpNomove = 0x0002;
private const int SwpShowwindow = 0x0040;
public static void SetWinTop(Dispatcher dispatcher, IntPtr handle)
{
dispatcher.Invoke
(
() =>
{
SetWindowPos(handle, HwndTopmost, 0, 0, 0, 0, SwpNomove | SwpNosize | SwpShowwindow);
}
);
}

IntPtr _handle;
private bool _isRunning = true;

public void Start(Dispatcher dispatcher, IntPtr handle)
{
_handle = handle;
_isRunning = true;
new Thread(() => {
while (_isRunning)
{
Thread.Sleep(500);
SetWinTop(dispatcher, _handle);
}
// ReSharper disable once FunctionNeverReturns
}).Start();
}

public void Stop()
{
_isRunning = false;
}
}
}

外部应用窗口置顶

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
public class ZWinUtil
{
#region ShowWindow 方法窗体状态的参数枚举

/// <summary>
/// 隐藏窗口并激活其他窗口
/// </summary>
private const int SW_HIDE = 0;

/// <summary>
/// 激活并显示一个窗口。如果窗口被最小化或最大化,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志
/// </summary>
private const int SW_SHOWNORMAL = 1;

/// <summary>
/// 激活窗口并将其最小化
/// </summary>
private const int SW_SHOWMINIMIZED = 2;

/// <summary>
/// 激活窗口并将其最大化
/// </summary>
private const int SW_SHOWMAXIMIZED = 3;

/// <summary>
/// 以窗口最近一次的大小和状态显示窗口。此值与SW_SHOWNORMAL相似,只是窗口没有被激活
/// </summary>
private const int SW_SHOWNOACTIVATE = 4;

/// <summary>
/// 在窗口原来的位置以原来的尺寸激活和显示窗口
/// </summary>
private const int SW_SHOW = 5;

/// <summary>
/// 最小化指定的窗口并且激活在Z序中的下一个顶层窗口
/// </summary>
private const int SW_MINIMIZE = 6;

/// <summary>
/// 最小化的方式显示窗口,此值与SW_SHOWMINIMIZED相似,只是窗口没有被激活
/// </summary>
private const int SW_SHOWMINNOACTIVE = 7;

/// <summary>
/// 以窗口原来的状态显示窗口。此值与SW_SHOW相似,只是窗口没有被激活
/// </summary>
private const int SW_SHOWNA = 8;

/// <summary>
/// 激活并显示窗口。如果窗口最小化或最大化,则系统将窗口恢复到原来的尺寸和位置。在恢复最小化窗口时,应用程序应该指定这个标志
/// </summary>
private const int SW_RESTORE = 9;

/// <summary>
/// 依据在STARTUPINFO结构中指定的SW_FLAG标志设定显示状态,STARTUPINFO 结构是由启动应用程序的程序传递给CreateProcess函数的
/// </summary>
private const int SW_SHOWDEFAULT = 10;

/// <summary>
/// 最小化窗口,即使拥有窗口的线程被挂起也会最小化。在从其他线程最小化窗口时才使用这个参数
/// </summary>
private const int SW_FORCEMINIMIZE = 11;

#endregion ShowWindow 方法窗体状态的参数枚举

//窗体置顶
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);

//取消窗体置顶
private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);

//不调整窗体位置
private const uint SWP_NOMOVE = 0x0002;

//不调整窗体大小
private const uint SWP_NOSIZE = 0x0001;

[DllImport("User32.dll", EntryPoint = "FindWindow")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

[DllImport("user32.dll", EntryPoint = "ShowWindow")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

/// <summary>
/// 使窗体置顶
/// </summary>
/// <param name="Name">需要置顶的窗体的名字</param>
public static void SetTop(string Name)
{
IntPtr CustomBar = FindWindow(null, Name);
if (CustomBar != null)
{
SetWindowPos(CustomBar, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
}

/// <summary>
/// 取消窗体置顶
/// </summary>
/// <param name="Name">需要置顶的窗体的名字</param>
public static void SetTopNO(string Name)
{
IntPtr CustomBar = FindWindow(null, Name);
if (CustomBar != null)
{
SetWindowPos(CustomBar, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
}

/// <summary>
/// 显示窗口
/// </summary>
/// <param name="Name"></param>
public static void SetWinShow(string Name)
{
IntPtr CustomBar = FindWindow(null, Name);
if (CustomBar != null)
{
ShowWindow(CustomBar, SW_SHOW);
}
}

/// <summary>
/// 隐藏窗口
/// </summary>
/// <param name="Name"></param>
public static void SetWinHide(string Name)
{
IntPtr CustomBar = FindWindow(null, Name);
if (CustomBar != null)
{
ShowWindow(CustomBar, SW_HIDE);
}
}
}

调用

1
2
3
ZWinUtil.SetWinShow("文件资源管理器");
ZWinUtil.SetTop("文件资源管理器");
ZWinUtil.SetWinHide("文件资源管理器");

当前窗口句柄

1
2
3
4
5
IntPtr handle;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
handle = new WindowInteropHelper(this).Handle;
}