====== Построение меню в программах ====== Авторы: (c) [[http://forum.sources.ru/index.php?showuser=5319|Romtek]] и [[http://forum.sources.ru/index.php?showuser=10362|volvo877]]. ===== Вступление ===== Часто приходится сталкиваться с программами, в которых студенты пытались построить нечто похожее на меню, и это было нечто... (слов не подобрать :-) ). Построение меню занимало б**о**льшую часть программы и алгоритм терялся в недрах гигантских программ. Программа состоит из набора алгоритмов, данных и интерфейса. Но ошибаются те, которые считают, что интерфейс главнее. Это большое заблужение! Потому, что **основную задачу выполняют именно __алгоритмы__**, а не украшательства. Интерфейс же совсем необязательная часть. Более того, **неприемлемо смешивание кода алгоритмов и интерфейса**: в этом случае очень затрудняется поиск ошибок и нарушается строение программы. ===== Построение меню ===== Поэтому советую взять за основу эту программу, которая строит меню (горизонтальное или вертикальное) с управлением клавиш стрелок.\\ Выбор пункта меню ведёт к отдельной подзадаче, каждая из которых оформлена в виде процедуры. В них вы будете выполнять действия, предусмотренные вашей программой. Я привёл пример для операций над числами. uses crt; Type MenuType = (Vertical, Horizontal); const width = 9; { Максимальная длина элементов (букв) меню } Items1 = 5; { Количество элементов меню 1 } optText1: array [0..Items1-1] of string = ('Сложение', 'Вычитание', 'Деление', 'Умножение', 'Выход'); Items2 = 3; { Количество элементов меню 2 } optText2: array [0..Items2-1] of string = ( 'item 1', 'item 2', 'item 3' ); optNormal = LightGray; { цвет невыделенных элементов меню } optSelected = Yellow; { цвет выделенных элементов меню } var X, Y, selected, { Индекс элемента, который будет подсвечиваться в начале программы } row: integer; _style: menuType; { Указывает на тип меню: вертикальный (Vertical) или горизонтальный (Horizontal) } { Эта процедура используется процедурой MenuOption для построения меню } procedure MakeMenu (optText: array of string; MaxItems: integer); var i, _X: byte; begin Y := row; _X := X; for i := 0 to MaxItems-1 do begin GoToXY (_X, Y); if i = selected then TextColor (optSelected) else TextColor (optNormal); write (optText[i]); If _style = Horizontal Then inc (_X, width + 1) Else inc (Y, 2); end; end; { выбираем нужный элемент меню этой функцией } function MenuOption (optText: array of string; MaxItems: integer): byte; var ch: char; begin selected := 0; If _style = Vertical then begin X := (80 - width) div 2; row := (25 - MaxItems) div 2; End Else Begin X := (80 - MaxItems * width) div 2; row := 2; { строчка, в которой будет находиться горизонтальное меню } GotoXY(1, row); ClrEol; { ... а для горизонтального - только строку где будет меню. } End; repeat MakeMenu (optText, MaxItems); ch := readkey; if ch = #0 then ch := readkey; case ch of #80, #77: {Down/Right} begin inc (Selected); if Selected = MaxItems then Selected := 0; MakeMenu (optText, MaxItems); end; #72, #75: {Up/Left} begin dec (Selected); if Selected < 0 then Selected := MaxItems-1; MakeMenu (optText, MaxItems); end; end; until ch = #13; {Enter} MenuOption := Selected + 1; TextColor (optNormal); If _style = Vertical Then clrscr; end; procedure Add; begin end; procedure Subtract; begin end; procedure Divide; begin end; procedure Multiply; begin end; var Option: byte; { номер выбранного пункта } begin clrscr; _style := Vertical; { вертикальное меню } Option := MenuOption (optText1, Items1); case option of 1: Add; { сложить числа } 2: Subtract; { вычесть числа } 3: Divide; { поделить числа } 4: Multiply; { умножить числа } 5: exit; { Выйти из программы } end; { вывести (если нужно) номер выбранного пункта } writeln ('Номер пункта: ', option); readln; _style := Horizontal; { горизонтальное меню } Option := MenuOption (optText2, Items2); end. ---- Бывает, что есть необходимость добавить пояснительный текст рядом с меню. В подавляющем количестве студенты пишут тогда следующий код: begin GotoXY (1,2); writeln ('Для управления пользуйтесь клавишами стрелок:'); GotoXY (1,4); writeln ('1 - сделать то-то...'); writeln ('2 - сделать то-то...'); writeln ('3 - сделать то-то...'); writeln ('4- сделать то-то...'); writeln ('5 - сделать то-то...'); end; Особенно неудобно в программах с большим текстом кода. В нём можно просто утонуть. Это дико неудобно возиться со множеством writeln, которые только отвлекают от сути, в то время как можно загрузить текст из текстового файла, заранее отредактированного в любом текстовом редакторе такой текст: Для управления пользуйтесь клавишами стрелок: 1 - сделать то-то... 2 - сделать то-то... 3 - сделать то-то... 4 - сделать то-то... 5 - сделать то-то... Не правда ли, удобнее? Теперь рассмотрим процедуру, которая загрузит текст из отдельного текстового файла. { Эта процедура используется процедурой TextWindow для загрузки текста из отдельного текстового файла. } procedure LoadText (fname: string); var F: text; str: string; begin Assign (F, fname); {$I-} Reset (F); {$I+} if IOresult = 0 then while Not EOF (F) do begin readln (F, str); writeln (str); end; Close (F); end; Эта процедура выведет текст в заданном регионе (окне) из текстового файла fn. { Выводит текст в заданном регионе из текстового файла fn } procedure TextWindow (x1, y1, x2, y2: integer; fn: string); const TextColor = Cyan; { цвет фона региона } begin Window (x1, y1, x2, y2); TextBackground (TextColor); ClrScr; LoadText (fn); Window (1, 1, 80, 25); TextBackground (Black); end; Таким образом, задав нужные вам параметры, вы увидите текст рядом с меню. Например, TextWindow (2, 2, 78, 7, 'strelki.txt'); _style := Vertical; { вертикальное меню } Option := MenuOption (optText1, Items1); Здесь текст выводится в окне с границами 2, 2, 78, 7 из файла 'strelki.txt'. ===== Вариант с использованием модуля ===== uses crt, menu_u; const width1 = 9; { Максимальная длина элементов (букв) меню } Items1 = 5; { Максимальное количество элементов меню 1 } optText1: array [0..Items1-1] of string = ('Сложение', 'Вычитание', 'Деление', 'Умножение', 'Выход'); width2 = 6; { Максимальная длина элементов (букв) меню } Items2 = 3; { Максимальное количество элементов меню 2 } optText2: array [0..Items2-1] of string = ( 'item 1', 'item 2', 'item 3' ); procedure Add; begin end; procedure Subtract; begin end; procedure Divide; begin end; procedure Multiply; begin end; var Option: byte; { номер выбранного пункта } begin clrscr; TextWindow (2, 2, 78, 7, 'menu.txt'); _style := Vertical; { вертикальное меню } Option := MenuOption (optText1, Items1, width1); case option of 1: Add; { сложить числа } 2: Subtract; { вычесть числа } 3: Divide; { поделить числа } 4: Multiply; { умножить числа } 5: exit; { Выйти из программы } end; { вывести (если нужно) номер выбранного пункта } writeln ('Номер пункта: ', option); readln; _style := Horizontal; { горизонтальное меню } Option := MenuOption (optText2, Items2, width2); end. ===== Описание программы ===== За построение меню отвечает процедура //MakeMenu// - её вызывает функция //MenuOption//, которая возвращает результат в виде индекса выбранного пункта. Стиль меню определяется значением //_style (Vertical/Horizontal)// - то, как меню будет располагаться на экране. _style := Vertical; { вертикальное меню } Цвет нормальных элементов определяется константой //optNormal//, а цвет выделенных - //optSelected//. Остаётся всего-лишь определить названия элементов меню в //optText//, константу //Items// (количество элементов меню), константу //width// (максимальная длина букв самой длинной строки меню) и меню готово! Для вывода пояснительного текста рядом с меню можно воспользоваться процедурой TextWindow. Пять параметров означают координаты окна и название файла, из которого загрузится описание для меню. == Примечание: == Процедура TextWindow должна находиться **перед** построением меню. Иначе текста не будет видно! ==== Снимок экрана ==== {{:pascal:menudemo.png|Меню программы}} ==== Скачать ==== {{:pascal:menudemo.zip}} - Menudemo.pas - программа построения меню без использования модуля. - Menu_t.pas - программа построения меню с модулем Menu_u.\\ Menu_u.pas - модуль с основными функциями и процедурами для построения меню. Вариант 2 более предпочтителен, т.к. за счёт использования модуля гораздо легче ориентироваться в программе и отслеживать ошибки.