WPFアプリを何度も作っていると、毎回同じファイルを用意したくなります。
例えば、MainWindowViewModel.cs、RelayCommand.cs、
そしてMainWindow.xamlのDataContext設定などです。
このような「いつものひな型」は、dotnet new用のオリジナルテンプレートとして登録しておくと便利です。
一度作っておけば、次回からはコマンド一発で自分用の雛形を生成できます。
今回は、WPF向けの最小MVVMテンプレートを例に、dotnet newで使えるテンプレートの作り方をまとめます。
今回作るテンプレートの構成
今回は欲張らず、最小限の構成にします。
MyTemplate
│
├─ App.xaml
├─ App.xaml.cs
├─ MainWindow.xaml
├─ MainWindow.xaml.cs
├─ MainWindowViewModel.cs
├─ RelayCommand.cs
└─ .template.config
└─ template.json
テンプレート用フォルダは任意でよい
テンプレート用フォルダの場所は任意です。
例えば次のような場所で構いません。
J:\git\MayworkCs\Templates\WpfMvvmTemplate
このフォルダの中に、テンプレート化したいプロジェクト一式と、.template.config/template.jsonを置きます。
特別な場所に置く必要はありません。
テンプレートの中身を用意する
まずは、通常のWPFプロジェクトとして動く状態のファイルを用意します。
その上で、テンプレート用の仮のプロジェクト名を決めておきます。
今回は MyTemplate という名前に統一します。
ここは重要なポイントです。
dotnet newのテンプレートは、名前の意味を理解して変換するのではなく、基本的には文字列置換です。
そのため、namespaceやXAML内の名前は、テンプレート用の仮名に揃えておく必要があります。
MainWindow.xaml
<Window x:Class="MyTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyTemplate"
Title="{Binding Title}"
Height="240"
Width="400">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid Margin="16">
<StackPanel>
<TextBlock Text="{Binding Title}" FontSize="20" Margin="0,0,0,12" />
<Button Content="Click" Command="{Binding ClickCommand}" Width="120" Height="32" />
</StackPanel>
</Grid>
</Window>
MainWindowViewModel.cs
using System.Reflection;
using System.ComponentModel;
using System.Windows.Input;
namespace MyTemplate;
public class MainWindowViewModel : INotifyPropertyChanged
{
string _title = Assembly.GetEntryAssembly()?.GetName().Name ?? "App";
public string Title
{
get => _title;
set
{
if (_title == value) return;
_title = value;
OnPropertyChanged(nameof(Title));
}
}
public ICommand ClickCommand { get; }
public MainWindowViewModel()
{
ClickCommand = new RelayCommand(_ =>
{
Title = $"{Assembly.GetEntryAssembly()?.GetName().Name ?? "App"} Clicked";
});
}
public event PropertyChangedEventHandler? PropertyChanged;
void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand.cs
using System;
using System.Windows.Input;
namespace MyTemplate;
public class RelayCommand : ICommand
{
readonly Action<object?> _execute;
readonly Func<object?, bool>? _canExecute;
public RelayCommand(Action<object?> execute, Func<object?, bool>? canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object? parameter)
{
return _canExecute?.Invoke(parameter) ?? true;
}
public void Execute(object? parameter)
{
_execute(parameter);
}
public event EventHandler? CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
XAMLでDataContextを指定する最小構成
今回のテンプレートでは、MainWindow.xamlの中で直接DataContextを指定しています。
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
本格的なMVVMでは、DIコンテナやViewModelLocatorなどを使う構成もあります。
ただし、最小テンプレートとしてはこのくらいのシンプルさの方が扱いやすいでしょう。
ウィンドウタイトルにアプリケーション名を入れる
初期タイトルには、実行中アセンブリ名を使っています。
Assembly.GetEntryAssembly()?.GetName().Name ?? "App"
これにより、テンプレートから生成したプロジェクト名が、そのまま初期タイトルに使われます。
小さなことですが、毎回タイトル文字列を直す手間が減ります。
template.json を作る
テンプレートとして認識させるには、.template.config/template.json が必要です。
{
"$schema": "http://json.schemastore.org/template",
"author": "maywork",
"classifications": [ "WPF", "MVVM" ],
"identity": "Maywork.Wpf.MvvmTemplate",
"name": "WPF MVVM Minimal Template",
"shortName": "wpfmvvm-min",
"sourceName": "MyTemplate",
"tags": {
"language": "C#",
"type": "project"
}
}
$schema について
$schema は、エディタの補完やバリデーションのための指定です。
必須ではありませんが、書いておくとVS Codeなどで補完が効きやすくなります。
sourceName が重要
今回の重要ポイントは sourceName です。
"sourceName": "MyTemplate"
これにより、テンプレート内にある MyTemplate という文字列が、プロジェクト生成時の新しい名前に置換されます。
例えば、テンプレート内に次のような記述があるとします。
namespace MyTemplate;
<Window x:Class="MyTemplate.MainWindow" ...>
<RootNamespace>MyTemplate</RootNamespace>
これが、例えば次のコマンドでプロジェクトを作ると、
dotnet new wpfmvvm-min -n ImageTool
次のように置換されます。
namespace ImageTool;
<Window x:Class="ImageTool.MainWindow" ...>
<RootNamespace>ImageTool</RootNamespace>
つまり、namespaceが特別に解釈されているわけではなく、あくまで文字列置換です。
そのため、テンプレート内では MyTemplate という名前に揃えておくのがコツです。
csproj もテンプレートに含める
テンプレートには .csproj も含めます。
例えば、次のようなファイルです。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<RootNamespace>MyTemplate</RootNamespace>
<AssemblyName>MyTemplate</AssemblyName>
</PropertyGroup>
</Project>
パッケージを使いたい場合は、PackageReferenceを書いておけば、生成されたプロジェクトにもそのまま含まれます。
ただし、パッケージバージョンがテンプレートに埋め込まれる点は好みが分かれるところです。
今回のような最小テンプレートなら、まずはNuGetパッケージなしで始める方が記事としても分かりやすいでしょう。
テンプレートをインストールする
テンプレートフォルダに移動して、次のコマンドを実行します。
dotnet new install .
これでローカルテンプレートとして登録されます。
登録後は一覧で確認できます。
dotnet new list
テンプレートから新しいプロジェクトを作る
登録後は、次のようにして新しいWPFプロジェクトを生成できます。
dotnet new wpfmvvm-min -n SampleApp
これで、MainWindowViewModel.csやRelayCommand.csを含んだ最小MVVM構成のWPFプロジェクトが作られます。
テンプレートを更新したい場合
テンプレートの内容を変更した場合は、一度アンインストールしてから再度インストールすると分かりやすいです。
dotnet new uninstall .
dotnet new install .
または、一覧に表示されるテンプレートIDを指定してアンインストールしても構いません。
不要なフォルダ・ファイルを削除
binやobj、vscodeを使っているなら.vscodeなどのフォルダは削除しておくほうが良いでしょう。
逆にGitを使っているなら.gitignoreを含めると良いでしょう
感想
大げさな仕組みを用意しなくても、自分用のWPFテンプレートは作れることが確認できました。
毎回コピーしているファイルが決まっているなら、それはもうテンプレート化の価値があります。
特にWPFでは、最初の数ファイルを作る手間が地味に面倒です。
dotnet new対応にしておけば、単なるコピペよりも少し整った形で雛形を再利用できます。
自分専用の開発スターターキットを作る感覚で試してみると面白いでしょう。
まとめ
- テンプレート用フォルダは任意の場所でよい
.template.config/template.jsonを置けばテンプレート化できるsourceNameで指定した文字列が新しいプロジェクト名に置換される- WPFでは
x:Class、namespace、RootNamespaceなどを同じ仮名に揃えるのがコツ - 最小構成なら
MainWindowViewModel.csとRelayCommand.csだけでも十分実用的
まずは最小構成でテンプレート化して、必要になったら少しずつ自分色を足していくのが良さそうです。

コメント