Eto.Forms 2.4.1 XAML のデータバインドについて調べる
TextBox.TextをWebView.Urlに渡したい。が、やってみると強制終了してしまった。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch(9.0) 2018-06-27
- Mono 5.16.0
- MonoDevelop 7.6 build 711
- Eto.Forms 2.4.1 拡張機能, NuGetパッケージ
前回
- http://ytyaru.hatenablog.com/entry/2020/01/26/000000
- http://ytyaru.hatenablog.com/entry/2020/01/25/000000
- http://ytyaru.hatenablog.com/entry/2020/01/24/000000
今回
前回のようにTextBoxに入力したURLをWebViewで表示したかった。これをXAMLでやりたかったのだが、非常に難解。MVVMだのデータバインドだのコードビハインドだの、色々と知る必要があるようだ。
問題
<TextBox Name="textBox1" Text="https://www.google.co.jp" /> <WebView Name="webView1" Url="{Binding ElementName=textBox1, Path=Text}" />
ビルドし実行すると何も表示されず、強制終了する。
原因の推測
原因は以下の調査によると、型の不一致と思われる。TextBox.TextはString型だが、WebView.UrlはUri型であるため、型の不一致でエラーになったのではないか。詳しくは後述。
対処
Eto.FormsではWPFのようにIValueConverter
で型変換を実装する仕組みがないらしい。もっと低レベルな階層から実装せねばならず、非常に面倒そう。詳しくは後述。
調査
- https://code.msdn.microsoft.com/10-VB-010c4037/
- http://blogs.itmedia.co.jp/mohno/2013/12/xaml15-c9fe.html
- https://msdn.microsoft.com/ja-jp/library/windows/apps/windows.ui.xaml.data.ivalueconverter
XAMLの場合、従来のイベント駆動式とは全く異なる設計思想と実装方法をとる。さっぱりわからないので調べた。
MVVM
従来のC#コードで画面を作成する方式はイベント駆動だった。UIで発生したイベントごとに任意の処理を実装していく。対してXAMLでUIを実装する方式はMVVM。データ(モデル)が変化したときにUIも変化するというもの。という認識で合ってるかな?
モデルはUIのデータのことだと思う。TextBoxなら文字列型のデータ。
バインディング
モデルとUIを紐付けることをバインディングという。のだと思う。
<TextBox Name="textBox1" /> <TextBlock Name="textBlock1" Text="{Binding ElementName=textBox1, Path=Text}" />
上記コードはtextBox1
の入力値であるText
を、textBlock1
の表示値であるText
に紐付けている。Text="{Binding ElementName=textBox1, Path=Text}"
がポイント。
- textBlockのTextにバインディングする
- どの要素を?: textBox1
- どのメンバ?: Text (textBox1.Text)
これの問題はインテリセンスが使えないこと。文字リテラルでなければ、IDEで入力候補が出せるのに……。こんな長ったらしい文字列を覚えるなど不可能。これだけでもうMVVMを使いたくない大きな理由になる。
というか、XML嫌い。バイナリでなくテキストであることは良いが、見づらく扱いにくい。
値コンバータ(IValueConverter)
先程の例はたまたまどちらも文字列型だったから、そのままバインディングできた。これが異なる型となると、自前で変換処理を実装せねばならない。
独特のルールがあるようだ。楽に作れるとはとても言えない。可読性が高いとも思えない。保守性や再利用性は高そう。
Eto.Formsではどうやって実装する?
Eto Data-Binding
なんかもうUI作成からしてXAMLを使わずC#コードで書いちゃってる。まさか、Eto.FormsのXAMLは同一型しかバインディングできないのか? 違う型をバインディングしたければコードで実装するしかない?
public class MyForm : Form { public MyForm() { var textBox = new TextBox(); textBox.TextBinding.BindDataContext((MyModel m) => m.MyString); Content = textBox; // set the view model for the form and all child controls var model = new MyModel { MyString = "Hello!" }; DataContext = model; } } // typically implemented view model public class MyModel : INotifyPropertyChanged { string myString; public string MyString { get { return myString; } set { if (myString != value) { myString = value; OnPropertyChanged(); } } } void OnPropertyChanged([CallerMemberName] string memberName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(memberName)); } public event PropertyChangedEventHandler PropertyChanged; }
INotifyPropertyChanged
。イベント処理はすべてこれで実装するっぽい。なにこれ超面倒くせえ……。
// for your textBox: textBox.BindDataContext(c => c.Enabled, (MyModel m) => m.IsEnabled); // and on your model: public class MyModel : INotifyPropertyChanged { // ... snipped for brevity bool isEnabled = true; public bool IsEnabled { get { return isEnabled; } set { if (isEnabled != value) { isEnabled = true; OnPropertyChanged(); } } } }
型変換。MyEnumってなんぞ? どっから出てきた?
textBox.TextBinding.BindDataContext( Binding.Property((MyModel m) => m.MyEnum) .Convert(r => r.ToString(), v => (MyEnum)Enum.Parse(typeof(MyEnum), v)) );
こちらを見ても、イベント部分はXAMLでは書けず、C#コードで実装している。Eto.Forms、ダサい……。そうまでしてC#と分離するメリットがあるだろうか?
他にも色々と罠やクセがありそう。クロスプラットフォームなコードを楽に実現できるかと思ったら、むしろ大変な部分も多そう。
Eto.FormsのコードはWPFよりも劣っていると言えそう。WPF自体ほとんど使ったこと無いけど。
コードビハインド
コードビハインドとは、XAMLで書けないイベント処理などをC#コードで書くこと。
MVC, MVVM
モデル(M)、コントロール(C)、ビュー(V)、を分離するため、コードとXMLに分離した。これによりコードの保守性を高めるのが目的。MVCはイベント駆動、MVVMはモデル駆動。
所感
もうXAML使いたくないな。だが、一応勉強する。