如何从另一个应用程序关闭 WPF 系统托盘应用程序?
How to close WPF system tray application from another app?
我有一个 windowless WPF 应用程序,使用了 Philipp Sumi 的 NotifyIcon。我很乐意更换基于 WinForms 的旧版本,但是,我遇到了一个问题。
我的应用程序有卸载程序,可以在删除可执行文件之前关闭托盘应用程序。通过向托盘应用程序进程发送 WM_CLOSE 消息来完成关闭操作。使用 WinForms 收听此类消息相对容易,但是 - 如何使用 WPF 做到这一点,然后,也许有更好的方法远程告诉我的 WPF 托盘应用程序关闭?我的意思是,还有什么?管道?
这不是重复的。
我的应用程序中没有 "main window"。所以引用 main windows 的任何内容都不起作用。
我相信系统必须有一种方法来告诉应用程序自行关闭,以防重启或关机。这就是我们收到 "this and that app is not responding, do you want to shutdown anyway" 或类似消息的原因。
这是我天真的方法:
using (var process = Process.GetProcessesByName("MyApp").FirstOrDefault()) {
const uint WM_SYSCOMMAND = 0x0112;
const uint SC_CLOSE = 0xF060;
const uint WM_CLOSE = 0x0010;
var hwnd = process.MainWindowHandle;
NativeMethods.SendMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
foreach (ProcessThread thread in process.Threads) {
NativeMethods.PostThreadMessage(
(uint)thread.Id,
WM_SYSCOMMAND,
(IntPtr)SC_CLOSE,
IntPtr.Zero
);
}
}
无效。当然hwnd总是IntPtr.Zero,除非我创建一个window,显然我不想创建window。应用程序线程忽略 SC_CLOSE,所以这里也没有乐趣。
好的,我尝试创建隐形 window。如果 window 将 ShowInTaskBar 设置为 true,则该方法有效。不好。
然后我从 System.Windows.Forms.NativeWindow.
创建了一个海绵 window
当然,隐形window完全可以接收WM_CLOSE和任何其他消息,但是,它没有设置为主进程window,所以我无法定位它使用我的卸载应用程序。
目前我没有想法。
大多数时候,我必须自己弄清楚。
方法如下。
首先:我们有流程。该进程没有 main window,根本没有 windows,因此与 windows 的所有通信都关闭了 table。但是进程有线程。线程有它们的消息队列,尽管它可以通过 Win32 API.
访问
所以这是接收端:
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application {
// (...)
/// <summary>
/// Sent as a signal that a window or an application should terminate.
/// </summary>
const uint WM_CLOSE = 0x0010;
/// <summary>
/// Retrieves a message from the calling thread's message queue. The function dispatches incoming sent messages until a posted message is available for retrieval.
/// </summary>
/// <param name="lpMsg">MSG structure that receives message information from the thread's message queue.</param>
/// <param name="hWnd">A handle to the window whose messages are to be retrieved. The window must belong to the current thread. Use <see cref="IntPtr.Zero"/> to retrieve thread message.</param>
/// <param name="wMsgFilterMin">The integer value of the lowest message value to be retrieved.</param>
/// <param name="wMsgFilterMax">The integer value of the highest message value to be retrieved.</param>
/// <returns>Non-zero for any message but WM_QUIT, zero for WM_QUIT, -1 for error.</returns>
[DllImport("user32.dll")]
static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
/// <summary>
/// Waits indefinitely for a specific thread message.
/// </summary>
/// <param name="signal">Signal, message value.</param>
private void WaitForSignal(uint signal) => GetMessage(out var msg, IntPtr.Zero, signal, signal);
/// <summary>
/// WPF application startup.
/// </summary>
/// <param name="e">Event arguments.</param>
protected override async void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
// ... initialization code ...
await Task.Run(() => WaitForSignal(WM_CLOSE));
Shutdown();
}
// (...)
}
发送结束:
/// <summary>
/// Demo.
/// </summary>
class Program {
/// <summary>
/// Sent as a signal that a window or an application should terminate.
/// </summary>
const uint WM_CLOSE = 0x0010;
/// <summary>
/// Posts a message to the message queue of the specified thread. It returns without waiting for the thread to process the message.
/// </summary>
/// <param name="threadId">The identifier of the thread to which the message is to be posted.</param>
/// <param name="msg">The type of message to be posted.</param>
/// <param name="wParam">Additional message-specific information.</param>
/// <param name="lParam">Additional message-specific information.</param>
/// <returns></returns>
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PostThreadMessage(uint threadId, uint msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// Closes a windowless application pointed with process name.
/// </summary>
/// <param name="processName">The name of the process to close.</param>
static void CloseWindowless(string processName) {
foreach (var process in Process.GetProcessesByName(processName)) {
using (process) {
foreach (ProcessThread thread in process.Threads) PostThreadMessage((uint)thread.Id, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
}
}
/// <summary>
/// Main application entry point.
/// </summary>
/// <param name="args">Command line arguments.</param>
static void Main(string[] args) => CloseWindowless("MyAppName");
}
背后的巫术:P/Invoke 部分非常明显。更有趣的是 WPF Application
.OnStartup
覆盖行为。
如果该方法是同步的并且不退出,则应用程序挂起。
但是,如果它被标记为异步,则它不必退出。它会无限期地等待任何 awaitable。这正是我们所需要的,因为我们只能从主 UI 线程调用 Shutdown()
。我们不能阻塞那个线程,所以我们必须等待另一个线程上的消息。我们将 WM_CLOSE 发送到所有进程线程,因此它会得到它。当它结束时,Shutdown
方法从主线程调用,仅此而已。
此解决方案中最好的部分不需要 System.Windows.Forms 参考。
我有一个 windowless WPF 应用程序,使用了 Philipp Sumi 的 NotifyIcon。我很乐意更换基于 WinForms 的旧版本,但是,我遇到了一个问题。
我的应用程序有卸载程序,可以在删除可执行文件之前关闭托盘应用程序。通过向托盘应用程序进程发送 WM_CLOSE 消息来完成关闭操作。使用 WinForms 收听此类消息相对容易,但是 - 如何使用 WPF 做到这一点,然后,也许有更好的方法远程告诉我的 WPF 托盘应用程序关闭?我的意思是,还有什么?管道?
这不是重复的。 我的应用程序中没有 "main window"。所以引用 main windows 的任何内容都不起作用。
我相信系统必须有一种方法来告诉应用程序自行关闭,以防重启或关机。这就是我们收到 "this and that app is not responding, do you want to shutdown anyway" 或类似消息的原因。
这是我天真的方法:
using (var process = Process.GetProcessesByName("MyApp").FirstOrDefault()) {
const uint WM_SYSCOMMAND = 0x0112;
const uint SC_CLOSE = 0xF060;
const uint WM_CLOSE = 0x0010;
var hwnd = process.MainWindowHandle;
NativeMethods.SendMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
foreach (ProcessThread thread in process.Threads) {
NativeMethods.PostThreadMessage(
(uint)thread.Id,
WM_SYSCOMMAND,
(IntPtr)SC_CLOSE,
IntPtr.Zero
);
}
}
无效。当然hwnd总是IntPtr.Zero,除非我创建一个window,显然我不想创建window。应用程序线程忽略 SC_CLOSE,所以这里也没有乐趣。
好的,我尝试创建隐形 window。如果 window 将 ShowInTaskBar 设置为 true,则该方法有效。不好。
然后我从 System.Windows.Forms.NativeWindow.
创建了一个海绵 window当然,隐形window完全可以接收WM_CLOSE和任何其他消息,但是,它没有设置为主进程window,所以我无法定位它使用我的卸载应用程序。
目前我没有想法。
大多数时候,我必须自己弄清楚。 方法如下。
首先:我们有流程。该进程没有 main window,根本没有 windows,因此与 windows 的所有通信都关闭了 table。但是进程有线程。线程有它们的消息队列,尽管它可以通过 Win32 API.
访问所以这是接收端:
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application {
// (...)
/// <summary>
/// Sent as a signal that a window or an application should terminate.
/// </summary>
const uint WM_CLOSE = 0x0010;
/// <summary>
/// Retrieves a message from the calling thread's message queue. The function dispatches incoming sent messages until a posted message is available for retrieval.
/// </summary>
/// <param name="lpMsg">MSG structure that receives message information from the thread's message queue.</param>
/// <param name="hWnd">A handle to the window whose messages are to be retrieved. The window must belong to the current thread. Use <see cref="IntPtr.Zero"/> to retrieve thread message.</param>
/// <param name="wMsgFilterMin">The integer value of the lowest message value to be retrieved.</param>
/// <param name="wMsgFilterMax">The integer value of the highest message value to be retrieved.</param>
/// <returns>Non-zero for any message but WM_QUIT, zero for WM_QUIT, -1 for error.</returns>
[DllImport("user32.dll")]
static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
/// <summary>
/// Waits indefinitely for a specific thread message.
/// </summary>
/// <param name="signal">Signal, message value.</param>
private void WaitForSignal(uint signal) => GetMessage(out var msg, IntPtr.Zero, signal, signal);
/// <summary>
/// WPF application startup.
/// </summary>
/// <param name="e">Event arguments.</param>
protected override async void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
// ... initialization code ...
await Task.Run(() => WaitForSignal(WM_CLOSE));
Shutdown();
}
// (...)
}
发送结束:
/// <summary>
/// Demo.
/// </summary>
class Program {
/// <summary>
/// Sent as a signal that a window or an application should terminate.
/// </summary>
const uint WM_CLOSE = 0x0010;
/// <summary>
/// Posts a message to the message queue of the specified thread. It returns without waiting for the thread to process the message.
/// </summary>
/// <param name="threadId">The identifier of the thread to which the message is to be posted.</param>
/// <param name="msg">The type of message to be posted.</param>
/// <param name="wParam">Additional message-specific information.</param>
/// <param name="lParam">Additional message-specific information.</param>
/// <returns></returns>
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PostThreadMessage(uint threadId, uint msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// Closes a windowless application pointed with process name.
/// </summary>
/// <param name="processName">The name of the process to close.</param>
static void CloseWindowless(string processName) {
foreach (var process in Process.GetProcessesByName(processName)) {
using (process) {
foreach (ProcessThread thread in process.Threads) PostThreadMessage((uint)thread.Id, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
}
}
/// <summary>
/// Main application entry point.
/// </summary>
/// <param name="args">Command line arguments.</param>
static void Main(string[] args) => CloseWindowless("MyAppName");
}
背后的巫术:P/Invoke 部分非常明显。更有趣的是 WPF Application
.OnStartup
覆盖行为。
如果该方法是同步的并且不退出,则应用程序挂起。
但是,如果它被标记为异步,则它不必退出。它会无限期地等待任何 awaitable。这正是我们所需要的,因为我们只能从主 UI 线程调用 Shutdown()
。我们不能阻塞那个线程,所以我们必须等待另一个线程上的消息。我们将 WM_CLOSE 发送到所有进程线程,因此它会得到它。当它结束时,Shutdown
方法从主线程调用,仅此而已。
此解决方案中最好的部分不需要 System.Windows.Forms 参考。