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:
@@ -26,12 +26,32 @@ public class GroupEditViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_AddMode_LoadsOnlyNonGroupTools()
|
||||
public void Constructor_AddMode_LoadsAllToolsIncludingGroups()
|
||||
{
|
||||
var vm = new GroupEditViewModel(_dataServiceMock.Object, _logServiceMock.Object);
|
||||
|
||||
Assert.Equal("添加组合", vm.WindowTitle);
|
||||
Assert.Equal(2, vm.AvailableTools.Count); // t1, t2 only (t3 is a group)
|
||||
Assert.Equal(3, vm.AvailableTools.Count); // t1, t2, t3 (new groups can include other groups)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_EditMode_ExcludesSelfAndAncestors()
|
||||
{
|
||||
// Group A (t3) contains t1 -> Group B being edited should exclude itself
|
||||
var group = new ToolItem
|
||||
{
|
||||
Id = "t4",
|
||||
Name = "GroupB",
|
||||
IsGroup = true,
|
||||
SubToolIds = new List<string>()
|
||||
};
|
||||
// t3 contains t4, so t3 is an ancestor of t4
|
||||
_config.Tools.First(t => t.Id == "t3").SubToolIds.Add("t4");
|
||||
|
||||
var vm = new GroupEditViewModel(_dataServiceMock.Object, _logServiceMock.Object, group);
|
||||
|
||||
// t4 (self) and t3 (ancestor) should be excluded
|
||||
Assert.Equal(2, vm.AvailableTools.Count); // t1, t2 only
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
238
PersonalToolBox/Helpers/IconProvider.cs
Normal file
238
PersonalToolBox/Helpers/IconProvider.cs
Normal 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, "安全"),
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
@@ -64,14 +70,20 @@ public class ProcessExecutionService : IProcessExecutionService
|
||||
_logService.Info($"开始启动组合 \"{group.Name}\",共 {subTools.Count} 个工具");
|
||||
|
||||
foreach (var subTool in subTools)
|
||||
{
|
||||
if (subTool.IsGroup)
|
||||
{
|
||||
await ExecuteGroupAsync(subTool, visited);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!subTool.IsValid)
|
||||
{
|
||||
_logService.Warning($"组合启动跳过 \"{subTool.Name}\",路径失效: {subTool.ExecutablePath}");
|
||||
continue;
|
||||
}
|
||||
|
||||
LaunchSingleTool(subTool);
|
||||
}
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 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="保存"
|
||||
|
||||
@@ -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}}"
|
||||
<!-- FontAwesome 图标(IconCode 有值时显示) -->
|
||||
<fa:IconBlock Icon="{Binding IconCode, Converter={StaticResource StringToIconCharConverter}}"
|
||||
FontSize="28"
|
||||
Foreground="{DynamicResource Theme.Accent}"
|
||||
HorizontalAlignment="Center"/>
|
||||
<!-- 组合角标 -->
|
||||
<TextBlock Text="📦" FontSize="11"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="0,0,-12,-4">
|
||||
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.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"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,8 +57,10 @@
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<Grid>
|
||||
<TextBlock Text="{Binding Text, RelativeSource={RelativeSource AncestorType=ComboBox}}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
<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"/>
|
||||
@@ -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"
|
||||
<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}"
|
||||
|
||||
Reference in New Issue
Block a user