Skip to content

Commit b7143a8

Browse files
committed
feat: PendingBox #11
1 parent de0928f commit b7143a8

23 files changed

+803
-1
lines changed

src/Wpf.Ui.Test/MainWindow.xaml

+17
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,23 @@
961961
</Button>
962962
</ui:StackPanel>
963963
</StackPanel>
964+
<StackPanel>
965+
<TextBlock
966+
MinWidth="120"
967+
Margin="0,8,0,0"
968+
FontSize="14"
969+
FontWeight="Black"
970+
Text="PendingBox" />
971+
<ui:StackPanel
972+
Margin="0,8,0,0"
973+
Orientation="Horizontal"
974+
Spacing="8">
975+
<ui:LoadingScreen />
976+
<vio:Loading />
977+
<Button Command="{Binding ShowPendingBoxCommand}" Content="Show PendingBox" />
978+
<Button Command="{Binding ShowPendingBoxWithCancelCommand}" Content="Show PendingBox with Cancel" />
979+
</ui:StackPanel>
980+
</StackPanel>
964981
</StackPanel>
965982
</ScrollViewer>
966983
</ui:Grid>

src/Wpf.Ui.Test/MainWindow.xaml.cs

+15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.ObjectModel;
77
using System.IO;
88
using System.Linq;
9+
using System.Reactive.Disposables;
910
using System.Threading;
1011
using System.Threading.Tasks;
1112
using Wpf.Ui.Appearance;
@@ -509,6 +510,20 @@ private void ThrowException()
509510
{
510511
throw new InvalidOperationException("The operation could not be completed because the system encountered an unexpected state. This might be due to incorrect usage of the API or an internal error. Please ensure that all prerequisites are met and the operation is performed under the correct conditions. If the problem persists, consult the documentation or contact support for further assistance.");
511512
}
513+
514+
[RelayCommand]
515+
private async Task ShowPendingBoxAsync()
516+
{
517+
using IPendingHandler pending = PendingBox.Show();
518+
await Task.Delay(3000);
519+
}
520+
521+
[RelayCommand]
522+
private async Task ShowPendingBoxWithCancelAsync()
523+
{
524+
using IPendingHandler pending = PendingBox.Show("Doing something", "I'm a title", isShowCancel: true);
525+
await Task.Delay(3000);
526+
}
512527
}
513528

514529
public partial class RegistryModel : ITreeModel

src/Wpf.Ui.Violeta/Controls/MessageBox/MessageBoxDialog.cs

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Runtime.InteropServices;
34
using System.Text;
45
using System.Text.RegularExpressions;
@@ -459,6 +460,8 @@ private void UpdateButtonTextState()
459460
templateSettings.CancelButtonText = string.IsNullOrEmpty(CancelButtonText) ? GetString(User32.DialogBoxCommand.IDCANCEL) : CancelButtonText;
460461
}
461462

463+
[SuppressMessage("Performance", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.")]
464+
[SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression")]
462465
private static string GetString(User32.DialogBoxCommand wBtn)
463466
{
464467
nint strPtr = User32.MB_GetString((uint)wBtn);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
3+
namespace Wpf.Ui.Violeta.Controls;
4+
5+
public interface IPendingHandler : IDisposable
6+
{
7+
public event EventHandler? Closed;
8+
9+
public event EventHandler? Cancel;
10+
11+
public string? Message { get; set; }
12+
public bool Cancelable { get; set; }
13+
public bool Canceled { get; }
14+
15+
public void Show();
16+
17+
public bool? ShowDialog();
18+
19+
public void Close();
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
using System.Globalization;
2+
using System;
3+
using System.Windows;
4+
using System.Windows.Controls;
5+
using System.Windows.Data;
6+
using System.Windows.Media;
7+
8+
namespace Wpf.Ui.Violeta.Controls;
9+
10+
public class Loading : Control
11+
{
12+
public bool IsRunning
13+
{
14+
get => (bool)GetValue(IsRunningProperty);
15+
set => SetValue(IsRunningProperty, value);
16+
}
17+
18+
public static readonly DependencyProperty IsRunningProperty =
19+
DependencyProperty.Register(nameof(IsRunning), typeof(bool), typeof(Loading), new PropertyMetadata(true));
20+
21+
public LoadingStyle LoadingStyle
22+
{
23+
get => (LoadingStyle)GetValue(LoadingStyleProperty);
24+
set => SetValue(LoadingStyleProperty, value);
25+
}
26+
27+
public static readonly DependencyProperty LoadingStyleProperty =
28+
DependencyProperty.Register(nameof(LoadingStyle), typeof(LoadingStyle), typeof(Loading), new PropertyMetadata(LoadingStyle.Ring));
29+
30+
public double Minimum
31+
{
32+
get => (double)GetValue(MinimumProperty);
33+
set => SetValue(MinimumProperty, value);
34+
}
35+
36+
public static readonly DependencyProperty MinimumProperty =
37+
DependencyProperty.Register(nameof(Minimum), typeof(double), typeof(Loading), new PropertyMetadata(0d));
38+
39+
public double Maximum
40+
{
41+
get => (double)GetValue(MaximumProperty);
42+
set => SetValue(MaximumProperty, value);
43+
}
44+
45+
public static readonly DependencyProperty MaximumProperty =
46+
DependencyProperty.Register(nameof(Maximum), typeof(double), typeof(Loading), new PropertyMetadata(100d));
47+
48+
public double Value
49+
{
50+
get => (double)GetValue(ValueProperty);
51+
set => SetValue(ValueProperty, value);
52+
}
53+
54+
public static readonly DependencyProperty ValueProperty =
55+
DependencyProperty.Register(nameof(Value), typeof(double), typeof(Loading), new PropertyMetadata(0d));
56+
57+
static Loading()
58+
{
59+
DefaultStyleKeyProperty.OverrideMetadata(typeof(Loading), new FrameworkPropertyMetadata(typeof(Loading)));
60+
}
61+
62+
public Loading()
63+
{
64+
}
65+
}
66+
67+
public enum LoadingStyle
68+
{
69+
Ring,
70+
}
71+
72+
internal sealed class LoadingBackgroundThicknessConverter : IValueConverter
73+
{
74+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
75+
{
76+
var actualWidth = (value as double?).GetValueOrDefault();
77+
return actualWidth == 0 ? 0 : (object)Math.Ceiling(actualWidth / 15);
78+
}
79+
80+
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
81+
{
82+
return DependencyProperty.UnsetValue;
83+
}
84+
}
85+
86+
internal sealed class LoadingLineYConverter : IValueConverter
87+
{
88+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
89+
{
90+
var actualWidth = (value as double?).GetValueOrDefault();
91+
if (actualWidth == 0)
92+
return 0;
93+
return Math.Ceiling(actualWidth / 4);
94+
}
95+
96+
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
97+
{
98+
return new object[] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue };
99+
}
100+
}
101+
102+
internal sealed class LoadingClassicMarginConverter : IValueConverter
103+
{
104+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
105+
{
106+
var actualWidth = (value as double?).GetValueOrDefault();
107+
if (actualWidth == 0)
108+
return 0;
109+
return new Thickness(actualWidth / 2, actualWidth / 2, 0, 0);
110+
}
111+
112+
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
113+
{
114+
return DependencyProperty.UnsetValue;
115+
}
116+
}
117+
118+
internal sealed class LoadingClassicEllipseSizeConverter : IValueConverter
119+
{
120+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
121+
{
122+
var actualWidth = (value as double?).GetValueOrDefault();
123+
if (actualWidth == 0)
124+
return 0;
125+
return Math.Ceiling(actualWidth / 8);
126+
}
127+
128+
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
129+
{
130+
return DependencyProperty.UnsetValue;
131+
}
132+
}
133+
134+
internal sealed class RingProgressBarConverter : IMultiValueConverter
135+
{
136+
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
137+
{
138+
var width = (double)values[0];
139+
var height = (double)values[1];
140+
141+
double radius = 0;
142+
if (values[2] is Thickness)
143+
radius = ((Thickness)values[2]).Left;
144+
else if (values[2] is double)
145+
radius = ((double)values[2]);
146+
147+
var percent = 0.33;
148+
if (values.Length == 6)
149+
{
150+
var min = (double)values[3];
151+
var max = (double)values[4];
152+
var value = (double)values[5];
153+
value = value > max ? max : value;
154+
value = value < min ? min : value;
155+
percent = (value - min) / (max - min);
156+
}
157+
158+
var point1X = height / 2 * Math.Cos((2 * percent - 0.5) * Math.PI) + height / 2;
159+
var point1Y = height / 2 - height / 2 * Math.Sin((2 * percent + 0.5) * Math.PI);
160+
var point2X = (height - radius) / 2 * Math.Cos((2 * percent - 0.5) * Math.PI) + height / 2;
161+
var point2Y = height / 2 - (height - radius) / 2 * Math.Sin((2 * percent + 0.5) * Math.PI);
162+
163+
var path = "";
164+
165+
if (percent == 0)
166+
{
167+
path = "";
168+
}
169+
else if (percent < 0.5)
170+
{
171+
path = "M " + width / 2 + "," + radius / 2 + " A " + (width - radius) / 2 + "," + (width - radius) / 2 + " 0 0 1 " + point2X + "," + point2Y + "";
172+
}
173+
else if (percent == 0.5)
174+
{
175+
path = "M " + width / 2 + "," + radius / 2 + " A " + (width - radius) / 2 + "," + (width - radius) / 2 + " 0 0 1 " + width / 2 + "," + (height - radius / 2);
176+
}
177+
else
178+
{
179+
path = "M " + width / 2 + "," + radius / 2 + " A " + (width - radius) / 2 + "," + (width - radius) / 2 + " 0 0 1 " + width / 2 + "," + (height - radius / 2) +
180+
" A " + (width - radius) / 2 + "," + (width - radius) / 2 + " 0 0 1 " + point2X + "," + point2Y + "";
181+
}
182+
return PathGeometry.Parse(path);
183+
}
184+
185+
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
186+
{
187+
return new object[] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue };
188+
}
189+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<ResourceDictionary
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:local="clr-namespace:Wpf.Ui.Violeta.Controls">
5+
<ResourceDictionary.MergedDictionaries>
6+
<ResourceDictionary>
7+
<local:LoadingBackgroundThicknessConverter x:Key="LoadingBackgroundThicknessConverter" />
8+
<local:LoadingLineYConverter x:Key="LoadingLineYConverter" />
9+
<local:LoadingClassicMarginConverter x:Key="LoadingClassicMarginConverter" />
10+
<local:LoadingClassicEllipseSizeConverter x:Key="LoadingClassicEllipseSizeConverter" />
11+
<local:RingProgressBarConverter x:Key="RingProgressBarConverter" />
12+
</ResourceDictionary>
13+
</ResourceDictionary.MergedDictionaries>
14+
15+
<Style TargetType="{x:Type local:Loading}">
16+
<Setter Property="Height" Value="35" />
17+
<Setter Property="Width" Value="35" />
18+
<Setter Property="LoadingStyle" Value="Ring" />
19+
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColorPrimaryBrush}" />
20+
<Setter Property="Background" Value="Transparent" />
21+
<Setter Property="Template">
22+
<Setter.Value>
23+
<ControlTemplate TargetType="{x:Type local:Loading}">
24+
<Grid x:Name="GrdMain">
25+
<Ellipse Stroke="{TemplateBinding Background}" StrokeThickness="{Binding ActualWidth, Converter={StaticResource LoadingBackgroundThicknessConverter}, RelativeSource={RelativeSource AncestorType=local:Loading}}" />
26+
<Path
27+
x:Name="Path"
28+
RenderTransformOrigin="0.5,0.5"
29+
Stroke="{TemplateBinding Foreground}"
30+
StrokeEndLineCap="Round"
31+
StrokeStartLineCap="Round"
32+
StrokeThickness="{Binding ActualWidth, Converter={StaticResource LoadingBackgroundThicknessConverter}, RelativeSource={RelativeSource AncestorType=local:Loading}}">
33+
<Path.Data>
34+
<MultiBinding Converter="{StaticResource RingProgressBarConverter}">
35+
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=local:Loading}" />
36+
<Binding Path="ActualHeight" RelativeSource="{RelativeSource AncestorType=local:Loading}" />
37+
<Binding
38+
Converter="{StaticResource LoadingBackgroundThicknessConverter}"
39+
Path="ActualWidth"
40+
RelativeSource="{RelativeSource AncestorType=local:Loading}" />
41+
<Binding Path="Minimum" RelativeSource="{RelativeSource AncestorType=local:Loading}" />
42+
<Binding Path="Maximum" RelativeSource="{RelativeSource AncestorType=local:Loading}" />
43+
<Binding Path="Value" RelativeSource="{RelativeSource AncestorType=local:Loading}" />
44+
</MultiBinding>
45+
</Path.Data>
46+
<Path.RenderTransform>
47+
<RotateTransform x:Name="rotate" Angle="0" />
48+
</Path.RenderTransform>
49+
</Path>
50+
</Grid>
51+
<ControlTemplate.Triggers>
52+
<Trigger Property="IsRunning" Value="True">
53+
<Setter TargetName="GrdMain" Property="Visibility" Value="Visible" />
54+
<Trigger.EnterActions>
55+
<BeginStoryboard>
56+
<Storyboard>
57+
<DoubleAnimation
58+
RepeatBehavior="Forever"
59+
Storyboard.TargetName="rotate"
60+
Storyboard.TargetProperty="Angle"
61+
To="360"
62+
Duration="0:0:1" />
63+
<DoubleAnimation
64+
AutoReverse="True"
65+
RepeatBehavior="Forever"
66+
Storyboard.TargetProperty="Value"
67+
From="10"
68+
To="80"
69+
Duration="0:0:1" />
70+
</Storyboard>
71+
</BeginStoryboard>
72+
</Trigger.EnterActions>
73+
<Trigger.ExitActions>
74+
<BeginStoryboard>
75+
<Storyboard>
76+
<DoubleAnimation
77+
Storyboard.TargetName="rotate"
78+
Storyboard.TargetProperty="Angle"
79+
Duration="0:0:0" />
80+
<DoubleAnimation Storyboard.TargetProperty="Value" Duration="0:0:0" />
81+
</Storyboard>
82+
</BeginStoryboard>
83+
</Trigger.ExitActions>
84+
</Trigger>
85+
</ControlTemplate.Triggers>
86+
</ControlTemplate>
87+
</Setter.Value>
88+
</Setter>
89+
</Style>
90+
91+
</ResourceDictionary>

0 commit comments

Comments
 (0)