I saw John Myczek's solution, and its comments about Compatibility to ComboBox
and PasswordBox
, so I improved John Myczek's solution, and here it is:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
///
/// Class that provides the Watermark attached property
///
public static class WatermarkService
{
///
/// Watermark Attached Dependency Property
///
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.RegisterAttached(
"Watermark",
typeof(object),
typeof(WatermarkService),
new FrameworkPropertyMetadata((object)null, new PropertyChangedCallback(OnWatermarkChanged)));
#region Private Fields
///
/// Dictionary of ItemsControls
///
private static readonly Dictionary itemsControls = new Dictionary();
#endregion
///
/// Gets the Watermark property. This dependency property indicates the watermark for the control.
///
/// to get the property from
/// The value of the Watermark property
public static object GetWatermark(DependencyObject d)
{
return (object)d.GetValue(WatermarkProperty);
}
///
/// Sets the Watermark property. This dependency property indicates the watermark for the control.
///
/// to set the property on
/// value of the property
public static void SetWatermark(DependencyObject d, object value)
{
d.SetValue(WatermarkProperty, value);
}
///
/// Handles changes to the Watermark property.
///
/// that fired the event
/// A that contains the event data.
private static void OnWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Control control = (Control)d;
control.Loaded += Control_Loaded;
if (d is TextBox || d is PasswordBox)
{
control.GotKeyboardFocus += Control_GotKeyboardFocus;
control.LostKeyboardFocus += Control_Loaded;
}
else if (d is ComboBox)
{
control.GotKeyboardFocus += Control_GotKeyboardFocus;
control.LostKeyboardFocus += Control_Loaded;
(d as ComboBox).SelectionChanged += new SelectionChangedEventHandler(SelectionChanged);
}
else if (d is ItemsControl)
{
ItemsControl i = (ItemsControl)d;
// for Items property
i.ItemContainerGenerator.ItemsChanged += ItemsChanged;
itemsControls.Add(i.ItemContainerGenerator, i);
// for ItemsSource property
DependencyPropertyDescriptor prop = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, i.GetType());
prop.AddValueChanged(i, ItemsSourceChanged);
}
}
///
/// Event handler for the selection changed event
///
/// The source of the event.
/// A that contains the event data.
private static void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Control control = (Control)sender;
if (ShouldShowWatermark(control))
{
ShowWatermark(control);
}
else
{
RemoveWatermark(control);
}
}
#region Event Handlers
///
/// Handle the GotFocus event on the control
///
/// The source of the event.
/// A that contains the event data.
private static void Control_GotKeyboardFocus(object sender, RoutedEventArgs e)
{
Control c = (Control)sender;
if (ShouldShowWatermark(c))
{
RemoveWatermark(c);
}
}
///
/// Handle the Loaded and LostFocus event on the control
///
/// The source of the event.
/// A that contains the event data.
private static void Control_Loaded(object sender, RoutedEventArgs e)
{
Control control = (Control)sender;
if (ShouldShowWatermark(control))
{
ShowWatermark(control);
}
}
///
/// Event handler for the items source changed event
///
/// The source of the event.
/// A that contains the event data.
private static void ItemsSourceChanged(object sender, EventArgs e)
{
ItemsControl c = (ItemsControl)sender;
if (c.ItemsSource != null)
{
if (ShouldShowWatermark(c))
{
ShowWatermark(c);
}
else
{
RemoveWatermark(c);
}
}
else
{
ShowWatermark(c);
}
}
///
/// Event handler for the items changed event
///
/// The source of the event.
/// A that contains the event data.
private static void ItemsChanged(object sender, ItemsChangedEventArgs e)
{
ItemsControl control;
if (itemsControls.TryGetValue(sender, out control))
{
if (ShouldShowWatermark(control))
{
ShowWatermark(control);
}
else
{
RemoveWatermark(control);
}
}
}
#endregion
#region Helper Methods
///
/// Remove the watermark from the specified element
///
/// Element to remove the watermark from
private static void RemoveWatermark(UIElement control)
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(control);
// layer could be null if control is no longer in the visual tree
if (layer != null)
{
Adorner[] adorners = layer.GetAdorners(control);
if (adorners == null)
{
return;
}
foreach (Adorner adorner in adorners)
{
if (adorner is WatermarkAdorner)
{
adorner.Visibility = Visibility.Hidden;
layer.Remove(adorner);
}
}
}
}
///
/// Show the watermark on the specified control
///
/// Control to show the watermark on
private static void ShowWatermark(Control control)
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(control);
// layer could be null if control is no longer in the visual tree
if (layer != null)
{
layer.Add(new WatermarkAdorner(control, GetWatermark(control)));
}
}
///
/// Indicates whether or not the watermark should be shown on the specified control
///
/// to test
/// true if the watermark should be shown; false otherwise
private static bool ShouldShowWatermark(Control c)
{
if (c is ComboBox)
{
return (c as ComboBox).SelectedItem == null;
//return (c as ComboBox).Text == string.Empty;
}
else if (c is TextBoxBase)
{
return (c as TextBox).Text == string.Empty;
}
else if (c is PasswordBox)
{
return (c as PasswordBox).Password == string.Empty;
}
else if (c is ItemsControl)
{
return (c as ItemsControl).Items.Count == 0;
}
else
{
return false;
}
}
#endregion
}
Now, a ComboBox
can be also Editable
, and PasswordBox
can add a watermark too.
Don't forget to use JoanComasFdz's comment above to solve the margin problem.
And, of course, All the credit goes to John Myczek.