Skip to content
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

Added BorderThickness attached DP for outlined date/time pickers #2805

Merged
merged 6 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions MainDemo.Wpf/Pickers.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,60 @@
materialDesign:HintAssist.HelperText="Helper text"
Style="{StaticResource MaterialDesignOutlinedDatePicker}"/>
</smtx:XamlDisplay>

<smtx:XamlDisplay
UniqueKey="pickers_unchanging_borderthickness"
HorizontalAlignment="Left"
Margin="0 16 0 0">
<DatePicker
Width="140"
materialDesign:HintAssist.Hint="Pick Date"
materialDesign:HintAssist.HelperText="Helper text"
materialDesign:DatePickerAssist.OutlinedBorderInactiveThickness="2"
materialDesign:HintAssist.FloatingOffset="0,-22"
Style="{StaticResource MaterialDesignOutlinedDatePicker}"/>
</smtx:XamlDisplay>

<smtx:XamlDisplay
UniqueKey="pickers_14_custom_borderthickness"
HorizontalAlignment="Left"
Margin="0 16 0 0">
<DatePicker
Width="140"
materialDesign:HintAssist.Hint="Pick Date"
materialDesign:HintAssist.HelperText="Helper text"
materialDesign:HintAssist.FloatingOffset="0,-23"
materialDesign:DatePickerAssist.OutlinedBorderInactiveThickness="3"
materialDesign:DatePickerAssist.OutlinedBorderActiveThickness="3"
Style="{StaticResource MaterialDesignOutlinedDatePicker}"/>
</smtx:XamlDisplay>

<smtx:XamlDisplay
UniqueKey="pickers_time"
HorizontalAlignment="Left"
Margin="0 16 0 0">
<materialDesign:TimePicker
Is24Hours="True"
Width="140"
materialDesign:HintAssist.Hint="Pick Time"
materialDesign:HintAssist.HelperText="Helper text"
Style="{StaticResource MaterialDesignOutlinedTimePicker}"/>
</smtx:XamlDisplay>

<smtx:XamlDisplay
UniqueKey="pickers_time_custom_borderthickness"
HorizontalAlignment="Left"
Margin="0 16 0 0">
<materialDesign:TimePicker
Is24Hours="True"
Width="140"
materialDesign:HintAssist.Hint="Pick Time"
materialDesign:HintAssist.HelperText="Helper text"
materialDesign:HintAssist.FloatingOffset="0,-23"
materialDesign:TimePickerAssist.OutlinedBorderInactiveThickness="3"
materialDesign:TimePickerAssist.OutlinedBorderActiveThickness="3"
Style="{StaticResource MaterialDesignOutlinedTimePicker}"/>
</smtx:XamlDisplay>
</StackPanel>

<smtx:XamlDisplay
Expand Down
4 changes: 2 additions & 2 deletions MaterialDesignThemes.UITests/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ protected async Task<Color> GetThemeColor(string name)
return resource.GetAs<Color?>() ?? throw new Exception($"Failed to convert resource '{name}' to color");
}

protected async Task<IVisualElement<T>> LoadXaml<T>(string xaml)
protected async Task<IVisualElement<T>> LoadXaml<T>(string xaml, params (string namespacePrefix, Type type)[] additionalNamespaceDeclarations)
{
await App.InitializeWithMaterialDesign();
return await App.CreateWindowWith<T>(xaml);
return await App.CreateWindowWith<T>(xaml, additionalNamespaceDeclarations);
}

protected async Task<IVisualElement> LoadUserControl<TControl>()
Expand Down
72 changes: 71 additions & 1 deletion MaterialDesignThemes.UITests/WPF/DatePickers/DatePickerTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.ComponentModel;
using System.Globalization;
using System.Threading.Tasks;
Expand Down Expand Up @@ -64,5 +64,75 @@ await Wait.For(async () =>

recorder.Success();
}

[Fact]
[Description("Issue 2737")]
public async Task OutlinedDatePicker_RespectsActiveAndInactiveBorderThickness_WhenAttachedPropertiesAreSet()
{
await using var recorder = new TestRecorder(App);

// Arrange
var expectedInactiveBorderThickness = new Thickness(4, 3, 2, 1);
var expectedActiveBorderThickness = new Thickness(1, 2, 3, 4);
var stackPanel = await LoadXaml<StackPanel>($@"
<StackPanel>
<DatePicker Style=""{{StaticResource MaterialDesignOutlinedDatePicker}}""
materialDesign:DatePickerAssist.OutlinedBorderInactiveThickness=""{expectedInactiveBorderThickness}""
materialDesign:DatePickerAssist.OutlinedBorderActiveThickness=""{expectedActiveBorderThickness}"">
<DatePicker.SelectedDate>
<Binding RelativeSource=""{{RelativeSource Self}}"" Path=""Tag"" UpdateSourceTrigger=""PropertyChanged"">
<Binding.ValidationRules>
<local:FutureDateValidationRule ValidatesOnTargetUpdated=""True""/>
</Binding.ValidationRules>
</Binding>
</DatePicker.SelectedDate>
</DatePicker>
<Button x:Name=""Button"" Content=""Some Button"" Margin=""0,20,0,0"" />
</StackPanel>", ("local", typeof(FutureDateValidationRule)));
var datePicker = await stackPanel.GetElement<DatePicker>("/DatePicker");
await datePicker.SetProperty(DatePicker.SelectedDateProperty, DateTime.Now.AddDays(1));
var datePickerTextBox = await datePicker.GetElement<TextBox>("PART_TextBox");
var button = await stackPanel.GetElement<Button>("Button");

// Act
await button.MoveCursorTo();
await Task.Delay(50); // Wait for the visual change
var inactiveBorderThickness = await datePickerTextBox.GetProperty<Thickness>(Control.BorderThicknessProperty);
await datePickerTextBox.MoveCursorTo();
await Task.Delay(50); // Wait for the visual change
var hoverBorderThickness = await datePickerTextBox.GetProperty<Thickness>(Control.BorderThicknessProperty);
await datePickerTextBox.LeftClick();
await Task.Delay(50); // Wait for the visual change
var focusedBorderThickness = await datePickerTextBox.GetProperty<Thickness>(Control.BorderThicknessProperty);

// TODO: It would be cool if a validation error could be set via XAMLTest without the need for the Binding and ValidationRules elements in the XAML above.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good request. I will have to look into what that API could look like. I typically have done that by building out a custom control in the test project (as this then supports things like code behind, view models, etc). But I can certainly see a desire to not have to jump through all of those hoops.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good request. I will have to look into what that API could look like. I typically have done that by building out a custom control in the test project (as this then supports things like code behind, view models, etc). But I can certainly see a desire to not have to jump through all of those hoops.

Cool! As mentioned in the PR, I already have a proof-of-concept PR in the XAMLTest library with one approach to adding this type of functionality. Looking forward to seeing how you would approach it.

await datePicker.SetProperty(DatePicker.SelectedDateProperty, DateTime.Now);
await Task.Delay(50); // Wait for the visual change
var withErrorBorderThickness = await datePickerTextBox.GetProperty<Thickness>(Control.BorderThicknessProperty);

// Assert
Assert.Equal(expectedInactiveBorderThickness, inactiveBorderThickness);
Assert.Equal(expectedActiveBorderThickness, hoverBorderThickness);
Assert.Equal(expectedActiveBorderThickness, focusedBorderThickness);
Assert.Equal(expectedActiveBorderThickness, withErrorBorderThickness);

recorder.Success();
}
}

public class FutureDateValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
DateTime time;
if (!DateTime.TryParse((value ?? "").ToString(),
CultureInfo.CurrentCulture,
DateTimeStyles.AssumeLocal | DateTimeStyles.AllowWhiteSpaces,
out time)) return new ValidationResult(false, "Invalid date");

return time.Date <= DateTime.Now.Date
? new ValidationResult(false, "Future date required")
: ValidationResult.ValidResult;
}
}
}
61 changes: 61 additions & 0 deletions MaterialDesignThemes.UITests/WPF/TimePickers/TimePickerTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Threading.Tasks;
namespace MaterialDesignThemes.UITests.WPF.TimePickers;

Expand Down Expand Up @@ -337,4 +338,64 @@ public async Task OnTimePickerHelperTextFontSize_ChangesHelperTextFontSize()
Assert.Equal(20, fontSize);
recorder.Success();
}

[Fact]
[Description("Issue 2737")]
public async Task OutlinedTimePicker_RespectsActiveAndInactiveBorderThickness_WhenAttachedPropertiesAreSet()
{
await using var recorder = new TestRecorder(App);

// Arrange
var expectedInactiveBorderThickness = new Thickness(4, 3, 2, 1);
var expectedActiveBorderThickness = new Thickness(1, 2, 3, 4);
var stackPanel = await LoadXaml<StackPanel>($@"
<StackPanel>
<materialDesign:TimePicker Style=""{{StaticResource MaterialDesignOutlinedTimePicker}}""
materialDesign:TimePickerAssist.OutlinedBorderInactiveThickness=""{expectedInactiveBorderThickness}""
materialDesign:TimePickerAssist.OutlinedBorderActiveThickness=""{expectedActiveBorderThickness}"">
<materialDesign:TimePicker.Text>
<Binding RelativeSource=""{{RelativeSource Self}}"" Path=""Tag"" UpdateSourceTrigger=""PropertyChanged"">
<Binding.ValidationRules>
<local:OnlyTenOClockValidationRule ValidatesOnTargetUpdated=""True""/>
</Binding.ValidationRules>
</Binding>
</materialDesign:TimePicker.Text>
</materialDesign:TimePicker>
<Button x:Name=""Button"" Content=""Some Button"" Margin=""0,20,0,0"" />
</StackPanel>", ("local", typeof(OnlyTenOClockValidationRule)));
var timePicker = await stackPanel.GetElement<TimePicker>("/TimePicker");
await timePicker.SetProperty(TimePicker.TextProperty, "10:00");
var timePickerTextBox = await timePicker.GetElement<TimePickerTextBox>("/TimePickerTextBox");
var button = await stackPanel.GetElement<Button>("Button");

// Act
await button.MoveCursorTo();
await Task.Delay(50); // Wait for the visual change
var inactiveBorderThickness = await timePickerTextBox.GetProperty<Thickness>(Control.BorderThicknessProperty);
await timePickerTextBox.MoveCursorTo();
await Task.Delay(50); // Wait for the visual change
var hoverBorderThickness = await timePickerTextBox.GetProperty<Thickness>(Control.BorderThicknessProperty);
await timePickerTextBox.LeftClick();
await Task.Delay(50); // Wait for the visual change
var focusedBorderThickness = await timePickerTextBox.GetProperty<Thickness>(Control.BorderThicknessProperty);

// TODO: It would be cool if a validation error could be set via XAMLTest without the need for the Binding and ValidationRules elements in the XAML above.
await timePicker.SetProperty(TimePicker.TextProperty, "11:00");
await Task.Delay(50); // Wait for the visual change
var withErrorBorderThickness = await timePickerTextBox.GetProperty<Thickness>(Control.BorderThicknessProperty);

// Assert
Assert.Equal(expectedInactiveBorderThickness, inactiveBorderThickness);
Assert.Equal(expectedActiveBorderThickness, hoverBorderThickness);
Assert.Equal(expectedActiveBorderThickness, focusedBorderThickness);
Assert.Equal(expectedActiveBorderThickness, withErrorBorderThickness);

recorder.Success();
}
}

public class OnlyTenOClockValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
=> value is not "10:00" ? new ValidationResult(false, "Only 10 o'clock allowed") : ValidationResult.ValidResult;
}
10 changes: 9 additions & 1 deletion MaterialDesignThemes.UITests/XamlTestMixins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using System.Windows.Controls;
using MaterialDesignColors;
using MaterialDesignThemes.UITests.WPF.DatePickers;
using MaterialDesignThemes.Wpf;
using XamlTest;

Expand Down Expand Up @@ -40,14 +41,21 @@ await app.Initialize(applicationResourceXaml,
Assembly.GetExecutingAssembly().Location);
}

public static async Task<IVisualElement<T>> CreateWindowWith<T>(this IApp app, string xaml)
public static async Task<IVisualElement<T>> CreateWindowWith<T>(this IApp app, string xaml, params (string namespacePrefix, Type type)[] additionalNamespaceDeclarations)
{
var extraNamespaceDeclarations = new StringBuilder("");
foreach ((string namespacePrefix, Type type) in additionalNamespaceDeclarations)
{
extraNamespaceDeclarations.AppendLine($@"xmlns:{namespacePrefix}=""clr-namespace:{type.Namespace};assembly={type.Assembly.GetName().Name}""");
}

string windowXaml = @$"<Window
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:d=""http://schemas.microsoft.com/expression/blend/2008""
xmlns:mc=""http://schemas.openxmlformats.org/markup-compatibility/2006""
xmlns:materialDesign=""http://materialdesigninxaml.net/winfx/xaml/themes""
{extraNamespaceDeclarations}
mc:Ignorable=""d""
Height=""800"" Width=""1100""
TextElement.Foreground=""{{DynamicResource MaterialDesignBody}}""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Globalization;
using System.Windows.Data;

namespace MaterialDesignThemes.Wpf.Converters;

public class OutlinedDateTimePickerActiveBorderThicknessConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 2
&& values[0] is Thickness baseThickness
&& values[1] is Thickness thicknessToSubtract)
{
var thickness = new Thickness(baseThickness.Left - thicknessToSubtract.Left,
baseThickness.Top - thicknessToSubtract.Top,
baseThickness.Right - thicknessToSubtract.Right,
baseThickness.Bottom - thicknessToSubtract.Bottom);
return thickness;
}
return default(Thickness);
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
33 changes: 33 additions & 0 deletions MaterialDesignThemes.Wpf/DatePickerAssist.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace MaterialDesignThemes.Wpf;

public static class DatePickerAssist
{
private static Thickness DefaultOutlinedBorderInactiveThickness { get; } = new(1);
private static Thickness DefaultOutlinedBorderActiveThickness { get; } = new(2);

public static readonly DependencyProperty OutlinedBorderInactiveThicknessProperty = DependencyProperty.RegisterAttached(
"OutlinedBorderInactiveThickness", typeof(Thickness), typeof(DatePickerAssist), new FrameworkPropertyMetadata(DefaultOutlinedBorderInactiveThickness, FrameworkPropertyMetadataOptions.Inherits));

public static void SetOutlinedBorderInactiveThickness(DependencyObject element, Thickness value)
{
element.SetValue(OutlinedBorderInactiveThicknessProperty, value);
}

public static Thickness GetOutlinedBorderInactiveThickness(DependencyObject element)
{
return (Thickness) element.GetValue(OutlinedBorderInactiveThicknessProperty);
}

public static readonly DependencyProperty OutlinedBorderActiveThicknessProperty = DependencyProperty.RegisterAttached(
"OutlinedBorderActiveThickness", typeof(Thickness), typeof(DatePickerAssist), new FrameworkPropertyMetadata(DefaultOutlinedBorderActiveThickness, FrameworkPropertyMetadataOptions.Inherits));

public static void SetOutlinedBorderActiveThickness(DependencyObject element, Thickness value)
{
element.SetValue(OutlinedBorderActiveThicknessProperty, value);
}

public static Thickness GetOutlinedBorderActiveThickness(DependencyObject element)
{
return (Thickness) element.GetValue(OutlinedBorderActiveThicknessProperty);
}
}
Loading