mcpField
D
dotnet-terminal-gui

dotnet-terminal-gui

github.com/wshaddix開発8677 回インストール35 週間前に更新
リポジトリ

dotnet-terminal-gui について

dotnet-terminal-guiは、Terminal.Gui v2で端末UIを開発する.NET開発者向けのスキルです。Windows、macOS、Linuxの端末に対応し、アプリのライフサイクルやレイアウト、ビュー、イベント処理を扱います。

主な機能

  1. 01アプリのライフサイクルとリソース管理
  2. 02PosとDimによるビュー配置
  3. 03ビュー、メニュー、ダイアログの構築
  4. 04キーボードとマウスのイベント処理
  5. 05テーマ、色、枠線の設定

活用シーン

  1. 01クロスプラットフォーム端末UIアプリの開発
  2. 02フォームやリストを備えたTUIの作成
  3. 03端末アプリへのメニューとショートカット追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-terminal-gui

インストール後、Claude Code で/dotnet-terminal-guiを入力して読み込まれたか確認します。

詳細

dotnet-terminal-gui

ウィンドウ、メニュー、ダイアログ、ビュー、レイアウト、イベント処理、カラーテーマ、マウスサポートを備えた、完全なターミナルユーザーインターフェイスを構築するための Terminal.Gui v2。Windows、macOS、Linux の各ターミナルでクロスプラットフォームに動作します。

バージョンに関する前提: .NET 8.0 以降をベースラインとします。Terminal.Gui 2.0.0-alpha(v2 Alpha は新規プロジェクト向けの現在の開発ラインです。API は包括的な機能を備えて安定していますが、Beta 前に破壊的変更が加わる可能性はあるものの、コアアーキテクチャは堅牢です)。v1.x(1.19.0)はメンテナンスモードで、新機能は追加されません。

対象範囲: このスキルでは、アプリケーションのライフサイクル、レイアウト、ビュー、メニュー、ダイアログ、イベント処理、テーマなど、Terminal.Gui を使った完全な TUI アプリケーション開発を扱います。テーブル、進捗バー、プロンプト、マークアップなどのリッチコンソール出力は [skill:dotnet-spectre-console] が担当します。CLI コマンドラインの解析は [skill:dotnet-system-commandline] が担当します。CLI アプリケーションのアーキテクチャと配布は [skill:dotnet-cli-architecture] と [skill:dotnet-cli-distribution] が担当します。

相互参照:[skill:dotnet-spectre-console] はリッチコンソール出力の代替手段、[skill:dotnet-csharp-async-patterns] は非同期 TUI パターン、[skill:dotnet-native-aot] は AOT コンパイルに関する考慮事項、[skill:dotnet-system-commandline] は CLI 解析、[skill:dotnet-csharp-dependency-injection] は TUI アプリでの DI、[skill:dotnet-accessibility] は TUI のアクセシビリティに関する制約とスクリーンリーダーに関する考慮事項を扱います。


パッケージの参照

<ItemGroup>
  
  <PackageReference Include="Terminal.Gui" Version="2.0.0-alpha.*" />
</ItemGroup>

Terminal.Gui v2 は .NET 8 以降および .NET Standard 2.0/2.1 を対象とします。v1 のメンテナンス対象プロジェクトでは、Version="1.19.*" を使用してください。


アプリケーションのライフサイクル

Terminal.Gui v2 は、適切にリソースをクリーンアップするため、IApplication と IDisposable を使ったインスタンスベースのモデルを採用しています。これは、v1 の静的な Application.Init() / Application.Run() / Application.Shutdown() パターンに代わるものです。

基本的なアプリケーション
using Terminal.Gui;

// Create and initialize the application (instance-based in v2)
using IApplication app = Application.Create().Init();

var window = new Window
{
    Title = "My TUI App",
    Width = Dim.Fill(),
    Height = Dim.Fill()
};

var label = new Label
{
    Text = "Hello, Terminal.Gui!",
    X = Pos.Center(),
    Y = Pos.Center()
};
window.Add(label);

app.Run(window);
型付きの結果を返すアプリケーション
using IApplication app = Application.Create().Init();

// Run a dialog and get a typed result
app.Run<MyInputDialog>();
string? result = app.GetResult<string>();
ライフサイクルイベント
// IsRunningChanging -- cancellable, fires before state change
// IsRunningChanged  -- non-cancellable, fires after state change
window.IsRunningChanged += (sender, args) =>
{
    if (!args.NewValue)
    {
        // Window is closing -- clean up resources
    }
};

レイアウトシステム

Terminal.Gui v2 では、レイアウトを単一のモデルに統合しています(v1 の Absolute/Computed の区別は廃止されています)。位置は Pos(X、Y)で、サイズは Dim(幅、高さ)で制御し、どちらも SuperView のコンテンツ領域を基準とします。

Pos の種類(配置)
// Absolute -- fixed coordinate
view.X = 5;                          // Pos.Absolute(5)

// Percent -- percentage of parent
view.X = Pos.Percent(25);            // 25% from left

// Center -- centered in parent
view.X = Pos.Center();

// AnchorEnd -- anchored from right/bottom edge
view.X = Pos.AnchorEnd(10);          // 10 from right edge

// Relative to another view
view.X = Pos.Right(otherView) + 1;   // 1 right of otherView
view.Y = Pos.Bottom(otherView) + 1;  // 1 below otherView
view.X = Pos.Left(otherView);        // aligned left with otherView
view.Y = Pos.Top(otherView);         // aligned top with otherView

// Align -- align groups of views
view.X = Pos.Align(Alignment.End);   // right-align (e.g., dialog buttons)

// Func -- custom function
view.X = Pos.Func(() => CalculateX());

// Arithmetic
view.X = Pos.Center() - 10;
view.Y = Pos.Bottom(label) + 2;
Dim の種類(サイズ指定)
// Absolute -- fixed size
view.Width = 40;                       // Dim.Absolute(40)

// Percent -- percentage of parent
view.Width = Dim.Percent(50);          // 50% of parent width

// Fill -- fill remaining space
view.Width = Dim.Fill();               // fill to right edge
view.Width = Dim.Fill(2);              // fill minus 2 (margin)

// Auto -- size based on content (replaces v1's AutoSize)
view.Width = Dim.Auto();
view.Width = Dim.Auto(minimumContentDim: 20);

// Relative to another view
view.Width = Dim.Width(otherView);
view.Height = Dim.Height(otherView);

// Func -- custom function
view.Width = Dim.Func(() => CalculateWidth());

// Arithmetic
view.Width = Dim.Fill() - 10;
view.Height = Dim.Height(label) + 2;
Frame と Viewport
  • Frame -- 最外部の矩形:SuperView を基準とする位置とサイズ
  • Viewport -- コンテンツ領域の表示部分:ビューのコンテンツをスクロールして表示するためのポータルとして機能します
// Set content size larger than viewport to enable scrolling
view.SetContentSize(new Size(200, 100));
// Viewport automatically provides scroll behavior

コアビュー

コンテナビュー
// Window -- top-level container with title bar and border
var window = new Window
{
    Title = "Main Window",
    Width = Dim.Fill(),
    Height = Dim.Fill()
};

// FrameView -- bordered container without title bar behavior
var frame = new FrameView
{
    Title = "Settings",
    X = 1, Y = 1,
    Width = Dim.Fill(1),
    Height = 10
};
window.Add(frame);
テキストビューと入力ビュー
// Label -- static text display
var label = new Label
{
    Text = "Username:",
    X = 1, Y = 1
};

// TextField -- single-line text input
var textField = new TextField
{
    X = Pos.Right(label) + 1,
    Y = Pos.Top(label),
    Width = 30,
    Text = ""
};

// TextView -- multi-line text editor
var textView = new TextView
{
    X = 1, Y = 3,
    Width = Dim.Fill(1),
    Height = Dim.Fill(1),
    Text = "Multi-line\nediting area"
};
ボタン
var button = new Button
{
    Text = "OK",
    X = Pos.Center(),
    Y = Pos.Bottom(textField) + 1
};

// Accept event (v2 replaces v1's Clicked)
button.Accepting += (sender, args) =>
{
    MessageBox.Query(button.App!, "Info", $"You entered: {textField.Text}", "OK");
    args.Handled = true; // prevent event bubbling
};
ListView と TableView
// ListView -- scrollable list
var items = new List<string> { "Item 1", "Item 2", "Item 3" };
var listView = new ListView
{
    X = 1, Y = 1,
    Width = Dim.Fill(1),
    Height = Dim.Fill(1),
    Source = new ListWrapper<string>(new ObservableCollection<string>(items))
};

listView.SelectedItemChanged += (sender, args) =>
{
    // args.Value is the selected item index
};
CheckBox と RadioGroup
var checkbox = new CheckBox
{
    Text = "Enable notifications",
    X = 1, Y = 1
};

checkbox.CheckedStateChanging += (sender, args) =>
{
    // args.NewValue is the new CheckState
};

var radioGroup = new RadioGroup
{
    X = 1, Y = 3,
    RadioLabels = ["Option A", "Option B", "Option C"]
};

radioGroup.SelectedItemChanged += (sender, args) =>
{
    // args.SelectedItem is the selected index
};
v2 のその他のビュー
// DatePicker -- calendar-based date input
var datePicker = new DatePicker
{
    X = 1, Y = 1,
    Date = DateTime.Today
};

// NumericUpDown -- numeric spinner
var spinner = new NumericUpDown<int>
{
    X = 1, Y = 3,
    Value = 42
};

// ColorPicker -- TrueColor selection
var colorPicker = new ColorPicker
{
    X = 1, Y = 5,
    SelectedColor = new Color(0, 120, 215)
};

メニューとステータスバー

MenuBar

v2 では、MenuBar のコンストラクター引数に MenuBarItem[] を渡します。MenuItem は、位置引数によるコンストラクターとオブジェクト初期化子構文の両方に対応しています。

var menuBar = new MenuBar([
    new MenuBarItem("_File",
    [
        new MenuItem("_New", "Create new file", () => NewFile()),
        new MenuItem("_Open", "Open existing file", () => OpenFile()),
        new MenuBarItem("_Recent",
        [
            new MenuItem("file1.txt", "", () => Open("file1.txt")),
            new MenuItem("file2.txt", "", () => Open("file2.txt"))
        ]),
        null,  // separator
        new MenuItem
        {
            Title = "_Quit",
            HelpText = "Exit application",
            Key = Application.QuitKey,
            Command = Command.Quit
        }
    ]),
    new MenuBarItem("_Edit",
    [
        new MenuItem("_Copy", "", () => Copy(), Key.C.WithCtrl),
        new MenuItem("_Paste", "", () => Paste(), Key.V.WithCtrl)
    ]),
    new MenuBarItem("_Help",
    [
        new MenuItem("_About", "About this app", () =>
            MessageBox.Query(app, "", "My TUI App v1.0", "OK"))
    ])
]);
window.Add(menuBar);
StatusBar

v2 では、StatusBar は v1 の StatusItem(廃止済み)の代わりに Shortcut オブジェクトを使用します。ショートカットは statusBar.Add() で追加します。

var statusBar = new StatusBar();

var helpShortcut = new Shortcut
{
    Title = "Help",
    Key = Key.F1,
    CanFocus = false
};
helpShortcut.Accepting += (sender, args) =>
{
    ShowHelp();
    args.Handled = true;
};

var saveShortcut = new Shortcut
{
    Title = "Save",
    Key = Key.F2,
    CanFocus = false
};
saveShortcut.Accepting += (sender, args) =>
{
    Save();
    args.Handled = true;
};

var quitShortcut = new Shortcut
{
    Title = "Quit",
    Key = Application.QuitKey,
    CanFocus = false
};

statusBar.Add(helpShortcut, saveShortcut, quitShortcut);
window.Add(statusBar);

ダイアログと MessageBox

Dialog
// Dialog with buttons
var dialog = new Dialog
{
    Title = "Confirm",
    Width = 50,
    Height = 10
};

var label = new Label
{
    Text = "Are you sure?",
    X = Pos.Center(),
    Y = 1
};
dialog.Add(label);

var okButton = new Button { Text = "OK" };
okButton.Accepting += (sender, args) =>
{
    dialog.RequestStop();
    args.Handled = true;
};

var cancelButton = new Button { Text = "Cancel" };
cancelButton.Accepting += (sender, args) =>
{
    dialog.RequestStop();
    args.Handled = true;
};

dialog.AddButton(okButton);
dialog.AddButton(cancelButton);

app.Run(dialog);
MessageBox

v2 では、MessageBox.Query と MessageBox.ErrorQuery の最初の引数に IApplication を指定します。

// Simple query dialog (returns button index)
// In v2, pass the app instance as first parameter
int result = MessageBox.Query(app, "Confirm Delete",
    "Delete this file permanently?",
    "Yes", "No");

if (result == 0)
{
    // User clicked "Yes"
}

// Error message
MessageBox.ErrorQuery(app, "Error",
    "Failed to save file.\nCheck permissions.",
    "OK");
FileDialog
var fileDialog = new FileDialog
{
    Title = "Open File",
    AllowedTypes = [new AllowedType("C# Files", ".cs", ".csx")],
    MustExist = true
};

app.Run(fileDialog);

if (!fileDialog.Canceled)
{
    string selectedPath = fileDialog.FilePath;
    // Process the selected file
}

イベント処理とキーのバインド

コマンドを使ったキーのバインド

Terminal.Gui v2 では、キーのバインドにコマンドパターンを使用します。ビューでサポートするコマンドを宣言し、それらのコマンドにキーを割り当てます。

// Add a custom command and bind a key to it
view.AddCommand(Command.Accept, (args) =>
{
    // Handle the accept command
    return true; // handled
});
view.KeyBindings.Add(Key.Enter, Command.Accept);

// Bind Ctrl+S to a save action
view.KeyBindings.Add(Key.S.WithCtrl, Command.Save);
view.AddCommand(Command.Save, (args) =>
{
    SaveDocument();
    return true;
});
キーイベントの処理
// KeyDown -- fires when a key is pressed
view.KeyDown += (sender, args) =>
{
    if (args.KeyCode == Key.F5)
    {
        RefreshData();
        args.Handled = true;
    }
};

// KeyUp -- fires when a key is released
view.KeyUp += (sender, args) =>
{
    // Handle key release
};
アプリケーションレベルのキー

v2 ではインスタンスベースの IApplication を使用しますが、Application.QuitKey は引き続き静的な構成プロパティであり、Init() の前に設定します。これらはインスタンスごとの状態ではなく、フレームワークレベルの設定です。

// Configure global quit key before Init (default: Esc)
Application.QuitKey = Key.Q.WithCtrl;

IApplication app = Application.Create().Init();
// Application.QuitKey is now in effect for this app instance
マウスイベント
// Mouse events provide viewport-relative coordinates
view.MouseClick += (sender, args) =>
{
    int col = args.Position.X;
    int row = args.Position.Y;
    // Handle click at viewport-relative position
};

view.MouseEvent += (sender, args) =>
{
    if (args.Flags.HasFlag(MouseFlags.Button1DoubleClicked))
    {
        // Handle double-click
    }
};

カラーテーマとスタイル設定

Terminal.Gui v2 は、デフォルトで 24 ビット TrueColor を使用し、機能が限られたターミナルでは 16 色モードに自動的にフォールバックします。

Color と Attribute
// TrueColor via RGB values
var customColor = new Color(0xFF, 0x99, 0x00); // orange

// Create an attribute (foreground + background + style)
var attr = new Attribute(
    new Color(255, 255, 255),  // foreground: white
    new Color(0, 0, 128)       // background: dark blue
);

// Apply to a view
view.ColorScheme = new ColorScheme
{
    Normal = attr,
    Focus = new Attribute(Color.Black, Color.BrightCyan),
    HotNormal = new Attribute(Color.Red, Color.Blue),
    HotFocus = new Attribute(Color.BrightRed, Color.BrightCyan)
};
テキストスタイル
// v2 supports text effects (terminal-dependent)
// Bold, Italic, Underline, Strikethrough, Blink, Reverse, Faint
テーマの構成

Terminal.Gui v2 は、ConfigurationManager を介した JSON ベースのテーマの永続化をサポートしています。ユーザーはコードを変更せずに、テーマ、キー バインド、ビューのプロパティをカスタマイズできます。

// Set a built-in theme via runtime config before Init
ConfigurationManager.RuntimeConfig = """{ "Theme": "Amber Phosphor" }""";
ConfigurationManager.Enable(ConfigLocations.All);

IApplication app = Application.Create().Init();
// Theme is now applied

装飾(境界線、マージン、パディング)

Terminal.Gui v2 は、視覚的な間隔と境界線のための装飾システムを提供します。

var view = new View
{
    X = 1, Y = 1,
    Width = 40, Height = 10
};

// Border styles: Single, Double, Heavy, Rounded, Dashed, Dotted
view.Border.LineStyle = LineStyle.Rounded;
view.Border.Thickness = new Thickness(1);

// Margin -- transparent spacing outside the border
view.Margin.Thickness = new Thickness(1);

// Padding -- internal spacing inside the border
view.Padding.Thickness = new Thickness(1, 0); // top/bottom=1, left/right=0

クロスプラットフォームに関する考慮事項

Terminal.Gui は、ドライバーを自動選択して Windows、macOS、Linux のターミナルをサポートします。

ターミナルとの互換性
機能Windows TerminalmacOS Terminal.appLinux (xterm/gnome)
TrueColor(24 ビット)はいはいはい(ほとんどの場合)
マウスのサポートはいはいはい
Unicode/絵文字はいはい異なります
Sixel 画像一部いいえ一部
キー修飾子完全限定的完全
プラットフォーム固有の注意点
  • macOS Terminal.app -- キー修飾子のサポートが限定的です。Alt キーとの組み合わせはターミナルに捕捉される場合があります。iTerm2 と WezTerm のほうが、キー修飾子をより適切にサポートしています。
  • SSH セッション -- ターミナルの機能はサーバーではなく、クライアントのターミナルに依存します。描画を確認するには、SSH 経由で TUI アプリをテストしてください。
  • Windows Console Host -- レガシーの conhost は Unicode のサポートが限定的です。Windows Terminal は完全にサポートしています。
  • tmux/screen -- 特定のキーの組み合わせを捕捉する場合があります。最適なカラーサポートを得るには、TERM=xterm-256color を設定してください。
ロギングの統合
// Terminal.Gui v2 supports Microsoft.Extensions.Logging
// Useful for debugging rendering and event issues without
// interfering with the TUI display

完全な例:シンプルなエディター

using Terminal.Gui;

using IApplication app = Application.Create().Init();

var window = new Window
{
    Title = $"Simple Editor ({Application.QuitKey} to quit)",
    Width = Dim.Fill(),
    Height = Dim.Fill()
};

// Declare textView first so menu lambda can capture it
var textView = new TextView
{
    X = 0, Y = 1,  // below menu bar
    Width = Dim.Fill(),
    Height = Dim.Fill(1),  // leave room for status bar
    Text = ""
};

// Menu bar
var menuBar = new MenuBar([
    new MenuBarItem("_File",
    [
        new MenuItem("_New", "Clear editor", () => textView.Text = ""),
        null,
        new MenuItem
        {
            Title = "_Quit",
            HelpText = "Exit",
            Key = Application.QuitKey,
            Command = Command.Quit
        }
    ])
]);

// Status bar with Shortcut objects (v2 API)
var statusBar = new StatusBar();
var helpShortcut = new Shortcut { Title = "Help", Key = Key.F1, CanFocus = false };
helpShortcut.Accepting += (s, e) =>
{
    MessageBox.Query(app, "Help", "Simple text editor.", "OK");
    e.Handled = true;
};
statusBar.Add(helpShortcut);

window.Add(menuBar, textView, statusBar);
app.Run(window);

エージェントに関する注意点

  1. v1 の静的ライフサイクルパターンを使用しないでください。 v2 では、IDisposable を使用するインスタンスベースの Application.Create().Init() を使います。v1 の Application.Init()/Application.Run()/Application.Shutdown() パターンは廃止されています。アプリケーションは必ず using ステートメントで囲んでください。
  2. View.AutoSize を使用しないでください。 v2 で削除されました。コンテンツに基づいてサイズを決めるには、代わりに Dim.Auto() を使用してください。
  3. Frame と Viewport を混同しないでください。 Frame は外側の矩形(SuperView に対する位置/サイズ)です。Viewport は表示されるコンテンツ領域(スクロールに対応)です。コンテンツに対する座標には Viewport を使用してください。
  4. Button.Clicked を使用しないでください。 v2 では Button.Accepting に置き換えられました。この意味上の変更はコマンドパターンを反映したもので、ボタンの受け入れアクションが実行されると Accepting が発生します。
  5. バックグラウンドスレッドから UI 操作を呼び出さないでください。 Terminal.Gui はシングルスレッドです。非同期コードから UI スレッドに呼び出しをマーシャリングするには、Application.Invoke() を使用してください。非同期パターンについては、[skill:dotnet-csharp-async-patterns] を参照してください。
  6. ウィンドウを閉じるときに RequestStop() を忘れないでください。 実行中のウィンドウで Dispose() を直接呼び出すと、ターミナルの状態が破損します。RequestStop() を使用して実行ループを適切に終了し、正しいクリーンアップを実行してください。
  7. ターミナルのサイズをハードコードしないでください。 ターミナルのサイズ変更に対応するレスポンシブなレイアウトには、Dim.Fill()、Dim.Percent()、Pos.Center() を使用してください。絶対座標は、ターミナルのサイズが異なると正しく動作しません。
  8. クラッシュ時にターミナルの状態を無視しないでください。 適切に破棄せずにアプリケーションがクラッシュすると、ターミナルが raw モードのままになる場合があります。app.Run() を try/catch で囲み、using ブロックでアプリケーションを確実に破棄して、ターミナルの状態を復元してください。
  9. ScrollView を使用しないでください。 v2 で削除されました。すべてのビューが、SetContentSize() と Viewport プロパティを介してネイティブにスクロールをサポートするようになりました。
  10. NStack.ustring を使用しないでください。 v2 で削除されました。常に標準の System.String を使用してください。
  11. StatusItem を使用しないでください。 v2 で削除されました。代わりに Shortcut オブジェクトを使用し、StatusBar.Add() で追加してください。ステータスバーのショートカットでは CanFocus = false を設定し、Accepting を args.Handled = true で処理してください。

前提条件

  • NuGet パッケージ: Terminal.Gui 2.0.0-alpha(v2)または 1.19.x(v1 メンテナンス版)
  • ターゲットフレームワーク: net8.0 以降(netstandard2.0/2.1 にも対応)
  • ターミナル: ANSI エスケープシーケンスに対応する任意のターミナルエミュレーター。最高の使用感を得るには、Windows Terminal、iTerm2、または最新の Linux ターミナルを推奨します(TrueColor、マウス、Unicode)。
  • GUI ランタイムは不要: Terminal.Gui は任意のターミナルで動作します。X11、Wayland、デスクトップ環境は必要ありません。

参考資料

コメント