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使いたくないな。だが、一応勉強する。