.NET: コマンドラインで WPF アプリをビルドする

Visual Studio がない環境で、Windows Presentation Framework (WPF) を利用したアプリをビルドしたい。

背景

業務中にちょっとした GUI のツールを作りたくなることがある。

規模の大きなものでなければ、Visual Studio があれば C#VB で、Java の開発環境があれば Swing や AWT でささっと作れる。

しかし、Visual Studio は基本的には有償であり、無償で使える Community は個人や教育目的に限られている。Java Runtime Environment は 2019 年 4 月 16 日以降ライセンスが変更され、Java 開発用途でなければ無償で利用できなくなったので、おそらく、Java 以外の言語で開発している人にツールの実行環境として入れてもらうのは NG だろう。

PythonTkinter や Kivy、あるいは Electron も考えられる。最近では Windows ではなく LinuxMac で開発するのも増えているから、クロスプラットフォームという意味ではよさそうだ。ただし、Tkinter は Look and Feel がイマイチで、Kivy は日本語入力が正常に行えず、Electron はサイズが大きいのでちょっとしたツールにはオーバースペックな感がある。

スタンドアロンのアプリを諦めて、node.js などを使って Web ベースのアプリにするという手もある。アプリを起動すると Web サーバが立ち上がって、ブラウザでアクセスするという形式。これはクロスプラットフォームでもあるのでよさそうだけど、アプリの起動・停止とブラウザを開いたり閉じたりするのと、という二手間かかるという小さなデメリットがある。

という感じでモヤモヤ悩んだあたりで、Windows に入っている .NET Framework に付属しているコンパイラやビルドツールを使って、.NET アプリを作る方法を検討してみることにした。

制限

Visual Studio Code .NET Core がインストールできる場合、デバッグ実行できる環境が無償で構築できる。しかし、今回は大人の事情で、Visual Studio Code は使えるが .NET Core はインストールできない、という条件で考える。つまり、デバッグ実行は諦める。

作ってみる

基本的には、Qiita の下記記事をそのままたどった。

qiita.com

ただし、ファイル名と、ファイルの中身を何ヶ所かいじった。

ファイル構成

練習がてらプロジェクト名を変えて HelloWpf として、ついでにバッチファイルを 1 つ足した結果、以下のようになった。

  • HelloWpf.csproj
  • enter.bat
  • src

XAML とセットで作る partial クラスのファイルは、元記事だと拡張子が単に .cs になっていたんだけど、Visual Studio の流儀に合わせて .xaml.cs とすることにした。

あと、元記事ではウィンドウのクラス名とファイル名は一致していなかったんだけど、同じにしてみた。

HelloWpf.csproj

MSBuild で使う構成ファイル。

<Project DefaultTargets="Build" ToolsVersion="4.0"
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
        <Platform Condition="'$(Platform)' == ''">AnyCPU</Platform>
        <RootNamespace>App</RootNamespace>
        <AssemblyName>HelloWpf</AssemblyName>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
        <DebugSymbols>true</DebugSymbols>
        <DebugType>full</DebugType>
        <Optimize>false</Optimize>
        <OutputPath>bin\Debug\</OutputPath>
        <OutputType>WinExe</OutputType>
        <DefineConstants>DEBUG;TRACE</DefineConstants>
        <ErrorReport>prompt</ErrorReport>
        <WarningLevel>4</WarningLevel>
        <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
        <DebugType>pdbonly</DebugType>
        <Optimize>true</Optimize>
        <OutputPath>bin\Release\</OutputPath>
        <OutputType>WinExe</OutputType>
        <DefineConstants>TRACE</DefineConstants>
        <ErrorReport>prompt</ErrorReport>
        <WarningLevel>4</WarningLevel>
        <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    </PropertyGroup>
    <ItemGroup>
        <Reference Include="System" />
        <Reference Include="System.Core">
          <RequiredTargetFramework>4.0</RequiredTargetFramework>
        </Reference>
        <Reference Include="System.Data" />
        <Reference Include="System.Xml" />
        <Reference Include="System.Xaml">
          <RequiredTargetFramework>4.0</RequiredTargetFramework>
        </Reference>
        <Reference Include="WindowsBase" />
        <Reference Include="PresentationCore" />
        <Reference Include="PresentationFramework" />
        <ApplicationDefinition Include="src\App.xaml" />
        <Page Include="src\MainWindow.xaml" />
        <Compile Include="src\MainWindow.xaml.cs" />
        <!--
        <Resource Include="" />
        -->
    </ItemGroup>
    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

最初、元記事のファイルから、アセンブリ名やソースファイル名を変えただけでいけるだろうと思ってたんだけど、そうでもなかった。

なんかエラーを解決するためにいろいろトライ&エラーしたから、余計なものところまでいじってしまったかもしれないけど、今これでビルドできているので、いったんこれでよしとする。

enter.bat

元記事では、MSBuild のパスを .csproj ファイルに書くか、パスを通してねと説明されている。僕は、パスを通したいが、システム環境変数とかユーザ環境変数はあまりいじりたくない。ということでバッチファイルを用意することにした。

set PATH=C:\WINDOWS\Microsoft.NET\Framework64\v4.0.30319;%PATH%

App.xaml

App.xaml はエントリポイントにあたるクラスを記述する XAML ファイルだ。x:Class はこのファイルから生成されるクラス名をフルネームで書く。

また、StartupUri には最初に表示されるウィンドウを指定する。これを間違えてもビルドは通ってしまい、しかしできた exe を起動しても何も表示されない、ということになる。というか、なった。

<Application
    x:Class="HelloWpf.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="\src\MainWindow.xaml">
    <Application.Resources>
    </Application.Resources>
</Application> 

MainWindow.xaml

最初に表示するウィンドウを XAML で記述。だいたい元記事そのままだけど、お試しでボタンだけ置いてみた。

<Window x:Class="HelloWpf.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Hello, WPF!"
    >
    <Grid>
        <Button>Hello</Button>
    </Grid>
</Window>

MainWindow.xaml.cs

メインウィンドウのプログラム部分。WPF では、XAML で記述した UI と、C# などの言語で記述した動作を、結合して 1 つのクラスを作る、という考え方を採用している。これをコードビハインドと呼ぶ。

以下のコードでは C# の partial クラスを宣言している。前項の XAML ファイルもビルド中に partial クラスに変換されてコンパイルされ、最終的に 1 つの MainWindow クラスをかたちづくる。

using System;
using System.Windows;

namespace HelloWpf {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }
    }
}

ビルド

コマンドプロンプトenter.bat を実行してパスを通した後、以下のコマンドを実行する。

msbuild

すると、bin\Debug\HelloWpf.exe ができあがる。

デバッグビルドといっても、Visual Studio Code では、(.NET Core がないと) デバッグ実行ができない。リリースビルドをするには以下を実行する。

msbuild /p:Configuration=Release

僕の環境では、いくつか警告が出るんだけど、できた exe は実行できているようだ。