一个完整的代码示例,使用互斥体(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 |
| { |
| |
| 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); |
| } |
| |
| |
| [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) |
| { |
| |
| 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) |
| { |
| |
| containerRegistry.RegisterForNavigation<LoginView, LoginViewModel>(); |
| containerRegistry.RegisterForNavigation<MainView, MainViewModel>(); |
| containerRegistry.RegisterSingleton<IEventAggregator, EventAggregator>(); |
| } |
| } |
| } |
| |
总结
通过上述完整的代码示例,你可以确保你的 WPF 应用程序是单实例运行的,并在已有实例存在时将其窗口置于前台。通过使用互斥体(Mutex)和命名管道(Named Pipes),可以实现更加可靠的单实例检查,并处理应用程序崩溃后的重启情况。同时,详细的注释可以帮助你更好地理解每个部分的功能和作用。