Reactive Extensions for nested objects in Exrin VisualState

62 Views Asked by At

I'm trying to enable a button that is bounded when a binded property nested field is entered, but the button is disabled even when all properties are filled with data. What should I change?

this is my base visual state

public class BaseVisualState : Exrin.Framework.VisualState
{
public BaseVisualState() : this(null)
{
}

public BaseVisualState(IBaseModel i_Model) : base(i_Model)
{
PropertyObservable = Observable.FromEventPattern(
                (EventHandler<PropertyChangedEventArgs> i_Event) => new PropertyChangedEventHandler(i_Event),
                i_EventChanged => this.PropertyChanged += i_EventChanged,
                i_EventChanged => this.PropertyChanged -= i_EventChanged);
        }

        public IObservable<EventPattern<PropertyChangedEventArgs>> PropertyObservable { get; private set; }
    }

and my visual state is

public class BeaconAddVisualState : BaseVisualState
    {
        private readonly IBeaconAddModel r_Model;

        public BeaconAddVisualState(IBeaconAddModel i_Model)
            : base(i_Model)
        {
            r_Model = i_Model;
        }

        [Binding(BindingType.TwoWay)]
        public Beacon Beacon
        {
            get
            {
                return Get<Beacon>();
            }

            set
            {
                Set(value);
            }
        }

        public override async void Init()
        {
            Beacon = new Beacon();
        }
    }

The beacon class notifies on property changed

public class Beacon : Document<Guid>, INotifyPropertyChanged
    {
        private string m_ProximityUuid;
        private int? m_Major;
        private int? m_Minor;

        [Required]
        public string ProximityUuid
        {
            get
            {
                return m_ProximityUuid;
            }

            set
            {
                if(m_ProximityUuid != value)
                {
                    m_ProximityUuid = value;
                    notifyPropertyChanged();
                }
            }
        }

        [Required]
        public int? Major
        {
            get
            {
                return m_Major;
            }

            set
            {
                if (m_Major != value)
                {
                    m_Major = value;
                    notifyPropertyChanged();
                }
            }
        }

        [Required]
        public int? Minor
        {
            get
            {
                return m_Minor;
            }

            set
            {
                if (m_Minor != value)
                {
                    m_Minor = value;
                    notifyPropertyChanged();
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        // This method is called by the Set accessor of each property.
        // The CallerMemberName attribute that is applied to the optional propertyName
        // parameter causes the property name of the caller to be substituted as an argument.
        private void notifyPropertyChanged([CallerMemberName] string i_PropertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(i_PropertyName));
            }
        }
    }

and this is the viewmodel

public class BeaconAddViewModel : BaseAuthViewModel
    {
        private readonly IBeaconAddModel r_Model;
        private readonly IDisposable r_Subscription;

        public BeaconAddViewModel(IBeaconAddModel i_Model)
            : base(new BeaconAddVisualState(i_Model))
        {
            r_Model = i_Model;
            r_Subscription = State.PropertyObservable.Where(
                i_Arg => i_Arg.EventArgs.PropertyName == nameof(State.Beacon.ProximityUuid)
                         || i_Arg.EventArgs.PropertyName == nameof(State.Beacon.Major) || i_Arg.EventArgs.PropertyName == nameof(State.Beacon.Minor)).Subscribe(
                i_Arg =>
                    {
                        AddCommand.OnCanExecuteChanged();
                    });
        }

        private BeaconAddVisualState State => VisualState as BeaconAddVisualState;

        public override Task OnNavigated(object i_Args)
        {
            return base.OnNavigated(i_Args);
        }

        public IRelayCommand AddCommand
        {
            get
            {
                return GetCommand(
                    () =>
                        {
                            return new RelayCommand(
                                async (i_Parameter) =>
                                    {
                                        await r_Model.AddBeacon(i_Parameter as Beacon);
                                        await NavigationService.Navigate("Beacons");
                                    }, (i_Obj) => !string.IsNullOrEmpty(State.Beacon.ProximityUuid) && State.Beacon.Major.HasValue && State.Beacon.Minor.HasValue;
                        });
            }
        }

        public override void Disposing()
        {
            base.Disposing();
            r_Subscription?.Dispose();
        }
    }
2

There are 2 best solutions below

3
On

The problem here is you are trigger INPC inside your Beacon class, but the BaseVisualState is only looking at whether Beacon (the object itself) is changing.

Hence you either have to bring the properties outside of Beacon, directly into the VisualState, or relay the INPC event.

e.g. in your Set of Beacon do

var beacon = value;
Set(value);
value.OnPropertyChanged += (s,e) => { OnPropertyChanged(nameof(Beacon)); }

That will mean, each time any property in Beacon is changed, it will say that the Beacon class itself has changed, and trigger the INPC for the VisualState.

Note: make sure the event is disposed on, when it's reset. This would mean not doing it in an anon func as I have shown above, but doing the += and tabbing it to create another method.

0
On

Thanks Adam for the help. this what id did in the end, maybe it can help someone else

public class BeaconAddVisualState : BaseVisualState
    {
        private readonly IBeaconAddModel r_Model;
        private Beacon m_Beacon;

        public BeaconAddVisualState(IBeaconAddModel i_Model)
            : base(i_Model)
        {
            r_Model = i_Model;
        }

        [Binding(BindingType.TwoWay)]
        public Beacon Beacon
        {
            get
            {
                return m_Beacon;
            }

            set
            {
                if(m_Beacon != value)
                {
                    if(m_Beacon != null)
                    {
                        m_Beacon.PropertyChanged -= m_Beacon_PropertyChanged;
                    }

                    m_Beacon = value;
                    m_Beacon.PropertyChanged += m_Beacon_PropertyChanged;
                    OnPropertyChanged();
                }
            }
        }

        public override async void Init()
        {
            Beacon = new Beacon();
        }

        public override void Disposing()
        {
            if (m_Beacon != null)
            {
                m_Beacon.PropertyChanged -= m_Beacon_PropertyChanged;
            }

            base.Disposing();
        }

        private void m_Beacon_PropertyChanged(
            object i_Sender,
            System.ComponentModel.PropertyChangedEventArgs i_EventArgs)
        {
            OnPropertyChanged(nameof(Beacon));
        }
    }