Wednesday, July 22, 2020

PostSharp and Dependency Properties

Writing dependency properties is another one of those boilerplate coding exercises. Fortunately we don't write many dependency properties unless we are WPF control authors, but it does get repetitive and easy to make mistakes.

Here's an example of one...


public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.RegisterAttached("Columns", typeof(ObservableCollection<DataGridColumn>), typeof(FooterGrid), new UIPropertyMetadata(new ObservableCollection<DataGridColumn>(), ColumnsPropertyChanged));

public static int GetColumnSpan(DependencyObject obj)
{
    return (int)obj.GetValue(ColumnSpanProperty);
}

public static void SetColumnSpan(DependencyObject obj, int value)
{
    obj.SetValue(ColumnSpanProperty, value);
}


With PostSharp this is reduced to... 

 [DependencyProperty]
 public ObservableCollection<DataGridColumn> Columns { get; set; }

PostSharp also simplifies other aspects of dependency properties too. Let's walk through some.

Start a new WPF project in Visual Studio. I am using 2019, C#, .Net Framework. Call the project PostSharpDependencyProperty. Use NuGet to add PostSharp.Patterns.Xaml and add your license information to the project.

Add a new class called DigitOnlyTextBox. This will subclass the TextBox and add a boolean dependency property called OnlyAllowDigits which will control whether or not the TextBox will accept non-digit characters. Make it subclass TextBox.

using System.Text.RegularExpressions;
using System.Windows.Controls;
using PostSharp.Patterns.Xaml;

namespace PostSharpDependencyProperty
{
    class DigitOnlyTextBox:TextBox
    {
        [DependencyProperty]
        public bool OnlyAllowDigits { get; set; }
    }
}

Defining that dependency property was pretty painless.

When the text changes, we check the new property to decide if we want to strip the non-digits out of the new text. Add this code...

using System.Text.RegularExpressions;
using System.Windows.Controls;
using PostSharp.Patterns.Xaml;

namespace PostSharpDependencyProperty
{
    class DigitOnlyTextBox:TextBox
    {
        [DependencyProperty]
        public bool OnlyAllowDigits { get; set; }

        public DigitOnlyTextBox()
        {
            this.TextChanged += DigitOnlyTextBox_TextChanged;
        }

        ~DigitOnlyTextBox()
        {
            this.TextChanged -= DigitOnlyTextBox_TextChanged;
        }

        private void DigitOnlyTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            ApplyOnlyAllowDigits();
        }

        private void ApplyOnlyAllowDigits()
        {
            if (OnlyAllowDigits)
                this.Text = StripNonDigits(this.Text);
        }

        private string StripNonDigits(string sIn)
        {
            return Regex.Replace(sIn, "[^0-9]", "");
        }
    }
}

Our last requirement is to strip out digits when the AllowOnlyDigits property is set true. Thank goodness we didn't put all that code directly in DigitOnlyTextBox_TextChanged.Writing the callback code is simply a matter writing a method with a specific name. Add this method. Note the name is On<PropertyName>Changed.

        private void OnOnlyAllowDigitsChanged()
        {
            ApplyOnlyAllowDigits();
        }
Here is some XAML for MainWindow.xaml that will instantiate and exercise our new text box.

<Window x:Class="PostSharpDependencyProperty.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PostSharpDependencyProperty"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <StackPanel Orientation="Vertical">
        <local:DigitOnlyTextBox Width="100" Height="20" OnlyAllowDigits="{Binding OnlyAllowDigits}"/>
        <CheckBox Content="Only allow digits: "  IsChecked="{Binding OnlyAllowDigits}"/>
    </StackPanel>
</Window>

The code-behind needs a OnlyAllowDigits property defined. Note we are using the PostSharp [NotifyPropertyChanged] decoration.

using PostSharp.Patterns.Model;
using System.Windows;

namespace PostSharpDependencyProperty
{
    [NotifyPropertyChanged]
    public partial class MainWindow : Window
    {
        public bool OnlyAllowDigits { get; set; }

        public MainWindow()
        {
            OnlyAllowDigits = true;
            InitializeComponent();
        }
    }
}

The TextBox starts by only allowing digits. Make sure you cannot enter non-digits. 
Now clear the checkbox and ensure you can enter non-digits.
Check the checkbox again and see that the non-digits are removed.


No comments:

Post a Comment