允许 Windows 应用程序只有一个实例, 并最小化或隐藏窗口
本文展示了如何保持应用程序只有一个实例,以及如何在应用程序被最小化或隐藏时将其弹出,例如通过托盘中的通知图标。
引言
首先,我必须说,我犹豫了很长时间才决定写这篇文章。已经有很多文章在做类似的事情了。那么我为什么要写呢?嗯,似乎其他解决方案可以找到应用程序的现有实例,但我没有看到很多示例能够在应用程序被最小化或隐藏时将其弹出。
背景
几年前,当我比现在更频繁地使用 Delphi 编程时,我有一个不错的单元,帮助我确保在用户的本地机器上只运行一个应用程序实例。所以我决定尝试在 .NET 中做一些类似的事情。我的解决方案中唯一有趣的地方是,如果表单被最小化或隐藏(例如在托盘图标中),它会将其弹出。
解决方案
首先,我要告诉你,我不知道任何纯 .NET 解决方案。你必须调用 API 才能使用 Windows 消息传递。我使用 Mutex 来查看应用程序是否已经在运行。然后,我使用 Windows 消息传递来告诉已经运行的应用程序显示自身,并在最小化时恢复到正常窗口状态。
代码
如果您从未见过如何导入 DLL 从而进行 Windows API 调用,以下是代码的样子,以便我们可以发送 Windows 消息
//C#
[DllImport("USER32.DLL", EntryPoint="BroadcastSystemMessageA",
SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern int BroadcastSystemMessage(Int32 dwFlags, ref Int32
pdwRecipients, int uiMessage, int wParam, int lParam);
' VB.net
<DLLIMPORT("USER32.DLL", EntryPoint:="BroadcastSystemMessageA", _
SetLastError:=True, CharSet:=CharSet.Unicode, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall> _
Public Shared Function BroadcastSystemMessage(ByVal dwFlags As Int32, _
ByRef pdwRecipients As Int32, ByVal uiMessage As Integer, _
ByVal wParam As Integer, ByVal lParam As Integer) As Integer
' Leave function empty - DLLImport attribute forwards calls to
' BroadcastSystemMessage to
' BroadcastSystemMessage in USER32.DLL.
End Function
接下来,我们有在表单的 OnLoad
事件中调用的方法。
//C#
private void CheckPrevious()
{
//Check for previous instance of this app
m_uniqueIdentifier = Application.ExecutablePath.Replace(@"\", "_");
m_Mutex = new System.Threading.Mutex(false, m_uniqueIdentifier);
//First register the windows message
MessageId = RegisterWindowMessage(m_uniqueIdentifier);
if (m_Mutex.WaitOne(1, true))
{
//we are the first instance don't need to do anything
}
else
{
//Cause the current form to show
//Now brodcast a message to cause the first instance to show up
Int32 BSMRecipients = BSM_APPLICATIONS; //Only go to applications
Int32 tmpuint32 = 0;
tmpuint32 = tmpuint32 | BSF_IGNORECURRENTTASK; //Ignore current app
tmpuint32 = tmpuint32 | BSF_POSTMESSAGE; //Post the windows message
int ret = BroadcastSystemMessage(tmpuint32, ref BSMRecipients,
MessageId, 0, 0);
//A differnt instance already exists exit now.
Application.Exit();
} //else
}
' VB.net
Private Sub checkprevious()
'Check for previous instance of this app
m_uniqueIdentifier = Application.ExecutablePath.Replace("\", "_")
m_Mutex = New System.Threading.Mutex(False, m_uniqueIdentifier)
'First register the windows message
MessageId = RegisterWindowMessage(m_uniqueIdentifier)
If m_Mutex.WaitOne(1, True) Then
'we are the first instance don't need to do anything
Else
'Cause the current form to show
'Now brodcast a message to cause the first instance to show up
Dim BSMRecipients As Int32 = BSM_APPLICATIONS 'Only go to applications
Dim tmpuint32 As Int32 = 0
tmpuint32 = tmpuint32 Or BSF_IGNORECURRENTTASK 'Ignore current app
tmpuint32 = tmpuint32 Or BSF_POSTMESSAGE 'Post the windows message
Dim ret As Integer
ret = BroadcastSystemMessage(tmpuint32, BSMRecipients, MessageId, 0, 0)
'A differnt instance already exists exit now.
Application.Exit()
End If
End Sub
最后,我们有检查表单 Windows 消息的方法。注意:您在重写此方法时必须非常小心。所有发送到此应用程序的 Windows 消息都将通过此方法传递。
//C#
protected override void DefWndProc(ref System.Windows.Forms.Message m)
{
//This overrides the windows messaging processing
if (m.Msg == MessageId) //If we found our message then activate
{
// Set the WindowState to normal if the form is minimized.
if (this.WindowState == FormWindowState.Minimized)
{
this.Show();
this.WindowState = FormWindowState.Normal;
}
// Activate the form.
this.Activate();
this.Focus();
}
else //Let the normal windows messaging process it.
{
base.DefWndProc(ref m);
}
}
' VB.net
Protected Overrides Sub DefWndProc(ByRef m As System.Windows.Forms.Message)
'This overrides the windows messaging processing
If m.Msg = MessageId Then 'If we found our message then activate
' Set the WindowState to normal if the form is minimized.
If (Me.WindowState = FormWindowState.Minimized) Then
Me.Show()
Me.WindowState = FormWindowState.Normal
End If
' Activate the form.
Me.Activate()
Me.Focus()
Else 'Let the normal windows messaging process it.
MyBase.DefWndProc(m)
End If
End Sub
结论
所以这是一个相当直接的解决方案。我希望对某人有所帮助。我在 Code Project 上看到了一些帖子,人们正在询问这个问题的解决方案,所以我决定还是写这篇文章。