.NET 10 WinForms で作る:マルチモニターツール2本 — ArtNet DMXモニター & ローカルWebマルチブラウザ
この記事は Claude(Anthropic)を使用して作成しました。
コード・設計・文章はAIとの対話をもとに生成・整理したものです。
内容の正確性には注意を払っていますが、参考にされる際はご自身でも動作確認をお願いします。
はじめに
照明制御現場で使う ArtNet DMXモニター と、ローカルネットワーク上のWebサーバーを一覧表示する マルチパネルブラウザ を .NET 10 Windows Forms で作りました。
どちらも「最大16画面を格子状に同時表示」「サイドバーでの一覧切り替え」「設定の自動保存/復元」という共通コンセプトで設計しています。
1. ArtNet Multi Monitor
概要
Art-Net(照明制御プロトコル)のDMXデータを、最大16ユニバース同時にリアルタイム表示するモニターです。

主な機能
- 最大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個のパネルで同時に閲覧できるマルチブラウザです。
主な機能
- 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マルチブラウザ

