質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Xamarin

Xamarin(ザマリン)は、iPhoneなどのiOSやAndroidで動作し、C# 言語を用いてアプリを開発できるクロスプラットフォーム開発環境です。Xamarin Studioと C# 言語を用いて、 iOS と Android の両方の開発を行うことができます。

Q&A

解決済

2回答

1949閲覧

[Xamarin.Forms]ReactiveProperty<独自クラス>のBindingとObservableCollectionのVMとM双方向同期

roamschemer

総合スコア31

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Xamarin

Xamarin(ザマリン)は、iPhoneなどのiOSやAndroidで動作し、C# 言語を用いてアプリを開発できるクロスプラットフォーム開発環境です。Xamarin Studioと C# 言語を用いて、 iOS と Android の両方の開発を行うことができます。

0グッド

0クリップ

投稿2018/11/08 07:53

前提・実現したいこと

Xamarin.FormsをPrism(MVVM)+ReactivePropertyで書いています。
Age、Name、Genderを入力または選択後、PushTappedでListViewに追加するような機能を作成しているのですが以下二つの問題点が発生しましたのでご助言を求めます。

1.ReactiveProperty<独自クラス>の場合のVとVMのBindingがうまくいきません。
2.ObservableCollectionのVMとMの双方向同期の記述方法がわかりません。

試したこと

1.に関しては独自クラスを使わずintやstringで分離した場合は正しく動作しました。以下コードでは正しく動いておらず、VMのInputPersonが常に空白になってしまいますのでPushTappedでは空白のListViewが追加されます。

2.に関しては以下にあるようにToReadOnlyReactiveCollectionを使ってVM←Mでは再現できましたが、その場合Model側にもInputPersonプロパティが必要になっていて、これが無駄な気がしています。VMのListViewに追加するだけでM側も同期して欲しいのです。

該当のソースコード

[View]

xaml

1<?xml version="1.0" encoding="utf-8" ?> 2<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 4 x:Class="JsonSample.Views.MainPage" 5 Title="JsonTest"> 6 7 <Grid> 8 <Grid.ColumnDefinitions> 9 <ColumnDefinition Width="*" /> 10 <ColumnDefinition Width="2*" /> 11 </Grid.ColumnDefinitions> 12 <StackLayout Grid.Column="0"> 13 <Picker Title="年齢" ItemsSource="{Binding AgeList}" SelectedItem="{Binding InputPerson.Age.Value}"/> 14 <Entry Placeholder="名前" Text ="{Binding InputPerson.Name.Value}" Keyboard="Text"/> 15 <Picker Title="性別" ItemsSource="{Binding GenderList}" SelectedItem="{Binding InputPerson.Gender.Value}" /> 16 <Button Text="追加" Command="{Binding PushTapped}"/> 17 </StackLayout> 18 <ListView Grid.Column="1" ItemsSource="{Binding ListView}"> 19 <ListView.ItemTemplate> 20 <DataTemplate> 21 <ViewCell> 22 <Grid> 23 <Grid.ColumnDefinitions> 24 <ColumnDefinition Width="*" /> 25 <ColumnDefinition Width="3*" /> 26 <ColumnDefinition Width="*" /> 27 </Grid.ColumnDefinitions> 28 <Label Grid.Column="0" Text="{Binding Age}" /> 29 <Label Grid.Column="1" Text="{Binding Name}" /> 30 <Label Grid.Column="2" Text="{Binding Gender}" /> 31 </Grid> 32 </ViewCell> 33 </DataTemplate> 34 </ListView.ItemTemplate> 35 </ListView> 36 </Grid> 37 38</ContentPage>

[ViewModel]

C#

1using JsonSample.Models; 2using Prism.Navigation; 3using Reactive.Bindings; 4using Reactive.Bindings.Extensions; 5using System; 6using System.Linq; 7using System.Reactive.Disposables; 8using System.Collections.ObjectModel; 9 10namespace JsonSample.ViewModels 11{ 12 public class MainPageViewModel : ViewModelBase, IDisposable 13 { 14 private CompositeDisposable Disposable { get; } = new CompositeDisposable(); 15 16 public ReadOnlyReactiveCollection<Person> ListView { get; private set; } 17 public ObservableCollection<int> AgeList { get; private set; } = new ObservableCollection<int>(); 18 public ObservableCollection<string> GenderList { get; private set; } = new ObservableCollection<string>(); 19 20 public ReactiveProperty<Person> InputPerson { get; private set; } = new ReactiveProperty<Person>(); 21 22 public ReactiveCommand PushTapped { get; private set; } = new ReactiveCommand(); 23 24 private Commander commander = new Commander(); 25 26 public MainPageViewModel(INavigationService navigationService) 27 : base(navigationService) 28 { 29 //Pickerリスト作成 30 AgeList = new ObservableCollection<int>(Enumerable.Range(1, 100).ToList()); 31 GenderList = new ObservableCollection<string>() { "男", "女", "不明" }; 32 33 //ViewModel←Model 34 ListView = commander.Commanders.ToReadOnlyReactiveCollection().AddTo(this.Disposable); //本当は双方向にしたかった 35 36 //ViewModel→Model 37 InputPerson = ReactiveProperty.FromObject(commander, x => x.InputPerson).AddTo(this.Disposable); 38 39 //Button 40 PushTapped.Subscribe(_ => commander.Push()); 41 //PushTapped.Subscribe(_ => ListView.Add(InputPerson)); 双方向だったならこんな感じで行けた? 42 43 } 44 45 public void Dispose() 46 { 47 this.Disposable.Dispose(); 48 } 49 } 50} 51

[Model]

C#

1using Prism.Mvvm; 2using System.Collections.ObjectModel; 3 4namespace JsonSample.Models 5{ 6 public class Person : BindableBase 7 { 8 private int age; 9 public int Age 10 { 11 get => age; 12 set => SetProperty(ref age, value); 13 } 14 15 private string name; 16 public string Name 17 { 18 get => name; 19 set => SetProperty(ref name, value); 20 } 21 22 private string gender; 23 public string Gender 24 { 25 get => gender; 26 set => SetProperty(ref gender, value); 27 } 28 } 29 30 public class Commander : BindableBase 31 { 32 private Person inputPerson; 33 public Person InputPerson 34 { 35 get => inputPerson; 36 set => SetProperty(ref inputPerson, value); 37 } 38 39 public ObservableCollection<Person> Commanders { get; private set; } = new ObservableCollection<Person>(); 40 41 public void Push() 42 { 43 Commanders.Add(InputPerson); 44 } 45 46 } 47 48}

その他

初心者である為、上記質問以外で直したほうが良いところがもしあればご指摘いただけると助かります。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

バインドしてもCommanderInputPersonのインスタンスが作られることはありません。
素直にボタン押したら、Personを作って追加するようにしています。

[View]

xml

1<?xml version="1.0" encoding="utf-8" ?> 2<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 4 x:Class="JsonSample.Views.MainPage" 5 Title="JsonTest"> 6 7 <Grid> 8 <Grid.ColumnDefinitions> 9 <ColumnDefinition Width="*" /> 10 <ColumnDefinition Width="2*" /> 11 </Grid.ColumnDefinitions> 12 <StackLayout Grid.Column="0"> 13 <Picker Title="年齢" ItemsSource="{Binding AgeList}" SelectedItem="{Binding Age.Value}"/> 14 <Entry Placeholder="名前" Text ="{Binding Name.Value}" Keyboard="Text"/> 15 <Picker Title="性別" ItemsSource="{Binding GenderList}" SelectedItem="{Binding Gender.Value}" /> 16 <Button Text="追加" Command="{Binding PushTapped}"/> 17 </StackLayout> 18 <ListView Grid.Column="1" ItemsSource="{Binding ListView}"> 19 <ListView.ItemTemplate> 20 <DataTemplate> 21 <ViewCell> 22 <Grid> 23 <Grid.ColumnDefinitions> 24 <ColumnDefinition Width="*" /> 25 <ColumnDefinition Width="3*" /> 26 <ColumnDefinition Width="*" /> 27 </Grid.ColumnDefinitions> 28 <Label Grid.Column="0" Text="{Binding Age}" /> 29 <Label Grid.Column="1" Text="{Binding Name}" /> 30 <Label Grid.Column="2" Text="{Binding Gender}" /> 31 </Grid> 32 </ViewCell> 33 </DataTemplate> 34 </ListView.ItemTemplate> 35 </ListView> 36 </Grid> 37 38</ContentPage>

[ViewModel]

C#

1using JsonSample.Models; 2using Prism.Navigation; 3using Reactive.Bindings; 4using Reactive.Bindings.Extensions; 5using System; 6using System.Linq; 7using System.Reactive.Disposables; 8using System.Collections.ObjectModel; 9 10namespace JsonSample.ViewModels 11{ 12 public class MainPageViewModel : ViewModelBase, IDisposable 13 { 14 private CompositeDisposable Disposable { get; } = new CompositeDisposable(); 15 16 public ReadOnlyReactiveCollection<Person> ListView { get; private set; } 17 public ObservableCollection<int> AgeList { get; private set; } = new ObservableCollection<int>(); 18 public ObservableCollection<string> GenderList { get; private set; } = new ObservableCollection<string>(); 19 20 //追加 21 public ReactiveProperty<int> Age { get; } = new ReactiveProperty<int>(); 22 public ReactiveProperty<string> Gender { get; } = new ReactiveProperty<string>(); 23 public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>(); 24 25 public ReactiveCommand PushTapped { get; private set; } = new ReactiveCommand(); 26 27 private Commander commander = new Commander(); 28 29 public MainPageViewModel(INavigationService navigationService) 30 : base(navigationService) 31 { 32 //Pickerリスト作成 33 AgeList = new ObservableCollection<int>(Enumerable.Range(1, 100).ToList()); 34 GenderList = new ObservableCollection<string>() { "男", "女", "不明" }; 35 36 //ViewModel←Model 37 ListView = commander.Commanders.ToReadOnlyReactiveCollection().AddTo(this.Disposable); //本当は双方向にしたかった 38 39 //Button 40 // 修正 41 PushTapped.Subscribe(_ => commander.Push(Age.Value, Gender.Value, Name.Value)); 42 //PushTapped.Subscribe(_ => ListView.Add(InputPerson)); 双方向だったならこんな感じで行けた? 43 44 } 45 46 public void Dispose() 47 { 48 this.Disposable.Dispose(); 49 } 50 } 51}

[Model]

C#

1using Prism.Mvvm; 2using System.Collections.ObjectModel; 3 4namespace JsonSample.Models 5{ 6 public class Person : BindableBase 7 { 8 private int age; 9 public int Age 10 { 11 get => age; 12 set => SetProperty(ref age, value); 13 } 14 15 private string name; 16 public string Name 17 { 18 get => name; 19 set => SetProperty(ref name, value); 20 } 21 22 private string gender; 23 public string Gender 24 { 25 get => gender; 26 set => SetProperty(ref gender, value); 27 } 28 } 29 30 public class Commander : BindableBase 31 { 32 public ObservableCollection<Person> Commanders { get; private set; } = new ObservableCollection<Person>(); 33 34 //修正 35 public void Push(int age, string name, string gender) 36 { 37 var person = new Person 38 { 39 Age = age, 40 Name = name, 41 Gender = gender 42 }; 43 44 Commanders.Add(person); 45 } 46 47 } 48 49}

あと、気になったところで、MainPageViewModelIDisposableを実装していますけど、どこかで、Disposeの呼び出しを行なっていますか?そうならそれでもいいですが、IDestructibleを実装しておけば、ページが破棄されたときにDestroyが呼び出されるので、そこで処理を行うのでもいいです。

投稿2018/11/08 09:34

f-miyu

総合スコア1625

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

roamschemer

2018/11/08 23:49 編集

やっぱりViewModelでAge,Name,GenderをそれぞれReactiveProperty型にする必要があるんですね。なんか勿体ないなとPersonクラスを使う方法を考えてみたんですが… でもこの方法ならModel側にInputPersonが不要なので手間的に言えばこれがベストですね。ありがとうございました。 後全く別の話になってしまって恐縮ですが、IDisposableに関してはReactivePropertyでModelと同期する場合にとりあえず付けておけば安心程度に考えていました。以下を参考にしています。 かずきのBlog@hatena ReactivePropertyの後始末 https://blog.okazuki.jp/entry/2016/04/30/073755 Prismのテンプレートを使う場合、ViewModelBaseクラスにIDestructibleが最初から実装されているので、こっちにあるDestroyメソッドに記述する方が良いのですかね?その場合どう書けば良いのでしょうか?
f-miyu

2018/11/09 01:19

Destroyをオーバーライドして、そこに書けばいいです。
roamschemer

2018/11/09 06:38

成程。ありがとうございました。
guest

0

こんにちは!

<1について>
Viewのバインド設定が間違ってませんか?
InputPersonはReactiveProperty、
PersonクラスのAgeプロパティはReactivePropertyではないので

誤 {Binding InputPerson.Age.Value}
正 {Binding InputPerson.Value.Age}

名前、性別等も同じです。

<2について>
ReactiveCollection(ObsrvableCollection)のアイテムを双方向で同期する機能はなかった気がします。

投稿2018/11/08 09:06

mikupedia

総合スコア159

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

roamschemer

2018/11/08 23:34

{Binding InputPerson.Value.Age}としても直らなかったです。ObsrvableCollectionは無いんですね。どおりでいくら探しても見つからないはずです。ありがとうございました。
mikupedia

2018/11/09 00:08

{Binding InputPerson.Value.Age}でだめなのはf-miyuさんが回答されましたが、InputPersonのValueにPersonのインスタンスが設定されていないためです。
roamschemer

2018/11/09 01:08

申し訳ございませんが 「InputPersonのValueにPersonのインスタンスが設定されていない」 という意味が分かりません。この場合どこにどのように書けば良いのでしょうか?
f-miyu

2018/11/09 01:32 編集

InputPersonはずっとnullのままということです。 InputPersonを使うのであれば、Commanderのコンストラクタで初期化しておけばいいです。 この場合でも、Push時では、別のPersonのインスタンスを追加するべきなので、Addする前にInputPersonの値を使って、Personを作るか、Addした後にInputPersonに新しいインスタンスを代入するかしないといけないです。 もっとも、自分の回答のようにシンプルに引数を受け取った方がいいとは思いますが。
roamschemer

2018/11/09 02:04

この場合以下で行けました。 ```C# public Commander() { InputPerson = new Person(); } public void Push() { var person = new Person { Age = InputPerson.Age, Name = InputPerson.Name, Gender = InputPerson.Gender }; Commanders.Add(person); } ``` 成程、簡単に出来るかと思いましたが面倒になってしまいました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問