diff --git a/PersonalToolBox/App.xaml.cs b/PersonalToolBox/App.xaml.cs index 8041e55..f540666 100644 --- a/PersonalToolBox/App.xaml.cs +++ b/PersonalToolBox/App.xaml.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Runtime.InteropServices; +using System.Threading; using System.Windows; using Microsoft.Extensions.DependencyInjection; using PersonalToolBox.Services; @@ -17,6 +19,17 @@ public partial class App : System.Windows.Application 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() { // 监听未处理的异常,写入文件日志 @@ -36,6 +49,15 @@ public partial class App : System.Windows.Application 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); diff --git a/PersonalToolBox/Helpers/HotKeyManager.cs b/PersonalToolBox/Helpers/HotKeyManager.cs index 02da5b2..61651b5 100644 --- a/PersonalToolBox/Helpers/HotKeyManager.cs +++ b/PersonalToolBox/Helpers/HotKeyManager.cs @@ -16,6 +16,8 @@ public class HotKeyManager private readonly ILogService _logService; private readonly IProcessExecutionService _processService; + public bool IsEnabled { get; set; } = true; + // ──────────────── Win32 API ──────────────── private const int WM_HOTKEY = 0x0312; @@ -103,6 +105,7 @@ public class HotKeyManager public bool TryHandleMessage(int msg, IntPtr wParam, IntPtr lParam) { if (msg != WM_HOTKEY) return false; + if (!IsEnabled) return true; int id = wParam.ToInt32(); if (_hotkeyMap.TryGetValue(id, out var tool)) diff --git a/PersonalToolBox/PersonalToolBox.csproj b/PersonalToolBox/PersonalToolBox.csproj index 5c2981e..f15fd21 100644 --- a/PersonalToolBox/PersonalToolBox.csproj +++ b/PersonalToolBox/PersonalToolBox.csproj @@ -7,6 +7,7 @@ disable true true + Resources\app.ico @@ -16,4 +17,10 @@ + + + PreserveNewest + + + diff --git a/PersonalToolBox/Resources/app.ico b/PersonalToolBox/Resources/app.ico new file mode 100644 index 0000000..f3aeb5f Binary files /dev/null and b/PersonalToolBox/Resources/app.ico differ diff --git a/PersonalToolBox/ViewModels/MainViewModel.cs b/PersonalToolBox/ViewModels/MainViewModel.cs index e669172..cffc297 100644 --- a/PersonalToolBox/ViewModels/MainViewModel.cs +++ b/PersonalToolBox/ViewModels/MainViewModel.cs @@ -66,6 +66,9 @@ public partial class MainViewModel : ObservableObject [ObservableProperty] private bool _isAutoStart; + [ObservableProperty] + private bool _isHotKeyEnabled = true; + // ───────────────────────────── 命令 ───────────────────────────── [RelayCommand] @@ -94,6 +97,15 @@ public partial class MainViewModel : ObservableObject _logService.Info(IsAutoStart ? "已启用开机自启动" : "已关闭开机自启动"); } + [RelayCommand] + private void ToggleHotKey() + { + IsHotKeyEnabled = !IsHotKeyEnabled; + _hotKeyManager.IsEnabled = IsHotKeyEnabled; + var status = IsHotKeyEnabled ? "已恢复" : "已暂停"; + _logService.Info($"{status}快捷键功能"); + } + /// /// 供 MainWindow 外部调用,用于输出提示信息 /// diff --git a/PersonalToolBox/Views/MainWindow.xaml b/PersonalToolBox/Views/MainWindow.xaml index 21c02e5..5499187 100644 --- a/PersonalToolBox/Views/MainWindow.xaml +++ b/PersonalToolBox/Views/MainWindow.xaml @@ -178,6 +178,7 @@ + + + diff --git a/PersonalToolBox/Views/MainWindow.xaml.cs b/PersonalToolBox/Views/MainWindow.xaml.cs index 951be3f..ac640d2 100644 --- a/PersonalToolBox/Views/MainWindow.xaml.cs +++ b/PersonalToolBox/Views/MainWindow.xaml.cs @@ -3,10 +3,13 @@ using System.Collections.Specialized; using System.ComponentModel; using System.Drawing; using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Data; using System.Windows.Forms; using System.Windows.Interop; +using System.Windows.Media.Imaging; using FontAwesome.Sharp; using PersonalToolBox.Helpers; using PersonalToolBox.ViewModels; @@ -18,8 +21,14 @@ public partial class MainWindow : Window private readonly MainViewModel _viewModel; private readonly HotKeyManager _hotKeyManager; private NotifyIcon? _notifyIcon; + private ToolStripMenuItem? _hotKeyToggleMenuItem; private bool _canActuallyClose; + [DllImport("user32.dll")] + private static extern uint RegisterWindowMessage(string lpString); + + private readonly int _showMainMsg; + public MainWindow(MainViewModel viewModel, HotKeyManager hotKeyManager) { InitializeComponent(); @@ -28,7 +37,18 @@ public partial class MainWindow : Window _hotKeyManager = hotKeyManager; DataContext = viewModel; + _showMainMsg = (int)RegisterWindowMessage("PersonalToolBox_ShowMain"); + + var iconPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "app.ico"); + if (File.Exists(iconPath)) + { + using var stream = File.OpenRead(iconPath); + var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + this.Icon = decoder.Frames[0]; + } + viewModel.Logs.CollectionChanged += OnLogsCollectionChanged; + viewModel.PropertyChanged += OnViewModelPropertyChanged; CreateTrayIcon(); } @@ -53,9 +73,14 @@ public partial class MainWindow : Window /// private void CreateTrayIcon() { + var iconPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "app.ico"); + var trayIcon = File.Exists(iconPath) + ? new System.Drawing.Icon(iconPath) + : System.Drawing.SystemIcons.Application; + _notifyIcon = new NotifyIcon { - Icon = CreateAppIcon(), + Icon = trayIcon, Visible = true, Text = "个人工具箱" }; @@ -68,26 +93,17 @@ public partial class MainWindow : Window var menu = new ContextMenuStrip(); menu.Items.Add("显示主界面", null, (_, _) => ShowMainWindow()); - menu.Items.Add("设置", null, (_, _) => OpenSettings()); + + _hotKeyToggleMenuItem = new ToolStripMenuItem( + _viewModel.IsHotKeyEnabled ? "暂停快捷键功能" : "恢复快捷键功能"); + _hotKeyToggleMenuItem.Click += (_, _) => _viewModel.ToggleHotKeyCommand.Execute(null); + menu.Items.Add(_hotKeyToggleMenuItem); + menu.Items.Add(new ToolStripSeparator()); menu.Items.Add("彻底退出", null, (_, _) => ExitApplication()); _notifyIcon.ContextMenuStrip = menu; } - /// - /// 程序化生成 32x32 托盘图标 - /// - private static System.Drawing.Icon CreateAppIcon() - { - var bmp = new System.Drawing.Bitmap(32, 32); - using var g = System.Drawing.Graphics.FromImage(bmp); - g.Clear(System.Drawing.Color.FromArgb(30, 30, 46)); - using var font = new System.Drawing.Font(System.Drawing.FontFamily.GenericSansSerif, 16, System.Drawing.FontStyle.Bold); - using var brush = new System.Drawing.SolidBrush(System.Drawing.Color.FromArgb(137, 180, 250)); - g.DrawString("T", font, brush, new System.Drawing.PointF(6, 3)); - return System.Drawing.Icon.FromHandle(bmp.GetHicon()); - } - /// /// 左键托盘图标:切换窗口显示/隐藏 /// @@ -109,15 +125,6 @@ public partial class MainWindow : Window Activate(); } - /// - /// 打开设置(当前无额外设置界面,提示使用内置功能) - /// - private void OpenSettings() - { - ShowMainWindow(); - _viewModel.LogServiceMessage("设置功能可通过左侧栏管理分类和主题切换"); - } - /// /// 彻底退出程序 /// @@ -146,10 +153,26 @@ public partial class MainWindow : Window private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { + if (msg == _showMainMsg) + { + Dispatcher.Invoke(() => ShowMainWindow()); + handled = true; + return IntPtr.Zero; + } + handled = _hotKeyManager.TryHandleMessage(msg, wParam, lParam); return IntPtr.Zero; } + private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(MainViewModel.IsHotKeyEnabled) && _hotKeyToggleMenuItem != null) + { + bool enabled = _viewModel.IsHotKeyEnabled; + _hotKeyToggleMenuItem.Text = enabled ? "暂停快捷键功能" : "恢复快捷键功能"; + } + } + private void OnLogsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add && LogListBox.Items.Count > 0) diff --git a/app.ico b/app.ico new file mode 100644 index 0000000..0461a17 Binary files /dev/null and b/app.ico differ