Skip to content

Commit 182823d

Browse files
authored
Fixing binding issues with expander (#1697)
* Handing warnings in the expander template These were caused because the ScaleTransform in the style was trying to create bindings that were failing because they were outside of the visual tree. Fixes #1571 * Adding unit tests for the converters
1 parent d721913 commit 182823d

File tree

10 files changed

+219
-38
lines changed

10 files changed

+219
-38
lines changed

MainDemo.Wpf/Expander.xaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
<StackPanel>
2222
<Expander HorizontalAlignment="Stretch" Header="Expander Example 1a">
2323
<StackPanel Orientation="Vertical"
24-
TextBlock.Foreground="{DynamicResource MaterialDesignBody}"
25-
Margin="24,8,24,16">
24+
TextBlock.Foreground="{DynamicResource MaterialDesignBody}"
25+
Margin="24,8,24,16">
2626
<TextBlock Text="Your Content" />
2727
<TextBlock Opacity=".68" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
2828
TextWrapping="Wrap"/>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Linq;
4+
using System.Windows.Controls;
5+
using System.Windows.Data;
6+
using MaterialDesignThemes.Wpf.Converters;
7+
using Xunit;
8+
9+
namespace MaterialDesignThemes.Wpf.Tests.Converters
10+
{
11+
12+
public class ExpanderDirectinConverterTests
13+
{
14+
[Theory]
15+
[InlineData(null, null, null)]
16+
[InlineData(null, typeof(int), "1,2,3,4")]
17+
[InlineData(ExpandDirection.Left, null, "1,2,3,4")]
18+
[InlineData(ExpandDirection.Left, typeof(int), null)]
19+
[InlineData(ExpandDirection.Left, typeof(int), "")]
20+
[InlineData(ExpandDirection.Left, typeof(int), "1,2")]
21+
public void WhenValuesInvalid_ItDoesNothing(object value, Type targetType, object parameter)
22+
{
23+
var converter = new ExpanderDirectionConverter();
24+
25+
Assert.Equal(Binding.DoNothing, converter.Convert(value, targetType, parameter, CultureInfo.CurrentUICulture));
26+
}
27+
28+
[Fact]
29+
public void WhenExpandDirectionInvalid_ItThrowsInvalidOperationException()
30+
{
31+
var converter = new ExpanderDirectionConverter();
32+
33+
var invalid = (ExpandDirection)(Enum.GetValues(typeof(ExpandDirection)).Cast<int>().Max() + 1);
34+
35+
Assert.ThrowsAny<InvalidOperationException>(
36+
() => converter.Convert(invalid, typeof(int), "1,2,3,4", CultureInfo.CurrentUICulture));
37+
}
38+
39+
[Theory]
40+
[InlineData(ExpandDirection.Left, "1,2,3,4", 1)]
41+
[InlineData(ExpandDirection.Up, "1,2,3,4", 2)]
42+
[InlineData(ExpandDirection.Right, "1,2,3,4", 3)]
43+
[InlineData(ExpandDirection.Down, "1,2,3,4", 4)]
44+
public void WhenValuesAreValid_ItParsesExpected(ExpandDirection direction, string parameter, int expected)
45+
{
46+
var converter = new ExpanderDirectionConverter();
47+
48+
Assert.Equal(expected, converter.Convert(direction, typeof(int), parameter, CultureInfo.CurrentUICulture));
49+
}
50+
51+
[Theory]
52+
[InlineData(ExpandDirection.Left, "1,2,3,4", "1")]
53+
[InlineData(ExpandDirection.Up, "1,2,3,4", "2")]
54+
[InlineData(ExpandDirection.Right, "1,2,3,4", "3")]
55+
[InlineData(ExpandDirection.Down, "1,2,3,4", "4")]
56+
public void WhenTargetTypeIsObject_ItReturnsStringValue(ExpandDirection direction, string parameter, string expected)
57+
{
58+
var converter = new ExpanderDirectionConverter();
59+
60+
Assert.Equal(expected, converter.Convert(direction, typeof(object), parameter, CultureInfo.CurrentUICulture));
61+
}
62+
}
63+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Globalization;
2+
using MaterialDesignThemes.Wpf.Converters;
3+
using Xunit;
4+
5+
namespace MaterialDesignThemes.Wpf.Tests.Converters
6+
{
7+
public class MathConverterTests
8+
{
9+
[Theory]
10+
[EnumData]
11+
public void EnumValues_AreAllHandled(MathOperation operation)
12+
{
13+
var converter = new MathConverter
14+
{
15+
Operation = operation
16+
};
17+
18+
Assert.True(converter.Convert(1.0, null, 1.0, CultureInfo.CurrentUICulture) is double);
19+
}
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Globalization;
2+
using MaterialDesignThemes.Wpf.Converters;
3+
using Xunit;
4+
5+
namespace MaterialDesignThemes.Wpf.Tests.Converters
6+
{
7+
public class MathMultipleConverterTests
8+
{
9+
[Theory]
10+
[EnumData]
11+
public void EnumValues_AreAllHandled(MathOperation operation)
12+
{
13+
var converter = new MathMultipleConverter
14+
{
15+
Operation = operation
16+
};
17+
18+
Assert.True(converter.Convert(new object[] { 1.0, 1.0 }, null, null, CultureInfo.CurrentUICulture) is double);
19+
}
20+
}
21+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
using Xunit.Sdk;
5+
6+
namespace MaterialDesignThemes.Wpf.Tests
7+
{
8+
public class EnumDataAttribute : DataAttribute
9+
{
10+
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
11+
{
12+
ParameterInfo[] parameters = testMethod.GetParameters();
13+
if (parameters.Length != 1 ||
14+
!parameters[0].ParameterType.IsEnum)
15+
{
16+
throw new Exception($"{testMethod.DeclaringType.FullName}.{testMethod.Name} must have a single enum parameter");
17+
}
18+
19+
return GetDataImplementation(parameters[0].ParameterType);
20+
21+
static IEnumerable<object[]> GetDataImplementation(Type parameterType)
22+
{
23+
foreach (object enumValue in Enum.GetValues(parameterType))
24+
{
25+
yield return new[] { enumValue };
26+
}
27+
}
28+
}
29+
}
30+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Globalization;
4+
using System.Windows.Controls;
5+
using System.Windows.Data;
6+
7+
namespace MaterialDesignThemes.Wpf.Converters
8+
{
9+
internal class ExpanderDirectionConverter : IValueConverter
10+
{
11+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
12+
{
13+
if (value is ExpandDirection direction &&
14+
targetType is { } &&
15+
parameter is string values &&
16+
values.Split(',') is { } directionValues &&
17+
directionValues.Length == 4)
18+
{
19+
int index = direction switch
20+
{
21+
ExpandDirection.Left => 0,
22+
ExpandDirection.Up => 1,
23+
ExpandDirection.Right => 2,
24+
ExpandDirection.Down => 3,
25+
_ => throw new InvalidOperationException()
26+
};
27+
var converter = TypeDescriptor.GetConverter(targetType);
28+
29+
return converter.CanConvertFrom(typeof(string)) ?
30+
converter.ConvertFromInvariantString(directionValues[index]) :
31+
directionValues[index];
32+
}
33+
return Binding.DoNothing;
34+
}
35+
36+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
37+
{
38+
throw new NotImplementedException();
39+
}
40+
}
41+
}

MaterialDesignThemes.Wpf/Converters/MathConverter.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@
44

55
namespace MaterialDesignThemes.Wpf.Converters
66
{
7-
public enum MathOperation
8-
{
9-
Add,
10-
Subtract,
11-
Multiply,
12-
Divide
13-
}
14-
157
public sealed class MathConverter : IValueConverter
168
{
179
public MathOperation Operation { get; set; }
@@ -32,6 +24,8 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
3224
return value1 * value2;
3325
case MathOperation.Subtract:
3426
return value1 - value2;
27+
case MathOperation.Pow:
28+
return Math.Pow(value1, value2);
3529
default:
3630
return Binding.DoNothing;
3731
}

MaterialDesignThemes.Wpf/Converters/MathMultipleConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public object Convert(object[] value, Type targetType, object parameter, Culture
2626
return value1 * value2;
2727
case MathOperation.Subtract:
2828
return value1 - value2;
29+
case MathOperation.Pow:
30+
return Math.Pow(value1, value2);
2931
}
3032

3133
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace MaterialDesignThemes.Wpf.Converters
2+
{
3+
public enum MathOperation
4+
{
5+
Add,
6+
Subtract,
7+
Multiply,
8+
Divide,
9+
Pow
10+
}
11+
}

MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.Expander.xaml

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@
207207
Content="{TemplateBinding Content}"
208208
ContentTemplate="{TemplateBinding ContentTemplate}"
209209
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"
210-
ContentStringFormat="{TemplateBinding ContentStringFormat}"
210+
ContentStringFormat="{TemplateBinding ContentStringFormat}"
211211
Margin="0,0,16,0" />
212212
<ToggleButton IsChecked="{Binding Path=IsChecked, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
213213
VerticalAlignment="Center"
@@ -239,6 +239,10 @@
239239
<Setter Property="Template">
240240
<Setter.Value>
241241
<ControlTemplate TargetType="{x:Type Expander}">
242+
<ControlTemplate.Resources>
243+
<converters:ExpanderDirectionConverter x:Key="ExpanderDirectionConverter" />
244+
<converters:MathMultipleConverter x:Key="PowConverter" Operation="Pow" />
245+
</ControlTemplate.Resources>
242246
<Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
243247
<VisualStateManager.VisualStateGroups>
244248
<VisualStateGroup x:Name="ExpansionStates">
@@ -300,63 +304,57 @@
300304
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
301305
ContentStringFormat="{TemplateBinding HeaderStringFormat}"/>
302306
<Border Name="ContentSite">
303-
<Grid Name="ContentPanel"
304-
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
307+
<Grid Name="ContentPanel"
305308
Margin="{TemplateBinding Padding}"
309+
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
306310
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
311+
<Grid.LayoutTransform>
312+
<ScaleTransform CenterX="{Binding ExpandDirection, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ExpanderDirectionConverter}, ConverterParameter='0,0.5,1,0.5'}"
313+
CenterY="{Binding ExpandDirection, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ExpanderDirectionConverter}, ConverterParameter='0.5,1,0.5,0'}">
314+
<ScaleTransform.ScaleX>
315+
<MultiBinding Converter="{StaticResource PowConverter}">
316+
<Binding Path="Opacity" ElementName="PART_Content" />
317+
<Binding Path="ExpandDirection" RelativeSource="{RelativeSource TemplatedParent}" Converter="{StaticResource ExpanderDirectionConverter}" ConverterParameter="1,0,1,0" />
318+
</MultiBinding>
319+
</ScaleTransform.ScaleX>
320+
<ScaleTransform.ScaleY>
321+
<MultiBinding Converter="{StaticResource PowConverter}">
322+
<Binding Path="Opacity" ElementName="PART_Content" />
323+
<Binding Path="ExpandDirection" RelativeSource="{RelativeSource TemplatedParent}" Converter="{StaticResource ExpanderDirectionConverter}" ConverterParameter="0,1,0,1" />
324+
</MultiBinding>
325+
</ScaleTransform.ScaleY>
326+
</ScaleTransform>
327+
</Grid.LayoutTransform>
307328
<ContentPresenter Name="PART_Content" Focusable="False" Opacity="0"
308329
ContentTemplate="{TemplateBinding ContentTemplate}"
309330
ContentStringFormat="{TemplateBinding ContentStringFormat}"
310-
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"/>
331+
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}" />
332+
311333
</Grid>
312334
</Border>
313335
</DockPanel>
314336
</Border>
315337
<ControlTemplate.Triggers>
316338
<Trigger Property="ExpandDirection" Value="Right">
317339
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Left"/>
318-
<Setter Property="LayoutTransform" TargetName="ContentPanel">
319-
<Setter.Value>
320-
<ScaleTransform CenterX="1" CenterY="0.5"
321-
ScaleX="{Binding Opacity, ElementName=PART_Content}" />
322-
</Setter.Value>
323-
</Setter>
324340
<Setter Property="Height" TargetName="ContentPanel" Value="Auto"/>
325341
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource MaterialDesignExpanderRightHeaderStyle}"/>
326342
</Trigger>
327343

328344
<Trigger Property="ExpandDirection" Value="Left">
329345
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Right"/>
330-
<Setter Property="LayoutTransform" TargetName="ContentPanel">
331-
<Setter.Value>
332-
<ScaleTransform CenterX="0" CenterY="0.5"
333-
ScaleX="{Binding Opacity, ElementName=PART_Content}" />
334-
</Setter.Value>
335-
</Setter>
336346
<Setter Property="Height" TargetName="ContentPanel" Value="Auto"/>
337347
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource MaterialDesignExpanderLeftHeaderStyle}"/>
338348
</Trigger>
339349

340350
<Trigger Property="ExpandDirection" Value="Up">
341351
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Bottom"/>
342-
<Setter Property="LayoutTransform" TargetName="ContentPanel">
343-
<Setter.Value>
344-
<ScaleTransform CenterX="0.5" CenterY="1"
345-
ScaleY="{Binding Opacity, ElementName=PART_Content}" />
346-
</Setter.Value>
347-
</Setter>
348352
<Setter Property="Width" TargetName="ContentPanel" Value="Auto"/>
349353
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource MaterialDesignExpanderUpHeaderStyle}"/>
350354
</Trigger>
351355

352356
<Trigger Property="ExpandDirection" Value="Down">
353357
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Top"/>
354-
<Setter Property="LayoutTransform" TargetName="ContentPanel">
355-
<Setter.Value>
356-
<ScaleTransform CenterX="0.5" CenterY="0"
357-
ScaleY="{Binding Opacity, ElementName=PART_Content}" />
358-
</Setter.Value>
359-
</Setter>
360358
<Setter Property="Width" TargetName="ContentPanel" Value="Auto"/>
361359
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource MaterialDesignExpanderDownHeaderStyle}"/>
362360
</Trigger>

0 commit comments

Comments
 (0)