- 安装 FontAwesome.Sharp v6.6.0,新增 Helpers/IconProvider.cs(190+ 图标,11 分类) - ToolEditWindow/GroupEditWindow: 添加图标选择 ComboBox,按分类分组,带图标预览 - MainWindow 卡片模板: 使用 fa:IconBlock 渲染图标,IconCode 为空时回退首字母 - 组合角标从 emoji 改为 FontAwesome Cubes 图标,放置于卡片右上角 - ComboBox 样式修复: TextBlock→ContentPresenter,支持 ItemTemplate+DisplayMemberPath - ComboBox 滚轮修复: CanContentScroll=False 解决分组模式下滑轮跳组问题 - 编辑弹窗: 所有输入控件添加 VerticalContentAlignment=Center - 组合可包含其他组合: GetAncestorIds 递归排除自身及祖先防止循环引用 - ProcessExecutionService: 支持嵌套组合递归执行,visited 集合防死循环 - MainWindow 右键菜单新增删除功能,工具和组合均支持 - 移除侧边栏顶部个人工具箱标题文字 - 更新测试: 适配组合嵌套逻辑,新增祖先排除测试 (83/83 通过)
209 lines
6.4 KiB
C#
209 lines
6.4 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 FontAwesome.Sharp;
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// string → IconChar: 将图标枚举名称字符串转换为 IconChar 枚举值
|
|
/// </summary>
|
|
public class StringToIconCharConverter : IValueConverter
|
|
{
|
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
{
|
|
if (value is string s && !string.IsNullOrEmpty(s) && System.Enum.TryParse<IconChar>(s, true, out var icon))
|
|
return icon;
|
|
return IconChar.None;
|
|
}
|
|
|
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
=> throw new NotImplementedException();
|
|
}
|