Reputation: 65
I'm using ValidationRule dynamically in a FooBox (upgraded textbox). It's working fine when used directly in a Window.
I have another custom control (LatLonEditor) to manage latitude and longitude using FooBox. In this particular case, when i get the Binding of the FooBox's value, i always get the binding of the first FooBox of the first LatLonEditor.
I have spent the day trying to fix this and i'm running out of solutions. I have read the BindingOperations.GetBinding source code (didn't gave me a clue). I have created an isolated project to reproduce the issue stripping all the useless part of the code.
FooBox template
<ControlTemplate x:Key="FooBoxTemplate" TargetType="{x:Type local:FooBox}">
<TextBox x:Name="Editor" Text="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,1,0" />
<ControlTemplate.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
<Setter Property="BorderBrush" TargetName="Editor" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="DefaultFooBoxStyle" TargetType="{x:Type local:FooBox}">
<Setter Property="Template" Value="{StaticResource FooBoxTemplate}"/>
<Setter Property="Height" Value="24" />
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
<Setter Property="Focusable" Value="False" />
</Style>
<Style TargetType="{x:Type local:FooBox}" BasedOn="{StaticResource DefaultFooBoxStyle}" />
FooBox control
public class FooBox : Control
{
static FooBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FooBox), new FrameworkPropertyMetadata(typeof(FooBox)));
}
public enum Type
{
Int,
Float,
Double,
String
}
private bool _templateApplied = false;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_templateApplied = true;
LoadValidationRules();
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(FooBox), new FrameworkPropertyMetadata()
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
});
public string Value
{
get { return (string)GetValue(ValueProperty); }
set
{
SetValue(ValueProperty, value);
}
}
public static readonly DependencyProperty ValueTypeProperty =
DependencyProperty.Register("ValueType", typeof(Type), typeof(FooBox), new FrameworkPropertyMetadata()
{
DefaultValue = Type.String
});
public Type ValueType
{
get { return (Type)GetValue(ValueTypeProperty); }
set
{
SetValue(ValueTypeProperty, value);
}
}
/// <summary>
/// For integral types, this is the max acceptable value
/// </summary>
public static readonly DependencyProperty DomainMaxProperty =
DependencyProperty.Register("DomainMax", typeof(decimal?), typeof(FooBox), new FrameworkPropertyMetadata());
public decimal? DomainMax
{
get { return (decimal?)GetValue(DomainMaxProperty); }
set
{
SetValue(DomainMaxProperty, value);
}
}
private void LoadValidationRules()
{
if (_templateApplied)
{
//For the second LatLonEditor, i've got the binding of the previous one
Binding b = BindingOperations.GetBinding(this, ValueProperty);
if (b != null)
{
b.ValidationRules.Clear();
if (ValueType == Type.Double)
{
b.ValidationRules.Add(new DoubleValidationRule()
{
DomainMax = DomainMax
});
}
}
}
}
}
LatLonEditor template
<ControlTemplate x:Key="LatLonDecimalDegreesEditorTemplate" TargetType="{x:Type local:LatLonEditor}">
<local:FooBox x:Name="PART_DD" ValueType="Double" Margin="2,0"
Value="{Binding Value, Delay=500, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource TemplatedParent}}"/>
<ControlTemplate.Triggers>
<Trigger Property="Type" Value="Latitude">
<Setter TargetName="PART_DD" Property="DomainMax" Value="90" />
</Trigger>
<Trigger Property="Type" Value="Longitude">
<Setter TargetName="PART_DD" Property="DomainMax" Value="180" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="DefaultLatLonEditorStyle" TargetType="{x:Type local:LatLonEditor}">
<Setter Property="Template" Value="{StaticResource LatLonDecimalDegreesEditorTemplate}"/>
</Style>
<Style BasedOn="{StaticResource DefaultLatLonEditorStyle}" TargetType="{x:Type local:LatLonEditor}" />
LatLonEditor control
public class LatLonEditor : Control
{
#region Statements
#endregion
#region Constructor/Destructor
static LatLonEditor()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LatLonEditor), new FrameworkPropertyMetadata(typeof(LatLonEditor)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
#endregion
/// <summary>
/// The types of value that can be input
/// </summary>
public enum CoordinateValueType : byte
{
Latitude,
Longitude
}
#region Properties
/// <summary>
/// Get/Set the input mode for this instance
/// </summary>
public static readonly DependencyProperty TypeProperty =
DependencyProperty.Register("Type", typeof(CoordinateValueType), typeof(LatLonEditor), new FrameworkPropertyMetadata()
{
DefaultValue = CoordinateValueType.Latitude
});
public CoordinateValueType Type
{
get { return (CoordinateValueType)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
/// <summary>
/// Formatted value to use externally
/// </summary>
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double?), typeof(LatLonEditor), new FrameworkPropertyMetadata());
public double? Value
{
get
{
return (double?)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
#endregion
}
Usage
<controls:LatLonEditor x:Name="H3LP" Width="120" Type="Longitude" Value="3" />
<controls:LatLonEditor x:Name="IfYouPlease" Width="120" Type="Latitude" Value="5" />
The first LatLonEditor should have a maximum value of 180 The second LatLonEditor should have a maximum value of 90
DomainMax are correctly set by the trigger.
The actual result is that both control have a maximum value of 90 (First binding apply the rule of the second control)
I'm certainly missing something, but i don't see what ? :-(
Upvotes: 0
Views: 153
Reputation: 169200
You should first get a reference to the element to which you have applied the binding and then use BindingOperations.GetBinding
:
private void LoadValidationRules()
{
if (_templateApplied)
{
TextBox Editor = Template.FindName("Editor", this) as TextBox;
Binding b = BindingOperations.GetBinding(Editor, TextBox.TextProperty);
if (b != null)
{
...
}
}
}
Since a Binding
is not supposed to be modified you might also create the initial binding programmatically:
private void LoadValidationRules()
{
if (_templateApplied)
{
TextBox Editor = Template.FindName("Editor", this) as TextBox;
Binding b = new Binding(nameof(Value)) { Source = this };
if (ValueType == Type.Double)
{
b.ValidationRules.Add(new DoubleValidationRule()
{
//DomainMax = DomainMax
});
}
BindingOperations.SetBinding(Editor, TextBox.TextProperty, b);
}
}
Then you will certainly get a unique binding per instance.
Upvotes: 1