在。net下使用c#和WPF(而不是Windows窗体或控制台),创建一个只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。
代码还需要通知已经运行的实例,用户试图启动第二个实例,如果存在命令行参数,还可能传递任何命令行参数。
在。net下使用c#和WPF(而不是Windows窗体或控制台),创建一个只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。
代码还需要通知已经运行的实例,用户试图启动第二个实例,如果存在命令行参数,还可能传递任何命令行参数。
当前回答
一个新的使用互斥和IPC的东西,也传递任何命令行参数到运行的实例,是WPF单实例应用程序。
其他回答
更新2017-01-25。在尝试了一些东西之后,我决定使用VisualBasic.dll,它更容易,工作效果更好(至少对我来说)。我让我之前的答案只是作为参考…
只是作为参考,这是我如何不传递参数(我找不到任何理由这样做…我指的是带有参数的单个应用程序,这些参数可以从一个实例传递到另一个实例)。 如果需要文件关联,那么应用程序应该(根据用户的标准期望)为每个文档实例化。如果你必须传递args到现有的应用程序,我想我会使用vb dll。
不传递参数(只是单实例应用程序),我更喜欢不注册一个新的窗口消息,不覆盖Matt Davis解决方案中定义的消息循环。虽然添加一个VisualBasic dll不是一个大问题,但我不喜欢添加一个新的引用只是做单个实例应用程序。此外,我更喜欢用Main实例化一个新类,而不是调用Shutdown from app. startup重写以确保尽快退出。
希望大家都喜欢……或者会启发一点:-)
项目启动类应该设置为“SingleInstanceApp”。
public class SingleInstanceApp
{
[STAThread]
public static void Main(string[] args)
{
Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");
if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
{
try
{
var app = new App();
app.InitializeComponent();
app.Run();
}
finally
{
_mutexSingleInstance.ReleaseMutex();
_mutexSingleInstance.Close();
}
}
else
{
MessageBox.Show("One instance is already running.");
var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
{
if (processes.Length > 1)
{
foreach (var process in processes)
{
if (process.Id != Process.GetCurrentProcess().Id)
{
WindowHelper.SetForegroundWindow(process.MainWindowHandle);
}
}
}
}
}
}
}
WindowHelper:
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
namespace HQ.Util.Unmanaged
{
public class WindowHelper
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
这就是我最终处理这个问题的方式。注意,调试代码仍然在那里进行测试。这段代码在App.xaml.cs文件的OnStartup中。(WPF)
// Process already running ?
if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1)
{
// Show your error message
MessageBox.Show("xxx is already running. \r\n\r\nIf the original process is hung up you may need to restart your computer, or kill the current xxx process using the task manager.", "xxx is already running!", MessageBoxButton.OK, MessageBoxImage.Exclamation);
// This process
Process currentProcess = Process.GetCurrentProcess();
// Get all processes running on the local computer.
Process[] localAll = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
// ID of this process...
int temp = currentProcess.Id;
MessageBox.Show("This Process ID: " + temp.ToString());
for (int i = 0; i < localAll.Length; i++)
{
// Find the other process
if (localAll[i].Id != currentProcess.Id)
{
MessageBox.Show("Original Process ID (Switching to): " + localAll[i].Id.ToString());
// Switch to it...
SetForegroundWindow(localAll[i].MainWindowHandle);
}
}
Application.Current.Shutdown();
}
这可能有我还没有发现的问题。如果我遇到了,我会更新我的答案。
请检查这里提出的解决方案,它使用信号量来确定现有实例是否已经在运行,适用于WPF应用程序,并且可以通过使用TcpListener和TcpClient将参数从第二个实例传递给第一个已经运行的实例:
它不仅适用于。net Framework,也适用于。net Core。
简单地使用一个StreamWriter,怎么样?
System.IO.File.StreamWriter OpenFlag = null; //globally
and
try
{
OpenFlag = new StreamWriter(Path.GetTempPath() + "OpenedIfRunning");
}
catch (System.IO.IOException) //file in use
{
Environment.Exit(0);
}
你可以使用Mutex类,但是你很快就会发现你需要自己实现传递参数的代码。当我读Chris Sell的书时,我学到了一个用WinForms编程的技巧。这个技巧使用了框架中已经可用的逻辑。我不知道你怎么想,但当我了解到可以在框架中重用的东西时,这通常是我采取的路线,而不是重新发明轮子。当然,除非它不能做到我想要的一切。
当我进入WPF时,我想到了一种使用相同代码的方法,但在WPF应用程序中。基于您的问题,这个解决方案应该能够满足您的需求。
首先,我们需要创建应用程序类。在这个类中,我们将重写OnStartup事件并创建一个名为Activate的方法,该方法将在稍后使用。
public class SingleInstanceApplication : System.Windows.Application
{
protected override void OnStartup(System.Windows.StartupEventArgs e)
{
// Call the OnStartup event on our base class
base.OnStartup(e);
// Create our MainWindow and show it
MainWindow window = new MainWindow();
window.Show();
}
public void Activate()
{
// Reactivate the main window
MainWindow.Activate();
}
}
Second, we will need to create a class that can manage our instances. Before we go through that, we are actually going to reuse some code that is in the Microsoft.VisualBasic assembly. Since, I am using C# in this example, I had to make a reference to the assembly. If you are using VB.NET, you don't have to do anything. The class we are going to use is WindowsFormsApplicationBase and inherit our instance manager off of it and then leverage properties and events to handle the single instancing.
public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
{
private SingleInstanceApplication _application;
private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;
public SingleInstanceManager()
{
IsSingleInstance = true;
}
protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
{
// First time _application is launched
_commandLine = eventArgs.CommandLine;
_application = new SingleInstanceApplication();
_application.Run();
return false;
}
protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
{
// Subsequent launches
base.OnStartupNextInstance(eventArgs);
_commandLine = eventArgs.CommandLine;
_application.Activate();
}
}
基本上,我们使用VB位来检测单个实例并进行相应的处理。OnStartup将在第一个实例加载时被触发。当应用程序再次运行时,OnStartupNextInstance被触发。如您所见,我可以通过事件参数获得在命令行上传递的内容。我将值设置为一个实例字段。您可以在这里解析命令行,也可以通过构造函数和对Activate方法的调用将它传递给应用程序。
第三,是时候创建我们的入口点了。我们将利用SingleInstanceManager,而不是像通常那样更新应用程序。
public class EntryPoint
{
[STAThread]
public static void Main(string[] args)
{
SingleInstanceManager manager = new SingleInstanceManager();
manager.Run(args);
}
}
好吧,我希望您能够理解所有内容,能够使用这个实现并使其成为您自己的实现。