C# 子属性更新调用它';s的父母';s`OnProperty已更改`

C# 子属性更新调用它';s的父母';s`OnProperty已更改`,c#,xamarin,binding,xamarin.android,custom-controls,C#,Xamarin,Binding,Xamarin.android,Custom Controls,我正在尝试创建一个XF组件,它的某些属性属于继承自BindableObject的类型。为了进行说明,我使用了具有双半径和颜色阴影颜色属性的类阴影,以及具有bool IsLoading和阴影阴影属性的类MyBoxText 我的视图和它的绑定按预期工作,但它的自定义渲染器有问题: 例如,当我更改Ghost属性时,我需要重新绘制整个视图(在MyBoxText控件中),以直观地更新阴影颜色 以下是一些mcve: 类别代码: public class MyBoxText : Label /* It's b

我正在尝试创建一个XF组件,它的某些属性属于继承自
BindableObject
的类型。为了进行说明,我使用了具有
双半径
颜色阴影颜色
属性的类
阴影
,以及具有
bool IsLoading
阴影阴影
属性的类
MyBoxText

我的
视图
和它的
绑定
按预期工作,但它的自定义渲染器有问题:

例如,当我更改
Ghost
属性时,我需要重新绘制整个视图(在
MyBoxText
控件中),以直观地更新阴影颜色

以下是一些mcve:

类别代码:

public class MyBoxText : Label /* It's bindable by inheritance */
{
    #region Properties
    public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(MyBoxText), false) ;
    public bool IsLoading
    {
        get { return (bool)GetValue(IsLoadingProperty); }
        set { SetValue(IsLoadingProperty, value); }
    }

    public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null) ;
    public Shadow Ghost
    {
        get { return (Shadow)GetValue(GhostProperty); }
        set { SetValue(GhostProperty, value); }
    }
    #endregion
}

public class Shadow : BindableObject /* It's explictly bindable */
{
    #region Properties
    public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(nameof(ShadowColor), typeof(Color), typeof(Shadow), Color.Black) ;
    public Color ShadowColor
    {
        get { return (Color)GetValue(ShadowColorProperty); }
        set { SetValue(ShadowColorProperty, value); }
    }

    public static readonly BindableProperty ShadowRadiusProperty = BindableProperty.Create(nameof(ShadowRadius), typeof(double), typeof(Shadow), 20) ;
    public double ShadowRadius
    {
        get { return (double)GetValue(ShadowRadiusProperty); }
        set { SetValue(ShadowRadiusProperty, value); }
    }
    #endregion

    public Shadow()
    {

    }
}
public class MyBoxText : LabelRenderer
{
    public MyBoxText()
    {
        SetWillNotDraw(false);
    }

    public override void Draw(Canvas canvas)
    {
        MyBoxText myView = (MyBoxText)this.Element;

        // Some drawing logic
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (e.PropertyName == MyBoxText.IsLoadingProperty.PropertyName  ||
            e.PropertyName == MyBoxText.GhostProperty.PropertyName )
            Invalidate();
    }
}
我的渲染器代码如下:

public class MyBoxText : Label /* It's bindable by inheritance */
{
    #region Properties
    public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(MyBoxText), false) ;
    public bool IsLoading
    {
        get { return (bool)GetValue(IsLoadingProperty); }
        set { SetValue(IsLoadingProperty, value); }
    }

    public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null) ;
    public Shadow Ghost
    {
        get { return (Shadow)GetValue(GhostProperty); }
        set { SetValue(GhostProperty, value); }
    }
    #endregion
}

public class Shadow : BindableObject /* It's explictly bindable */
{
    #region Properties
    public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(nameof(ShadowColor), typeof(Color), typeof(Shadow), Color.Black) ;
    public Color ShadowColor
    {
        get { return (Color)GetValue(ShadowColorProperty); }
        set { SetValue(ShadowColorProperty, value); }
    }

    public static readonly BindableProperty ShadowRadiusProperty = BindableProperty.Create(nameof(ShadowRadius), typeof(double), typeof(Shadow), 20) ;
    public double ShadowRadius
    {
        get { return (double)GetValue(ShadowRadiusProperty); }
        set { SetValue(ShadowRadiusProperty, value); }
    }
    #endregion

    public Shadow()
    {

    }
}
public class MyBoxText : LabelRenderer
{
    public MyBoxText()
    {
        SetWillNotDraw(false);
    }

    public override void Draw(Canvas canvas)
    {
        MyBoxText myView = (MyBoxText)this.Element;

        // Some drawing logic
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (e.PropertyName == MyBoxText.IsLoadingProperty.PropertyName  ||
            e.PropertyName == MyBoxText.GhostProperty.PropertyName )
            Invalidate();
    }
}
问题是,当我更改Ghost.ShadowColor属性时,不会调用我的“OnElementPropertyChanged”覆盖,并且视图在屏幕上保留旧颜色

是否有方法将子级的“属性更新”事件传播到父级视图“属性已更改”或其他方法来实现此目的

问题是,当我更改Ghost.ShadowColor属性时,不会调用我的“OnElementPropertyChanged”覆盖,视图将保留屏幕上的旧颜色。 是否有方法将子级的“属性更新”事件传播到父级视图“属性已更改”或其他方法来实现此目的

是的,有办法。因为您的阴影继承自
BindableObject
,后者实现了
INotifyPropertyChanged
接口。您可以设置通知
ShadowColor
更改:

  • OnPropertyChanged()
    添加到Shadow.cs中的
    ShadowColor
    的Setter中:

    public class Shadow : BindableObject /* It's explictly bindable */
    {
        #region Properties
        public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(nameof(ShadowColor), typeof(Color), typeof(Shadow), Color.Black);
        public Color ShadowColor
        {
            get { return (Color)GetValue(ShadowColorProperty); }
            set { SetValue(ShadowColorProperty, value);
                //Notify the ShadowColorProperty Changed
                OnPropertyChanged();
            }
        }
       ...
    }
    
  • 修改您的
    MyBoxText.cs
    如下:

    public class MyBoxText : Label /* It's bindable by inheritance */
    {
     ...
    
        public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null);
        public Shadow Ghost
        {
            get { return (Shadow)GetValue(GhostProperty); }
            set {
                //register the ShadowColor change event
                value.PropertyChanged += ShadowColor_PropertyChanged;
                SetValue(GhostProperty, value); }
        }
    
        private void ShadowColor_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            //unregister the event
            this.Ghost.PropertyChanged -= ShadowColor_PropertyChanged;
            //set this.Ghost to a new object with new ShadowColor to trigger the OnPropertyChanged
            this.Ghost = new Shadow
            {
                ShadowColor = (sender as Shadow).ShadowColor,
                ShadowRadius = Ghost.ShadowRadius
            };
        }
    }
    

  • 多亏埃尔维斯的回答,我才明白。基于他的想法,我做了一些修改,在其他组件上重用它,现在我正在共享它,以防其他人需要类似的东西

    我认为通过这种方式,我们可以得到更清晰、简单的代码:

    public class MyBoxText : Label /* It's bindable by inheritance */
    {
        // Added this as private property
        private ChangingPropagator changingPropagator;
        private ChangingPropagator ChangingPropagator
        {
            get
            {
                if (changingPropagator == null)
                    changingPropagator = new ChangingPropagator(this, OnPropertyChanged, nameof(Shadow.ShadowColor), nameof(Shadow.ShadowRadius));
    
                return changingPropagator;
            }
        }
    
        #region Properties
        public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(MyBoxText), false) ;
        public bool IsLoading
        {
            get { return (bool)GetValue(IsLoadingProperty); }
            set { SetValue(IsLoadingProperty, value); }
        }
    
        public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null) ;
        public Shadow Ghost
        {
            // Here I use the ChangingPropagator's Getter and Setter instead of the deafult ones:
            get { return ChangingPropagator.GetValue<Shadow>(GhostProperty); }
            set { ChangingPropagator.SetValue(GhostProperty,ref value); }
        }
        #endregion
    }
    

    它看起来确实像是一个解决方案,正如我所需要的,但它有一个小错误:当我从XAML上的绑定设置
    MyBoxText.Ghost
    时,它没有命中
    set
    方法(我们开始听的地方)。。。我将根据这些解决方案进行一些尝试,并让您知道。谢谢你的时间和努力。我也在
    get
    方法中消除并添加了侦听器,避免了这个错误。现在很好用了,谢谢。我根据您的答案创建了一个单独的类来重用该解决方案,为了进一步的重用,我将在这里发布它。