Click here to Skip to main content
15,897,163 members
Articles / Desktop Programming / WPF
Article

Encapsulating property state in MVVM WPF applications

Rate me:
Please Sign up or sign in to vote.
3.57/5 (6 votes)
18 Dec 2015CPOL7 min read 16.9K   173   11  
How to enable a more natural way of using C# properties within an MVVM WPF application

Introduction

This article will discuss traditional C# properties, their limitations, and how we can enable a more Object Oriented way of using properties to enable MVVM WPF applications. This includes a suggestion on how to extend C#. It also includes implementation of a base class for creating MVVM WPF applications.

Data fields - the beginning

Traditionally the state of an object has been stored in a public field. This allowed the caller to view and modify the state of the object at will. Unfortunately this meant we couldn't validate to make sure the field was in a valid state. Neither could we generate events when the field changed.

This increased the complexity of the code as special verifyer code was needed to be called whenever the value had to be used.

The only way to manage object state was to use 'get' and 'set' methods and hide the underlying field. This was cumbersome and unnatural.

As a result C# introduced properties.

C# Properties - syntactic sugar

Properties are syntactic sugar that encapsulate get and/or set methods, as seen by external callers. The external caller could now reference the state in a more natural way. At the same time the object could enforce a consistent state.

The advantages of properties are that:

  1. We could raise events when a property changed.
  2. Throw exceptions on invalid state assignments.
  3. Create calculated properties that were based on other properties.
  4. IDEs such as Visual Studio could perform reference counting.
  5. They were necessary for MVVM WPF applications.
C#
private string myProperty;

public string MyPropery
{
    get
    {
        return myProperty;
    }

    set
    {
        if (!Validate(value))
        {
            // Throw some validation exception
        }
        else
        {
            // Add validation code here
            myProperty = value;
            OnStateChanged();
        }
    }
}

Unfortunately with this we have problems:

  1. Broken encapsulation. We can easily bypass validation and break encapsulation by using myProperty directly within the class.
  2. Infinite recursion. If you're not careful and mix using myProperty and MyProperty, you might end up with one calling the other endlessly.
  3. Refactoring. Since we have two items referring to the same logical state, added complexity is introduced when the time to refactor the code comes.
  4. Increased complexity. The maintenance costs increase as the number of properties increase. This can be seen in WPF programs that use the MVVM model.

Therefore C# properties don't encapsulate state in any object oriented way.

There is an exception to this.

public string MyProperty { get; set; }

public string MyReadOnlyProperty { get; private set; }

With this construct, the compiler creates a hidden field that holds the state. Unfortunately this is little more than a field with a few added benefits.

C# Properties - true encapsulation, true OOP

To enable true OOP properties, we need to be able to encapsulate state. One way is to use a dedicated keyword (not currently implemented in C#). The other way is to create our own backing store (see next section).

Dedicated keyword $state

We can introduce a keyword to represent state. (Note the dollar sign $)

public string MyProperty
{
    get
    {
        return $state;
    }

    set
    {
        if (!$state.Equals(value))
        {
            // Validation code
            // Kickoff relevant events
            // Assign $state as necessary, or throw an exception
        }
    }
}

The keyword $state is only valid within the body of a get or set.

Unfortunately the $state keyword currently doesn't exist, so we need a workaround.

Property Encapsulation Workaround

The biggest use for C# properties I've come across is either the data models or view models needed when coding MVVM WPF applications.

WPF Applications

In WPF applications, properties aren't simple things. Instead they are hooks WPF uses to enable the MVVM pattern.

In the XAML file we have:

XML
<TextBox Text="{binding FirstName, Mode=TwoWay}" />

In the view model file we have:

public class Person: INotifyPropertyChanged
{
    /// <summary>
    /// Multicast event for property change notifications.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
// ... (stuff)
    /// <summary>
    /// Gets or sets first name
    /// </summary>
    public string FirstName
    {
        get
        {
            return firstName
        }
        set
        {
            if (!this.Equals(firstName))
            {
                OnStateChanged();
                firstName = this;
            }
        }
    }
    private firstName;
// ... (some more stuff)
    /// <summary>
    /// Notifies listeners that a property value has changed
    /// </summary>
    /// <param name="propertyName">Leave blank. Property name will auto-fill.</param>
    private void OnStateChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

The property here performs two functions. First it stores state. Second it notifies WPF when the state has changed.

When I first started creating WPF applications, I decided to put the private backing field below the public property.
 This: 

  1. Allowed the public property to have XML documentation.
  2. Enabled pseudo encapsulation of the property making it easier to maintain the class.

Unfortunately this generated Style Cop errors, was cumbersome, and confused other developers.

Note: Microsoft introduced the standard of placing the private backing field above the property in Windows Store Applications. Unfortunately that got in the way of the XML comments.

View Model Base

My solution was to create a view model base to derive my models and view models from.

Note: I didn't use any of the existing frameworks because my boss had concerns about using third party software.

The job of the view model base was:
1: Encapsulate state
2: Notify WPF when state changed
3: Implement Commands

First we implement our INotifyPropertyChanged interface and a means of raising the event.

C#
public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnStateChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Next we have a means of storing state:

C#
private Dictionary<string, object> propertyStore = new Dictionary<string, object>();

Third we set state:

C#
protected bool SetState<TData>(TData value, [CallerMemberName] string propertyName = null)
{
    if (propertyStore.ContainsKey(propertyName))
    {
        if (object.Equals(propertyStore[propertyName], value))
        {
            return false;
        }
        else
        {
            propertyStore[propertyName] = value;
            this.OnStateChanged(propertyName);

            return true;
        }
    }
    else
    {
        propertyStore[propertyName] = value;
        this.OnStateChanged(propertyName);
        return true;
    }
}

TData is the data type we are storing.

We then retrieve state:

C#
    protected TData GetState<TData>(
              TData defaultValue = default(TData),
              [CallerMemberName] string propertyName = null)
    {
        if (!propertyStore.ContainsKey(propertyName))
        {
            propertyStore[propertyName] = defaultValue;
        }

        return (TData)propertyStore[propertyName];
    }

Here we give the user a means of defining a default default. This means we don't have to set it in the constructor if we don't want to.

Note: Collections need to be assigned or it will remain null. Then again that is standard behavior and expected.

Persistent data
To deal with the case where we want to store our data in a persistant store, we can use:

C#
protected bool HasChanged<TData>(TData storage, TData value, string propertyName)
{
    if (object.Equals(storage, value))
    {
        return false;
    }
    else
    {
        this.OnStateChanged(propertyName);
        return true;
    }

This is useful when storing data in external stores, such as App.config.

Calculated properties
Next we need to handle calculated properties. These properties don't store state, but reflect the state of other properties. As a result we need a way to notify WPF when the calculated property has to be checked.

C#
protected void NotifyPropertyUpdated(string calculatedPropertyName)
{
    this.OnStateChanged(calculatedPropertyName);
}

Commands

Finally we have a mechanism for storing and using commands. This accepts command parameters from the XAML code.

C#
    protected RelayCommand<TCommandParameter> Command<TCommandParameter>(
              Action<TCommandParameter> execute,
              Func<TCommandParameter, bool> canExecute = null,
              [CallerMemberName] string propertyName = null)
    {
        if (!propertyStore.ContainsKey(propertyName))
        {
            propertyStore[propertyName] = new RelayCommand<TCommandParameter>(execute, canExecute);
        }

        return (RelayCommand<TCommandParameter>)propertyStore[propertyName];
    }

}

The TCommandParameter represents the type of the CommandParameter passed in from the XAML file.

For the case where we don't pass command arguments, we have:

C#
    protected RelayCommand Command(
              System.Action execute,
              Func<bool> canExecute = null,
              [CallerMemberName] string propertyName = null)
    {
        if (!propertyStore.ContainsKey(propertyName))
        {
            propertyStore[propertyName] = new RelayCommand(execute, canExecute);
        }

        return (RelayCommand)propertyStore[propertyName];
    }

It's the same except for the relay command.

Relay Command T

To create the relay command, I used the base class Microsoft shipped with its Windows Store Visual Studio templates and modified it.

The RelayCommand<TCommandParameter> encapsulates ICommand, and is needed for WPF controls that expose click commands.

First we create the command object and then store references to the methods that do the actual work.

C#
  private readonly Action<TCommandParameter> execute;

  private readonly Func<TCommandParameter, bool> canExecute​;

  public RelayCommand(
      Action<TCommandParameter> execute,
      Func<TCommandParameter, bool> canExecute = null)
  {
    if (execute == null)
    {
       throw new ArgumentNullException("execute");
    }

    this.execute = execute;
    this.canExecute = canExecute;
  }

Next we implement the interface. WPF uses CanExecute() with an optional parameter to check whether a control should be enabled. WPF then calls Execute() with an optional parameter when the user clicks the control.

CanExecute() and Execute() take objects since that's what the interface demands. Fortunately we can hide this ugliness.

C#
public class RelayCommand<TCommandParameter> : ICommand
{
   public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter = null)
    {
        return canExecute == null ? true : canExecute(parameter);
    }

    public void Execute(object parameter = null)
    {
        execute(parameter);
    }

We then have RaiseCanExecuteChanged() to notify WPF when the enabled state of a control needs to be changed.

C#
    public void RaiseCanExecuteChanged()
    {
        var handler = CanExecuteChanged;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

We can also create another relay command that doesn't accept parameters for convenience. We would then use each as needed.

Using the code

It is now time to demo the code.

Sample program
Image 1

The demo:

  1. Build solution and run program.
  2. Select a color from the dropdown. This enables the Enable-Disable button.
  3. Click 'Enable-Disable' to enable or disable the test button.
  4. The text button changes the color of the text, according to the color that was selected.

Note:

  • One button uses command arguments and the other doesn't.
  • In addition we have property binding, enabling the standard scenarios.

The XAML

First we create a WPF demo application and add some controls.

XML
<Window x:Class="PropertyStateDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Property state demo" Height="350" Width="525" Background="Green" >
    <Grid VerticalAlignment="Center" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Margin="20" Content="Select color:" />
        <ComboBox Grid.Row="0" Grid.Column="1" Margin="20" Name="colorSelector" IsEditable="True"
                  Text="{Binding SelectedText}" ItemsSource="{Binding ColorList}" />

        <ToggleButton Grid.Row="1" Grid.Column="1" Margin="20"
                      Content="Enable-Disable" IsChecked="{Binding TestButtonIsEnabled}"
                      Command="{Binding EnableDisableButton}" />

        <Button Grid.Row="2" Grid.Column="1" Margin="20" Content="Test button" 
                Command="{Binding ChangeColor}"
                CommandParameter="{Binding ElementName=colorSelector, Path=SelectedItem }" />

        <Label Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Margin="20" Content="{Binding Message}"
               Foreground="{Binding MessageColor}" FontWeight="Bold" FontSize="14" />
    </Grid>
</Window>

In the code-behind file we just add our data context.

C#
public MainWindow()
{
    InitializeComponent();
    this.DataContext = new MainWindowViewModel();
}

View Model

First we create our color list property, using our private data store.

C#
public ObservableCollection<GroupItem> ColorList
{
    get
    {
        return this.GetState<ObservableCollection<GroupItem>>();
    }

    set
    {
        this.SetState<ObservableCollection<GroupItem>>(value);
    }
}

Then we create a new collection and initialize it in the constructor.

C#
public MainWindowViewModel()
{
    ColorList = new ObservableCollection<GroupItem>();
    ColorList.Add(new GroupItem() { Content = string.Empty, Foreground = Brushes.White });
    ColorList.Add(new GroupItem() { Content = "Red", Foreground = Brushes.Red });
    ColorList.Add(new GroupItem() { Content = "Yellow", Foreground = Brushes.Yellow });
    ColorList.Add(new GroupItem() { Content = "Green", Foreground = Brushes.Green });
    ColorList.Add(new GroupItem() { Content = "Blue", Foreground = Brushes.Blue });
    ColorList.Add(new GroupItem() { Content = "Purple", Foreground = Brushes.Purple });
}

Next we define our SelectedText property. Here we are storing user selection in Settings.Default.SelectedText. We also raise events and set messages.

C#
public string SelectedText
{
    get
    {
        return Settings.Default.SelectedText;
    }

    set
    {
        // Update only if text changed
        if (this.HasChanged(Settings.Default.SelectedText, value))
        {
            Settings.Default.SelectedText = value;
            Settings.Default.Save();

            if (string.IsNullOrWhiteSpace(value))
            {
                Message = "Select color from dropdown";
            }
            else
            {
                Message = "Click Enable-Disable to enable or disable text button";
            }

            EnableDisableButton.RaiseCanExecuteChanged();
        }
    }
}

Command EnableDisableButton.RaiseCanExecuteChanged(); causes the CanExecute lambda expression to be executed, enabling or disabling the button.

C#
public RelayCommand EnableDisableButton
{
    get
    {
        return this.Command(
        () =>
        {
            // Execute command
            if (TestButtonIsEnabled)
            {
                Message = "Test button has been enabled";
            }
            else
            {
                Message = "Test button has been disabled";
            }

            ChangeColor.RaiseCanExecuteChanged();
        },
        () =>
        {
            // CanExecute command
            if (string.IsNullOrWhiteSpace(SelectedText))
            {
                TestButtonIsEnabled = false;
                ChangeColor.RaiseCanExecuteChanged();

                return false;
            }
            else
            {
                return true;
            }
        });
    }
}

The button "Test button" uses command arguments, of type GroupItem. We use it to set message color: MessageColor = val.Foreground

C#
public RelayCommand<GroupItem> ChangeColor
{
    get
    {
        return this.Command<GroupItem>(
        (val) =>
        {
            // Execute command
            MessageColor = val.Foreground;
            Message = "User selected " + SelectedText;
        },
        (val) =>
        {
            // CanExecute command
            return TestButtonIsEnabled;
        });
    }
}

The other properties are straight forward

Points of Interest

I am using a Dictionary to store state. When dealing with large data models, performance might become an issue. However I haven't tested the performance aspect of this so I don't know. It is up to you to decide if the tradeoff between performance and maintainability is worth it.

I also use generics because I like the type safety.

I'm looking to implement a more generalized version that can work with other data stores such as XML and JSON documents. In addition, I will seek to make persisting state automatic, according to your desired settings. Stay tuned.

History

Keep a running update of any changes or improvements you've made here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
I’ve been a software developer for the last 11 years.
My language of choice is C#.
My strength lies in windows based programming as an SDET.

Strengths include:
• Object Oriented Programming
• Design Patterns
• Data Structures, Algorithms
• Windows Automation

Principle technologies include:
• Visual Studios 2010 (Ultimate, TFS, Test Manager)
• C# 4.0, XML, WPF, SQL Server

Education includes:
• BSEE - Concordia University, Quebec, Canada
• Programmer Analyst Certificate - John Abbott College, Qc, Canada
• Certified Scrum Master Training - Danube, Bellevue, WA
• Windows Azure Training - Wintellect, Bellevue, WA

Certification:
• Microsoft Certified Solution Developer for the MS.NET Platform

Comments and Discussions

 
-- There are no messages in this forum --