Tagged: custom

Single Decimal Point

Among the first issues I encountered when working on a windows phone application was the simple, plain textbox. This apparently friendly control has a dark side in a particular use case. When the InputScope is set to Numerical, it allows entering multiple decimal points.

before

In my case, I didn’t needed this “feature”. The best solution I could find to limit the decimal point to one, or none, was to develop an attached behavior.

You can find the complete solution here.

Below is the behavior’s structure:

BehaviorStructure

The attached property IsDecimalPointAllowed will set the behavior to allow one or zero decimal points in the input.

public static readonly DependencyProperty IsDecimalPointAllowedProperty =
DependencyProperty.RegisterAttached("IsDecimalPointAllowed", typeof(bool?), typeof(DecimaPointBehavior), new PropertyMetadata(null, OnIsDecimalPointAllowedChanged));

public static bool? GetIsDecimalPointAllowed(DependencyObject obj)
{
  return (bool?)obj.GetValue(IsDecimalPointAllowedProperty);
}

public static void SetIsDecimalPointAllowed(DependencyObject obj, bool? value)
{
  obj.SetValue(IsDecimalPointAllowedProperty, value);
}

When the attached property is set to true or false, the value of the regex is established.

  • For limiting the input to allow only one decimal point:
"^([0-9]+)?([,|\.])?([0-9]+)?$"
  • For limiting the input to block any decimal point:
"^([0-9]+)?$"

 

When the text inside the textbox changes, the new value is matched against the established regex:

private static void textBox_TextChanged(object sender, TextChangedEventArgs e)
{
 var textBox = (TextBox)sender;
 var match = Regex.Match(textBox.Text, regex);
 if (!match.Success)
 {
   textBox.Text = textBox.Text.Remove(textBox.Text.Length - 1);
   textBox.Select(textBox.Text.Length, 0);
 }
}

 

For no decimal point allowed, set the IsDecimalPointAllowed to false:

<TextBox Height="100"
InputScope="Number"
behaviors:DecimaPointBehavior.IsDecimalPointAllowed="False" />;

NoDecimalPoint

For one decimal point allowed, set the IsDecimalPointAllowed to true:

<TextBox Height="100"
InputScope="Number"
behaviors:DecimaPointBehavior.IsDecimalPointAllowed="True" />;

OneDecimalPoint

Advertisement

Watermark Behavior

There are a lot of scenarios where you have to apply a watermark on a WPF control. Since there is no such functionality on the standard WPF controls, the WPF Control Toolkit provides an extended text box that meets the requirements.

This was hardly sufficient in my case and of course, I thought I can do better.

I developed an attached behavior instead, since I wanted to apply the watermark on multiple types of control. Also, I intended to keep some elements of the original background in sight, so a lot of the code written is focused in that direction.

You can download the complete solution from here.

WatermarkedTextBox

For the above textbox, the XAML code looks like this (after defining the namespace where the behavior is located):

 <TextBox Height="23"
                 local:WatermarkBehavior.IsWatermarkEnabled="True"
                 local:WatermarkBehavior.WatermarkText="Watermark"
                 Background="Gold"
                 Name="textBox1"
                 Width="120"/>

Let’s have a look of the implementation.

First of all, there are a couple of attached properties registered:

Property for enabling the watermark:

 public static readonly DependencyProperty IsWatermarkEnabledProperty =
    DependencyProperty.RegisterAttached("IsWatermarkEnabled", typeof(bool), typeof(WatermarkBehavior), new UIPropertyMetadata(false, OnIsWatermarkEnabled));

Property for defining the text that will appear as watermark:

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

The watermark can be a different element, not just text. This property defines the UIElement that will be placed as watermark:

public static readonly DependencyProperty WatermarkUIElementProperty =
            DependencyProperty.RegisterAttached("WatermarkUIElement", typeof(UIElement), typeof(WatermarkBehavior), new UIPropertyMetadata(null));

Here an example for using this property:
ComplexWatermark

<RichTextBox local:WatermarkBehavior.IsWatermarkEnabled="True"
                     Height="100"
                     Name="richTextBox1"
                     Width="214">
            <local:WatermarkBehavior.WatermarkUIElement>
                <StackPanel Orientation="Horizontal">
                    <Label Content="watermark"/>
                    <Image Source="http://www.osa-opn.org/opn/media/Images/ImageOfTheWeek/12-10-22.jpg?width=1024&amp;height=1024&amp;ext=.jpg"/>
                </StackPanel>
            </local:WatermarkBehavior.WatermarkUIElement>
        </RichTextBox>

Property for defining the property of the control that is checked before applying the watermark. If this property is not set, a couple of predefined properties are checked.

         public static readonly DependencyProperty WatermarkPropertyProperty =
            DependencyProperty.RegisterAttached("WatermarkProperty", typeof(string), typeof(WatermarkBehavior), new UIPropertyMetadata(string.Empty));

An example is the watermarked button used above:

 <Button local:WatermarkBehavior.IsWatermarkEnabled="True"
                local:WatermarkBehavior.WatermarkText="Watermark"
                local:WatermarkBehavior.WatermarkProperty="Content"                
                Height="23"                
                Name="button1"
                Width="120" />

Setting the property IsWatermarkEnabled to true will register the handlers for the Loaded, GotFocus and LostFocus events of the control:

        private static void OnIsWatermarkEnabled(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var frameworkElement = d as FrameworkElement;
            if (frameworkElement == null)
            {
                return;
            }

            var isEnabled = (bool)e.NewValue;
            if (isEnabled)
            {
                // hook to the events of the control
                frameworkElement.GotFocus += ControlGotFocus;
                frameworkElement.LostFocus += ControlLostFocus;
                frameworkElement.Loaded += ControlLoaded;
            }
            else
            {
                // unhook to the events of the control
                frameworkElement.GotFocus -= ControlGotFocus;
                frameworkElement.LostFocus -= ControlLostFocus;
                frameworkElement.Loaded -= ControlLoaded;
            }
        }

The class ControlBackground holds the logic of the background creation:
ControlBackgroundClassDiagramThis class contains the property WatermarkLabel, which will be used when applying the watermark in the behavior class.
Also, the class stores the original background of the control. This way, when the watermark is no longer needed, the control’s background can return to it’s original state.
The behavior class holds a list of ControlBackgrounds. Every time a control is loaded, the original background is stored in a new record of this list that also holds the correspondent watermark.

private static readonly List OriginalTextBoxBackgrounds = new List();

private static void ControlLoaded(object sender, RoutedEventArgs e)
        {
            var control = sender as Control;
            if (control == null)
            {
                return;
            }

            // Save the original look of the control
            var newControlBackground = new ControlBackground
                                           {
                                               Control = control,
                                               OriginalBackground = control.Background
                                           };
            CreateWatermark(newControlBackground);
            OriginalTextBoxBackgrounds.Add(newControlBackground);
            var stringProperty = GetWatermarkProperty(control);
            var propertyIsEmpty = string.IsNullOrWhiteSpace(stringProperty) ? CheckDefaultPropertiesEmptyOrNull(control) : PropertyIsEmpty(control, stringProperty);
            if (propertyIsEmpty)
            {
                ApplyWatermark(control, newControlBackground.WatermarkLabel);
            }
        }

The watermark is created by changing the background of the control with a new VisualBrush that has the Visual of this label.

 private static void ApplyWatermark(Control control, Label label)
        {
            var customVisualBrush = new VisualBrush { Stretch = Stretch.None, Visual = label };
            control.Background = customVisualBrush;
        }

The watermark is applied when the control is loaded and when it has lost the focused. When the control is focused, the background must return to the original value:

 private static void ControlLostFocus(object sender, RoutedEventArgs e)
        {
            var control = sender as Control;
            if (control == null)
            {
                return;
            }

            var stringProperty = GetWatermarkProperty(control);
            var propertyIsEmpty = string.IsNullOrWhiteSpace(stringProperty) ? CheckDefaultPropertiesEmptyOrNull(control) : PropertyIsEmpty(control, stringProperty);

            if (!propertyIsEmpty)
            {
                return;
            }

            var controlBackground =
                OriginalTextBoxBackgrounds.FirstOrDefault(cb => cb.Control.GetHashCode() == control.GetHashCode());
            if (controlBackground != null && controlBackground.WatermarkLabel != null)
            {
                ApplyWatermark(control, controlBackground.WatermarkLabel);
            }
        }

 private static void ControlGotFocus(object sender, RoutedEventArgs e)
        {
            var control = sender as Control;
            var dependencyPropertyName = GetWatermarkProperty(control);
            if (control == null)
            {
                return;
            }

            var hashCode = control.GetHashCode();
            var controlBackground = OriginalTextBoxBackgrounds.FirstOrDefault(cb => cb.Control.GetHashCode() == hashCode);

            if (string.IsNullOrWhiteSpace(dependencyPropertyName) && controlBackground != null)
            {
                control.Background = controlBackground.OriginalBackground;
            }
        }

In the case of lost focus, before applying the watermark, the property provided in WatermarkProperty is checked for it’s default value. If no property is provided, a few default properties are checked:

 private static bool CheckDefaultPropertiesEmptyOrNull(Control control)
        {
            // For Password
            var passwordPropertyInfo = control.GetType().GetProperty("Password");
            if (passwordPropertyInfo != null && passwordPropertyInfo.GetValue(control, null).ToString() == string.Empty)
            {
                return true;
            }

            // For rich textbox
            var richTextBoxPropertyInfo = control.GetType().GetProperty("Document");
            if (richTextBoxPropertyInfo != null)
            {
                var richTextBoxvalue = richTextBoxPropertyInfo.GetValue(control, null) as FlowDocument;
                if (richTextBoxvalue != null)
                {
                    var textRange = new TextRange(richTextBoxvalue.ContentStart, richTextBoxvalue.ContentEnd);
                    if (string.IsNullOrWhiteSpace(textRange.Text))
                    {
                        return true;
                    }
                }
            }

            // For Selector
            var comboboxPropertyInfo = control.GetType().GetProperty("SelectedItem");

            if (comboboxPropertyInfo != null && comboboxPropertyInfo.GetValue(control, null) == null)
            {
                return true;
            }

            // For textbox
            var textPropertyInfo = control.GetType().GetProperty("Text");
            return textPropertyInfo != null && textPropertyInfo.GetValue(control, null).ToString() == string.Empty;
        }

For any improvements or issues of this solution, please feel free to comment.

Dynamic custom type information

Have you ever wondered how could you define custom properties at runtime? I didn’t… but recently I came across this issue.
The interface ICustomTypeDescriptor came to save the day. This interface is used to add custom type description outside of what the standard TypeDescriptor provides. If ICustomTypeDescriptor is not used, the default behaviour is given by a static TypeDescriptor at runtime, which provides type information based on the meta data obtained via reflection.

Let’s get to the code (here are the sources):github

The PropertyGrid is widely used to display an object’s properties and values. I used this control in a WPF application to prove the utility of using this interface.

<Window x:Class="TestCustomProperty.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:swf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <WindowsFormsHost    Name="windowsFormsHost1"   >
            <swf:PropertyGrid x:Name="propertyGrid"/>
        </WindowsFormsHost>
    </Grid>
</Window>

In code-behind:

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += this.Window_Loaded;
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {

            MyClass c = new MyClass();
            propertyGrid.SelectedObject = c;
        }
    }

Initially MyClass has only two properties:

public class MyClass
    {
        public string Name { get; set; }
        public int Nr { get; set; }

        public MyClass()
        {
            Name = "sss";
            Nr = 1;
        }

    }

With these two properties, the result looks something like this:

Here comes the interface ICustomTypeDescriptor in handy.
The interface implementation consist of a lot of methods that I didn’t need. The heart of the whole thing is the GetProperties method. The purpose of this method is to return a PropertyDescriptorCollection that describes all of the properties of the object.

public class DictionaryPropertyGridAdapter : ICustomTypeDescriptor
    {
        IDictionary _dictionary;
        public DictionaryPropertyGridAdapter(IDictionary d)
        {
            _dictionary = d;
        }

        public DictionaryPropertyGridAdapter()
        {
            _dictionary = new Hashtable();
        }
        public AttributeCollection GetAttributes()
        {
            return TypeDescriptor.GetAttributes(this, true);
        }

        public string GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }

        public string GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }

        public TypeConverter GetConverter()
        {
            return TypeDescriptor.GetConverter(this, true);
        }

        public EventDescriptor GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this, true);
        }

        public PropertyDescriptor GetDefaultProperty()
        {
            return null;
        }

        public object GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this, editorBaseType, true);
        }

        public EventDescriptorCollection GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(this, attributes, true);
        }

        public EventDescriptorCollection GetEvents()
        {
            return TypeDescriptor.GetEvents(this, true);
        }

        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            ArrayList properties = new ArrayList();
            foreach (DictionaryEntry e in _dictionary)
            {
                properties.Add(new DictionaryPropertyDescriptor(_dictionary, e.Key));
            }

            PropertyDescriptor[] props =
                (PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor));

            return new PropertyDescriptorCollection(props);
        }

        public PropertyDescriptorCollection GetProperties()
        {
            return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
        }

        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            return _dictionary;
        }

        public void AddProperty(object key, object value)
        {
            _dictionary.Add(key, value);
        }
    }

You will also notice in the GetProperties method that the Property Descriptors added for the dictionary entries are of type DictionaryPropertyDescriptor. This is a custom Property Descriptor class that manages how properties are set and retrieved. The main methods to look at on this class are GetValue and SetValue. Here you can see the component being casted as a dictionary and the value of the key inside it being set or retrieved. Take a look at the implementation below:

public class DictionaryPropertyDescriptor : PropertyDescriptor
    {
        IDictionary _dictionary;
        object _key;

        internal DictionaryPropertyDescriptor(IDictionary d, object key)
            : base(key.ToString(), null)
        {
            _dictionary = d;
            _key = key;
        }
        public override Type PropertyType
        {
            get { return _dictionary[_key].GetType(); }
        }

        public override void SetValue(object component, object value)
        {
            _dictionary[_key] = value;
        }

        public override object GetValue(object component)
        {
            return _dictionary[_key];
        }

        public override bool IsReadOnly
        {
            get { return false; }
        }

        public override Type ComponentType
        {
            get { return null; }
        }

        public override bool CanResetValue(object component)
        {
            return false;
        }

        public override void ResetValue(object component)
        {
        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }
    }

I modified MyClass so that it derives from the DictionaryPropertyGridAdapter and added the initial properties of the class to the dictionary in the constructors. I guess there is a better way to do this. Please fell free to comment with another solution.

public class MyClass : DictionaryPropertyGridAdapter
    {
        public string Name { get; set; }
        public int Nr { get; set; }
        public MyClass(IDictionary d) : base(d)
        {
            Name = "sss";
            Nr = 1;
            foreach (PropertyInfo pi in typeof(MyClass).GetProperties())
            {
                d.Add(pi.Name, pi.GetValue(this, null));
            }
        }

        public MyClass():base()
        {
            Name = "sss";
            foreach (PropertyInfo pi in typeof(MyClass).GetProperties())
            {
                this.AddProperty(pi.Name, pi.GetValue(this, null));
            }
            Nr = 1;
        }

    }

I added the other properties in code-behind:

private void Window_Loaded(object sender, RoutedEventArgs e)
        {

            MyClass c = new MyClass();
            c.AddProperty("hello","world");
            c.AddProperty("testint", 2);

            propertyGrid.SelectedObject = c;
        }

Here is the end result:

Sources: MSDNsource1, source2, source3