feat: add LargeMessageBoxControl and improve MessageBox functionality

This commit is contained in:
2026-01-22 16:48:21 +08:00
parent 975757dbb8
commit c0a9852f9e
5 changed files with 319 additions and 18 deletions

View File

@@ -1,4 +1,4 @@
<ResourceDictionary
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
@@ -56,4 +56,40 @@
</Style>
</ControlTheme>
<ControlTheme
x:Key="MessageCloseButton"
BasedOn="{StaticResource OverlayCloseButton}"
TargetType="Button">
<Setter Property="CornerRadius" Value="6" />
<Setter Property="Padding" Value="4" />
<Style Selector="^:pointerover /template/ Border">
<Setter Property="Background" Value="{DynamicResource CaptionButtonClosePointeroverBackground}" />
</Style>
<Style Selector="^:pointerover /template/ PathIcon">
<Setter Property="Foreground" Value="White" />
</Style>
<Style Selector="^:pressed /template/ Border">
<Setter Property="Background" Value="{DynamicResource CaptionButtonClosePressedBackground}" />
</Style>
<Style Selector="^:pressed /template/ PathIcon">
<Setter Property="Foreground" Value="White" />
</Style>
<Setter Property="Template">
<ControlTemplate TargetType="Button">
<Border
Padding="{TemplateBinding Padding}"
Background="Transparent"
CornerRadius="{TemplateBinding CornerRadius}">
<Viewbox>
<PathIcon
Foreground="{DynamicResource SemiColorText1}"
Theme="{StaticResource InnerPathIcon}"
Data="{DynamicResource SemiIconClose}" />
</Viewbox>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>

View File

@@ -1,11 +1,11 @@
<ResourceDictionary
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa"
xmlns:iri="https://irihi.tech/shared">
<Design.PreviewWith>
<StackPanel Margin="20">
<u:MessageBoxControl
<!--<u:MessageBoxControl
Content="此修改将不可逆"
MessageIcon="None"
Buttons="OK" />
@@ -21,11 +21,12 @@
Title="确定是否要保存此修改?"
Content="此修改将不可逆"
MessageIcon="Warning"
Buttons="OKCancel" />
Buttons="OKCancel" />-->
<u:MessageBoxControl
Title="确定是否要保存此修改?"
Content="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
MessageIcon="Warning"
Theme="{StaticResource LargeMessageBoxControl}"
Buttons="OKCancel" />
</StackPanel>
</Design.PreviewWith>
@@ -340,4 +341,170 @@
<Setter Property="Data" Value="{DynamicResource DialogSuccessIconGlyph}" />
</Style>
</ControlTheme>
<ControlTheme x:Key="LargeMessageBoxControl" TargetType="u:MessageBoxControl">
<Setter Property="CornerRadius" Value="6" />
<Setter Property="Padding" Value="48 24" />
<Setter Property="u:DialogControlBase.CanDragMove" Value="True" />
<Setter Property="FontSize" Value="18"/>
<Setter Property="Template">
<ControlTemplate TargetType="u:MessageBoxControl">
<Border
Padding="0"
Focusable="True"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Classes="Shadow"
ClipToBounds="False"
Background="{DynamicResource SemiColorBackground3}"
CornerRadius="{TemplateBinding CornerRadius}"
IsHitTestVisible="True"
Theme="{DynamicResource CardBorder}">
<Border ClipToBounds="True" CornerRadius="{TemplateBinding CornerRadius}">
<Grid RowDefinitions="Auto, *, Auto">
<Grid Grid.Row="0" ColumnDefinitions="Auto, *, Auto">
<Panel
Name="{x:Static u:DialogControlBase.PART_TitleArea}"
Grid.Column="0"
Grid.ColumnSpan="3"
Background="Transparent" />
<PathIcon
Name="PART_Icon"
Grid.Column="0"
Theme="{StaticResource InnerPathIcon}"
Classes="ExtraLarge"
Margin="24,24,8,0"
VerticalAlignment="Center"
IsHitTestVisible="False" />
<TextBlock
Name="PART_Title"
Grid.Column="1"
Margin="0,24,0,0"
VerticalAlignment="Center"
FontSize="18"
FontWeight="{DynamicResource TextBlockTitleFontWeight}"
IsHitTestVisible="False"
Text="{TemplateBinding Title}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
<Button
Name="{x:Static u:MessageBoxWindow.PART_CloseButton}"
Grid.Column="2" Height="36" Width="36"
Margin="0,24,24,0"
Theme="{DynamicResource MessageCloseButton}" />
</Grid>
<Grid
Grid.Row="1"
MaxWidth="{DynamicResource MessageBoxWindowContentMaxWidth}"
Margin="{TemplateBinding Padding}">
<ScrollViewer
MaxHeight="300"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<ContentPresenter
Name="PART_ContentPresenter"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextAlignment="Left"
TextWrapping="Wrap" />
</ScrollViewer>
</Grid>
<StackPanel
Grid.Row="2"
Margin="24,0,24,24"
Spacing="8"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button Name="{x:Static u:MessageBoxControl.PART_CancelButton}" />
<Button Name="{x:Static u:MessageBoxControl.PART_NoButton}" />
<Button Name="{x:Static u:MessageBoxControl.PART_YesButton}" />
<Button Name="{x:Static u:MessageBoxControl.PART_OKButton}" />
</StackPanel>
</Grid>
</Border>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^ /template/ Panel#PART_TitleArea">
<Setter Property="ContextFlyout">
<MenuFlyout>
<MenuItem Command="{Binding $parent[u:MessageBoxControl].Close}" Header="{DynamicResource STRING_MENU_DIALOG_CLOSE}">
<MenuItem.Icon>
<PathIcon
Theme="{StaticResource InnerPathIcon}"
Data="{DynamicResource SemiIconClose}" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Setter>
</Style>
<Style Selector="^ /template/ Button#PART_CancelButton">
<Setter Property="Grid.Column" Value="0" />
<Setter Property="iri:ClassHelper.Classes" Value="Tertiary" />
<Setter Property="Content" Value="{DynamicResource STRING_MENU_DIALOG_CANCEL}" />
<Setter Property="Padding" Value="14 6"/>
<Setter Property="FontSize" Value="18"/>
</Style>
<Style Selector="^ /template/ Button#PART_NoButton">
<Setter Property="Grid.Column" Value="1" />
<Setter Property="iri:ClassHelper.Classes" Value="Danger" />
<Setter Property="Content" Value="{DynamicResource STRING_MENU_DIALOG_NO}" />
<Setter Property="Theme" Value="{DynamicResource SolidButton}" />
<Setter Property="Padding" Value="14 6"/>
<Setter Property="FontSize" Value="18"/>
</Style>
<Style Selector="^ /template/ Button#PART_YesButton">
<Setter Property="Grid.Column" Value="2" />
<Setter Property="iri:ClassHelper.Classes" Value="Primary" />
<Setter Property="Content" Value="{DynamicResource STRING_MENU_DIALOG_YES}" />
<Setter Property="Theme" Value="{DynamicResource SolidButton}" />
<Setter Property="Padding" Value="14 6"/>
<Setter Property="FontSize" Value="18"/>
</Style>
<Style Selector="^ /template/ Button#PART_OKButton">
<Setter Property="Grid.Column" Value="3" />
<Setter Property="iri:ClassHelper.Classes" Value="Primary" />
<Setter Property="Content" Value="{DynamicResource STRING_MENU_DIALOG_OK}" />
<Setter Property="Theme" Value="{DynamicResource SolidButton}" />
<Setter Property="Padding" Value="14 6"/>
<Setter Property="FontSize" Value="18"/>
</Style>
<Style Selector="^[MessageIcon=None] /template/ PathIcon#PART_Icon">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^[MessageIcon=None] /template/ TextBlock#PART_Title">
<Setter Property="Margin" Value="24 24 0 0" />
</Style>
<Style Selector="^[MessageIcon=Asterisk] /template/ PathIcon#PART_Icon, ^[MessageIcon=Information] /template/ PathIcon#PART_Icon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Foreground" Value="{DynamicResource SemiBlue6}" />
<Setter Property="Data" Value="{DynamicResource DialogInformationIconGlyph}" />
</Style>
<Style Selector="^[MessageIcon=Error] /template/ PathIcon#PART_Icon, ^[MessageIcon=Hand] /template/ PathIcon#PART_Icon, ^[MessageIcon=Stop] /template/ PathIcon#PART_Icon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Foreground" Value="{DynamicResource SemiRed6}" />
<Setter Property="Data" Value="{DynamicResource DialogErrorIconGlyph}" />
</Style>
<Style Selector="^[MessageIcon=Exclamation] /template/ PathIcon#PART_Icon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Foreground" Value="{DynamicResource SemiYellow6}" />
<Setter Property="Data" Value="{DynamicResource DialogWarningIconGlyph}" />
</Style>
<Style Selector="^[MessageIcon=Question] /template/ PathIcon#PART_Icon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Foreground" Value="{DynamicResource SemiBlue6}" />
<Setter Property="Data" Value="{DynamicResource DialogQuestionIconGlyph}" />
</Style>
<Style Selector="^[MessageIcon=Warning] /template/ PathIcon#PART_Icon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Foreground" Value="{DynamicResource SemiOrange6}" />
<Setter Property="Data" Value="{DynamicResource DialogWarningIconGlyph}" />
</Style>
<Style Selector="^[MessageIcon=Success] /template/ PathIcon#PART_Icon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Foreground" Value="{DynamicResource SemiGreen6}" />
<Setter Property="Data" Value="{DynamicResource DialogSuccessIconGlyph}" />
</Style>
</ControlTheme>
</ResourceDictionary>

View File

@@ -132,6 +132,8 @@ public class Marquee : ContentControl
private void TimerOnTick(object? sender, System.EventArgs e)
{
try
{
if (Presenter is null) return;
var layoutValues = Dispatcher.UIThread.Invoke(GetLayoutValues);
@@ -143,6 +145,11 @@ public class Marquee : ContentControl
Canvas.SetLeft(Presenter, location.Value.left);
}, DispatcherPriority.Render);
}
catch (Exception exception)
{
// pass
}
}
private void InvalidatePresenterPosition()
{

View File

@@ -2,6 +2,8 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Styling;
using Ursa.Common;
namespace Ursa.Controls;
@@ -81,6 +83,7 @@ public static class MessageBox
MessageIcon = icon,
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle
};
if (!string.IsNullOrWhiteSpace(styleClass))
{
var styles = styleClass!.Split(Constants.SpaceSeparator, StringSplitOptions.RemoveEmptyEntries);
@@ -90,4 +93,92 @@ public static class MessageBox
var result = await messageControl.ShowAsync<MessageBoxResult>();
return result;
}
public static async Task<MessageBoxResult> ShowLargeOverlayAsync(
string message,
string? title = null,
MessageBoxIcon icon = MessageBoxIcon.None,
MessageBoxButton button = MessageBoxButton.OK,
string yesString = "",
string noString = "",
string cancelString = "",
string okString = "")
{
var host = OverlayDialogManager.GetHost(null, null);
if (host is null) return MessageBoxResult.None;
var messageControl = new MessageBoxControl
{
Content = message,
Title = title,
Buttons = button,
MessageIcon = icon,
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle
};
if (Application.Current?.TryGetResource("LargeMessageBoxControl", ThemeVariant.Default, out var themeObj) == true
&& themeObj is ControlTheme theme)
{
messageControl.Theme = theme;
}
object? originalYes = null;
object? originalNo = null;
object? originalCancel = null;
object? originalOk = null;
var hadYes = false;
var hadNo = false;
var hadCancel = false;
var hadOk = false;
if (!string.IsNullOrEmpty(yesString))
{
hadYes = messageControl.Resources.TryGetValue("STRING_MENU_DIALOG_YES", out originalYes);
messageControl.Resources["STRING_MENU_DIALOG_YES"] = yesString;
}
if (!string.IsNullOrEmpty(noString))
{
hadNo = messageControl.Resources.TryGetValue("STRING_MENU_DIALOG_NO", out originalNo);
messageControl.Resources["STRING_MENU_DIALOG_NO"] = noString;
}
if (!string.IsNullOrEmpty(cancelString))
{
hadCancel = messageControl.Resources.TryGetValue("STRING_MENU_DIALOG_CANCEL", out originalCancel);
messageControl.Resources["STRING_MENU_DIALOG_CANCEL"] = cancelString;
}
if (!string.IsNullOrEmpty(okString))
{
hadOk = messageControl.Resources.TryGetValue("STRING_MENU_DIALOG_OK", out originalOk);
messageControl.Resources["STRING_MENU_DIALOG_OK"] = okString;
}
DialogControlBase.SetCanDragMove(messageControl, false);
host.AddModalDialog(messageControl);
try
{
var result = await messageControl.ShowAsync<MessageBoxResult>();
return result;
}
finally
{
if (!string.IsNullOrEmpty(yesString))
{
if (hadYes) messageControl.Resources["STRING_MENU_DIALOG_YES"] = originalYes!;
else messageControl.Resources.Remove("STRING_MENU_DIALOG_YES");
}
if (!string.IsNullOrEmpty(noString))
{
if (hadNo) messageControl.Resources["STRING_MENU_DIALOG_NO"] = originalNo!;
else messageControl.Resources.Remove("STRING_MENU_DIALOG_NO");
}
if (!string.IsNullOrEmpty(cancelString))
{
if (hadCancel) messageControl.Resources["STRING_MENU_DIALOG_CANCEL"] = originalCancel!;
else messageControl.Resources.Remove("STRING_MENU_DIALOG_CANCEL");
}
if (!string.IsNullOrEmpty(okString))
{
if (hadOk) messageControl.Resources["STRING_MENU_DIALOG_OK"] = originalOk!;
else messageControl.Resources.Remove("STRING_MENU_DIALOG_OK");
}
}
}
}

View File

@@ -104,8 +104,8 @@ public class MessageBoxControl : DialogControlBase
private void SetButtonVisibility()
{
var closeButtonVisible = Buttons != MessageBoxButton.YesNo;
IsVisibleProperty.SetValue(closeButtonVisible, _closeButton);
//var closeButtonVisible = Buttons != MessageBoxButton.YesNo;
IsVisibleProperty.SetValue(true, _closeButton);
switch (Buttons)
{
case MessageBoxButton.OK:
@@ -137,7 +137,7 @@ public class MessageBoxControl : DialogControlBase
{
MessageBoxButton.OK => MessageBoxResult.OK,
MessageBoxButton.OKCancel => MessageBoxResult.Cancel,
MessageBoxButton.YesNo => MessageBoxResult.No,
MessageBoxButton.YesNo => MessageBoxResult.Cancel,
MessageBoxButton.YesNoCancel => MessageBoxResult.Cancel,
_ => MessageBoxResult.None
};