Files
home-PC f33c89d2c4 添加单实例限制、应用图标、移除托盘设置菜单、快捷键暂停/恢复功能
单实例限制:通过命名Mutex和窗口消息广播确保只能运行一个实例,再次启动时唤起已有窗口

应用图标:exe文件嵌入app.ico,托盘和窗口图标统一使用该图标文件

移除托盘右键菜单中与显示主界面重复的设置选项

快捷键暂停/恢复:托盘菜单和主界面侧边栏均添加切换按钮,通过MainViewModel.IsHotKeyEnabled双向同步
2026-05-10 14:10:52 +08:00

132 lines
4.5 KiB
C#

using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using PersonalToolBox.Services;
using PersonalToolBox.Views;
namespace PersonalToolBox;
/// <summary>
/// WPF 应用程序入口,负责依赖注入容器初始化和启动主窗口
/// </summary>
public partial class App : System.Windows.Application
{
public static IServiceProvider Services { get; private set; } = null!;
private static readonly string CrashLogPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"PersonalToolBox", "crash.log");
private const string AppMutexName = "PersonalToolBox_SingleInstance_8E2F4A1C";
private static Mutex? _mutex;
[DllImport("user32.dll")]
private static extern uint RegisterWindowMessage(string lpString);
[DllImport("user32.dll")]
private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
public App()
{
// 监听未处理的异常,写入文件日志
DispatcherUnhandledException += (s, e) =>
{
WriteCrashLog($"UI线程异常: {e.Exception}");
e.Handled = true;
MessageBox.Show($"发生未处理异常:\n{e.Exception.Message}\n\n详情已写入:\n{CrashLogPath}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
};
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
WriteCrashLog($"未处理异常: {e.ExceptionObject}");
};
}
protected override async void OnStartup(StartupEventArgs e)
{
_mutex = new Mutex(true, AppMutexName, out bool createdNew);
if (!createdNew)
{
uint msg = RegisterWindowMessage("PersonalToolBox_ShowMain");
PostMessage(HWND_BROADCAST, msg, IntPtr.Zero, IntPtr.Zero);
Shutdown();
return;
}
try
{
base.OnStartup(e);
var services = new ServiceCollection();
ConfigureServices(services);
Services = services.BuildServiceProvider();
// 启动时加载配置文件(含路径验证与容错)
var dataService = Services.GetRequiredService<IDataService>();
dataService.Load();
var mainWindow = Services.GetRequiredService<MainWindow>();
var mainViewModel = Services.GetRequiredService<ViewModels.MainViewModel>();
// -autostart 参数:开机自启时隐藏窗口
if (e.Args.Contains("-autostart", StringComparer.OrdinalIgnoreCase))
{
mainWindow.Hide();
}
else
{
mainWindow.Show();
}
// 启动标记为自动运行的工具和组合
await mainViewModel.ExecuteAutoRunToolsAsync();
}
catch (Exception ex)
{
WriteCrashLog($"启动失败: {ex}");
MessageBox.Show($"启动失败:\n{ex.Message}\n\n详情已写入:\n{CrashLogPath}",
"启动错误", MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown();
}
}
/// <summary>
/// 注册所有服务到 DI 容器
/// </summary>
private static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ILogService, LogService>();
services.AddSingleton<IDataService, JsonDataService>();
services.AddSingleton<IProcessExecutionService, ProcessExecutionService>();
services.AddSingleton<Helpers.HotKeyManager>();
services.AddSingleton<ViewModels.MainViewModel>();
services.AddTransient<ViewModels.ToolEditViewModel>();
services.AddTransient<ViewModels.CategoryEditViewModel>();
services.AddTransient<ViewModels.GroupEditViewModel>();
services.AddSingleton<MainWindow>();
}
/// <summary>
/// 将崩溃信息写入文件日志
/// </summary>
public static void WriteCrashLog(string message)
{
try
{
var dir = Path.GetDirectoryName(CrashLogPath);
if (dir != null) Directory.CreateDirectory(dir);
File.AppendAllText(CrashLogPath,
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}{Environment.NewLine}{Environment.NewLine}");
}
catch
{
// 无法写入日志时静默忽略
}
}
}