From bef259d9aa233f08559e4ae78f7316d2c3171a15 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 18:47:46 +0800 Subject: [PATCH 01/14] refactor(msgbox): add customizable DialogControl + Dialog API Adds DialogControl (chrome with ContentPresenter), Dialog static API, DialogContext/DialogTheme/DialogButton types. MyMsgBoxTick now routes FrameworkElement content to DialogControl. MsgBoxWrapper delegates to Dialog, old types marked [Obsolete]. --- PCL.Core/UI/Dialog.cs | 76 ++++++++ PCL.Core/UI/MsgBoxWrapper.cs | 21 +- .../Controls/Dialog/DialogControl.cs | 181 ++++++++++++++++++ .../Controls/Dialog/DialogControl.xaml | 41 ++++ Plain Craft Launcher 2/FormMain.xaml.cs | 2 + Plain Craft Launcher 2/Modules/ModMain.cs | 90 ++++++++- 6 files changed, 402 insertions(+), 9 deletions(-) create mode 100644 PCL.Core/UI/Dialog.cs create mode 100644 Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs create mode 100644 Plain Craft Launcher 2/Controls/Dialog/DialogControl.xaml diff --git a/PCL.Core/UI/Dialog.cs b/PCL.Core/UI/Dialog.cs new file mode 100644 index 000000000..3f68bb3c6 --- /dev/null +++ b/PCL.Core/UI/Dialog.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using PCL.Core.App.Localization; + +namespace PCL.Core.UI; + +public class DialogButton +{ + public string Text { get; set; } + public Action? OnClick { get; set; } + public bool IsPrimary { get; set; } + + public DialogButton(string text, Action? onClick = null, bool isPrimary = false) + { + Text = text; + OnClick = onClick; + IsPrimary = isPrimary; + } +} + +public static class Dialog +{ + public static event Action? OnShow; + + public static int Show(string caption, string? title = null, + DialogTheme theme = DialogTheme.Info, bool block = true, + params string[] buttons) + { + return Show(new DialogContext + { + Caption = caption, + Title = title ?? Lang.Text("Common.Dialog.Title"), + Theme = theme, + Block = block, + Content = null, + Buttons = BuildButtons(buttons, theme), + }); + } + + public static int Show(DialogContext context) + { + if (context.Buttons.Count == 0) + context.Buttons.Add(new DialogButton(Lang.Text("Common.Action.Confirm"))); + OnShow?.Invoke(context); + return context.Result; + } + + private static Collection BuildButtons(string[] buttonTexts, DialogTheme theme) + { + var list = new Collection(); + for (var i = 0; i < buttonTexts.Length; i++) + { + list.Add(new DialogButton(buttonTexts[i], isPrimary: i == 0)); + } + return list; + } +} + +public enum DialogTheme +{ + Info, + Warning, + Error +} + +public class DialogContext +{ + public string Caption { get; set; } = ""; + public string Title { get; set; } = ""; + public DialogTheme Theme { get; set; } = DialogTheme.Info; + public bool Block { get; set; } = true; + public object? Content { get; set; } + public Collection Buttons { get; set; } = []; + public int Result { get; set; } +} diff --git a/PCL.Core/UI/MsgBoxWrapper.cs b/PCL.Core/UI/MsgBoxWrapper.cs index b22174ad7..5c843d14e 100644 --- a/PCL.Core/UI/MsgBoxWrapper.cs +++ b/PCL.Core/UI/MsgBoxWrapper.cs @@ -1,16 +1,19 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using PCL.Core.App.Localization; namespace PCL.Core.UI; +[Obsolete("Use PCL.Core.UI.Dialog instead")] public record MsgBoxButtonInfo( string Context, int Value = 0, Action? OnClick = null ); +[Obsolete("Use PCL.Core.UI.DialogTheme instead")] public enum MsgBoxTheme { Info, @@ -27,6 +30,7 @@ public delegate void MsgBoxHandler( ref int result ); +[Obsolete("Use PCL.Core.UI.Dialog instead")] public static class MsgBoxWrapper { public static event MsgBoxHandler? OnShow; @@ -38,9 +42,20 @@ public static int ShowWithCustomButtons( bool block, ICollection buttonCollection) { - var result = 0; - if (buttonCollection.Count == 0) buttonCollection = [new MsgBoxButtonInfo(Lang.Text("Common.Action.Confirm"))]; - OnShow?.Invoke(message, caption, buttonCollection, theme, block, ref result); + var buttons = new Collection(); + foreach (var btn in buttonCollection) + { + buttons.Add(new DialogButton(btn.Context, btn.OnClick)); + } + var result = Dialog.Show(new DialogContext + { + Caption = message, + Title = caption, + Theme = (DialogTheme)(int)theme, + Block = block, + Content = null, + Buttons = buttons, + }); return result; } diff --git a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs new file mode 100644 index 000000000..22a40fd09 --- /dev/null +++ b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs @@ -0,0 +1,181 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Threading; +using PCL.Core.UI.Controls; + +namespace PCL; + +public partial class DialogControl +{ + private int _result; + private bool _exited; + private readonly int _uuid = ModBase.GetUuid(); + private readonly List _buttons = []; + + public DispatcherFrame WaitFrame { get; } = new(true); + + public string Title + { + get => LabTitle.Text; + set => LabTitle.Text = value; + } + + public bool IsWarn { get; set; } + + public UIElement? DialogContent + { + get => (UIElement?)ContentArea.Content; + set => ContentArea.Content = value; + } + + public int Result => _result; + + public DialogControl() + { + try + { + InitializeComponent(); + ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d); + } + catch (Exception ex) + { + ModBase.Log(ex, "DialogControl 初始化失败", ModBase.LogLevel.Hint); + } + + Loaded += OnLoad; + } + + public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = false) + { + var btn = new MyButton + { + Text = text, + ColorType = isPrimary + ? (IsWarn ? MyButton.ColorState.Red : MyButton.ColorState.Highlight) + : MyButton.ColorState.Normal, + Visibility = string.IsNullOrEmpty(text) ? Visibility.Collapsed : Visibility.Visible, + IsEnabled = true, + }; + btn.ApplyTemplate(); + btn.TextPadding = new Thickness(7); + btn.Padding = new Thickness(5, 0, 5, 0); + btn.Margin = new Thickness(12, 0, 0, 0); + btn.Name += ModBase.GetUuid(); + var index = _buttons.Count + 1; + btn.Click += (_, _) => + { + if (_exited) return; + if (onClick is not null) + { + onClick(); + } + else + { + _exited = true; + _result = index; + Close(); + } + }; + _buttons.Add(btn); + PanBtn.Children.Add(btn); + return btn; + } + + public event Action? OnClosed; + + private void OnLoad(object sender, RoutedEventArgs e) + { + try + { + if (_buttons.Count > 1 && _buttons[0].ColorType != MyButton.ColorState.Red) + _buttons[0].ColorType = MyButton.ColorState.Highlight; + if (_buttons.Count > 0) + _buttons[0].Focus(); + + Opacity = 0d; + ModAnimation.AniStart( + ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, + (IsWarn + ? new ModBase.MyColor(140d, 80d, 0d, 0d) + : new ModBase.MyColor(90d, 0d, 0d, 0d)) - ModMain.frmMain.PanMsgBackground.Background, 200), + "PanMsgBackground Background"); + ModAnimation.AniStart( + new ModAnimation.AniData[] + { + ModAnimation.AaOpacity(this, 1d, 120, 60), + ModAnimation.AaDouble(i => TransformPos.Y += (double)i, + -TransformPos.Y, 300, 60, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)), + ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, + -TransformRotate.Angle, 300, 60, + new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)) + }, "DialogControl " + _uuid); + + ModBase.Log("[Dialog] " + LabTitle.Text); + } + catch (Exception ex) + { + ModBase.Log(ex, "DialogControl 加载失败", ModBase.LogLevel.Hint); + } + } + + public void Close() + { + if (_exited && _result == 0) + _result = -1; + _exited = true; + + try + { + WaitFrame.Continue = false; + } + catch + { + // ignore + } + + try + { + ComponentDispatcher.PopModal(); + } + catch + { + // ignore + } + + OnClosed?.Invoke(_result); + + ModAnimation.AniStart( + new ModAnimation.AniData[] + { + ModAnimation.AaCode(() => + { + if (!ModMain.WaitingMyMsgBox.Any()) + ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, + BlurBorder.BackgroundProperty, + new ModBase.MyColor(0d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200, + ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak))); + }, 30), + ModAnimation.AaOpacity(this, -Opacity, 80, 20), + ModAnimation.AaDouble(i => TransformPos.Y += (double)i, 20d - TransformPos.Y, + 150, 0, new ModAnimation.AniEaseOutFluent()), + ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, + 6d - TransformRotate.Angle, 150, 0, new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)), + ModAnimation.AaCode(() => ((Grid)Parent)?.Children.Remove(this), after: true) + }, "DialogControl " + _uuid); + } + + private void Drag(object sender, MouseButtonEventArgs e) + { + try + { + if (e.LeftButton == MouseButtonState.Pressed && e.GetPosition(ShapeLine).Y <= 2d) + ModMain.frmMain.DragMove(); + } + catch (Exception ex) + { + ModBase.Log(ex, "拖拽移动失败", ModBase.LogLevel.Hint); + } + } +} diff --git a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.xaml b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.xaml new file mode 100644 index 000000000..6f861cd8c --- /dev/null +++ b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.xaml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/FormMain.xaml.cs b/Plain Craft Launcher 2/FormMain.xaml.cs index 481c2395f..83187d57f 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.cs +++ b/Plain Craft Launcher 2/FormMain.xaml.cs @@ -107,6 +107,8 @@ public FormMain() AddHandler(DragDrop.DragOverEvent, new DragEventHandler(HandleDrag), true); // 注册 MsgBox 事件 MsgBoxWrapper.OnShow += ModMain.MsgBoxWrapper_OnShow; + // 注册 Dialog 事件 + PCL.Core.UI.Dialog.OnShow += ModMain.Dialog_OnShow; // 注册 Hint 事件 HintWrapper.OnShow += ModMain.HintWrapper_OnShow; // 加载 UI diff --git a/Plain Craft Launcher 2/Modules/ModMain.cs b/Plain Craft Launcher 2/Modules/ModMain.cs index 1e8d28c66..d2392c05f 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.cs +++ b/Plain Craft Launcher 2/Modules/ModMain.cs @@ -109,6 +109,8 @@ private static ModBase.SafeList HintWaiting /// public static List WaitingMyMsgBox { get; } = []; + + private static void TimerMain() { try @@ -847,31 +849,49 @@ public static void MyMsgBoxTick() { // 没有弹窗,显示一个等待的弹窗 frmMain.PanMsgBackground.Visibility = Visibility.Visible; - switch (WaitingMyMsgBox[0].Type) + var converter = WaitingMyMsgBox[0]; + if (converter.Content is FrameworkElement customContent) + { + var dialog = new DialogControl + { + Title = converter.Title, + IsWarn = converter.IsWarn, + DialogContent = customContent, + }; + dialog.AddButton(converter.Button1, converter.Button1Action, isPrimary: true); + if (!string.IsNullOrEmpty(converter.Button2)) + dialog.AddButton(converter.Button2, converter.Button2Action); + if (!string.IsNullOrEmpty(converter.Button3)) + dialog.AddButton(converter.Button3, converter.Button3Action); + converter.WaitFrame = dialog.WaitFrame; + dialog.OnClosed += result => converter.Result = result; + frmMain.PanMsg.Children.Add(dialog); + } + else switch (converter.Type) { case MyMsgBoxType.Input: { - frmMain.PanMsg.Children.Add(new MyMsgInput(WaitingMyMsgBox[0])); + frmMain.PanMsg.Children.Add(new MyMsgInput(converter)); break; } case MyMsgBoxType.Select: { - frmMain.PanMsg.Children.Add(new MyMsgSelect(WaitingMyMsgBox[0])); + frmMain.PanMsg.Children.Add(new MyMsgSelect(converter)); break; } case MyMsgBoxType.Text: { - frmMain.PanMsg.Children.Add(new MyMsgText(WaitingMyMsgBox[0])); + frmMain.PanMsg.Children.Add(new MyMsgText(converter)); break; } case MyMsgBoxType.Login: { - frmMain.PanMsg.Children.Add(new MyMsgLogin(WaitingMyMsgBox[0])); + frmMain.PanMsg.Children.Add(new MyMsgLogin(converter)); break; } case MyMsgBoxType.Markdown: { - frmMain.PanMsg.Children.Add(new MyMsgMarkdown(WaitingMyMsgBox[0])); + frmMain.PanMsg.Children.Add(new MyMsgMarkdown(converter)); break; } } @@ -906,6 +926,64 @@ public static void MsgBoxWrapper_OnShow(string message, string caption, ICollect button1Action: btnAct1, button2Action: btnAct2, button3Action: btnAct3); } + public static void Dialog_OnShow(DialogContext context) + { + var isWarn = context.Theme == DialogTheme.Warning || context.Theme == DialogTheme.Error; + + var content = context.Content; + if (content is null && !string.IsNullOrEmpty(context.Caption)) + { + content = new TextBlock + { + Text = context.Caption, + TextWrapping = TextWrapping.Wrap, + FontSize = 15, + }; + } + + var converter = new MyMsgBoxConverter + { + Type = MyMsgBoxType.Text, + Button1 = context.Buttons.Count > 0 ? context.Buttons[0].Text : GetDefaultConfirmText(), + Button2 = context.Buttons.Count > 1 ? context.Buttons[1].Text : "", + Button3 = context.Buttons.Count > 2 ? context.Buttons[2].Text : "", + Button1Action = context.Buttons.Count > 0 ? context.Buttons[0].OnClick : null, + Button2Action = context.Buttons.Count > 1 ? context.Buttons[1].OnClick : null, + Button3Action = context.Buttons.Count > 2 ? context.Buttons[2].OnClick : null, + Text = context.Caption, + Title = context.Title, + IsWarn = isWarn, + ForceWait = context.Block, + Content = content, + }; + WaitingMyMsgBox.Add(converter); + if (ModBase.RunInUi()) + MyMsgBoxTick(); + if (context.Block) + { + if (frmMain is null || (frmMain.PanMsg is null && ModBase.RunInUi())) + { + WaitingMyMsgBox.Remove(converter); + Interaction.MsgBox(context.Caption, MsgBoxStyle.OkOnly, context.Title); + context.Result = 1; + } + else + { + try + { + frmMain?.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(converter.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + context.Result = (int)(converter.Result ?? 0); + } + } + } + #endregion #region 愚人节 From bf89224786a9296bb530e4f9be46120ba28f5bb0 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 19:04:05 +0800 Subject: [PATCH 02/14] feat(demo): add dialog test button in toolbox (debug-only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BtnDialogTest in 百宝箱 demonstrates rich content in DialogControl: text, border, progress bar, color swatches, styled layout. Visible only in DEBUG builds. --- .../Pages/PageTools/PageToolsTest.xaml | 3 + .../Pages/PageTools/PageToolsTest.xaml.cs | 92 +++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml b/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml index b72fb5bb0..9574ff481 100644 --- a/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml +++ b/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml @@ -23,6 +23,9 @@ Click="BtnCreateShortcut_Click" /> + diff --git a/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs b/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs index 902edbf37..d3de30753 100644 --- a/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs @@ -20,6 +20,7 @@ using PCL.Network.Loaders; using PCL.Core.App.Localization; using System.Globalization; +using WpfMedia = System.Windows.Media; namespace PCL; @@ -39,6 +40,7 @@ public PageToolsTest() Loaded += (_, _) => MeLoaded(); #if DEBUG BtnCrash.Visibility = Visibility.Visible; + BtnDialogTest.Visibility = Visibility.Visible; #endif } @@ -595,6 +597,96 @@ private void BtnCrash_Click(object sender, MouseButtonEventArgs e) throw new Exception(Lang.Text("Tools.Test.Crash.ManualCrash")); } + private void BtnDialogTest_Click(object sender, MouseButtonEventArgs e) + { + var panel = new StackPanel(); + + // Header + panel.Children.Add(new TextBlock + { + Text = "自定义弹窗示例", + FontSize = 18, + FontWeight = FontWeights.Bold, + Margin = new Thickness(0, 0, 0, 10), + }); + + // Info box + var infoBorder = new Border + { + BorderBrush = new WpfMedia.SolidColorBrush(WpfMedia.Color.FromRgb(74, 144, 217)), + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(6), + Padding = new Thickness(12), + Margin = new Thickness(0, 0, 0, 12), + }; + var infoStack = new StackPanel(); + infoStack.Children.Add(new TextBlock + { + Text = "✅ 此弹窗使用 DialogControl", + FontSize = 14, + Margin = new Thickness(0, 0, 0, 4), + }); + infoStack.Children.Add(new TextBlock + { + Text = "内容区可放置任意 FrameworkElement:\n文本、进度条、自定义控件等。\n按钮可绑定任意回调。", + TextWrapping = TextWrapping.Wrap, + FontSize = 13, + Foreground = WpfMedia.Brushes.Gray, + }); + infoBorder.Child = infoStack; + panel.Children.Add(infoBorder); + + // Progress + panel.Children.Add(new TextBlock + { + Text = "加载进度 70%", + FontSize = 13, + Margin = new Thickness(0, 0, 0, 4), + }); + panel.Children.Add(new ProgressBar + { + Value = 70, + Height = 6, + Margin = new Thickness(0, 0, 0, 12), + }); + + // Color swatches + var swatchBrushes = new[] { WpfMedia.Brushes.DodgerBlue, WpfMedia.Brushes.MediumSeaGreen, WpfMedia.Brushes.Tomato, WpfMedia.Brushes.Gold, WpfMedia.Brushes.MediumOrchid }; + var colorRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 8) }; + foreach (var brush in swatchBrushes) + { + colorRow.Children.Add(new System.Windows.Shapes.Rectangle + { + Width = 18, Height = 18, + Fill = brush, + RadiusX = 4, RadiusY = 4, + Margin = new Thickness(0, 0, 5, 0), + }); + } + panel.Children.Add(colorRow); + + // Hint + panel.Children.Add(new TextBlock + { + Text = "更多内容可由调用方自由定制。", + FontSize = 12, + Foreground = WpfMedia.Brushes.Gray, + Margin = new Thickness(0, 4, 0, 0), + }); + + Dialog.Show(new DialogContext + { + Title = "弹窗系统展示", + Content = panel, + Theme = DialogTheme.Info, + Buttons = + [ + new DialogButton("确认", isPrimary: true), + new DialogButton("取消"), + ], + }); + } + private int GetHeadSize() => CmbHeadSize.SelectedIndex switch { 0 => 64, From a81caa7cfbb63505a574f8b13008981c67de2dc0 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 21:08:34 +0800 Subject: [PATCH 03/14] feat(dialog): add Id to DialogButton, preset button factories, i18n DialogButton.Id returns on close instead of index. Presets: DialogButton.Confirm(), .Cancel(), .Yes(), .No() Constants: DialogResult.Ok=1, Cancel=2, Yes=6, No=7 All text via Lang.Text for i18n. --- PCL.Core/UI/Dialog.cs | 37 ++++++++++++++++--- .../Controls/Dialog/DialogControl.cs | 6 +-- Plain Craft Launcher 2/Modules/ModMain.cs | 14 +++++-- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/PCL.Core/UI/Dialog.cs b/PCL.Core/UI/Dialog.cs index 3f68bb3c6..0be6fb553 100644 --- a/PCL.Core/UI/Dialog.cs +++ b/PCL.Core/UI/Dialog.cs @@ -5,18 +5,45 @@ namespace PCL.Core.UI; +/// +/// Standard dialog result IDs for button matching. +/// +public static class DialogResult +{ + public const int Ok = 1; + public const int Cancel = 2; + public const int Yes = 6; + public const int No = 7; +} + public class DialogButton { public string Text { get; set; } + public int Id { get; set; } public Action? OnClick { get; set; } public bool IsPrimary { get; set; } - public DialogButton(string text, Action? onClick = null, bool isPrimary = false) + public DialogButton(string text, Action? onClick = null, bool isPrimary = false, int id = 0) { Text = text; OnClick = onClick; IsPrimary = isPrimary; + Id = id; } + + // -- presets -- + + public static DialogButton Confirm(string? text = null) + => new(text ?? Lang.Text("Common.Action.Confirm"), isPrimary: true, id: DialogResult.Ok); + + public static DialogButton Cancel(string? text = null) + => new(text ?? Lang.Text("Common.Action.Cancel"), id: DialogResult.Cancel); + + public static DialogButton Yes(string? text = null) + => new(text ?? Lang.Text("Common.Option.Yes"), isPrimary: true, id: DialogResult.Yes); + + public static DialogButton No(string? text = null) + => new(text ?? Lang.Text("Common.Option.No"), id: DialogResult.No); } public static class Dialog @@ -34,24 +61,24 @@ public static int Show(string caption, string? title = null, Theme = theme, Block = block, Content = null, - Buttons = BuildButtons(buttons, theme), + Buttons = _BuildButtons(buttons), }); } public static int Show(DialogContext context) { if (context.Buttons.Count == 0) - context.Buttons.Add(new DialogButton(Lang.Text("Common.Action.Confirm"))); + context.Buttons.Add(DialogButton.Confirm()); OnShow?.Invoke(context); return context.Result; } - private static Collection BuildButtons(string[] buttonTexts, DialogTheme theme) + private static Collection _BuildButtons(string[] buttonTexts) { var list = new Collection(); for (var i = 0; i < buttonTexts.Length; i++) { - list.Add(new DialogButton(buttonTexts[i], isPrimary: i == 0)); + list.Add(new DialogButton(buttonTexts[i], isPrimary: i == 0, id: i + 1)); } return list; } diff --git a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs index 22a40fd09..243bc0a13 100644 --- a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs +++ b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs @@ -47,7 +47,7 @@ public DialogControl() Loaded += OnLoad; } - public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = false) + public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = false, int id = 0) { var btn = new MyButton { @@ -63,7 +63,7 @@ public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = btn.Padding = new Thickness(5, 0, 5, 0); btn.Margin = new Thickness(12, 0, 0, 0); btn.Name += ModBase.GetUuid(); - var index = _buttons.Count + 1; + var buttonId = id > 0 ? id : _buttons.Count + 1; btn.Click += (_, _) => { if (_exited) return; @@ -74,7 +74,7 @@ public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = else { _exited = true; - _result = index; + _result = buttonId; Close(); } }; diff --git a/Plain Craft Launcher 2/Modules/ModMain.cs b/Plain Craft Launcher 2/Modules/ModMain.cs index d2392c05f..9694827c0 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.cs +++ b/Plain Craft Launcher 2/Modules/ModMain.cs @@ -548,6 +548,8 @@ public class MyMsgBoxConverter public Collection> ValidateRules; public DispatcherFrame WaitFrame = new(true); + + public int[] ButtonIds = [1, 2, 3]; } public enum MyMsgBoxType @@ -858,11 +860,11 @@ public static void MyMsgBoxTick() IsWarn = converter.IsWarn, DialogContent = customContent, }; - dialog.AddButton(converter.Button1, converter.Button1Action, isPrimary: true); + dialog.AddButton(converter.Button1, converter.Button1Action, isPrimary: true, id: converter.ButtonIds[0]); if (!string.IsNullOrEmpty(converter.Button2)) - dialog.AddButton(converter.Button2, converter.Button2Action); + dialog.AddButton(converter.Button2, converter.Button2Action, id: converter.ButtonIds[1]); if (!string.IsNullOrEmpty(converter.Button3)) - dialog.AddButton(converter.Button3, converter.Button3Action); + dialog.AddButton(converter.Button3, converter.Button3Action, id: converter.ButtonIds[2]); converter.WaitFrame = dialog.WaitFrame; dialog.OnClosed += result => converter.Result = result; frmMain.PanMsg.Children.Add(dialog); @@ -955,6 +957,12 @@ public static void Dialog_OnShow(DialogContext context) IsWarn = isWarn, ForceWait = context.Block, Content = content, + ButtonIds = new[] + { + context.Buttons.Count > 0 && context.Buttons[0].Id > 0 ? context.Buttons[0].Id : 1, + context.Buttons.Count > 1 && context.Buttons[1].Id > 0 ? context.Buttons[1].Id : 2, + context.Buttons.Count > 2 && context.Buttons[2].Id > 0 ? context.Buttons[2].Id : 3, + }, }; WaitingMyMsgBox.Add(converter); if (ModBase.RunInUi()) From afb6f7464e5cbdfc84201ed182002c5176859170 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 21:12:46 +0800 Subject: [PATCH 04/14] feat(dialog): add async wait model, support sync/async blocking Dialog.ShowAsync() returns Task for awaitable non-blocking. DialogContext.OnClosed callback fires on dialog close. MyMsgBoxConverter.OnCloseCallback bridges non-blocking path. Sync Dialog.Show() still works via Dispatcher.PushFrame. --- PCL.Core/UI/Dialog.cs | 28 +++++++++++++++++++++++ Plain Craft Launcher 2/Modules/ModMain.cs | 18 ++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/PCL.Core/UI/Dialog.cs b/PCL.Core/UI/Dialog.cs index 0be6fb553..019ae9a2f 100644 --- a/PCL.Core/UI/Dialog.cs +++ b/PCL.Core/UI/Dialog.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Threading.Tasks; using PCL.Core.App.Localization; namespace PCL.Core.UI; @@ -69,10 +70,36 @@ public static int Show(DialogContext context) { if (context.Buttons.Count == 0) context.Buttons.Add(DialogButton.Confirm()); + context.Block = true; + context.OnClosed = null; OnShow?.Invoke(context); return context.Result; } + public static Task ShowAsync(DialogContext context) + { + if (context.Buttons.Count == 0) + context.Buttons.Add(DialogButton.Confirm()); + var tcs = new TaskCompletionSource(); + context.Block = false; + context.OnClosed = result => tcs.TrySetResult(result); + OnShow?.Invoke(context); + return tcs.Task; + } + + public static Task ShowAsync(string caption, string? title = null, + DialogTheme theme = DialogTheme.Info, params string[] buttons) + { + return ShowAsync(new DialogContext + { + Caption = caption, + Title = title ?? Lang.Text("Common.Dialog.Title"), + Theme = theme, + Content = null, + Buttons = _BuildButtons(buttons), + }); + } + private static Collection _BuildButtons(string[] buttonTexts) { var list = new Collection(); @@ -100,4 +127,5 @@ public class DialogContext public object? Content { get; set; } public Collection Buttons { get; set; } = []; public int Result { get; set; } + public Action? OnClosed { get; set; } } diff --git a/Plain Craft Launcher 2/Modules/ModMain.cs b/Plain Craft Launcher 2/Modules/ModMain.cs index 9694827c0..490350e20 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.cs +++ b/Plain Craft Launcher 2/Modules/ModMain.cs @@ -550,6 +550,8 @@ public class MyMsgBoxConverter public DispatcherFrame WaitFrame = new(true); public int[] ButtonIds = [1, 2, 3]; + + public Action? OnCloseCallback; } public enum MyMsgBoxType @@ -866,7 +868,11 @@ public static void MyMsgBoxTick() if (!string.IsNullOrEmpty(converter.Button3)) dialog.AddButton(converter.Button3, converter.Button3Action, id: converter.ButtonIds[2]); converter.WaitFrame = dialog.WaitFrame; - dialog.OnClosed += result => converter.Result = result; + dialog.OnClosed += result => + { + converter.Result = result; + converter.OnCloseCallback?.Invoke(result); + }; frmMain.PanMsg.Children.Add(dialog); } else switch (converter.Type) @@ -974,6 +980,7 @@ public static void Dialog_OnShow(DialogContext context) WaitingMyMsgBox.Remove(converter); Interaction.MsgBox(context.Caption, MsgBoxStyle.OkOnly, context.Title); context.Result = 1; + context.OnClosed?.Invoke(1); } else { @@ -988,8 +995,17 @@ public static void Dialog_OnShow(DialogContext context) ComponentDispatcher.PopModal(); } context.Result = (int)(converter.Result ?? 0); + context.OnClosed?.Invoke(context.Result); } } + else + { + converter.OnCloseCallback = result => + { + context.Result = result; + context.OnClosed?.Invoke(result); + }; + } } #endregion From 1bd383f90bcf25be47404e362468392c24767e71 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 21:25:06 +0800 Subject: [PATCH 05/14] refactor(dialog): eliminate MyMsgBoxConverter from Dialog path Dialog_OnShow now creates DialogControl directly via Dispatcher.Invoke. No more converter/queue for new Dialog API. Old MyMsgBox path untouched. DialogControl.Close background check accounts for stacked dialogs. --- .../Controls/Dialog/DialogControl.cs | 4 +- Plain Craft Launcher 2/Modules/ModMain.cs | 79 ++++++++++--------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs index 243bc0a13..268b6cda8 100644 --- a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs +++ b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs @@ -151,7 +151,9 @@ public void Close() { ModAnimation.AaCode(() => { - if (!ModMain.WaitingMyMsgBox.Any()) + var hasMore = ModMain.WaitingMyMsgBox.Any() + || (ModMain.frmMain?.PanMsg?.Children.Count ?? 0) > 1; + if (!hasMore) ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, new ModBase.MyColor(0d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200, diff --git a/Plain Craft Launcher 2/Modules/ModMain.cs b/Plain Craft Launcher 2/Modules/ModMain.cs index 490350e20..dddf70759 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.cs +++ b/Plain Craft Launcher 2/Modules/ModMain.cs @@ -936,6 +936,24 @@ public static void MsgBoxWrapper_OnShow(string message, string caption, ICollect public static void Dialog_OnShow(DialogContext context) { + if (frmMain is null) + { + Interaction.MsgBox(context.Caption, MsgBoxStyle.OkOnly, context.Title); + context.Result = 1; + context.OnClosed?.Invoke(1); + return; + } + + if (frmMain.Dispatcher.CheckAccess()) + ShowDialogOnUi(context); + else + frmMain.Dispatcher.Invoke(() => ShowDialogOnUi(context)); + } + + private static void ShowDialogOnUi(DialogContext context) + { + if (frmMain?.PanMsg is null) return; + var isWarn = context.Theme == DialogTheme.Warning || context.Theme == DialogTheme.Error; var content = context.Content; @@ -949,58 +967,41 @@ public static void Dialog_OnShow(DialogContext context) }; } - var converter = new MyMsgBoxConverter + var dialog = new DialogControl { - Type = MyMsgBoxType.Text, - Button1 = context.Buttons.Count > 0 ? context.Buttons[0].Text : GetDefaultConfirmText(), - Button2 = context.Buttons.Count > 1 ? context.Buttons[1].Text : "", - Button3 = context.Buttons.Count > 2 ? context.Buttons[2].Text : "", - Button1Action = context.Buttons.Count > 0 ? context.Buttons[0].OnClick : null, - Button2Action = context.Buttons.Count > 1 ? context.Buttons[1].OnClick : null, - Button3Action = context.Buttons.Count > 2 ? context.Buttons[2].OnClick : null, - Text = context.Caption, Title = context.Title, IsWarn = isWarn, - ForceWait = context.Block, - Content = content, - ButtonIds = new[] - { - context.Buttons.Count > 0 && context.Buttons[0].Id > 0 ? context.Buttons[0].Id : 1, - context.Buttons.Count > 1 && context.Buttons[1].Id > 0 ? context.Buttons[1].Id : 2, - context.Buttons.Count > 2 && context.Buttons[2].Id > 0 ? context.Buttons[2].Id : 3, - }, + DialogContent = content as UIElement, }; - WaitingMyMsgBox.Add(converter); - if (ModBase.RunInUi()) - MyMsgBoxTick(); + + for (var i = 0; i < context.Buttons.Count && i < 3; i++) + { + var btn = context.Buttons[i]; + var isPrimary = i == 0 || btn.IsPrimary; + var id = btn.Id > 0 ? btn.Id : i + 1; + dialog.AddButton(btn.Text, btn.OnClick, isPrimary, id); + } + + frmMain.PanMsg.Children.Add(dialog); + if (context.Block) { - if (frmMain is null || (frmMain.PanMsg is null && ModBase.RunInUi())) + try { - WaitingMyMsgBox.Remove(converter); - Interaction.MsgBox(context.Caption, MsgBoxStyle.OkOnly, context.Title); - context.Result = 1; - context.OnClosed?.Invoke(1); + frmMain.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(dialog.WaitFrame); } - else + finally { - try - { - frmMain?.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - context.Result = (int)(converter.Result ?? 0); - context.OnClosed?.Invoke(context.Result); + ComponentDispatcher.PopModal(); } + context.Result = dialog.Result; + context.OnClosed?.Invoke(context.Result); } else { - converter.OnCloseCallback = result => + dialog.OnClosed += result => { context.Result = result; context.OnClosed?.Invoke(result); From e38a2c575226524b27555a4f991514001b5777d6 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 21:35:58 +0800 Subject: [PATCH 06/14] refactor(dialog): route all MyMsg types through DialogControl MyMsgBoxTick now creates DialogControl for Text/Input/Select/Markdown. DialogControl.Close(int result) added for custom button handlers. Login still uses MyMsgLogin (complex OAuth lifecycle). Old MyMsgText/Input/Select/Markdown files kept, no longer instantiated. --- .../Controls/Dialog/DialogControl.cs | 20 +- Plain Craft Launcher 2/Modules/ModMain.cs | 214 ++++++++++++++---- 2 files changed, 181 insertions(+), 53 deletions(-) diff --git a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs index 268b6cda8..1c656c7aa 100644 --- a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs +++ b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs @@ -73,9 +73,7 @@ public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = } else { - _exited = true; - _result = buttonId; - Close(); + Close(buttonId); } }; _buttons.Add(btn); @@ -120,12 +118,24 @@ private void OnLoad(object sender, RoutedEventArgs e) } } + public void Close(int result) + { + if (_exited) return; + _exited = true; + _result = result; + CloseInternal(); + } + public void Close() { - if (_exited && _result == 0) - _result = -1; + if (_exited) return; _exited = true; + _result = -1; + CloseInternal(); + } + private void CloseInternal() + { try { WaitFrame.Continue = false; diff --git a/Plain Craft Launcher 2/Modules/ModMain.cs b/Plain Craft Launcher 2/Modules/ModMain.cs index dddf70759..79e2ddd3d 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.cs +++ b/Plain Craft Launcher 2/Modules/ModMain.cs @@ -854,55 +854,9 @@ public static void MyMsgBoxTick() // 没有弹窗,显示一个等待的弹窗 frmMain.PanMsgBackground.Visibility = Visibility.Visible; var converter = WaitingMyMsgBox[0]; - if (converter.Content is FrameworkElement customContent) - { - var dialog = new DialogControl - { - Title = converter.Title, - IsWarn = converter.IsWarn, - DialogContent = customContent, - }; - dialog.AddButton(converter.Button1, converter.Button1Action, isPrimary: true, id: converter.ButtonIds[0]); - if (!string.IsNullOrEmpty(converter.Button2)) - dialog.AddButton(converter.Button2, converter.Button2Action, id: converter.ButtonIds[1]); - if (!string.IsNullOrEmpty(converter.Button3)) - dialog.AddButton(converter.Button3, converter.Button3Action, id: converter.ButtonIds[2]); - converter.WaitFrame = dialog.WaitFrame; - dialog.OnClosed += result => - { - converter.Result = result; - converter.OnCloseCallback?.Invoke(result); - }; + var dialog = CreateDialogFromConverter(converter); + if (dialog is not null) frmMain.PanMsg.Children.Add(dialog); - } - else switch (converter.Type) - { - case MyMsgBoxType.Input: - { - frmMain.PanMsg.Children.Add(new MyMsgInput(converter)); - break; - } - case MyMsgBoxType.Select: - { - frmMain.PanMsg.Children.Add(new MyMsgSelect(converter)); - break; - } - case MyMsgBoxType.Text: - { - frmMain.PanMsg.Children.Add(new MyMsgText(converter)); - break; - } - case MyMsgBoxType.Login: - { - frmMain.PanMsg.Children.Add(new MyMsgLogin(converter)); - break; - } - case MyMsgBoxType.Markdown: - { - frmMain.PanMsg.Children.Add(new MyMsgMarkdown(converter)); - break; - } - } WaitingMyMsgBox.RemoveAt(0); } @@ -918,6 +872,170 @@ public static void MyMsgBoxTick() } } + private static DialogControl? CreateDialogFromConverter(MyMsgBoxConverter conv) + { + switch (conv.Type) + { + case MyMsgBoxType.Input: + return CreateInputDialog(conv); + case MyMsgBoxType.Select: + return CreateSelectDialog(conv); + case MyMsgBoxType.Login: + // MyMsgLogin manages its own chrome + lifecycle + frmMain?.PanMsg.Children.Add(new MyMsgLogin(conv)); + return null; + case MyMsgBoxType.Markdown: + { + var mdViewer = new Markdig.Wpf.MarkdownViewer { Markdown = conv.Text }; + return CreateStandardDialog(conv, mdViewer); + } + default: + { + var content = conv.Content as UIElement + ?? new TextBlock { Text = conv.Text, TextWrapping = TextWrapping.Wrap, FontSize = 15 }; + return CreateStandardDialog(conv, content); + } + } + } + + private static DialogControl CreateStandardDialog(MyMsgBoxConverter conv, UIElement content) + { + var dialog = new DialogControl + { + Title = conv.Title, + IsWarn = conv.IsWarn, + DialogContent = content, + }; + dialog.AddButton(conv.Button1, conv.Button1Action, isPrimary: true, id: conv.ButtonIds[0]); + if (!string.IsNullOrEmpty(conv.Button2)) + dialog.AddButton(conv.Button2, conv.Button2Action, id: conv.ButtonIds[1]); + if (!string.IsNullOrEmpty(conv.Button3)) + dialog.AddButton(conv.Button3, conv.Button3Action, id: conv.ButtonIds[2]); + conv.WaitFrame = dialog.WaitFrame; + dialog.OnClosed += result => + { + conv.Result = result; + conv.OnCloseCallback?.Invoke(result); + }; + return dialog; + } + + private static DialogControl CreateInputDialog(MyMsgBoxConverter conv) + { + var stack = new StackPanel(); + if (!string.IsNullOrEmpty(conv.Text)) + { + stack.Children.Add(new TextBlock + { + Text = conv.Text, + TextWrapping = TextWrapping.Wrap, + FontSize = 15, + Margin = new Thickness(0, 0, 0, 7), + }); + } + + var textBox = new MyTextBox + { + Text = (string)(conv.Content ?? ""), + HintText = conv.HintText, + ValidateRules = conv.ValidateRules ?? [], + MinWidth = 450, + }; + stack.Children.Add(textBox); + + var dialog = new DialogControl + { + Title = conv.Title, + IsWarn = conv.IsWarn, + DialogContent = stack, + }; + + dialog.AddButton(conv.Button1, onClick: () => + { + textBox.Validate(); + if (!textBox.IsValidated) return; + conv.Result = textBox.Text; + dialog.Close(conv.ButtonIds[0]); + }, isPrimary: true, id: conv.ButtonIds[0]); + + if (!string.IsNullOrEmpty(conv.Button2)) + { + dialog.AddButton(conv.Button2, onClick: () => + { + conv.Result = null; + dialog.Close(conv.ButtonIds[1]); + }, id: conv.ButtonIds[1]); + } + + conv.WaitFrame = dialog.WaitFrame; + return dialog; + } + + private static DialogControl CreateSelectDialog(MyMsgBoxConverter conv) + { + var panel = new StackPanel(); + var dialog = new DialogControl + { + Title = conv.Title, + IsWarn = conv.IsWarn, + DialogContent = panel, + }; + + var b1 = dialog.AddButton(conv.Button1, onClick: () => + { + // handled by extra Click below + }, isPrimary: true, id: conv.ButtonIds[0]); + b1.IsEnabled = false; + + if (!string.IsNullOrEmpty(conv.Button2)) + { + dialog.AddButton(conv.Button2, onClick: () => + { + conv.Result = null; + dialog.Close(conv.ButtonIds[1]); + }, id: conv.ButtonIds[1]); + } + + var selectedIndex = -1; + var index = 0; + foreach (var raw in (IEnumerable)conv.Content!) + { + var item = MyVirtualizingElement.TryInit((FrameworkElement)raw); + if (item is IMyRadio radio) + { + if (item is MyListItem listItem) + { + listItem.Type = MyListItem.CheckType.RadioBox; + listItem.MinHeight = 24; + } + else if (item is MyRadioBox radioBox) + { + radioBox.MinHeight = 24; + } + + var currentIndex = index; + radio.Check += (_, _) => + { + selectedIndex = currentIndex; + b1.IsEnabled = true; + }; + panel.Children.Add((UIElement)radio); + index++; + } + } + + // Override Btn1's dummy onClick with real logic + b1.Click += (_, _) => + { + if (selectedIndex < 0) return; + conv.Result = selectedIndex; + dialog.Close(conv.ButtonIds[0]); + }; + + conv.WaitFrame = dialog.WaitFrame; + return dialog; + } + public static void MsgBoxWrapper_OnShow(string message, string caption, ICollection buttons, MsgBoxTheme theme, bool block, ref int result) { From 072e6416f12175ce0f798a408123cc17be7dbfc3 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 21:43:15 +0800 Subject: [PATCH 07/14] refactor: extract dialog code to ModMain.Dialog.cs partial class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ModMain.cs: removed ~650 lines of dialog code, added 'partial' keyword. ModMain.Dialog.cs: new file with all dialog logic (MyMsgBoxConverter, MyMsgBox*, MyMsgBoxTick, Dialog_OnShow, CreateDialogFromConverter, etc.). Zero call-site changes — all public API remains on ModMain. --- .codegraph/.gitignore | 5 + .../Modules/ModMain.Dialog.cs | 674 ++++++++++++++++++ Plain Craft Launcher 2/Modules/ModMain.cs | 663 +---------------- .../Pages/PageTools/PageToolsTest.xaml.cs | 6 +- 4 files changed, 681 insertions(+), 667 deletions(-) create mode 100644 .codegraph/.gitignore create mode 100644 Plain Craft Launcher 2/Modules/ModMain.Dialog.cs diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 000000000..d20c0fe4b --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,5 @@ +# CodeGraph data files — local to each machine, not for committing. +# Ignore everything in .codegraph/ except this file itself, so transient +# files (the database, daemon.pid, sockets, logs) never show up in git. +* +!.gitignore diff --git a/Plain Craft Launcher 2/Modules/ModMain.Dialog.cs b/Plain Craft Launcher 2/Modules/ModMain.Dialog.cs new file mode 100644 index 000000000..e749b5630 --- /dev/null +++ b/Plain Craft Launcher 2/Modules/ModMain.Dialog.cs @@ -0,0 +1,674 @@ +using System.Collections; +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Interop; +using System.Windows.Threading; +using FluentValidation; +using Microsoft.VisualBasic; +using PCL.Core.App.Localization; +using PCL.Core.UI; + +namespace PCL; + +public static partial class ModMain +{ + /// + /// 等待显示的弹窗。 + /// + public static List WaitingMyMsgBox { get; } = []; + + #region 弹窗 + + /// + /// 存储弹窗信息的转换器。 + /// + public class MyMsgBoxConverter + { + // 设置轮询 Url + public object AuthUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; + public string Button1 = ""; + + /// + /// 点击第一个按钮将执行该方法,不关闭弹窗。 + /// + public Action Button1Action; + + public string Button2 = ""; + + /// + /// 点击第二个按钮将执行该方法,不关闭弹窗。 + /// + public Action Button2Action; + + public string Button3 = ""; + + /// + /// 点击第三个按钮将执行该方法,不关闭弹窗。 + /// + public Action Button3Action; + + /// + /// 输入模式:文本框的文本。 + /// 选择模式:需要放进去的 List(Of MyListItem)。 + /// 登录模式:登录步骤 1 中返回的 JSON。 + /// + public object Content; + + public bool ForceWait; + + /// + /// 有多个按钮时,是否给第一个按钮加高亮。 + /// + public bool HighLight; + + /// + /// 输入模式:提示文本。 + /// + public string HintText = ""; + + /// + /// 弹窗是否已经关闭。 + /// + public bool IsExited = false; + + public bool IsWarn; + + /// + /// 输入模式:输入的文本。若点击了 非 第一个按钮,则为 Nothing。 + /// 选择模式:点击的按钮编号,从 1 开始。 + /// 登录模式:字符串数组 {AccessToken, RefreshToken} 或一个 Exception。 + /// + public object Result; + + public string Text; + public string Title; + public MyMsgBoxType Type; + + /// + /// 输入模式:输入验证规则。 + /// + public Collection> ValidateRules; + + public DispatcherFrame WaitFrame = new(true); + + public int[] ButtonIds = [1, 2, 3]; + + public Action? OnCloseCallback; + } + + public enum MyMsgBoxType + { + Text, + Select, + Input, + Login, + Markdown + } + + private static string GetDefaultDialogTitle() => Lang.Text("Common.Dialog.Title"); + + private static string GetDefaultConfirmText() => Lang.Text("Common.Action.Confirm"); + + private static string GetDefaultCancelText() => Lang.Text("Common.Action.Cancel"); + + /// + /// 显示弹窗,返回点击按钮的编号(从 1 开始)。 + /// + /// 弹窗的标题。 + /// 弹窗的内容。 + /// 显示的第一个按钮,默认为"确定"。 + /// 显示的第二个按钮,默认为空。 + /// 显示的第三个按钮,默认为空。 + /// 点击第一个按钮将执行该方法,不关闭弹窗。 + /// 点击第二个按钮将执行该方法,不关闭弹窗。 + /// 点击第三个按钮将执行该方法,不关闭弹窗。 + /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 + public static int MyMsgBox(string caption, string? title = null, string? button1 = null, string? button2 = "", + string? button3 = "", bool isWarn = false, bool highLight = true, bool forceWait = false, + Action button1Action = null, Action button2Action = null, Action button3Action = null) + { + title ??= GetDefaultDialogTitle(); + button1 ??= GetDefaultConfirmText(); + button2 ??= ""; + button3 ??= ""; + // 将弹窗列入队列 + var converter = new MyMsgBoxConverter + { + Type = MyMsgBoxType.Text, Button1 = button1, Button2 = button2, Button3 = button3, Text = caption, + IsWarn = isWarn, Title = title, HighLight = highLight, ForceWait = true, Button1Action = button1Action, + Button2Action = button2Action, Button3Action = button3Action + }; + WaitingMyMsgBox.Add(converter); + if (ModBase.RunInUi()) + // 若为 UI 线程,立即执行弹窗刻, 避免快速(连点器)点击时多次弹窗 + MyMsgBoxTick(); + if (button2.Length > 0 || forceWait) + { + // 若有多个按钮则开始等待 + if (frmMain is null || (frmMain.PanMsg is null && ModBase.RunInUi())) + { + // 主窗体尚未加载,用老土的弹窗来替代 + WaitingMyMsgBox.Remove(converter); + if (button2.Length > 0) + { + var rawResult = Interaction.MsgBox(caption, + (MsgBoxStyle)((int)(button3.Length > 0 ? MsgBoxStyle.YesNoCancel : MsgBoxStyle.YesNo) + + (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); + switch (rawResult) + { + case MsgBoxResult.Yes: + { + converter.Result = 1; + break; + } + case MsgBoxResult.No: + { + converter.Result = 2; + break; + } + case MsgBoxResult.Cancel: + { + converter.Result = 3; + break; + } + } + } + else + { + Interaction.MsgBox(caption, + (MsgBoxStyle)((int)MsgBoxStyle.OkOnly + + (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); + converter.Result = 1; + } + + ModBase.Log("[Control] 主窗体加载完成前出现意料外的等待弹窗:" + button1 + "," + button2 + "," + button3, + ModBase.LogLevel.Debug); + } + else + { + try + { + frmMain.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(converter.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + } + + ModBase.Log($"[Control] 普通弹框返回:{converter.Result ?? "null"}"); + return (int)converter.Result; + } + + // 不进行等待,直接返回 + return 1; + } + + /// + /// 显示弹窗,返回点击按钮的编号(从 1 开始)。 + /// + /// 弹窗的标题。 + /// 弹窗的内容。 + /// 显示的第一个按钮,默认为"确定"。 + /// 显示的第二个按钮,默认为空。 + /// 显示的第三个按钮,默认为空。 + /// 点击第一个按钮将执行该方法,不关闭弹窗。 + /// 点击第二个按钮将执行该方法,不关闭弹窗。 + /// 点击第三个按钮将执行该方法,不关闭弹窗。 + /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 + public static int MyMsgBoxMarkdown(string caption, string? title = null, string? button1 = null, string? button2 = "", + string? button3 = "", bool isWarn = false, bool highLight = true, bool forceWait = false, + Action button1Action = null, Action button2Action = null, Action button3Action = null) + { + title ??= GetDefaultDialogTitle(); + button1 ??= GetDefaultConfirmText(); + button2 ??= ""; + button3 ??= ""; + // 将弹窗列入队列 + var converter = new MyMsgBoxConverter + { + Type = MyMsgBoxType.Markdown, Button1 = button1, Button2 = button2, Button3 = button3, Text = caption, + IsWarn = isWarn, Title = title, HighLight = highLight, ForceWait = true, Button1Action = button1Action, + Button2Action = button2Action, Button3Action = button3Action + }; + WaitingMyMsgBox.Add(converter); + if (ModBase.RunInUi()) + // 若为 UI 线程,立即执行弹窗刻, 避免快速(连点器)点击时多次弹窗 + MyMsgBoxTick(); + if (button2.Length > 0 || forceWait) + { + // 若有多个按钮则开始等待 + if (frmMain is null || (frmMain.PanMsg is null && ModBase.RunInUi())) + { + // 主窗体尚未加载,用老土的弹窗来替代 + WaitingMyMsgBox.Remove(converter); + if (button2.Length > 0) + { + var rawResult = Interaction.MsgBox(caption, + (MsgBoxStyle)((int)(button3.Length > 0 ? MsgBoxStyle.YesNoCancel : MsgBoxStyle.YesNo) + + (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); + switch (rawResult) + { + case MsgBoxResult.Yes: + { + converter.Result = 1; + break; + } + case MsgBoxResult.No: + { + converter.Result = 2; + break; + } + case MsgBoxResult.Cancel: + { + converter.Result = 3; + break; + } + } + } + else + { + Interaction.MsgBox(caption, + (MsgBoxStyle)((int)MsgBoxStyle.OkOnly + + (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); + converter.Result = 1; + } + + ModBase.Log("[Control] 主窗体加载完成前出现意料外的等待弹窗:" + button1 + "," + button2 + "," + button3, + ModBase.LogLevel.Debug); + } + else + { + try + { + frmMain.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(converter.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + } + + ModBase.Log($"[Control] 普通弹框返回:{converter.Result ?? "null"}"); + return (int)converter.Result; + } + + // 不进行等待,直接返回 + return 1; + } + + /// + /// 显示输入框并返回输入的文本。若点击第二个按钮,则返回 Nothing。 + /// + /// 弹窗的标题。 + /// 文本框的输入检测。 + /// 弹窗的介绍文本。 + /// 文本框的默认内容。 + /// 文本框的提示内容。 + /// 显示的第一个按钮,默认为"确定"。 + /// 显示的第二个按钮,默认为"取消"。 + /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 + public static string MyMsgBoxInput(string title, string text = "", string defaultInput = "", + Collection>? validateRules = null, string hintText = "", string? button1 = null, + string? button2 = null, bool isWarn = false) + { + button1 ??= GetDefaultConfirmText(); + button2 ??= GetDefaultCancelText(); + // 将弹窗列入队列 + var converter = new MyMsgBoxConverter + { + Text = text, HintText = hintText, Type = MyMsgBoxType.Input, + ValidateRules = validateRules ?? [], Button1 = button1, Button2 = button2, + Content = defaultInput, IsWarn = isWarn, Title = title + }; + WaitingMyMsgBox.Add(converter); + // 虽然我也不知道这是啥但是能用就成了 :) + try + { + frmMain?.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(converter.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + + ModBase.Log($"[Control] 输入弹框返回:{converter.Result}"); + return converter.Result?.ToString(); + } + + /// + /// 显示选择框并返回选择的第几项(从 0 开始)。若点击第二个按钮,则返回 Nothing。 + /// + /// 弹窗的标题。 + /// 显示的第一个按钮,默认为 "确定"。 + /// 显示的第二个按钮,默认为空。 + /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 + public static int? MyMsgBoxSelect(List selections, string? title = null, string? button1 = null, + string? button2 = "", bool isWarn = false) + { + title ??= GetDefaultDialogTitle(); + button1 ??= GetDefaultConfirmText(); + button2 ??= ""; + // 将弹窗列入队列 + var converter = new MyMsgBoxConverter + { + Type = MyMsgBoxType.Select, Button1 = button1, Button2 = button2, Content = selections, IsWarn = isWarn, + Title = title + }; + WaitingMyMsgBox.Add(converter); + // 虽然我也不知道这是啥但是能用就成了 :) + try + { + if (frmMain is not null) + frmMain.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(converter.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + + ModBase.Log($"[Control] 选择弹框返回:{converter.Result ?? "null"}"); + return (int?)converter.Result; + } + + + public static void MyMsgBoxTick() + { + try + { + if (frmMain is null || frmMain.PanMsg is null || frmMain.WindowState == WindowState.Minimized) + return; + if (frmMain.PanMsg.Children.Count > 0) + { + // 弹窗中 + frmMain.PanMsgBackground.Visibility = Visibility.Visible; + } + else if (WaitingMyMsgBox.Any()) + { + // 没有弹窗,显示一个等待的弹窗 + frmMain.PanMsgBackground.Visibility = Visibility.Visible; + var converter = WaitingMyMsgBox[0]; + var dialog = CreateDialogFromConverter(converter); + if (dialog is not null) + frmMain.PanMsg.Children.Add(dialog); + + WaitingMyMsgBox.RemoveAt(0); + } + // 没有弹窗,没有等待的弹窗 + else if (!(frmMain.PanMsgBackground.Visibility == Visibility.Collapsed)) + { + frmMain.PanMsgBackground.Visibility = Visibility.Collapsed; + } + } + catch (Exception ex) + { + ModBase.Log(ex, "处理等待中的弹窗失败", ModBase.LogLevel.Feedback); + } + } + + private static DialogControl? CreateDialogFromConverter(MyMsgBoxConverter conv) + { + switch (conv.Type) + { + case MyMsgBoxType.Input: + return CreateInputDialog(conv); + case MyMsgBoxType.Select: + return CreateSelectDialog(conv); + case MyMsgBoxType.Login: + // MyMsgLogin manages its own chrome + lifecycle + frmMain?.PanMsg.Children.Add(new MyMsgLogin(conv)); + return null; + case MyMsgBoxType.Markdown: + { + var mdViewer = new Markdig.Wpf.MarkdownViewer { Markdown = conv.Text }; + return CreateStandardDialog(conv, mdViewer); + } + default: + { + var content = conv.Content as UIElement + ?? new TextBlock { Text = conv.Text, TextWrapping = TextWrapping.Wrap, FontSize = 15 }; + return CreateStandardDialog(conv, content); + } + } + } + + private static DialogControl CreateStandardDialog(MyMsgBoxConverter conv, UIElement content) + { + var dialog = new DialogControl + { + Title = conv.Title, + IsWarn = conv.IsWarn, + DialogContent = content, + }; + dialog.AddButton(conv.Button1, conv.Button1Action, isPrimary: true, id: conv.ButtonIds[0]); + if (!string.IsNullOrEmpty(conv.Button2)) + dialog.AddButton(conv.Button2, conv.Button2Action, id: conv.ButtonIds[1]); + if (!string.IsNullOrEmpty(conv.Button3)) + dialog.AddButton(conv.Button3, conv.Button3Action, id: conv.ButtonIds[2]); + conv.WaitFrame = dialog.WaitFrame; + dialog.OnClosed += result => + { + conv.Result = result; + conv.OnCloseCallback?.Invoke(result); + }; + return dialog; + } + + private static DialogControl CreateInputDialog(MyMsgBoxConverter conv) + { + var stack = new StackPanel(); + if (!string.IsNullOrEmpty(conv.Text)) + { + stack.Children.Add(new TextBlock + { + Text = conv.Text, + TextWrapping = TextWrapping.Wrap, + FontSize = 15, + Margin = new Thickness(0, 0, 0, 7), + }); + } + + var textBox = new MyTextBox + { + Text = (string)(conv.Content ?? ""), + HintText = conv.HintText, + ValidateRules = conv.ValidateRules ?? [], + MinWidth = 450, + }; + stack.Children.Add(textBox); + + var dialog = new DialogControl + { + Title = conv.Title, + IsWarn = conv.IsWarn, + DialogContent = stack, + }; + + dialog.AddButton(conv.Button1, onClick: () => + { + textBox.Validate(); + if (!textBox.IsValidated) return; + conv.Result = textBox.Text; + dialog.Close(conv.ButtonIds[0]); + }, isPrimary: true, id: conv.ButtonIds[0]); + + if (!string.IsNullOrEmpty(conv.Button2)) + { + dialog.AddButton(conv.Button2, onClick: () => + { + conv.Result = null; + dialog.Close(conv.ButtonIds[1]); + }, id: conv.ButtonIds[1]); + } + + conv.WaitFrame = dialog.WaitFrame; + return dialog; + } + + private static DialogControl CreateSelectDialog(MyMsgBoxConverter conv) + { + var panel = new StackPanel(); + var dialog = new DialogControl + { + Title = conv.Title, + IsWarn = conv.IsWarn, + DialogContent = panel, + }; + + var b1 = dialog.AddButton(conv.Button1, onClick: () => + { + // handled by extra Click below + }, isPrimary: true, id: conv.ButtonIds[0]); + b1.IsEnabled = false; + + if (!string.IsNullOrEmpty(conv.Button2)) + { + dialog.AddButton(conv.Button2, onClick: () => + { + conv.Result = null; + dialog.Close(conv.ButtonIds[1]); + }, id: conv.ButtonIds[1]); + } + + var selectedIndex = -1; + var index = 0; + foreach (var raw in (IEnumerable)conv.Content!) + { + var item = MyVirtualizingElement.TryInit((FrameworkElement)raw); + if (item is IMyRadio radio) + { + if (item is MyListItem listItem) + { + listItem.Type = MyListItem.CheckType.RadioBox; + listItem.MinHeight = 24; + } + else if (item is MyRadioBox radioBox) + { + radioBox.MinHeight = 24; + } + + var currentIndex = index; + radio.Check += (_, _) => + { + selectedIndex = currentIndex; + b1.IsEnabled = true; + }; + panel.Children.Add((UIElement)radio); + index++; + } + } + + // Override Btn1's dummy onClick with real logic + b1.Click += (_, _) => + { + if (selectedIndex < 0) return; + conv.Result = selectedIndex; + dialog.Close(conv.ButtonIds[0]); + }; + + conv.WaitFrame = dialog.WaitFrame; + return dialog; + } + + public static void MsgBoxWrapper_OnShow(string message, string caption, ICollection buttons, + MsgBoxTheme theme, bool block, ref int result) + { + var btnText1 = buttons.Count < 1 ? GetDefaultConfirmText() : buttons.ElementAt(0).Context; + var btnAct1 = (Action)(buttons.Count < 1 ? (object)null : buttons.ElementAt(0).OnClick); + var btnText2 = buttons.Count < 2 ? GetDefaultCancelText() : buttons.ElementAt(1).Context; + var btnAct2 = (Action)(buttons.Count < 2 ? (object)null : buttons.ElementAt(1).OnClick); + var btnText3 = buttons.Count < 3 ? "" : buttons.ElementAt(2).Context; + var btnAct3 = (Action)(buttons.Count < 3 ? (object)null : buttons.ElementAt(2).OnClick); + + var isWarn = theme == MsgBoxTheme.Warning || theme == MsgBoxTheme.Error; + + result = MyMsgBox(message, caption, btnText1, btnText2, btnText3, isWarn, forceWait: block, + button1Action: btnAct1, button2Action: btnAct2, button3Action: btnAct3); + } + + public static void Dialog_OnShow(DialogContext context) + { + if (frmMain is null) + { + Interaction.MsgBox(context.Caption, MsgBoxStyle.OkOnly, context.Title); + context.Result = 1; + context.OnClosed?.Invoke(1); + return; + } + + if (frmMain.Dispatcher.CheckAccess()) + ShowDialogOnUi(context); + else + frmMain.Dispatcher.Invoke(() => ShowDialogOnUi(context)); + } + + private static void ShowDialogOnUi(DialogContext context) + { + if (frmMain?.PanMsg is null) return; + + var isWarn = context.Theme == DialogTheme.Warning || context.Theme == DialogTheme.Error; + + var content = context.Content; + if (content is null && !string.IsNullOrEmpty(context.Caption)) + { + content = new TextBlock + { + Text = context.Caption, + TextWrapping = TextWrapping.Wrap, + FontSize = 15, + }; + } + + var dialog = new DialogControl + { + Title = context.Title, + IsWarn = isWarn, + DialogContent = content as UIElement, + }; + + for (var i = 0; i < context.Buttons.Count && i < 3; i++) + { + var btn = context.Buttons[i]; + var isPrimary = i == 0 || btn.IsPrimary; + var id = btn.Id > 0 ? btn.Id : i + 1; + dialog.AddButton(btn.Text, btn.OnClick, isPrimary, id); + } + + frmMain.PanMsg.Children.Add(dialog); + + if (context.Block) + { + try + { + frmMain.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(dialog.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + context.Result = dialog.Result; + context.OnClosed?.Invoke(context.Result); + } + else + { + dialog.OnClosed += result => + { + context.Result = result; + context.OnClosed?.Invoke(result); + }; + } + } + + #endregion +} diff --git a/Plain Craft Launcher 2/Modules/ModMain.cs b/Plain Craft Launcher 2/Modules/ModMain.cs index 79e2ddd3d..5aabfcfc0 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.cs +++ b/Plain Craft Launcher 2/Modules/ModMain.cs @@ -21,7 +21,7 @@ namespace PCL; -public static class ModMain +public static partial class ModMain { public static FormMain? frmMain; public static SplashScreen? frmStart; @@ -104,13 +104,6 @@ private static ModBase.SafeList HintWaiting set; } - /// - /// 等待显示的弹窗。 - /// - public static List WaitingMyMsgBox { get; } = []; - - - private static void TimerMain() { try @@ -475,660 +468,6 @@ private static void HideAllHint() #endregion - #region 弹窗 - - /// - /// 存储弹窗信息的转换器。 - /// - public class MyMsgBoxConverter - { - // 设置轮询 Url - public object AuthUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; - public string Button1 = ""; - - /// - /// 点击第一个按钮将执行该方法,不关闭弹窗。 - /// - public Action Button1Action; - - public string Button2 = ""; - - /// - /// 点击第二个按钮将执行该方法,不关闭弹窗。 - /// - public Action Button2Action; - - public string Button3 = ""; - - /// - /// 点击第三个按钮将执行该方法,不关闭弹窗。 - /// - public Action Button3Action; - - /// - /// 输入模式:文本框的文本。 - /// 选择模式:需要放进去的 List(Of MyListItem)。 - /// 登录模式:登录步骤 1 中返回的 JSON。 - /// - public object Content; - - public bool ForceWait; - - /// - /// 有多个按钮时,是否给第一个按钮加高亮。 - /// - public bool HighLight; - - /// - /// 输入模式:提示文本。 - /// - public string HintText = ""; - - /// - /// 弹窗是否已经关闭。 - /// - public bool IsExited = false; - - public bool IsWarn; - - /// - /// 输入模式:输入的文本。若点击了 非 第一个按钮,则为 Nothing。 - /// 选择模式:点击的按钮编号,从 1 开始。 - /// 登录模式:字符串数组 {AccessToken, RefreshToken} 或一个 Exception。 - /// - public object Result; - - public string Text; - public string Title; - public MyMsgBoxType Type; - - /// - /// 输入模式:输入验证规则。 - /// - public Collection> ValidateRules; - - public DispatcherFrame WaitFrame = new(true); - - public int[] ButtonIds = [1, 2, 3]; - - public Action? OnCloseCallback; - } - - public enum MyMsgBoxType - { - Text, - Select, - Input, - Login, - Markdown - } - - private static string GetDefaultDialogTitle() => Lang.Text("Common.Dialog.Title"); - - private static string GetDefaultConfirmText() => Lang.Text("Common.Action.Confirm"); - - private static string GetDefaultCancelText() => Lang.Text("Common.Action.Cancel"); - - /// - /// 显示弹窗,返回点击按钮的编号(从 1 开始)。 - /// - /// 弹窗的标题。 - /// 弹窗的内容。 - /// 显示的第一个按钮,默认为“确定”。 - /// 显示的第二个按钮,默认为空。 - /// 显示的第三个按钮,默认为空。 - /// 点击第一个按钮将执行该方法,不关闭弹窗。 - /// 点击第二个按钮将执行该方法,不关闭弹窗。 - /// 点击第三个按钮将执行该方法,不关闭弹窗。 - /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 - public static int MyMsgBox(string caption, string? title = null, string? button1 = null, string? button2 = "", - string? button3 = "", bool isWarn = false, bool highLight = true, bool forceWait = false, - Action button1Action = null, Action button2Action = null, Action button3Action = null) - { - title ??= GetDefaultDialogTitle(); - button1 ??= GetDefaultConfirmText(); - button2 ??= ""; - button3 ??= ""; - // 将弹窗列入队列 - var converter = new MyMsgBoxConverter - { - Type = MyMsgBoxType.Text, Button1 = button1, Button2 = button2, Button3 = button3, Text = caption, - IsWarn = isWarn, Title = title, HighLight = highLight, ForceWait = true, Button1Action = button1Action, - Button2Action = button2Action, Button3Action = button3Action - }; - WaitingMyMsgBox.Add(converter); - if (ModBase.RunInUi()) - // 若为 UI 线程,立即执行弹窗刻, 避免快速(连点器)点击时多次弹窗 - MyMsgBoxTick(); - if (button2.Length > 0 || forceWait) - { - // 若有多个按钮则开始等待 - if (frmMain is null || (frmMain.PanMsg is null && ModBase.RunInUi())) - { - // 主窗体尚未加载,用老土的弹窗来替代 - WaitingMyMsgBox.Remove(converter); - if (button2.Length > 0) - { - var rawResult = Interaction.MsgBox(caption, - (MsgBoxStyle)((int)(button3.Length > 0 ? MsgBoxStyle.YesNoCancel : MsgBoxStyle.YesNo) + - (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); - switch (rawResult) - { - case MsgBoxResult.Yes: - { - converter.Result = 1; - break; - } - case MsgBoxResult.No: - { - converter.Result = 2; - break; - } - case MsgBoxResult.Cancel: - { - converter.Result = 3; - break; - } - } - } - else - { - Interaction.MsgBox(caption, - (MsgBoxStyle)((int)MsgBoxStyle.OkOnly + - (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); - converter.Result = 1; - } - - ModBase.Log("[Control] 主窗体加载完成前出现意料外的等待弹窗:" + button1 + "," + button2 + "," + button3, - ModBase.LogLevel.Debug); - } - else - { - try - { - frmMain.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - } - - ModBase.Log($"[Control] 普通弹框返回:{converter.Result ?? "null"}"); - return (int)converter.Result; - } - - // 不进行等待,直接返回 - return 1; - } - - /// - /// 显示弹窗,返回点击按钮的编号(从 1 开始)。 - /// - /// 弹窗的标题。 - /// 弹窗的内容。 - /// 显示的第一个按钮,默认为“确定”。 - /// 显示的第二个按钮,默认为空。 - /// 显示的第三个按钮,默认为空。 - /// 点击第一个按钮将执行该方法,不关闭弹窗。 - /// 点击第二个按钮将执行该方法,不关闭弹窗。 - /// 点击第三个按钮将执行该方法,不关闭弹窗。 - /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 - public static int MyMsgBoxMarkdown(string caption, string? title = null, string? button1 = null, string? button2 = "", - string? button3 = "", bool isWarn = false, bool highLight = true, bool forceWait = false, - Action button1Action = null, Action button2Action = null, Action button3Action = null) - { - title ??= GetDefaultDialogTitle(); - button1 ??= GetDefaultConfirmText(); - button2 ??= ""; - button3 ??= ""; - // 将弹窗列入队列 - var converter = new MyMsgBoxConverter - { - Type = MyMsgBoxType.Markdown, Button1 = button1, Button2 = button2, Button3 = button3, Text = caption, - IsWarn = isWarn, Title = title, HighLight = highLight, ForceWait = true, Button1Action = button1Action, - Button2Action = button2Action, Button3Action = button3Action - }; - WaitingMyMsgBox.Add(converter); - if (ModBase.RunInUi()) - // 若为 UI 线程,立即执行弹窗刻, 避免快速(连点器)点击时多次弹窗 - MyMsgBoxTick(); - if (button2.Length > 0 || forceWait) - { - // 若有多个按钮则开始等待 - if (frmMain is null || (frmMain.PanMsg is null && ModBase.RunInUi())) - { - // 主窗体尚未加载,用老土的弹窗来替代 - WaitingMyMsgBox.Remove(converter); - if (button2.Length > 0) - { - var rawResult = Interaction.MsgBox(caption, - (MsgBoxStyle)((int)(button3.Length > 0 ? MsgBoxStyle.YesNoCancel : MsgBoxStyle.YesNo) + - (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); - switch (rawResult) - { - case MsgBoxResult.Yes: - { - converter.Result = 1; - break; - } - case MsgBoxResult.No: - { - converter.Result = 2; - break; - } - case MsgBoxResult.Cancel: - { - converter.Result = 3; - break; - } - } - } - else - { - Interaction.MsgBox(caption, - (MsgBoxStyle)((int)MsgBoxStyle.OkOnly + - (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); - converter.Result = 1; - } - - ModBase.Log("[Control] 主窗体加载完成前出现意料外的等待弹窗:" + button1 + "," + button2 + "," + button3, - ModBase.LogLevel.Debug); - } - else - { - try - { - frmMain.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - } - - ModBase.Log($"[Control] 普通弹框返回:{converter.Result ?? "null"}"); - return (int)converter.Result; - } - - // 不进行等待,直接返回 - return 1; - } - - /// - /// 显示输入框并返回输入的文本。若点击第二个按钮,则返回 Nothing。 - /// - /// 弹窗的标题。 - /// 文本框的输入检测。 - /// 弹窗的介绍文本。 - /// 文本框的默认内容。 - /// 文本框的提示内容。 - /// 显示的第一个按钮,默认为“确定”。 - /// 显示的第二个按钮,默认为“取消”。 - /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 - public static string MyMsgBoxInput(string title, string text = "", string defaultInput = "", - Collection>? validateRules = null, string hintText = "", string? button1 = null, - string? button2 = null, bool isWarn = false) - { - button1 ??= GetDefaultConfirmText(); - button2 ??= GetDefaultCancelText(); - // 将弹窗列入队列 - var converter = new MyMsgBoxConverter - { - Text = text, HintText = hintText, Type = MyMsgBoxType.Input, - ValidateRules = validateRules ?? [], Button1 = button1, Button2 = button2, - Content = defaultInput, IsWarn = isWarn, Title = title - }; - WaitingMyMsgBox.Add(converter); - // 虽然我也不知道这是啥但是能用就成了 :) - try - { - frmMain?.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - - ModBase.Log($"[Control] 输入弹框返回:{converter.Result}"); - return converter.Result?.ToString(); - } - - /// - /// 显示选择框并返回选择的第几项(从 0 开始)。若点击第二个按钮,则返回 Nothing。 - /// - /// 弹窗的标题。 - /// 显示的第一个按钮,默认为 “确定”。 - /// 显示的第二个按钮,默认为空。 - /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 - public static int? MyMsgBoxSelect(List selections, string? title = null, string? button1 = null, - string? button2 = "", bool isWarn = false) - { - title ??= GetDefaultDialogTitle(); - button1 ??= GetDefaultConfirmText(); - button2 ??= ""; - // 将弹窗列入队列 - var converter = new MyMsgBoxConverter - { - Type = MyMsgBoxType.Select, Button1 = button1, Button2 = button2, Content = selections, IsWarn = isWarn, - Title = title - }; - WaitingMyMsgBox.Add(converter); - // 虽然我也不知道这是啥但是能用就成了 :) - try - { - if (frmMain is not null) - frmMain.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - - ModBase.Log($"[Control] 选择弹框返回:{converter.Result ?? "null"}"); - return (int?)converter.Result; - } - - - public static void MyMsgBoxTick() - { - try - { - if (frmMain is null || frmMain.PanMsg is null || frmMain.WindowState == WindowState.Minimized) - return; - if (frmMain.PanMsg.Children.Count > 0) - { - // 弹窗中 - frmMain.PanMsgBackground.Visibility = Visibility.Visible; - } - else if (WaitingMyMsgBox.Any()) - { - // 没有弹窗,显示一个等待的弹窗 - frmMain.PanMsgBackground.Visibility = Visibility.Visible; - var converter = WaitingMyMsgBox[0]; - var dialog = CreateDialogFromConverter(converter); - if (dialog is not null) - frmMain.PanMsg.Children.Add(dialog); - - WaitingMyMsgBox.RemoveAt(0); - } - // 没有弹窗,没有等待的弹窗 - else if (!(frmMain.PanMsgBackground.Visibility == Visibility.Collapsed)) - { - frmMain.PanMsgBackground.Visibility = Visibility.Collapsed; - } - } - catch (Exception ex) - { - ModBase.Log(ex, "处理等待中的弹窗失败", ModBase.LogLevel.Feedback); - } - } - - private static DialogControl? CreateDialogFromConverter(MyMsgBoxConverter conv) - { - switch (conv.Type) - { - case MyMsgBoxType.Input: - return CreateInputDialog(conv); - case MyMsgBoxType.Select: - return CreateSelectDialog(conv); - case MyMsgBoxType.Login: - // MyMsgLogin manages its own chrome + lifecycle - frmMain?.PanMsg.Children.Add(new MyMsgLogin(conv)); - return null; - case MyMsgBoxType.Markdown: - { - var mdViewer = new Markdig.Wpf.MarkdownViewer { Markdown = conv.Text }; - return CreateStandardDialog(conv, mdViewer); - } - default: - { - var content = conv.Content as UIElement - ?? new TextBlock { Text = conv.Text, TextWrapping = TextWrapping.Wrap, FontSize = 15 }; - return CreateStandardDialog(conv, content); - } - } - } - - private static DialogControl CreateStandardDialog(MyMsgBoxConverter conv, UIElement content) - { - var dialog = new DialogControl - { - Title = conv.Title, - IsWarn = conv.IsWarn, - DialogContent = content, - }; - dialog.AddButton(conv.Button1, conv.Button1Action, isPrimary: true, id: conv.ButtonIds[0]); - if (!string.IsNullOrEmpty(conv.Button2)) - dialog.AddButton(conv.Button2, conv.Button2Action, id: conv.ButtonIds[1]); - if (!string.IsNullOrEmpty(conv.Button3)) - dialog.AddButton(conv.Button3, conv.Button3Action, id: conv.ButtonIds[2]); - conv.WaitFrame = dialog.WaitFrame; - dialog.OnClosed += result => - { - conv.Result = result; - conv.OnCloseCallback?.Invoke(result); - }; - return dialog; - } - - private static DialogControl CreateInputDialog(MyMsgBoxConverter conv) - { - var stack = new StackPanel(); - if (!string.IsNullOrEmpty(conv.Text)) - { - stack.Children.Add(new TextBlock - { - Text = conv.Text, - TextWrapping = TextWrapping.Wrap, - FontSize = 15, - Margin = new Thickness(0, 0, 0, 7), - }); - } - - var textBox = new MyTextBox - { - Text = (string)(conv.Content ?? ""), - HintText = conv.HintText, - ValidateRules = conv.ValidateRules ?? [], - MinWidth = 450, - }; - stack.Children.Add(textBox); - - var dialog = new DialogControl - { - Title = conv.Title, - IsWarn = conv.IsWarn, - DialogContent = stack, - }; - - dialog.AddButton(conv.Button1, onClick: () => - { - textBox.Validate(); - if (!textBox.IsValidated) return; - conv.Result = textBox.Text; - dialog.Close(conv.ButtonIds[0]); - }, isPrimary: true, id: conv.ButtonIds[0]); - - if (!string.IsNullOrEmpty(conv.Button2)) - { - dialog.AddButton(conv.Button2, onClick: () => - { - conv.Result = null; - dialog.Close(conv.ButtonIds[1]); - }, id: conv.ButtonIds[1]); - } - - conv.WaitFrame = dialog.WaitFrame; - return dialog; - } - - private static DialogControl CreateSelectDialog(MyMsgBoxConverter conv) - { - var panel = new StackPanel(); - var dialog = new DialogControl - { - Title = conv.Title, - IsWarn = conv.IsWarn, - DialogContent = panel, - }; - - var b1 = dialog.AddButton(conv.Button1, onClick: () => - { - // handled by extra Click below - }, isPrimary: true, id: conv.ButtonIds[0]); - b1.IsEnabled = false; - - if (!string.IsNullOrEmpty(conv.Button2)) - { - dialog.AddButton(conv.Button2, onClick: () => - { - conv.Result = null; - dialog.Close(conv.ButtonIds[1]); - }, id: conv.ButtonIds[1]); - } - - var selectedIndex = -1; - var index = 0; - foreach (var raw in (IEnumerable)conv.Content!) - { - var item = MyVirtualizingElement.TryInit((FrameworkElement)raw); - if (item is IMyRadio radio) - { - if (item is MyListItem listItem) - { - listItem.Type = MyListItem.CheckType.RadioBox; - listItem.MinHeight = 24; - } - else if (item is MyRadioBox radioBox) - { - radioBox.MinHeight = 24; - } - - var currentIndex = index; - radio.Check += (_, _) => - { - selectedIndex = currentIndex; - b1.IsEnabled = true; - }; - panel.Children.Add((UIElement)radio); - index++; - } - } - - // Override Btn1's dummy onClick with real logic - b1.Click += (_, _) => - { - if (selectedIndex < 0) return; - conv.Result = selectedIndex; - dialog.Close(conv.ButtonIds[0]); - }; - - conv.WaitFrame = dialog.WaitFrame; - return dialog; - } - - public static void MsgBoxWrapper_OnShow(string message, string caption, ICollection buttons, - MsgBoxTheme theme, bool block, ref int result) - { - var btnText1 = buttons.Count < 1 ? GetDefaultConfirmText() : buttons.ElementAt(0).Context; - var btnAct1 = (Action)(buttons.Count < 1 ? (object)null : buttons.ElementAt(0).OnClick); - var btnText2 = buttons.Count < 2 ? GetDefaultCancelText() : buttons.ElementAt(1).Context; - var btnAct2 = (Action)(buttons.Count < 2 ? (object)null : buttons.ElementAt(1).OnClick); - var btnText3 = buttons.Count < 3 ? "" : buttons.ElementAt(2).Context; - var btnAct3 = (Action)(buttons.Count < 3 ? (object)null : buttons.ElementAt(2).OnClick); - - var isWarn = theme == MsgBoxTheme.Warning || theme == MsgBoxTheme.Error; - - result = MyMsgBox(message, caption, btnText1, btnText2, btnText3, isWarn, forceWait: block, - button1Action: btnAct1, button2Action: btnAct2, button3Action: btnAct3); - } - - public static void Dialog_OnShow(DialogContext context) - { - if (frmMain is null) - { - Interaction.MsgBox(context.Caption, MsgBoxStyle.OkOnly, context.Title); - context.Result = 1; - context.OnClosed?.Invoke(1); - return; - } - - if (frmMain.Dispatcher.CheckAccess()) - ShowDialogOnUi(context); - else - frmMain.Dispatcher.Invoke(() => ShowDialogOnUi(context)); - } - - private static void ShowDialogOnUi(DialogContext context) - { - if (frmMain?.PanMsg is null) return; - - var isWarn = context.Theme == DialogTheme.Warning || context.Theme == DialogTheme.Error; - - var content = context.Content; - if (content is null && !string.IsNullOrEmpty(context.Caption)) - { - content = new TextBlock - { - Text = context.Caption, - TextWrapping = TextWrapping.Wrap, - FontSize = 15, - }; - } - - var dialog = new DialogControl - { - Title = context.Title, - IsWarn = isWarn, - DialogContent = content as UIElement, - }; - - for (var i = 0; i < context.Buttons.Count && i < 3; i++) - { - var btn = context.Buttons[i]; - var isPrimary = i == 0 || btn.IsPrimary; - var id = btn.Id > 0 ? btn.Id : i + 1; - dialog.AddButton(btn.Text, btn.OnClick, isPrimary, id); - } - - frmMain.PanMsg.Children.Add(dialog); - - if (context.Block) - { - try - { - frmMain.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(dialog.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - context.Result = dialog.Result; - context.OnClosed?.Invoke(context.Result); - } - else - { - dialog.OnClosed += result => - { - context.Result = result; - context.OnClosed?.Invoke(result); - }; - } - } - - #endregion - #region 愚人节 public static bool isAprilEnabled = DateTime.Now.Month == 4 && DateTime.Now.Day == 1; diff --git a/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs b/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs index d3de30753..d6b545076 100644 --- a/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs @@ -679,11 +679,7 @@ private void BtnDialogTest_Click(object sender, MouseButtonEventArgs e) Title = "弹窗系统展示", Content = panel, Theme = DialogTheme.Info, - Buttons = - [ - new DialogButton("确认", isPrimary: true), - new DialogButton("取消"), - ], + Buttons = [DialogButton.Confirm()], }); } From f7ad35d169ac3f8b505e578a4f01f84d99ef9e9a Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 21:56:12 +0800 Subject: [PATCH 08/14] refactor(dialog): DialogManager with XamlRoot, split into clean files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dialog/DialogManager.cs — orchestrator init with FormMain, Show/ShowAsync, ShowText/ShowMarkdown/ShowInput/ShowSelect conveniences, converter logic. Dialog/DialogContext.cs, DialogButton.cs, DialogResult.cs, DialogTheme.cs — type files (moved from PCL.Core.UI). Dialog/LegacyDialog.cs — partial ModMain: MyMsgBoxConverter, WaitingMyMsgBox, thin wrappers delegating to DialogManager.Instance. PCL.Core/UI/MsgBoxWrapper.cs — reverted to event pattern, no Dialog dependency. Removed: PCL.Core/UI/Dialog.cs, Modules/ModMain.Dialog.cs (merged above). ModMain.cs timer calls DialogManager.Instance.Tick(). --- PCL.Core/UI/Dialog.cs | 131 ---- PCL.Core/UI/MsgBoxWrapper.cs | 22 +- Plain Craft Launcher 2/Dialog/DialogButton.cs | 32 + .../Dialog/DialogContext.cs | 16 + .../Dialog/DialogManager.cs | 444 ++++++++++++ Plain Craft Launcher 2/Dialog/DialogResult.cs | 9 + Plain Craft Launcher 2/Dialog/DialogTheme.cs | 8 + Plain Craft Launcher 2/Dialog/LegacyDialog.cs | 120 ++++ Plain Craft Launcher 2/FormMain.xaml.cs | 4 +- .../Modules/ModMain.Dialog.cs | 674 ------------------ Plain Craft Launcher 2/Modules/ModMain.cs | 2 +- .../Pages/PageTools/PageToolsTest.xaml.cs | 2 +- 12 files changed, 637 insertions(+), 827 deletions(-) delete mode 100644 PCL.Core/UI/Dialog.cs create mode 100644 Plain Craft Launcher 2/Dialog/DialogButton.cs create mode 100644 Plain Craft Launcher 2/Dialog/DialogContext.cs create mode 100644 Plain Craft Launcher 2/Dialog/DialogManager.cs create mode 100644 Plain Craft Launcher 2/Dialog/DialogResult.cs create mode 100644 Plain Craft Launcher 2/Dialog/DialogTheme.cs create mode 100644 Plain Craft Launcher 2/Dialog/LegacyDialog.cs delete mode 100644 Plain Craft Launcher 2/Modules/ModMain.Dialog.cs diff --git a/PCL.Core/UI/Dialog.cs b/PCL.Core/UI/Dialog.cs deleted file mode 100644 index 019ae9a2f..000000000 --- a/PCL.Core/UI/Dialog.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Threading.Tasks; -using PCL.Core.App.Localization; - -namespace PCL.Core.UI; - -/// -/// Standard dialog result IDs for button matching. -/// -public static class DialogResult -{ - public const int Ok = 1; - public const int Cancel = 2; - public const int Yes = 6; - public const int No = 7; -} - -public class DialogButton -{ - public string Text { get; set; } - public int Id { get; set; } - public Action? OnClick { get; set; } - public bool IsPrimary { get; set; } - - public DialogButton(string text, Action? onClick = null, bool isPrimary = false, int id = 0) - { - Text = text; - OnClick = onClick; - IsPrimary = isPrimary; - Id = id; - } - - // -- presets -- - - public static DialogButton Confirm(string? text = null) - => new(text ?? Lang.Text("Common.Action.Confirm"), isPrimary: true, id: DialogResult.Ok); - - public static DialogButton Cancel(string? text = null) - => new(text ?? Lang.Text("Common.Action.Cancel"), id: DialogResult.Cancel); - - public static DialogButton Yes(string? text = null) - => new(text ?? Lang.Text("Common.Option.Yes"), isPrimary: true, id: DialogResult.Yes); - - public static DialogButton No(string? text = null) - => new(text ?? Lang.Text("Common.Option.No"), id: DialogResult.No); -} - -public static class Dialog -{ - public static event Action? OnShow; - - public static int Show(string caption, string? title = null, - DialogTheme theme = DialogTheme.Info, bool block = true, - params string[] buttons) - { - return Show(new DialogContext - { - Caption = caption, - Title = title ?? Lang.Text("Common.Dialog.Title"), - Theme = theme, - Block = block, - Content = null, - Buttons = _BuildButtons(buttons), - }); - } - - public static int Show(DialogContext context) - { - if (context.Buttons.Count == 0) - context.Buttons.Add(DialogButton.Confirm()); - context.Block = true; - context.OnClosed = null; - OnShow?.Invoke(context); - return context.Result; - } - - public static Task ShowAsync(DialogContext context) - { - if (context.Buttons.Count == 0) - context.Buttons.Add(DialogButton.Confirm()); - var tcs = new TaskCompletionSource(); - context.Block = false; - context.OnClosed = result => tcs.TrySetResult(result); - OnShow?.Invoke(context); - return tcs.Task; - } - - public static Task ShowAsync(string caption, string? title = null, - DialogTheme theme = DialogTheme.Info, params string[] buttons) - { - return ShowAsync(new DialogContext - { - Caption = caption, - Title = title ?? Lang.Text("Common.Dialog.Title"), - Theme = theme, - Content = null, - Buttons = _BuildButtons(buttons), - }); - } - - private static Collection _BuildButtons(string[] buttonTexts) - { - var list = new Collection(); - for (var i = 0; i < buttonTexts.Length; i++) - { - list.Add(new DialogButton(buttonTexts[i], isPrimary: i == 0, id: i + 1)); - } - return list; - } -} - -public enum DialogTheme -{ - Info, - Warning, - Error -} - -public class DialogContext -{ - public string Caption { get; set; } = ""; - public string Title { get; set; } = ""; - public DialogTheme Theme { get; set; } = DialogTheme.Info; - public bool Block { get; set; } = true; - public object? Content { get; set; } - public Collection Buttons { get; set; } = []; - public int Result { get; set; } - public Action? OnClosed { get; set; } -} diff --git a/PCL.Core/UI/MsgBoxWrapper.cs b/PCL.Core/UI/MsgBoxWrapper.cs index 5c843d14e..3861e3761 100644 --- a/PCL.Core/UI/MsgBoxWrapper.cs +++ b/PCL.Core/UI/MsgBoxWrapper.cs @@ -1,19 +1,16 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using PCL.Core.App.Localization; namespace PCL.Core.UI; -[Obsolete("Use PCL.Core.UI.Dialog instead")] public record MsgBoxButtonInfo( string Context, int Value = 0, Action? OnClick = null ); -[Obsolete("Use PCL.Core.UI.DialogTheme instead")] public enum MsgBoxTheme { Info, @@ -30,7 +27,6 @@ public delegate void MsgBoxHandler( ref int result ); -[Obsolete("Use PCL.Core.UI.Dialog instead")] public static class MsgBoxWrapper { public static event MsgBoxHandler? OnShow; @@ -42,20 +38,10 @@ public static int ShowWithCustomButtons( bool block, ICollection buttonCollection) { - var buttons = new Collection(); - foreach (var btn in buttonCollection) - { - buttons.Add(new DialogButton(btn.Context, btn.OnClick)); - } - var result = Dialog.Show(new DialogContext - { - Caption = message, - Title = caption, - Theme = (DialogTheme)(int)theme, - Block = block, - Content = null, - Buttons = buttons, - }); + var result = 0; + if (buttonCollection.Count == 0) + buttonCollection = [new MsgBoxButtonInfo(Lang.Text("Common.Action.Confirm"))]; + OnShow?.Invoke(message, caption, buttonCollection, theme, block, ref result); return result; } diff --git a/Plain Craft Launcher 2/Dialog/DialogButton.cs b/Plain Craft Launcher 2/Dialog/DialogButton.cs new file mode 100644 index 000000000..53f360af7 --- /dev/null +++ b/Plain Craft Launcher 2/Dialog/DialogButton.cs @@ -0,0 +1,32 @@ +using System; +using PCL.Core.App.Localization; + +namespace PCL.Core.UI; + +public class DialogButton +{ + public string Text { get; set; } + public int Id { get; set; } + public Action? OnClick { get; set; } + public bool IsPrimary { get; set; } + + public DialogButton(string text, Action? onClick = null, bool isPrimary = false, int id = 0) + { + Text = text; + OnClick = onClick; + IsPrimary = isPrimary; + Id = id; + } + + public static DialogButton Confirm(string? text = null) + => new(text ?? Lang.Text("Common.Action.Confirm"), isPrimary: true, id: DialogResult.Ok); + + public static DialogButton Cancel(string? text = null) + => new(text ?? Lang.Text("Common.Action.Cancel"), id: DialogResult.Cancel); + + public static DialogButton Yes(string? text = null) + => new(text ?? Lang.Text("Common.Option.Yes"), isPrimary: true, id: DialogResult.Yes); + + public static DialogButton No(string? text = null) + => new(text ?? Lang.Text("Common.Option.No"), id: DialogResult.No); +} diff --git a/Plain Craft Launcher 2/Dialog/DialogContext.cs b/Plain Craft Launcher 2/Dialog/DialogContext.cs new file mode 100644 index 000000000..f540f42b7 --- /dev/null +++ b/Plain Craft Launcher 2/Dialog/DialogContext.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.ObjectModel; + +namespace PCL.Core.UI; + +public class DialogContext +{ + public string Caption { get; set; } = ""; + public string Title { get; set; } = ""; + public DialogTheme Theme { get; set; } = DialogTheme.Info; + public bool Block { get; set; } = true; + public object? Content { get; set; } + public Collection Buttons { get; set; } = []; + public int Result { get; set; } + public Action? OnClosed { get; set; } +} diff --git a/Plain Craft Launcher 2/Dialog/DialogManager.cs b/Plain Craft Launcher 2/Dialog/DialogManager.cs new file mode 100644 index 000000000..ab0a10be8 --- /dev/null +++ b/Plain Craft Launcher 2/Dialog/DialogManager.cs @@ -0,0 +1,444 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Interop; +using System.Windows.Threading; +using Microsoft.VisualBasic; +using PCL.Core.App.Localization; +using PCL.Core.UI; + +namespace PCL; + +public class DialogManager +{ + public static DialogManager? Instance { get; private set; } + + private readonly FormMain _mainForm; + + public DialogManager(FormMain mainForm) + { + _mainForm = mainForm; + Instance = this; + } + + // -- new API -- + + public int Show(DialogContext context) + { + if (context.Buttons.Count == 0) + context.Buttons.Add(DialogButton.Confirm()); + context.Block = true; + ShowOnUi(context); + return context.Result; + } + + public Task ShowAsync(DialogContext context) + { + if (context.Buttons.Count == 0) + context.Buttons.Add(DialogButton.Confirm()); + var tcs = new TaskCompletionSource(); + context.Block = false; + context.OnClosed = result => tcs.TrySetResult(result); + ShowOnUi(context); + return tcs.Task; + } + + // -- convenience methods -- + + public int ShowText(string caption, string? title = null, + DialogTheme theme = DialogTheme.Info, bool block = true, + params string[] buttons) + { + return Show(new DialogContext + { + Caption = caption, + Title = title ?? Lang.Text("Common.Dialog.Title"), + Theme = theme, + Block = block, + Buttons = _BuildButtons(buttons), + }); + } + + public int ShowMarkdown(string markdown, string? title = null, + DialogTheme theme = DialogTheme.Info, bool block = true, + params string[] buttons) + { + return Show(new DialogContext + { + Caption = markdown, + Title = title ?? Lang.Text("Common.Dialog.Title"), + Theme = theme, + Block = block, + Buttons = _BuildButtons(buttons), + // Markdown content is created in ShowOnUi via CreateDialogFromContext + }); + } + + public string ShowInput(string title, string text = "", string defaultInput = "", + Collection>? validateRules = null, + string hintText = "", string? button1 = null, string? button2 = null, bool isWarn = false) + { + button1 ??= Lang.Text("Common.Action.Confirm"); + button2 ??= Lang.Text("Common.Action.Cancel"); + var converter = new ModMain.MyMsgBoxConverter + { + Text = text, + HintText = hintText, + Type = ModMain.MyMsgBoxType.Input, + ValidateRules = validateRules ?? [], + Button1 = button1, + Button2 = button2, + Content = defaultInput, + IsWarn = isWarn, + Title = title, + }; + ModMain.WaitingMyMsgBox.Add(converter); + try + { + if (_mainForm is not null) + _mainForm.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(converter.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + return converter.Result?.ToString() ?? ""; + } + + public int? ShowSelect(List selections, string? title = null, + string? button1 = null, string? button2 = "", bool isWarn = false) + { + title ??= Lang.Text("Common.Dialog.Title"); + button1 ??= Lang.Text("Common.Action.Confirm"); + button2 ??= ""; + var converter = new ModMain.MyMsgBoxConverter + { + Type = ModMain.MyMsgBoxType.Select, + Button1 = button1, + Button2 = button2, + Content = selections, + IsWarn = isWarn, + Title = title, + }; + ModMain.WaitingMyMsgBox.Add(converter); + try + { + if (_mainForm is not null) + _mainForm.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(converter.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + return (int?)converter.Result; + } + + // -- legacy bridge: called by MsgBoxWrapper via ModMain.MsgBoxWrapper_OnShow -- + + public int ShowLegacy(string message, string caption, ICollection buttons, + MsgBoxTheme theme, bool block) + { + var ctx = new DialogContext + { + Caption = message, + Title = caption, + Theme = (DialogTheme)(int)theme, + Block = block, + Buttons = new Collection(), + }; + foreach (var btn in buttons) + ctx.Buttons.Add(new DialogButton(btn.Context, btn.OnClick)); + return Show(ctx); + } + + // -- tick: called from ModMain timer -- + + public void Tick() + { + try + { + if (_mainForm is null || _mainForm.PanMsg is null || _mainForm.WindowState == WindowState.Minimized) + return; + if (_mainForm.PanMsg.Children.Count > 0) + { + _mainForm.PanMsgBackground.Visibility = Visibility.Visible; + } + else if (ModMain.WaitingMyMsgBox.Any()) + { + _mainForm.PanMsgBackground.Visibility = Visibility.Visible; + var converter = ModMain.WaitingMyMsgBox[0]; + var dialog = CreateDialogFromConverter(converter); + if (dialog is not null) + _mainForm.PanMsg.Children.Add(dialog); + ModMain.WaitingMyMsgBox.RemoveAt(0); + } + else if (_mainForm.PanMsgBackground.Visibility != Visibility.Collapsed) + { + _mainForm.PanMsgBackground.Visibility = Visibility.Collapsed; + } + } + catch (Exception ex) + { + ModBase.Log(ex, "对话框 Tick 失败", ModBase.LogLevel.Feedback); + } + } + + // -- converter helpers (legacy queue → DialogControl) -- + + private static DialogControl? CreateDialogFromConverter(ModMain.MyMsgBoxConverter conv) + { + switch (conv.Type) + { + case ModMain.MyMsgBoxType.Input: + return CreateInputDialog(conv); + case ModMain.MyMsgBoxType.Select: + return CreateSelectDialog(conv); + case ModMain.MyMsgBoxType.Login: + Instance?._mainForm?.PanMsg.Children.Add(new MyMsgLogin(conv)); + return null; + case ModMain.MyMsgBoxType.Markdown: + { + var mdViewer = new Markdig.Wpf.MarkdownViewer { Markdown = conv.Text }; + return CreateStandardDialog(conv, mdViewer); + } + default: + { + var content = conv.Content as UIElement + ?? new TextBlock { Text = conv.Text, TextWrapping = TextWrapping.Wrap, FontSize = 15 }; + return CreateStandardDialog(conv, content); + } + } + } + + private static DialogControl CreateStandardDialog(ModMain.MyMsgBoxConverter conv, UIElement content) + { + var dialog = new DialogControl + { + Title = conv.Title, + IsWarn = conv.IsWarn, + DialogContent = content, + }; + dialog.AddButton(conv.Button1, conv.Button1Action, isPrimary: true, id: conv.ButtonIds[0]); + if (!string.IsNullOrEmpty(conv.Button2)) + dialog.AddButton(conv.Button2, conv.Button2Action, id: conv.ButtonIds[1]); + if (!string.IsNullOrEmpty(conv.Button3)) + dialog.AddButton(conv.Button3, conv.Button3Action, id: conv.ButtonIds[2]); + conv.WaitFrame = dialog.WaitFrame; + dialog.OnClosed += result => + { + conv.Result = result; + conv.OnCloseCallback?.Invoke(result); + }; + return dialog; + } + + private static DialogControl CreateInputDialog(ModMain.MyMsgBoxConverter conv) + { + var stack = new StackPanel(); + if (!string.IsNullOrEmpty(conv.Text)) + { + stack.Children.Add(new TextBlock + { + Text = conv.Text, + TextWrapping = TextWrapping.Wrap, + FontSize = 15, + Margin = new Thickness(0, 0, 0, 7), + }); + } + + var textBox = new MyTextBox + { + Text = (string)(conv.Content ?? ""), + HintText = conv.HintText, + ValidateRules = conv.ValidateRules ?? [], + MinWidth = 450, + }; + stack.Children.Add(textBox); + + var dialog = new DialogControl + { + Title = conv.Title, + IsWarn = conv.IsWarn, + DialogContent = stack, + }; + + dialog.AddButton(conv.Button1, onClick: () => + { + textBox.Validate(); + if (!textBox.IsValidated) return; + conv.Result = textBox.Text; + dialog.Close(conv.ButtonIds[0]); + }, isPrimary: true, id: conv.ButtonIds[0]); + + if (!string.IsNullOrEmpty(conv.Button2)) + { + dialog.AddButton(conv.Button2, onClick: () => + { + conv.Result = null; + dialog.Close(conv.ButtonIds[1]); + }, id: conv.ButtonIds[1]); + } + + conv.WaitFrame = dialog.WaitFrame; + return dialog; + } + + private static DialogControl CreateSelectDialog(ModMain.MyMsgBoxConverter conv) + { + var panel = new StackPanel(); + var dialog = new DialogControl + { + Title = conv.Title, + IsWarn = conv.IsWarn, + DialogContent = panel, + }; + + var b1 = dialog.AddButton(conv.Button1, onClick: () => + { + // handled by extra Click below + }, isPrimary: true, id: conv.ButtonIds[0]); + b1.IsEnabled = false; + + if (!string.IsNullOrEmpty(conv.Button2)) + { + dialog.AddButton(conv.Button2, onClick: () => + { + conv.Result = null; + dialog.Close(conv.ButtonIds[1]); + }, id: conv.ButtonIds[1]); + } + + var selectedIndex = -1; + var index = 0; + foreach (var raw in (IEnumerable)conv.Content!) + { + var item = MyVirtualizingElement.TryInit((FrameworkElement)raw); + if (item is IMyRadio radio) + { + if (item is MyListItem listItem) + { + listItem.Type = MyListItem.CheckType.RadioBox; + listItem.MinHeight = 24; + } + else if (item is MyRadioBox radioBox) + { + radioBox.MinHeight = 24; + } + + var currentIndex = index; + radio.Check += (_, _) => + { + selectedIndex = currentIndex; + b1.IsEnabled = true; + }; + panel.Children.Add((UIElement)radio); + index++; + } + } + + // Override Btn1's dummy onClick with real logic + b1.Click += (_, _) => + { + if (selectedIndex < 0) return; + conv.Result = selectedIndex; + dialog.Close(conv.ButtonIds[0]); + }; + + conv.WaitFrame = dialog.WaitFrame; + return dialog; + } + + // -- internal show logic -- + + private void ShowOnUi(DialogContext context) + { + if (_mainForm is null) + { + Interaction.MsgBox(context.Caption, MsgBoxStyle.OkOnly, context.Title); + context.Result = 1; + context.OnClosed?.Invoke(1); + return; + } + + if (_mainForm.Dispatcher.CheckAccess()) + ShowDialogOnUiThread(context); + else + _mainForm.Dispatcher.Invoke(() => ShowDialogOnUiThread(context)); + } + + private void ShowDialogOnUiThread(DialogContext context) + { + if (_mainForm?.PanMsg is null) return; + + var isWarn = context.Theme == DialogTheme.Warning || context.Theme == DialogTheme.Error; + + var content = context.Content; + if (content is null && !string.IsNullOrEmpty(context.Caption)) + { + content = new TextBlock + { + Text = context.Caption, + TextWrapping = TextWrapping.Wrap, + FontSize = 15, + }; + } + + var dialog = new DialogControl + { + Title = context.Title, + IsWarn = isWarn, + DialogContent = content as UIElement, + }; + + for (var i = 0; i < context.Buttons.Count && i < 3; i++) + { + var btn = context.Buttons[i]; + var isPrimary = i == 0 || btn.IsPrimary; + var id = btn.Id > 0 ? btn.Id : i + 1; + dialog.AddButton(btn.Text, btn.OnClick, isPrimary, id); + } + + _mainForm.PanMsg.Children.Add(dialog); + + if (context.Block) + { + try + { + _mainForm.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(dialog.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + context.Result = dialog.Result; + context.OnClosed?.Invoke(context.Result); + } + else + { + dialog.OnClosed += result => + { + context.Result = result; + context.OnClosed?.Invoke(result); + }; + } + } + + private static Collection _BuildButtons(string[] buttonTexts) + { + var list = new Collection(); + for (var i = 0; i < buttonTexts.Length; i++) + list.Add(new DialogButton(buttonTexts[i], isPrimary: i == 0, id: i + 1)); + return list; + } +} diff --git a/Plain Craft Launcher 2/Dialog/DialogResult.cs b/Plain Craft Launcher 2/Dialog/DialogResult.cs new file mode 100644 index 000000000..f733f92d9 --- /dev/null +++ b/Plain Craft Launcher 2/Dialog/DialogResult.cs @@ -0,0 +1,9 @@ +namespace PCL.Core.UI; + +public static class DialogResult +{ + public const int Ok = 1; + public const int Cancel = 2; + public const int Yes = 6; + public const int No = 7; +} diff --git a/Plain Craft Launcher 2/Dialog/DialogTheme.cs b/Plain Craft Launcher 2/Dialog/DialogTheme.cs new file mode 100644 index 000000000..565c0f242 --- /dev/null +++ b/Plain Craft Launcher 2/Dialog/DialogTheme.cs @@ -0,0 +1,8 @@ +namespace PCL.Core.UI; + +public enum DialogTheme +{ + Info, + Warning, + Error +} diff --git a/Plain Craft Launcher 2/Dialog/LegacyDialog.cs b/Plain Craft Launcher 2/Dialog/LegacyDialog.cs new file mode 100644 index 000000000..49eedc60a --- /dev/null +++ b/Plain Craft Launcher 2/Dialog/LegacyDialog.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows.Threading; +using FluentValidation; +using PCL.Core.App.Localization; +using PCL.Core.UI; + +namespace PCL; + +public static partial class ModMain +{ + /// + /// 等待显示的弹窗。 + /// + public static List WaitingMyMsgBox { get; } = []; + + public class MyMsgBoxConverter + { + public object AuthUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; + public string Button1 = ""; + public Action? Button1Action; + public string Button2 = ""; + public Action? Button2Action; + public string Button3 = ""; + public Action? Button3Action; + public object? Content; + public bool ForceWait; + public bool HighLight; + public string HintText = ""; + public bool IsExited; + public bool IsWarn; + public object? Result; + public string? Text; + public string? Title; + public MyMsgBoxType Type; + public Collection>? ValidateRules; + public DispatcherFrame WaitFrame = new(true); + public int[] ButtonIds = [1, 2, 3]; + public Action? OnCloseCallback; + } + + public enum MyMsgBoxType + { + Text, Select, Input, Login, Markdown + } + + #region Legacy wrappers — delegate to DialogManager + + public static int MyMsgBox(string caption, string? title = null, string? button1 = null, string? button2 = "", + string? button3 = "", bool isWarn = false, bool highLight = true, bool forceWait = false, + Action? button1Action = null, Action? button2Action = null, Action? button3Action = null) + { + var mgr = DialogManager.Instance; + if (mgr is null) return 1; + + var buttons = new List(); + if (!string.IsNullOrEmpty(button1)) buttons.Add(button1!); + if (!string.IsNullOrEmpty(button2)) buttons.Add(button2!); + if (!string.IsNullOrEmpty(button3)) buttons.Add(button3!); + if (buttons.Count == 0) buttons.Add(Lang.Text("Common.Action.Confirm")); + + return mgr.ShowText(caption, title, + isWarn ? DialogTheme.Warning : DialogTheme.Info, + forceWait || buttons.Count > 1, + buttons.ToArray()); + } + + public static int MyMsgBoxMarkdown(string caption, string? title = null, string? button1 = null, string? button2 = "", + string? button3 = "", bool isWarn = false, bool highLight = true, bool forceWait = false, + Action? button1Action = null, Action? button2Action = null, Action? button3Action = null) + { + var mgr = DialogManager.Instance; + if (mgr is null) return 1; + + var buttons = new List(); + if (!string.IsNullOrEmpty(button1)) buttons.Add(button1!); + if (!string.IsNullOrEmpty(button2)) buttons.Add(button2!); + if (!string.IsNullOrEmpty(button3)) buttons.Add(button3!); + if (buttons.Count == 0) buttons.Add(Lang.Text("Common.Action.Confirm")); + + return mgr.ShowMarkdown(caption, title, + isWarn ? DialogTheme.Warning : DialogTheme.Info, + forceWait || buttons.Count > 1, + buttons.ToArray()); + } + + public static string MyMsgBoxInput(string title, string text = "", string defaultInput = "", + Collection>? validateRules = null, string hintText = "", string? button1 = null, + string? button2 = null, bool isWarn = false) + { + return DialogManager.Instance?.ShowInput(title, text, defaultInput, + validateRules, hintText, button1, button2, isWarn) ?? ""; + } + + public static int? MyMsgBoxSelect(List selections, string? title = null, string? button1 = null, + string? button2 = "", bool isWarn = false) + { + return DialogManager.Instance?.ShowSelect(selections, title, button1, button2, isWarn); + } + + public static void MsgBoxWrapper_OnShow(string message, string caption, ICollection buttons, + MsgBoxTheme theme, bool block, ref int result) + { + result = DialogManager.Instance?.ShowLegacy(message, caption, buttons, theme, block) ?? 1; + } + + public static void Dialog_OnShow(DialogContext context) + { + // Forward to DialogManager for backward compat with PCL.Core.UI.Dialog.OnShow event + if (DialogManager.Instance is null) return; + if (context.Block) + DialogManager.Instance.Show(context); + else + _ = DialogManager.Instance.ShowAsync(context); + } + + #endregion +} diff --git a/Plain Craft Launcher 2/FormMain.xaml.cs b/Plain Craft Launcher 2/FormMain.xaml.cs index 83187d57f..1b3b2d837 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.cs +++ b/Plain Craft Launcher 2/FormMain.xaml.cs @@ -105,10 +105,10 @@ public FormMain() // 注册拖拽事件(不能直接加 Handles,否则没用;#6340) AddHandler(DragDrop.DragEnterEvent, new DragEventHandler(HandleDrag), true); AddHandler(DragDrop.DragOverEvent, new DragEventHandler(HandleDrag), true); + // 初始化 DialogManager + new DialogManager(this); // 注册 MsgBox 事件 MsgBoxWrapper.OnShow += ModMain.MsgBoxWrapper_OnShow; - // 注册 Dialog 事件 - PCL.Core.UI.Dialog.OnShow += ModMain.Dialog_OnShow; // 注册 Hint 事件 HintWrapper.OnShow += ModMain.HintWrapper_OnShow; // 加载 UI diff --git a/Plain Craft Launcher 2/Modules/ModMain.Dialog.cs b/Plain Craft Launcher 2/Modules/ModMain.Dialog.cs deleted file mode 100644 index e749b5630..000000000 --- a/Plain Craft Launcher 2/Modules/ModMain.Dialog.cs +++ /dev/null @@ -1,674 +0,0 @@ -using System.Collections; -using System.Collections.ObjectModel; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Interop; -using System.Windows.Threading; -using FluentValidation; -using Microsoft.VisualBasic; -using PCL.Core.App.Localization; -using PCL.Core.UI; - -namespace PCL; - -public static partial class ModMain -{ - /// - /// 等待显示的弹窗。 - /// - public static List WaitingMyMsgBox { get; } = []; - - #region 弹窗 - - /// - /// 存储弹窗信息的转换器。 - /// - public class MyMsgBoxConverter - { - // 设置轮询 Url - public object AuthUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; - public string Button1 = ""; - - /// - /// 点击第一个按钮将执行该方法,不关闭弹窗。 - /// - public Action Button1Action; - - public string Button2 = ""; - - /// - /// 点击第二个按钮将执行该方法,不关闭弹窗。 - /// - public Action Button2Action; - - public string Button3 = ""; - - /// - /// 点击第三个按钮将执行该方法,不关闭弹窗。 - /// - public Action Button3Action; - - /// - /// 输入模式:文本框的文本。 - /// 选择模式:需要放进去的 List(Of MyListItem)。 - /// 登录模式:登录步骤 1 中返回的 JSON。 - /// - public object Content; - - public bool ForceWait; - - /// - /// 有多个按钮时,是否给第一个按钮加高亮。 - /// - public bool HighLight; - - /// - /// 输入模式:提示文本。 - /// - public string HintText = ""; - - /// - /// 弹窗是否已经关闭。 - /// - public bool IsExited = false; - - public bool IsWarn; - - /// - /// 输入模式:输入的文本。若点击了 非 第一个按钮,则为 Nothing。 - /// 选择模式:点击的按钮编号,从 1 开始。 - /// 登录模式:字符串数组 {AccessToken, RefreshToken} 或一个 Exception。 - /// - public object Result; - - public string Text; - public string Title; - public MyMsgBoxType Type; - - /// - /// 输入模式:输入验证规则。 - /// - public Collection> ValidateRules; - - public DispatcherFrame WaitFrame = new(true); - - public int[] ButtonIds = [1, 2, 3]; - - public Action? OnCloseCallback; - } - - public enum MyMsgBoxType - { - Text, - Select, - Input, - Login, - Markdown - } - - private static string GetDefaultDialogTitle() => Lang.Text("Common.Dialog.Title"); - - private static string GetDefaultConfirmText() => Lang.Text("Common.Action.Confirm"); - - private static string GetDefaultCancelText() => Lang.Text("Common.Action.Cancel"); - - /// - /// 显示弹窗,返回点击按钮的编号(从 1 开始)。 - /// - /// 弹窗的标题。 - /// 弹窗的内容。 - /// 显示的第一个按钮,默认为"确定"。 - /// 显示的第二个按钮,默认为空。 - /// 显示的第三个按钮,默认为空。 - /// 点击第一个按钮将执行该方法,不关闭弹窗。 - /// 点击第二个按钮将执行该方法,不关闭弹窗。 - /// 点击第三个按钮将执行该方法,不关闭弹窗。 - /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 - public static int MyMsgBox(string caption, string? title = null, string? button1 = null, string? button2 = "", - string? button3 = "", bool isWarn = false, bool highLight = true, bool forceWait = false, - Action button1Action = null, Action button2Action = null, Action button3Action = null) - { - title ??= GetDefaultDialogTitle(); - button1 ??= GetDefaultConfirmText(); - button2 ??= ""; - button3 ??= ""; - // 将弹窗列入队列 - var converter = new MyMsgBoxConverter - { - Type = MyMsgBoxType.Text, Button1 = button1, Button2 = button2, Button3 = button3, Text = caption, - IsWarn = isWarn, Title = title, HighLight = highLight, ForceWait = true, Button1Action = button1Action, - Button2Action = button2Action, Button3Action = button3Action - }; - WaitingMyMsgBox.Add(converter); - if (ModBase.RunInUi()) - // 若为 UI 线程,立即执行弹窗刻, 避免快速(连点器)点击时多次弹窗 - MyMsgBoxTick(); - if (button2.Length > 0 || forceWait) - { - // 若有多个按钮则开始等待 - if (frmMain is null || (frmMain.PanMsg is null && ModBase.RunInUi())) - { - // 主窗体尚未加载,用老土的弹窗来替代 - WaitingMyMsgBox.Remove(converter); - if (button2.Length > 0) - { - var rawResult = Interaction.MsgBox(caption, - (MsgBoxStyle)((int)(button3.Length > 0 ? MsgBoxStyle.YesNoCancel : MsgBoxStyle.YesNo) + - (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); - switch (rawResult) - { - case MsgBoxResult.Yes: - { - converter.Result = 1; - break; - } - case MsgBoxResult.No: - { - converter.Result = 2; - break; - } - case MsgBoxResult.Cancel: - { - converter.Result = 3; - break; - } - } - } - else - { - Interaction.MsgBox(caption, - (MsgBoxStyle)((int)MsgBoxStyle.OkOnly + - (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); - converter.Result = 1; - } - - ModBase.Log("[Control] 主窗体加载完成前出现意料外的等待弹窗:" + button1 + "," + button2 + "," + button3, - ModBase.LogLevel.Debug); - } - else - { - try - { - frmMain.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - } - - ModBase.Log($"[Control] 普通弹框返回:{converter.Result ?? "null"}"); - return (int)converter.Result; - } - - // 不进行等待,直接返回 - return 1; - } - - /// - /// 显示弹窗,返回点击按钮的编号(从 1 开始)。 - /// - /// 弹窗的标题。 - /// 弹窗的内容。 - /// 显示的第一个按钮,默认为"确定"。 - /// 显示的第二个按钮,默认为空。 - /// 显示的第三个按钮,默认为空。 - /// 点击第一个按钮将执行该方法,不关闭弹窗。 - /// 点击第二个按钮将执行该方法,不关闭弹窗。 - /// 点击第三个按钮将执行该方法,不关闭弹窗。 - /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 - public static int MyMsgBoxMarkdown(string caption, string? title = null, string? button1 = null, string? button2 = "", - string? button3 = "", bool isWarn = false, bool highLight = true, bool forceWait = false, - Action button1Action = null, Action button2Action = null, Action button3Action = null) - { - title ??= GetDefaultDialogTitle(); - button1 ??= GetDefaultConfirmText(); - button2 ??= ""; - button3 ??= ""; - // 将弹窗列入队列 - var converter = new MyMsgBoxConverter - { - Type = MyMsgBoxType.Markdown, Button1 = button1, Button2 = button2, Button3 = button3, Text = caption, - IsWarn = isWarn, Title = title, HighLight = highLight, ForceWait = true, Button1Action = button1Action, - Button2Action = button2Action, Button3Action = button3Action - }; - WaitingMyMsgBox.Add(converter); - if (ModBase.RunInUi()) - // 若为 UI 线程,立即执行弹窗刻, 避免快速(连点器)点击时多次弹窗 - MyMsgBoxTick(); - if (button2.Length > 0 || forceWait) - { - // 若有多个按钮则开始等待 - if (frmMain is null || (frmMain.PanMsg is null && ModBase.RunInUi())) - { - // 主窗体尚未加载,用老土的弹窗来替代 - WaitingMyMsgBox.Remove(converter); - if (button2.Length > 0) - { - var rawResult = Interaction.MsgBox(caption, - (MsgBoxStyle)((int)(button3.Length > 0 ? MsgBoxStyle.YesNoCancel : MsgBoxStyle.YesNo) + - (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); - switch (rawResult) - { - case MsgBoxResult.Yes: - { - converter.Result = 1; - break; - } - case MsgBoxResult.No: - { - converter.Result = 2; - break; - } - case MsgBoxResult.Cancel: - { - converter.Result = 3; - break; - } - } - } - else - { - Interaction.MsgBox(caption, - (MsgBoxStyle)((int)MsgBoxStyle.OkOnly + - (int)(isWarn ? MsgBoxStyle.Critical : MsgBoxStyle.Question)), title); - converter.Result = 1; - } - - ModBase.Log("[Control] 主窗体加载完成前出现意料外的等待弹窗:" + button1 + "," + button2 + "," + button3, - ModBase.LogLevel.Debug); - } - else - { - try - { - frmMain.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - } - - ModBase.Log($"[Control] 普通弹框返回:{converter.Result ?? "null"}"); - return (int)converter.Result; - } - - // 不进行等待,直接返回 - return 1; - } - - /// - /// 显示输入框并返回输入的文本。若点击第二个按钮,则返回 Nothing。 - /// - /// 弹窗的标题。 - /// 文本框的输入检测。 - /// 弹窗的介绍文本。 - /// 文本框的默认内容。 - /// 文本框的提示内容。 - /// 显示的第一个按钮,默认为"确定"。 - /// 显示的第二个按钮,默认为"取消"。 - /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 - public static string MyMsgBoxInput(string title, string text = "", string defaultInput = "", - Collection>? validateRules = null, string hintText = "", string? button1 = null, - string? button2 = null, bool isWarn = false) - { - button1 ??= GetDefaultConfirmText(); - button2 ??= GetDefaultCancelText(); - // 将弹窗列入队列 - var converter = new MyMsgBoxConverter - { - Text = text, HintText = hintText, Type = MyMsgBoxType.Input, - ValidateRules = validateRules ?? [], Button1 = button1, Button2 = button2, - Content = defaultInput, IsWarn = isWarn, Title = title - }; - WaitingMyMsgBox.Add(converter); - // 虽然我也不知道这是啥但是能用就成了 :) - try - { - frmMain?.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - - ModBase.Log($"[Control] 输入弹框返回:{converter.Result}"); - return converter.Result?.ToString(); - } - - /// - /// 显示选择框并返回选择的第几项(从 0 开始)。若点击第二个按钮,则返回 Nothing。 - /// - /// 弹窗的标题。 - /// 显示的第一个按钮,默认为 "确定"。 - /// 显示的第二个按钮,默认为空。 - /// 是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。 - public static int? MyMsgBoxSelect(List selections, string? title = null, string? button1 = null, - string? button2 = "", bool isWarn = false) - { - title ??= GetDefaultDialogTitle(); - button1 ??= GetDefaultConfirmText(); - button2 ??= ""; - // 将弹窗列入队列 - var converter = new MyMsgBoxConverter - { - Type = MyMsgBoxType.Select, Button1 = button1, Button2 = button2, Content = selections, IsWarn = isWarn, - Title = title - }; - WaitingMyMsgBox.Add(converter); - // 虽然我也不知道这是啥但是能用就成了 :) - try - { - if (frmMain is not null) - frmMain.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - - ModBase.Log($"[Control] 选择弹框返回:{converter.Result ?? "null"}"); - return (int?)converter.Result; - } - - - public static void MyMsgBoxTick() - { - try - { - if (frmMain is null || frmMain.PanMsg is null || frmMain.WindowState == WindowState.Minimized) - return; - if (frmMain.PanMsg.Children.Count > 0) - { - // 弹窗中 - frmMain.PanMsgBackground.Visibility = Visibility.Visible; - } - else if (WaitingMyMsgBox.Any()) - { - // 没有弹窗,显示一个等待的弹窗 - frmMain.PanMsgBackground.Visibility = Visibility.Visible; - var converter = WaitingMyMsgBox[0]; - var dialog = CreateDialogFromConverter(converter); - if (dialog is not null) - frmMain.PanMsg.Children.Add(dialog); - - WaitingMyMsgBox.RemoveAt(0); - } - // 没有弹窗,没有等待的弹窗 - else if (!(frmMain.PanMsgBackground.Visibility == Visibility.Collapsed)) - { - frmMain.PanMsgBackground.Visibility = Visibility.Collapsed; - } - } - catch (Exception ex) - { - ModBase.Log(ex, "处理等待中的弹窗失败", ModBase.LogLevel.Feedback); - } - } - - private static DialogControl? CreateDialogFromConverter(MyMsgBoxConverter conv) - { - switch (conv.Type) - { - case MyMsgBoxType.Input: - return CreateInputDialog(conv); - case MyMsgBoxType.Select: - return CreateSelectDialog(conv); - case MyMsgBoxType.Login: - // MyMsgLogin manages its own chrome + lifecycle - frmMain?.PanMsg.Children.Add(new MyMsgLogin(conv)); - return null; - case MyMsgBoxType.Markdown: - { - var mdViewer = new Markdig.Wpf.MarkdownViewer { Markdown = conv.Text }; - return CreateStandardDialog(conv, mdViewer); - } - default: - { - var content = conv.Content as UIElement - ?? new TextBlock { Text = conv.Text, TextWrapping = TextWrapping.Wrap, FontSize = 15 }; - return CreateStandardDialog(conv, content); - } - } - } - - private static DialogControl CreateStandardDialog(MyMsgBoxConverter conv, UIElement content) - { - var dialog = new DialogControl - { - Title = conv.Title, - IsWarn = conv.IsWarn, - DialogContent = content, - }; - dialog.AddButton(conv.Button1, conv.Button1Action, isPrimary: true, id: conv.ButtonIds[0]); - if (!string.IsNullOrEmpty(conv.Button2)) - dialog.AddButton(conv.Button2, conv.Button2Action, id: conv.ButtonIds[1]); - if (!string.IsNullOrEmpty(conv.Button3)) - dialog.AddButton(conv.Button3, conv.Button3Action, id: conv.ButtonIds[2]); - conv.WaitFrame = dialog.WaitFrame; - dialog.OnClosed += result => - { - conv.Result = result; - conv.OnCloseCallback?.Invoke(result); - }; - return dialog; - } - - private static DialogControl CreateInputDialog(MyMsgBoxConverter conv) - { - var stack = new StackPanel(); - if (!string.IsNullOrEmpty(conv.Text)) - { - stack.Children.Add(new TextBlock - { - Text = conv.Text, - TextWrapping = TextWrapping.Wrap, - FontSize = 15, - Margin = new Thickness(0, 0, 0, 7), - }); - } - - var textBox = new MyTextBox - { - Text = (string)(conv.Content ?? ""), - HintText = conv.HintText, - ValidateRules = conv.ValidateRules ?? [], - MinWidth = 450, - }; - stack.Children.Add(textBox); - - var dialog = new DialogControl - { - Title = conv.Title, - IsWarn = conv.IsWarn, - DialogContent = stack, - }; - - dialog.AddButton(conv.Button1, onClick: () => - { - textBox.Validate(); - if (!textBox.IsValidated) return; - conv.Result = textBox.Text; - dialog.Close(conv.ButtonIds[0]); - }, isPrimary: true, id: conv.ButtonIds[0]); - - if (!string.IsNullOrEmpty(conv.Button2)) - { - dialog.AddButton(conv.Button2, onClick: () => - { - conv.Result = null; - dialog.Close(conv.ButtonIds[1]); - }, id: conv.ButtonIds[1]); - } - - conv.WaitFrame = dialog.WaitFrame; - return dialog; - } - - private static DialogControl CreateSelectDialog(MyMsgBoxConverter conv) - { - var panel = new StackPanel(); - var dialog = new DialogControl - { - Title = conv.Title, - IsWarn = conv.IsWarn, - DialogContent = panel, - }; - - var b1 = dialog.AddButton(conv.Button1, onClick: () => - { - // handled by extra Click below - }, isPrimary: true, id: conv.ButtonIds[0]); - b1.IsEnabled = false; - - if (!string.IsNullOrEmpty(conv.Button2)) - { - dialog.AddButton(conv.Button2, onClick: () => - { - conv.Result = null; - dialog.Close(conv.ButtonIds[1]); - }, id: conv.ButtonIds[1]); - } - - var selectedIndex = -1; - var index = 0; - foreach (var raw in (IEnumerable)conv.Content!) - { - var item = MyVirtualizingElement.TryInit((FrameworkElement)raw); - if (item is IMyRadio radio) - { - if (item is MyListItem listItem) - { - listItem.Type = MyListItem.CheckType.RadioBox; - listItem.MinHeight = 24; - } - else if (item is MyRadioBox radioBox) - { - radioBox.MinHeight = 24; - } - - var currentIndex = index; - radio.Check += (_, _) => - { - selectedIndex = currentIndex; - b1.IsEnabled = true; - }; - panel.Children.Add((UIElement)radio); - index++; - } - } - - // Override Btn1's dummy onClick with real logic - b1.Click += (_, _) => - { - if (selectedIndex < 0) return; - conv.Result = selectedIndex; - dialog.Close(conv.ButtonIds[0]); - }; - - conv.WaitFrame = dialog.WaitFrame; - return dialog; - } - - public static void MsgBoxWrapper_OnShow(string message, string caption, ICollection buttons, - MsgBoxTheme theme, bool block, ref int result) - { - var btnText1 = buttons.Count < 1 ? GetDefaultConfirmText() : buttons.ElementAt(0).Context; - var btnAct1 = (Action)(buttons.Count < 1 ? (object)null : buttons.ElementAt(0).OnClick); - var btnText2 = buttons.Count < 2 ? GetDefaultCancelText() : buttons.ElementAt(1).Context; - var btnAct2 = (Action)(buttons.Count < 2 ? (object)null : buttons.ElementAt(1).OnClick); - var btnText3 = buttons.Count < 3 ? "" : buttons.ElementAt(2).Context; - var btnAct3 = (Action)(buttons.Count < 3 ? (object)null : buttons.ElementAt(2).OnClick); - - var isWarn = theme == MsgBoxTheme.Warning || theme == MsgBoxTheme.Error; - - result = MyMsgBox(message, caption, btnText1, btnText2, btnText3, isWarn, forceWait: block, - button1Action: btnAct1, button2Action: btnAct2, button3Action: btnAct3); - } - - public static void Dialog_OnShow(DialogContext context) - { - if (frmMain is null) - { - Interaction.MsgBox(context.Caption, MsgBoxStyle.OkOnly, context.Title); - context.Result = 1; - context.OnClosed?.Invoke(1); - return; - } - - if (frmMain.Dispatcher.CheckAccess()) - ShowDialogOnUi(context); - else - frmMain.Dispatcher.Invoke(() => ShowDialogOnUi(context)); - } - - private static void ShowDialogOnUi(DialogContext context) - { - if (frmMain?.PanMsg is null) return; - - var isWarn = context.Theme == DialogTheme.Warning || context.Theme == DialogTheme.Error; - - var content = context.Content; - if (content is null && !string.IsNullOrEmpty(context.Caption)) - { - content = new TextBlock - { - Text = context.Caption, - TextWrapping = TextWrapping.Wrap, - FontSize = 15, - }; - } - - var dialog = new DialogControl - { - Title = context.Title, - IsWarn = isWarn, - DialogContent = content as UIElement, - }; - - for (var i = 0; i < context.Buttons.Count && i < 3; i++) - { - var btn = context.Buttons[i]; - var isPrimary = i == 0 || btn.IsPrimary; - var id = btn.Id > 0 ? btn.Id : i + 1; - dialog.AddButton(btn.Text, btn.OnClick, isPrimary, id); - } - - frmMain.PanMsg.Children.Add(dialog); - - if (context.Block) - { - try - { - frmMain.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(dialog.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - context.Result = dialog.Result; - context.OnClosed?.Invoke(context.Result); - } - else - { - dialog.OnClosed += result => - { - context.Result = result; - context.OnClosed?.Invoke(result); - }; - } - } - - #endregion -} diff --git a/Plain Craft Launcher 2/Modules/ModMain.cs b/Plain Craft Launcher 2/Modules/ModMain.cs index 5aabfcfc0..2472b45a4 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.cs +++ b/Plain Craft Launcher 2/Modules/ModMain.cs @@ -111,7 +111,7 @@ private static void TimerMain() #region 每 50ms 执行一次的代码 HintTick(); - MyMsgBoxTick(); + DialogManager.Instance?.Tick(); frmMain!.DragTick(); ModLoader.LoaderTaskbarProgressRefresh(); } diff --git a/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs b/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs index d6b545076..e13b650ca 100644 --- a/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs @@ -674,7 +674,7 @@ private void BtnDialogTest_Click(object sender, MouseButtonEventArgs e) Margin = new Thickness(0, 4, 0, 0), }); - Dialog.Show(new DialogContext + DialogManager.Instance?.Show(new DialogContext { Title = "弹窗系统展示", Content = panel, From be51f26410b00ab94fc3bf8d90128a0214b55d46 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 22:09:41 +0800 Subject: [PATCH 09/14] refactor: remove MyMsgBoxConverter, migrate MS login, delete old controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MyMsgLogin now takes (data, authUrl, onFinished) callback — no converter. ModLaunch OAuth flow uses ManualResetEvent + MyMsgLogin directly. ShowInput/ShowSelect create DialogControl directly via Dispatcher.Invoke. Removed: MyMsgBoxConverter, MyMsgBoxType, WaitingMyMsgBox queue. Removed: MyMsgText/Input/Select/Markdown XAML+CS files. DialogManager.Tick simplified to background visibility only. Keyboard handler (Enter/Esc) updated for DialogControl. --- .../Controls/Dialog/DialogControl.cs | 5 +- .../Controls/MyMsg/MyMsgInput.xaml | 52 --- .../Controls/MyMsg/MyMsgInput.xaml.cs | 161 --------- .../Controls/MyMsg/MyMsgMarkdown.xaml | 52 --- .../Controls/MyMsg/MyMsgMarkdown.xaml.cs | 184 ---------- .../Controls/MyMsg/MyMsgSelect.xaml | 45 --- .../Controls/MyMsg/MyMsgSelect.xaml.cs | 192 ---------- .../Controls/MyMsg/MyMsgText.xaml | 52 --- .../Controls/MyMsg/MyMsgText.xaml.cs | 183 ---------- .../Dialog/DialogManager.cs | 336 ++++++------------ Plain Craft Launcher 2/Dialog/LegacyDialog.cs | 46 --- Plain Craft Launcher 2/FormMain.xaml.cs | 31 +- .../Modules/Minecraft/ModLaunch.cs | 28 +- .../Pages/PageLaunch/MyMsgLogin.xaml.cs | 76 ++-- 14 files changed, 174 insertions(+), 1269 deletions(-) delete mode 100644 Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml delete mode 100644 Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.cs delete mode 100644 Plain Craft Launcher 2/Controls/MyMsg/MyMsgMarkdown.xaml delete mode 100644 Plain Craft Launcher 2/Controls/MyMsg/MyMsgMarkdown.xaml.cs delete mode 100644 Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml delete mode 100644 Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.cs delete mode 100644 Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml delete mode 100644 Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml.cs diff --git a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs index 1c656c7aa..a9efdb21b 100644 --- a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs +++ b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs @@ -12,7 +12,7 @@ public partial class DialogControl private int _result; private bool _exited; private readonly int _uuid = ModBase.GetUuid(); - private readonly List _buttons = []; + internal readonly List _buttons = []; public DispatcherFrame WaitFrame { get; } = new(true); @@ -161,8 +161,7 @@ private void CloseInternal() { ModAnimation.AaCode(() => { - var hasMore = ModMain.WaitingMyMsgBox.Any() - || (ModMain.frmMain?.PanMsg?.Children.Count ?? 0) > 1; + var hasMore = (ModMain.frmMain?.PanMsg?.Children.Count ?? 0) > 1; if (!hasMore) ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml deleted file mode 100644 index f0a783de9..000000000 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.cs b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.cs deleted file mode 100644 index b8dc7c782..000000000 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Interop; -using PCL.Core.UI.Controls; - -namespace PCL; - -public partial class MyMsgInput -{ - private readonly ModMain.MyMsgBoxConverter myConverter; - private readonly int uuid = ModBase.GetUuid(); - - public MyMsgInput(ModMain.MyMsgBoxConverter converter) - { - try - { - InitializeComponent(); - AppendUniqueNameSuffix(Btn1); - AppendUniqueNameSuffix(Btn2); - myConverter = converter; - LabTitle.Text = converter.Title; - LabText.Text = converter.Text; - PanText.Visibility = string.IsNullOrEmpty(converter.Text) ? Visibility.Collapsed : Visibility.Visible; - TextArea.Text = (string)converter.Content; - TextArea.HintText = converter.HintText; - TextArea.ValidateRules = converter.ValidateRules; - ConfigurePrimaryButton(converter.Button1, converter.IsWarn); - ConfigureSecondaryButton(converter.Button2); - ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d); - } - - catch (Exception ex) - { - ModBase.Log(ex, "输入弹窗初始化失败", ModBase.LogLevel.Hint); - } - - Loaded += Load; - } - - private void AppendUniqueNameSuffix(FrameworkElement element) - { - element.Name += ModBase.GetUuid(); - } - - private void ConfigurePrimaryButton(string text, bool isWarn) - { - Btn1.Text = text; - if (isWarn) - { - Btn1.ColorType = MyButton.ColorState.Red; - LabTitle.SetResourceReference(TextBlock.ForegroundProperty, "ColorBrushRedLight"); - } - } - - private void ConfigureSecondaryButton(string text) - { - Btn2.Text = text; - Btn2.Visibility = string.IsNullOrEmpty(text) ? Visibility.Collapsed : Visibility.Visible; - } - - private void Load(object sender, EventArgs e) - { - try - { - // UI 初始化 - if (Btn2.IsVisible && !(Btn1.ColorType == MyButton.ColorState.Red)) - Btn1.ColorType = MyButton.ColorState.Highlight; - TextArea.Focus(); - TextArea.SelectionStart = TextArea.Text.Length; - // 动画 - Opacity = 0d; - ModAnimation.AniStart( - ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, - (myConverter.IsWarn - ? new ModBase.MyColor(140d, 80d, 0d, 0d) - : new ModBase.MyColor(90d, 0d, 0d, 0d)) - ModMain.frmMain.PanMsgBackground.Background, 200), - "PanMsgBackground Background"); - ModAnimation.AniStart( - new[] - { - ModAnimation.AaOpacity(this, 1d, 120, 60), - ModAnimation.AaDouble(i => TransformPos.Y += (double)i, - -TransformPos.Y, 300, 60, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)), - ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, - -TransformRotate.Angle, 300, 60, - new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)) - }, "MyMsgBox " + uuid); - // 记录日志 - ModBase.Log("[Control] 输入弹窗:" + LabTitle.Text); - } - - catch (Exception ex) - { - ModBase.Log(ex, "输入弹窗加载失败", ModBase.LogLevel.Hint); - } - } - - private void Close() - { - // 结束线程阻塞 - myConverter.WaitFrame.Continue = false; - ComponentDispatcher.PopModal(); - // 动画 - ModAnimation.AniStart(new[] - { - ModAnimation.AaCode(() => - { - if (!ModMain.WaitingMyMsgBox.Any()) - ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, - BlurBorder.BackgroundProperty, - new ModBase.MyColor(0d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200, - ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak))); - }, 30), - ModAnimation.AaOpacity(this, -Opacity, 80, 20), - ModAnimation.AaDouble(i => TransformPos.Y += (double)i, 20d - TransformPos.Y, - 150, 0, new ModAnimation.AniEaseOutFluent()), - ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, - 6d - TransformRotate.Angle, 150, 0, new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)), - ModAnimation.AaCode(() => ((Grid)Parent).Children.Remove(this), after: true) - }, "MyMsgBox " + uuid); - } - - public void Btn1_Click(object sender, MouseButtonEventArgs e) - { - TextArea.Validate(); // #5773 - if (myConverter.IsExited || !TextArea.IsValidated) - return; - myConverter.IsExited = true; - myConverter.Result = TextArea.Text; - Close(); - } - - public void Btn2_Click(object sender, MouseButtonEventArgs e) - { - if (myConverter.IsExited) - return; - myConverter.IsExited = true; - myConverter.Result = null; - Close(); - } - - private void TextCaption_ValidateChanged(object sender, EventArgs e) - { - Btn1.IsEnabled = TextArea.IsValidated; - } - - private void Drag(object sender, MouseButtonEventArgs e) - { - try - { - if (e.LeftButton == MouseButtonState.Pressed) - if (e.GetPosition(ShapeLine).Y <= 2d) - ModMain.frmMain.DragMove(); - } - catch (Exception ex) - { - ModBase.Log(ex, "拖拽移动失败", ModBase.LogLevel.Hint); - } - } -} diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgMarkdown.xaml b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgMarkdown.xaml deleted file mode 100644 index d34fd682f..000000000 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgMarkdown.xaml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgMarkdown.xaml.cs b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgMarkdown.xaml.cs deleted file mode 100644 index b6bc2faaf..000000000 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgMarkdown.xaml.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Interop; -using PCL.Core.UI.Controls; - -namespace PCL; - -public partial class MyMsgMarkdown -{ - private readonly ModMain.MyMsgBoxConverter myConverter; - private readonly int uuid = ModBase.GetUuid(); - - public MyMsgMarkdown(ModMain.MyMsgBoxConverter converter) - { - try - { - InitializeComponent(); - AppendUniqueNameSuffix(Btn1); - AppendUniqueNameSuffix(Btn2); - AppendUniqueNameSuffix(Btn3); - myConverter = converter; - LabTitle.Text = converter.Title; - LabCaption.Markdown = converter.Text; - DataContext = this; - ConfigurePrimaryButton(converter.Button1, converter.IsWarn); - ConfigureSecondaryButton(Btn2, converter.Button2); - ConfigureSecondaryButton(Btn3, converter.Button3); - ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d); - } - - catch (Exception ex) - { - ModBase.Log(ex, "普通弹窗初始化失败", ModBase.LogLevel.Hint); - } - - Loaded += Load; - } - - private void AppendUniqueNameSuffix(FrameworkElement element) - { - element.Name += ModBase.GetUuid(); - } - - private void ConfigurePrimaryButton(string text, bool isWarn) - { - Btn1.Text = text; - if (isWarn) - { - Btn1.ColorType = MyButton.ColorState.Red; - LabTitle.SetResourceReference(TextBlock.ForegroundProperty, "ColorBrushRedLight"); - } - } - - private static void ConfigureSecondaryButton(MyButton button, string text) - { - button.Text = text; - button.Visibility = string.IsNullOrEmpty(text) ? Visibility.Collapsed : Visibility.Visible; - } - - private void Load(object sender, EventArgs e) - { - try - { - // UI 初始化 - if (Btn2.IsVisible && !(Btn1.ColorType == MyButton.ColorState.Red)) - Btn1.ColorType = MyButton.ColorState.Highlight; - Btn1.Focus(); - // 动画 - Opacity = 0d; - ModAnimation.AniStart( - ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, - (myConverter.IsWarn - ? new ModBase.MyColor(140d, 80d, 0d, 0d) - : new ModBase.MyColor(90d, 0d, 0d, 0d)) - ModMain.frmMain.PanMsgBackground.Background, 200), - "PanMsgBackground Background"); - ModAnimation.AniStart( - new[] - { - ModAnimation.AaOpacity(this, 1d, 120, 60), - ModAnimation.AaDouble(i => TransformPos.Y += (double)i, - -TransformPos.Y, 300, 60, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)), - ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, - -TransformRotate.Angle, 300, 60, - new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)) - }, "MyMsgBox " + uuid); - // 记录日志 - ModBase.Log("[Control] 普通弹窗:" + LabTitle.Text + "\r\n" + LabCaption.Markdown); - } - - catch (Exception ex) - { - ModBase.Log(ex, "普通弹窗加载失败", ModBase.LogLevel.Hint); - } - } - - private void Close() - { - // 结束线程阻塞 - if (myConverter.ForceWait || !string.IsNullOrEmpty(myConverter.Button2)) - myConverter.WaitFrame.Continue = false; - ComponentDispatcher.PopModal(); - // 动画 - ModAnimation.AniStart(new[] - { - ModAnimation.AaCode(() => - { - if (!ModMain.WaitingMyMsgBox.Any()) - ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, - BlurBorder.BackgroundProperty, - new ModBase.MyColor(0d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200, - ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak))); - }, 30), - ModAnimation.AaOpacity(this, -Opacity, 80, 20), - ModAnimation.AaDouble(i => TransformPos.Y += (double)i, 20d - TransformPos.Y, - 150, 0, new ModAnimation.AniEaseOutFluent()), - ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, - 6d - TransformRotate.Angle, 150, 0, new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)), - ModAnimation.AaCode(() => ((Grid)Parent).Children.Remove(this), after: true) - }, "MyMsgBox " + uuid); - } - - public void Btn1_Click(object sender, MouseButtonEventArgs e) - { - if (myConverter.IsExited) - return; - if (myConverter.Button1Action is not null) - { - myConverter.Button1Action(); - } - else - { - myConverter.IsExited = true; - myConverter.Result = 1; - Close(); - } - } - - public void Btn2_Click(object sender, MouseButtonEventArgs e) - { - if (myConverter.IsExited) - return; - if (myConverter.Button2Action is not null) - { - myConverter.Button2Action(); - } - else - { - myConverter.IsExited = true; - myConverter.Result = 2; - Close(); - } - } - - public void Btn3_Click(object sender, MouseButtonEventArgs e) - { - if (myConverter.IsExited) - return; - if (myConverter.Button3Action is not null) - { - myConverter.Button3Action(); - } - else - { - myConverter.IsExited = true; - myConverter.Result = 3; - Close(); - } - } - - private void Drag(object? sender = null, MouseButtonEventArgs? e = null) - { - try - { - if (e.LeftButton == MouseButtonState.Pressed) - if (e.GetPosition(ShapeLine).Y <= 2d) - ModMain.frmMain.DragMove(); - } - catch (Exception ex) - { - ModBase.Log(ex, "拖拽移动失败", ModBase.LogLevel.Hint); - } - } -} diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml deleted file mode 100644 index 1ba30ccf8..000000000 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.cs b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.cs deleted file mode 100644 index e33749f8f..000000000 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System.Collections; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Interop; -using PCL.Core.UI.Controls; - -namespace PCL; - -public partial class MyMsgSelect -{ - private readonly ModMain.MyMsgBoxConverter myConverter; - private readonly int uuid = ModBase.GetUuid(); - - private int selectedIndex = -1; - - public MyMsgSelect(ModMain.MyMsgBoxConverter converter) - { - try - { - InitializeComponent(); - AppendUniqueNameSuffix(Btn1); - AppendUniqueNameSuffix(Btn2); - myConverter = converter; - LabTitle.Text = converter.Title; - ConfigurePrimaryButton(converter.Button1, converter.IsWarn); - ConfigureSecondaryButton(converter.Button2); - ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d); - InitializeSelectionList(converter.Content); - } - - catch (Exception ex) - { - ModBase.Log(ex, "选择弹窗初始化失败", ModBase.LogLevel.Hint); - } - - Loaded += Load; - Btn1.Click += Btn1_Click; - Btn2.Click += Btn2_Click; - LabTitle.MouseLeftButtonDown += Drag; - PanBorder.MouseLeftButtonDown += Drag; - } - - private void AppendUniqueNameSuffix(FrameworkElement element) - { - element.Name += ModBase.GetUuid(); - } - - private void ConfigurePrimaryButton(string text, bool isWarn) - { - Btn1.Text = text; - if (isWarn) - { - Btn1.ColorType = MyButton.ColorState.Red; - LabTitle.SetResourceReference(TextBlock.ForegroundProperty, "ColorBrushRedLight"); - } - } - - private void ConfigureSecondaryButton(string text) - { - Btn2.Text = text; - Btn2.Visibility = string.IsNullOrEmpty(text) ? Visibility.Collapsed : Visibility.Visible; - } - - private void InitializeSelectionList(object content) - { - // 添加选择控件 - Btn1.IsEnabled = false; - foreach (var rawContent in (IEnumerable)content) - { - // 1. Initialize and get the actual element - // Note: We use a new variable because 'foreach' variables are read-only - var selectionContent = MyVirtualizingElement.TryInit((FrameworkElement)rawContent); - - // 2. Interface casting and event subscription - if (selectionContent is IMyRadio selection) - { - PanSelection.Children.Add((UIElement)selection); - selection.Check += (sender, e) => OnChecked((IMyRadio)sender, e); - - // 3. Property configuration based on specific type - if (selection is MyListItem listItem) - { - listItem.Type = MyListItem.CheckType.RadioBox; - listItem.MinHeight = 24.0; - } - else if (selection is MyRadioBox radioBox) - { - radioBox.MinHeight = 24.0; - } - } - } - } - - private void Load(object sender, EventArgs e) - { - try - { - // UI 初始化 - if (Btn2.IsVisible && !(Btn1.ColorType == MyButton.ColorState.Red)) - Btn1.ColorType = MyButton.ColorState.Highlight; - // 动画 - Opacity = 0d; - ModAnimation.AniStart( - ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, - (myConverter.IsWarn - ? new ModBase.MyColor(140d, 80d, 0d, 0d) - : new ModBase.MyColor(90d, 0d, 0d, 0d)) - ModMain.frmMain.PanMsgBackground.Background, 200), - "PanMsgBackground Background"); - ModAnimation.AniStart( - new[] - { - ModAnimation.AaOpacity(this, 1d, 120, 60), - ModAnimation.AaDouble(i => TransformPos.Y += (double)i, - -TransformPos.Y, 300, 60, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)), - ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, - -TransformRotate.Angle, 300, 60, - new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)) - }, "MyMsgBox " + uuid); - // 记录日志 - ModBase.Log("[Control] 选择弹窗:" + LabTitle.Text); - } - - catch (Exception ex) - { - ModBase.Log(ex, "选择弹窗加载失败", ModBase.LogLevel.Hint); - } - } - - private void Close() - { - // 结束线程阻塞 - myConverter.WaitFrame.Continue = false; - ComponentDispatcher.PopModal(); - // 动画 - ModAnimation.AniStart(new[] - { - ModAnimation.AaCode(() => - { - if (!ModMain.WaitingMyMsgBox.Any()) - ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, - BlurBorder.BackgroundProperty, - new ModBase.MyColor(0d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200, - ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak))); - }, 30), - ModAnimation.AaOpacity(this, -Opacity, 80, 20), - ModAnimation.AaDouble(i => TransformPos.Y += (double)i, 20d - TransformPos.Y, - 150, 0, new ModAnimation.AniEaseOutFluent()), - ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, - 6d - TransformRotate.Angle, 150, 0, new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)), - ModAnimation.AaCode(() => ((Grid)Parent).Children.Remove(this), after: true) - }, "MyMsgBox " + uuid); - } - - public void Btn1_Click(object sender, MouseButtonEventArgs e) - { - if (myConverter.IsExited || selectedIndex == -1) - return; - myConverter.IsExited = true; - myConverter.Result = selectedIndex; - Close(); - } - - public void Btn2_Click(object sender, MouseButtonEventArgs e) - { - if (myConverter.IsExited) - return; - myConverter.IsExited = true; - myConverter.Result = null; - Close(); - } - - private void OnChecked(IMyRadio sender, EventArgs e) - { - Btn1.IsEnabled = true; - selectedIndex = PanSelection.Children.IndexOf((UIElement)sender); - } - - private void Drag(object sender, MouseButtonEventArgs e) - { - try - { - if (e.LeftButton == MouseButtonState.Pressed) - if (e.GetPosition(ShapeLine).Y <= 2d) - ModMain.frmMain.DragMove(); - } - catch (Exception ex) - { - ModBase.Log(ex, "拖拽移动失败", ModBase.LogLevel.Hint); - } - } -} diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml deleted file mode 100644 index ed540acd2..000000000 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml.cs b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml.cs deleted file mode 100644 index ece7fe122..000000000 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Interop; -using PCL.Core.UI.Controls; - -namespace PCL; - -public partial class MyMsgText -{ - private readonly ModMain.MyMsgBoxConverter myConverter; - private readonly int uuid = ModBase.GetUuid(); - - public MyMsgText(ModMain.MyMsgBoxConverter converter) - { - try - { - InitializeComponent(); - AppendUniqueNameSuffix(Btn1); - AppendUniqueNameSuffix(Btn2); - AppendUniqueNameSuffix(Btn3); - myConverter = converter; - LabTitle.Text = converter.Title; - LabCaption.Text = converter.Text; - ConfigurePrimaryButton(converter.Button1, converter.IsWarn); - ConfigureSecondaryButton(Btn2, converter.Button2); - ConfigureSecondaryButton(Btn3, converter.Button3); - ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d); - } - - catch (Exception ex) - { - ModBase.Log(ex, "普通弹窗初始化失败", ModBase.LogLevel.Hint); - } - - Loaded += Load; - } - - private void AppendUniqueNameSuffix(FrameworkElement element) - { - element.Name += ModBase.GetUuid(); - } - - private void ConfigurePrimaryButton(string text, bool isWarn) - { - Btn1.Text = text; - if (isWarn) - { - Btn1.ColorType = MyButton.ColorState.Red; - LabTitle.SetResourceReference(TextBlock.ForegroundProperty, "ColorBrushRedLight"); - } - } - - private static void ConfigureSecondaryButton(MyButton button, string text) - { - button.Text = text; - button.Visibility = string.IsNullOrEmpty(text) ? Visibility.Collapsed : Visibility.Visible; - } - - private void Load(object sender, RoutedEventArgs e) - { - try - { - // UI 初始化 - if (Btn2.IsVisible && !(Btn1.ColorType == MyButton.ColorState.Red)) - Btn1.ColorType = MyButton.ColorState.Highlight; - Btn1.Focus(); - // 动画 - Opacity = 0d; - ModAnimation.AniStart( - ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, - (myConverter.IsWarn - ? new ModBase.MyColor(140d, 80d, 0d, 0d) - : new ModBase.MyColor(90d, 0d, 0d, 0d)) - ModMain.frmMain.PanMsgBackground.Background, 200), - "PanMsgBackground Background"); - ModAnimation.AniStart( - new[] - { - ModAnimation.AaOpacity(this, 1d, 120, 60), - ModAnimation.AaDouble(i => TransformPos.Y += (double)i, - -TransformPos.Y, 300, 60, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)), - ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, - -TransformRotate.Angle, 300, 60, - new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)) - }, "MyMsgBox " + uuid); - // 记录日志 - ModBase.Log("[Control] 普通弹窗:" + LabTitle.Text + "\r\n" + LabCaption.Text); - } - - catch (Exception ex) - { - ModBase.Log(ex, "普通弹窗加载失败", ModBase.LogLevel.Hint); - } - } - - private void Close() - { - // 结束线程阻塞 - if (myConverter.ForceWait || !string.IsNullOrEmpty(myConverter.Button2)) - myConverter.WaitFrame.Continue = false; - ComponentDispatcher.PopModal(); - // 动画 - ModAnimation.AniStart(new[] - { - ModAnimation.AaCode(() => - { - if (!ModMain.WaitingMyMsgBox.Any()) - ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, - BlurBorder.BackgroundProperty, - new ModBase.MyColor(0d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200, - ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak))); - }, 30), - ModAnimation.AaOpacity(this, -Opacity, 80, 20), - ModAnimation.AaDouble(i => TransformPos.Y += (double)i, 20d - TransformPos.Y, - 150, 0, new ModAnimation.AniEaseOutFluent()), - ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, - 6d - TransformRotate.Angle, 150, 0, new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)), - ModAnimation.AaCode(() => ((Grid)Parent).Children.Remove(this), after: true) - }, "MyMsgBox " + uuid); - } - - public void Btn1_Click(object? sender = null, MouseButtonEventArgs? e = null) - { - if (myConverter.IsExited) - return; - if (myConverter.Button1Action is not null) - { - myConverter.Button1Action(); - } - else - { - myConverter.IsExited = true; - myConverter.Result = 1; - Close(); - } - } - - public void Btn2_Click(object sender, MouseButtonEventArgs e) - { - if (myConverter.IsExited) - return; - if (myConverter.Button2Action is not null) - { - myConverter.Button2Action(); - } - else - { - myConverter.IsExited = true; - myConverter.Result = 2; - Close(); - } - } - - public void Btn3_Click(object sender, MouseButtonEventArgs e) - { - if (myConverter.IsExited) - return; - if (myConverter.Button3Action is not null) - { - myConverter.Button3Action(); - } - else - { - myConverter.IsExited = true; - myConverter.Result = 3; - Close(); - } - } - - private void Drag(object sender, MouseButtonEventArgs e) - { - try - { - if (e.LeftButton == MouseButtonState.Pressed) - if (e.GetPosition(ShapeLine).Y <= 2d) - ModMain.frmMain.DragMove(); - } - catch (Exception ex) - { - ModBase.Log(ex, "拖拽移动失败", ModBase.LogLevel.Hint); - } - } -} diff --git a/Plain Craft Launcher 2/Dialog/DialogManager.cs b/Plain Craft Launcher 2/Dialog/DialogManager.cs index ab0a10be8..5a33ca276 100644 --- a/Plain Craft Launcher 2/Dialog/DialogManager.cs +++ b/Plain Craft Launcher 2/Dialog/DialogManager.cs @@ -68,15 +68,16 @@ public int ShowMarkdown(string markdown, string? title = null, DialogTheme theme = DialogTheme.Info, bool block = true, params string[] buttons) { - return Show(new DialogContext + var ctx = new DialogContext { Caption = markdown, Title = title ?? Lang.Text("Common.Dialog.Title"), Theme = theme, Block = block, Buttons = _BuildButtons(buttons), - // Markdown content is created in ShowOnUi via CreateDialogFromContext - }); + Content = new Markdig.Wpf.MarkdownViewer { Markdown = markdown }, + }; + return Show(ctx); } public string ShowInput(string title, string text = "", string defaultInput = "", @@ -85,31 +86,58 @@ public string ShowInput(string title, string text = "", string defaultInput = "" { button1 ??= Lang.Text("Common.Action.Confirm"); button2 ??= Lang.Text("Common.Action.Cancel"); - var converter = new ModMain.MyMsgBoxConverter - { - Text = text, - HintText = hintText, - Type = ModMain.MyMsgBoxType.Input, - ValidateRules = validateRules ?? [], - Button1 = button1, - Button2 = button2, - Content = defaultInput, - IsWarn = isWarn, - Title = title, - }; - ModMain.WaitingMyMsgBox.Add(converter); - try + string? result = null; + + Action showOnUi = () => { - if (_mainForm is not null) + var stack = new StackPanel(); + if (!string.IsNullOrEmpty(text)) + { + stack.Children.Add(new TextBlock + { + Text = text, TextWrapping = TextWrapping.Wrap, FontSize = 15, + Margin = new Thickness(0, 0, 0, 7), + }); + } + var textBox = new MyTextBox + { + Text = defaultInput, HintText = hintText, + ValidateRules = validateRules ?? [], MinWidth = 450, + }; + stack.Children.Add(textBox); + + var dialog = new DialogControl { Title = title, IsWarn = isWarn, DialogContent = stack }; + dialog.AddButton(button1!, onClick: () => + { + textBox.Validate(); + if (!textBox.IsValidated) return; + result = textBox.Text; + dialog.Close(1); + }, isPrimary: true, id: 1); + if (!string.IsNullOrEmpty(button2)) + { + dialog.AddButton(button2!, onClick: () => + { + result = null; + dialog.Close(2); + }, id: 2); + } + + _mainForm.PanMsg.Children.Add(dialog); + try + { _mainForm.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - return converter.Result?.ToString() ?? ""; + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(dialog.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } + }; + + _mainForm.Dispatcher.Invoke(showOnUi); + return result ?? ""; } public int? ShowSelect(List selections, string? title = null, @@ -118,28 +146,63 @@ public string ShowInput(string title, string text = "", string defaultInput = "" title ??= Lang.Text("Common.Dialog.Title"); button1 ??= Lang.Text("Common.Action.Confirm"); button2 ??= ""; - var converter = new ModMain.MyMsgBoxConverter + int? result = null; + + Action showOnUi = () => { - Type = ModMain.MyMsgBoxType.Select, - Button1 = button1, - Button2 = button2, - Content = selections, - IsWarn = isWarn, - Title = title, + var panel = new StackPanel(); + var dialog = new DialogControl { Title = title, IsWarn = isWarn, DialogContent = panel }; + + var b1 = dialog.AddButton(button1!, onClick: () => { }, isPrimary: true, id: 1); + b1.IsEnabled = false; + + if (!string.IsNullOrEmpty(button2)) + { + dialog.AddButton(button2!, onClick: () => + { + result = null; + dialog.Close(2); + }, id: 2); + } + + int selectedIndex = -1; + for (var i = 0; i < selections.Count; i++) + { + var raw = selections[i]; + var item = MyVirtualizingElement.TryInit((FrameworkElement)raw); + if (item is IMyRadio radio) + { + if (item is MyListItem listItem) { listItem.Type = MyListItem.CheckType.RadioBox; listItem.MinHeight = 24; } + else if (item is MyRadioBox radioBox) { radioBox.MinHeight = 24; } + + var idx = i; + radio.Check += (_, _) => { selectedIndex = idx; b1.IsEnabled = true; }; + panel.Children.Add((UIElement)radio); + } + } + + b1.Click += (_, _) => + { + if (selectedIndex < 0) return; + result = selectedIndex; + dialog.Close(1); + }; + + _mainForm.PanMsg.Children.Add(dialog); + try + { + if (_mainForm is not null) _mainForm.DragStop(); + ComponentDispatcher.PushModal(); + Dispatcher.PushFrame(dialog.WaitFrame); + } + finally + { + ComponentDispatcher.PopModal(); + } }; - ModMain.WaitingMyMsgBox.Add(converter); - try - { - if (_mainForm is not null) - _mainForm.DragStop(); - ComponentDispatcher.PushModal(); - Dispatcher.PushFrame(converter.WaitFrame); - } - finally - { - ComponentDispatcher.PopModal(); - } - return (int?)converter.Result; + + _mainForm.Dispatcher.Invoke(showOnUi); + return result; } // -- legacy bridge: called by MsgBoxWrapper via ModMain.MsgBoxWrapper_OnShow -- @@ -166,25 +229,11 @@ public void Tick() { try { - if (_mainForm is null || _mainForm.PanMsg is null || _mainForm.WindowState == WindowState.Minimized) - return; + if (_mainForm is null || _mainForm.PanMsg is null) return; if (_mainForm.PanMsg.Children.Count > 0) - { - _mainForm.PanMsgBackground.Visibility = Visibility.Visible; - } - else if (ModMain.WaitingMyMsgBox.Any()) - { _mainForm.PanMsgBackground.Visibility = Visibility.Visible; - var converter = ModMain.WaitingMyMsgBox[0]; - var dialog = CreateDialogFromConverter(converter); - if (dialog is not null) - _mainForm.PanMsg.Children.Add(dialog); - ModMain.WaitingMyMsgBox.RemoveAt(0); - } else if (_mainForm.PanMsgBackground.Visibility != Visibility.Collapsed) - { _mainForm.PanMsgBackground.Visibility = Visibility.Collapsed; - } } catch (Exception ex) { @@ -192,171 +241,6 @@ public void Tick() } } - // -- converter helpers (legacy queue → DialogControl) -- - - private static DialogControl? CreateDialogFromConverter(ModMain.MyMsgBoxConverter conv) - { - switch (conv.Type) - { - case ModMain.MyMsgBoxType.Input: - return CreateInputDialog(conv); - case ModMain.MyMsgBoxType.Select: - return CreateSelectDialog(conv); - case ModMain.MyMsgBoxType.Login: - Instance?._mainForm?.PanMsg.Children.Add(new MyMsgLogin(conv)); - return null; - case ModMain.MyMsgBoxType.Markdown: - { - var mdViewer = new Markdig.Wpf.MarkdownViewer { Markdown = conv.Text }; - return CreateStandardDialog(conv, mdViewer); - } - default: - { - var content = conv.Content as UIElement - ?? new TextBlock { Text = conv.Text, TextWrapping = TextWrapping.Wrap, FontSize = 15 }; - return CreateStandardDialog(conv, content); - } - } - } - - private static DialogControl CreateStandardDialog(ModMain.MyMsgBoxConverter conv, UIElement content) - { - var dialog = new DialogControl - { - Title = conv.Title, - IsWarn = conv.IsWarn, - DialogContent = content, - }; - dialog.AddButton(conv.Button1, conv.Button1Action, isPrimary: true, id: conv.ButtonIds[0]); - if (!string.IsNullOrEmpty(conv.Button2)) - dialog.AddButton(conv.Button2, conv.Button2Action, id: conv.ButtonIds[1]); - if (!string.IsNullOrEmpty(conv.Button3)) - dialog.AddButton(conv.Button3, conv.Button3Action, id: conv.ButtonIds[2]); - conv.WaitFrame = dialog.WaitFrame; - dialog.OnClosed += result => - { - conv.Result = result; - conv.OnCloseCallback?.Invoke(result); - }; - return dialog; - } - - private static DialogControl CreateInputDialog(ModMain.MyMsgBoxConverter conv) - { - var stack = new StackPanel(); - if (!string.IsNullOrEmpty(conv.Text)) - { - stack.Children.Add(new TextBlock - { - Text = conv.Text, - TextWrapping = TextWrapping.Wrap, - FontSize = 15, - Margin = new Thickness(0, 0, 0, 7), - }); - } - - var textBox = new MyTextBox - { - Text = (string)(conv.Content ?? ""), - HintText = conv.HintText, - ValidateRules = conv.ValidateRules ?? [], - MinWidth = 450, - }; - stack.Children.Add(textBox); - - var dialog = new DialogControl - { - Title = conv.Title, - IsWarn = conv.IsWarn, - DialogContent = stack, - }; - - dialog.AddButton(conv.Button1, onClick: () => - { - textBox.Validate(); - if (!textBox.IsValidated) return; - conv.Result = textBox.Text; - dialog.Close(conv.ButtonIds[0]); - }, isPrimary: true, id: conv.ButtonIds[0]); - - if (!string.IsNullOrEmpty(conv.Button2)) - { - dialog.AddButton(conv.Button2, onClick: () => - { - conv.Result = null; - dialog.Close(conv.ButtonIds[1]); - }, id: conv.ButtonIds[1]); - } - - conv.WaitFrame = dialog.WaitFrame; - return dialog; - } - - private static DialogControl CreateSelectDialog(ModMain.MyMsgBoxConverter conv) - { - var panel = new StackPanel(); - var dialog = new DialogControl - { - Title = conv.Title, - IsWarn = conv.IsWarn, - DialogContent = panel, - }; - - var b1 = dialog.AddButton(conv.Button1, onClick: () => - { - // handled by extra Click below - }, isPrimary: true, id: conv.ButtonIds[0]); - b1.IsEnabled = false; - - if (!string.IsNullOrEmpty(conv.Button2)) - { - dialog.AddButton(conv.Button2, onClick: () => - { - conv.Result = null; - dialog.Close(conv.ButtonIds[1]); - }, id: conv.ButtonIds[1]); - } - - var selectedIndex = -1; - var index = 0; - foreach (var raw in (IEnumerable)conv.Content!) - { - var item = MyVirtualizingElement.TryInit((FrameworkElement)raw); - if (item is IMyRadio radio) - { - if (item is MyListItem listItem) - { - listItem.Type = MyListItem.CheckType.RadioBox; - listItem.MinHeight = 24; - } - else if (item is MyRadioBox radioBox) - { - radioBox.MinHeight = 24; - } - - var currentIndex = index; - radio.Check += (_, _) => - { - selectedIndex = currentIndex; - b1.IsEnabled = true; - }; - panel.Children.Add((UIElement)radio); - index++; - } - } - - // Override Btn1's dummy onClick with real logic - b1.Click += (_, _) => - { - if (selectedIndex < 0) return; - conv.Result = selectedIndex; - dialog.Close(conv.ButtonIds[0]); - }; - - conv.WaitFrame = dialog.WaitFrame; - return dialog; - } - // -- internal show logic -- private void ShowOnUi(DialogContext context) diff --git a/Plain Craft Launcher 2/Dialog/LegacyDialog.cs b/Plain Craft Launcher 2/Dialog/LegacyDialog.cs index 49eedc60a..180f0b2b5 100644 --- a/Plain Craft Launcher 2/Dialog/LegacyDialog.cs +++ b/Plain Craft Launcher 2/Dialog/LegacyDialog.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Windows.Threading; using FluentValidation; using PCL.Core.App.Localization; using PCL.Core.UI; @@ -11,41 +10,6 @@ namespace PCL; public static partial class ModMain { - /// - /// 等待显示的弹窗。 - /// - public static List WaitingMyMsgBox { get; } = []; - - public class MyMsgBoxConverter - { - public object AuthUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; - public string Button1 = ""; - public Action? Button1Action; - public string Button2 = ""; - public Action? Button2Action; - public string Button3 = ""; - public Action? Button3Action; - public object? Content; - public bool ForceWait; - public bool HighLight; - public string HintText = ""; - public bool IsExited; - public bool IsWarn; - public object? Result; - public string? Text; - public string? Title; - public MyMsgBoxType Type; - public Collection>? ValidateRules; - public DispatcherFrame WaitFrame = new(true); - public int[] ButtonIds = [1, 2, 3]; - public Action? OnCloseCallback; - } - - public enum MyMsgBoxType - { - Text, Select, Input, Login, Markdown - } - #region Legacy wrappers — delegate to DialogManager public static int MyMsgBox(string caption, string? title = null, string? button1 = null, string? button2 = "", @@ -106,15 +70,5 @@ public static void MsgBoxWrapper_OnShow(string message, string caption, ICollect result = DialogManager.Instance?.ShowLegacy(message, caption, buttons, theme, block) ?? 1; } - public static void Dialog_OnShow(DialogContext context) - { - // Forward to DialogManager for backward compat with PCL.Core.UI.Dialog.OnShow event - if (DialogManager.Instance is null) return; - if (context.Block) - DialogManager.Instance.Show(context); - else - _ = DialogManager.Instance.ShowAsync(context); - } - #endregion } diff --git a/Plain Craft Launcher 2/FormMain.xaml.cs b/Plain Craft Launcher 2/FormMain.xaml.cs index 1b3b2d837..1f22ee0f4 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.cs +++ b/Plain Craft Launcher 2/FormMain.xaml.cs @@ -696,10 +696,9 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) var msg = PanMsg.Children[0]; Action? enterAction = msg switch { - MyMsgInput input => () => input.Btn1_Click(sender, null), - MyMsgSelect select => () => select.Btn1_Click(sender, null), - MyMsgText text => () => text.Btn1_Click(sender, null), - MyMsgMarkdown markdown => () => markdown.Btn1_Click(sender, null), + DialogControl dlg => dlg._buttons.Count > 0 + ? () => dlg._buttons[0].RaiseEvent(new RoutedEventArgs(Button.ClickEvent)) + : null, MyMsgLogin login => () => login.Btn1_Click(sender, null), _ => null }; @@ -712,22 +711,12 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) var msg = PanMsg.Children[0]; Action? escapeAction = msg switch { - MyMsgInput input => input.Btn2.Visibility == Visibility.Visible - ? () => input.Btn2_Click(sender, null) - : () => input.Btn1_Click(sender, null), - MyMsgSelect select => select.Btn2.Visibility == Visibility.Visible - ? () => select.Btn2_Click(sender, null) - : () => select.Btn1_Click(sender, null), - MyMsgText text => text.Btn3.Visibility == Visibility.Visible - ? () => text.Btn3_Click(sender, null) - : text.Btn2.Visibility == Visibility.Visible - ? () => text.Btn2_Click(sender, null) - : () => text.Btn1_Click(sender, null), - MyMsgMarkdown markdown => markdown.Btn3.Visibility == Visibility.Visible - ? () => markdown.Btn3_Click(sender, null) - : markdown.Btn2.Visibility == Visibility.Visible - ? () => markdown.Btn2_Click(sender, null) - : () => markdown.Btn1_Click(sender, null), + DialogControl dlg => dlg._buttons.Count switch + { + >= 3 => () => dlg._buttons[2].RaiseEvent(new RoutedEventArgs(Button.ClickEvent)), + >= 2 => () => dlg._buttons[1].RaiseEvent(new RoutedEventArgs(Button.ClickEvent)), + _ => () => dlg._buttons[0].RaiseEvent(new RoutedEventArgs(Button.ClickEvent)), + }, MyMsgLogin login => login.Btn3.Visibility == Visibility.Visible ? () => login.Btn3_Click(sender, null) : () => login.Btn1_Click(sender, null), @@ -789,7 +778,7 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) private void FormMain_MouseDown(object sender, MouseButtonEventArgs e) { // 鼠标侧键返回上一级 - if (ModMain.frmMain!.PanMsg.Children.Count > 0 || ModMain.WaitingMyMsgBox.Any()) + if (ModMain.frmMain!.PanMsg.Children.Count > 0) return; // 弹窗中(#5513) if (e.ChangedButton == MouseButton.XButton1 || e.ChangedButton == MouseButton.XButton2) TriggerPageBack(); diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs index 411d26dbc..9875cad89 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs @@ -890,13 +890,23 @@ private static string[] MsLoginStep1New(ModLoader.LoaderTask + { + var login = new MyMsgLogin(prepareJson, "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", + result => + { + loginResult = result; + loginReady.Set(); + }); + ModMain.frmMain!.PanMsg.Children.Add(login); + }); + + loginReady.Wait(); + if (loginResult is ModBase.RestartException) { if (ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.PasswordRequired.Message", ModBase.vbLQ, ModBase.vbRQ), @@ -907,9 +917,9 @@ private static string[] MsLoginStep1New(ModLoader.LoaderTask diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.cs b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.cs index 6e5a6481b..35d8ff6f8 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.cs @@ -12,16 +12,34 @@ namespace PCL; public partial class MyMsgLogin { private readonly JsonObject data; - private string deviceCode; // 用于轮询的设备代码 - private string oAuthUrl = ""; // OAuth 轮询验证地址 - private string userCode; // 需要用户在网页上输入的设备代码 - private string website; // 验证网页的网址 + private string deviceCode; + private string oAuthUrl = ""; + private string userCode; + private string website; private Task? workingThread; + private readonly Action? _onFinished; + private bool _finished; + private readonly int uuid = ModBase.GetUuid(); - public MyMsgLogin() + public MyMsgLogin(JsonObject data, string authUrl, Action onFinished) { - InitializeComponent(); - // Handles + try + { + InitializeComponent(); + Btn1.Name += ModBase.GetUuid(); + Btn2.Name += ModBase.GetUuid(); + Btn3.Name += ModBase.GetUuid(); + this.data = data; + oAuthUrl = authUrl; + _onFinished = onFinished; + ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d); + Init(); + } + catch (Exception ex) + { + ModBase.Log(ex, Lang.Text("Launch.Account.LoginDialog.Error.Init"), ModBase.LogLevel.Hint); + } + Loaded += Load; Btn1.Click += Btn1_Click; Btn3.Click += Btn3_Click; @@ -31,13 +49,12 @@ public MyMsgLogin() private void Finished(object result) { - if (myConverter.IsExited) - return; - myConverter.IsExited = true; - myConverter.Result = result; + if (_finished) return; + _finished = true; + _onFinished?.Invoke(result); ModBase.RunInUi(Close); Thread.Sleep(200); - ModMain.frmMain.ShowWindowToTop(); + ModMain.frmMain!.ShowWindowToTop(); } private void Init() @@ -71,14 +88,14 @@ private record ErrorBody( private async Task WorkThread() { await Task.Delay(2000).ConfigureAwait(false); - if (myConverter.IsExited) + if (_finished) return; ModBase.OpenWebsite(website); ModBase.ClipboardSet(userCode); var delayTime = (data["interval"].ToObject() - 1) * 1000; // 轮询 var unknownFailureCount = 0; - while (!myConverter.IsExited) + while (!_finished) { try { @@ -135,31 +152,6 @@ await Task.Delay(delayTime) #region 弹窗 - private readonly ModMain.MyMsgBoxConverter myConverter; - private readonly int uuid = ModBase.GetUuid(); - - public MyMsgLogin(ModMain.MyMsgBoxConverter converter) - { - try - { - InitializeComponent(); - Btn1.Name += ModBase.GetUuid(); - Btn2.Name += ModBase.GetUuid(); - Btn3.Name += ModBase.GetUuid(); - myConverter = converter; - ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d); - data = (JsonObject)converter.Content; - oAuthUrl = converter.AuthUrl?.ToString() ?? ""; - Init(); - } - catch (Exception ex) - { - ModBase.Log(ex, Lang.Text("Launch.Account.LoginDialog.Error.Init"), ModBase.LogLevel.Hint); - } - - Loaded += Load; - } - private void Load(object sender, EventArgs e) { try @@ -168,9 +160,7 @@ private void Load(object sender, EventArgs e) Opacity = 0d; ModAnimation.AniStart( ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, - (myConverter.IsWarn - ? new ModBase.MyColor(140d, 80d, 0d, 0d) - : new ModBase.MyColor(90d, 0d, 0d, 0d)) - ModMain.frmMain.PanMsgBackground.Background, 200), + (new ModBase.MyColor(90d, 0d, 0d, 0d)) - ModMain.frmMain.PanMsgBackground.Background, 200), "PanMsgBackground Background"); ModAnimation.AniStart( new[] @@ -198,7 +188,7 @@ private void Close() { ModAnimation.AaCode(() => { - if (!ModMain.WaitingMyMsgBox.Any()) + if ((ModMain.frmMain?.PanMsg?.Children.Count ?? 0) <= 1) ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, new ModBase.MyColor(0d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200, From 3dec5229c717eb99d0675a1841e84ad6c1e5d196 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 22:14:05 +0800 Subject: [PATCH 10/14] fix(dialog): catch TaskCanceledException on Dispatcher.Invoke ShowOnUi, ShowInput, ShowSelect now guard against dispatcher shutdown by catching TaskCanceledException when marshaling to UI thread. --- Plain Craft Launcher 2/Dialog/DialogManager.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Plain Craft Launcher 2/Dialog/DialogManager.cs b/Plain Craft Launcher 2/Dialog/DialogManager.cs index 5a33ca276..e6d2442a5 100644 --- a/Plain Craft Launcher 2/Dialog/DialogManager.cs +++ b/Plain Craft Launcher 2/Dialog/DialogManager.cs @@ -136,7 +136,8 @@ public string ShowInput(string title, string text = "", string defaultInput = "" } }; - _mainForm.Dispatcher.Invoke(showOnUi); + try { _mainForm.Dispatcher.Invoke(showOnUi); } + catch (TaskCanceledException) { } return result ?? ""; } @@ -201,7 +202,8 @@ public string ShowInput(string title, string text = "", string defaultInput = "" } }; - _mainForm.Dispatcher.Invoke(showOnUi); + try { _mainForm.Dispatcher.Invoke(showOnUi); } + catch (TaskCanceledException) { } return result; } @@ -245,7 +247,7 @@ public void Tick() private void ShowOnUi(DialogContext context) { - if (_mainForm is null) + if (_mainForm is null || _mainForm.Dispatcher.HasShutdownStarted) { Interaction.MsgBox(context.Caption, MsgBoxStyle.OkOnly, context.Title); context.Result = 1; @@ -256,7 +258,8 @@ private void ShowOnUi(DialogContext context) if (_mainForm.Dispatcher.CheckAccess()) ShowDialogOnUiThread(context); else - _mainForm.Dispatcher.Invoke(() => ShowDialogOnUiThread(context)); + try { _mainForm.Dispatcher.Invoke(() => ShowDialogOnUiThread(context)); } + catch (TaskCanceledException) { } } private void ShowDialogOnUiThread(DialogContext context) From 9e4d841bab89aaf3bbe9899fbd043170d699ff0e Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 23:03:34 +0800 Subject: [PATCH 11/14] feat(dialog): generic Dialog, Show(content), cancel flag, keyboard refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DialogControl: ShowTitle property, isCancel in AddButton, auto-collapse button row. DialogButton: IsCancel, Cancel/No presets set it. DialogContext: ShowTitle. DialogManager: Show(UIElement) returns handle, Show() with GetResultAsync(). Dialog.cs: Dialog generic wrapper. Legacy: last multi-button auto-marked cancel. FormMain_KeyDown: Enter→first non-cancel, Esc→first cancel, no fallback. --- .../Controls/Dialog/DialogControl.cs | 17 +++++- .../Controls/Dialog/DialogControl.xaml | 2 +- Plain Craft Launcher 2/Dialog/Dialog.cs | 27 ++++++++++ Plain Craft Launcher 2/Dialog/DialogButton.cs | 8 +-- .../Dialog/DialogContext.cs | 2 + .../Dialog/DialogManager.cs | 54 ++++++++++++++++++- Plain Craft Launcher 2/FormMain.xaml.cs | 26 +++++---- .../Modules/Base/ModBase.cs | 9 +--- 8 files changed, 120 insertions(+), 25 deletions(-) create mode 100644 Plain Craft Launcher 2/Dialog/Dialog.cs diff --git a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs index a9efdb21b..41f9d10e1 100644 --- a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs +++ b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs @@ -24,6 +24,18 @@ public string Title public bool IsWarn { get; set; } + private bool _showTitle = true; + internal bool ShowTitle + { + get => _showTitle; + set + { + _showTitle = value; + LabTitle.Visibility = value ? Visibility.Visible : Visibility.Collapsed; + ShapeLine.Visibility = value ? Visibility.Visible : Visibility.Collapsed; + } + } + public UIElement? DialogContent { get => (UIElement?)ContentArea.Content; @@ -47,7 +59,7 @@ public DialogControl() Loaded += OnLoad; } - public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = false, int id = 0) + public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = false, int id = 0, bool isCancel = false) { var btn = new MyButton { @@ -57,6 +69,7 @@ public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = : MyButton.ColorState.Normal, Visibility = string.IsNullOrEmpty(text) ? Visibility.Collapsed : Visibility.Visible, IsEnabled = true, + Tag = isCancel ? "Cancel" : null, }; btn.ApplyTemplate(); btn.TextPadding = new Thickness(7); @@ -91,6 +104,8 @@ private void OnLoad(object sender, RoutedEventArgs e) _buttons[0].ColorType = MyButton.ColorState.Highlight; if (_buttons.Count > 0) _buttons[0].Focus(); + else + PanBtn.Visibility = Visibility.Collapsed; Opacity = 0d; ModAnimation.AniStart( diff --git a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.xaml b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.xaml index 6f861cd8c..967d094a8 100644 --- a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.xaml +++ b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.xaml @@ -18,7 +18,7 @@ - + diff --git a/Plain Craft Launcher 2/Dialog/Dialog.cs b/Plain Craft Launcher 2/Dialog/Dialog.cs new file mode 100644 index 000000000..f9b9c503f --- /dev/null +++ b/Plain Craft Launcher 2/Dialog/Dialog.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using System.Windows; + +namespace PCL; + +public class Dialog where T : FrameworkElement, new() +{ + private readonly DialogControl _dialog; + + public T Content { get; } + + public Dialog(DialogControl dialog) + { + _dialog = dialog; + Content = new T(); + _dialog.DialogContent = Content; + } + + public Task GetResultAsync() + { + var tcs = new TaskCompletionSource(); + _dialog.OnClosed += result => tcs.TrySetResult(result); + return tcs.Task; + } + + public void Close(int result) => _dialog.Close(result); +} diff --git a/Plain Craft Launcher 2/Dialog/DialogButton.cs b/Plain Craft Launcher 2/Dialog/DialogButton.cs index 53f360af7..920fd29ed 100644 --- a/Plain Craft Launcher 2/Dialog/DialogButton.cs +++ b/Plain Craft Launcher 2/Dialog/DialogButton.cs @@ -9,24 +9,26 @@ public class DialogButton public int Id { get; set; } public Action? OnClick { get; set; } public bool IsPrimary { get; set; } + public bool IsCancel { get; set; } - public DialogButton(string text, Action? onClick = null, bool isPrimary = false, int id = 0) + public DialogButton(string text, Action? onClick = null, bool isPrimary = false, int id = 0, bool isCancel = false) { Text = text; OnClick = onClick; IsPrimary = isPrimary; Id = id; + IsCancel = isCancel; } public static DialogButton Confirm(string? text = null) => new(text ?? Lang.Text("Common.Action.Confirm"), isPrimary: true, id: DialogResult.Ok); public static DialogButton Cancel(string? text = null) - => new(text ?? Lang.Text("Common.Action.Cancel"), id: DialogResult.Cancel); + => new(text ?? Lang.Text("Common.Action.Cancel"), id: DialogResult.Cancel, isCancel: true); public static DialogButton Yes(string? text = null) => new(text ?? Lang.Text("Common.Option.Yes"), isPrimary: true, id: DialogResult.Yes); public static DialogButton No(string? text = null) - => new(text ?? Lang.Text("Common.Option.No"), id: DialogResult.No); + => new(text ?? Lang.Text("Common.Option.No"), id: DialogResult.No, isCancel: true); } diff --git a/Plain Craft Launcher 2/Dialog/DialogContext.cs b/Plain Craft Launcher 2/Dialog/DialogContext.cs index f540f42b7..c3f2bde5d 100644 --- a/Plain Craft Launcher 2/Dialog/DialogContext.cs +++ b/Plain Craft Launcher 2/Dialog/DialogContext.cs @@ -9,8 +9,10 @@ public class DialogContext public string Title { get; set; } = ""; public DialogTheme Theme { get; set; } = DialogTheme.Info; public bool Block { get; set; } = true; + public bool ShowTitle { get; set; } = true; public object? Content { get; set; } public Collection Buttons { get; set; } = []; public int Result { get; set; } public Action? OnClosed { get; set; } + internal DialogControl? _dialog; } diff --git a/Plain Craft Launcher 2/Dialog/DialogManager.cs b/Plain Craft Launcher 2/Dialog/DialogManager.cs index e6d2442a5..b9b3e56f6 100644 --- a/Plain Craft Launcher 2/Dialog/DialogManager.cs +++ b/Plain Craft Launcher 2/Dialog/DialogManager.cs @@ -48,6 +48,44 @@ public Task ShowAsync(DialogContext context) return tcs.Task; } + // -- lightweight: pure content, return handle -- + + public DialogControl Show(UIElement content, string? title = null, + DialogButton[]? buttons = null, bool isWarn = false, bool showTitle = true) + { + var ctx = new DialogContext + { + Title = title ?? "", + Theme = isWarn ? DialogTheme.Warning : DialogTheme.Info, + Block = false, + ShowTitle = showTitle && !string.IsNullOrEmpty(title), + Content = content, + Buttons = new Collection(), + }; + if (buttons is not null) + foreach (var b in buttons) ctx.Buttons.Add(b); + ShowOnUi(ctx); + return ctx._dialog!; + } + + // -- Show: typed content factory -- + + public Dialog Show(string? title = null, DialogButton[]? buttons = null, + bool isWarn = false, bool showTitle = true) where T : FrameworkElement, new() + { + var dialog = new DialogControl + { + Title = title ?? "", + IsWarn = isWarn, + ShowTitle = showTitle && !string.IsNullOrEmpty(title), + }; + var wrapper = new Dialog(dialog); + if (buttons is not null) + foreach (var b in buttons) dialog.AddButton(b.Text, b.OnClick, b.IsPrimary, b.Id, b.IsCancel); + _showContentOnUi(dialog); + return wrapper; + } + // -- convenience methods -- public int ShowText(string caption, string? title = null, @@ -283,6 +321,7 @@ private void ShowDialogOnUiThread(DialogContext context) { Title = context.Title, IsWarn = isWarn, + ShowTitle = context.ShowTitle, DialogContent = content as UIElement, }; @@ -291,9 +330,11 @@ private void ShowDialogOnUiThread(DialogContext context) var btn = context.Buttons[i]; var isPrimary = i == 0 || btn.IsPrimary; var id = btn.Id > 0 ? btn.Id : i + 1; - dialog.AddButton(btn.Text, btn.OnClick, isPrimary, id); + dialog.AddButton(btn.Text, btn.OnClick, isPrimary, id, btn.IsCancel); } + context._dialog = dialog; + _mainForm.PanMsg.Children.Add(dialog); if (context.Block) @@ -321,11 +362,20 @@ private void ShowDialogOnUiThread(DialogContext context) } } + private void _showContentOnUi(DialogControl dialog) + { + _mainForm.Dispatcher.Invoke(() => + { + _mainForm.PanMsg.Children.Add(dialog); + }); + } + private static Collection _BuildButtons(string[] buttonTexts) { var list = new Collection(); for (var i = 0; i < buttonTexts.Length; i++) - list.Add(new DialogButton(buttonTexts[i], isPrimary: i == 0, id: i + 1)); + list.Add(new DialogButton(buttonTexts[i], isPrimary: i == 0, id: i + 1, + isCancel: buttonTexts.Length > 1 && i == buttonTexts.Length - 1)); return list; } } diff --git a/Plain Craft Launcher 2/FormMain.xaml.cs b/Plain Craft Launcher 2/FormMain.xaml.cs index 1f22ee0f4..acf6af092 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.cs +++ b/Plain Craft Launcher 2/FormMain.xaml.cs @@ -696,9 +696,12 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) var msg = PanMsg.Children[0]; Action? enterAction = msg switch { - DialogControl dlg => dlg._buttons.Count > 0 - ? () => dlg._buttons[0].RaiseEvent(new RoutedEventArgs(Button.ClickEvent)) - : null, + DialogControl dlg when dlg._buttons.Count > 0 => () => + { + var confirm = dlg._buttons.FirstOrDefault(b => b.Tag as string != "Cancel") + ?? dlg._buttons[0]; + confirm.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); + }, MyMsgLogin login => () => login.Btn1_Click(sender, null), _ => null }; @@ -711,15 +714,18 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) var msg = PanMsg.Children[0]; Action? escapeAction = msg switch { - DialogControl dlg => dlg._buttons.Count switch + DialogControl dlg when dlg._buttons.Count > 0 => () => { - >= 3 => () => dlg._buttons[2].RaiseEvent(new RoutedEventArgs(Button.ClickEvent)), - >= 2 => () => dlg._buttons[1].RaiseEvent(new RoutedEventArgs(Button.ClickEvent)), - _ => () => dlg._buttons[0].RaiseEvent(new RoutedEventArgs(Button.ClickEvent)), + var cancel = dlg._buttons.FirstOrDefault(b => b.Tag as string == "Cancel"); + cancel?.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); + }, + MyMsgLogin login => () => + { + if (login.Btn3.Visibility == Visibility.Visible) + login.Btn3_Click(sender, null); + else + login.Btn1_Click(sender, null); }, - MyMsgLogin login => login.Btn3.Visibility == Visibility.Visible - ? () => login.Btn3_Click(sender, null) - : () => login.Btn1_Click(sender, null), _ => null }; escapeAction?.Invoke(); diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.cs b/Plain Craft Launcher 2/Modules/Base/ModBase.cs index edd7f29fb..c6db67b03 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.cs +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.cs @@ -2425,20 +2425,13 @@ public static bool IsInstanceOfGenericType(this Type genericType, object obj) } private static int uuid = 1; - private static object uuidLock; /// /// 获取一个全程序内不会重复的数字(伪 Uuid)。 /// public static int GetUuid() { - if (uuidLock is null) - uuidLock = new object(); - lock (uuidLock) - { - uuid += 1; - return uuid; - } + return Interlocked.Increment(ref uuid); } /// From 99a6acb3525f5d998dca7842807614064aed9bfa Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 23:09:15 +0800 Subject: [PATCH 12/14] =?UTF-8?q?refactor:=20rename=20MyMsgLogin=20?= =?UTF-8?q?=E2=86=92=20DialogMsOAuthLogin,=20use=20DialogManager.ShowOAuth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MyMsgLogin.xaml/.cs → DialogMsOAuthLogin.xaml/.cs DialogManager.ShowOAuth(data, authUrl) encapsulates creation + blocking. ModLaunch.cs simplified to single call: DialogManager.Instance.ShowOAuth(...) FormMain keyboard handler updated for new class name. --- .../Dialog/DialogManager.cs | 24 +++++++++++++++++++ Plain Craft Launcher 2/FormMain.xaml.cs | 4 ++-- .../Modules/Minecraft/ModLaunch.cs | 19 +++------------ ...yMsgLogin.xaml => DialogMsOAuthLogin.xaml} | 2 +- ...gin.xaml.cs => DialogMsOAuthLogin.xaml.cs} | 4 ++-- 5 files changed, 32 insertions(+), 21 deletions(-) rename Plain Craft Launcher 2/Pages/PageLaunch/{MyMsgLogin.xaml => DialogMsOAuthLogin.xaml} (99%) rename Plain Craft Launcher 2/Pages/PageLaunch/{MyMsgLogin.xaml.cs => DialogMsOAuthLogin.xaml.cs} (98%) diff --git a/Plain Craft Launcher 2/Dialog/DialogManager.cs b/Plain Craft Launcher 2/Dialog/DialogManager.cs index b9b3e56f6..8a538383f 100644 --- a/Plain Craft Launcher 2/Dialog/DialogManager.cs +++ b/Plain Craft Launcher 2/Dialog/DialogManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -11,6 +12,7 @@ using Microsoft.VisualBasic; using PCL.Core.App.Localization; using PCL.Core.UI; +using PCL.Core.Utils; namespace PCL; @@ -86,6 +88,28 @@ public Dialog Show(string? title = null, DialogButton[]? buttons = null, return wrapper; } + // -- OAuth login (specialized) -- + + public object? ShowOAuth(JsonObject data, string authUrl) + { + object? result = null; + var ready = new ManualResetEventSlim(false); + + _mainForm.Dispatcher.Invoke(() => + { + var login = new DialogMsOAuthLogin(data, authUrl, r => + { + result = r; + ready.Set(); + }); + _mainForm.PanMsg.Children.Add(login); + _mainForm.PanMsgBackground.Visibility = Visibility.Visible; + }); + + ready.Wait(); + return result; + } + // -- convenience methods -- public int ShowText(string caption, string? title = null, diff --git a/Plain Craft Launcher 2/FormMain.xaml.cs b/Plain Craft Launcher 2/FormMain.xaml.cs index acf6af092..973d682cf 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.cs +++ b/Plain Craft Launcher 2/FormMain.xaml.cs @@ -702,7 +702,7 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) ?? dlg._buttons[0]; confirm.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); }, - MyMsgLogin login => () => login.Btn1_Click(sender, null), + DialogMsOAuthLogin login => () => login.Btn1_Click(sender, null), _ => null }; enterAction?.Invoke(); @@ -719,7 +719,7 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) var cancel = dlg._buttons.FirstOrDefault(b => b.Tag as string == "Cancel"); cancel?.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); }, - MyMsgLogin login => () => + DialogMsOAuthLogin login => () => { if (login.Btn3.Visibility == Visibility.Visible) login.Btn3_Click(sender, null); diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs index 9875cad89..2eabf0eda 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.cs @@ -890,22 +890,9 @@ private static string[] MsLoginStep1New(ModLoader.LoaderTask - { - var login = new MyMsgLogin(prepareJson, "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", - result => - { - loginResult = result; - loginReady.Set(); - }); - ModMain.frmMain!.PanMsg.Children.Add(login); - }); - - loginReady.Wait(); + // 弹窗 — 使用 DialogManager 显示 OAuth 登录 + var loginResult = DialogManager.Instance?.ShowOAuth(prepareJson, + "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"); if (loginResult is ModBase.RestartException) { if (ModMain.MyMsgBox( diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml similarity index 99% rename from Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml rename to Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml index f785c6484..1d85a2c39 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml +++ b/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml @@ -1,4 +1,4 @@ - onFinished) + public DialogMsOAuthLogin(JsonObject data, string authUrl, Action onFinished) { try { From 024de3a720eabfcf0a8fa5635e4e8e9e7da02015 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Wed, 17 Jun 2026 23:22:21 +0800 Subject: [PATCH 13/14] fix: dialog color --- .../Controls/Dialog/DialogControl.cs | 2 + .../Dialog/DialogManager.cs | 12 +- Plain Craft Launcher 2/FormMain.xaml.cs | 8 - .../Pages/PageLaunch/DialogMsOAuthLogin.xaml | 62 ---- .../PageLaunch/DialogMsOAuthLogin.xaml.cs | 316 ++++++++---------- 5 files changed, 146 insertions(+), 254 deletions(-) delete mode 100644 Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml diff --git a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs index 41f9d10e1..a56bc762f 100644 --- a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs +++ b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs @@ -100,6 +100,8 @@ private void OnLoad(object sender, RoutedEventArgs e) { try { + if (IsWarn) + LabTitle.SetResourceReference(TextBlock.ForegroundProperty, "ColorBrushRedLight"); if (_buttons.Count > 1 && _buttons[0].ColorType != MyButton.ColorState.Red) _buttons[0].ColorType = MyButton.ColorState.Highlight; if (_buttons.Count > 0) diff --git a/Plain Craft Launcher 2/Dialog/DialogManager.cs b/Plain Craft Launcher 2/Dialog/DialogManager.cs index 8a538383f..b708fdac6 100644 --- a/Plain Craft Launcher 2/Dialog/DialogManager.cs +++ b/Plain Craft Launcher 2/Dialog/DialogManager.cs @@ -97,16 +97,22 @@ public Dialog Show(string? title = null, DialogButton[]? buttons = null, _mainForm.Dispatcher.Invoke(() => { - var login = new DialogMsOAuthLogin(data, authUrl, r => + var dialog = new DialogControl + { + IsWarn = false, + ShowTitle = true, + }; + _ = new DialogMsOAuthLogin(dialog, data, authUrl, r => { result = r; ready.Set(); }); - _mainForm.PanMsg.Children.Add(login); - _mainForm.PanMsgBackground.Visibility = Visibility.Visible; + _mainForm.PanMsg.Children.Add(dialog); }); ready.Wait(); + Thread.Sleep(200); + ModMain.frmMain!.ShowWindowToTop(); return result; } diff --git a/Plain Craft Launcher 2/FormMain.xaml.cs b/Plain Craft Launcher 2/FormMain.xaml.cs index 973d682cf..9fe422471 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.cs +++ b/Plain Craft Launcher 2/FormMain.xaml.cs @@ -702,7 +702,6 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) ?? dlg._buttons[0]; confirm.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); }, - DialogMsOAuthLogin login => () => login.Btn1_Click(sender, null), _ => null }; enterAction?.Invoke(); @@ -719,13 +718,6 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) var cancel = dlg._buttons.FirstOrDefault(b => b.Tag as string == "Cancel"); cancel?.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); }, - DialogMsOAuthLogin login => () => - { - if (login.Btn3.Visibility == Visibility.Visible) - login.Btn3_Click(sender, null); - else - login.Btn1_Click(sender, null); - }, _ => null }; escapeAction?.Invoke(); diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml deleted file mode 100644 index 1d85a2c39..000000000 --- a/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml.cs b/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml.cs index 4c3c7df36..a72cceded 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml.cs @@ -1,224 +1,178 @@ +using System; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; using System.Windows.Controls; using System.Windows.Input; using PCL.Core.App; using PCL.Core.App.Localization; -using PCL.Core.UI.Controls; using PCL.Core.Utils; using PCL.Core.IO.Net.Http; -using System.Text.Json.Serialization; namespace PCL; -public partial class DialogMsOAuthLogin +public class DialogMsOAuthLogin { - private readonly JsonObject data; - private string deviceCode; - private string oAuthUrl = ""; - private string userCode; - private string website; - private Task? workingThread; - private readonly Action? _onFinished; + private readonly JsonObject _data; private bool _finished; - private readonly int uuid = ModBase.GetUuid(); - - public DialogMsOAuthLogin(JsonObject data, string authUrl, Action onFinished) - { - try - { - InitializeComponent(); - Btn1.Name += ModBase.GetUuid(); - Btn2.Name += ModBase.GetUuid(); - Btn3.Name += ModBase.GetUuid(); - this.data = data; - oAuthUrl = authUrl; - _onFinished = onFinished; - ShapeLine.StrokeThickness = ModBase.GetWPFSize(1d); - Init(); - } - catch (Exception ex) - { - ModBase.Log(ex, Lang.Text("Launch.Account.LoginDialog.Error.Init"), ModBase.LogLevel.Hint); - } + private Task? _workingThread; + private TextBlock _caption = null!; + private MyButton _btnCancel = null!; + private string _website = ""; + private string _userCode = ""; - Loaded += Load; - Btn1.Click += Btn1_Click; - Btn3.Click += Btn3_Click; - PanBorder.MouseLeftButtonDown += Drag; - LabTitle.MouseLeftButtonDown += Drag; - } - - private void Finished(object result) + public DialogMsOAuthLogin(DialogControl dialog, JsonObject data, string authUrl, Action onFinished) { - if (_finished) return; - _finished = true; - _onFinished?.Invoke(result); - ModBase.RunInUi(Close); - Thread.Sleep(200); - ModMain.frmMain!.ShowWindowToTop(); - } + _data = data; - private void Init() - { - userCode = (string)data["user_code"]; - deviceCode = (string)data["device_code"]; + _userCode = (string)data["user_code"]!; + var deviceCode = (string)data["device_code"]!; ModBase.ClipboardSet(deviceCode); + + string captionText; if (data["verification_uri_complete"] is not null) { - website = (string)data["verification_uri_complete"]; - LabCaption.Text = Lang.Text("Launch.Account.LoginDialog.MicrosoftInstructions.WithAutoFill", userCode, website); + _website = (string)data["verification_uri_complete"]!; + captionText = Lang.Text("Launch.Account.LoginDialog.MicrosoftInstructions.WithAutoFill", _userCode, _website); } else { - website = (string)data["verification_uri"]; - LabCaption.Text = Lang.Text("Launch.Account.LoginDialog.MicrosoftInstructions", userCode, website); + _website = (string)data["verification_uri"]!; + captionText = Lang.Text("Launch.Account.LoginDialog.MicrosoftInstructions", _userCode, _website); } - // 设置 UI - LabTitle.Text = Lang.Text("Launch.Account.LoginDialog.MinecraftLogin"); - CustomEventService.SetEventData(Btn1, website); - CustomEventService.SetEventData(Btn2, userCode); - // 启动工作线程 - workingThread = WorkThread(); - } + // Build content + _caption = new TextBlock + { + Text = captionText, + FontSize = 15, + LineHeight = 18, + TextWrapping = TextWrapping.Wrap, + FontWeight = FontWeights.Normal, + Padding = new Thickness(1), + }; + + var scrollViewer = new MyScrollViewer + { + Content = _caption, + VerticalScrollBarVisibility = ScrollBarVisibility.Auto, + HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled, + DeltaMult = 0.7, + }; + + dialog.Title = Lang.Text("Launch.Account.LoginDialog.MinecraftLogin"); + dialog.DialogContent = scrollViewer; + + // Buttons + var btnReopen = dialog.AddButton( + Lang.Text("Launch.Account.LoginDialog.ReopenWebpage"), + onClick: () => ModBase.OpenWebsite(_website), id: 1); + CustomEventService.SetEventData(btnReopen, _website); + + var btnCopy = dialog.AddButton( + Lang.Text("Launch.Account.LoginDialog.CopyCode"), + onClick: () => ModBase.ClipboardSet(_userCode), id: 2); + CustomEventService.SetEventData(btnCopy, _userCode); + + _btnCancel = dialog.AddButton( + Lang.Text("Common.Action.Cancel"), isCancel: true, id: 3); + + // Finish callback + dialog.OnClosed += result => + { + if (_finished) return; + _finished = true; + if (result == 3) + onFinished(new ThreadInterruptedException()); + }; - private record ErrorBody( - [property: JsonPropertyName("error")] string Error, - [property: JsonPropertyName("error_description")] string Desc); + _btnCancel.Click += (_, _) => + { + _finished = true; + onFinished(new ThreadInterruptedException()); + dialog.Close(3); + }; + + // Start OAuth polling + _workingThread = WorkThread(deviceCode, data, authUrl, onFinished, dialog); + } - private async Task WorkThread() + private async Task WorkThread(string deviceCode, JsonObject data, string oAuthUrl, + Action onFinished, DialogControl dialog) { - await Task.Delay(2000).ConfigureAwait(false); - if (_finished) - return; - ModBase.OpenWebsite(website); - ModBase.ClipboardSet(userCode); - var delayTime = (data["interval"].ToObject() - 1) * 1000; - // 轮询 - var unknownFailureCount = 0; - while (!_finished) + try { - try + await Task.Delay(2000).ConfigureAwait(false); + if (_finished) return; + + if (!string.IsNullOrEmpty(_website)) + ModBase.OpenWebsite(_website); + + ModBase.ClipboardSet(_userCode); + + var delayTime = (data["interval"]!.ToObject() - 1) * 1000; + var unknownFailureCount = 0; + + while (!_finished) { - var bodyData = $"grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id={Secrets.MSOAuthClientId}&device_code={deviceCode}&scope=XboxLive.signin%20offline_access"; - using var result = await HttpRequest - .Create("https://login.microsoftonline.com/consumers/oauth2/v2.0/token") - .WithFormContent(bodyData) - .SendAsync(enableLogging: false) - .ConfigureAwait(false); - if (!result.IsSuccess) + try { - var error = await result.AsJsonAsync() + var bodyData = $"grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id={Secrets.MSOAuthClientId}&device_code={deviceCode}&scope=XboxLive.signin%20offline_access"; + using var resp = await HttpRequest + .Create("https://login.microsoftonline.com/consumers/oauth2/v2.0/token") + .WithFormContent(bodyData) + .SendAsync(enableLogging: false) .ConfigureAwait(false); - switch(error?.Error) + + if (!resp.IsSuccess) { - case "authorization_pending": - { - await Task.Delay(delayTime) - .ConfigureAwait(false); + var error = await resp.AsJsonAsync().ConfigureAwait(false); + switch (error?.Error) + { + case "authorization_pending": + await Task.Delay(delayTime).ConfigureAwait(false); continue; - } - default: - { + default: throw new Exception(error?.Error ?? "Unable to get body"); - } + } } + + var ctx = await resp.AsStringAsync().ConfigureAwait(false); + var resultJson = (JsonObject)ModBase.GetJson(ctx); + ModProfile.ProfileLog($"令牌过期时间:{resultJson["expires_in"]} 秒"); + ModMain.Hint(Lang.Text("Launch.Account.LoginDialog.Success"), ModMain.HintType.Finish); + + _finished = true; + onFinished(new[] { resultJson["access_token"]!.ToString(), resultJson["refresh_token"]!.ToString() }); + dialog.Close(0); + return; } - // 获取结果 - var ctx = await result.AsStringAsync().ConfigureAwait(false); - var resultJson = (JsonObject)ModBase.GetJson(ctx); - ModProfile.ProfileLog($"令牌过期时间:{resultJson["expires_in"]} 秒"); - ModMain.Hint(Lang.Text("Launch.Account.LoginDialog.Success"), ModMain.HintType.Finish); - Finished(new[] { resultJson["access_token"].ToString(), resultJson["refresh_token"].ToString() }); - return; - } - catch (Exception ex) - { - if (unknownFailureCount <= 2) - { - unknownFailureCount += 1; - ModBase.Log(ex, $"正版验证轮询第 {unknownFailureCount} 次失败"); - ModBase.Log(ex.Message); - await Task.Delay(2000).ConfigureAwait(false); - } - else + catch (Exception ex) { - Finished(new Exception(Lang.Text("Launch.Account.LoginDialog.PollingFailed"), ex)); - return; + if (unknownFailureCount <= 2) + { + unknownFailureCount++; + ModBase.Log(ex, $"正版验证轮询第 {unknownFailureCount} 次失败"); + await Task.Delay(2000).ConfigureAwait(false); + } + else + { + _finished = true; + onFinished(new Exception(Lang.Text("Launch.Account.LoginDialog.PollingFailed"), ex)); + dialog.Close(-1); + return; + } } } } - } - - - #region 弹窗 - - private void Load(object sender, EventArgs e) - { - try - { - // 动画 - Opacity = 0d; - ModAnimation.AniStart( - ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, BlurBorder.BackgroundProperty, - (new ModBase.MyColor(90d, 0d, 0d, 0d)) - ModMain.frmMain.PanMsgBackground.Background, 200), - "PanMsgBackground Background"); - ModAnimation.AniStart( - new[] - { - ModAnimation.AaOpacity(this, 1d, 120, 60), - ModAnimation.AaDouble(i => TransformPos.Y += (double)i, - -TransformPos.Y, 300, 60, new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)), - ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, - -TransformRotate.Angle, 300, 60, - new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak)) - }, "MyMsgBox " + uuid); - // 记录日志 - ModBase.Log($"[Control] 正版验证弹窗:{LabTitle.Text}\r\n{LabCaption.Text}"); - } catch (Exception ex) { - ModBase.Log(ex, Lang.Text("Launch.Account.LoginDialog.Error.Load"), ModBase.LogLevel.Hint); + ModBase.Log(ex, Lang.Text("Launch.Account.LoginDialog.Error.Init"), ModBase.LogLevel.Hint); } } - private void Close() - { - // 动画 - ModAnimation.AniStart(new[] - { - ModAnimation.AaCode(() => - { - if ((ModMain.frmMain?.PanMsg?.Children.Count ?? 0) <= 1) - ModAnimation.AniStart(ModAnimation.AaColor(ModMain.frmMain.PanMsgBackground, - BlurBorder.BackgroundProperty, - new ModBase.MyColor(0d, 0d, 0d, 0d) - ModMain.frmMain.PanMsgBackground.Background, 200, - ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Weak))); - }, 30), - ModAnimation.AaOpacity(this, -Opacity, 80, 20), - ModAnimation.AaDouble(i => TransformPos.Y += (double)i, 20d - TransformPos.Y, - 150, 0, new ModAnimation.AniEaseOutFluent()), - ModAnimation.AaDouble(i => TransformRotate.Angle += (double)i, - 6d - TransformRotate.Angle, 150, 0, new ModAnimation.AniEaseInFluent(ModAnimation.AniEasePower.Weak)), - ModAnimation.AaCode(() => ((Grid)Parent).Children.Remove(this), after: true) - }, "MyMsgBox " + uuid); - } - - // 实现回车和 Esc 的接口(#4857) - public void Btn1_Click(object sender, MouseButtonEventArgs e) - { - } - - public void Btn3_Click(object sender, MouseButtonEventArgs e) - { - Finished(new ThreadInterruptedException()); - } - - private void Drag(object sender, MouseButtonEventArgs e) - { - // On Error Resume Next - if (e.GetPosition(ShapeLine).Y <= 2d) - ModMain.frmMain.DragMove(); - } - - #endregion -} \ No newline at end of file + private record ErrorBody( + [property: JsonPropertyName("error")] string Error, + [property: JsonPropertyName("error_description")] string Desc); +} From 17f67619dd178e8075a633c45ab70cb4a9349a28 Mon Sep 17 00:00:00 2001 From: tangge233 Date: Thu, 18 Jun 2026 11:05:35 +0800 Subject: [PATCH 14/14] refactor(dialog): AddButton accepts DialogButton, ButtonBuilder, drop Tag hack AddButton now takes DialogButton (not raw params). ButtonBuilder constructs MyButton. _buttons stores (DialogButton, MyButton) tuple preserving metadata. Cancel detection uses Data.IsCancel instead of Tag string hack. All callers updated. --- .../Controls/Dialog/DialogControl.cs | 40 ++++++------------- .../Dialog/DialogButtonBuilder.cs | 26 ++++++++++++ .../Dialog/DialogManager.cs | 26 ++++++------ Plain Craft Launcher 2/FormMain.xaml.cs | 8 ++-- .../PageLaunch/DialogMsOAuthLogin.xaml.cs | 13 +++--- 5 files changed, 62 insertions(+), 51 deletions(-) create mode 100644 Plain Craft Launcher 2/Dialog/DialogButtonBuilder.cs diff --git a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs index a56bc762f..fb2821a47 100644 --- a/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs +++ b/Plain Craft Launcher 2/Controls/Dialog/DialogControl.cs @@ -3,6 +3,7 @@ using System.Windows.Input; using System.Windows.Interop; using System.Windows.Threading; +using PCL.Core.UI; using PCL.Core.UI.Controls; namespace PCL; @@ -12,7 +13,7 @@ public partial class DialogControl private int _result; private bool _exited; private readonly int _uuid = ModBase.GetUuid(); - internal readonly List _buttons = []; + internal readonly List<(DialogButton Data, MyButton Control)> _buttons = []; public DispatcherFrame WaitFrame { get; } = new(true); @@ -59,38 +60,21 @@ public DialogControl() Loaded += OnLoad; } - public MyButton AddButton(string text, Action? onClick = null, bool isPrimary = false, int id = 0, bool isCancel = false) + public MyButton AddButton(DialogButton button) { - var btn = new MyButton - { - Text = text, - ColorType = isPrimary - ? (IsWarn ? MyButton.ColorState.Red : MyButton.ColorState.Highlight) - : MyButton.ColorState.Normal, - Visibility = string.IsNullOrEmpty(text) ? Visibility.Collapsed : Visibility.Visible, - IsEnabled = true, - Tag = isCancel ? "Cancel" : null, - }; - btn.ApplyTemplate(); - btn.TextPadding = new Thickness(7); - btn.Padding = new Thickness(5, 0, 5, 0); - btn.Margin = new Thickness(12, 0, 0, 0); - btn.Name += ModBase.GetUuid(); - var buttonId = id > 0 ? id : _buttons.Count + 1; + var btn = DialogButtonBuilder.Build(button, IsWarn); + var buttonId = button.Id > 0 ? button.Id : _buttons.Count + 1; btn.Click += (_, _) => { if (_exited) return; - if (onClick is not null) - { - onClick(); - } + if (button.OnClick is not null) + button.OnClick(); else - { Close(buttonId); - } }; - _buttons.Add(btn); + _buttons.Add((button, btn)); PanBtn.Children.Add(btn); + return btn; } @@ -102,10 +86,10 @@ private void OnLoad(object sender, RoutedEventArgs e) { if (IsWarn) LabTitle.SetResourceReference(TextBlock.ForegroundProperty, "ColorBrushRedLight"); - if (_buttons.Count > 1 && _buttons[0].ColorType != MyButton.ColorState.Red) - _buttons[0].ColorType = MyButton.ColorState.Highlight; + if (_buttons.Count > 1 && _buttons[0].Control.ColorType != MyButton.ColorState.Red) + _buttons[0].Control.ColorType = MyButton.ColorState.Highlight; if (_buttons.Count > 0) - _buttons[0].Focus(); + _buttons[0].Control.Focus(); else PanBtn.Visibility = Visibility.Collapsed; diff --git a/Plain Craft Launcher 2/Dialog/DialogButtonBuilder.cs b/Plain Craft Launcher 2/Dialog/DialogButtonBuilder.cs new file mode 100644 index 000000000..6ef5c23c3 --- /dev/null +++ b/Plain Craft Launcher 2/Dialog/DialogButtonBuilder.cs @@ -0,0 +1,26 @@ +using System.Windows; +using PCL.Core.UI; + +namespace PCL; + +public static class DialogButtonBuilder +{ + public static MyButton Build(DialogButton button, bool isWarn) + { + var btn = new MyButton + { + Text = button.Text, + ColorType = button.IsPrimary + ? (isWarn ? MyButton.ColorState.Red : MyButton.ColorState.Highlight) + : MyButton.ColorState.Normal, + Visibility = string.IsNullOrEmpty(button.Text) ? Visibility.Collapsed : Visibility.Visible, + IsEnabled = true, + }; + btn.ApplyTemplate(); + btn.TextPadding = new Thickness(7); + btn.Padding = new Thickness(5, 0, 5, 0); + btn.Margin = new Thickness(12, 0, 0, 0); + btn.Name += ModBase.GetUuid(); + return btn; + } +} diff --git a/Plain Craft Launcher 2/Dialog/DialogManager.cs b/Plain Craft Launcher 2/Dialog/DialogManager.cs index b708fdac6..e1ec5c0fc 100644 --- a/Plain Craft Launcher 2/Dialog/DialogManager.cs +++ b/Plain Craft Launcher 2/Dialog/DialogManager.cs @@ -83,7 +83,7 @@ public Dialog Show(string? title = null, DialogButton[]? buttons = null, }; var wrapper = new Dialog(dialog); if (buttons is not null) - foreach (var b in buttons) dialog.AddButton(b.Text, b.OnClick, b.IsPrimary, b.Id, b.IsCancel); + foreach (var b in buttons) dialog.AddButton(b); _showContentOnUi(dialog); return wrapper; } @@ -148,7 +148,7 @@ public int ShowMarkdown(string markdown, string? title = null, return Show(ctx); } - public string ShowInput(string title, string text = "", string defaultInput = "", + public string? ShowInput(string title, string text = "", string defaultInput = "", Collection>? validateRules = null, string hintText = "", string? button1 = null, string? button2 = null, bool isWarn = false) { @@ -175,20 +175,20 @@ public string ShowInput(string title, string text = "", string defaultInput = "" stack.Children.Add(textBox); var dialog = new DialogControl { Title = title, IsWarn = isWarn, DialogContent = stack }; - dialog.AddButton(button1!, onClick: () => + dialog.AddButton(new DialogButton(button1!, onClick: () => { textBox.Validate(); if (!textBox.IsValidated) return; result = textBox.Text; dialog.Close(1); - }, isPrimary: true, id: 1); + }, isPrimary: true, id: 1)); if (!string.IsNullOrEmpty(button2)) { - dialog.AddButton(button2!, onClick: () => + dialog.AddButton(new DialogButton(button2!, isCancel: true, onClick: () => { result = null; dialog.Close(2); - }, id: 2); + }, id: 2)); } _mainForm.PanMsg.Children.Add(dialog); @@ -206,7 +206,7 @@ public string ShowInput(string title, string text = "", string defaultInput = "" try { _mainForm.Dispatcher.Invoke(showOnUi); } catch (TaskCanceledException) { } - return result ?? ""; + return result; } public int? ShowSelect(List selections, string? title = null, @@ -222,16 +222,16 @@ public string ShowInput(string title, string text = "", string defaultInput = "" var panel = new StackPanel(); var dialog = new DialogControl { Title = title, IsWarn = isWarn, DialogContent = panel }; - var b1 = dialog.AddButton(button1!, onClick: () => { }, isPrimary: true, id: 1); + var b1 = dialog.AddButton(new DialogButton(button1!, onClick: () => { }, isPrimary: true, id: 1)); b1.IsEnabled = false; if (!string.IsNullOrEmpty(button2)) { - dialog.AddButton(button2!, onClick: () => + dialog.AddButton(new DialogButton(button2!, isCancel: true, onClick: () => { result = null; dialog.Close(2); - }, id: 2); + }, id: 2)); } int selectedIndex = -1; @@ -358,9 +358,9 @@ private void ShowDialogOnUiThread(DialogContext context) for (var i = 0; i < context.Buttons.Count && i < 3; i++) { var btn = context.Buttons[i]; - var isPrimary = i == 0 || btn.IsPrimary; - var id = btn.Id > 0 ? btn.Id : i + 1; - dialog.AddButton(btn.Text, btn.OnClick, isPrimary, id, btn.IsCancel); + if (i == 0) btn.IsPrimary = true; + if (btn.Id <= 0) btn.Id = i + 1; + dialog.AddButton(btn); } context._dialog = dialog; diff --git a/Plain Craft Launcher 2/FormMain.xaml.cs b/Plain Craft Launcher 2/FormMain.xaml.cs index 9fe422471..cb05bf7b6 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.cs +++ b/Plain Craft Launcher 2/FormMain.xaml.cs @@ -698,8 +698,8 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) { DialogControl dlg when dlg._buttons.Count > 0 => () => { - var confirm = dlg._buttons.FirstOrDefault(b => b.Tag as string != "Cancel") - ?? dlg._buttons[0]; + var confirmEntry = dlg._buttons.FirstOrDefault(b => !b.Data.IsCancel); + var confirm = confirmEntry.Control ?? dlg._buttons[0].Control; confirm.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); }, _ => null @@ -715,8 +715,8 @@ private void FormMain_KeyDown(object sender, KeyEventArgs e) { DialogControl dlg when dlg._buttons.Count > 0 => () => { - var cancel = dlg._buttons.FirstOrDefault(b => b.Tag as string == "Cancel"); - cancel?.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); + var cancelEntry = dlg._buttons.FirstOrDefault(b => b.Data.IsCancel); + cancelEntry.Control?.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); }, _ => null }; diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml.cs b/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml.cs index a72cceded..58771c59c 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageLaunch/DialogMsOAuthLogin.xaml.cs @@ -7,6 +7,7 @@ using System.Windows.Input; using PCL.Core.App; using PCL.Core.App.Localization; +using PCL.Core.UI; using PCL.Core.Utils; using PCL.Core.IO.Net.Http; @@ -65,18 +66,18 @@ public DialogMsOAuthLogin(DialogControl dialog, JsonObject data, string authUrl, dialog.DialogContent = scrollViewer; // Buttons - var btnReopen = dialog.AddButton( + var btnReopen = dialog.AddButton(new DialogButton( Lang.Text("Launch.Account.LoginDialog.ReopenWebpage"), - onClick: () => ModBase.OpenWebsite(_website), id: 1); + onClick: () => ModBase.OpenWebsite(_website), id: 1)); CustomEventService.SetEventData(btnReopen, _website); - var btnCopy = dialog.AddButton( + var btnCopy = dialog.AddButton(new DialogButton( Lang.Text("Launch.Account.LoginDialog.CopyCode"), - onClick: () => ModBase.ClipboardSet(_userCode), id: 2); + onClick: () => ModBase.ClipboardSet(_userCode), id: 2)); CustomEventService.SetEventData(btnCopy, _userCode); - _btnCancel = dialog.AddButton( - Lang.Text("Common.Action.Cancel"), isCancel: true, id: 3); + _btnCancel = dialog.AddButton(new DialogButton( + Lang.Text("Common.Action.Cancel"), isCancel: true, id: 3)); // Finish callback dialog.OnClosed += result =>