Correct XAML pattern to bind element to BindingContext property ('x:Name' binds to page not context)

Hi, I'm new to Xamarin, but I've already built a few pages and have got most of the binding working flawlessly.
I am, however, having an issue figuring out the proper way to bind an element in XAML (a Map to be precise) to a child property of the BindingContext (a ViewModel). I see that the "x:Name" attribute will bind it to a property of the page class, but not the context. All the other binding examples affect values of an element, but not the element object itself.
Is there a way to do this cleanly, or do I have to do what I'm doing now where the page class passes the element (Map) along into the ViewContext manually at startup?

Answers

  • KamrankhalilKamrankhalil ✭✭ USMember ✭✭

    @GlennPowell You should be able to bind directly to view model. Can you give example code, what you're trying to do ?

  • ChaseFlorellChaseFlorell mod CAInsider, University mod
    edited December 2017

    in code behind

    public class MyPage: Page {
        public MyPage() {
            InitializeComponent();
            BindingContext = new MyPageModel();
        }
    }
    
    public class MyPageModel : INotifyPropertyChanged {
        public string Name {
            get {  return _name; }
            set {
                _name = value;
                RaisePropertyChanged();
            }
        }
    }
    

    in Xaml

    <Label Text="{Binding Name}" />
    

    note: x:Name just exposes that name to the code behind.

    <Label x:Name="MyLabel" />
    
    public MyPage() {
        InitializeComponent();
        BindingContext = new MyPageModel();
        MyLabel.TextChanged += (sender, args) => {
            // do something when text changes.
        }
    }
    
  • GlennPowellGlennPowell USMember

    @ChaseFlorell - This code is essentially what I'm using now, but I'm trying to point the MyLabel XAML element to MyPageModel.MyLabel (Not MyPage.MyLabel, as it is right now). Then I don't have to manually pass the MyLabel from Page into PageModel.
    The { Binding ... } syntax works well to bind xaml-attributes directly to PageModel properties. Is there a way for the x:Name (or some other) xaml-attribute to bind the entire element to a PageModel property as well? (Note that this is specifically to bind a Map element to a local property on my ViewModel.)

  • JoeMankeJoeManke ✭✭✭✭✭ USMember ✭✭✭✭✭
    edited December 2017

    I'm not sure if I'm grasping the situation quite right, but I think this is what you're going for:

    You have a view model MyPageModel with a MyMapModel property. You want the map to bind to properties of the MyMapModel.

    public class MyPageModel
    {
        public MyMapModel MapModel { get; set; }
    }
    
    public class MyMapModel
    {
        public Position MapCenter { get; set; }
        public List<Position> PinPositions { get; set; }
    }
    

    If this is what you mean, you can take advantage of the fact that BindingContext is itself a bindable property.

    <ContentPage x:Class="MyPage">
        <Map BindingContext="{Binding MapModel}"/>
    </ContentPage>
    

    Unfortunately, the most useful properties of the Map class, Pins and VisibleRegion, are not bindable. So you will have to create those in the code-behind.

    <ContentPage x:Class="MyPage">
        <Map x:Name="MyMap"/>
    </ContentPage>
    
    public MyPage()
    {
        InitializeComponent();
    
        var viewModel = new MyPageModel();
        BindingContext = viewModel;
    
        MyMap.MoveToRegion(MapSpan.FromCenterAndRadius(viewModel.MapModel.MapCenter, Distance.FromMiles(1.0)));
        foreach(var position in viewModel.MapModel.PinPositions)
        {
            MyMap.Pins.Add(new Pin
            {
                Position = position
            });
        }
    }
    
  • GlennPowellGlennPowell USMember

    @JoeManke So I did see that you could set the BindingContext attribute on a XAML element, but I thought it just defines what object all the nested {Binding ...} properties would use when fetching values.

    Example:
    <Label BindingContext="{Binding SomeObjectInViewModel}" Text="{Binding Text}"/>

    This would bind the Text value to a property of SomeObjectInViewModel (rather than to a property of the ViewModel itself). But can the BindingContext attribute also bind the entire Label element to a Label object in the ViewModel? Am I not understanding how the BindingContext attribute works?

    I have tried adding BindingContext="{Binding MyMap}" to the

    element, and I have a MyMap property in ViewModel, but it never gets set to anything at runtime.

    Your final code sample actually uses a MyMap property of the Page class again (rather than the ViewModel). Is there an example of using the BindingContext attribute to bind a XAML element to a child property of the view model?

  • JoeMankeJoeManke ✭✭✭✭✭ USMember ✭✭✭✭✭

    Alright, so your view model looks like this?

    public class MyPageModel
    {
        public Label MyLabel { get; set; }
    
        public Map MyMap { get; set; }
    }
    

    Don't do that. Not using MVVM at all is better than that. Your view models really shouldn't even be in a project that references Xamarin.Forms.

    Besides that, no. You have to individually bind each property you want bound, there's no way to do automatic full mappings.

  • GlennPowellGlennPowell USMember

    Ok, I figured I wasn't adhering to MVVM in some way. Thanks all

  • PeterMurphyPeterMurphy ✭✭ USMember ✭✭

    @JoeManke Most of the examples I've seen for MVVM applications include the Command class in the ViewModel, and Command lives in Xamarin.Forms. How do you handle Command (or ICommand) if you don't reference Xamarin.Forms in the project that contains your ViewModels?

    Thanks,

    Peter.

  • JoeMankeJoeManke ✭✭✭✭✭ USMember ✭✭✭✭✭

    We made our own implementations of the ICommand interface.

  • aquabayaquabay ✭✭ AUMember ✭✭
    edited September 2018

    @JoeManke said:
    Alright, so your view model looks like this?

    public class MyPageModel
    {
        public Label MyLabel { get; set; }
    
        public Map MyMap { get; set; }
    }
    

    Don't do that. Not using MVVM at all is better than that. Your view models really shouldn't even be in a project that references Xamarin.Forms.

    Besides that, no. You have to individually bind each property you want bound, there's no way to do automatic full mappings.

    Let's start with sensible naming conventions and call this class MyPageViewModel, so we can distinguish from it's name that it's a VM and not a POCO Model. Placing ViewModels in the same project as the Forms lib is surely ok as the ViewModel is logically bound to that technology. Models however should be in a separate project as they should have no visibility of the technology used to implement the UI.

Sign In or Register to comment.