Binding ViewModel Property to ViewCell

LippelLippel DEMember ✭✭
edited September 21 in Xamarin.Forms

Hi,
I have a Switch Control in my ViewCell. The "IsToggled" property of the ViewCell should be bound to a bool property on my ViewModel.

ViewCell: (XAML)

<ViewCell>
            <Switch IsToggled="{Binding Path=BindingContext.IsTEQ, Source={x:Reference Name=myWonderfulPage}}" />
</ViewCell>

ViewModel: (C#)

Class "MyViewModel" with:
- INPC Property bool IsTEQ
- INPC ObservableCollection<object> MasterMenuItemList

View: (C#)

public MyPage()
        {
            InitializeComponent();
            BindingContext = new MyViewModel();
        }

View: (XAML)

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyProject.Layout.Pages.MyPage"
<!-- [...] -->
             x:Name="myWonderfulPage">

    <!-- declare name to bind IsTEQ to this pages binding context HERE? -->

    <ContentPage.Resources>
        <ResourceDictionary>
            <DataTemplate x:Key="template1">
                <vc:Template1ViewCell/>
            </DataTemplate>
            <DataTemplate x:Key="template2">
                <vc:Template2ViewCell/>
            </DataTemplate>
            <DataTemplate x:Key="template3">
                <vc:Template3ViewCell/>
            </DataTemplate>
            <DataTemplate x:Key="template4">
                <vc:Template4ViewCell/>
            </DataTemplate>
            <utils:MyWonderfulTemplateSelector x:Key="selector"
                Template1ViewCell="{StaticResource template1}"
                Template2ViewCell="{StaticResource template2}"
                Template3ViewCell="{StaticResource template3}"
                Template4ViewCell="{StaticResource template4}" />
        </ResourceDictionary>
    </ContentPage.Resources>


    <ContentPage.Content>
            <ListView x:Name="listView"
                      [...]
                      ItemsSource="{Binding MasterMenuItemList}"
                      ItemTemplate="{StaticResource selector}">
            </ListView>
    </ContentPage.Content>
</ContentPage>

I thought the solution given in this thread would solve my Issue:
https://forums.xamarin.com/discussion/58709/context-actions-command-binding-with-parameter

As you can see, I hopefully did it the right way, sadly it does not work.
I get the error:
"System.Exception: Can't resolve name on Element"

It probably comes due to the fact that I use the template selector to create the ViewCell I have shown above.

How can I bind my ViewCells Switch "IsToggled" to a property on the ViewModel?

Best Answer

  • LippelLippel DEMember ✭✭
    edited September 22 Accepted Answer

    I did it! Thanks so much to @ClintStLaurent & @NMackay !
    In the end, the solution found in the post provided by NMackay made it.

    Facts:
    DataTemplate selectors break the usage of being able to bind from a viewcell directly to the viewmodel of the list it belongs to.

    So this is not working: (where "myWonderfulPage" is the Page that holds the ListView)
    <Switch IsToggled="{Binding Path=BindingContext.VMBOOL, Source={x:Reference Name=myWonderfulPage}}" />

    What I did to solve it:

    • extend the viewcell with a property called "ParentBindingContext" (1)
    • make the datatemplate selector set the "ParentBindingContext" property to the viewmodel (1)
    • Bind my switch to the "ParentBindingContext.VMBOOL" (2)

    (1) https://forums.xamarin.com/discussion/71462/datatemplate-with-templateselector-bind-to-parent-command
    (2) extended viewcell xaml: (dunno how to fix layout :( )

    <local:ExtendedViewCell xmlns="http://xamarin.com/schemas/2014/forms" 
    

    [...]
    x:Name="thisVC">

                <Switch Grid.Column="2" IsToggled="{Binding ParentBindingContext.VMBOOL, Source={x:Reference thisVC}}" />
    
    </local:ExtendedViewCell>
    

    Note: It actually does not bind to the BindingContext, but rather a property on the View itself.

Answers

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭
    edited September 21

    I have a Swich in my ViewCell. The "IsToggled" property of the ViewCell should be bound to a bool property on my ViewModel.

    Are you sure about that? Shouldn't it be binded to a property on the MODEL - the element in the collection that the ListView is using as its .Source ?

    If you bind the the ViewCell to a property on the ViewModel, then every element in the ListView will have the same value: All on and all off together. What would be the point of putting it in the List at all then? Might as well have a single switch outside the ListView.

    But if you want to take a 30 minute tutorial tour... I do a LIstView binding in my RedPillXamarin series.

  • LippelLippel DEMember ✭✭

    The thing is that I have 4 different Templates in the List.
    One of them should include a Switch that should make the ViewModel add/remove two more items to the list, based on the IsToggled.
    I attached a screenshot for clarification

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    So that is a per item need. Right? Therefore it would be a property on the item in the collection, not on the ViewModel that contains the collection (The ViewModel for the entire ContentPage or ContentView)

    IE: If the 4th element in the collection has its IsMale property set to true, then show this additional subsection for that element.

  • LippelLippel DEMember ✭✭
    edited September 21

    Ok so right now the ViewCells BindingContext is an item (a Model) from the ObservableCollection in the ViewModel.
    I can ofcourse make the Switch change a bool on the Model. But how would my Model trigger a method on the ViewModel then?

    EDIT: Yes what you just said is completely right, if the element has its "IsMale" changed, then show XY.

    So how to make a method call on ViewModel when the Models "IsMale" changes.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭
    edited September 21

    So how to make a method call on ViewModel when the Models "IsMale" changes.

    I think that's the flaw in your design. You shouldn't need to execute a method on the VM just to show those additional items. The VM shouldn't be trying to micro-manage every element in the collection.

    But let's say there is a valid reason for it. Maybe you have to go fetch data from a server based on that specific element's ID number or something. You could have the model raise a Command and use some unique object as the CommandParameter - Maybe the even the element itself as the parameter... That way the command handler method can take whatever info it needs such as ID, Gender, age... whatever.

    The model doesn't know or care what class handles the Command - that's not its job. It just hollars out to the universe "WidgetCommand" and let's the responsible party do their thing.

  • LippelLippel DEMember ✭✭

    In my situation, I already have a filled list that contains the two items that should be dynamically removed/added as well.
    So no REST-Service calls or anything is needed.

    Who should manage the collection if not the VM?

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    The collection shouldn't need to be managed.
    The VM holds the collection. Its a property like any other. What management needs to be done to it?

    The individual objects in the collection should be taking care of themselves. The VM is little more than a container for all the properties that are related to the same need/feature.

  • LippelLippel DEMember ✭✭
    edited September 21

    The collection has for example 10 Elements. Based on the Bool of one Element (the switch) there are 2 Elements added/removed and supposed to be displayed/not displayed.

    The elements themselves dont really change, the amount of elements of the collection changes.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭
    edited September 21

    So if you have 10 dogs in your collection. And one of the dogs is male. You add 2 more dogs to the parent collection?
    Or...
    If 1 of the dogs is male, you add two properties to that dog?

    Because that screen shot looks like the two added items are a subsection of the individual element - not the entire collection. (isn't that why they are indented?)

  • LippelLippel DEMember ✭✭
    edited September 21

    No they are not intended to be subsections, they should belong to the main collection

    EDIT: If I were to have these 2 dynamically added/removed elements as children of this Switch-Element, then i would end up having a listview inside a listview, am I correct?

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    @Lippel said:
    No they are not intended to be subsections, they should belong to the main collection

    Ok. Well how elements get added to the main collection shouldn't matter. If they come from a switch throw... from a REST event... From someone getting a new high score... Its all the same thing: Add a new element. So you need a command for AddNewDoohickyCommand. Then all the actions that could trigger that just raise that command. If one of those happens to be a model already in the collection: Cool.

    EDIT: If I were to have these 2 dynamically added/removed elements as children of this Switch-Element, then i would end up having a listview inside a listview, am I correct?

    Well... They could be a collection and thus shown by a ListView. They could be anything really though. The two additional things are just properties. Maybe its HairColor and FoodAlergy properties of that specific Dog element. But if it were say... geneology. And the two new items were true children of the parent dog... then yeah, it could be a ListVIew within a ListView... sure.

  • LippelLippel DEMember ✭✭

    Ok so if I understood correctly:

    ViewCells Switch "IsToggled" should trigger a command. The Command will execute a method on the ViewModel "UpdateList()" for example?

    I dont unterstand how the command will trigger the ViewModel method. I know about commands tho.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    I dont unterstand how the command will trigger the ViewModel method.
    I know about commands tho.

    Those two statements are in conflict with each other.

  • LippelLippel DEMember ✭✭

    :D Ok i mean, i used commands before, bound a command from a view to a viewmodel.
    But Here it will be from view to model to viewmodel, cause the collection is on the viewmodel.
    Again, how will a command help me if i dont know how to make the view make a command call to the viewmodel

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    But Here it will be from view to model to viewmodel,

    What's the view got to do with it?

    The model has a bool property. When that gets toggled (regardless whether it is from the switch or some calculation or whatever... The set method of the property can raise the event.

    You don't want to do things based on the UI. UI is a human convenience. You want to do things based on the code... properties... When the property changes, then do something. The UI is just a reflection of the state of the properties and a way for the user to interact with the objects. UI is a side affect - not the cause.

  • LippelLippel DEMember ✭✭

    Ok ty :) i knew this. But look, i wanted to make the IsToggled on the view change the property on the ViewModel. That would be handy, because the collection is on the viewmodel.

    Now your point is, that the IsToggled will change the property on the model. Thats completely OK. But how will the changing property on the model trigger my "UpdateList()" on the viewmodel?

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    We're going around in circles now.
    The command raised by the model, is handled by the ViewModel.

  • LippelLippel DEMember ✭✭

    Yeah we are indeed.
    Can you show me a pseudo code example of how the Command triggered in the Model is handled in the ViewModel?

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    I'll put something together over the weekend. I can't get to it before then.

  • LippelLippel DEMember ✭✭

    Alright fair enough, thank you!

  • LippelLippel DEMember ✭✭

    Update: I found out that my approach seems to break if used with a DataTemplate Selector... I will make a MessagingCenter call from the Model and make the ViewModel Subscribe to it. This is pretty inelegant, but for the sake of time it fixes my issue well enough.

  • NMackayNMackay GBInsider, University ✭✭✭✭✭

    Maybe your running into this issue, RelativeSource binding goes a bit wonky when switching the data template, well in Radlist anyway.

    https://forums.xamarin.com/discussion/71462/datatemplate-with-templateselector-bind-to-parent-command

  • LippelLippel DEMember ✭✭
    edited September 22

    @NMackay
    this looks like the perfect solution, sadly I dont get it to work, what am I missing?
    I created the view cell property "ParentBindingContext" as mentioned in your post. I checked if it will be assigned correctly at runtime. It does.
    Still the IsToggled is looking for the bool on its binding context, rather than on the ParentBindingContext property.

    <Switch IsToggled="{Binding ParentBindingContext.IsTEQ}" />

    Error:
    Binding: 'IsTEQ' property not found on 'MyProject.Models.MyViewCellBindingContextModel', target property: 'Xamarin.Forms.Switch.IsToggled'

    It should actually look for the property at:
    thisViewCell.ParentBindingContext.IsTEQ where ParentBindingContext is my ViewModel class.

    It seems to ignore the "ParentBindingContext" part in the XAML Binding.

    EDIT: oh yeah i think its because it automatically looks for the binding in the BindingContext, but the "ParentBindingContext" property is rather a property on the ViewCell, but not of the bindingcontext

  • NMackayNMackay GBInsider, University ✭✭✭✭✭

    @Lippel

    You'll need to play about with it, I'm just suggesting this may be part of your issue, I don't have time to work on a repo sample of this. You have a workaround in the interim.

  • LippelLippel DEMember ✭✭
    edited September 22 Accepted Answer

    I did it! Thanks so much to @ClintStLaurent & @NMackay !
    In the end, the solution found in the post provided by NMackay made it.

    Facts:
    DataTemplate selectors break the usage of being able to bind from a viewcell directly to the viewmodel of the list it belongs to.

    So this is not working: (where "myWonderfulPage" is the Page that holds the ListView)
    <Switch IsToggled="{Binding Path=BindingContext.VMBOOL, Source={x:Reference Name=myWonderfulPage}}" />

    What I did to solve it:

    • extend the viewcell with a property called "ParentBindingContext" (1)
    • make the datatemplate selector set the "ParentBindingContext" property to the viewmodel (1)
    • Bind my switch to the "ParentBindingContext.VMBOOL" (2)

    (1) https://forums.xamarin.com/discussion/71462/datatemplate-with-templateselector-bind-to-parent-command
    (2) extended viewcell xaml: (dunno how to fix layout :( )

    <local:ExtendedViewCell xmlns="http://xamarin.com/schemas/2014/forms" 
    

    [...]
    x:Name="thisVC">

                <Switch Grid.Column="2" IsToggled="{Binding ParentBindingContext.VMBOOL, Source={x:Reference thisVC}}" />
    
    </local:ExtendedViewCell>
    

    Note: It actually does not bind to the BindingContext, but rather a property on the View itself.

Sign In or Register to comment.