Files
better-genshin-impact/BetterGenshinImpact/ViewModel/Windows/GearTask/AddTriggerDialogViewModel.cs
辉鸭蛋 769e08edfd fix: 修复DPI缩放获取和定时触发器同步问题
修复 DpiHelper 在未初始化窗口句柄时获取 DPI 缩放值的问题,现在能正确处理多显示器场景。重构 QuartzSchedulerService 的触发器同步逻辑,确保定时任务正确更新。在添加/编辑触发器时增加 Cron 表达式格式校验,避免无效表达式导致调度失败。同时修复 ScriptService 中任务启动时的线程调度问题。
2026-05-09 02:50:52 +08:00

333 lines
10 KiB
C#

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using BetterGenshinImpact.Helpers.Extensions;
using BetterGenshinImpact.Model;
using BetterGenshinImpact.Model.Gear.Triggers;
using BetterGenshinImpact.Service;
using BetterGenshinImpact.View.Windows;
using BetterGenshinImpact.ViewModel.Pages.Component;
using Microsoft.Extensions.Logging;
using Quartz;
namespace BetterGenshinImpact.ViewModel.Windows.GearTask;
/// <summary>
/// 新增/编辑触发器对话框 ViewModel。
/// </summary>
public partial class AddTriggerDialogViewModel : ObservableObject
{
private const string DefaultCronExpression = "1 0 4 * * ?";
private readonly GearTaskStorageService _storageService;
private readonly ILogger<AddTriggerDialogViewModel> _logger;
[ObservableProperty]
private string _dialogTitle = "新增触发器";
[ObservableProperty]
private string _triggerName = string.Empty;
[ObservableProperty]
private TriggerType _selectedTriggerType = TriggerType.Timed;
[ObservableProperty]
private string _cronExpression = DefaultCronExpression;
[ObservableProperty]
private CronInputMode _selectedCronInputMode = CronInputMode.Preset;
[ObservableProperty]
private HotKey? _selectedHotkey;
[ObservableProperty]
private HotKeyTypeEnum _hotkeyType = HotKeyTypeEnum.KeyboardMonitor;
/// <summary>
/// 快捷键类型显示名称
/// </summary>
public string HotkeyTypeName => HotkeyType.ToChineseName();
[ObservableProperty]
private string _selectedTaskDefinitionName = string.Empty;
[ObservableProperty]
private bool _isEnabled = true;
/// <summary>
/// 触发器类型选择是否可用
/// </summary>
[ObservableProperty]
private bool _isTriggerTypeSelectionEnabled = true;
/// <summary>
/// 可用的触发器类型
/// </summary>
public ObservableCollection<EnumItem<TriggerType>> TriggerTypes { get; } = new()
{
EnumItem<TriggerType>.Create(TriggerType.Timed),
EnumItem<TriggerType>.Create(TriggerType.Hotkey)
};
/// <summary>
/// Cron 输入模式
/// </summary>
public ObservableCollection<EnumItem<CronInputMode>> CronInputModes { get; } = new()
{
EnumItem<CronInputMode>.Create(CronInputMode.Preset),
EnumItem<CronInputMode>.Create(CronInputMode.Manual)
};
/// <summary>
/// 可用的任务定义列表
/// </summary>
[ObservableProperty]
private ObservableCollection<string> _availableTaskDefinitions = new();
/// <summary>
/// 请求关闭事件
/// </summary>
public event EventHandler<bool>? RequestClose;
/// <summary>
/// 创建的触发器
/// </summary>
public GearTriggerViewModel? CreatedTrigger { get; private set; }
public AddTriggerDialogViewModel(GearTaskStorageService storageService, ILogger<AddTriggerDialogViewModel> logger)
{
_storageService = storageService;
_logger = logger;
// 生成默认名称
GenerateDefaultName();
// 加载可用的任务定义
LoadAvailableTaskDefinitions();
}
public AddTriggerDialogViewModel(
GearTaskStorageService storageService,
ILogger<AddTriggerDialogViewModel> logger,
TriggerType? predefinedType = null)
: this(storageService, logger)
{
if (predefinedType.HasValue)
{
SelectedTriggerType = predefinedType.Value;
IsTriggerTypeSelectionEnabled = false;
}
}
public AddTriggerDialogViewModel(
GearTaskStorageService storageService,
ILogger<AddTriggerDialogViewModel> logger,
GearTriggerViewModel existingTrigger)
: this(storageService, logger)
{
DialogTitle = "编辑触发器";
IsTriggerTypeSelectionEnabled = false;
SelectedTriggerType = existingTrigger.TriggerType;
TriggerName = existingTrigger.Name;
IsEnabled = existingTrigger.IsEnabled;
SelectedTaskDefinitionName = existingTrigger.TaskDefinitionName;
CronExpression = existingTrigger.TriggerType == TriggerType.Timed
? existingTrigger.CronExpression ?? DefaultCronExpression
: DefaultCronExpression;
SelectedCronInputMode = existingTrigger.TriggerType == TriggerType.Timed
? CronInputMode.Manual
: CronInputMode.Preset;
SelectedHotkey = existingTrigger.TriggerType == TriggerType.Hotkey ? existingTrigger.Hotkey : null;
HotkeyType = existingTrigger.TriggerType == TriggerType.Hotkey
? existingTrigger.HotkeyType
: HotKeyTypeEnum.KeyboardMonitor;
}
/// <summary>
/// 生成默认触发器名称
/// </summary>
private void GenerateDefaultName()
{
var typeName = SelectedTriggerType == TriggerType.Timed ? "定时触发器" : "热键触发器";
TriggerName = $"{typeName} {DateTime.Now:MMdd_HHmm}";
}
/// <summary>
/// 加载可用的任务定义
/// </summary>
private async void LoadAvailableTaskDefinitions()
{
try
{
AvailableTaskDefinitions.Clear();
// 从 GearTaskStorageService 加载所有任务定义
var taskDefinitions = await _storageService.LoadAllTaskDefinitionsAsync();
// 提取任务定义名称并添加到列表中
foreach (var taskDefinition in taskDefinitions)
{
if (!string.IsNullOrWhiteSpace(taskDefinition.Name))
{
AvailableTaskDefinitions.Add(taskDefinition.Name);
}
}
_logger.LogInformation("已加载 {Count} 个可用的任务定义", AvailableTaskDefinitions.Count);
// 如果有任务定义,默认选择第一个
if (AvailableTaskDefinitions.Count > 0 && string.IsNullOrWhiteSpace(SelectedTaskDefinitionName))
{
SelectedTaskDefinitionName = AvailableTaskDefinitions[0];
}
}
catch (Exception ex)
{
_logger.LogDebug(ex, "加载可用任务定义时发生错误");
// 发生错误时添加一个提示项
AvailableTaskDefinitions.Clear();
AvailableTaskDefinitions.Add("(无可用任务定义)");
}
}
/// <summary>
/// 快捷键类型改变时的处理
/// </summary>
partial void OnHotkeyTypeChanged(HotKeyTypeEnum value)
{
// 通知UI更新快捷键类型显示名称
OnPropertyChanged(nameof(HotkeyTypeName));
// 清空当前快捷键设置
SelectedHotkey = null;
}
/// <summary>
/// 触发器类型改变时的处理
/// </summary>
partial void OnSelectedTriggerTypeChanged(TriggerType value)
{
GenerateDefaultName();
}
partial void OnSelectedCronInputModeChanged(CronInputMode value)
{
if (SelectedTriggerType == TriggerType.Timed && string.IsNullOrWhiteSpace(CronExpression))
{
CronExpression = DefaultCronExpression;
}
}
[RelayCommand]
private void Confirm()
{
if (string.IsNullOrWhiteSpace(TriggerName))
{
ThemedMessageBox.Error("请输入触发器名称", "保存失败");
return;
}
if (SelectedTriggerType == TriggerType.Timed && string.IsNullOrWhiteSpace(CronExpression))
{
var message = SelectedCronInputMode == CronInputMode.Manual
? "请输入 Cron 表达式"
: "请先完成定时选择";
ThemedMessageBox.Error(message, "保存失败");
return;
}
if (SelectedTriggerType == TriggerType.Timed && !IsValidCronExpression(CronExpression, out var cronErrorMessage))
{
ThemedMessageBox.Error(cronErrorMessage, "Cron 表达式错误");
return;
}
if (SelectedTriggerType == TriggerType.Hotkey && SelectedHotkey == null)
{
ThemedMessageBox.Error("请选择热键", "保存失败");
return;
}
CreatedTrigger = new GearTriggerViewModel(TriggerName, SelectedTriggerType)
{
IsEnabled = IsEnabled,
TaskDefinitionName = SelectedTaskDefinitionName,
CronExpression = SelectedTriggerType == TriggerType.Timed ? CronExpression : null,
Hotkey = SelectedTriggerType == TriggerType.Hotkey ? SelectedHotkey : null,
HotkeyType = SelectedTriggerType == TriggerType.Hotkey ? HotkeyType : HotKeyTypeEnum.KeyboardMonitor
};
RequestClose?.Invoke(this, true);
}
/// <summary>
/// 取消创建
/// </summary>
[RelayCommand]
private void Cancel()
{
RequestClose?.Invoke(this, false);
}
/// <summary>
/// 切换快捷键类型
/// </summary>
[RelayCommand]
private void SwitchHotKeyType()
{
HotkeyType = HotkeyType == HotKeyTypeEnum.GlobalRegister
? HotKeyTypeEnum.KeyboardMonitor
: HotKeyTypeEnum.GlobalRegister;
}
/// <summary>
/// 选择热键
/// </summary>
[RelayCommand]
private void SelectHotkey()
{
}
private static bool IsValidCronExpression(string? cronExpression, out string errorMessage)
{
errorMessage = string.Empty;
if (string.IsNullOrWhiteSpace(cronExpression))
{
errorMessage = "Cron 表达式不能为空";
return false;
}
try
{
_ = new CronExpression(cronExpression);
return true;
}
catch (FormatException ex)
{
errorMessage = $"Cron 表达式格式无效:{ex.Message}";
return false;
}
catch (Exception ex)
{
errorMessage = $"Cron 表达式校验失败:{ex.Message}";
return false;
}
}
}
public enum CronInputMode
{
[Description("可视化选择")]
Preset,
[Description("手动 Cron")]
Manual
}