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:
- We could raise events when a property changed.
- Throw exceptions on invalid state assignments.
- Create calculated properties that were based on other properties.
- IDEs such as Visual Studio could perform reference counting.
- They were necessary for MVVM WPF applications.
private string myProperty;
public string MyPropery
{
get
{
return myProperty;
}
set
{
if (!Validate(value))
{
}
else
{
myProperty = value;
OnStateChanged();
}
}
}
Unfortunately with this we have problems:
- Broken encapsulation. We can easily bypass validation and break encapsulation by using myProperty directly within the class.
- Infinite recursion. If you're not careful and mix using myProperty and MyProperty, you might end up with one calling the other endlessly.
- Refactoring. Since we have two items referring to the same logical state, added complexity is introduced when the time to refactor the code comes.
- 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))
{
}
}
}
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:
<TextBox Text="{binding FirstName, Mode=TwoWay}" />
In the view model file we have:
public class Person: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string FirstName
{
get
{
return firstName
}
set
{
if (!this.Equals(firstName))
{
OnStateChanged();
firstName = this;
}
}
}
private firstName;
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:
- Allowed the public property to have XML documentation.
- 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.
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:
private Dictionary<string, object> propertyStore = new Dictionary<string, object>();
Third we set state:
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:
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:
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.
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.
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:
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.
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.
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.
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 | The demo:
- Build solution and run program.
- Select a color from the dropdown. This enables the Enable-Disable button.
- Click 'Enable-Disable' to enable or disable the test button.
- 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.
<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.
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
View Model
First we create our color list property, using our private data store.
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.
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.
public string SelectedText
{
get
{
return Settings.Default.SelectedText;
}
set
{
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.
public RelayCommand EnableDisableButton
{
get
{
return this.Command(
() =>
{
if (TestButtonIsEnabled)
{
Message = "Test button has been enabled";
}
else
{
Message = "Test button has been disabled";
}
ChangeColor.RaiseCanExecuteChanged();
},
() =>
{
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
public RelayCommand<GroupItem> ChangeColor
{
get
{
return this.Command<GroupItem>(
(val) =>
{
MessageColor = val.Foreground;
Message = "User selected " + SelectedText;
},
(val) =>
{
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.