Files
personal-toolbox/PersonalToolBox/Views/MainWindow.xaml.cs
home-PC 599964f078 Phase 5: 系统托盘与开机自启
- 系统托盘: NotifyIcon 常驻, 程序化生成 32x32 图标, 左键切换显示/隐藏, 右键菜单(显示主界面/设置/彻底退出)

- 窗口关闭拦截: OnClosing 取消关闭改为 Hide(), 退出菜单执行真实 Shutdown()

- 开机自启: AutoStartHelper 写入/删除 HKCU\...\Run 注册表项, UI 开关按钮显示 ✔/⊗ 状态

- 静默启动: -autostart 参数下 mainWindow.Hide() 直接最小化到托盘

- 类型冲突解决: ImplicitUsings=disable + GlobalUsings.cs 统一全局 using, 消除 WPF/WinForms 共享类型冲突

- 测试: 75 tests total (含 AutoStartHelper 2 tests)
2026-05-09 23:31:39 +08:00

192 lines
5.7 KiB
C#

using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Forms;
using System.Windows.Interop;
using PersonalToolBox.Helpers;
using PersonalToolBox.ViewModels;
namespace PersonalToolBox.Views;
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel;
private readonly HotKeyManager _hotKeyManager;
private NotifyIcon? _notifyIcon;
private bool _canActuallyClose;
public MainWindow(MainViewModel viewModel, HotKeyManager hotKeyManager)
{
InitializeComponent();
_viewModel = viewModel;
_hotKeyManager = hotKeyManager;
DataContext = viewModel;
viewModel.Logs.CollectionChanged += OnLogsCollectionChanged;
CreateTrayIcon();
}
/// <summary>
/// 窗口句柄就绪后注册全局快捷键
/// </summary>
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var hwndSource = (HwndSource)PresentationSource.FromVisual(this)!;
_hotKeyManager.Initialize(hwndSource.Handle);
hwndSource.AddHook(WndProcHook);
_viewModel.RegisterAllHotKeys();
}
// ──────────────── 系统托盘 ────────────────
/// <summary>
/// 创建系统托盘图标及右键菜单
/// </summary>
private void CreateTrayIcon()
{
_notifyIcon = new NotifyIcon
{
Icon = CreateAppIcon(),
Visible = true,
Text = "个人工具箱"
};
_notifyIcon.MouseClick += (s, e) =>
{
if (e.Button == MouseButtons.Left)
ToggleVisibility();
};
var menu = new ContextMenuStrip();
menu.Items.Add("显示主界面", null, (_, _) => ShowMainWindow());
menu.Items.Add("设置", null, (_, _) => OpenSettings());
menu.Items.Add(new ToolStripSeparator());
menu.Items.Add("彻底退出", null, (_, _) => ExitApplication());
_notifyIcon.ContextMenuStrip = menu;
}
/// <summary>
/// 程序化生成 32x32 托盘图标
/// </summary>
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());
}
/// <summary>
/// 左键托盘图标:切换窗口显示/隐藏
/// </summary>
private void ToggleVisibility()
{
if (IsVisible)
Hide();
else
ShowMainWindow();
}
/// <summary>
/// 显示主窗口并置前
/// </summary>
private void ShowMainWindow()
{
Show();
WindowState = WindowState.Normal;
Activate();
}
/// <summary>
/// 打开设置(当前无额外设置界面,提示使用内置功能)
/// </summary>
private void OpenSettings()
{
ShowMainWindow();
_viewModel.LogServiceMessage("设置功能可通过左侧栏管理分类和主题切换");
}
/// <summary>
/// 彻底退出程序
/// </summary>
public void ExitApplication()
{
_canActuallyClose = true;
_notifyIcon?.Dispose();
_hotKeyManager.Dispose();
System.Windows.Application.Current.Shutdown();
}
/// <summary>
/// 点击关闭按钮时隐藏窗口而非退出
/// </summary>
protected override void OnClosing(CancelEventArgs e)
{
if (!_canActuallyClose)
{
e.Cancel = true;
Hide();
}
base.OnClosing(e);
}
// ──────────────── 其他 ────────────────
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)
{
if (e.Action == NotifyCollectionChangedAction.Add && LogListBox.Items.Count > 0)
{
Dispatcher.BeginInvoke(new Action(() =>
{
LogListBox.ScrollIntoView(LogListBox.Items[^1]);
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
}
/// <summary>
/// bool → double: true=1.0, false=0.4 (工具卡片失效时置灰)
/// </summary>
public class BoolToOpacityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is bool b && b ? 1.0 : 0.4;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
/// <summary>
/// string → string: 取字符串首字符作为图标占位
/// </summary>
public class FirstCharConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var s = value as string;
return string.IsNullOrEmpty(s) ? "?" : s[0].ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}