Conditional Formatting of a TextBox

FormatAndPaste

I recently came across a scenario where I needed to bind a TextBox to a domain property but also have the value formatted for display. To make things more interesting the format was to be dynamic and the value needed to be editable.

The initial investigation led me to consider a ValueConverter. Ideally the TextBox.Text property could be bound and the Converter could be used to format to/from the required on-screen value. For a dynamic format it would be nice to bind the ConverterParameter to a property that exposed the format. Of course that doesn’t work because ConverterParameter doesn’t support data binding. I found a hack that gets around this – but it isn’t pretty. There are also some examples of using a MultiValueConverter and passing both the value to format and the format string itself as separate individual bindings. This approach has some difficulties too when converting both ways and its just feels like an abuse of the ValueConverter.

This lead me to think about the problem a little more… maybe a different approach is required? Thinking back to the WinForms days and I realised that I had solved this problem before, several times in fact. My approach to this problem for WinForms had been:

  • Subclass TextBox and add a Value property of type object that allows data binding to data types other than just string. Common types that could be used with a TextBox include int, decimal, double, bool, DateTime and enums.
  • The inherited TextBox also has a Format property. On GotFocus the Value property is formatted and used to populate the Text property. On LostFocus the reverse happens, the Text property is parsed back into the Value property. Of course this requires the data type to be known so a DataType property is required as well.

The benefits that this has:

  • TextBox works for data types other than string.
  • The value is formatted as required for display but upon data entry (GotFocus) the formatting is removed. This actually makes it easier to enter/modify the value because you don’t need to parse currency symbols, percentage signs and the like.

So the approach sounds good and its worked well for me in WinForms but its… well… not very WPF’ish. Upon starting any major development the first requirement in WinForms was to subclass all the controls – because they were just so lacking if functionality and even more importantly didn’t expose a common set of interfaces. However, I very rarely subclass controls in WPF – instead we can use attached behaviors to extend the control.

The attached behaviors required are:

  • object TypedValue
  • Type DataType 
  • string StringFormat

In XAML instead of binding to the TextBox.Text property we bind to the TypedValue attached property. The StringFormat can also be bound. The DataType can be inferred by the TypedValue – but for nullable types its best to be set explicitly. With a sample class as follows:

public class ModelItem
{
public object Value { get; set; }
public string Format { get; set; }
}

The XAML is then:

<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Any Type TextBox">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="local:TextBoxExtensions.StringFormat" Value="{Binding Format}"/>
<Setter Property="local:TextBoxExtensions.TypedValue" Value="{Binding Value}"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="{x:Type TextBox}">
<Setter Property="local:TextBoxExtensions.StringFormat" Value="{Binding Format}"/>
<Setter Property="local:TextBoxExtensions.TypedValue" Value="{Binding Value}"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Format" Binding="{Binding Format}" IsReadOnly="True"/>
<DataGridTextColumn Header="Value" Binding="{Binding Value}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>

Which generates a DataGrid bound to a collection of ModelItems. Each ModelItem allows a different data type and format to be applied – great for a “user-defined fields” scenario.

Populating the ModelItems collection as follows in our main ViewModel:

    public class Model
{
public Model()
{
Primary = new ModelItem() { Format = "{0:#,##0.0}", Value = 12345678.765 };
Items = new ObservableCollection<ModelItem>();
Items.Add(new ModelItem() { Format = "{0:C2}", Value = 123.42 });
Items.Add(new ModelItem() { Format = "{02}", Value= 0.125 });
Items.Add(new ModelItem() { Format = "{0}", Value = "Fred" });
Items.Add(new ModelItem() { Format = null, Value = true });
Items.Add(new ModelItem() { Format = "Uncle {0}", Value = "George" });
Items.Add(new ModelItem() { Format = null, Value = Colors.Black });
Items.Add(new ModelItem() { Format = null, Value = System.DayOfWeek.Monday });
Items.Add(new ModelItem() { Format = "{0:0;minus 0;zip}", Value = -123.4 });
}
public ModelItem Primary { get; set; }
public ObservableCollection<ModelItem> Items { get; private set; }
}

Generates the following grid, which allows for editing of the strongly typed values.

FormatAndPaste

One thought on “Conditional Formatting of a TextBox”

Comments are closed.