Add GearTriggerPage and related ViewModel; implement trigger functionality

This commit is contained in:
辉鸭蛋
2025-07-29 23:39:31 +08:00
parent 994db21b3d
commit b78d644101
8 changed files with 1142 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System;
using BetterGenshinImpact.Model.Gear.Tasks;
using BetterGenshinImpact.Model;
namespace BetterGenshinImpact.Model.Gear.Triggers;
/// <summary>
/// 热键触发器
/// </summary>
public class HotkeyGearTrigger : GearBaseTrigger
{
/// <summary>
/// 热键配置
/// </summary>
public HotKey? Hotkey { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 冷却时间(毫秒),防止重复触发
/// </summary>
public int CooldownMs { get; set; } = 1000;
private DateTime _lastExecutionTime = DateTime.MinValue;
private bool _isExecuting = false;
public override async Task Run()
{
// 热键触发器通常不直接调用Run方法
// 而是通过热键事件触发ExecuteTasks方法
await ExecuteTasks();
}
/// <summary>
/// 热键触发时执行任务
/// </summary>
public async Task OnHotkeyPressed()
{
if (!IsEnabled || _isExecuting)
return;
// 检查冷却时间
var now = DateTime.Now;
if ((now - _lastExecutionTime).TotalMilliseconds < CooldownMs)
return;
_lastExecutionTime = now;
_isExecuting = true;
try
{
await ExecuteTasks();
}
finally
{
_isExecuting = false;
}
}
private async Task ExecuteTasks()
{
List<BaseGearTask> list = GearTaskRefenceList.Select(gearTask => gearTask.ToGearTask()).ToList();
foreach (var gearTask in list)
{
await gearTask.Run(CancellationToken.None);
}
}
/// <summary>
/// 获取热键显示文本
/// </summary>
public string GetHotkeyText()
{
return Hotkey?.ToString() ?? "未设置";
}
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using BetterGenshinImpact.Model.Gear.Tasks;
namespace BetterGenshinImpact.Model.Gear.Triggers;
/// <summary>
/// 定时触发器
/// </summary>
public class TimedGearTrigger : GearBaseTrigger
{
/// <summary>
/// 触发间隔(毫秒)
/// </summary>
public int IntervalMs { get; set; } = 5000;
/// <summary>
/// 是否重复执行
/// </summary>
public bool IsRepeating { get; set; } = true;
/// <summary>
/// 延迟启动时间(毫秒)
/// </summary>
public int DelayMs { get; set; } = 0;
/// <summary>
/// 最大执行次数0表示无限制
/// </summary>
public int MaxExecutions { get; set; } = 0;
private int _executionCount = 0;
private CancellationTokenSource? _cancellationTokenSource;
public override async Task Run()
{
_cancellationTokenSource = new CancellationTokenSource();
_executionCount = 0;
// 延迟启动
if (DelayMs > 0)
{
await Task.Delay(DelayMs, _cancellationTokenSource.Token);
}
do
{
if (_cancellationTokenSource.Token.IsCancellationRequested)
break;
// 执行任务
await ExecuteTasks();
_executionCount++;
// 检查是否达到最大执行次数
if (MaxExecutions > 0 && _executionCount >= MaxExecutions)
break;
// 如果不重复执行,则退出
if (!IsRepeating)
break;
// 等待下次执行
await Task.Delay(IntervalMs, _cancellationTokenSource.Token);
}
while (IsRepeating && !_cancellationTokenSource.Token.IsCancellationRequested);
}
/// <summary>
/// 停止触发器
/// </summary>
public void Stop()
{
_cancellationTokenSource?.Cancel();
}
private async Task ExecuteTasks()
{
List<BaseGearTask> list = GearTaskRefenceList.Select(gearTask => gearTask.ToGearTask()).ToList();
foreach (var gearTask in list)
{
if (_cancellationTokenSource?.Token.IsCancellationRequested == true)
break;
await gearTask.Run(_cancellationTokenSource?.Token ?? CancellationToken.None);
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace BetterGenshinImpact.View.Converters;
[ValueConversion(typeof(Enum), typeof(Visibility))]
public class EnumToVisibilityConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value == null || parameter == null)
{
return Visibility.Collapsed;
}
var enumValue = value.ToString();
var targetValue = parameter.ToString();
return string.Equals(enumValue, targetValue, StringComparison.OrdinalIgnoreCase)
? Visibility.Visible
: Visibility.Collapsed;
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace BetterGenshinImpact.View.Converters;
[ValueConversion(typeof(object), typeof(Visibility))]
public class NullToVisibilityConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
var isInvert = parameter?.ToString()?.Equals("Invert", StringComparison.OrdinalIgnoreCase) == true;
var isNull = value == null;
if (isInvert)
{
return isNull ? Visibility.Visible : Visibility.Collapsed;
}
else
{
return isNull ? Visibility.Collapsed : Visibility.Visible;
}
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,404 @@
<UserControl x:Class="BetterGenshinImpact.View.Pages.GearTriggerPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dd="urn:gong-wpf-dragdrop"
xmlns:local="clr-namespace:BetterGenshinImpact.View.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:pages="clr-namespace:BetterGenshinImpact.ViewModel.Pages"
xmlns:component="clr-namespace:BetterGenshinImpact.ViewModel.Pages.Component"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
d:DataContext="{d:DesignInstance Type=pages:GearTriggerPageViewModel}"
d:DesignHeight="850"
d:DesignWidth="800"
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
FontFamily="{StaticResource TextThemeFontFamily}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<Style x:Key="ConsistentTabControlStyle" TargetType="TabControl"
BasedOn="{StaticResource {x:Type TabControl}}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid x:Name="templateRoot"
Background="{TemplateBinding Background}"
ClipToBounds="True"
SnapsToDevicePixels="True">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0" Width="Auto" />
<ColumnDefinition x:Name="ColumnDefinition1" Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto" />
<RowDefinition x:Name="RowDefinition1" Height="*" />
</Grid.RowDefinitions>
<TabPanel x:Name="headerPanel"
Grid.Column="0"
Grid.Row="0"
Background="Transparent"
IsItemsHost="True"
Panel.ZIndex="1" />
<Border x:Name="contentPanel"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="1"
Background="{ui:ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderThickness="0">
<ContentPresenter x:Name="PART_SelectedContentHost"
ContentTemplate="{TemplateBinding SelectedContentTemplate}"
Content="{TemplateBinding SelectedContent}"
ContentStringFormat="{TemplateBinding SelectedContentStringFormat}"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ConsistentTabItemStyle" TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Width" Value="36" />
<Setter Property="Height" Value="26" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid x:Name="templateRoot"
Background="Transparent"
SnapsToDevicePixels="True">
<Border x:Name="mainBorder"
Background="{ui:ThemeResource CardBackgroundFillColorSecondaryBrush}"
BorderThickness="0"
CornerRadius="8,8,0,0"
Margin="0,0,1,0">
<ContentPresenter x:Name="contentPresenter"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Content="{TemplateBinding Header}"
ContentStringFormat="{TemplateBinding HeaderStringFormat}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{ui:ThemeResource TextFillColorPrimaryBrush}" />
<Setter TargetName="mainBorder" Property="Background"
Value="{ui:ThemeResource CardBackgroundFillColorDefaultBrush}" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="{ui:ThemeResource TextFillColorPrimaryBrush}" />
<Setter TargetName="mainBorder" Property="Background"
Value="{ui:ThemeResource CardBackgroundFillColorDefaultBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 触发器项模板 -->
<DataTemplate x:Key="TriggerItemTemplate" DataType="{x:Type component:GearTriggerViewModel}">
<ui:Card Margin="0,2,0,2" Padding="10,8,10,8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0"
Width="8"
Height="8"
Margin="0,0,8,0"
Fill="{DynamicResource AccentFillColorDefaultBrush}" />
<StackPanel Grid.Column="1">
<TextBlock VerticalAlignment="Center"
IsHitTestVisible="False"
Text="{Binding Name}"
FontWeight="Medium" />
<TextBlock Text="{Binding Description}"
FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
IsHitTestVisible="False" />
</StackPanel>
<ui:ToggleSwitch Grid.Column="2"
IsChecked="{Binding IsEnabled, Mode=TwoWay}" />
</Grid>
</ui:Card>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid Margin="8,0,8,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220" />
<ColumnDefinition Width="0" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 左侧触发器类型选择区域 -->
<Border Grid.Column="0"
Margin="0,8,0,8"
CornerRadius="8">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TabControl Grid.Row="1" Style="{StaticResource ConsistentTabControlStyle}">
<!-- 顺序触发选项卡 -->
<TabItem Style="{StaticResource ConsistentTabItemStyle}">
<TabItem.Header>
<ui:SymbolIcon Symbol="ArrowSort24" />
</TabItem.Header>
<Grid>
<StackPanel Margin="8">
<ui:TextBlock Margin="0,0,0,8"
Text="顺序触发器"
FontWeight="SemiBold"
TextWrapping="Wrap" />
<ui:ListView
ItemsSource="{Binding SequentialTriggers}"
SelectedItem="{Binding SelectedTrigger, Mode=TwoWay}"
SelectionMode="Single"
ItemTemplate="{StaticResource TriggerItemTemplate}">
<ui:ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ui:ListView.ItemContainerStyle>
</ui:ListView>
</StackPanel>
</Grid>
</TabItem>
<!-- 定时触发选项卡 -->
<TabItem Style="{StaticResource ConsistentTabItemStyle}">
<TabItem.Header>
<ui:SymbolIcon Symbol="Clock24" />
</TabItem.Header>
<Grid>
<StackPanel Margin="8">
<ui:TextBlock Margin="0,0,0,8"
Text="定时触发器"
FontWeight="SemiBold"
TextWrapping="Wrap" />
<ui:ListView
ItemsSource="{Binding TimedTriggers}"
SelectedItem="{Binding SelectedTrigger, Mode=TwoWay}"
SelectionMode="Single"
ItemTemplate="{StaticResource TriggerItemTemplate}">
<ui:ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ui:ListView.ItemContainerStyle>
</ui:ListView>
</StackPanel>
</Grid>
</TabItem>
<!-- 热键触发选项卡 -->
<TabItem Style="{StaticResource ConsistentTabItemStyle}">
<TabItem.Header>
<ui:SymbolIcon Symbol="Keyboard24" />
</TabItem.Header>
<Grid>
<StackPanel Margin="8">
<ui:TextBlock Margin="0,0,0,8"
Text="热键触发器"
FontWeight="SemiBold"
TextWrapping="Wrap" />
<ui:ListView
ItemsSource="{Binding HotkeyTriggers}"
SelectedItem="{Binding SelectedTrigger, Mode=TwoWay}"
SelectionMode="Single"
ItemTemplate="{StaticResource TriggerItemTemplate}">
<ui:ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ui:ListView.ItemContainerStyle>
</ui:ListView>
</StackPanel>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Border>
<!-- 右侧触发器配置区域 -->
<Border Grid.Column="2" Margin="0,14,6,15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 操作按钮 -->
<StackPanel Grid.Row="2"
Orientation="Horizontal"
Margin="0,0,0,8">
<ui:Button Command="{Binding AddSequentialTriggerCommand}"
Content="添加顺序触发器"
Icon="{ui:SymbolIcon Add24}"
Height="26"
Padding="8,2,14,0"
Margin="0,0,8,0"/>
<ui:Button Command="{Binding AddTimedTriggerCommand}"
Content="添加定时触发器"
Icon="{ui:SymbolIcon Add24}"
Height="26"
Padding="8,2,14,0"
Margin="0,0,8,0"/>
<ui:Button Command="{Binding AddHotkeyTriggerCommand}"
Content="添加热键触发器"
Icon="{ui:SymbolIcon Add24}"
Height="26"
Padding="8,2,14,0"
Margin="0,0,8,0"/>
<ui:Button Command="{Binding DeleteTriggerCommand}"
Content="删除触发器"
Icon="{ui:SymbolIcon Delete24}"
Height="26"
Padding="8,2,14,0"
IsEnabled="{Binding SelectedTrigger, Converter={StaticResource NotNullConverter}}"/>
</StackPanel>
<!-- 触发器配置详情 -->
<ui:TreeListView Grid.Row="3"
ItemsSource="{Binding TaskReferences}"
Visibility="{Binding SelectedTrigger, Converter={StaticResource NotNullConverter}}">
<ui:TreeListView.Columns>
<GridViewColumnCollection>
<ui:GridViewColumn Header="功能" Width="200">
<ui:GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ui:GridViewColumn.CellTemplate>
</ui:GridViewColumn>
<ui:GridViewColumn Header="说明" Width="300">
<ui:GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ui:GridViewColumn.CellTemplate>
</ui:GridViewColumn>
<ui:GridViewColumn Header="状态" Width="80">
<ui:GridViewColumn.CellTemplate>
<DataTemplate>
<ui:ToggleSwitch IsChecked="{Binding IsEnabled, Mode=TwoWay}" />
</DataTemplate>
</ui:GridViewColumn.CellTemplate>
</ui:GridViewColumn>
</GridViewColumnCollection>
</ui:TreeListView.Columns>
</ui:TreeListView>
<!-- 无选中触发器时的提示 -->
<StackPanel Grid.Row="3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding SelectedTrigger, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Invert}">
<ui:SymbolIcon Symbol="Settings24" FontSize="48" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
<TextBlock Text="请选择一个触发器进行配置"
FontSize="16"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
HorizontalAlignment="Center"
Margin="0,16,0,0" />
</StackPanel>
</Grid>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,19 @@
using System.Windows.Controls;
using BetterGenshinImpact.ViewModel.Pages;
namespace BetterGenshinImpact.View.Pages;
/// <summary>
/// GearTriggerPage.xaml 的交互逻辑
/// </summary>
public partial class GearTriggerPage : UserControl
{
public GearTriggerPageViewModel ViewModel { get; }
public GearTriggerPage(GearTriggerPageViewModel viewModel)
{
ViewModel = viewModel;
DataContext = this;
InitializeComponent();
}
}

View File

@@ -0,0 +1,176 @@
using System;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using BetterGenshinImpact.Model.Gear.Triggers;
using BetterGenshinImpact.Model.Gear.Tasks;
using BetterGenshinImpact.Model;
namespace BetterGenshinImpact.ViewModel.Pages.Component;
/// <summary>
/// 触发器ViewModel
/// </summary>
public partial class GearTriggerViewModel : ObservableObject
{
[ObservableProperty]
private string _name = string.Empty;
[ObservableProperty]
private string _description = string.Empty;
[ObservableProperty]
private bool _isEnabled = true;
[ObservableProperty]
private bool _isSelected = false;
[ObservableProperty]
private DateTime _createdTime = DateTime.Now;
[ObservableProperty]
private DateTime _modifiedTime = DateTime.Now;
/// <summary>
/// 触发器类型
/// </summary>
[ObservableProperty]
private TriggerType _triggerType = TriggerType.Sequential;
/// <summary>
/// 任务引用列表
/// </summary>
[ObservableProperty]
private ObservableCollection<GearTaskRefence> _taskReferences = new();
// 顺序触发器属性(无额外属性)
// 定时触发器属性
[ObservableProperty]
private int _intervalMs = 5000;
[ObservableProperty]
private bool _isRepeating = true;
[ObservableProperty]
private int _delayMs = 0;
[ObservableProperty]
private int _maxExecutions = 0;
// 热键触发器属性
[ObservableProperty]
private HotKey? _hotkey;
[ObservableProperty]
private int _cooldownMs = 1000;
public GearTriggerViewModel()
{
}
public GearTriggerViewModel(string name, TriggerType triggerType)
{
Name = name;
TriggerType = triggerType;
Description = GetDefaultDescription(triggerType);
}
/// <summary>
/// 获取触发器类型的默认描述
/// </summary>
private string GetDefaultDescription(TriggerType triggerType)
{
return triggerType switch
{
TriggerType.Sequential => "按顺序执行任务",
TriggerType.Timed => "定时执行任务",
TriggerType.Hotkey => "热键触发执行任务",
_ => "未知触发器类型"
};
}
/// <summary>
/// 获取触发器类型显示名称
/// </summary>
public string TriggerTypeDisplayName => TriggerType switch
{
TriggerType.Sequential => "顺序触发",
TriggerType.Timed => "定时触发",
TriggerType.Hotkey => "热键触发",
_ => "未知类型"
};
/// <summary>
/// 获取热键显示文本
/// </summary>
public string HotkeyDisplayText => Hotkey?.ToString() ?? "未设置";
/// <summary>
/// 获取定时器配置显示文本
/// </summary>
public string TimerDisplayText
{
get
{
if (TriggerType != TriggerType.Timed) return "";
var text = $"间隔: {IntervalMs}ms";
if (DelayMs > 0) text += $", 延迟: {DelayMs}ms";
if (MaxExecutions > 0) text += $", 最大次数: {MaxExecutions}";
if (!IsRepeating) text += ", 单次执行";
return text;
}
}
/// <summary>
/// 转换为对应的触发器实例
/// </summary>
public GearBaseTrigger ToTrigger()
{
GearBaseTrigger trigger = TriggerType switch
{
TriggerType.Sequential => new SequentialGearTrigger(),
TriggerType.Timed => new TimedGearTrigger
{
IntervalMs = IntervalMs,
IsRepeating = IsRepeating,
DelayMs = DelayMs,
MaxExecutions = MaxExecutions
},
TriggerType.Hotkey => new HotkeyGearTrigger
{
Hotkey = Hotkey,
CooldownMs = CooldownMs,
IsEnabled = IsEnabled
},
_ => new SequentialGearTrigger()
};
trigger.Name = Name;
trigger.GearTaskRefenceList = new ObservableCollection<GearTaskRefence>(TaskReferences);
return trigger;
}
}
/// <summary>
/// 触发器类型枚举
/// </summary>
public enum TriggerType
{
/// <summary>
/// 顺序触发
/// </summary>
Sequential,
/// <summary>
/// 定时触发
/// </summary>
Timed,
/// <summary>
/// 热键触发
/// </summary>
Hotkey
}

View File

@@ -0,0 +1,310 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using BetterGenshinImpact.ViewModel.Pages.Component;
using BetterGenshinImpact.Model.Gear.Tasks;
using BetterGenshinImpact.Model;
namespace BetterGenshinImpact.ViewModel.Pages;
/// <summary>
/// 任务触发页面ViewModel
/// </summary>
public partial class GearTriggerPageViewModel : ViewModel
{
private readonly ILogger<GearTriggerPageViewModel> _logger;
/// <summary>
/// 顺序触发器列表
/// </summary>
[ObservableProperty]
private ObservableCollection<GearTriggerViewModel> _sequentialTriggers = new();
/// <summary>
/// 定时触发器列表
/// </summary>
[ObservableProperty]
private ObservableCollection<GearTriggerViewModel> _timedTriggers = new();
/// <summary>
/// 热键触发器列表
/// </summary>
[ObservableProperty]
private ObservableCollection<GearTriggerViewModel> _hotkeyTriggers = new();
/// <summary>
/// 当前选中的触发器
/// </summary>
[ObservableProperty]
private GearTriggerViewModel? _selectedTrigger;
/// <summary>
/// 当前选中的触发器类型Tab
/// </summary>
[ObservableProperty]
private int _selectedTriggerTypeIndex = 0;
/// <summary>
/// 触发器配置中的任务定义列表
/// </summary>
[ObservableProperty]
private ObservableCollection<GearTaskDefinitionViewModel> _availableTaskDefinitions = new();
/// <summary>
/// 当前触发器的任务引用列表
/// </summary>
[ObservableProperty]
private ObservableCollection<GearTaskRefence> _currentTaskReferences = new();
/// <summary>
/// 当前选中的任务引用
/// </summary>
[ObservableProperty]
private GearTaskRefence? _selectedTaskReference;
public GearTriggerPageViewModel(ILogger<GearTriggerPageViewModel> logger)
{
_logger = logger;
InitializeData();
}
private void InitializeData()
{
// 初始化示例数据
var sequentialTrigger = new GearTriggerViewModel("示例顺序触发器", TriggerType.Sequential);
SequentialTriggers.Add(sequentialTrigger);
var timedTrigger = new GearTriggerViewModel("示例定时触发器", TriggerType.Timed)
{
IntervalMs = 10000,
IsRepeating = true
};
TimedTriggers.Add(timedTrigger);
var hotkeyTrigger = new GearTriggerViewModel("示例热键触发器", TriggerType.Hotkey)
{
Hotkey = new HotKey(System.Windows.Input.Key.F1, System.Windows.Input.ModifierKeys.Control)
};
HotkeyTriggers.Add(hotkeyTrigger);
// 初始化可用的任务定义(这里应该从实际的任务定义服务获取)
LoadAvailableTaskDefinitions();
}
private void LoadAvailableTaskDefinitions()
{
// 这里应该从实际的任务定义服务获取
// 暂时创建示例数据
var sampleTask = new GearTaskDefinitionViewModel("示例任务组", "这是一个示例任务组");
AvailableTaskDefinitions.Add(sampleTask);
}
partial void OnSelectedTriggerChanged(GearTriggerViewModel? value)
{
UpdateCurrentTaskReferences();
}
private void UpdateCurrentTaskReferences()
{
CurrentTaskReferences.Clear();
if (SelectedTrigger != null)
{
foreach (var taskRef in SelectedTrigger.TaskReferences)
{
CurrentTaskReferences.Add(taskRef);
}
}
}
/// <summary>
/// 添加顺序触发器
/// </summary>
[RelayCommand]
private void AddSequentialTrigger()
{
var trigger = new GearTriggerViewModel($"顺序触发器 {SequentialTriggers.Count + 1}", TriggerType.Sequential);
SequentialTriggers.Add(trigger);
SelectedTrigger = trigger;
SelectedTriggerTypeIndex = 0;
}
/// <summary>
/// 添加定时触发器
/// </summary>
[RelayCommand]
private void AddTimedTrigger()
{
var trigger = new GearTriggerViewModel($"定时触发器 {TimedTriggers.Count + 1}", TriggerType.Timed);
TimedTriggers.Add(trigger);
SelectedTrigger = trigger;
SelectedTriggerTypeIndex = 1;
}
/// <summary>
/// 添加热键触发器
/// </summary>
[RelayCommand]
private void AddHotkeyTrigger()
{
var trigger = new GearTriggerViewModel($"热键触发器 {HotkeyTriggers.Count + 1}", TriggerType.Hotkey);
HotkeyTriggers.Add(trigger);
SelectedTrigger = trigger;
SelectedTriggerTypeIndex = 2;
}
/// <summary>
/// 删除触发器
/// </summary>
[RelayCommand]
private void DeleteTrigger(GearTriggerViewModel? trigger)
{
if (trigger == null) return;
var result = MessageBox.Show($"确定要删除触发器 '{trigger.Name}' 吗?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
switch (trigger.TriggerType)
{
case TriggerType.Sequential:
SequentialTriggers.Remove(trigger);
break;
case TriggerType.Timed:
TimedTriggers.Remove(trigger);
break;
case TriggerType.Hotkey:
HotkeyTriggers.Remove(trigger);
break;
}
if (SelectedTrigger == trigger)
{
SelectedTrigger = null;
}
}
}
/// <summary>
/// 编辑触发器
/// </summary>
[RelayCommand]
private void EditTrigger(GearTriggerViewModel? trigger)
{
if (trigger == null) return;
SelectedTrigger = trigger;
// 切换到对应的Tab
SelectedTriggerTypeIndex = trigger.TriggerType switch
{
TriggerType.Sequential => 0,
TriggerType.Timed => 1,
TriggerType.Hotkey => 2,
_ => 0
};
}
/// <summary>
/// 添加任务定义到当前触发器
/// </summary>
[RelayCommand]
private void AddTaskDefinition(GearTaskDefinitionViewModel? taskDefinition)
{
if (taskDefinition == null || SelectedTrigger == null) return;
// 创建任务引用
var taskRef = new GearTaskRefence
{
Name = taskDefinition.Name,
Enabled = true,
GearTaskFilePath = $"tasks/{taskDefinition.Name}.json" // 这里应该是实际的文件路径
};
SelectedTrigger.TaskReferences.Add(taskRef);
CurrentTaskReferences.Add(taskRef);
}
/// <summary>
/// 删除任务引用
/// </summary>
[RelayCommand]
private void RemoveTaskReference(GearTaskRefence? taskRef)
{
if (taskRef == null || SelectedTrigger == null) return;
SelectedTrigger.TaskReferences.Remove(taskRef);
CurrentTaskReferences.Remove(taskRef);
}
/// <summary>
/// 上移任务引用
/// </summary>
[RelayCommand]
private void MoveTaskReferenceUp(GearTaskRefence? taskRef)
{
if (taskRef == null || SelectedTrigger == null) return;
var index = CurrentTaskReferences.IndexOf(taskRef);
if (index > 0)
{
CurrentTaskReferences.Move(index, index - 1);
SelectedTrigger.TaskReferences.Move(index, index - 1);
}
}
/// <summary>
/// 下移任务引用
/// </summary>
[RelayCommand]
private void MoveTaskReferenceDown(GearTaskRefence? taskRef)
{
if (taskRef == null || SelectedTrigger == null) return;
var index = CurrentTaskReferences.IndexOf(taskRef);
if (index < CurrentTaskReferences.Count - 1)
{
CurrentTaskReferences.Move(index, index + 1);
SelectedTrigger.TaskReferences.Move(index, index + 1);
}
}
/// <summary>
/// 保存配置
/// </summary>
[RelayCommand]
private void SaveConfiguration()
{
try
{
// 这里应该实现保存到文件的逻辑
_logger.LogInformation("触发器配置已保存");
MessageBox.Show("配置保存成功!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
_logger.LogError(ex, "保存触发器配置时发生错误");
MessageBox.Show($"保存失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// 加载配置
/// </summary>
[RelayCommand]
private void LoadConfiguration()
{
try
{
// 这里应该实现从文件加载的逻辑
_logger.LogInformation("触发器配置已加载");
MessageBox.Show("配置加载成功!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
_logger.LogError(ex, "加载触发器配置时发生错误");
MessageBox.Show($"加载失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}