-
Notifications
You must be signed in to change notification settings - Fork 113
refactor(msgbox): add customizable DialogControl + Dialog API #3168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 1 commit
bef259d
bf89224
a81caa7
afb6f74
1bd383f
e38a2c5
072e641
f7ad35d
be51f26
3dec522
9e4d841
99a6acb
024de3a
17f6761
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<DialogContext>? 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<DialogButton> BuildButtons(string[] buttonTexts, DialogTheme theme) | ||
| { | ||
| var list = new Collection<DialogButton>(); | ||
| 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<DialogButton> Buttons { get; set; } = []; | ||
| public int Result { get; set; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
|
|
@@ -21,15 +24,16 @@ | |
| public delegate void MsgBoxHandler( | ||
| string message, | ||
| string caption, | ||
| ICollection<MsgBoxButtonInfo> buttons, | ||
|
Check warning on line 27 in PCL.Core/UI/MsgBoxWrapper.cs
|
||
| MsgBoxTheme theme, | ||
|
Check warning on line 28 in PCL.Core/UI/MsgBoxWrapper.cs
|
||
| bool block, | ||
| ref int result | ||
| ); | ||
|
|
||
| [Obsolete("Use PCL.Core.UI.Dialog instead")] | ||
| public static class MsgBoxWrapper | ||
| { | ||
| public static event MsgBoxHandler? OnShow; | ||
|
Check warning on line 36 in PCL.Core/UI/MsgBoxWrapper.cs
|
||
|
|
||
| public static int ShowWithCustomButtons( | ||
| string message, | ||
|
|
@@ -38,9 +42,20 @@ | |
| bool block, | ||
| ICollection<MsgBoxButtonInfo> 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<DialogButton>(); | ||
| foreach (var btn in buttonCollection) | ||
| { | ||
| buttons.Add(new DialogButton(btn.Context, btn.OnClick)); | ||
| } | ||
| var result = Dialog.Show(new DialogContext | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When existing code subscribes to Useful? React with 👍 / 👎. |
||
| { | ||
| Caption = message, | ||
| Title = caption, | ||
| Theme = (DialogTheme)(int)theme, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): MsgBoxButtonInfo.Value 被丢弃了,可能会影响依赖自定义结果代码的调用方。 之前, Original comment in Englishissue (bug_risk): MsgBoxButtonInfo.Value is dropped, potentially breaking callers that depend on custom result codes. Previously, |
||
| Block = block, | ||
| Content = null, | ||
| Buttons = buttons, | ||
| }); | ||
| return result; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<MyButton> _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<int>? 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(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
|
|
||
| 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); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| <Grid x:Class="PCL.DialogControl" | ||
| xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
| xmlns:my="clr-namespace:PCL" | ||
| RenderTransformOrigin="0,0.5" UseLayoutRounding="True" SnapsToDevicePixels="True" MinWidth="400" | ||
| HorizontalAlignment="Center" VerticalAlignment="Center" Margin="25"> | ||
| <Grid.RenderTransform> | ||
| <TransformGroup> | ||
| <RotateTransform x:Name="TransformRotate" Angle="-4" /> | ||
| <TranslateTransform x:Name="TransformPos" X="0" Y="40" /> | ||
| </TransformGroup> | ||
| </Grid.RenderTransform> | ||
| <Border Name="PanBorder" CornerRadius="7" Background="{DynamicResource ColorBrushBackground}"> | ||
| <Border.Effect> | ||
| <DropShadowEffect Color="{DynamicResource ColorObjectMsgBoxShadow}" BlurRadius="20" ShadowDepth="2" | ||
| RenderingBias="Performance" Opacity="0.8" x:Name="EffectShadow" /> | ||
| </Border.Effect> | ||
| <Grid Name="PanMain" VerticalAlignment="Top" Margin="22,22,22,23"> | ||
| <Grid.RowDefinitions> | ||
| <RowDefinition Height="Auto" /> | ||
| <RowDefinition Height="2" /> | ||
| <RowDefinition Height="13" /> | ||
| <RowDefinition Height="1*" /> | ||
| <RowDefinition Height="Auto" /> | ||
| </Grid.RowDefinitions> | ||
| <TextBlock Grid.Row="0" FontSize="23" TextTrimming="None" Foreground="{DynamicResource ColorBrush2}" | ||
| HorizontalAlignment="Left" Name="LabTitle" Margin="7,-1,70,9" | ||
| VerticalAlignment="Top" SnapsToDevicePixels="False" UseLayoutRounding="False" | ||
| MouseLeftButtonDown="Drag" /> | ||
| <Rectangle x:Name="ShapeLine" Grid.Row="1" Height="2" Fill="{Binding Foreground, ElementName=LabTitle}" /> | ||
| <my:MyScrollViewer Grid.Row="3" VerticalAlignment="Top" x:Name="ContentScrollViewer" Margin="0,0,0,17" | ||
| Padding="7,0,15,0" | ||
| VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" | ||
| DeltaMult="0.7"> | ||
| <ContentControl x:Name="ContentArea" Focusable="False" /> | ||
| </my:MyScrollViewer> | ||
| <StackPanel Grid.Row="4" Name="PanBtn" VerticalAlignment="Top" HorizontalAlignment="Right" | ||
| Margin="150,0,8,0" Orientation="Horizontal" /> | ||
| </Grid> | ||
| </Border> | ||
| </Grid> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When legacy callers use
MsgBoxWrapper.ShowWithCustomButtonswithMsgBoxButtonInfo.Valuevalues, this conversion drops the value and creates eachDialogButtonwith the defaultidof 0.Dialog_OnShowthen falls back to positional IDs, so selecting a button returns 1/2/3 instead of the caller-specified value; passbtn.Valueinto theDialogButtonid to keep the existing result contract.Useful? React with 👍 / 👎.