Библиотека: А. Горев, С. Макашарипов, Р. Ахаян. Эффективная работа с СУБД

Глава 9
Разработка пользовательского интерфейса
9.1. Инструментарий разработчика
9.2. Конструируем форму
Создание формы "Прием заказов" на Visual FoxPro
Создание формы "Прием заказов" на Access
9.3. Разработка управляющего меню
Разработка меню в Visual FoxPro
Разработка меню в Access

    Даже программисты быстро забыли, что еще несколько лет назад общались с компьютером с помощью колоды перфокарт, а результат работы программы могли видеть лишь на листе бумаги. Теперь самую лучшую программу никто не оценит по достоинству, если она не будет иметь удобного пользовательского интерфейса.
    При работе с современными средствами разработки приложений программист с самого начала окунается в проблемы построения интерфейса. Также и мы, обсуждая проблемы работы с данными, не могли не коснуться этих проблем. В этой главе мы как бы подведем итог обсуждаемым ранее вопросам под привычным лозунгом Microsoft "Putting It All Together!" - "Объединим все вместе!"
    Рассуждая о принципах и методах построения пользовательского интерфейса можно много говорить о различных писаных и неписаных правилах. А можно этого не делать, а попросить читателя повнимательнее посмотреть на тот интерфейс, который обеспечивает любимое средство разработки. Посмотрели? Это лучший образец для подражания.

9.1. Инструментарий разработчика

    В третьей главе мы уже рассматривали визуальный инструментарий для разработки пользовательского интерфейса. Все три пакета для построения пользовательских программ (Visual FoxPro, Visual Basic и Access) имеют соответствующие средства, выполненные в виде Конструкторов и Мастеров. Если в Visual Basic и Access основным визуальным инструментарием для построения пользовательского интерфейса следует считать Конструктор формы, то в Visual FoxPro сюда следует отнести и Конструктор класса, который может эффективно использоваться для разработки многократно используемых элементов от отдельных объектов до целых форм.
    В этом параграфе мы рассмотрим средства, доступные для разработчика пользовательского приложения программным путем.
    В первую очередь это, конечно, средства языка программирования. С помощью программного кода мы можем описывать действия, которые приложение должно выполнять при наступлении событий, подобных выбору пользователем какого-либо пункта меню или щелчку мыши на том или ином элементе пользовательского интерфейса. Возможности языков программирования Visual FoxPro и Visual Basic в этом плане достаточно широки, примерно равны по мощности и могут обеспечить самую изощренную функциональность для вашей программы. Авторы выражают слабую надежду, что читатель успел в этом убедиться, прочитав предыдущие главы нашей книги. Поэтому более подробно мы остановимся на дополнительных средствах, доступных программисту в рассматриваемых средствах разработки.
    И все-таки даже такие мощные программные средства порой бессильны перед жесткими требованиями пользователя и неудержимым желанием программиста сделать свою программу самой совершенной в мире. В этом случае на помощь можно призвать дополнительные средства в виде богатого набора функций операционной системы - Windows API.


Windows API - это интерфейс, позволяющий прикладной программе использовать функции, встроенные в операционную систему.

    При использовании функций Windows API программист может реализовать следующие преимущества:

  • Повышение скорости работы. В большинстве случаев функции, встроенные в ОС, обеспечивают более высокую скорость выполнения операций, чем встроенные функции Visual FoxPro или Visual Basic. В первую очередь это касается выполнения графических операций.
  • Стабильность и высокая надежность работы функций, связанная с высокой степенью отладки ОС.
  • Отсутствие потребности в дополнительных модулях. Альтернативой использования Windows API является приобретение дополнительных компонентов, обеспечивающих расширенную функциональность, что повышает затраты на разработку пользовательского приложения и создает дополнительные сложности при его распространении.

    В то же время мы рекомендуем использовать Windows API только в крайнем случае и при необходимости повышения скорости работы программы, так как здесь программисту приходится сталкиваться с более сложными программными конструкциями. Это, естественно, требует времени на освоение, так как те сложные задачи, которые были скрыты от программиста при использовании любимого языка программирования высокого уровня с приставкой Visual, теперь придется решать самому. При этом учтите, что Microsoft до сих не выпустила хорошего руководства или четко структурированного файла контекстной помощи по Windows API и вам придется долго блуждать среди многих сотен функций с мудреными названиями. С профессиональными версиями Visual FoxPro (только на CD-ROM) и Visual Basic поставляется файл со справочными сведениями по Windows API, но он содержит синтаксис для программирования на языке Си.
    Если вы все же не раздумали использовать функции Windows API, в своей программе вам придется позаботиться о двух необходимых составляющих. Во-первых, вы должны определить путь для передачи параметров в вызываемую функцию. Во-вторых - определить механизм для получения значения, возвращаемого функцией. Эти задачи выполняет команда DECLARE.
    В Visual Basic она имеет следующий синтаксис:

[Public | Private] Declare Function FunctionName Lib "LibraryName" [Alias " AliasName" ] [([ParamList])][As ParamType]

    В Visual FoxPro при том же составе параметров синтаксис команды выглядит чуть иначе:

DECLARE [cFunctionType] FunctionName IN LibraryName [AS AliasName] [cParamType1 [@] ParamName1,cParamType2 [@] ParamName2, ...]

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

  • Сведения о функции определяются ее именем FunctionName, заданном в библиотеке DLL. Имя функции следует указывать с соблюдением регистра! Если функция возвращает какое-либо значение, перед ее именем следует указать тип возвращаемого значения cFunctionType. Вероятно, здесь уместно напомнить, что в Visual Basic объявление Public используется для обеспечения доступа к функции из всех процедур, раположенных во всех модулях. Объявление Private обеспечивает доступ к функции только внутри данного модуля.
  • Имя библиотеки DLL Windows LibraryName, содержащей требуемую функцию. Если вы хотите использовать функцию, входящую в API Windows, то здесь достаточно написать WIN32API, что автоматически обеспечит поиск функции в одном из следующих файлов: KERNEL32.DLL, GDI32.DLL, USER32.DLL, MPR.DLL или ADVAPI32. DLL.
  • Псевдоним имени функции для использования в Visual FoxPro или в Visual Basic. Его удобно использовать для сокращения слишком длинного названия функции или при угрозе совпадения имени функции с зарезервированным словом.
  • Список передаваемых параметров включает обозначение типа и имени параметров, передаваемых из приложения в функцию DLL Windows. Знак @ после типа параметра показывает, что он будет передаваться по ссылке, а не по значению. Имя параметра никак не используется ни приложением, ни функцией DLL и может применяться в команде только в информационных целях. Если функция DLL требует в качестве аргумента передачи нулевого указателя (null pointer), то его следует передавать как целое число по значению.

    Для передачи параметров в Visual Basic следует использовать следующий синтаксис:

[Optional][ByVal | ByRef][ParamArray] varname[( )][As ParamType]

    Опции ByVal или ByRef определяют, что параметр будет передан по значению или по ссылке. В следующей таблице показано, к каким результатам приведет передача параметров по значению или по ссылке различного типа:

Тип параметраПо значениюПо ссылке
IntegerПомещает в стек вызовов 16-битовое значение.Помещает в стек вызовов 32-битовый адрес. Этот адрес обеспечивает ссылку на 16-битовое значение параметра.
LongПомещает в стек вызовов 32-битовое значение. Этот адрес обеспечивает ссылку на 32-битовое значение параметра.Помещает 32-битовый адрес в стек.
StringПреобразует строку символов в формат, принятый в С, с нулевым (CHR(0)) символом в конце. Помещает 32-битовыйый адрес этой строки в стек.Помещает 32-битовый указатель в стек.

    Помимо правильного использования параметров, довольно важным при использовании функций DLL является применение указателя.


Указатель (handle) - это число, уникально идентифицирующее объект.

    Среда Windows поддерживает очень много объектов различного типа. Это разнообразные окна, блоки памяти, меню, шрифты, палитры и т. д. Допустим, вы хотите нарисовать голубую линию; тогда вы должны создать объект "голубой карандаш". Функция Windows API CreatePen() вернет указатель на этот "карандаш", который вы затем можете использовать для рисования. Когда вы закончите работу, этот объект можно убрать, передав указатель в функцию DestroyObject().
    Рассмотрим несколько примеров для демонстрации возможностей Windows API. Начнем с таких задач, которые невозможно выполнить стандартными средствами.
    Давайте посмотрим, как сделать в Visual FoxPro список, в котором будут появляться все устройства, доступные на данном компьютере. При этом для дисководов гибких дисков должна проверяться их готовность к работе (наличие дискеты). Этот список лучше всего оформить в виде класса. Тогда при добавлении его в форму мы сразу получим требуемый элемент управления.
    Список доступных логических устройств для данного компьютера мы можем получить с помощью следующей функции API Windows (в синтаксисе C, как приведено в файле WIN32API.HLP):

DWORD GetLogicalDriveStrings (DWORD nBufferLength, LPTSTR lpBuffer);

    Эта функция возвращает список доступных устройств в виде строки символов.
    Тип устройства можно определить с помощью функции

UINT GetDriveType (LPCTSTR lpRootPathName);
    Эта функция возвращает одно из следующих значений:
  • 0 - тип устройства не определен;
  • 1 - нет загрузочного сектора;
  • 2 - устройство для сменного носителя данных;
  • 3 - жесткий диск;
  • 4 - сетевое устройство;
  • 5 - дисковод для лазерных дисков;
  • 6 - виртуальный диск.

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

UINT SetErrorMode(UINT fuErrorMode);

    Эта функция возвращает предыдущую установку реакции на ошибки. В Visual FoxPro нет такого многообразия типов данных, как в Си ++, поэтому тип возвращаемого этими тремя функциями значения DWORD (двойное слово) или UINT (целое без знака) заменим имеющим 4 бита INTEGER.
    Теперь, проведя необходимую теоретическую подготовку, можно приступать к практическому созданию требуемого класса.
    В Project Manager при активной вкладке Classes щелкнем на кнопке New и в диалоговом окне New Class укажем название создаваемого класса, класс, на основании которого он создается, и имя библотеки, где он будет храниться. Будем надеяться, что к этому моменту у вас уже есть собственный набор классов и класс для списка в том числе.
    Дадим этому классу имя lstDrives и установим для него следующие свойства:

  • BoundColumn = 2 (две колонки);
  • ColumnLines = .F. (без разграничительных линий);
  • FontSize = 12 (размер шрифта, обычно лучше использовать 10, но отдельные буквы можно увеличить).

    Код для создания требуемого списка в событии Init может выглядеть следующим образом:

* Определяем функцию для получения списка доступных
* устройств
DECLARE INTEGER GetLogicalDriveStrings IN Win32API AS GetDrive;
INTEGER, STRING
* Определяем функцию для определения типа устройства
DECLARE INTEGER GetDriveType IN Win32API AS GetType;
STRING
* Описываем локальные переменные
LOCAL cLetter, nLetter, NType, nDrivesNum
* Резервируем строку для размещения списка доступных
* устройств
lpString=SPACE(200)
* Определяем размер буфера для строки
nBuffSize=LEN(lpString)
* Получаем список устройств
= GetDrive(nBuffSize,@lpString)
* Определяем количество устройств
nDrivesNum = OCCURS(":",lpString)
* Получаем их имена
FOR nLetter = 1 TO nDrivesNum
     cLetter = UPPER(" " + SUBSTR(lpString,AT(":",lpString,nLetter)-1,1))
     * Определяем тип устройства
     nType = GetType(ALLTRIM(cLetter + ":\"))
     * Добавляем обозначение устройства (первая колонка)
     * и его тип (вторая колонка) в список
     This.AddItem(cLetter, nLetter, 1)
     This.List(nLetter, 2) = STR(nType)
     * Задаем соответствующее изображение
     DO CASE
CASE NType = 2
     This.Picture(nLetter) = "FLOPPY.BMP"
CASE NType = 3
     This.Picture(nLetter) = "DRIVE.BMP"
CASE NType = 4
     This.Picture(nLetter) = "NET.BMP"
CASE NType = 5
     This.Picture(nLetter) = "CDROM.BMP"
     ENDCASE
ENDFOR

    Все необходимые файлы изображений вы найдете на дискете. Нам их пришлось создать заново, так как обширная библиотека изображений, поставляемая с Visual FoxPro, содержала подходящие изображения только в формате ICO 32x32. В подобных списках лучше смотрятся изображения 16x16. Впрочем, это легко сделать с помощью утилиты Imagedit из состава Visual FoxPro. Здесь только необходимо учесть, что при выводе изображения Visual FoxPro очень своеобразно обходится с белым цветом, считая, что его вообще нет. Если вы выведете изображение, содержащее белый цвет, например, на серую кнопку, то те места изображения, которые были белые, станут серыми. Для сохранения белого цвета для каждого изображения надо создать двойника - маску (файл с расширением MSK), в котором на месте белого цвета должен располагаться черный. В качестве примера этой операции можно руководствоваться изображениями, которые использует Wizard (Мастер) для графических кнопок управления в форме.
    В событии Click класса запишем следующий код:

LOCAL cLetter
cLetter = This.DisplayValue + ":"
* Если устройством является дисковод для гибких дисков,
* то проверяем наличие
* дискеты с помощью метода IsDiskIn
IF ALLTRIM(This.Value) = "2"
     IF This.IsDiskIn(cLetter) = .T.
     This.Parent.cmdOk.Enabled = .T.
     ELSE
= MESSAGEBOX("Вставьте дискету в выбранное устройство!",0,;
     "Не готов дисковод")
IF This.IsDiskIn(cLetter) = .T.
     This.Parent.cmdOk.Enabled = .T.
ELSE
     This.Parent.cmdOk.Enabled = .F.
ENDIF
     ENDIF
     RETURN
ENDIF
This.Parent.cmdOk.Enabled = .T.

    Для определения наличия дискеты в дисководе создадим в нашем классе путем выбора команды New Method в меню Class специальный метод IsDiskIn() и запишем в него следующий код:

LPARAMETERS cDrive
LOCAL lOldError
* Регистрируем функцию SetErrorMode, которая определяет,
* как ОС реагирует на некоторые критические ошибки,
* в том числе дисковые операции
DECLARE INTEGER SetErrorMode IN Win32API INTEGER
* Определяем наличие и сохраняем имя обработчика ошибок
lOldError = ON('ERROR')
ON ERROR lDiskError = .T.
* Задание функции SetErrorMode с аргументом 1 позволяет
* приложению самому обрабатывать критические ошибки
* вместо ОС
nOldErrorState = SetErrorMode(1)
* По умолчанию считаем, что ошибки нет, если не так,
* значение будет изменено
lDiskError = .f.
* Пытаемся найти NUL файл в загрузочной области
* указанного устройства
lDriveState = FILE(cDrive + "\NUL")
IF .NOT. lDiskError
     IF lDriveState
lDriveOk = .t.
     ELSE
lDriveOk = .f.
     ENDIF
ELSE
     lDriveOk = .f.
ENDIF
* Восстанавливаем прежний обработчик ошибок
IF .NOT. EMPTY(lOldError)
     ON ERROR DO (lOldError)
ELSE
     ON ERROR
ENDIF
* Восстанавливаем реакцию на ошибки ОС
nRestState = SetErrorMode(nOldErrorState)
RETURN lDriveOk

    После этого класс списка можно помещать в любой требуемой форме.
    Во втором примере использования функций Windows API рассмотрим возможность ускорения выполнения графических операций.
    Сначала создадим форму, в которой будем выводить зигзагообразную линию средствами Visual FoxPro (используя метод Line). Для этого в методе Paint формы запишем следующий код:

nY = 1
FOR nX = 5 TO 375 STEP 5
     ThisForm.Line(nX, nY*100)
     nY = -nY
ENDFOR

    Теперь попробуем такую же линию нарисовать средствами Windows API. В методе Paint второй формы запишем:

*** Возвращает указатель на контекст указанного окна
DECLARE INTEGER GetDC IN WIN32API INTEGER
* HWND Указатель окна
*** Стирает контекст указанного окна
DECLARE INTEGER ReleaseDC IN WIN32API INTEGER, INTEGER
* HWND Указатель окна
* HDC Указатель контекста устройства
*** Рисует линию с текущей позиции до указанных координат
DECLARE INTEGER LineTo IN WIN32API INTEGER, INTEGER, INTEGER
* HDC Указатель контекста устройства
* int nXEnd x-координата конечной точки линии
* int nYEnd y-координата конечной точки линии
*** Обновляет текущее положение указанной точки
DECLARE INTEGER MoveToEx IN WIN32API INTEGER, INTEGER, INTEGER, STRING
* HDC Указатель контекста устройства
* int X x-координата нового текущего положения
* int Y y-координата нового текущего положения
* LPPOINT адрес предыдущего положения (NULL in FoxPro)
*** Функция GetFocus не имеет параметров и возвращает HWND активного окна
DECLARE INTEGER GetFocus IN WIN32API
mynull = .NULL.
pmhand = GetFocus()
IF pmhand<<>>0 && Если есть открытое активное окно
     hmdc = GetDC(pmhand)
     = MoveToEx(hmdc, 0, 0, mynull)
     nY = 1
     FOR nX = 5 TO 375 STEP 5
= LineTo(hmdc, nX, nY*100)
nY = -nY
     ENDFOR
     = ReleaseDC(pmhand, hmdc)
ELSE
     WAIT WINDOW "Нет активного окна"
ENDIF

    Запустите обе формы и вы убедитесь, что второй вариант работает в несколько раз быстрее. Для рисования линии средствами Windows API нам пришлось использовать четыре функции. Передаваемые параметры перечислены в строчках комментария после объявления функций.
    В этом примере мы используем новое понятие - контекст устройства (device context). Что это такое?


Контекст устройства - это совокупность параметров, описывающих условия работы с каким-либо устройством.

    Использование контекста устройства позволяет программисту сосредоточиться на выполнении какого-то действия (например, на написании кода для рисования) и избежать имеющих вспомогательное значение операций. Для рисования такими вспомогательными операциями могут быть установленное разрешения экрана, число поддерживаемых данным устройством цветов и т. д. Если вы пишете код для рисования красного прямоугольника, то можете быть уверены, что Windows обеспечит рисование этого объекта с учетом возможностей того устройства, которое выбрано для его отображения, будь то экран, принтер или плоттер.
    С точки зрения программиста такой подход обозначает рисование в объекте, называемом контекстом устройства. При этом контекст устройства может рассматриваться как "черный ящик", обладающий определенными характеристиками. Важнейшие из них:

  • Система координат, которая может быть представлена такими фиксированными единицами, как сантиметры, дюймы или пикселы, или комбинированными единицами.
  • Параметры рисуемой линии (объект Pen) определяют цвет, ширину и стиль линии.
  • Параметры закрашивания (объект Brush) определяют цвета, используемые для заполнения фона, и палитру для графических команд.
  • Шрифт (объект Font) описывает шрифт, используемый при работе команд вывода данных.

    В заключение обсуждения проблем, связанных с использованием функций Windows API, затронем еще один важный вопрос. Он связан с тем, что многие функции Windows API в качестве параметров используют структуры.
    При использовании Visual Basic у программиста не появится никаких проблем - это средство разработки поддерживает структуры. Visual FoxPro не поддерживает структур и программисту придется формировать структуру в виде символьной строки. В качестве примера такой работы приведем фрагмент программного кода для соединения с сервером по телефонной линии. Само соединение предварительно создается в специальной утилите внешнего доступа Dial-Up Networking. Windows хранит параметры созданного соединения в регистре. В Visual FoxPro требуемый фрагмент будет выглядеть так:

DECLARE INTEGER RasDial IN rasapi32 ;
     STRING DialExtetions, ;
     STRING cPhonebookfile, ;
     STRING @ cParameters, ;
     INTEGER nCallBack, ;
     STRING cCallBack, ;
     STRING @ nConnHandle
nConnHandle = CHR(0)+CHR(0)+CHR(0)+CHR(0)
&& Указатель соединения
cConnName = "RcomPPP" && Имя соединения
cPhone = "2594277"
&& Номер телефона, подключенного к серверу
cUserName = "Administrator" && Имя пользователя
cPassWord = "AndreyG" && Пароль для соединения
cDomain = "" && Имя домена Windows NT
* Формируем структуру
cParam = CHR(28)+CHR(4)+CHR(0)+CHR(0)+;
     PADR(cConnName+CHR(0), 256+1)+;
     PADR(CHR(0), 128+1)+;
     PADR(CHR(0), 128+1)+;
     PADR(cUserName+CHR(0), 256+1)+;
     PADR(cPassword+CHR(0), 256+1)+;
     PADR(cDomain+CHR(0), 15+1)+" "
* Выполняем соединение
nRes = RasDial(NULL, NULL, @cParam, 0, NULL, @nConnHandle)

    Равный по функциональности фрагмент, написанный на Visual Basic, будет выглядеть так:

Private nConnHandle As Long, nRes As Long
' Описываем структуру параметров соединения
Private Type DIALPARAMS
     dwSize As Long
     szEntryName(256 + 1) As String
     szPhoneNumber(128 + 1) As String
     szCallbackNumber(128 + 1) As String
     szUserName(256 + 1) As String
     szPassword(256 + 1) As String
     szDomain(15 + 1) As String
End Type
Private ConnParams As DIALPARAMS
' Функция для установки соединения
Private Declare Function RasDialA Lib "rasapi32" _
     (DialExtetions, _
     cPhonebookfile, _
     ByRef Parameters As DIALPARAMS, _
     nCallBack As Long, _
     cCallBack, _
     ByRef hConnHandle As Long) As Long
` Присваиваем значения параметрам
nRes = 0
nConnHandle = 0
` Это значение dwSize устанавливается при внешнем доступе из Windows 95
ConnParams.dwSize = 1052
' Наименование соединения в регистре Windows
ConnParams.szEntryName(0) = "R"
ConnParams.szEntryName(1) = "c"
ConnParams.szEntryName(2) = "o"
ConnParams.szEntryName(3) = "m"
ConnParams.szEntryName(4) = "P"
ConnParams.szEntryName(5) = "P"
ConnParams.szEntryName(6) = "P"
ConnParams.szEntryName(7) = Chr(0)
' Номер телефона берем из регистра
ConnParams.szPhoneNumber(0) = Chr(0)
ConnParams.szCallbackNumber(0) = Chr(0)
ConnParams.szUserName(0) = "A"
ConnParams.szUserName(1) = Chr(0)
ConnParams.szPassword(0) = Chr(0)
ConnParams.szDomain(0) = Chr(0)
` Выполняем соединение
nRes = RasDialA(Null, Null, ConnParams, 0, Null, nConnHandle)

9.2. Конструируем форму

    При описании различных действий, возникающих при работе с данными, мы уже многократно говорили о формах, которые составляют основу интерфейса любого приложения для обработки данных.
    В этом параграфе мы более детально разберем процесс проектирования форм для работы с данными при использовании различных СУБД.
    Формы предназначены для представления, ввода и редактирования данных удобным и привычным для пользователя способом. Помимо этого в формы можно включать различные объекты, реагирующие на события (системные или пользовательские). С помощью этих объектов можно разрабатывать простые и интуитивно понятные интерфейсы пользователя для управления данными.
    Несмотря на то, что вы полностью можете описать форму с помощью программирования, как правило, более простым и быстрым способом будет визуальный. При этом Конструктор формы возьмет на себя труд перевода ваших пожеланий на соответствующий язык программирования. Язык, который используется в Visual Basic, а значит, и в Access, в отличие от Visual FoxPro не является объектно-ориентированным, тем не менее он объектный. Таким образом, какой бы путь создания формы вы ни выбрали, работа будет заключаться в создании объектов и обеспечении требуемой функциональности за счет использования их свойств и методов. Большинство свойств позволяют указать действия, которые должны выполниться при наступлении событий, доступных для конкретного объекта. Используя Конструктор формы, с помощью окна Свойства (Properties) мы получаем легкий доступ к любому из свойств и можем описать действия объекта в ответ на события. В коде событий мы можем использовать методы объектов, например, для перемещения на него курсора (придания статуса активного объекта в форме).
    Например, в форме "Прием заказов" мы будем использовать командные кнопки, комбинированный список и текстовое поле. Для переноса элементов управления на проектируемую форму можно использовать панель инструментов с элементами управления. Для установки некоторых визуальных свойств формы, например цвета фона и шрифта, можно использовать панель инструментов для форматирования. При этом очень часто использованию панелей инструментов есть хорошая альтернатива в виде всплывающего меню, вызываемого при нажатии правой кнопки мыши и являющееся контекстным по отношению к выделенному элементу.
    Формы при работе с данными используют свой набор данных. Организация процесса взаимодействия формы с данными происходит посредством установки свойства RecordSource (Источник данных) и связанных с конкретными полями источника данных объектов. Если вы хотите отображать с помощью элементов управления данные из полей, которые не относятся к источнику данных, то прямое указание в свойстве ControlSource (Данные) ни к чему не приведет. Вам необходимо организовать синхронизацию записей между данными, входящими в источник данных, и данными, не входящими, и, естественно, использовать несвязанное поле для отображения информации, не относящейся к источнику данных. Если вы ничего не поняли из этой фразы, то правильно сделали. Как правило, так не поступают, потому что подобный путь требует сложного и не оправдывающего себя кодирования. Лучший способ - это создание запроса, который одновременно может отобразить данные из нескольких таблиц. Помимо этого используйте подчиненные формы. При этом необходимо иметь поля, по которым вы можете связать данные в основной и подчиненной формах. Еще проще вызывать форму, содержащую нужные вам, но хранящиеся в другом источнике данные, динамически, с помощью реакции на события. Во многих случаях этого более чем достаточно.
    Иногда, при решении более сложных задач, когда вам необходимо, чтобы никакие обстоятельства, вплоть до перебоев в энергопитании, не смогли нарушить установленные правила взаимодействий между данными, вам необходимо использовать транзакции. Для использования транзакций в Access необходимо работать с такими наборами данных, создание и доступ к которым осуществляются с помощью методов объектов доступа к данным (DAO).
    Опишем форму, которую мы будем создавать в качестве примера.
    На основе таблицы Order_ создается форма для принятия заказа. Ни одно из полей в таблице Order_ пользователь непосредственно не заполняет. Путем использования комбинированных списков и вызываемых форм он заполняет другие таблицы, при этом автоматически заполняются поля в новой записи таблицы Order_. Перед началом редактирования новой записи начинается транзакция.
    Поле key_order заполняется автоматически, так как имеет тип Счетчик. Поле key_salman заполняется именем текущего пользователя приложения (для примера на Access) или выбирается из списка продавцов (для примера на Visual FoxPro). Заказчик выбирается из списка заказчиков по фамилии, имени и адресу или заносится вновь (для примера на Access), при этом необходимо вызвать форму для заполнения данных о заказчике. При вызове формы для заполнения данных о заказчике также начнется транзакция, но не вложенная, а параллельная, так как данные о заказчике будут сохраняться в любом случае, для того чтобы потом "засыпать" его проспектами и приглашениями на презентации новых моделей автомобилей.
    После этого, на основе данных, хранящихся в таблице Model, подбирается модель и, если она есть на складе и удовлетворяет заказчика, то заполняется запись в таблице Account. После этого завершается транзакция и запись вносится в таблицу физически.

Создание формы "Прием заказов" на Visual FoxPro

    Начнем с систематизации данных о способах создания новой формы в Visual FoxPro. Таких способов достаточно много:

  • Использование Мастера форм обсуждалось ранее. Это эффективный способ создания простых форм или "заготовок" для более сложных форм.
  • Выбор команды New из меню File главного меню Visual FoxPro. В открывшемся диалоговом окне New необходимо щелкнуть на кнопке Form.
  • Выполнение команды CREATE FORM.
  • В Project Manager выделение пункта Form и выбор New.

    Независимо от выбранного способа загружается Конструктор формы, окно которого с основными визуальными инструментами представлено на рис. 9.1. При этом в главном меню Visual FoxPro появляется меню Form, с помощью которого можно выполнить следующие действия:


Рис. 9.1.
  • New property - позволяет добавить свое свойство для создаваемой формы.
  • New method - позволяет добавить свой метод для создаваемой формы.
  • Edit Property/Method - позволяет удалить добавленные свойства и методы из описания формы или изменить комментарий.
  • Include File - позволяет определить файл, содержащий директивы компилятора.
  • Create Form Set - создает набор форм - объект, который будет являться контейнером по отношению к нескольким формам и панелям инструментов.
  • Remove Form Set - удаляет набор форм, если он создан.
  • Add New Form - позволяет добавить форму в набор форм.
  • Remove Form - удаляет форму из набора форм.
  • Quick Form - запускает Построитель формы (Form Builder), с помощью которого можно быстро создать простую форму для работы с данными из одной таблицы и в последующем использовать как заготовку в Конструкторе формы. Построитель формы представляет собой упрощенную версию Мастера формы.
  • Run Form - запускает разрабатываемую форму.

    Для ускорения разработки формы служат несколько панелей инструментов.
    Панель инструментов Form Designer предназначена для быстрого перехода или запуска различных элементов Конструктора формы. Она появляется автоматически при запуске Конструктора формы. Назначение кнопок на этой панели описано на рис. 9.2.


Рис. 9.2.

    Панель инструментов Layout предназначена для быстрого изменения расположения объектов на завершающем этапе создания формы. Ее возможности будут вам ясны, если вы посмотрите на рис. 9.3. Перед выполнением какого-либо действия предварительно нужно выбрать объект или группу объектов, удерживая клавишу Shift и щелкая на них мышкой. Операции выравнивания, которые выполняются относительно группы объектов, за образец берут самый большой размер, иначе перед выполнением операции выравнивания необходимо удерживать клавишу Ctrl. Если выбранное действие не дало желаемого результата или вовсе привело к нежелательным последствиям, не стоит отчаиваться, вспомните про кнопку Undo на стандартной панели инструментов Visual FoxPro.


Рис. 9.3. Панель инструментов Layout

    Панель инструментов Form Controls позволяет включать в разрабатываемую или редактируемую форму тот или иной элемент управления. На рис. 9.4 описаны кнопки данной панели инструментов.


Рис. 9.4.

    Перед тем как вы начнете разрабатывать формы для своего приложения, не поленитесь заглянуть в диалоговое окно Options меню Tools. Выберите вкладку Forms и установите на ней требуемые значения для расположенных там параметров (рис. 9.5). Для облегчения расположения элементов управления на форме включите вывод координатной сетки. Наиболее удобный шаг координатной сетки - от 6 до 10. В качестве единицы измерения установите пикселы - Pixels. В опции Maximum Design Area установите разрешение 640x480 - это означает, что разработанная вами форма наверняка поместится на экране, имеющем стандартное разрешение VGA. Если вы установите более высокое разрешение, то форма может прекрасно выглядеть на вашем компьютере, но окажется слишком большой на экране компьютера у пользователя вашей программы.


Рис. 9.5.

    Если какие-либо свойства проектируемого объекта по умолчанию имеют отличные от требуемых значения, то нужные значения свойств можно установить в окне Properties, которое для удобства поиска требуемого свойства или метода разбито на пять вкладок: "All", "Data", "Methods", "Layout", "Other". Элементы окна Properties показаны на рис. 9.6.


Рис. 9.6. Список свойств в Конструкторе формы (окно Properties)

    Вкладка "All" содержит все элементы, включенные в остальные разделы окна.
    Убедимся с помощью окна Properties, что форма имеет следующие свойства:
    AutoCenter = .T. - определяем автоматическое центрирование объекта Form;
BackColor = RGB(176,176,176) - задаем цвет фона;
Caption = Прием заказов - задаем текст, отображаемый в названии объекта;
Closable = .F. - ликвидируем возможность закрытия объекта Form, двойным щелчком кнопки управляющего меню, или выбора в этом меню команды Close;
Height = 300 - задаем ширину объекта на экране;
Icon = "c:\mybook\sample\net13.ico" - задаем значок, который отображается на этапе выполнения для объекта Form при его свертывании;
MaxButton = .F. - ликвидируем доступ к кнопке Maximize;
MinButton = .F. - ликвидируем доступ к кнопке Minimize;
ShowTips = .T. - определяем возможность вывода подсказки для элементов управления заданного объекта;
Width = 540 - задаем ширину объекта.
    Следующим этапом мы включаем необходимые элементы управления, такие как Label, Text Box, Command Button, Combo Box, Shape, и определяем уже для вновь созданных элементов их свойства.
    Для управления работой формы создадим специальный элемент управления. Скорее всего мы будем использовать его и в других формах. В этом случае самым лучшим решением будет создание нового класса mygo на базе класса Container (контейнера). После этого мы сможем легко включить в нашу форму собственный объект mygo1, который основан на базе класса mygo. Прервем ненадолго описание процесса создания формы, чтобы уделить необходимое внимание новому классу.
    При создании классов в Конструкторе класса необходимо определить базовый класс для нового класса и задать имя библиотеки, в которую этот класс будет записан. Для создания класса необходимо выбрать команду New из меню File главного меню Visual FoxPro. В открывшемся диалоговом окне New щелкаем на кнопке выбора Class и нажимаем кнопку New File. В окне New Class вводим имя класса (Class Name), выбираем имя класса, на котором будет основан создаваемый класс (Based On), и имя библиотеки (Store In), в которой будет храниться новый класс (рис. 9.7). Либо в Project Manager выбрать вкладку Classes и нажать кнопку New.


Рис. 9.7. Диалоговое окно New Class

    Как видно из рис. 9.7, имя нашего класса mygo, базовый класс Container и имя библиотеки, в которую будет включен класс, - Roub.vcx
    Вы можете включать в форму объекты своего класса прямо из панели инструментов Form Controls, если предварительно зарегистрируете класс.
    Зарегистрировать библиотеку классов в Конструкторе формы можно следующим образом:

  1. Нажмите кнопку View Classes панели инструментов Form Controls.
  2. В открывшемся меню выберите команду Add.
  3. В диалоговом окне Open укажите библиотеку классов и нажмите кнопку Open.

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


Рис. 9.8. Разработка нового класса в Конструкторе класса
Таблица 9.1. Назначение элементов класса mygo
ОбъектЗначение
Com1Первая запись
Com2Назад
Com3Поиск
Com4Вперед
Com5Последняя запись
Com6Добавить
Com7Удалить
Com8Сохранить
Com9Отменить
Com10Выход из формы

    Добавляем последовательно два новых метода для нашего класса, my_show_s_u и myrefresh, выбрав в меню Form команду New Method.
    В код метода my_show_s_u запишем следующие строчки:

ThisForm.mygo1.Com8.Enabled = .F.
ThisForm.mygo1.Com9.Enabled = .F.
MyUpdate = 0
IF Del_ = 0
     ThisForm.mygo1.Myrefresh
ELSE
     Del_ = 0
ENDIF
    В код метода myrefresh запишем:
ThisForm.Text1.Refresh
ThisForm.Text2.Refresh
ThisForm.Text3.Refresh
ThisForm.Text4.Refresh
ThisForm.Refresh_list
    В код события Click для кнопки Com1 запишем:
GO TOP
ThisForm.mygo1.Com1.Enabled=.F.
ThisForm.mygo1.Com2.Enabled=.F.
ThisForm.mygo1.Com4.Enabled=.T.
ThisForm.mygo1.Com5.Enabled=.T.
ThisForm.mygo1.MyRefresh
    В код события Click для кнопки Com2 запишем:
IF BOF()=.F.
SKIP -1
     ThisForm.mygo1.Com4.Enabled=.T.
     ThisForm.mygo1.Com5.Enabled=.T.
ELSE
     ThisForm.mygo1.Com1.Enabled=.F.
     ThisForm.mygo1.Com2.Enabled=.F.
     GO TOP
ENDIF
ThisForm.mygo1.MyRefresh
    В код события Click для кнопки Com3 запишем:
DO FORM Find_ord.scx
    В код события Click для кнопки Com4 запишем:
IF EOF()=.F.
ThisForm.mygo1.Com1.Enabled=.T.
ThisForm.mygo1.Com2.Enabled=.T.
SKIP 1
     IF EOF()=.T.
ThisForm.mygo1.Com4.Enabled=.F.
ThisForm.mygo1.Com5.Enabled=.F.
GO BOTTOM
     ENDIF
ELSE
ThisForm.mygo1.Com4.Enabled=.F.
ThisForm.mygo1.Com5.Enabled=.F.
GO BOTTOM
ENDIF
ThisForm.mygo1.MyRefresh
    В код события Click для кнопки Com5 запишем:
GO BOTTOM
ThisForm.mygo1.Com1.Enabled=.T.
ThisForm.mygo1.Com2.Enabled=.T.
ThisForm.mygo1.Com4.Enabled=.F.
ThisForm.mygo1.Com5.Enabled=.F.
ThisForm.mygo1.MyRefresh
    В код события Click для кнопки Com6 запишем:
INSERT INTO order_view (key_salman, key_customer, key_model) ;
VALUES (order_view.key_salman, order_view.key_customer, order_view.key_model)
MyUpdate=1
ThisForm.mygo1.Com8.Enabled=.T.
ThisForm.mygo1.Com9.Enabled=.T.
ThisForm.Text1.Visible=.F.
ThisForm.mygo1.MyRefresh
    В код события Click для кнопки Com7 запишем:
Answer_d = MESSAGEBOX("Удалить данную запись ?", 4+32+256, "Вопрос")
IF Answer_d=6
     DELETE
     Del_=1
     SKIP 1
     IF EOF()=.T.
GO BOTTOM
     ENDIF
     MyUpdate=1
     ThisForm.mygo1.Com8.Enabled=.T.
     ThisForm.mygo1.Com9.Enabled=.T.
     ThisForm.mygo1.MyRefresh
ENDIF
    В код события Click для кнопки Com8 запишем:
=TABLEUPDATE(.T.,.T.)
ThisForm.mygo1.My_Show_s_u
    В код события Click для кнопки Com9 запишем:
=TABLEREVERT(.T.)
ThisForm.mygo1.My_Show_s_u
    В код события Click для кнопки Com10 запишем:
IF MyUpdate=1
    Answer_s_u = MESSAGEBOX("Сохранить изменения ?", 4+32, "Вопрос")
     DO CASE
CASE Answer_s_u=6 &&Да
     =TABLEUPDATE(.T.,.T.)
CASE Answer_s_u=7 &&Нет
     =TABLEREVERT(.T.)
     ENDCASE
     ThisForm.mygo1.My_Show_s_u
ENDIF
ThisForm.Release

    Далее приведен программный код для событий проектируемой формы. На рис. 9.9 показан внешний вид разрабатываемой формы и отношения между описанными ниже объектами, событиями и методами.


Рис. 9.9. Отношения между объектами, событиями и методами в разрабатываемой форме
    Код для события Load формы:
&& В администраторе ODBC необходимо иметь соединение auto к БД Auto_store
OPEN DATABASE Auto_store EXCLUSIVE
CREATE CONNECTION remote_01 DATASOURCE auto
DO CASE
     CASE LevDostup=1
=DBSETPROP('remote_01', 'CONNECTION', 'ConnectString', ;
'DSN=auto;UID=login_lev1;PWD=lev1')
     CASE LevDostup=2
=DBSETPROP('remote_01', 'CONNECTION', 'ConnectString', ;
'DSN=auto;UID=login_lev2;PWD=lev2')
     CASE LevDostup=3
=DBSETPROP('remote_01', 'CONNECTION', 'ConnectString', ;
'DSN=auto;UID=login_lev3;PWD=lev3')
     CASE LevDostup=4
=DBSETPROP('remote_01', 'CONNECTION', 'ConnectString', ;
'DSN=auto;UID=login_lev4;PWD=lev4')
     CASE LevDostup=5
=DBSETPROP('remote_01', 'CONNECTION', 'ConnectString', ;
'DSN=auto;UID=login_lev5;PWD=lev5')
ENDCASE
&& Создаем просмотры
a_v=ADBOBJECTS(a_view, 'VIEW')
IF a_v>>0
     prm=ASCAN(a_view, 'propmodel_view')
     lau=ASCAN(a_view, 'lauto_view')
     smn=ASCAN(a_view, 'sman_view')
     cus=ASCAN(a_view, 'cust_view')
     mod=ASCAN(a_view, 'model_view')
     ord=ASCAN(a_view, 'order_view')
ENDIF
IF prm=0
CREATE SQL VIEW propmodel_view CONNECTION remote_01 SHARE ;
AS SELECT model.key_model, model.name_model, model.swept_volume, ;
model.quantity_drum, model.capacity, model.torgue, model.top_speed, model.starting, ;
model.quantity_door, model.quantity_sead, model.length, model.width, ;
model.height, model.expense_90, model.expense_120, model.expense_town, ;
firm.name_firm, country.name_country,fuel_oil.name_fuel_oil, ;
tyre.name_tyre, body.name_body ;
FROM model ,firm, country, fuel_oil, tyre, body ;
WHERE model.key_firm=firm.key_firm ;
AND firm.key_country=country.key_country ;
AND model.key_fuel_oil=fuel_oil.key_fuel_oil ;
AND model.key_tyre=tyre.key_tyre ;
AND model.key_body=body.key_body
ENDIF
IF lau=0
CREATE SQL VIEW lauto_view CONNECTION remote_01 SHARE ;
AS SELECT automobile_passenger_car.key_model, automobile_passenger_car.date_issue, ;
automobile_passenger_car.cost, account.selled ;
FROM automobile_passenger_car, account ;
WHERE automobile_passenger_car.key_auto=account.key_auto
ENDIF
IF smn=0
CREATE SQL VIEW sman_view CONNECTION remote_01 SHARE ;
AS SELECT salesman.key_salman, ;
salesman.last_name+' '+salesman.first_name+' '+salesman.patronymic as sman ;
FROM salesman
ENDIF
SELECT * FROM sman_view INTO ARRAY Arcombo1
IF cus=0
CREATE SQL VIEW cust_view CONNECTION remote_01 SHARE ;
AS SELECT customer.key_customer, customer.name_customer ;
FROM customer
ENDIF
SELECT * FROM cust_view INTO ARRAY Arcombo2
IF mod=0
CREATE SQL VIEW model_view CONNECTION remote_01 SHARE ;
AS SELECT model.key_model, model.name_model ;
FROM model
ENDIF
SELECT * FROM model_view INTO ARRAY Arcombo3
IF ord=0
CREATE SQL VIEW order_view CONNECTION remote_01 SHARE ;
AS SELECT order_.key_order, order_.key_salman, order_.key_customer, order_.key_model ;
FROM order_
&& Устанавливаем таблицу order_ обновляемой
=DBSETPROP('order_view', 'View', 'Tables', 'order_')
&& Устанавливаем имена для обновления
=DBSETPROP('order_view.key_order', 'Field', 'UpdateName', 'order_.key_order')
=DBSETPROP('order_view.key_salman', 'Field', 'UpdateName', 'order_.key_salman')
=DBSETPROP('order_view.key_customer', 'Field', 'UpdateName', 'order_.key_customer')
=DBSETPROP('order_view.key_model', 'Field', 'UpdateName', 'order_.key_model')
&& Задаем простой уникальный ключ на основе одного поля таблицы Order_
=DBSETPROP('order_view.key_order', 'Field', 'KeyField', .T.)
&& Задаем обновляемые поля
=DBSETPROP('order_view.key_salman', 'Field', 'Updatable', .T.)
=DBSETPROP('order_view.key_customer', 'Field', 'Updatable', .T.)
=DBSETPROP('order_view.key_model', 'Field', 'Updatable', .T.)
&& Активизация процесса обновления
=DBSETPROP('order_view', 'View', 'SendUpdates', .T.)
&& Задаем сравнение временной метки всех полей записи,
&& расположенной на удаленном источнике данных
=DBSETPROP('order_view', 'View', 'WhereType', 4)
ENDIF
USE order_view
=CURSORSETPROP("Buffering", 5)
    Код для события Unload формы:
CLOSE DATABASES ALL
CLOSE TABLES ALL
    Добавим в форму метод Combo_init и запишем для него следующий код:
nnAddItem="ThisForm.Combo"+ALLT(cNum)+".AddItem"
nnArn="Arcombo"+ALLT(cNum)
FOR I=1 TO ALEN(&nnArn,1)
&nnAddItem(allt(&nnArn(i,2)))
ENDFOR
    Добавим в форму метод Refresh_list и запишем для него следующий код:
ThisForm.Combo1.Value=((ASCAN(Arcombo1,order_view.key_salman))+1)/2 {dt>ThisForm.Combo2.Value=((ASCAN(Arcombo2,order_view.key_customer))+1)/2 {dt>ThisForm.Combo3.Value=((ASCAN(Arcombo3,order_view.key_model))+1)/2{/dl>     Добавим в форму метод Show_sav_und и запишем для него следующий код:
MyUpdate=1
ThisForm.mygo1.Com8.Enabled=.T.
ThisForm.mygo1.Com9.Enabled=.T.
    Для события Click кнопки Propmodel (характеристика модели автомобиля) запишем следующий код:
glkey_mod=order_view.key_model
DO FORM prop_mod.scx
    Для события Click кнопки Lauto (список автомобилей) запишем следующий код:
glkey_mod=order_view.key_model
glname_mod=ALLT(Arcombo3(ThisForm.Combo3.Value,2))
DO FORM listauto.scx
    Для события Init всех элементов управления типа Combo Box (Combo1, Combo2 и Combo3) запишем следующий код:
cNum=RIGHT(This.Name,1)
ThisForm.combo_init
    Для события InteractiveChange элемента управления Combo1 запишем следующий код:
REPLACE order_view.key_salman WITH Arcombo1(ThisForm.Combo1.Value,1)
ThisForm.Text2.Refresh
ThisForm.Show_sav_und
    Для события InteractiveChange элемента управления Combo2 запишем следующий код:
REPLACE order_view.key_customer WITH Arcombo2(ThisForm.Combo2.Value,1)
ThisForm.Text3.Refresh
ThisForm.Show_sav_und
    Для события InteractiveChange элемента управления Combo3 запишем следующий код:
REPLACE order_view.key_model WITH Arcombo3(ThisForm.Combo3.Value,1)
ThisForm.Text4.Refresh
ThisForm.Show_sav_und

    Как видно из кода события Click кнопки lauto, сначала мы определяем значения кода (glkey_mod) и наименования (glname_mod) выбранной модели для дальнейшей выборки списка автомобилей по данной модели. После чего запускаем форму LISTAUTO (Список автомобилей). Похожая ситуация и с событием Click кнопки propmodel, где мы определяем значение кода (glkey_mod) модели и запускаем форму PROP_MOD (Характеристика модели автомобиля).
    Кратко опишем вызываемые формы.

Форма LISTAUTO (Список автомобилей).
    Некоторые формы используют данные из одной или нескольких таблиц. Для включения требуемых таблиц в описание формы выберите команду Data Environment из меню View главного меню Visual FoxPro. Откроется окно Data Environment, напоминающее область просмотра таблиц Конструктора базы данных.
    Для добавления таблицы следует выбрать команду Add в меню Data Environment, которое добавляется в главное меню Visual FoxPro, или в контекстном меню, появляющемся при нажатии правой кнопки мыши. После этого выберите требуемую таблицу из диалогового окна Add Table or View.
    Обратите внимание, что можно либо использовать несколько таблиц и определять отношения между ними в самой форме, либо использовать готовые многотабличные представления данных.
    Для установления отношения между двумя таблицами нажмите на имя поля в главной таблице и перенесите его в подчиненную таблицу. При этом между таблицами появится линия, как показано на рис. 9.10. Заметим, однако, что для этого необходимо заранее определить соответствующие индексы.


Рис. 9.10.

    В форме LISTAUTO мы используем данные из представления lauto_view, и одним из возможных вариантов решения задачи является включение представления lauto_view в окружение формы (Data Environment) вышеизложенным способом. И этот вариант был бы не худшим. Однако мы в своем примере поступаем по-другому. А именно, включаем в событие, имеющее место непосредственно перед созданием формы (Form.Load), запрос к представлению lauto_view с выборкой данных по текущей модели, поместив результат запроса в курсор lauto. Подобный вариант приемлем только для просмотра данных. Дело в том, что данные в курсоре редактированию не подлежат.
    В событие Load формы запишем следующий код:

SELECT lauto_view.date_issue, lauto_view.cost, ; lauto_view.selled ;
FROM lauto_view ;
WHERE lauto_view.key_model=glkey_mod ;
INTO CURSOR lauto

    Далее, посредством панели инструментов Form Controls, в форму LISTAUTO включаем объект - таблицу (Grid).
    В свойство источника данных (RecordSource), к которому привязан элемент управления Grid, помещаем имя курсора - lauto. После чего форму уже можно запускать на выполнение. И результат будет положительным. Хотя мы думаем, наш читатель вряд ли из тех, кого удовлетворит подобный результат. Дело в том, что заголовки колонок примут значения наименований полей курсора, элемент управления для отображения значений во всех колонках будет текстовым, что не всегда удобно, и т. д.
    Поэтому мы несколько усовершенствуем данную форму.
    На вкладке Layout для элемента управления Grid задаем количество объектов Column (свойство ColumnCount = 3), ликвидируем отображение столбца маркеров удаления (свойство DeleteMark = .F.), задаем тип полос прокрутки (свойство ScrollBars = 2 - только вертикальная полоса), задаем высоту заголовков столбцов (свойство HeaderHeight = 25) и высоту строк в элементе управления Grid (свойство RowHeight = 25).
    На вкладке Data для каждой колонки таблицы Grid.Column задаем источник данных, к которому привязывается объект:

Grid.Column1.ControlSours=lauto.date_issue
Grid.Column2.ControlSours=lauto.cost
Grid.Column3.ControlSours=lauto.selled

    Для третьей колонки указываем элемент управления в объекте Column - CurrentControl = CheckBox, который будет использоваться для отображения значений активной ячейки. Предварительно поместив его посредством перетаскивания элемента CheckBox из панели управления Form Control в колонку таблицы. Для этой же колонки позволяем свойству CurrentControl распространяться на все ячейки объекта Column (свойство Sparse = .F.).
    На вкладке Layout для каждого заголовка колонки таблицы Grid.Column.Header, задаем текст (свойство Grid.Column1.Caption = Дата выпуска, Grid.Column2.Caption = Стоимость, Grid.Column3.Caption = Продажа), а вид выравнивания текста задаем по центру (свойство Alignment = 2).
    После чего наша форма LISTAUTO будет иметь вид, показанный на рис. 9.11.


Рис. 9.11. Форма "Список автомобилей" в Конструкторе формы

Форма PROP_MOD (Характеристика модели автомобиля).
    В форме PROP_MOD мы используем данные из представления propmodel_view, предварительно включив в событие, имеющее место непосредственно перед созданием формы (Form.Load), запрос к данному представлению с выборкой данных по текущей модели и поместив результат запроса в курсор propmodel.
    В событие Load Формы запишем следующий код:

SELECT * FROM propmodel_view ;
WHERE propmodel_view.key_model=glkey_mod ;
INTO CURSOR propmodel

    Далее с помощью панели инструментов Form Controls в форму PROP_MOD последовательно включаем объекты TextBox для всех полей курсора propmodel. Для каждого из них задаем источник данных (свойство ControlSours = <<имя_курсора.имя_поля>>). На рис. 9.12. показана форма PROP_MOD.


Рис. 9.12. Форма "Характеристика модели автомобиля" в Конструкторе формы

    На примере данной формы нам бы хотелось показать, каким образом можно определять порядок перехода по объектам. Дело в том, что каждый объект имеет свой порядковый номер, по умолчанию совпадающий с порядком добавления объектов при создании формы. И при переходе с одного объекта на другой с помощью клавиатуры, порядок следования соответствует порядковым номерам. Однако это не всегда совпадает с логикой ввода данных.
    Изменять порядок следования полей можно двумя способами: интерактивно (Interactive) и с помощью списка (By List). Требуемый метод выбирается во вкладке Forms в раскрывающемся списке Tab Ordering диалогового окна Options (см. рис. 9.5).
    Если установлен метод интерактивного изменения порядка активизации полей в форме, то при выборе команды Tab Order из меню View главного меню Visual FoxPro в верхнем левом углу каждого объекта появится его порядковый номер (рис. 9.13).


Рис. 9.13. Интерактивный метод изменения порядка активизации объектов в форме

    Для изменения порядка перехода нажмите клавишу Shift и кнопку мыши над нужным объектом. При этом номер объекта будет убран, а все остальные объекты перенумерованы. При повторном нажатии объект будет помещен в конец списка. Очевидно, что применение данного метода для большой формы займет довольно много времени.
    Более быстрый метод состоит в нажатии на первый объект в последовательности без нажатия на клавишу Shift. При этом объекту присваивается номер один, а все остальные номера исчезают. Затем, нажимая Shift, обойдите все остальные объекты в нужном порядке. Когда все сделано, нажмите кнопку Reorder.
    Второй способ изменения порядка активизации полей - с помощью списка. В этом случае после выбора пункта меню Tab Order откроется диалоговое окно, показанное на рис. 9.14.


Рис. 9.14.

    На левой стороне окна появляется прокручиваемый список всех объектов формы. Нажимая и перетаскивая кнопки слева от имен, можно как угодно изменять порядок активизации объектов.
    Как видно из рис. 9.14, напротив каждого объекта находится маленькая пиктограмма, служащая для облегчения определения типа объекта. Кроме того, кнопки By Row и By Column позволяют быстро автоматически определить требуемый порядок, хотя на сложных формах их применение может и не дать желаемого результата. Добившись нужного порядка активизации объектов, нажмите кнопку OK.
    В этой форме мы допустили возможность транспортировки данных в Microsoft Word и Microsoft Excel (с построением диаграммы). Как мы это сделали, будет рассказано в следующей главе.
    Описанные в данной главе формы используются в примере SAMPLE.EXE, поставляемом на дискете.

Создание формы "Прием заказов" на Access

    Мы уже неоднократно упоминали о широких возможностях Access как хорошего помощника в нелегком труде программиста. Здесь мы сосредоточимся на создании относительно сложной формы, при проектировании которой только Мастерами не обойтись.
    Так же как в Visual FoxPro, основным инструментом для проектирования формы в Access является Конструктор формы, показанный на рис. 9.15. Для создания новой формы с помощью Конструктора формы в контейнере БД активизируйте вкладку Форма и нажмите кнопку Создать. В появившемся диалоговом окне Новая форма выберите в списке пункт Конструктор.


Рис. 9.15. Конструктор формы в Access 7.0

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

'Создание переменных рабочего пространства, которые будут использоваться
'для начала и конца транзакций
Public mywksp As Workspace
Public mywksp1 As Workspace
'Создание переменных наборов данных
Public rstOrd As Recordset
Public rstcust As Recordset
Public rstAc As Recordset

    Объекты типа Workspace необходимы нам для начала, завершения и отката транзакций. Объекты типа Recordset будут использоваться для работы с необходимыми нам данными, так как в некоторых случаях нам придется использовать формы, которые не имеют источника данных и соответственно не могут иметь связанных с какими-либо полями элементов управления. Например, форма "Прием заказов" не имеет источника данных.
    Прием заказа начинается с нажатия на кнопку с соответствующим названием - "Прием заказа". После этого выполняется следующий код:

Private Sub Кнопка1_Click()
'Это строка условия поиска продавца в таблице Salesman
     Dim strSearch As String
'Инициализируем переменную рабочего пространства
     Set mywksp = DBEngine.Workspaces(0)
' Инициализируем переменную набора данных
     Set rstOrd = mywksp.Databases(0).OpenRecordset("order_", _ dbOpenDynaset)
'начинаем транзакцию
     mywksp.BeginTrans
     'Строка для поиска кода продавца создается из названия поля
     ' Last_name и login текущего пользователя приложения
     strSearch = "Last_name=" & Chr(34) &_ mywksp.UserName & Chr(34)
     'Добавляем запись
     rstOrd.AddNew
     'Идентификатор продавца заносится в новую добавленную запись
     rstOrd!key_salman = DLookup("key_salman",_"salesman", strSearch)
     rstOrd.UPDATE
     Me!cmbCust.Enabled = True
     Me!txtFam.Enabled = True
     End Sub

    В результате выполнения этого кода в таблицу order_ вносится одна запись, но, обратите внимание, началась транзакция. Эти изменения еще не окончательные, мы можем при определенных обстоятельствах сделать откат. При работе программы предполагается, что каждый продавец, оформляющий сделку, будет запускать приложение со своим пользовательским именем, а с помощью этого имени будет находиться идентификатор, который и попадет в новую запись таблицы order_. Для этого мы используем свойство Username объекта mywksp.

strSearch = "Last_name=" & Chr(34) & mywksp.UserName &_ Chr(34)
     'Добавляем запись
     rstOrd.AddNew
     'Идентификатор продавца заносится в новую
     'добавленную запись
     rstOrd!key_salman = DLookup("key_salman",_ "salesman", strSearch)

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

Private Sub cmbNcl_Click()
Set mywksp1 = DBEngine.Workspaces(0)
Set rstcust = mywksp1.Databases(0).OpenRecordset("customer", _ dbOpenDynaset)
mywksp1.BeginTrans
DoCmd.OpenForm "Клиенты", acNormal, , , acAdd
End Sub

    Эта транзакция завершится уже в форме "Клиенты". При этом, несмотря на все прекрасные перспективы дальнейшего использования данных о клиенте, существует возможность отката и в этом случае, например, если клиент вдруг неожиданно скроется, не успев назвать свой адрес и факс. Без них он для вас неинтересен.
    Если клиент настаивает на том, что он совершал у вас покупку, то можно поискать его в вашей базе данных с помощью комбинированного списка. Применение фильтра ускорит поиск, фильтр можно установить с помощью единственного текстового поля, которое присутствует в нашей форме. Этого мы добиваемся с помощью кода, который выполняется при наступлении события LostFocus (Потеря фокуса):

Private Sub txtFam_LostFocus()
     If Len(Trim(txtFam)) >> 0 Then
Me!cmbCust.RowSource = "SELECT DISTINCTROW _
customer.key_customer,customer.name_customer,_
customer.address, customer.tel FROM customer Where _ customer.name_customer
like " & Chr(34) & Trim(txtFam) &_
"*" & _ Chr(34) & ";"
     Else
Me!cmbCust.RowSource = "SELECT DISTINCTROW _
customer.key_customer,customer.name_customer,
_ customer.address, customer.tel FROM customer ;"
     End If
End Sub

    В данном коде используется свойство комбинированного списка - динамически изменять содержимое списка значений, другими словами, возможность использовать свойство RowSource для чтения и записи.
    Выбрав клиента, необходимо выбрать и автомобиль для него. Для этого предназначена форма "Наличие автомобилей, дата выпуска, цена", которая вызывается с помощью кнопки "Выбор автомобиля". Используется метод OpenForm объекта DoCmd:

Private Sub cmbCAuto_Click()
     DoCmd.OpenForm _
"Наличие автомобилей, дата выпуска, цена", acNormal
End Sub

    Форма "Наличие автомобилей, дата выпуска, цена" имеет установленное свойство RecordSource (Источник данных). Источником данных для этой формы служит запрос с одноименным названием. Для поиска автомобилей предназначены два комбинированных списка: с помощью первого вы отфильтровываете данные по названию производителя, а с помощью второго - по моделям. После этого из ограниченного списка данных вы легко выбираете автомобиль для вашего клиента. При этом используется замечательное свойство форм использовать фильтр и динамически менять его. Это становится ясно из следующего фрагмента кода. Как уже неоднократно отмечалось, с отфильтрованными данными Access работает значительно быстрее, особенно если вы их не изменяете, а просто просматриваете.

Private Sub name_model_AfterUpdate()
     If Len(Trim(name_model)) >> 0 Then
Me.FilterOn = False
Me.Filter = "name_model=" & Chr(39) _
& Me!name_model & Chr(39) & _
"and name_firm=" & Chr(39) & Me!name_firm & Chr(39)
Me.FilterOn = True
     End If
End Sub

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

Private Sub Кнопка9_Click()
     DoCmd.OpenForm "Подробно о модели", _
acNormal, , "[Name_model]=" & Chr(39) & _
Me![name_model] & Chr(39), acReadOnly
End Sub

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

Private Sub Кнопка8_Click()
     Dim FStr As String
     FStr = "name_customer=" & Chr(39) &_ Forms![Прием заказов].cmbCust & Chr(39)
     Set rstAc = mywksp.Databases(0).OpenRecordset("account", _ dbOpenDynaset)
     rstAc.AddNew
     rstAc!key_customer = DLookup("key_customer", _ "customer", FStr)
     rstAc!key_auto = Me!key_auto
     rstAc!date_write = Now()
     rstAc.UPDATE
     Forms![Прием заказов].cmbAc.Enabled = True
     DoCmd.Close acForm, "Наличие автомобилей,_дата выпуска, цена"
End Sub

    После возврата в форму "Прием заказов" вы принимаете окончательное решение о продаже автомобиля. Пользователь имеет последнюю возможность отказаться. После нажатия на кнопку "Оформление счета" транзакция завершается.

mywksp.CommitTrans
rstOrd.Close
rstAc.Close
mywksp.Close
Me![Кнопка1].Enabled = True
DoCmd.OpenTable "account"
DoCmd.GoToRecord acTable, "account", acLast

    Обратите внимание на то, что объекты rstOrd, rstAc и mywksp закрываются с помощью имеющегося у них метода Close. Если их не закрывать, то при нескольких повторных использованиях формы "Прием заказов" появится сообщение, что Access больше не может открыть таблицу.
    Если покупатель все-таки решил не покупать автомобиль, то используйте кнопку "Отказ от заказа". При этом произойдет откат транзакции, и ни в одну из таблиц новые данные внесены не будут. Правда, есть один момент: если вы используете поле типа счетчик, то внутри базы данных его номер прирастет, поэтому при следующей попытке добавить запись, его значение будет больше на значение приращения.

Private Sub cmbRback_Click()
     mywksp.Rollback
     rstOrd.Close
     rstAc.Close
     mywksp.Close
     Me![Кнопка1].Enabled = True
     Me!cmbCust.Enabled = False
     Me!txtFam.Enabled = False
     Me!cmbAc.Enabled = False
End Sub

9.3. Разработка управляющего меню

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

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

Разработка меню в Visual FoxPro

    Для разработки меню в Visual FoxPro проще всего использовать Конструктор меню (Menu Designer). Интересно отметить, что с помощью Конструктора меню можно не только разрабатывать меню для пользовательского приложения, но и настраивать меню Visual FoxPro для наиболее эффективной работы программиста.
    В проекте выберем вкладку Other, найдем заголовок Menus и дадим команду New. На экране появится окно Конструктора меню, внешний вид которого приведен на рис. 9.16. На этом же рисунке поясняются основные элементы этого Конструктора. Обратите внимание, что Конструктор меню принципиально отличается от других визуальных средств проектирования Visual FoxPro. После того как мы опишем с его помощью меню, необходимо сгенерировать программу. Файл с этой программой будет иметь расширение MPR, а файл после компиляции - MPX. Этот файл и следует запускать для работы с созданным меню. Недаром раньше такой инструмент программиста так и назывался - Генератор. Для генерации программы в меню Menu необходимо выбрать команду Generate.


Рис. 9.16. Конструктор меню в Visual FoxPro

    Если в вашу задачу входит настройка главного меню Visual FoxPro, то самое подходящее в этом случае решение - в меню Menu выбрать команду Quick Menu. После этого в Конструкторе меню в качестве заготовки мы получаем копию существующего меню Visual FoxPro, с которым можем проделывать любые изменения, включая удаление каких-либо меню и команд, добавление новых и т. д. Не стоит слишком усердствовать с удалением, помните, что, изымая какую-либо команду из меню, вы теряете предусмотренную наличием этой команды функциональность. Например, удалив меню Edit, вы не сможете использовать стандартные возможности переноса, копирования и поиска при работе с текстом. В соответствии со сложившимися правилами включать свои собственные меню в главное меню следует перед меню Help, которое должно оставаться последним.
    Для разработки меню пользовательской программы нам придется вручную описать все необходимые пункты. Рассмотрим последовательность, в которой следует выполнить эти действия.
    Запишем заголовки пользовательского меню в столбце Prompt. Если после выбора меню на экране должен появиться список команд, выберем в столбце Result пункт Submenu и щелкнем на появившейся справа кнопке Create. Мы окажемся на следующем уровне меню, где запишем заголовки команд, входящих в это подменю. Если структура нашего меню предусматривает еще один уровень вложения, повторите указанные действия. Для перехода с уровня подменю на верхний уровень необходимо выбрать его из комбинированного списка Menu Level. Если вы хотите выделить группы команд, поместите в столбце Prompt в строке, разделяющей группы команд меню, знаки \-. Вы всегда можете проверить, как визуально будет выглядеть ваше меню на экране, нажав клавишу Preview.
    Для быстрого перемещения по меню можно назначить для его команд "горячие клавиши". Нажимая соответствующую алфавитную клавишу, пользователь может сразу выполнить нужную команду или перейти в какое-либо меню. Выбранная клавиша в меню подчеркивается. Для задания такой клавиши перед нужной буквой необходимо поставить знаки \<<. Например, если мы хотим, чтобы команда Счет выполнялась при нажатии клавиши С, мы должны заголовок этой команды написать в виде \<<Счет. После этого в меню буква С будет подчеркнута. Естественно, в одном меню не может быть несколько команд, использующих в качестве "горячих клавиш" одну и ту же букву. К сожалению, при использовании русских заголовков для меню не все так просто и, чтобы можно было реализовать описанные возможности, необходимо прочитать следующий абзац.
    Наиболее часто выполняемым командам в меню можно присвоить клавиатурные комбинации. Для этого напротив соответствующего заголовка необходимо щелкнуть мышкой на кнопке в колонке Options. На экране появится диалоговое окно Prompt Options, с помощью которого для меню можно задать дополнительные условия. Внешний вид этого окна с необходимыми комментариями приведен на рис. 9.17. Щелкнем на поле проверки Shortcut, появится диалоговое окно Key Definition. Нажмем нужное сочетание клавиш. Их обозначение появится в поле Key Label. Символы в поле Key Text будут написаны рядом с командой меню, их можно отредактировать. Командам меню принято присваивать клавиатурные комбинации, начинающиеся с клавиши Ctrl. В клавиатурных комбинациях Visual FoxPro использует скан-код клавиши, поэтому не имеет значения текущий регистр и не важно, включена ли русская или латинская раскладка клавиатуры. Если вы используете знаки \<< для меню верхнего уровня, Visual FoxPro автоматически поместит в поле Key Label сочетание клавиш Alt и выделенной этими знаками буквы. Если буква русская, при запуске меню произойдет ошибка. Поэтому надо открыть окно Key Definition и вручную в поле Key Label исправить русскую букву на соответствующую той же клавише латинскую.


Рис. 9.17.

    Вы можете регулировать доступ к тем или иным командам меню с помощью поля проверки Skip For, находящегося в диалоговом окне Prompt Options. Если вы щелкнете на этом поле, появится уже знакомое вам окно Построителя выражения, в котором можно сформировать условие доступа к этой команде меню. Если условие будет равно .F., команда будет доступна, если .T. - недоступна. Наиболее часто эта возможность используется для регулирования доступа различных пользователей прикладной программы к тем или иным ее функциям.
    В связи с тем, что Visual FoxPro поддерживает редактирование на месте OLE-объектов, с помощью поля проверки Negotiate мы можем для меню верхнего уровня указать, где меню будет располагаться после активизации OLE-сервера. По умолчанию действует установка None - меню убирается при редактировании OLE-объекта.
    Для пояснения назначения той или иной команды меню с помощью поля проверки Message можно задать текст, который будет появляться в строке состояния. Для задания такого текста используется Построитель выражения. Если вы непосредственно набираете текст, не забудьте поместить его в кавычки.
    В окне Prompt Options осталось еще одно поле проверки - Pad Name, о котором можно было бы ничего и не говорить, если бы мы не использовали русские заголовки для меню. При проектировании меню Конструктор автоматически присваивает имена меню на основании их заголовков. В то же время Visual FoxPro, как и его предшественники, на дух не переносит русских названий в меню. Не будем раздражаться по столь мелкому поводу, щелкнем на поле проверки Pad Name и изменим русское название на латинское.
    Для выполнения каких-либо действий при выборе пользователем команд меню необходимо в колонке Result для каждой команды меню назначить команду Visual FoxPro, функцию или процедуру. Для назначения команды Visual FoxPro в столбце Result выберите пункт Command и наберите соответствующую команду в текстовом поле справа. Например, DO Log_user. Если указанная в этой команде процедура находится в блоке Cleanup Конструктора меню (о нем мы расскажем чуть позднее), то команду следует записать в виде: DO Log_user IN Main, где Main - это имя файла меню.
    Для задания при выборе команды меню выполнения какой-либо процедуры, в случае, когда меню не имеет подменю, необходимо в колонке Result выбрать пункт Procedure. Щелкнуть на кнопке Create и в появившемся окне поместить необходимый код.
    В ряде случаев с помощью команд меню могут выполняться какие-то сходные для всех команд этого меню действия, не требующие написания большого объема кода. В этом случае мы можем задать при выборе команды меню выполнение одной процедуры для меню, имеющего подменю. Для этого все команды меню должны в колонке Result иметь пункт Bar#, который не предусматривает привязки к команде какого-то действия. В списке Menu Level выберем соответствующий уровень меню. Из меню View Visual FoxPro выберем команду Menu Options, после чего на экране появится одноименное диалоговое окно. Нажмем кнопку Edit и наберем код в соответствии с приведенным ниже шаблоном:

<<Код, выполняемый при выборе любой из команд>>
DO CASE
     CASE BAR() = 1
     <<Код, выполняемый для первой команды в меню>>
     CASE BAR() = 2
     << Код, выполняемый для второй команды в меню>>
...
ENDCASE

    Функция BAR() возвращает номер выбранной команды меню. Вы можете при необходимости присвоить свои номера командам меню, использовав поле справа от колонки Result.
    Общие установки для системы меню можно выполнить, выбрав из меню View Visual FoxPro команду General Options. После появления диалогового окна с таким же названием, приведенного на рис. 9.18, можно написать программный код, который будет выполняться перед расположением меню на экране, выбрав поле проверки Setup. Процедуры, которые вы используете для выполнения команд меню, и действия, которые необходимо выполнить после исчезновения меню с экрана, можно записать, выбрав поле проверки Cleanup. С помощью группы кнопок выбора Location можно задать условия расположения меню после его запуска:


Рис. 9.18.
  • Кнопка Replace - заменяет существующую систему меню.
  • Кнопка Append - добавляет данное меню к существующей системе.
  • Кнопка Before - располагает меню перед указанным в появляющемся справа списке меню.
  • Кнопка After - располагает меню после указанного в появляющемся справа списке меню.

    После запуска пользовательского меню вернуться к главному меню Visual FoxPro можно с помощью команды SET SYSMENU TO DEFAULT.
    Если ваша пользовательская программа будет работать в виде самостоятельного модуля (EXE-файл) и вы планируете использовать меню в качестве главной программы вашего приложения, разместите команду READ EVENTS в блоке процедур Cleanup, а для команды меню, обеспечивающей прекращение работы приложения, задайте команду CLEAR EVENTS.

Разработка меню в Access

    Для создания меню в Access используются два способа. Первый способ - это использование надстройки Построитель меню. Второй - использование Конструктора макросов. Первый способ наглядней и проще, но в итоге вы получаете тот же набор макросов, записанных с определенными параметрами и аргументами. Любое построенное меню вы можете подключить к любой форме или отчету с помощью свойства формы (отчета) Строка меню. Таким образом, в вашем приложении при переключении с одной формы на другую на экране будет отображаться меню, необходимое для решения данной задачи.
    Вызовите надстройку Построитель меню с помощью команды Надстройка меню Сервис. На экране появится диалоговое окно, в котором вам будет предложено отредактировать уже существующее, создать новое или даже удалить существующее меню, как это показано на рис. 9.19.


Рис. 9.19.

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


Рис. 9.20.

    Следующим окном, если вы выбрали кнопку OK в диалоге выбора шаблона, будет окно Конструктора меню. Это окно состоит из двух частей, как показано на рис. 9.21. В верхней части мы выбираем или вновь создаем название меню, команды меню или подменю. При работе с командой меню или подменю можно присвоить им макрокоманду, которая будет выполняться при ее выборе.


Рис. 9.21.

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

Sub Прием заказа_открыть()
     DoCmd.OpenForm "Прием заказа"
End Sub

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

Глава 8 || Содержание || Глава 10