feat: 优化触发器配置体验
- 根据触发器类型动态展示对应配置区域,减少无关字段干扰。 - 将单次、每日、每周、每月和生效时间范围改为日期选择器与时分秒下拉选择,避免手动输入时间格式。 - 为单次执行增加延后执行快捷设置,支持常用快捷按钮和自定义分钟、小时、天后执行。 - 移除开机自启设置、注册表写入逻辑和相关配置字段,降低对用户系统的影响。 - 同步优化部分任务状态、触发摘要和设置界面文案。
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
<Style TargetType="DataGrid">
|
<Style TargetType="DataGrid">
|
||||||
<Setter Property="AutoGenerateColumns" Value="False" />
|
<Setter Property="AutoGenerateColumns" Value="False" />
|
||||||
<Setter Property="CanUserAddRows" Value="False" />
|
<Setter Property="CanUserAddRows" Value="False" />
|
||||||
|
<Setter Property="CanUserSortColumns" Value="True" />
|
||||||
<Setter Property="IsReadOnly" Value="True" />
|
<Setter Property="IsReadOnly" Value="True" />
|
||||||
<Setter Property="GridLinesVisibility" Value="Horizontal" />
|
<Setter Property="GridLinesVisibility" Value="Horizontal" />
|
||||||
<Setter Property="HeadersVisibility" Value="Column" />
|
<Setter Property="HeadersVisibility" Value="Column" />
|
||||||
@@ -56,13 +57,13 @@
|
|||||||
SelectedItem="{Binding SelectedTask, Mode=TwoWay}"
|
SelectedItem="{Binding SelectedTask, Mode=TwoWay}"
|
||||||
MouseDoubleClick="TasksGrid_MouseDoubleClick">
|
MouseDoubleClick="TasksGrid_MouseDoubleClick">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridCheckBoxColumn Header="启用" Binding="{Binding IsEnabled}" Width="58" IsReadOnly="False" />
|
<DataGridCheckBoxColumn Header="启用" Binding="{Binding IsEnabled}" SortMemberPath="IsEnabled" Width="58" IsReadOnly="False" />
|
||||||
<DataGridTextColumn Header="任务名称" Binding="{Binding Name}" Width="190" />
|
<DataGridTextColumn Header="任务名称" Binding="{Binding Name}" SortMemberPath="Name" Width="190" />
|
||||||
<DataGridTextColumn Header="频道" Binding="{Binding Channel}" Width="150" />
|
<DataGridTextColumn Header="频道" Binding="{Binding Channel}" SortMemberPath="Channel" Width="150" />
|
||||||
<DataGridTextColumn Header="触发规则" Binding="{Binding TriggerSummary}" Width="*" />
|
<DataGridTextColumn Header="触发规则" Binding="{Binding TriggerSummary}" SortMemberPath="TriggerSummary" Width="*" />
|
||||||
<DataGridTextColumn Header="上次运行" Binding="{Binding LastRunAt, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="155" />
|
<DataGridTextColumn Header="上次运行" Binding="{Binding LastRunAt, StringFormat=yyyy-MM-dd HH:mm:ss}" SortMemberPath="LastRunAt" Width="155" />
|
||||||
<DataGridTextColumn Header="下次运行" Binding="{Binding NextRunAt, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="155" />
|
<DataGridTextColumn Header="下次运行" Binding="{Binding NextRunAt, StringFormat=yyyy-MM-dd HH:mm:ss}" SortMemberPath="NextRunAt" Width="155" />
|
||||||
<DataGridTextColumn Header="最近状态" Binding="{Binding LastStatus}" Width="90" />
|
<DataGridTextColumn Header="最近状态" Binding="{Binding LastStatus}" SortMemberPath="LastStatus" Width="90" />
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
using Forms = System.Windows.Forms;
|
using Forms = System.Windows.Forms;
|
||||||
|
|
||||||
namespace OmniScheduler;
|
namespace OmniScheduler;
|
||||||
@@ -102,7 +104,16 @@ public partial class MainWindow : Window, INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void TasksGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
private void TasksGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||||
=> EditTask_Click(sender, e);
|
{
|
||||||
|
var row = FindVisualParent<DataGridRow>(e.OriginalSource as DependencyObject);
|
||||||
|
if (row?.Item is not ScheduledTask task)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedTask = task;
|
||||||
|
EditTask_Click(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
private void DeleteTask_Click(object sender, RoutedEventArgs e)
|
private void DeleteTask_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -148,7 +159,6 @@ public partial class MainWindow : Window, INotifyPropertyChanged
|
|||||||
{
|
{
|
||||||
if (SettingsWindow.Edit(this, State.Settings))
|
if (SettingsWindow.Edit(this, State.Settings))
|
||||||
{
|
{
|
||||||
StartupRegistration.Apply(State.Settings.StartWithWindows);
|
|
||||||
SaveAndRefresh("设置已保存");
|
SaveAndRefresh("设置已保存");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,4 +232,19 @@ public partial class MainWindow : Window, INotifyPropertyChanged
|
|||||||
_allowExit = true;
|
_allowExit = true;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static T? FindVisualParent<T>(DependencyObject? child) where T : DependencyObject
|
||||||
|
{
|
||||||
|
while (child is not null)
|
||||||
|
{
|
||||||
|
if (child is T parent)
|
||||||
|
{
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
child = VisualTreeHelper.GetParent(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ public sealed class AppSettings : NotifyObject
|
|||||||
private string _omniNotifyUrl = "http://127.0.0.1:19845/notify";
|
private string _omniNotifyUrl = "http://127.0.0.1:19845/notify";
|
||||||
private int _logRetentionDays = 30;
|
private int _logRetentionDays = 30;
|
||||||
private int _maxLogRecords = 10000;
|
private int _maxLogRecords = 10000;
|
||||||
private bool _startWithWindows;
|
|
||||||
|
|
||||||
public string OmniNotifyUrl
|
public string OmniNotifyUrl
|
||||||
{
|
{
|
||||||
@@ -66,11 +65,6 @@ public sealed class AppSettings : NotifyObject
|
|||||||
set => SetField(ref _maxLogRecords, Math.Max(100, value));
|
set => SetField(ref _maxLogRecords, Math.Max(100, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool StartWithWindows
|
|
||||||
{
|
|
||||||
get => _startWithWindows;
|
|
||||||
set => SetField(ref _startWithWindows, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ScheduledTask : NotifyObject
|
public sealed class ScheduledTask : NotifyObject
|
||||||
@@ -83,7 +77,7 @@ public sealed class ScheduledTask : NotifyObject
|
|||||||
private string _body = "";
|
private string _body = "";
|
||||||
private DateTime? _lastRunAt;
|
private DateTime? _lastRunAt;
|
||||||
private DateTime? _nextRunAt;
|
private DateTime? _nextRunAt;
|
||||||
private string _lastStatus = "Idle";
|
private string _lastStatus = "待命";
|
||||||
|
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
@@ -200,8 +194,8 @@ public sealed class TaskTrigger : NotifyObject
|
|||||||
TriggerKind.OneTime => $"单次 {OneTimeAt:yyyy-MM-dd HH:mm:ss}",
|
TriggerKind.OneTime => $"单次 {OneTimeAt:yyyy-MM-dd HH:mm:ss}",
|
||||||
TriggerKind.Interval => $"每 {IntervalValue} {IntervalUnitText}",
|
TriggerKind.Interval => $"每 {IntervalValue} {IntervalUnitText}",
|
||||||
TriggerKind.Daily => $"每日 {TimeOfDay:hh\\:mm\\:ss}",
|
TriggerKind.Daily => $"每日 {TimeOfDay:hh\\:mm\\:ss}",
|
||||||
TriggerKind.Weekly => $"每周 {WeekDays} {TimeOfDay:hh\\:mm\\:ss}",
|
TriggerKind.Weekly => $"每周 {WeekDaysText} {TimeOfDay:hh\\:mm\\:ss}",
|
||||||
TriggerKind.Monthly => LastBusinessDay ? $"每月最后工作日 {TimeOfDay:hh\\:mm\\:ss}" : $"每月 {MonthDay} 日 {TimeOfDay:hh\\:mm\\:ss}",
|
TriggerKind.Monthly => LastBusinessDay ? $"每月最后一个工作日 {TimeOfDay:hh\\:mm\\:ss}" : $"每月 {MonthDay} 日 {TimeOfDay:hh\\:mm\\:ss}",
|
||||||
TriggerKind.Cron => $"Cron {CronExpression}",
|
TriggerKind.Cron => $"Cron {CronExpression}",
|
||||||
_ => Kind.ToString()
|
_ => Kind.ToString()
|
||||||
};
|
};
|
||||||
@@ -216,6 +210,23 @@ public sealed class TaskTrigger : NotifyObject
|
|||||||
_ => IntervalUnit.ToString()
|
_ => IntervalUnit.ToString()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string WeekDaysText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var days = new List<string>();
|
||||||
|
if (WeekDays.HasFlag(DayOfWeekFlags.Sunday)) days.Add("日");
|
||||||
|
if (WeekDays.HasFlag(DayOfWeekFlags.Monday)) days.Add("一");
|
||||||
|
if (WeekDays.HasFlag(DayOfWeekFlags.Tuesday)) days.Add("二");
|
||||||
|
if (WeekDays.HasFlag(DayOfWeekFlags.Wednesday)) days.Add("三");
|
||||||
|
if (WeekDays.HasFlag(DayOfWeekFlags.Thursday)) days.Add("四");
|
||||||
|
if (WeekDays.HasFlag(DayOfWeekFlags.Friday)) days.Add("五");
|
||||||
|
if (WeekDays.HasFlag(DayOfWeekFlags.Saturday)) days.Add("六");
|
||||||
|
return days.Count == 0 ? "未选择" : string.Join(",", days);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public TaskTrigger Clone() => new()
|
public TaskTrigger Clone() => new()
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Microsoft.Win32;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -54,8 +53,9 @@ public sealed class StateStore
|
|||||||
{
|
{
|
||||||
var task = new ScheduledTask
|
var task = new ScheduledTask
|
||||||
{
|
{
|
||||||
|
IsEnabled = false,
|
||||||
Name = "OmniNotify 连通性测试",
|
Name = "OmniNotify 连通性测试",
|
||||||
Description = "可克隆此任务作为新消息模板。",
|
Description = "可克隆此任务作为新消息模板。默认禁用,避免首次启动自动发送。",
|
||||||
Channel = "default",
|
Channel = "default",
|
||||||
Title = "OmniScheduler 测试",
|
Title = "OmniScheduler 测试",
|
||||||
Body = "来自 {TaskName} 的测试消息,触发时间 {CurrentTime}。"
|
Body = "来自 {TaskName} 的测试消息,触发时间 {CurrentTime}。"
|
||||||
@@ -138,7 +138,7 @@ public sealed class NotifyClient
|
|||||||
private static string BuildErrorMessage(string response, string fallback)
|
private static string BuildErrorMessage(string response, string fallback)
|
||||||
=> response.Contains("IllegalChannel", StringComparison.OrdinalIgnoreCase)
|
=> response.Contains("IllegalChannel", StringComparison.OrdinalIgnoreCase)
|
||||||
? "ERROR: IllegalChannel,请检查频道名配置"
|
? "ERROR: IllegalChannel,请检查频道名配置"
|
||||||
: $"发送失败: {fallback}";
|
: $"发送失败 {fallback}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SchedulerService
|
public sealed class SchedulerService
|
||||||
@@ -180,7 +180,7 @@ public sealed class SchedulerService
|
|||||||
|
|
||||||
public async Task FireNowAsync(ScheduledTask task)
|
public async Task FireNowAsync(ScheduledTask task)
|
||||||
{
|
{
|
||||||
await ExecuteAsync(task, "Manual");
|
await ExecuteAsync(task, "手动触发");
|
||||||
task.NextRunAt = FindNext(task, DateTime.Now);
|
task.NextRunAt = FindNext(task, DateTime.Now);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ public sealed class SchedulerService
|
|||||||
{
|
{
|
||||||
task.LastRunAt = DateTime.Now;
|
task.LastRunAt = DateTime.Now;
|
||||||
var log = await _client.SendAsync(task, triggerType, _state.Settings.OmniNotifyUrl);
|
var log = await _client.SendAsync(task, triggerType, _state.Settings.OmniNotifyUrl);
|
||||||
task.LastStatus = log.Level == "INFO" ? "Success" : "Failed";
|
task.LastStatus = log.Level == "INFO" ? "成功" : "失败";
|
||||||
_logSink(log);
|
_logSink(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ public sealed class SchedulerService
|
|||||||
var trigger = task.Triggers
|
var trigger = task.Triggers
|
||||||
.OrderBy(t => Math.Abs(((NextRunCalculator.NextRun(t, DateTime.Now.AddDays(-1)) ?? dueAt) - dueAt).TotalSeconds))
|
.OrderBy(t => Math.Abs(((NextRunCalculator.NextRun(t, DateTime.Now.AddDays(-1)) ?? dueAt) - dueAt).TotalSeconds))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
return trigger?.Kind.ToString() ?? "Scheduled";
|
return trigger?.Summary ?? "计划触发";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DateTime? FindNext(ScheduledTask task, DateTime from)
|
private static DateTime? FindNext(ScheduledTask task, DateTime from)
|
||||||
@@ -294,7 +294,12 @@ public static class NextRunCalculator
|
|||||||
_ => TimeSpan.FromMinutes(trigger.IntervalValue)
|
_ => TimeSpan.FromMinutes(trigger.IntervalValue)
|
||||||
};
|
};
|
||||||
|
|
||||||
var start = trigger.StartsAt ?? DateTime.Now;
|
if (!trigger.StartsAt.HasValue)
|
||||||
|
{
|
||||||
|
return from.Add(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
var start = trigger.StartsAt.Value;
|
||||||
if (from <= start)
|
if (from <= start)
|
||||||
{
|
{
|
||||||
return start;
|
return start;
|
||||||
@@ -424,26 +429,3 @@ public static class NextRunCalculator
|
|||||||
return date.Day;
|
return date.Day;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class StartupRegistration
|
|
||||||
{
|
|
||||||
private const string RunKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
|
|
||||||
|
|
||||||
public static void Apply(bool enabled)
|
|
||||||
{
|
|
||||||
using var key = Registry.CurrentUser.OpenSubKey(RunKey, true);
|
|
||||||
if (key is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enabled)
|
|
||||||
{
|
|
||||||
key.SetValue("OmniScheduler", $"\"{Environment.ProcessPath}\" --tray");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
key.DeleteValue("OmniScheduler", false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -27,17 +27,14 @@
|
|||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock Text="开机自启动" VerticalAlignment="Center" />
|
<TextBlock Text="OmniNotify API 地址" VerticalAlignment="Center" />
|
||||||
<CheckBox x:Name="StartWithWindowsBox" Grid.Column="1" VerticalAlignment="Center" />
|
<TextBox x:Name="UrlBox" Grid.Column="1" Height="30" />
|
||||||
|
|
||||||
<TextBlock Text="OmniNotify API" Grid.Row="1" Margin="0,12,0,0" VerticalAlignment="Center" />
|
<TextBlock Text="日志保留天数" Grid.Row="1" Margin="0,12,0,0" VerticalAlignment="Center" />
|
||||||
<TextBox x:Name="UrlBox" Grid.Row="1" Grid.Column="1" Margin="0,12,0,0" Height="30" />
|
<TextBox x:Name="RetentionDaysBox" Grid.Row="1" Grid.Column="1" Margin="0,12,0,0" Height="30" />
|
||||||
|
|
||||||
<TextBlock Text="日志保留天数" Grid.Row="2" Margin="0,12,0,0" VerticalAlignment="Center" />
|
<TextBlock Text="最大日志条数" Grid.Row="2" Margin="0,12,0,0" VerticalAlignment="Center" />
|
||||||
<TextBox x:Name="RetentionDaysBox" Grid.Row="2" Grid.Column="1" Margin="0,12,0,0" Height="30" />
|
<TextBox x:Name="MaxRecordsBox" Grid.Row="2" Grid.Column="1" Margin="0,12,0,0" Height="30" />
|
||||||
|
|
||||||
<TextBlock Text="最大日志条数" Grid.Row="3" Margin="0,12,0,0" VerticalAlignment="Center" />
|
|
||||||
<TextBox x:Name="MaxRecordsBox" Grid.Row="3" Grid.Column="1" Margin="0,12,0,0" Height="30" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ public partial class SettingsWindow : Window
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Owner = owner;
|
Owner = owner;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
StartWithWindowsBox.IsChecked = settings.StartWithWindows;
|
|
||||||
UrlBox.Text = settings.OmniNotifyUrl;
|
UrlBox.Text = settings.OmniNotifyUrl;
|
||||||
RetentionDaysBox.Text = settings.LogRetentionDays.ToString();
|
RetentionDaysBox.Text = settings.LogRetentionDays.ToString();
|
||||||
MaxRecordsBox.Text = settings.MaxLogRecords.ToString();
|
MaxRecordsBox.Text = settings.MaxLogRecords.ToString();
|
||||||
@@ -44,7 +43,6 @@ public partial class SettingsWindow : Window
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_settings.StartWithWindows = StartWithWindowsBox.IsChecked == true;
|
|
||||||
_settings.OmniNotifyUrl = UrlBox.Text.Trim();
|
_settings.OmniNotifyUrl = UrlBox.Text.Trim();
|
||||||
_settings.LogRetentionDays = days;
|
_settings.LogRetentionDays = days;
|
||||||
_settings.MaxLogRecords = max;
|
_settings.MaxLogRecords = max;
|
||||||
|
|||||||
@@ -2,12 +2,33 @@
|
|||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="任务编辑"
|
Title="任务编辑"
|
||||||
Height="650"
|
Height="690"
|
||||||
Width="860"
|
Width="900"
|
||||||
MinHeight="600"
|
MinHeight="620"
|
||||||
MinWidth="780"
|
MinWidth="820"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
Icon="app.ico">
|
Icon="app.ico">
|
||||||
|
<Window.Resources>
|
||||||
|
<Style x:Key="SectionTitle" TargetType="TextBlock">
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
<Setter Property="Margin" Value="0,0,0,8" />
|
||||||
|
</Style>
|
||||||
|
<Style x:Key="FieldLabel" TargetType="TextBlock">
|
||||||
|
<Setter Property="Width" Value="90" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="Margin" Value="0,0,10,0" />
|
||||||
|
</Style>
|
||||||
|
<Style x:Key="FieldRow" TargetType="StackPanel">
|
||||||
|
<Setter Property="Orientation" Value="Horizontal" />
|
||||||
|
<Setter Property="Margin" Value="0,8,0,0" />
|
||||||
|
</Style>
|
||||||
|
<Style x:Key="TimeCombo" TargetType="ComboBox">
|
||||||
|
<Setter Property="Width" Value="58" />
|
||||||
|
<Setter Property="Height" Value="30" />
|
||||||
|
<Setter Property="DisplayMemberPath" Value="Label" />
|
||||||
|
</Style>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
<DockPanel Margin="12">
|
<DockPanel Margin="12">
|
||||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
|
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
|
||||||
<TextBlock x:Name="ValidationText" Foreground="#B42318" VerticalAlignment="Center" Margin="0,0,14,0" />
|
<TextBlock x:Name="ValidationText" Foreground="#B42318" VerticalAlignment="Center" Margin="0,0,14,0" />
|
||||||
@@ -55,72 +76,159 @@
|
|||||||
|
|
||||||
<Border Grid.Column="2" BorderBrush="#D8DEE9" BorderThickness="1" Padding="12">
|
<Border Grid.Column="2" BorderBrush="#D8DEE9" BorderThickness="1" Padding="12">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<TextBlock x:Name="TriggerEmptyText"
|
||||||
<ColumnDefinition Width="150" />
|
Text="请先添加或选择一个触发器。"
|
||||||
<ColumnDefinition Width="*" />
|
HorizontalAlignment="Center"
|
||||||
</Grid.ColumnDefinitions>
|
VerticalAlignment="Center"
|
||||||
<Grid.RowDefinitions>
|
Foreground="#667085" />
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<TextBlock Text="触发类型" VerticalAlignment="Center" />
|
<DockPanel x:Name="TriggerEditorPanel">
|
||||||
<ComboBox x:Name="KindBox" Grid.Column="1" Height="30" SelectionChanged="Field_Changed" />
|
<StackPanel DockPanel.Dock="Top">
|
||||||
|
<TextBlock Text="触发类型" Style="{StaticResource SectionTitle}" />
|
||||||
|
<ComboBox x:Name="KindBox" Height="30" DisplayMemberPath="Label" SelectionChanged="Field_Changed" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Text="单次时间" Grid.Row="1" Margin="0,10,0,0" VerticalAlignment="Center" />
|
<GroupBox DockPanel.Dock="Bottom" Header="高级调度与预览" Margin="0,14,0,0">
|
||||||
<TextBox x:Name="OneTimeBox" Grid.Row="1" Grid.Column="1" Margin="0,10,0,0" Height="30" ToolTip="格式:yyyy-MM-dd HH:mm:ss" TextChanged="Field_Changed" />
|
<DockPanel Margin="10">
|
||||||
|
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
|
||||||
|
<TextBlock Text="错过触发补偿策略" VerticalAlignment="Center" Margin="0,0,10,0" />
|
||||||
|
<ComboBox x:Name="MisfireBox" Width="180" Height="30" DisplayMemberPath="Label" />
|
||||||
|
</StackPanel>
|
||||||
|
<TextBox x:Name="PreviewBox" Margin="0,12,0,0" IsReadOnly="True" AcceptsReturn="True" Height="130" />
|
||||||
|
</DockPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
<TextBlock Text="间隔" Grid.Row="2" Margin="0,10,0,0" VerticalAlignment="Center" />
|
<ScrollViewer VerticalScrollBarVisibility="Auto" Margin="0,14,0,0">
|
||||||
<StackPanel Grid.Row="2" Grid.Column="1" Margin="0,10,0,0" Orientation="Horizontal">
|
<StackPanel>
|
||||||
<TextBox x:Name="IntervalValueBox" Width="80" Height="30" TextChanged="Field_Changed" />
|
<StackPanel x:Name="OneTimeSection">
|
||||||
<ComboBox x:Name="IntervalUnitBox" Width="120" Height="30" Margin="8,0,0,0" SelectionChanged="Field_Changed" />
|
<TextBlock Text="单次执行" Style="{StaticResource SectionTitle}" />
|
||||||
</StackPanel>
|
<StackPanel Style="{StaticResource FieldRow}">
|
||||||
|
<TextBlock Text="快速设置" Style="{StaticResource FieldLabel}" />
|
||||||
|
<TextBox x:Name="OneTimeDelayValueBox" Width="70" Height="30" Text="5" />
|
||||||
|
<ComboBox x:Name="OneTimeDelayUnitBox" Width="90" Height="30" Margin="8,0,0,0" DisplayMemberPath="Label" />
|
||||||
|
<TextBlock Text="后执行" VerticalAlignment="Center" Margin="8,0,10,0" />
|
||||||
|
<Button Content="应用" Width="70" Click="ApplyOneTimeDelay_Click" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="100,8,0,0">
|
||||||
|
<Button Content="5 分钟后" Tag="5" Click="QuickOneTimeDelay_Click" />
|
||||||
|
<Button Content="30 分钟后" Tag="30" Margin="8,0,0,0" Click="QuickOneTimeDelay_Click" />
|
||||||
|
<Button Content="1 小时后" Tag="60" Margin="8,0,0,0" Click="QuickOneTimeDelay_Click" />
|
||||||
|
<Button Content="明天此时" Tag="1440" Margin="8,0,0,0" Click="QuickOneTimeDelay_Click" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Style="{StaticResource FieldRow}">
|
||||||
|
<TextBlock Text="执行日期" Style="{StaticResource FieldLabel}" />
|
||||||
|
<DatePicker x:Name="OneTimeDatePicker" Width="150" Height="30" SelectedDateChanged="Field_Changed" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Style="{StaticResource FieldRow}">
|
||||||
|
<TextBlock Text="执行时间" Style="{StaticResource FieldLabel}" />
|
||||||
|
<ComboBox x:Name="OneTimeHourBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="时" VerticalAlignment="Center" Margin="4,0,8,0" />
|
||||||
|
<ComboBox x:Name="OneTimeMinuteBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="分" VerticalAlignment="Center" Margin="4,0,8,0" />
|
||||||
|
<ComboBox x:Name="OneTimeSecondBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="秒" VerticalAlignment="Center" Margin="4,0,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Text="起止时间" Grid.Row="3" Margin="0,10,0,0" VerticalAlignment="Center" />
|
<StackPanel x:Name="IntervalSection">
|
||||||
<StackPanel Grid.Row="3" Grid.Column="1" Margin="0,10,0,0" Orientation="Horizontal">
|
<TextBlock Text="固定间隔" Style="{StaticResource SectionTitle}" />
|
||||||
<TextBox x:Name="StartsAtBox" Width="180" Height="30" ToolTip="留空表示立即生效" TextChanged="Field_Changed" />
|
<StackPanel Style="{StaticResource FieldRow}">
|
||||||
<TextBox x:Name="EndsAtBox" Width="180" Height="30" Margin="8,0,0,0" ToolTip="留空表示长期有效" TextChanged="Field_Changed" />
|
<TextBlock Text="每隔" Style="{StaticResource FieldLabel}" />
|
||||||
</StackPanel>
|
<TextBox x:Name="IntervalValueBox" Width="80" Height="30" TextChanged="Field_Changed" />
|
||||||
|
<ComboBox x:Name="IntervalUnitBox" Width="120" Height="30" Margin="8,0,0,0" DisplayMemberPath="Label" SelectionChanged="Field_Changed" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Text="每日/每周时间" Grid.Row="4" Margin="0,10,0,0" VerticalAlignment="Center" />
|
<StackPanel x:Name="DailySection">
|
||||||
<TextBox x:Name="TimeOfDayBox" Grid.Row="4" Grid.Column="1" Margin="0,10,0,0" Height="30" ToolTip="格式:HH:mm:ss" TextChanged="Field_Changed" />
|
<TextBlock Text="每日定时" Style="{StaticResource SectionTitle}" />
|
||||||
|
<StackPanel Style="{StaticResource FieldRow}">
|
||||||
|
<TextBlock Text="执行时间" Style="{StaticResource FieldLabel}" />
|
||||||
|
<ComboBox x:Name="DailyHourBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="时" VerticalAlignment="Center" Margin="4,0,8,0" />
|
||||||
|
<ComboBox x:Name="DailyMinuteBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="分" VerticalAlignment="Center" Margin="4,0,8,0" />
|
||||||
|
<ComboBox x:Name="DailySecondBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="秒" VerticalAlignment="Center" Margin="4,0,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Text="周几" Grid.Row="5" Margin="0,10,0,0" VerticalAlignment="Center" />
|
<StackPanel x:Name="WeeklySection">
|
||||||
<WrapPanel Grid.Row="5" Grid.Column="1" Margin="0,10,0,0">
|
<TextBlock Text="每周定时" Style="{StaticResource SectionTitle}" />
|
||||||
<CheckBox x:Name="SunBox" Content="日" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
<StackPanel Style="{StaticResource FieldRow}">
|
||||||
<CheckBox x:Name="MonBox" Content="一" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
<TextBlock Text="星期" Style="{StaticResource FieldLabel}" />
|
||||||
<CheckBox x:Name="TueBox" Content="二" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
<WrapPanel>
|
||||||
<CheckBox x:Name="WedBox" Content="三" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
<CheckBox x:Name="SunBox" Content="日" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
||||||
<CheckBox x:Name="ThuBox" Content="四" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
<CheckBox x:Name="MonBox" Content="一" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
||||||
<CheckBox x:Name="FriBox" Content="五" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
<CheckBox x:Name="TueBox" Content="二" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
||||||
<CheckBox x:Name="SatBox" Content="六" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
<CheckBox x:Name="WedBox" Content="三" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
||||||
</WrapPanel>
|
<CheckBox x:Name="ThuBox" Content="四" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
||||||
|
<CheckBox x:Name="FriBox" Content="五" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
||||||
|
<CheckBox x:Name="SatBox" Content="六" Margin="0,0,10,0" Checked="Field_Changed" Unchecked="Field_Changed" />
|
||||||
|
</WrapPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Style="{StaticResource FieldRow}">
|
||||||
|
<TextBlock Text="执行时间" Style="{StaticResource FieldLabel}" />
|
||||||
|
<ComboBox x:Name="WeeklyHourBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="时" VerticalAlignment="Center" Margin="4,0,8,0" />
|
||||||
|
<ComboBox x:Name="WeeklyMinuteBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="分" VerticalAlignment="Center" Margin="4,0,8,0" />
|
||||||
|
<ComboBox x:Name="WeeklySecondBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="秒" VerticalAlignment="Center" Margin="4,0,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Text="每月" Grid.Row="6" Margin="0,10,0,0" VerticalAlignment="Center" />
|
<StackPanel x:Name="MonthlySection">
|
||||||
<StackPanel Grid.Row="6" Grid.Column="1" Margin="0,10,0,0" Orientation="Horizontal">
|
<TextBlock Text="每月定时" Style="{StaticResource SectionTitle}" />
|
||||||
<TextBox x:Name="MonthDayBox" Width="80" Height="30" TextChanged="Field_Changed" />
|
<StackPanel Style="{StaticResource FieldRow}">
|
||||||
<CheckBox x:Name="LastBusinessDayBox" Content="最后一个工作日" Margin="10,0,0,0" VerticalAlignment="Center" Checked="Field_Changed" Unchecked="Field_Changed" />
|
<TextBlock Text="日期" Style="{StaticResource FieldLabel}" />
|
||||||
</StackPanel>
|
<TextBox x:Name="MonthDayBox" Width="80" Height="30" TextChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="日" VerticalAlignment="Center" Margin="4,0,10,0" />
|
||||||
|
<CheckBox x:Name="LastBusinessDayBox" Content="每月最后一个工作日" VerticalAlignment="Center" Checked="Field_Changed" Unchecked="Field_Changed" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Style="{StaticResource FieldRow}">
|
||||||
|
<TextBlock Text="执行时间" Style="{StaticResource FieldLabel}" />
|
||||||
|
<ComboBox x:Name="MonthlyHourBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="时" VerticalAlignment="Center" Margin="4,0,8,0" />
|
||||||
|
<ComboBox x:Name="MonthlyMinuteBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="分" VerticalAlignment="Center" Margin="4,0,8,0" />
|
||||||
|
<ComboBox x:Name="MonthlySecondBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="秒" VerticalAlignment="Center" Margin="4,0,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Text="Cron 表达式" Grid.Row="7" Margin="0,10,0,0" VerticalAlignment="Center" />
|
<StackPanel x:Name="CronSection">
|
||||||
<TextBox x:Name="CronBox" Grid.Row="7" Grid.Column="1" Margin="0,10,0,0" Height="30" TextChanged="Field_Changed" />
|
<TextBlock Text="Cron 表达式" Style="{StaticResource SectionTitle}" />
|
||||||
|
<TextBox x:Name="CronBox" Height="30" TextChanged="Field_Changed" />
|
||||||
|
<TextBlock Text="适合高级规则;下方会实时预览未来 5 次执行时间。" Foreground="#667085" Margin="0,6,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<GroupBox Grid.Row="8" Grid.ColumnSpan="2" Header="高级调度与预览" Margin="0,14,0,0">
|
<Separator Margin="0,16,0,12" />
|
||||||
<DockPanel Margin="10">
|
<StackPanel x:Name="WindowSection">
|
||||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
|
<TextBlock Text="生效时间范围" Style="{StaticResource SectionTitle}" />
|
||||||
<TextBlock Text="错过触发补偿策略" VerticalAlignment="Center" Margin="0,0,10,0" />
|
<CheckBox x:Name="UseStartsAtBox" Content="设置起始生效时间" Checked="WindowOption_Changed" Unchecked="WindowOption_Changed" />
|
||||||
<ComboBox x:Name="MisfireBox" Width="180" Height="30" />
|
<StackPanel x:Name="StartsAtPanel" Style="{StaticResource FieldRow}">
|
||||||
|
<TextBlock Text="开始于" Style="{StaticResource FieldLabel}" />
|
||||||
|
<DatePicker x:Name="StartsAtDatePicker" Width="150" Height="30" SelectedDateChanged="Field_Changed" />
|
||||||
|
<ComboBox x:Name="StartsAtHourBox" Style="{StaticResource TimeCombo}" Margin="8,0,0,0" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text=":" VerticalAlignment="Center" Margin="4,0" />
|
||||||
|
<ComboBox x:Name="StartsAtMinuteBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text=":" VerticalAlignment="Center" Margin="4,0" />
|
||||||
|
<ComboBox x:Name="StartsAtSecondBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
</StackPanel>
|
||||||
|
<CheckBox x:Name="UseEndsAtBox" Content="设置结束失效时间" Margin="0,10,0,0" Checked="WindowOption_Changed" Unchecked="WindowOption_Changed" />
|
||||||
|
<StackPanel x:Name="EndsAtPanel" Style="{StaticResource FieldRow}">
|
||||||
|
<TextBlock Text="结束于" Style="{StaticResource FieldLabel}" />
|
||||||
|
<DatePicker x:Name="EndsAtDatePicker" Width="150" Height="30" SelectedDateChanged="Field_Changed" />
|
||||||
|
<ComboBox x:Name="EndsAtHourBox" Style="{StaticResource TimeCombo}" Margin="8,0,0,0" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text=":" VerticalAlignment="Center" Margin="4,0" />
|
||||||
|
<ComboBox x:Name="EndsAtMinuteBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
<TextBlock Text=":" VerticalAlignment="Center" Margin="4,0" />
|
||||||
|
<ComboBox x:Name="EndsAtSecondBox" Style="{StaticResource TimeCombo}" SelectionChanged="Field_Changed" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TextBox x:Name="PreviewBox" Margin="0,12,0,0" IsReadOnly="True" AcceptsReturn="True" Height="130" />
|
</ScrollViewer>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</GroupBox>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -142,14 +250,14 @@
|
|||||||
|
|
||||||
<TextBlock Text="目标地址" VerticalAlignment="Center" />
|
<TextBlock Text="目标地址" VerticalAlignment="Center" />
|
||||||
<TextBox x:Name="TargetUrlBox" Grid.Column="1" Height="30" IsReadOnly="True" />
|
<TextBox x:Name="TargetUrlBox" Grid.Column="1" Height="30" IsReadOnly="True" />
|
||||||
<TextBlock Text="Channel" Grid.Row="1" Margin="0,10,0,0" VerticalAlignment="Center" />
|
<TextBlock Text="频道" Grid.Row="1" Margin="0,10,0,0" VerticalAlignment="Center" />
|
||||||
<ComboBox x:Name="ChannelBox" Grid.Row="1" Grid.Column="1" Margin="0,10,0,0" Height="30" IsEditable="True" />
|
<ComboBox x:Name="ChannelBox" Grid.Row="1" Grid.Column="1" Margin="0,10,0,0" Height="30" IsEditable="True" />
|
||||||
<TextBlock Text="Title" Grid.Row="2" Margin="0,10,0,0" VerticalAlignment="Center" />
|
<TextBlock Text="标题" Grid.Row="2" Margin="0,10,0,0" VerticalAlignment="Center" />
|
||||||
<TextBox x:Name="TitleBox" Grid.Row="2" Grid.Column="1" Margin="0,10,0,0" Height="30" />
|
<TextBox x:Name="TitleBox" Grid.Row="2" Grid.Column="1" Margin="0,10,0,0" Height="30" />
|
||||||
<TextBlock Text="Body" Grid.Row="3" Margin="0,10,0,0" />
|
<TextBlock Text="内容" Grid.Row="3" Margin="0,10,0,0" />
|
||||||
<TextBox x:Name="BodyBox" Grid.Row="3" Grid.Column="1" Margin="0,10,0,0" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" />
|
<TextBox x:Name="BodyBox" Grid.Row="3" Grid.Column="1" Margin="0,10,0,0" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" />
|
||||||
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" Margin="0,10,0,0">
|
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" Margin="0,10,0,0">
|
||||||
<Button Content="Send Test Message" Click="TestSend_Click" />
|
<Button Content="发送测试消息" Click="TestSend_Click" />
|
||||||
<TextBlock x:Name="TestResultText" VerticalAlignment="Center" Margin="8,0,0,0" />
|
<TextBlock x:Name="TestResultText" VerticalAlignment="Center" Margin="8,0,0,0" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using Button = System.Windows.Controls.Button;
|
||||||
|
using ComboBox = System.Windows.Controls.ComboBox;
|
||||||
|
|
||||||
namespace OmniScheduler;
|
namespace OmniScheduler;
|
||||||
|
|
||||||
@@ -9,8 +10,40 @@ public partial class TaskEditorWindow : Window
|
|||||||
private readonly ScheduledTask _task;
|
private readonly ScheduledTask _task;
|
||||||
private readonly AppSettings _settings;
|
private readonly AppSettings _settings;
|
||||||
private readonly NotifyClient _client;
|
private readonly NotifyClient _client;
|
||||||
|
private readonly List<Option<int>> _hours = Enumerable.Range(0, 24).Select(v => new Option<int>(v, v.ToString("00"))).ToList();
|
||||||
|
private readonly List<Option<int>> _minutesAndSeconds = Enumerable.Range(0, 60).Select(v => new Option<int>(v, v.ToString("00"))).ToList();
|
||||||
|
private readonly List<Option<DelayUnit>> _delayUnits =
|
||||||
|
[
|
||||||
|
new(DelayUnit.Minutes, "分钟"),
|
||||||
|
new(DelayUnit.Hours, "小时"),
|
||||||
|
new(DelayUnit.Days, "天")
|
||||||
|
];
|
||||||
private bool _loading;
|
private bool _loading;
|
||||||
|
|
||||||
|
private readonly List<Option<TriggerKind>> _triggerKinds =
|
||||||
|
[
|
||||||
|
new(TriggerKind.OneTime, "单次执行"),
|
||||||
|
new(TriggerKind.Interval, "固定间隔"),
|
||||||
|
new(TriggerKind.Daily, "每日定时"),
|
||||||
|
new(TriggerKind.Weekly, "每周定时"),
|
||||||
|
new(TriggerKind.Monthly, "每月定时"),
|
||||||
|
new(TriggerKind.Cron, "Cron 表达式")
|
||||||
|
];
|
||||||
|
|
||||||
|
private readonly List<Option<IntervalUnit>> _intervalUnits =
|
||||||
|
[
|
||||||
|
new(IntervalUnit.Seconds, "秒"),
|
||||||
|
new(IntervalUnit.Minutes, "分钟"),
|
||||||
|
new(IntervalUnit.Hours, "小时"),
|
||||||
|
new(IntervalUnit.Days, "天")
|
||||||
|
];
|
||||||
|
|
||||||
|
private readonly List<Option<MisfireStrategy>> _misfireStrategies =
|
||||||
|
[
|
||||||
|
new(MisfireStrategy.DoNothing, "忽略,等待下一次"),
|
||||||
|
new(MisfireStrategy.FireOnceNow, "立即补偿一次")
|
||||||
|
];
|
||||||
|
|
||||||
private TaskEditorWindow(Window owner, ScheduledTask task, AppSettings settings, NotifyClient client)
|
private TaskEditorWindow(Window owner, ScheduledTask task, AppSettings settings, NotifyClient client)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -19,13 +52,16 @@ public partial class TaskEditorWindow : Window
|
|||||||
_settings = settings;
|
_settings = settings;
|
||||||
_client = client;
|
_client = client;
|
||||||
|
|
||||||
KindBox.ItemsSource = Enum.GetValues<TriggerKind>();
|
KindBox.ItemsSource = _triggerKinds;
|
||||||
IntervalUnitBox.ItemsSource = Enum.GetValues<IntervalUnit>();
|
IntervalUnitBox.ItemsSource = _intervalUnits;
|
||||||
MisfireBox.ItemsSource = Enum.GetValues<MisfireStrategy>();
|
OneTimeDelayUnitBox.ItemsSource = _delayUnits;
|
||||||
|
OneTimeDelayUnitBox.SelectedItem = _delayUnits.First(o => o.Value == DelayUnit.Minutes);
|
||||||
|
MisfireBox.ItemsSource = _misfireStrategies;
|
||||||
ChannelBox.ItemsSource = owner is MainWindow main
|
ChannelBox.ItemsSource = owner is MainWindow main
|
||||||
? main.State.Tasks.Select(t => t.Channel).Where(c => !string.IsNullOrWhiteSpace(c)).Distinct().ToList()
|
? main.State.Tasks.Select(t => t.Channel).Where(c => !string.IsNullOrWhiteSpace(c)).Distinct().ToList()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
InitializeTimeSelectors();
|
||||||
LoadTask();
|
LoadTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +80,16 @@ public partial class TaskEditorWindow : Window
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InitializeTimeSelectors()
|
||||||
|
{
|
||||||
|
SetTimeItems(OneTimeHourBox, OneTimeMinuteBox, OneTimeSecondBox);
|
||||||
|
SetTimeItems(DailyHourBox, DailyMinuteBox, DailySecondBox);
|
||||||
|
SetTimeItems(WeeklyHourBox, WeeklyMinuteBox, WeeklySecondBox);
|
||||||
|
SetTimeItems(MonthlyHourBox, MonthlyMinuteBox, MonthlySecondBox);
|
||||||
|
SetTimeItems(StartsAtHourBox, StartsAtMinuteBox, StartsAtSecondBox);
|
||||||
|
SetTimeItems(EndsAtHourBox, EndsAtMinuteBox, EndsAtSecondBox);
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadTask()
|
private void LoadTask()
|
||||||
{
|
{
|
||||||
NameBox.Text = _task.Name;
|
NameBox.Text = _task.Name;
|
||||||
@@ -53,9 +99,10 @@ public partial class TaskEditorWindow : Window
|
|||||||
ChannelBox.Text = _task.Channel;
|
ChannelBox.Text = _task.Channel;
|
||||||
TitleBox.Text = _task.Title;
|
TitleBox.Text = _task.Title;
|
||||||
BodyBox.Text = _task.Body;
|
BodyBox.Text = _task.Body;
|
||||||
MisfireBox.SelectedItem = _task.MisfireStrategy;
|
MisfireBox.SelectedItem = _misfireStrategies.First(o => o.Value == _task.MisfireStrategy);
|
||||||
TriggersList.ItemsSource = _task.Triggers;
|
TriggersList.ItemsSource = _task.Triggers;
|
||||||
TriggersList.SelectedIndex = _task.Triggers.Count > 0 ? 0 : -1;
|
TriggersList.SelectedIndex = _task.Triggers.Count > 0 ? 0 : -1;
|
||||||
|
UpdateEditorAvailability();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadTrigger(TaskTrigger? trigger)
|
private void LoadTrigger(TaskTrigger? trigger)
|
||||||
@@ -65,16 +112,23 @@ public partial class TaskEditorWindow : Window
|
|||||||
{
|
{
|
||||||
if (trigger is null)
|
if (trigger is null)
|
||||||
{
|
{
|
||||||
|
UpdateEditorAvailability();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
KindBox.SelectedItem = trigger.Kind;
|
KindBox.SelectedItem = _triggerKinds.First(o => o.Value == trigger.Kind);
|
||||||
OneTimeBox.Text = trigger.OneTimeAt.ToString("yyyy-MM-dd HH:mm:ss");
|
SetDateTime(OneTimeDatePicker, OneTimeHourBox, OneTimeMinuteBox, OneTimeSecondBox, trigger.OneTimeAt);
|
||||||
IntervalValueBox.Text = trigger.IntervalValue.ToString();
|
IntervalValueBox.Text = trigger.IntervalValue.ToString();
|
||||||
IntervalUnitBox.SelectedItem = trigger.IntervalUnit;
|
IntervalUnitBox.SelectedItem = _intervalUnits.First(o => o.Value == trigger.IntervalUnit);
|
||||||
StartsAtBox.Text = trigger.StartsAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? "";
|
|
||||||
EndsAtBox.Text = trigger.EndsAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? "";
|
UseStartsAtBox.IsChecked = trigger.StartsAt.HasValue;
|
||||||
TimeOfDayBox.Text = trigger.TimeOfDay.ToString(@"hh\:mm\:ss");
|
SetOptionalDateTime(StartsAtDatePicker, StartsAtHourBox, StartsAtMinuteBox, StartsAtSecondBox, trigger.StartsAt);
|
||||||
|
UseEndsAtBox.IsChecked = trigger.EndsAt.HasValue;
|
||||||
|
SetOptionalDateTime(EndsAtDatePicker, EndsAtHourBox, EndsAtMinuteBox, EndsAtSecondBox, trigger.EndsAt);
|
||||||
|
|
||||||
|
SetTimeOnly(DailyHourBox, DailyMinuteBox, DailySecondBox, trigger.TimeOfDay);
|
||||||
|
SetTimeOnly(WeeklyHourBox, WeeklyMinuteBox, WeeklySecondBox, trigger.TimeOfDay);
|
||||||
|
SetTimeOnly(MonthlyHourBox, MonthlyMinuteBox, MonthlySecondBox, trigger.TimeOfDay);
|
||||||
MonthDayBox.Text = trigger.MonthDay.ToString();
|
MonthDayBox.Text = trigger.MonthDay.ToString();
|
||||||
LastBusinessDayBox.IsChecked = trigger.LastBusinessDay;
|
LastBusinessDayBox.IsChecked = trigger.LastBusinessDay;
|
||||||
CronBox.Text = trigger.CronExpression;
|
CronBox.Text = trigger.CronExpression;
|
||||||
@@ -86,6 +140,10 @@ public partial class TaskEditorWindow : Window
|
|||||||
ThuBox.IsChecked = trigger.WeekDays.HasFlag(DayOfWeekFlags.Thursday);
|
ThuBox.IsChecked = trigger.WeekDays.HasFlag(DayOfWeekFlags.Thursday);
|
||||||
FriBox.IsChecked = trigger.WeekDays.HasFlag(DayOfWeekFlags.Friday);
|
FriBox.IsChecked = trigger.WeekDays.HasFlag(DayOfWeekFlags.Friday);
|
||||||
SatBox.IsChecked = trigger.WeekDays.HasFlag(DayOfWeekFlags.Saturday);
|
SatBox.IsChecked = trigger.WeekDays.HasFlag(DayOfWeekFlags.Saturday);
|
||||||
|
|
||||||
|
UpdateVisibleSections(trigger.Kind);
|
||||||
|
UpdateWindowControlState();
|
||||||
|
UpdateEditorAvailability();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -101,8 +159,8 @@ public partial class TaskEditorWindow : Window
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger.Kind = KindBox.SelectedItem is TriggerKind kind ? kind : TriggerKind.Interval;
|
trigger.Kind = KindBox.SelectedItem is Option<TriggerKind> kind ? kind.Value : TriggerKind.Interval;
|
||||||
trigger.IntervalUnit = IntervalUnitBox.SelectedItem is IntervalUnit unit ? unit : IntervalUnit.Minutes;
|
trigger.IntervalUnit = IntervalUnitBox.SelectedItem is Option<IntervalUnit> unit ? unit.Value : IntervalUnit.Minutes;
|
||||||
trigger.LastBusinessDay = LastBusinessDayBox.IsChecked == true;
|
trigger.LastBusinessDay = LastBusinessDayBox.IsChecked == true;
|
||||||
trigger.CronExpression = CronBox.Text.Trim();
|
trigger.CronExpression = CronBox.Text.Trim();
|
||||||
|
|
||||||
@@ -116,18 +174,24 @@ public partial class TaskEditorWindow : Window
|
|||||||
trigger.MonthDay = Math.Clamp(monthDay, 1, 31);
|
trigger.MonthDay = Math.Clamp(monthDay, 1, 31);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DateTime.TryParse(OneTimeBox.Text, out var oneTime))
|
if (GetDateTime(OneTimeDatePicker, OneTimeHourBox, OneTimeMinuteBox, OneTimeSecondBox) is { } oneTime)
|
||||||
{
|
{
|
||||||
trigger.OneTimeAt = oneTime;
|
trigger.OneTimeAt = oneTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger.StartsAt = DateTime.TryParse(StartsAtBox.Text, out var startsAt) ? startsAt : null;
|
trigger.StartsAt = UseStartsAtBox.IsChecked == true
|
||||||
trigger.EndsAt = DateTime.TryParse(EndsAtBox.Text, out var endsAt) ? endsAt : null;
|
? GetDateTime(StartsAtDatePicker, StartsAtHourBox, StartsAtMinuteBox, StartsAtSecondBox)
|
||||||
|
: null;
|
||||||
|
trigger.EndsAt = UseEndsAtBox.IsChecked == true
|
||||||
|
? GetDateTime(EndsAtDatePicker, EndsAtHourBox, EndsAtMinuteBox, EndsAtSecondBox)
|
||||||
|
: null;
|
||||||
|
|
||||||
if (TimeSpan.TryParse(TimeOfDayBox.Text, out var time))
|
trigger.TimeOfDay = trigger.Kind switch
|
||||||
{
|
{
|
||||||
trigger.TimeOfDay = time;
|
TriggerKind.Weekly => GetTimeOnly(WeeklyHourBox, WeeklyMinuteBox, WeeklySecondBox),
|
||||||
}
|
TriggerKind.Monthly => GetTimeOnly(MonthlyHourBox, MonthlyMinuteBox, MonthlySecondBox),
|
||||||
|
_ => GetTimeOnly(DailyHourBox, DailyMinuteBox, DailySecondBox)
|
||||||
|
};
|
||||||
|
|
||||||
var days = DayOfWeekFlags.None;
|
var days = DayOfWeekFlags.None;
|
||||||
if (SunBox.IsChecked == true) days |= DayOfWeekFlags.Sunday;
|
if (SunBox.IsChecked == true) days |= DayOfWeekFlags.Sunday;
|
||||||
@@ -138,6 +202,9 @@ public partial class TaskEditorWindow : Window
|
|||||||
if (FriBox.IsChecked == true) days |= DayOfWeekFlags.Friday;
|
if (FriBox.IsChecked == true) days |= DayOfWeekFlags.Friday;
|
||||||
if (SatBox.IsChecked == true) days |= DayOfWeekFlags.Saturday;
|
if (SatBox.IsChecked == true) days |= DayOfWeekFlags.Saturday;
|
||||||
trigger.WeekDays = days;
|
trigger.WeekDays = days;
|
||||||
|
|
||||||
|
UpdateVisibleSections(trigger.Kind);
|
||||||
|
UpdateWindowControlState();
|
||||||
trigger.Notify(nameof(TaskTrigger.Summary));
|
trigger.Notify(nameof(TaskTrigger.Summary));
|
||||||
TriggersList.Items.Refresh();
|
TriggersList.Items.Refresh();
|
||||||
RefreshPreview();
|
RefreshPreview();
|
||||||
@@ -162,6 +229,18 @@ public partial class TaskEditorWindow : Window
|
|||||||
ValidationText.Text = "固定间隔不能小于 1";
|
ValidationText.Text = "固定间隔不能小于 1";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trigger.Kind == TriggerKind.Weekly && trigger.WeekDays == DayOfWeekFlags.None)
|
||||||
|
{
|
||||||
|
ValidationText.Text = "每周定时至少选择一个星期";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trigger.StartsAt.HasValue && trigger.EndsAt.HasValue && trigger.StartsAt >= trigger.EndsAt)
|
||||||
|
{
|
||||||
|
ValidationText.Text = "结束失效时间必须晚于起始生效时间";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_task.Name = NameBox.Text.Trim();
|
_task.Name = NameBox.Text.Trim();
|
||||||
@@ -170,7 +249,7 @@ public partial class TaskEditorWindow : Window
|
|||||||
_task.Channel = ChannelBox.Text.Trim();
|
_task.Channel = ChannelBox.Text.Trim();
|
||||||
_task.Title = TitleBox.Text.Trim();
|
_task.Title = TitleBox.Text.Trim();
|
||||||
_task.Body = BodyBox.Text;
|
_task.Body = BodyBox.Text;
|
||||||
_task.MisfireStrategy = MisfireBox.SelectedItem is MisfireStrategy strategy ? strategy : MisfireStrategy.DoNothing;
|
_task.MisfireStrategy = MisfireBox.SelectedItem is Option<MisfireStrategy> strategy ? strategy.Value : MisfireStrategy.DoNothing;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +262,7 @@ public partial class TaskEditorWindow : Window
|
|||||||
_task.Triggers.Add(trigger);
|
_task.Triggers.Add(trigger);
|
||||||
TriggersList.SelectedItem = trigger;
|
TriggersList.SelectedItem = trigger;
|
||||||
TriggersList.Items.Refresh();
|
TriggersList.Items.Refresh();
|
||||||
|
UpdateEditorAvailability();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveTrigger_Click(object sender, RoutedEventArgs e)
|
private void RemoveTrigger_Click(object sender, RoutedEventArgs e)
|
||||||
@@ -191,6 +271,7 @@ public partial class TaskEditorWindow : Window
|
|||||||
{
|
{
|
||||||
_task.Triggers.Remove(trigger);
|
_task.Triggers.Remove(trigger);
|
||||||
TriggersList.Items.Refresh();
|
TriggersList.Items.Refresh();
|
||||||
|
UpdateEditorAvailability();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +283,50 @@ public partial class TaskEditorWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void WindowOption_Changed(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_loading)
|
||||||
|
{
|
||||||
|
EnsureOptionalDateDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateWindowControlState();
|
||||||
|
Field_Changed(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyOneTimeDelay_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!int.TryParse(OneTimeDelayValueBox.Text, out var value) || value < 1)
|
||||||
|
{
|
||||||
|
OneTimeDelayValueBox.Text = "1";
|
||||||
|
value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var unit = OneTimeDelayUnitBox.SelectedItem is Option<DelayUnit> option ? option.Value : DelayUnit.Minutes;
|
||||||
|
ApplyOneTimeDelay(value, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QuickOneTimeDelay_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button { Tag: string minutesText } && int.TryParse(minutesText, out var minutes))
|
||||||
|
{
|
||||||
|
ApplyOneTimeDelay(minutes, DelayUnit.Minutes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyOneTimeDelay(int value, DelayUnit unit)
|
||||||
|
{
|
||||||
|
var delay = unit switch
|
||||||
|
{
|
||||||
|
DelayUnit.Hours => TimeSpan.FromHours(value),
|
||||||
|
DelayUnit.Days => TimeSpan.FromDays(value),
|
||||||
|
_ => TimeSpan.FromMinutes(value)
|
||||||
|
};
|
||||||
|
|
||||||
|
SetDateTime(OneTimeDatePicker, OneTimeHourBox, OneTimeMinuteBox, OneTimeSecondBox, DateTime.Now.Add(delay));
|
||||||
|
SaveTriggerFields();
|
||||||
|
}
|
||||||
|
|
||||||
private async void TestSend_Click(object sender, RoutedEventArgs e)
|
private async void TestSend_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!SaveTaskFields())
|
if (!SaveTaskFields())
|
||||||
@@ -210,7 +335,7 @@ public partial class TaskEditorWindow : Window
|
|||||||
}
|
}
|
||||||
|
|
||||||
TestResultText.Text = "发送中...";
|
TestResultText.Text = "发送中...";
|
||||||
var log = await _client.SendAsync(_task, "Test", _settings.OmniNotifyUrl);
|
var log = await _client.SendAsync(_task, "测试发送", _settings.OmniNotifyUrl);
|
||||||
TestResultText.Text = log.Level == "INFO" ? "测试发送成功" : log.Message;
|
TestResultText.Text = log.Level == "INFO" ? "测试发送成功" : log.Message;
|
||||||
TestResultText.Foreground = log.Level == "INFO"
|
TestResultText.Foreground = log.Level == "INFO"
|
||||||
? System.Windows.Media.Brushes.ForestGreen
|
? System.Windows.Media.Brushes.ForestGreen
|
||||||
@@ -243,4 +368,102 @@ public partial class TaskEditorWindow : Window
|
|||||||
? "无法计算未来执行时间,请检查规则。"
|
? "无法计算未来执行时间,请检查规则。"
|
||||||
: string.Join(Environment.NewLine, preview.Select(d => d.ToString("yyyy-MM-dd HH:mm:ss")));
|
: string.Join(Environment.NewLine, preview.Select(d => d.ToString("yyyy-MM-dd HH:mm:ss")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateEditorAvailability()
|
||||||
|
{
|
||||||
|
var hasTrigger = TriggersList.SelectedItem is TaskTrigger;
|
||||||
|
TriggerEmptyText.Visibility = hasTrigger ? Visibility.Collapsed : Visibility.Visible;
|
||||||
|
TriggerEditorPanel.Visibility = hasTrigger ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateVisibleSections(TriggerKind kind)
|
||||||
|
{
|
||||||
|
OneTimeSection.Visibility = kind == TriggerKind.OneTime ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
IntervalSection.Visibility = kind == TriggerKind.Interval ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
DailySection.Visibility = kind == TriggerKind.Daily ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
WeeklySection.Visibility = kind == TriggerKind.Weekly ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
MonthlySection.Visibility = kind == TriggerKind.Monthly ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
CronSection.Visibility = kind == TriggerKind.Cron ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
WindowSection.Visibility = kind is TriggerKind.Interval or TriggerKind.Daily or TriggerKind.Weekly or TriggerKind.Monthly or TriggerKind.Cron
|
||||||
|
? Visibility.Visible
|
||||||
|
: Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateWindowControlState()
|
||||||
|
{
|
||||||
|
StartsAtPanel.IsEnabled = UseStartsAtBox.IsChecked == true;
|
||||||
|
EndsAtPanel.IsEnabled = UseEndsAtBox.IsChecked == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureOptionalDateDefaults()
|
||||||
|
{
|
||||||
|
if (UseStartsAtBox.IsChecked == true && StartsAtDatePicker.SelectedDate is null)
|
||||||
|
{
|
||||||
|
SetDateTime(StartsAtDatePicker, StartsAtHourBox, StartsAtMinuteBox, StartsAtSecondBox, DateTime.Now);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UseEndsAtBox.IsChecked == true && EndsAtDatePicker.SelectedDate is null)
|
||||||
|
{
|
||||||
|
SetDateTime(EndsAtDatePicker, EndsAtHourBox, EndsAtMinuteBox, EndsAtSecondBox, DateTime.Now.AddDays(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTimeItems(ComboBox hourBox, ComboBox minuteBox, ComboBox secondBox)
|
||||||
|
{
|
||||||
|
hourBox.ItemsSource = _hours;
|
||||||
|
minuteBox.ItemsSource = _minutesAndSeconds;
|
||||||
|
secondBox.ItemsSource = _minutesAndSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetDateTime(DatePicker datePicker, ComboBox hourBox, ComboBox minuteBox, ComboBox secondBox, DateTime value)
|
||||||
|
{
|
||||||
|
datePicker.SelectedDate = value.Date;
|
||||||
|
SetTimeOnly(hourBox, minuteBox, secondBox, value.TimeOfDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetOptionalDateTime(DatePicker datePicker, ComboBox hourBox, ComboBox minuteBox, ComboBox secondBox, DateTime? value)
|
||||||
|
{
|
||||||
|
if (value.HasValue)
|
||||||
|
{
|
||||||
|
SetDateTime(datePicker, hourBox, minuteBox, secondBox, value.Value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
datePicker.SelectedDate = null;
|
||||||
|
SetTimeOnly(hourBox, minuteBox, secondBox, TimeSpan.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTimeOnly(ComboBox hourBox, ComboBox minuteBox, ComboBox secondBox, TimeSpan value)
|
||||||
|
{
|
||||||
|
hourBox.SelectedItem = _hours.First(o => o.Value == value.Hours);
|
||||||
|
minuteBox.SelectedItem = _minutesAndSeconds.First(o => o.Value == value.Minutes);
|
||||||
|
secondBox.SelectedItem = _minutesAndSeconds.First(o => o.Value == value.Seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime? GetDateTime(DatePicker datePicker, ComboBox hourBox, ComboBox minuteBox, ComboBox secondBox)
|
||||||
|
{
|
||||||
|
if (datePicker.SelectedDate is not { } date)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.Date.Add(GetTimeOnly(hourBox, minuteBox, secondBox));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TimeSpan GetTimeOnly(ComboBox hourBox, ComboBox minuteBox, ComboBox secondBox)
|
||||||
|
{
|
||||||
|
var hour = hourBox.SelectedItem is Option<int> h ? h.Value : 0;
|
||||||
|
var minute = minuteBox.SelectedItem is Option<int> m ? m.Value : 0;
|
||||||
|
var second = secondBox.SelectedItem is Option<int> s ? s.Value : 0;
|
||||||
|
return new TimeSpan(hour, minute, second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record Option<T>(T Value, string Label);
|
||||||
|
|
||||||
|
public enum DelayUnit
|
||||||
|
{
|
||||||
|
Minutes,
|
||||||
|
Hours,
|
||||||
|
Days
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user