やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Eto.Forms 2.4.1 XAML のデータバインドについて調べる

 TextBox.TextをWebView.Urlに渡したい。が、やってみると強制終了してしまった。

対象環境

前回

今回

 前回のようにTextBoxに入力したURLをWebViewで表示したかった。これをXAMLでやりたかったのだが、非常に難解。MVVMだのデータバインドだのコードビハインドだの、色々と知る必要があるようだ。

問題

  1. Eto.FrormsでXAMLプロジェクトを作成する。
  2. XAMLファイルを開き、以下のようにTextBoxとWebViewを追加する
<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で型変換を実装する仕組みがないらしい。もっと低レベルな階層から実装せねばならず、非常に面倒そう。詳しくは後述。

調査

 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}"がポイント。

  1. textBlockのTextにバインディングする
  2. どの要素を?: textBox1
  3. どのメンバ?: 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使いたくないな。だが、一応勉強する。