Wednesday, October 20, 2010

Creating and Showing Smart Tooltip in WPF XBAP application

The Smart Tooltips are displayed when user hovers the cursor over an item, without clicking it, and a tooltip may appear. It may take a second or two to display the tooltip, but when it does appear, it usually is a small box with a yellow background explaining what the icon represents. For example, in Microsoft Word, when you mouse over the Paste icon, the “Paste” tooltip appears with its description. It helps us to know about the icon as well as its functionality. 

MS Word 2010 Smart Tool-tip appearance
In my project application, I thought of to show the Smart tooltip in my Enrolment module. My application is WPF XBAP application; hence I am not able to customize the Tooltip with my own style using shapes. Because internally Tooltips are showing using Popup, the customized tooltip are showing with black coloured shadow in smart tooltip. Then I have search through the internet about this issue and come to know about Adorners and its feature from this link then decided to use Adorner for Smart Tooltip. Here is the adorner control source. 

ControlAdorner Class:-

public class ControlAdorner : Adorner
    {
        private Control _child;

        public ControlAdorner(UIElement adornedElement)
            : base(adornedElement) { }

        protected override int VisualChildrenCount
        {
            get
            {
                return 1;
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            if (index != 0) throw new ArgumentOutOfRangeException();
            return _child;
        }

        public Control Child
        {
            get { return _child; }
            set
            {
                if (_child != null)
                {
                    RemoveVisualChild(_child);
                }
                _child = value;
                if (_child != null)
                {
                    AddVisualChild(_child);
                }
            }
        }

        protected override Size MeasureOverride(Size constraint)
        {
            Size max = new Size(double.PositiveInfinity, double.PositiveInfinity);
            this._child.Measure(max);
            this.InvalidateArrange();
            return this._child.DesiredSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            FrameworkElement target = AdornedElement as FrameworkElement;
            Point location = new Point(target.ActualWidth + 2, -((_child.ActualHeight - target.ActualHeight) / 2));
            Rect rect = new Rect(location, finalSize);
            this._child.Arrange(rect);
            return this._child.RenderSize;
        }
    }

    public class Adorners : ContentControl
    {
        #region Attached Properties

        // Template attached property
        public static readonly DependencyProperty TemplateProperty =
            DependencyProperty.RegisterAttached("Template", typeof(ControlTemplate), typeof(Adorners), new PropertyMetadata(TemplateChanged));

        public static ControlTemplate GetTemplate(UIElement target)
        {
            return (ControlTemplate)target.GetValue(TemplateProperty);
        }
        public static void SetTemplate(UIElement target, ControlTemplate value)
        {
            target.SetValue(TemplateProperty, value);
        }
        private static void TemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UpdateAdroner((UIElement)d, GetIsVisible((UIElement)d), (ControlTemplate)e.NewValue);
        }

        // IsVisible attached property
        public static readonly DependencyProperty IsVisibleProperty =
            DependencyProperty.RegisterAttached("IsVisible", typeof(bool), typeof(Adorners), new PropertyMetadata(IsVisibleChanged));

        public static bool GetIsVisible(UIElement target)
        {
            return (bool)target.GetValue(IsVisibleProperty);
        }
        public static void SetIsVisible(UIElement target, bool value)
        {
            target.SetValue(IsVisibleProperty, value);
        }
        private static void IsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UserControl rootPage = FindVisualParent<UserControl>((UIElement)d);
            Control ctrl = GetChildWindow(rootPage) as Control;
            if (ctrl != null)
                UpdateAdroner((UIElement)d, (bool)e.NewValue, ctrl);
            else
                UpdateAdroner((UIElement)d, (bool)e.NewValue, GetTemplate((UIElement)d));
        }

        // ChildWindow attached property
        public static readonly DependencyProperty ChildWindowProperty =
            DependencyProperty.RegisterAttached("ChildWindow", typeof(DependencyObject), typeof(Adorners));

        public static DependencyObject GetChildWindow(DependencyObject target)
        {
            return (DependencyObject)target.GetValue(ChildWindowProperty);
        }
        public static void SetChildWindow(UIElement target, DependencyObject value)
        {
            target.SetValue(ChildWindowProperty, value);
        }

        // InternalAdorner attached property
        public static readonly DependencyProperty InternalAdornerProperty =
            DependencyProperty.RegisterAttached("InternalAdorner", typeof(ControlAdorner), typeof(Adorners));

        public static ControlAdorner GetInteranlAdorner(DependencyObject target)
        {
            return (ControlAdorner)target.GetValue(InternalAdornerProperty);
        }
        public static void SetInternalAdorner(DependencyObject target, ControlAdorner value)
        {
            target.SetValue(InternalAdornerProperty, value);
        }

        #endregion

        #region Implementation

        // Actually do all the work:
        private static void UpdateAdroner(UIElement adorned)
        {
            UpdateAdroner(adorned, GetIsVisible(adorned), GetTemplate(adorned));
        }

        private static void UpdateAdroner(UIElement adorned, bool isVisible, ControlTemplate controlTemplate)
        {
            var layer = AdornerLayer.GetAdornerLayer(adorned);

            if (layer == null)
            {
                // if we don't have an adorner layer it's probably because it's too early in the window's construction.
                Dispatcher.CurrentDispatcher.BeginInvoke( DispatcherPriority.Loaded,
                    new Action(() => { UpdateAdroner(adorned); }));
                return;
            }

            var existingAdorner = GetInteranlAdorner(adorned);

            if (existingAdorner == null)
            {
                if (controlTemplate != null && isVisible)
                {
                    // show
                    var newAdorner = new ControlAdorner(adorned);
                    newAdorner.Child = new Control() { Template = controlTemplate, Focusable = false, };
                    layer.Add(newAdorner);
                    SetInternalAdorner(adorned, newAdorner);
                }
            }
            else
            {
                if (controlTemplate != null && isVisible)
                {
                    // switch template
                    Control ctrl = existingAdorner.Child;
                    ctrl.Template = controlTemplate;
                }
                else
                {
                    // hide
                    existingAdorner.Child = null;
                    layer.Remove(existingAdorner);
                    SetInternalAdorner(adorned, null);
                }
            }
        }

        private static void UpdateAdroner(UIElement adorned, bool isVisible, Control childControl)
        {
            var layer = AdornerLayer.GetAdornerLayer(adorned);

            if (layer == null)
            {
                // if we don't have an adorner layer it's probably because it's too early in the window's construction.
                Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Loaded,
                    new Action(() => { UpdateAdroner(adorned); }));
                return;
            }

            var existingAdorner = GetInteranlAdorner(adorned);
            if (existingAdorner == null)
            {
                if (childControl != null && isVisible)
                {
                    // show
                    var newAdorner = new ControlAdorner(adorned);
                    newAdorner.Child = childControl;
                    layer.Add(newAdorner);
                    SetInternalAdorner(adorned, newAdorner);
                }
            }
            else
            {
                if (childControl != null && isVisible)
                {
                    // switch template
                    Control ctrl = childControl;
                }
                else
                {
                    // hide
                    existingAdorner.Child = null;
                    layer.Remove(existingAdorner);
                    SetInternalAdorner(adorned, null);
                }
            }
        }

        public static T FindVisualParent<T>(UIElement element) where T : UIElement
        {
            UIElement parent = element;
            while (parent != null)
            {
                T correctlyTyped = parent as T;
                if (correctlyTyped != null)
                {
                    return correctlyTyped;
                }

                parent = VisualTreeHelper.GetParent(parent) as UIElement;
            }

            return null;
        }

        #endregion
    }

Sample Output

 
Happy coding.

Win7/Vista like busy Indicator control using Spinner animation in WPF

I have been working on WPF XBAP projects which establish the connection between mobile and system similar to Remote Desktop connectivity. I am in need to show the current status and waiting status between mobile and system connection. Hence I've created this control which is showing similar to Win7/Vista busy indicator. I am happy to share the control with you.

Initially I have a tried to show the circle shaped rectangular using rotate animation.  But I couldn't get the expected feel similar to Win7/Vista like busy indicator. Then I have spent few minutes in net and I come to know about the spinner animation and found the ImageStrip control from this link for implementing Image animation.  Then I have created Win7/Vista like busy indicator using the following code and control template.
ImageStrip Class:-
 
1 public class ImageStrip : Control 2 { 3 #region Dependency Properties 4 5 public int Frame 6 { 7 get { return (int)GetValue(FrameProperty); } 8 set { SetValue(FrameProperty, value); } 9 } 10 11 public static readonly DependencyProperty FrameProperty = 12 DependencyProperty.Register("Frame", typeof(int), typeof(ImageStrip), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender)); 13 14 public double FrameSize 15 { 16 get { return (double)GetValue(FrameSizeProperty); } 17 set { SetValue(FrameSizeProperty, value); } 18 } 19 20 public static readonly DependencyProperty FrameSizeProperty = 21 DependencyProperty.Register("FrameSize", typeof(double), typeof(ImageStrip), new FrameworkPropertyMetadata(0D, FrameworkPropertyMetadataOptions.AffectsRender)); 22 23 public ImageSource Image 24 { 25 get { return (ImageSource)GetValue(ImageProperty); } 26 set { SetValue(ImageProperty, value); } 27 } 28 29 public static readonly DependencyProperty ImageProperty = 30 DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageStrip), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); 31 32 public Orientation Orientation 33 { 34 get { return (Orientation)GetValue(OrientationProperty); } 35 set { SetValue(OrientationProperty, value); } 36 } 37 38 public static readonly DependencyProperty OrientationProperty = 39 DependencyProperty.Register("Orientation", typeof(Orientation), typeof(ImageStrip), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsRender)); 40 41 #endregion 42 43 #region Rendering 44 45 protected override void OnRender(DrawingContext drawingContext) 46 { 47 if (Image != null) 48 { 49 Rect rect = new Rect(0, 0, RenderSize.Width, RenderSize.Height); 50 51 ImageBrush brush = new ImageBrush(Image); 52 brush.Stretch = Stretch.None; 53 brush.Viewbox = (Orientation == Orientation.Vertical) ? 54 new Rect(0, (((Frame + 0.5) * FrameSize) / Image.Height) - 0.5, 1, 1) : 55 new Rect((((Frame + 0.5) * FrameSize) / Image.Width) - 0.5, 0, 1, 1); 56 57 drawingContext.DrawRectangle(brush, null, rect); 58 } 59 } 60 61 #endregion 62 } 63

BusyIndicator Class:-
 
1 public class BusyIndicator : Control 2 { 3 static BusyIndicator() 4 { 5 DefaultStyleKeyProperty.OverrideMetadata(typeof(BusyIndicator), new FrameworkPropertyMetadata(typeof(BusyIndicator))); 6 } 7 8 public string Text 9 { 10 get { return (string)GetValue(TextProperty); } 11 set { SetValue(TextProperty, value); } 12 } 13 public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(BusyIndicator), new UIPropertyMetadata(null)); 14 } 15
XAML Style:-
 
1 <ResourceDictionary 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:local="clr-namespace:SpinnerAnimation"> 5 6 <Style x:Key="{x:Type local:BusyIndicator}" TargetType="{x:Type local:BusyIndicator}"> 7 <Setter Property="HorizontalAlignment" Value="Center" /> 8 <Setter Property="VerticalAlignment" Value="Center" /> 9 <Setter Property="Template" > 10 <Setter.Value> 11 <ControlTemplate TargetType="{x:Type local:BusyIndicator}"> 12 <Border x:Name="mainBorder" Background="#FFFFFFFF" CornerRadius="3"> 13 <Grid> 14 <Border x:Name="shaddowBorder" Background="#FFFFFFFF" CornerRadius="3" BorderBrush="#0D566B" BorderThickness="1"> 15 <Border.Effect> 16 <DropShadowEffect BlurRadius="2" ShadowDepth="3" Opacity="0.6" Color="#FF000000" Direction="310"/> 17 </Border.Effect> 18 </Border> 19 <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> 20 <Viewbox Height="24" Width="24" Margin="2"> 21 <local:ImageStrip Image="Images\BusyIndicator.png" FrameSize="20" Width="20" Height="20" Margin="0 3 0 0"> 22 <local:ImageStrip.Effect> 23 <DropShadowEffect BlurRadius="3" ShadowDepth="3" Opacity="0.6" Color="#FF000000" Direction="300"/> 24 </local:ImageStrip.Effect> 25 <FrameworkElement.Triggers> 26 <EventTrigger RoutedEvent="FrameworkElement.Loaded"> 27 <BeginStoryboard> 28 <Storyboard> 29 <Int32Animation From="0" To="17" Duration="0:0:0.75" Storyboard.TargetProperty="Frame" RepeatBehavior="Forever" /> 30 </Storyboard> 31 </BeginStoryboard> 32 </EventTrigger> 33 </FrameworkElement.Triggers> 34 </local:ImageStrip> 35 </Viewbox> 36 <Label x:Name="label" FontFamily="Calibri" Content="{TemplateBinding Text}" FontSize="12" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" /> 37 </StackPanel> 38 </Grid> 39 </Border> 40 <ControlTemplate.Triggers> 41 <Trigger Property="Text" Value="{x:Null}"> 42 <Setter Property="Visibility" Value="Collapsed" TargetName="label" /> 43 <Setter Property="Visibility" Value="Collapsed" TargetName="shaddowBorder" /> 44 </Trigger> 45 </ControlTemplate.Triggers> 46 </ControlTemplate> 47 </Setter.Value> 48 </Setter> 49 </Style> 50 51 </ResourceDictionary>

By using ImageStrip control, we can also show the downloading, ready to connect, etc., indicators. We just need to create an appropriate image and give the Frame, FrameSize, Image and Orientation (If it is required). The sample code is below,
 
1 <local:ImageStrip Image="Images/Download.png" FrameSize="22.27" Width="22" Height="22" Margin="10" Grid.Row="2" Grid.Column="3"> 2 <FrameworkElement.Triggers> 3 <EventTrigger RoutedEvent="FrameworkElement.Loaded"> 4 <BeginStoryboard> 5 <Storyboard> 6 <Int32Animation From="0" To="6" Duration="0:0:1.5" Storyboard.TargetProperty="Frame" RepeatBehavior="Forever" /> 7 </Storyboard> 8 </BeginStoryboard> 9 </EventTrigger> 10 </FrameworkElement.Triggers> 11 </local:ImageStrip> 12 13 <local:ImageStrip Image="Images/Ready.png" FrameSize="21" Width="21" Height="21" Grid.Row="3" Grid.Column="2"> 14 <FrameworkElement.Triggers> 15 <EventTrigger RoutedEvent="FrameworkElement.Loaded"> 16 <BeginStoryboard> 17 <Storyboard> 18 <Int32Animation From="0" To="5" Duration="0:0:1.3" Storyboard.TargetProperty="Frame" RepeatBehavior="Forever" /> 19 </Storyboard> 20 </BeginStoryboard> 21 </EventTrigger> 22 </FrameworkElement.Triggers> 23 </local:ImageStrip> 24

Sample Output:-
Happy Coding… J

Tuesday, August 10, 2010

Error while downloading .NET Framework 3.5 pre-requisite (.msp file) with a WPF project in ClickOnce

In my WPF XBAP application, I've been trying to deploy it into remote server (It contains public domain) and install an application using certificate authority installer pre-requisites (since my application is running in Full-Trust mode) and the .NET Framework 3.5 SP1. I have set option which should download all the pre-requisites from the same location as the application.  The application is published using Visual Studio 2008 Professional Edition. If .NET Framework 3.5 is already installed then the ClickOnce installation should works fine. Otherwise it should download the missing pre-requisites from the same location and install to the Client machine.

If you try to download the application on the clean XP machine, you might have been facing the following issue.

"An error occurred downloading the following resource:
http://hostname/sampleapp/DotNetFX35/dotNetFX20/aspnet.msp"

In order to avoiding this issue, you would have to add the MIME type of the .msp file to the IIS of hosting server. You can access the MIME types by going into your site's properties -> HTTP headers -> MIME types then click NEW and add an extension and MIME type.

Extension            :               .msp

MIME                  :               application/microsoftpatch

Extension            :               .msu

MIME                  :               application/microsoftupdate

Please refer this link for more details.
Hope it will help!!

Wednesday, June 2, 2010

How to add Watermark Text to TextBox and PasswordBox in WPF?

I had a requirement in my sample application to show the watermark text (Help text) in input TextBox and PasswordBox (like Win7 Style of authentication). After a few minutes of search through internet, I got lots of sample application for showing watermark text in TextBox. But I couldn't get a sample application for showing watermark text to the PasswordBox. Again I searched for PasswordBox and finally I got an idea from this forum post. 

Since the PasswordBox is a sealed class, you cannot inherit a custom class from PasswordBox. Also Password property is not a dependency property hence you cannot write triggers. Hence I have created a custom class with attached properties such as WatermarkText (for showing the help text about the box), IsMonitoring (for monitoring the input), TextLength (for finding the input text length) and HasText (an internal property which decides whether the watermark text needs to be shown in box or not). The following code and style will help you to achieve this functionality.

WaterMarkTextHelper class:-

public class WaterMarkTextHelper : DependencyObject
    {
        #region Attached Properties

        public static bool GetIsMonitoring(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsMonitoringProperty);
        }

        public static void SetIsMonitoring(DependencyObject obj, bool value)
        {
            obj.SetValue(IsMonitoringProperty, value);
        }

        public static readonly DependencyProperty IsMonitoringProperty =
            DependencyProperty.RegisterAttached("IsMonitoring", typeof(bool), typeof(WaterMarkTextHelper), new UIPropertyMetadata(false, OnIsMonitoringChanged));

        
        public static bool GetWatermarkText(DependencyObject obj)
        {
            return (bool)obj.GetValue(WatermarkTextProperty);
        }

        public static void SetWatermarkText(DependencyObject obj, string value)
        {
            obj.SetValue(WatermarkTextProperty, value);
        }

        public static readonly DependencyProperty WatermarkTextProperty =
            DependencyProperty.RegisterAttached("WatermarkText", typeof(string), typeof(WaterMarkTextHelper), new UIPropertyMetadata(string.Empty));

        
        public static int GetTextLength(DependencyObject obj)
        {
            return (int)obj.GetValue(TextLengthProperty);
        }

        public static void SetTextLength(DependencyObject obj, int value)
        {
            obj.SetValue(TextLengthProperty, value);

            if (value >= 1)
                obj.SetValue(HasTextProperty, true);
            else
                obj.SetValue(HasTextProperty, false);
        }

        public static readonly DependencyProperty TextLengthProperty =
            DependencyProperty.RegisterAttached("TextLength", typeof(int), typeof(WaterMarkTextHelper), new UIPropertyMetadata(0));

        #endregion

        #region Internal DependencyProperty

        public bool HasText
        {
            get { return (bool)GetValue(HasTextProperty); }
            set { SetValue(HasTextProperty, value); }
        }
        
        private static readonly DependencyProperty HasTextProperty =
            DependencyProperty.RegisterAttached("HasText", typeof(bool), typeof(WaterMarkTextHelper), new FrameworkPropertyMetadata(false));

        #endregion

        #region Implementation

        static void OnIsMonitoringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is TextBox)
            {
                TextBox txtBox = d as TextBox;

                if ((bool)e.NewValue)
                    txtBox.TextChanged += TextChanged;
                else
                    txtBox.TextChanged -= TextChanged;
            }
            else if (d is PasswordBox)
            {
                PasswordBox passBox = d as PasswordBox;

                if ((bool)e.NewValue)
                    passBox.PasswordChanged += PasswordChanged;
                else
                    passBox.PasswordChanged -= PasswordChanged;
            }
        }

        static void TextChanged(object sender, TextChangedEventArgs e)
        {
            TextBox txtBox = sender as TextBox;
            if (txtBox == null) return;
            SetTextLength(txtBox, txtBox.Text.Length);
        }

        static void PasswordChanged(object sender, RoutedEventArgs e)
        {
            PasswordBox passBox = sender as PasswordBox;
            if (passBox == null) return;
            SetTextLength(passBox, passBox.Password.Length);
        }
        
        #endregion
    }

XAML Style:-
<Style TargetType="{x:Type PasswordBox}"> <Setter Property="local:WaterMarkTextHelper.IsMonitoring" Value="True"/> <Setter Property="local:WaterMarkTextHelper.WatermarkText" Value="Password" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type PasswordBox}"> <ControlTemplate.Resources> <Storyboard x:Key="enterGotFocus" > <DoubleAnimation Duration="0:0:0.4" To=".2" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Message"/> </Storyboard> <Storyboard x:Key="exitGotFocus" > <DoubleAnimation Duration="0:0:0.4" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Message"/> </Storyboard> <Storyboard x:Key="enterHasText" > <DoubleAnimation Duration="0:0:0.4" From=".2" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Message"/> </Storyboard> <Storyboard x:Key="exitHasText" > <DoubleAnimation Duration="0:0:0.4" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Message"/> </Storyboard> </ControlTemplate.Resources> <Border Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Grid> <ScrollViewer x:Name="PART_ContentHost" VerticalAlignment="Center" Margin="1" /> <TextBlock x:Name="Message" FontStyle="Italic" Text="{TemplateBinding local:WaterMarkTextHelper.WatermarkText}" Foreground="Gray" IsHitTestVisible="False" FontFamily="Calibri" Opacity="0.8" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="6,0,0,0"/> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="false"> <Setter TargetName="Bd" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> </Trigger> <Trigger Property="IsEnabled" Value="True"> <Setter Property="Opacity" Value="1" TargetName="Bd"/> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="local:WaterMarkTextHelper.HasText" Value="False"/> <Condition Property="IsFocused" Value="True"/> </MultiTrigger.Conditions> <MultiTrigger.EnterActions> <BeginStoryboard Storyboard="{StaticResource enterGotFocus}"/> </MultiTrigger.EnterActions> <MultiTrigger.ExitActions> <BeginStoryboard Storyboard="{StaticResource exitGotFocus}"/> </MultiTrigger.ExitActions> </MultiTrigger> <Trigger Property="local:WaterMarkTextHelper.HasText" Value="True"> <Trigger.EnterActions> <BeginStoryboard Storyboard="{StaticResource enterHasText}"/> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard Storyboard="{StaticResource exitHasText}"/> </Trigger.ExitActions> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="WaterMarkTextBox" TargetType="{x:Type TextBox}"> <Setter Property="local:WaterMarkTextHelper.IsMonitoring" Value="True"/> <Setter Property="local:WaterMarkTextHelper.WatermarkText" Value="Username" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <ControlTemplate.Resources> <Storyboard x:Key="enterGotFocus" > <DoubleAnimation Duration="0:0:0.4" To=".2" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Message"/> </Storyboard> <Storyboard x:Key="exitGotFocus" > <DoubleAnimation Duration="0:0:0.4" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Message"/> </Storyboard> <Storyboard x:Key="enterHasText" > <DoubleAnimation Duration="0:0:0.4" From=".2" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Message"/> </Storyboard> <Storyboard x:Key="exitHasText" > <DoubleAnimation Duration="0:0:0.4" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Message"/> </Storyboard> </ControlTemplate.Resources> <Border Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Grid> <ScrollViewer x:Name="PART_ContentHost" VerticalAlignment="Center" Margin="1" /> <TextBlock x:Name="Message" Text="{TemplateBinding local:WaterMarkTextHelper.WatermarkText}" FontStyle="Italic" Foreground="Gray" IsHitTestVisible="False" FontFamily="Calibri" Opacity="0.8" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="6,0,0,0"/> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="false"> <Setter TargetName="Bd" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="local:WaterMarkTextHelper.HasText" Value="False"/> <Condition Property="IsFocused" Value="True"/> </MultiTrigger.Conditions> <MultiTrigger.EnterActions> <BeginStoryboard Storyboard="{StaticResource enterGotFocus}"/> </MultiTrigger.EnterActions> <MultiTrigger.ExitActions> <BeginStoryboard Storyboard="{StaticResource exitGotFocus}"/> </MultiTrigger.ExitActions> </MultiTrigger> <Trigger Property="local:WaterMarkTextHelper.HasText" Value="True"> <Trigger.EnterActions> <BeginStoryboard Storyboard="{StaticResource enterHasText}"/> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard Storyboard="{StaticResource exitHasText}"/> </Trigger.ExitActions> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
Since the custom class contains attached properties, we can use these attached properties to any TextBox control in our application for showing Watermark. You just need to merge the “ResourceDictionary” which contains the Style of Textbox and then need to assign the Watermark text to the TextBox using “WatermarkText” attached property. The sample code is given below.
<Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Resource.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources> <GroupBox Header="Dial-up Setting" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1"> <StackPanel VerticalAlignment="Center"> <TextBox Height="25" Width="200" Margin="10" Style="{StaticResource WaterMarkTextBox}"/> <PasswordBox Height="25" Width="200" /> <TextBox Height="25" Width="200" Margin="10" Style="{StaticResource WaterMarkTextBox}" local:WaterMarkTextHelper.WatermarkText="Domain"/> </StackPanel> </GroupBox>

Sample Output:-



Put a comment if you have any questions. Happy coding.