一个完整的代码示例,使用互斥体(Mutex)和命名管道(Named Pipes)来确保 WPF 应用程序是单实例运行的,并在已有实例存在时将其窗口置于前台。包括详细的注释。
1. 单实例检查器
SingleInstanceChecker.cs
using System;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace YourNamespace
{
public static class SingleInstanceChecker
{
private static Mutex _mutex;
private const string PipeName = "YourAppNameSingleInstancePipe";
// 检查应用程序是否是单实例
public static bool IsSingleInstance(string assemblyName = null)
{
if (string.IsNullOrEmpty(assemblyName))
{
assemblyName = Path.GetFileNameWithoutExtension(GetExecutablePathNative());
}
try
{
// 尝试创建互斥体
_mutex = new Mutex(true, assemblyName, out bool isNewInstance);
if (isNewInstance)
{
// 如果是新实例,启动命名管道服务器
Task.Run(() => StartPipeServer());
return true;
}
else
{
// 如果已有实例在运行,通知已有实例并返回 false
NotifyExistingInstance();
return false;
}
}
catch (Exception ex)
{
// 处理异常情况,例如记录日志等
Console.WriteLine(ex.Message);
return false;
}
}
// 启动命名管道服务器,监听新的实例请求
private static void StartPipeServer()
{
while (true)
{
using (var server = new NamedPipeServerStream(PipeName))
{
server.WaitForConnection();
using (var reader = new StreamReader(server))
{
var message = reader.ReadLine();
if (message == "BringToFront")
{
BringWindowToFront();
}
}
}
}
}
// 通知已有实例将其窗口置于前台
private static void NotifyExistingInstance()
{
using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.Out))
{
client.Connect();
using (var writer = new StreamWriter(client))
{
writer.WriteLine("BringToFront");
writer.Flush();
}
}
}
// 将已有实例的窗口置于前台
private static void BringWindowToFront()
{
IntPtr hWnd = GetForegroundWindow();
if (IsIconic(hWnd))
{
ShowWindowAsync(hWnd, SW_RESTORE);
}
SetForegroundWindow(hWnd);
}
// P/Invoke 声明,用于窗口操作
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
private static extern IntPtr GetForegroundWindow();
private const int SW_RESTORE = 9;
// 获取可执行文件路径
private static string GetExecutablePathNative()
{
var buffer = new StringBuilder(1024);
GetModuleFileName(IntPtr.Zero, buffer, buffer.Capacity);
return buffer.ToString();
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern uint GetModuleFileName(IntPtr hModule, StringBuilder lpFilename, int nSize);
}
}
2. App.xaml.cs
App.xaml.cs
using Prism.Ioc;
using Prism.Unity;
using YourNamespace.Views;
using System.Windows;
namespace YourNamespace
{
public partial class App : PrismApplication
{
// 启动时检查是否是单实例
protected override void OnStartup(StartupEventArgs e)
{
if (!SingleInstanceChecker.IsSingleInstance())
{
Current.Shutdown();
return;
}
base.OnStartup(e);
}
// 创建主窗口
protected override Window CreateShell()
{
return Container.Resolve<Shell>();
}
// 注册类型和服务
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// 注册 View 和 ViewModel
containerRegistry.RegisterForNavigation<LoginView, LoginViewModel>();
containerRegistry.RegisterForNavigation<MainView, MainViewModel>();
containerRegistry.RegisterSingleton<IEventAggregator, EventAggregator>();
}
}
}
3. Shell 窗口
Views/Shell.xaml
<pu:WindowX x:Class="YourNamespace.Views.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:pu="clr-namespace:Panuon.WPF.UI;assembly=Panuon.WPF.UI"
Title="Shell"
WindowStartupLocation="CenterScreen"
Height="{Binding ShellHeight}"
Width="{Binding ShellWidth}">
<Grid>
<!-- 定义区域 -->
<ContentControl prism:RegionManager.RegionName="ContentRegion"/>
</Grid>
</pu:WindowX>
Views/Shell.xaml.cs
using Prism.Ioc;
using System.Windows;
namespace YourNamespace.Views
{
public partial class Shell : Window
{
public Shell()
{
InitializeComponent();
this.DataContext = App.Current.Container.Resolve<ViewModels.ShellViewModel>();
}
}
}
4. 登录视图和主视图
Views/LoginView.xaml
<UserControl x:Class="YourNamespace.Views.LoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:pu="clr-namespace:Panuon.WPF.UI;assembly=Panuon.WPF.UI">
<Grid>
<!-- 登录界面的内容 -->
<TextBox Name="Username" Width="200" Margin="10" PlaceholderText="Username" Text="{Binding Username, Mode=TwoWay}"/>
<PasswordBox Name="Password" Width="200" Margin="10,40,10,10" Password="{Binding Password, Mode=TwoWay}"/>
<Button Name="LoginButton" Content="Login" Width="100" Height="30" VerticalAlignment="Bottom" HorizontalAlignment="Center" Command="{Binding LoginCommand}"/>
</Grid>
</UserControl>
Views/MainView.xaml
<UserControl x:Class="YourNamespace.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:pu="clr-namespace:Panuon.WPF.UI;assembly=Panuon.WPF.UI">
<Grid>
<!-- 主界面的内容 -->
<TextBlock Text="Welcome to the Main Window" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Button Content="Logout" Width="100" Height="30" VerticalAlignment="Bottom" HorizontalAlignment="Center" Command="{Binding LogoutCommand}"/>
</Grid>
</UserControl>
5. 对应的 ViewModel
ViewModels/LoginViewModel.cs
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using YourNamespace.Events;
namespace YourNamespace.ViewModels
{
public class LoginViewModel : BindableBase
{
private readonly IEventAggregator _eventAggregator;
public LoginViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
LoginCommand = new DelegateCommand(OnLogin);
}
private string _username;
public string Username
{
get { return _username; }
set { SetProperty(ref _username, value); }
}
private string _password;
public string Password
{
get { return _password; }
set { SetProperty(ref _password, value); }
}
public DelegateCommand LoginCommand { get; }
private void OnLogin()
{
// 实现登录验证逻辑
if (IsLoginSuccessful())
{
// 发布登录成功事件
_eventAggregator.GetEvent<LoginEvent>().Publish();
}
else
{
// 登录失败处理
}
}
private bool IsLoginSuccessful()
{
// 实现登录验证逻辑,这里仅作示例
return Username == "user" && Password == "password";
}
}
}
ViewModels/MainViewModel.cs
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using YourNamespace.Events;
namespace YourNamespace.ViewModels
{
public class MainViewModel : BindableBase
{
private readonly IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
LogoutCommand = new DelegateCommand(OnLogout);
}
public DelegateCommand LogoutCommand { get; }
private void OnLogout()
{
// 发布注销事件
_eventAggregator.GetEvent<LogoutEvent>().Publish();
}
}
}
ViewModels/ShellViewModel.cs
using Prism.Events;
using Prism.Mvvm;
using Prism.Regions;
using YourNamespace.Events;
namespace YourNamespace.ViewModels
{
public class ShellViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
private readonly IEventAggregator _eventAggregator;
private double _shellHeight;
private double _shellWidth;
public ShellViewModel(IRegionManager regionManager, IEventAggregator eventAggregator)
{
_regionManager = regionManager;
_eventAggregator = eventAggregator;
// 订阅登录和注销事件
_eventAggregator.GetEvent<LoginEvent>().Subscribe(OnLoginEvent);
_eventAggregator.GetEvent<LogoutEvent>().Subscribe(OnLogoutEvent);
// 初始化时导航到登录界面并设置窗口尺寸
NavigateToLogin();
}
public double ShellHeight
{
get => _shellHeight;
set => SetProperty(ref _shellHeight, value);
}
public double ShellWidth
{
get => _shellWidth;
set => SetProperty(ref _shellWidth, value);
}
private void OnLoginEvent()
{
// 登录成功后导航到主界面并调整窗口尺寸
NavigateToMain();
}
private void OnLogoutEvent()
{
// 注销后导航回登录界面并调整窗口尺寸
NavigateToLogin();
}
private void NavigateToLogin()
{
SetShellSize(300, 400);
_regionManager.RequestNavigate("ContentRegion", "LoginView");
}
private void NavigateToMain()
{
SetShellSize(600, 800);
_regionManager.RequestNavigate("ContentRegion", "MainView");
}
private void SetShellSize(double height, double width)
{
ShellHeight = height;
ShellWidth = width;
}
}
}
6. 定义 LoginEvent 和 LogoutEvent
Events/LoginEvent.cs
using Prism.Events;
namespace YourNamespace.Events
{
public class LoginEvent : PubSubEvent
{
}
}
Events/LogoutEvent.cs
using Prism.Events;
namespace YourNamespace.Events
{
public class LogoutEvent : PubSubEvent
{
}
}
7. 更新 App.xaml.cs
确保在 App.xaml.cs
中注册了所有必要的服务和视图模型。
App.xaml.cs
using Prism.Ioc;
using Prism.Unity;
using YourNamespace.Views;
using System.Windows;
namespace YourNamespace
{
public partial class App : PrismApplication
{
// 启动时检查是否是单实例
protected override void OnStartup(StartupEventArgs e)
{
if (!SingleInstanceChecker.IsSingleInstance())
{
Current.Shutdown();
return;
}
base.OnStartup(e);
}
// 创建主窗口
protected override Window CreateShell()
{
return Container.Resolve<Shell>();
}
// 注册类型和服务
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// 注册 View 和 ViewModel
containerRegistry.RegisterForNavigation<LoginView, LoginViewModel>();
containerRegistry.RegisterForNavigation<MainView, MainViewModel>();
containerRegistry.RegisterSingleton<IEventAggregator, EventAggregator>();
}
}
}
总结
通过上述完整的代码示例,你可以确保你的 WPF 应用程序是单实例运行的,并在已有实例存在时将其窗口置于前台。通过使用互斥体(Mutex)和命名管道(Named Pipes),可以实现更加可靠的单实例检查,并处理应用程序崩溃后的重启情况。同时,详细的注释可以帮助你更好地理解每个部分的功能和作用。