diff --git a/ModernWpf.Controls/ProgressRing/ProgressRing.cs b/ModernWpf.Controls/ProgressRing/ProgressRing.cs index 54c468af..63739f4e 100644 --- a/ModernWpf.Controls/ProgressRing/ProgressRing.cs +++ b/ModernWpf.Controls/ProgressRing/ProgressRing.cs @@ -7,19 +7,12 @@ namespace ModernWpf.Controls { - [TemplateVisualState(GroupName = SizeStatesGroup, Name = LargeState)] - [TemplateVisualState(GroupName = SizeStatesGroup, Name = SmallState)] - [TemplateVisualState(GroupName = ActiveStatesGroup, Name = InactiveState)] - [TemplateVisualState(GroupName = ActiveStatesGroup, Name = ActiveState)] public class ProgressRing : Control { - private const string SizeStatesGroup = "SizeStates"; - private const string LargeState = "Large"; - private const string SmallState = "Small"; - - private const string ActiveStatesGroup = "ActiveStates"; - private const string InactiveState = "Inactive"; - private const string ActiveState = "Active"; + const string s_ActiveStateName = "Active"; + const string s_InactiveStateName = "Inactive"; + const string s_SmallStateName = "Small"; + const string s_LargeStateName = "Large"; static ProgressRing() { @@ -28,12 +21,9 @@ static ProgressRing() public ProgressRing() { - TemplateSettings = new ProgressRingTemplateSettings(); - } + SetValue(TemplateSettingsPropertyKey, new ProgressRingTemplateSettings()); - protected override AutomationPeer OnCreateAutomationPeer() - { - return new ProgressRingAutomationPeer(this); + SizeChanged += OnSizeChanged; } #region IsActive @@ -49,47 +39,102 @@ public bool IsActive nameof(IsActive), typeof(bool), typeof(ProgressRing), - new FrameworkPropertyMetadata(OnIsActiveChanged)); + new FrameworkPropertyMetadata(OnIsActivePropertyChanged)); + + private static void OnIsActivePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + ((ProgressRing)sender).OnIsActivePropertyChanged(args); + } + + #endregion + + #region TemplateSettings + + private static readonly DependencyPropertyKey TemplateSettingsPropertyKey = + DependencyProperty.RegisterReadOnly( + nameof(TemplateSettings), + typeof(ProgressRingTemplateSettings), + typeof(ProgressRing), + null); + + public static readonly DependencyProperty TemplateSettingsProperty = + TemplateSettingsPropertyKey.DependencyProperty; - private static void OnIsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + public ProgressRingTemplateSettings TemplateSettings { - ((ProgressRing)d).ChangeVisualState(true); + get => (ProgressRingTemplateSettings)GetValue(TemplateSettingsProperty); } #endregion - public ProgressRingTemplateSettings TemplateSettings { get; } + protected override AutomationPeer OnCreateAutomationPeer() + { + return new ProgressRingAutomationPeer(this); + } public override void OnApplyTemplate() { base.OnApplyTemplate(); - ChangeVisualState(false); + ChangeVisualState(); } - protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) + void OnSizeChanged(object sender, SizeChangedEventArgs e) { - base.OnRenderSizeChanged(sizeInfo); - - double maxSideLength = Math.Min(sizeInfo.NewSize.Width, sizeInfo.NewSize.Height); - double ellipseDiameter = 0.1 * maxSideLength; - if (maxSideLength <= 40) - { - ellipseDiameter += 1; - } + ApplyTemplateSettings(); + ChangeVisualState(); + } - var templateSettings = TemplateSettings; - templateSettings.EllipseDiameter = ellipseDiameter; - templateSettings.EllipseOffset = new Thickness(0, maxSideLength / 2 - ellipseDiameter, 0, 0); - templateSettings.MaxSideLength = maxSideLength; + void OnIsActivePropertyChanged(DependencyPropertyChangedEventArgs args) + { + ChangeVisualState(); + } - ChangeVisualState(true); + void ChangeVisualState() + { + VisualStateManager.GoToState(this, IsActive ? s_ActiveStateName : s_InactiveStateName, true); + VisualStateManager.GoToState(this, TemplateSettings.MaxSideLength < 60 ? s_SmallStateName : s_LargeStateName, true); } - private void ChangeVisualState(bool useTransitions) + void ApplyTemplateSettings() { - VisualStateManager.GoToState(this, IsActive ? ActiveState : InactiveState, useTransitions); - VisualStateManager.GoToState(this, TemplateSettings.MaxSideLength < 60 ? SmallState : LargeState, useTransitions); + // TemplateSetting properties from WUXC for backwards compatibility. + var templateSettings = TemplateSettings; + + var (width, diameterValue, anchorPoint) = calcSettings(); + (double, double, double) calcSettings() + { + if (ActualWidth != 0) + { + double width = Math.Min(ActualWidth, ActualHeight); + + double diameterAdditive; + { + double init() + { + if (width <= 40.0) + { + return 1.0; + } + return 0.0; + } + diameterAdditive = init(); + } + + double diamaterValue = (width * 0.1) + diameterAdditive; + double anchorPoint = (width * 0.5) - diamaterValue; + return (width, diamaterValue, anchorPoint); + } + + return (0.0, 0.0, 0.0); + }; + + templateSettings.EllipseDiameter = diameterValue; + + Thickness thicknessEllipseOffset = new Thickness(0, anchorPoint, 0, 0); + + templateSettings.EllipseOffset = thicknessEllipseOffset; + templateSettings.MaxSideLength = width; } } } diff --git a/ModernWpf.Controls/ProgressRing/ProgressRing.xaml b/ModernWpf.Controls/ProgressRing/ProgressRing.xaml index 76129b94..5ce132b5 100644 --- a/ModernWpf.Controls/ProgressRing/ProgressRing.xaml +++ b/ModernWpf.Controls/ProgressRing/ProgressRing.xaml @@ -7,12 +7,13 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ModernWpfTestApp/ProgressRingPage.xaml.cs b/test/ModernWpfTestApp/ProgressRingPage.xaml.cs new file mode 100644 index 00000000..3baf5409 --- /dev/null +++ b/test/ModernWpfTestApp/ProgressRingPage.xaml.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace MUXControlsTestApp +{ + [TopLevelTestPage(Name = "ProgressRing")] + public sealed partial class ProgressRingPage : TestPage + { + public ProgressRingPage() + { + this.InitializeComponent(); + Loaded += ProgressRingPage_Loaded; + + //NavigateToCustomLottieSourcePage.Click += delegate { Frame.NavigateWithoutAnimation(typeof(ProgressRingCustomLottieSourcePage), 0); }; + NavigateToStoryboardAnimationPage.Click += delegate { Frame.NavigateWithoutAnimation(typeof(ProgressRingStoryboardAnimationPage), 0); }; + } + + private void ProgressRingPage_Loaded(object sender, RoutedEventArgs e) + { + var layoutRoot = (FrameworkElement)VisualTreeHelper.GetChild(TestProgressRing, 0); + + var commonStatesGroup = (VisualStateGroup)VisualStateManager.GetVisualStateGroups(layoutRoot)[0]; + commonStatesGroup.CurrentStateChanged += this.ProgressRingPage_CurrentStateChanged; + VisualStateText.Text = commonStatesGroup.CurrentState.Name; + foreach (var state in commonStatesGroup.States.Cast().Where(s => s.Storyboard != null)) + { + // Change the animation to 0 duration to avoid timing issues in the test. + state.Storyboard.Children[0].Duration = new Duration(TimeSpan.FromSeconds(0)); + } + + //var animatedVisualPlayer = (ModernWpf.Controls.AnimatedVisualPlayer)VisualTreeHelper.GetChild(layoutRoot, 0); + + //IsPlayingText.Text = animatedVisualPlayer.IsPlaying.ToString(); + OpacityText.Text = layoutRoot.Opacity.ToString(); + + Loaded -= ProgressRingPage_Loaded; + } + + private void ProgressRingPage_CurrentStateChanged(object sender, VisualStateChangedEventArgs e) + { + VisualStateText.Text = e.NewState.Name; + + var layoutRoot = (FrameworkElement)VisualTreeHelper.GetChild(TestProgressRing, 0); + //var animatedVisualPlayer = (ModernWpf.Controls.AnimatedVisualPlayer)VisualTreeHelper.GetChild(layoutRoot, 0); + //IsPlayingText.Text = animatedVisualPlayer.IsPlaying.ToString(); + OpacityText.Text = layoutRoot.Opacity.ToString(); + } + + public void UpdateWidth_Click(object sender, RoutedEventArgs e) + { + TestProgressRing.Width = String.IsNullOrEmpty(WidthInput.Text) ? Double.Parse(WidthInput.PlaceholderText) : Double.Parse(WidthInput.Text); + TestProgressRing.Height = String.IsNullOrEmpty(WidthInput.Text) ? Double.Parse(WidthInput.PlaceholderText) : Double.Parse(WidthInput.Text); + } + } +} diff --git a/test/ModernWpfTestApp/ProgressRingStoryboardAnimationPage.xaml b/test/ModernWpfTestApp/ProgressRingStoryboardAnimationPage.xaml new file mode 100644 index 00000000..9f6f5ae7 --- /dev/null +++ b/test/ModernWpfTestApp/ProgressRingStoryboardAnimationPage.xaml @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + Visible + + + + + + + + + + + + + + + Visible + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ModernWpfTestApp/ProgressRingStoryboardAnimationPage.xaml.cs b/test/ModernWpfTestApp/ProgressRingStoryboardAnimationPage.xaml.cs new file mode 100644 index 00000000..de24a15f --- /dev/null +++ b/test/ModernWpfTestApp/ProgressRingStoryboardAnimationPage.xaml.cs @@ -0,0 +1,40 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace MUXControlsTestApp +{ + public sealed partial class ProgressRingStoryboardAnimationPage : TestPage + { + public ProgressRingStoryboardAnimationPage() + { + this.InitializeComponent(); + Loaded += ProgressRingStoryboardAnimationPage_Loaded; + + NavigateToMainPage.Click += delegate { Frame.NavigateWithoutAnimation(typeof(ProgressRingPage), 0); }; + } + + private void ProgressRingStoryboardAnimationPage_Loaded(object sender, RoutedEventArgs e) + { + var layoutRoot = (FrameworkElement)VisualTreeHelper.GetChild(TestStoryboardAnimationProgressRing, 0); + + var commonStatesGroup = (VisualStateGroup)VisualStateManager.GetVisualStateGroups(layoutRoot)[1]; + commonStatesGroup.CurrentStateChanged += this.ProgressRingStoryboardAnimationPage_CurrentStateChanged; + VisualStateText.Text = commonStatesGroup.CurrentState.Name; + + Loaded -= ProgressRingStoryboardAnimationPage_Loaded; + } + + private void ProgressRingStoryboardAnimationPage_CurrentStateChanged(object sender, VisualStateChangedEventArgs e) + { + VisualStateText.Text = e.NewState.Name; + } + + public void UpdateWidth_Click(object sender, RoutedEventArgs e) + { + TestStoryboardAnimationProgressRing.Width = String.IsNullOrEmpty(WidthInput.Text) ? Double.Parse(WidthInput.PlaceholderText) : Double.Parse(WidthInput.Text); + TestStoryboardAnimationProgressRing.Height = String.IsNullOrEmpty(WidthInput.Text) ? Double.Parse(WidthInput.PlaceholderText) : Double.Parse(WidthInput.Text); + } + } +} diff --git a/test/TestAppUtils/BindExtension.cs b/test/TestAppUtils/BindExtension.cs new file mode 100644 index 00000000..e4f0325a --- /dev/null +++ b/test/TestAppUtils/BindExtension.cs @@ -0,0 +1,80 @@ +using System.ComponentModel; +using System.Windows.Data; +using System.Windows.Markup; +using System.Xaml; + +namespace System.Windows +{ + [MarkupExtensionReturnType(typeof(object))] + public class BindExtension : MarkupExtension + { + public BindExtension() + { + } + + public BindExtension(string path) + { + if (path != null) + { + Path = new PropertyPath(path, null); + } + } + + public PropertyPath Path { get; set; } + + [DefaultValue(BindingMode.OneTime)] + public BindingMode Mode { get; set; } = BindingMode.OneWay; + + [DefaultValue(null)] + public IValueConverter Converter { get; set; } + + [DefaultValue(UpdateSourceTrigger.PropertyChanged)] + public UpdateSourceTrigger UpdateSourceTrigger { get; set; } = UpdateSourceTrigger.PropertyChanged; + + public override object ProvideValue(IServiceProvider serviceProvider) + { + var rootObject = (serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider)?.RootObject; + if (rootObject == null) + throw new InvalidOperationException("Cannot find RootObject"); + + PropertyPath effectivePath = Path; + string elementName = null; + + if (rootObject is FrameworkElement rootElement && rootElement.TemplatedParent == null) + { + if (Path != null && Path.PathParameters.Count == 0) + { + string path = Path.Path; + if (!string.IsNullOrEmpty(path)) + { + var names = path.Split('.'); + if (names.Length == 2) + { + elementName = names[0]; + effectivePath = new PropertyPath(names[1]); + } + } + } + } + + var binding = new Binding + { + Path = effectivePath, + Mode = Mode, + UpdateSourceTrigger = UpdateSourceTrigger, + Converter = Converter + }; + + if (elementName != null) + { + binding.ElementName = elementName; + } + else + { + binding.Source = rootObject; + } + + return binding.ProvideValue(serviceProvider); + } + } +} diff --git a/test/TestAppUtils/Controls/TextBoxEx.cs b/test/TestAppUtils/Controls/TextBoxEx.cs index 7fc9bf77..4fca08ee 100644 --- a/test/TestAppUtils/Controls/TextBoxEx.cs +++ b/test/TestAppUtils/Controls/TextBoxEx.cs @@ -16,6 +16,30 @@ public CornerRadius CornerRadius #endregion + #region Header + + public static readonly DependencyProperty HeaderProperty = ControlHelper.HeaderProperty.AddOwner(typeof(TextBoxEx)); + + public object Header + { + get => GetValue(HeaderProperty); + set => SetValue(HeaderProperty, value); + } + + #endregion + + #region PlaceholderText + + public static readonly DependencyProperty PlaceholderTextProperty = ControlHelper.PlaceholderTextProperty.AddOwner(typeof(TextBoxEx)); + + public string PlaceholderText + { + get => (string)GetValue(PlaceholderTextProperty); + set => SetValue(PlaceholderTextProperty, value); + } + + #endregion + protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); diff --git a/test/TestAppUtils/Properties/AssemblyInfo.cs b/test/TestAppUtils/Properties/AssemblyInfo.cs index 35c2ac19..78bdc615 100644 --- a/test/TestAppUtils/Properties/AssemblyInfo.cs +++ b/test/TestAppUtils/Properties/AssemblyInfo.cs @@ -23,4 +23,5 @@ [assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "System.Windows")] [assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "System.Windows.Controls")] [assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "System.Windows.Controls.Primitives")] +[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "System.Windows")] [assembly: XmlnsDefinition("http://schemas.modernwpf.com/2019", "ModernWpf.Controls")]