初始提交:WinUI 3 一键运行轮盘工具
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user