Introduction
For a line of business application I worked on recently, there was a need to put various screens in read-only mode based on various criteria. In this article, I will present a simple approach I took to allowing screens to be placed in read-only mode using styles and binding.
The Read-Only Helper
The first thing that I needed was a way to tell a screen (or just a section of a screen), and it’s child controls, whether or not read-only mode is enabled. For this I used a simple attached property. An attached property is a good fit here since it can be set on any object and its value can be inherited down the tree.
public static class ReadOnlyHelper
{
public static readonly DependencyProperty ReadOnlyModeProperty = DependencyProperty.RegisterAttached("ReadOnlyMode",
typeof(bool), typeof(ReadOnlyHelper), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
public static bool GetReadOnlyMode(DependencyObject element)
{
return (bool)element.GetValue(ReadOnlyModeProperty);
}
public static void SetReadOnlyMode(DependencyObject element, bool value)
{
element.SetValue(ReadOnlyModeProperty, value);
}
}
Application Styles
Now that I had a mechanism to tell a screen it should be in read-only mode, I needed a way for the various controls to obey that setting and make themselves read-only or not. To handle this, I chose to use application wide styles for each of the control types for the following reasons.
- I was already using them to provide a consistent look of all controls throughout the application.
- I would not have to write any procedural code.
- The mechanism would be available by default anywhere in the application.
All that I had to do was to add property setter(s) that were bound to the attached property provided by the read-only helper to control what it meant for a control to be in read-only mode.
One of the simplest implementations was for the TextBox. It already provides a read-only mode, through the IsReadOnly property, that allows a user to copy the contents (which can be nice) but does not allow changes to it. All that I needed to do was bind the IsReadOnly property to the ReadOnlyHelper ReadOnlyMode property.
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsReadOnly" Value="{Binding Path=(local:ReadOnlyHelper.ReadOnlyMode), RelativeSource={RelativeSource Self}}" />
</Style>
Of course not all controls have an IsReadOnly property nor do most define what it means to be read-only. In these cases it was up to me to decide how to handle read-only mode. One example of this was the CheckBox. Although it was an option, I didn’t want to just disable all CheckBox controls when read-only because I did like the grayed out text. What I decided to do was:
- Disable hit testing so that a user could not click on the check box to change the value.
- Disable tab stop so that a user could not tab to the control to change the value with the spacebar.
An important point here is that you have some flexibility in what can do to make a control read-only. If you want to just disable a control for read-only mode, you can.
I then went through the same exercise for all other controls in the application. In fact, for some buttons (Save buttons for example) I actually made them hidden when the parent screen was read-only since it would not apply.
Putting the Two Together
Now that I had a way to specify read-only mode and a way to modify control properties based on that setting, all that was left was to define the screens. For a screen that needed to have a read-only mode, the attached property was placed on the root object of the screen. It was bound to a property of the view model that controlled the mode of the screen.
Conclusion
The combination of an Attached Property and application wide styles became an effective mechanism for managing the ability to place a screen (or parts of a screen) into read-only mode without adding much to the overall architecture of the application.