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; /// 快捷指令列表:增删、排序、编辑器(名称/类型/目标/参数/图标/颜色/测试运行)。 public sealed partial class CommandsPage : Page { public CommandsPage() { InitializeComponent(); Loaded += (_, _) => RefreshList(); } private static List 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); } /// 打开指令编辑器;返回 true 表示用户保存。 private async Task 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; } }