5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WPFでMVVMを使わずに開発する (MVBパターンの紹介)

Posted at

はじめに

私は職場でも個人でもWindowsデスクトップアプリケーションを開発する時は、WPFを使っています。
WPFでの開発の際は、「MVVM」というアーキテクチャがマイクロソフトからは推奨されています。
しかしこのMVVM、ものすごくわかりにくいですし、設計的にもイマイチと思います。
ですので私は、「MVB (Model View Binder)」パターンを採用しています。
職場でチームリーダーを担当した際にアプリの基本実装方針として採用し、メンバーからも「MVVMよりも開発しやすい」と好評でした。
今日はそのMVBパターンを紹介します。

MVBパターンとは

MVBは「Model View Binder」の略です。
私の職場ではある程度有名なアーキテクチャなのですが、Googleなどで検索しても全く出てきません。
ですので、ここから先は私の理解を元に書いており、正確な定義とは違うかもしれませんが、ご了承ください。

まず「Model」と「View」は、それぞれModel = データ、View = UIを指します。
これは他のMVVMやMVPパターンと同じです。
特徴的なのが「Binder」で、ModelとViewを「Bind」 = 接続することによって、データをUIに反映させたり、UIからデータを変更したりする、という考え方です。
イメージとしては、こんな感じです。

UIで何かしらイベント(ボタンクリックとか)が起こった時にモデルを変更するのが矢印1、逆にモデルに変更があったらUIに反映するのが矢印2です。
この2方向の処理を書いてModelとViewを連携させるのがBinder、ということになります。

この際、UIでのイベントの発生と、モデルでの変更の発生を検知するために、Reactive ExtensionsのIObservableを使います。
IObservableは、Reactive Extensionsの根幹を成す概念で、非常に重要かつ有用なインターフェースなのですが、解説すると長くなりすぎるのでここでは省略します。
( 解説ページもたくさんあります。)
簡単に言うと、イベントを変数化したもので、IObservableに対してSubscribeすることで、イベントが発生した時の処理が書けるものです。

ここから先は、具体的なコードで説明していきます。

MVBパターンの実装例

以下のような、「+」ボタンを押したら数が増え、「ー」ボタンを押したら数が減るという、単純なアプリで説明します。

image.png

Xamlでは、以下のように、デザインと各コントロールのNameだけ定義します。

<Window x:Class="MvbSample.SampleWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="SampleWindow" Height="100" Width="300">
    <Grid Margin="16">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Button Margin="5" Name="ButtonPlus" Content="+"/>
        <Button Margin="5" Name="ButtonMinus" Content="-" Grid.Column="1"/>
        <TextBlock Grid.Column="2" VerticalAlignment="Center" Margin="5" Name="TextBlockValue"/>
    </Grid>
</Window>

このSampleWindowのコードは以下のようになっています。

    public partial class SampleWindow
    {
        public SampleWindow()
        {
            InitializeComponent();
        }

        public IDisposable Bind()
        {
            var valueProperty = new ReactivePropertySlim<int>(0);

            return new CompositeDisposable(
                // UIのイベントが発生したらモデルを変更(矢印1)
                ButtonPlus.ClickAsObservable().Select(_ => 1)
                    .Merge(ButtonMinus.ClickAsObservable().Select(_ => -1))
                    .Subscribe(change =>
                    {
                        valueProperty.Value += change;
                    }),

                // モデルが変わったらUIに表示(矢印2)
                valueProperty.Subscribe(value =>
                {
                    TextBlockValue.Text = value.ToString(CultureInfo.CurrentCulture);
                }),

                valueProperty);
        }
    }

ここで「valueProperty」と定義している「ReactivePropertySlim」がモデルです。
ボタンクリックのイベント時にモデルの値を変更し(矢印1)、モデルの値が変わったらTextBoxに値を表示する(矢印2)という処理になります。

MVBパターンの場合、Modelとなるクラスは、単にデータを保持するだけでなく、データに変更があったらそれを通知できるようなクラスにする必要があります。
私の場合は、Reactive Extensionsライブラリの「BehaiviourSubject」か、Reactive Propertyライブラリの「ReactivePropertySlim」を使うことが多いです。

本質的には、以下の3つのメソッドがあれば、何でもOKです。
・データを取得するGetメソッド
・データをセットするSetメソッド
・データが変わった時に発行されるIObservable

また、このコードの中の「ClickAsObservable」は、WPFのボタンのクリックイベントをIObservableに変換するためのメソッドです。

        public static IObservable<EventPattern<RoutedEventArgs>> ClickAsObservable(this ButtonBase source)
        {
            return Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>(
                h => source.Click += h,
                h => source.Click -= h);
        }

私は、自動コード生成を使って、WPFのほぼすべてのコントロールのイベントをIObservableに変換するメソッドを用意し、それを使っています。
一度用意すれば、どこでもずっと使えるので、それほど手間ではないです。

このSampleWIndowを呼び出す側では、SampleWindowを作り、Bindメソッドを呼んだ後、ShowDialogで表示します。

                var window = new SampleWindow();
                using (window.Bind())
                {
                    window.ShowDialog();
                }

このMVBパターンで実装を進めていくと、WPFのBinding機能は一切不要になります。
ItemSourceも基本的に使いません。

今回の例は非常に単純なので、1ウィンドウにBindメソッド一つだけでしたが、複雑になっていっても考え方は同じです。
1つのウィンドウ or コントロールに、「Bind」メソッドを定義して、親コントロールのBindメソッドから子コントロールのBindメソッドを呼ぶだけで、簡単に拡張していくことができます。

MVBパターンでWPFアプリを実装したときのメリット・デメリット

MVBパターンでWPFを実装するということは、MVVMのバインディングを自分たちで実装するということに相当します。
そのため、MVVMを熟知している人からすると、MVBで処理を書くのはやや冗長に感じられるかもしれません。
しかし、コード例を見ていただいてわかるように、MVVMで実装した場合と比べてコード量も多いわけではないですし、大きなデメリットではないと思います。

メリットとしては、MVBの場合Bind処理すべてを自分たちで実装できるので、WPFのBindingの仕様は一切気にすることなく、実現したい仕様を実現できます。
MVVMの仕組みを全く知らなくても実装できるので、WPFを知らない開発メンバーのハードルもかなり下がります。
それでいて、WPFのGUIはそのまま使えるので、Windows.Formsなどに比べてリッチなUIにすることができます。

さいごに

MVBパターンでのWPFアプリの実装、いかがだったでしょうか。
私はWindowsデスクトップアプリケーションの開発を10年以上やっていますが、その中で出会ったがのこのMVBパターンで、これ以上のパターンはないと思っています。
WPFでなくてもWindows.Formsでも使えますし、UIの仕組みに依存しない汎用的なパターンではないかと思います。
なぜ世の中で流行っていないのかわからないのですが、私としては非常におすすめです。

5
8
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
5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?