diff --git a/MaterialDesignThemes.UITests/WPF/TextBoxes/TextBoxTests.cs b/MaterialDesignThemes.UITests/WPF/TextBoxes/TextBoxTests.cs index 9e34efdf66..38066c3417 100644 --- a/MaterialDesignThemes.UITests/WPF/TextBoxes/TextBoxTests.cs +++ b/MaterialDesignThemes.UITests/WPF/TextBoxes/TextBoxTests.cs @@ -391,7 +391,7 @@ public async Task VerticalContentAlignment_ProperlyAlignsText() foreach (var alignment in Enum.GetValues()) { await textBox.SetVerticalContentAlignment(alignment); - Assert.Equal(alignment, await scrollViewer.GetVerticalAlignment()); + Assert.Equal(alignment, await scrollViewer.GetVerticalContentAlignment()); } recorder.Success(); @@ -562,6 +562,34 @@ public async Task TextBox_WithHintAndValidationError_RespectsPadding(string styl recorder.Success(); } + + [Theory] + [InlineData(VerticalAlignment.Stretch, VerticalAlignment.Top)] + [InlineData(VerticalAlignment.Top, VerticalAlignment.Top)] + [InlineData(VerticalAlignment.Bottom, VerticalAlignment.Bottom)] + [InlineData(VerticalAlignment.Center, VerticalAlignment.Center)] + [Description("Issue 3161")] + public async Task TextBox_MultiLineAndFixedHeight_RespectsVerticalContentAlignment(VerticalAlignment alignment, VerticalAlignment expectedFloatingHintAlignment) + { + await using var recorder = new TestRecorder(App); + + var stackPanel = await LoadXaml($$""" + + + + """); + + IVisualElement textBox = await stackPanel.GetElement("/TextBox"); + IVisualElement hintClippingGrid = await textBox.GetElement("HintClippingGrid"); + + Assert.Equal(expectedFloatingHintAlignment, await hintClippingGrid.GetVerticalAlignment()); + + recorder.Success(); + } } public class NotEmptyValidationRule : ValidationRule diff --git a/MaterialDesignThemes.Wpf/SmartHint.cs b/MaterialDesignThemes.Wpf/SmartHint.cs index 6f84f71761..3019decd08 100644 --- a/MaterialDesignThemes.Wpf/SmartHint.cs +++ b/MaterialDesignThemes.Wpf/SmartHint.cs @@ -1,230 +1,240 @@ using System.ComponentModel; +using System.Globalization; +using System.Windows.Data; using MaterialDesignThemes.Wpf.Converters; -namespace MaterialDesignThemes.Wpf +namespace MaterialDesignThemes.Wpf; + +/// +/// A control that implement placeholder behavior. Can work as a simple placeholder either as a floating hint, see property. +/// +/// To set a target control you should set the HintProxy property. Use the converter which converts a control into the IHintProxy interface. +/// +[TemplateVisualState(GroupName = ContentStatesGroupName, Name = HintRestingPositionName)] +[TemplateVisualState(GroupName = ContentStatesGroupName, Name = HintFloatingPositionName)] +public class SmartHint : Control { + public const string ContentStatesGroupName = "ContentStates"; - /// - /// A control that implement placeholder behavior. Can work as a simple placeholder either as a floating hint, see property. - /// - /// To set a target control you should set the HintProxy property. Use the converter which converts a control into the IHintProxy interface. - /// - [TemplateVisualState(GroupName = ContentStatesGroupName, Name = HintRestingPositionName)] - [TemplateVisualState(GroupName = ContentStatesGroupName, Name = HintFloatingPositionName)] - public class SmartHint : Control - { - public const string ContentStatesGroupName = "ContentStates"; + public const string HintRestingPositionName = "HintRestingPosition"; + public const string HintFloatingPositionName = "HintFloatingPosition"; - public const string HintRestingPositionName = "HintRestingPosition"; - public const string HintFloatingPositionName = "HintFloatingPosition"; + #region ManagedProperty - #region ManagedProperty + public static readonly DependencyProperty HintProxyProperty = DependencyProperty.Register( + nameof(HintProxy), typeof(IHintProxy), typeof(SmartHint), new PropertyMetadata(default(IHintProxy?), HintProxyPropertyChangedCallback)); - public static readonly DependencyProperty HintProxyProperty = DependencyProperty.Register( - nameof(HintProxy), typeof(IHintProxy), typeof(SmartHint), new PropertyMetadata(default(IHintProxy?), HintProxyPropertyChangedCallback)); + public IHintProxy? HintProxy + { + get => (IHintProxy)GetValue(HintProxyProperty); + set => SetValue(HintProxyProperty, value); + } - public IHintProxy? HintProxy - { - get => (IHintProxy)GetValue(HintProxyProperty); - set => SetValue(HintProxyProperty, value); - } + #endregion - #endregion + #region HintProperty - #region HintProperty + public static readonly DependencyProperty HintProperty = DependencyProperty.Register( + nameof(Hint), typeof(object), typeof(SmartHint), new PropertyMetadata(null)); - public static readonly DependencyProperty HintProperty = DependencyProperty.Register( - nameof(Hint), typeof(object), typeof(SmartHint), new PropertyMetadata(null)); + public object? Hint + { + get => GetValue(HintProperty); + set => SetValue(HintProperty, value); + } - public object? Hint - { - get => GetValue(HintProperty); - set => SetValue(HintProperty, value); - } + #endregion - #endregion + #region IsContentNullOrEmpty - #region IsContentNullOrEmpty + private static readonly DependencyPropertyKey IsContentNullOrEmptyPropertyKey = + DependencyProperty.RegisterReadOnly( + "IsContentNullOrEmpty", typeof(bool), typeof(SmartHint), + new PropertyMetadata(default(bool))); - private static readonly DependencyPropertyKey IsContentNullOrEmptyPropertyKey = - DependencyProperty.RegisterReadOnly( - "IsContentNullOrEmpty", typeof(bool), typeof(SmartHint), - new PropertyMetadata(default(bool))); + public static readonly DependencyProperty IsContentNullOrEmptyProperty = + IsContentNullOrEmptyPropertyKey.DependencyProperty; - public static readonly DependencyProperty IsContentNullOrEmptyProperty = - IsContentNullOrEmptyPropertyKey.DependencyProperty; + public bool IsContentNullOrEmpty + { + get => (bool)GetValue(IsContentNullOrEmptyProperty); + private set => SetValue(IsContentNullOrEmptyPropertyKey, value); + } - public bool IsContentNullOrEmpty - { - get => (bool)GetValue(IsContentNullOrEmptyProperty); - private set => SetValue(IsContentNullOrEmptyPropertyKey, value); - } + #endregion - #endregion + #region IsHintInFloatingPosition - #region IsHintInFloatingPosition + private static readonly DependencyPropertyKey IsHintInFloatingPositionPropertyKey = + DependencyProperty.RegisterReadOnly( + "IsHintInFloatingPosition", typeof(bool), typeof(SmartHint), + new PropertyMetadata(default(bool))); - private static readonly DependencyPropertyKey IsHintInFloatingPositionPropertyKey = - DependencyProperty.RegisterReadOnly( - "IsHintInFloatingPosition", typeof(bool), typeof(SmartHint), - new PropertyMetadata(default(bool))); + public static readonly DependencyProperty IsHintInFloatingPositionProperty = + IsHintInFloatingPositionPropertyKey.DependencyProperty; - public static readonly DependencyProperty IsHintInFloatingPositionProperty = - IsHintInFloatingPositionPropertyKey.DependencyProperty; + public bool IsHintInFloatingPosition + { + get => (bool)GetValue(IsHintInFloatingPositionProperty); + private set => SetValue(IsHintInFloatingPositionPropertyKey, value); + } - public bool IsHintInFloatingPosition - { - get => (bool)GetValue(IsHintInFloatingPositionProperty); - private set => SetValue(IsHintInFloatingPositionPropertyKey, value); - } + #endregion - #endregion + #region UseFloating - #region UseFloating + public static readonly DependencyProperty UseFloatingProperty = DependencyProperty.Register( + nameof(UseFloating), typeof(bool), typeof(SmartHint), new PropertyMetadata(false)); - public static readonly DependencyProperty UseFloatingProperty = DependencyProperty.Register( - nameof(UseFloating), typeof(bool), typeof(SmartHint), new PropertyMetadata(false)); + public bool UseFloating + { + get => (bool)GetValue(UseFloatingProperty); + set => SetValue(UseFloatingProperty, value); + } - public bool UseFloating - { - get => (bool)GetValue(UseFloatingProperty); - set => SetValue(UseFloatingProperty, value); - } + #endregion - #endregion + #region FloatingScale & FloatingOffset - #region FloatingScale & FloatingOffset + public static readonly DependencyProperty FloatingScaleProperty = DependencyProperty.Register( + nameof(FloatingScale), typeof(double), typeof(SmartHint), new PropertyMetadata(.74)); - public static readonly DependencyProperty FloatingScaleProperty = DependencyProperty.Register( - nameof(FloatingScale), typeof(double), typeof(SmartHint), new PropertyMetadata(.74)); + public double FloatingScale + { + get => (double)GetValue(FloatingScaleProperty); + set => SetValue(FloatingScaleProperty, value); + } - public double FloatingScale - { - get => (double)GetValue(FloatingScaleProperty); - set => SetValue(FloatingScaleProperty, value); - } + public static readonly DependencyProperty FloatingOffsetProperty = DependencyProperty.Register( + nameof(FloatingOffset), typeof(Point), typeof(SmartHint), new PropertyMetadata(new Point(1, -16))); - public static readonly DependencyProperty FloatingOffsetProperty = DependencyProperty.Register( - nameof(FloatingOffset), typeof(Point), typeof(SmartHint), new PropertyMetadata(new Point(1, -16))); + public Point FloatingOffset + { + get => (Point)GetValue(FloatingOffsetProperty); + set => SetValue(FloatingOffsetProperty, value); + } - public Point FloatingOffset - { - get => (Point)GetValue(FloatingOffsetProperty); - set => SetValue(FloatingOffsetProperty, value); - } + #endregion - #endregion + #region HintOpacity - #region HintOpacity + public static readonly DependencyProperty HintOpacityProperty = DependencyProperty.Register( + nameof(HintOpacity), typeof(double), typeof(SmartHint), new PropertyMetadata(.46)); - public static readonly DependencyProperty HintOpacityProperty = DependencyProperty.Register( - nameof(HintOpacity), typeof(double), typeof(SmartHint), new PropertyMetadata(.46)); + public double HintOpacity + { + get => (double)GetValue(HintOpacityProperty); + set => SetValue(HintOpacityProperty, value); + } - public double HintOpacity - { - get => (double)GetValue(HintOpacityProperty); - set => SetValue(HintOpacityProperty, value); - } + #endregion - #endregion + static SmartHint() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SmartHint), new FrameworkPropertyMetadata(typeof(SmartHint))); + } + + private static void HintProxyPropertyChangedCallback(DependencyObject? dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + var smartHint = dependencyObject as SmartHint; + if (smartHint is null) return; - static SmartHint() + if (dependencyPropertyChangedEventArgs.OldValue is IHintProxy oldHintProxy) { - DefaultStyleKeyProperty.OverrideMetadata(typeof(SmartHint), new FrameworkPropertyMetadata(typeof(SmartHint))); + oldHintProxy.IsVisibleChanged -= smartHint.OnHintProxyIsVisibleChanged; + oldHintProxy.ContentChanged -= smartHint.OnHintProxyContentChanged; + oldHintProxy.Loaded -= smartHint.OnHintProxyContentChanged; + oldHintProxy.FocusedChanged -= smartHint.OnHintProxyFocusedChanged; + oldHintProxy.Dispose(); } - private static void HintProxyPropertyChangedCallback(DependencyObject? dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + if (dependencyPropertyChangedEventArgs.NewValue is IHintProxy newHintProxy) { - var smartHint = dependencyObject as SmartHint; - if (smartHint is null) return; - - if (dependencyPropertyChangedEventArgs.OldValue is IHintProxy oldHintProxy) - { - oldHintProxy.IsVisibleChanged -= smartHint.OnHintProxyIsVisibleChanged; - oldHintProxy.ContentChanged -= smartHint.OnHintProxyContentChanged; - oldHintProxy.Loaded -= smartHint.OnHintProxyContentChanged; - oldHintProxy.FocusedChanged -= smartHint.OnHintProxyFocusedChanged; - oldHintProxy.Dispose(); - } - - if (dependencyPropertyChangedEventArgs.NewValue is IHintProxy newHintProxy) - { - newHintProxy.IsVisibleChanged += smartHint.OnHintProxyIsVisibleChanged; - newHintProxy.ContentChanged += smartHint.OnHintProxyContentChanged; - newHintProxy.Loaded += smartHint.OnHintProxyContentChanged; - newHintProxy.FocusedChanged += smartHint.OnHintProxyFocusedChanged; - smartHint.RefreshState(false); - } + newHintProxy.IsVisibleChanged += smartHint.OnHintProxyIsVisibleChanged; + newHintProxy.ContentChanged += smartHint.OnHintProxyContentChanged; + newHintProxy.Loaded += smartHint.OnHintProxyContentChanged; + newHintProxy.FocusedChanged += smartHint.OnHintProxyFocusedChanged; + smartHint.RefreshState(false); } + } - protected virtual void OnHintProxyFocusedChanged(object? sender, EventArgs e) + protected virtual void OnHintProxyFocusedChanged(object? sender, EventArgs e) + { + if (HintProxy is { } hintProxy) { - if (HintProxy is { } hintProxy) - { - if (hintProxy.IsLoaded) - RefreshState(true); - else - hintProxy.Loaded += HintProxySetStateOnLoaded; - } + if (hintProxy.IsLoaded) + RefreshState(true); + else + hintProxy.Loaded += HintProxySetStateOnLoaded; } + } - protected virtual void OnHintProxyContentChanged(object? sender, EventArgs e) + protected virtual void OnHintProxyContentChanged(object? sender, EventArgs e) + { + IsContentNullOrEmpty = HintProxy?.IsEmpty() == true; + + if (HintProxy is { } hintProxy) { - IsContentNullOrEmpty = HintProxy?.IsEmpty() == true; - - if (HintProxy is { } hintProxy) - { - if (hintProxy.IsLoaded) - RefreshState(true); - else - hintProxy.Loaded += HintProxySetStateOnLoaded; - } + if (hintProxy.IsLoaded) + RefreshState(true); + else + hintProxy.Loaded += HintProxySetStateOnLoaded; } + } - private void HintProxySetStateOnLoaded(object? sender, EventArgs e) + private void HintProxySetStateOnLoaded(object? sender, EventArgs e) + { + RefreshState(false); + if (HintProxy is { } hintProxy) { - RefreshState(false); - if (HintProxy is { } hintProxy) - { - hintProxy.Loaded -= HintProxySetStateOnLoaded; - } + hintProxy.Loaded -= HintProxySetStateOnLoaded; } + } - protected virtual void OnHintProxyIsVisibleChanged(object? sender, EventArgs e) - => RefreshState(false); + protected virtual void OnHintProxyIsVisibleChanged(object? sender, EventArgs e) + => RefreshState(false); - private void RefreshState(bool useTransitions) - { - IHintProxy? proxy = HintProxy; + private void RefreshState(bool useTransitions) + { + IHintProxy? proxy = HintProxy; - if (proxy is null) return; - if (!proxy.IsVisible) return; + if (proxy is null) return; + if (!proxy.IsVisible) return; - var action = new Action(() => - { - string state = string.Empty; + var action = new Action(() => + { + string state = string.Empty; - bool isEmpty = proxy.IsEmpty(); - bool isFocused = proxy.IsFocused(); + bool isEmpty = proxy.IsEmpty(); + bool isFocused = proxy.IsFocused(); - if (UseFloating) - state = !isEmpty || isFocused ? HintFloatingPositionName : HintRestingPositionName; - else - state = !isEmpty ? HintFloatingPositionName : HintRestingPositionName; + if (UseFloating) + state = !isEmpty || isFocused ? HintFloatingPositionName : HintRestingPositionName; + else + state = !isEmpty ? HintFloatingPositionName : HintRestingPositionName; - IsHintInFloatingPosition = state == HintFloatingPositionName; + IsHintInFloatingPosition = state == HintFloatingPositionName; - VisualStateManager.GoToState(this, state, useTransitions); - }); + VisualStateManager.GoToState(this, state, useTransitions); + }); - if (DesignerProperties.GetIsInDesignMode(this)) - { - action(); - } - else - { - Dispatcher.BeginInvoke(action); - } + if (DesignerProperties.GetIsInDesignMode(this)) + { + action(); + } + else + { + Dispatcher.BeginInvoke(action); } } } + +public class VerticalAlignmentConverter : IValueConverter +{ + public VerticalAlignment StretchReplacement { get; set; } = VerticalAlignment.Top; + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + => value is VerticalAlignment.Stretch ? StretchReplacement : value; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => throw new NotImplementedException(); +} diff --git a/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.PasswordBox.xaml b/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.PasswordBox.xaml index 4af3af365c..3c150e07e6 100644 --- a/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.PasswordBox.xaml +++ b/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.PasswordBox.xaml @@ -140,7 +140,6 @@ @@ -604,7 +603,6 @@ diff --git a/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.SmartHint.xaml b/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.SmartHint.xaml index 88588f563a..f43e0bdb0c 100644 --- a/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.SmartHint.xaml +++ b/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.SmartHint.xaml @@ -11,6 +11,7 @@ + 1.0 @@ -140,7 +141,7 @@ - + @@ -149,7 +150,7 @@ - + - + @@ -147,7 +147,7 @@ @@ -172,7 +172,8 @@