初始提交:WinUI 3 一键运行轮盘工具

This commit is contained in:
2026-08-30 01:37:56 +08:00
commit 83011e5d29
58 changed files with 4373 additions and 0 deletions
@@ -0,0 +1,341 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using OneClickRun.App.Services;
using OneClickRun.Core.Data;
using OneClickRun.Core.Models;
using OneClickRun.Core.Services;
using Windows.Storage.Pickers;
namespace OneClickRun.App.Pages;
/// <summary>快捷指令列表:增删、排序、编辑器(名称/类型/目标/参数/图标/颜色/测试运行)。</summary>
public sealed partial class CommandsPage : Page
{
public CommandsPage()
{
InitializeComponent();
Loaded += (_, _) => RefreshList();
}
private static List<CommandItem> Items => App.Instance.Settings.Commands;
private void RefreshList()
{
var items = Items.ToList();
CommandList.ItemsSource = items;
CountText.Text = "共 " + items.Count + " 个指令(轮盘扇区数量与指令数量一致,上限 " + AppSettings.MaxCommands + " 个)";
}
private int SelectedIndex => CommandList.SelectedIndex;
private async void Add_Click(object sender, RoutedEventArgs e)
{
if (Items.Count >= AppSettings.MaxCommands)
{
App.Instance.ReportError("最多支持 " + AppSettings.MaxCommands + " 个指令,请先删除部分指令。");
return;
}
var item = new CommandItem { IconGlyph = GlyphCatalog.DefaultGlyphFor(ActionType.App) };
if (await EditItemAsync(item, isNew: true))
{
Items.Add(item);
App.Instance.SaveSettings();
App.Instance.ApplySettingsToSystem();
RefreshList();
CommandList.SelectedIndex = Items.Count - 1;
}
}
private async void Edit_Click(object sender, RoutedEventArgs e)
{
if (SelectedIndex < 0)
{
App.Instance.ReportError("请先在列表中选择一个指令。");
return;
}
var original = Items[SelectedIndex];
var copy = new CommandItem
{
Id = original.Id,
Name = original.Name,
ActionType = original.ActionType,
Target = original.Target,
Arguments = original.Arguments,
IconGlyph = original.IconGlyph,
ColorIndex = original.ColorIndex,
RunAsAdmin = original.RunAsAdmin,
HideWindow = original.HideWindow,
};
if (await EditItemAsync(copy, isNew: false))
{
Items[SelectedIndex] = copy;
App.Instance.SaveSettings();
App.Instance.ApplySettingsToSystem();
RefreshList();
}
}
private async void Delete_Click(object sender, RoutedEventArgs e)
{
if (SelectedIndex < 0)
{
App.Instance.ReportError("请先在列表中选择一个指令。");
return;
}
var item = Items[SelectedIndex];
var dialog = new ContentDialog
{
Title = "删除指令",
Content = "确定删除指令「" + item.Name + "」吗?",
PrimaryButtonText = "删除",
CloseButtonText = "取消",
DefaultButton = ContentDialogButton.Close,
XamlRoot = XamlRoot,
};
if (await dialog.ShowAsync() == ContentDialogResult.Primary)
{
Items.RemoveAt(SelectedIndex);
App.Instance.SaveSettings();
App.Instance.ApplySettingsToSystem();
RefreshList();
}
}
private void MoveUp_Click(object sender, RoutedEventArgs e)
{
int i = SelectedIndex;
if (i <= 0) return;
(Items[i], Items[i - 1]) = (Items[i - 1], Items[i]);
App.Instance.SaveSettings();
App.Instance.ApplySettingsToSystem();
RefreshList();
CommandList.SelectedIndex = i - 1;
}
private void MoveDown_Click(object sender, RoutedEventArgs e)
{
int i = SelectedIndex;
if (i < 0 || i >= Items.Count - 1) return;
(Items[i], Items[i + 1]) = (Items[i + 1], Items[i]);
App.Instance.SaveSettings();
App.Instance.ApplySettingsToSystem();
RefreshList();
CommandList.SelectedIndex = i + 1;
}
private void CommandList_DoubleTapped(object sender, Microsoft.UI.Xaml.Input.DoubleTappedRoutedEventArgs e)
{
if (SelectedIndex >= 0) Edit_Click(sender, e);
}
/// <summary>打开指令编辑器;返回 true 表示用户保存。</summary>
private async Task<bool> EditItemAsync(CommandItem item, bool isNew)
{
var dialog = new ContentDialog
{
Title = isNew ? "新增指令" : "编辑指令",
PrimaryButtonText = "保存",
CloseButtonText = "取消",
DefaultButton = ContentDialogButton.Primary,
XamlRoot = XamlRoot,
};
var nameBox = new TextBox { Header = "名称", Text = item.Name, PlaceholderText = "例如:记事本" };
var typeBox = new ComboBox { Header = "类型", HorizontalAlignment = HorizontalAlignment.Stretch };
typeBox.Items.Add(new ComboBoxItem { Content = "打开软件" });
typeBox.Items.Add(new ComboBoxItem { Content = "打开文件夹" });
typeBox.Items.Add(new ComboBoxItem { Content = "打开网址" });
typeBox.Items.Add(new ComboBoxItem { Content = "运行 PowerShell 脚本" });
typeBox.SelectedIndex = (int)item.ActionType;
var targetBox = new TextBox { Header = "目标", Text = item.Target };
var browseButton = new Button { Content = "浏览…", Margin = new Thickness(8, 26, 0, 0) };
var targetGrid = new Grid();
targetGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
targetGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
Grid.SetColumn(targetBox, 0);
Grid.SetColumn(browseButton, 1);
targetGrid.Children.Add(targetBox);
targetGrid.Children.Add(browseButton);
var argsBox = new TextBox { Header = "参数(可选)", Text = item.Arguments ?? "" };
var adminBox = new CheckBox { Content = "以管理员身份运行", IsChecked = item.RunAsAdmin };
var hideBox = new CheckBox { Content = "隐藏窗口运行", IsChecked = item.HideWindow };
bool glyphAuto = true;
var glyphBox = new ComboBox { Header = "图标", HorizontalAlignment = HorizontalAlignment.Stretch, MinWidth = 200 };
foreach (var option in GlyphCatalog.All)
{
var icon = new FontIcon { Glyph = option.Glyph, FontSize = 16 };
var text = new TextBlock { Text = option.Name, Margin = new Thickness(8, 0, 0, 0) };
var optionPanel = new StackPanel { Orientation = Orientation.Horizontal };
optionPanel.Children.Add(icon);
optionPanel.Children.Add(text);
glyphBox.Items.Add(new ComboBoxItem { Content = optionPanel, Tag = option.Glyph });
}
int glyphIndex = GlyphCatalog.IndexOf(item.IconGlyph);
if (glyphIndex < 0)
{
glyphBox.Items.Add(new ComboBoxItem { Content = new TextBlock { Text = "自定义" }, Tag = item.IconGlyph });
glyphIndex = glyphBox.Items.Count - 1;
}
glyphBox.SelectedIndex = glyphIndex;
glyphBox.SelectionChanged += (_, _) => glyphAuto = false;
var colorBox = new ComboBox { Header = "颜色", HorizontalAlignment = HorizontalAlignment.Stretch, MinWidth = 200 };
for (int i = 0; i < WheelPalette.Colors.Length; i++)
{
var swatch = new Border
{
Background = new SolidColorBrush(ColorHelper.FromHex(WheelPalette.Colors[i])),
Width = 64,
Height = 14,
CornerRadius = new CornerRadius(3),
};
var swatchPanel = new StackPanel { Orientation = Orientation.Horizontal };
swatchPanel.Children.Add(swatch);
swatchPanel.Children.Add(new TextBlock { Text = "颜色 " + (i + 1), Margin = new Thickness(8, 0, 0, 0) });
colorBox.Items.Add(new ComboBoxItem { Content = swatchPanel, Tag = i });
}
colorBox.SelectedIndex = Math.Clamp(item.ColorIndex, 0, WheelPalette.Colors.Length - 1);
var warnText = new TextBlock
{
Foreground = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 230, 72, 72)),
TextWrapping = TextWrapping.Wrap,
Visibility = Visibility.Collapsed,
};
var testButton = new Button { Content = "测试运行", HorizontalAlignment = HorizontalAlignment.Left };
var panel = new StackPanel { Spacing = 12, MinWidth = 440 };
panel.Children.Add(nameBox);
panel.Children.Add(typeBox);
panel.Children.Add(targetGrid);
panel.Children.Add(argsBox);
panel.Children.Add(adminBox);
panel.Children.Add(hideBox);
panel.Children.Add(glyphBox);
panel.Children.Add(colorBox);
panel.Children.Add(warnText);
panel.Children.Add(testButton);
dialog.Content = panel;
void UpdateVisibility()
{
var type = (ActionType)typeBox.SelectedIndex;
argsBox.Visibility = type == ActionType.App ? Visibility.Visible : Visibility.Collapsed;
adminBox.Visibility = type is ActionType.App or ActionType.Script ? Visibility.Visible : Visibility.Collapsed;
hideBox.Visibility = type == ActionType.Script ? Visibility.Visible : Visibility.Collapsed;
browseButton.Visibility = type == ActionType.Url ? Visibility.Collapsed : Visibility.Visible;
targetBox.Header = type switch
{
ActionType.App => "程序路径",
ActionType.Folder => "文件夹路径",
ActionType.Url => "网址(无协议时自动补 https://",
ActionType.Script => "脚本文件(.ps1",
_ => "目标",
};
if (glyphAuto)
{
var glyph = GlyphCatalog.DefaultGlyphFor(type);
int idx = GlyphCatalog.IndexOf(glyph);
if (idx >= 0) glyphBox.SelectedIndex = idx;
}
}
typeBox.SelectionChanged += (_, _) => UpdateVisibility();
UpdateVisibility();
browseButton.Click += async (_, _) =>
{
var type = (ActionType)typeBox.SelectedIndex;
if (type == ActionType.Folder)
{
var picker = new FolderPicker();
WinRT.Interop.InitializeWithWindow.Initialize(picker, WinRT.Interop.WindowNative.GetWindowHandle(App.Instance.Main));
var folder = await picker.PickSingleFolderAsync();
if (folder != null) targetBox.Text = folder.Path;
}
else if (type is ActionType.App or ActionType.Script)
{
var picker = new FileOpenPicker();
WinRT.Interop.InitializeWithWindow.Initialize(picker, WinRT.Interop.WindowNative.GetWindowHandle(App.Instance.Main));
if (type == ActionType.Script)
{
picker.FileTypeFilter.Add(".ps1");
picker.FileTypeFilter.Add(".cmd");
picker.FileTypeFilter.Add(".bat");
}
else
{
picker.FileTypeFilter.Add(".exe");
picker.FileTypeFilter.Add(".lnk");
picker.FileTypeFilter.Add("*");
}
var file = await picker.PickSingleFileAsync();
if (file != null) targetBox.Text = file.Path;
}
};
CommandItem BuildFromInputs()
{
return new CommandItem
{
Name = nameBox.Text.Trim(),
ActionType = (ActionType)typeBox.SelectedIndex,
Target = targetBox.Text.Trim(),
Arguments = argsBox.Text.Trim(),
IconGlyph = (glyphBox.SelectedItem as ComboBoxItem)?.Tag as string ?? "\uE756",
ColorIndex = (colorBox.SelectedItem as ComboBoxItem)?.Tag is int c ? c : 0,
RunAsAdmin = adminBox.IsChecked == true,
HideWindow = hideBox.IsChecked == true,
};
}
testButton.Click += (_, _) =>
{
var probe = BuildFromInputs();
var error = CommandRunner.Validate(probe);
if (error != null)
{
warnText.Text = error;
warnText.Visibility = Visibility.Visible;
return;
}
try
{
CommandRunner.Run(probe);
warnText.Text = "已启动,请检查目标是否按预期打开。";
warnText.Visibility = Visibility.Visible;
}
catch (Exception ex)
{
warnText.Text = "启动失败:" + ex.Message;
warnText.Visibility = Visibility.Visible;
}
};
dialog.PrimaryButtonClick += (_, args) =>
{
var probe = BuildFromInputs();
var error = CommandRunner.Validate(probe);
if (error != null)
{
warnText.Text = error;
warnText.Visibility = Visibility.Visible;
args.Cancel = true;
return;
}
item.Name = probe.Name;
item.ActionType = probe.ActionType;
item.Target = probe.Target;
item.Arguments = probe.Arguments;
item.IconGlyph = probe.IconGlyph;
item.ColorIndex = probe.ColorIndex;
item.RunAsAdmin = probe.RunAsAdmin;
item.HideWindow = probe.HideWindow;
};
return await dialog.ShowAsync() == ContentDialogResult.Primary;
}
}