Skip to content

Commit 4c1d73d

Browse files
committed
Merge branch 'l1pton17-ValidationErrorTemplate'
2 parents 70bdc25 + ce40dbf commit 4c1d73d

8 files changed

+240
-14
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Windows;
3+
using System.Windows.Controls;
4+
using System.Windows.Controls.Primitives;
5+
using System.Windows.Input;
6+
7+
namespace MaterialDesignThemes.Wpf
8+
{
9+
/// <summary>
10+
/// Customised popup for validation purposes.
11+
/// </summary>
12+
/// <remarks>
13+
/// Originally taken from MahApps.
14+
/// </remarks>
15+
public class CustomValidationPopup : Popup
16+
{
17+
private Window _hostWindow;
18+
19+
public CustomValidationPopup()
20+
{
21+
Loaded += OnLoaded;
22+
Unloaded += OnUnloaded;
23+
}
24+
25+
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
26+
{
27+
IsOpen = false;
28+
}
29+
30+
private void OnLoaded(object sender, RoutedEventArgs e)
31+
{
32+
var target = PlacementTarget as FrameworkElement;
33+
if (target == null) return;
34+
35+
target.SizeChanged += HostWindowSizeOrLocationChanged;
36+
37+
_hostWindow = Window.GetWindow(target);
38+
if (_hostWindow == null) return;
39+
40+
_hostWindow.LocationChanged += HostWindowSizeOrLocationChanged;
41+
_hostWindow.SizeChanged += HostWindowSizeOrLocationChanged;
42+
43+
_hostWindow.StateChanged += HostWindowOnStateChanged;
44+
}
45+
46+
private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs)
47+
{
48+
var target = PlacementTarget as FrameworkElement;
49+
if (target != null)
50+
target.SizeChanged -= HostWindowSizeOrLocationChanged;
51+
52+
if (_hostWindow == null) return;
53+
54+
_hostWindow.LocationChanged -= HostWindowSizeOrLocationChanged;
55+
_hostWindow.SizeChanged -= HostWindowSizeOrLocationChanged;
56+
_hostWindow.StateChanged -= HostWindowOnStateChanged;
57+
}
58+
59+
private void HostWindowOnStateChanged(object sender, EventArgs e)
60+
{
61+
if (_hostWindow == null || _hostWindow.WindowState == WindowState.Minimized) return;
62+
63+
var target = PlacementTarget as FrameworkElement;
64+
var holder = target?.DataContext as AdornedElementPlaceholder;
65+
66+
if (holder?.AdornedElement == null) return;
67+
68+
var errorTemplate = holder.AdornedElement.GetValue(Validation.ErrorTemplateProperty);
69+
holder.AdornedElement.SetValue(Validation.ErrorTemplateProperty, null);
70+
holder.AdornedElement.SetValue(Validation.ErrorTemplateProperty, errorTemplate);
71+
}
72+
73+
private void HostWindowSizeOrLocationChanged(object sender, EventArgs e)
74+
{
75+
var offset = HorizontalOffset;
76+
// "bump" the offset to cause the popup to reposition itself on its own
77+
HorizontalOffset = offset + 1;
78+
HorizontalOffset = offset;
79+
}
80+
}
81+
}

MaterialDesignThemes.Wpf/MaterialDesignThemes.Wpf.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@
192192
<SubType>Designer</SubType>
193193
<Generator>MSBuild:Compile</Generator>
194194
</Page>
195+
<Page Include="Themes\MaterialDesignTheme.ValidationErrorTemplate.xaml">
196+
<SubType>Designer</SubType>
197+
<Generator>MSBuild:Compile</Generator>
198+
</Page>
195199
</ItemGroup>
196200
<ItemGroup>
197201
<Compile Include="Card.cs" />
@@ -215,6 +219,7 @@
215219
<Compile Include="Converters\NotZeroToVisibilityConverter.cs" />
216220
<Compile Include="Converters\SizeToRectConverter.cs" />
217221
<Compile Include="CustomPopupPlacementCallbackHelper.cs" />
222+
<Compile Include="CustomValidationPopup.cs" />
218223
<Compile Include="DataGridAssist.cs" />
219224
<Compile Include="DateTimeEx.cs" />
220225
<Compile Include="ListSortDirectionIndicator.cs" />
@@ -248,6 +253,7 @@
248253
<Compile Include="ToolTipAssist.cs" />
249254
<Compile Include="RippleAssist.cs" />
250255
<Compile Include="Ripple.cs" />
256+
<Compile Include="ValidationAssist.cs" />
251257
<EmbeddedResource Include="Properties\Resources.resx">
252258
<Generator>ResXFileCodeGenerator</Generator>
253259
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
@@ -282,6 +288,7 @@
282288
<Name>MaterialDesignColors.Wpf</Name>
283289
</ProjectReference>
284290
</ItemGroup>
291+
<ItemGroup />
285292
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
286293
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
287294
Other similar extension points exist, see Microsoft.Common.targets.

MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.Dark.xaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
<!-- plan is to add all of the light/dark specific brushes, quite granular at first. can then consolodate any repition later on if necessary -->
66

7+
<Color x:Key="ValidationErrorColor">#f44336</Color>
8+
<SolidColorBrush x:Key="ValidationErrorBrush" Color="{StaticResource ValidationErrorColor}"/>
9+
710
<SolidColorBrush x:Key="MaterialDesignBackground" Color="#FF000000"/>
811
<SolidColorBrush x:Key="MaterialDesignPaper" Color="#FF37474f"/>
912
<SolidColorBrush x:Key="MaterialDesignBody" Color="#DDFFFFFF"/>

MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.Defaults.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf">
44

55
<!-- use this resource dictionary to set up the most common themese for standard controls -->
6-
6+
77
<ResourceDictionary.MergedDictionaries>
8+
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ValidationErrorTemplate.xaml" />
89
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Shadows.xaml" />
910
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.button.xaml" />
1011
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.calendar.xaml" />

MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.Light.xaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
22
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
3-
3+
44
<!-- plan is to add all of the light/dark specific brushes, quite granular at first. can then consolodate any repition later on if necessary -->
5+
<Color x:Key="ValidationErrorColor">#f44336</Color>
6+
<SolidColorBrush x:Key="ValidationErrorBrush" Color="{StaticResource ValidationErrorColor}"/>
57

68
<SolidColorBrush x:Key="MaterialDesignBackground" Color="#FFFFFFFF"/>
79
<SolidColorBrush x:Key="MaterialDesignPaper" Color="#FFfafafa"/>

MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.TextBox.xaml

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf"
44
xmlns:converters="clr-namespace:MaterialDesignThemes.Wpf.Converters">
55

6+
<ResourceDictionary.MergedDictionaries>
7+
<ResourceDictionary Source="MaterialDesignTheme.ValidationErrorTemplate.xaml" />
8+
</ResourceDictionary.MergedDictionaries>
9+
610
<converters:TextFieldHintVisibilityConverter x:Key="TextFieldHintVisibilityConverter" />
711

812
<Style x:Key="MaterialDesignTextBox" TargetType="{x:Type TextBox}">
@@ -19,16 +23,7 @@
1923
<Setter Property="AllowDrop" Value="true"/>
2024
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
2125
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
22-
<Setter Property="Validation.ErrorTemplate">
23-
<Setter.Value>
24-
<ControlTemplate>
25-
<StackPanel>
26-
<AdornedElementPlaceholder />
27-
<TextBlock FontSize="10" Foreground="#f44336" Text="{Binding Path=[0].ErrorContent}" />
28-
</StackPanel>
29-
</ControlTemplate>
30-
</Setter.Value>
31-
</Setter>
26+
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource MaterialDesignValidationErrorTemplate}"/>
3227
<Setter Property="Template">
3328
<Setter.Value>
3429
<ControlTemplate TargetType="{x:Type TextBox}">
@@ -58,7 +53,7 @@
5853
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryHueMidBrush}"/>
5954
</Trigger>
6055
<Trigger Property="Validation.HasError" Value="true">
61-
<Setter Property="BorderBrush" Value="#f44336"/>
56+
<Setter Property="BorderBrush" Value="{DynamicResource ValidationErrorBrush}"/>
6257
</Trigger>
6358
</ControlTemplate.Triggers>
6459
</ControlTemplate>
@@ -164,7 +159,7 @@
164159
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryHueMidBrush}"/>
165160
</Trigger>
166161
<Trigger Property="Validation.HasError" Value="true">
167-
<Setter Property="BorderBrush" Value="#f44336"/>
162+
<Setter Property="BorderBrush" Value="{DynamicResource ValidationErrorBrush}"/>
168163
</Trigger>
169164
</ControlTemplate.Triggers>
170165
</ControlTemplate>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf">
4+
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
5+
6+
<ControlTemplate x:Key="MaterialDesignValidationErrorTemplate">
7+
<ControlTemplate.Resources>
8+
<DataTemplate DataType="{x:Type ValidationError}">
9+
<TextBlock Foreground="{DynamicResource ValidationErrorBrush}"
10+
FontSize="10"
11+
MaxWidth="250"
12+
Margin="2"
13+
TextWrapping="Wrap"
14+
Text="{Binding ErrorContent}"
15+
UseLayoutRounding="false" />
16+
</DataTemplate>
17+
</ControlTemplate.Resources>
18+
<StackPanel>
19+
<AdornedElementPlaceholder Name="Placeholder" />
20+
<Border Name="DefaultErrorViewer"
21+
Visibility="Collapsed"
22+
Background="{DynamicResource MaterialDesignPaper}">
23+
<ContentPresenter Content="{Binding CurrentItem}" />
24+
</Border>
25+
26+
<wpf:CustomValidationPopup x:Name="ValidationPopup"
27+
IsOpen="False"
28+
Placement="Bottom" PlacementTarget="{Binding ElementName=Placeholder}"
29+
AllowsTransparency="True">
30+
<Border Background="{DynamicResource MaterialDesignPaper}">
31+
<ContentPresenter Content="{Binding CurrentItem}" />
32+
</Border>
33+
</wpf:CustomValidationPopup>
34+
</StackPanel>
35+
<ControlTemplate.Triggers>
36+
<MultiDataTrigger>
37+
<MultiDataTrigger.Conditions>
38+
<Condition Binding="{Binding ElementName=Placeholder, Path=AdornedElement.(wpf:ValidationAssist.OnlyShowOnFocus)}" Value="False"/>
39+
<Condition Binding="{Binding ElementName=Placeholder, Path=AdornedElement.(wpf:ValidationAssist.UsePopup)}" Value="True"/>
40+
</MultiDataTrigger.Conditions>
41+
<MultiDataTrigger.Setters>
42+
<Setter TargetName="ValidationPopup" Property="IsOpen" Value="True"/>
43+
</MultiDataTrigger.Setters>
44+
</MultiDataTrigger>
45+
46+
<MultiDataTrigger>
47+
<MultiDataTrigger.Conditions>
48+
<Condition Binding="{Binding ElementName=Placeholder, Path=AdornedElement.(wpf:ValidationAssist.OnlyShowOnFocus)}" Value="False"/>
49+
<Condition Binding="{Binding ElementName=Placeholder, Path=AdornedElement.(wpf:ValidationAssist.UsePopup)}" Value="False"/>
50+
</MultiDataTrigger.Conditions>
51+
<MultiDataTrigger.Setters>
52+
<Setter TargetName="DefaultErrorViewer" Property="Visibility" Value="Visible"/>
53+
</MultiDataTrigger.Setters>
54+
</MultiDataTrigger>
55+
56+
<MultiDataTrigger>
57+
<MultiDataTrigger.Conditions>
58+
<Condition Binding="{Binding ElementName=Placeholder, Path=AdornedElement.(wpf:ValidationAssist.OnlyShowOnFocus)}" Value="True"/>
59+
<Condition Binding="{Binding ElementName=Placeholder, Path=AdornedElement.(wpf:ValidationAssist.UsePopup)}" Value="True"/>
60+
</MultiDataTrigger.Conditions>
61+
<MultiDataTrigger.Setters>
62+
<Setter TargetName="ValidationPopup" Property="IsOpen"
63+
Value="{Binding ElementName=Placeholder, Path=AdornedElement.IsKeyboardFocusWithin, Mode=OneWay}"/>
64+
</MultiDataTrigger.Setters>
65+
</MultiDataTrigger>
66+
67+
<MultiDataTrigger>
68+
<MultiDataTrigger.Conditions>
69+
<Condition Binding="{Binding ElementName=Placeholder, Path=AdornedElement.(wpf:ValidationAssist.OnlyShowOnFocus)}" Value="True"/>
70+
<Condition Binding="{Binding ElementName=Placeholder, Path=AdornedElement.(wpf:ValidationAssist.UsePopup)}" Value="False"/>
71+
</MultiDataTrigger.Conditions>
72+
<MultiDataTrigger.Setters>
73+
<Setter TargetName="DefaultErrorViewer" Property="Visibility"
74+
Value="{Binding ElementName=Placeholder, Path=AdornedElement.IsKeyboardFocusWithin, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>
75+
</MultiDataTrigger.Setters>
76+
</MultiDataTrigger>
77+
</ControlTemplate.Triggers>
78+
</ControlTemplate>
79+
</ResourceDictionary>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using System.Windows;
7+
8+
namespace MaterialDesignThemes.Wpf
9+
{
10+
public static class ValidationAssist
11+
{
12+
#region ShowOnFocusProperty
13+
14+
/// <summary>
15+
/// The hint property
16+
/// </summary>
17+
public static readonly DependencyProperty OnlyShowOnFocusProperty = DependencyProperty.RegisterAttached(
18+
"OnlyShowOnFocus",
19+
typeof(bool),
20+
typeof(ValidationAssist),
21+
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
22+
23+
public static bool GetOnlyShowOnFocus(DependencyObject element)
24+
{
25+
return (bool)element.GetValue(OnlyShowOnFocusProperty);
26+
}
27+
28+
public static void SetOnlyShowOnFocus(DependencyObject element, bool value)
29+
{
30+
element.SetValue(OnlyShowOnFocusProperty, value);
31+
}
32+
33+
#endregion
34+
35+
#region UsePopupProperty
36+
37+
/// <summary>
38+
/// The hint property
39+
/// </summary>
40+
public static readonly DependencyProperty UsePopupProperty = DependencyProperty.RegisterAttached(
41+
"UsePopup",
42+
typeof(bool),
43+
typeof(ValidationAssist),
44+
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
45+
46+
public static bool GetUsePopup(DependencyObject element)
47+
{
48+
return (bool)element.GetValue(OnlyShowOnFocusProperty);
49+
}
50+
51+
public static void SetUsePopup(DependencyObject element, bool value)
52+
{
53+
element.SetValue(OnlyShowOnFocusProperty, value);
54+
}
55+
56+
#endregion
57+
}
58+
}

0 commit comments

Comments
 (0)