C# WPF 窗体传递消息


 




 对于存在窗体的WPF程序(或者说,起码在任务栏上有个图标,即ShowInTaskbar = true),互相传递消息是很容易的。

步骤:

1,寻找窗体的句柄

2,运用windows API: SendMessage或PostMessage

3,目标窗体收到消息

 

这里的代码,展示了一个APP不希望多开所以在启动时检查是否存在一个已经运行的进程。如果进程已经存在,则给对方发送消息并结束自身。

1.判断/获取已经运行的实例



C# WPF 窗体传递消息_ide

/// <summary>
/// 获取当前exe文件的是否已经在电脑上运行了一个实例,如果是,则返回那个实例的Process;如果否,就返回null;
/// 如果有窗体,可以通过process.MainWindowHandle获取窗体的句柄,如果没窗体(ShowInTaskbar = false),则句柄为空
/// </summary>
public static Process GetCurrentExeProcess()
{
Process targetProcess = null;

Process currentProcess = Process.GetCurrentProcess();
string exeName = string.Format("{0}.exe", currentProcess.ProcessName);
//PTZ.Tracer.AppLog.Info("$$$$$Get Current Process Successfully, current process exe: {0}", exeName);

try
{
Process[] aryProcess = Process.GetProcessesByName(currentProcess.ProcessName);

//PTZ.Tracer.AppLog.Info("$$$$$Get aryProcess Successfully, aryProcess count: {0}", aryProcess.Count());
foreach (Process process in aryProcess)
{
if (process.Id != currentProcess.Id && process.ProcessName == currentProcess.ProcessName)
{
targetProcess = process;
//PTZ.Tracer.AppLog.Info("$$$$$Get MainWindowHandle Successfully, hWnd: {0}", hWnd);
break;
}
}
}
catch (System.PlatformNotSupportedException pEx)
{
//PTZ.Tracer.AppLog.Error("$$$$$GetCurrentWindowsInstanceHandle exception:", pEx);
}
catch (System.InvalidOperationException iEx)
{
//PTZ.Tracer.AppLog.Error("$$$$$GetCurrentWindowsInstanceHandle exception:", iEx);
}
catch (System.ComponentModel.Win32Exception win32Ex)
{
//PTZ.Tracer.AppLog.Error("$$$$$GetCurrentWindowsInstanceHandle exception:", win32Ex);
}

return targetProcess;
}


C# WPF 窗体传递消息_ide


但是这个方法在某些奇怪权限设置的电脑上会遭遇UnauthorizedException,所以,如果只是想获取窗体句柄的话,还可以将上述方法的Try & Catch部分换成下面的代码:



C# WPF 窗体传递消息_ide

try
{
string wmiQueryString = string.Format("SELECT ProcessId, Handle,ExecutablePath,Name FROM Win32_Process WHERE ProcessId != {0} AND Name = '{1}'", currentProcess.Id, exeName);
using (var searcher = new ManagementObjectSearcher(wmiQueryString))
{
using (var results = searcher.Get())
{
ManagementObject mo = results.Cast<ManagementObject>().FirstOrDefault();
if (mo != null)
{
string s = (string)mo["ExecutablePath"];
string handleStr = (string)mo["Handle"];
string sss = (string)mo["Name"];
Tracing.Trace.Information("¥*¥ManagementObjectSearcher, name: {0} Handle: {1} Path: {2}", sss, handleStr, s);

int handle = int.Parse(handleStr);
Process p = Process.GetProcessById(handle);
hWnd = p.MainWindowHandle;
isRunExist = true;
Tracing.Trace.Information("¥*¥ManagementObjectSearcher, hWnd: {0}", hWnd);
}
}
}
}
catch (Exception e)
{
Tracing.Trace.Error("ManagementObjectSearcher exception:", e);
}


C# WPF 窗体传递消息_ide


2.注册WindowsAPI



C# WPF 窗体传递消息_ide

#region Dll Imports

[DllImport("user32.dll")]
static extern IntPtr PostMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);

public const uint WM_APP = 0x9112;////0x8001~0xBFFF,随意#endregion Dll Imports


C# WPF 窗体传递消息_ide


3.修改App.OnStartup方法

当程序启动,就需要检测实例。如已有实例运行,则发送消息;如没有,则继续正常运行。所以我们需要修改Appxaml.cs的OnStartup方法:



C# WPF 窗体传递消息_ide



C# WPF 窗体传递消息_ide

protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

Process targetProcess = SingleExeHelper.GetCurrentExeProcess();

if (targetProcess!=null)//Software has been running...
{
//currentHandle通过targetProcess.MainWindowHandle得到
PostMessage(currentHandle,WM_APP, IntPtr.Zero, IntPtr.Zero);
Environment.Exit(0);//Software has been running, close this
}
else
{
//do nothing
}
}


C# WPF 窗体传递消息_ide


 


C# WPF 窗体传递消息_ide


 

4.主窗体监听消息:



C# WPF 窗体传递消息_ide

protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
((HwndSource)PresentationSource.FromVisual(this)).AddHook(myHook);

//HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
//source.AddHook(new HwndSourceHook(myHook));
}

private IntPtr myHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
int m = (int)App.WM_APP;
if (msg == m)
{
System.Windows.MessageBox.Show("haha");
}
return IntPtr.Zero;
}


C# WPF 窗体传递消息_ide



 对于存在窗体的WPF程序(或者说,起码在任务栏上有个图标,即ShowInTaskbar = true),互相传递消息是很容易的。

步骤:

1,寻找窗体的句柄

2,运用windows API: SendMessage或PostMessage

3,目标窗体收到消息

 

这里的代码,展示了一个APP不希望多开所以在启动时检查是否存在一个已经运行的进程。如果进程已经存在,则给对方发送消息并结束自身。

1.判断/获取已经运行的实例



C# WPF 窗体传递消息_ide

/// <summary>
/// 获取当前exe文件的是否已经在电脑上运行了一个实例,如果是,则返回那个实例的Process;如果否,就返回null;
/// 如果有窗体,可以通过process.MainWindowHandle获取窗体的句柄,如果没窗体(ShowInTaskbar = false),则句柄为空
/// </summary>
public static Process GetCurrentExeProcess()
{
Process targetProcess = null;

Process currentProcess = Process.GetCurrentProcess();
string exeName = string.Format("{0}.exe", currentProcess.ProcessName);
//PTZ.Tracer.AppLog.Info("$$$$$Get Current Process Successfully, current process exe: {0}", exeName);

try
{
Process[] aryProcess = Process.GetProcessesByName(currentProcess.ProcessName);

//PTZ.Tracer.AppLog.Info("$$$$$Get aryProcess Successfully, aryProcess count: {0}", aryProcess.Count());
foreach (Process process in aryProcess)
{
if (process.Id != currentProcess.Id && process.ProcessName == currentProcess.ProcessName)
{
targetProcess = process;
//PTZ.Tracer.AppLog.Info("$$$$$Get MainWindowHandle Successfully, hWnd: {0}", hWnd);
break;
}
}
}
catch (System.PlatformNotSupportedException pEx)
{
//PTZ.Tracer.AppLog.Error("$$$$$GetCurrentWindowsInstanceHandle exception:", pEx);
}
catch (System.InvalidOperationException iEx)
{
//PTZ.Tracer.AppLog.Error("$$$$$GetCurrentWindowsInstanceHandle exception:", iEx);
}
catch (System.ComponentModel.Win32Exception win32Ex)
{
//PTZ.Tracer.AppLog.Error("$$$$$GetCurrentWindowsInstanceHandle exception:", win32Ex);
}

return targetProcess;
}


C# WPF 窗体传递消息_ide


但是这个方法在某些奇怪权限设置的电脑上会遭遇UnauthorizedException,所以,如果只是想获取窗体句柄的话,还可以将上述方法的Try & Catch部分换成下面的代码:



C# WPF 窗体传递消息_ide

try
{
string wmiQueryString = string.Format("SELECT ProcessId, Handle,ExecutablePath,Name FROM Win32_Process WHERE ProcessId != {0} AND Name = '{1}'", currentProcess.Id, exeName);
using (var searcher = new ManagementObjectSearcher(wmiQueryString))
{
using (var results = searcher.Get())
{
ManagementObject mo = results.Cast<ManagementObject>().FirstOrDefault();
if (mo != null)
{
string s = (string)mo["ExecutablePath"];
string handleStr = (string)mo["Handle"];
string sss = (string)mo["Name"];
Tracing.Trace.Information("¥*¥ManagementObjectSearcher, name: {0} Handle: {1} Path: {2}", sss, handleStr, s);

int handle = int.Parse(handleStr);
Process p = Process.GetProcessById(handle);
hWnd = p.MainWindowHandle;
isRunExist = true;
Tracing.Trace.Information("¥*¥ManagementObjectSearcher, hWnd: {0}", hWnd);
}
}
}
}
catch (Exception e)
{
Tracing.Trace.Error("ManagementObjectSearcher exception:", e);
}


C# WPF 窗体传递消息_ide


2.注册WindowsAPI



C# WPF 窗体传递消息_ide

#region Dll Imports

[DllImport("user32.dll")]
static extern IntPtr PostMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);

public const uint WM_APP = 0x9112;////0x8001~0xBFFF,随意#endregion Dll Imports


C# WPF 窗体传递消息_ide


3.修改App.OnStartup方法

当程序启动,就需要检测实例。如已有实例运行,则发送消息;如没有,则继续正常运行。所以我们需要修改Appxaml.cs的OnStartup方法:



C# WPF 窗体传递消息_ide



C# WPF 窗体传递消息_ide

protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

Process targetProcess = SingleExeHelper.GetCurrentExeProcess();

if (targetProcess!=null)//Software has been running...
{
//currentHandle通过targetProcess.MainWindowHandle得到
PostMessage(currentHandle,WM_APP, IntPtr.Zero, IntPtr.Zero);
Environment.Exit(0);//Software has been running, close this
}
else
{
//do nothing
}
}


C# WPF 窗体传递消息_ide


 


C# WPF 窗体传递消息_ide


 

4.主窗体监听消息:



C# WPF 窗体传递消息_ide

protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
((HwndSource)PresentationSource.FromVisual(this)).AddHook(myHook);

//HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
//source.AddHook(new HwndSourceHook(myHook));
}

private IntPtr myHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
int m = (int)App.WM_APP;
if (msg == m)
{
System.Windows.MessageBox.Show("haha");
}
return IntPtr.Zero;
}


C# WPF 窗体传递消息_ide