1

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();
        }
    }
Adam
  • 16,089
  • 6
  • 66
  • 109

2 Answers2

0

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.

Adam
  • 16,089
  • 6
  • 66
  • 109
  • when it's reset, you mean to override the disposing method of visualstate right? – Maxim Rubchinsky Aug 21 '17 at 08:39
  • I'm referring to the event handler you would be putting in the Set of the Beacon property. Make sure you unhook that event. e.g. value.OnPropertyChanged -= – Adam Aug 21 '17 at 08:41
  • Yes you would do it on the Disposing of the VisualState, and, you need to do it, every time you Set that property. So check if it's null first, if it's not, remove the existing event handler. – Adam Aug 21 '17 at 08:57
0

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