Phase 4: 全局快捷键拦截 (Win32 API)
- 新增 HotKeyManager (Helpers/), 使用 user32.dll RegisterHotKey/UnregisterHotKey 注册系统级快捷键 - 支持 Ctrl/Alt/Shift/Win 修饰键组合, 单字符键和命名键(F1-F12等)解析 - MainWindow.OnSourceInitialized 挂载 HwndSource.AddHook 拦截 WM_HOTKEY 消息 - 启动时自动注册所有含快捷键的工具, 工具增删改后自动重新注册 - 快捷键冲突时记录 Win32 错误码, 无效格式打印警告 - 新增 12 个 HotKeyManager 单元测试 (73 tests total)
This commit is contained in:
135
PersonalToolBox.Tests/Helpers/HotKeyManagerTests.cs
Normal file
135
PersonalToolBox.Tests/Helpers/HotKeyManagerTests.cs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
using Moq;
|
||||||
|
using PersonalToolBox.Helpers;
|
||||||
|
using PersonalToolBox.Models;
|
||||||
|
using PersonalToolBox.Services;
|
||||||
|
|
||||||
|
namespace PersonalToolBox.Tests.Helpers;
|
||||||
|
|
||||||
|
public class HotKeyManagerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void TryParseHotKey_ValidCtrlAltT_ReturnsCorrectModifiersAndVk()
|
||||||
|
{
|
||||||
|
var result = HotKeyManager.TryParseHotKey("Ctrl+Alt+T", out uint mod, out uint vk);
|
||||||
|
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.NotEqual(0u, mod);
|
||||||
|
// 'T' → VK = 0x54
|
||||||
|
Assert.Equal((uint)'T', vk);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryParseHotKey_ValidCtrlShiftF1_ReturnsCorrectVk()
|
||||||
|
{
|
||||||
|
var result = HotKeyManager.TryParseHotKey("Ctrl+Shift+F1", out uint mod, out uint vk);
|
||||||
|
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.NotEqual(0u, mod);
|
||||||
|
// F1 → VK_F1 = 0x70
|
||||||
|
Assert.Equal(0x70u, vk);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryParseHotKey_OnlyModifier_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var result = HotKeyManager.TryParseHotKey("Ctrl", out _, out _);
|
||||||
|
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryParseHotKey_OnlyKey_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var result = HotKeyManager.TryParseHotKey("T", out _, out _);
|
||||||
|
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryParseHotKey_Empty_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var result = HotKeyManager.TryParseHotKey("", out _, out _);
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryParseHotKey_Null_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var result = HotKeyManager.TryParseHotKey(null!, out _, out _);
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryParseHotKey_Whitespace_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var result = HotKeyManager.TryParseHotKey(" ", out _, out _);
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryParseHotKey_WinKey_B_ReturnsCorrectVk()
|
||||||
|
{
|
||||||
|
var result = HotKeyManager.TryParseHotKey("Win+B", out uint mod, out uint vk);
|
||||||
|
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.Equal((uint)'B', vk);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryParseHotKey_UnknownModifier_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var result = HotKeyManager.TryParseHotKey("Foo+T", out _, out _);
|
||||||
|
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RegisterAll_SkipsEmptyHotKeys()
|
||||||
|
{
|
||||||
|
var logMock = new Mock<ILogService>();
|
||||||
|
var processMock = new Mock<IProcessExecutionService>();
|
||||||
|
var manager = new HotKeyManager(logMock.Object, processMock.Object);
|
||||||
|
|
||||||
|
var tools = new[]
|
||||||
|
{
|
||||||
|
new ToolItem { Name = "NoHotkey", HotKey = "" },
|
||||||
|
new ToolItem { Name = "BlankHotkey", HotKey = " " }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Should not throw
|
||||||
|
manager.RegisterAll(tools);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RegisterAll_InvalidFormat_LogsWarning()
|
||||||
|
{
|
||||||
|
var logMock = new Mock<ILogService>();
|
||||||
|
var processMock = new Mock<IProcessExecutionService>();
|
||||||
|
var manager = new HotKeyManager(logMock.Object, processMock.Object);
|
||||||
|
|
||||||
|
// Initialize needs a hwnd, but RegisterAll calls Register which checks _hwnd
|
||||||
|
// So calling RegisterAll without Initialize should be a no-op
|
||||||
|
var tools = new[]
|
||||||
|
{
|
||||||
|
new ToolItem { Name = "Bad", HotKey = "not-a-valid-key" }
|
||||||
|
};
|
||||||
|
|
||||||
|
// hwnd is IntPtr.Zero, so should skip silently
|
||||||
|
manager.RegisterAll(tools);
|
||||||
|
|
||||||
|
// hwnd is zero, so Register is skipped entirely
|
||||||
|
logMock.Verify(x => x.Warning(It.IsAny<string>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryHandleMessage_NonWMHOTKEY_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var logMock = new Mock<ILogService>();
|
||||||
|
var processMock = new Mock<IProcessExecutionService>();
|
||||||
|
var manager = new HotKeyManager(logMock.Object, processMock.Object);
|
||||||
|
|
||||||
|
var handled = manager.TryHandleMessage(9999, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
|
||||||
|
Assert.False(handled);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Moq;
|
using Moq;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using PersonalToolBox.Helpers;
|
||||||
using PersonalToolBox.Models;
|
using PersonalToolBox.Models;
|
||||||
using PersonalToolBox.Services;
|
using PersonalToolBox.Services;
|
||||||
using PersonalToolBox.ViewModels;
|
using PersonalToolBox.ViewModels;
|
||||||
@@ -12,6 +13,7 @@ public class MainViewModelTests
|
|||||||
private readonly Mock<IDataService> _dataServiceMock = new();
|
private readonly Mock<IDataService> _dataServiceMock = new();
|
||||||
private readonly Mock<IProcessExecutionService> _processServiceMock = new();
|
private readonly Mock<IProcessExecutionService> _processServiceMock = new();
|
||||||
private readonly Mock<IServiceProvider> _serviceProviderMock = new();
|
private readonly Mock<IServiceProvider> _serviceProviderMock = new();
|
||||||
|
private readonly Mock<HotKeyManager> _hotKeyManagerMock;
|
||||||
private readonly AppConfig _config;
|
private readonly AppConfig _config;
|
||||||
|
|
||||||
public MainViewModelTests()
|
public MainViewModelTests()
|
||||||
@@ -33,11 +35,13 @@ public class MainViewModelTests
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_hotKeyManagerMock = new Mock<HotKeyManager>(_logServiceMock.Object, _processServiceMock.Object);
|
||||||
_dataServiceMock.Setup(d => d.Config).Returns(_config);
|
_dataServiceMock.Setup(d => d.Config).Returns(_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MainViewModel CreateViewModel() =>
|
private MainViewModel CreateViewModel() =>
|
||||||
new(_logServiceMock.Object, _dataServiceMock.Object, _processServiceMock.Object, _serviceProviderMock.Object);
|
new(_logServiceMock.Object, _dataServiceMock.Object, _processServiceMock.Object,
|
||||||
|
_serviceProviderMock.Object, _hotKeyManagerMock.Object);
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Constructor_LoadsCategories_IncludingAll()
|
public void Constructor_LoadsCategories_IncludingAll()
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ public partial class App : Application
|
|||||||
services.AddSingleton<ILogService, LogService>();
|
services.AddSingleton<ILogService, LogService>();
|
||||||
services.AddSingleton<IDataService, JsonDataService>();
|
services.AddSingleton<IDataService, JsonDataService>();
|
||||||
services.AddSingleton<IProcessExecutionService, ProcessExecutionService>();
|
services.AddSingleton<IProcessExecutionService, ProcessExecutionService>();
|
||||||
|
services.AddSingleton<Helpers.HotKeyManager>();
|
||||||
services.AddSingleton<ViewModels.MainViewModel>();
|
services.AddSingleton<ViewModels.MainViewModel>();
|
||||||
services.AddTransient<ViewModels.ToolEditViewModel>();
|
services.AddTransient<ViewModels.ToolEditViewModel>();
|
||||||
services.AddTransient<ViewModels.CategoryEditViewModel>();
|
services.AddTransient<ViewModels.CategoryEditViewModel>();
|
||||||
|
|||||||
174
PersonalToolBox/Helpers/HotKeyManager.cs
Normal file
174
PersonalToolBox/Helpers/HotKeyManager.cs
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using PersonalToolBox.Models;
|
||||||
|
using PersonalToolBox.Services;
|
||||||
|
|
||||||
|
namespace PersonalToolBox.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 全局快捷键管理器,使用 Win32 API RegisterHotKey / UnregisterHotKey
|
||||||
|
/// </summary>
|
||||||
|
public class HotKeyManager
|
||||||
|
{
|
||||||
|
private IntPtr _hwnd;
|
||||||
|
private int _nextId = 1;
|
||||||
|
private readonly Dictionary<int, ToolItem> _hotkeyMap = new();
|
||||||
|
private readonly ILogService _logService;
|
||||||
|
private readonly IProcessExecutionService _processService;
|
||||||
|
|
||||||
|
// ──────────────── Win32 API ────────────────
|
||||||
|
|
||||||
|
private const int WM_HOTKEY = 0x0312;
|
||||||
|
private const uint MOD_ALT = 0x0001;
|
||||||
|
private const uint MOD_CONTROL = 0x0002;
|
||||||
|
private const uint MOD_SHIFT = 0x0004;
|
||||||
|
private const uint MOD_WIN = 0x0008;
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
||||||
|
|
||||||
|
// ──────────────── 构造函数 ────────────────
|
||||||
|
|
||||||
|
public HotKeyManager(ILogService logService, IProcessExecutionService processService)
|
||||||
|
{
|
||||||
|
_logService = logService;
|
||||||
|
_processService = processService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置窗口句柄(在 MainWindow.OnSourceInitialized 中调用)
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize(IntPtr hwnd)
|
||||||
|
{
|
||||||
|
_hwnd = hwnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────── 注册/注销 ────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量注册所有工具的快捷键
|
||||||
|
/// </summary>
|
||||||
|
public void RegisterAll(IEnumerable<ToolItem> tools)
|
||||||
|
{
|
||||||
|
UnregisterAll();
|
||||||
|
foreach (var tool in tools.Where(t => !string.IsNullOrWhiteSpace(t.HotKey)))
|
||||||
|
Register(tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为单个工具注册快捷键
|
||||||
|
/// </summary>
|
||||||
|
public void Register(ToolItem tool)
|
||||||
|
{
|
||||||
|
if (_hwnd == IntPtr.Zero) return;
|
||||||
|
if (string.IsNullOrWhiteSpace(tool.HotKey)) return;
|
||||||
|
|
||||||
|
if (!TryParseHotKey(tool.HotKey, out uint modifiers, out uint vk))
|
||||||
|
{
|
||||||
|
_logService.Warning($"快捷键格式无效: {tool.HotKey}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int id = _nextId++;
|
||||||
|
if (RegisterHotKey(_hwnd, id, modifiers, vk))
|
||||||
|
{
|
||||||
|
_hotkeyMap[id] = tool;
|
||||||
|
_logService.Info($"已注册快捷键: {tool.HotKey} → {tool.Name}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int err = Marshal.GetLastWin32Error();
|
||||||
|
_logService.Error($"快捷键注册失败: {tool.HotKey} (错误码: {err},可能与其他程序冲突)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注销所有已注册的快捷键
|
||||||
|
/// </summary>
|
||||||
|
private void UnregisterAll()
|
||||||
|
{
|
||||||
|
foreach (var kvp in _hotkeyMap)
|
||||||
|
UnregisterHotKey(_hwnd, kvp.Key);
|
||||||
|
_hotkeyMap.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────── 消息处理 ────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 由 WndProc 钩子调用,处理 WM_HOTKEY 消息
|
||||||
|
/// </summary>
|
||||||
|
public bool TryHandleMessage(int msg, IntPtr wParam, IntPtr lParam)
|
||||||
|
{
|
||||||
|
if (msg != WM_HOTKEY) return false;
|
||||||
|
|
||||||
|
int id = wParam.ToInt32();
|
||||||
|
if (_hotkeyMap.TryGetValue(id, out var tool))
|
||||||
|
{
|
||||||
|
_logService.Info($"通过快捷键启动: {tool.Name}");
|
||||||
|
_processService.Execute(tool);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────── 快捷键解析 ────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析快捷键字符串(如 "Ctrl+Alt+T")为修饰键和虚拟键码
|
||||||
|
/// </summary>
|
||||||
|
public static bool TryParseHotKey(string hotkey, out uint modifiers, out uint vk)
|
||||||
|
{
|
||||||
|
modifiers = 0;
|
||||||
|
vk = 0;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(hotkey)) return false;
|
||||||
|
|
||||||
|
var parts = hotkey.Split('+', StringSplitOptions.TrimEntries);
|
||||||
|
if (parts.Length < 2) return false;
|
||||||
|
|
||||||
|
// 解析修饰键(最后一段之前的部分)
|
||||||
|
foreach (var part in parts.Take(parts.Length - 1))
|
||||||
|
{
|
||||||
|
uint mod = part.ToUpperInvariant() switch
|
||||||
|
{
|
||||||
|
"CTRL" or "CONTROL" => MOD_CONTROL,
|
||||||
|
"ALT" => MOD_ALT,
|
||||||
|
"SHIFT" => MOD_SHIFT,
|
||||||
|
"WIN" or "WINDOWS" => MOD_WIN,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mod == 0) return false;
|
||||||
|
modifiers |= mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifiers == 0) return false;
|
||||||
|
|
||||||
|
// 解析按键(最后一段)
|
||||||
|
var keyStr = parts.Last();
|
||||||
|
if (keyStr.Length == 1)
|
||||||
|
{
|
||||||
|
vk = char.ToUpperInvariant(keyStr[0]);
|
||||||
|
}
|
||||||
|
else if (Enum.TryParse<Key>(keyStr, true, out var key))
|
||||||
|
{
|
||||||
|
vk = (uint)KeyInterop.VirtualKeyFromKey(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序退出时清理所有快捷键
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
UnregisterAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
private readonly IDataService _dataService;
|
private readonly IDataService _dataService;
|
||||||
private readonly IProcessExecutionService _processService;
|
private readonly IProcessExecutionService _processService;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly HotKeyManager _hotKeyManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 全部分类的虚拟对象
|
/// 全部分类的虚拟对象
|
||||||
@@ -30,12 +31,14 @@ public partial class MainViewModel : ObservableObject
|
|||||||
ILogService logService,
|
ILogService logService,
|
||||||
IDataService dataService,
|
IDataService dataService,
|
||||||
IProcessExecutionService processService,
|
IProcessExecutionService processService,
|
||||||
IServiceProvider serviceProvider)
|
IServiceProvider serviceProvider,
|
||||||
|
HotKeyManager hotKeyManager)
|
||||||
{
|
{
|
||||||
_logService = logService;
|
_logService = logService;
|
||||||
_dataService = dataService;
|
_dataService = dataService;
|
||||||
_processService = processService;
|
_processService = processService;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
|
_hotKeyManager = hotKeyManager;
|
||||||
|
|
||||||
LoadData();
|
LoadData();
|
||||||
}
|
}
|
||||||
@@ -201,9 +204,20 @@ public partial class MainViewModel : ObservableObject
|
|||||||
// 恢复之前选中的分类(若仍存在),否则选中"全部"
|
// 恢复之前选中的分类(若仍存在),否则选中"全部"
|
||||||
SelectedCategory = Categories.FirstOrDefault(c => c.Id == previousCategoryId) ?? AllCategory;
|
SelectedCategory = Categories.FirstOrDefault(c => c.Id == previousCategoryId) ?? AllCategory;
|
||||||
ApplyFilter();
|
ApplyFilter();
|
||||||
|
|
||||||
|
// 工具列表变更后重新注册快捷键
|
||||||
|
RegisterAllHotKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ───────────────────────────── 初始化 ─────────────────────────────
|
// ───────────────────────────── 快捷键 ─────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册所有工具的全局快捷键(由 MainWindow.OnSourceInitialized 及数据变更后调用)
|
||||||
|
/// </summary>
|
||||||
|
public void RegisterAllHotKeys()
|
||||||
|
{
|
||||||
|
_hotKeyManager.RegisterAll(Tools);
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadData()
|
private void LoadData()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ using System.Collections.Specialized;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using PersonalToolBox.Helpers;
|
||||||
using PersonalToolBox.ViewModels;
|
using PersonalToolBox.ViewModels;
|
||||||
|
|
||||||
namespace PersonalToolBox.Views;
|
namespace PersonalToolBox.Views;
|
||||||
@@ -10,17 +12,45 @@ namespace PersonalToolBox.Views;
|
|||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
private readonly MainViewModel _viewModel;
|
private readonly MainViewModel _viewModel;
|
||||||
|
private readonly HotKeyManager _hotKeyManager;
|
||||||
|
|
||||||
public MainWindow(MainViewModel viewModel)
|
public MainWindow(MainViewModel viewModel, HotKeyManager hotKeyManager)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
_viewModel = viewModel;
|
_viewModel = viewModel;
|
||||||
|
_hotKeyManager = hotKeyManager;
|
||||||
DataContext = viewModel;
|
DataContext = viewModel;
|
||||||
|
|
||||||
viewModel.Logs.CollectionChanged += OnLogsCollectionChanged;
|
viewModel.Logs.CollectionChanged += OnLogsCollectionChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗口句柄就绪后注册全局快捷键
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnSourceInitialized(EventArgs e)
|
||||||
|
{
|
||||||
|
base.OnSourceInitialized(e);
|
||||||
|
|
||||||
|
var hwndSource = (HwndSource)PresentationSource.FromVisual(this)!;
|
||||||
|
_hotKeyManager.Initialize(hwndSource.Handle);
|
||||||
|
|
||||||
|
// 挂载 WndProc 钩子,拦截 WM_HOTKEY 消息
|
||||||
|
hwndSource.AddHook(WndProcHook);
|
||||||
|
|
||||||
|
// 注册所有已配置快捷键的工具
|
||||||
|
_viewModel.RegisterAllHotKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗口消息钩子
|
||||||
|
/// </summary>
|
||||||
|
private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||||
|
{
|
||||||
|
handled = _hotKeyManager.TryHandleMessage(msg, wParam, lParam);
|
||||||
|
return IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnLogsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
private void OnLogsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Action == NotifyCollectionChangedAction.Add && LogListBox.Items.Count > 0)
|
if (e.Action == NotifyCollectionChangedAction.Add && LogListBox.Items.Count > 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user