25
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VSCode + WPF + Blazor Hybridを動かしてみたら想像以上に面白かった

Last updated at Posted at 2025-09-29

はじめに

VSCode だけで WPF アプリと Blazor Hybrid を組み合わせて開発する方法を紹介します。

今回は動作確認のため、↓のようにシンプルなディレクトリ構成で進めます。

/root
 ┣ /bin : ビルド成果物
 ┣ /obj
 ┣ /wwwroot : Blazor側の静的ファイル(CSS, JS, 画像など)を配置
 ┣ App.xaml : WPFのエントリポイント
 ┣ MainWindow.xaml : WPF側のメインウィンドウ
 ┣ BlazorWpfApp.csproj : プロジェクトファイル(依存関係やビルド設定を記述)
 ┣ BlazorWpfApp.sln : ソリューションファイル(複数プロジェクトの管理)
 ┗ Counter.razor : Blazorコンポーネント

実際のアプリではディレクトリ構成が複雑になるはずです(コンポーネント数や設計思想に依存)。

🎯 対象読者

  • 普段 Visual Studio を使っているけど、VSCode で WPF アプリを作ってみたい人
  • Blazor Hybrid を導入してモダンな UI を試したい人

結論だけ知りたいよ~という方のために以下にまとめます。

✅ メリット

  • 普段使い慣れたVSCode だけで WPF + Blazor Hybrid を動かせる
  • Web技術(Razor, CSS, JS)をデスクトップアプリ に組み込み可能
    • カラースキーマ等をCSSで受け取ることができる場合、デスクトップアプリ用に別途デザイン定義ファイルを用意する必要がなくなる

❌ デメリット

  • VSCode では XAML デザイナが使えない
  • 事例を記載した記事が少ない
  • WPFだけでなくBlazorの知識も必要なので学習コストが高い
  • 公式ドキュメントやテンプレートは Visual Studio 前提のことが多い

実際のアプリ構築手順

  • dotnetコマンドが動く環境を用意

公式サイトから.NET SDKをダウンロード&インストールしてください。

https://dotnet.microsoft.com/ja-jp/

  • WPF プロジェクト作成

    dotnet new wpf -n BlazorWpfApp
    
  • WPFでBlazorコンポーネントを扱えるようにする

    dotnet add package Microsoft.AspNetCore.Components.WebView.Wpf
    
  • wwwrootフォルダを追加
    フォルダ配下は次のようにします。

    /wwwroot
     ┣ /css
     ┃  ┗ /bootstrap
     ┃    ┗ bootstrap.min.css
     ┗ app.css
    

今回はBootstrapを使っていますが、cssなら何でも良いですし、ローカルに用意するのではなく、CDNで提供されているファイルを読み込むような方法もあるかと思います。Bootstrap5.3のダウンロードはこちら

index.html
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>WpfBlazor</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link href="WpfBlazor.styles.css" rel="stylesheet" />
</head>

<body class="bg-light">
    <div class="container py-4">
        <div id="app">Loading...</div>
    </div>

    <div id="blazor-error-ui" data-nosnippet class="alert alert-danger position-fixed bottom-0 end-0 m-3 shadow">
        An unhandled error has occurred.
        <a href="" class="alert-link">Reload</a>
        <button type="button" class="btn-close ms-2" aria-label="Close"></button>
    </div>
    <script src="_framework/blazor.webview.js"></script>
</body>

</html>
app.css
html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

h1:focus {
    outline: none;
}

a, .btn-link {
    color: #0071c1;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }
  • プロジェクト直下にRazorコンポーネントを作成

次のCounter.razorを追加します。

Counter.razor
@page "/counter"

<div class="card shadow-sm">
    <div class="card-header bg-primary text-white">
        <h4 class="mb-0">Counter Demo</h4>
    </div>
    <div class="card-body">
        <p class="fs-5">
            Current count:
            <span class="badge bg-secondary fs-6">@currentCount</span>
        </p>
        <button class="btn btn-lg btn-primary me-2" @onclick="IncrementCount">
            <i class="bi bi-plus-circle"></i> Click me
        </button>
        <button class="btn btn-outline-secondary btn-lg" @onclick="ResetCount">
            <i class="bi bi-arrow-counterclockwise"></i> Reset
        </button>
    </div>
</div>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }

    private void ResetCount()
    {
        currentCount = 0;
    }
}

  • WPFでBlazorコンポーネントを表示するためのあれこれ

    次のように_imports.razorファイルを追加します。_Imports.razorに記載したusing文があることで各razorファイルでのusingを省略できます。

_imports.razor
@using Microsoft.AspNetCore.Components.Web

MainWindow.xamlと``MainWindow.xaml.cs`を次のように変更します。

MainWindow.xaml.cs
using Microsoft.Extensions.DependencyInjection;
using System.Windows;

namespace BlazorWpfApp;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        // ↓を追加
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddWpfBlazorWebView();
        Resources.Add("services", serviceCollection.BuildServiceProvider());
    }
}
MainWindow.xaml
<Window x:Class="BlazorWpfApp.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:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf"
        xmlns:local="clr-namespace:BlazorWpfApp"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="450"
        Width="800">
    <Grid>
        <blazor:BlazorWebView HostPage="wwwroot\index.html"
                              Services="{DynamicResource services}">
            <blazor:BlazorWebView.RootComponents>
                <blazor:RootComponent Selector="#app"
                                      ComponentType="{x:Type local:Counter}"/>
            </blazor:BlazorWebView.RootComponents>
        </blazor:BlazorWebView>
    </Grid>
</Window>

動作確認してみる

プロジェクトのルートディレクトリで次のコマンドを実行します。

dotnet run

なんということでしょう…匠の華麗などうのこうので動作確認までできました♪

recording-qiita.gif

まとめ

VSCode でも WPF と Blazor コンポーネントを共存させた開発が可能であることが分かりました。

世に出回っている Blazor Hybrid の事例が少ないという不安要素もありますが、次のような観点で見ると良い選択になるかもしれません。

  • レガシーとモダンの橋渡しをしたいプロジェクトへのアプローチ
  • Visual Studioのライセンス数を抑えたい場合
  • 使いなれたVSCodeで開発効率を上げたい場合

次回はMicrosoft.Office.Interop.ExcelClosedXMLをインストールして簡単なエクセル操作ができるかどうかも確かめたいですね!

25
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?