mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
fix: 修复DPI缩放获取和定时触发器同步问题
修复 DpiHelper 在未初始化窗口句柄时获取 DPI 缩放值的问题,现在能正确处理多显示器场景。重构 QuartzSchedulerService 的触发器同步逻辑,确保定时任务正确更新。在添加/编辑触发器时增加 Cron 表达式格式校验,避免无效表达式导致调度失败。同时修复 ScriptService 中任务启动时的线程调度问题。
This commit is contained in:
@@ -83,4 +83,7 @@
|
||||
编译指令参考,如果出现程序占用场景,直接放弃编译验证即可
|
||||
```
|
||||
dotnet build BetterGenshinImpact.sln -c Debug
|
||||
```
|
||||
```
|
||||
###其他要求
|
||||
|
||||
1. 改动时候请不要删除已有的注释,你可以修改,但是不要删!
|
||||
@@ -39,7 +39,7 @@ namespace BetterGenshinImpact.GameTask
|
||||
GameHandle = hWnd;
|
||||
PostMessageSimulator = Simulation.PostMessage(GameHandle);
|
||||
SystemInfo = new SystemInfo(hWnd);
|
||||
DpiScale = DpiHelper.ScaleY;
|
||||
DpiScale = DpiHelper.GetScale(hWnd).Y;
|
||||
//MaskWindowHandle = new WindowInteropHelper(MaskWindow.Instance()).Handle;
|
||||
IsInitialized = true;
|
||||
}
|
||||
@@ -108,4 +108,4 @@ namespace BetterGenshinImpact.GameTask
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ public class DpiHelper
|
||||
|
||||
private static float GetScaleY()
|
||||
{
|
||||
if (Environment.OSVersion.Version >= new Version(6, 3)
|
||||
&& UIDispatcherHelper.MainWindow != null)
|
||||
if (Environment.OSVersion.Version >= new Version(6, 3))
|
||||
{
|
||||
HWND hWnd = HWND.NULL;
|
||||
if (TaskContext.Instance().IsInitialized)
|
||||
@@ -26,12 +25,15 @@ public class DpiHelper
|
||||
}
|
||||
else
|
||||
{
|
||||
hWnd = new WindowInteropHelper(Application.Current?.MainWindow).Handle;
|
||||
hWnd = GetMainWindowHandle();
|
||||
}
|
||||
|
||||
HMONITOR hMonitor = User32.MonitorFromWindow(hWnd, User32.MonitorFlags.MONITOR_DEFAULTTONEAREST);
|
||||
SHCore.GetDpiForMonitor(hMonitor, SHCore.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out _, out uint dpiY);
|
||||
return dpiY / 96f;
|
||||
if (hWnd != HWND.NULL)
|
||||
{
|
||||
HMONITOR hMonitor = User32.MonitorFromWindow(hWnd, User32.MonitorFlags.MONITOR_DEFAULTTONEAREST);
|
||||
SHCore.GetDpiForMonitor(hMonitor, SHCore.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out _, out uint dpiY);
|
||||
return dpiY / 96f;
|
||||
}
|
||||
}
|
||||
|
||||
HDC hdc = User32.GetDC(HWND.NULL);
|
||||
@@ -40,6 +42,27 @@ public class DpiHelper
|
||||
return scaleY / 96f;
|
||||
}
|
||||
|
||||
private static HWND GetMainWindowHandle()
|
||||
{
|
||||
var application = Application.Current;
|
||||
if (application?.Dispatcher == null)
|
||||
{
|
||||
return HWND.NULL;
|
||||
}
|
||||
|
||||
if (application.Dispatcher.CheckAccess())
|
||||
{
|
||||
return application.MainWindow == null
|
||||
? HWND.NULL
|
||||
: new WindowInteropHelper(application.MainWindow).Handle;
|
||||
}
|
||||
|
||||
return application.Dispatcher.Invoke(() =>
|
||||
application.MainWindow == null
|
||||
? HWND.NULL
|
||||
: new WindowInteropHelper(application.MainWindow).Handle);
|
||||
}
|
||||
|
||||
public static DpiScaleF GetScale(nint hWnd = 0)
|
||||
{
|
||||
if (hWnd != IntPtr.Zero)
|
||||
|
||||
@@ -4,10 +4,12 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.Model.Gear.Triggers;
|
||||
using BetterGenshinImpact.Model.Gear.Triggers.QuartzJob;
|
||||
using BetterGenshinImpact.ViewModel.Pages.Component;
|
||||
using BetterGenshinImpact.Service.GearTask;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Quartz;
|
||||
using Quartz.Impl.Matchers;
|
||||
|
||||
namespace BetterGenshinImpact.Service;
|
||||
|
||||
@@ -19,11 +21,38 @@ public class QuartzSchedulerService(ILogger<QuartzSchedulerService> logger,
|
||||
GearTriggerStorageService triggerStorageService) : IHostedService
|
||||
{
|
||||
private readonly ILogger<QuartzSchedulerService> _logger = logger;
|
||||
private const string GearGroupName = "gear";
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var (timedTriggers, _) = await triggerStorageService.LoadTriggersAsync();
|
||||
await SyncTimedTriggersAsync(timedTriggers, cancellationToken);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task SyncTimedTriggersAsync(IEnumerable<GearTriggerViewModel> timedTriggers, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
|
||||
var existingJobKeys = await scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(GearGroupName), cancellationToken);
|
||||
|
||||
if (existingJobKeys.Count > 0)
|
||||
{
|
||||
await scheduler.DeleteJobs(existingJobKeys.ToList(), cancellationToken);
|
||||
}
|
||||
|
||||
var jobsDictionary = BuildJobsDictionary(timedTriggers);
|
||||
if (jobsDictionary.Count > 0)
|
||||
{
|
||||
await scheduler.ScheduleJobs(jobsDictionary, replace: true, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>> BuildJobsDictionary(IEnumerable<GearTriggerViewModel> timedTriggers)
|
||||
{
|
||||
var allData = timedTriggers
|
||||
.Where(t => t.IsEnabled && !string.IsNullOrWhiteSpace(t.CronExpression))
|
||||
.Select(t => t.ToTrigger())
|
||||
@@ -48,12 +77,12 @@ public class QuartzSchedulerService(ILogger<QuartzSchedulerService> logger,
|
||||
};
|
||||
|
||||
var job = JobBuilder.Create<QuartzGearTaskJob>()
|
||||
.WithIdentity($"job:{data.Name}", "gear")
|
||||
.WithIdentity($"job:{data.Name}", GearGroupName)
|
||||
.UsingJobData(jobDataMap)
|
||||
.Build();
|
||||
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.WithIdentity($"trigger:{data.Name}", "gear")
|
||||
.WithIdentity($"trigger:{data.Name}", GearGroupName)
|
||||
.WithCronSchedule(data.CronExpression)
|
||||
.ForJob(job)
|
||||
.Build();
|
||||
@@ -61,16 +90,7 @@ public class QuartzSchedulerService(ILogger<QuartzSchedulerService> logger,
|
||||
jobsDictionary.Add(job, new HashSet<ITrigger> { trigger });
|
||||
}
|
||||
|
||||
var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
|
||||
if (jobsDictionary.Count > 0)
|
||||
{
|
||||
await scheduler.ScheduleJobs(jobsDictionary, replace: true, cancellationToken);
|
||||
}
|
||||
return jobsDictionary;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using BetterGenshinImpact.Core.Script;
|
||||
using BetterGenshinImpact.Core.Script.Dependence;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
@@ -559,7 +560,19 @@ public partial class ScriptService : IScriptService
|
||||
var homePageViewModel = App.GetService<HomePageViewModel>();
|
||||
if (!homePageViewModel!.TaskDispatcherEnabled)
|
||||
{
|
||||
await homePageViewModel.OnStartTriggerAsync();
|
||||
var dispatcher = Application.Current?.Dispatcher;
|
||||
if (dispatcher?.CheckAccess() == true)
|
||||
{
|
||||
await homePageViewModel.OnStartTriggerAsync();
|
||||
}
|
||||
else if (dispatcher != null)
|
||||
{
|
||||
await dispatcher.InvokeAsync(homePageViewModel.OnStartTriggerAsync).Task.Unwrap();
|
||||
}
|
||||
else
|
||||
{
|
||||
await homePageViewModel.OnStartTriggerAsync();
|
||||
}
|
||||
|
||||
if (waitForMainUi)
|
||||
{
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using BetterGenshinImpact.ViewModel.Pages.Component;
|
||||
using BetterGenshinImpact.Model.Gear.Triggers;
|
||||
using BetterGenshinImpact.Model;
|
||||
using BetterGenshinImpact.View.Windows.GearTask;
|
||||
using BetterGenshinImpact.Model.Gear.Triggers;
|
||||
using BetterGenshinImpact.Service;
|
||||
using BetterGenshinImpact.Service.GearTask;
|
||||
using BetterGenshinImpact.View.Windows;
|
||||
using BetterGenshinImpact.View.Windows.GearTask;
|
||||
using BetterGenshinImpact.ViewModel.Pages.Component;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Quartz;
|
||||
|
||||
namespace BetterGenshinImpact.ViewModel.Pages;
|
||||
|
||||
@@ -16,6 +20,7 @@ public partial class GearTriggerPageViewModel : ViewModel
|
||||
{
|
||||
private readonly ILogger<GearTriggerPageViewModel> _logger;
|
||||
private readonly GearTriggerStorageService _storageService;
|
||||
private readonly QuartzSchedulerService _quartzSchedulerService;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<GearTriggerViewModel> _timedTriggers = new();
|
||||
@@ -26,18 +31,27 @@ public partial class GearTriggerPageViewModel : ViewModel
|
||||
[ObservableProperty]
|
||||
private GearTriggerViewModel? _selectedTrigger;
|
||||
|
||||
[ObservableProperty]
|
||||
private GearTaskDefinitionViewModel? _selectedTaskDefinition;
|
||||
|
||||
public GearTriggerPageViewModel(
|
||||
ILogger<GearTriggerPageViewModel> logger,
|
||||
GearTriggerStorageService storageService,
|
||||
QuartzSchedulerService quartzSchedulerService)
|
||||
{
|
||||
_logger = logger;
|
||||
_storageService = storageService;
|
||||
_quartzSchedulerService = quartzSchedulerService;
|
||||
}
|
||||
|
||||
partial void OnSelectedTriggerChanged(GearTriggerViewModel? value)
|
||||
{
|
||||
EditTriggerCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private GearTaskDefinitionViewModel? _selectedTaskDefinition;
|
||||
|
||||
public GearTriggerPageViewModel(ILogger<GearTriggerPageViewModel> logger, GearTriggerStorageService storageService)
|
||||
public override void OnNavigatedTo()
|
||||
{
|
||||
_logger = logger;
|
||||
_storageService = storageService;
|
||||
_ = LoadTriggersAsync();
|
||||
}
|
||||
|
||||
private void UpdateTimedTriggersNextRunTime()
|
||||
@@ -48,107 +62,155 @@ public partial class GearTriggerPageViewModel : ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo()
|
||||
{
|
||||
_ = LoadTriggersAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载触发器数据
|
||||
/// </summary>
|
||||
private async Task LoadTriggersAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var (timedTriggers, hotkeyTriggers) = await _storageService.LoadTriggersAsync();
|
||||
|
||||
|
||||
TimedTriggers.Clear();
|
||||
HotkeyTriggers.Clear();
|
||||
|
||||
|
||||
foreach (var trigger in timedTriggers)
|
||||
{
|
||||
trigger.UpdateNextRunTime();
|
||||
TimedTriggers.Add(trigger);
|
||||
}
|
||||
|
||||
|
||||
foreach (var trigger in hotkeyTriggers)
|
||||
{
|
||||
HotkeyTriggers.Add(trigger);
|
||||
}
|
||||
|
||||
|
||||
UpdateTimedTriggersNextRunTime();
|
||||
|
||||
_logger.LogInformation("已加载 {TimedCount} 个定时触发器和 {HotkeyCount} 个快捷键触发器",
|
||||
TimedTriggers.Count, HotkeyTriggers.Count);
|
||||
_logger.LogInformation("已加载 {TimedCount} 个定时触发器和 {HotkeyCount} 个热键触发器", TimedTriggers.Count, HotkeyTriggers.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "加载触发器数据时发生错误");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存触发器数据
|
||||
/// </summary>
|
||||
private async Task SaveTriggersAsync()
|
||||
|
||||
private async Task<bool> SaveTriggersAsync()
|
||||
{
|
||||
if (!ValidateTimedTriggers())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _storageService.SaveTriggersAsync(TimedTriggers, HotkeyTriggers);
|
||||
_logger.LogInformation("触发器数据已保存");
|
||||
await _quartzSchedulerService.SyncTimedTriggersAsync(TimedTriggers);
|
||||
_logger.LogInformation("触发器数据已保存,并已同步到 Quartz 调度器");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "保存触发器数据时发生错误");
|
||||
ThemedMessageBox.Error($"保存触发器失败:{ex.Message}", "保存失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateTimedTriggers()
|
||||
{
|
||||
var invalidTrigger = TimedTriggers.FirstOrDefault(t =>
|
||||
t.TriggerType == TriggerType.Timed &&
|
||||
t.IsEnabled &&
|
||||
!string.IsNullOrWhiteSpace(t.CronExpression) &&
|
||||
!IsValidCronExpression(t.CronExpression, out _));
|
||||
|
||||
if (invalidTrigger == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
IsValidCronExpression(invalidTrigger.CronExpression, out var errorMessage);
|
||||
ThemedMessageBox.Error($"触发器“{invalidTrigger.Name}”的 Cron 表达式无效。\n{errorMessage}", "保存失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
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 = ex.Message;
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AddTimedTrigger()
|
||||
private async Task AddTimedTrigger()
|
||||
{
|
||||
var dialog = AddTriggerDialog.ShowAddTriggerDialog(TriggerType.Timed);
|
||||
if (dialog != null)
|
||||
if (dialog == null)
|
||||
{
|
||||
var newTrigger = new GearTriggerViewModel(dialog.TriggerName, TriggerType.Timed)
|
||||
{
|
||||
CronExpression = dialog.CronExpression,
|
||||
TaskDefinitionName = dialog.SelectedTaskDefinitionName,
|
||||
IsEnabled = true
|
||||
};
|
||||
TimedTriggers.Add(newTrigger);
|
||||
SelectedTrigger = newTrigger;
|
||||
|
||||
// 保存数据
|
||||
_ = SaveTriggersAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var newTrigger = new GearTriggerViewModel(dialog.TriggerName, TriggerType.Timed)
|
||||
{
|
||||
CronExpression = dialog.CronExpression,
|
||||
TaskDefinitionName = dialog.SelectedTaskDefinitionName,
|
||||
IsEnabled = dialog.IsEnabled
|
||||
};
|
||||
|
||||
TimedTriggers.Add(newTrigger);
|
||||
SelectedTrigger = newTrigger;
|
||||
newTrigger.UpdateNextRunTime();
|
||||
|
||||
await SaveTriggersAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AddHotkeyTrigger()
|
||||
private async Task AddHotkeyTrigger()
|
||||
{
|
||||
var dialog = AddTriggerDialog.ShowAddTriggerDialog(TriggerType.Hotkey);
|
||||
if (dialog != null)
|
||||
if (dialog == null)
|
||||
{
|
||||
var newTrigger = new GearTriggerViewModel(dialog.TriggerName, TriggerType.Hotkey)
|
||||
{
|
||||
Hotkey = dialog.SelectedHotkey,
|
||||
HotkeyType = dialog.HotkeyType,
|
||||
TaskDefinitionName = dialog.SelectedTaskDefinitionName,
|
||||
IsEnabled = true
|
||||
};
|
||||
HotkeyTriggers.Add(newTrigger);
|
||||
SelectedTrigger = newTrigger;
|
||||
|
||||
// 保存数据
|
||||
_ = SaveTriggersAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var newTrigger = new GearTriggerViewModel(dialog.TriggerName, TriggerType.Hotkey)
|
||||
{
|
||||
Hotkey = dialog.SelectedHotkey,
|
||||
HotkeyType = dialog.HotkeyType,
|
||||
TaskDefinitionName = dialog.SelectedTaskDefinitionName,
|
||||
IsEnabled = dialog.IsEnabled
|
||||
};
|
||||
|
||||
HotkeyTriggers.Add(newTrigger);
|
||||
SelectedTrigger = newTrigger;
|
||||
|
||||
await SaveTriggersAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void DeleteTrigger()
|
||||
private async Task DeleteTrigger()
|
||||
{
|
||||
if (SelectedTrigger == null) return;
|
||||
if (SelectedTrigger == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (SelectedTrigger.TriggerType)
|
||||
{
|
||||
@@ -161,9 +223,7 @@ public partial class GearTriggerPageViewModel : ViewModel
|
||||
}
|
||||
|
||||
SelectedTrigger = null;
|
||||
|
||||
// 保存数据
|
||||
_ = SaveTriggersAsync();
|
||||
await SaveTriggersAsync();
|
||||
}
|
||||
|
||||
private bool CanEditTrigger()
|
||||
@@ -172,7 +232,7 @@ public partial class GearTriggerPageViewModel : ViewModel
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanEditTrigger))]
|
||||
private void EditTrigger()
|
||||
private async Task EditTrigger()
|
||||
{
|
||||
if (SelectedTrigger is not { } selectedTrigger)
|
||||
{
|
||||
@@ -204,6 +264,6 @@ public partial class GearTriggerPageViewModel : ViewModel
|
||||
selectedTrigger.ModifiedTime = DateTime.Now;
|
||||
selectedTrigger.UpdateNextRunTime();
|
||||
|
||||
_ = SaveTriggersAsync();
|
||||
await SaveTriggersAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using BetterGenshinImpact.ViewModel.Pages.Component;
|
||||
using BetterGenshinImpact.Model.Gear.Triggers;
|
||||
using BetterGenshinImpact.Model;
|
||||
using BetterGenshinImpact.Service;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Wpf.Ui.Violeta.Controls;
|
||||
using BetterGenshinImpact.Helpers.Extensions;
|
||||
using System.ComponentModel;
|
||||
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
|
||||
/// 新增/编辑触发器对话框 ViewModel。
|
||||
/// </summary>
|
||||
public partial class AddTriggerDialogViewModel : ObservableObject
|
||||
{
|
||||
private const string DefaultCronExpression = "1 0 4 * * ?";
|
||||
|
||||
private readonly GearTaskStorageService _storageService;
|
||||
private readonly ILogger<AddTriggerDialogViewModel> _logger;
|
||||
|
||||
@@ -32,7 +34,7 @@ public partial class AddTriggerDialogViewModel : ObservableObject
|
||||
private TriggerType _selectedTriggerType = TriggerType.Timed;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _cronExpression = "1 0 4 * * ?"; // 默认每天 04:00:01
|
||||
private string _cronExpression = DefaultCronExpression;
|
||||
|
||||
[ObservableProperty]
|
||||
private CronInputMode _selectedCronInputMode = CronInputMode.Preset;
|
||||
@@ -106,33 +108,25 @@ public partial class AddTriggerDialogViewModel : ObservableObject
|
||||
LoadAvailableTaskDefinitions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,用于指定触发器类型
|
||||
/// </summary>
|
||||
public AddTriggerDialogViewModel(GearTaskStorageService storageService, ILogger<AddTriggerDialogViewModel> logger, TriggerType? predefinedType = null)
|
||||
public AddTriggerDialogViewModel(
|
||||
GearTaskStorageService storageService,
|
||||
ILogger<AddTriggerDialogViewModel> logger,
|
||||
TriggerType? predefinedType = null)
|
||||
: this(storageService, logger)
|
||||
{
|
||||
_storageService = storageService;
|
||||
_logger = logger;
|
||||
|
||||
// 如果指定了预定义类型,则设置并禁用选择
|
||||
if (predefinedType.HasValue)
|
||||
{
|
||||
SelectedTriggerType = predefinedType.Value;
|
||||
IsTriggerTypeSelectionEnabled = false;
|
||||
}
|
||||
|
||||
// 生成默认名称
|
||||
GenerateDefaultName();
|
||||
|
||||
// 加载可用的任务定义
|
||||
LoadAvailableTaskDefinitions();
|
||||
}
|
||||
|
||||
public AddTriggerDialogViewModel(GearTaskStorageService storageService, ILogger<AddTriggerDialogViewModel> logger, GearTriggerViewModel existingTrigger)
|
||||
public AddTriggerDialogViewModel(
|
||||
GearTaskStorageService storageService,
|
||||
ILogger<AddTriggerDialogViewModel> logger,
|
||||
GearTriggerViewModel existingTrigger)
|
||||
: this(storageService, logger)
|
||||
{
|
||||
_storageService = storageService;
|
||||
_logger = logger;
|
||||
|
||||
DialogTitle = "编辑触发器";
|
||||
IsTriggerTypeSelectionEnabled = false;
|
||||
|
||||
@@ -142,21 +136,16 @@ public partial class AddTriggerDialogViewModel : ObservableObject
|
||||
SelectedTaskDefinitionName = existingTrigger.TaskDefinitionName;
|
||||
|
||||
CronExpression = existingTrigger.TriggerType == TriggerType.Timed
|
||||
? (existingTrigger.CronExpression ?? CronExpression)
|
||||
: CronExpression;
|
||||
? existingTrigger.CronExpression ?? DefaultCronExpression
|
||||
: DefaultCronExpression;
|
||||
SelectedCronInputMode = existingTrigger.TriggerType == TriggerType.Timed
|
||||
? CronInputMode.Manual
|
||||
: CronInputMode.Preset;
|
||||
|
||||
SelectedHotkey = existingTrigger.TriggerType == TriggerType.Hotkey
|
||||
? existingTrigger.Hotkey
|
||||
: null;
|
||||
|
||||
SelectedHotkey = existingTrigger.TriggerType == TriggerType.Hotkey ? existingTrigger.Hotkey : null;
|
||||
HotkeyType = existingTrigger.TriggerType == TriggerType.Hotkey
|
||||
? existingTrigger.HotkeyType
|
||||
: HotKeyTypeEnum.KeyboardMonitor;
|
||||
|
||||
LoadAvailableTaskDefinitions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -188,9 +177,9 @@ public partial class AddTriggerDialogViewModel : ObservableObject
|
||||
AvailableTaskDefinitions.Add(taskDefinition.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_logger.LogInformation("已加载 {Count} 个可用的任务定义", AvailableTaskDefinitions.Count);
|
||||
|
||||
|
||||
// 如果有任务定义,默认选择第一个
|
||||
if (AvailableTaskDefinitions.Count > 0 && string.IsNullOrWhiteSpace(SelectedTaskDefinitionName))
|
||||
{
|
||||
@@ -229,44 +218,42 @@ public partial class AddTriggerDialogViewModel : ObservableObject
|
||||
|
||||
partial void OnSelectedCronInputModeChanged(CronInputMode value)
|
||||
{
|
||||
if (SelectedTriggerType != TriggerType.Timed)
|
||||
if (SelectedTriggerType == TriggerType.Timed && string.IsNullOrWhiteSpace(CronExpression))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(CronExpression))
|
||||
{
|
||||
CronExpression = "1 0 4 * * ?";
|
||||
CronExpression = DefaultCronExpression;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认创建触发器
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
private void Confirm()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(TriggerName))
|
||||
{
|
||||
Toast.Error("请输入触发器名称");
|
||||
ThemedMessageBox.Error("请输入触发器名称", "保存失败");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedTriggerType == TriggerType.Timed && string.IsNullOrWhiteSpace(CronExpression))
|
||||
{
|
||||
Toast.Error(SelectedCronInputMode == CronInputMode.Manual
|
||||
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)
|
||||
{
|
||||
Toast.Error("请选择热键");
|
||||
ThemedMessageBox.Error("请选择热键", "保存失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建触发器 ViewModel
|
||||
CreatedTrigger = new GearTriggerViewModel(TriggerName, SelectedTriggerType)
|
||||
{
|
||||
IsEnabled = IsEnabled,
|
||||
@@ -294,7 +281,9 @@ public partial class AddTriggerDialogViewModel : ObservableObject
|
||||
[RelayCommand]
|
||||
private void SwitchHotKeyType()
|
||||
{
|
||||
HotkeyType = HotkeyType == HotKeyTypeEnum.GlobalRegister ? HotKeyTypeEnum.KeyboardMonitor : HotKeyTypeEnum.GlobalRegister;
|
||||
HotkeyType = HotkeyType == HotKeyTypeEnum.GlobalRegister
|
||||
? HotKeyTypeEnum.KeyboardMonitor
|
||||
: HotKeyTypeEnum.GlobalRegister;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -303,8 +292,33 @@ public partial class AddTriggerDialogViewModel : ObservableObject
|
||||
[RelayCommand]
|
||||
private void SelectHotkey()
|
||||
{
|
||||
// 移除旧的示例代码,现在使用HotKeyTextBox直接设置
|
||||
// HotKeyTextBox会直接绑定到SelectedHotkey属性
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,6 +326,7 @@ public enum CronInputMode
|
||||
{
|
||||
[Description("可视化选择")]
|
||||
Preset,
|
||||
|
||||
[Description("手动 Cron")]
|
||||
Manual
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user