web-dev-qa-db-ja.com

Xamarin.Formsバインディングが初期値の後に更新されない

Xamarin.Forms.ContentPageのタイトルをビューモデル(VM)のプロパティBuggyTitleにバインドしています。 VMはMvxViewModelから派生しています。簡略化されたバージョンは次のとおりです。

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
        }
    }
}
_

コンストラクターでのInitializeComponent()の呼び出し以外は、コードビハインドではほとんど何も起こっていません。

このページは、一般的に私のプロジェクト(実際には「私の」プロジェクトではなく、既存のデザイン)でVMにマップされ、次の(ここでも簡略化された)コード行に要約されます。

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

問題:

BuggyPageが読み込まれると、最初はタイトルの正しい値を取得します。その後表示されるたびに、デバッガーでBuggyTitleが正しく更新されていることがわかりますが、変更はページに表示されません。

質問:

BuggyTitleの更新がページに反映されないのはなぜですか?

編集1:

奇妙さをさらに説明するために、LabelContentPageに_x:Name="BuggyLabel"_と_Text="{Binding BuggyLabelText}"_で追加しました。私のコードビハインドで、私はこれを追加しました:

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

_BuggyLabel.Text =_にブレークポイントを設定しました。ページが読み込まれるたびにヒットし、_BuggyLabel.Text_はすでに正しい値(つまり、_binding_context.BuggyLabelText_が設定されているもの)を持っているようです。ただし、表示される実際のページには、このラベルのテキストが最初に設定されている内容のみが表示されます。

そして、はい、約百万回クリーン/ビルドしました。

編集2(さらなる奇妙さ):

これをコードビハインドに入れて、ページの読み込み中に実行されるようにします。

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

これにより、デバッガーの値が再び変更されますが、これらの変更は表示されたページには反映されません。

次に、ページにボタンを追加し、それをRefreshTitleCommandにバインドしました。ページの表示が更新されます。

残念ながらこれは使えません。それは信じられないほどハックであるだけでなく、ユーザーがボタンを押して、ロード時に意図した内容をページに表示させることはできません。

MvvmCrossまたはXamarinでキャッシュが行われているのではないかと思います。

6
Ash

回答

BuggyTitleプロパティ宣言にRaisePropertyChangedを追加する必要があります。

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

- - -新しいアップデート - - -

コードの背後にあるコード

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

Xamarinの使用経験はまったくありませんが(将来、UWPにできるだけ慣れたら試してみたいと思います)、データバインディングプロセスは使用しているものと同じように機能するはずです。そこへ ...

ページが最初に読み込まれたときに設定された値に問題はないとおっしゃっていますが、実際に値を更新すると、デバッグ時に値が何かに設定されているのが実際に表示されているにもかかわらず、ビジュアルレイヤーへの「リンク」はありません。初期状態とは全然違います。プロパティのみのビューモデルを扱っているため(たとえば、UWPのコレクションは公開する必要のある別のレベルのイベントです)、RaisePropertyChangedは正しい選択のようです。

私が理解できないのは、最初にページを作成したときに、作成しているバインディングが少なくとも一方向モードとして指定されている場合です。そのため、ビューモデルプロパティの変更は、設定されたアクセサーメソッドが呼び出されたときにUIに伝播されます。

ページコンテキストをviewmodelに設定しているため(各i図はUWP/WPFのDataContextと同じです)、したがって、{Binding}マークアップを使用してこれらのプロパティに実際にアクセスできます。しかし、Xamarinでのこの操作のデフォルトモードは何ですか? (UWPでは実際にはOneWayであるため、この状況ではすぐに機能します...)。 Xamarinでは、デフォルトオプションもあるため、少し異なる場合があることを確認しました。それでいいの?

PS。 Xamarinの経験が不足しているにもかかわらず、これがお役に立てば幸いです。

Edit2 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));
        }
   }
2
André B