Terminal.Gui

最も人気の高いクロスプラットフォーム.NET TUIツールキット。豊富なビルトインビュー、柔軟なレイアウトシステム、MVVM対応、True Color・マウスサポートなど包括的な機能を提供。

TUITerminalC#.NETCross-Platform

GitHub概要

gui-cs/Terminal.Gui

Cross Platform Terminal UI toolkit for .NET

スター10,210
ウォッチ143
フォーク720
作成日:2017年12月11日
言語:C#
ライセンス:MIT License

トピックス

consoleconsole-applicationcross-platformcsharpcursesdotnetterminalterminal-basedterminal-uitui

スター履歴

gui-cs/Terminal.Gui Star History
データ取得日時: 2025/7/25 11:09

Terminal.Gui

Terminal.Guiは、.NET用のクロスプラットフォームターミナルユーザーインターフェース(TUI)ツールキットです。Windows、macOS、Linux/Unixで動作し、豊富なコンソールアプリケーションをテキストベースのユーザーインターフェースで構築できます。

特徴

クロスプラットフォームサポート

  • Windows: Windows Consoleドライバー
  • macOS/Linux: Cursesドライバー
  • フォールバック: .NET Consoleドライバー
  • 抽象化: IConsoleDriverインターフェースでプラットフォーム非依存

豊富なUIコンポーネント

  • 基本コントロール: Button、Label、TextField、TextView
  • リスト系: ListView、ComboBox、RadioGroup、CheckBox
  • データ表示: TableView、TreeView、HexView
  • ダイアログ: MessageBox、OpenDialog、SaveDialog
  • レイアウト: FrameView、ScrollView、TabView

高度なレイアウトシステム

  • Pos/Dimシステム: 絶対・相対・パーセントベースの位置指定
  • ViewArrangement: ユーザーインタラクティブレイアウト(移動・サイズ変更)
  • 自動レイアウト: コンテンツベースのサイズ調整
  • アダーンメント: ボーダー、マージン、パディングの統一管理

モダン機能

  • True Colorサポート: 24ビットRGBカラー(v2)
  • マウスサポート: クリック、ドラッグアンドドロップ
  • クリップボード: カット、コピー、ペースト機能
  • MVVMサポート: Reactive Extensions、ReactiveUIデータバインディング

基本的な使用方法

インストール

<!-- .csprojファイル -->
<PackageReference Include="Terminal.Gui" Version="1.19.0" />

<!-- v2 Alpha -->
<PackageReference Include="Terminal.Gui" Version="2.0.0-pre.1" />
# NuGet CLI
dotnet add package Terminal.Gui

# Package Manager Console
Install-Package Terminal.Gui

Hello World

using Terminal.Gui;

class Program
{
    static void Main()
    {
        Application.Init();
        
        // メインウィンドウを作成
        var top = Application.Top;
        
        // ウィンドウを作成
        var win = new Window("Hello Terminal.Gui!")
        {
            X = 0,
            Y = 1,
            Width = Dim.Fill(),
            Height = Dim.Fill()
        };
        
        // ラベルを追加
        var label = new Label("Welcome to Terminal.Gui!")
        {
            X = Pos.Center(),
            Y = Pos.Center() - 1
        };
        
        // ボタンを追加
        var button = new Button("Quit")
        {
            X = Pos.Center(),
            Y = Pos.Center() + 1
        };
        button.Clicked += () => Application.RequestStop();
        
        // コンポーネントをウィンドウに追加
        win.Add(label, button);
        
        // ウィンドウをトップレベルに追加
        top.Add(win);
        
        // アプリケーションを実行
        Application.Run();
        
        // クリーンアップ
        Application.Shutdown();
    }
}

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

using Terminal.Gui;

class MainApp
{
    static void Main()
    {
        Application.Init();
        
        var top = Application.Top;
        
        // メニューバーを作成
        var menuBar = new MenuBar(new MenuBarItem[]
        {
            new MenuBarItem("_File", new MenuItem[]
            {
                new MenuItem("_New", "Create new file", NewFile),
                new MenuItem("_Open", "Open existing file", OpenFile),
                new MenuItem("_Save", "Save current file", SaveFile),
                null, // セパレーター
                new MenuItem("_Quit", "Exit application", () => Application.RequestStop())
            }),
            new MenuBarItem("_Edit", new MenuItem[]
            {
                new MenuItem("_Copy", "Copy to clipboard", Copy),
                new MenuItem("_Paste", "Paste from clipboard", Paste)
            }),
            new MenuBarItem("_Help", new MenuItem[]
            {
                new MenuItem("_About", "About this application", About)
            })
        });
        
        // ステータスバーを作成
        var statusBar = new StatusBar(new StatusItem[]
        {
            new StatusItem(Key.F1, "~F1~ Help", () => ShowHelp()),
            new StatusItem(Key.F2, "~F2~ Save", SaveFile),
            new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Application.RequestStop())
        });
        
        // メインウィンドウ
        var mainWindow = new Window("Main Window")
        {
            X = 0,
            Y = 1,
            Width = Dim.Fill(),
            Height = Dim.Fill() - 1
        };
        
        // テキストエディタを追加
        var textView = new TextView()
        {
            X = 0,
            Y = 0,
            Width = Dim.Fill(),
            Height = Dim.Fill(),
            Text = "ここにテキストを入力できます..."
        };
        
        mainWindow.Add(textView);
        
        top.Add(menuBar, mainWindow, statusBar);
        
        Application.Run();
        Application.Shutdown();
    }
    
    static void NewFile() {
        MessageBox.Query(50, 7, "新規作成", "新しいファイルを作成しますか?", "Yes", "No");
    }
    
    static void OpenFile() {
        var openDialog = new OpenDialog("ファイルを開く", "ファイルを選択してください");
        Application.Run(openDialog);
        
        if (!openDialog.Canceled)
        {
            MessageBox.Query(50, 7, "ファイルオープン", $"ファイル: {openDialog.FilePath}", "OK");
        }
    }
    
    static void SaveFile() {
        MessageBox.Query(50, 7, "保存", "ファイルを保存しました", "OK");
    }
    
    static void Copy() {
        MessageBox.Query(50, 7, "コピー", "クリップボードにコピーしました", "OK");
    }
    
    static void Paste() {
        MessageBox.Query(50, 7, "ペースト", "クリップボードからペーストしました", "OK");
    }
    
    static void About() {
        MessageBox.Query(50, 7, "アプリケーションについて", 
                        "Terminal.Gui Demo Application\nVersion 1.0\n\nBuilt with Terminal.Gui", "OK");
    }
    
    static void ShowHelp() {
        MessageBox.Query(50, 10, "ヘルプ", 
                        "F1: ヘルプ表示\nF2: ファイル保存\nCtrl+Q: アプリケーション終了\n\nメニューはマウスまたはAlt+キーでアクセスできます", "OK");
    }
}

データバインディングとMVVM

using System.ComponentModel;
using Terminal.Gui;
using System.Collections.ObjectModel;

// モデルクラス
public class Person : INotifyPropertyChanged
{
    private string _name;
    private int _age;
    
    public string Name
    {
        get => _name;
        set
        {
            if (_name != value)
            {
                _name = value;
                OnPropertyChanged(nameof(Name));
            }
        }
    }
    
    public int Age
    {
        get => _age;
        set
        {
            if (_age != value)
            {
                _age = value;
                OnPropertyChanged(nameof(Age));
            }
        }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// ビューモデルクラス
public class PersonViewModel : INotifyPropertyChanged
{
    private Person _selectedPerson;
    
    public ObservableCollection<Person> People { get; } = new ObservableCollection<Person>();
    
    public Person SelectedPerson
    {
        get => _selectedPerson;
        set
        {
            if (_selectedPerson != value)
            {
                _selectedPerson = value;
                OnPropertyChanged(nameof(SelectedPerson));
            }
        }
    }
    
    public PersonViewModel()
    {
        People.Add(new Person { Name = "山田太郎", Age = 30 });
        People.Add(new Person { Name = "佐藤花子", Age = 25 });
        People.Add(new Person { Name = "鈴木一郎", Age = 35 });
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// ビュークラス
class DataBindingDemo
{
    static void Main()
    {
        Application.Init();
        
        var viewModel = new PersonViewModel();
        
        var top = Application.Top;
        
        var window = new Window("データバインディングデモ")
        {
            X = 0,
            Y = 0,
            Width = Dim.Fill(),
            Height = Dim.Fill()
        };
        
        // リストビュー
        var listView = new ListView()
        {
            X = 0,
            Y = 0,
            Width = 30,
            Height = Dim.Fill() - 2,
            AllowsMarking = false
        };
        
        // データソースを設定
        var peopleNames = viewModel.People.Select(p => p.Name).ToList();
        listView.SetSource(peopleNames);
        
        // 詳細表示エリア
        var detailFrame = new FrameView("詳細情報")
        {
            X = 32,
            Y = 0,
            Width = Dim.Fill(),
            Height = Dim.Fill() - 2
        };
        
        var nameLabel = new Label("名前:") { X = 1, Y = 1 };
        var nameField = new TextField("")
        {
            X = 10,
            Y = 1,
            Width = Dim.Fill() - 1
        };
        
        var ageLabel = new Label("年齢:") { X = 1, Y = 3 };
        var ageField = new TextField("")
        {
            X = 10,
            Y = 3,
            Width = Dim.Fill() - 1
        };
        
        // 選択変更イベント
        listView.SelectedItemChanged += (args) =>
        {
            if (args.Item >= 0 && args.Item < viewModel.People.Count)
            {
                viewModel.SelectedPerson = viewModel.People[args.Item];
                nameField.Text = viewModel.SelectedPerson.Name;
                ageField.Text = viewModel.SelectedPerson.Age.ToString();
            }
        };
        
        // フィールドの変更イベント
        nameField.TextChanged += (oldText) =>
        {
            if (viewModel.SelectedPerson != null)
            {
                viewModel.SelectedPerson.Name = nameField.Text.ToString();
                // リストを更新
                peopleNames[listView.SelectedItem] = viewModel.SelectedPerson.Name;
                listView.SetNeedsDisplay();
            }
        };
        
        ageField.TextChanged += (oldText) =>
        {
            if (viewModel.SelectedPerson != null && 
                int.TryParse(ageField.Text.ToString(), out int age))
            {
                viewModel.SelectedPerson.Age = age;
            }
        };
        
        detailFrame.Add(nameLabel, nameField, ageLabel, ageField);
        window.Add(listView, detailFrame);
        
        // 初期選択
        if (viewModel.People.Any())
        {
            viewModel.SelectedPerson = viewModel.People[0];
            nameField.Text = viewModel.SelectedPerson.Name;
            ageField.Text = viewModel.SelectedPerson.Age.ToString();
        }
        
        top.Add(window);
        
        Application.Run();
        Application.Shutdown();
    }
}

テーブルとチャート

using Terminal.Gui;
using System.Data;

class TableViewDemo
{
    static void Main()
    {
        Application.Init();
        
        var window = new Window("テーブルビューデモ")
        {
            X = 0,
            Y = 0,
            Width = Dim.Fill(),
            Height = Dim.Fill()
        };
        
        // データテーブルを作成
        var dataTable = new DataTable();
        dataTable.Columns.Add("商品名", typeof(string));
        dataTable.Columns.Add("価格", typeof(decimal));
        dataTable.Columns.Add("在庫", typeof(int));
        dataTable.Columns.Add("カテゴリ", typeof(string));
        
        // サンプルデータを追加
        dataTable.Rows.Add("ノートPC", 89800, 15, "電子機器");
        dataTable.Rows.Add("マウス", 2980, 50, "電子機器");
        dataTable.Rows.Add("キーボード", 8800, 25, "電子機器");
        dataTable.Rows.Add("モニター", 35000, 8, "電子機器");
        dataTable.Rows.Add("コーヒーカップ", 450, 100, "飲物");
        
        // テーブルビューを作成
        var tableView = new TableView()
        {
            X = 0,
            Y = 1,
            Width = Dim.Fill(),
            Height = Dim.Fill() - 3
        };
        
        tableView.Table = dataTable;
        
        // カラム幅を調整
        tableView.Style.ColumnStyles[0].MinWidth = 15; // 商品名
        tableView.Style.ColumnStyles[1].MinWidth = 8;  // 価格
        tableView.Style.ColumnStyles[2].MinWidth = 6;  // 在庫
        tableView.Style.ColumnStyles[3].MinWidth = 10; // カテゴリ
        
        // フィルターフィールド
        var filterLabel = new Label("フィルター:") { X = 0, Y = 0 };
        var filterField = new TextField("")
        {
            X = 10,
            Y = 0,
            Width = 20
        };
        
        // フィルター機能
        filterField.TextChanged += (oldText) =>
        {
            var filterText = filterField.Text.ToString().ToLower();
            var view = dataTable.DefaultView;
            
            if (string.IsNullOrEmpty(filterText))
            {
                view.RowFilter = string.Empty;
            }
            else
            {
                view.RowFilter = $"商品名 LIKE '*{filterText}*' OR カテゴリ LIKE '*{filterText}*'";
            }
            
            tableView.Update();
        };
        
        // ステータスラベル
        var statusLabel = new Label("行を選択してください")
        {
            X = 0,
            Y = Pos.Bottom(tableView)
        };
        
        // 選択イベント
        tableView.SelectedCellChanged += (args) =>
        {
            if (tableView.Table?.Rows.Count > 0 && args.NewRow >= 0)
            {
                var row = tableView.Table.Rows[args.NewRow];
                statusLabel.Text = $"選択: {row["商品名"]} - 価格: {row["価格"]:C} - 在庫: {row["在庫"]}個";
            }
        };
        
        window.Add(filterLabel, filterField, tableView, statusLabel);
        
        Application.Top.Add(window);
        Application.Run();
        Application.Shutdown();
    }
}

高度な機能

カスタムコントロール

using Terminal.Gui;

public class ProgressBar : View
{
    private float _progress = 0f;
    
    public float Progress
    {
        get => _progress;
        set
        {
            _progress = Math.Max(0f, Math.Min(1f, value));
            SetNeedsDisplay();
        }
    }
    
    public ProgressBar()
    {
        Height = 1;
        Width = Dim.Fill();
    }
    
    public override void Redraw(Rect bounds)
    {
        Driver.SetAttribute(ColorScheme.Normal);
        
        int width = bounds.Width;
        int filledWidth = (int)(width * _progress);
        
        for (int x = 0; x < width; x++)
        {
            if (x < filledWidth)
            {
                Driver.SetAttribute(ColorScheme.HotNormal);
                Driver.AddRune(x, 0, '█');
            }
            else
            {
                Driver.SetAttribute(ColorScheme.Normal);
                Driver.AddRune(x, 0, '░');
            }
        }
        
        // パーセンテージを表示
        string percent = $"{(int)(_progress * 100)}%";
        int textX = (width - percent.Length) / 2;
        
        Driver.SetAttribute(ColorScheme.HotNormal);
        for (int i = 0; i < percent.Length && textX + i < width; i++)
        {
            Driver.AddRune(textX + i, 0, percent[i]);
        }
    }
}

テーマカスタマイズ

// カスタムカラースキーム
var customScheme = new ColorScheme
{
    Normal = Attribute.Make(Color.White, Color.Blue),
    Focus = Attribute.Make(Color.Yellow, Color.Blue),
    HotNormal = Attribute.Make(Color.Cyan, Color.Blue),
    HotFocus = Attribute.Make(Color.Yellow, Color.Red),
    Disabled = Attribute.Make(Color.Gray, Color.Blue)
};

// コントロールに適用
window.ColorScheme = customScheme;
button.ColorScheme = customScheme;

キーバインディング

// グローバルキーバインディング
Application.Top.KeyDown += (keyEvent) =>
{
    if (keyEvent.KeyEvent.Key == Key.F5)
    {
        // F5キーでリフレッシュ
        RefreshData();
        keyEvent.Handled = true;
    }
};

// コントロール固有のキーバインディング
textView.KeyDown += (keyEvent) =>
{
    if (keyEvent.KeyEvent.Key == (Key.CtrlMask | Key.S))
    {
        // Ctrl+Sで保存
        SaveFile();
        keyEvent.Handled = true;
    }
};

エコシステム

サポートされるプラットフォーム

  • Windows: Windows 10/11、Windows Server
  • macOS: macOS 10.15+
  • Linux: Ubuntu、Debian、CentOS、RHEL、Fedora
  • Unix: FreeBSD、OpenBSD、Solaris

.NETバージョンサポート

  • .NET 6.0+: 推奨バージョン
  • .NET Core 3.1+: サポート
  • .NET Framework 4.8+: Windowsのみ
  • Mono: 限定的サポート

ツールとユーティリティ

  • UICatalog: コンポーネントデモアプリ
  • Visual Designer: ビジュアルデザイナー(実験的)
  • NuGet: 公式パッケージ配布

利点

  • 成熟度: .NETTUILibの中で最も成熟したフレームワーク
  • 機能の充実: 豊富なビルトインコンポーネント
  • クロスプラットフォーム: 真のクロスプラットフォーム対応
  • アクティブ開発: 継続的な開発と改善
  • コミュニティ: 大きなコミュニティとサポート

制約事項

  • 学習曲線: 豊富なAPIのため初期学習コストが高い
  • パフォーマンス: 大量のデータではパフォーマンス問題あり
  • v2移行: v1からv2への破壊的変更
  • ドキュメント: 日本語ドキュメントが限定的

他のライブラリとの比較

項目Terminal.GuiConsoloniaConsole Framework
アプローチネイティブTUIAvaloniaベースWPFライク
XAMLサポート×
成熟度非常に高
コンポーネント数非常に多い中程度基本的
パフォーマンス

まとめ

Terminal.Guiは、.NETエコシステムにおける最も成熟したクロスプラットフォームTUIツールキットです。豊富なコンポーネント、柔軟なレイアウトシステム、MVVMサポート、モダンな機能(True Color、マウスサポート)を提供し、プロフェッショナルなコンソールアプリケーション開発に最適です。v2 Alphaはさらなる改善を提供し、新規プロジェクトでは積極的な採用が推奨されます。