Posted at

Livetで始めるWPF(ざっくり)入門

More than 3 years have passed since last update.


この記事は何?

WPF入門記事です。が、巷の入門記事見たく深堀りはしません。アプリを作れるようになるのが目標です。そのため色々と楽をします。具体的に言うとLivetの使用を前提とします。ちなみにLivetはBlend SDKというものを含んでますので自動的にBlend SDKの使用も前提とします。というかLivetはともかくとしてBlend SDKなしのWPF開発はマゾゲーだと思います。

あ、あと言語はC#を使います。ただ.NETですので別にC#じゃないとダメってことではないです。C#は最新のC#5.0を前提にしていますがまあ別に古くても大丈夫だと思います。


WPFとは?

GUIアプリを作るためのやつです。WinFormsの次のやつです。8年前に登場した技術なのに未だにWinFormsで開発してたりする人が多いとか多くないとか聞きます。恐ろしいデスネ。WinFormsはWindownAPIの.NETラッパーですが、WPFはWindowsAPIとは独立した設計になってます。

WinFormsと対比させた時のWPFのメリットは

1. Windows8のストアアプリとかがWPFと似たような設計になっている(XAML系技術)

2. MSがWinFormsに積極投資することはもうない

3. 高DPI対応やらベクタグラフィックスやらが簡単に手に入る

あたりでしょうか。正直このご時世にWinFormsで開発するのは生のPHPでWeb開発するくらいにはマゾい気がします。まあでも正直デスクトップアプリ自体がオワコン、いや、なんでもないです。


Livetとは?

MVVMインフラストラクチャだそうで、簡単に言うとWPF上のフレームワークみたいな感じです。MVVMというものをやりやすくしてくれるとかそういう話は今はどうでもいいことです。ふわっと、CakePHPとかRuby on Rails的な存在なんだなと思っておけばいいと思います。

何故この記事がLivetに依存しているかというと、生のWPFで頑張ると結果的にオレオレLivetを再発明することになってしまうからです。RubyでRackからWebサービスの書き方を教えるつもりがいつのまにかオレオレSinatraの作り方を教えてしまっていた経験のある僕がいうのでそこそこ信憑性があります。今どきフレームワークなしでWeb開発を教えないのと同じ道理です。


環境構築

まずWPFはWindowsでしか動きません。Monoは開発リソース的な理由でWPFを対応していないらしいです。ということでWindows上でVisual Studioを入れてきてください。ここではVisual Studio 2013を前提にしています。僕はProfessinalバージョン使ってますが、まあExpressでも大丈夫だと思います。多分。

次にLivetですが、Nugetでも配布されてるんですが、スニペットやら色々便利なんでVS拡張を入れましょう。インストールはこちらからどうぞ。


Hello World

では早速作っていきましょう。VSのプロジェクトテンプレートに「Livet WPF4.5 MVVM アプリケーション」があると思うのでそれからプロジェクトを作ってください。あ、WPF4の方でも大丈夫です。4.5の方はCaller Infoとか使ってくれるんですが、まあなくても問題無いです。

実行したら空のウィンドウが出るかと思われます。Hello WorldがしたければViewModels/MainWindowViewModel.csのInitializeメソッドにConsole.WriteLine("hello world!");と書けばVSの出力ウィンドウに表示されます。


ファイル階層

App.xamlというのがエントリポイント的なものです。中を覗くと以下の様な感じになってると思います。


App.xaml

<Application x:Class="LivetWPFApplication1.App"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Views\MainWindow.xaml"
Startup="Application_Startup">
<Application.Resources>

</Application.Resources>
</Application>


StartupUri="Views\MainWindow.xaml"からわかるように、Views\MainWindow.xamlが最初に表示されます。次にStartup="Application_Startup"とありますが、VSのソリューションエクスプローラーからApp.xamlの矢印をクリックするとApp.xaml.csが出てくるのでそれを覗いてみましょう。Appクラスというものが定義されており、Application_Startupメソッドがあります。これはWPFアプリを実行したタイミングで呼ばれるメソッドで、まあ何かしらをやっていますね。この辺は後ほど説明しますので、今はまだ「こんな感じの処理の流れなのかー」くらいで大丈夫です。


XAMLとは?

当然のように出てきたこのXAMLとは何でしょう?XMLを拡張したやつなんですが、平たく言うとHTML的な存在です。つまり、WPFではHTMLみたいにXAMLを書きます。そしてJavaScriptみたいにC#(か、お好きな.NET言語)を書きます。

もうちょっと突っ込んだ話をすると、XAMLはC#にコンパイルされます。コンパイルされたC#は、まあ、WinFormsみたいにUIオブジェクト作ってプロパティから描画位置を指定してみたいなことをやってます。これはAltJSみたいなもんだと思ってください。生のJS書くのが辛いように、生のUIオブジェクトを触るのは色々と辛いのです。そこでHTMLっぽいXAMLというやつからC#を吐くようにしたのです。

というわけでさっきのApp.xamlももちろんC#コードにコンパイルされます。で、App.xaml.csですが、partialが付いてるので部分クラスですね。App.xamlはコンパイルされるとAppという名前の部分クラスを作ります。で、それがApp.xaml.csを通してアクセスできるということです。

先ほどのApp.xamlはコンパイルされると以下のような感じになります。


App.g.i.cs

public partial class App : System.Windows.Application {

/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent() {

#line 5 "..\..\..\App.xaml"
this.Startup += new System.Windows.StartupEventHandler(this.Application_Startup);

#line default
#line hidden

#line 4 "..\..\..\App.xaml"
this.StartupUri = new System.Uri("Views\\MainWindow.xaml", System.UriKind.Relative);

#line default
#line hidden
}

/// <summary>
/// Application Entry Point.
/// </summary>
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public static void Main() {
LivetWPFApplication1.App app = new LivetWPFApplication1.App();
app.InitializeComponent();
app.Run();
}
}


実際に$(ProjectDir)/obj/x86/Debug/App.g.i.csに生成されたC#コードがあります。ここにMainメソッドがありますね。まあそんな感じなのかー程度でいいです。

つまり、重要なのはHoge.xamlで使用してHoge.xaml.csで定義すれば動くということです。何故ならどちらも部分クラスなので同じクラスを表現してるからです。


MVVMとは?

さっきのように、XAMLで定義して部分クラスで定義するやり方をコードビハインドと言います。コードビハインドは小さなアプリだったり、コアの部分がしっかりしていてあとはUIの部分をつなぐだけです的なアプリだと全然問題無いと思います。ただ、普通にコードビハインドありきでそこそこな規模のアプリを作るとすぐに死ねます。これはまあMVCやらずにWebサービス作ろうみたいなのと同じようなもんだと思えば良いんじゃないでしょうか。

で、MVVMというものがあるんですが、MVVMとは、とか、そもそもMVCとは、みたいな話、皆さん大好物だと思うんですが、今回の趣旨はあくまでもアプリをちゃっちゃと作ろうぜ、なので割愛します。ただ、一点だけ。MVVM、Model-View-ViewModelにおいてView-ViewModel間のデータに関するやり取りはデータバインディングという機構を通じて行います。データバインディングを使ったMVCの事をMVVMと呼ぶ、くらいで良いんじゃないでしょうか。

で、データバインディングですが、幸いながらWPFはデータバインディングをデフォルトでサポートしてくれます。ということで早速書いてみましょう。


データバインディング入門

この手の記事にありがちなんですが、時計アプリをつくろうと思います。時計、アナログ時計の場合は1秒毎にUIを更新する必要があります。面倒ですね。でもデータバインディングならあら簡単。

データバインディングは前述の通りView-ViewModel間でやりとりします。ので、ViewModelに書きます。ViewModel/MainWindowViewModelを開くと何やらコメントが大量にあってビビリますが(ちなみにこのコメントがLivetの(現状ほとんど唯一の)ドキュメントです)、まあ今は無視しましょう。さて、ここでおもむろにlpropnと打ってTABを2回押すと変更通知プロパティが自動生成されます。これがデータバインディングでViewとやりとりするデータです。今回は時計ですが、表示するのは文字列なので型をstring、名前はClockとかにしておきましょう。こんな感じです。


MainWindowViewModel.cs

...

#region Clock変更通知プロパティ
private string _Clock;

public string Clock
{
get
{ return _Clock; }
set
{
if (_Clock == value)
return;
_Clock = value;
RaisePropertyChanged();
}
}
#endregion

...


お次にViewを作りましょう。Views/MainWindow.xamlを開き、VSの左の方にあるツールボックスからTextBlockをD&Dしてデザイナに投げ入れましょう。そうすると下の方に表示されてるXAML部分が変化したはずです。XAMLを一番下までスクロールするとこんな感じになってるかと思います。


MainWindow.xaml

...

<Grid>
<TextBlock HorizontalAlignment="Left" Margin="225,149,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top"/>
</Grid>

...


まあわかりますよね。GridとかはHTMLにないんであれですが、TextBlockはpタグ的な何かです。

で、XAMLを表示しているウィンドウから直接TextBloxkタグを編集します。Text="TextBlock"となってるかと思いますが、そこをText={Binding Clock}と書き換えてください。あ、その前にCtrl+Bでビルドしておいたほうが良いかも。うまく行った場合、入力の途中に名前補完が効いたと思います。これでビルドしてみましょう。はい、何も表示されなくなりましたね。これはViewModelのClocknullだからです。試しにprivateフィールドの_Clock_Clock = "hello, world!"としてみてください。はい、改めてHello Worldの出来上がりです。

で、時計です。1秒ごとにClock = DateTime.Now.ToString()するだけです。実際のコードはこんな感じです。Taskを使っているのでusing System.Threading.Tasks;を付けるのを忘れないで下さい。


MainWindowViewModel.cs

...

public void Initialize()
{
Loop();
}

async Task Loop()
{
while (true)
{
Clock = DateTime.Now.ToString();
await Task.Delay(1000);
}
}

...


これで実行するとちゃんと時計になっていると思います。あ、ちょっとLoopの呼び出しで警告食らってますが、まあ、今回はスルーで。Taskを受けていないのでキャンセルできなくなっちゃうよってやつです。async voidしてawait Loop()すれば警告は消えるんですが、根本的な解決になってません。が、今回はその話ではないので、まあ、勘弁願います。

さて、データバインディングとはなんじゃらほいってことですが、プロパティに代入するとViewが勝手に更新される魔法のことです。プロパティ内のRaisePropertyChanged()が非常に怪しそうですがまあ良いでしょう。

MVVM自体はクライアントサイドWebで流行ってるっぽいので馴染みのある方は多いかもですね。Livetを使っていると、ひたすらViewにUIを書いて、ひたすらViewModelにデータバインディングを書いて、ひたすらModelにロジックやらなんやらを書けばアプリが出来ます。多分。


まとめ

WPFの話ばっかりであんまりLivetの話してませんね。今回作った時計アプリコードをGistに貼っつけておきます。まあ、コードは全部ここで見せたのでアレですが。

多分次はView<->ViewModel<->Model間の通信についての話になるかなーと思います。ViewModel->Viewはちょっとヘビーなので、まあ、頑張りましょう。