0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

.NET 10 WinForms で作る:マルチモニターツール2本 — ArtNet DMXモニター & ローカルWebマルチブラウザ

0
Last updated at Posted at 2026-03-01

.NET 10 WinForms で作る:マルチモニターツール2本 — ArtNet DMXモニター & ローカルWebマルチブラウザ

この記事は Claude(Anthropic)を使用して作成しました。
コード・設計・文章はAIとの対話をもとに生成・整理したものです。
内容の正確性には注意を払っていますが、参考にされる際はご自身でも動作確認をお願いします。

Claude Code Vsc_wk.jpg

はじめに

照明制御現場で使う ArtNet DMXモニター と、ローカルネットワーク上のWebサーバーを一覧表示する マルチパネルブラウザ を .NET 10 Windows Forms で作りました。

どちらも「最大16画面を格子状に同時表示」「サイドバーでの一覧切り替え」「設定の自動保存/復元」という共通コンセプトで設計しています。


1. ArtNet Multi Monitor

概要

Art-Net(照明制御プロトコル)のDMXデータを、最大16ユニバース同時にリアルタイム表示するモニターです。
image.png

主な機能

  • 最大16パネル同時表示(格子状グリッドレイアウト)
  • 3セクション構成の各パネル
    • 上80%:512ch セルグリッド(値に応じて緑色グラデーション)
    • 中10%:選択チャンネルの時系列波形グラフ
    • 下10%:全512ch ミニ棒グラフ
  • ユニバーススキャナーサイドバー(どのユニバースにデータが来ているか一覧表示)
  • グリッドレイアウト切り替え:Auto / 2列 / 3列 / 4列

アーキテクチャ

UDP 6454 受信
    ↓
ArtNetHub(シングルソケット)
    ├─ UniverseData[0]  ←─ UniversePanel[0] が参照
    ├─ UniverseData[1]  ←─ UniversePanel[1] が参照
    │   ...
    └─ _allActivity     ←─ UniverseScannerPanel が参照(スキャナー)

1つのUDPソケットで全パケットを受信し、ConcurrentDictionary でユニバースごとに振り分けます。

Art-Netパケット解析

// Art-Net ヘッダチェック
if (buf[0] != 'A' || buf[1] != 'r' || ...) return;

// Opcode = ArtDMX (0x5000)
int opcode = buf[8] | (buf[9] << 8);
if (opcode != 0x5000) return;

// Port-Address(15bit ユニバース番号)
int universe = buf[14] | ((buf[15] & 0x7F) << 8);

パネルの描画(カスタムGDI+)

// 3セクション高さ計算
int gridH = (int)(bodyH * 0.80);  // チャンネルグリッド
int histH = (int)(bodyH * 0.10);  // 波形グラフ
int barH  = bodyH - gridH - histH; // ミニ棒グラフ

DrawChannelGrid(g, bodyX, bodyY,           bodyW, gridH, dmx);
DrawHistoryGraph(g, bodyX, bodyY + gridH,  bodyW, histH);
DrawMiniBarChart(g, bodyX, bodyY + gridH + histH, bodyW, barH, dmx);

ユニバーススキャナーサイドバー

カスタム描画(Control サブクラス)で実装。

  • サブネット行(0〜15):データ受信中は緑に点灯
  • ユニバース行(0〜15):監視中はシアン、アクティブは緑
  • ボタンをクリックするとパネルの追加/削除をトグル
// 10秒以内に受信があれば「アクティブ」とみなす
bool active = _hub.GetAllActivity()
    .TryGetValue(addr, out var t) && (DateTime.Now - t).TotalSeconds < 10;

グリッドレイアウト切り替え

// _fixedCols = 0 → Auto(√n 列)
// _fixedCols > 0 → 固定列数
int cols = _fixedCols > 0 ? _fixedCols : (int)Math.Ceiling(Math.Sqrt(n));
cols = Math.Max(1, Math.Min(n, cols));
int rows = (int)Math.Ceiling((double)n / cols);

2. Local Web Multi Browser

概要

ローカルネットワーク上のWebサーバーを自動スキャンし、最大16個のパネルで同時に閲覧できるマルチブラウザです。

image.png

主な機能

  • WebView2(Microsoft Edge Chromium) ベースのブラウザパネル
  • サブネット自動スキャン(/24、最大80並列、9ポート対応)
  • グリッドレイアウト切り替え:Auto / 2列 / 3列 / 4列
  • URL・レイアウトの自動保存/復元

サブネットスキャン

// 検出対象ポート
private static readonly int[] PORTS = { 80, 8080, 8081, 8888, 3000, 5000, 3001, 443, 8443 };

// TCP接続確認(400ms タイムアウト)
using var tcp = new TcpClient();
var conn = tcp.ConnectAsync(ip, port);
if (!await Task.WhenAny(conn, Task.Delay(400)) == conn) continue;

// HTTP HEAD でタイトル取得(1200ms タイムアウト)
var response = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cts.Token);

最大80スレッド(SemaphoreSlim)で並列スキャンし、見つかり次第リストに追加します。

WebView2パネル

HWND被さり問題の対処

WebView2はHWNDベースのコントロールのため、DockStyle.Fill だとWinFormsのナビゲーションバーに被さることがあります。

// ❌ DockStyle.Fill だと topbar に被さる場合がある
_webView = new WebView2 { Dock = DockStyle.Fill };

// ✅ SetBounds で明示的に topbar の下に配置
private void PositionWebView()
{
    _webView.SetBounds(0, BAR_H, ClientSize.Width,
        Math.Max(0, ClientSize.Height - BAR_H));
}

// リサイズのたびに呼び出す
SizeChanged += (_, _) => PositionWebView();
// WebView2 初期化完了後にも呼び出す
await _webView.EnsureCoreWebView2Async(null);
PositionWebView();
_webView.Visible = true;

URLの確実な追跡

クローズ時に _webView.Source が取れない場合に備え、ナビゲーションの各フェーズで _currentUrl フィールドを更新します。

private string _currentUrl = "";
public string CurrentUrl => _currentUrl;

// Navigate 呼び出し時
_currentUrl = url;

// ナビゲーション開始時
void OnNavStarting(...) => _currentUrl = e.Uri;

// ナビゲーション完了時
void OnNavCompleted(...)
{
    string src = _webView.Source?.ToString() ?? _currentUrl;
    _currentUrl = src;
}

設定の自動保存

{
  "window_x": 0, "window_y": 0,
  "window_width": 1440, "window_height": 900,
  "maximized": false,
  "scanner_visible": true,
  "fixed_cols": 0,
  "panels": [
    { "url": "http://192.168.0.10/" },
    { "url": "http://192.168.0.11:8080/" }
  ]
}

共通設計パターン

マルチパネルのグリッドレイアウト

両アプリで同じアルゴリズムを使用しています。

// Auto: パネル数の平方根で列数を決める
int cols = _fixedCols > 0 ? _fixedCols : (int)Math.Ceiling(Math.Sqrt(n));
cols = Math.Max(1, Math.Min(n, cols));
int rows = (int)Math.Ceiling((double)n / cols);

// セルサイズを均等に分割
int cellW = Math.Max(minW, (availW - GAP * (cols - 1)) / cols);
int cellH = Math.Max(minH, (availH - GAP * (rows - 1)) / rows);
パネル数 Auto列数
1 1列
2〜4 2列
5〜9 3列
10〜16 4列

ツールバーのグリッドボタン

private static readonly (string label, int cols)[] GRID_PRESETS =
{
    ("Auto", 0), ("2", 2), ("3", 3), ("4", 4),
};

// アクティブなボタンをハイライト
private void UpdateGridButtons()
{
    for (int i = 0; i < GRID_PRESETS.Length; i++)
        _gridBtns[i].BackColor = GRID_PRESETS[i].cols == _fixedCols
            ? C_GRIDBTN_ON   // 青でハイライト
            : C_GRIDBTN;     // 通常色
}

開発環境

項目
言語 C# 13
フレームワーク .NET 10.0-windows
UIフレームワーク Windows Forms
外部パッケージ Microsoft.Web.WebView2 1.0.*
ターゲット Windows 10/11

まとめ

ArtNet Multi Monitor Local Web Multi Browser
用途 DMX照明データ監視 ローカルWebサーバー一覧表示
コア技術 UDP + GDI+カスタム描画 TCP/HTTP スキャン + WebView2
最大パネル 16ユニバース 16ブラウザ
サイドバー ユニバーススキャナー Webサーバースキャナー
グリッド Auto/2/3/4列 Auto/2/3/4列

どちらも「現場で実際に使うツール」として、起動後すぐに前回の状態が復元されること、スキャナーで対象をすばやく見つけられることを重視して設計しました。


ソースコード

GitHubリポジトリ:

  • ArtNetMultiMonitor/ — ArtNet DMX マルチモニター
  • LocalWebMultiBrowser/ — ローカルWebマルチブラウザ
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?