Firing method in the View when a property in the ViewModel changes

KrahosKrahos ITMember ✭✭
Hi all,
As the title says I would like to call a method in my View when a property in my ViewModel changes. I was wondering if this is possible to do it by using the INotifyPropertyChanged interface or if I should implement the observer - observable pattern on my own for this particular task.

Practical example: I have a StackLayout in the View and an integer property called number in the ViewModel, I want to call a method in the View which makes sure there are exactly n (where n is the value of number) entries in the StackLayout every time number changes.

Note: number is the output of a calculation in the model and it doesn't have to appear anywhere in the view.

Thanks to everyone in advance.
Tagged:

Best Answer

  • AlessandroCaliaroAlessandroCaliaro IT ✭✭✭✭✭
    Accepted Answer

    I think you can use MessagingCenter. When your property change, send a message to the view passing the "number" value.

Answers

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭
    Accepted Answer

    I think you can use MessagingCenter. When your property change, send a message to the view passing the "number" value.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    You got there first today @AlessandroCaliaro - MessagingCenter was where my mind went as well.

    @Krahos Here's a little more why on that advice.
    Remember that a ViewModel can be the backing to multiple Views. This is why the ViewModel doesn't know anything about the 0:n view(s) that may or may not be binded to it. So the ViewModel shouldn't directly try to tell a view what to do. Instead it should send out a message saying "Bob just had a birthday" or whatever the event was. Any listening class that cares about that can then react according to its own rules. When ViewAlpha hears that MessageCenter message it can flash blue... when the SoundManager class hears the same message it can play the trumpet noise... when ViewBravo hears the very same message, it will look up Bob's new age and update the Label to say 51 instead of 50. Or whatever each of these other subscribing classes are supposed to do.

    The point being, its not the job of the ViewModel to micro-manage what everyone else should do in response to something happening. We don't like that tight coupling. Instead it just yells out a message and goes about its business. Those listening to the message can then take care of their own responsibilities.

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭
    edited March 2018

    returning to this... I think there is another way, using "Action".

    For example, I would like to move the ListView to the last inserted item. In my ViewModel I have this

        public Action<PalletSpezzoneRootObject> OnPalletScannerizzato { get; set; }
    

    When the item is added to the ListView (to the ObservableCollection...) I execute this

         OnPalletScannerizzato?.Invoke(ListaPallet[idx]); //Now run the Action which, if it is not null, your ContentPage should have set to do the scrolling
    

    So I Invoke the action passing the item I have added (ListaPallet is an ObservableCollection).

    In my CodeBehind I have something like

            ((MyViewModel)this.BindingContext).OnPalletScannerizzato =((obj) => 
            { //We tell the action to scroll to the passed in object here
                lv.ScrollTo(obj, ScrollToPosition.MakeVisible, true);
            });
    

    Hope not to be wrong.....

  • KrahosKrahos ITMember ✭✭
    @AlessandroCaliaro I was infact hoping somehow to do it through the INotifyPropertyChanged interface so that I might avoid to insert in the ViewModel the "using Xamarin.Forms" directive which is, in my opinion, not ideal (since I want my ViewModel to know nothing about the View, including the technology I use to implement it). I gave up for now and used MessagingCenter since I want to conclude my project as quickly as possible, but I'll further investigate on this in the future.
    Thanks for your help.
  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    I was in fact hoping somehow to do it through the INotifyPropertyChanged interface

    If your View subscribes to the PropertyChanged event on the ViewModel for this then you are tightly coupling the View to the ViewModel

    I want my ViewModel to know nothing about the View

    Which is the correct thing to want.

    Coupling the other direction from View to ViewModel is equally bad just in the opposite direction.

    I gave up for now and used MessagingCenter

    Perfect!

  • KrahosKrahos ITMember ✭✭
    @ClintStLaurent I would disagree on it being bad because I just wanted to use the already implemented mechanism of binding in a slightly different way.
    The View is already observing the ViewModel and the ViewModel is already notifying the View through the NotifyPropertyChanged method call. Observer - observable pattern by textbook, even if it happens behind the scenes thanks to the binding.
    What I could have done is to bind to that integer property the text of an invisible entry and using the OnTextChanged event to call the function I want to call, but it is a really dirty choice.

    I wanted my View to observe that particular property but, instead of refreshing the related visual element, calling a method. In other (improper) words "binding an action to a property".
  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    What is the action you want to have happen?
    If its basically a change in UI you could put a DataTrigger on the binded property, in XAML.

    The View is already observing the ViewModel and the ViewModel is already notifying the View through the NotifyPropertyChanged method call. Observer - observable pattern by textbook, even if it happens behind the scenes thanks to the binding.

    Yes and no. If the BindingContext of the View changes to a different ViewModel then there is no harm. That's happens 100 times.
    If your view subscribes to the PropertyChanged event of a particular ViewModel instance and you don't properly unsubscribe you get a memory leak. If you change BindingContext and thus all the rest of the UI is pointing at instance FredFlintstone but the event subscription is still pointing at BarneyRuble, then your UI is out of sync with the data. Now you can work around that by the view handling the BindingContextChanged and then you unsubscribe from the old object and subscribe now to the new object. That is a cleaner work around. But still a work around.

    Which leads to this. I suspect this leads to that orphaned event subscription situation if the BindingContext for the view is changed. There is no mechanism to unsubscribe. Or at the very least, if you don't repeat this subscription within the OnBindingContextChanged handler, the action won't happen after you have a new object as the binding context. (I repeat that I suspect this; Until I have time to confirm my theory)
    @AlessandroCaliaro said:

    In my CodeBehind I have something like

            ((MyViewModel)this.BindingContext).OnPalletScannerizzato =((obj) => 
            { //We tell the action to scroll to the passed in object here
                lv.ScrollTo(obj, ScrollToPosition.MakeVisible, true);
            });
    
  • KrahosKrahos ITMember ✭✭
    @ClintStLaurent the action is indeed a change in the View. I have a StackLayout and I want it to have 3 children if the property value is 3, 4 if it's 4. A similar behaviour to a ListView but horizontal. I'll check the DataTrigger when I have time.

    And yes you might be right about everything else, infact I was asking if there was a proper way to do something like this. Thanks a lot.
  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    similar behaviour to a ListView but horizontal.

    There are a number of freely available horizontal ListVIew controls if that makes life easier
    Or just use listview. You can rotate the control 90° and rotate each element -90°, and taaa daaa you have a horizontal ListView.

  • UnreachableCodeUnreachableCode USMember ✭✭✭

    Adam Pedley goes into great length to discuss all of the options you can use: https://xamarinhelp.com/access-a-control-method-from-a-viewmodel/

    In particular, command chaining, using bindable properties is a good option that ensures the VM and View have no reference to eachother. It can be a bit hard to wrap your head around and you have to extend the control for it to work.

  • SteveShaw.5557SteveShaw.5557 USMember ✭✭✭
    edited July 17

    @ClintStLaurent - very good points.

    One comment re "If your view subscribes to the PropertyChanged event of a particular ViewModel (VM) instance and you don't properly unsubscribe you get a memory leak. If you change BindingContext ..."

    In my experience, most custom Views set their BindingContext exactly once, during construction/initialization. So there never is a later call to OnBindingContextChanged.

    As for memory leak, if the VM is only referenced by the View, then when the View is no longer used, both View and VM get discarded, so there is no leak, AFAIK. In general when using events, see Jon Skeet's answer re events and memory leaks - "However, in my experience this is rarely actually a problem - because typically I find that the publisher and subscriber have roughly equal lifetimes anyway.".
    CAVEAT: Jon isn't giving an answer that is always true regarding events; as a programmer you have to know how the publisher and subscriber(s) are being used, if you are going to rely on this. In particular, there is always the risk of creating a maintenance problem down the road, when code changes.

    Personally, I do sometimes, in View, attach to a VM event.
    The one risk that concerns me when I do so, is whether I am creating a sequence of code that would make it possible for that event to try to do something on the View, AFTER the View is no longer visible.
    Hence the importance of unsubscribing ("-=") in View's OnDisappearing.

    OTOH, I agree that "loose coupling", as you recommend, is a safer solution.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    @SteveShaw.5557 said:
    In my experience, most custom Views set their BindingContext exactly once, during construction/initialization. So there never is a later call to OnBindingContextChanged.

    Easy example.
    You have a control that shows the details of the SelectedPet. When SelectedPet changes a method subscribes to some event or another. Now a new Pet is selected from the Picker of ObserserbleCollection<Pet> . But no unsubscribe happened. Bam! Memory leak. Totally common scenario whether its selected pet, selected employee, selected truck....

    As for memory leak, if the VM is only referenced by the View, then when the View is no longer used, both View and VM get discarded, so there is no leak, AFAIK.

    Its a really common misconception dating all the way back to WinForms and MFC. Disposing of the subscriber and target objects (view and VM in this case but doesn't have to be) does NOT automatically disconnect the subscription and dispose of the subscription. You need to do that in your Dispose() method for the class or someplace else before disposing. The subscription is still present, still attached to a memory address - only now you have no object reference to use for the unsubscribe call. THAT is the leak. The subscription exists with no way to dispose of it.

    Personally, I do sometimes, in View, attach to a VM event.

    You know thats dirty and breaks good MVVM practice, right?

    The one risk that concerns me when I do so, is whether I am creating a sequence of code that would make it possible for that event to try to do something on the View,

    What? Your ViewModel is trying to do something on the View? Naaa... I'm reading that wrong, I hope. The VM is meant to be totally ignorant of however many views are using it as their BindingContext. What happens if you have 10 views all using the same VM? How do you decide which to affect in this dirty manner?

Sign In or Register to comment.