2. Исчезающее меню


Добавил:DMT
Дата создания:5 апреля 2008, 15:03
Дата обновления:5 апреля 2008, 15:03
Просмотров:5691 последний ---
Комментариев: 0

2. Исчезающее меню

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

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

В исчезающем меню на экран выводится список опций, и после выбора одной из них меню исчезает, а содержимое экрана, измененное выводом опций меню, восстанавливается.

2.1. Алгоритм создания исчезающего меню

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

Эта подпрограмма имеет список опций в качестве параметра и будет возвращать целое число, равное номеру опции, выбранной пользователем.

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

Значение номера выбранной опции определяется либо передвижением освещенной области на нужное поле и нажатием клавиши < ENTER >, либо нажатием ''горячей'' клавиши, обычно соответствующей первой букве выбранной возможности. В подпрограмму исчезающего меню должны быть переданы строка, состоящая из '' горячих '' клавиш, позиция левого верхнего угла области вывода меню и количество опций. Определим прототип этой подпрограммы:

int popup (char *menu[], // текст меню

char * keys , // горячие клавиши

int count , // количество опций

int x , int y , // координаты левого верхнего угла

int border ); // если 0, то без рамки

Последовательность действий подпрограммы popup ():

•  сохраняет область вывода меню;

•  выводит рамку, если нужно;

•  выводит меню;

•  получает от пользователя номер выбранной опции;

•  восстанавливает экран в исходное состояние;

•  возвращает номер выбранной опции.

 

2.2. Сохранение и восстановление экрана

В текстовом режиме адрес начала видеопамяти обычно равен В8000h . В некоторых текстовых режимах он имеет значение В0000h .

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

char far *vid_mem;

значение которого устанавливается в главной программе, например, с помощью операторов

int vmode = video _ mode (); // определение режима с помощью

//подпрограммы из пп. 1.5 (пример 1)

if (vmode == 7)

vid_mem = (char far *) 0xB0000000; // монохромный режим

else

vid_mem = (char far *) 0xB8000000; // цветной режим

Мы будем предполагать, 569f что установлен один из режимов 2, 3 или 7.

Для того чтобы сохранить часть экрана, будем считывать символы и атрибуты из видеопамяти и записывать их в указанную в подпрограмме аргументом

unsigned *buf_ptr

область оперативной памяти. Получим следующую подпрограмму сохранения области экрана:

void save_video (int startx, int endx, int starty, int endy,

unsigned *buf_ptr)

// ( startx , starty ) – координаты левого верхнего угла

// ( endx , endy ) - координаты правого нижнего угла

{

register int i , j ; // счетчики строки и столбца

char far * v , far * t ; // указатели на видеопамять

v = vid _ mem ; // начало видеопамяти

for (i=starty; i<=endy; i++)

for (j=startx; j<=endx; j++)

{

t = v +160* i +2* j ; // адрес символа и атрибута

* buf _ ptr ++ = * t ++; // сохранение символа

* buf _ ptr ++ = * t ; // сохранение атрибута

}

}

Для восстановления области экрана теперь достаточно переписать данные из области сохранения в видеопамять. Получим следующую подпрограмму восстановления:

void restore_video (int startx, int endx, int starty, int endy,

unsigned *buf_ptr)

{

register int i, j;

char far * v , far * t ; // указатели на видеопамять

v = vid_mem;

t = v;

for (i=starty; i<=endy; i++)

for (j=startx; j<=endx; j++)

{

v = t+160*i+2*j;

* v ++ = * buf _ ptr ++; // восстановление символа

* v = * buf _ ptr ++; // восстановление атрибута

}

}

 

2.3. Вывод меню и рамки

После сохранения области вывода меню, согласно пп. 2.1, выводится рамка и меню. Текст опций меню составляет массив строк и определяется, например, следующим образом:

char * menu [] =

{

“Цвет текста”,

“Цвет фона”,

“Эхо”,

“Выход”

};

Для того чтобы вывести такое меню, нужно сначала закрасить прямоугольник цветом фона, а потом вывести строки из заданного массива:

void display_menu (char *menu[], int startx, int starty, int endx,

int endy, int count) // вывод меню на экран

{

register int i, j;

for (i=starty; i<=endy; i++)

for (j=startx; j<=endx; j++)

write_char (j, i,' ', 7); // закраска области

for (i=0; i<count; i++, starty++)

write_string (startx, starty, menu[i],7);

}

Здесь мы использовали подпрограммы write_char() и write_string() из пп.1.5 (примеры 6 - 7). Байт атрибута равен 7, следовательно, подпрограмма будет выводить строки меню белым цветом на черном фоне.

Для вывода рамки будем использовать коды символов псевдографики: 218 для левого верхнего угла, 191 – для правого верхнего, 192 – для левого нижнего, 217 – для правого нижнего, 196 – для верхней и нижней строк и 179 – для боковых столбцов.

void draw_border (int startx, int starty, int endx, int endy)

{

register int i;

write_char(startx, starty, 218, 7);

write_char(endx, starty, 191, 7);

write_char(startx, endy, 192, 7);

write_char(endx, endy, 217, 7);

for (i = startx+1; i<endx; i++)

{

write_char (i, starty, 196, 7);

write_char (i, endy, 196, 7);

}

for (i = starty+1; i<endy; i++)

{

write_char (startx, i, 179, 7);

write_char (endx, i, 179, 7);

}

}

2.4. Определение выбора пользователя

Пользователь осуществляет свой выбор одним из двух способов. При первом способе он, нажимая клавиши ' ? ' и ' ­ ', может переместить освещение на выбранную строку и нажать < ENTER >. Освещение производится с помощью вывода в инверсном режиме, черным по белому, с атрибутом 0 x 70. Второй способ – нажать ''горячую'' клавишу.

Для ввода управляющих кодов и символов применим функцию bioskey() . С помощью bioskey(1) ожидается нажатие клавиши, а с помощью bioskey(0) принимается код клавиши, состоящий из двух байт для управляющего кода и из одного – для символа.

Функцию is_in() , возвращающую номер выбранной ''горячей'' клавиши, разработаем после подпрограммы определения выбора пользователя, в которой она применяется:

int get_resp(int x, int y, int count, char *menu[], char *keys)

{

union inkey

{ // совмещены

char ch[2]; // массив

int i ; // и число

} c;

int arrow_choice=0, // позиция в меню

key_choice; // горячая клавиша

write _ string ( x , y , menu [0], 0 x 70); // выделить первый выбор

for (;;) // бесконечный цикл

{

while (! bioskey (1)); // ждать нажатия

c . i = bioskey (0); // принять символ с клавиатуры

write_string (x, y+arrow_choice,

menu [ arrow _ choice ], 7); // отмена выделения

if ( c . ch [0]) // если c . ch [0] ? 0, то обычная клавиша

{

key_choice = is_in(keys,tolower(c.ch[0]));

if(key_choice) return key_choice;

switch(c.ch[0])

{

case '\r': return arrow_choice; // нажата <ENTER>

case ' ' : arrow_choice++; break;

case 27 : return -1; // <ESCAPE>

break ;

}

}

else // если c . ch [0]=0, то специальная клавиша

{

switch(c.ch[1])

{

case UP : arrow_choice--; // стрелка в верх

break; // UP=72

case DOWN: arrow_choice++; // стрелка вниз

break; // DOWN=80

}

}

if(arrow_choice==count) arrow_choice=0;

if(arrow_choice==-1) arrow_choice=count-1;

write_string(x, y+arrow_choice, menu[arrow_choice], 0x70);

// выделяет выбранную опцию

} // конец цикла

}

Здесь при нажатии стрелки вверх из положения опции 0 переменная arrow_choice становится равной (-1), и мы ей присваиваем номер последней опции count-1 . Аналогично, если была нажата стрелка вниз из последнего положения, то эта переменная становится равной count , и мы ее переводим в положение 0. Тем самым осуществляется цикличность выбора опций меню. Напишем теперь подпрограмму выбора опции по « горячей » клавише:

int is_in (char *S, char C)

// возвращает номер в строке S символа C , если такого символа в S нет,

// то возвращает 0

{

register int i;

for (i=0; *S; i++)

if (*S++ == C) return i+1; // номер позиции +1

return 0; //нет такого символа

}

2.5. Подпрограмма исчезающего меню

Согласно пп.2.1, алгоритм создания исчезающего меню состоит из следующих шагов: сохранения области вывода меню, вывода меню и рамки, получения номера выбранной опции и восстановления экрана. Поскольку аргументами подпрограммы сохранения являются координаты углов прямоугольника области вывода меню, то перед вызовом этой подпрограммы нужно вычислить координаты этих углов. Если область вывода меню выходит за пределы экрана, то вывести сообщение о невозможности создания меню. Затем захватить область памяти для сохранения области вывода и перейти к выполнению следующих шагов. Последним, дополнительным, шагом будет вызов подпрограммы select() , выполняющей действия, соответствующие выбранному пункту меню.

Приходим к следующей подпрограмме:

int popup( char *menu[], char *keys, int count, int x, int y, int border)

{

register int i,len;

int endx, endy, choice;

unsigned char *p;

if((x>79)||(x<0)||(y>24)||(y<0))

{

printf("область меню выходит за пределы экрана");

return -2; // возвращает –2, если меню создать невозможно

}

len=0; // ширина меню

for (i=0; i<count; i++)

if(strlen(menu[i]) > len)

len = strlen(menu[i]);

endx = len+x+1;

endy = count+y+1;

if((endx > 79) || (endy > 24))

{

printf("область меню выходит за пределы экрана");

return -2;

}

p=(unsigned int *) malloc ((len+2)*(count+2)*2);

if(!p)

{

printf("нет памяти для области сохранения");

return -2;

}

save_video(x, endx, y, endy, p);

if (border) draw_border(x, y, endx, endy);

display_menu(menu, x+1, y+1, endx-1, endy-1, count);

choice = get_resp(x+1, y+1, count, menu, keys);

select(menu, choice);

return choice;

}

Пример . Предположим, что мы пишем программу, которая в режиме редактирования позволяет вводить текстовые данные. Требуется написать подпрограмму, которая изменяет атрибуты символов, выводимых на экран. Для того чтобы выполнить это задание, напишем подпрограмму исчезающего меню, состоящую из четырех опций: ''Цвет вывода'', ''Цвет фона'', ''Эхо'', ''Выход'' . Если выбрана опция ''Цвет вывода'' , то изменяется цвет вывода символов, если выбрана опция ''Цвет фона'' , то изменяется цвет фона. При выборе ''Эхо'' главная программа возвращается в режим редактирования, а ''Выход'' приводит к выходу из главной программы. "Горячие клавиши '' : 'т', 'ф', 'э', 'в' . Меню активизируется с помощью нажатия клавиши F2 . Напишем главную программу, имитирующую режим редактирования, который осуществляется с помощью оператора switch . В случае нажатия стрелок влево, вправо, вверх и вниз, курсор перемещается на одну позицию в соответствующую сторону. Если нажат символ, то он выводится на экран. В случае нажатия F1 выводится сообщение о том, что для вызова меню нужно нажать F2 .

Текст программы и подпрограмм:

 

#include "stdio.h"

#include "dos.h"

#include "stdlib.h"

#include "string.h"

#include "bios.h"

#include "ctype.h"

#include "conio.h"

#define BORDER 1

#define ESC 27 // Escape

#define ENTER 13

#define BKC 8

#define F1 59 // F1

#define F2 60 // F2

#define REV_VID 0x70

#define NORM_VID 7

#define LEFT 75 // стрелка влево

#define RIGHT 77 // стрелка вправо

#define UP 72 // стрелка вверх

#define DOWN 80 // стрелка вниз

void save_video(int, int, int, int, unsigned char*);

void restore_video(int, int, int, int, unsigned char*);

void display_menu(char**,int,int,int,int,int);

void draw_border(int, int, int, int);

void write_string(int, int, char*, int);

void write_char(int,int,char,int);

void select(char**, int);

int popup(char**,char*,int,int,int,int);

int get_resp(int,int,int,char**,char*);

int is_in(char*, char);

int video_mode();

char far *vid_mem;

int last_x, last_y;

int TEXT_ATTR=14, TEXT_COLOR=0, BK_COLOR=0;

char *menu[] =

{

" Цвет текста ",

" Цвет фона ",

" Эхо ",

" Выход "

};

void main(void)

{

char press;

int x,y;

clrscr(); // очистка экрана

int vmode = video_mode();

if((vmode!=2) && (vmode!=3) && (vmode!=7))

{

printf("должен быть 80 символьный текстовый режим");

exit(1);

}

/* присвоить соответствующий адрес видеопамяти */

if(vmode==7)

vid_mem=(char far *)0xB0000000; // монохромный режим

else

vid_mem=(char far *)0xB8000000; // цветной режим

window(2,2,79,24);

gotoxy(1,1);

while(1)

{

press = getch();

if(!press) press = getch(); // расширенная клавиатура

x=wherex(), y=wherey();

switch(press)

{

case LEFT : gotoxy(x-1,y);break;

case RIGHT: gotoxy(x+1,y);break;

case UP : gotoxy(x,y-1);break;

case DOWN : gotoxy(x,y+1);break;

case F1 : write_string(1,24,"F2 - меню , ESC - выход ",TEXT_ATTR);

break;

case F2 : last_x=x;last_y=y;

popup(menu," тфэв ",4,60,0,BORDER); break;

case ENTER: cprintf("\n\r"); break;

case BKC : if (x==1) gotoxy(78,y-1);

else gotoxy(x-1, y);

write_char(wherex(),wherey(),' ',TEXT_ATTR); break;

case ESC : exit(1); break;

default : write_char(x,y, press,TEXT_ATTR);

if(x==78) gotoxy(1,y+1);

else gotoxy(x+1,y); break;

}

}

}

/* вывести исчезающее меню и вернуть выбор

возвращает -2, если меню не может быть создано

возвращает -1, если пользователь нажал клавишу ESC

в остальных случаях она возвращает номер выбранного

режима, начиная с 0 */

int popup( char *menu[], /* текст меню */

char *keys, /* горячие клавиши */

int count, /* число режимов */

int x, int y, /* координаты верхнего левого угла */

int border) /* если 0 то без рамки */

{

register int i,len;

int endx, endy, choice;

unsigned char *p;

if((x>79)||(x<0)||(y>24)||(y<0))

{

printf(" выход за пределы экрана");

return -2;

}

/* вычисление размеров меню*/

len=0;

for(i=0;i<count;i++)

if(strlen(menu[i]) > len)

len = strlen(menu[i]);

endx=len+x+1;

endy=count+y+1;

if((endx>79) || (endy>24))

{

printf(" выход за пределы экрана ");

return -2;

}

/* размещение памяти для видеобуфера */

p=(unsigned char *)malloc((len+2)*(count+2)*2);

if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

/* сохранение части экрана */

save_video(x, endx, y, endy, p);

/* рисуем рамку, если необходимо */

if(border) draw_border(x,y,endx,endy);

/* высвечивание меню на своем месте */

display_menu(menu,x+1,y+1,endx-1, endy-1, count);

/* ввести выбор пользователя */

choice=get_resp(x+1,y+1,count,menu,keys);

select(menu, choice);

/* восстановление части экрана */

restore_video(x, endx, y, endy, p);

/* освобождаем память*/

free(p);

return choice;

}

/* высвечивание меню на своем месте */

void display_menu(char* menu[], int startx, int starty, int endx, int endy,

int count)

{

register int i, j;

// закраска области меню

for(i=starty; i<=endy;i++)

for(j=startx; j<=endx; j++)

write_char(j, i , ' ', NORM_VID);

for(i=0;i<count;i++,starty++)

write_string(startx, starty, menu[i], NORM_VID);

}

/* рисование рамки меню */

void draw_border(int startx, int starty, int endx, int endy)

{

register int i;

write_char(startx, starty, 218, NORM_VID);

write_char(endx, starty, 191, NORM_VID);

write_char(startx, endy, 192, NORM_VID);

write_char(endx, endy, 217, NORM_VID);

for(i=startx+1; i<endx; i++)

{

write_char(i, starty, 196, NORM_VID);

write_char(i, endy, 196, NORM_VID);

}

for(i=starty+1;i<endy;i++)

{

write_char(startx, i,179, NORM_VID);

write_char(endx, i,179, NORM_VID);

}

}

/* ввести выбор пользователя */

int get_resp(int x, int y, int count, char* menu[], char* keys)

{

union inkey

{

char ch[2];

int i;

} c;

int arrow_choice=0, key_choice;

/* осветить первый выбор */

write_string(x, y, menu[0], REV_VID);

for(;;)

{

while(!bioskey(1)); // ждать нажатия

c.i=bioskey(0);

/* вернуть выбор в нормальный режим */

write_string(x, y+arrow_choice, menu[arrow_choice], NORM_VID);

if(c.ch[0]) // обычная клавиша

{

// горячая клавиша ?

key_choice = is_in(keys,tolower(c.ch[0]));

if(key_choice) return key_choice;

switch(c.ch[0])

{

case '\r' : return arrow_choice;

case ' ' : arrow_choice++; break;

case ESC : return -1; // выйти

}

}

else // специальная клавиша

{

switch(c.ch[1])

{

case UP : arrow_choice--; // стрелка вниз

break;

case DOWN: arrow_choice++; // стрелка вверх

break;

}

}

/* осуществить цикличность меню */

if(arrow_choice==count) arrow_choice=0;

if(arrow_choice<0) arrow_choice=count-1;

/* подсветить выбранную опцию */

write_string(x, y+arrow_choice, menu[arrow_choice], REV_VID);

}

}

/* вывод строки с определенным атрибутом */

void write_string(int x, int y, char* p, int attrib)

{

register int i;

char far *v; // адрес символа

v = vid_mem; // в v помещаем адрес начала видеопамяти

v += y*160+x*2; // вычисляем адрес начала строки в видеопамяти

for(i=y;*p;i++)

{

*v++ = *p++; // записываем в видеопамять символ

*v++ = attrib; // записываем в видеопамять атрибут

}

}

/* запись символа с определенным атрибутом */

void write_char(int x, int y, char ch, int attrib)

{

char far *v; // адрес символа

v = vid_mem; // в v помещаем адрес начала видеопамяти

v += y*160+x*2; // вычисляем адрес символа в видеопамяти

*v++ = ch; // записываем в видеопамять символ

*v = attrib; // записываем в видеопамять атрибут

}

/* сохранение части экрана с использованием

прямого доступа к видеопамяти */

void save_video(int startx, int endx, int starty, int endy,

unsigned char *buf_ptr)

{

register int i, j;

char far *v, far *t;

v=vid_mem;

for(i=starty;i<=endy;i++)

{

for(j=startx; j<=endx; j++)

{

t = v+i*160+j*2; // вычисляем адрес

*buf_ptr++ = *t++; // записываем в буфер символ

*buf_ptr++ = *t; // записываем в буфер атрибут

}

}

}

/* восстановление части экрана с использованием

прямого доступа к видеопамяти */

void restore_video(int startx, int endx, int starty, int endy,

unsigned char *buf_ptr)

{

register int i,j;

char far *v,far *t; // временные переменные

v = vid_mem; // адрес начала видеопамяти

t = v;

for(i=starty; i<=endy; i++)

{

for(j=startx; j<=endx; j++)

{

v = t;

v += i*160+j*2; // вычисление адреса

*v++ = *buf_ptr++; // восстановление из буфера символа

*v = *buf_ptr++; // восстановление из буфера атрибута

}

}

}

/* запрос текущего видеорежима */

int video_mode()

{

union REGS r;

r.h.ah = 15; /* получить режим */

return int86(0x10,&r,&r) & 255;

}

/* проверка принадлежности символа с строке s */

int is_in(char* s, char c)

{

register int i;

for(i=0; *s; i++)

if(*s++ == c) return i;

return 0;

}

/* анализ выбора пункта меню */

void select(char* menu[], int pos)

{

// здесь должна быть Ваша обработка событий меню

switch(pos)

{

case 0: // увеличиваем индекс цвета текста

TEXT_COLOR++;

if (TEXT_COLOR<16) TEXT_ATTR=BK_COLOR*16+TEXT_COLOR;

else {TEXT_COLOR=0; TEXT_ATTR=BK_COLOR*16+TEXT_COLOR;} break;

case 1: // увеличиваем индекс цвета фона

BK_COLOR++;

if(BK_COLOR<16) TEXT_ATTR = BK_COLOR*16+TEXT_COLOR;

else {BK_COLOR=0; TEXT_ATTR = BK_COLOR*16+TEXT_COLOR;} break;

case 2: return;

case 3: exit(1);

}

}

Загрузим программу, введем символы и нажмем клавишу F2 . Меню будет выведено в правой верхней части экрана:

up