Skip to content

9. Окно

lomaster edited this page Apr 15, 2023 · 2 revisions

Окна являются наиболее важной концепцией в curses. Вы видели выше стандартное окно stdscr, где все функции неявно работали с этим окном. Теперь, чтобы спроектировать даже простейший графический интерфейс, необходимо прибегнуть к помощи окон. Основная причина, по которой вы можете захотеть использовать окна, - это раздельное управление частями экрана, повышение эффективности, обновление только тех окон, которые необходимо изменить, и улучшение дизайна. Я бы сказал, что последняя причина является самой важной при выборе окон. Вы всегда должны стремиться к лучшему и легко управляемому дизайну в ваших программах. Если вы пишете большие, сложные графические интерфейсы, это имеет решающее значение, прежде чем вы начнете что-либо делать.

9.1. Основы

Окно может быть создано вызовом функции newwin(). На самом деле она не создает ничего на экране. Она выделяет память под структуру для управления окном и обновляет структуру данными об окне, такими как его размер, beginy, beginx и т.д.. Таким образом, в curses окно - это просто абстракция воображаемого окна, которым можно манипулировать независимо от других частей экрана. Функция newwin() возвращает указатель на структуру WINDOW, который может быть передан в функции, связанные с окнами, такие как wprintw() и т.д.. Наконец, окно может быть уничтожено с помощью функции delwin(). Это приведет к деаллокации памяти, связанной со структурой окна.

9.2. Пример да будет окно!!!

Что за радость, если окно создано, а мы его не видим. Поэтому самое интересное начинается с отображения окна. Функция box() может быть использована для рисования границы вокруг окна. Давайте рассмотрим эти функции более подробно в этом примере:

#include <ncurses.h>

WINDOW *create_newwin(int height, int width, int starty, int startx);
void destroy_win(WINDOW *local_win);

int main(int argc, char *argv[]) {
    WINDOW *my_win;
    int startx, starty, width, height;
    int ch;

    initscr(); /* Запуск режима curses */
    cbreak(); /* Буферизация строк отключена, передавайте
                * все вещи мне */
    keypad(stdscr, TRUE); /* Мне нужна эта замечательная F1 */

    height = 3;
    width = 10;
    starty = (LINES - height) / 2; /* Расчет для размещения по центру */
    startx = (COLS - width) / 2; /* окна */

    printw("Нажмите F1 для выхода");
    refresh();
    my_win = create_newwin(height, width, starty, startx);

    while ((ch = getch()) != KEY_F(1)) {
        switch (ch) {
            case KEY_LEFT:
                destroy_win(my_win);
                my_win = create_newwin(height, width, starty, --startx);
                break;
            case KEY_RIGHT:
                destroy_win(my_win);
                my_win = create_newwin(height, width, starty, ++startx);
                break;
            case KEY_UP:
                destroy_win(my_win);
                my_win = create_newwin(height, width, --starty, startx);
                break;
            case KEY_DOWN:
                destroy_win(my_win);
                my_win = create_newwin(height, width, ++starty, startx);
                break;
        }
    }

    endwin(); /* Завершение режима curses */
    return 0;
}

WINDOW *create_newwin(int height, int width, int starty, int startx) {
    WINDOW *local_win;
    local_win = newwin(height, width, starty, startx);
    box(local_win, 0, 0); /* 0, 0 задает символы по умолчанию
                             * для вертикальной и горизонтальной
                             * линий */
    wrefresh(local_win); /* Покажите эту коробку */
    return local_win;
}

void destroy_win(WINDOW *local_win) {
    /* box(local_win, ' ', ' '); : Это не приведет к желаемому
     * результат стирания окна. Оно оставит четыре угла
     * и таким образом останется уродливый остаток окна.
     */
    wborder(local_win, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
    /* Принимаются следующие параметры.
     * 1. win: окно, с которым нужно работать.
     * 2. ls: символ, который будет использоваться для левой стороны окна
     * 3. rs: символ, используемый для правой стороны окна
     * 4. ts: символ, используемый для верхней стороны окна
     * 5. bs: символ, используемый для нижней стороны окна
     * 6. tl: символ, используемый для верхнего левого угла окна
     * 7. tr: символ, используемый для верхнего правого угла окна
     * 8. bl: символ, используемый для нижнего левого угла окна
     * 9. br: символ, используемый для нижнего правого угла окна
     */

    wrefresh(local_win);
    delwin(local_win);
}

9.3. Пояснение

Не пугайся. Я знаю, что это большой пример. Но я должен объяснить некоторые важные вещи :-). Эта программа создает прямоугольное окно, которое можно перемещать с помощью клавиш со стрелками влево, вправо, вверх, вниз. Она многократно создает и уничтожает окна по мере нажатия пользователем клавиш. Не выходите за границы экрана. Проверку этих границ мы оставляем на усмотрение читателя. Давайте разберем ее построчно.

Функция create_newwin() создает окно с помощью newwin() и выводит границу вокруг него с помощью box. Функция destroy_win() сначала стирает окно с экрана, рисуя границу символом ' ', а затем вызывает delwin() для деаллокации связанной с ним памяти. В зависимости от того, какую клавишу нажимает пользователь, изменяется starty или startx и создается новое окно.

В destroy_win, как вы можете видеть, я использовал wborder вместо box. Причина написана в комментариях (Вы пропустили. Я знаю. Читайте код)))))))). wborder рисует границу вокруг окна с символами, заданными ему как 4 угловые точки и 4 линии. Если выражаться понятнее, то если вы вызвали wborder, как показано ниже:

wborder(win, '|', '|', '-', '-',  +', '+', '+', '+');

Получается что-то вроде:

+-------------------+
|                   |
|                   |
|                   |
|                   |
|                   |
|                   |
+-------------------+

9.4. Остальное в примере

Вы также можете видеть в приведенных выше примерах, что я использовал переменные COLS, LINES, которые инициализируются размерами экрана после initscr(). Они могут быть полезны при определении размеров экрана и нахождении координат центра экрана, как описано выше. Функция getch(), как обычно, получает клавишу с клавиатуры и в соответствии с ней выполняет соответствующую работу. Этот тип переключения очень часто встречается в любых программах, основанных на графическом интерфейсе.

9.5. Другие пограничные функции

Приведенная выше программа крайне неэффективна в том смысле, что при каждом нажатии клавиши окно уничтожается и создается другое. Поэтому давайте напишем более эффективную программу, которая использует другие функции, связанные с границами.

Следующая программа использует mvhline() и mvvline() для достижения аналогичного эффекта. Эти две функции просты. Они создают горизонтальную или вертикальную линию заданной длины в заданной позиции.

Пример 8 улучшенный

#include <ncurses.h>

typedef struct _win_border_struct {
    chtype ls, rs, ts, bs,
           tl, tr, bl, br;
} WIN_BORDER;

typedef struct _WIN_struct {
    int startx, starty;
    int height, width;
    WIN_BORDER border;
} WIN;

void init_win_params(WIN *p_win);
void print_win_params(WIN *p_win);
void create_box(WIN *win, bool flag);

int main(int argc, char *argv[])
{
    WIN win;
    int ch;
    initscr(); /* Запуск режима curses */
    start_color(); /* Запуск функциональности цвета */
    cbreak(); /* Буферизация строк отключена, передавайте
                * все вещи мне */
    keypad(stdscr, TRUE); /* Мне нужна эта удобная F1 */
    noecho();
    init_pair(1, COLOR_CYAN, COLOR_BLACK);

    /* Инициализация параметров окна */
    init_win_params(&win);
    print_win_params(&win);
    attron(COLOR_PAIR(1));
    printw("Нажмите F1 для выхода");
    refresh();
    attroff(COLOR_PAIR(1));
    create_box(&win, TRUE);

    while ((ch = getch()) != KEY_F(1)) {
        switch (ch) {
            case KEY_LEFT:
                create_box(&win, FALSE);
                --win.startx;
                create_box(&win, TRUE);
                break;
            case KEY_RIGHT:
                create_box(&win, FALSE);
                ++win.startx;
                create_box(&win, TRUE);
                break;
            case KEY_UP:
                create_box(&win, FALSE);
                --win.starty;
                create_box(&win, TRUE);
                break;
            case KEY_DOWN:
                create_box(&win, FALSE);
                ++win.starty;
                create_box(&win, TRUE);
                break;
        }
    }

    endwin(); /* Завершение режима проклятий */
    return 0;
}

void init_win_params(WIN *p_win)
{
    p_win->height = 3;
    p_win->width = 10;
    p_win->starty = (LINES - p_win->height) / 2;
    p_win->startx = (COLS - p_win->width) / 2;
    p_win->border.ls = '|';
    p_win->border.rs = '|';
    p_win->border.ts = '-';
    p_win->border.bs = '-';
    p_win->border.tl = '+';
    p_win->border.tr = '+';
    p_win->border.bl = '+';
    p_win->border.br = '+';
}

void print_win_params(WIN *p_win)
{
#ifdef _DEBUG
    mvprintw(25, 0, "%d %d %d %d %d", p_win->startx, p_win->starty,
             p_win->width, p_win->height);
    refresh();
#endif
}

void create_box(WIN *p_win, bool flag)
{
    int i, j;
    int x, y, w, h;
    x = p_win->startx;
    y = p_win->starty;
    w = p_win->width;
    h = p_win->height;

    if (flag == TRUE) {
        mvaddch(y, x, p_win->border.tl);
        mvaddch(y, x + w, p_win->border.tr);
        mvaddch(y + h, x, p_win->border.bl);
        mvaddch(y + h, x + w, p_win->border.br);
        mvhline(y, x + 1, p_win->border.ts, w - 1);
        mvhline(y + h, x + 1, p_win->border.bs, w - 1);
        mvvline(y + 1, x, p_win->border.ls, h - 1);
        mvvline(y + 1, x + w, p_win->border.rs, h - 1);
    }
    else 
    {
        for(j = y; j <= y + h; ++j)
        {
            for(i = x; i <= x + w; ++i) 
            {
                mvaddch(j, i, ' ');
            }
        }
        refresh();
    }
}