Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Friday, September 20, 2013

Globalize WPF application using Localization

When you create a new product, it should be available to all people in worldwide and they could understand by his own language. When you limit your product's availability to only one language (Say in English), you limit your potential customer base. If you want your applications to reach a global audience, cost-effective localization of your product is one of the best and most economical ways to reach more customers.

Here I am going to apply globalization and localization in my Windows Presentation Foundation (WPF) application. Globalization is the design and development of applications that perform in multiple locations. Localization is the translation of application resources into localized versions for the specific cultures that the application supports.

A simple and effective way to localize application resources is to write a custom MarkupExtension that provides a localized value. The extension takes a parameter in the constructor that is the unique resource key. When the DepdendencyProperty asks for the value, the markup extension looks up the value from a generic resource provider. This gives you the flexibility to reuse the resource management and translation process that is already established within your company.

The localization module consists of the following parts.

Localization manager

The localization manager is a static class which manages the current language and notifies to all markup extensions which are specified in RESX file, to update their values when the language changes. It also provides access to localized resources. The resources itself are provided by a generic translation provider.

public class LocalizationManager
    {
        public static event EventHandler CultureChanged;
        public static ILocalizedResourceProvider LocalizationProvider { get; set; }
        public static IList<CultureInfo> SupportedCultures { get; private set; }

        public static CultureInfo CurrentCulture
        {
            get { return CultureInfo.CurrentUICulture; }
            set
            {
                Thread.CurrentThread.CurrentUICulture = value;

                if (CultureChanged != null)
                {
                    CultureChanged(null, EventArgs.Empty);
                }
            }
        }

        public static object GetValue(string key)
        {
            if (LocalizationProvider != null)
            {
                return LocalizationProvider.GetValue(key);
            }
            return null;
        }

        static LocalizationManager()
        {
            SupportedCultures = new List<CultureInfo>();
        }
    }

Localize markup extension

The localize markup extension knows the resource key and provides the localized value. It listens to the CultureChanged event of the localization manager and update its value. 

[MarkupExtensionReturnType(typeof(string)), Localizability(LocalizationCategory.NeverLocalize)]
    public class LocalizeExtension : MarkupExtension
    {
        private DependencyObject _targetObject;
        private DependencyProperty _targetProperty;
        private TypeConverter _typeConverter;

        public LocalizeExtension()
        {
            LocalizationManager.CultureChanged += LocalizationManager_CultureChanged;
        }

        ~LocalizeExtension()
        {
            LocalizationManager.CultureChanged -= LocalizationManager_CultureChanged;
        }

        private void LocalizationManager_CultureChanged(object sender, EventArgs e)
        {
            UpdateTarget();
        }

        public LocalizeExtension(string key) : this()
        {
            Key = key;
        }

        [ConstructorArgument("key")]
        public string Key { get; set; }

        [ConstructorArgument("format")]
        public string Format { get; set; }

        [ConstructorArgument("DefaultValue")]
        public object DefaultValue { get; set; }

        [ConstructorArgument("Converter")]
        public IValueConverter Converter { get; set; }

        internal void UpdateTarget()
        {
            if (_targetObject != null && _targetProperty != null)
            {
                _targetObject.SetValue(_targetProperty, ProvideValueInternal());
            }
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            // Resolve the depending object and property
            if (_targetObject == null)
            {
                var targetHelper = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
                _targetObject = targetHelper.TargetObject as DependencyObject;
                _targetProperty = targetHelper.TargetProperty as DependencyProperty;
                _typeConverter = TypeDescriptor.GetConverter(_targetProperty.PropertyType);
            }

            return ProvideValueInternal();
        }

        private object ProvideValueInternal()
        {
            // Get the localized value
            object value = LocalizationManager.GetValue(Key);

            // Automatically convert the type if a matching type converter is available
            if (value != null && _typeConverter != null && _typeConverter.CanConvertFrom(value.GetType()))
            {
                value = _typeConverter.ConvertFrom(value);
            }

            // If the value is null, use the fallback value if available
            if ((value == null && DefaultValue != null) || Convert.ToString(value) == "")
            {
                value = DefaultValue;
            }

            // If no fallback value is available, return the key
            if (value == null)
            {
                if (_targetProperty != null && _targetProperty.PropertyType == typeof(string))
                {
                    // Return the key surrounded by question marks for string type properties
                    value = string.Concat("?", Key, "?");
                }
                else
                {
                    // Return the UnsetValue for all other types of dependency properties
                    return DependencyProperty.UnsetValue;
                }
            }

            if (Converter != null)
            {
                value = Converter.Convert(value, _targetProperty.PropertyType, null, CultureInfo.CurrentCulture);
            }

            // Format the value if a format string is provided and the type implements IFormattable
            if (value is IFormattable && Format != null)
            {
                ((IFormattable)value).ToString(Format, CultureInfo.CurrentCulture);
            }
            return value;
        }
    }

Resource provider

The resource provider is a class that provides the localized resources. It has to implement the ILocalizedResourceProvider and can access any kind of resources you like RESX files.

public class ResourceFileProvider : ResourceManager, ILocalizedResourceProvider
    {
        private ResourceSet _resourceSet;

        public object GetValue(string key)
        {
            try
            {
                if (_resourceSet != null)
                {
                    return _resourceSet.GetObject(key);
                }
                return null;
            }
            catch
            {
                return null;
            }
        }

        private void LoadResources()
        {
            ReleaseAllResources();
            _resourceSet = GetResourceSet(CultureInfo.CurrentUICulture, true, true);
        }

        public ResourceFileProvider(string baseName, Assembly assembly) : base(baseName, assembly)
        {
            LoadResources();
            LocalizationManager.CultureChanged += (sender, e) => { LoadResources(); };
        }
    }

How to use it

The usage of the markup extension is very simple. Just replace the string you want to localize by {Localize resourceKey}. Where resource key is the ID which is defined in .resx file.

In XAML :-

<TextBlock Text="{Localize Txt_Language, DefaultValue=Language}" />
<TextBlock Text="{Localize Txt_Welcome, DefaultValue='Welcome to WPF Applicaiton'}" />

CodeBehind :-

LocalizationManager.GetValue("Txt_Language")
LocalizationManager.GetValue("Txt_Welcome")

Sample Output

In English :-


In German :-



Monday, May 16, 2011

Saving WPF Framework element as Image (In different formats)

In WPF, we have an option to set the object of DrawingBrush, RadialGradientBrush, LinearGradientBrush, etc., to the Background of FrameworkElements. We can save the element as an Image using the following code snippet.


        public static void SaveImage(string fileName, FrameworkElement element)
        {
            string imageExtension = null;
            imageExtension = new FileInfo(fileName).Extension.ToLower(CultureInfo.InvariantCulture);
            BitmapEncoder imgEncoder = null;
            switch (imageExtension)
            {
                case ".bmp":
                    imgEncoder = new BmpBitmapEncoder();
                    break;
                case ".jpg":
                case ".jpeg":
                    imgEncoder = new JpegBitmapEncoder();
                    break;
                case ".png":
                    imgEncoder = new PngBitmapEncoder();
                    break;
                case ".gif":
                    imgEncoder = new GifBitmapEncoder();
                    break;
                case ".tif":
                case ".tiff":
                    imgEncoder = new TiffBitmapEncoder();
                    break;
                case ".wdp":
                    imgEncoder = new WmpBitmapEncoder();
                    break;
                default:
                    imgEncoder = new BmpBitmapEncoder();
                    break;
            }
            if (element != null)
            {
                RenderTargetBitmap bmpSource = new RenderTargetBitmap((int)element.ActualWidth, (int)element.ActualHeight, 96, 96, PixelFormats.Pbgra32);
                bmpSource.Render(element);
                imgEncoder.Frames.Add(BitmapFrame.Create(bmpSource));
                using (Stream stream = File.Create(fileName))
                {
                    imgEncoder.Save(stream);
                    stream.Close();
                }
            }
        }

Hope it will help you. Leave your comments about it. Happy coding.

Wednesday, October 20, 2010

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

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.