【问题标题】:How can I bind a model property to another property of the same instance of that model?如何将模型属性绑定到该模型的同一实例的另一个属性?
【发布时间】:2016-02-29 03:46:34
【问题描述】:

TL;DR:

<UserControl.DataContext>
    <Foo:Bar>
        <Foo:Bar.Baz Blat={Binding Source={RelativeSource Self}, Path=Bing, Mode=TwoWay}"/>

导致以下错误消息:

  • System.Windows.Data 警告:40:
    • BindingExpression 路径错误:在 'object' 'RelativeSource' (HashCode=38995967)' 上找不到 'Bing' 属性。
    • BindingExpression:路径=必应; DataItem='RelativeSource' (HashCode=38995967);
    • 目标元素是'Bar' (HashCode=53937671);
    • 目标属性是“Baz”(类型“Baz”)`

最终绑定失败 - 为什么?

问题的关键

我有一个相当复杂的ColorModel 类,我在其中继承了DependencyObjectINotifyPropertyChanged

[Serializable]
public class ColorModel : DependencyObject, INotifyPropertyChanged {

}

在这个类中有 4 个双精度值 - AlphaRedGreenBlue

//The latter 3 DependencyProperties pretty much are the same as this : 
public static readonly DependencyProperty
    AlphaProperty = DependencyProperty.Register( "Alpha", typeof(double),   
        typeof(ColorModel), new PropertyMetadata(1.0D));

public double Alpha {
    get { return ( double )this.GetValue( AlphaProperty ); }
    set {
        this.SetValue( AlphaProperty, value );
        this.T = Task.WhenAll( new Task[ ] {
            this.OnPropertyChanged( "Alpha" ),
            this.OnPropertyChanged( "Color" )
        } );
    }
}

然后是 OnPropertyChanged 方法:

protected async Task OnPropertyChanged( string v ) {
    if ( this.PropertyChanged != null )
        await this.PropertyChanged.Async(
            this, new PropertyChangedEventArgs( v ) ).DontBlock( );
}

最后我有了Color 属性:

public static readonly DependencyProperty
    ColorProperty = DependencyProperty.Register( "Color", typeof(Color), 
        typeof(ColorModel), new PropertyMetadata(Colors.Black));

/// <summary>
/// Get or Set Color Property.
/// </summary>
public Color Color {
    get { return ( Color )this.GetValue( ColorProperty ); }
    set {
        this.SetValue( ColorProperty, value );
        this.T = Task.WhenAll( new Task[ ] {
            this.OnPropertyChanged("Alpha").DontBlock( ),
            this.OnPropertyChanged("Red").DontBlock( ),
            this.OnPropertyChanged("Green").DontBlock( ),
            this.OnPropertyChanged("Blue").DontBlock( ),
            this.OnPropertyChanged("Color").DontBlock( )
        } ).DontBlock( );
    }
}

不要介意与这个问题无关的异步恶作剧,我已经构建了一个控件,它应该允许用户控制单一颜色的所有方面:

我将每个滑动条双向绑定到我的 ColorModel 的 A/R/G/B 属性:

<Slider
    Value="{Binding Alpha, Mode=TwoWay}" .../>

最后,在这个控件 XAML 中,我定义了 DataContext:

<UserControl.DataContext>
    <Controls:ColorModel>
        <Controls:ColorModel.Color>
            <MultiBinding Converter="{StaticResource CMC}">
                <Binding Path="Alpha" Mode="TwoWay" Source="{RelativeSource Self}"/>
                <Binding Path="Red" Mode="TwoWay" Source="{RelativeSource Self}"/>
                <Binding Path="Green" Mode="TwoWay" Source="{RelativeSource Self}"/>
                <Binding Path="Blue" Mode="TwoWay" Source="{RelativeSource Self}"/>
            </MultiBinding>
        </Controls:ColorModel.Color>
    </Controls:ColorModel>
</UserControl.DataContext>

这是我正在使用的转换器:

/// <summary>
/// Class For Converting Colors To/From Raw Values.
/// </summary>
public class ColorMultiConverter : IMultiValueConverter {
    /// <summary>
    /// Convert incoming raw color values into a single color.
    /// </summary>
    /// <param name="values">
    /// [0] : Alpha
    /// [1] : Red
    /// [2] : Green
    /// [3] : Blue
    /// </param>
    /// <param name="targetType">Color</param>
    /// <param name="parameter"></param>
    /// <param name="culture"></param>
    /// <returns></returns>
    public object Convert(
        object[ ] values,
        Type targetType,
        object parameter,
        CultureInfo culture ) {
        return values.Contains( DependencyProperty.UnsetValue )
                ? DependencyProperty.UnsetValue
                : Color.FromScRgb(
                    DoubleToFloat( ( double )values[ 0 ] ),
                    DoubleToFloat( ( double )values[ 1 ] ),
                    DoubleToFloat( ( double )values[ 2 ] ),
                    DoubleToFloat( ( double )values[ 3 ] ) );
    }

    public object[ ] ConvertBack(
        object value,
        Type[ ] targetTypes,
        object parameter,
        CultureInfo culture ) {
        if ( value == DependencyProperty.UnsetValue )
            return new object[ ] {
                DependencyProperty.UnsetValue,
                DependencyProperty.UnsetValue,
                DependencyProperty.UnsetValue,
                DependencyProperty.UnsetValue
            };
        Color C = ( Color )value;
        return new object[ ] {
            FloatToDouble( C.ScA ),
            FloatToDouble( C.ScR ),
            FloatToDouble( C.ScG ),
            FloatToDouble( C.ScB )
        };
    }

    private float DoubleToFloat( double d ) { return System.Convert.ToSingle( d ); }
    private double FloatToDouble( float f ) { return System.Convert.ToDouble( f ); }
}

令我非常懊恼和完全没有惊喜的是,它惨遭失败。当我使用控件加载窗口时,我收到以下错误消息:

  • System.Windows.Data 警告:40:
    • BindingExpression 路径错误:在“对象”“RelativeSource”(HashCode=38995967)上找不到“Alpha”属性。 BindingExpression:Path=Alpha;
    • DataItem='RelativeSource' (HashCode=38995967);
    • 目标元素是'ColorModel' (HashCode=53937671);
    • 目标属性是“颜色”(类型“颜色”)`

在最长的引入之后,这让我们想到了我的问题 - 我如何将模型的一个属性(或 Multi-Bind,或...无论如何)绑定到该模型的同一实例的另一个属性型号?

【问题讨论】:

    标签: c# wpf xaml data-binding datacontext


    【解决方案1】:

    好的,所以我能够找到一种方法来做到这一点,但至少就我有限的理解而言,我能够做到这一点的唯一方法是在 ColorModel 的构造函数中。

    此外,我还在模型的 DependencyProperties 中添加了一滴代码。

    public static readonly DependencyProperty
        ColorProperty = DependencyProperty.Register(
            "Color",
            typeof( Color ),
            typeof( ColorModel ),
            new PropertyMetadata(
                Colors.Black,
                async ( S, E ) =>
                    await ( S as ColorModel ).OnPropertyChanged( "Color" ).DontBlock( ) ) ),
        AlphaProperty = DependencyProperty.Register(
            "Alpha",
            typeof( double ),
            typeof( ColorModel ),
            new PropertyMetadata(
                1.0D,
                async ( S, E ) =>
                    await ( S as ColorModel ).OnPropertyChanged( "Alpha" ).DontBlock( ) ) );
    

    这些 DependencyProperties 触发对底层 ColorModels OnPropertyChanged 方法的调用,以便通过 INotifyPropertyChanged 接口绑定的任何内容也会被通知(未经测试,可能很危险,并且可能通过燃烧的死亡螺旋导致灾难性故障无限燃烧循环)。

    然后在构造函数中我添加了以下代码:

    public ColorModel( ) {
        MultiBinding MB = new MultiBinding( );
        MB.Converter = new ColorMultiConverter( );
    
        MB.Bindings.Add( new Binding( "Alpha" ) { Source = this, Mode = BindingMode.TwoWay } );
        MB.Bindings.Add( new Binding( "Red" ) { Source = this, Mode = BindingMode.TwoWay } );
        MB.Bindings.Add( new Binding( "Green" ) { Source = this, Mode = BindingMode.TwoWay } );
        MB.Bindings.Add( new Binding( "Blue" ) { Source = this, Mode = BindingMode.TwoWay } );
    
        BindingOperations.SetBinding( this, ColorProperty, MB );
    }
    

    这是我能想到的最佳解决方案 - 根据另一个答案,尝试使用 RelativeSource 仅适用于 VisualTree 元素,所以我想这解释了为什么当我尝试在 XAML 中这样做时它失败了。

    【讨论】:

      【解决方案2】:

      RelativeSource 仅适用于 VisualTree 元素。它不适用于普通的类/属性场景。因此,在您的情况下,RelativeSource 将点击应用 DataContext 的控件。

      CoerceValueCallbackPropertyChangedCallback 用于强制属性之间的关系。

      例如,

      Slider 控件对其SelectionStartSelectionEnd 属性使用值强制。

      Slider.SelectionStartProperty = DependencyProperty.Register("SelectionStart", typeof(double), typeof(Slider), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(Slider.OnSelectionStartChanged), new CoerceValueCallback(Slider.CoerceSelectionStart)), new ValidateValueCallback(Slider.IsValidDoubleValue));
      
      private static object CoerceSelectionStart(DependencyObject d, object value)
      {
          Slider slider = (Slider)d;
          double num = (double)value;
          double minimum = slider.Minimum;
          double maximum = slider.Maximum;
          if (num < minimum)
          {
              return minimum;
          }
          if (num > maximum)
          {
              return maximum;
          }
          return value;
      }
      

      同样,你也可以在你的虚拟机中实现多个属性依赖。

      几个链接:

      Dr. Wpf

      【讨论】:

      • 滑块与ColorModel 有什么关系?我可以将此模型实现到任何控件中,无论滑块是否存在(或不存在),并尝试将ColorModels 属性绑定到它自己的其他属性,我敢打赌我仍然会看到这个错误出现。
      • @Will 当您将使用CoerceValueCallbak 时,则根本不需要您的Binding
      • 感谢您向我解释为什么 RelativeSource 不起作用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-06
      • 1970-01-01
      • 2016-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多