单实例限制:通过命名Mutex和窗口消息广播确保只能运行一个实例,再次启动时唤起已有窗口 应用图标:exe文件嵌入app.ico,托盘和窗口图标统一使用该图标文件 移除托盘右键菜单中与显示主界面重复的设置选项 快捷键暂停/恢复:托盘菜单和主界面侧边栏均添加切换按钮,通过MainViewModel.IsHotKeyEnabled双向同步
357 lines
12 KiB
C#
357 lines
12 KiB
C#
using System.Collections.ObjectModel;
|
||
using System.Linq;
|
||
using System.Windows;
|
||
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using PersonalToolBox.Helpers;
|
||
using PersonalToolBox.Models;
|
||
using PersonalToolBox.Services;
|
||
using PersonalToolBox.Views;
|
||
|
||
namespace PersonalToolBox.ViewModels;
|
||
|
||
/// <summary>
|
||
/// 主窗口 ViewModel,管理 UI 状态、数据绑定和用户交互逻辑
|
||
/// </summary>
|
||
public partial class MainViewModel : ObservableObject
|
||
{
|
||
private readonly ILogService _logService;
|
||
private readonly IDataService _dataService;
|
||
private readonly IProcessExecutionService _processService;
|
||
private readonly IServiceProvider _serviceProvider;
|
||
private readonly HotKeyManager _hotKeyManager;
|
||
|
||
/// <summary>
|
||
/// 全部分类的虚拟对象
|
||
/// </summary>
|
||
private static readonly Category AllCategory = new() { Id = "", Name = "全部" };
|
||
|
||
public MainViewModel(
|
||
ILogService logService,
|
||
IDataService dataService,
|
||
IProcessExecutionService processService,
|
||
IServiceProvider serviceProvider,
|
||
HotKeyManager hotKeyManager)
|
||
{
|
||
_logService = logService;
|
||
_dataService = dataService;
|
||
_processService = processService;
|
||
_serviceProvider = serviceProvider;
|
||
_hotKeyManager = hotKeyManager;
|
||
|
||
LoadData();
|
||
}
|
||
|
||
// ───────────────────────────── 可观察属性 ─────────────────────────────
|
||
|
||
[ObservableProperty]
|
||
private string _searchText = string.Empty;
|
||
|
||
[ObservableProperty]
|
||
private Category? _selectedCategory;
|
||
|
||
public ObservableCollection<LogEntry> Logs => _logService.Logs;
|
||
|
||
public ObservableCollection<Category> Categories { get; } = new();
|
||
|
||
public ObservableCollection<ToolItem> Tools { get; } = new();
|
||
|
||
[ObservableProperty]
|
||
private ObservableCollection<ToolItem> _filteredTools = new();
|
||
|
||
[ObservableProperty]
|
||
private string _currentTheme = "Dark";
|
||
|
||
[ObservableProperty]
|
||
private bool _isAutoStart;
|
||
|
||
[ObservableProperty]
|
||
private bool _isHotKeyEnabled = true;
|
||
|
||
// ───────────────────────────── 命令 ─────────────────────────────
|
||
|
||
[RelayCommand]
|
||
private void ClearLogs() => _logService.Clear();
|
||
|
||
[RelayCommand]
|
||
private void ToggleTheme()
|
||
{
|
||
CurrentTheme = CurrentTheme == "Dark" ? "Light" : "Dark";
|
||
ThemeHelper.ApplyTheme(CurrentTheme);
|
||
_dataService.Config.Theme = CurrentTheme;
|
||
_dataService.Save();
|
||
_logService.Info($"已切换到{(CurrentTheme == "Dark" ? "暗黑" : "明亮")}主题");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换开机自启动
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void ToggleAutoStart()
|
||
{
|
||
IsAutoStart = !IsAutoStart;
|
||
AutoStartHelper.SetAutoStart(IsAutoStart);
|
||
_dataService.Config.AutoStart = IsAutoStart;
|
||
_dataService.Save();
|
||
_logService.Info(IsAutoStart ? "已启用开机自启动" : "已关闭开机自启动");
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void ToggleHotKey()
|
||
{
|
||
IsHotKeyEnabled = !IsHotKeyEnabled;
|
||
_hotKeyManager.IsEnabled = IsHotKeyEnabled;
|
||
var status = IsHotKeyEnabled ? "已恢复" : "已暂停";
|
||
_logService.Info($"{status}快捷键功能");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 供 MainWindow 外部调用,用于输出提示信息
|
||
/// </summary>
|
||
public void LogServiceMessage(string message)
|
||
{
|
||
_logService.Info(message);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开添加工具弹窗
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void AddTool()
|
||
{
|
||
var vm = _serviceProvider.GetRequiredService<ToolEditViewModel>();
|
||
var window = new ToolEditWindow(vm);
|
||
window.ShowDialog();
|
||
|
||
if (vm.Saved)
|
||
{
|
||
RefreshData();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开编辑工具弹窗
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void EditTool(ToolItem tool)
|
||
{
|
||
if (tool == null) return;
|
||
|
||
// 创建编辑 ViewModel(需要新建实例,DI 容器无法区分参数)
|
||
var dataService = _serviceProvider.GetRequiredService<IDataService>();
|
||
var logService = _serviceProvider.GetRequiredService<ILogService>();
|
||
var editVm = new ToolEditViewModel(dataService, logService, tool);
|
||
var window = new ToolEditWindow(editVm);
|
||
window.ShowDialog();
|
||
|
||
if (editVm.Saved)
|
||
{
|
||
RefreshData();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行工具(双击卡片或右键菜单)
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private async Task ExecuteTool(ToolItem? tool)
|
||
{
|
||
if (tool == null) return;
|
||
await _processService.ExecuteAsync(tool);
|
||
}
|
||
|
||
// ───────────────────────────── 分类管理命令 ─────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 添加分类
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void AddCategory()
|
||
{
|
||
var vm = _serviceProvider.GetRequiredService<CategoryEditViewModel>();
|
||
var window = new CategoryEditWindow(vm);
|
||
window.ShowDialog();
|
||
|
||
if (vm.Saved) RefreshData();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 编辑分类(右键菜单触发)
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void EditCategory(Category? category)
|
||
{
|
||
if (category == null || string.IsNullOrEmpty(category.Id)) return;
|
||
|
||
var dataService = _serviceProvider.GetRequiredService<IDataService>();
|
||
var logService = _serviceProvider.GetRequiredService<ILogService>();
|
||
var editVm = new CategoryEditViewModel(dataService, logService, category);
|
||
var window = new CategoryEditWindow(editVm);
|
||
window.ShowDialog();
|
||
|
||
if (editVm.Saved) RefreshData();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除分类,其下所有工具移入"全部"
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void DeleteCategory(Category? category)
|
||
{
|
||
if (category == null || string.IsNullOrEmpty(category.Id)) return;
|
||
|
||
if (MessageBox.Show(
|
||
$"确定删除分类 \"{category.Name}\" 吗?\n该分类下的所有工具将移入「全部」。",
|
||
"确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning) != MessageBoxResult.Yes)
|
||
return;
|
||
|
||
// 将属于该分类的工具设为"全部"
|
||
foreach (var tool in _dataService.Config.Tools.Where(t => t.CategoryId == category.Id))
|
||
tool.CategoryId = string.Empty;
|
||
|
||
// 从配置中移除分类
|
||
_dataService.Config.Categories.Remove(category);
|
||
_dataService.Save();
|
||
|
||
_logService.Info($"已删除分类: {category.Name}");
|
||
RefreshData();
|
||
}
|
||
|
||
// ───────────────────────────── 组合管理命令 ─────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 添加组合
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void AddGroup()
|
||
{
|
||
var vm = _serviceProvider.GetRequiredService<GroupEditViewModel>();
|
||
var window = new GroupEditWindow(vm);
|
||
window.ShowDialog();
|
||
|
||
if (vm.Saved) RefreshData();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 编辑组合
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void EditGroup(ToolItem tool)
|
||
{
|
||
if (tool == null || !tool.IsGroup) return;
|
||
|
||
var dataService = _serviceProvider.GetRequiredService<IDataService>();
|
||
var logService = _serviceProvider.GetRequiredService<ILogService>();
|
||
var editVm = new GroupEditViewModel(dataService, logService, tool);
|
||
var window = new GroupEditWindow(editVm);
|
||
window.ShowDialog();
|
||
|
||
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>
|
||
/// 启动时自动运行标记为 AutoRunOnStart 的工具和组合
|
||
/// </summary>
|
||
public async Task ExecuteAutoRunToolsAsync()
|
||
{
|
||
var autoTools = _dataService.Config.Tools.Where(t => t.AutoRunOnStart).ToList();
|
||
if (autoTools.Count == 0) return;
|
||
|
||
_logService.Info($"正在启动 {autoTools.Count} 个自启动工具...");
|
||
foreach (var tool in autoTools)
|
||
{
|
||
await _processService.ExecuteAsync(tool);
|
||
await Task.Delay(500);
|
||
}
|
||
}
|
||
|
||
// ───────────────────────────── 数据刷新 ─────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 从配置重新加载数据并刷新 UI
|
||
/// </summary>
|
||
public void RefreshData()
|
||
{
|
||
var previousCategoryId = SelectedCategory?.Id;
|
||
|
||
Categories.Clear();
|
||
Categories.Add(AllCategory);
|
||
|
||
foreach (var cat in _dataService.Config.Categories)
|
||
Categories.Add(cat);
|
||
|
||
Tools.Clear();
|
||
foreach (var tool in _dataService.Config.Tools)
|
||
Tools.Add(tool);
|
||
|
||
// 恢复之前选中的分类(若仍存在),否则选中"全部"
|
||
SelectedCategory = Categories.FirstOrDefault(c => c.Id == previousCategoryId) ?? AllCategory;
|
||
ApplyFilter();
|
||
|
||
// 工具列表变更后重新注册快捷键
|
||
RegisterAllHotKeys();
|
||
}
|
||
|
||
// ───────────────────────────── 快捷键 ─────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 注册所有工具的全局快捷键(由 MainWindow.OnSourceInitialized 及数据变更后调用)
|
||
/// </summary>
|
||
public void RegisterAllHotKeys()
|
||
{
|
||
_hotKeyManager.RegisterAll(Tools);
|
||
}
|
||
|
||
private void LoadData()
|
||
{
|
||
RefreshData();
|
||
|
||
SelectedCategory = AllCategory;
|
||
CurrentTheme = _dataService.Config.Theme;
|
||
IsAutoStart = _dataService.Config.AutoStart;
|
||
ThemeHelper.ApplyTheme(CurrentTheme);
|
||
}
|
||
|
||
// ───────────────────────────── 过滤逻辑 ─────────────────────────────
|
||
|
||
partial void OnSearchTextChanged(string value) => ApplyFilter();
|
||
|
||
partial void OnSelectedCategoryChanged(Category? value) => ApplyFilter();
|
||
|
||
private void ApplyFilter()
|
||
{
|
||
var filtered = Tools.AsEnumerable();
|
||
|
||
if (SelectedCategory != null && !string.IsNullOrEmpty(SelectedCategory.Id))
|
||
filtered = filtered.Where(t => t.CategoryId == SelectedCategory.Id);
|
||
|
||
if (!string.IsNullOrWhiteSpace(SearchText))
|
||
filtered = filtered.Where(t =>
|
||
t.Name.Contains(SearchText, System.StringComparison.OrdinalIgnoreCase));
|
||
|
||
FilteredTools = new ObservableCollection<ToolItem>(filtered);
|
||
}
|
||
}
|