如何将模型属性绑定到该模型的同一实例的另一个属性?

时间:2016-02-29 03:46:35

标签: c# wpf xaml data-binding datacontext

TL; DR:

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

导致以下错误消息:

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

并最终导致绑定失败 - 为什么?

问题的关键

我有一个相当复杂的ColorModel课程,其中我继承了DependencyObject以及INotifyPropertyChanged

[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( );
    }
}

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

enter image description here

我将每个滑动条双向绑定到我的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路径错误:'object'''RelativeSource'(HashCode = 38995967)'上找不到'Alpha'属性。 BindingExpression:路径=字母;
    •   
    • DataItem ='RelativeSource'(HashCode = 38995967);
    •   
    • 目标元素是'ColorModel'(HashCode = 53937671);
    •   
    • target属性为'Color'(类型'Color')`
    •   
  •   

在有史以来最长的引导之后,我们将问题带到了我们 - 如何将模型的属性绑定(或多绑定,或者......等)到同一个实例的另一个属性模型?

2 个答案:

答案 0 :(得分:0)

好的,所以我能够找到一种方法来做到这一点但是,至少在我有限的理解方面,我能够做到的唯一方法是在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中。

答案 1 :(得分:-1)

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;
}

同样,您也可以在VM中实现多个属性依赖。

很少有链接:

Dr. Wpf