Глава 8:Сводим все воедино - программа ведения деловых контактов 

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

Файлы и индексы

В этой маленькой системе всего два файла. Файл, где хранится список дел, является дочерним файлом файла PEOPLE и связан с ним по полю COMPANY (табл. 8-1).

Для вывода списка запланированных дел используется индекс по полю DATE.

Элементы управления


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

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



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

Обработка отношений Один-ко-многим

Так как каждый контакт может запланировать себе несколько дел, вы имеете отношение один-ко-многим. Практическая проблема заключается в неопределенном числе строк, которые нужно вывести на экран. Вместо использования BROWSE я создаю меню, вызываемое при выборе опции Things to do. Меню Edit и Delete выводят список запланированных дел для выбранного контакта и предлагают пользователю сделать выбор и нажать Enter; Esc отменяет запрос. Я использую Edit для подробного просмотра выбранного элемента. Если вы решаете удалить элемент списка, необходимо подтвердить свое решение.

Специальный прием обработки memo-полей

Для получения возможности редактировать содержимое memo-поля Notes я использовал маленький трюк. Пользователь щелкает кнопкой мыши, когда курсор расположен на поле Notes (или переводит на него курсор и нажимает Enter). Поле этого memo- поле открывается в окне. В данном случае нужно признать, что я нарушил свои собственные принципы и записываю данные непосредственно в файл. Иногда приходится нарушать правила.

Проектирование программы

Я хотел, чтобы программа давала возможность поиска, редактирования, добавления и удаления записей по желанию пользователя. Если вы выберете одну из функций меню Things to do, они будут обрабатывать текущую запись (ту, что в данный момент отражена на экране), кроме случая, когда вы выберете опцию Ctrl-G Global things to do, выводящую весь список для всех контактов с ближайшими делами, идущими первыми.

Удаленные записи используются повторно, так что COMPANY=[~] и EOF() - это одно и то же. Я думаю, что вам понравится прием с повторным использованием удаленных записей если вы попробуете его в вашей программе. Кроме того, объем дополнительной работы минимален.

При редактировании memo-полей я использую F10 для сохранения и Esc для выхода без сохранения. Это реализуется командой ON KEY LABEL F10 KEYBOARD [{Ctrl-W}].

Я просто не хочу заставлять пользователей нажимать Ctrl-W. Подойдет любая другая комбинация. FoxPro 2.0 позволяет вам выполнить любые переназначения клавиатуры.

Если вы удаляете контакт, имеющий запланированные дела, программа выдаст предупреждение. При подтверждении удаления удаляются данные о контакте и все дела, запланированные для его компании.

Код

Программа приведена в четырех листингах. Программы STARTUP.PRG и PROCLIB.PRG написаны в редакторе FoxPro. PEOPLE.SPR и CONTACTS.MPR сгенерированы конструкторами экранов и меню соответственно. Вы можете видеть места, где блоки кода были введены в сгенерированный код. Если вы приобрете дискету с кодами, то можете модифицировать экраны и проследить за результатами.

Программа Startup

STARTUP.PRG (Листинг 8-1) инициализирует приложение, открывает процедурный файл, файлы данных и запускает программы создания меню и экранов. После активизации процедурного файла (включающего процедуру обработки ошибок), я проверяю параметр FILES в CONFIG.SYS на предмет установки 60 файлов или более. Проверка выполняется функциями низкого уровня доступа. После этого я открываю файлы данных и инициализирую несколько глобальных переменных, включая MEMVARS, связанную с моими двумя файлами. После чего определяю меню и запускаю программу создания экрана. С этого момента начинается работа. После выхода из основного экрана система завершает работу.

Файл процедур

Процедуры, вызываемые системой, размещены в файле PROCLIB (Листинг 8-2). Имя совершенно обычное. Можно было разместить все процедуры в одном файле, но мне хотелось показать как организуется вызов, это может пригодиться.

FoxPro 2.0 не зависит от процедурного файла в той степени как это было для FoxBase или даже для FoxPro 1.02. Если вы собираете свое приложение с использованием менеджера проектов тот простой факт, что процедуры и функции известны приложению делает их доступными. Кроме того методика, предлагаемая Аланом Шварцем, делает возможным доступ к файлам без включения их в APP (см. статью "Модели сопроводимости" в декабрьском 1992 г. выпуске The Pinter FoxPro Letter /русское издание/, - прим. пер.).

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

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

Программа создания экрана

PEOPLE.SPR (Листинг 8-3) создает экран ввода и обеспечивает анализ данных, введенных в поля CATEGORY и m.memfield. Для вывода ранее введенных категорий я использую выпадающее меню. Если введенное значение отсутствует в массиве категорий (получаемой командой SQL SELECT DISTINCT при открытии файлов) выводится меню со списком. Если пользователь нажимает Esc, выводится диалог с предложением удалить введенное значение или добавить его в список. При добавлении массив заново сортируется.

Экран имеет несколько горячих точек. Я использую F10 для сохранения данных и Esc для отмены редактирования. Обратите внимание на новое, более грамотное использование [{Ctrl-W}] вместо CHR(23) для завершения MEMO READ. Опции выпадающих меню срабатывают по комбинациям Ctrl-<клавиша>, что позволяет выполнять разнообразные действия одним движением.

Много внимания пришлось уделить сохранению текущего окна перед выводом нового с последующим восстановлением сохраненного. Команда IF NOT EMPTY(currwind) имеет принципиальное значение, т. к. ACTIVATE WINDOW "" является ошибкой.

Структура, связанная с предложением READ SHOW, обновляет содержимое экрана каждый раз когда я меняю активный индекс и выводит измененный номер записи.

Предложение VALID, связанное с m.action, обрабатывает выбор, сделанный во втором окне. Структура обрабатывает положение указателя в файле данных таким же образом как это сделано в меню, только делает это тайно. Кроме того я ввел вторую версию команды Find, использующую текущий индекс по умолчанию. Пользователи могут быстро просмотреть список штатов просто нажав

Ctrl-F или использовать два нажатия клавиш для смены индекса.

Программа организации меню

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

Листинг 8-4 представляет собой программу организации меню .MPR, полученную конструктором меню. Для удобства разработчика я включил в меню возможность вызова окон отладки и трассировки. Программа никогда не бывает доработана окончательно.

Некоторые из опций меню добавляются очень просто. Например, в меню Find я добавил процедуру Browse, позволяющую пользователю перемещаться по таблице непосредственно. При нажатии Esc пользователь возвращается туда, откуда он попал в режим просмотра. При нажатии Enter происходит выбор записи. Клавиша Enter переназначена на Ctrl-W для выбора в пределах Browse. Не забудьте снять назначение, иначе вы можете испортить себе настроение на весь день.

Заключение

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

Листинг 8-4

SET SYSMENU TO

SET SYSMENU AUTOMATIC

DEFINE PAD _qa211gcjs OF _MSYSMENU PROMPT "\<System" COLOR SCHEME 3 ;
KEY ALT+S, "ALT+S"
DEFINE PAD _qa211gcjz OF _MSYSMENU PROMPT "\<Add" COLOR SCHEME 3 ;
KEY ALT+A, "ALT+A"
DEFINE PAD _qa211gck4 OF _MSYSMENU PROMPT "\<Delete" COLOR SCHEME 3 ;
KEY ALT+D, "ALT+D"
DEFINE PAD _qa211gck9 OF _MSYSMENU PROMPT "\<Find" COLOR SCHEME 3 ;
KEY ALT+F, "ALT+F"
DEFINE PAD _qa211gckf OF _MSYSMENU PROMPT "\<Things to do" COLOR SCHEME 3 ;
KEY ALT+T, "ALT+T"
DEFINE PAD _qa211gckl OF _MSYSMENU PROMPT "\<Report" COLOR SCHEME 3 ;
KEY ALT+R, "ALT+R"
DEFINE PAD _qa211gcm3 OF _MSYSMENU PROMPT "\<Windows" COLOR SCHEME 3 ;
KEY ALT+W, "ALT+W"
DEFINE PAD _qa211gcm8 OF _MSYSMENU PROMPT "\<Quit" COLOR SCHEME 3 ;
KEY ALT+Q, "ALT+Q"
ON PAD _qa211gcjs OF _MSYSMENU ACTIVATE POPUP system
ON SELECTION PAD _qa211gcjz OF _MSYSMENU ;
DO _qa211gcmq ;
IN LOCFILE("CONTACTS" ,"MPX;MPR|FXP;PRG" ,"Where is CONTACTS?")
ON SELECTION PAD _qa211gck4 OF _MSYSMENU ;
DO _qa211gcn2 ;
IN LOCFILE("CONTACTS" ,"MPX;MPR|FXP;PRG" ,"Where is CONTACTS?")
ON PAD _qa211gck9 OF _MSYSMENU ACTIVATE POPUP find
ON PAD _qa211gckf OF _MSYSMENU ACTIVATE POPUP thingstodo
ON PAD _qa211gckl OF _MSYSMENU ACTIVATE POPUP report
ON PAD _qa211gcm3 OF _MSYSMENU ACTIVATE POPUP windows
ON SELECTION PAD _qa211gcm8 OF _MSYSMENU ;
DO _qa211gcnn ;
IN LOCFILE("CONTACTS" ,"MPX;MPR|FXP;PRG" ,"Where is CONTACTS?")

DEFINE POPUP system MARGIN RELATIVE SHADOW COLOR SCHEME 4
DEFINE BAR _MST_DIARY OF system PROMPT "Diary" ;
KEY CTRL+M, "CTRL+M"

DEFINE POPUP find MARGIN RELATIVE SHADOW COLOR SCHEME 4
DEFINE BAR 1 OF find PROMPT "\<Company" ;
KEY CTRL+C, "CTRL+C"
DEFINE BAR 2 OF find PROMPT "\<Last Name" ;
KEY CTRL+L, "CTRL+L"
DEFINE BAR 3 OF find PROMPT "\<First Name" ;
KEY CTRL+F, "CTRL+F"
DEFINE BAR 4 OF find PROMPT "C\<ity" ;
KEY CTRL+I, "CTRL+I"
DEFINE BAR 5 OF find PROMPT "\<State" ;
KEY CTRL+S, "CTRL+S"
DEFINE BAR 6 OF find PROMPT "\<Assigned to" ;
KEY CTRL+A, "CTRL+A"
DEFINE BAR 7 OF find PROMPT "\<Browse" ;
KEY CTRL+B, "CTRL+B"
ON SELECTION BAR 1 OF find DO Search with [COMPANY]
ON SELECTION BAR 2 OF find DO Search with [LASTNAME]
ON SELECTION BAR 3 OF find DO Search with [FIRSTNAME]
ON SELECTION BAR 4 OF find DO Search with [CITY]
ON SELECTION BAR 5 OF find DO Search with [STATE]
ON SELECTION BAR 6 OF find DO Search with [ASSIGNEDTO]
ON SELECTION BAR 7 OF find ;
DO _qa211gcqt ;
IN LOCFILE("CONTACTS" ,"MPX;MPR|FXP;PRG" ,"Where is CONTACTS?")

DEFINE POPUP thingstodo MARGIN RELATIVE SHADOW COLOR SCHEME 4
DEFINE BAR 1 OF thingstodo PROMPT "Add"
DEFINE BAR 2 OF thingstodo PROMPT "Edit"
DEFINE BAR 3 OF thingstodo PROMPT "Delete"
DEFINE BAR 4 OF thingstodo PROMPT "\<Global" ;
KEY CTRL+G, "CTRL+G"
ON SELECTION BAR 1 OF thingstodo DO AddtoDo
ON SELECTION BAR 2 OF thingstodo DO EditToDo
ON SELECTION BAR 3 OF thingstodo DO DelToDo
ON SELECTION BAR 4 OF thingstodo DO GlobToDo

DEFINE POPUP report MARGIN RELATIVE SHADOW COLOR SCHEME 4
DEFINE BAR 1 OF report PROMPT "Mailing List"
DEFINE BAR 2 OF report PROMPT "Address Labels"
ON SELECTION BAR 1 OF report DO MailList
ON SELECTION BAR 2 OF report DO AdLabels

DEFINE POPUP windows MARGIN RELATIVE SHADOW COLOR SCHEME 4
DEFINE BAR _MWI_DEBUG OF windows PROMPT "Debug"
DEFINE BAR _MWI_TRACE OF windows PROMPT "Trace"

PROCEDURE _qa211gcmq
* Add a contact to PEOPLE.DBF

SELECT PEOPLE

SaveRecNo = RECNO()
Adding = .T.

SCATTER MEMVAR BLANK

DO PEOPLE.SPR

IF EMPTY ( m.LastName )
GO SaveRecNo
SCATTER MEMVAR
ELSE
GO BOTTOM
IF COMPANY <> [~]
APPEND BLANK
ENDIF
ENDIF

Adding = .F.

GATHER MEMVAR
SHOW GETS

PROCEDURE _qa211gcn2
* Delete a contact and any associated things to do

CurrWind = WOUTPUT()

ACTIVATE SCREEN

SELECT TODOLIST
SET ORDER TO TODOLIST
SEEK UPPER ( PEOPLE.COMPANY )
SELECT PEOPLE

IF TODOLIST.COMPANY = PEOPLE.COMPANY
=UH_OH()
WAIT WINDOW [Things to do remain for this person] NOWAIT
ENDIF

IF CONFIRM ( [Delete this person?] )

SaveComp = PEOPLE.COMPANY

SCATTER MEMVAR BLANK
GATHER MEMVAR
REPLACE NEXT 1 COMPANY WITH [~]

TagName = [COMPANY] && for display...
SET ORDER TO &TagName

SET NEAR ON
SEEK SaveComp
SET NEAR OFF

IF COMPANY = [~]
SKIP -1
ENDIF

SCATTER MEMVAR
SHOW GETS

SELECT TODOLIST
SET ORDER TO TODOLIST

SEEK UPPER ( SaveComp )
DO WHILE COMPANY = SaveComp
SCATTER MEMVAR BLANK
GATHER MEMVAR
REPLACE NEXT 1 COMPANY WITH [~]
SEEK UPPER ( SaveComp )
ENDDO

SELECT PEOPLE
m.COMPANY = PEOPLE.COMPANY

ENDIF

ACTIVATE WINDOW PEOPLE



PROCEDURE _qa211gcnn
POP MENU _MSYSMENU
SET SYSMENU TO DEFAULT
ON ERROR
CLEAR READ
CLOSE ALL
QUIT

PROCEDURE _qa211gcqt
SaveRecno = RECNO()

ON KEY LABEL ENTER KEYBOARD [{Ctrl+W}]

DEFINE WINDOW BROWWIND FROM 2, 8 TO 21, 70 ;
SHADOW ;
TITLE [Select a contact and press -+, or ESCAPE to cancel]

BROWSE ;
FIELDS ;
Name = TRIM(FirstName)+[ ]+TRIM(LastName) :20 , ;
Company:20 , ;
Phone :20 ;
NOMODIFY ;
WINDOW BROWWIND ;
COLOR SCHEME 10

ON KEY LABEL ENTER

IF LASTKEY() = 27
GO SaveRecno
ELSE
SCATTER MEMVAR
SHOW GETS
ENDIF