Files
personal-toolbox/PersonalToolBox/ViewModels/GroupEditViewModel.cs
home-PC 85919381b1 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 通过)
2026-05-10 02:19:32 +08:00

191 lines
6.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
namespace PersonalToolBox.ViewModels;
/// <summary>
/// 组合编辑窗口 ViewModel管理一键多开组合的创建与编辑
/// </summary>
public partial class GroupEditViewModel : ObservableObject
{
private readonly IDataService _dataService;
private readonly ILogService _logService;
private readonly ToolItem? _editingGroup;
// ──────── 可观察属性 ────────
[ObservableProperty]
private string _windowTitle = "添加组合";
[ObservableProperty]
private string _name = string.Empty;
[ObservableProperty]
private string _hotKey = string.Empty;
[ObservableProperty]
private Category? _selectedCategory;
[ObservableProperty]
private IconProvider.IconOption? _selectedIcon;
/// <summary>
/// 可供勾选的普通工具列表IsGroup == false
/// </summary>
public ObservableCollection<SelectableTool> AvailableTools { get; } = new();
/// <summary>
/// 分类下拉列表
/// </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; }
// ──────── 构造 ────────
public GroupEditViewModel(IDataService dataService, ILogService logService, ToolItem? groupToEdit = null)
{
_dataService = dataService;
_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 => !excludeIds.Contains(t.Id)))
{
AvailableTools.Add(new SelectableTool
{
Tool = tool,
IsSelected = selectedIds.Contains(tool.Id)
});
}
if (groupToEdit != null)
{
WindowTitle = "编辑组合";
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);
}
}
}
// ──────── 命令 ────────
[RelayCommand]
private void Save()
{
try
{
if (string.IsNullOrWhiteSpace(Name))
{
_logService.Warning("组合名称不能为空");
return;
}
var selectedIds = AvailableTools
.Where(t => t.IsSelected)
.Select(t => t.Tool.Id)
.ToList();
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;
_editingGroup.IsValid = true;
_logService.Info($"已更新组合: {Name.Trim()}(包含 {selectedIds.Count} 个工具)");
}
else
{
_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,
SubToolIds = selectedIds,
IsValid = true
});
_logService.Info($"已添加组合: {Name.Trim()}(包含 {selectedIds.Count} 个工具)");
}
_dataService.Save();
Saved = true;
CloseAction?.Invoke(true);
}
catch (Exception ex)
{
_logService.Error($"保存组合失败: {ex.Message}");
}
}
[RelayCommand]
private void Cancel()
{
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>
/// 可勾选的工具项(用于 CheckBox 列表绑定)
/// </summary>
public partial class SelectableTool : ObservableObject
{
public ToolItem Tool { get; set; } = null!;
[ObservableProperty]
private bool _isSelected;
}