我正在开发一个wpf应用程序,而不是在用户关闭按钮时退出应用程序我将其最小化到托盘(类似于谷歌谈话)。
void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
我需要的是,如果用户忘记了应用程序的实例并尝试打开新实例,我必须关闭第二个实例并将我的应用程序设置为前台应用程序。如果应用程序处于最小化状态(未隐藏),我可以执行此操作。我使用以下代码
protected override void OnStartup(StartupEventArgs e)
{
Process currentProcess = Process.GetCurrentProcess();
var runningProcess = (from process in Process.GetProcesses()
where
process.Id != currentProcess.Id &&
process.ProcessName.Equals(
currentProcess.ProcessName,
StringComparison.Ordinal)
select process).FirstOrDefault();
if (runningProcess != null)
{
Application.Current.Shutdown();
ShowWindow(runningProcess.MainWindowHandle, 5);
ShowWindow(runningProcess.MainWindowHandle, 3);
}
}
[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);
当应用程序最小化时,它对MainWindowHandle有一些独特的价值。当我隐藏应用程序时, runningProcess 的MainWindowHandle显示为0.我认为这就是为什么我的应用程序在处于隐藏状态时不会打开,但不知道如何修复它。
告诉我是否需要发布更多代码或澄清任何内容。提前谢谢。
答案 0 :(得分:8)
你是对的。如果进程没有与之关联的图形界面(隐藏/最小化),则当我隐藏应用程序时,runningProcess的MainWindowHandle显示为0
MainWindowHandle
值为零。
作为解决方法,您可以尝试使用HANDLE
函数枚举所有打开的窗口来获取隐藏窗口的EnumDesktopWindows
,并将其进程ID与隐藏/最小化窗口的进程ID进行比较。
<强>更新强>
WPF的WIN32窗口与标准WIN32窗口的行为略有不同。它具有由单词HwndWrapper
组成的类名,它在其中创建的AppDomain的名称,以及唯一的随机Guid
(每次启动时都会更改),例如 HwndWrapper [WpfApp。 EXE ;; 4d426cdc-31cf-4e4c-88c7-ede846ab6d44] 。
更新2
当使用Hide()
方法隐藏WPF的窗口时,它会在内部调用UpdateVisibilityProperty(Visibility.Hidden)
,然后将UIElement
的内部可见性标志设置为false。当我们调用Show()
的{{1}}方法时,会调用WPF Window
,并切换UpdateVisibilityProperty(Visibility.Visible)
的内部可见性标记。
当我们使用UIElement
显示WPF窗口时,ShowWindow()
方法不会被触发,因此内部可见性标记不会被反转(这会导致窗口显示黑色背景)。
通过查看UpdateVisibilityProperty()
内部实现,在不调用WPF Window
或Show()
方法的情况下切换内部visiblity标志的唯一方法是发送Hide()
消息。
WM_SHOWWINDOW
使用示例
const int GWL_EXSTYLE = (-20);
const uint WS_EX_APPWINDOW = 0x40000;
const uint WM_SHOWWINDOW = 0x0018;
const int SW_PARENTOPENING = 3;
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsProc ewp, int lParam);
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
[DllImport("user32.dll")]
private static extern uint GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern uint GetWindowText(IntPtr hWnd, StringBuilder lpString, uint nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);
static bool IsApplicationWindow(IntPtr hWnd) {
return (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_APPWINDOW) != 0;
}
static IntPtr GetWindowHandle(int pid, string title) {
var result = IntPtr.Zero;
EnumWindowsProc enumerateHandle = delegate(IntPtr hWnd, int lParam)
{
int id;
GetWindowThreadProcessId(hWnd, out id);
if (pid == id) {
var clsName = new StringBuilder(256);
var hasClass = GetClassName(hWnd, clsName, 256);
if (hasClass) {
var maxLength = (int)GetWindowTextLength(hWnd);
var builder = new StringBuilder(maxLength + 1);
GetWindowText(hWnd, builder, (uint)builder.Capacity);
var text = builder.ToString();
var className = clsName.ToString();
// There could be multiple handle associated with our pid,
// so we return the first handle that satisfy:
// 1) the handle title/ caption matches our window title,
// 2) the window class name starts with HwndWrapper (WPF specific)
// 3) the window has WS_EX_APPWINDOW style
if (title == text && className.StartsWith("HwndWrapper") && IsApplicationWindow(hWnd))
{
result = hWnd;
return false;
}
}
}
return true;
};
EnumDesktopWindows(IntPtr.Zero, enumerateHandle, 0);
return result;
}
答案 1 :(得分:-1)
感谢 IronGeek,这很棒。我只是在学习 c# 并努力让它工作了一段时间。我也不能“添加评论”,因为这里的声誉不足,因此这篇文章。我正在使用 WPF .Net 5.0。 我四处搜索以实现这一点,因此对于其他新手,他们还需要在他们的程序中使用以下内容来接收消息(抱歉,不确定我从哪个页面复制了这个,他们中的很多人(个人需要自己制作) Mainwindow_Loaded 事件处理程序)。
private void Mainwindow_Loaded_Event(object sender, RoutedEventArgs e)
{
hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
hwndSource.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SHOWWINDOW)
{
MessageBox.Show("I recieved WM_SHOWWINDOW");
handled = true;
}
return IntPtr.Zero;
}
在我的情况下也需要您提到的“带到前台”提示,这是需要的: (来自Bring Word to Front) 把它放在声明部分:
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
并将以下内容放在'SendMessage(handle, WM_SHOWWINDOW, IntPtr.Zero, new IntPtr(SW_PARENTOPENING));'之后声明:
SetForegroundWindow(handle);
没有这个,激活的窗口就会隐藏在其他窗口后面,必须通过在任务栏中手动钓鱼来找到它。 所以现在我终于得到了一个非隐藏窗口,但现在需要看看隐藏窗口需要什么,因为这是我的真正目标。
答案 2 :(得分:-1)
从我早期的帖子开始,并引用 IronGeek 的早期评论,问题是“如果进程没有与之关联的图形界面(隐藏/最小化),则 MainWindowHandle 值为零”。因此,任何传递隐藏窗口句柄的尝试都注定失败,因为它不存在。
所以我找到了一种解决方法,尽管它需要目标进程定期检查新消息的存在。因此这仍然不理想,但它在 2021 年对我有用(WPF,.Net 5.0)并且不需要导入 user32.dll。相反,它使用 MainWindowTitle 作为容器来执行一种临时类型的进程间通信 (IPC),以被动地发送消息。 MainWindowTitle 在运行时是可设置的,并且可以从其他进程查看,因此它可以像进程间变量一样使用。这是我下面的整个解决方案,注意需要将其发布到本地文件夹以查看其运行方式,因为要点是运行多个实例。
<Window x:Class="TitleComsTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TitleComsTest"
mc:Ignorable="d"
Name="TitleComsTest" Title="TitleComsTest" Height="400" Width="600"
WindowStartupLocation = "CenterScreen" Closing="TitleComsTest_Closing" Visibility="Hidden">
<Grid>
<TextBox Name ="TextBox1" HorizontalAlignment="Center" VerticalAlignment="Center" Height="230" Width="460"/>
<Button Name="QuitButton" Content=" Really Quit " HorizontalAlignment="Center" Margin="0,0,0,30" VerticalAlignment="Bottom" Click="QuitButton_Click"/>
</Grid>
背后的代码:
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
namespace TitleComsTest
{
public partial class MainWindow : Window
{
//string for MainWindowTitle when first instance and visible:
const string my_Visible_exe_Name = "TitleComstest";
//string for MainWindowTitle when first instance and Hidden:
const string my_Hidden_exe_Name = "TitleComstest...";
//string for MainWindowTitle when 2nd instance :
const string my_exe_Name_Flag = "TitleComstest, Please-Wait";
bool reallyCloseThisProgram = false;
private DispatcherTimer timer1, timer2;
public MainWindow()
{
InitializeComponent();
//Get an array of processes with the chosen name
Process[] TitleComstests = Process.GetProcessesByName(my_Visible_exe_Name);
if (TitleComstests.Length > 1)
{ //Then this is not the first instance
for (int i = 0; i < TitleComstests.Length; i++)
{
if (TitleComstests[i].MainWindowTitle == my_Visible_exe_Name)
{ //The first instance is visible as the MainWindowTitle has been set to the visible name
Close(); //Quit - nothing to do but close the new instance
}
}
//The first instance is hidden, so set MainWindowTitle so the first instance can see it and react
this.Title = my_exe_Name_Flag;
this.WindowState = WindowState.Minimized; //Minimize the window to avoid having two windows shown at once
this.Visibility = Visibility.Visible; //The second instance needs to be visible (minimized is enough) to be seen
StartTimerQuit(4000); //arbitrary time, needs to be longer than 2000ms which is the checking period - see StartTimerLook(2000);
}
else
{
TextBox1.Text = "This is Multi-instance demo using the 'MainWindowTitle' to send messages\r\nto the first (hidden) instance to wake it up.";
TextBox1.Text += "\r\n\r\nThis demo requires the program be published to a local folder and \r\nnot run in the debugger.";
TextBox1.Text += "\r\n\r\nYou can type here to mark this instance: _____________ \r\n\r\nand then hide me by clicking top right close window 'X'";
TextBox1.Text += "\r\n\r\nOnce closed then start the program again to see the 1st instance pop up.";
TextBox1.Text += "\r\n\r\nFinally use the 'Really Quit' button to end this demo.";
this.Visibility = Visibility.Visible;
}
}
private void StartTimerQuit(Int32 interval) //Timer to Quit setup and start
{
timer1 = new DispatcherTimer(); timer1.Tick += timerQuit_Tick;
timer1.Interval = new TimeSpan(0, 0, 0, 0, interval); timer1.Start();
}
private void timerQuit_Tick(object sender, EventArgs e)
{
reallyCloseThisProgram = true; Close();
}
private void TitleComsTest_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (!reallyCloseThisProgram)
{
e.Cancel = true;
this.Title = my_Hidden_exe_Name; //Set the Title text to flag a hidden state
this.Visibility = Visibility.Hidden;
//Start checking every 2 secs at the process names - could be faster but this is a constant background process
StartTimerLook(2000);
}
}
private void StartTimerLook(Int32 interval) //Timer to look for new instances setup and start
{
timer2 = new DispatcherTimer(); timer2.Tick += timerLook_Tick;
timer2.Interval = new TimeSpan(0, 0, 0, 0, interval); timer2.Start();
}
private void timerLook_Tick(object sender, EventArgs e)
{ //Every timer interval check to see if a process is present with the Ttile name flag
Process[] myNameFlagProcesses = Process.GetProcessesByName(my_Visible_exe_Name);
for (int i = 0; i < myNameFlagProcesses.Length; i++)
{
if (myNameFlagProcesses[i].MainWindowTitle == my_exe_Name_Flag) //If name flag is seen ...
{ //... then wake up
TextBox1.Text += "\r\n Saw the other window";
this.Visibility = Visibility.Visible;
this.Title = my_Visible_exe_Name; //Set the Title text to flag a visible state
this.Show();
this.Activate();
timer2.Stop();
}
}
}
private void QuitButton_Click(object sender, RoutedEventArgs e)
{
reallyCloseThisProgram = true; Close();
}
}
}