一个完整的代码示例,使用互斥体(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),可以实现更加可靠的单实例检查,并处理应用程序崩溃后的重启情况。同时,详细的注释可以帮助你更好地理解每个部分的功能和作用。