Skip to content

Commit 3f50f00

Browse files
Fix2658 (#2757)
* Added card styles and sample Default card style is now MaterialDesignElevatedCard and a new alternative style, MaterialDesignOutlineCard added. * First unit test for OutlinedCard added * More unit tests Also ensured the DoubleToCornerRadiusConverter returns a CornerRadius of 0 for negative values.
1 parent ceba17b commit 3f50f00

File tree

7 files changed

+224
-38
lines changed

7 files changed

+224
-38
lines changed

MainDemo.Wpf/Cards.xaml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,68 @@
8383
</materialDesign:Card>
8484
</smtx:XamlDisplay>
8585

86+
<smtx:XamlDisplay
87+
UniqueKey="cards_1b"
88+
Margin="4 4 0 16"
89+
VerticalContentAlignment="Top">
90+
<materialDesign:Card Width="200" Style="{StaticResource MaterialDesignOutlinedCard}">
91+
<Grid>
92+
<Grid.RowDefinitions>
93+
<RowDefinition Height="140" />
94+
<RowDefinition Height="*" />
95+
<RowDefinition Height="Auto" />
96+
</Grid.RowDefinitions>
97+
<Image
98+
Source="Resources/Chartridge046_small.jpg"
99+
Height="140"
100+
Width="196"
101+
Stretch="UniformToFill"/>
102+
<Button
103+
Grid.Row="0"
104+
Style="{StaticResource MaterialDesignFloatingActionMiniAccentButton}"
105+
HorizontalAlignment="Right"
106+
VerticalAlignment="Bottom"
107+
Margin="0 0 16 -20">
108+
<materialDesign:PackIcon Kind="Bike" />
109+
</Button>
110+
<StackPanel Grid.Row="1" Margin="8 24 8 0" >
111+
<TextBlock FontWeight="Bold">Outlined style</TextBlock>
112+
<TextBlock
113+
TextWrapping="Wrap"
114+
VerticalAlignment="Center"
115+
Text="Removes the drop shadow and flattens the look of the card."/>
116+
</StackPanel>
117+
<StackPanel
118+
HorizontalAlignment="Right"
119+
Grid.Row="2"
120+
Orientation="Horizontal"
121+
Margin="8">
122+
<Button
123+
Style="{StaticResource MaterialDesignToolButton}"
124+
Width="30" Padding="2 0 2 0"
125+
materialDesign:RippleAssist.IsCentered="True">
126+
<materialDesign:PackIcon Kind="ShareVariant" />
127+
</Button>
128+
<Button
129+
Style="{StaticResource MaterialDesignToolButton}"
130+
Width="30"
131+
Padding="2 0 2 0"
132+
materialDesign:RippleAssist.IsCentered="True">
133+
<materialDesign:PackIcon Kind="Heart" />
134+
</Button>
135+
<materialDesign:PopupBox
136+
Style="{StaticResource MaterialDesignToolPopupBox}"
137+
Padding="2 0 2 0">
138+
<StackPanel>
139+
<Button Content="More"/>
140+
<Button Content="Options"/>
141+
</StackPanel>
142+
</materialDesign:PopupBox>
143+
</StackPanel>
144+
</Grid>
145+
</materialDesign:Card>
146+
</smtx:XamlDisplay>
147+
86148
<smtx:XamlDisplay
87149
UniqueKey="cards_2"
88150
Margin="4 4 0 16"
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.Windows.Media;
2+
3+
namespace MaterialDesignThemes.UITests.WPF.Cards;
4+
5+
public class OutlinedCardTests : TestBase
6+
{
7+
public OutlinedCardTests(ITestOutputHelper output)
8+
: base(output)
9+
{ }
10+
11+
[Fact]
12+
public async Task OutlinedCard_UsesThemeColorForBorder()
13+
{
14+
await using var recorder = new TestRecorder(App);
15+
16+
//Arrange
17+
IVisualElement<Card> card = await LoadXaml<Card>(
18+
@"<materialDesign:Card Content=""Hello World"" Style=""{StaticResource MaterialDesignOutlinedCard}""/>");
19+
Color dividerColor = await GetThemeColor("MaterialDesignDivider");
20+
IVisualElement<Border> internalBorder = await card.GetElement<Border>();
21+
22+
//Act
23+
Color? internalBorderColor = await internalBorder.GetBorderBrushColor();
24+
25+
//Assert
26+
Assert.Equal(dividerColor, internalBorderColor);
27+
28+
recorder.Success();
29+
}
30+
31+
[Fact]
32+
public async Task OutlinedCard_UniformCornerRadiusApplied_AppliesCornerRadiusOnBorder()
33+
{
34+
await using var recorder = new TestRecorder(App);
35+
36+
//Arrange
37+
IVisualElement<Card> card = await LoadXaml<Card>(
38+
@"<materialDesign:Card Content=""Hello World"" Style=""{StaticResource MaterialDesignOutlinedCard}"" UniformCornerRadius=""5"" />");
39+
IVisualElement<Border> internalBorder = await card.GetElement<Border>();
40+
41+
//Act
42+
CornerRadius? internalBorderCornerRadius = await internalBorder.GetCornerRadius();
43+
44+
//Assert
45+
Assert.Equal(5, internalBorderCornerRadius.Value.TopLeft);
46+
Assert.Equal(5, internalBorderCornerRadius.Value.TopRight);
47+
Assert.Equal(5, internalBorderCornerRadius.Value.BottomRight);
48+
Assert.Equal(5, internalBorderCornerRadius.Value.BottomLeft);
49+
50+
recorder.Success();
51+
}
52+
}

MaterialDesignThemes.UITests/XamlTestMixins.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public static async Task InitialzeWithMaterialDesign(this IApp app,
2929
<ResourceDictionary.MergedDictionaries>
3030
<materialDesign:BundledTheme BaseTheme=""{baseTheme}"" PrimaryColor=""{primary}"" SecondaryColor=""{secondary}"" {colorAdjustString}/>
3131
32+
<ResourceDictionary Source = ""pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/Generic.xaml"" />
3233
<ResourceDictionary Source = ""pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml"" />
3334
</ResourceDictionary.MergedDictionaries>
3435
</ResourceDictionary>";
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.Globalization;
2+
using MaterialDesignThemes.Wpf.Converters;
3+
using Xunit;
4+
5+
namespace MaterialDesignThemes.Wpf.Tests.Converters;
6+
7+
public class DoubleToCornerRadiusConverterTests
8+
{
9+
[Theory]
10+
[InlineData(-0.16, 0.0)]
11+
[InlineData(0.16, 0.16)]
12+
[InlineData(5.0, 5.0)]
13+
public void AllCultureParseParameterCorrectly(object parameter, double expectedCornerRadius)
14+
{
15+
var converter = new DoubleToCornerRadiusConverter();
16+
foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures))
17+
{
18+
var cornerRadius = (CornerRadius?)converter.Convert(parameter, typeof(CornerRadius), parameter, culture);
19+
20+
Assert.Equal(expectedCornerRadius, cornerRadius.Value.TopLeft);
21+
Assert.Equal(expectedCornerRadius, cornerRadius.Value.TopRight);
22+
Assert.Equal(expectedCornerRadius, cornerRadius.Value.BottomRight);
23+
Assert.Equal(expectedCornerRadius, cornerRadius.Value.BottomLeft);
24+
}
25+
}
26+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Globalization;
2+
using System.Windows.Data;
3+
4+
namespace MaterialDesignThemes.Wpf.Converters;
5+
6+
internal class DoubleToCornerRadiusConverter : IValueConverter
7+
{
8+
public static readonly DoubleToCornerRadiusConverter Instance = new();
9+
10+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => new CornerRadius(Math.Max(0, (double)value));
11+
12+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
13+
}

MaterialDesignThemes.Wpf/Themes/Generic.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<!-- set up default styles for our custom Material Design in XAML Toolkit controls -->
3434
<Style TargetType="{x:Type local:Clock}" BasedOn="{StaticResource MaterialDesignClock}" />
3535
<Style TargetType="{x:Type local:Badged}" BasedOn="{StaticResource MaterialDesignBadge}" />
36+
<Style TargetType="{x:Type local:Card}" BasedOn="{StaticResource MaterialDesignElevatedCard}" />
3637
<Style TargetType="{x:Type local:PopupBox}" BasedOn="{StaticResource MaterialDesignPopupBox}" />
3738
<Style TargetType="{x:Type local:TimePicker}" BasedOn="{StaticResource MaterialDesignTimePicker}" />
3839

MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.Card.xaml

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,78 @@
77
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Shadows.xaml" />
88
</ResourceDictionary.MergedDictionaries>
99

10-
<ControlTemplate TargetType="{x:Type wpf:Card}" x:Key="CardTemplate">
11-
<ControlTemplate.Resources>
12-
<converters:ShadowEdgeConverter x:Key="ShadowEdgeConverter" />
13-
</ControlTemplate.Resources>
14-
<Grid Background="Transparent">
15-
<AdornerDecorator CacheMode="{Binding RelativeSource={RelativeSource Self}, Path=(wpf:ShadowAssist.CacheMode)}">
16-
<AdornerDecorator.OpacityMask>
17-
<MultiBinding Converter="{StaticResource ShadowEdgeConverter}">
18-
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="ActualWidth"/>
19-
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="ActualHeight"/>
20-
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:ShadowAssist.ShadowDepth)" />
21-
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:ShadowAssist.ShadowEdges)" />
22-
</MultiBinding>
23-
</AdornerDecorator.OpacityMask>
24-
<Border Effect="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(wpf:ShadowAssist.ShadowDepth), Converter={x:Static converters:ShadowConverter.Instance}}"
25-
CornerRadius="{TemplateBinding UniformCornerRadius}">
26-
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"
27-
x:Name="PART_ClipBorder"
28-
Clip="{TemplateBinding ContentClip}" />
29-
</Border>
30-
</AdornerDecorator>
31-
<ContentPresenter
32-
x:Name="ContentPresenter"
33-
Margin="{TemplateBinding Padding}"
34-
Content="{TemplateBinding ContentControl.Content}"
35-
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
36-
ContentTemplateSelector="{TemplateBinding ContentControl.ContentTemplateSelector}"
37-
ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}">
38-
</ContentPresenter>
39-
</Grid>
40-
<ControlTemplate.Triggers>
41-
<Trigger Property="ClipContent" Value="True">
42-
<Setter Property="Clip" TargetName="ContentPresenter" Value="{Binding ContentClip, RelativeSource={RelativeSource TemplatedParent}}" />
43-
</Trigger>
44-
</ControlTemplate.Triggers>
45-
</ControlTemplate>
46-
<Style TargetType="{x:Type wpf:Card}">
47-
<Setter Property="Template" Value="{StaticResource CardTemplate}" />
10+
<Style x:Key="MaterialDesignElevatedCard" TargetType="{x:Type wpf:Card}">
4811
<Setter Property="Background" Value="{DynamicResource MaterialDesignCardBackground}" />
4912
<Setter Property="wpf:ShadowAssist.ShadowDepth" Value="Depth2" />
5013
<Setter Property="Focusable" Value="False"/>
14+
<Setter Property="Template">
15+
<Setter.Value>
16+
<ControlTemplate TargetType="{x:Type wpf:Card}">
17+
<ControlTemplate.Resources>
18+
<converters:ShadowEdgeConverter x:Key="ShadowEdgeConverter" />
19+
</ControlTemplate.Resources>
20+
<Grid Background="Transparent">
21+
<AdornerDecorator CacheMode="{Binding RelativeSource={RelativeSource Self}, Path=(wpf:ShadowAssist.CacheMode)}">
22+
<AdornerDecorator.OpacityMask>
23+
<MultiBinding Converter="{StaticResource ShadowEdgeConverter}">
24+
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="ActualWidth"/>
25+
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="ActualHeight"/>
26+
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:ShadowAssist.ShadowDepth)" />
27+
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:ShadowAssist.ShadowEdges)" />
28+
</MultiBinding>
29+
</AdornerDecorator.OpacityMask>
30+
<Border Effect="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(wpf:ShadowAssist.ShadowDepth), Converter={x:Static converters:ShadowConverter.Instance}}"
31+
CornerRadius="{TemplateBinding UniformCornerRadius}">
32+
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"
33+
x:Name="PART_ClipBorder"
34+
Clip="{TemplateBinding ContentClip}" />
35+
</Border>
36+
</AdornerDecorator>
37+
<ContentPresenter x:Name="ContentPresenter"
38+
Margin="{TemplateBinding Padding}"
39+
Content="{TemplateBinding ContentControl.Content}"
40+
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
41+
ContentTemplateSelector="{TemplateBinding ContentControl.ContentTemplateSelector}"
42+
ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" />
43+
</Grid>
44+
<ControlTemplate.Triggers>
45+
<Trigger Property="ClipContent" Value="True">
46+
<Setter Property="Clip" TargetName="ContentPresenter" Value="{Binding ContentClip, RelativeSource={RelativeSource TemplatedParent}}" />
47+
</Trigger>
48+
</ControlTemplate.Triggers>
49+
</ControlTemplate>
50+
</Setter.Value>
51+
</Setter>
5152
</Style>
5253

54+
<Style x:Key="MaterialDesignOutlinedCard" TargetType="{x:Type wpf:Card}">
55+
<Setter Property="Background" Value="{DynamicResource MaterialDesignCardBackground}" />
56+
<Setter Property="Focusable" Value="False"/>
57+
<Setter Property="Template">
58+
<Setter.Value>
59+
<ControlTemplate TargetType="{x:Type wpf:Card}">
60+
<Grid Background="Transparent">
61+
<Border BorderThickness="1" CornerRadius="{TemplateBinding UniformCornerRadius, Converter={x:Static converters:DoubleToCornerRadiusConverter.Instance}}" BorderBrush="{DynamicResource MaterialDesignDivider}">
62+
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"
63+
x:Name="PART_ClipBorder"
64+
Clip="{TemplateBinding ContentClip}">
65+
<ContentPresenter x:Name="ContentPresenter"
66+
Margin="{TemplateBinding Padding}"
67+
Content="{TemplateBinding ContentControl.Content}"
68+
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
69+
ContentTemplateSelector="{TemplateBinding ContentControl.ContentTemplateSelector}"
70+
ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" />
71+
</Border>
72+
</Border>
73+
</Grid>
74+
<ControlTemplate.Triggers>
75+
<Trigger Property="ClipContent" Value="True">
76+
<Setter Property="Clip" TargetName="ContentPresenter" Value="{Binding ContentClip, RelativeSource={RelativeSource TemplatedParent}}" />
77+
</Trigger>
78+
</ControlTemplate.Triggers>
79+
</ControlTemplate>
80+
</Setter.Value>
81+
</Setter>
82+
</Style>
83+
5384
</ResourceDictionary>

0 commit comments

Comments
 (0)