Skip to content

Commit d8bca1c

Browse files
Refactor SmartHint to allow taking up available space when floated (#3041)
* Refactor SmartHint to allow taking up allowed space Places the FloatingHintTextBox inside of a Canvas and splits the RenderTransform in 2 by applying the TranslateTransform to the Canvas (to move it) and the ScaleTransform to the FloatingHintTextBlock (to scale it down) * Fix CA error in unit tests * Minor cleanup on the converter Co-authored-by: Kevin Bost <kitokeboo@gmail.com>
1 parent 37727bb commit d8bca1c

File tree

5 files changed

+126
-83
lines changed

5 files changed

+126
-83
lines changed

MainDemo.Wpf/ComboBoxes.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@
237237

238238
<ComboBox x:Name="FilledComboBox"
239239
Width="256"
240-
materialDesign:HintAssist.Hint="Some item"
240+
materialDesign:HintAssist.Hint="Some item with a very long hint text that will cut off when floating"
241241
IsEnabled="{Binding Path=IsChecked, ElementName=FilledComboBoxEnabledCheckBox}"
242242
Style="{StaticResource MaterialDesignFilledComboBox}">
243243
<ComboBoxItem Content="Item 1" />

MainDemo.Wpf/Fields.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@
299299

300300
<TextBox MaxWidth="400"
301301
VerticalAlignment="Top"
302-
materialDesign:HintAssist.Hint="Floating hint in a box"
302+
materialDesign:HintAssist.Hint="Floating hint in a box that will cut off"
303303
AcceptsReturn="True"
304304
IsEnabled="{Binding Path=IsChecked, ElementName=MaterialDesignFilledTextBoxEnabledComboBox}"
305305
Style="{StaticResource MaterialDesignFilledTextBox}"

MaterialDesignThemes.Wpf.Tests/Converters/FloatingHintTransformConverterTests.cs

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,81 @@
33
using MaterialDesignThemes.Wpf.Converters;
44
using Xunit;
55

6-
namespace MaterialDesignThemes.Wpf.Tests.Converters
6+
namespace MaterialDesignThemes.Wpf.Tests.Converters;
7+
8+
public class FloatingHintTransformConverterTests
79
{
8-
public class FloatingHintTransformConverterTests
9-
{
10-
public static IEnumerable<object?[]> InvalidParameters =>
11-
new[]
12-
{
13-
new object?[] {null, null, null, null},
14-
new object?[] {1.0, null, null, null},
15-
new object?[] {null, 1.0, null, null},
16-
new object?[] {null, null, 1.0, null},
17-
new object?[] {null, null, null, new Point()},
18-
new object?[] {1.0, DependencyProperty.UnsetValue, DependencyProperty.UnsetValue, DependencyProperty.UnsetValue},
19-
new object?[] {DependencyProperty.UnsetValue, 1.0, DependencyProperty.UnsetValue, DependencyProperty.UnsetValue},
20-
new object?[] {DependencyProperty.UnsetValue, DependencyProperty.UnsetValue, 1.0, DependencyProperty.UnsetValue},
21-
new object?[] {DependencyProperty.UnsetValue, DependencyProperty.UnsetValue, DependencyProperty.UnsetValue, new Point() },
22-
};
23-
24-
[StaTheory]
25-
[MemberData(nameof(InvalidParameters))]
26-
public void WhenParametersAreNotSetItReturnsIdentity(object? scale, object? lower, object? upper, object? offset)
10+
public static IEnumerable<object?[]> InvalidParameters =>
11+
new[]
2712
{
28-
var converter = new FloatingHintTransformConverter();
13+
new object?[] {null, null, null, null},
14+
new object?[] {1.0, null, null, null},
15+
new object?[] {null, 1.0, null, null},
16+
new object?[] {null, null, 1.0, null},
17+
new object?[] {null, null, null, new Point()},
18+
new object?[] {1.0, DependencyProperty.UnsetValue, DependencyProperty.UnsetValue, DependencyProperty.UnsetValue},
19+
new object?[] {DependencyProperty.UnsetValue, 1.0, DependencyProperty.UnsetValue, DependencyProperty.UnsetValue},
20+
new object?[] {DependencyProperty.UnsetValue, DependencyProperty.UnsetValue, 1.0, DependencyProperty.UnsetValue},
21+
new object?[] {DependencyProperty.UnsetValue, DependencyProperty.UnsetValue, DependencyProperty.UnsetValue, new Point() },
22+
};
2923

30-
var result = converter.Convert(new[] { scale, lower, upper, offset },
31-
typeof(Transform), null, CultureInfo.CurrentUICulture);
24+
[Theory]
25+
[MemberData(nameof(InvalidParameters))]
26+
public void WhenParametersAreNotSetItReturnsIdentity(object? scale, object? lower, object? upper, object? offset)
27+
{
28+
var converter = new FloatingHintTransformConverter();
3229

33-
Assert.Equal(Transform.Identity, result);
34-
}
30+
var result = converter.Convert(new[] { scale, lower, upper, offset },
31+
typeof(Transform), null, CultureInfo.CurrentUICulture);
3532

36-
[StaTheory]
37-
[InlineData(2.0, 1.5, 3.0, 3.0, 4.0)]
38-
[InlineData(1.5, 2.0, 3.0, 2.0, 3.0)]
39-
public void WhenParametersAreSpecifiedItReturnsTransforms(double scale, double lower, double upper, double x, double y)
40-
{
41-
var converter = new FloatingHintTransformConverter();
33+
Assert.Equal(Transform.Identity, result);
34+
}
35+
36+
[Theory]
37+
[InlineData(2.0, 1.5, 3.0, 3.0, 4.0)]
38+
[InlineData(1.5, 2.0, 3.0, 2.0, 3.0)]
39+
public void WhenParametersAreSpecifiedItReturnsTransforms(double scale, double lower, double upper, double x, double y)
40+
{
41+
var converter = new FloatingHintTransformConverter();
4242

43-
var result = (TransformGroup?)converter.Convert(new object?[] { scale, lower, upper, new Point(x, y) }, typeof(Transform), null, CultureInfo.CurrentUICulture);
43+
var result = (TransformGroup?)converter.Convert(new object?[] { scale, lower, upper, new Point(x, y) }, typeof(Transform), null, CultureInfo.CurrentUICulture);
4444

45-
Assert.NotNull(result);
46-
var scaleTransform = (ScaleTransform)result!.Children[0];
47-
var translateTransform = (TranslateTransform)result.Children[1];
45+
Assert.NotNull(result);
46+
var scaleTransform = (ScaleTransform)result!.Children[0];
47+
var translateTransform = (TranslateTransform)result.Children[1];
4848

49-
Assert.Equal(upper + (lower - upper) * scale, scaleTransform.ScaleX);
50-
Assert.Equal(upper + (lower - upper) * scale, scaleTransform.ScaleY);
49+
Assert.Equal(upper + (lower - upper) * scale, scaleTransform.ScaleX);
50+
Assert.Equal(upper + (lower - upper) * scale, scaleTransform.ScaleY);
5151

52-
Assert.Equal(scale * x, translateTransform.X);
53-
Assert.Equal(scale * y, translateTransform.Y);
54-
}
52+
Assert.Equal(scale * x, translateTransform.X);
53+
Assert.Equal(scale * y, translateTransform.Y);
5554
}
5655

56+
[Theory]
57+
[InlineData(2.0, 1.5, 3.0, 3.0, 4.0)]
58+
[InlineData(1.5, 2.0, 3.0, 2.0, 3.0)]
59+
public void WhenParametersAreSpecifiedAndScaleTransformDisabledItReturnsTransforms(double scale, double lower, double upper, double x, double y)
60+
{
61+
var converter = new FloatingHintTransformConverter { ApplyScaleTransform = false };
5762

63+
var result = (TransformGroup?)converter.Convert(new object?[] { scale, lower, upper, new Point(x, y) }, typeof(Transform), null, CultureInfo.CurrentUICulture);
64+
65+
Assert.NotNull(result);
66+
Assert.Single(result.Children);
67+
Assert.IsType<TranslateTransform>(result.Children[0]);
68+
}
69+
70+
[Theory]
71+
[InlineData(2.0, 1.5, 3.0, 3.0, 4.0)]
72+
[InlineData(1.5, 2.0, 3.0, 2.0, 3.0)]
73+
public void WhenParametersAreSpecifiedAndTranslateTransformDisabledItReturnsTransforms(double scale, double lower, double upper, double x, double y)
74+
{
75+
var converter = new FloatingHintTransformConverter { ApplyTranslateTransform = false };
76+
77+
var result = (TransformGroup?)converter.Convert(new object?[] { scale, lower, upper, new Point(x, y) }, typeof(Transform), null, CultureInfo.CurrentUICulture);
78+
79+
Assert.NotNull(result);
80+
Assert.Single(result.Children);
81+
Assert.IsType<ScaleTransform>(result.Children[0]);
82+
}
5883
}

MaterialDesignThemes.Wpf/Converters/FloatingHintTransformConverter.cs

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,47 @@
22
using System.Windows.Data;
33
using System.Windows.Media;
44

5-
namespace MaterialDesignThemes.Wpf.Converters
5+
namespace MaterialDesignThemes.Wpf.Converters;
6+
7+
internal class FloatingHintTransformConverter : IMultiValueConverter
68
{
7-
internal class FloatingHintTransformConverter : IMultiValueConverter
9+
public bool ApplyScaleTransform { get; set; } = true;
10+
public bool ApplyTranslateTransform { get; set; } = true;
11+
12+
public object? Convert(object?[]? values, Type targetType, object? parameter, CultureInfo culture)
813
{
9-
public object? Convert(object?[]? values, Type targetType, object? parameter, CultureInfo culture)
14+
if (values?.Length != 4
15+
|| values.Any(v => v == null)
16+
|| !double.TryParse(values[0]!.ToString(), out double scale)
17+
|| !double.TryParse(values[1]!.ToString(), out double lower)
18+
|| !double.TryParse(values[2]!.ToString(), out double upper)
19+
|| values[3] is not Point floatingOffset)
1020
{
11-
if (values == null
12-
|| values.Length != 4
13-
|| values.Any(v => v == null)
14-
|| !double.TryParse(values[0]?.ToString(), out double scale)
15-
|| !double.TryParse(values[1]?.ToString(), out double lower)
16-
|| !double.TryParse(values[2]?.ToString(), out double upper)
17-
|| !(values[3] is Point floatingOffset))
18-
{
19-
return Transform.Identity;
20-
}
21+
return Transform.Identity;
22+
}
2123

22-
double result = upper + (lower - upper) * scale;
24+
double result = upper + (lower - upper) * scale;
2325

24-
var transformGroup = new TransformGroup();
26+
var transformGroup = new TransformGroup();
27+
if (ApplyScaleTransform)
28+
{
2529
transformGroup.Children.Add(new ScaleTransform
2630
{
2731
ScaleX = result,
2832
ScaleY = result
2933
});
34+
}
35+
if (ApplyTranslateTransform)
36+
{
3037
transformGroup.Children.Add(new TranslateTransform
3138
{
3239
X = scale * floatingOffset.X,
3340
Y = scale * floatingOffset.Y
3441
});
35-
return transformGroup;
3642
}
37-
38-
public object?[]? ConvertBack(object? value, Type[] targetTypes, object? parameter, CultureInfo culture)
39-
=> throw new NotImplementedException();
43+
return transformGroup;
4044
}
45+
46+
public object?[]? ConvertBack(object? value, Type[] targetTypes, object? parameter, CultureInfo culture)
47+
=> throw new NotImplementedException();
4148
}

MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.SmartHint.xaml

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
1+
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
22
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
33
xmlns:converters="clr-namespace:MaterialDesignThemes.Wpf.Converters"
44
xmlns:system="clr-namespace:System;assembly=mscorlib"
@@ -8,12 +8,13 @@
88
<converters:BooleanToVisibilityConverter x:Key="InverseBoolToVisConverter"
99
FalseValue="Visible"
1010
TrueValue="Collapsed" />
11-
<converters:FloatingHintTransformConverter x:Key="FloatingHintTransformConverter" />
11+
<converters:FloatingHintTransformConverter x:Key="FloatingHintCanvasTransformConverter" ApplyScaleTransform="False" />
12+
<converters:FloatingHintTransformConverter x:Key="FloatingHintTransformConverter" ApplyTranslateTransform="False" />
1213
<system:Double x:Key="NoContentFloatingScale">1.0</system:Double>
1314
<CubicEase x:Key="AnimationEasingFunction" EasingMode="EaseInOut" />
1415

1516
<Style TargetType="{x:Type wpf:SmartHint}">
16-
<Setter Property="HorizontalAlignment" Value="Left" />
17+
<Setter Property="HorizontalAlignment" Value="Stretch" />
1718
<Setter Property="HorizontalContentAlignment" Value="Left" />
1819
<Setter Property="IsHitTestVisible" Value="False" />
1920
<Setter Property="IsTabStop" Value="False" />
@@ -138,29 +139,39 @@
138139
</VisualStateGroup>
139140
</VisualStateManager.VisualStateGroups>
140141
<wpf:ScaleHost x:Name="ScaleHost" />
141-
<ContentControl x:Name="FloatingHintTextBlock"
142-
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
143-
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
144-
Content="{TemplateBinding Hint}"
145-
FontFamily="{TemplateBinding FontFamily}"
146-
FontSize="{TemplateBinding FontSize}"
147-
IsHitTestVisible="False"
148-
IsTabStop="False"
149-
Opacity="{TemplateBinding HintOpacity}"
150-
RenderTransformOrigin="0,0"
151-
Visibility="{TemplateBinding UseFloating, Converter={StaticResource BoolToVisConverter}}">
152-
<ContentControl.Tag>
153-
<system:Double>0.0</system:Double>
154-
</ContentControl.Tag>
155-
<ContentControl.RenderTransform>
156-
<MultiBinding Converter="{StaticResource FloatingHintTransformConverter}">
142+
<Canvas ClipToBounds="True" HorizontalAlignment="Stretch" Height="{Binding ElementName=FloatingHintTextBlock, Path=ActualHeight}">
143+
<Canvas.RenderTransform>
144+
<MultiBinding Converter="{StaticResource FloatingHintCanvasTransformConverter}">
157145
<Binding ElementName="ScaleHost" Path="Scale" />
158146
<Binding Path="FloatingScale" RelativeSource="{RelativeSource TemplatedParent}" />
159147
<Binding Source="{StaticResource NoContentFloatingScale}" />
160148
<Binding Path="FloatingOffset" RelativeSource="{RelativeSource TemplatedParent}" />
161149
</MultiBinding>
162-
</ContentControl.RenderTransform>
163-
</ContentControl>
150+
</Canvas.RenderTransform>
151+
<ContentControl x:Name="FloatingHintTextBlock"
152+
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
153+
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
154+
Content="{TemplateBinding Hint}"
155+
FontFamily="{TemplateBinding FontFamily}"
156+
FontSize="{TemplateBinding FontSize}"
157+
IsHitTestVisible="False"
158+
IsTabStop="False"
159+
Opacity="{TemplateBinding HintOpacity}"
160+
RenderTransformOrigin="0,0"
161+
Visibility="{TemplateBinding UseFloating, Converter={StaticResource BoolToVisConverter}}">
162+
<ContentControl.Tag>
163+
<system:Double>0.0</system:Double>
164+
</ContentControl.Tag>
165+
<ContentControl.RenderTransform>
166+
<MultiBinding Converter="{StaticResource FloatingHintTransformConverter}">
167+
<Binding ElementName="ScaleHost" Path="Scale" />
168+
<Binding Path="FloatingScale" RelativeSource="{RelativeSource TemplatedParent}" />
169+
<Binding Source="{StaticResource NoContentFloatingScale}" />
170+
<Binding Path="FloatingOffset" RelativeSource="{RelativeSource TemplatedParent}" />
171+
</MultiBinding>
172+
</ContentControl.RenderTransform>
173+
</ContentControl>
174+
</Canvas>
164175
</Grid>
165176
</ControlTemplate>
166177
</Setter.Value>
@@ -169,4 +180,4 @@
169180
</Style.Triggers>
170181
</Style>
171182

172-
</ResourceDictionary>
183+
</ResourceDictionary>

0 commit comments

Comments
 (0)