ContentViewで作成したオリジナルコントロールでViewからViewModelに変更が通知されなかった話

オリジナルコントロールを作成する

Xamarinでオリジナルコントロールを作成する場合、
ContentViewを継承して作成します。

例えば以下のように実装すると、オリジナルコントロールを作成できます。

MyEntry.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinSample.Views.Controls.MyEntry">
  <ContentView.Content>
      <StackLayout>
            <Label x:Name="label"
                 HorizontalOptions="FillAndExpand" />
            <Entry x:Name="entry"
                HorizontalOptions="FillAndExpand" />
        </StackLayout>
  </ContentView.Content>
</ContentView>
MyEntry.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MyEntry : ContentView
    {
        public MyEntry()
        {
            InitializeComponent();
        }
    }

作成したコントロールを独自クラスからしようする場合は、
xmlnsを指定して実装したクラスを記載するだけ。

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             xmlns:cont="clr-namespace:XamarinSample.Views.Controls"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XamarinSample.Views.MainPage"
             Title="MainPage">
  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
        <cont:MyEntry />
    </StackLayout>
</ContentPage>

ViewModelからBindingできるようにする

作成したコントロールにBindingPropertyを追加すると、ViewModelで変更が通知されるようになります。

MyEntry.xaml.cs
        /// <summary>
        /// Text
        /// </summary>
        public static readonly BindableProperty ValueProperty =
        BindableProperty.Create(
            nameof(Value),
            typeof(string),
            typeof(MyEntry),
            string.Empty,
            propertyChanged: (bindable, oldValue, newValue) => {
                ((MyEntry)bindable).Value = (string)newValue;
            },
            defaultBindingMode: BindingMode.TwoWay
        );

        public string Value
        {
            get { return (string)GetValue(ValueProperty); }
            set
            {
                SetValue(ValueProperty, value);
                entry.Text = (string)GetValue(ValueProperty);
                label.Text = (string)GetValue(ValueProperty);
            }
        }
MainPageViewModel.cs
    public class MainPageViewModel : BindableBase, INavigationAware
    {
        private string _title;
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        private string _value;
        public string Value
        {
            get { return _value; }
            set { SetProperty(ref _value, value); }
        }

        public MainPageViewModel()
        {
            Value = "First";
        }
    }
MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             xmlns:cont="clr-namespace:XamarinSample.Views.Controls"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XamarinSample.Views.MainPage"
             Title="MainPage">
  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
        <cont:MyEntry Value="{Binding Value}"/>
    </StackLayout>
</ContentPage>

実行してみると、ViewModelから、Viewへのバンディングは成功している。

image.png

しかし入力ボックスを変更してみると。。。

image.png

テキスト部分しか変わらず、バインディングがうまくいっていない。。。
以下を参考にすると、どうやらオリジナルコントロールのViewとViewModelもバインディングする必要があるらしい。

https://forums.xamarin.com/discussion/72304/cascading-xaml-contentviews-binding-not-working-properly

そこで参考に独自コントロールを編集する。

MainPage.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentView x:Name="this"
             xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinSample.Views.Controls.MyEntry">
  <ContentView.Content>
      <StackLayout>
            <Label x:Name="label"
                   Text="{Binding Source={x:Reference this},Path=Value}"
                 HorizontalOptions="FillAndExpand" />
            <Entry x:Name="entry"
                   Text="{Binding Source={x:Reference this},Path=Value}"
                HorizontalOptions="FillAndExpand" />
        </StackLayout>
  </ContentView.Content>
</ContentView>
MyEntry.xaml.cs
 [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MyEntry : ContentView
    {
        #region Binding Propterty

        /// <summary>
        /// Text
        /// </summary>
        public static readonly BindableProperty ValueProperty =
        BindableProperty.Create(
            nameof(Value),
            typeof(string),
            typeof(MyEntry),
            string.Empty,
            propertyChanged: (bindable, oldValue, newValue) => {
                ((MyEntry)bindable).Value = (string)newValue;
            },
            defaultBindingMode: BindingMode.TwoWay
        );

        public string Value
        {
            get
            {
                return (string)GetValue(ValueProperty);
            }
            set
            {
                SetValue(ValueProperty, value);
            }
        }

        #endregion

        public MyEntry()
        {
            InitializeComponent();
        }
    }

image.png

きちんとView側からViewModelに通知されていることがわかる。

これを解決するのに、何時間かかったことなのか。。。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.