C#でWPF学習中「WPFプロジェクトを作成するスクリプト2」

C# コンピュータ
C#

WPFのクラスライブラリを作成し、WPFアプリケーションのプロジェクトとConsoleアプリケーションのプロジェクトから参照するソリューションを作成するスクリプトを作成しました。

スクリプト

スクリプト名:Create-WPFSolution.ps1

<#
.SYNOPSIS
WPFプロジェクトを含むソリューションを作成

.DESCRIPTION
WPFのアプリケーション、ライブラリとコンソールプロジェクトを作成します。
アプリケーションにはView

.EXAMPLE
mkdir ソリューション名
cd ソリューション名
Create-WPFSolution.ps1

#>

$ErrorActionPreference = "STOP" # エラーが発生した場合スクリプトを停止する。

$SolutionName = Split-Path (Get-Location).Path -Leaf 
$result =Read-Host "Do you want to create a ${SolutionName} solution?(Y/N)"
if ($result.ToUpper() -ne "Y") {
    Exit
}
dotnet new sln --name $SolutionName # ソリューションの作成


# クラスライブラリ作成

New-Item -ItemType Directory -Path "Lib"
dotnet new classlib --name "${SolutionName}.Lib" --output "Lib"
Set-Location "Lib"
dotnet sln "..\${SolutionName}.sln" add "${SolutionName}.Lib.csproj"

$sourceCode = @"
using System;

namespace ${SolutionName}.Lib;

public class Class1
{
    public const string CLASS_NAME = "Class1"; 
}
"@
$outFile = Join-Path (Get-location).Path "Class1.cs"
$writer = New-Object System.IO.StreamWriter($outFile, $false, [System.Text.Encoding]::GetEncoding("utf-8"))
$writer.WriteLine($sourceCode)
$writer.Close()

Set-Location ".."


# WPFプロジェクトの作成
New-Item -ItemType Directory -Path "WPFApp"
dotnet new wpf --name "${SolutionName}.WPFApp" --output "WPFApp"
Set-Location "WPFApp"


# csproj
$inFile = Join-Path (Get-location).Path ("${SolutionName}.WPFApp.csproj")
$xmlDoc = [System.Xml.XmlDocument](Get-Content -Encoding UTF8 -Raw $inFile)

$child4 = $xmlDoc.CreateElement("NoWarn")
$child4.InnerText = "NU1701"
$pos = $xmlDoc.getElementsByTagName("UseWPF")[0]
$xmlDoc.Project.PropertyGroup.insertAfter($child4, $pos) | Out-Null
$xmlDoc.Save($inFile)

dotnet sln "..\${SolutionName}.sln" add "${SolutionName}.WPFApp.csproj"
dotnet add reference "..\Lib"
dotnet add package Microsoft.Xaml.Behaviors.WPF
dotnet add package ReactiveProperty.WPF

# ViewModel
$MainWindowViewModel = @"
using System.Diagnostics;
using System;
using System.ComponentModel;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System.Reactive.Disposables;

namespace ${SolutionName};

public class MainWindowViewModel : INotifyPropertyChanged, IDisposable
{
#region
    // INotifyPropertyChanged
    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged(string name) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    // IDisposable
    private CompositeDisposable Disposable { get; } = [];
    public void Dispose() => Disposable.Dispose();
#endregion
    public ReactiveProperty<string> Title { get; private set; }
    public MainWindowViewModel()
    {
        PropertyChanged += (o, e) => {};
        string className = ${SolutionName}.Lib.Class1.CLASS_NAME;
        Title = new ReactiveProperty<string>(className).AddTo(this.Disposable);
    }
}
"@

$outFile = Join-Path (Get-location).Path "MainWindowViewModel.cs"
$writer = New-Object System.IO.StreamWriter($outFile, $false, [System.Text.Encoding]::GetEncoding("utf-8"))
$writer.WriteLine($MainWindowViewModel)
$writer.Close()

# ViewModelCleanupBehavior
$ViewModelCleanupBehavior = @"
using System.Xml;
using System.Xml.Schema;

using Microsoft.Xaml.Behaviors;
using System;
using System.Windows;
using System.ComponentModel;

namespace ${SolutionName};

public class ViewModelCleanupBehavior : Behavior<Window>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.Closed += this.WindowClosed;
    }

    private void WindowClosed(object? sender, EventArgs e)
    {
        (this.AssociatedObject.DataContext as IDisposable)?.Dispose();
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        this.AssociatedObject.Closed -= this.WindowClosed;
    }
}
"@
$outFile = Join-Path (Get-location).Path "ViewModelCleanupBehavior.cs"
$writer = New-Object System.IO.StreamWriter($outFile, $false, [System.Text.Encoding]::GetEncoding("utf-8"))
$writer.WriteLine($ViewModelCleanupBehavior)
$writer.Close()

# XAML
$inFile = Join-Path (Get-location).Path "MainWindow.xaml"
$xmlDoc = [System.Xml.XmlDocument](Get-Content -Encoding UTF8 -Raw $inFile)

$ns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
$nslocal = "clr-namespace:${SolutionName}"
$nsi = "clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors"
$nsinteractivity = "clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPFApp"

$attri = $xmlDoc.CreateAttribute("xmlns:i")
$attri.Value = $nsi
$xmlDoc.Window.Attributes.Append($attri) | Out-Null

$attri2 = $xmlDoc.CreateAttribute("xmlns:interactivity")
$attri2.Value = $nsinteractivity
$xmlDoc.Window.Attributes.Append($attri) | Out-Null

$xmlDoc.Window.setAttribute("Title", "{Binding Title.Value}")

$child = $xmlDoc.CreateElement("Window.DataContext", $ns)
$child2 = $xmlDoc.CreateElement("local:MainWindowViewModel", $nslocal)

$pos = $xmlDoc.getElementsByTagName("Grid")[0]
$dc = $xmlDoc.Window.insertBefore($child, $pos)
$dc.appendChild($child2) | Out-Null


$child3 = $xmlDoc.CreateElement("i:Interaction.Behaviors", $nsi)
$child4 = $xmlDoc.CreateElement("local:ViewModelCleanupBehavior", $nslocal)

$ib = $xmlDoc.Window.insertBefore($child3, $pos)
$ib.appendChild($child4) | Out-Null

$xmlDoc.Save($inFile) 

# Windowが長いので改行
$txt = Get-Content $inFile | ForEach-Object -Begin{$i=0} -Process { if($i -eq 0) { $_ -replace '" ', """`n`t" } else { $_ }; $i++ }
Set-Content -Path $inFile -Value $txt


Set-Location ".."

# コンソールアプリ 作成
New-Item -ItemType Directory -Path "ConsoleApp"
dotnet new console --name "${SolutionName}.ConsoleApp"  --output "ConsoleApp"
Set-Location "ConsoleApp"

# ConsoleプロジェクトのターゲットフレームワークをnetX.XからnetX.X-windowsへ変更
$inFile = Join-Path (Get-location).Path "${SolutionName}.ConsoleApp.csproj"
$xmlDoc = [System.Xml.XmlDocument](Get-Content -Encoding UTF8 -Raw $inFile)

$target = $xmlDoc.Project.PropertyGroup.TargetFramework

if ($target -match '^net\d.\d$') {
    $xmlDoc.Project.PropertyGroup.TargetFramework = ($target + "-windows")
}

$xmlDoc.Save($inFile) 

dotnet sln "..\${SolutionName}.sln" add "${SolutionName}.ConsoleApp.csproj"
dotnet add reference "..\Lib"
dotnet add package System.Console

$sourceCode = @"
namespace ${SolutionName};

class Program
{
    static void Main()
    {
        string className = ${SolutionName}.Lib.Class1.CLASS_NAME;
        Console.WriteLine($"ClassName:{className}");
    }
}
"@
$outFile = Join-Path (Get-location).Path "Program.cs"
$writer = New-Object System.IO.StreamWriter($outFile, $false, [System.Text.Encoding]::GetEncoding("utf-8"))
$writer.WriteLine($sourceCode)
$writer.Close()

Set-Location ..

使い方

ソリューション用のディレクトリを作成し、そのディレクトリへカレントディレクトリを移動します。
上記スクリプトを実行するとプロジェクトが作成されます。

コメント