WPFのサンプルアプリケーションを作ってみました。


この１年半ほど取り組んできたWPFの仕事がそろそろ終わりそうなので、得たノウハウ（というほどのものではないけど）を残しておこうと小さなサンプルアプリケーションを作ってみました。もし次にまたWPF関連の仕事にありつけたらその時のたたき台として使うためですが、これからWPFを使って開発をしようという人のお手本というつもりはありませんけど、とっかかりにでもなればと思い公開します。

一人で開発してたので対象となるコードも少なく、WPFの一般的なお作法から大きく外れてたり、まともなプログラマとしてこれはねーだろうというダサいコーディングや、そもそもバグってるかもしれんので、そういう指摘をいただけると嬉しいです。

アプリケーションは蔵書管理を目的とした小さなものですが、WPFでのアプリケーション開発での、MVVMパターン、データバインディング（INotifyPropertyChangedによる変更通知、IDataErrorInfoによるエラー処理、コマンド処理）、添付ビヘイビアによるコントロールへの機能追加、データーコンバーターによる入出力時のデータ変換など最低限のものは実装しています。（フレームワークを利用すれば自分で実装する必要はあまりないのかもしれませんが）あと、あまりネットで情報を見なかった印刷機能も実装しています。（ただ、WPFとは関係のないデータの永続化に関しては実装してません。）

アプリケーションの実行イメージは下図の感じです。

![main.png](https://qiita-image-store.s3.amazonaws.com/0/31482/29fef211-72c4-1c2c-37c6-fd45c9761eb8.png "main.png")

![sub.png](https://qiita-image-store.s3.amazonaws.com/0/31482/87e03ca0-5de2-60bb-8181-c0a96c65e65f.png "sub.png")

コードはサンプルアプリケーション本体とライブラリでプロジェクトを分割しています。

例えば、モデルとなる書籍クラスは以下のようなコードになっています。

```csharp:Book.cs

using hsb.Utils;
using hsb.WPF;

namespace SampleApp.Models
{
    #region 【Class : Book】
    /// <summary>
    /// 書籍クラス
    /// </summary>
    class Book : DataBindModelBase
    {
        #region ■ Constructor ■

        #region **** Constructor(1)
        /// <summary>
        /// コンストラクタ(1)
        /// </summary>
        public Book()
        {
            // プロパティ値の初期設定

            // Id
            IdProperty = CreateDataBindProperty<int>("Id", 0);

            // 書籍名
            TitleProperty = CreateDataBindProperty<string>("Title", null, v => 
                { 
                    v.CreateValidator(s => !String.IsNullOrEmpty(s), "書名が未入力です。");
                    v.IOFilter = IOFilters.Trim;
                    v.Description = "書籍名を入力してください。";
                });

			 // 中略… 
        }
        #endregion

        #endregion

        #region ■ Properties ■

        #region **** Property : Id (Read Only)
        /// <summary>
        /// ID値
        /// </summary>
        public DataBindPropertyItem<int> IdProperty { get; private set; }
        public int Id
        {
            get { return IdProperty.Value; }
        }
        #endregion

        #region **** Property : Title
        /// <summary>
        /// 書籍名
        /// </summary>
        public DataBindPropertyItem<string> TitleProperty { get; private set; }
        public string Title
        {
            get { return TitleProperty.Value; }
            set { TitleProperty.Value = value; }
        }
        #endregion

        #endregion


        // 中略…

        #endregion


        // 以下メソッド定義は省略
    }
    #endregion

}
```

DataBindModelBase を継承することで、Viewへのデータバインディングとバリデーションチェック機能などを持たせています。
実際にデータバインドさせるプロパティはCreateDataBindProperty<T>メソッドにより、DataBindPropertyItem<T>のクラスのインスタンスとして生成するようになっています。DataBindPropertyItem<T>はINotifyPropertyChangedとIDataErrorInfoを実装しているので、Viewに対してプロパティ値の変更通知やエラー通知を勝手にやってくれるようになってます。

書籍編集ダイアログのXAMLはこんな感じになってます。

```XAML:SubWindow.xaml（抜粋）
<GroupBox Header="書籍情報" DockPanel.Dock="Bottom" Margin="20,20,10,20" >
    <ContentControl DataContext="{Binding Book}" Margin="10,10,10,0">
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Vertical" Margin="0,0,0,4">
                <StackPanel Orientation="Horizontal" Height="24" Margin="0,0,0,2">
                    <TextBlock Text="書名：" Width="70" VerticalAlignment="Center" />
                    <TextBox Text="{Binding Title, ValidatesOnDataErrors=True}" 
                             ToolTip="{Binding TitleProperty.Description}" 
                             Width="300" VerticalContentAlignment="Center"  
                             InputMethod.PreferredImeState="On"
                             InputMethod.PreferredImeConversionMode="FullShape,Native" />
                </StackPanel>
            <TextBlock Text="{Binding TitleProperty.ErrorMessage}" FontSize="10" 
                       Foreground="Red" Margin="70,0,0,0"  />
        </StackPanel>
        <!-- 中略… -->
    </ContentControl>
</GroupBox>
```

Viewのコードビハインドにはあまりコードを書かないようにはしていますが、ややこしいことをするぐらいならコードビハインドにコードを書くというスタンスになってます。例えば、ViewModelからダイアログボックスを表示したり、ViewをクローズさせたいといったViewModeからViewへの通知には単純にViewModelにデリゲートないしイベントを作成し、Viewからそれらに接続するというスタイルにしています。ViewModelの基底クラスであるViewModelBaseで定義しているイベントには、ほぼ共通なのでWindowクラスにイベントへの接続を行う拡張メソッドを定義して、Viewのコンストラクターから拡張メソッドを呼び出すコードをコードビハインドに記述しています。

ソースはgithubにて公開しています。

https://github.com/hugo-sb/hsb