Phase 7: 自定义图标系统 + 组合嵌套 + 删除功能 + UI 优化

- 安装 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 通过)
This commit is contained in:
2026-05-10 02:19:32 +08:00
parent 2c985e8d63
commit 85919381b1
13 changed files with 695 additions and 89 deletions

View File

@@ -0,0 +1,238 @@
using System.Collections.Generic;
using FontAwesome.Sharp;
namespace PersonalToolBox.Helpers;
public static class IconProvider
{
public record IconOption(string Name, IconChar Icon, string Category);
public static List<IconOption> AvailableIcons { get; } = new()
{
// ── 开发工具 ──
new("代码", IconChar.Code, "开发"),
new("终端", IconChar.Terminal, "开发"),
new("Bug", IconChar.Bug, "开发"),
new("Git", IconChar.GitAlt, "开发"),
new("GitHub", IconChar.Github, "开发"),
new("数据库", IconChar.Database, "开发"),
new("服务器", IconChar.Server, "开发"),
new("云服务", IconChar.Cloud, "开发"),
new("代码分支", IconChar.CodeBranch, "开发"),
new("键盘", IconChar.Keyboard, "开发"),
new("Docker", IconChar.Docker, "开发"),
new("Python", IconChar.Python, "开发"),
new("HTML5", IconChar.Html5, "开发"),
new("CSS3", IconChar.Css3Alt, "开发"),
new("JS", IconChar.Js, "开发"),
new("Markdown", IconChar.Markdown, "开发"),
new("NPM", IconChar.Npm, "开发"),
new("Node", IconChar.NodeJs, "开发"),
new("Rust", IconChar.Rust, "开发"),
// ── 文件与文件夹 ──
new("文件夹", IconChar.Folder, "文件"),
new("文件夹打开", IconChar.FolderOpen, "文件"),
new("文件夹树", IconChar.FolderTree, "文件"),
new("文件", IconChar.File, "文件"),
new("文件代码", IconChar.FileCode, "文件"),
new("文件图片", IconChar.FileImage, "文件"),
new("文件PDF", IconChar.FilePdf, "文件"),
new("文件Word", IconChar.FileWord, "文件"),
new("文件Excel", IconChar.FileExcel, "文件"),
new("文件存档", IconChar.FileArchive, "文件"),
new("文件音频", IconChar.FileAudio, "文件"),
new("文件视频", IconChar.FileVideo, "文件"),
new("文件签名", IconChar.FileSignature, "文件"),
new("下载", IconChar.Download, "文件"),
new("上传", IconChar.Upload, "文件"),
new("保存", IconChar.Save, "文件"),
new("复制", IconChar.Copy, "文件"),
new("粘贴", IconChar.Paste, "文件"),
new("剪切", IconChar.Cut, "文件"),
new("打印", IconChar.Print, "文件"),
new("剪贴板", IconChar.Clipboard, "文件"),
new("回收站", IconChar.Trash, "文件"),
new("撤销", IconChar.Undo, "文件"),
new("重做", IconChar.Redo, "文件"),
// ── 网页与网络 ──
new("地球", IconChar.Globe, "网络"),
new("WiFi", IconChar.Wifi, "网络"),
new("链接", IconChar.Link, "网络"),
new("RSS", IconChar.Rss, "网络"),
new("网络", IconChar.NetworkWired, "网络"),
new("蓝牙", IconChar.Bluetooth, "网络"),
new("信号", IconChar.Signal, "网络"),
new("卫星", IconChar.Satellite, "网络"),
new("上传云", IconChar.CloudUploadAlt, "网络"),
new("下载云", IconChar.CloudDownloadAlt, "网络"),
new("共享", IconChar.ShareAlt, "网络"),
new("同步", IconChar.Sync, "网络"),
new("邮件发送", IconChar.PaperPlane, "网络"),
new("以太网", IconChar.Ethernet, "网络"),
// ── 设置与工具 ──
new("齿轮", IconChar.Cog, "设置"),
new("扳手", IconChar.Wrench, "设置"),
new("工具", IconChar.Tools, "设置"),
new("搜索", IconChar.Search, "设置"),
new("锁定", IconChar.Lock, "设置"),
new("解锁", IconChar.Unlock, "设置"),
new("盾牌", IconChar.ShieldAlt, "设置"),
new("钥匙", IconChar.Key, "设置"),
new("放大镜+", IconChar.SearchPlus, "设置"),
new("放大镜-", IconChar.SearchMinus, "设置"),
new("过滤器", IconChar.Filter, "设置"),
new("排序", IconChar.Sort, "设置"),
new("垃圾桶", IconChar.TrashAlt, "设置"),
new("编辑", IconChar.Edit, "设置"),
new("笔", IconChar.Pen, "设置"),
new("铅笔", IconChar.PencilAlt, "设置"),
new("放大", IconChar.Expand, "设置"),
new("缩小", IconChar.Compress, "设置"),
new("二维码", IconChar.Qrcode, "设置"),
new("条形码", IconChar.Barcode, "设置"),
new("指纹", IconChar.Fingerprint, "设置"),
new("拼图", IconChar.PuzzlePiece, "设置"),
// ── 媒体与娱乐 ──
new("播放", IconChar.Play, "媒体"),
new("暂停", IconChar.Pause, "媒体"),
new("停止", IconChar.Stop, "媒体"),
new("音乐", IconChar.Music, "媒体"),
new("图片", IconChar.Image, "媒体"),
new("视频", IconChar.Video, "媒体"),
new("相机", IconChar.Camera, "媒体"),
new("游戏手柄", IconChar.Gamepad, "媒体"),
new("耳机", IconChar.Headphones, "媒体"),
new("音量", IconChar.VolumeUp, "媒体"),
new("静音", IconChar.VolumeMute, "媒体"),
new("麦克风", IconChar.Microphone, "媒体"),
new("电视", IconChar.Tv, "媒体"),
new("电影", IconChar.Film, "媒体"),
new("CD", IconChar.CompactDisc, "媒体"),
new("唱片", IconChar.Headphones, "媒体"),
// ── 常用 ──
new("主页", IconChar.Home, "常用"),
new("用户", IconChar.User, "常用"),
new("用户组", IconChar.Users, "常用"),
new("时钟", IconChar.Clock, "常用"),
new("日历", IconChar.Calendar, "常用"),
new("日历检查", IconChar.CalendarCheck, "常用"),
new("日历天", IconChar.CalendarDay, "常用"),
new("日历周", IconChar.CalendarWeek, "常用"),
new("信封", IconChar.Envelope, "常用"),
new("邮件打开", IconChar.EnvelopeOpen, "常用"),
new("铃铛", IconChar.Bell, "常用"),
new("铃铛斜线", IconChar.BellSlash, "常用"),
new("书签", IconChar.Bookmark, "常用"),
new("星标", IconChar.Star, "常用"),
new("空心星", IconChar.StarHalfAlt, "常用"),
new("心形", IconChar.Heart, "常用"),
new("旗帜", IconChar.Flag, "常用"),
new("地图标记", IconChar.MapMarkerAlt, "常用"),
new("指南针", IconChar.Compass, "常用"),
new("地图", IconChar.Map, "常用"),
new("火箭", IconChar.Rocket, "常用"),
new("灯泡", IconChar.Lightbulb, "常用"),
new("标签", IconChar.Tag, "常用"),
new("标签组", IconChar.Tags, "常用"),
new("列表", IconChar.List, "常用"),
new("列表项", IconChar.ListAlt, "常用"),
new("图表", IconChar.ChartBar, "常用"),
new("折线图", IconChar.ChartLine, "常用"),
new("饼图", IconChar.ChartPie, "常用"),
new("信息", IconChar.InfoCircle, "常用"),
new("警告", IconChar.ExclamationTriangle, "常用"),
new("问题", IconChar.QuestionCircle, "常用"),
new("检查", IconChar.CheckCircle, "常用"),
new("购物车", IconChar.ShoppingCart, "常用"),
new("信用卡", IconChar.CreditCard, "常用"),
new("钱包", IconChar.Wallet, "常用"),
new("电源", IconChar.PowerOff, "常用"),
new("闪电", IconChar.Bolt, "常用"),
new("调色板", IconChar.Palette, "常用"),
new("火", IconChar.Fire, "常用"),
new("水滴", IconChar.Tint, "常用"),
new("磁铁", IconChar.Magnet, "常用"),
new("奖杯", IconChar.Trophy, "常用"),
// ── 办公 ──
new("编辑", IconChar.Edit, "办公"),
new("笔", IconChar.Pen, "办公"),
new("注释", IconChar.Comment, "办公"),
new("评论", IconChar.Comments, "办公"),
new("邮件@", IconChar.At, "办公"),
new("地址簿", IconChar.AddressBook, "办公"),
new("便签", IconChar.StickyNote, "办公"),
new("论文", IconChar.FileAlt, "办公"),
new("文件夹+", IconChar.FolderPlus, "办公"),
new("书", IconChar.Book, "办公"),
new("书打开", IconChar.BookOpen, "办公"),
new("商务包", IconChar.Briefcase, "办公"),
new("计算器", IconChar.Calculator, "办公"),
new("图表区", IconChar.ChartArea, "办公"),
new("收件箱", IconChar.Inbox, "办公"),
new("任务列表", IconChar.Tasks, "办公"),
new("笔尖", IconChar.PenNib, "办公"),
new("签名", IconChar.Signature, "办公"),
new("印章", IconChar.Stamp, "办公"),
// ── 系统 ──
new("Windows", IconChar.Windows, "系统"),
new("桌面", IconChar.Desktop, "系统"),
new("笔记本", IconChar.Laptop, "系统"),
new("手机", IconChar.MobileAlt, "系统"),
new("平板", IconChar.TabletAlt, "系统"),
new("命令行", IconChar.Terminal, "系统"),
new("微芯片", IconChar.Microchip, "系统"),
new("硬盘", IconChar.Hdd, "系统"),
new("内存", IconChar.Memory, "系统"),
new("USB", IconChar.Usb, "系统"),
new("插件", IconChar.Plug, "系统"),
new("电池满", IconChar.BatteryFull, "系统"),
new("鼠标", IconChar.Mouse, "系统"),
new("SD卡", IconChar.SdCard, "系统"),
new("处理器", IconChar.Microchip, "系统"),
new("风扇", IconChar.Fan, "系统"),
new("温度计", IconChar.ThermometerHalf, "系统"),
// ── 社交 ──
new("微信", IconChar.Weixin, "社交"),
new("微博", IconChar.Weibo, "社交"),
new("QQ", IconChar.Qq, "社交"),
new("Discord", IconChar.Discord, "社交"),
new("Slack", IconChar.Slack, "社交"),
new("Telegram", IconChar.Telegram, "社交"),
new("Reddit", IconChar.Reddit, "社交"),
new("YouTube", IconChar.Youtube, "社交"),
new("Twitch", IconChar.Twitch, "社交"),
new("Steam", IconChar.Steam, "社交"),
// ── 箭头与导航 ──
new("右箭头", IconChar.ArrowRight, "导航"),
new("左箭头", IconChar.ArrowLeft, "导航"),
new("上箭头", IconChar.ArrowUp, "导航"),
new("下箭头", IconChar.ArrowDown, "导航"),
new("循环", IconChar.Redo, "导航"),
new("刷新", IconChar.Sync, "导航"),
new("返回", IconChar.ChevronLeft, "导航"),
new("前进", IconChar.ChevronRight, "导航"),
new("位置", IconChar.LocationArrow, "导航"),
new("十字准星", IconChar.Crosshairs, "导航"),
// ── 安全 ──
new("用户锁", IconChar.UserLock, "安全"),
new("用户盾", IconChar.UserShield, "安全"),
new("病毒", IconChar.Virus, "安全"),
new("安全盾", IconChar.ShieldVirus, "安全"),
new("指纹", IconChar.Fingerprint, "安全"),
new("ID卡", IconChar.IdCard, "安全"),
new("门禁", IconChar.DoorOpen, "安全"),
new("面具", IconChar.Mask, "安全"),
new("眼睛", IconChar.Eye, "安全"),
new("眼睛斜线", IconChar.EyeSlash, "安全"),
};
}

View File

@@ -18,7 +18,7 @@ public class ToolItem
public string Name { get; set; } = string.Empty;
/// <summary>
/// 内置图标库的字符编码
/// 内置图标库的图标枚举名称(如 "Github", "Wrench"),用于 FontAwesome 图标渲染
/// </summary>
public string IconCode { get; set; } = string.Empty;

View File

@@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="FontAwesome.Sharp" Version="6.6.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="10.0.7" />
</ItemGroup>

View File

@@ -1,11 +1,14 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using PersonalToolBox.Models;
namespace PersonalToolBox.Services;
/// <summary>
/// 进程执行服务,负责启动外部工具进程并处理异常
/// 支持一键多开:当 IsGroup 为 true 时批量启动子工具
/// 支持一键多开:当 IsGroup 为 true 时批量启动子工具(支持嵌套组合,带循环检测)
/// </summary>
public class ProcessExecutionService : IProcessExecutionService
{
@@ -26,14 +29,12 @@ public class ProcessExecutionService : IProcessExecutionService
return;
}
// 组合卡片:遍历子工具列表逐一启动
if (tool.IsGroup)
{
await ExecuteGroupAsync(tool);
await ExecuteGroupAsync(tool, new HashSet<string>());
return;
}
// 普通工具:直接启动
if (!tool.IsValid)
{
_logService.Warning($"无法运行工具 \"{tool.Name}\",路径失效: {tool.ExecutablePath}");
@@ -44,12 +45,17 @@ public class ProcessExecutionService : IProcessExecutionService
}
/// <summary>
/// 批量启动组合中的所有子工具,每次间隔 500ms 防止系统卡顿
/// 批量启动组合中的所有子工具,支持嵌套组合,每次间隔 500ms 防止系统卡顿
/// </summary>
private async Task ExecuteGroupAsync(ToolItem group)
private async Task ExecuteGroupAsync(ToolItem group, HashSet<string> visited)
{
var subTools = new List<ToolItem>();
if (!visited.Add(group.Id))
{
_logService.Warning($"组合启动跳过:检测到循环引用 ({group.Name})");
return;
}
var subTools = new List<ToolItem>();
foreach (var subId in group.SubToolIds)
{
var subTool = _dataService.Config.Tools.FirstOrDefault(t => t.Id == subId);
@@ -65,13 +71,19 @@ public class ProcessExecutionService : IProcessExecutionService
foreach (var subTool in subTools)
{
if (!subTool.IsValid)
if (subTool.IsGroup)
{
_logService.Warning($"组合启动跳过 \"{subTool.Name}\",路径失效: {subTool.ExecutablePath}");
continue;
await ExecuteGroupAsync(subTool, visited);
}
else
{
if (!subTool.IsValid)
{
_logService.Warning($"组合启动跳过 \"{subTool.Name}\",路径失效: {subTool.ExecutablePath}");
continue;
}
LaunchSingleTool(subTool);
}
LaunchSingleTool(subTool);
await Task.Delay(500);
}

View File

@@ -1,7 +1,9 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using PersonalToolBox.Helpers;
using PersonalToolBox.Models;
using PersonalToolBox.Services;
@@ -30,6 +32,9 @@ public partial class GroupEditViewModel : ObservableObject
[ObservableProperty]
private Category? _selectedCategory;
[ObservableProperty]
private IconProvider.IconOption? _selectedIcon;
/// <summary>
/// 可供勾选的普通工具列表IsGroup == false
/// </summary>
@@ -40,6 +45,11 @@ public partial class GroupEditViewModel : ObservableObject
/// </summary>
public ObservableCollection<Category> Categories { get; } = new();
/// <summary>
/// 按分类分组的可用图标列表
/// </summary>
public ListCollectionView AvailableIconsView { get; }
public Action<bool?>? CloseAction { get; set; }
public bool Saved { get; private set; }
@@ -51,12 +61,16 @@ public partial class GroupEditViewModel : ObservableObject
_logService = logService;
_editingGroup = groupToEdit;
AvailableIconsView = new ListCollectionView(IconProvider.AvailableIcons);
AvailableIconsView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
foreach (var cat in _dataService.Config.Categories)
Categories.Add(cat);
// 加载所有普通工具(非组合
// 加载所有工具(排除自身及祖先组合,防止循环引用
var excludeIds = GetAncestorIds();
var selectedIds = _editingGroup?.SubToolIds ?? new List<string>();
foreach (var tool in _dataService.Config.Tools.Where(t => !t.IsGroup))
foreach (var tool in _dataService.Config.Tools.Where(t => !excludeIds.Contains(t.Id)))
{
AvailableTools.Add(new SelectableTool
{
@@ -71,6 +85,11 @@ public partial class GroupEditViewModel : ObservableObject
Name = groupToEdit.Name;
HotKey = groupToEdit.HotKey;
SelectedCategory = Categories.FirstOrDefault(c => c.Id == groupToEdit.CategoryId);
if (!string.IsNullOrEmpty(groupToEdit.IconCode))
{
SelectedIcon = IconProvider.AvailableIcons.FirstOrDefault(i => i.Icon.ToString() == groupToEdit.IconCode);
}
}
}
@@ -95,6 +114,7 @@ public partial class GroupEditViewModel : ObservableObject
if (_editingGroup != null)
{
_editingGroup.Name = Name.Trim();
_editingGroup.IconCode = SelectedIcon?.Icon.ToString() ?? string.Empty;
_editingGroup.HotKey = HotKey.Trim();
_editingGroup.CategoryId = SelectedCategory?.Id ?? string.Empty;
_editingGroup.SubToolIds = selectedIds;
@@ -106,6 +126,7 @@ public partial class GroupEditViewModel : ObservableObject
_dataService.Config.Tools.Add(new ToolItem
{
Name = Name.Trim(),
IconCode = SelectedIcon?.Icon.ToString() ?? string.Empty,
HotKey = HotKey.Trim(),
CategoryId = SelectedCategory?.Id ?? string.Empty,
IsGroup = true,
@@ -130,6 +151,31 @@ public partial class GroupEditViewModel : ObservableObject
{
CloseAction?.Invoke(false);
}
/// <summary>
/// 获取需要排除的工具 ID 集合(自身 + 所有祖先组合),防止循环引用
/// </summary>
private System.Collections.Generic.HashSet<string> GetAncestorIds()
{
var ancestors = new System.Collections.Generic.HashSet<string>();
if (_editingGroup != null)
{
ancestors.Add(_editingGroup.Id);
CollectAncestors(_editingGroup.Id, ancestors);
}
return ancestors;
}
private void CollectAncestors(string groupId, System.Collections.Generic.HashSet<string> ancestors)
{
foreach (var tool in _dataService.Config.Tools.Where(t => t.IsGroup))
{
if (tool.SubToolIds.Contains(groupId) && ancestors.Add(tool.Id))
{
CollectAncestors(tool.Id, ancestors);
}
}
}
}
/// <summary>

View File

@@ -238,6 +238,25 @@ public partial class MainViewModel : ObservableObject
if (editVm.Saved) RefreshData();
}
/// <summary>
/// 删除工具或组合
/// </summary>
[RelayCommand]
private void DeleteTool(ToolItem? tool)
{
if (tool == null) return;
var type = tool.IsGroup ? "组合" : "工具";
if (MessageBox.Show($"确定删除{type} \"{tool.Name}\" 吗?",
"确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning) != MessageBoxResult.Yes)
return;
_dataService.Config.Tools.Remove(tool);
_dataService.Save();
_logService.Info($"已删除{type}: {tool.Name}");
RefreshData();
}
// ───────────────────────────── 数据刷新 ─────────────────────────────
/// <summary>

View File

@@ -1,9 +1,11 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Win32;
using PersonalToolBox.Helpers;
using PersonalToolBox.Models;
using PersonalToolBox.Services;
@@ -38,11 +40,19 @@ public partial class ToolEditViewModel : ObservableObject
[ObservableProperty]
private Category? _selectedCategory;
[ObservableProperty]
private IconProvider.IconOption? _selectedIcon;
/// <summary>
/// 分类下拉列表
/// </summary>
public ObservableCollection<Category> Categories { get; } = new();
/// <summary>
/// 按分类分组的可用图标列表
/// </summary>
public ListCollectionView AvailableIconsView { get; }
/// <summary>
/// 窗口关闭回调(由 View 层设置)
/// </summary>
@@ -61,6 +71,9 @@ public partial class ToolEditViewModel : ObservableObject
_logService = logService;
_editingTool = toolToEdit;
AvailableIconsView = new ListCollectionView(IconProvider.AvailableIcons);
AvailableIconsView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
// 加载分类列表
foreach (var cat in _dataService.Config.Categories)
Categories.Add(cat);
@@ -74,6 +87,11 @@ public partial class ToolEditViewModel : ObservableObject
Arguments = toolToEdit.Arguments;
HotKey = toolToEdit.HotKey;
SelectedCategory = Categories.FirstOrDefault(c => c.Id == toolToEdit.CategoryId);
if (!string.IsNullOrEmpty(toolToEdit.IconCode))
{
SelectedIcon = IconProvider.AvailableIcons.FirstOrDefault(i => i.Icon.ToString() == toolToEdit.IconCode);
}
}
}
@@ -125,6 +143,7 @@ public partial class ToolEditViewModel : ObservableObject
if (_editingTool != null)
{
_editingTool.Name = Name.Trim();
_editingTool.IconCode = SelectedIcon?.Icon.ToString() ?? string.Empty;
_editingTool.ExecutablePath = ExecutablePath.Trim();
_editingTool.Arguments = Arguments.Trim();
_editingTool.HotKey = HotKey.Trim();
@@ -139,6 +158,7 @@ public partial class ToolEditViewModel : ObservableObject
var newTool = new ToolItem
{
Name = Name.Trim(),
IconCode = SelectedIcon?.Icon.ToString() ?? string.Empty,
ExecutablePath = ExecutablePath.Trim(),
Arguments = Arguments.Trim(),
HotKey = HotKey.Trim(),

View File

@@ -26,6 +26,7 @@
Background="{DynamicResource Theme.InputBackground}"
Foreground="{DynamicResource Theme.Foreground}"
BorderBrush="{DynamicResource Theme.InputBorder}"
VerticalContentAlignment="Center"
Height="30" Margin="0,0,0,16" Padding="6,0"/>
<Border Grid.Row="2" Grid.ColumnSpan="2"

View File

@@ -1,17 +1,108 @@
<Window x:Class="PersonalToolBox.Views.GroupEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding WindowTitle}" Height="500" Width="500"
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
Title="{Binding WindowTitle}" Height="580" Width="520"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
Background="{DynamicResource Theme.Background}">
<Window.Resources>
<!-- ComboBox 下拉项样式 -->
<Style TargetType="ComboBoxItem">
<Setter Property="Background" Value="{DynamicResource Theme.InputBackground}"/>
<Setter Property="Foreground" Value="{DynamicResource Theme.Foreground}"/>
<Setter Property="Padding" Value="6,3"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border x:Name="Border"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Theme.Accent}"/>
<Setter Property="Foreground" Value="{DynamicResource Theme.ButtonForeground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ComboBox 整体样式(兼容 DisplayMemberPath 和 ItemTemplate -->
<Style TargetType="ComboBox">
<Setter Property="Background" Value="{DynamicResource Theme.InputBackground}"/>
<Setter Property="Foreground" Value="{DynamicResource Theme.Foreground}"/>
<Setter Property="BorderBrush" Value="{DynamicResource Theme.InputBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<Grid>
<ToggleButton x:Name="ToggleButton"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<Grid>
<ContentPresenter x:Name="ContentSite"
Content="{Binding SelectionBoxItem, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}"
ContentTemplate="{Binding SelectionBoxItemTemplate, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}"
ContentStringFormat="{Binding SelectionBoxItemStringFormat, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="6,0,0,0"/>
<Path x:Name="Arrow" Data="M0,0 L4,4 8,0" Stroke="{TemplateBinding Foreground}"
StrokeThickness="1.5" HorizontalAlignment="Right" Width="8"
VerticalAlignment="Center" Margin="0,0,6,0"/>
</Grid>
</Border>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
<Popup x:Name="Popup"
IsOpen="{TemplateBinding IsDropDownOpen}"
Placement="Bottom"
AllowsTransparency="True"
Focusable="False"
PopupAnimation="Slide">
<Grid MaxHeight="{TemplateBinding MaxDropDownHeight}"
MinWidth="{TemplateBinding ActualWidth}">
<Border Background="{DynamicResource Theme.InputBackground}"
BorderBrush="{DynamicResource Theme.InputBorder}"
BorderThickness="1"
SnapsToDevicePixels="True">
<ScrollViewer CanContentScroll="False">
<ItemsPresenter/>
</ScrollViewer>
</Border>
</Grid>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
@@ -22,41 +113,84 @@
<!-- 组合名称 -->
<TextBlock Grid.Row="0" Grid.Column="0"
Text="名称:" Foreground="{DynamicResource Theme.Foreground}"
Text="名称:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="0,0,0,10"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
Background="{DynamicResource Theme.InputBackground}"
Foreground="{DynamicResource Theme.Foreground}"
BorderBrush="{DynamicResource Theme.InputBorder}"
VerticalContentAlignment="Center"
Height="28" Margin="0,0,0,10" Padding="6,0"/>
<!-- 所属分类 -->
<!-- 图标 -->
<TextBlock Grid.Row="1" Grid.Column="0"
Text="分类:" Foreground="{DynamicResource Theme.Foreground}"
Text="图标:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="0,0,0,10"/>
<ComboBox Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
DisplayMemberPath="Name"
Height="28" Margin="0,0,0,10" Padding="4,0"/>
ItemsSource="{Binding AvailableIconsView}"
SelectedItem="{Binding SelectedIcon}"
Height="28" Margin="0,0,0,10" Padding="4,0">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"
FontWeight="SemiBold"
Foreground="{DynamicResource Theme.Accent}"
FontSize="11"
Margin="10,6,0,2"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<fa:IconBlock Icon="{Binding Icon}"
FontSize="16" Width="28"
Foreground="{DynamicResource Theme.Accent}"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding Name}"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- 所属分类 -->
<TextBlock Grid.Row="2" Grid.Column="0"
Text="分类:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="0,0,0,10"/>
<ComboBox Grid.Row="2" Grid.Column="1"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
DisplayMemberPath="Name"
Height="28" Margin="0,0,0,10" Padding="4,0"/>
<!-- 快捷键 -->
<TextBlock Grid.Row="2" Grid.Column="0"
Text="快捷键:" Foreground="{DynamicResource Theme.Foreground}"
<TextBlock Grid.Row="3" Grid.Column="0"
Text="快捷键:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="0,0,0,10"/>
<TextBox Grid.Row="2" Grid.Column="1"
<TextBox Grid.Row="3" Grid.Column="1"
Text="{Binding HotKey, UpdateSourceTrigger=PropertyChanged}"
Background="{DynamicResource Theme.InputBackground}"
Foreground="{DynamicResource Theme.Foreground}"
BorderBrush="{DynamicResource Theme.InputBorder}"
VerticalContentAlignment="Center"
Height="28" Margin="0,0,0,10" Padding="6,0"/>
<!-- 子工具选择 -->
<TextBlock Grid.Row="3" Grid.Column="0"
Text="包含工具:" Foreground="{DynamicResource Theme.Foreground}"
<TextBlock Grid.Row="4" Grid.Column="0"
Text="包含工具:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Top" Margin="0,4,0,0"/>
<Border Grid.Row="3" Grid.Column="1"
<Border Grid.Row="4" Grid.Column="1"
Background="{DynamicResource Theme.InputBackground}"
BorderBrush="{DynamicResource Theme.InputBorder}"
BorderThickness="1"
@@ -68,16 +202,35 @@
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}"
Content="{Binding Tool.Name}"
Foreground="{DynamicResource Theme.Foreground}"
Margin="4,2"/>
Margin="4,2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Tool.Name}"
Foreground="{DynamicResource Theme.Foreground}"/>
<TextBlock Text=" [组合]"
Foreground="{DynamicResource Theme.Accent}"
FontSize="10"
VerticalAlignment="Center"
Margin="2,0,0,0">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Tool.IsGroup}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
<!-- 按钮 -->
<StackPanel Grid.Row="5" Grid.Column="1"
<StackPanel Grid.Row="6" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Right"
Margin="0,10,0,0">
<Button Content="保存"

View File

@@ -6,6 +6,7 @@
xmlns:local="clr-namespace:PersonalToolBox.Views"
xmlns:vm="clr-namespace:PersonalToolBox.ViewModels"
xmlns:models="clr-namespace:PersonalToolBox.Models"
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
mc:Ignorable="d"
Title="个人工具箱" Height="650" Width="960"
WindowStartupLocation="CenterScreen"
@@ -15,6 +16,7 @@
<!-- 值转换器(必须定义在 DataTemplate 之前,确保 StaticResource 能解析) -->
<local:BoolToOpacityConverter x:Key="BoolToOpacityConverter"/>
<local:FirstCharConverter x:Key="FirstCharConverter"/>
<local:StringToIconCharConverter x:Key="StringToIconCharConverter"/>
<!-- 工具卡片模板 -->
<DataTemplate x:Key="ToolCardTemplate" DataType="{x:Type models:ToolItem}">
@@ -66,24 +68,41 @@
</Style>
</MenuItem.Style>
</MenuItem>
<Separator/>
<MenuItem Header="删除"
Command="{Binding PlacementTarget.Tag.DeleteToolCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding}"/>
</ContextMenu>
</Border.ContextMenu>
<Grid>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid HorizontalAlignment="Center" Margin="0,0,0,4">
<TextBlock Text="{Binding Name, Converter={StaticResource FirstCharConverter}}"
FontSize="28"
<!-- FontAwesome 图标IconCode 有值时显示) -->
<fa:IconBlock Icon="{Binding IconCode, Converter={StaticResource StringToIconCharConverter}}"
FontSize="28"
Foreground="{DynamicResource Theme.Accent}"
HorizontalAlignment="Center">
<fa:IconBlock.Style>
<Style TargetType="fa:IconBlock">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IconCode}" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</fa:IconBlock.Style>
</fa:IconBlock>
<!-- 首字母回退IconCode 为空时显示) -->
<TextBlock FontSize="28"
Foreground="{DynamicResource Theme.Accent}"
HorizontalAlignment="Center"/>
<!-- 组合角标 -->
<TextBlock Text="📦" FontSize="11"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0,0,-12,-4">
HorizontalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="Text" Value="{Binding Name, Converter={StaticResource FirstCharConverter}}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsGroup}" Value="True">
<DataTrigger Binding="{Binding IconCode}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
@@ -98,6 +117,24 @@
TextTrimming="CharacterEllipsis"
MaxWidth="120"/>
</StackPanel>
<!-- 组合角标 -->
<fa:IconBlock Icon="Cubes" FontSize="11"
Foreground="{DynamicResource Theme.Accent}"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,4,6,0">
<fa:IconBlock.Style>
<Style TargetType="fa:IconBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsGroup}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</fa:IconBlock.Style>
</fa:IconBlock>
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
@@ -120,21 +157,13 @@
<Border Grid.Column="0" Background="{DynamicResource Theme.SidebarBackground}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="42"/>
<RowDefinition Height="42"/>
<RowDefinition Height="45"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="个人工具箱"
FontSize="18" FontWeight="Bold"
Foreground="{DynamicResource Theme.Accent}"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<ListBox Grid.Row="1"
<ListBox Grid.Row="0"
x:Name="CategoryListBox"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
@@ -186,7 +215,7 @@
</ListBox>
<!-- 分类管理按钮 -->
<Button Grid.Row="2"
<Button Grid.Row="1"
Command="{Binding AddCategoryCommand}"
Background="Transparent"
BorderThickness="1"
@@ -202,7 +231,7 @@
</StackPanel>
</Button>
<Button Grid.Row="3"
<Button Grid.Row="2"
Command="{Binding ToggleAutoStartCommand}"
Background="Transparent"
BorderThickness="1"
@@ -224,7 +253,7 @@
<TextBlock VerticalAlignment="Center" TextAlignment="Center"/>
</Button>
<Button Grid.Row="4"
<Button Grid.Row="3"
Command="{Binding ToggleThemeCommand}"
Background="Transparent"
BorderThickness="1"

View File

@@ -7,6 +7,7 @@ using System.Windows;
using System.Windows.Data;
using System.Windows.Forms;
using System.Windows.Interop;
using FontAwesome.Sharp;
using PersonalToolBox.Helpers;
using PersonalToolBox.ViewModels;
@@ -189,3 +190,19 @@ public class FirstCharConverter : IValueConverter
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();
}

View File

@@ -2,13 +2,14 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:models="clr-namespace:PersonalToolBox.Models"
Title="{Binding WindowTitle}" Height="380" Width="480"
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
Title="{Binding WindowTitle}" Height="480" Width="520"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
Background="{DynamicResource Theme.Background}">
<Window.Resources>
<!-- ComboBox 下拉项统一样式 -->
<!-- ComboBox 下拉项样式 -->
<Style TargetType="ComboBoxItem">
<Setter Property="Background" Value="{DynamicResource Theme.InputBackground}"/>
<Setter Property="Foreground" Value="{DynamicResource Theme.Foreground}"/>
@@ -19,7 +20,7 @@
<Border x:Name="Border"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}">
<ContentPresenter/>
<ContentPresenter VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True">
@@ -32,12 +33,13 @@
</Setter>
</Style>
<!-- ComboBox 整体样式(覆盖弹出层背景 -->
<!-- ComboBox 整体样式(兼容 DisplayMemberPath 和 ItemTemplate -->
<Style TargetType="ComboBox">
<Setter Property="Background" Value="{DynamicResource Theme.InputBackground}"/>
<Setter Property="Foreground" Value="{DynamicResource Theme.Foreground}"/>
<Setter Property="BorderBrush" Value="{DynamicResource Theme.InputBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
@@ -46,9 +48,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Foreground="{TemplateBinding Foreground}"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Padding="{TemplateBinding Padding}">
ClickMode="Press">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Border Background="{TemplateBinding Background}"
@@ -56,11 +57,13 @@
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<Grid>
<TextBlock Text="{Binding Text, RelativeSource={RelativeSource AncestorType=ComboBox}}"
Foreground="{TemplateBinding Foreground}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="6,0,0,0"/>
<ContentPresenter x:Name="ContentSite"
Content="{Binding SelectionBoxItem, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}"
ContentTemplate="{Binding SelectionBoxItemTemplate, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}"
ContentStringFormat="{Binding SelectionBoxItemStringFormat, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="6,0,0,0"/>
<Path x:Name="Arrow" Data="M0,0 L4,4 8,0" Stroke="{TemplateBinding Foreground}"
StrokeThickness="1.5" HorizontalAlignment="Right" Width="8"
VerticalAlignment="Center" Margin="0,0,6,0"/>
@@ -81,7 +84,7 @@
BorderBrush="{DynamicResource Theme.InputBorder}"
BorderThickness="1"
SnapsToDevicePixels="True">
<ScrollViewer>
<ScrollViewer CanContentScroll="False">
<ItemsPresenter/>
</ScrollViewer>
</Border>
@@ -102,6 +105,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
@@ -113,26 +117,67 @@
<!-- 工具名称 -->
<TextBlock Grid.Row="0" Grid.Column="0"
Text="名称:" Foreground="{DynamicResource Theme.Foreground}"
Text="名称:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="0,0,0,12"/>
<TextBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
Background="{DynamicResource Theme.InputBackground}"
Foreground="{DynamicResource Theme.Foreground}"
BorderBrush="{DynamicResource Theme.InputBorder}"
VerticalContentAlignment="Center"
Height="30" Margin="0,0,0,12" Padding="6,0"/>
<!-- 工具路径 -->
<!-- 工具图标 -->
<TextBlock Grid.Row="1" Grid.Column="0"
Text="路径:" Foreground="{DynamicResource Theme.Foreground}"
Text="图标:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="0,0,0,12"/>
<TextBox Grid.Row="1" Grid.Column="1"
<ComboBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
ItemsSource="{Binding AvailableIconsView}"
SelectedItem="{Binding SelectedIcon}"
Height="30" Margin="0,0,0,12" Padding="4,0">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"
FontWeight="SemiBold"
Foreground="{DynamicResource Theme.Accent}"
FontSize="11"
Margin="10,6,0,2"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<fa:IconBlock Icon="{Binding Icon}"
FontSize="16" Width="28"
Foreground="{DynamicResource Theme.Accent}"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding Name}"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- 工具路径 -->
<TextBlock Grid.Row="2" Grid.Column="0"
Text="路径:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="0,0,0,12"/>
<TextBox Grid.Row="2" Grid.Column="1"
Text="{Binding ExecutablePath, UpdateSourceTrigger=PropertyChanged}"
Background="{DynamicResource Theme.InputBackground}"
Foreground="{DynamicResource Theme.Foreground}"
BorderBrush="{DynamicResource Theme.InputBorder}"
VerticalContentAlignment="Center"
Height="30" Margin="0,0,0,12" Padding="6,0"/>
<Button Grid.Row="1" Grid.Column="2"
<Button Grid.Row="2" Grid.Column="2"
Content="..."
Command="{Binding BrowseFileCommand}"
Background="{DynamicResource Theme.ButtonBackground}"
@@ -142,50 +187,55 @@
FontWeight="Bold" Cursor="Hand"/>
<!-- 运行参数 -->
<TextBlock Grid.Row="2" Grid.Column="0"
Text="参数:" Foreground="{DynamicResource Theme.Foreground}"
<TextBlock Grid.Row="3" Grid.Column="0"
Text="参数:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="0,0,0,12"/>
<TextBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
<TextBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
Text="{Binding Arguments, UpdateSourceTrigger=PropertyChanged}"
Background="{DynamicResource Theme.InputBackground}"
Foreground="{DynamicResource Theme.Foreground}"
BorderBrush="{DynamicResource Theme.InputBorder}"
VerticalContentAlignment="Center"
Height="30" Margin="0,0,0,12" Padding="6,0"/>
<!-- 所属分类 -->
<TextBlock Grid.Row="3" Grid.Column="0"
Text="分类:" Foreground="{DynamicResource Theme.Foreground}"
<TextBlock Grid.Row="4" Grid.Column="0"
Text="分类:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="0,0,0,12"/>
<ComboBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
DisplayMemberPath="Name"
Height="30" Margin="0,0,0,12" Padding="4,0"/>
<ComboBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
DisplayMemberPath="Name"
Height="30" Margin="0,0,0,12" Padding="4,0"/>
<!-- 全局快捷键 -->
<TextBlock Grid.Row="4" Grid.Column="0"
Text="快捷键:" Foreground="{DynamicResource Theme.Foreground}"
<TextBlock Grid.Row="5" Grid.Column="0"
Text="快捷键:"
Foreground="{DynamicResource Theme.Foreground}"
VerticalAlignment="Center" Margin="0,0,0,12"/>
<TextBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2"
<TextBox Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2"
Text="{Binding HotKey, UpdateSourceTrigger=PropertyChanged}"
Background="{DynamicResource Theme.InputBackground}"
Foreground="{DynamicResource Theme.Foreground}"
BorderBrush="{DynamicResource Theme.InputBorder}"
VerticalContentAlignment="Center"
Height="30" Margin="0,0,0,12" Padding="6,0"/>
<!-- 提示文字 -->
<TextBlock Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2"
<TextBlock Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="2"
Text="快捷键格式示例: Ctrl+Alt+T"
Foreground="{DynamicResource Theme.TextSecondary}"
FontSize="11" Margin="0,0,0,16"/>
<!-- 分隔线 -->
<Border Grid.Row="6" Grid.ColumnSpan="3"
<Border Grid.Row="7" Grid.ColumnSpan="3"
BorderBrush="{DynamicResource Theme.CardBorder}"
BorderThickness="0,1,0,0" Margin="0,0,0,14"/>
<!-- 按钮 -->
<StackPanel Grid.Row="7" Grid.Column="1" Grid.ColumnSpan="2"
<StackPanel Grid.Row="8" Grid.Column="1" Grid.ColumnSpan="2"
Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="保存"
Command="{Binding SaveCommand}"