Каскадное обновление связанных списковИсточник: accessoft
В опубликованной ранее статье "Справочники" кроме описания процесса и краткой теории создания справочных форм и таблиц также приводился пример использования функции AppendLookupTable, позволяющей добавлять отсутствующее значение в список. Добавление происходило автоматически после прямого ввода нужного значения в список. Однако эта функция позволяла добавлять данные только в одиночный список. На практике же довольно часто бывает необходимость подобного автозаполнения, но уже связанных (зависимых) списков. О варианте решения - доработке функции AppendLookupTable и пойдет речь в этой статье. Для рассмотрения примера создадим три справочных таблицы:
Индексируем поля таблиц для получения возможности автоматического отслеживания уникальности значений. В противном случае может случится, что одно и то же значение в справочнике повторяется. Для этого открываем таблицу в режиме конструктора, жмем на значок в меню "Индексы" (значок молнии). В появившемся диалоговом окне в поле "Индекс" пишем название индекса (любое), например "Страна". Затем в поле "Имя поля" выбираем из списка "Страна" и в нижней части формы выбираем из списка "Уникальный индекс" - Да. Теперь при попытке ввести уже существующее значение появится соответствующее сообщение и ввод будет заблокирован. В таблицах "Производители" и "Товары" индекс должен быть составной, так как нужно отслеживать уникальность пары Страна - Производитель. Ведь разные страны могут выпускать одинаковые товары. Пример создания составного индекса:
Связываем отношением один ко многим справочные таблицы: Страны - Производители - Товары. Таким образом получили систему из трех связанных справочников, в которой кроме данных устанавливаются так же зависимости между таблицами. Создадим так же для демонстрации примера автозаполнения две рабочих таблицы: Заказы и Список товаров.
Связываем таблицы "Заказы" и "Список товаров" соотношением один ко многим по полям "id_Заказ" (на один заказ может быть много товаров).
Во втором случае нужно предусмотреть процедуру, препятствующую вводу данных в форму "Производители", если не заведены данные в форме "Страны". Иначе получим не связанную запись. Для этого служит процедура события формы Enter (Вход).
Как видим, при попытке ввести данные в форму, когда не введен ключ от главной формы
появляется сообщение об ошибке и фокус ввода переносится на поле главной формы. Аналогичная процедура блокировки сделана и для подчиненной табличной формы на форме "Заказы". На форме "Заказы" присутствуют два списка: Страны и Производители. Причем второй связан с первым через ссылку на него в запросе (источнике своих данных). Откройте форму "Заказы" в конструкторе и посмотрите на источник строк списка Производитель(id_Компании). На поле id_Страны установлено условие отбора
Аналогично и на поле со списком табличной подчиненной формы "subЗаказы"
Необычное в этих ссылках - это их обработка через функцию Eval(). Но об этом чуть позже. Теперь рассмотрим реализацию каскадного заполнения списков. Как уже говорилось, задача состоит в том, чтобы при автозаполнении связанного списка кроме внесения нового значения в таблицу - источник, нужно так же внести и ключевое значение от главного списка. Иначе получим не связанную запись. По этой причине вводится новая переменная ctl1. Функция NotInList дорабатывалась с учетом, чтобы через нее можно было добавлять отсутствующие значения ив обычные списки, а не только связанные. В этом случае при вызове функции в качестве второго аргумента (главного списка) нужно повторить ссылку на список. Это будет указанием, что добавлять данные нужно только в один список. Вызов функции автозаполнения списков нужно делать из процедуры NotInList (отсутствие в списке). В этом случае после проверки условия (каскадное или обычное добавление) отсутствующее значение добавляется так же как в старой функции
Если нужно добавить данные в связанный список, тогда при вызове функции AppendLookupTable указываем в качестве аргументов два списка.
А теперь по поводу Eval(). Дело в том, что процедура добавления отсутствующего значения в таблицу - источник списка происходит при помощи объектной модели DAO. Если в строке запроса встретится выражение типа Forms!Заказы!id_Страны - будет сообщение об ошибке типа: "Требуется параметр". Потому, что DAO, знать ничего не знает, об открытых формах, именно по этому и ругается. Ему дали строку SQL, он пытается открыть соответствующий рекордсет. Пытается найти поле таблицы с именм Forms! и разумеется не находит. Поэтому, когда используется работа с запросом при помощи DAO, то ссылки на элементы форм нужно оформлять через Eval(). Например так:
Или вместо имени приводить сам текст запроса, и в нем указывать ссылку:
Эти рекомендации справедливы и при работе с объектной моделью ADO. Часто первое, на чем спотыкаются те, кто впервые решил перевести свой проект с mdb на adp - это ошибки при выполнении запросов, где есть ссылки на элементы формы. Ведь теперь обработка данных происходит на сервере, на котором нет никаких форм. Впрочем, это уже совсем другая тема, к данной статье не имеющая отношения - переход от mdb к adp. В заключение остановлюсь еще на одном вопросе, который так же часто задают начинающие разработчики: как отключить стандартные сообщение Access? Например, при использовании процедуры на удаление записи
если пользователь нажмет "Отмена" будет сообщение: "Прервано выполнение макрокоманды DoMenuItem".
Но в этом случае отключаются все сообщения, а не только "Прервано выполнение макрокоманды DoMenuItem". И если при выполнении "огражденной" процедуры возникнет какая либо другая ошибка - никто об этом "не узнает". А ведь ошибки бывают и фатальными, с "вылетом" из программы. Еще хуже, если увлекшись включением/отключением стандартных сообщений разработчик забудет потом в коде программы включить их. Поэтому более разумный вариант - создать свою процедуру обработки ошибок, в которой в зависимости от кода ошибки можно выводить свои сообщения (или не выводить ничего). Пример такой процедуры (на удаление записей)
Для ее работы в глобальном модуле Constants введена переменная Public ErrNum As Long. Через нее передается номер ошибки. Сама же процедура удаления записи выглядит так (для формы "СПРАВОЧНИК страны производители товары"):
Здесь возможны две ошибки:
Первую ошибку можно просто игнорировать - Err.Clear, а вот в случае возникновения второй - не помешает вывести соответствующее сообщение, чтобы было понятно, почему нельзя удалять. Таким образом, можно создавать свои процедуры обработки ошибок выполнения. Чтобы узнать номера ошибок, можно воспользоваться Err.Number. Например, при отладке новой процедуры включить в нее обработчик ошибок:
|