简体   繁体   中英

Xamarin.Forms binding not updating after initial value

I'm binding the title of my Xamarin.Forms.ContentPage to a property BuggyTitle in my view model (VM). The VM derives from MvxViewModel. Here's the simplified version:

BuggyPage.xaml:

<?xml version="1.0" encoding="UTF-8"?>
<local:ContentPage Title="{Binding BuggyTitle}"
            xmlns="http://xamarin.com/schemas/2014/forms" 
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
            x:Class="MyProject.BuggyPage"
            xmlns:local="clr-namespace:Xamarin.Forms;assembly=MyProject">
<ContentPage.Content NavigationPage.HasNavigationBar="false">
        <Grid>
            <ScrollView>
                <!--and so on-->
</ContentPage.Content>
</local:ContentPage>

BuggyViewModel.cs:

namespace MyProject
{
    [ImplementPropertyChanged]
    public class BuggyViewModel : MvxViewModel
    {
      private Random _random;

      public string BuggyTitle {get; set;}

      public BuggyViewModel()
      {
          _random = new Random();
      }

      public override void Start()
        {
            base.Start();
            BuggyTitle = "" + _random.Next(1000);
            RaisePropertyChanged("BuggyTitle"); // this seems to make no difference
        }
    }
}

There's not much going on in the code behind other than a call to InitializeComponent() in the constructor.

The page is mapped to the VM generically in my project (not actually 'my' project, it's existing design), and it boils down to these (again, simplified) lines of code:

public static Page CreatePage(MvxViewModelRequest request)
{
    var viewModelName = request.ViewModelType.Name;
    var pageName = viewModelName.Replace ("ViewModel", "Page");
    var pageType = (typeof (MvxPagePresentationHelpers)).GetTypeInfo ().Assembly.CreatableTypes().FirstOrDefault(t => t.Name == pageName);
    var viewModelLoader = Mvx.Resolve<IMvxViewModelLoader>();
    var viewModel = viewModelLoader.LoadViewModel(request, null);
    var page = Activator.CreateInstance(pageType) as Page;
    page.BindingContext = viewModel;

   return page;
}

The problem:

When BuggyPage loads, I initially get the correct value for the title. Whenever it is displayed after that, even though I can see in the debugger that BuggyTitle is getting updated correctly, the change does not appear in the page.

Question:

Why don't updates to BuggyTitle get reflected in the page?

Edit 1:

To further describe the weirdness, I added a Label to my ContentPage , with x:Name="BuggyLabel" and Text="{Binding BuggyLabelText}" . In my code-behind, I added this:

var binding_context = (BindingContext as BuggyViewModel);
if (binding_context != null)
{
    BuggyLabel.Text = binding_context.BuggyLabelText;
}

I set a breakpoint at BuggyLabel.Text = . It gets hit every time the page loads, and BuggyLabel.Text already seems to have the correct value (ie, whatever binding_context.BuggyLabelText is set to). However, the actual page displayed only ever shows what the text in this label is initially set to.

And yes, have clean/built about a million times.

Edit 2 (further weirdness):

I put this in the code-behind so that it runs during page load:

var binding_context = (BindingContext as BuggyViewModel);
if (binding_context != null)
{
    Device.BeginInvokeOnMainThread(() =>
    {
        binding_context.RefreshTitleCommand.Execute(null);
    });
}

This again changes values in the debugger, but these changes don't get reflected in the displayed page.

I then added a button to the page and bound it to RefreshTitleCommand , and wham! the page updates its display.

Unfortunately I can't use this. Not only is it incredibly hackish, I can't have the user pressing buttons to have the page display what it's meant to on load.

I wonder if there's some caching going on with MvvmCross or Xamarin.

Answer

You need to add RaisePropertyChanged in BuggyTitle property declaration.

ViewModel

namespace MyProject
{
    [ImplementPropertyChanged]
    public class BuggyViewModel : MvxViewModel
    {
        private Random _random;

        string  _BuggyTitle { get; set; }

        public string BuggyTitle
        {
            get { return _BuggyTitle; }
            set { _BuggyTitle = value; RaisePropertyChanged(() => BuggyTitle); }
        }

        public BuggyViewModel()
        {
            _random = new Random();
        }

        public override void Start()
        {
            base.Start();
            BuggyTitle = "" + _random.Next(1000);
        }
    }
}

-----New Update------

Code behind code

var binding_context = (BindingContext as BuggyViewModel);
if (binding_context != null)
{
    Device.BeginInvokeOnMainThread(() =>
    {
        BuggyLabel.Text = binding_context.BuggyLabelText;
    });
}

I don't have any experience at all with Xamarin (but i do want to try it out in the future when i get as comfortable as possible with UWP), but i guess the Data Binding process should be working similar to what i am used to there ...

You are mentioning that you have no problem with the values that are set when the page first loads, however when you actually update the values there's no "linking" to the visual layer, despite at debug time you actually seeing the value being set to something completely different from it's initial state. Since you are dealing with properties-only viewmodel (Collections for instance in UWP are another level of events which need to be exposed), RaisePropertyChanged seems like the correct choice.

What i cannot understand is if when you first create your page, the Binding which you are creating is at least specified as One-Way mode, so changes in your viewmodel properties are propagated onto your UI when their set accessor methods are called.

You are setting your page context to viewmodel (each i figure is the same as DataContext in UWP/WPF), and therefore you can actually access those properties with the {Binding } markup. But what is the default mode for this operation in Xamarin ? (in UWP it is actually OneWay, and therefore it would work right of the bat for this situation ...). I have seen that in Xamarin it might be a bit different , since you also have the Default option. Can that be it?

PS. Hopefully this might be useful to you, despite my lack of experience with Xamarin.

Edit2 Implementing the INotifyPropertyChanged,

   public class BuggyViewModel : MvxViewModel, INotifyPropertyChanged
   {
        public event PropertyChangedEventHandler PropertyChanged;
        private Random _random;

        string  _BuggyTitle { get; set; }

        public string BuggyTitle
        {
            get { return _BuggyTitle; }
            set { _BuggyTitle = value; RaisePropertyChanged(() => 
                   BuggyTitle); }
        }

        public BuggyViewModel()
        {
            _random = new Random();
        }

        public override void Start()
        {
            base.Start();
            BuggyTitle = "" + _random.Next(1000);
        }


        protected void OnPropertyChanged(string propertyName)
        {
           var handler = PropertyChanged;
           if (handler != null)
              handler(this, new PropertyChangedEventArgs(propertyName));
        }
   }

I was setting a controls binding context from the property changed event of one of its properties.

This made my control stop tracking changes despite everything else binding correctly still, and the control would also correctly bind initially (first time is fine, further changes do not fire the property changed event again).

page.BindingContext = viewModel;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM