Все статьи раздела «Программирование»

Оформляем консольное приложение

Некоторые приемы по управлению цветом и позиционированием текста в консольных приложениях как в DOS/Windows, так и в UNIX.

Многие считают, что консольная программа может выглядеть лишь черно-белой и невзрачной, "радуя" пользователя только сухими строчками текста. В лучшем случае при выводе табличных данных они более-менее выравниваются и обрамляются псевдографикой из плюсов-минусов (кстати, так же делали при печати таблиц на пишущих машинках в докомпьютерную эпоху). Между тем не так давно, когда Windows и Mac еще не приучили мир к графическому интерфейсу, программы отлично рисовали цветные таблички-окошки безо всякой графики. До сих пор вы можете найти такие программы, например, широко распространенный файловый менеджер Far, один из последователей знаменитого Norton Commander. Более того, некоторые программы (например текстовая версия Norton Utilities) создавали "почти графический" интерфейс, заменяя таблицу символов видеоадаптера и даже делая это "на лету" для вывода полноценного графического курсора мыши. Я не буду рассматривать последний случай (скорее всего, для написания графического приложения вы будете использовать графическую платформу, например Windows), а вот о том, как красиво оформить консольное приложение, пару слов скажу.

Цвет

Для начала раскрасим стандартный черно-серый экран. Если вы используете обычный вывод в консоль (например, в C это функции семейства printf), то предпочтительный способ управления цветами зависит от платформы, под которую разрабатывается приложение. Если это UNIX, то самым простым и естественным способом установки цвета, а также и позиционирования курсора, будут ESC-команды. Это последовательности символов, начинающиеся с комбинации "\x27[" (\x27 – это и есть код символа ESC), за которой следуют параметры через запятую. Здесь и далее все справочные материалы вынесены во всплывающие окна. Вот таблица ESC-последовательностей для управления курсором и цветами выводимого текста.

Везде в тексте ESC обозначает символ с кодом 27 (0x1B), буквы x, y, n – переменные, значения которых записываются в десятичной форме.

Управление курсором

ESC[y;xH или ESC[y;xf – переместить курсор в позицию (x,y).

ESC[yA – переместить курсор на y строк вверх.

ESC[yB – переместить курсор на y строк вниз.

ESC[xC – переместить курсор на x символов вправо.

ESC[xD – переместить курсор на x символов влево.

Управление цветами

ESC[nm или ESC[n;nm ESC[n;n;nm  – установить атрибуты вывода текста. Список атрибутов следующий:

Атрибут Значение n
Установить атрибуты по умолчанию 0
Полужирный шрифт 1
Подчеркнутый шрифт 4
Черный цвет символов 30
Красный цвет символов 31
Зеленый цвет символов 32
Желтый цвет символов 33
Синий цвет символов 34
Малиновый (magenta) цвет символов 35
Бирюзовый (cyan) цвет символов 36
Белый цвет символов 37
Черный цвет фона 40
Красный цвет фона 41
Зеленый цвет фона 42
Желтый цвет фона 43
Синий цвет фона 44
Малиновый (magenta) цвет фона 45
Бирюзовый (cyan) цвет фона 46
Белый цвет фона 47

ESC-последовательности можно использовать и в Windows-консоли, но для этого потребуется специальный драйвер ansi.sys, который может быть и не установлен у пользователя вашей программы. Поэтому для приложения под Win32 лучше использовать функции, специально предназначенные для оформления вывода в консоль. Функция SetConsoleTextAttribute  позволяет установить цвета и другие атрибуты (например, подчеркивание), а функция GetConsoleScreenBufferInfo  — получить текущие значения атрибутов и различные другие параметры. Принцип установки атрибутов очень прост: все символы, выведенные после вызова SetConsoleTextAttribute будут иметь установленные атрибуты. Устанавливаете нужные цвета, печатаете текст этими цветами, устанавливаете цвет следующей порции текста, выводите его, и так далее. Подробное описание этих функций доступно в MSDN (щелкните по имени функции), я его не буду повторять, а сразу приведу пример использования, из которого будет все понятно.

  HANDLE consoleOutput;

	// Получаем хэндл консоли 
  consoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); 

	// Устанавливаем цвета и выводим строку
  SetConsoleTextAttribute(consoleOutput, BACKGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY);
  printf("Test string: red on blue\n");

	// Устанавливаем другие цвета и снова выводим строку
  SetConsoleTextAttribute(consoleOutput, BACKGROUND_RED | BACKGROUND_INTENSITY | FOREGROUND_GREEN);
  printf("Test string: green on red\n");

Позиционирование

Теперь рассмотрим способы, которыми можно нарушить стандартный порядок "слева-направо сверху-вниз" вывода данных в консоль. Сразу замечу, что как только вы начинаете использовать позиционирование при выводе, результат работы программы будет, скорее всего, непригоден для перенаправления в файл. Если раскраска при выводе в файл будет просто проигнорирована (при использовании ESC-последовательностей они останутся в файле, но зато вы сможете просто вывести файл на консоль и весь текст будет вновь раскрашен, как при работе программы), то позиционирование приведет к тому, что вывод на экран и текст в файле будут отличаться друг от друга, возможно очень сильно. Но в любом случае, знать некоторые приемы не помешает.

Прием первый: динамическая строка.

Пока вы не перевели строку при выводе, ее содержимое можно активно менять. Этим, например, можно пользоваться для отображения прогресса какой-либо длительной операции. Для изменения содержимого строки можно поступить двумя путями.
Первый: удаляем выведенные символы с помощью символа "backspace" с кодом 8. Он возвращает курсор на одну позицию назад, символ, правда, не стирает, но позволяет записать другой поверх. В этом случае нужно точно считать число выведенных символов, чтобы всегда знать позицию курсора.
Второй: вернуться к началу строки и напечатать новую. Для возврата к началу строки БЕЗ перехода на новую, используйте символ "CR" (Carrige Return) с кодом 13 (0x0C). После этого напечатайте новую строку, заведомо длиннее уже выведенной, чтобы затереть все старые символы (если вы не хотите добиться специальных эффектов, обновляя только начало строки).

Прием второй: прямое управление курсором

В случае UNIX-приложения можно использовать уже описанные в разделе про цвет ESC-последовательности. Windows же предоставляет две функции для управления курсором в консоли: SetConsoleCursorPosition  позволяет установить позицию курсора, а GetConsoleScreenBufferInfo  позволяет получить текущие координаты курсора, размер консоли, положение видимой области (если включена прокрутка) и другие атрибуты. Обе эти функции используют дескриптор консоли, процесс получения которого уже был рассмотрен выше. Как и раньше, ограничусь примером.

  HANDLE consoleOutput;
  COORD cursorPos;

	// Получаем хэндл консоли 
  consoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); 

	// Задаем координаты курсора и перемещаем курсор
  cursorPos.X = 30;
  cursorPos.Y = 3;
  SetConsoleCursorPosition(consoleOutput, cursorPos);

	// Выводим строку
  printf("Test string at position (30, 3)");

	// Повторяем с другими координатами...
  cursorPos.X = 15;
  cursorPos.Y = 8;
  SetConsoleCursorPosition(consoleOutput, cursorPos);

  printf("Test string at position (15, 8)");

Рисование линий и рамок

Чтобы придать выводимому тексту красивый и законченный вид, можно использовать рамки. Особенно уместно они выглядят при выводе табличных данных, однако их можно использовать и для оформления ввода данных (приглашение и поле для ввода оформить в рамку), а при желании можно создать собственную псевдографическую оконную библиотеку (вспомним TurboVision и его многочисленные аналоги, столь популярные лет 10-15 назад). Псевдографику можно использовать и совсем нетрадиционным способом, как это сделано, например, в моем анализаторе COM-порта. Рамки и линии рисуются с помощью набора из 40 символов, 4 из которых являются прямыми линиями (вертикальные/горизонтальные, одинарные и двойные), а оставшиеся 36 – уголками и соединениями между ними во всех возможных комбинациях. Ниже приведена таблица всех этих символов в двух кодировках: CP866 (консоль DOS/Windows – верхняя строка) и KOI-8R (нижняя строка). Первой пользуемся в Windows, второй в UNIX. Числа в шестнадцатеричном представлении, префиксы (типа \x или 0x) опущены для краткости.

 Таблица символов псевдографики в кодировках CP866 (DOS) и KOI8-R

Таблица символов псевдографики для рисования рамок

Для проверки вы можете воспользоваться простой программой на PHP, которая выводит такие же рамки, как те, что изображены выше. Первая половина вывода рассчитана на DOS-консоль, вторая на UNIX в кодировке KOI8-R.

<?php
  print "DOS:\n";
  print "\xDA\xC2\xBF   \xD6\xD2\xB7  \n";
  print "\xC3\xC5\xB4 \xB3 \xC7\xD7\xB6  \n";
  print "\xC0\xC1\xD9   \xD3\xD0\xBD  \n";
  print " \xC4     \xCD\n";
  print "\xD5\xD1\xB8   \xC9\xCB\xBB\n";
  print "\xC6\xD8\xB5 \xBA \xCC\xCE\xB9\n";
  print "\xD4\xCF\xBE   \xC8\xCA\xBC\n";  

  print "KOI8-R:\n";
  print "\x82\x88\x83   \xA4\xB7\xA7  \n";
  print "\x86\x8A\x87 \x81 \xB0\xBD\xB4  \n";
  print "\x84\x89\x85   \xAA\xBA\xAD  \n";
  print " \x80     \xA0\n";
  print "\xA2\xB6\xA6   \xA5\xB8\xA8\n";
  print "\xAF\xBC\xB2 \xA1 \xB1\xBE\xB5\n";
  print "\xA9\xB9\xAC   \xAB\xBB\xAE\n";  
?>