OK cowboy
OK cowboy

Reputation: 65

BindingOperations.GetBinding give another binding

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

Answers (1)

mm8
mm8

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

Related Questions