Terminal.Gui
最も人気の高いクロスプラットフォーム.NET TUIツールキット。豊富なビルトインビュー、柔軟なレイアウトシステム、MVVM対応、True Color・マウスサポートなど包括的な機能を提供。
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
スター履歴
データ取得日時: 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.Gui | Consolonia | Console Framework |
---|---|---|---|
アプローチ | ネイティブTUI | Avaloniaベース | WPFライク |
XAMLサポート | × | ○ | ○ |
成熟度 | 非常に高 | 中 | 低 |
コンポーネント数 | 非常に多い | 中程度 | 基本的 |
パフォーマンス | 高 | 中 | 中 |
まとめ
Terminal.Guiは、.NETエコシステムにおける最も成熟したクロスプラットフォームTUIツールキットです。豊富なコンポーネント、柔軟なレイアウトシステム、MVVMサポート、モダンな機能(True Color、マウスサポート)を提供し、プロフェッショナルなコンソールアプリケーション開発に最適です。v2 Alphaはさらなる改善を提供し、新規プロジェクトでは積極的な採用が推奨されます。