Глава 5:Концепции построения интерфейса

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

Однажды я имел разговор с потенциальным клиентом, который спросил меня, сколько времени потребуется, чтобы написать программу обработки счетов. Я ответил, что это обойдется примерно между 100 и 1 000 000 долл., в зависимости от того, что ему нужно. На это мне было сказано, раз я не знаю, что из себя представляет подобная система, вряд ли у меня достаточно опыта для такой работы.


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

В этой главе я предлагаю несколько подходов к проектированию программного обеспечения. Начнем с простого плоского файла, затем перейдем к более сложной системе с несколькими главными файлами и дочерним файлом с данными по счетам, после чего рассмотрим сложное приложение для медицинского учреждения, где несколько дочерних файлов связаны с каждым из родительских файлов. И в конце я покажу как можно имитировать хорошо проработанный интерфейс, используемый Novell NetWare и PCanywhere.



Рассматриваемые приложения построены на базе цикла обработки событий. Они прекрасно выполняют то, что от них требуется и написаны на чистом FoxPro 2.0. Если событийно-управляемая модель вам дается слишком трудно или вы не можете понять ее сути, используйте предлагаемые модели, проверенные временем.

Модель с использованием плоского файла

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

В Листинге 5-1( Простое меню для системы работы с плоским файлом ) приводится код, типичный для результатов работы генераторов приложений типа Tempest (MicroSearch Associates). Меню определяется в начале программы. Выбор опции меню вызывает процедуру, принимающую решение о дальнейших действиях на основании передаваемого ей значения BAR(). Строки программы, содержащие команду ON PAD, определяют внешний вид и функциональность меню.

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

Модель STAGE

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

Теперь меню переместилось в нижнюю часть экрана, так как выпадающие меню мешают видеть данные.

Интерфейс, включащий верхнюю строку меню и нижнее мини-меню, типичен для коммерческих приложений. Листинг 5-2 ( Организация меню, подобного примеру STAGE ) содержит код для меню такого типа. В листинге не приведены программы ввода данных и BROWSE.

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

Множественные дочерние файлы

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

Листинг 5-3( Программа для сбора данных о пациентах ) позволяет получить представление о том как можно вывести максимальное количество информации на минимальном числе экранов. Такой подход позволяет оператору определить статус пациента - критически важную медицинскую информацию также как и платежи и текущее состояние счета - одним взглядо.

Если вы выберете опцию Allergies/Show, то будет выведено окно BROWSE с восемью-девятью строками данных. Если вы строите индекс по выражению вроде:

PATIENDID+DTOC(DATE,1)

то можно использовать опцию Descending для вывода группы записей за самый последний период времени без дополнительного программирования.

Выбор опции EDIT приводит вас в окно BROWSE с возможностью поиска и вывода в отдельное окно записи, которая в данный момент может быть не видна на экране. Использованная в программе функция редактирования представляет собой скелет, удобный для создания прототипов, но требующий некоторого наполнения в реальном приложении.

Событийно-управляемый интерфейс

FoxPro поставляется с двумя примерами интересного подхода к построению приложений, работающих с несколькими файлами. Примеры называются EX1 и EX2 и расположены в каталоге GOODIES\FNDATION. Обе программы позволяют щелчком мыши на том или ином окне активизировать связанное с этим окном приложение. Кроме того, вы можете использовать меню. Проанализировав код, можно понять как работают эти приложения. Алан Гривер написал прекрасную книгу о том как писать подобного рода программы, так что это не секрет и вы, конечно, можете научиться такой технике.

Я использую Foundation READ с тех пор, как получил на тестирование бета-версию FoxPro 2.0. Я считаю, что приложения на базе Foundation READ работают медленно, сложны и дороги в программировании, кроме того их трудно отлаживать. В результате я использую их только в том случае, когда заказчик конкретно указывает на необходимость их применения. Я показываю как все это выглядит, объясняю за и против и прикидываю затраты и производительность с использованием альтернативных моделей. До сих пор ни один из заказчиков не стал настаивать на их применении. Я использую новую технологию для домашнего применения, но не на продажу.

Я пишу эту книгу для широкой аудитории. У меня есть несколько очень талантливых друзей, испытывающих стойкую неприязнь к генераторам приложений. Они могут не понимать, что когда ты ищешь хоть что-нибудь, что работает, тонкости передовой технологии могут ускользнуть от внимания. Я учился языку xBASE по коду, создаваемому генераторами, если конкретно, ViewGen Луиса Кастро (предшественника STAGE). Сейчас я не пользуюсь генераторами, но доволен, что применял их в свое время. В эту главу я не включаю примеров по Foundation READ.

Интерфейс в стиле С-Worthy (Nowell NetWare)

("C-Worthy Library" - библиотека функций построения интерфейса, использованная при создании интерфейса Nowell NetWare 386, - прим. пер.).

В мае 1990 я написал статью под названием "Что скрывает интерфейс", в которой дал программу, имитирующую элегантный интерфейс Novell NetWare, позднее также использованный в PCanywhere. Для случая, когда у вас имеется множество связанных маленьких файлов по несколько записей в каждом, такой подход удобнее традиционного, используемого для системы с плоским файлом.

Если, например, ваши данные описываются схемой, приведенной в таблице 5-1, то приведенные ранее модели будут вполне пригодны. В том же случае, когда ваши данные больше похожи на те, что приведены в таблице 5-2, т. е. имеют выраженную сетевую структуру, то более подходящим оказывается Novell-подобный интерфейс.

Предлагаемая программа ( Листинг 5-4( Программа-календарь с интерфейсом в стиле C-Worthy )) представляет собой ежедневник. Каждому дню соответствует запись в основном плане. При нажатии Enter выбранный день раскрывается на компоненты. И, наконец, вы можете ввести дополнительную информацию в текстовое поле, появляющееся только на самом нижнем уровне детализации. Предлагаемая модель не имеет ограничений по глубине вложения. Я использовал трюк с вызовом макрокоманд для организации выхода из меню и одновременной передачи информации о том, как произошел выход. В некоторых случаях, когда FoxPro не позволяет поместить в буфер клавиатуры последовательность, включающую управляющий символ, я использую PLAY MACRO. Это не требует много времени и позволяет обеспечить необходимый контроль.

Заключение

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

Листинг 5-4

.
* Program-ID....: Calendar.PRG
* Purpose.......: Demonstrates C-Worthy interface in FoxPro 2.0
*
* For this calendar program, we'll use a popup of unique dates on file.
* The ENTER, DEL and INS keys will be redefined to issue the following:
*
* INS - play macro alt-a : CHR(13) + [A] : Add
* ENTER - play macro alt_e : CHR(13) + [E] : Select/Edit
* DEL - play macro alt-d : CHR(13) + [D] : Delete
*
* Upon exiting a popup, we SHOW POPUP again, then take action.
* Since any editing can change the popup contents, we deactivate
* within a loop, then ACTIVATE POPUP again. ESC returns an empty
* PROMPT(), which terminates the loop.
* RESTORE MACROS FROM AED

ESCKEY = 12
DateKey = DATE()

TestColor = IsColor()
AtrMain = IIF ( TestColor , [GR+/B] , [W+/N] )
AtrText = IIF ( TestColor , [GR+/B,N/W] , [W+/N] )
AtrConfTxt = IIF ( TestColor , [GR+/W,W+/BR] , [W+/N] )
AtrMsgTxt = IIF ( TestColor , [GR+/B] , [W+/N] )

DO MAINSCRN

DEFINE WINDOW CONFIRM FROM 17, 34 TO 20, 46 DOUBLE ;
COLOR ,GR+/W,GR+/W,GR+/W,GR+/W,W+/BR ;
TITLE [Delete?]

DEFINE WINDOW EVENT FROM 7, 10 TO 19, 68 DOUBLE ;
COLOR ,GR+/W,GR+/W,GR+/W,GR+/W,W+/BR ;
TITLE [ Detail of activity for this event ] ;
FOOTER [ Press F10 to Save, or ESCape to cancel without saving ]

USE CALENDAR

* -- Structure for CALENDAR.DBF
*
* 1 DATE Date 8
* 2 TIME Character 5
* 3 EVENT Character 20
* 4 DESCRIP Character 240

IF .NOT. FILE( [CALENDAR.CDX] )
INDEX ON DATE UNIQUE TAG DATE OF CALENDAR && main calendar
INDEX ON DTOC(DATE)+TIME TAG ALLDATES OF CALENDAR && detail display
INDEX ON DTOC(DATE)+UPPER(EVENT) TAG DATEVENT OF CALENDAR && unique key
INDEX ON UPPER(EVENT) TAG EVENT OF CALENDAR && find by event
ENDIF

SET ORDER TO DATE

DO KeysON

DO WHILE .T.

DO MainScrn

SET ORDER TO DATE
REINDEX
SET FILTER TO Event <> [~]

DEFINE POPUP DATES FROM 5, 8 ;
PROMPT FIELD [ ¦] + LEFT(CDOW(DATE)+[, ]+SPACE(10),10) +;
DTOC(Date) + [ ] ;
COLOR ,W+/B,GR+/B,GR+/B,GR+/B,N/W ;
TITLE [ Active Dates ]

IF RECCOUNT() = 0
ACTIVATE WINDOW CONFIRM
@ (WROWS()-1)/2, 0 SAY PADC([No calendar entries],WCOLS()-1)
DEACTIVATE WINDOW CONFIRM
SHOW WINDOW CONFIRM
WAIT WINDOW [Press INS to start a calendar]
ELSE
ON SELECTION POPUP DATES DEACTIVATE POPUP DATES
ACTIVATE POPUP DATES
ENDIF

* -- Ends up here after any of ENTER/INS/DEL/ESC is pressed. Check PROMPT().

IF LEN(PROMPT()) = 0
EXIT
ENDIF

SHOW POPUP DATES && for background

ONEBYTE = UPPER(CHR(INKEY(0)))

DO CASE

CASE ONEBYTE = [A]

DO AddEvent

CASE ONEBYTE = [E]

DO SeeDates

CASE ONEBYTE = [D]

=Alarm()
WAIT WINDOW [ Can't delete at this level - expand using ENTER];
TIMEOUT 1
LOOP

ENDCASE

HIDE POPUP DATES
DEACTIVATE POPUP DATES
RELEASE POPUP DATES

ENDDO

DO KeysOFF
DO ENDPROG

RETURN



PROCEDURE AddEvent

DO KeysOFF

SCATTER MEMVAR BLANK
M->DATE = DATE

DO EVENTSCR

IF MOD(READKEY(),256) <> ESCKEY
SET ORDER TO EVENT
GO BOTTOM
IF EVENT <> [~]
APPEND BLANK
ENDIF
GATHER MEMVAR
ENDIF

DO KeysON

RETURN



PROCEDURE SeeEvent

DO KeysOFF

SET ORDER TO DatEvent
key = DTOC(CTOD(SUBSTR(PROMPT(),3,8))) + UPPER(SUBSTR(PROMPT(),22,20))

SEEK key
SCATTER MEMVAR

DO EVENTSCR

IF MOD(READKEY(),256) <> ESCKEY
GATHER MEMVAR
ENDIF

HIDE POPUP AllDates

DO KeysON

SET ORDER TO Date

RETURN



PROCEDURE EVENTScr

DO KeysOFF

ACTIVATE WINDOW EVENT

SET COLOR TO &AtrText
CLEAR
@ 0, 0 SAY PADC([Calendar Entry],WCOLS()-1)

@ 2, 10 SAY [ Date:] GET M.Date MESSAGE [Date for this event]
@ 3, 10 SAY [ Time:] GET M.Time MESSAGE [Time for this event] PICT [##:##]
@ 4, 10 SAY [Event:] GET M.Event PICTURE [!] + REPLICATE([X],LEN(Event)-1) ;
MESSAGE [Brief description of this event]
@ 5, 0 TO 5, WCOLS()-1
@ 6, 10 EDIT M.Descrip SIZE 4,40 MESSAGE [Press F10 to exit comment field]

on key label f10 keyboard chr(23)

READ CYCLE COLOR N/W,W+/R

on key label f10

M.Descrip = ALLTRIM(m.descrip)

DEACTIVATE WINDOW EVENT

SET COLOR TO N/G
@ 24, 0 SAY ;
[F1=Help =Choose -+=Accept Ins=Add Del=Delete Esc=End]

DO KeysON

RETURN



PROCEDURE MainScrn

SET COLOR TO &AtrMain
CLEAR
@ 0, 0 TO 4, 79
@ 1, 1 SAY PADC ( [Pinter Consulting] , 78 )
@ 2, 1 SAY PADC ( [Events Calendar] , 78 )
@ 3, 1 SAY PADC ( [Today's date is: ] + DTOC(DATE()) , 78 )

SET COLOR TO &AtrMsgTxt
@ 5, 0, 23, 79 BOX [_________]
SET COLOR TO N/G
@ 24, 0 SAY ;
[F1=Help =Choose -+=Accept Ins=Add Del=Delete Esc=End]

RETURN



PROCEDURE SeeDates
* Displays detailed calendar entries; waits for keystroke

datekey = DTOC(CTOD(SUBSTR(PROMPT(),13,8)))

SET ORDER TO AllDates

SEEK datekey

DEFINE POPUP AllDates FROM 6, 15 ;
COLOR ,W+/B,GR+/B,GR+/B,GR+/B,B/W ;
TITLE [ Scheduled Events ]


Today = Date

I = 1
SCAN WHILE Date = Today
DEFINE BAR I OF AllDates ;
PROMPT [ ¦] + DTOC(Date) + [ ¦ ] + Time + [ ¦ ] + Event + [ ]
I = I + 1
ENDSCAN

ON SELECTION POPUP AllDates DEACTIVATE POPUP AllDates

ACTIVATE POPUP AllDates

IF LASTKEY() <> 27

ONEBYTE = UPPER(CHR(INKEY(0)))

DO CASE

CASE ONEBYTE = [A]

DO AddEvent

CASE ONEBYTE = [E]

DO SeeEvent

CASE ONEBYTE = [D]

=Alarm()
DO DelEvent

ENDCASE

ENDIF

RELEASE POPUP ALLDATES

RETURN



PROCEDURE DelEvent

DO KeysOFF

ACTIVATE WINDOW CONFIRM

SET COLOR TO &AtrConfTxt

Option = 2
@ 0, 3 GET Option PICTURE [@& Yes;No] COLOR GR+/W,W+/BR
READ CYCLE

DEACTIVATE WINDOW CONFIRM

IF Option = 1
SCATTER MEMVAR BLANK
GATHER MEMVAR
REPLACE NEXT 1 EVENT WITH [~]
ENDIF

DO KeysON

RETURN



PROCEDURE ERRMSG
PARAMETERS errnum,linenum,progname,errmsg,badcode

ACTIVATE SCREEN
DO KeysOFF

@ 1, 0 clear TO 8, 50
@ 1, 0 TO 8, 50 DOUBLE
@ 2, 1 SAY [ Error number: ] + str(errnum,4)
@ 3, 1 SAY [ on line: ] + str(linenum,4) + [ of ] + progname
@ 4, 1 TO 4, 49
@ 5, 1 SAY errmsg
@ 6, 1 SAY [ Code causing error: ]
@ 7, 1 SAY ALLTRIM(badcode)

SUSPEND && see what happened....




PROCEDURE EndProg

CLEAR WINDOWS
CLEAR POPUPS
CLEAR MACROS

CLEAR ALL
CLOSE ALL

RETURN && or QUIT


PROCEDURE KeysON

ON KEY LABEL Ins play macro alt_a
ON KEY LABEL Del play macro alt_d
ON KEY LABEL Enter play macro alt_e

RETURN



PROCEDURE KeysOFF

ON KEY LABEL Ins
ON KEY LABEL Del
ON KEY LABEL Enter

RETURN


FUNCTION Alarm

FOR I = 1 to 3
SET BELL TO 900, 1
?? CHR(7)
SET BELL TO 1200, 1
?? CHR(7)
ENDFOR

RETURN .T.