【问题标题】:Child properties update calling it's parent's `OnPropertyChanged`子属性更新调用它的父的`OnPropertyChanged`
【发布时间】:2018-08-06 04:07:53
【问题描述】:

我正在尝试创建一个 XF 组件,它的某些属性是继承自 BindableObject 的类型。为了说明,我有一个类Shadow 具有double RadiusColor ShadowColor 属性和一个类MyBoxText,它具有一个bool IsLoading 和一个Shadow Ghost 属性。

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

当我更改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();
    }
}

问题是,当我更改 Ghost.ShadowColor 属性时,我的“OnElementPropertyChanged”覆盖没有被调用,并且视图在屏幕上保持旧颜色。

有没有办法将孩子的“属性更新”事件传播到父视图“属性已更改”或其他方式来实现这一点?

【问题讨论】:

    标签: c# xamarin binding xamarin.android custom-controls


    【解决方案1】:

    问题是,当我更改 Ghost.ShadowColor 属性时,不会调用我的“OnElementPropertyChanged”覆盖,并且视图会在屏幕上保持旧颜色。 有没有办法将孩子的“属性更新”事件传播到父视图“属性已更改”或其他方式来实现这一点?

    是的,有办法。由于您的 Shadow 继承自 BindableObject,它实现了 INotifyPropertyChanged 接口。你可以设置通知ShadowColor更改:

    1. 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();
              }
          }
         ...
      }
      
    2. 像这样修改你的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
              };
          }
      }
      

    【讨论】:

    • 它看起来确实是一个解决方案,完全符合我的需要,但它有一个小错误:当我从 XAML 的绑定中设置 MyBoxText.Ghost 时,它没有命中 set方法(我们开始听的地方)...我会根据这个解决方案进行一些尝试,我会告诉你的。感谢您的时间和精力。
    • 我也绕过了在get 方法中删除和添加侦听器的错误。现在可以正常使用了,谢谢。我根据您的回答制作了一个单独的课程以重用该解决方案,我将在此处发布它只是为了进一步重用。
    【解决方案2】:

    感谢猫王的回答,我明白了。根据他的想法,我进行了一些更改以在其他组件上重用它,我现在分享它以防其他人需要这样的东西。

    我认为这样使用它可以得到更简洁的代码:

    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
    }
    

    这是ChangingPropagator 类:

    public class ChangingPropagator
    {
        string[] listenedProperties = new string[0];
        Action<string> changesNotifyer = null;
        BindableObject propagationRootObject = null;
        List<KeyValuePair<string, object>> propagationProperties = new List<KeyValuePair<string, object>>();
    
        public ChangingPropagator(BindableObject bindableObject, Action<string> onPropertyChangedMethod, params string[] propertyToListenTo)
        {
            changesNotifyer = onPropertyChangedMethod;
            propagationRootObject = bindableObject;
            listenedProperties = propertyToListenTo ?? listenedProperties;
    
            // ToDo: Add some consistency checks
        }
    
        public void AddPropertyToListenTo(params string[] propertyName)
        {
            listenedProperties = listenedProperties.Union(propertyName).ToArray();
        }
    
        // I need handle it here too 'cause when I use the child `Ghost` property coming from XAML binding, it didn't hit the `set` method
        public T GetValue<T>(BindableProperty property)
        {
            var value = propagationRootObject?.GetValue(property);
    
            if (value != null)
            {
                INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
    
                if (bindableSubObject != null)
                {
                    bindableSubObject.PropertyChanged -= PropagatorListener;
                    bindableSubObject.PropertyChanged += PropagatorListener;
    
                    if (!propagationProperties.Any(a => a.Key == property.PropertyName))
                        propagationProperties.Add(new KeyValuePair<string, object>(property.PropertyName, value));
                }
            }
    
            return (T)value;
        }
    
        public void SetValue<T>(BindableProperty property, ref T value)
        {
            var oldValue = propagationRootObject?.GetValue(property);
    
            if (oldValue != null)
            {
                INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
    
                if (bindableSubObject != null)
                    bindableSubObject.PropertyChanged -= PropagatorListener;
            }
    
            if (value != null)
            {
                INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
                if (bindableSubObject != null)
                {
                    bindableSubObject.PropertyChanged += PropagatorListener;
    
                    propagationProperties.RemoveAll(p => p.Key == property.PropertyName);
                    propagationProperties.Add(new KeyValuePair<string, object>(property.PropertyName, value));
                }
            }
    
            propagationRootObject.SetValue(property, value);
        }
    
        private void PropagatorListener(object sender, PropertyChangedEventArgs e)
        {
            if (listenedProperties?.Contains(e.PropertyName) ?? true)
                PropagationThrower(sender);
        }
    
        private void PropagationThrower(object sender)
        {
            if (propagationProperties.Any(p => p.Value == sender))
            {
                var prop = propagationProperties.FirstOrDefault(p => p.Value == sender);
                changesNotifyer?.Invoke(prop.Key);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2023-01-27
      • 2017-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-10
      • 2018-09-26
      • 2018-12-13
      相关资源
      最近更新 更多