feat: 实现工具箱主界面与编辑流程
新增主窗口 MVVM、分类列表、工具卡片网格、底部信息区、工具编辑窗口、组合编辑窗口和设置窗口。 界面中的主要输入、按钮、卡片和管理项均提供悬浮帮助说明,并接入搜索、启动、增删改、自动运行、快捷键和配置管理入口。
This commit is contained in:
@@ -1,9 +1,32 @@
|
|||||||
<Application x:Class="ToolboxApp.App"
|
<Application x:Class="ToolboxApp.App"
|
||||||
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"
|
||||||
xmlns:local="clr-namespace:ToolboxApp"
|
|
||||||
StartupUri="MainWindow.xaml">
|
StartupUri="MainWindow.xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
|
<SolidColorBrush x:Key="AppBackgroundBrush" Color="#F7F8FA" />
|
||||||
|
<SolidColorBrush x:Key="PanelBackgroundBrush" Color="#FFFFFF" />
|
||||||
|
<SolidColorBrush x:Key="BorderBrushSoft" Color="#D9DEE7" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryBrush" Color="#2563EB" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryTextBrush" Color="#172033" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryTextBrush" Color="#5C667A" />
|
||||||
|
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="MinHeight" Value="32" />
|
||||||
|
<Setter Property="Padding" Value="12,4" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TextBox">
|
||||||
|
<Setter Property="Padding" Value="8,4" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ComboBox">
|
||||||
|
<Setter Property="Padding" Value="8,4" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ListBoxItem">
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||||
|
</Style>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@@ -1,12 +1,295 @@
|
|||||||
<Window x:Class="ToolboxApp.MainWindow"
|
<Window x:Class="ToolboxApp.MainWindow"
|
||||||
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"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:local="clr-namespace:ToolboxApp"
|
xmlns:vm="clr-namespace:ToolboxApp.ViewModels"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="MainWindow" Height="450" Width="800">
|
Title="个人工具箱"
|
||||||
<Grid>
|
Height="720"
|
||||||
|
Width="1120"
|
||||||
|
MinHeight="620"
|
||||||
|
MinWidth="960"
|
||||||
|
Background="{StaticResource AppBackgroundBrush}"
|
||||||
|
Loaded="Window_OnLoaded"
|
||||||
|
Closing="Window_OnClosing">
|
||||||
|
<Grid Margin="16">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Border Background="{StaticResource PanelBackgroundBrush}"
|
||||||
|
BorderBrush="{StaticResource BorderBrushSoft}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="12">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBox x:Name="SearchTextBox"
|
||||||
|
Width="360"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
ToolTip="搜索工具名称、说明、分类、路径或网址。按 Ctrl + F 可快速聚焦。"
|
||||||
|
KeyDown="SearchTextBox_OnKeyDown" />
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Button Content="添加本地"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Command="{Binding AddLocalToolCommand}"
|
||||||
|
ToolTip="添加 exe、lnk、bat、cmd、文件或文件夹等本地工具。" />
|
||||||
|
<Button Content="添加网址"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Command="{Binding AddUrlToolCommand}"
|
||||||
|
ToolTip="添加使用默认浏览器打开的网址工具。" />
|
||||||
|
<Button Content="添加组合"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Command="{Binding AddCombinationCommand}"
|
||||||
|
ToolTip="创建由多个工具或组合组成的一键启动组合。" />
|
||||||
|
<Button Content="启动"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Command="{Binding LaunchSelectedCommand}"
|
||||||
|
ToolTip="启动当前选中的工具。也可双击卡片或按 Enter。" />
|
||||||
|
<Button Content="编辑"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Command="{Binding EditSelectedCommand}"
|
||||||
|
ToolTip="编辑当前选中的工具或组合。" />
|
||||||
|
<Button Content="删除"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Command="{Binding DeleteSelectedCommand}"
|
||||||
|
ToolTip="删除当前选中的工具。删除前会确认。" />
|
||||||
|
<Button Content="设置"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Command="{Binding OpenSettingsCommand}"
|
||||||
|
ToolTip="打开常规、自动运行、快捷键和数据设置。" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" Margin="0,12,0,12">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="230" />
|
||||||
|
<ColumnDefinition Width="12" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Border Background="{StaticResource PanelBackgroundBrush}"
|
||||||
|
BorderBrush="{StaticResource BorderBrushSoft}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="12">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="分类"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{StaticResource PrimaryTextBrush}"
|
||||||
|
ToolTip="一级分类列表,工具只能属于一个分类。" />
|
||||||
|
|
||||||
|
<ListBox Grid.Row="1"
|
||||||
|
Margin="0,10,0,10"
|
||||||
|
ItemsSource="{Binding Categories}"
|
||||||
|
SelectedItem="{Binding SelectedCategory}"
|
||||||
|
BorderThickness="0"
|
||||||
|
ToolTip="单击切换分类;搜索时会跨分类显示结果。">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Name}"
|
||||||
|
Padding="8,7"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
ToolTip="{Binding Name}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2">
|
||||||
|
<Button Content="新建分类"
|
||||||
|
Command="{Binding AddCategoryCommand}"
|
||||||
|
ToolTip="创建一个新的一级分类。" />
|
||||||
|
<Button Content="重命名分类"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
Command="{Binding RenameCategoryCommand}"
|
||||||
|
ToolTip="重命名当前选中的分类。" />
|
||||||
|
<Button Content="删除分类"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
Command="{Binding DeleteCategoryCommand}"
|
||||||
|
ToolTip="删除当前分类,并把其中工具移动到未分类。" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Grid.Column="2"
|
||||||
|
Background="{StaticResource PanelBackgroundBrush}"
|
||||||
|
BorderBrush="{StaticResource BorderBrushSoft}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="12">
|
||||||
|
<ListBox x:Name="ToolsListBox"
|
||||||
|
ItemsSource="{Binding Tools}"
|
||||||
|
SelectedItem="{Binding SelectedTool}"
|
||||||
|
BorderThickness="0"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
|
MouseDoubleClick="ToolsListBox_OnMouseDoubleClick"
|
||||||
|
PreviewMouseRightButtonDown="ToolsListBox_OnPreviewMouseRightButtonDown"
|
||||||
|
KeyDown="ToolsListBox_OnKeyDown"
|
||||||
|
ToolTip="单击选中卡片,双击启动,右键打开管理菜单。">
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel IsItemsHost="True" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type vm:ToolCardViewModel}">
|
||||||
|
<Border Width="210"
|
||||||
|
Height="146"
|
||||||
|
Margin="6"
|
||||||
|
Padding="12"
|
||||||
|
CornerRadius="8"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{StaticResource BorderBrushSoft}"
|
||||||
|
Background="#FBFCFE"
|
||||||
|
ToolTip="{Binding DetailText}">
|
||||||
|
<Border.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="启动"
|
||||||
|
ToolTip="启动该工具或组合。"
|
||||||
|
Click="ContextLaunch_OnClick" />
|
||||||
|
<MenuItem Header="编辑"
|
||||||
|
ToolTip="编辑该工具或组合。"
|
||||||
|
Click="ContextEdit_OnClick" />
|
||||||
|
<MenuItem Header="删除"
|
||||||
|
ToolTip="删除该工具。"
|
||||||
|
Click="ContextDelete_OnClick" />
|
||||||
|
</ContextMenu>
|
||||||
|
</Border.ContextMenu>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<DockPanel>
|
||||||
|
<Border Width="42"
|
||||||
|
Height="32"
|
||||||
|
CornerRadius="6"
|
||||||
|
Background="#E8F0FF"
|
||||||
|
DockPanel.Dock="Left"
|
||||||
|
ToolTip="工具图标占位;后续会扩展自动图标和图标库。">
|
||||||
|
<TextBlock Text="{Binding IconText}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{StaticResource PrimaryBrush}" />
|
||||||
|
</Border>
|
||||||
|
<TextBlock Text="{Binding TypeLabel}"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{StaticResource SecondaryTextBrush}"
|
||||||
|
ToolTip="工具类型标记。" />
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1"
|
||||||
|
Text="{Binding Name}"
|
||||||
|
Margin="0,10,0,0"
|
||||||
|
FontSize="15"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{StaticResource PrimaryTextBrush}"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
ToolTip="{Binding Name}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2"
|
||||||
|
Text="{Binding Description}"
|
||||||
|
Margin="0,6,0,0"
|
||||||
|
Foreground="{StaticResource SecondaryTextBrush}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
ToolTip="{Binding Description}" />
|
||||||
|
|
||||||
|
<ItemsControl Grid.Row="3"
|
||||||
|
ItemsSource="{Binding StatusBadges}"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
ToolTip="状态标记:路径失效、快捷键、自动运行或管理员。">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border Background="#EDF2F7"
|
||||||
|
CornerRadius="4"
|
||||||
|
Padding="5,2"
|
||||||
|
Margin="0,0,5,4">
|
||||||
|
<TextBlock Text="{Binding}"
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="{StaticResource SecondaryTextBrush}" />
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Expander Grid.Row="2"
|
||||||
|
Header="底部信息"
|
||||||
|
IsExpanded="{Binding IsLogPanelExpanded}"
|
||||||
|
ToolTip="显示启动、组合、自动运行、导入导出和错误信息。">
|
||||||
|
<Border Background="{StaticResource PanelBackgroundBrush}"
|
||||||
|
BorderBrush="{StaticResource BorderBrushSoft}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="10"
|
||||||
|
Margin="0,8,0,0">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="120" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
|
<Button Content="复制"
|
||||||
|
Width="70"
|
||||||
|
Command="{Binding CopyLogsCommand}"
|
||||||
|
ToolTip="复制当前底部信息区内容。" />
|
||||||
|
<Button Content="清空"
|
||||||
|
Width="70"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Command="{Binding ClearLogsCommand}"
|
||||||
|
ToolTip="清空本次运行的底部信息。" />
|
||||||
|
</StackPanel>
|
||||||
|
<ListBox Grid.Row="1"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
ItemsSource="{Binding Logs}"
|
||||||
|
BorderThickness="0"
|
||||||
|
ToolTip="普通错误不会打断操作,会显示在这里。">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding DisplayText}"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
FontSize="12"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
ToolTip="{Binding DisplayText}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Expander>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,23 +1,202 @@
|
|||||||
using System.Text;
|
using System.ComponentModel;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Documents;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using ToolboxApp.Models;
|
||||||
using System.Windows.Navigation;
|
using ToolboxApp.Services;
|
||||||
using System.Windows.Shapes;
|
using ToolboxApp.ViewModels;
|
||||||
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
|
||||||
namespace ToolboxApp;
|
namespace ToolboxApp;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for MainWindow.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
|
private readonly MainWindowViewModel _viewModel;
|
||||||
|
private readonly HotkeyService _hotkeyService = new();
|
||||||
|
private TrayService? _trayService;
|
||||||
|
private bool _initialized;
|
||||||
|
private bool _exitRequested;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_viewModel = new MainWindowViewModel();
|
||||||
|
_viewModel.HotkeysRefreshRequested += (_, _) => RegisterHotkeys();
|
||||||
|
DataContext = _viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Window_OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
await _viewModel.InitializeAsync(this);
|
||||||
|
|
||||||
|
Width = _viewModel.Data.Settings.MainWindowWidth;
|
||||||
|
Height = _viewModel.Data.Settings.MainWindowHeight;
|
||||||
|
|
||||||
|
_trayService = new TrayService(
|
||||||
|
this,
|
||||||
|
() => _viewModel.OpenSettingsCommand.Execute(null),
|
||||||
|
enabled => _viewModel.SetGlobalHotkeysEnabled(enabled),
|
||||||
|
() => _viewModel.Data.Settings.GlobalHotkeysEnabled,
|
||||||
|
RequestExit);
|
||||||
|
|
||||||
|
RegisterHotkeys();
|
||||||
|
|
||||||
|
if (_viewModel.Data.Settings.StartHiddenToTray)
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
_trayService.RefreshMenuText();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _viewModel.RunAutoRunAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_OnClosing(object? sender, CancelEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_exitRequested && _viewModel.Data.Settings.HideOnClose)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
Hide();
|
||||||
|
_trayService?.RefreshMenuText();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_viewModel.Data.Settings.MainWindowWidth = Width;
|
||||||
|
_viewModel.Data.Settings.MainWindowHeight = Height;
|
||||||
|
_viewModel.SaveAsync().GetAwaiter().GetResult();
|
||||||
|
_hotkeyService.Dispose();
|
||||||
|
_trayService?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SearchTextBox_OnKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Escape)
|
||||||
|
{
|
||||||
|
SearchTextBox.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnPreviewKeyDown(e);
|
||||||
|
|
||||||
|
if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.F)
|
||||||
|
{
|
||||||
|
SearchTextBox.Focus();
|
||||||
|
SearchTextBox.SelectAll();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToolsListBox_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (_viewModel.LaunchSelectedCommand.CanExecute(null))
|
||||||
|
{
|
||||||
|
_viewModel.LaunchSelectedCommand.Execute(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToolsListBox_OnKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Enter && _viewModel.LaunchSelectedCommand.CanExecute(null))
|
||||||
|
{
|
||||||
|
_viewModel.LaunchSelectedCommand.Execute(null);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToolsListBox_OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
var item = FindParent<ListBoxItem>((DependencyObject)e.OriginalSource);
|
||||||
|
if (item is not null)
|
||||||
|
{
|
||||||
|
item.IsSelected = true;
|
||||||
|
item.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ContextLaunch_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_viewModel.LaunchSelectedCommand.CanExecute(null))
|
||||||
|
{
|
||||||
|
_viewModel.LaunchSelectedCommand.Execute(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ContextEdit_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_viewModel.EditSelectedCommand.CanExecute(null))
|
||||||
|
{
|
||||||
|
_viewModel.EditSelectedCommand.Execute(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ContextDelete_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_viewModel.DeleteSelectedCommand.CanExecute(null))
|
||||||
|
{
|
||||||
|
_viewModel.DeleteSelectedCommand.Execute(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterHotkeys()
|
||||||
|
{
|
||||||
|
if (!_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hotkeyService.RegisterAll(
|
||||||
|
this,
|
||||||
|
_viewModel.Data.Tools,
|
||||||
|
_viewModel.Data.Settings.GlobalHotkeysEnabled,
|
||||||
|
_viewModel.LaunchToolByIdAsync,
|
||||||
|
message => _viewModel.AddLog(message.Level, message.Message));
|
||||||
|
_trayService?.RefreshMenuText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RequestExit()
|
||||||
|
{
|
||||||
|
if (_viewModel.Data.Settings.ConfirmExit)
|
||||||
|
{
|
||||||
|
var confirmed = MessageBox.Show(
|
||||||
|
this,
|
||||||
|
"确定退出个人工具箱吗?退出后托盘和全局快捷键都会停止。",
|
||||||
|
"退出",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Question) == MessageBoxResult.Yes;
|
||||||
|
|
||||||
|
if (!confirmed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_exitRequested = true;
|
||||||
|
Close();
|
||||||
|
System.Windows.Application.Current.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T? FindParent<T>(DependencyObject child)
|
||||||
|
where T : DependencyObject
|
||||||
|
{
|
||||||
|
var current = child;
|
||||||
|
while (current is not null)
|
||||||
|
{
|
||||||
|
if (current is T result)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = VisualTreeHelper.GetParent(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,7 @@ public sealed class ConfigurationService
|
|||||||
public async Task<ToolboxData> LoadAsync(CancellationToken cancellationToken = default)
|
public async Task<ToolboxData> LoadAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
EnsureDirectories();
|
EnsureDirectories();
|
||||||
|
var toolsFileExists = File.Exists(ToolsPath);
|
||||||
|
|
||||||
var data = new ToolboxData
|
var data = new ToolboxData
|
||||||
{
|
{
|
||||||
@@ -45,7 +46,11 @@ public sealed class ConfigurationService
|
|||||||
|
|
||||||
data.Settings.DataVersion = CurrentDataVersion;
|
data.Settings.DataVersion = CurrentDataVersion;
|
||||||
SystemToolService.EnsureDefaultCategories(data.Categories);
|
SystemToolService.EnsureDefaultCategories(data.Categories);
|
||||||
|
if (!toolsFileExists || data.Tools.Count == 0)
|
||||||
|
{
|
||||||
SystemToolService.RestoreDefaultSystemTools(data.Categories, data.Tools);
|
SystemToolService.RestoreDefaultSystemTools(data.Categories, data.Tools);
|
||||||
|
}
|
||||||
|
|
||||||
MergeAutoRunEntries(data);
|
MergeAutoRunEntries(data);
|
||||||
|
|
||||||
await SaveAsync(data, cancellationToken);
|
await SaveAsync(data, cancellationToken);
|
||||||
|
|||||||
55
src/ToolboxApp/ViewModels/CombinationMemberViewModel.cs
Normal file
55
src/ToolboxApp/ViewModels/CombinationMemberViewModel.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using ToolboxApp.Models;
|
||||||
|
|
||||||
|
namespace ToolboxApp.ViewModels;
|
||||||
|
|
||||||
|
public sealed class CombinationMemberViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly Func<string, ToolItem?> _toolResolver;
|
||||||
|
private bool _enabled;
|
||||||
|
private int _intervalAfterMs;
|
||||||
|
|
||||||
|
public CombinationMemberViewModel(CombinationMember member, Func<string, ToolItem?> toolResolver)
|
||||||
|
{
|
||||||
|
Member = member;
|
||||||
|
_toolResolver = toolResolver;
|
||||||
|
_enabled = member.Enabled;
|
||||||
|
_intervalAfterMs = member.IntervalAfterMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CombinationMember Member { get; }
|
||||||
|
public string ToolId => Member.ToolId;
|
||||||
|
public string ToolName => _toolResolver(Member.ToolId)?.Name ?? "引用不存在";
|
||||||
|
public string ToolTypeLabel => _toolResolver(Member.ToolId)?.Type switch
|
||||||
|
{
|
||||||
|
ToolType.System => "系统",
|
||||||
|
ToolType.Local => "本地",
|
||||||
|
ToolType.Url => "网址",
|
||||||
|
ToolType.Combination => "组合",
|
||||||
|
_ => "未知"
|
||||||
|
};
|
||||||
|
|
||||||
|
public bool Enabled
|
||||||
|
{
|
||||||
|
get => _enabled;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _enabled, value))
|
||||||
|
{
|
||||||
|
Member.Enabled = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IntervalAfterMs
|
||||||
|
{
|
||||||
|
get => _intervalAfterMs;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var normalized = Math.Max(0, value);
|
||||||
|
if (SetProperty(ref _intervalAfterMs, normalized))
|
||||||
|
{
|
||||||
|
Member.IntervalAfterMs = normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
552
src/ToolboxApp/ViewModels/MainWindowViewModel.cs
Normal file
552
src/ToolboxApp/ViewModels/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows;
|
||||||
|
using ToolboxApp.Commands;
|
||||||
|
using ToolboxApp.Models;
|
||||||
|
using ToolboxApp.Services;
|
||||||
|
using ToolboxApp.Views;
|
||||||
|
using Clipboard = System.Windows.Clipboard;
|
||||||
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
|
||||||
|
namespace ToolboxApp.ViewModels;
|
||||||
|
|
||||||
|
public sealed class MainWindowViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly ConfigurationService _configurationService;
|
||||||
|
private readonly ToolLaunchService _toolLaunchService;
|
||||||
|
private readonly AutoRunService _autoRunService;
|
||||||
|
private readonly PathValidationService _pathValidationService;
|
||||||
|
private readonly StartupService _startupService;
|
||||||
|
private ToolboxData _data = new();
|
||||||
|
private CategoryItem? _selectedCategory;
|
||||||
|
private ToolCardViewModel? _selectedTool;
|
||||||
|
private string _searchText = "";
|
||||||
|
private bool _isLogPanelExpanded = true;
|
||||||
|
|
||||||
|
public MainWindowViewModel()
|
||||||
|
{
|
||||||
|
_configurationService = new ConfigurationService();
|
||||||
|
_toolLaunchService = new ToolLaunchService();
|
||||||
|
_autoRunService = new AutoRunService(_toolLaunchService);
|
||||||
|
_pathValidationService = new PathValidationService();
|
||||||
|
_startupService = new StartupService();
|
||||||
|
|
||||||
|
AddLocalToolCommand = new RelayCommand(AddLocalTool);
|
||||||
|
AddUrlToolCommand = new RelayCommand(AddUrlTool);
|
||||||
|
AddCombinationCommand = new RelayCommand(AddCombination);
|
||||||
|
LaunchSelectedCommand = new AsyncRelayCommand(LaunchSelectedAsync, () => SelectedTool is not null);
|
||||||
|
EditSelectedCommand = new RelayCommand(EditSelectedTool, () => SelectedTool is not null);
|
||||||
|
DeleteSelectedCommand = new RelayCommand(DeleteSelectedTool, () => SelectedTool is not null);
|
||||||
|
OpenSettingsCommand = new RelayCommand(OpenSettings);
|
||||||
|
AddCategoryCommand = new RelayCommand(AddCategory);
|
||||||
|
RenameCategoryCommand = new RelayCommand(RenameCategory, () => SelectedCategory is not null);
|
||||||
|
DeleteCategoryCommand = new RelayCommand(DeleteCategory, () => SelectedCategory is not null && SelectedCategory.Id != SystemToolService.UncategorizedCategoryId);
|
||||||
|
ClearLogsCommand = new RelayCommand(() => Logs.Clear());
|
||||||
|
CopyLogsCommand = new RelayCommand(CopyLogs, () => Logs.Count > 0);
|
||||||
|
ToggleLogPanelCommand = new RelayCommand(() => IsLogPanelExpanded = !IsLogPanelExpanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler? HotkeysRefreshRequested;
|
||||||
|
|
||||||
|
public Window? Owner { get; set; }
|
||||||
|
|
||||||
|
public ObservableCollection<CategoryItem> Categories { get; } = [];
|
||||||
|
public ObservableCollection<ToolCardViewModel> Tools { get; } = [];
|
||||||
|
public ObservableCollection<LogMessage> Logs { get; } = [];
|
||||||
|
|
||||||
|
public RelayCommand AddLocalToolCommand { get; }
|
||||||
|
public RelayCommand AddUrlToolCommand { get; }
|
||||||
|
public RelayCommand AddCombinationCommand { get; }
|
||||||
|
public AsyncRelayCommand LaunchSelectedCommand { get; }
|
||||||
|
public RelayCommand EditSelectedCommand { get; }
|
||||||
|
public RelayCommand DeleteSelectedCommand { get; }
|
||||||
|
public RelayCommand OpenSettingsCommand { get; }
|
||||||
|
public RelayCommand AddCategoryCommand { get; }
|
||||||
|
public RelayCommand RenameCategoryCommand { get; }
|
||||||
|
public RelayCommand DeleteCategoryCommand { get; }
|
||||||
|
public RelayCommand ClearLogsCommand { get; }
|
||||||
|
public RelayCommand CopyLogsCommand { get; }
|
||||||
|
public RelayCommand ToggleLogPanelCommand { get; }
|
||||||
|
|
||||||
|
public ToolboxData Data => _data;
|
||||||
|
public ConfigurationService ConfigurationService => _configurationService;
|
||||||
|
public StartupService StartupService => _startupService;
|
||||||
|
|
||||||
|
public CategoryItem? SelectedCategory
|
||||||
|
{
|
||||||
|
get => _selectedCategory;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _selectedCategory, value))
|
||||||
|
{
|
||||||
|
RefreshTools();
|
||||||
|
RaiseSelectionCommandState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToolCardViewModel? SelectedTool
|
||||||
|
{
|
||||||
|
get => _selectedTool;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _selectedTool, value))
|
||||||
|
{
|
||||||
|
RaiseSelectionCommandState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SearchText
|
||||||
|
{
|
||||||
|
get => _searchText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _searchText, value))
|
||||||
|
{
|
||||||
|
RefreshTools();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLogPanelExpanded
|
||||||
|
{
|
||||||
|
get => _isLogPanelExpanded;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _isLogPanelExpanded, value))
|
||||||
|
{
|
||||||
|
_data.Settings.LogPanelExpanded = value;
|
||||||
|
_ = SaveAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync(Window owner)
|
||||||
|
{
|
||||||
|
Owner = owner;
|
||||||
|
_data = await _configurationService.LoadAsync();
|
||||||
|
_isLogPanelExpanded = _data.Settings.LogPanelExpanded;
|
||||||
|
OnPropertyChanged(nameof(IsLogPanelExpanded));
|
||||||
|
|
||||||
|
var invalidCount = _pathValidationService.ValidateTools(_data.Tools);
|
||||||
|
RefreshCategories();
|
||||||
|
RefreshTools();
|
||||||
|
|
||||||
|
AddLog(LogLevel.Info, $"配置目录:{_configurationService.ConfigDirectory}");
|
||||||
|
AddLog(LogLevel.Success, "个人工具箱已启动。");
|
||||||
|
if (invalidCount > 0)
|
||||||
|
{
|
||||||
|
AddLog(LogLevel.Warning, $"路径检查完成:发现 {invalidCount} 个失效工具。");
|
||||||
|
}
|
||||||
|
|
||||||
|
await SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunAutoRunAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
_configurationService.MergeAutoRunEntries(_data);
|
||||||
|
await SaveAsync(cancellationToken);
|
||||||
|
await _autoRunService.ExecuteAsync(_data, AddLog, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LaunchToolByIdAsync(string toolId)
|
||||||
|
{
|
||||||
|
var tool = _data.Tools.FirstOrDefault(item => item.Id == toolId && !item.IsDeleted);
|
||||||
|
if (tool is null)
|
||||||
|
{
|
||||||
|
AddLog(LogLevel.Warning, $"快捷键目标不存在:{toolId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _toolLaunchService.LaunchAsync(tool, _data.Tools, AddLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
_configurationService.MergeAutoRunEntries(_data);
|
||||||
|
await _configurationService.SaveAsync(_data, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshHotkeys()
|
||||||
|
{
|
||||||
|
HotkeysRefreshRequested?.Invoke(this, EventArgs.Empty);
|
||||||
|
RefreshTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetGlobalHotkeysEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
_data.Settings.GlobalHotkeysEnabled = enabled;
|
||||||
|
AddLog(LogLevel.Info, enabled ? "全局快捷键已开启。" : "全局快捷键已关闭。");
|
||||||
|
_ = SaveAsync();
|
||||||
|
RefreshHotkeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddLog(LogLevel level, string message)
|
||||||
|
{
|
||||||
|
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
Logs.Add(new LogMessage
|
||||||
|
{
|
||||||
|
Level = level,
|
||||||
|
Message = message
|
||||||
|
});
|
||||||
|
|
||||||
|
while (Logs.Count > 500)
|
||||||
|
{
|
||||||
|
Logs.RemoveAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyLogsCommand.RaiseCanExecuteChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddLog(LogMessage message)
|
||||||
|
{
|
||||||
|
AddLog(message.Level, message.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LaunchSelectedAsync()
|
||||||
|
{
|
||||||
|
if (SelectedTool is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _toolLaunchService.LaunchAsync(SelectedTool.Tool, _data.Tools, AddLog);
|
||||||
|
RefreshTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddLocalTool()
|
||||||
|
{
|
||||||
|
var tool = CreateBaseTool(ToolType.Local);
|
||||||
|
tool.Name = "新的本地工具";
|
||||||
|
tool.IconKey = "folder";
|
||||||
|
|
||||||
|
var edited = ToolEditorWindow.Edit(tool, Categories, Owner);
|
||||||
|
if (edited is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
edited.SortOrder = NextToolSortOrder();
|
||||||
|
_data.Tools.Add(edited);
|
||||||
|
_pathValidationService.ValidateTools(_data.Tools);
|
||||||
|
RefreshTools();
|
||||||
|
AddLog(LogLevel.Success, $"已添加本地工具:{edited.Name}");
|
||||||
|
_ = SaveAsync();
|
||||||
|
RefreshHotkeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddUrlTool()
|
||||||
|
{
|
||||||
|
var tool = CreateBaseTool(ToolType.Url);
|
||||||
|
tool.Name = "新的网址";
|
||||||
|
tool.IconKey = "link";
|
||||||
|
|
||||||
|
var edited = ToolEditorWindow.Edit(tool, Categories, Owner);
|
||||||
|
if (edited is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
edited.SortOrder = NextToolSortOrder();
|
||||||
|
_data.Tools.Add(edited);
|
||||||
|
RefreshTools();
|
||||||
|
AddLog(LogLevel.Success, $"已添加网址工具:{edited.Name}");
|
||||||
|
_ = SaveAsync();
|
||||||
|
RefreshHotkeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddCombination()
|
||||||
|
{
|
||||||
|
var tool = CreateBaseTool(ToolType.Combination);
|
||||||
|
tool.Name = "新的组合";
|
||||||
|
tool.IconKey = "combination";
|
||||||
|
tool.Combination = new CombinationConfig();
|
||||||
|
|
||||||
|
var edited = CombinationEditorWindow.Edit(tool, Categories, _data.Tools, _toolLaunchService, Owner);
|
||||||
|
if (edited is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
edited.SortOrder = NextToolSortOrder();
|
||||||
|
_data.Tools.Add(edited);
|
||||||
|
RefreshTools();
|
||||||
|
AddLog(LogLevel.Success, $"已添加组合:{edited.Name}");
|
||||||
|
_ = SaveAsync();
|
||||||
|
RefreshHotkeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditSelectedTool()
|
||||||
|
{
|
||||||
|
if (SelectedTool is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var original = SelectedTool.Tool;
|
||||||
|
var candidate = original.Clone();
|
||||||
|
ToolItem? edited = original.Type == ToolType.Combination
|
||||||
|
? CombinationEditorWindow.Edit(candidate, Categories, _data.Tools, _toolLaunchService, Owner)
|
||||||
|
: ToolEditorWindow.Edit(candidate, Categories, Owner);
|
||||||
|
|
||||||
|
if (edited is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyToolValues(edited, original);
|
||||||
|
_pathValidationService.ValidateTools(_data.Tools);
|
||||||
|
RefreshTools();
|
||||||
|
AddLog(LogLevel.Success, $"已保存工具:{original.Name}");
|
||||||
|
_ = SaveAsync();
|
||||||
|
RefreshHotkeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteSelectedTool()
|
||||||
|
{
|
||||||
|
if (SelectedTool is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tool = SelectedTool.Tool;
|
||||||
|
var confirmed = MessageBox.Show(
|
||||||
|
Owner,
|
||||||
|
$"确定删除“{tool.Name}”吗?组合中对它的引用也会失效。",
|
||||||
|
"删除工具",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Warning) == MessageBoxResult.Yes;
|
||||||
|
|
||||||
|
if (!confirmed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tool.IsDeleted = true;
|
||||||
|
foreach (var combination in _data.Tools.Where(item => item.Type == ToolType.Combination && item.Combination is not null))
|
||||||
|
{
|
||||||
|
combination.Combination!.Members.RemoveAll(member => member.ToolId == tool.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshTools();
|
||||||
|
AddLog(LogLevel.Warning, $"已删除工具:{tool.Name}");
|
||||||
|
_ = SaveAsync();
|
||||||
|
RefreshHotkeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSettings()
|
||||||
|
{
|
||||||
|
var window = new SettingsWindow(_data, _configurationService, _startupService, _pathValidationService, _toolLaunchService)
|
||||||
|
{
|
||||||
|
Owner = Owner
|
||||||
|
};
|
||||||
|
|
||||||
|
if (window.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
_data = window.Data;
|
||||||
|
_pathValidationService.ValidateTools(_data.Tools);
|
||||||
|
RefreshCategories();
|
||||||
|
RefreshTools();
|
||||||
|
AddLog(LogLevel.Success, "设置已保存。");
|
||||||
|
_ = SaveAsync();
|
||||||
|
RefreshHotkeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddCategory()
|
||||||
|
{
|
||||||
|
var name = PromptWindow.Prompt("新建分类", "分类名称", "", Owner);
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_data.Categories.Add(new CategoryItem
|
||||||
|
{
|
||||||
|
Name = name.Trim(),
|
||||||
|
IconKey = "category",
|
||||||
|
SortOrder = _data.Categories.Count == 0 ? 0 : _data.Categories.Max(category => category.SortOrder) + 1
|
||||||
|
});
|
||||||
|
|
||||||
|
RefreshCategories();
|
||||||
|
AddLog(LogLevel.Success, $"已新建分类:{name.Trim()}");
|
||||||
|
_ = SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenameCategory()
|
||||||
|
{
|
||||||
|
if (SelectedCategory is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = PromptWindow.Prompt("重命名分类", "分类名称", SelectedCategory.Name, Owner);
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedCategory.Name = name.Trim();
|
||||||
|
RefreshCategories();
|
||||||
|
RefreshTools();
|
||||||
|
AddLog(LogLevel.Success, $"已重命名分类:{name.Trim()}");
|
||||||
|
_ = SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteCategory()
|
||||||
|
{
|
||||||
|
if (SelectedCategory is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SelectedCategory.Id == SystemToolService.UncategorizedCategoryId)
|
||||||
|
{
|
||||||
|
AddLog(LogLevel.Warning, "未分类为固定分类,不能删除。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var confirmed = MessageBox.Show(
|
||||||
|
Owner,
|
||||||
|
$"删除分类“{SelectedCategory.Name}”后,其中工具会移动到“未分类”。是否继续?",
|
||||||
|
"删除分类",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Warning) == MessageBoxResult.Yes;
|
||||||
|
|
||||||
|
if (!confirmed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var tool in _data.Tools.Where(tool => tool.CategoryId == SelectedCategory.Id))
|
||||||
|
{
|
||||||
|
tool.CategoryId = SystemToolService.UncategorizedCategoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deletedName = SelectedCategory.Name;
|
||||||
|
_data.Categories.Remove(SelectedCategory);
|
||||||
|
RefreshCategories();
|
||||||
|
SelectedCategory = _data.Categories.FirstOrDefault(category => category.Id == SystemToolService.UncategorizedCategoryId);
|
||||||
|
AddLog(LogLevel.Warning, $"已删除分类:{deletedName}");
|
||||||
|
_ = SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyLogs()
|
||||||
|
{
|
||||||
|
if (Logs.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Clipboard.SetText(string.Join(Environment.NewLine, Logs.Select(log => log.DisplayText)));
|
||||||
|
AddLog(LogLevel.Success, "已复制底部信息。");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ToolItem CreateBaseTool(ToolType type)
|
||||||
|
{
|
||||||
|
return new ToolItem
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid().ToString("N"),
|
||||||
|
Type = type,
|
||||||
|
CategoryId = SelectedCategory?.Id ?? Categories.FirstOrDefault()?.Id ?? SystemToolService.UncategorizedCategoryId,
|
||||||
|
SortOrder = NextToolSortOrder()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int NextToolSortOrder()
|
||||||
|
{
|
||||||
|
return _data.Tools.Count == 0 ? 0 : _data.Tools.Max(tool => tool.SortOrder) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshCategories()
|
||||||
|
{
|
||||||
|
Categories.Clear();
|
||||||
|
foreach (var category in _data.Categories.OrderBy(category => category.SortOrder).ThenBy(category => category.Name))
|
||||||
|
{
|
||||||
|
Categories.Add(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedCategory ??= Categories.FirstOrDefault();
|
||||||
|
if (SelectedCategory is not null && Categories.All(category => category.Id != SelectedCategory.Id))
|
||||||
|
{
|
||||||
|
SelectedCategory = Categories.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteCategoryCommand.RaiseCanExecuteChanged();
|
||||||
|
RenameCategoryCommand.RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshTools()
|
||||||
|
{
|
||||||
|
var selectedId = SelectedTool?.Id;
|
||||||
|
Tools.Clear();
|
||||||
|
|
||||||
|
var query = _data.Tools.Where(tool => !tool.IsDeleted);
|
||||||
|
if (!string.IsNullOrWhiteSpace(SearchText))
|
||||||
|
{
|
||||||
|
var keyword = SearchText.Trim();
|
||||||
|
query = query.Where(tool => MatchesSearch(tool, keyword));
|
||||||
|
}
|
||||||
|
else if (SelectedCategory is not null)
|
||||||
|
{
|
||||||
|
query = query.Where(tool => tool.CategoryId == SelectedCategory.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var tool in query.OrderBy(tool => tool.SortOrder).ThenBy(tool => tool.Name))
|
||||||
|
{
|
||||||
|
Tools.Add(new ToolCardViewModel(tool, ResolveCategoryName));
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedTool = Tools.FirstOrDefault(tool => tool.Id == selectedId);
|
||||||
|
RaiseSelectionCommandState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MatchesSearch(ToolItem tool, string keyword)
|
||||||
|
{
|
||||||
|
return Contains(tool.Name, keyword)
|
||||||
|
|| Contains(tool.Description, keyword)
|
||||||
|
|| Contains(tool.LaunchTarget, keyword)
|
||||||
|
|| Contains(tool.Url, keyword)
|
||||||
|
|| Contains(ResolveCategoryName(tool.CategoryId), keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Contains(string? source, string keyword)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(source)
|
||||||
|
&& source.Contains(keyword, StringComparison.CurrentCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ResolveCategoryName(string categoryId)
|
||||||
|
{
|
||||||
|
return _data.Categories.FirstOrDefault(category => category.Id == categoryId)?.Name ?? "未分类";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseSelectionCommandState()
|
||||||
|
{
|
||||||
|
LaunchSelectedCommand.RaiseCanExecuteChanged();
|
||||||
|
EditSelectedCommand.RaiseCanExecuteChanged();
|
||||||
|
DeleteSelectedCommand.RaiseCanExecuteChanged();
|
||||||
|
RenameCategoryCommand.RaiseCanExecuteChanged();
|
||||||
|
DeleteCategoryCommand.RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyToolValues(ToolItem source, ToolItem target)
|
||||||
|
{
|
||||||
|
target.Name = source.Name;
|
||||||
|
target.Description = source.Description;
|
||||||
|
target.Type = source.Type;
|
||||||
|
target.CategoryId = source.CategoryId;
|
||||||
|
target.IconKey = source.IconKey;
|
||||||
|
target.LaunchTarget = source.LaunchTarget;
|
||||||
|
target.Url = source.Url;
|
||||||
|
target.Arguments = source.Arguments;
|
||||||
|
target.WorkingDirectory = source.WorkingDirectory;
|
||||||
|
target.SystemToolKey = source.SystemToolKey;
|
||||||
|
target.Hotkey = source.Hotkey;
|
||||||
|
target.AutoRunEnabled = source.AutoRunEnabled;
|
||||||
|
target.RunAsAdmin = source.RunAsAdmin;
|
||||||
|
target.Combination = source.Combination?.Clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/ToolboxApp/ViewModels/ToolCardViewModel.cs
Normal file
124
src/ToolboxApp/ViewModels/ToolCardViewModel.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
using ToolboxApp.Models;
|
||||||
|
|
||||||
|
namespace ToolboxApp.ViewModels;
|
||||||
|
|
||||||
|
public sealed class ToolCardViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly Func<string, string> _categoryNameResolver;
|
||||||
|
|
||||||
|
public ToolCardViewModel(ToolItem tool, Func<string, string> categoryNameResolver)
|
||||||
|
{
|
||||||
|
Tool = tool;
|
||||||
|
_categoryNameResolver = categoryNameResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToolItem Tool { get; }
|
||||||
|
|
||||||
|
public string Id => Tool.Id;
|
||||||
|
public string Name => Tool.Name;
|
||||||
|
public string Description => string.IsNullOrWhiteSpace(Tool.Description) ? "暂无说明" : Tool.Description;
|
||||||
|
public string CategoryName => _categoryNameResolver(Tool.CategoryId);
|
||||||
|
public string IconText => IconKeyToText(Tool.IconKey, Tool.Type);
|
||||||
|
public string TypeLabel => Tool.Type switch
|
||||||
|
{
|
||||||
|
ToolType.System => "系统",
|
||||||
|
ToolType.Local => "本地",
|
||||||
|
ToolType.Url => "网址",
|
||||||
|
ToolType.Combination => "组合",
|
||||||
|
_ => "工具"
|
||||||
|
};
|
||||||
|
|
||||||
|
public IReadOnlyList<string> StatusBadges
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var badges = new List<string>();
|
||||||
|
if (Tool.PathInvalid)
|
||||||
|
{
|
||||||
|
badges.Add("路径失效");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(Tool.Hotkey))
|
||||||
|
{
|
||||||
|
badges.Add("快捷键");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Tool.AutoRunEnabled)
|
||||||
|
{
|
||||||
|
badges.Add("自动运行");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Tool.RunAsAdmin)
|
||||||
|
{
|
||||||
|
badges.Add("管理员");
|
||||||
|
}
|
||||||
|
|
||||||
|
return badges;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DetailText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Tool.Type switch
|
||||||
|
{
|
||||||
|
ToolType.Local => Tool.LaunchTarget ?? "",
|
||||||
|
ToolType.Url => Tool.Url ?? "",
|
||||||
|
ToolType.System => Tool.LaunchTarget ?? "",
|
||||||
|
ToolType.Combination => $"{Tool.Combination?.Members.Count ?? 0} 个成员",
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(Name));
|
||||||
|
OnPropertyChanged(nameof(Description));
|
||||||
|
OnPropertyChanged(nameof(CategoryName));
|
||||||
|
OnPropertyChanged(nameof(IconText));
|
||||||
|
OnPropertyChanged(nameof(TypeLabel));
|
||||||
|
OnPropertyChanged(nameof(StatusBadges));
|
||||||
|
OnPropertyChanged(nameof(DetailText));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string IconKeyToText(string iconKey, ToolType type)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(iconKey))
|
||||||
|
{
|
||||||
|
var mapped = iconKey.ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"notepad" => "TXT",
|
||||||
|
"calculator" => "123",
|
||||||
|
"taskmgr" => "CPU",
|
||||||
|
"control" => "CTL",
|
||||||
|
"settings" => "SET",
|
||||||
|
"device" => "DEV",
|
||||||
|
"disk" => "DSK",
|
||||||
|
"service" => "SVC",
|
||||||
|
"registry" => "REG",
|
||||||
|
"network" => "NET",
|
||||||
|
"apps" => "APP",
|
||||||
|
"link" => "URL",
|
||||||
|
"folder" => "DIR",
|
||||||
|
"combination" => "COM",
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(mapped))
|
||||||
|
{
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
ToolType.System => "SYS",
|
||||||
|
ToolType.Local => "LOC",
|
||||||
|
ToolType.Url => "URL",
|
||||||
|
ToolType.Combination => "COM",
|
||||||
|
_ => "BOX"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
166
src/ToolboxApp/Views/CombinationEditorWindow.xaml
Normal file
166
src/ToolboxApp/Views/CombinationEditorWindow.xaml
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<Window x:Class="ToolboxApp.Views.CombinationEditorWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="编辑组合"
|
||||||
|
Width="760"
|
||||||
|
Height="640"
|
||||||
|
MinWidth="680"
|
||||||
|
MinHeight="560"
|
||||||
|
WindowStartupLocation="CenterOwner">
|
||||||
|
<Grid Margin="18">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="96" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="96" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="名称" VerticalAlignment="Center" />
|
||||||
|
<TextBox x:Name="NameTextBox"
|
||||||
|
Grid.Column="1"
|
||||||
|
MinHeight="32"
|
||||||
|
Margin="0,0,16,8"
|
||||||
|
ToolTip="组合卡片显示名称。" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="2" Text="分类" VerticalAlignment="Center" />
|
||||||
|
<ComboBox x:Name="CategoryComboBox"
|
||||||
|
Grid.Column="3"
|
||||||
|
MinHeight="32"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
DisplayMemberPath="Name"
|
||||||
|
SelectedValuePath="Id"
|
||||||
|
ToolTip="组合也作为一种工具,只能属于一个一级分类。" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Text="说明" VerticalAlignment="Top" Margin="0,8,0,0" />
|
||||||
|
<TextBox x:Name="DescriptionTextBox"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
MinHeight="58"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
ToolTip="描述这个组合会打开哪些环境或工具。" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2" Text="快捷键" VerticalAlignment="Center" />
|
||||||
|
<TextBox x:Name="HotkeyTextBox"
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="1"
|
||||||
|
MinHeight="32"
|
||||||
|
Margin="0,8,16,0"
|
||||||
|
ToolTip="格式示例:Ctrl + Alt + D。组合可被全局快捷键直接启动。" />
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
Grid.Column="2"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Margin="0,8,0,0">
|
||||||
|
<CheckBox x:Name="AutoRunCheckBox"
|
||||||
|
Content="工具箱启动时自动运行"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ToolTip="开启后组合会出现在自动运行列表中。" />
|
||||||
|
<ComboBox x:Name="FailurePolicyComboBox"
|
||||||
|
Width="180"
|
||||||
|
Margin="18,0,0,0"
|
||||||
|
ToolTip="设置成员启动失败后继续执行,还是停止整个组合。">
|
||||||
|
<ComboBoxItem Content="失败后继续" Tag="Continue" />
|
||||||
|
<ComboBoxItem Content="失败后停止" Tag="Stop" />
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" Margin="0,16,0,0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<ComboBox x:Name="ToolPickerComboBox"
|
||||||
|
Width="300"
|
||||||
|
DisplayMemberPath="Name"
|
||||||
|
ToolTip="从已有工具或组合中选择一个成员。" />
|
||||||
|
<Button Content="添加成员"
|
||||||
|
Width="96"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
ToolTip="把选择的工具加入组合成员列表。"
|
||||||
|
Click="AddMemberButton_OnClick" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Button Content="上移"
|
||||||
|
Width="70"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
ToolTip="将选中成员提前执行。"
|
||||||
|
Click="MoveUpButton_OnClick" />
|
||||||
|
<Button Content="下移"
|
||||||
|
Width="70"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
ToolTip="将选中成员延后执行。"
|
||||||
|
Click="MoveDownButton_OnClick" />
|
||||||
|
<Button Content="移除"
|
||||||
|
Width="70"
|
||||||
|
ToolTip="从组合中移除选中成员。"
|
||||||
|
Click="RemoveMemberButton_OnClick" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<DataGrid x:Name="MembersDataGrid"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0,10,0,0"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
SelectionMode="Single"
|
||||||
|
ToolTip="组合成员会按列表顺序启动,可临时禁用成员并设置执行后的等待间隔。">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridCheckBoxColumn Header="启用"
|
||||||
|
Width="70"
|
||||||
|
Binding="{Binding Enabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<DataGridTextColumn Header="名称"
|
||||||
|
Width="*"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding ToolName}" />
|
||||||
|
<DataGridTextColumn Header="类型"
|
||||||
|
Width="90"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding ToolTypeLabel}" />
|
||||||
|
<DataGridTextColumn Header="间隔(ms)"
|
||||||
|
Width="110"
|
||||||
|
Binding="{Binding IntervalAfterMs, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="0,14,0,0">
|
||||||
|
<Button Content="保存"
|
||||||
|
Width="88"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
IsDefault="True"
|
||||||
|
ToolTip="校验循环引用和重复工具后保存组合。"
|
||||||
|
Click="SaveButton_OnClick" />
|
||||||
|
<Button Content="取消"
|
||||||
|
Width="88"
|
||||||
|
IsCancel="True"
|
||||||
|
ToolTip="放弃本次组合修改。"
|
||||||
|
Click="CancelButton_OnClick" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
189
src/ToolboxApp/Views/CombinationEditorWindow.xaml.cs
Normal file
189
src/ToolboxApp/Views/CombinationEditorWindow.xaml.cs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using ToolboxApp.Models;
|
||||||
|
using ToolboxApp.Services;
|
||||||
|
using ToolboxApp.ViewModels;
|
||||||
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
|
||||||
|
namespace ToolboxApp.Views;
|
||||||
|
|
||||||
|
public partial class CombinationEditorWindow : Window
|
||||||
|
{
|
||||||
|
private readonly ToolItem _tool;
|
||||||
|
private readonly IReadOnlyList<ToolItem> _allTools;
|
||||||
|
private readonly ToolLaunchService _launchService;
|
||||||
|
private readonly ObservableCollection<CombinationMemberViewModel> _members = [];
|
||||||
|
|
||||||
|
private CombinationEditorWindow(
|
||||||
|
ToolItem tool,
|
||||||
|
IEnumerable<CategoryItem> categories,
|
||||||
|
IEnumerable<ToolItem> allTools,
|
||||||
|
ToolLaunchService launchService)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_tool = tool;
|
||||||
|
_allTools = allTools.Where(item => !item.IsDeleted).ToList();
|
||||||
|
_launchService = launchService;
|
||||||
|
|
||||||
|
_tool.Combination ??= new CombinationConfig();
|
||||||
|
var combination = _tool.Combination;
|
||||||
|
|
||||||
|
CategoryComboBox.ItemsSource = categories.ToList();
|
||||||
|
CategoryComboBox.SelectedValue = tool.CategoryId;
|
||||||
|
ToolPickerComboBox.ItemsSource = _allTools.Where(item => item.Id != tool.Id).OrderBy(item => item.Name).ToList();
|
||||||
|
MembersDataGrid.ItemsSource = _members;
|
||||||
|
|
||||||
|
NameTextBox.Text = tool.Name;
|
||||||
|
DescriptionTextBox.Text = tool.Description;
|
||||||
|
HotkeyTextBox.Text = tool.Hotkey;
|
||||||
|
AutoRunCheckBox.IsChecked = tool.AutoRunEnabled;
|
||||||
|
FailurePolicyComboBox.SelectedIndex = combination.FailurePolicy == FailurePolicy.Stop ? 1 : 0;
|
||||||
|
|
||||||
|
foreach (var member in combination.Members.OrderBy(member => member.SortOrder))
|
||||||
|
{
|
||||||
|
_members.Add(new CombinationMemberViewModel(member.Clone(), ResolveTool));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToolItem EditedTool => _tool;
|
||||||
|
|
||||||
|
public static ToolItem? Edit(
|
||||||
|
ToolItem tool,
|
||||||
|
IEnumerable<CategoryItem> categories,
|
||||||
|
IEnumerable<ToolItem> allTools,
|
||||||
|
ToolLaunchService launchService,
|
||||||
|
Window? owner)
|
||||||
|
{
|
||||||
|
var window = new CombinationEditorWindow(tool, categories, allTools, launchService)
|
||||||
|
{
|
||||||
|
Owner = owner
|
||||||
|
};
|
||||||
|
|
||||||
|
return window.ShowDialog() == true ? window.EditedTool : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddMemberButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ToolPickerComboBox.SelectedItem is not ToolItem selectedTool)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_members.Any(member => member.ToolId == selectedTool.Id))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "该成员已经在当前组合中。", "添加成员", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_members.Add(new CombinationMemberViewModel(new CombinationMember
|
||||||
|
{
|
||||||
|
ToolId = selectedTool.Id,
|
||||||
|
Enabled = true,
|
||||||
|
SortOrder = _members.Count,
|
||||||
|
IntervalAfterMs = 0
|
||||||
|
}, ResolveTool));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveMemberButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (MembersDataGrid.SelectedItem is CombinationMemberViewModel selected)
|
||||||
|
{
|
||||||
|
_members.Remove(selected);
|
||||||
|
ReorderMembers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveUpButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
MoveSelected(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveDownButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
MoveSelected(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var name = NameTextBox.Text.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "请输入组合名称。", "保存组合", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CategoryComboBox.SelectedValue is not string categoryId || string.IsNullOrWhiteSpace(categoryId))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "请选择分类。", "保存组合", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hotkey = HotkeyTextBox.Text.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(hotkey) && !HotkeyParser.TryParse(hotkey, out _, out _))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "快捷键格式无效,请使用类似 Ctrl + Alt + D 的格式。", "保存组合", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReorderMembers();
|
||||||
|
_tool.Name = name;
|
||||||
|
_tool.Description = DescriptionTextBox.Text.Trim();
|
||||||
|
_tool.CategoryId = categoryId;
|
||||||
|
_tool.Hotkey = string.IsNullOrWhiteSpace(hotkey) ? null : HotkeyParser.Normalize(hotkey);
|
||||||
|
_tool.AutoRunEnabled = AutoRunCheckBox.IsChecked == true;
|
||||||
|
_tool.IconKey = "combination";
|
||||||
|
_tool.Combination = new CombinationConfig
|
||||||
|
{
|
||||||
|
FailurePolicy = FailurePolicyComboBox.SelectedIndex == 1 ? FailurePolicy.Stop : FailurePolicy.Continue,
|
||||||
|
Members = _members.Select(member => member.Member.Clone()).ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
var validationTools = _allTools.Where(item => item.Id != _tool.Id).Concat([_tool]).ToList();
|
||||||
|
var validation = _launchService.ValidateCombination(_tool, validationTools);
|
||||||
|
if (!validation.Success)
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, validation.ErrorMessage ?? "组合校验失败。", "保存组合", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogResult = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ToolItem? ResolveTool(string toolId)
|
||||||
|
{
|
||||||
|
return _allTools.FirstOrDefault(tool => tool.Id == toolId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveSelected(int offset)
|
||||||
|
{
|
||||||
|
if (MembersDataGrid.SelectedItem is not CombinationMemberViewModel selected)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = _members.IndexOf(selected);
|
||||||
|
var targetIndex = index + offset;
|
||||||
|
if (targetIndex < 0 || targetIndex >= _members.Count)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_members.Move(index, targetIndex);
|
||||||
|
MembersDataGrid.SelectedItem = selected;
|
||||||
|
ReorderMembers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReorderMembers()
|
||||||
|
{
|
||||||
|
for (var index = 0; index < _members.Count; index++)
|
||||||
|
{
|
||||||
|
_members[index].Member.SortOrder = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/ToolboxApp/Views/PromptWindow.xaml
Normal file
45
src/ToolboxApp/Views/PromptWindow.xaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<Window x:Class="ToolboxApp.Views.PromptWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="输入"
|
||||||
|
Width="380"
|
||||||
|
Height="180"
|
||||||
|
ResizeMode="NoResize"
|
||||||
|
WindowStartupLocation="CenterOwner">
|
||||||
|
<Grid Margin="18">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock x:Name="PromptText"
|
||||||
|
FontSize="14"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
ToolTip="当前需要输入的信息名称。" />
|
||||||
|
|
||||||
|
<TextBox x:Name="ValueTextBox"
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0,12,0,0"
|
||||||
|
MinHeight="32"
|
||||||
|
ToolTip="请输入名称,按 Enter 确认。"
|
||||||
|
KeyDown="ValueTextBox_OnKeyDown" />
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="3"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<Button Content="确定"
|
||||||
|
Width="82"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
IsDefault="True"
|
||||||
|
ToolTip="保存输入内容并关闭窗口。"
|
||||||
|
Click="OkButton_OnClick" />
|
||||||
|
<Button Content="取消"
|
||||||
|
Width="82"
|
||||||
|
IsCancel="True"
|
||||||
|
ToolTip="放弃本次输入。"
|
||||||
|
Click="CancelButton_OnClick" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
47
src/ToolboxApp/Views/PromptWindow.xaml.cs
Normal file
47
src/ToolboxApp/Views/PromptWindow.xaml.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace ToolboxApp.Views;
|
||||||
|
|
||||||
|
public partial class PromptWindow : Window
|
||||||
|
{
|
||||||
|
private PromptWindow(string title, string prompt, string defaultValue)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Title = title;
|
||||||
|
PromptText.Text = prompt;
|
||||||
|
ValueTextBox.Text = defaultValue;
|
||||||
|
ValueTextBox.SelectAll();
|
||||||
|
Loaded += (_, _) => ValueTextBox.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Value => ValueTextBox.Text.Trim();
|
||||||
|
|
||||||
|
public static string? Prompt(string title, string prompt, string defaultValue, Window? owner)
|
||||||
|
{
|
||||||
|
var window = new PromptWindow(title, prompt, defaultValue)
|
||||||
|
{
|
||||||
|
Owner = owner
|
||||||
|
};
|
||||||
|
|
||||||
|
return window.ShowDialog() == true ? window.Value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OkButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValueTextBox_OnKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Enter)
|
||||||
|
{
|
||||||
|
DialogResult = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
196
src/ToolboxApp/Views/SettingsWindow.xaml
Normal file
196
src/ToolboxApp/Views/SettingsWindow.xaml
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
<Window x:Class="ToolboxApp.Views.SettingsWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="设置"
|
||||||
|
Width="820"
|
||||||
|
Height="620"
|
||||||
|
MinWidth="740"
|
||||||
|
MinHeight="540"
|
||||||
|
WindowStartupLocation="CenterOwner">
|
||||||
|
<Grid Margin="18">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TabControl>
|
||||||
|
<TabItem Header="常规" ToolTip="管理窗口、托盘和快捷键总开关。">
|
||||||
|
<StackPanel Margin="18">
|
||||||
|
<CheckBox x:Name="StartHiddenCheckBox"
|
||||||
|
Content="启动后默认隐藏到托盘"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ToolTip="开启后应用启动时不显示主窗口,只保留托盘图标。" />
|
||||||
|
<CheckBox x:Name="HideOnCloseCheckBox"
|
||||||
|
Content="关闭窗口时隐藏到托盘"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ToolTip="开启后点击窗口关闭按钮不会退出应用。" />
|
||||||
|
<CheckBox x:Name="ConfirmExitCheckBox"
|
||||||
|
Content="退出前确认"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ToolTip="通过托盘菜单退出时是否显示确认提示。" />
|
||||||
|
<CheckBox x:Name="LogExpandedCheckBox"
|
||||||
|
Content="默认展开底部信息区"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ToolTip="控制主界面底部日志区域的默认展开状态。" />
|
||||||
|
<CheckBox x:Name="GlobalHotkeysCheckBox"
|
||||||
|
Content="启用全局快捷键"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ToolTip="关闭后所有已配置快捷键临时失效,但配置不会删除。" />
|
||||||
|
<CheckBox x:Name="StartupCheckBox"
|
||||||
|
Content="开机自启"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ToolTip="使用当前用户注册表启动项,不需要管理员权限。" />
|
||||||
|
</StackPanel>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem Header="自动运行" ToolTip="动态汇总所有开启启动时自动运行的工具。">
|
||||||
|
<Grid Margin="18">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<DataGrid x:Name="AutoRunDataGrid"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
SelectionMode="Single"
|
||||||
|
ToolTip="自动运行项来自工具自身设置,可调整顺序、启用状态和间隔。">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridCheckBoxColumn Header="启用"
|
||||||
|
Width="70"
|
||||||
|
Binding="{Binding Enabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<DataGridTextColumn Header="顺序"
|
||||||
|
Width="70"
|
||||||
|
Binding="{Binding SortOrder, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<DataGridTextColumn Header="工具"
|
||||||
|
Width="*"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding ToolName}" />
|
||||||
|
<DataGridTextColumn Header="类型"
|
||||||
|
Width="90"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding ToolTypeLabel}" />
|
||||||
|
<DataGridTextColumn Header="间隔(ms)"
|
||||||
|
Width="110"
|
||||||
|
Binding="{Binding IntervalAfterMs, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Button Content="上移"
|
||||||
|
Width="76"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
ToolTip="提高选中自动运行项的执行顺序。"
|
||||||
|
Click="AutoRunMoveUpButton_OnClick" />
|
||||||
|
<Button Content="下移"
|
||||||
|
Width="76"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
ToolTip="降低选中自动运行项的执行顺序。"
|
||||||
|
Click="AutoRunMoveDownButton_OnClick" />
|
||||||
|
<Button Content="移除"
|
||||||
|
Width="76"
|
||||||
|
ToolTip="从自动运行中移除,等价于关闭该工具的自动运行设置。"
|
||||||
|
Click="AutoRunRemoveButton_OnClick" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem Header="快捷键" ToolTip="查看和编辑工具快捷键状态。">
|
||||||
|
<Grid Margin="18">
|
||||||
|
<DataGrid x:Name="HotkeysDataGrid"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
ToolTip="快捷键状态会在主窗口注册后更新;可直接编辑快捷键文本。">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="快捷键"
|
||||||
|
Width="160"
|
||||||
|
Binding="{Binding Hotkey, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<DataGridTextColumn Header="工具"
|
||||||
|
Width="*"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding Name}" />
|
||||||
|
<DataGridTextColumn Header="类型"
|
||||||
|
Width="90"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding Type}" />
|
||||||
|
<DataGridTextColumn Header="状态"
|
||||||
|
Width="130"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding HotkeyStatus}" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem Header="数据" ToolTip="打开、导入、导出、重置和检查本地配置。">
|
||||||
|
<StackPanel Margin="18">
|
||||||
|
<Button Content="打开配置目录"
|
||||||
|
Width="160"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ToolTip="在资源管理器中打开 %AppData%\ToolboxApp。"
|
||||||
|
Click="OpenConfigButton_OnClick" />
|
||||||
|
<Button Content="导出全部配置"
|
||||||
|
Width="160"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ToolTip="导出 appsettings、categories、tools、autorun 和 icons 目录为 zip。"
|
||||||
|
Click="ExportButton_OnClick" />
|
||||||
|
<Button Content="导入配置"
|
||||||
|
Width="160"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ToolTip="从 zip 导入配置,导入后会建议检查自动运行和快捷键。"
|
||||||
|
Click="ImportButton_OnClick" />
|
||||||
|
<Button Content="检查路径失效"
|
||||||
|
Width="160"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ToolTip="检查本地工具路径和工作目录是否仍然存在。"
|
||||||
|
Click="ValidatePathsButton_OnClick" />
|
||||||
|
<Button Content="重置配置"
|
||||||
|
Width="160"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
ToolTip="备份当前配置后恢复默认配置。"
|
||||||
|
Click="ResetButton_OnClick" />
|
||||||
|
<TextBlock x:Name="DataStatusTextBlock"
|
||||||
|
Margin="0,18,0,0"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
ToolTip="显示最近一次配置操作结果。" />
|
||||||
|
</StackPanel>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem Header="系统工具" ToolTip="恢复默认 Windows 系统工具。">
|
||||||
|
<StackPanel Margin="18">
|
||||||
|
<Button Content="恢复默认系统工具"
|
||||||
|
Width="180"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
ToolTip="只补回缺失的默认系统工具,不重复创建已有项。"
|
||||||
|
Click="RestoreSystemToolsButton_OnClick" />
|
||||||
|
<TextBlock x:Name="SystemToolsStatusTextBlock"
|
||||||
|
Margin="0,18,0,0"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
ToolTip="显示默认系统工具恢复结果。" />
|
||||||
|
</StackPanel>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="0,14,0,0">
|
||||||
|
<Button Content="保存"
|
||||||
|
Width="88"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
IsDefault="True"
|
||||||
|
ToolTip="保存设置并关闭窗口。"
|
||||||
|
Click="SaveButton_OnClick" />
|
||||||
|
<Button Content="取消"
|
||||||
|
Width="88"
|
||||||
|
IsCancel="True"
|
||||||
|
ToolTip="关闭设置窗口。已执行的导入、导出和重置操作不会回滚。"
|
||||||
|
Click="CancelButton_OnClick" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
302
src/ToolboxApp/Views/SettingsWindow.xaml.cs
Normal file
302
src/ToolboxApp/Views/SettingsWindow.xaml.cs
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows;
|
||||||
|
using ToolboxApp.Models;
|
||||||
|
using ToolboxApp.Services;
|
||||||
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
||||||
|
using SaveFileDialog = Microsoft.Win32.SaveFileDialog;
|
||||||
|
|
||||||
|
namespace ToolboxApp.Views;
|
||||||
|
|
||||||
|
public partial class SettingsWindow : Window
|
||||||
|
{
|
||||||
|
private readonly ConfigurationService _configurationService;
|
||||||
|
private readonly StartupService _startupService;
|
||||||
|
private readonly PathValidationService _pathValidationService;
|
||||||
|
private readonly ToolLaunchService _toolLaunchService;
|
||||||
|
private readonly ObservableCollection<AutoRunRow> _autoRunRows = [];
|
||||||
|
private ToolboxData _data;
|
||||||
|
|
||||||
|
public SettingsWindow(
|
||||||
|
ToolboxData data,
|
||||||
|
ConfigurationService configurationService,
|
||||||
|
StartupService startupService,
|
||||||
|
PathValidationService pathValidationService,
|
||||||
|
ToolLaunchService toolLaunchService)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_configurationService = configurationService;
|
||||||
|
_startupService = startupService;
|
||||||
|
_pathValidationService = pathValidationService;
|
||||||
|
_toolLaunchService = toolLaunchService;
|
||||||
|
_data = CloneData(data);
|
||||||
|
|
||||||
|
LoadControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToolboxData Data => _data;
|
||||||
|
|
||||||
|
private void LoadControls()
|
||||||
|
{
|
||||||
|
StartHiddenCheckBox.IsChecked = _data.Settings.StartHiddenToTray;
|
||||||
|
HideOnCloseCheckBox.IsChecked = _data.Settings.HideOnClose;
|
||||||
|
ConfirmExitCheckBox.IsChecked = _data.Settings.ConfirmExit;
|
||||||
|
LogExpandedCheckBox.IsChecked = _data.Settings.LogPanelExpanded;
|
||||||
|
GlobalHotkeysCheckBox.IsChecked = _data.Settings.GlobalHotkeysEnabled;
|
||||||
|
StartupCheckBox.IsChecked = _startupService.IsEnabled();
|
||||||
|
|
||||||
|
RebuildAutoRunRows();
|
||||||
|
AutoRunDataGrid.ItemsSource = _autoRunRows;
|
||||||
|
HotkeysDataGrid.ItemsSource = _data.Tools.Where(tool => !tool.IsDeleted && !string.IsNullOrWhiteSpace(tool.Hotkey)).OrderBy(tool => tool.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildAutoRunRows()
|
||||||
|
{
|
||||||
|
_configurationService.MergeAutoRunEntries(_data);
|
||||||
|
_autoRunRows.Clear();
|
||||||
|
foreach (var entry in _data.AutoRunEntries.OrderBy(entry => entry.SortOrder))
|
||||||
|
{
|
||||||
|
var tool = _data.Tools.FirstOrDefault(item => item.Id == entry.ToolId);
|
||||||
|
if (tool is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_autoRunRows.Add(new AutoRunRow(entry, tool));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_data.Settings.StartHiddenToTray = StartHiddenCheckBox.IsChecked == true;
|
||||||
|
_data.Settings.HideOnClose = HideOnCloseCheckBox.IsChecked == true;
|
||||||
|
_data.Settings.ConfirmExit = ConfirmExitCheckBox.IsChecked == true;
|
||||||
|
_data.Settings.LogPanelExpanded = LogExpandedCheckBox.IsChecked == true;
|
||||||
|
_data.Settings.GlobalHotkeysEnabled = GlobalHotkeysCheckBox.IsChecked == true;
|
||||||
|
|
||||||
|
foreach (var row in _autoRunRows)
|
||||||
|
{
|
||||||
|
row.Apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
_data.AutoRunEntries = _autoRunRows.Select(row => row.Entry).OrderBy(row => row.SortOrder).ToList();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_startupService.SetEnabled(StartupCheckBox.IsChecked == true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, $"设置开机自启失败:{ex.Message}", "保存设置", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogResult = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutoRunMoveUpButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
MoveAutoRunRow(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutoRunMoveDownButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
MoveAutoRunRow(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutoRunRemoveButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (AutoRunDataGrid.SelectedItem is not AutoRunRow row)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.Tool.AutoRunEnabled = false;
|
||||||
|
_autoRunRows.Remove(row);
|
||||||
|
ReorderAutoRunRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenConfigButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_configurationService.OpenConfigDirectory();
|
||||||
|
DataStatusTextBlock.Text = "已打开配置目录。";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ExportButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var dialog = new SaveFileDialog
|
||||||
|
{
|
||||||
|
Title = "导出配置",
|
||||||
|
Filter = "Zip 文件 (*.zip)|*.zip",
|
||||||
|
FileName = $"ToolboxApp_Config_{DateTime.Now:yyyyMMdd_HHmmss}.zip"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog(this) != true)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _configurationService.ExportAsync(dialog.FileName, _data);
|
||||||
|
DataStatusTextBlock.Text = $"配置已导出:{dialog.FileName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ImportButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var confirmed = MessageBox.Show(
|
||||||
|
this,
|
||||||
|
"导入配置会先备份当前配置,然后覆盖当前配置。是否继续?",
|
||||||
|
"导入配置",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Warning) == MessageBoxResult.Yes;
|
||||||
|
|
||||||
|
if (!confirmed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "导入配置",
|
||||||
|
Filter = "Zip 文件 (*.zip)|*.zip|所有文件 (*.*)|*.*"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog(this) != true)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_data = await _configurationService.ImportAsync(dialog.FileName);
|
||||||
|
_pathValidationService.ValidateTools(_data.Tools);
|
||||||
|
LoadControls();
|
||||||
|
DataStatusTextBlock.Text = "配置导入完成。建议检查自动运行列表和快捷键,确认其中没有不需要启动的工具。";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidatePathsButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var invalidCount = _pathValidationService.ValidateTools(_data.Tools);
|
||||||
|
DataStatusTextBlock.Text = $"路径检查完成:发现 {invalidCount} 个失效工具。";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ResetButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var confirmed = MessageBox.Show(
|
||||||
|
this,
|
||||||
|
"重置配置会先备份当前配置,然后恢复默认系统工具。是否继续?",
|
||||||
|
"重置配置",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Warning) == MessageBoxResult.Yes;
|
||||||
|
|
||||||
|
if (!confirmed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_data = await _configurationService.ResetAsync();
|
||||||
|
LoadControls();
|
||||||
|
DataStatusTextBlock.Text = "配置已重置。";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreSystemToolsButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var added = SystemToolService.RestoreDefaultSystemTools(_data.Categories, _data.Tools);
|
||||||
|
SystemToolsStatusTextBlock.Text = added == 0
|
||||||
|
? "默认系统工具已经完整,无需补回。"
|
||||||
|
: $"已补回 {added} 个默认系统工具。";
|
||||||
|
RebuildAutoRunRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveAutoRunRow(int offset)
|
||||||
|
{
|
||||||
|
if (AutoRunDataGrid.SelectedItem is not AutoRunRow selected)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = _autoRunRows.IndexOf(selected);
|
||||||
|
var targetIndex = index + offset;
|
||||||
|
if (targetIndex < 0 || targetIndex >= _autoRunRows.Count)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_autoRunRows.Move(index, targetIndex);
|
||||||
|
AutoRunDataGrid.SelectedItem = selected;
|
||||||
|
ReorderAutoRunRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReorderAutoRunRows()
|
||||||
|
{
|
||||||
|
for (var index = 0; index < _autoRunRows.Count; index++)
|
||||||
|
{
|
||||||
|
_autoRunRows[index].SortOrder = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ToolboxData CloneData(ToolboxData source)
|
||||||
|
{
|
||||||
|
return new ToolboxData
|
||||||
|
{
|
||||||
|
Settings = new AppSettings
|
||||||
|
{
|
||||||
|
DataVersion = source.Settings.DataVersion,
|
||||||
|
StartHiddenToTray = source.Settings.StartHiddenToTray,
|
||||||
|
HideOnClose = source.Settings.HideOnClose,
|
||||||
|
ConfirmExit = source.Settings.ConfirmExit,
|
||||||
|
GlobalHotkeysEnabled = source.Settings.GlobalHotkeysEnabled,
|
||||||
|
LogPanelExpanded = source.Settings.LogPanelExpanded,
|
||||||
|
Theme = source.Settings.Theme,
|
||||||
|
CardSize = source.Settings.CardSize,
|
||||||
|
MainWindowWidth = source.Settings.MainWindowWidth,
|
||||||
|
MainWindowHeight = source.Settings.MainWindowHeight
|
||||||
|
},
|
||||||
|
Categories = source.Categories.Select(category => category.Clone()).ToList(),
|
||||||
|
Tools = source.Tools.Select(tool => tool.Clone()).ToList(),
|
||||||
|
AutoRunEntries = source.AutoRunEntries.Select(entry => new AutoRunEntry
|
||||||
|
{
|
||||||
|
ToolId = entry.ToolId,
|
||||||
|
Enabled = entry.Enabled,
|
||||||
|
SortOrder = entry.SortOrder,
|
||||||
|
IntervalAfterMs = entry.IntervalAfterMs
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AutoRunRow
|
||||||
|
{
|
||||||
|
public AutoRunRow(AutoRunEntry entry, ToolItem tool)
|
||||||
|
{
|
||||||
|
Entry = entry;
|
||||||
|
Tool = tool;
|
||||||
|
Enabled = entry.Enabled;
|
||||||
|
SortOrder = entry.SortOrder;
|
||||||
|
IntervalAfterMs = entry.IntervalAfterMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutoRunEntry Entry { get; }
|
||||||
|
public ToolItem Tool { get; }
|
||||||
|
public string ToolName => Tool.Name;
|
||||||
|
public string ToolTypeLabel => Tool.Type switch
|
||||||
|
{
|
||||||
|
ToolType.System => "系统",
|
||||||
|
ToolType.Local => "本地",
|
||||||
|
ToolType.Url => "网址",
|
||||||
|
ToolType.Combination => "组合",
|
||||||
|
_ => "工具"
|
||||||
|
};
|
||||||
|
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
public int IntervalAfterMs { get; set; }
|
||||||
|
|
||||||
|
public void Apply()
|
||||||
|
{
|
||||||
|
Entry.Enabled = Enabled;
|
||||||
|
Entry.SortOrder = SortOrder;
|
||||||
|
Entry.IntervalAfterMs = Math.Max(0, IntervalAfterMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
154
src/ToolboxApp/Views/ToolEditorWindow.xaml
Normal file
154
src/ToolboxApp/Views/ToolEditorWindow.xaml
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
<Window x:Class="ToolboxApp.Views.ToolEditorWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="编辑工具"
|
||||||
|
Width="560"
|
||||||
|
Height="560"
|
||||||
|
MinWidth="520"
|
||||||
|
WindowStartupLocation="CenterOwner">
|
||||||
|
<Grid Margin="18">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="112" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<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="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="类型" VerticalAlignment="Center" />
|
||||||
|
<TextBlock x:Name="TypeTextBlock"
|
||||||
|
Grid.Column="1"
|
||||||
|
MinHeight="32"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ToolTip="工具类型由创建入口决定,用户侧只暴露系统、本地、网址和组合。" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Text="名称" VerticalAlignment="Center" />
|
||||||
|
<TextBox x:Name="NameTextBox"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
MinHeight="32"
|
||||||
|
ToolTip="卡片显示名称,建议简短清晰。" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2" Text="分类" VerticalAlignment="Center" />
|
||||||
|
<ComboBox x:Name="CategoryComboBox"
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
MinHeight="32"
|
||||||
|
DisplayMemberPath="Name"
|
||||||
|
SelectedValuePath="Id"
|
||||||
|
ToolTip="工具只能属于一个一级分类。" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="3" Text="说明" VerticalAlignment="Top" Margin="0,14,0,0" />
|
||||||
|
<TextBox x:Name="DescriptionTextBox"
|
||||||
|
Grid.Row="3"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
MinHeight="72"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
ToolTip="用于描述这个工具的用途,会显示在卡片上。" />
|
||||||
|
|
||||||
|
<TextBlock x:Name="TargetLabel"
|
||||||
|
Grid.Row="4"
|
||||||
|
Text="目标路径"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<Grid x:Name="TargetPanel"
|
||||||
|
Grid.Row="4"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,8,0,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox x:Name="TargetTextBox"
|
||||||
|
MinHeight="32"
|
||||||
|
ToolTip="本地工具路径、系统工具启动目标或网址。" />
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
Content="文件"
|
||||||
|
Width="64"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
ToolTip="选择本地程序、快捷方式、脚本或普通文件。"
|
||||||
|
Click="BrowseFileButton_OnClick" />
|
||||||
|
<Button Grid.Column="2"
|
||||||
|
Content="文件夹"
|
||||||
|
Width="72"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
ToolTip="选择一个文件夹作为本地工具。"
|
||||||
|
Click="BrowseFolderButton_OnClick" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="5" Text="启动参数" VerticalAlignment="Center" />
|
||||||
|
<TextBox x:Name="ArgumentsTextBox"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
MinHeight="32"
|
||||||
|
ToolTip="传给可执行文件或脚本的参数,普通文件和文件夹通常不需要。" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="6" Text="工作目录" VerticalAlignment="Center" />
|
||||||
|
<TextBox x:Name="WorkingDirectoryTextBox"
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
MinHeight="32"
|
||||||
|
ToolTip="可执行文件或脚本启动时使用的工作目录,可留空。" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="7" Text="快捷键" VerticalAlignment="Center" />
|
||||||
|
<TextBox x:Name="HotkeyTextBox"
|
||||||
|
Grid.Row="7"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
MinHeight="32"
|
||||||
|
ToolTip="格式示例:Ctrl + Alt + T。第一版要求至少包含一个修饰键。" />
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="8"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,12,0,0">
|
||||||
|
<CheckBox x:Name="AutoRunCheckBox"
|
||||||
|
Content="工具箱启动时自动运行"
|
||||||
|
ToolTip="开启后会出现在设置页的自动运行列表中。" />
|
||||||
|
<CheckBox x:Name="RunAsAdminCheckBox"
|
||||||
|
Content="以管理员身份运行"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
ToolTip="仅启动该工具时触发 UAC,工具箱自身不提权。" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="0,14,0,0">
|
||||||
|
<Button Content="保存"
|
||||||
|
Width="88"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
IsDefault="True"
|
||||||
|
ToolTip="校验并保存工具设置。"
|
||||||
|
Click="SaveButton_OnClick" />
|
||||||
|
<Button Content="取消"
|
||||||
|
Width="88"
|
||||||
|
IsCancel="True"
|
||||||
|
ToolTip="放弃本次修改。"
|
||||||
|
Click="CancelButton_OnClick" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
157
src/ToolboxApp/Views/ToolEditorWindow.xaml.cs
Normal file
157
src/ToolboxApp/Views/ToolEditorWindow.xaml.cs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using Forms = System.Windows.Forms;
|
||||||
|
using ToolboxApp.Models;
|
||||||
|
using ToolboxApp.Services;
|
||||||
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
||||||
|
|
||||||
|
namespace ToolboxApp.Views;
|
||||||
|
|
||||||
|
public partial class ToolEditorWindow : Window
|
||||||
|
{
|
||||||
|
private readonly ToolItem _tool;
|
||||||
|
|
||||||
|
private ToolEditorWindow(ToolItem tool, IEnumerable<CategoryItem> categories)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_tool = tool;
|
||||||
|
|
||||||
|
CategoryComboBox.ItemsSource = categories.ToList();
|
||||||
|
CategoryComboBox.SelectedValue = tool.CategoryId;
|
||||||
|
|
||||||
|
Title = tool.Type switch
|
||||||
|
{
|
||||||
|
ToolType.System => "编辑系统工具",
|
||||||
|
ToolType.Local => "编辑本地工具",
|
||||||
|
ToolType.Url => "编辑网址工具",
|
||||||
|
_ => "编辑工具"
|
||||||
|
};
|
||||||
|
|
||||||
|
TypeTextBlock.Text = tool.Type switch
|
||||||
|
{
|
||||||
|
ToolType.System => "系统工具",
|
||||||
|
ToolType.Local => "本地工具",
|
||||||
|
ToolType.Url => "网址",
|
||||||
|
_ => "工具"
|
||||||
|
};
|
||||||
|
|
||||||
|
NameTextBox.Text = tool.Name;
|
||||||
|
DescriptionTextBox.Text = tool.Description;
|
||||||
|
TargetTextBox.Text = tool.Type == ToolType.Url ? tool.Url : tool.LaunchTarget;
|
||||||
|
ArgumentsTextBox.Text = tool.Arguments;
|
||||||
|
WorkingDirectoryTextBox.Text = tool.WorkingDirectory;
|
||||||
|
HotkeyTextBox.Text = tool.Hotkey;
|
||||||
|
AutoRunCheckBox.IsChecked = tool.AutoRunEnabled;
|
||||||
|
RunAsAdminCheckBox.IsChecked = tool.RunAsAdmin;
|
||||||
|
|
||||||
|
if (tool.Type == ToolType.Url)
|
||||||
|
{
|
||||||
|
TargetLabel.Text = "网址";
|
||||||
|
ArgumentsTextBox.IsEnabled = false;
|
||||||
|
WorkingDirectoryTextBox.IsEnabled = false;
|
||||||
|
RunAsAdminCheckBox.IsEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tool.Type == ToolType.System)
|
||||||
|
{
|
||||||
|
TargetLabel.Text = "启动目标";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToolItem EditedTool => _tool;
|
||||||
|
|
||||||
|
public static ToolItem? Edit(ToolItem tool, IEnumerable<CategoryItem> categories, Window? owner)
|
||||||
|
{
|
||||||
|
var window = new ToolEditorWindow(tool, categories)
|
||||||
|
{
|
||||||
|
Owner = owner
|
||||||
|
};
|
||||||
|
|
||||||
|
return window.ShowDialog() == true ? window.EditedTool : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BrowseFileButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "选择本地工具",
|
||||||
|
CheckFileExists = false,
|
||||||
|
Filter = "所有文件 (*.*)|*.*"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog(this) == true)
|
||||||
|
{
|
||||||
|
TargetTextBox.Text = dialog.FileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BrowseFolderButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
using var dialog = new Forms.FolderBrowserDialog
|
||||||
|
{
|
||||||
|
Description = "选择文件夹作为本地工具",
|
||||||
|
UseDescriptionForTitle = true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() == Forms.DialogResult.OK)
|
||||||
|
{
|
||||||
|
TargetTextBox.Text = dialog.SelectedPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var name = NameTextBox.Text.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "请输入工具名称。", "保存工具", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CategoryComboBox.SelectedValue is not string categoryId || string.IsNullOrWhiteSpace(categoryId))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "请选择分类。", "保存工具", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hotkey = HotkeyTextBox.Text.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(hotkey) && !HotkeyParser.TryParse(hotkey, out _, out _))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "快捷键格式无效,请使用类似 Ctrl + Alt + T 的格式。", "保存工具", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_tool.Type == ToolType.Url)
|
||||||
|
{
|
||||||
|
if (!PathValidationService.IsValidUrl(TargetTextBox.Text, out var normalizedUrl))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "请输入有效的网址。", "保存工具", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tool.Url = normalizedUrl;
|
||||||
|
_tool.LaunchTarget = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_tool.LaunchTarget = TargetTextBox.Text.Trim();
|
||||||
|
_tool.Url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tool.Name = name;
|
||||||
|
_tool.Description = DescriptionTextBox.Text.Trim();
|
||||||
|
_tool.CategoryId = categoryId;
|
||||||
|
_tool.Arguments = ArgumentsTextBox.Text.Trim();
|
||||||
|
_tool.WorkingDirectory = WorkingDirectoryTextBox.Text.Trim();
|
||||||
|
_tool.Hotkey = string.IsNullOrWhiteSpace(hotkey) ? null : HotkeyParser.Normalize(hotkey);
|
||||||
|
_tool.AutoRunEnabled = AutoRunCheckBox.IsChecked == true;
|
||||||
|
_tool.RunAsAdmin = RunAsAdminCheckBox.IsChecked == true;
|
||||||
|
|
||||||
|
DialogResult = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user