#はじめに
この記事はWinFormsで開発していた人間が、WPFにチャレンジして4ヶ月ぐらい経過した時点での導入前と導入後の雑感です。
未使用ですが、同じくMVVMやXAMLを使うXamarinやUWPにも共通している点があると思います。
WPFを検討している、推進したいが理解が得られない、やってみたけど挫折しそうになっている、といった人の参考になれば幸いです。
良い悪い、ともに現時点で素直に感じたことを書いていますが、私の勉強不足による勘違いもあるかもしれません。
その点に関しては是非ご指摘下さい。
#直接の導入のきっかけ
前からWPFにチャレンジしたいとは思っていましたが、導入のきっかけはポジティブなものではありませんでした。
それはある日の昼下がりのことです。
WinFormsで開発したアプリを自前のPCで動作確認後、実際に使用するノートPCで動作させたのです。
すると、見た目が崩れまくっていたのです(@_@)!
その画面はギチギチにコントロールが詰まった設定画面で、設定値を入力することさえ困難でした。
原因は最近のノートPCでよくある96DPI以上の高画素密度のモニターでした。
WinFormsだと見た目が崩れます。それもDPI値によって崩れ方がみな違います。
当初はapp.manifestのdpiAwareや実行時のDPI値取得からの全コントロールのサイズ調整などでの対策を行ったが完全には解決しませんでした。
最終的に一度描画したTextBoxの大きさを計測して……などという対策をする羽目になってしまったのです。
これはちょっと耐え難い
→なんでもWPFならDPIによらず大きさを一定にできるらしい
→WPFやってみるか
#導入前の印象
以前から予備調査はしていたのでその時点での印象です。
- 高DPI環境でも崩れない
- デザイナーとの分業で見た目をカッコよくできる
- WinFormsでは使えたものじゃなかったデザイナー画面が有用になるらしい
- XAMLは冗長にみえる
- なんでも文字列で指定なXAMLに違和感、コンパイルエラーしないの?
- MVVMで開発が楽になる
- データバインディングというよくわらん
黒魔術技術が便利 - MVVMがめんどくさそう
- コード量が極端に増える
- 動作が重い
- VSの名前変更やインテリセンスを使った楽ちんコーディングから昔に戻らなきゃいけないのか
- 今まではコンパイルエラーだったのがバインディングを使うと実行時じゃないとエラーしない
- インターネット上の(日本語の)情報が少ない
- ライブラリを使うのが必須なため、その少ない情報がさらにライブラリ違いのため参考にならなかったりする
- 登場して10年だが、あまり普及した印象が無い
- VisualStudioでさえ機能豊富で使い切れないのにBlendとかいう別のIDEを覚える必要がある
というわけで、ほぼ悪い印象ですw
#導入後
これらの印象が導入後にどう変化したか、以下の3つに分類しながら解説します
◯:良い印象、懸念点は解決された
△:まだ不明、懸念点は残っているがそれほど問題にならなくなった
☓:悪い印象、問題だと思っている
・高DPI環境でも崩れない
⇒◯ 崩れませんでした。あの苦行はなんだったのだろうか。問題解決!(゚∇゚ノノ"☆
・デザイナーとの分業で見た目をかっこよくできる
⇒◯ アプリの見た目を整えるのがとても容易です。
StyleやResourceDictionaryを使用してカッコよく、しかも複数の画面で統一した見た目にできます。
当初はカッコよくできると言ってもデザイナーとの分業が前提でしょ、と思っていましたが、
拾ってきたThemeなどを導入するだけで印象がガラッと変わります。
MaterialDesignInXamlToolkitがオススメです。
見た目を変えるのが楽しすぎて、過度にデザインに時間をかけてしまうのが問題です(個人差が有ります)。
・WinFormsでは使えたものじゃなかったデザイナー画面が有用になるらしい
⇒◯ 比べる相手の問題かもしれませんが、やはり有用です。
ただツールバーからドラッグ・アンド・ドロップで開発するのではなく、XAMLの手打ちが基本になります。
画面はXAMLの結果のプレビューとして使います。
デザイン用のデモデータを渡せば外部データを受け取った後の実行時の見た目を再現しながらコーディングなんてことが簡単にできます。
・XAMLは冗長にみえる
⇒△ やはり冗長には見えます。
xmlns:x="http:~
などのおまじない感はだいぶあります。
終了タグは</>
ではだめだったのか?
ただし慣れてくるとXAMLを読み飛ばせるようになるので以前ほどは気にならなりました。
C#と比べてXAMLの良いと感じた点はXAMLインデント構造がそのままUI構造になるので全体の見通しは良いことです。
例えば
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
であったら、概ねこの画面は2列3行で区切られているということがわかります。
その上で<Grid>
と同じネストのタグを(折りたたむなどして)見ていくと、
「+」<Menu Grid.Row="0" Grid.ColumnSpan ="2"...>
「+」<UserControl Grid.Row="1" Grid.Column="0"...>
...
0行目は全列貫通で<Menu>
、1行0列目は<UserControl>
で……といった形で把握できます。
WinFormsでUI部品がどのSplitContainerの、どのPanelにいるか、なんていうのを探すのに比べるとすごく楽です。
・なんでも文字列で指定なXAMLに違和感、コンパイルエラーしないの?
⇒△ Height="870"
のダブルクオーテーションにはやはり違和感があります。
ただしHeight="870a"
などはコンパイルエラーになるので、内部的には文字列でない?
・MVVMで開発が楽になる
・データバインディングというよくわらん 黒魔術 技術が便利
・MVVMがめんどくさそう
⇒◯ MVVMはめんどい部分もあるけど、便利!。
静的なUI表現(View)、コアとなるビジネスロジック(Model)が分離されることでそれぞれのコーディングに集中できます。
両者をつなぎUIの状態を保持するViewModelを使うことでビジネスロジックと分離したUI状態の管理できます。
これによりアプリケーションの複雑さを軽減しながら柔軟なUIを作成できます。
またWPF自体はMVVMを使わず、今までのようにコードビハインドで書くこともできます。
MVVMが嫌になったらそれでも問題ありません。
小さいアプリならむしろそれでいいと思います。
ただしある程度の大きさ(クラスが10以上?)になったらMVVMを導入した方がいいのではでしょうか。
導入前はWPFはMVVMでやる必要がある、と考えていました。
しかしこれは話の順序が逆で、規模の大きい開発ではMVVMの方が良い、それを前提でWPFは設計されている、ということなのです。
またMVVMやデータバインディングを使う場合でも全くコードビハインドを使ってはいけないわけではないという気もしないでもないです(諸説あります)。
実際に導入後に開発したアプリも大量の描画が必要な部分はView対する参照をViewModelに持ち、コードで描写しています(もっと良いやり方がある気もしますが)。
既存のライブラリやクラスを利用する部分もデータバインディングは使えないので、そのあたりをModelまたはViewModel階層で吸収しています。
MVVMはこうすべき、というのは色々議論もありますが、教条主義に陥らず、自分にあったやり方でやっていけばいいんじゃないかな~と思っています。
・コード量が極端に増える
⇒△ 増えたが思っていたほどでは無かったです。
単純に言って今までFormにべた書きしていたものがView-ViewModel(-Model)に増えた上で、それぞれ重複内容があります。
ファイルから読み取った文字列を画面に出す、というコードをWinFormsとMVVMなWPFそれぞれで書くと以下の通りです。usingや名前空間などは抜いてあります。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InputTextBox.Text = ReadFile();
}
private string ReadFile()
{
using (var fs = new FileStream("test.txt", FileMode.Open))
using (var sr = new StreamReader(fs))
{
return sr.ReadLine();
}v
}
}
だったものが
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding InputText}"/>
</StackPanel>
class MainViewModel : INotifyPropertyChanged
{
MainModel model = new MainModel();
public string InputText
{
get { return model.InputText; }
set
{
if (value != model.InputText)
{
model.InputText = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
class MainModel : INotifyPropertyChanged
{
public MainModel(
{
this.InputText = ReadFile();
}
private string ReadFile()
{
using (var fs = new FileStream("test.txt", FileMode.Open))
using (var sr = new StreamReader(fs))
{
return sr.ReadLine();
}
}
private string inputText;
public string InputText
{
get { return inputText; }
set
{
if (value != inputText)
{
inputText = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
とご覧のありさまです。
しかしこれは前述したように規模の小さなアプリケーションではMVVMのメリットが小さい、という代表例のようなものです。
ファイル読込部分が複雑になり、さらに非同期・一定間隔でファイルを読込、となったらWinFormsだって別クラスに処理を移譲するでしょう。
また特にViewModelの記述については
INotifyPropertyChanged実装のありえない面倒くささと、ReactivePropertyの信じられない素晴らしさ
の通りにReactivePropertyを使うことである程度改善しました。
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding InputText.Value}"/>
</StackPanel>
class MainViewModel
{
MainModel model = new MainModel();
public ReactiveProperty<string> InputText { get; private set; }
public MainViewModel()
{
this.InputText = model.InputText;
}
}
class MainModel
{
public ReactiveProperty<string> InputText { get; private set; }
= new ReactiveProperty<string>();
public MainModel()
{
InputText.Value = ReadFile();
}
private string ReadFile()
{
using (var fs = new FileStream("test.txt", FileMode.Open))
using (var sr = new StreamReader(fs))
{
return sr.ReadLine();
}
}
}
感動的に短い!
注意点はXAMLの InputText.Value
の.Valueのつけ忘れです。これWarning出す方法ないのかなー。
・動作が重い
⇒△ 今のところ重いと感じていません。
実行するPCがそれなりなスペック(Corei3以上)だからかもしれません。
また前述したように、大量の描画が必要なところはコードビハインドにしてしまいました。
おそらく工夫次第で解決できる、という楽観を持っています。
・VSの名前変更やインテリセンスを使った楽ちんコーディングから昔に戻らなきゃいけないのか
⇒△ XAMLにもインテリセンスはあります。
ただC#のそれに比べれば貧弱で、ヘルパーテキストとかは表示されません。
VisualStudioの名前変更リファクタリングも反映されません。コメントすら変更できるのに何故?
ただ思ってたよりもXAMLを書く量が多くなかったのでそれほど困りませんでした。
ViewModel以降は当然C#なので今まで通り使えます。
INotifyPropertyChangedのプロパティ名通知が文字列指定の場合、リファクタリングが効きませんが、
最新のC# 6.0でMVVMパターンを実装する
(-2.2. nameof演算子でリファクタリングが捗る!?)
の通りにnameof演算子を使用するとそれも回避できます。
・今まではコンパイルエラーだったのがバインディングを使うと実行時じゃないとエラーしない
⇒△ ある程度はあります。
ただバインディングのエラーはコンパイル時に出力ウインドウにWarningが出るので(見逃さなければ)わかるVSColorOutputがあると見やすいです。
またUWPだと{x:Bind}
を使えばコンパイルエラーを起こすらしい。うらやま
・インターネット上の(日本語の)情報が少ない
⇒☓ やはり少ないです。
XamarinやUWPを理解して応用できれば情報も増えるかもしれませんがまだ良くわかっていません。
・ライブラリが必須なため、その少ない情報がさらにライブラリ違いのため参考にならなかったりする
⇒△ 代表的なライブラリを覚えれば、読み替えて(概ね)応用可能でした。
でもライブラリそれぞれの情報はやはり相対的に少ないです。
・登場して10年だが、あまり普及した印象が無い
⇒△ WPF自体はやはり普及したとは言えないと思います。
第7回 業務アプリ開発についてのアンケート結果(2012年9月実施)
http://www.atmarkit.co.jp/ait/articles/1210/26/news009.html
WinForms:49%、WPF:10%
4年前と少し古い情報ですが、この時点からあまり増えた印象も無いです。
XamarinやUWPなどを合わせれば増えているかもしれません。
とはいえWindows7も対象とした開発となると、これらは使えません。
ある日突然Microsoftが世界中のPCをWinding10に勝手にアップデートしない限りは。
ただしWPFは駄目でもXAMLはこれからもMicrosoft系開発技術では使われていきそうですし、
デスクトップ開発自体が消滅してもMVVMはAngularJSなど他のMVC技術にも応用可能ですので、
覚えた技術が無駄にはならなそうかな、とは感じています。
・VisualStudioでさえ機能豊富で使い切れないのにBlendとかいう別のIDEを覚える必要がある
⇒◯ 結局使わずとも出来ました。
アニメーションを豊富に使うなどする場合は必要?見た目や操作も近いし覚えるとしてもVisualStudioの延長でいけそうです。
そもそもなぜVisualStudioに統合しなかったのだろうか?
#まとめ
結論から言えば、WPF導入して良かったです。
最初はなぜMVVMなんて面倒なことをしなきゃいけないんだ!全部コードビハインドにしてやる!
と思うこともしばしばでしたが、無理矢理にでもMVVMで書いて、徐々に交通整理をしていくとそのメリットも理解できました。
まだ実施していないの書きませんでしたが、MVVMにすることによる自動テストのメリットも大きいでしょう。
悪口デメリットも書きましたが、新しく始める人はこんなとこでつまずくんだ、という例の1つと思って下さい。
#参考
WPF4.5入門 その61「データバインディングを前提としたプログラミングモデル その2」
むずかしくないWPF