C для профессиональных программистов
-- 1 -- Предисловие ----------------------------------------------------------------- Если вы хотите создавать программы мирового уровня, написанные на Си, то эта книга - для вас! Меня зовут Герб Шилдт. Я ветеран программирования во многих компаниях и программирую на Си более десяти лет. Перед тем, как начать писать эту книгу, я изучил большое количество удачного программного обеспечения, пытаясь определить, какие черты они имеют в отличие от менее удачных программ. Я хотел понять, что делает одни программы более удачными, чем другие, аналогичные им. После этого прояснились общие черты. Удачные программы были написаны людьми, которые не только имели крепкую хватку в специальной области, но и в совершенстве освоили оборудование компьютера, включая операционную систему и аппаратное обеспечение. Только программист, осуществляющий полный контроль над ними может писать программы с дружелюбным интерфейсом пользователя которые эффективно выполняются и дают пользователям большую гибкость. Эта книга открывает многие секреты, используемые мастерами программирования для достижения профессиональных результатов. С ее помощью вы расширите подходы и методы, которые делают программы интересными. После прочтения книги вы будете способны писать программы, которые заслужат внимание. Здесь рассматриваются следующие вопросы: # Прямой доступ к памяти экрана для быстрого отображения # Исчезающие и иерархические (popup и pulldown) меню # Процедуры работы с окнами # Завершение программ и оставление их в памяти # Интерфейс с мышью # Графические функции, включая вращение объектов # Языковые интерпретаторы # Передача файлов через последовательный порт Эта книга для любого и каждого программиста на Си, от новичка до профессионала. Даже если вы начинающий, вы можете использовать функции и программы из этой книги без понимания отдельных деталей их работы. Более подготовленные читатели могут использовать эти программы как основу для своих приложений. Исходные тексты этой книги соответствуют стандарту ANSI, кроме некоторых функций, специфичных для ПК. Таким образом все эти программы можно компилировать на любом компиляторе, который поддерживает стандарт. Я использовал для их разработки Турбо Си и Microsoft Си. Некоторые из многих полезных и интересных функций и программ, содержащихся в книги достаточно длинны. Если вы, как и я, возможно хотите использовать их, но вам лень набивать их на компьютере и потом выискивать неизбежные опечатки, то для такого случая я предоставляю исходные коды, содержащиеся в этой книге, Глава I -- 2 -- на дискете. Цена услуги - 24.95$. Заполните приложенную форму и пошлите ее по указанному адресу вместе с оплатой. Можно использовать карточки MasterCard и Visa. Глава I -- 3 -- ГЛАВА 1. -------- Исчезающие и иерархические меню. ----------------------------------------------------------------- Одна из наиболее очевидных черт профессионально написанных программ - это использование исчезающих и иерархических меню. При правильном использовании, эти меню дают программам дружелюбие, которое пользователи от них и ожидают. Хотя по существу и простые, и исчезающие, и иерархические меню представляют некоторые трудности в программировании. Создание исчезающих и иерархических меню требует прямого управления экраном. Хотя основные программы меню полностью мобильны, программы доступа к экрану зависят от операционной системы и оборудования и не используют обычные функции Си ввода/вывода на консоль. Программы видео доступа разработаны для работы с любым компьютером, использующим ДОС и имеющим BIOS операционной системы совместимый с IBM. BIOS-ДОС выбран потому что он широко используется, но вы можете применить основные идеи и в других системах. Даже если вас сейчас не интересуют исчезающие и иерархические меню, то вам следует прочитать часть этой главы, в которой обсуждаются видеоадаптеры, знание многих основных идей необходимо для понимания последующих глав. Глава I -- 4 -- Что такое исчезающие и иерархические меню? ----------------------------------------------------------------- Важно понимать что такое исчезающие и иерархические меню и чем они отличаются от стандартных меню. При использовании стандартных меню экран очищается или сдвигается, и появляется меню. Когда выбор сделан, экран опять очищается или сдвигается и программа продолжается. Выбор выполняется по номеру или по первой букве каждой альтернативы. Когда используется исчезающее или иерархическое меню, то оно покрывает прямо содержимое экрана. После выбора режима, экран возвращается в предыдущее состояние. Вы выбираете нужный режим из меню одним из двух способов: (1) нажимая активную клавишу, которая является буквой или номером, связанным с выбором, или (2) используя клавиши управления курсором для передвижения подсвеченного поля и клавишу Ввод. Обычно текущее поле показывается в инверсном виде. Основная разница между стандартными меню и исчезающими и иерархическими меню в том, что стандартное меню прерывает программу. Исчезающие и иерархические меню только приостанавливают текущие действия программы. С точки зрения пользователя стандартное меню - прерывание концентрации, тогда как исчезающее меню - просто легкая приостановка, концентрация внимания пользователя не нарушена. Разница между исчезающими и иерархическими меню проста. Только одно исчезающее меню может быть на экране в данный момент времени. Оно используется когда меню имеет только один уровень в глубину, это бывает, когда выбор из меню не имеет подвыборов. С другой стороны несколько иерархических меню могут быть активны одновременно. Они используются когда выбор из одного меню может потребовать использования другого меню для определения некоторых альтернатив. Например, вы можете использовать иерархическое меню, если вы пишете программу, которая определяет фрукт. Если пользователь выбрал "яблоко", следующее меню предлагает выбрать цвет яблока, а третье меню высвечивает яблоки, которые удоволетворяют предыдущим выборам. Вы можете представлять исчезающее меню просто как иерархическое меню, которое не имеет подменю, но разработка отдельных процедур для этих типов меню имеет то преимущество, что иерархическое меню требует значительно более сложной программы, чем простое исчезающее меню. Хотя имеется много способов расположения меню на экране, функции, разработанные в этой главе имеют наиболее общий вид. Этот метод помещает очередное поле меню на новую строку под первым полем. Глава I -- 5 -- Работа видеоадаптеров. ----------------------------------------------------------------- Из-за того, что создание исчезающих и иерархических меню требует прямого управления экраном, важно понимание адаптеров дисплея. Три основных типа адаптеров - это одноцветный адаптер, цветной/графический адаптер (CGA) и усовершенствованный графический адаптер (EGA). CGA и EGA могут иметь несколько режимов работы, включая 40- или 80- символьный текст или графические операции. Эти режимы показаны в таблице 1-1. Программы меню, разработанные в этой главе, разработаны для использования режима 80-символьного текста, который является наиболее общим режимом для общецелевых применений. Это значит, что видео режим системы должен быть 2, 3 или 7. Независимо от используемого режима - координаты левого верхнего угла - 0,0. Таблица 1-1. ------------ Режим Тип Размеры Адаптеры --------------------------------------------------------------- 0 текст,ч/б 40*25 CGA,EGA 1 текст 16 цветов 40*25 CGA,EGA 2 текст ч/б 80*25 CGA,EGA 3 текст 16 цветов 80*25 CGA,EGA 4 графика 4 цвета 320*200 CGA,EGA 5 графика 4 серых тона 320*200 CGA,EGA 6 графика ч/б 640*200 CGA,EGA 7 текст ч/б 80*25 монохромный 8 графика 16 цветов 160*200 PCjr 9 графика 16 цветов 320*200 PCjr 10 графика 4 или 16 цв. 640*200 PCjr,EGA 13 графика 16 цветов 320*200 EGA 14 графика 16 цветов 640*200 EGA 15 графика 4 цвета 640*350 EGA -------------------------------------------------------------- Символы, выводимые на экран, содержатся в некоторой зарезервированной области памяти на адаптере дисплея. Адрес одноцветной информации В0000000H. И CGA, и EGA хранят информацию, начиная с B80000000H. (Они различны для того, чтобы позволить использовать раздельно текстовый и графический экран - но на практике это делается редко.) Хотя функции CGA и EGA различны в разных режимах, они одинаковы в режимах 2 и 3. Каждый символ, выводимый на экран, требует два байта видео памяти. Первый байт содержит собственно символ, второй содержит аттрибуты экрана. Для цветного экрана байт аттрибутов интерпретируется так, как показано в таблице 1-2. Если у вас EGA или CGA, то по умолчанию принимается режим 3, и символы выводятся с байтом аттрибутов 7. Это значение включает три основных цвета, производя для символа белый цвет. Для переключения в инверсный Глава I -- 6 -- режим небходимо выключить три основных бита и включить три фоновых бита, что дает значение 70H. Одноцветный адаптер распознает биты мигания и интенсивности. К счастью, он разработан так, что интерпретирует аттрибут 7, как нормальный текст (белое на черном) и 70Н как инверсное видео. Кстати, значение 1 дает подчеркнутые символы. Таблица 1-2 ----------- Байт видеоадаптера Бит Двоичная величина Значение при установке ---------------------------------------------------------------- 0 1 голубой основной 1 2 зеленый основной 2 4 красный основной 3 8 малая интенсивность 4 16 голубой фоновый 5 32 зеленый фоновый 6 64 красный фоновый 7 128 мигающий символ ---------------------------------------------------------------- Каждый адаптер имеет по крайней мере в 4 раза больше памяти, чем необходимо для вывода текста в 80-символьном режиме. Для этого есть две причины. Во-первых, лишняя память нужна для графики, (конечно кроме одноцветного адаптера). Во-вторых это позволяет держать в памяти несколько экранов и потом просто переключаться между ними по мере необходимости. Каждая область памяти называется видеостраницей и эффект от переключения между видеостраницами впечатляющ. По умолчанию при инициализации ДОС используется страница 0, и виртуально все приложения используют страницу 0. По этой причине она используется и в этой главе. Однако, вы можете использовать и другие страницы, если захотите. Имеется три способа доступа к видеоадаптеру. Первый это через прерывание ДОС, которое достаточно медленно для исчезающего меню. Второй - это через процедуры BIOS, которые быстрее, и на быстродействующих машинах, таких, как AT или PS/2 достаточно быстры, если меню невелики. Третий способ - это чтение и запись прямо в видеопамять, что происходит очень быстро, но требует большей работы от вас. Эта глава рассматривает два разных подхода в видео процедурах. Один использует BIOS, а другой прямой доступ к видеопамяти. Глава I -- 7 -- Доступ к экрану через BIOS ----------------------------------------------------------------- Из-за того, что исчезающие и иерархические меню должны сохранять информацию с того места экрана, на котором они расположены, и восстанавливать его после выбора, вы должны иметь процедуры, которые сохраняют и загружают часть экрана. Метод сохранения и восстановления части экрана, рассматриваемый в этом разделе связан с вызовами двух встроенных в BIOS функций, которые читают и записывают символы на экран. Как вы знаете, вызовы BIOS могут быть очень медленными. Однако, они (более или менее) гарантируют работу на любом компьютере, который имеет BIOS, совместимый с IBM, даже если аппаратура экрана другая. Позже в этой главе вы узнаете, как выполнять прямой доступ к видеопамяти на IBM PC и 100% совместимых машинах для того, чтобы увеличить скорость выполнения. Однако, использование прямого доступа к видеопамяти снижает в некоторой степени переносимость, так как требуется 100% совместимость компьютера с IBM PC. Программы меню, основанные на BIOS следует использовать в применениях, которые требуют большей мобильности. Глава I -- 8 -- Использование int86() ----------------------------------------------------------------- Вызовы BIOS используют программные прерывания. BIOS имеет несколько различных прерываний для разных целей. Одно из них мы будем использовать для доступа к экрану. Это прерывание 16 (10Н), которое используется для доступа к дисплею. (Если вы не знакомы с доступом к BIOS, то вы найдете хорошее описание в моей книге "Си: Полный справочник", Беркли, 1987). Как и многие прерывания BIOS, прерывание 16 имеет несколько режимов, выбор которых выполняется по значению регистра AH. Если функция возвращает значение, то оно заносится в регистр AL. Однако, иногда для возвращения нескольких значений используются другие регистры. Для доступа к прерываниям BIOS вам придется использовать функцию Си int86(). (Некоторые компиляторы могут называть эту функцию другим именем, но MicroSoft C и Турбо Си называют ее int86(). Последующие рассуждения ориентированы на эти трансляторы, но вы можете их обобщить. Функция int86() имеет следующую форму: int int86(num,inregs,outregs) int num; /* номер прерывания */ union REGS *inregs; /* входные значения регистров */ union REGS *outregs; /* выходные значения регистров */ Функция int86() возвращает значение регистра АХ. Тип REGS описывается в заголовке DOS.H. Этот тип показан здесь так, как он определен в Турбо Си, однако, он аналогично определен в MisroSoft C и в других компиляторах. struct WORDREGS { unsigned int ax, bx, cx, dx, si, di, cflag, flags; }; struct BYTEREGS { unsigned char al, ah, bl, bh, cl, ch, dl, dh; }; union REGS { struct WORDREGS x; struct BYTEREGS h; }; Как вы можете видеть, REGS - это объединение двух структур. Использование структуры WORDREGS позволяет рассматривать регистры ЦП как 16-битные числа. BYTREGS дает вам доступ к отдельным 8- битным регистрам. Например, для доступа к прерыванию 16, функции 5, вы должны использовать следующую последовательность. union REGS in,out; in.h.ah=5; int86(16,&in,&out); Глава I -- 9 -- Сохранение части экрана. ----------------------------------------------------------------- Для сохранения содержимого экрана, должно быть прочитано и запомнено текущее значение каждой позиции экрана. Для считывания символа с определенной позиции экрана, используется прерывание 16, функция 8, которая возвращает символ и связанный с ним аттрибут текущей позиции курсора. Для считывания символа с определенного места экрана, вы должны иметь способ установки курсора. Хотя некоторые компиляторы Си поддерживают эту функцию, многие ее не имеют. Тем не менее показанная ниже функция goto_xy() может быть использована. Она использует прерывание 16, функцию 2 с координатой столбца в DL и координатой ряда в DH. Видеостраница задается в ВН (используется страница 0 по умолчанию). /* установка курсора в x,y */ void goto_xy(x,y) int x,y; { union REGS r; r.h.ah=2; /* функция установки курсора */ r.h.dl=y; /* координата колонки */ r.h.dh=x; /* координата строки */ r.h.bh=0; /* видео страница */ int86(0x10,&r,&r); } Прерывание 16, функция 8 возвращает символ из текущей позиции курсора в AL и его атрибут в AH. Функция save_video(), показанная здесь, считывает часть экрана, сохраняет информацию в буфер, и очищает эту часть экрана. /* сохранение части экрана */ void save_video(startx,endx,starty,endy,buf_ptr) int startx,endx,starty,endy; unsigned int *buf_ptr; { union REGS r; register int i,j; for(i=starty;i<endy;i++) for(j=startx;j<endx;j++) { goto_xy(j,i); r.h.ah=8; /* функция чтения символа */ r.h.bh=0; /* видео страница */ *buf_ptr++ = int86(0x10,&r,&r); putchar(' '); /* очистка экрана */ } } Первые четыре параметра save_video определяют координаты Глава I -- 10 -- верхнего левого и правого нижнего угла сохраняемой области. Параметр buf_ptr это целый указатель на область памяти, которая содержит информацию. Она должна быть достаточно большой, чтобы разместить всю информацию, считанную с экрана. Программы в этой главе размещают буфер динамически, но вы можете использовать любую другую схему, если это важно для ваших приложений. Не забудьте, однако, что буфер должен существовать, до тех пор, пока экран не вернется в исходное состояние. Эта функция также чистит область, записывая пробел в каждой позиции. Глава I -- 11 -- Восстановление экрана ----------------------------------------------------------------- Восстановление экрана после сделанного выбора из меню, заключается просто в записи предварительно запомненной информации назад в видео память. Для того, чтобы сделать это, используйте прерывание 16, функцию 9, которая требует, чтобы символ был в AL, аттрибут в BL, видео страница в ВН, а количество записываемых символов в CX (в нашем случае 1). Функция restore_video(), описанная здесь, помещает информацию из буфера, на который указывает buf_ptr, на экран, заданный начальными и конечными координатами X и Y. /* восстановление части экрана */ void restore_video(startx,endx,starty,endy,buf_ptr) int startx,endx,starty,endy; unsigned int *buf_ptr; { union REGS r; register int i,j; for(i=starty;i<endy;i++) for(j=startx;j<endx;j++) { goto_xy(j,i); r.h.ah=9; /* функция записи символа */ r.h.bh=0; /* видео страница */ r.x.cx=1; /* число повторений символа */ r.h.al=*buf_ptr++; /* символ */ r.h.bl=*buf_ptr++; /* атрибут */ *buf_ptr++ = int86(0x10,&r,&r); } } Глава I -- 12 -- Создание исчезающих меню ----------------------------------------------------------------- Функции, создающей исчезающее меню, должна быть передана некоторая информация. Во-первых, это список предоставляемых меню режимов. Поскольку в меню передаются высвечиваемые строки, то простейший путь передачи списка строк в функцию - помещение их в двумерный массив и передача указателя на массив. Как утверждалось ранее, значение меню может быть выбрано либо передвижением освещенной области на нужное поле и нажатием ВК или нажатием клавиши, указывающей на это поле. Для того, чтобы функция знала, какие клавиши "горячие" и что они обозначают, ей должны быть переданы их имена. Лучший путь для этого - передать строку, которая содержит символы "горячих" клавиш в том же порядке, что и строки меню. Функция popup() должна также знать как много режимов в меню, и поэтому это число должно быть передано ей. Она должна также знать где расположить меню, то есть нужны координаты X и Y. Наконец, в некоторых ситуациях может быть желательным помещать меню в рамку, а в других - нет. Поэтому должно быть передано значение рамка включена/выключена. Для того, чтобы начать разработку функции popup() нам нужно описание : /* высветить исчезающее меню и возвратить выбор */ int popup(menu,keys,count,x,y,border) char *menu[]; /* текст меню */ char *keys; /* горячие клавиши */ int count; /* число режимов */ int x,y; /* координаты левого верхнего угла */ int border; /* если 0 то без рамки */ Функция popup() делает следующее : # Сохраняет область экрана под меню # Высвечивает рамку, если надо # Высвечивает меню # Получает ответ пользователя # Восстанавливает экран в исходное состояние Две из этих целей были достигнуты в save_video() и restore_video(), описанных в предыдущем разделе. Давайте рассмотрим как достигнуть три оставшиеся. Глава I -- 13 -- Высвечивание меню. ----------------------------------------------------------------- Для того, чтобы высветить меню, необходимо помнить, что popup получает указатель на массив указателей на строки. Для высвечивания отдельных строк вы просто индексируете указатель, как массив. Каждый элемент в массиве является указателем на соответствующий элемент меню. Следующая функция display_menu() высвечивает каждый элемент меню. /* высвечивание меню на своем месте */ void display_menu(menu,x,y,count) char *menu[]; int x,y,count; { register int i; for(i=0;i<count;i++,x++) { goto_xy(x,y); printf(menu[i]); } } Как вы можете видеть, эта функция получает указатель на массив строк, которые надо вывести, координаты начала меню и количество строк в меню. В дальнейшем простейший способ создать двумерный массив символов, который содержит строки меню - это создание переменной в общем виде: char *<имя меню> [] = { "первая строка", "вторая строка", . . . "N-ая строка" }; Это описание автоматически заставляет компилятор Си поместить строки в таблицу строк. Переменная указывает на первый символ первой строки в таблице. Например, это описание создает переменную fruit (фрукт), которая указывает на "Я" в "Яблоко". char *fruit[] = { "Яблоко", "Апельсин", "Груша", "Грейпфрут", "Малина", "Клубника" }; Глава I -- 14 -- Глава I -- 15 -- Высвечивание рамки ----------------------------------------------------------------- Если нужна рамка, то можно воспользоваться нижеприведенной программой для вывода рамки вокруг меню с заданными координатами левого верхнего и правого нижнего углов. Она использует символы, которые являются частью стандартного набора символов на машинах, совместимых с IBM. Если вы хотите, вы можете выбрать другие. void draw_border(startx,starty,endx,endy) int startx,starty,endx,endy; { register int i; for(i=startx+1;i<endx;i++) { goto_xy(i,starty); putchar(179); goto_xy(i,endy); putchar(179); } for(i=starty+1;i<endy;i++) { goto_xy(startx,i); putchar(196); goto_xy(endx,i); putchar(196); } goto_xy(startx,starty); putchar(218); goto_xy(startx,endy ); putchar(191); goto_xy(endx ,starty); putchar(192); goto_xy(endx ,endy ); putchar(217); } Глава I -- 16 -- Ввод выбора пользователя ----------------------------------------------------------------- Как утверждалось, пользователь может вводить выбор одним из двух способов. Во-первых с помощью клавиш СТРЕЛКА ВНИЗ и СТРЕЛКА ВВЕРХ может переместить освещение на строку и нажать Ввод для ее выбора, (Обычно освещение строки выполняется в инверсном режиме.) Освещенную строку также можно передвигать пробелом. Второй способ это нажатие клавиши, связанной с выбором. Функция get_resp(), показанная здесь, достигает этих целей. /* ввести выбор пользователя */ get_resp(x,y,count,menu,keys) int x,y,count; char *menu[]; char *keys; { union inkey { char ch[2]; int i; } c; int arrow_choice=0,key_choice; y++; /* осветить первый выбор */ goto_xy(x,y); write_video(x,y,menu[0],REV_VID); for(;;) { while(!bioskey(1)); /* ждать нажатия */ c.i=bioskey(0); /* вернуть выбор в номальный режим */ goto_hy(arrow_choice,y); write_video(x+arrow_choice,y, menu[arrow_choice],norm_vid); if(c.ch[0]) { /* обычная клавиша */ key_choice= is_in(keys,tоlower(c.ch[0])); if(key_choice) return key_choice-1; switch(c.ch[0]) { case '\r' : return arrow_choice; case ' ' : arrow_choice++; break; case ESC : return -1; /* выйти */ } } else { /* специальная клавиша */ switch(c.ch[1]) { case 72 : arrow_choice--; /* стрелка вниз */ break; case 80 : arrow_choice++; /* стрелка вверх */ break; } Глава I -- 17 -- } if(arrow_choice==count) arrow_choice=0; if(arrow_choice<0) arrow_choice=count-1; /* подсветить выбранную опцию */ goto_xy(x+arrow_choice,y); write_video(x+arrow_choice,y,menu[arrow_choice],REV_VID); } } Kогда get_resp() начинает выполняться, освещается первое значение меню. Макроопределение REV_VID определяется везде, как 70Н, а NORM_VID как 7Н. Клавиша ESCAPE используется для окончания работы с меню. Значение ESC 27. После этого программа входит в цикл, ожидающий действий пользователя. Она использует функцию bioskey() для того, чтобы дождаться нажатия клавиши, а затем для считывания с этой клавиши. Функция bioskey() специфична для Турбо Си. Если вы используете другой транслятор, вы можете использовать следующую версию функции. /* эмуляция части функции bioskey Турбо Си */ bioskey(c) int c; { switch(c) { case 0: return get_key(); case 1: return kbhit(); } } /* чтение 16 битного скан кода клавиши */ get_key() { union REGS r; r.h.ah=0; return int86(0x16, &r,&r); Причина, по которой вы должны использовать bioskey() вместо getchar() состоит в том, что программа должна иметь возможность считывать полный 16 битный скан код, генерируемый нажатием клавиши. Если нажата символьная клавиша, то символ помещается в младшие 8 бит, а старшие 8 бит равны 0. Однако, если нажата специальная клавиша, такая, как стрелка, младший байт равен 0, а старший содержит код позиции клавиши. Коды позиции для стрелки вверх и стрелки вниз равны 72 и 80 соответственно. Такие функции, как getchar(), возвращают только код символа, и поэтому необходимо получить прямо скан код помимо них. Каждый раз при нажатии стрелки освещенная опция переходит в нормальное изображение, а следующая - освещается. Нажатие стрелки вниз, в момент освещения крайней нижней позиции, означает возвращение к первому значению. То же самое, но наоборот происходит когда нажимается стрелка вверх при освещенной первой Глава I -- 18 -- строке. Функция write_video() используется функцией get_resp() для записи строки на дисплей в позиции Х,Y с определенным атрибутом. Write_video(), показанная здесь, используется для вывода строки меню в инверсном режиме, если она освещена, или в нормальном, если она не освещена. /* вывод строки с определенным атрибутом */ void write_video(x,y,p,attrib) int x,y; char *p; int attrib; { union REGS r; register int i,j; for(i=y; *p; i++) { goto_xy(x,i); r.h.ah=9; /* функция записи символа */ r.h.bh=0; /* видео страница */ r.x.cx=1; /* число повторений символа */ r.h.al=*p++; /* символ */ r.h.bl=attrib; /* атрибут */ int86(0x10,&r,&r); } Функция is_in() возвращает позицию "горячей" клавиши в строке. Если пользователь нажал не ключевую клавишу, то is_in возвращает 0. is_in(s,c) char *s,c; { register int i; for(i=0; *s; i++) if(*s++ == c) return i+1; return 0; } Глава I -- 19 -- Функция popup() ----------------------------------------------------------------- А теперь, когда все части созданы, функция popup может быть записана, как это показано здесь. /* вывести исчезающее меню и вернуть выбор возвращает -2, если меню не может быть создано возвращает -1, если пользователь нажал клавишу ESC в остальных случаях она возвращает номер выбранной альтернативы, начиная с 0 */ int popup(menu,keys,count,x,y,border) char *menu[]; /* текст меню */ char *keys; /* горячие клавиши */ int count; /* число альтернатив */ int x,y; /* координаты левого верхнего угла */ int border; /* если 0 то без рамки */ { register int i,len; int endx endy choice; unsigned int *p; if((x>24)||(x<0)||(y>79)||(y<0)) { printf(" выход за пределы экрана"); return -2; } /* вычисление размеров */ len=0; for(i=0;i<count;i++) if(strlen(menu[i]) > len) len=strlen(menu[i]); endy=len+2+y; endx=count+1+x; if((endx+1>24) || (endy+1>79)) { printf(" выход за пределы экрана"); return -2; } /* размещение памяти для видео буфера */ p=(unsigned int *)malloc((endx-x+1)*(endy-y+1)); if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */ /* сохранение части экрана */ void save_video(startx,endx,starty,endy,p); if(border) draw_border(x,y,endx,endy); /* высвечивание меню на своем месте */ void display_menu(menu,x,y,count); /* ввести выбор пользователя */ choice=get_resp(x,y,count,menu,keys) /* восстановление части экрана */ void restore_video(startx,endx,starty,endy,p); free(p); Глава I -- 20 -- return choice; } Как вы можете видеть, popup() проверяет выход за пределы экрана и слишком большой размер меню. Она возвращает -2, если возникла одна из этих ситуаций. Из-за того, что get_resp() возвращает -1, при нажатии клавиши ESC, возвращение этого значения функцией popup следует рассматривать как "уничтожение" меню. Если пользователь сделал выбор, то возвращаемое значение будет в пределах от 0 до count-1 с соответствием первому значению меню 0. Как уже указывалось, popup() использует динамическое размещение памяти для обеспечения временной памяти для информации об экране. Обычно это лучший подход, но вы можете свободно изменить его, если это важно для вашего приложения. Глава I -- 21 -- Общий обзор ----------------------------------------------------------------- Простая программа, показанная здесь, использует все программы, разработанные для использования исчезающих меню. Вы не видели только функции cls(), которая очищает экран. Некоторые трансляторы Си не имеют функции для этого, и если это так, то вы не можете использовать следующую программу (в чистом виде). /* процедура исчезающего меню для работы в текстовом режиме */ #include "stdio.h" #include "dos.h" #include "stdlib.h" #define BORDER 1 #define ESC 27 #define REV_VID 0x70 #define NORM_VID 7 void save_video(),restore_video(); void goto_xy(),cls(),write_video(); void display_menu(),draw_border(); char *fruit[] = { "Яблоко", "Апельсин", "Груша", "Грейпфрут", "Малина", "Клубника" }; char *color[]={ "Красный", "Желтый", "Оранжевый", "Зеленый" }; char *apple_type[] = { "Красный деликатес", "Джонатан", "Белый налив", "Антоновка" }; main() { int i; cls(); goto_xy(0,0); for(i=0;i<25;i++) printf("Это тест исчезающего меню\n"); Глава I -- 22 -- popup(fruit,"яагрмк",6,1,3,BORDER); popup(color,"кжоз",4,5,10,BORDER); popup(apple_type,"кдба",4,10,18,BORDER); } /* вывести исчезающее меню и вернуть выбор возвращает -2, если меню не может быть создано возвращает -1, если пользователь нажал клавишу ESC в остальных случаях она возвращает номер выбранной альтернативы, начиная с 0 */ int popup(menu,keys,count,x,y,border) char *menu[]; /* текст меню */ char *keys; /* горячие клавиши */ int count; /* число альтернатив */ int x,y; /* координаты левого верхнего угла */ int border; /* если 0 то без рамки */ { register int i,len; int endx endy choice; unsigned char *p; if((x>24)||(x<0)||(y>79)||(y<0)) { printf(" выход за пределы экрана"); return -2; } /* вычисление размеров */ len=0; for(i=0;i<count;i++) if(strlen(menu[i]) > len) len=strlen(menu[i]); endy=len+2+y; endx=count+1+x; if((endx+1>24) || (endy+1>79)) { printf(" выход за пределы экрана"); return -2; } /* размещение памяти для видео буфера */ p=(unsigned int *)malloc((endx-x+1)*(endy-y+1)); if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */ /* сохранение части экрана */ void save_video(x,endx+1,y,endy+1,p); if(border) draw_border(x,y,endx,endy); /* высвечивание меню на своем месте */ void display_menu(menu,x,y,count); /* ввести выбор пользователя */ choice=get_resp(x,y,count,menu,keys) /* восстановление части экрана */ void restore_video(x,endx+1,y,endy+1,p); free(p); return choice; Глава I -- 23 -- } /* высвечивание меню на своем месте */ void display_menu(menu,x,y,count) char *menu[]; int x,y,count; { register int i; for(i=0;i<count;i++,x++) { goto_xy(x,y); printf(menu[i]); } } void draw_border(startx,starty,endx,endy) int startx,starty,endx,endy; { register int i; for(i=startx+1;i<endx;i++) { goto_xy(i,starty); putchar(179); goto_xy(i,endy); putchar(179); } for(i=starty+1;i<endy;i++) { goto_xy(startx,i); putchar(196); goto_xy(endx,i); putchar(196); } goto_xy(startx,starty); putchar(218); goto_xy(startx,endy ); putchar(191); goto_xy(endx ,starty); putchar(192); goto_xy(endx ,endy ); putchar(217); } /* ввести выбор пользователя */ get_resp(x,y,count,menu,keys) int x,y,count; char *menu[]; char *keys; { union inkey { char ch[2]; int i; } c; int arrow_choice=0,key_choice; y++; /* осветить первый выбор */ Глава I -- 24 -- goto_xy(x,y); write_video(x,y,menu[0],REV_VID); for(;;) { while(!bioskey(1)); /* ждать нажатия */ c.i=bioskey(0); /* вернуть выбор в номальный режим */ goto_xy(arrow_choice,y); write_video(x+arrow_choice,y, menu[arrow_choice],norm_vid); if(c.ch[0]) { /* обычная клавиша */ key_choice= is_in(keys,tоlower(c.ch[0])); if(key_choice) return key_choice-1; switch(c.ch[0]) { case '\r' : return arrow_choice; case ' ' : arrow_choice++; break; case ESC : return -1; /* выйти */ } } else { /* специальная клавиша */ switch(c.ch[1]) { case 72 : arrow_choice--; /* стрелка вниз */ break; case 80 : arrow_choice++; /* стрелка вверх */ break; } } if(arrow_choice==count) arrow_choice=0; if(arrow_choice<0) arrow_choice=count-1; /* подсветить выбранную опцию */ goto_xy(x+arrow_choice,y); write_video(x+arrow_choice,y,menu[arrow_choice],REV_VID); } } /* вывод строки с определенным атрибутом */ void write_video(x,y,p,attrib) int x,y; char *p; int attrib; { union REGS r; register int i,j; for(i=y; *p; i++) { goto_xy(x,i); r.h.ah=9; /* функция записи символа */ r.h.bh=0; /* видео страница */ r.x.cx=1; /* число повторений символа */ r.h.al=*p++; /* символ */ r.h.bl=attrib; /* атрибут */ Глава I -- 25 -- int86(0x10,&r,&r); } } /* сохранение части экрана */ void save_video(startx,endx,starty,endy,buf_ptr) int startx,endx,starty,endy; unsigned int *buf_ptr; { union REGS r; register int i,j; for(i=starty;i<endy;i++) for(j=startx;j<endx;j++) { goto_xy(j,i); r.h.ah=8; /* функция чтения символа */ r.h.bh=0; /* видео страница */ *buf_ptr++ = int86(0x10,&r,&r); putchar(' '); /* очистка экрана */ } } /* восстановление части экрана */ void restore_video(startx,endx,starty,endy,buf_ptr) int startx,endx,starty,endy; unsigned int *buf_ptr; { union REGS r; register int i,j; for(i=starty;i<endy;i++) for(j=startx;j<endx;j++) { goto_xy(j,i); r.h.ah=9; /* функция записи символа */ r.h.bh=0; /* видео страница */ r.x.cx=1; /* число повторений символа */ r.h.al=*buf_ptr++; /* символ */ r.h.bl=*buf_ptr++; /* атрибут */ int86(0x10,&r,&r); } } /* очистка экрана */ void cls() { union REGS r; r.h.ah=6; /* код прокрутки экрана */ r.h.al=0; /* код очистки экрана */ r.h.ch=0; /* начальная строка */ r.h.cl=0; /* начальная колонка */ r.h.dh=24; /* конечная строка */ r.h.dl=79; /* конечная колонка */ r.h.bh=7; /* очистка пустой строки */ Глава I -- 26 -- int86(0x10,&r,&r); } Вводите эту программу в ваш компьютер и запускаете ее. В ходе ее выполнения каждое меню будет высвечено и исчезнет. (В этой программе все ответы теряются, но реальное применение будет, конечно, их обрабатывать.) Даже если ваш компьютер очень быстрый, вы возможно заметите, что исчезновение и появление меню требуют определенной задержки. Единственный путь решения этой проблемы - читать и писать символы прямо в видео память, что и обсуждается в следующем разделе. Еще раз отметим, что единственное важное достоинство использование BIOS в том, что такие меню работают на любом компьютере, который поддерживает BIOS, совместимый с IBM, даже если компьютер не 100% совместимый. Глава I -- 27 -- Прямой доступ к видео памяти ----------------------------------------------------------------- Для создания меню, которые действительно "исчезают" вы должны миновать вызовы функций BIOS и прямо обращаться к видео памяти. Это позволяет высвечивать символы с молниеносной быстротой. При прямой записи и чтении из видео памяти вы можете использовать исчезающие меню в реальном времени! Чтение и запись в видео память требует использования ДАЛЬНИХ указателей. Если ваш компилятор не поддерживает дальних указателей, то вы не имеете прямого доступа к видео памяти. Дальние указатели могут быть поддерживаемы транслятором Си одним из двух способов. Первый - использование ключевого слова far, используемого в большинстве компиляторов. Они позволяют определять указатель, как дальний. Другой способ - использование большой модели памяти, в которой все указатели по умолчанию дальние. Программы, используемые в этой главе используют описатель far. Если вы хотите, вы можете просто удалить его и скомпилировать программу, используя транслятор с большой моделью памяти. Глава I -- 28 -- Определение расположения видео памяти ----------------------------------------------------------------- Одноцветный адаптер использует для видео памяти адрес B0000000H, a все остальные - В8000000Н. Для того, чтобы программы с меню работали правильно с каждым адаптером, они должны знать, какой адаптер имеет система. К счастью, для этого существует простой способ. Прерывание BIOS 16, функция 15 возвращает текущий видео режим. Как упоминалось раньше, программы, разработанные в этой главе, требуют режима 2, 3 или 7. Адаптеры CGA и EGA могут использовать режим 2 и 3, но не режим 7. Только одноцветный адаптер использует этот режим. Таким образом, если текущий видео режим 7, то используется одноцветный адаптер, в остальных случаях это EGA или CGA. Для наших задач, в текстовом режиме EGA и CGA одинаковы и поэтому все равно, какой из адаптеров у системы. Таким образом функция popup() должна поверить какой из адаптеров у системы и присвоить глобальной переменной указатель на соответствующий адрес. Этот фрагмент программы позволяет сделать это. 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; Функция video_mode() возвращает текущий видео режим, и переменную vid_mem, объявленную везде как char far. Глава I -- 29 -- Изменение save_video() и restore_video() ----------------------------------------------------------------- Kaк только переменной vid_mem присвоен соответствующий адрес, появляется простой способ использовать ее для чтения и записи символов в видео память. Запомните, видео память требует двух байтов для каждого символа, один для символа, а другой для атрибута. Из-за того, что символьный байт первый, а атрибутный - второй, то каждой строке экрана требуется 160 байт. Для того, чтобы определить адрес отдельного символа вы должны использовать формулу: адрес = адрес_адаптера + X*160 + Y*2 Функции save_video() и restore_video() при использовании прямого доступа к видео памяти выглядят следующим образом. void save_video(startx,endx,starty,endy,buf_ptr) int startx,endx,starty,endy; unsigned int *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 + (j*160) + i*2; /* вычисляем адрес */ *buf_ptr++ = *t++; /* чтение символа */ *buf_ptr++ = *t; /* чтение атрибута */ *(t-1) = ' '; /* очистка окна */ } } /* восстановление части экрана */ void restore_video(startx,endx,starty,endy,buf_ptr) int startx,endx,starty,endy; unsigned int *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 += (j*160) + i*2; /* вычисляем адрес */ *v++ = *buf_ptr++; /* запись символа */ *v = *buf_ptr++; /* запись атрибута */ } } Как вы видете, символы и атрибуты записываются или читаются Глава I -- 30 -- с помощью использования указателей на видео память. Другие функции, которые читают и записывают символы преобразуются подобным образом. Если весь доступ к дисплею делать прямым, требуется одна новая функция (показанная здесь). Функция write_char() записывает один символ в определенную позицию экрана с определенным атрибутом. /* запись символа с определенным аттрибутом */ void write_char(x,y,ch,attrib) int x,y; char ch; int attrib; { register int i; char far *v; v=vid_mem; v += (x*160) +y*2; *v++ = ch; /* запись символа */ *v = attrib; /* запись атрибута */ } Полная версия исчезающих меню с прямым доступом приведена здесь с тем же простым тестовым примером. Введите его в в свой компьютер и сравните по производительности с версией, использующей BIOS. Как вы увидете, разница потрясающая. Кажется, что меню появляются и исчезают мгновенно. /* Программа исчезающих меню для текстового режима с использованием прямого доступа к видео памяти */ #include "stdio.h" #include "dos.h" #include "stdlib.h" #define BORDER 1 #define ESC 27 #define REV_VID 0x70 #define NORM_VID 7 void save_video(),restore_video(); void goto_xy(),cls(),write_video(); void display_menu(),draw_border(); char far *vid_mem; char *fruit[] = { "Яблоко", "Апельсин", "Груша", "Грейпфрут", "Малина", "Клубника" }; Глава I -- 31 -- char *color[]={ "Красный", "Желтый", "Оранжевый", "Зеленый" }; char *apple_type[] = { "Красный деликатес", "Джонатан", "Белый налив", "Антоновка" }; main() { int i; cls(); goto_xy(0,0); for(i=0;i<25;i++) printf("Это тест исчезающего меню\n"); popup(fruit,"яагрмк",6,1,3,BORDER); popup(color,"кжоз",4,5,10,BORDER); popup(apple_type,"кдба",4,10,18,BORDER); } /* вывести исчезающее меню и вернуть выбор возвращает -2, если меню не может быть создано возвращает -1, если пользователь нажал клавишу ESC в остальных случаях она возвращает номер выбранного режима, начиная с 0 */ int popup(menu,keys,count,x,y,border) char *menu[]; /* текст меню */ char *keys; /* горячие клавиши */ int count; /* число режимов */ int x,y; /* координаты левого верхнего угла */ int border; /* если 0 то без рамки */ { register int i,len; int endx endy choice; unsigned char *p; if((x>24)||(x<0)||(y>79)||(y<0)) { printf(" выход за пределы экрана"); return -2; } /* вычисление размеров */ len=0; for(i=0;i<count;i++) if(strlen(menu[i]) > len) len=strlen(menu[i]); endy=len+2+y; endx=count+1+x; Глава I -- 32 -- if((endx+1>24) || (endy+1>79)) { printf(" выход за пределы экрана"); return -2; } 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; /* размещение памяти для видео буфера */ p=(unsigned int *)malloc((endx-x+1)*(endy-y+1)); if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */ /* сохранение части экрана */ void save_video(x,endx+1,y,endy+1,p); if(border) draw_border(x,y,endx,endy); /* высвечивание меню на своем месте */ void display_menu(menu,x,y,count); /* ввести выбор пользователя */ choice=get_resp(x,y,count,menu,keys) /* восстановление части экрана */ void restore_video(x,endx+1,y,endy+1,p); free(p); return choice; } /* высвечивание меню на своем месте */ void display_menu(menu,x,y,count) char *menu[]; int x,y,count; { register int i; for(i=0;i<count;i++,x++) { write_string(x,y,menu[i],NORM_VID); } } void draw_border(startx,starty,endx,endy) int startx,starty,endx,endy; { register int i; char far *v,far *t; v=vid_mem; t=v; Глава I -- 33 -- for(i=startx+1;i<endx;i++) { v += (i*160) + starty*2; *v++ = 179; *v = NORM_VID; v=t; v += (i*160) + endy*2; *v++ = 179; *v = NORM_VID; v=t; } for(i=starty+1;i<endy;i++) { v += (startx*160) + i*2; *v++ = 196; *v = NORM_VID; v=t; v += (endx*160) + i*2; *v++ = 196; *v = NORM_VID; v=t; } write_char(startx,starty,218,NORM_VID); write_char(startx,endy ,191,NORM_VID); write_char(endx ,starty,192,NORM_VID); write_char(endx ,endy ,217,NORM_VID); goto_xy(startx,endy ); putchar(191); goto_xy(endx ,starty); putchar(192); goto_xy(endx ,endy ); putchar(217); } /* ввести выбор пользователя */ get_resp(x,y,count,menu,keys) int x,y,count; char *menu[]; char *keys; { union inkey { char ch[2]; int i; } c; int arrow_choice=0,key_choice; y++; /* осветить первый выбор */ goto_xy(x,y); write_string(x,y,menu[0],REV_VID); for(;;) { while(!bioskey(1)); /* ждать нажатия */ c.i=bioskey(0); /* вернуть выбор в номальный режим */ goto_xy(arrow_choice,y); Глава I -- 34 -- write_string(x+arrow_choice,y, menu[arrow_choice],norm_vid); if(c.ch[0]) { /* обычная клавиша */ key_choice= is_in(keys,tolower(c.ch[0])); if(key_choice) return key_choice-1; switch(c.ch[0]) { case '\r' : return arrow_choice; case ' ' : arrow_choice++; break; case ESC : return -1; /* выйти */ } } else { /* специальная клавиша */ switch(c.ch[1]) { case 72 : arrow_choice--; /* стрелка вниз */ break; case 80 : arrow_choice++; /* стрелка вверх */ break; } } if(arrow_choice==count) arrow_choice=0; if(arrow_choice<0) arrow_choice=count-1; /* подсветить выбранную опцию */ goto_xy(x+arrow_choice,y); write_string(x+arrow_choice,y,menu[arrow_choice],REV_VID); } } /* вывод строки с определенным атрибутом */ void write_string(x,y,p,attrib) int x,y; char *p; int attrib; { register int i,j; char far *v; v=vid_mem; v += (x*160) + y*2; for(i=y; *p; i++) { *v++ =*p++; /* запись символа */ *v++ =attrib; /* запись атрибута */ } } /* запись символа с определенным аттрибутом */ void write_char(x,y,ch,attrib) int x,y; char ch; int attrib; { register int i; char far *v; Глава I -- 35 -- v=vid_mem; v += (x*160) +y*2; *v++ = ch; /* запись символа */ *v = attrib; /* запись атрибута */ } /* сохранение части экрана с использованием прямого доступа к видео памяти */ void save_video(startx,endx,starty,endy,buf_ptr) int startx,endx,starty,endy; unsigned int *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 + (j*160) + i*2; /* вычисляем адрес */ *buf_ptr++ = *t++; /* чтение символа */ *buf_ptr++ = *t; /* чтение атрибута */ *(t-1) = ' '; /* очистка окна */ } } /* восстановление части экрана с использованием прямого доступа к видео памяти */ void restore_video(startx,endx,starty,endy,buf_ptr) int startx,endx,starty,endy; unsigned int *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 += (j*160) + i*2; /* вычисляем адрес */ *v++ = *buf_ptr++; /* запись символа */ *v = *buf_ptr++; /* запись атрибута */ } } /* очистка экрана */ void cls() { union REGS r; r.h.ah=6; /* код прокрутки экрана */ r.h.al=0; /* код очистки экрана */ r.h.ch=0; /* начальная строка */ Глава I -- 36 -- r.h.cl=0; /* начальная колонка */ r.h.dh=24; /* конечная строка */ r.h.dl=79; /* конечная колонка */ r.h.bh=7; /* очистка пустой строки */ int86(0x10,&r,&r); } /* установка курсора в x,y */ void goto_xy(x,y) int x,y; { union REGS r; r.h.ah=2; /* функция установки курсора */ r.h.dl=y; /* координата колонки */ r.h.dh=x; /* координата строки */ r.h.bh=0; /* видео страница */ int86(0x10,&r,&r); } /* запрос текущего видео режима */ video_mode() { union REGS r; r.h.ah = 15; /* получить режим */ return int86(0x10,&r,&r) & 255; } is_in(s,c) char *s,c; { register int i; for(i=0; *s; i++) if(*s++ == c) return i+1; return 0; } Глава I -- 37 -- Создание иерархических окон ----------------------------------------------------------------- Иерархические окна фундаментально отличаются от простых исчезающих меню тем, что два или более исчезающих меню могут быть активными одновременно. Вообще иерархические меню позволяют пользователю выбирать режимы непосредственно из режимов и используются для поддержки системы меню. В отличие от функции popup(), которая сохраняет экран, высвечивает меню, и восстанавливает экран, функция pulldown(), разработанная в этом разделе только сохраняет экран (если это нужно), высвечивает меню и возвращает выбор пользователя. Восстановление экрана обрабатывается как отдельная задача в любом месте программы. Перед тем как вы сможете создать иерархическое меню, вы должны изменить свое представление о меню. Глава I -- 38 -- Фреймы меню ----------------------------------------------------------------- Центральным понятием для создания иерархического, многоуровневого меню является фрейм меню. В сущности, программы иерархических меню требуют чтобы каждое меню имело свой фрейм ссылок, определенных до того, как программа, которая испольует меню, начала выполняться. Каждое меню активизируется по номеру его фрейма и необходимая информация загружается по мере необходимости для различных функций, поддерживающих меню. Лучший способ для поддержки фреймов меню - создать массив структур, которые содержат информацию, относящуюся к меню. Эта структура определяется так, как описано здесь: struct menu_frame { int startx,endx,starty,endy; unsigned char *p; /* указатель на информацию экрана */ char **menu; /* указатель на строки меню */ int border; /* рамка включено/выключено */ int count; /* число альтернатив */ int astive; /* активно ли меню сейчас */ } frame[MAX_FRAME]; где МAX_FRAME - макроконстанта, которая определяет как много меню вы можете иметь. Только одна дополнительная информация требуется для иерархических меню, которая не нужна для исчезающих меню - флаг активности. Флаг используется в качестве сигнала, что меню уже на экране и предупреждает перезаписывание информации с экрана. Глава I -- 39 -- Создание фрейма меню ----------------------------------------------------------------- Перед использованием меню для этого должен быть создан фрейм. Функция make_menu(), показанная здесь, создает фрейм меню. /* создание фрейма иерархического меню. 1 если фрейм может быть создан в противном случае 0 */ make_menu(num,menu,keys,count,x,y,border) int num; /* номер меню */ char *menu; /* текст меню */ char *keys; /* горячие клавиши */ int count; /* число альтернатив */ int x,y; /* левый верхний угол */ int border; /* рамка */ { register int i,len; int endx,endy,choice,vmode; unsigned char *p; if(num>MAX_FRAME) { printf("Слишком много меню"); return 0; } if((x>24)||(x<0)||(y>79)||(y<0)) { printf(" выход за пределы экрана"); return 0; } /* вычисление размеров */ len=0; for(i=0;i<count;i++) if(strlen(menu[i]) > len) len=strlen(menu[i]); endy=len+2+y; endx=count+1+x; if((endx+1>24) || (endy+1>79)) { printf(" выход за пределы экрана"); return 0; } /* размещение памяти для видео буфера */ p=(unsigned int *)malloc((endx-x+1)*(endy-y+1)); if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */ /* создание фрейма */ frame[num].startx=x; frame[num].endx=endx; frame[num].starty=y; frame[num].endy=endy; frame[num].p = p; frame[num].menu = (char **) menu; frame[num].border = border; frame[num].keys = keys; Глава I -- 40 -- frame[num].count = count; frame[num].active =0; return 1; } Вы вызываете make_menu с теми же аргументами, какие используются в popup() кроме номера меню, который должен быть определен в первом аргументе. Этот номер используется для идентификации меню. Глава I -- 41 -- Функция pulldown() ----------------------------------------------------------------- Функция pulldown() показана здесь: /* Высветить меню и получить выбор возвращает -1, если пользователь нажал клавишу ESC в остальных случаях номер альтернативы, начиная с 0 */ int pulldown(num) int num; /* номер фрейма */ { int vmode,choice; 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; /* узнать активность окна */ if(!frame[num].active) { /* не активно */ save_video(num); frame[num].active= 1; /* установить признак активности */ } if( frame[num].border) draw_worder(num); display_menu(num); /* высветить меню */ return get_resp(num); /* возвратить выбор */ } Для использования pulldown() просто передайте номер того меню, которое вы хотите высветить. Однако вы должны помнить о восстановлении окна, используя restore_video() везде в своей программе. Запомните, что основное отличие иерархических меню в том, что они позволяют двум или более меню оставаться на экране одновременно и быть потенциально активными, по мере того как пользователь выбирает опции. Тем не менее вам не следует восстанавливать экран до того, как процесс выбора полностью завершен. Обратите внимание, что часть экрана, используемая для меню, сохраняется только если признак активности равен 0. Так как иерархическое меню может быть вызвано повторно, экран не должен сохраняться много раз. (Другими словами при повторном вхождении будет сохранено само меню, записанное поверх первоначального содержимого экрана, которое уже сохранено.) Глава I -- 42 -- Восстановление экрана ----------------------------------------------------------------- Как и другие функции поддержки меню, измененная restore_video показанная здесь, преобразована для работы с фреймами. Поэтому функции restore_video() теперь передается только номер меню, что делает интерфейс более очевидным. /* восстановление части экрана */ void restore_video(num) int num; { register int i,j; char far *v, far *t; char *buf_ptr; buf_ptr=frame[num].p; v=vid_mem; t=v; for(i=frame[num].starty;i<frame[num].endy;i++) for(j=frame[num].startx;j<frame[num].endx;j++) { v = t; v += (j*160) + i*2; /* вычисляем адрес */ *v++ = *buf_ptr++; /* запись символа */ *v = *buf_ptr++; /* запись атрибута */ } frame[num].active= 0; } Глава I -- 43 -- Простая программа, использующая процедуру pulldown ----------------------------------------------------------------- Все функции для иерархических меню показаны здесь вместе с простой программой-образцом и их можно прямо вводить в ваш компьютер. /* процедура иерархического меню для текстового режима и простая программа-пример */ #include "stdio.h" #include "dos.h" #include "stdlib.h" #define BORDER 1 #define ESC 27 #define REV_VID 0x70 #define NORM_VID 7 void save_video(),restore_video(); void goto_xy(),cls(),write_video(); void display_menu(),draw_border(); char far *vid_mem; struct menu_frame { int startx,endx,starty,endy; unsigned char *p; /* указатель на информацию экрана */ char **menu; /* указатель на строки меню */ int border; /* рамка включено/выключено */ int count; /* число альтернатив */ int astive; /* активно ли меню сейчас */ } frame[MAX_FRAME]; char *fruit[] = { "Яблоко", "Апельсин", "Груша", "гРейпфрут", "Малина" "Клубника" }; char *color[]={ "Красный", "Желтый", "Оранжевый", "Зеленый" }; char *apple_type[] = { "Красный деликатес", ? "Джонатан", "Белый налив", "Aнтоновка" }; Глава I -- 44 -- char *grape_type[]= { "Конкорд", "кАнадский", "Томпсон", "кРасное пламя" }; main() { int i; cls(); goto_xy(0,0); /* во-первых создадим фреймы меню */ make_menu(0,fruit,"яагрмк",6,5,20,BORDER); make_menu(1,color,"кжоз",4,9,28,BORDER); make_menu(2,apple_type,"кдба",4,12,32,BORDER); make_menu(3,grape_type,"катр",4,9,10,BORDER); printf("Выберите фрукт:"); pd_driver(); /* запуск системы меню */ } void pd_driver() { int choice1,choice2,selection; /* активизация окон по мере надобности */ while((choice1=pulldown(0)) != -1) { switch ( choice1 ) { case 0 : /* яблоко */ while((choice2=pulldown(1)) != -1) { if(choice2 ==0) selection=pulldown(2);/*красное яблоко */ restore_video(2); } restore_video(1); break; case 1 : case 2 : goto_xy(1,0); printf("неправильный выбор"); break; case 3 : /* грейпфрут */ selection=pulldown(3); restore_video(3); break; case 4 : case 5 : goto_xy(1,0); printf("неправильный выбор"); break; } Глава I -- 45 -- } restore_video(0); } /* Высветить меню и получить выбор */ int pulldown(num) int num; /* номер фрейма */ { int vmode,choice; 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; /* узнать активнсть окна */ if(!frame[num].active) { /* не активно */ save_video(num); frame[num].active= 1; /* установить признак активности */ } if( frame[num].border) draw_worder(num); display_menu(num); /* высветить меню */ return get_resp(num); /* возвратить выбор */ } /* создание фрейма иерархического меню. 1 если фрейм может быть создан в противном случае 0 */ make_menu(num,menu,keys,count,x,y,border) int num; /* номер меню */ char *menu; /* текст меню */ char *keys; /* горячие клавиши */ int count; /* число альтернатив */ int x,y; /* левый верхний угол */ int border; /* рамка */ { register int i,len; int endx,endy,choice,vmode; unsigned char *p; if(num>MAX_FRAME) { printf("Слишком много меню"); return 0; } if((x>24)||(x<0)||(y>79)||(y<0)) { printf(" выход за пределы экрана"); Глава I -- 46 -- return 0; } /* вычисление размеров */ len=0; for(i=0;i<count;i++) if(strlen(menu[i]) > len) len=strlen(menu[i]); endy=len+2+y; endx=count+1+x; if((endx+1>24) || (endy+1>79)) { printf(" выход за пределы экрана"); return 0; } /* размещение памяти для видео буфера */ p=(unsigned int *)malloc((endx-x+1)*(endy-y+1)); if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */ /* создание фрейма */ frame[num].startx=x; frame[num].endx=endx; frame[num].starty=y; frame[num].endy=endy; frame[num].p = p; frame[num].menu = (char **) menu; frame[num].border = border; frame[num].keys = keys; frame[num].count = count; frame[num].active =0; return 1; } /* высвечивание меню на своем месте */ void display_menu(num) int num; { char **m; register int i, x; x = frame[num].startx+1; m = frame[num].menu; for(i=0;i<frame[num].count;i++,x++) { write_string(x,frame[num].starty+1,m[i],NORM_VID); } } void draw_border(num) int num ; { register int i; char far *v,far *t; v=vid_mem; Глава I -- 47 -- t=v; for(i=frame[num].startx+1;i<frame[num].endx;i++) { v += (i*160) + frame[num].starty*2; *v++ = 179; *v = NORM_VID; v=t; v += (i*160) + frame[num].endy*2; *v++ = 179; *v = NORM_VID; v=t; } for(i=frame[num].starty+1;i<frame[num].endy;i++) { v += (frame[num].startx*160) + i*2; *v++ = 196; *v = NORM_VID; v=t; v += (frame[num].endx*160) + i*2; *v++ = 196; *v = NORM_VID; v=t; } write_char(frame[num].startx,frame[num].starty,218,NORM_VID); write_char(frame[num].startx,frame[num].endy ,191,NORM_VID); write_char(frame[num].endx ,frame[num].starty,192,NORM_VID); write_char(frame[num].endx ,frame[num].endy ,217,NORM_VID); goto_xy(frame[num].startx,frame[num].endy ); putchar(191); goto_xy(frame[num].endx ,frame[num].starty); putchar(192); goto_xy(frame[num].endx ,frame[num].endy ); putchar(217); } /* ввести выбор пользователя */ get_resp(num) int num; { union inkey { char ch[2]; int i; } c; int arrow_choice=0,key_choice; int x,y; x=frame[num].startx+1; y=frame[num].starty+1; /* осветить первый выбор */ goto_xy(x,y); write_string(x,y,frame[num].menu[0],REV_VID); for(;;) { while(!bioskey(1)); /* ждать нажатия */ c.i=bioskey(0); Глава I -- 48 -- /* вернуть выбор в номальный режим */ goto_xy(arrow_choice,y); write_string(x+arrow_choice,y, frame[num].menu[arrow_choice],norm_vid); if(c.ch[0]) { /* обычная клавиша */ key_choice= is_in(frame[num].keys,tolower(c.ch[0])); if(key_choice) return key_choice-1; switch(c.ch[0]) { case '\r' : return arrow_choice; case ' ' : arrow_choice++; break; case ESC : return -1; /* выйти */ } } else { /* специальная клавиша */ switch(c.ch[1]) { case 72 : arrow_choice--; /* стрелка вниз */ break; case 80 : arrow_choice++; /* стрелка вверх */ break; } } if(arrow_choice==frame[num].count) arrow_choice=0; if(arrow_choice<0) arrow_choice=frame[num].count-1; /* подсветить выбранную опцию */ goto_xy(x+arrow_choice,y); write_string(x+arrow_choice,y, frame[num].menu[arrow_choice],REV_VID); } } /* вывод строки с определенным атрибутом */ void write_string(x,y,p,attrib) int x,y; char *p; int attrib; { register int i,j; char far *v; v=vid_mem; v += (x*160) + y*2; for(i=y; *p; i++) { *v++ =*p++; /* запись символа */ *v++ =attrib; /* запись атрибута */ } } /* запись символа с определенным аттрибутом */ void write_char(x,y,ch,attrib) int x,y; char ch; int attrib; Глава I -- 49 -- { register int i; char far *v; v=vid_mem; v += (x*160) +y*2; *v++ = ch; /* запись символа */ *v = attrib; /* запись атрибута */ } /* сохранение части экрана с использованием прямого доступа к видео памяти */ void save_video(num) int num; { register int i,j; char far *v, far *t; char *buf_ptr; buf_ptr=frame[num].p; v=vid_mem; for(i=frame[num].starty;i<frame[num].endy;i++) for(j=frame[num].startx;j<frame[num].endx;j++) { t = (v + (j*160) + i*2); /* вычисляем адрес */ *buf_ptr++ = *t++; /* чтение символа */ *buf_ptr++ = *t; /* чтение атрибута */ *(t-1) = ' '; /* очистка окна */ } } /* восстановление части экрана */ void restore_video(num) int num; { register int i,j; char far *v, far *t; char *buf_ptr; buf_ptr=frame[num].p; v=vid_mem; t=v; for(i=frame[num].starty;i<frame[num].endy;i++) for(j=frame[num].startx;j<frame[num].endx;j++) { v = t; v += (j*160) + i*2; /* вычисляем адрес */ *v++ = *buf_ptr++; /* запись символа */ *v = *buf_ptr++; /* запись атрибута */ } frame[num].active= 0; } /* очистка экрана */ void cls() { Глава I -- 50 -- union REGS r; r.h.ah=6; /* код прокрутки экрана */ r.h.al=0; /* код очистки экрана */ r.h.ch=0; /* начальная строка */ r.h.cl=0; /* начальная колонка */ r.h.dh=24;/* конечная строка */ r.h.dl=79;/* конечная колонка */ r.h.bh=7; /* очистка пустой строки */ int86(0x10,&r,&r); } /* установка курсора в x,y */ void goto_xy(x,y) int x,y; { union REGS r; r.h.ah=2; /* функция установки курсора */ r.h.dl=y; /* координата колонки */ r.h.dh=x; /* координата строки */ r.h.bh=0; /* видео страница */ int86(0x10,&r,&r); } /* запрос текущего видео режима */ video_mode() { union REGS r; r.h.ah = 15; /* получить режим */ return int86(0x10,&r,&r) & 255; } is_in(s,c) char *s,c; { register int i; for(i=0; *s; i++) if(*s++ == c) return i+1; return 0; } В этом примере, если пользователь выберет "Яблоко", то он или она будет запрошен о цвете яблока; если выбран "Красный" цвет, то будет высвечен список красных сортов яблок. Если же будет выбран грейпфрут то пользователь будет запрошен о желаемом типе. Меню для выбора яблок показано на рисунке. Глава I -- 51 -- --------------------------------------------------------¬ ¦ выберите фрукт: ¦ ¦ ----------¬ ¦ ¦ ¦Яблоко ¦ ¦ ¦ ¦Апельсин ¦ ¦ ¦ ¦Груша ¦ ¦ ¦ ¦гРейпфрут¦ ¦ ¦ ¦Малин----+---¬ ¦ ¦ ¦Клубн¦Красный¦ ¦ ¦ L-----+Желтый ¦ ¦ ¦ ¦Ора----+-------------¬ ¦ ¦ ¦Зел¦Красный деликатес¦ ¦ ¦ L---¦Д*ж*о*н*а*т*а*н**¦ ¦ ¦ ¦Белый налив ¦ ¦ ¦ ¦Антоновка ¦ ¦ ¦ L------------------ ¦ ¦ ¦ L-------------------------------------------------------- Посмотрите внимательно на функцию pd_driver(), которая следует за главной функцией main(). При использовании иерархических меню вы должны создавать функцию, которая управляет системой меню. Основа стратегии управляющей функции должна быть аналогична функции pd_driver() из этого примера. Не забывайте, что эта простая программа только иллюстрирует как активизировать меню. Ваша реальная прикладная программа будет обрабатывать выбранные режимы более разумным образом. Запомните, что для использования иерархических меню нужна следующая последовательность действий. 1. Создать меню, используя make_menu(). 2. Активизировать меню, используя pulldown(). 3. Восстановить экран, используя restore_video(), при выходе из каждого меню. Добавочные опции ----------------------------------------------------------------- Процедуры меню, разработанные в этой главе, подходят в большинстве ситуаций. Однако, вы можете по желанию добавить некоторые из следующих возможностей: # Высвечивание заголовка меню # Линейное меню (все опции на одной строке) # Использование разных цветов для разных меню
Оставить комментарий
Комментарии
1.
+1 / -1
25 февраля 2008, 23:32:14
не кажется, что несколько устарело?
2.
+1 / -3
25 февраля 2008, 23:32:13
не кажется, что несколько устарело?
3.
9 ноября 2005, 21:25:20
Не можете ли вы отправить на мыло книгу "Язык Си для профессиональных программистов" или дать ссылку.