WPF ダッシュボードを JSON を読み込んで表示するGUIアプリ(PowerShell + XAML)

コンセプト

  • XAML … UI の見た目を定義(カード・リスト・リンクなど)。
  • PowerShell … JSON を読み込み、XAML にバインド、イベント処理(更新ボタン・リンククリックなど)。
  • JSON … すべての表示データを記述。編集すれば即座に UI に反映。

1. JSON データ (cards.json)

{
  "header": { "title": "ダッシュボード", "lastUpdated": "2025-10-03T06:00:00+09:00" },
  "kpiCards": [
    { "title": "本日のジョブ", "value": 12, "unit": "件", "delta": "+2", "accent": "primary" },
    { "title": "エラー", "value": 1, "unit": "件", "delta": "-3", "accent": "alert" },
    { "title": "稼働率", "value": 98.5, "unit": "%", "delta": "+0.4", "accent": "ok" }
  ],
  "progress": { "label": "全体進捗", "value": 35 },
  "tasks": [
    { "title": "DBバックアップ", "state": "進行中", "due": "2025-10-05", "assignee": "ユーザーA" },
    { "title": "API キー更新", "state": "未着手", "due": "2025-10-07", "assignee": "チーム" }
  ],
  "links": [
    { "label": "Redmine", "url": "https://example.com/redmine" },
    { "label": "API ドキュメント", "url": "https://example.com/docs" }
  ],
  "news": [
    { "text": "新機能: レポートのエクスポートを追加" },
    { "text": "注意: API キーの更新期限が近づいています" }
  ]
}

2. PowerShell スクリプト (Main.ps1)

# ===== Main.ps1 =====
if ([Threading.Thread]::CurrentThread.ApartmentState -ne 'STA') {
  $argsList = @('-sta','-NoProfile','-ExecutionPolicy','Bypass','-File', $PSCommandPath)
  Start-Process -FilePath (Get-Command powershell).Source -ArgumentList $argsList | Out-Null
  exit
}

Add-Type -AssemblyName PresentationCore,PresentationFramework,WindowsBase
Set-Location (Split-Path $PSCommandPath)

function Load-Xaml([string]$path) {
  [xml]$xaml = Get-Content -LiteralPath $path -Raw
  $reader = New-Object System.Xml.XmlNodeReader $xaml
  [Windows.Markup.XamlReader]::Load($reader)
}

# JSON ロード関数
$Global:JsonPath = ".\\cards.json"
function Import-CardsJson {
  Get-Content -LiteralPath $Global:JsonPath -Raw | ConvertFrom-Json
}

# XAML 読み込み
$window = Load-Xaml ".\\MainWindow.xaml"

# JSON を DataContext に
$data = Import-CardsJson
$window.DataContext = $data

# リンククリックイベントを追加
$window.AddHandler([System.Windows.Documents.Hyperlink]::RequestNavigateEvent,
  [System.Windows.Navigation.RequestNavigateEventHandler]{
    param($s,$e)
    Start-Process $e.Uri.AbsoluteUri
    $e.Handled = $true
})

# 更新ボタンの処理
$btnRefresh = $window.FindName("BtnRefresh")
if ($btnRefresh) {
  $btnRefresh.Add_Click({
    $fresh = Import-CardsJson
    $window.DataContext = $fresh
    [System.Windows.MessageBox]::Show("JSONを再読込しました。")
  })
}

# ウィンドウ表示
$null = $window.ShowDialog()

3. XAML (MainWindow.xaml)

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="{Binding header.title, FallbackValue=ダッシュボード}"
  Height="720" Width="1200" WindowStartupLocation="CenterScreen"
  Background="White">

  <Grid Margin="24">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="12"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="12"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="24"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <!-- Header -->
    <DockPanel Grid.Row="0">
      <StackPanel Orientation="Vertical" DockPanel.Dock="Left">
        <TextBlock Text="{Binding header.title}" FontSize="28" FontWeight="Bold"/>
        <TextBlock Margin="0,4,0,0" Foreground="Gray">
          <Run Text="最終更新: "/>
          <Run Text="{Binding header.lastUpdated}"/>
        </TextBlock>
      </StackPanel>
      <Button x:Name="BtnRefresh" DockPanel.Dock="Right" Margin="8,0,0,0" Padding="12,6">更新</Button>
    </DockPanel>

    <!-- KPI Cards -->
    <ItemsControl Grid.Row="2" ItemsSource="{Binding kpiCards}">
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
      <ItemsControl.ItemTemplate>
        <DataTemplate>
          <Border BorderBrush="#DDD" BorderThickness="1" CornerRadius="8" Padding="12" Margin="6">
            <StackPanel>
              <TextBlock Text="{Binding title}" Foreground="Gray"/>
              <StackPanel Orientation="Horizontal" Margin="0,6,0,0">
                <TextBlock Text="{Binding value}" FontSize="24" FontWeight="Bold"/>
                <TextBlock Text="{Binding unit}" Margin="6,6,0,0" Foreground="Gray"/>
              </StackPanel>
              <TextBlock Text="{Binding delta}" Foreground="Gray"/>
            </StackPanel>
          </Border>
        </DataTemplate>
      </ItemsControl.ItemTemplate>
    </ItemsControl>

    <!-- Progress -->
    <Border Grid.Row="4" BorderBrush="#DDD" BorderThickness="1" CornerRadius="8" Padding="12">
      <StackPanel>
        <TextBlock Text="{Binding progress.label}" Foreground="Gray"/>
        <ProgressBar Minimum="0" Maximum="100" Value="{Binding progress.value}" Height="12" Margin="0,12,0,0"/>
      </StackPanel>
    </Border>

    <!-- Tasks + Links/News -->
    <Grid Grid.Row="6">
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="24"/>
        <ColumnDefinition Width="*"/>
      </Grid.ColumnDefinitions>

      <!-- Tasks -->
      <Border Grid.Column="0" BorderBrush="#DDD" BorderThickness="1" CornerRadius="8" Padding="12">
        <ItemsControl ItemsSource="{Binding tasks}">
          <ItemsControl.ItemTemplate>
            <DataTemplate>
              <StackPanel Margin="0,0,0,8">
                <TextBlock Text="{Binding title}" FontWeight="Bold"/>
                <TextBlock Text="{Binding state}" Foreground="Gray"/>
                <TextBlock Text="{Binding due}" Foreground="Gray"/>
              </StackPanel>
            </DataTemplate>
          </ItemsControl.ItemTemplate>
        </ItemsControl>
      </Border>

      <!-- Links + News -->
      <StackPanel Grid.Column="2">
        <Border BorderBrush="#DDD" BorderThickness="1" CornerRadius="8" Padding="12" Margin="0,0,0,12">
          <ItemsControl ItemsSource="{Binding links}">
            <ItemsControl.ItemTemplate>
              <DataTemplate>
                <TextBlock Margin="0,0,0,6">
                  <Hyperlink NavigateUri="{Binding url}">
                    <Run Text="{Binding label}"/>
                  </Hyperlink>
                </TextBlock>
              </DataTemplate>
            </ItemsControl.ItemTemplate>
          </ItemsControl>
        </Border>

        <Border BorderBrush="#DDD" BorderThickness="1" CornerRadius="8" Padding="12">
          <ItemsControl ItemsSource="{Binding news}">
            <ItemsControl.ItemTemplate>
              <DataTemplate>
                <TextBlock Text="{Binding text}" Margin="0,0,0,6"/>
              </DataTemplate>
            </ItemsControl.ItemTemplate>
          </ItemsControl>
        </Border>
      </StackPanel>
    </Grid>
  </Grid>
</Window>

解説

仕組み

  • DataContext に JSON オブジェクト全体を割り当てています。
  • 各コントロールは Binding で JSON のプロパティを参照します。
  • 例: <TextBlock Text="{Binding header.title}"/>cards.jsonheader.title を表示。

特徴

  • JSON を編集するだけで UI が変わる
    新しい KPI を追加したり、ニュースを差し替えたりできます。
  • 更新ボタン で再読込 → DataContext を差し替え。
  • Hyperlink のクリックは PowerShell 側で Start-Process で処理。

応用

  • accent 値を使って色を DataTrigger で切替可能。
  • due を日付形式にして並び替えや期限色分けも可能。

まとめ

これをベースに、業務向けのダッシュボードやタスク管理ビューに拡張できます。UI は XAML、データは JSON、制御は PowerShell というシンプルな役割分担により、管理も拡張も容易です。

スポンサーリンク

-IT関連
-,