Skip to content

Commit

Permalink
Feature/auto watermark (#2722)
Browse files Browse the repository at this point in the history
* [Introducing] AutoWatermark #2712

This commit introduces the possibility to retrieve the automark text directly from the property that is used for data binding

* Add property mapping for controls

* Add AttachedPropertyBrowsableForType-Attribute

* Add WatermarkAttribute

* Add NET4_5 compiler switch

* Add example to DateExamples.xaml

* Add code to support auto-watermark on <NET4.5 too

* Use DisplayAttribute instead of WatermarkAttribute

* Use GetDescription instead of Description to support localizable strings

* Revert XAML-Style changes introduced by mistake

* Code cleanup

* Add Dependency Property documentation

* Subscribe to event only in case of Autowatermark is true.
If control is already loaded, directly call the method for retrieving the information.
If deactivated unsubscribe from event.

* [Fix] Issue with DOT-binding
e.g. "{Binding Property.OtherProperty}"

* [Fix] Issue when using Array Indexer as binding path
e.g. PropertyWithCollection[0]. This is not supported

* [Fix] Issue whith using . Paths in NET4

* Allow binding to element of collection

* Allow Write-Only Properties too.

* Extracted Methods for better understanding

* Adding XML-Documentation

* Use Display.Prompt intead of Display.Description
  • Loading branch information
Michael Mairegger authored and punker76 committed Nov 19, 2016
1 parent 703b830 commit 2056eb3
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Data" />
<Reference Include="System.Windows.Interactivity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Data" />
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
Margin="0, 10, 0, 0"
HorizontalAlignment="Center"
IsEnabled="False" />
<DatePicker Width="200"
Margin="0, 10, 0, 0"
HorizontalAlignment="Center"
SelectedDate="{Binding DatePickerDate}"
Controls:TextBoxHelper.AutoWatermark="True" />
<DatePicker Width="300"
Margin="0, 10, 0, 0"
HorizontalAlignment="Center"
Expand Down Expand Up @@ -177,7 +182,7 @@
<i:Interaction.Behaviors>
<behaviors:DateTimeNowBehavior />
</i:Interaction.Behaviors>
</Controls:DateTimePicker>
</Controls:DateTimePicker>
<Controls:TimePicker SelectedTime="{Binding Path=SelectedTime, ElementName=DateTimePicker}"
Culture="{Binding Path=SelectedItem, ElementName=DateTimePickerCulture, TargetNullValue={x:Static globalization:CultureInfo.CurrentCulture}}"
HandVisibility="{Binding Path=SelectedItem, ElementName=DateTimePickerHandVisibility, Mode=TwoWay}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,5 +342,4 @@
</Grid>
</AdornerDecorator>

</UserControl>

</UserControl>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ComponentModel;
using System.Globalization;
Expand Down Expand Up @@ -125,6 +126,8 @@ public int? IntegerGreater10Property
}

DateTime? _datePickerDate;

[Display(Prompt = "Auto resolved Watermark")]
public DateTime? DatePickerDate
{
get { return this._datePickerDate; }
Expand Down Expand Up @@ -254,6 +257,7 @@ public string this[string columnName]
}
}

[Description("Test-Property")]
public string Error { get { return string.Empty; } }

private ICommand singleCloseTabCommand;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.ComponentModel;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Data;
using JetBrains.Annotations;

namespace MahApps.Metro.Controls
{
using System.ComponentModel;

/// <summary>
/// A helper class that provides various attached properties for the TextBox control.
/// </summary>
Expand Down Expand Up @@ -50,6 +54,24 @@ public class TextBoxHelper

public static readonly DependencyProperty IsSpellCheckContextMenuEnabledProperty = DependencyProperty.RegisterAttached("IsSpellCheckContextMenuEnabled", typeof(bool), typeof(TextBoxHelper), new FrameworkPropertyMetadata(false, UseSpellCheckContextMenuChanged));

/// <summary>
/// This property can be used to retrieve the watermark using the <see cref="DisplayAttribute"/> of bound property.
/// </summary>
/// <remarks>
/// Setting this property to true will uses reflection.
/// </remarks>
public static readonly DependencyProperty AutoWatermarkProperty = DependencyProperty.RegisterAttached("AutoWatermark", typeof(bool), typeof(TextBoxHelper), new PropertyMetadata(default(bool), OnAutoWatermarkChanged));

private static readonly Dictionary<Type, DependencyProperty> AutoWatermarkPropertyMapping = new Dictionary<Type, DependencyProperty>
{
{ typeof(TextBox), TextBox.TextProperty },
{ typeof(ComboBox), Selector.SelectedItemProperty },
{ typeof(NumericUpDown), NumericUpDown.ValueProperty },
{ typeof(DatePicker), DatePicker.SelectedDateProperty },
{ typeof(TimePicker), TimePickerBase.SelectedTimeProperty },
{ typeof(DateTimePicker), DateTimePicker.SelectedDateProperty }
};

/// <summary>
/// Indicates if a TextBox or RichTextBox should use SpellCheck context menu
/// </summary>
Expand All @@ -66,6 +88,153 @@ public static void SetIsSpellCheckContextMenuEnabled(UIElement element, bool val
element.SetValue(IsSpellCheckContextMenuEnabledProperty, value);
}

[Category(AppName.MahApps)]
[AttachedPropertyBrowsableForType(typeof(TextBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(TimePickerBase))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
public static bool GetAutoWatermark(DependencyObject element)
{
return (bool)element.GetValue(AutoWatermarkProperty);
}

/// <summary>
/// Indicates if the watermark is automatically retrieved by using the <see cref="DisplayAttribute"/> of the bound property.
/// </summary>
/// <remarks>This attached property uses reflection; thus it might reduce the performance of the application.
/// The auto-watermak does work for the following controls:
/// In the following case no custom watermark is shown
/// <list type="bullet">
/// <item>There is no binding</item>
/// <item>Binding path errors</item>
/// <item>Binding to a element of a collection without using a property of that element <c>Binding Path=Collection[0]</c> use: <c>Binding Path=Collection[0].SubProperty</c></item>
/// <item>The bound property does not have a <see cref="DisplayAttribute"/></item>
/// </list></remarks>
public static void SetAutoWatermark(DependencyObject element, bool value)
{
element.SetValue(AutoWatermarkProperty, value);
}

private static void OnAutoWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = d as FrameworkElement;
bool? enable = e.NewValue as bool?;
if (element != null)
{
if (enable.GetValueOrDefault())
{
if (element.IsLoaded)
{
OnControlWithAutoWatermarkSupportLoaded(element, new RoutedEventArgs());
}
else
{
element.Loaded += OnControlWithAutoWatermarkSupportLoaded;
}
}
else
{
element.Loaded -= OnControlWithAutoWatermarkSupportLoaded;
}
}
}

private static void OnControlWithAutoWatermarkSupportLoaded(object o, RoutedEventArgs routedEventArgs)
{
FrameworkElement obj = (FrameworkElement)o;
obj.Loaded -= OnControlWithAutoWatermarkSupportLoaded;

DependencyProperty dependencyProperty;

if (!AutoWatermarkPropertyMapping.TryGetValue(obj.GetType(), out dependencyProperty))
{
throw new NotSupportedException($"{nameof(AutoWatermarkProperty)} is not supported for {obj.GetType()}");
}

var resolvedProperty = ResolvePropertyFromBindingExpression(obj.GetBindingExpression(dependencyProperty));
if (resolvedProperty != null)
{
#if NET4
var attribute = resolvedProperty.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute;
#else
var attribute = resolvedProperty.GetCustomAttribute<DisplayAttribute>();
#endif
if (attribute != null)
{
obj.SetValue(WatermarkProperty, attribute.GetPrompt());
}
}
}

[CanBeNull]
private static PropertyInfo ResolvePropertyFromBindingExpression(BindingExpression bindingExpression)
{
if (bindingExpression != null)
{
if (bindingExpression.Status == BindingStatus.PathError)
{
return null;
}
#if NET4
var propertyName = bindingExpression.ParentBinding.Path.Path;
if (propertyName != null && propertyName.Contains('.'))
{
propertyName = propertyName.Substring(propertyName.LastIndexOf('.') + 1);
}
#else
var propertyName = bindingExpression.ResolvedSourcePropertyName;
#endif
if (!string.IsNullOrEmpty(propertyName))
{
#if NET4
var resolvedType = ResolveBinding(bindingExpression.DataItem.GetType(), bindingExpression.ParentBinding.Path.Path.Split('.'));
#elif NET4_5
var resolvedType = bindingExpression.ResolvedSource?.GetType();
#endif
if (resolvedType != null)
{
return resolvedType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
}
}

}
return null;
}

#if NET4
[CanBeNull]
private static Type ResolveBinding(Type type, string[] paths)
{
if (type != null && paths != null)
{
if (paths.Length == 1)
{
return type;
}
var propertyName = paths[0];

if (propertyName.Contains('[') && propertyName.EndsWith("]"))
{
var indexOf = propertyName.IndexOf('[');
propertyName = propertyName.Substring(0, indexOf);
}

var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance)?.PropertyType;
if (property != null)
{
var remainingPath = paths.Skip(1).ToArray();
if (property.IsArray)
{
return ResolveBinding(property.GetElementType(), remainingPath);
}
return ResolveBinding(property, remainingPath);
}
}
return null;
}
#endif

private static void UseSpellCheckContextMenuChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tb = d as TextBoxBase;
Expand Down Expand Up @@ -212,7 +381,7 @@ public static void SetUseFloatingWatermark(DependencyObject obj, bool value)
{
obj.SetValue(UseFloatingWatermarkProperty, value);
}

/// <summary>
/// Gets if the attached TextBox has text.
/// </summary>
Expand Down Expand Up @@ -310,15 +479,15 @@ private static void OnIsMonitoringChanged(DependencyObject d, DependencyProperty
}
}
}

private static void SetTextLength<TDependencyObject>(TDependencyObject sender, Func<TDependencyObject, int> funcTextLength) where TDependencyObject : DependencyObject
{
if (sender != null)
{
var value = funcTextLength(sender);
sender.SetValue(TextLengthProperty, value);
sender.SetValue(HasTextProperty, value >= 1);
}
}
}

private static void TextChanged(object sender, RoutedEventArgs e)
Expand Down
1 change: 1 addition & 0 deletions src/MahApps.Metro/MahApps.Metro/MahApps.Metro.NET40.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Management" />
<Reference Include="System.Windows.Interactivity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
Expand Down
1 change: 1 addition & 0 deletions src/MahApps.Metro/MahApps.Metro/MahApps.Metro.NET45.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Management" />
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
Expand Down

0 comments on commit 2056eb3

Please sign in to comment.