Что-то меня последнее время стал не устраивать обычный серый стиль консольного приложения)
И вот я решил это дело немного приукрасить и в результате написал маленький модуль с набором утилитарных функций.
Далее я расскажу о подробностях реализации и приведу листинг самого модуля, а так же минимальный низкоуровневый код самого консольного приложения.
Цвет выводимого текста и фона под ним устанавливается функцией SetConsoleTextAttribute(), прототип который выглядит следующим образом:
Где hConsoleOutput - это дескриптор экранного буфера консоли, который можно получить следующим образом:
wAttributes - атрибут, который включает цвета символа и фона. Цвет символа(foreground) заключён в разрядах 1-4 младшего байта, а цвет фона(background) в разрядах 5-8. Цвета идут от 0 до 15.
По умолчанию стоит серебряный цвет символа(7) на чёрном фоне(0), записывается это так:
А, например, чтобы установить красный шрифт(12) на тёмно синем(1) фоне, надо вызвать функцию
SetConsoleTextAttribute() с такими параметрами:
Думаю с этим ясно.
Такими средствами я быстренько сообразил функцию, которая выводила цветную строку, но потом я стал испытывать неудобство, потому что чтобы написать в одну строку несколько цветов, я должен был несколько раз вызывать эту функцию, да ещё и прописывать в неё атрибуты, указывающие на цвет.
Поэтому я начал думать над гибкостью в этом плане.
Как это всё выглядит можно увидеть в листинге утилитарного модуля uConsoleUtils о котором я тут рассказываю)
Назначения функций в данном модуле:
Насчёт разноцветности это всё, теперь "приукрасим" организацию кода самого консольного приложения.
И вот я решил это дело немного приукрасить и в результате написал маленький модуль с набором утилитарных функций.
Далее я расскажу о подробностях реализации и приведу листинг самого модуля, а так же минимальный низкоуровневый код самого консольного приложения.
Цветные надписи.
Итак, сначала я задумался о том, чтобы выводить хотя бы сроки разного цвета.Цвет выводимого текста и фона под ним устанавливается функцией SetConsoleTextAttribute(), прототип который выглядит следующим образом:
C++
- BOOL WINAPI SetConsoleTextAttribute(
- __in HANDLE hConsoleOutput,
- __in WORD wAttributes
- );
Delphi
- outHnd := GetStdHandle(STD_OUTPUT_HANDLE);
По умолчанию стоит серебряный цвет символа(7) на чёрном фоне(0), записывается это так:
Delphi
- SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 + 0 * 16);
Delphi
- SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 12 + 1 * 16);
Такими средствами я быстренько сообразил функцию, которая выводила цветную строку, но потом я стал испытывать неудобство, потому что чтобы написать в одну строку несколько цветов, я должен был несколько раз вызывать эту функцию, да ещё и прописывать в неё атрибуты, указывающие на цвет.
Поэтому я начал думать над гибкостью в этом плане.
Разноцветные строки.
В ходе раздумий, я придумал определённые теги, которые можно использовать прямо в строке.Как это всё выглядит можно увидеть в листинге утилитарного модуля uConsoleUtils о котором я тут рассказываю)
Delphi 2007
- unit uConsoleUtils;
- interface
- uses
- Windows;
- type
- TConsoleTextColor = (tcBlack, tcNavy, tcGreen, tcTeal, tcMaroon, tcPurple,
- tcOlive, tcSilver, tcGray, tcBlue, tcLime, tcAqua, tcRed, tcFuchsia,
- tcYellow, tcWhite);
- TArrOfCTC = array[0..ord(High(TConsoleTextColor))] of string[2];
- const
- // console colors string tags
- CCST: TArrOfCTC = ('^k', '^n', '^g', '^t', '^m', '^p', '^o', '^s', '^v',
- '^b', '^l', '^a', '^r', '^f', '^y', '^w');
- var
- inHnd, outHnd : THandle;
- // funcs
- procedure cuWriteText(aText: string; aFG: TConsoleTextColor = tcSilver; aBG: TConsoleTextColor = tcBlack);
- function cuGetStrColorCode(const aText: string): integer;
- function cuWriteColStr(const aText: string; aBG: TConsoleTextColor = tcBlack): boolean;
- function cuWriteLnColStr(const aText: string; aBG: TConsoleTextColor = tcBlack): boolean;
- implementation
- procedure cuWriteText(aText: string; aFG: TConsoleTextColor = tcSilver; aBG: TConsoleTextColor = tcBlack);
- var
- txt : string;
- begin
- // Конвертируем из Windows-кодировки в DOS
- SetLength(txt, length(aText)); // Создаем буфер для конвертации
- AnsiToOem(PAnsiChar(aText), PAnsiChar(txt));
- // Устанавливаем цвет текста
- SetConsoleTextAttribute(outHnd, ord(aFG) + ord(aBG) * 16);
- write(txt);
- txt := '';
- end;
- function cuGetStrColorCode(const aText: string): integer;
- var
- i : integer;
- begin
- Result := -1;
- if length(aText) <> 2 then
- Exit;
- for i := 0 to length(CCST) - 1 do
- if aText = CCST[i] then
- begin
- Result := i;
- Exit;
- end;
- end;
- function cuWriteColStr(const aText: string; aBG: TConsoleTextColor = tcBlack): boolean;
- var
- i : integer;
- sl : integer;
- txt, outt : string;
- fg : smallint;
- begin
- Result := False;
- sl := length(aText);
- txt := '';
- i := 1;
- while i <= sl do
- begin
- fg := -1;
- if i <= (sl - 1) then
- fg := cuGetStrColorCode(copy(aText, i, 2));
- if fg >= 0 then
- begin
- Result := True;
- if txt <> '' then
- begin
- SetLength(outt, length(txt));
- AnsiToOem(PAnsiChar(txt), PAnsiChar(outt));
- write(outt);
- outt := '';
- txt := '';
- end;
- SetConsoleTextAttribute(outHnd, fg + ord(aBG) * 16);
- inc(i, 2);
- end
- else
- begin
- txt := txt + aText[i];
- inc(i);
- end;
- end;
- if txt <> '' then
- begin
- SetLength(outt, length(txt));
- AnsiToOem(PAnsiChar(txt), PAnsiChar(outt));
- write(outt);
- end;
- outt := '';
- end;
- function cuWriteLnColStr(const aText: string; aBG: TConsoleTextColor = tcBlack): boolean;
- begin
- Result := cuWriteColStr(aText, aBG);
- writeln;
- end;
- initialization
- inHnd := GetStdHandle(STD_INPUT_HANDLE);
- outHnd := GetStdHandle(STD_OUTPUT_HANDLE);
- end.
- cuWriteText - выводит текст указанного цвета на указанном фоне;
- cuGetStrColorCode - возвращает код цвета по его тегу;
- cuWriteColStr - пишет строку с учётом всех цветовых тегов и возвращает True если найден хоть один цветовой тег;
- cuWriteLnColStr - яснопонятно.
Насчёт разноцветности это всё, теперь "приукрасим" организацию кода самого консольного приложения.
Цикл обработки ввода.
Для себя я определил следующий минимум требований для приложения:
С самого начала
можно заметить с помощью какой функции переименовывается окно приложения, а далее функция cuWriteColStr() в действии. Потом пользователя информируют о возможных командах приложения и запускается главный цикл.
В цикле изображена низкоуровневая обработка нажатий клавиш, в том числе и виртуальных. Т.к. обработка низкоуровневая, то многие основные действия необходимо обрабатывать самому, например Enter и BackSpace.
Для распознавания написания цветовых тегов я ввёл дополнительную переменную prevCh т.к. тег состоит из двух символов.
Ну а в самом низу цикла можно наблюдать отлов нажатия виртуальных клавиш)
Смотрим всё это дело в действии:
В дальнейшем, может быть, расширю обзор по фишкам в консольных приложениях, если понадобиться, ну а пока на этом всё.
PS: только один недочёт почему-то проявляется - печать русских символов отображается со сдвигом по ascii таблице кратным 32-м.
- переименование окна приложения;
- перехват нажатий виртуальных клавиш;
- возможность вводить текстовые команды;
- возможность писать текст в котором будут "на лету" учитываться цветовые теги.
Delphi 2007
- program ConAppUtilsDemo;
- {$APPTYPE CONSOLE}
- uses
- Windows, SysUtils,
- uConsoleUtils;
- var
- IR : INPUT_RECORD; // вспомогательная
- crd : cardinal; // вспомогательная
- prevCh : AnsiChar; // предыдущий нажатый символ
- ach : AnsiChar; // текущий нажатый символ
- cmd : string; // введённая команда
- // funcs
- procedure cmdInfo;
- begin
- cuWriteLnColStr('^w--------------------------------------------');
- cuWriteLnColStr(#9'^sДоступны следующие команды:');
- cuWriteLnColStr(#9#9'^w info^s - запрос информации по командам;');
- cuWriteLnColStr(#9#9'^w exit^s - выход из главного цикла.');
- cuWriteLnColStr('^w--------------------------------------------^s');
- end;
- begin
- // переименовываем консольное окно
- SetConsoleTitle(PChar('Con App Utils Demo'));
- writeln;
- cuWriteLnColStr(#9#9'^b<<< Console Application Utils Demo. >>>');
- writeln;
- cuWriteLnColStr('^lЗапущен главный цикл.^s Для выхода введите команду^w exit^s либо нажмите^w F10^s.');
- cmdInfo;
- // запускаем основной цикл приложения
- prevCh := #0;
- cmd := '';
- repeat
- WaitForSingleObjectEx(inHnd, 0, False); // ждем события
- ReadConsoleInput(inHnd, IR, 1, crd); // берем запись в переменную IR
- // обработка нажатия клавиш
- if (IR.EventType = KEY_EVENT) and IR.Event.KeyEvent.bKeyDown then
- begin
- ach := IR.Event.KeyEvent.AsciiChar;
- if ord(ach) >= 128 then
- ach := chr(ord(ach) + 64);
- case ach of
- #8: // back space
- begin
- write(#8); write(#0); write(#8);
- prevCh := #0; // обнуляем предыдущий символ
- cmd := copy(cmd, 1, length(cmd) - 1);
- end;
- #9: // tab
- begin
- write(#9);
- prevCh := #0; // обнуляем предыдущий символ
- end;
- #13: // enter
- begin
- if cmd = 'exit' then
- begin // выходим из главного цикла
- writeln;
- Break;
- end
- else
- if cmd = 'info' then
- begin // выводим список доступных команд
- writeln;
- cmdInfo;
- end
- else
- if cmd <> '' then
- begin
- writeln;
- cuWriteColStr('^rНеизвестная команда:^w ' + cmd + '^s');
- end;
- cmd := '';
- write(#13#10);
- prevCh := #0;
- end;
- //#27: Break; // обработка Esc
- #32..#255:
- begin
- if prevCh >= #32 then
- begin
- write(#8);
- if cuWriteColStr(prevCh + ach) then
- begin
- write(#0);
- write(#8);
- cmd := copy(cmd, 1, length(cmd) - 1);
- ach := #0;
- end;
- end
- else
- write(ach);
- prevCh := ach;
- if ach <> #0 then
- cmd := cmd + ach; // состыковываем команду
- end;
- end;
- end;
- // обработка нажатия виртуальных клавиш
- if IR.Event.KeyEvent.wVirtualKeyCode = VK_F10 then
- begin
- writeln;
- Break;
- end;
- sleep(2); // не напрягаем проц
- until False;
- cuWriteColStr('^lГлавный цикл остановлен.^s для выхода нажмите^w Enter^s...');
- readln;
- end.
В цикле изображена низкоуровневая обработка нажатий клавиш, в том числе и виртуальных. Т.к. обработка низкоуровневая, то многие основные действия необходимо обрабатывать самому, например Enter и BackSpace.
Для распознавания написания цветовых тегов я ввёл дополнительную переменную prevCh т.к. тег состоит из двух символов.
Ну а в самом низу цикла можно наблюдать отлов нажатия виртуальных клавиш)
Смотрим всё это дело в действии:
В дальнейшем, может быть, расширю обзор по фишкам в консольных приложениях, если понадобиться, ну а пока на этом всё.
PS: только один недочёт почему-то проявляется - печать русских символов отображается со сдвигом по ascii таблице кратным 32-м.

Комментариев нет:
Отправить комментарий