среда, 6 марта 2019 г.

Пишем консольное динамическое меню

Мне было интересно реализовать динамическое меню для консольного приложения. Динамическое оно, потому что, перемещение по пунктам меню будет происходить при помощи клавиш вверх и вниз. В качестве языка программирования будем использовать Си.

И так приступим. Для начала нам необходимо подключить заголовочные файлы.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <conio.h>
А так же для простоты введем макрос для вывода названия пункта меню.
#define PRINT( val ) printf("\n %s\n", val);
Далее, реализуем пункты меню. При выборе пункта меню, он будет выделен рамкой. Но так как любое консольное приложение это прежде всего текстовое приложение без какой либо графики, то в меню задействуем псевдографику. Например элемент псевдографики соответствует шестнадцатеричному коду 0xC9, а элемент коду 0xBA и т. д.
Функция frame отрисовывает рамку.
void frame(char *item)
{
    int i, len;
    len = strlen(item);
    printf("%c", 0xC9);
    for( i=0; i<len; i++ ) printf("%c", 0xCD);
    printf("%c", 0xBC);
}
Для перемещения по пунктам меню необходимо использовать клавиши стрелок — вверх и вниз. За клавишу вверх отвечает код 72, а за клавишу вниз 80. Функция menu_items перебирает всевозможные варианты перемещения по меню и отображает собственно само меню.
unsigned int  menu_items(  char *item[],
                        unsigned int cur_item,
                        unsigned int count_item,
                        unsigned int ch ) /* 80  DOWN key */
                                         /* 72  UP key   */
{
    int i;
 
    if ( (ch == 80 ) && ( cur_item != (count_item - 1) ) )
          cur_item++;
    else if ( (ch == 72) &&  ( cur_item != 0 ) )
          cur_item--;
    else if ( ( ch == 80 ) && ( cur_item == (count_item - 1) ) )
            cur_item = 0;
    else if ( ( ch == 72 ) && ( cur_item == 0 ) )
            cur_item = count_item - 1;
 
    for ( i = 0; i < count_item; i ++ )
    {
        if ( i == cur_item )
        {
            frame( item[ cur_item ] );
        }
        else
        {
            PRINT( item[i] );
         }
    }
 
    return cur_item;
}
Для того, что бы создать иллюзию, что список пунктов меню остается на месте,
а рамка по ним перемещается, нужно в цикле очищать консольный вывод от
предыдущего отображения меню. Делаем это при помощи команды операционной
системы CLS.
Так же в этом цикле нам нужно считывать нажатие клавиш. Клавиши стрелок относятся
к системным, поэтому необходимо сначала считывать системный эскейп код 27,
который говорит нам, что нажата системная клавиша, а затем код самой клавиши.
void menu( char *item[],  unsigned int count_item )
{
    unsigned int crtl, ch = 0,
                    cur_item = 0;
 
    system("cls");
    menu_items( item, cur_item, count_item, 0);
 
    while ( 1 )
     {
        crtl = getch();
        if ( crtl == 27 )  // ESC key
            break;
        ch = getch();
 
        system( "cls" );
        cur_item = menu_items( item, cur_item, count_item, ch );
    }
}
Ну вот мы и добрались до финала, осталось только создать массив пунктов меню
и вызвать функцию menu.
int main()
{
    char *item[] = { "item1", "item2",  "item3", "item4", 
                     "item5", "item6", "item7" };
    unsigned int count = sizeof(item) / sizeof(item[0]);
 
    menu( item,  count );
    return 0;
}
В результате получилось вот такое меню.



P.S.
В данном посте рассмотрен пример реализации под Windows, кому инетересен вариант под Linux, смотрим на гитхабе.


1 комментарий: