CLI приложения #15. Спиннеры и статусы
Прогресс-бар хорош, когда известен объём работы. Но огромная часть операций устроена иначе: запрос к серверу, ожидание ответа базы, сборка проекта. Сколько это займёт — неизвестно, и проценты показывать не из чего.
Показывать всё равно надо, иначе пользователь смотрит в застывший терминал и гадает, работает программа или повисла. Для этого есть спиннер — крутящийся индикатор, который говорит ровно одно: «программа работает, идёт фоновый процесс».
AnsiConsole.Status()
.Start("Подключение к серверу...", ctx =>
{
Thread.Sleep(2000);
});
Три строки, и пользователь видит вращающийся индикатор с подписью.
Самое полезное здесь — возможность менять подпись по ходу дела. Операция длинная, но её этапы обычно известны:
AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.SpinnerStyle(Style.Parse("green"))
.Start("Подключение...", ctx =>
{
Connect();
ctx.Status("Загрузка метаданных...");
LoadMetadata();
ctx.Status("Проверка целостности...");
Verify();
});
Пользователь видит не просто «что-то происходит», а на каком именно шаге программа находится. Если она застрянет, сразу понятно, где именно, — и это экономит массу времени при разборе проблем.
Вариантов спиннеров в библиотеке несколько десятков: Dots, Line, Star, Arrow, Clock и другие. Посмотреть все можно на сайте библиотеки — spectreconsole.net.
Есть и более мощный инструмент — живое обновление произвольного содержимого. Не просто строка со спиннером, а целая таблица, которая меняется на глазах:
var table = new Table();
table.AddColumn("Сервис");
table.AddColumn("Статус");
AnsiConsole.Live(table)
.Start(ctx =>
{
foreach (var service in services)
{
table.AddRow(
Markup.Escape(service.Name),
CheckHealth(service) ? "[green]OK[/]" : "[red]недоступен[/]");
ctx.Refresh();
}
});
Таблица растёт построчно, прямо на экране. Тот же приём работает с панелями, деревьями и любым другим элементом Spectre.
Важно!
Ограничения те же, что у прогресс-бара из прошлого поста: пока активен живой блок, нельзя писать в консоль напрямую, и одновременно может работать только один такой блок. Всё, что нужно показать, меняйте внутри самого элемента и вызывайтеctx.Refresh().
В качестве совета: спиннер уместен для операций примерно от секунды-двух. Если работа занимает меньше, он мелькнёт и исчезнет — это выглядит так себе. А вот для всего, что тянется дольше пары секунд, индикатор обязателен: молчащая программа воспринимается как зависшая, даже если внутри всё идёт по плану.
На этом блок про внешний вид закончен. Дальше двинемся в сторону взаимодействия: научим приложение задавать вопросы, принимать данные из других программ и корректно реагировать на Ctrl+C.