首页 > 解决方案 > BindingOperations.GetBinding 给出另一个绑定

问题描述

我在 FooBox (升级的文本框)中动态使用 ValidationRule。直接在窗口中使用时它工作正常。

我有另一个自定义控件(LatLonEditor)来使用 FooBox 管理纬度和经度。在这种特殊情况下,当我获得 FooBox 值的绑定时,我总是获得第一个 LatLonEditor 的第一个 FooBox 的绑定。

我花了一天时间试图解决这个问题,但我的解决方案已经用完了。我已经阅读了 BindingOperations.GetBinding 源代码(没有给我任何线索)。我创建了一个独立的项目来重现剥离代码中所有无用部分的问题。

FooBox 模板

<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 控件

 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 模板

 <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 控件

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
}

用法

 <controls:LatLonEditor x:Name="H3LP" Width="120"  Type="Longitude" Value="3" />
    <controls:LatLonEditor x:Name="IfYouPlease" Width="120" Type="Latitude" Value="5" />

第一个 LatLonEditor 的最大值应为 180 第二个 LatLonEditor 的最大值应为 90

触发器正确设置了 DomainMax。

实际结果是两个控件的最大值都是90(第一个绑定应用第二个控件的规则)

我当然错过了一些东西,但我没有看到什么?:-(

标签: .netwpfxaml

解决方案


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.


推荐阅读