Workaround for Telerik WPF RadNumericUpDown Control Minimum Value Binding Problem

Summary
 

There is a known problem whereby binding in the Telerik RadNumericUpDown control is broken.  If a minimum value is greater than zero, then the WPF binding of the control will break during initialization. For our design, this rendered the control unusable.

Since this problem has not been fixed nor appears to be fixed any time soon, I took it upon myself to investigate and code a workaround to prevent design changes, further development costs, and impacting the software delivery schedule.

 
Background

For the next generation of our Performance Sentry™ Administration application we chose WPF as the platform and elected to utilize the Telerik for WPF controls.  Our Data Collection Sets (DCSs) have numerous data collection parameters with different custom types that allow very flexible data collection through our Performance Sentry™ product.  To allow the user to edit these properties, I created a DCS property editor user control.  This component takes advantage of WPF in that it renders each property with a content presenter based upon the property type.  The property types each have a class to represent them and new classes and presenters can be added as needed. With this framework in place, maintenance is easier since the code is object oriented and located in one place.

Implementation

For numeric properties with a specified range, we had previous used a numeric up down control in WinForms which did not require data validation.  Telerik has a RadNumericUpDown control that would be a perfect replacement for the WinForms version.

Here is a picture of the DCS property editor constructed in WPF using Telerik controls:

Take a look at the xaml for the ListView that is used to allow the user to edit these properties.

<ListView Grid.ColumnSpan=”2″ x:Name=”CycleProperties”
Style=”{StaticResource PropListView}”
ItemsSource=”{Binding Path=CycleProps}”
ItemContainerStyle=”{StaticResource ResourceKey=PropListViewItemStyle}”>
<ListView.View>
<GridView AllowsColumnReorder=”False”
ColumnHeaderContainerStyle=”{StaticResource myHeaderStyle}”>
<GridViewColumn CellTemplate=”{StaticResource ResourceKey=PropDescription}”/>
<GridViewColumn CellTemplate=”{StaticResource ResourceKey=CustomPresenter}”/>
</GridView>
</ListView.View>
</ListView>

In the C# code behind, you would see the following initialization to define the cycle collection properties as an array.  Only one property is shown to save space.

// Collection Parameters
_CycleProps = new PropertyBase[] {
new IntegerProperty(dcs,
“Cycle Duration”,
“Length of the data collection cycle in hours.”,
“CycleDuration”,
1.0,
999.0),

Of course this initialization could be moved to a configuration file but our features do not change enough to warrant that during this release.

The integer property has a range as indicated by the min and max parameters.  These would be bound to the RadNumericUpDown control.

class IntegerProperty : PropertyBase
{
public IntegerProperty( object DataContext,
string DisplayName,
string Description,
string PropertyName, double Min, double Max, bool) : base(DataContext, DisplayName, Description, PropertyName, ReadOnly)
{
this._Min = Min;
this._Max = Max;
}
private double _Min;
public double Min { get { return _Min; } set { _Min = value; OnPropertyChanged(“Min”); } }
private double _Max;
public double Max { get { return _Max; } set { _Max = value; OnPropertyChanged(“Max”); } }
}

The base class for all properties handles the property change notification and defines the Value property to retrieve the value from the object that was being edited.  The property is created with a data context that is used to get the value to be rendered or set by the custom content presenter.

public class PropertyBase : INotifyPropertyChanged
{

public PropertyBase( object DataContext,
string DisplayName,
string Description,
string PropertyName,
… )
public object Value … Implemented Get/Set logic for the data context…
}

The custom presenter xaml for the IntegerProperty is below.

<DataTemplate DataType=”{x:Type dts:IntegerProperty}”>
<telerik:RadNumericUpDown HorizontalAlignment=”Right”
HorizontalContentAlignment=”Right”
Margin=”2 2 2 2″
SmallChange=”1″
LargeChange=”10″
AutoReverse=”True”
MinWidth=”60″
IsTabStop=”True”
ValueFormat=”Numeric”
IsEditable=”{Binding Path=IsEditable}”
IsEnabled=”{Binding Path=IsEditable}”
IsInteger=”True”
DataContext=”{Binding}”
Minimum=”{Binding Path=Min, Mode=TwoWay}”
Maximum=”{Binding Path=Max, Mode=TwoWay}”
Value=”{Binding Path=Value, Converter={StaticResource ResourceKey=RadNumericUpDownConverter}, Mode=TwoWay}”
Loaded=”RadNumericUpDown_Loaded” />
</DataTemplate>

The problem

We have a parameter for the data collection cycle duration in hours which previously had the range of 1-999.  As the data files were loaded and I began to test the implementation, the horror began.  The common value of 24 found in our data files would not bind in the control.  The value was being set to the minimum value of 1.

After MUCH debugging and questioning of the infrastructure I created, I discovered that Telerik has a bug in this the seemingly simplest of controls!  If the minimum value is > 0, the binding is broken and the value bound is set to the minimum.  Here is the PITS record I was referred to although it has a slightly different definition than what I submitted.  This issue was initially part of a different post so I speculate there was some confusion on Telerik’s part to the full extent of the problem.

http://www.telerik.com/support/pits.aspx#/public/wpf/4266

A workaround?

The initial response from telerik was to not use the control in a data template which obviously was not an option for this type of design.  They apologized for not getting it into Q3 but until it was fixed this control would be useless for our design.  These are the darkest times of trying to accomplish reuse in software engineering.  You never know what is lurking around the next corner to throw a wrench in your product plans.   Having invested in Telerik, we did not want to incur licensing for another control library and go through another learning curve in the middle of the project.  And of course we don’t want to reinvent the wheel for something as common place as a numeric up down control.  So we need a workaround to make progress.

What about setting the minimum to zero until Telerik fixed their problem?  This is something we could put in release notes.

It was decided that it was important that the user not be able to set this parameter to zero and we better find a workaround.

So that brings us to the point of this whole article.  Trying to relieve the pain and suffering of anyone in a similar situation.  There were several code/run/debug attempts and then it occurred to me.  After the control is loaded what would happen if we set the minimum property again even though the binding was supposed to be set?

Purists, look away from your screen.  This is the type of code that you rarely get to see from the outside, but I think most developers will admit it is the glue that holds systems and commerce together even in 2010.  This is the compromise between the business world of dollars and cents and the theoretical world of computer science that must be often must be reached to deliver systems even with advanced tools as WPF and Visual Studio 2010.

Change the minimum value on the property to the control so it is bound to a minimum greater than zero.  This prevents the binding from breaking.

new IntegerProperty(dcs,
“Cycle Duration”,
“Length of the data collection cycle in hours.”,
“CycleDuration”,
0.0,  // Telerik Bug Requires 0.0 should be 1.0
999.0),

In the custom content presenter, add the following event to get called after the control loads.

Loaded=”RadNumericUpDown_Loaded” />

Then implement the following event handler to change your minimum binding to what it should be:

// Work around for Telerik RadUpDownNumeric bug when you cannot specify a minimum

private void RadNumericUpDown_Loaded(object sender, RoutedEventArgs e)
{
Telerik.Windows.Controls.RadNumericUpDown c = sender as Telerik.Windows.Controls.RadNumericUpDown;
if (c != null)
{
IntegerProperty i = c.DataContext as IntegerProperty;
if (i.Name == “Cycle Duration”)
c.Minimum = 1;
else if (i.Name == “Disk Limit MB”)
c.Minimum = 1;
}
}

It turns out if you set the minimum just after the control loads the binding will still take effect and your minimum value > 0 will be applied successfully.

To this day the fix is not schedule for release Q4.   Maybe some renegade working for Telerik will see this post and add it to Q4 or and work “off task” fixing this while editing a nearby file!

,

Comments are closed.