MSBuildで始めるWPF(C#、VB)

# はじめに
ここ最近で、Visual Studioを使用せずにWPF(XAML)アプリケーションの作り方を勉強していた。
大よそ何となくやりたいことは出来るようになったのでこの際まとめておく。
日本語記事も少ないことだし。
ちなみに、csc.exeやvbc.exeでのコンパイル方法(オプション)も何となく把握できている方が理解は進むと思う。

# MSBuildの各種オプション
[MSBuild コンパイラオプション一覧](https://msdn.microsoft.com/ja-jp/library/ms164311.aspx)
[MSBuild Project Properties一覧](https://msdn.microsoft.com/ja-jp/library/bb629394.aspx)
[MSBuild .Targetsファイル](https://msdn.microsoft.com/ja-jp/library/ms164312.aspx)

# 参考文献
[XAML + WPFアプリケーションをコマンドラインでビルドする方法](http://progfire.seesaa.net/article/383127624.html)
[テキストエディタでWPF + XAMLなアプリケーションを開発する](http://symfoware.blog68.fc2.com/blog-entry-1086.html)
[メインウィンドウから子ウィンドウを開く](http://vdlz.xyz/Csharp/WPF/Control/Window/AnothorWindow/AnotherWindowOpen.html)
[WPF(VB.net & xaml)をMSBuildでコンパイル](http://yosgspec.blog103.fc2.com/blog-entry-2961.html)

# MSBuildの環境について
[ここ](https://www.microsoft.com/ja-JP/download/details.aspx?id=48159)からダウンロードしてインストールできる。
VisualStudioでも可。
インストールした後のパスは大体、

```txt:
C:\Program Files\MSBuild\14.0\Bin\msbuild.exe
C:\Program Files(x86)\MSBuild\14.0\Bin
```
にある。
ここからPathに突っ込むなりbat作るなりして自由に使う。
ちなみにWindows7に標準でインストールされているMSBuildは.Net Framework3.5相当になるので結構古く、注意。

# ビルドの実行
基本的にはプロジェクトファイルに移動してmsbuildコマンドを実行すればよい。

```bat:
C:\> cd C:\sample
C:\sample

C:\sample> msbuild
```
同じフォルダにprojectファイルが複数存在する場合は、msbuildのコマンドライン引数にそのままファイル名を渡せばいいが、滅多にそれを行うことはないだろう。
他の詳細については下記を参照のこと。
[MSBuild コンパイラオプション一覧](https://msdn.microsoft.com/ja-jp/library/ms164311.aspx)

# 雛型
MSBuildのプロジェクトファイルの雛型は以下となる。
拡張子はC#、VB共用のprojかC#用のcsproj、VB用のvbprojとなる。

```xml:
<?xml version="1.0" encoding="utf-8" ?>
<Project
	xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
	DefaultTargets="Build"
>

	<PropertyGroup>
		<!-- コンパイラオプション -->
	</PropertyGroup>
	
	<ItemGroup>
		<!-- コンパイルするファイル、using、Imports設定 -->
	</ItemGroup>

	<!-- .targetファイル -->
	<Import Project= />

</Project>
```
じっくり見てみると複雑そうに見えて構造は結構単純。
上の方はいまいち理解が足りないけど、おまじないとしておいておくのが吉。多分変えることもそう多くはない。
文法こそ異なるがやったことあるのならNW.jsやElectronのpackage.jsonと似た感じで構築できる。

# PropertyGroup(コンパイラオプションについて)
このPropertyGroupに含まれるものはアプリケーションの名前などプロフィールや、csc.exe、vbc.exeでのコンパイラオプションとほぼ同一のものとなる。
どのようなものや機能があるのかは、
[MSBuild Project Properties一覧](https://msdn.microsoft.com/ja-jp/library/bb629394.aspx)
を見て確認すること。
以下では実際に作ったアプリケーションの例として載せる。

```xml:project.csproj
<?xml version="1.0" encoding="utf-8" ?>
<Project
	xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
	DefaultTargets="Build"
>

	<PropertyGroup>
		<!-- 出力ファイル名 -->
		<AssemblyName>txtLret</AssemblyName>
		<!-- 出力ファイルのファイル形式(Winexe=GUIプログラミング(WPF)) -->
		<OutputType>Winexe</OutputType>
		<!-- 出力パス -->
		<OutputPath>.\</OutputPath>
		<!-- 使用する.NetFrameworkのバージョン -->
		<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
		<!-- コンパイルをデバッグで行うかリリースで行うか -->
		<Configuration>Release</Configuration>
		<!-- コード最適化の有無(true=有) -->
		<Optimize>true</Optimize>
	</PropertyGroup>
	
	<ItemGroup>
		<!-- コンパイルするファイル、using、Imports設定 -->
	</ItemGroup>

	<!-- .Targetsファイル -->
	<Import Project= />

</Project>
```

```xml:project.vbproj
<?xml version="1.0" encoding="utf-8" ?>
<Project
	xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
	DefaultTargets="Build"
>

	<PropertyGroup>
		<!-- 出力ファイル名 -->
		<AssemblyName>txtLret</AssemblyName>
		<!-- 出力ファイルのファイル形式(Winexe=GUIプログラミング(WPF)) -->
		<OutputType>Winexe</OutputType>
		<!-- 出力パス -->
		<OutputPath>.\</OutputPath>
		<!-- 使用する.NetFrameworkのバージョン -->
		<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
		<!-- コンパイルをデバッグで行うかリリースで行うか -->
		<Configuration>Release</Configuration>
		<!-- コード最適化の有無(true=有) -->
		<Optimize>true</Optimize>
		<!-- VBのOption Infer(型推論)をON=trueにする(デフォルトOFF=false) -->
		<OptionInfer>true</OptionInfer>
		<!-- VBのオーバーフローチェックを無効化する(デフォルトOFF=false) -->
		<RemoveIntegerChecks>true</RemoveIntegerChecks>
	</PropertyGroup>
```

# ItemGroup
ここには、プログラムの部品として必要なソースやライブラリを追加する。
これのItem名としては、

```xml:
<ApplicationDefinition Include="app.xaml" />
<Page Include="index.xaml" />
<Compile Include="main.vb" />
<Reference Include="System" />
<Import Include="System" />
```
などがある。などというのは、僕もこれ以外に何が存在するのかも把握できていないため。恐らく、csc.exeやvbc.exeでアイテムとして追加できるものであれば、対応するオプションが存在するはず。

これを上から説明していく。

## ApplicationDefinition
ここにはエントリポイントとしてxamlを呼び出すxamlファイルを定義する。

```xml:
<ApplicationDefinition Include="app.xaml" />
```
このxamlファイルは少々特殊で、本当にエントリポイントを指定するためだけのファイルになりそれ以外には何も記述をしない。

```xml:app.xaml
<Application
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	StartupUri="index.xaml"
/>
```
ちなみに、[ここ](https://msdn.microsoft.com/ja-jp/library/ms743714(v=vs.110).aspx)を見る限りだと、あるプログラムに変換されるようで、自動的に標準のxamlファイルにエントリポイントを渡すところまでを行うようだ。

ちなみにApplicationDefinitionはあるプログラムに変換されるといったように、そこを自力で書ければApplicationDefinitionプロパティがプロジェクトファイル内に存在しなくてもWPFプログラムを作ることは可能。これは後述する。

## Page
ページファイル、つまりxamlファイルはここにIncludeする。
必要な分だけIncludeする。

```xml:
<Page Include="index.xaml" />
```
起動時に立ち上がるページ(エントリポイント)はApplicationDefinitionで指定したxaml内に記述する。

## Compile
コンパイルファイル、つまりc#やVBソースファイルはここに該当する。
ワイルドカードを指定することも可能。

```xml:
<Compile Include="main.cs" />
<Compile Include="main.vb" />
<Compile Include="*.vb />
```

## Reference
必要なライブラリの参照をここに定義する。
ちなみにWPFでプログラミングを行う場合は下記の参照が必ず必要となるので必ず追加すること。

```xml:
<Reference Include="System" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
```

## Import
名前空間の追加。
その名の通り、C#のusingやVBのImportsに相当する物。
ここに追加すれば、グローバルで拡張されることになるので、追加する物はある程度吟味した方がいいのかもしれない。
ちなみにVBの"Microsoft.VisualBasic"名前空間も標準で追加されないので必要であれば追加しておく。

```xml
<Import Include="System" />
<Import Include="Microsoft.VisualBasic" />
```

# .targets
最後に、.targetsと呼ばれるファイルを指定する。
正直、ここは僕も理解できていないけれど、[公式](https://msdn.microsoft.com/ja-jp/library/ms164312.aspx)を読む感じでは、使用する言語に合わせて、

```xml:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
```
のどちらかにしておけば間違いはなさそうだ。

# サンプルソース
これらを踏まえて一つのサンプルソースを載せておく。フォルダ構成としては下記のようになる。

```text:
sample\
    project.csproj 又は project.vbproj
	app.xaml
    index.xaml
    main.cs 又は main.vb
```

```xml:project.csproj
<?xml version="1.0" encoding="utf-8" ?>
<Project
	xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
	DefaultTargets="Build"
>

	<PropertyGroup>
		<!-- 出力ファイル名 -->
		<AssemblyName>txtLret</AssemblyName>
		<!-- 出力ファイルのファイル形式(Winexe=GUIプログラミング(WPF)) -->
		<OutputType>Winexe</OutputType>
		<!-- 出力パス -->
		<OutputPath>.\</OutputPath>
		<!-- 使用する.NetFrameworkのバージョン -->
		<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
		<!-- コンパイルをデバッグで行うかリリースで行うか -->
		<Configuration>Release</Configuration>
		<!-- コード最適化の有無(true=有) -->
		<Optimize>true</Optimize>
	</PropertyGroup>
	
	<ItemGroup>
		<!-- エントリポイント指定用XAMLファイル -->
		<ApplicationDefinition Include="app.xaml" />

		<!-- XAMLファイル -->
		<Page Include="index.xaml" />

		<!-- CSファイル -->
		<Compile Include="main.cs" />

		<!-- 参照の追加 -->
		<Reference Include="System" />
		<Reference Include="System.Xaml" />
		<Reference Include="WindowsBase" />
		<Reference Include="PresentationCore" />
		<Reference Include="PresentationFramework" />
	</ItemGroup>

	<!-- .targets -->
	<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

</Project>
```

```xml:project.vbproj
<?xml version="1.0" encoding="utf-8" ?>
<Project
	xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
	DefaultTargets="Build"
>

	<PropertyGroup>
		<!-- 出力ファイル名 -->
		<AssemblyName>txtLret</AssemblyName>
		<!-- 出力ファイルのファイル形式(Winexe=GUIプログラミング(WPF)) -->
		<OutputType>Winexe</OutputType>
		<!-- 出力パス -->
		<OutputPath>.\</OutputPath>
		<!-- 使用する.NetFrameworkのバージョン -->
		<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
		<!-- コンパイルをデバッグで行うかリリースで行うか -->
		<Configuration>Release</Configuration>
		<!-- コード最適化の有無(true=有) -->
		<Optimize>true</Optimize>
		<!-- VBのOption Infer(型推論)をON=trueにする(デフォルトOFF=false) -->
		<OptionInfer>true</OptionInfer>
		<!-- VBのオーバーフローチェックを無効化する(デフォルトOFF=false) -->
		<RemoveIntegerChecks>true</RemoveIntegerChecks>
	</PropertyGroup>
	
	<ItemGroup>
		<!-- エントリポイント指定用XAMLファイル -->
		<ApplicationDefinition Include="app.xaml" />

		<!-- XAMLファイル -->
		<Page Include="index.xaml" />

		<!-- VBファイル -->
		<Compile Include="main.vb" />

		<!-- 参照の追加 -->
		<Reference Include="System" />
		<Reference Include="System.Xaml" />
		<Reference Include="WindowsBase" />
		<Reference Include="PresentationCore" />
		<Reference Include="PresentationFramework" />

		<!-- 名前空間の追加 -->
		<Import Include="System" />
		<Import Include="Microsoft.VisualBasic" />
	</ItemGroup>

	<!-- .targets -->
	<Import Project="$(MSBuildBinPath)\Microsoft.VisualBasic.targets" />

</Project>
```

```xml:app.xaml(C#、VB)
<Application
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	StartupUri="index.xaml"
/>
```

```xml:index.xaml(C#、VB)
<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="txtLret"
	Title="テキストルーレット"
	Width="620"
	Height="440"
	ResizeMode="NoResize"
	FontFamily="Meiryo UI"
	FontSize="16"
	Foreground="#000099"
	Background="#3399FF"
>
	<Window.Resources>
		<Style TargetType="TextBox">
			<Setter Property="Width" Value="580" />
			<Setter Property="HorizontalAlignment" Value="Center" />
		</Style>
		<Style TargetType="Button">
			<Setter Property="Height" Value="24" />
			<Setter Property="Margin" Value="10" />
		</Style>
	</Window.Resources>
	<StackPanel>
		<TextBlock
			Margin="0,10,0,5"
			HorizontalAlignment="Center"
			Text="ルーレットしたい値を入れてね。"
		/>
		<TextBox
			AcceptsReturn="True"
			VerticalScrollBarVisibility="Auto"
			Height="260"
			Name="txtList"
		/>
		<WrapPanel HorizontalAlignment="Center">
			<Button Width="200" Click="lretMain" Content="Let's ルーレット!" />
			<Button Width="100" Click="lretDefault" Content="MH武器" />
		</WrapPanel>
		<TextBox Name="resultList" />
	</StackPanel>
</Window>
```

```csharp:main.cs(C#)
using System;
using System.Windows;

partial class txtLret{
	Random rand=new Random();

	void lretMain(object sender,RoutedEventArgs e){
		string[] txtLists=txtList.Text.Split('\n');
		resultList.Text=txtLists[rand.Next(txtLists.Length)];
	}

	void lretDefault(object sender,RoutedEventArgs e){
		txtList.Text=
@"大剣
太刀
片手剣
双剣
ランス
ガンランス
ハンマー
狩猟笛
操虫棍
穿龍棍
ライトボウガン
ヘビィボウガン
弓";
	}
}
```

```vbnet:main.vb(VB)
Option Strict On

Imports System
Imports System.Windows

Class txtLret
	Dim rand As New Random()

	Sub lretMain()
		Dim txtLists As String()=Split(txtList.Text,vbLf)
		resultList.Text=txtLists(rand.Next(txtLists.Length))
	End Sub

	Sub lretDefault()
		txtList.Text= _
$"大剣
太刀
片手剣
双剣
ランス
ガンランス
ハンマー
狩猟笛
操虫棍
穿龍棍
ライトボウガン
ヘビィボウガン
弓"

	End Sub
End Class
```

# ApplicationDefinitionを指定しないでビルドする。
ApplicationDefinitionを指定しなかった場合はプログラムのエントリポイントが通常通りMain関数となる。そこから内部的にApplicationDefinitionが行っていることをダイレクトにやってあげれば良い。[※](https://msdn.microsoft.com/ja-jp/library/ms743714(v=vs.110).aspx)
やることとしては特に難しくないのでこれでも良いのかもしれない。
ちなみにXAMLのWindow.x:Classに指定したクラス名はそのページファイルの実体にもなる。すなわち、そのクラス名でnewすれば、そのページ/XAMLもnewできるということ。

```csharp:main.cs
class Entry{
	[STAThread]
	static void Main(){
		var app=new Application();
		var win=new txtLret();
		win.InitializeComponent();
		app.Run(win);
	}
}
```

```vbnet:main.vb
Module Entry
	<STAThread>
	Sub Main()
		Dim app As New Application()
		Dim win As New txtLret()
		app.Run(win)
	End Sub
End Module
```
最低限やるべきことはそれぞれこれ位。
([STAThread](http://dobon.net/vb/dotnet/form/stathread.html)についてはこちらでを参照。)
ちなみにVBの方は[Window].InitializeComponentが無くても読みこむことには問題が無いみたい。

Mainでページファイルを扱うことができるということはXAMLのWindow内に定義していたプロパティもMainの中で定義できるということです。次のサンプルではこれも踏まえて載せておく。

# サンプルソースEX
フォルダ構成としては下記のようになる。ファイル構成としてはapp.xamlが抜けただけである。

```text:
sample\
    project.csproj 又は project.vbproj
    index.xaml
    main.cs 又は main.vb
```

```xml:project.csproj
<?xml version="1.0" encoding="utf-8" ?>
<Project
	xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
	DefaultTargets="Build"
>

	<PropertyGroup>
		<!-- 出力ファイル名 -->
		<AssemblyName>txtLret</AssemblyName>
		<!-- 出力ファイルのファイル形式(Winexe=GUIプログラミング(WPF)) -->
		<OutputType>Winexe</OutputType>
		<!-- 出力パス -->
		<OutputPath>.\</OutputPath>
		<!-- 使用する.NetFrameworkのバージョン -->
		<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
		<!-- コンパイルをデバッグで行うかリリースで行うか -->
		<Configuration>Release</Configuration>
		<!-- コード最適化の有無(true=有) -->
		<Optimize>true</Optimize>
	</PropertyGroup>
	
	<ItemGroup>
		<!-- XAMLファイル -->
		<Page Include="index.xaml" />

		<!-- CSファイル -->
		<Compile Include="main.cs" />

		<!-- 参照の追加 -->
		<Reference Include="System" />
		<Reference Include="System.Xaml" />
		<Reference Include="WindowsBase" />
		<Reference Include="PresentationCore" />
		<Reference Include="PresentationFramework" />
	</ItemGroup>

	<!-- .targets -->
	<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

</Project>
```

```xml:project.vbproj
<?xml version="1.0" encoding="utf-8" ?>
<Project
	xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
	DefaultTargets="Build"
>

	<PropertyGroup>
		<!-- 出力ファイル名 -->
		<AssemblyName>txtLret</AssemblyName>
		<!-- 出力ファイルのファイル形式(Winexe=GUIプログラミング(WPF)) -->
		<OutputType>Winexe</OutputType>
		<!-- 出力パス -->
		<OutputPath>.\</OutputPath>
		<!-- 使用する.NetFrameworkのバージョン -->
		<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
		<!-- コンパイルをデバッグで行うかリリースで行うか -->
		<Configuration>Release</Configuration>
		<!-- コード最適化の有無(true=有) -->
		<Optimize>true</Optimize>
		<!-- VBのOption Infer(型推論)をON=trueにする(デフォルトOFF=false) -->
		<OptionInfer>true</OptionInfer>
		<!-- VBのオーバーフローチェックを無効化する(デフォルトOFF=false) -->
		<RemoveIntegerChecks>true</RemoveIntegerChecks>
	</PropertyGroup>
	
	<ItemGroup>
		<!-- XAMLファイル -->
		<Page Include="index.xaml" />

		<!-- VBファイル -->
		<Compile Include="main.vb" />

		<!-- 参照の追加 -->
		<Reference Include="System" />
		<Reference Include="System.Xaml" />
		<Reference Include="WindowsBase" />
		<Reference Include="PresentationCore" />
		<Reference Include="PresentationFramework" />

		<!-- 名前空間の追加 -->
		<Import Include="System" />
		<Import Include="Microsoft.VisualBasic" />
	</ItemGroup>

	<!-- .targets -->
	<Import Project="$(MSBuildBinPath)\Microsoft.VisualBasic.targets" />

</Project>
```

```xml:index.xaml(C#、VB)
<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="txtLret"
>
	<Window.Resources>
		<Style TargetType="TextBox">
			<Setter Property="Width" Value="580" />
			<Setter Property="HorizontalAlignment" Value="Center" />
		</Style>
		<Style TargetType="Button">
			<Setter Property="Height" Value="24" />
			<Setter Property="Margin" Value="10" />
		</Style>
	</Window.Resources>
	<StackPanel>
		<TextBlock
			Margin="0,10,0,5"
			HorizontalAlignment="Center"
			Text="ルーレットしたい値を入れてね。"
		/>
		<TextBox
			AcceptsReturn="True"
			VerticalScrollBarVisibility="Auto"
			Height="260"
			Name="txtList"
		/>
		<WrapPanel HorizontalAlignment="Center">
			<Button Width="200" Click="lretMain" Content="Let's ルーレット!" />
			<Button Width="100" Click="lretDefault" Content="MH武器" />
		</WrapPanel>
		<TextBox Name="resultList" />
	</StackPanel>
</Window>
```

```csharp:main.cs(C#)
using System;
using System.Windows;
using System.Windows.Media;

class Entry{
	[STAThread]
	static void Main(){
		var app=new Application();
		var win=new txtLret();
		new Action<txtLret>(w=>{
			w.Title="テキストルーレット";
			w.Width=620;
			w.Height=440;
			w.ResizeMode=ResizeMode.NoResize;
			w.FontFamily=new FontFamily("Meiryo UI");
			w.FontSize=16;
			w.Foreground=new SolidColorBrush(Color.FromArgb(0xFF,0x00,0x00,0x99));
			w.Background=new SolidColorBrush(Color.FromArgb(0xFF,0x33,0x99,0xFF));
			w.InitializeComponent();
		})(win);
		app.Run(win);
	}
}

partial class txtLret{
	Random rand=new Random();

	void lretMain(object sender,RoutedEventArgs e){
		string[] txtLists=txtList.Text.Split('\n');
		resultList.Text=txtLists[rand.Next(txtLists.Length)];
	}

	void lretDefault(object sender,RoutedEventArgs e){
		txtList.Text=
@"大剣
太刀
片手剣
双剣
ランス
ガンランス
ハンマー
狩猟笛
操虫棍
穿龍棍
ライトボウガン
ヘビィボウガン
弓";
	}
}
```

```vbnet:main.vb(VB)
Option Strict On

Imports System
Imports System.Windows
Imports System.Windows.Media

Module Entry
	<STAThread>
	Sub Main()
		Dim app As New Application()
		Dim win As New txtLret()
		With win
			.Title="テキストルーレット"
			.Width=620
			.Height=440
			.ResizeMode=ResizeMode.NoResize
			.FontFamily=New FontFamily("Meiryo UI")
			.FontSize=16
			.Foreground=New SolidColorBrush(Color.FromArgb(&HFF,&H00,&H00,&H99))
			.Background=New SolidColorBrush(Color.FromArgb(&HFF,&H33,&H99,&HFF))
		End With
		app.Run(win)
	End Sub
End Module

Class txtLret
	Dim rand As New Random()

	Sub lretMain()
		Dim txtLists As String()=Split(txtList.Text,vbLf)
		resultList.Text=txtLists(rand.Next(txtLists.Length))
	End Sub

	Sub lretDefault()
		txtList.Text= _
$"大剣
太刀
片手剣
双剣
ランス
ガンランス
ハンマー
狩猟笛
操虫棍
穿龍棍
ライトボウガン
ヘビィボウガン
弓"

	End Sub
End Class
```