はじめに
現在参画中の案件にて初めてC#やWPFを触る機会があったため、自己学習として簡単なWPFアプリを作成しています
まだ開発途中ですが、途中経過を記事に残しておこうと思い記事を作成しました
色々な技術へ触れて知識を増やすことが今回の学習の目的なので、作成したいものに対して適切な環境でないかもしれませんがご容赦ください。。。
■今回行うこと
- 環境構築(dockerメイン)
- 簡易的な機能の実装
- 動作確認
作成したアプリの概要
画面に入力した内容をExcelに出力します
現在はA1セルへ固定出力していますが、将来的に機能を拡張しより柔軟な出力処理が行えるようにできたらと思っています
環境構築
今回の環境は下記の通りです
※現在の簡易版アプリではDBは使用していませんが、今後追加予定の機能にてDBが必要なためDB環境も導入しておきます
| 分類 | 使用するもの |
|---|---|
| IDE | Visual Studio 2022 |
| フロント | WPF (.NET) |
| DB | PostgreSQL |
| ORM | Entity Framework Core |
| マイグレーション | Flyway |
| 仮想化 | Docker / Docker Compose |
| バージョン管理 | Git |
全体構成
WPFアプリ
↓ EF Core
PostgreSQL(Docker)
↑ Flyway
- DBはDockerコンテナ上に構築
- DB定義はFlywayのSQLでバージョン管理
- アプリ側はEF Core(DBファースト)でアクセス
VS2022、DockerDesktop、Gitのインストールについては今回は省略し、dockerの設定周りについて書いていきます
ちなみにGitのインストールについては以前投稿した以下の記事で触れているので、よければご覧いただけますと幸いです😊(宣伝です)
VSCodeとgit(gitHub)を連携してみた
wpfプロジェクトの作成
VS2022で新しいプロジェクトの作成⇒WPFアプリケーション⇒プロジェクト名を入力し、.NETバージョンを選択して作成
Docker + PostgreSQL + Flyway環境の構築
docker関連のディレクトリ構成
docker関連のファイルはアプリ用のファイルとは分けたいので、dockerフォルダへまとめる構成にしました
docker
├ docker-compose.yaml
└ sql
└ V1__create_tables.sql
- docker-compose.yaml
PostgreSQL / Flywayコンテナの設定を記載 - sql配下
Flywayが実行するマイグレーション用SQLを配置
docker-compose.yamlの作成
dockerの設定ファイルです
記載内容は作成したい環境に合わせて変更してください
flyway:~の部分はflyway用の設定のため、flywayを導入しない場合は不要です
version: "3.9"
services:
db:
image: postgres:15
container_name: postgres-db
ports:
- "5432:5432"
environment:
POSTGRES_USER: sampleuser
POSTGRES_PASSWORD: samplepass
POSTGRES_DB: sampledb
volumes:
- db_data:/var/lib/postgresql/data
flyway:
image: flyway/flyway:10
container_name: flyway
command: -url=jdbc:postgresql://db:5432/sampledb -user=sampleuser -password=samplepass -connectRetries=60 migrate
volumes:
- ./sql:/flyway/sql
depends_on:
- db
volumes:
db_data:
※ちなみに最近のdockerではversion指定を省略しても動くみたい?です
flyway用のsql作成
flywayを導入すると、DBのテーブル定義やデータ変更をSQLファイルとしてバージョン管理できるようになります
- SQLファイルの履歴=DBの変更履歴として管理できる
- 新しい環境でも同じSQLを流すだけでDBを再現できる
まずは初期テーブル作成用のsqlを配置します(テスト用の仮テーブルです)
flyway
└ sql
└ V1__create_tables.sql
CREATE TABLE Customers (
Id SERIAL PRIMARY KEY,
Name VARCHAR(100) NOT NULL
);
INSERT INTO Customers (Name) VALUES ('Alice'), ('Bob'), ('Charlie');
コンテナ起動
DockerDesktopを起動し、docker compose up -dを実行するとyamlファイルに記載したポスグレのコンテナを立ち上げることができます
この際、flywayが自動でSQLを実行しDBにテーブルが作成されます
毎回コマンドを打つのは手間なので、バッチファイルにしておくと起動が楽です
EF Coreの導入
Entity Framework Coreの略称
C#からデータベースを操作するためのORM(ObjectRelationalMapper)です
SQLを直接書かなくても、テーブル=クラス、レコード=オブジェクトとして扱うことができるためアプリ側の実装がシンプルになります
今回はDB定義からEFのクラスを生成します
(DBファースト方式というらしい)
※参考:【C#】Entity Framework Core(EF Core)について
NuGetパッケージ追加
Vs2022のパッケージマネージャーコンソールから以下をインストールします
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Tools
DbContext / エンティティの生成(Scaffoldコマンド)
Scaffoldコマンドを実行すると、DBからEFのクラスを作成することができます
- DbContext クラス
- テーブルに対応したエンティティクラス
OutputDir :生成したファイルの配置先
Context :DbContextクラスの名前
Scaffold-DbContext
Host=localhost;Port=5432;Database=sampledb;Username=sampleuser;Password=samplepass
Npgsql.EntityFrameworkCore.PostgreSQL
-OutputDir Models
-Context AppDbContext
こちらもバッチファイルにしておくと実行が楽です
(中身は環境に合わせて変えてください)
@echo off
REM =========================================
REM EF Core Scaffold 実行バッチ
REM =========================================
REM 環境に合わせてcdコマンドを設定(.csprojがあるフォルダに移動)
cd XXX
REM 接続文字列を設定(自分の環境に合わせて変更)
set CONNECTION="Host=XXXX;Port=XXXX;Database=XXXX;Username=XXXX;Password=XXXX"
REM 使用するプロバイダ
set PROVIDER=Npgsql.EntityFrameworkCore.PostgreSQL
REM 出力先フォルダ
set OUTPUT_DIR=ModelsGenerated
REM DbContext のクラス名
set CONTEXT=AppDbContext
echo Running scaffold...
dotnet ef dbcontext scaffold %CONNECTION% %PROVIDER% -o %OUTPUT_DIR% --context %CONTEXT% --force
echo.
echo Scaffold 完了しました。
pause
DBの接続テスト
先ほどのScaffoldコマンドを実行すると、以下のようなファイルが出力されます
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseNpgsql("Host=localhost;Port=5432;Database=sampledb;Username=sampleuser;Password=samplepass");
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
DBに接続できているかテストするために、レコード数を表示するプログラムを作成します
using WPFSampleProject.Models;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System;
using System.IO;
using System.Windows;
using ClosedXML.Excel;
namespace WPFSampleProject
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//DB接続テスト用
using var db = new AppDbContext();
var tables = db.Customers.ToList();
MessageBox.Show($"レコード数: {tables.Count}");
}
}
}
メッセージボックスにレコード数が表示されたらOKです
今回は先ほど作成したsqlファイルで3件データをinsertしているので「レコード数:3」と表示されています

簡易版のアプリ作成
環境の構築ができたので、ここからはアプリの作成を行います
プロジェクト構成
今回はMVVM構成で作成する方針にしたいと思っています
- View
- 画面の見た目を定義(xaml)
- ユーザー操作を受け付けるが、処理は持たない
- ViewModel
- 画面の状態や処理ロジックを定義
- ViewとはBinding / Commandを通して連携
- Model
- アプリケーションで扱うデータ構造(EFのエンティティクラスなど)
WPFSampleProject
├ docker
│ ├ docker-compose.yaml
│ ├ sql
│ │ └ V1__create_tables.sql
│ └ scaffold.bat
└ WPFSampleProject
├ Views
│ └ MainWindow.xaml
├ ViewModels
│ └ MainWindowViewModel.cs
├ Models
├ ModelsGenerated
│ ├ AppDbContext.cs
│ └ Customer.cs
└ WPFSampleProject.csproj
MainWindowViewModel.cs
using WPFSampleProject.Models;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System;
using System.IO;
using System.Windows;
using ClosedXML.Excel;
using System.ComponentModel.DataAnnotations.Schema;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace WPFSampleProject.ViewModels
{
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
public string _inputTextBox = "";
[RelayCommand]
private void OutputButtonClick()
{
try
{
string text = InputTextBox;
if (string.IsNullOrWhiteSpace(text))
{
MessageBox.Show("文字を入力してください");
return;
}
// 出力先(実行フォルダ)
string outputDir = AppDomain.CurrentDomain.BaseDirectory;
string fileName = $"Sample_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
string filePath = System.IO.Path.Combine(outputDir, fileName);
// Excel作成
using (var workbook = new XLWorkbook())
{
var sheet = workbook.Worksheets.Add("Sheet1");
sheet.Cell("A1").Value = text;
workbook.SaveAs(filePath);
}
MessageBox.Show("Excel出力が完了しました。\n" + filePath);
}
catch (Exception ex)
{
MessageBox.Show("出力に失敗しました。\n" + ex.Message);
}
}
}
}
MainWindow.xaml
<Window x:Class="WPFSampleProject.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFSampleProject"
xmlns:vm="clr-namespace:WPFSampleProject.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox x:Name="InputTextBox"
Height="30"
Margin="0,0,0,10"
Text="{Binding InputTextBox}"/>
<Button Grid.Row="1"
Height="30"
Content="Excelに出力"
Command="{Binding OutputButtonClickCommand}"/>
</Grid>
</Window>
実装のポイント
画面(xaml)の入力値をViewModelに渡す
WPFではBindingを使って値の受け渡しを行います
今回はテキストボックスの値をvmのInputTextBoxプロパティにバインドしています
<TextBox
Height="30"
Margin="0,0,0,10"
Text="{Binding InputTextBox}" />
[ObservableProperty]
private string _inputTextBox = "";
[ObservableProperty]はCommunityToolkit.Mvvmというライブラリが提供する機能です
WPFでは「Binding設定+変更通知の設定」を行うことで変更通知がきたときに値の受け渡しをする…という仕組みになっているそうです
[ObservableProperty]をつけておくとこの変更通知の設定を自動で行ってくれます
※vmにObservableObjectを継承させる必要があります(public partial class MainWindowViewModel : ObservableObjectの部分)
※ライブラリを使わず自分で変更通知の設定を書くこともできます
画面(xaml)でボタンが押下された時のイベントをvmで処理する
<Button
Height="30"
Content="Excelに出力"
Command="{Binding OutputButtonClickCommand}" />
[RelayCommand]
private void OutputButtonClick()
{
// Excel出力処理
}
[RelayCommand]もCommunityToolkit.Mvvmが提供している機能です
これにより、xaml.csにClickイベントを書くことなくボタン押下時の処理をvmに集約できるので
- View(xaml)は見た目と操作のみを担当
- ViewModel は処理ロジックのみを担当
というMVVMの責務分離を意識した構成にすることができます
最後に
ここまで読んでいただきありがとうございます
Javaを自己学習で少しだけ触ったことがあったので、View / ViewModelを分離する設計思想はController / Serviceの分離と似ているのかなと感じました。BindingやCommandの設定が難しく苦戦したので、値渡しなどの仕組みは今後も意識して学習していきたいです
EntityFrameworkの使い方にもあまり慣れていないので、DB関連の操作もこれから学習していけたらと思います
最後に備忘として今後対応したいことだけメモして終わります
- 機能拡張
- Excel操作に使用するライブラリの精査
- 今回は(chatGPTにおすすめされたので)ClosedXMLを使用しましたが、各ライブラリのメリットやデメリットを調べて適切なものを選べるようにしたいです
- DB接続文字列の分離
- 今は簡易さと分かりやすさ重視で各所に直書きしていますが、実際はセキュリティ面や一元管理の観点から分離するのが一般的なので修正したいです

