LoginSignup
8
3

More than 5 years have passed since last update.

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

Posted at

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

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もバインディングする必要があるらしい。

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

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に通知されていることがわかる。

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

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3