VBA в Microsoft Access, MS Office, Програмні керівництва, статті

Більшість додатків, поширюваних серед користувачів, містить той чи інший обсяг коду VBA (Visual Basic for Applications). Оскільки VBA є єдиним засобом для виконання багатьох стандартних завдань в Access (робота зі змінними, побудова команд SQL під час роботи програми, обробка помилок, використання Windows API і т. д.), багатьом розробникам рано чи пізно доводиться розбиратися в тонкощах цієї мови. У цій главі розглядаються деякі аспекти VBA, які зазвичай не згадуються в підручниках з Access. У ній докладно розглядається робота з внутрішніми рядками (тобто рядками, що знаходяться усередині інших рядків), часто використовуваними при динамічному побудові команд SQL і інших виразів. Два розділу присвячені створенню стека процедур, що дозволяє стежити за тим, яка процедура виконується і звідки вона була викликана. У другому розділі на базі стека будується файл журналу, який дозволяє дізнатися, скільки часу витрачається на виконання тієї чи іншої процедури. Потім ми розглянемо команду DoEvents, яка дає можливість системі Windows обробляти повідомлення під час виконання програми. У наступній групі з чотирьох рішень описана методика заповнення списків функціями зворотного виклику, передачі масивів в параметрах функцій, сортування масивів і заповнення списку результатами пошуку. Глава завершується прикладами використання об’єктів DAO (Data Access Objects) для читання і записи властивостей і перевірки існування об’єктів в додатках.

Побудова рядків з внутрішніми лапками


Проблема


Ви намагаєтеся визначати критерії для текстових полів і полів даних, але якою б синтаксис ви не пробували, результат виявляється одним і тим же – помилки або невірні результати. Що не так?

Рішення


Подібні проблеми зазвичай виникають в Access при побудові строкових виразів, що містять інші рядки, наприклад, при використанні доменних функцій (DLookup, функціяDLookup, DMax, функціяDMax, DMin, функціяDMin і т. д.), при побудові команд SQL під час роботи програми і при використанні методів Find, методFind (FindFirst, FindNext, FindPrevious і FindPrevious, методFindLast, методFindLast) з наборами записів. Всі рядки повинні бути укладені в лапки, а присутність цього символу в інших рядках викликає проблеми. Багато програмістів мучаться з цими конструкціями, але насправді проблема не так вже складна. У цьому розділі пояснюється суть проблеми і пропонується універсальне рішення.

Почнемо з практичного прикладу побудови строкових виразів під час роботи програми. Відкрийте базу даних 07-01.MDB і запустіть форму frmQuoteTest. На цій формі, показаної на рис. 7.1, вводяться критерії пошуку. Після клацання на кнопці Search процедура, пов’язана з кнопкою, генерує команду SQL і відповідним чином задає властивість Джерело рядків (RowSource) для списку в нижній частині форми. Команда SQL виводиться в текстовому полі.

 

Рис. 7.1. Тестова форма frmQuoteTest з виділеним підмножиною записів

Щоб ви краще зрозуміли, як працює форма, виконайте наступну вправу.

1.В текстовому полі First Name введіть символ A. При натисканні клавіші Enter форма будує відповідну команду SQL і фільтрує вміст списку. Зверніть увагу: введене вами значення в команді SQL укладено в лапки (як показано на малюнку).

2.В текстовому полі Birth Date введіть рядок 3/13/60. Форма знову фільтрує вміст списку, скорочуючи його до запису. У команді SQL введена дата повинна бути укладена між знаками # # (решітка).

3.Щелкніте на кнопці Reset, щоб видалити всі дані з чотирьох текстових полів. Список знову заповнюється усіма записами джерела. Введіть 8 в текстовому полі ID і натисніть клавішу Enter. Зверніть увагу: на цей раз введене значення в команді SQL не укладається між символами-обмежувачами.

Коментар


Все, що робилося вище, повинно було привернути вашу увагу до одного важливого факту: стаючи частиною висловлювання, різні типи даних повинні полягати в різні обмежувачі. Наприклад, при використанні функції DLookup для пошуку запису, у якій поле LastName містить рядок Smith, вираз має виглядати так:

[LastName] = “Smith”

При відсутності лапок Access вирішить, що ви посилаєтеся на змінну з ім’ям Smith.

Дати полягають не в лапки, а в спеціальні обмежувачі #. Наприклад, вираз для пошуку записи, у якої в поле BirthDate зберігається дата 16 травня 1956 року, виглядає так:

[BirthDate] = #5/16/56#

Якщо прибрати обмежувачі, Access буде вважати, що ви намагаєтеся розділити 5 на 16, а потім на 56.

Числові дані використовуються без обмежувачів. Наприклад, при пошуку запису з полем ID, рівним 8, можна використовувати такий вираз:

[ID] = 8

Access правильно зрозуміє, що ви намагаєтеся зробити.

В Access часто виникає необхідність у створенні рядків, що визначають критерії пошуку. Оскільки ядру Jet, ядроJet нічого не відомо про мову Access Basic і його змінних, для застосування критерію пошуку необхідно задати фактично використовуються дані. Інакше кажучи, створюється рядковий вираз має містити значення змінних, а не їх імена.

Будь-яке з трьох виразів, наведених вище, може використовуватися як критерій пошуку, при цьому рядкові значення повинні полягати в лапки. Нижче детально описана процедура створення рядків пошуку.

При побудові висловлювання, в якому задіяні змінні, повинні бути задані всі необхідні обмежувачі. Для числових виразів обмежувачі не потрібні. Так, якщо змінна intID містить значення 8, критерій пошуку задається наступним виразом:

“[ID] = ” & intID

Якщо включити цей рядок в команду SQL або передати її у вигляді параметра функції DLookup, вона буде правильно інтерпретована Access.

В критерій пошуку, що містить змінну дати, повинні бути включені обмежувачі #. Припустимо, є змінна varDate типу Variant, яка містить дату 22 травня 1959 року, і ви хочете отримати вираз

“[BirthDate] = #5/22/59#”

В цьому випадку роздільники вам доведеться вкласти самостійно. Рішення може виглядати так:

“[BirthDate] = #” & varDate & “#”

Більш складні ситуації виникають при включенні рядків. У таких випадках доводиться створювати рядковий вираз, що містить включається рядок у лапках, і укладати все вираз в додаткові лапки. Нижче наведені правила роботи з рядками в Access.

  • Вираз, укладену в лапки, не може містити внутрішні лапки.
  • Дві лапки (“”) в рядку сприймаються в Access як одна лапка.
  • “(Апостроф) апостроф (“) можуть використовуватися в якості обмежувачів рядків.
  • Вираз, укладену в апострофи, не може містити внутрішні апострофи.
  • Для представлення символу лапки в строковому вираженні може використовуватися значення Chr $ (34), сімволChr $ (34), де 34 – код лапки в стандарті ANSI.

    Ці правила дозволяють запропонувати ряд рішень описаної проблеми. Припустимо, мінлива strLastName містить рядок “Smith” і ви хочете створити секцію WHERE для пошуку з цього імені; виходить наступна рядок:

    “[LastName] = “Smith””

    Однак такий вираз заборонено, оскільки воно містить внутрішні лапки. Наступний рядок була б допустима:

    “[LastName] = “”Smith”””

    Проте в даному випадку літерал Smith знаходиться всередині вираження. На перший погляд здається, що його слід замінити ім’ям змінної strLastName:

    “[LastName] = “”strLastName”””

    Але в цьому випадку Access буде шукати запис з прізвищем strLastName (і ймовірно, не знайде).

    Таким чином, перше рішення полягає в тому, щоб розбити вираз на три частини – символи, що передують змінної, саму змінну і символи, наступні за змінної (тобто завершальну лапки):

    “[LastName] = “”” & strLastName & “”””

    Хоча на перший погляд достаток лапок виглядає дивно, насправді все вірно. Перша частина висловлювання:

    “[LastName] = “””

    Ця частина являє собою рядок, що містить ім’я поля, знак рівності і дві лапки. Згідно з наведеними вище правилами дві лапки в рядку інтерпретуються як одна. Аналогічна логіка застосовна і до тієї частини виразу, яка слідує за змінної (“” “”) – це рядок, що містить два символи лапки, які Access сприймає як одну лапку. Хоча таке рішення працює, воно виглядає досить заплутано.

    Для спрощення запису можна укласти внутрішню рядок в “(апостроф) апострофи:

    “[LastName] = “” & strLastName & “””

    Новий варіант виглядає простіше, але у нього є серйозний недолік: якщо прізвище містить внутрішні апострофи (наприклад, «O’Connor»), виникнуть серйозні неприємності. Таке рішення підходить тільки в тому випадку, якщо вміст змінної свідомо не містить апострофів.

    Внутрішні лапки найпростіше створюються за допомогою конструкції Chr $ (34). Попереднє вираз в цьому випадку виглядає так:

    “[LastName] = ” & Chr$(34) & strLastName & Chr$(34)

    Якщо ви сумніваєтеся в тому, що таке рішення буде нормально працювати, викличте вікно налагодження і введіть команду:

    ? Chr$(34)

    Access виводить значення Chr $ (34) – символ “.

    Щоб трохи спростити рішення, можна оголосити на початку процедури строкову змінну і привласнити їй результат виклику Chr $ (34):

    Dim strQuote As String

    Dim strLookup As String

     

    strQuote = Chr$(34)

    strLookup = “[LastName] = ” & strQuote & strLastName & strQuote

    Програма стає майже зрозумілою!

    Нарешті, якщо вам набридне визначати цю змінну у всіх процедурах, скористайтеся глобальної константою. Спочатку виникає спокуса використовувати запис виду:

    Const QUOTE = Chr$(34)

    На жаль, Access не дозволяє визначати константи, значення яких задається виразом з викликами функцій. Якщо ви захочете використовувати таку константу, покладіться на синтаксис «подвоєних лапок»:

    Const QUOTE = “”””

    Можливо, в цьому виразі розібратися з першого погляду нелегко, але воно нормально працює. Константа складається з двох лапок (які сприймаються Access як одна), укладених в лапки. При наявності такий константи наведене вище вираз приймає вигляд:

    strLookup = “[LastName] = ” & QUOTE & strLastName & QUOTE

    Всі перераховані правила реалізуються функцією acbFixUp з модуля basFixUpValue бази даних 07-01.MDB. Функція отримує параметр типу Variant і укладає його у відповідні обмежувачі. Код функції acbFixUp виглядає так:

    Function acbFixUp(ByVal varValue As Variant) As Variant

     

    “Додавання обмежувачів в залежності від типу даних параметра.

    “Текст полягає в лапки, дати – між символами” # “,

    “Числа не вимагають роздільників.

        ”

    “Якщо у виразі є перевірка рівності,

    “Замість виклику цієї функції слід використовувати

    “Функцію Basic BuildCriteria.

     

        Const QUOTE = “”””

     

        Select Case VarType(varValue)

            Case vbInteger, vbSingle, vbDouble, vbLong, vbCurrency

                acbFixUp = CStr(varValue)

            Case vbString

                acbFixUp = QUOTE & varValue & QUOTE

            Case vbDate

                acbFixUp = “#” & varValue & “#”

            Case Else

                acbFixUp = Null

        End Select

    End Function

    Якщо ця функція буде імпортована в ваше додаток, ви можете викликати її замість того, щоб займатися самостійним форматуванням даних. Приклади зустрічаються в коді форми frmQuoteTest. Наприклад, вираз з попереднього прикладу приймає наступний вигляд:

    “[LastName] = ” & FixUp(strLastName)

    Функція acbFixUp сама визначає тип даних і укладає їх у відповідні обмежувачі.

    ПРИМІТКА

    В Access також є корисна функція BuildCriteria, функціяBuildCriteria, якій при виклику передається ім’я поля, тип даних і значення поля. Функція з застосуванням обмежувачів в залежності від типу даних створює вираз виду

    ім’я_поля = “значення”

    В нашому прикладі ця функція застосовується при знятому прапорці Use Like. Наявність пошукових метасимволів не дозволяє використовувати функцію BuildCriteria, але при пошуку точного збігу вона спрощує вставку правильних обмежувачів. За практичним прикладом звертайтеся до функції BuildWhere модуля frmQuoteTest.

    Створення глобального стека процедур


    Проблема


    У додатках досить часто потрібно дізнатися ім’я поточної процедури всередині програми. Наприклад, помилки можуть оброблятися узагальненої функцією, яка виводить ім’я процедури з помилкою (а також всіх процедур, викликаних перед нею). VBA не містить засобів для отримання цієї інформації. Як дізнатися ім’я поточної процедури?

    Рішення


    Ведіть список активних процедур, включайте в нього нові імена при вході і видаляйте їх при виході, і ви завжди зможете дізнатися, яка процедура виконується в даний момент і як відбувалася передача управління. Такий підхід застосовується в багатьох ситуаціях (приклад наведено у наступному розділі), але одне з простих застосувань пов’язано з визначенням імені поточної процедури при глобальної обробці помилок.

    Структура даних, яка використовується для ведення списку, називається стек стеком. При вході в нову процедуру її ім’я заноситься в стек, а при виході з процедури воно витягується («виштовхується») з стека. На рис. 7.2 представлена ​​діаграма роботи стека. Стрілки вказують напрямки, в яких змінюється розмір стека при додаванні і видаленні елементів.

    Щоб побачити, як працює стек процедур, завантажте базу даних 07-02.MDB. Відкрийте модуль basTestStack в режимі конструктора і викличте вікно налагодження, вибравши команду View4Immediate Window. Введіть команду

    ? A()

     

    Рис. 7.2. Стек викликів і процедури його заповнення

    Команда виконує функцію з ім’ям A. На рис. 7.2 представлена ​​функція A і викликані нею процедури. На кожному кроці поточна процедура заносить своє ім’я в стек процедур і викликає процедуру наступного рівня, а при поверненні управління вона виштовхує своє ім’я з стека. Крім того, кожна процедура виводить у вікні налагодження ім’я поточної процедури (за допомогою функції acbCurrentProc, описаної нижче). Зразковий вид вікна налагодження після виконання функції A зображений на рис. 7.3.

     

    Рис. 7.3. Результат виконання функції A

    Щоб реалізувати аналогічну можливість у своєму додатку, виконайте такі дії.

    1.Імпортіруйте модуль basStack в свій додаток. У модулі містяться процедури, що забезпечують ініціалізацію та ведення стека процедур.

    2.Включіте виклик процедури acbInitStack в код, що виконується на початку роботи програми, – наприклад, в процедуру обробки події Load головної форми програми. Процедура acbInitStack повинна викликатися при кожному перезапуску програми в процесі розробки, тому її, мабуть, не слід включати в макрос Autoexec, що виконується тільки при першому завантаженні бази даних. Виклик процедури acbInitStack здійснюється або простим зазначенням її імені acbInitStack, або конструкцією з ключовим словом CallCall:

        Call acbInitStack

    3.Каждая функція або процедура програми повинна починатися з виклику процедури acbPushStack, яка зберігає отриманий параметр в стек. При кожному виклику acbPushStack передається один аргумент – ім’я поточної процедури. У нашому прикладі за іменами функцій слідують круглі дужки, а імена процедур передаються без дужок; втім, на роботу програми це не впливає. Всі процедури повинні завершуватися викликом процедури acbPopStack, яка витягає ім’я поточної процедури з стека.

    4.Чтоби дізнатися ім’я поточної процедури в будь-якій точці програми, викличте функцію acbCurrentProc. Функція аналізує верхній елемент стека і повертає знайдену рядок. Отримана інформація може використовуватися для обробки помилок або для хронометражу, як показано в наступному розділі.

    Коментар


    Модуль basStack, імпортований з бази даних 07-02.MDB, містить визначення локальної змінної стека процедур, а також код виконання стекових операцій. Модуль має шість точок входу, тобто процедур, викликаються ззовні. Ці процедури перераховані в табл. 7.1. Оскільки весь код стека инкапсулирован в одному модулі, вам навіть не обов’язково знати, як він працює, але насправді все просто.

    Таблиця 7.1. Шість точок входу модуля basStack
































    Ім’я процедури


    Призначення


    Параметри


    acbInitStack


    Ініціалізація стека



    acbPushStack


    Занесення нового елемента в стек


    Рядок, що заносяться в стек


    acbPopStack


    Видалення елемента зі стека



    acbCurrentStack


    Читання імені поточної процедури



    acbGetStack


    Читання елемента із заданим номером


    Номер елемента стека


    acbGetStackItems


    Визначення кількості елементів в стеку



    У модулі basStack визначаються дві змінні рівня модуля. Масив рядків mastrStack являє собою безпосередню реалізацію стека, а целочисленная мінлива mintStackTop містить індекс масиву, з яким наступний елемент буде занесений в стек. На початку роботи зі стеком мінлива mintStackTop дорівнює 0, тому перший елемент зберігається в позиції з номером 0. Ініціалізація стека в процедурі acbInitStack обмежується простим обнуленням змінної mintStackTop:

    Public Sub acbInitStack()

    “Обнулення покажчика на вершину стека.

        mintStackTop = 0

    End Sub

    Процедура acbPushStack заносить в стек новий елемент. При виклику їй передається строкове значення, яке зберігається в стеку. Параметр зберігається в елементі масиву з індексом mintStackTop, після чого значення індексного змінної збільшується. Код процедури acbPushStack наведено нижче.

    Public Sub acbPushStack(strToPush As String)

     

    “Занесення рядка в стек.

    “У разі переповнення стека виводиться повідомлення про помилку.

    “При наявності вільного місця елемент зберігається в стеку.

       

    “Спочатку обробляється можлива помилка переповнення.

        If mintStackTop > acbcMaxStack Then

            MsgBox acbcMsgStackOverflow

        Else

    “Збереження рядка.

            mastrStack(mintStackTop) = strToPush

     

    “Мінлива mintStackTop визначає індекс

    “НАСТУПНОГО елемента, заносимого в стек.

            mintStackTop = mintStackTop + 1

        End If

    End Sub

    Єдина потенційна проблема пов’язана з переповненням стека. Максимальна глибина стека (константа acbcMaxStack) в нашому додатку дорівнює 20, а цього має бути цілком достатньо – адже мінлива mintStackTop збільшується тільки при переході на наступний рівень, тобто при виклику процедури з іншої процедури. Якщо у вашому додатку глибина виклику процедур перевищує 20 рівнів, краще подумати про зміну структури програми, ніж про збільшення стека! У разі переповнення процедура acbPushStack виводить попередження і не зберігає елемент в стеку.

    При виході з процедури верхній елемент видаляється з стека. Задача вирішується викликом процедури acbPopStack:

    Public Sub acbPopStack()

       

    “Витяг рядки з стека.

    “Якщо стек порожній, виводиться повідомлення про помилку.

    “В іншому випадку покажчик на вершину стека

    “Зміщується до попереднього елементу. При необхідності

    “Інформація реєструється в журналі.

       

    “Спочатку обробляється можлива помилка.

        If mintStackTop = 0 Then

            MsgBox acbcMsgStackUnderflow

        Else

    “Оскільки елемент видаляється, а не заноситься в стек,

    “Покажчик на вершину стека переміщається до попереднього елементу.

    “Наступний елемент, що заноситься в стек, буде збережений

    “На місці вилученого елемента.

            mintStackTop = mintStackTop – 1

        End If

    End Sub

    Як і в процедурі acbPushStack, ми спочатку перевіряємо потенційну помилку – спробу видалення елемента з порожнього стека. В цьому випадку acbPopStack виводить попередження і завершує роботу. Якщо стек містить елементи, процедура зменшує змінну mintStackTop, в результаті чого наступний виклик acbPushStack збереже новий елемент на місці старого.

    Функція acbGetCurrentProc читає значення елемента, що знаходиться на вершині стека, без збереження або видалення елементів:

    Public Function acbCurrentProc() As String

    “Оскільки mintStackTop завжди вказує на позицію

    “Такого елемента, заносимого в стек,

    “Функція повинна повернути значення елемента

    “В позиції minStackTop – 1.

        If mintStackTop > 0 Then

           acbCurrentProc = mastrStack(mintStackTop – 1)

        Else

            acbCurrentProc = “”

        End If

    End Function

    Функція читає останній елемент, занесений в стек (його індекс дорівнює mintStackTop-1, оскільки mintStackTop завжди визначає індекс наступного зберігається елементу). Просто переглянути вміст mastrStack (Без виклику інтерфейсної функції) неможливо, оскільки дані стека є локальними для модуля basStack – саме так і повинно бути. Подробиці реалізації стека приховані від зовнішніх користувачів, тому ви можете внести зміни в basStack, вибрати іншу структуру даних для зберігання стека і т. д.; решті код програми буде працювати з новою версією стека так само, як зі старою.

    Додаткова інформація про стеку повертається функціями acbGetStackItems (кількість елементів в стеку) і acbGetStack (читання елемента стека, що знаходиться в заданій позиції). Наприклад, наступний фрагмент виводить весь вміст стека (саме це робиться в процедурі D модуля basTestStack):

    Debug.Print “Stack items currently:”

    For intI = 0 To acbGetStackItems() – 1

        Debug.Print , acbGetStack(intI)

    Next intI

    Функція acbGetStackItems влаштована дуже просто: вона повертає значення mintStackTop, оскільки ця змінна завжди збігається з кількістю елементів в стеку.

    Public Function acbGetStackItems() As Integer

    “Функція повертає кількість елементів в стеку.

        acbGetStackItems = mintStackTop

    End Function

    Функція acbGetStack влаштована трохи складніше. Вона отримує номер елемента від вершини стека (0 відповідає верхньому елементу) і обчислює позицію повертається елемента. Код функції acbGetStack виглядає так:

    Public Function acbGetStack(mintItem As Integer) As String

    “Функція повертає елемент, що знаходиться на відстані mintItems

    “Від вершини стека. Таким чином, acbGetStack (0)

    “Збігається з результатом виклику acbCurrentProc,

    “А acbGetStack (3) повертає четвертий елемент від вершини стека.

        If mintStackTop >= mintItem Then

            acbGetStack = mastrStack(mintStackTop – mintItem – 1)

        Else

            acbGetStack = “”

        End If

    End Function

    Для нормальної роботи стека процедур при вході і виході з кожної процедури необхідно викликати процедури acbPushStack і acbPopStack. Вважається, що при хорошому стилі програмування в кожній процедурі повинна існувати тільки одна точка виходу, але навіть найкращі програмісти іноді порушують це правило. При використанні стека викликів кожній точці виходу повинен відповідати свій виклик acbPopStack. Пам’ятайте про це, коли будете адаптувати старий код або розробляти новий код для роботи зі стеком процедур. Втім, процедуру завжди можна запрограмувати з однією точкою виходу, що істотно спростить супровід програми.

    Хронометраж викликів функцій


    Проблема


    Ви хочете оптимізувати свій код VBA, але на практиці майже неможливо визначити, скільки часу витрачається на виконання тієї чи іншої функції і які процедури найчастіше викликаються в програмі. Требуется дізнатися, які процедури викликаються програмою, в якій послідовності і скільки часу витрачається на виконання кожної з них. Як це зробити?

    Рішення


    Використовуючи методику, описану в розділі «Створення глобального стека процедур», можна створити програму ведення журналу на базі стека і відслідковувати послідовність і витрати часу на виконання процедур додатки. Хоча програма виходить більш складною, ніж описана в попередньому розділі, написати її не так уже й складно, а використовувати ще простіше, оскільки вся робота виконується в одному модулі.

    Відкрийте базу даних 07-03.MDB і завантажте модуль basTestProfiler в режимі конструктора. Введіть у вікні отладкаотладкі команду

    ? A()

    Команда запускає тестову функцію. На рис. 7.4 представлений стек викликів і код функції A. Як видно з малюнка, A викликає процедуру B, яка викликає C, яка в свою чергу викликає D; ця процедура робить паузу в 100 мс і потім повертає керування C. Процедура C теж робить паузу в 100 мс і знову викликає D. Після виходу з D процедура C повертає керування B; ця процедура теж вичікує 100 мс і знову викликає C. Все це повторюється до тих пір, поки управління не буде повернуто функції A для остаточного завершення. Результати хронометражу, показані на рис. 7.4, були отримані в результаті тестового запуску.

     

    Рис. 7.4. Стек викликів і процедури, які використовуються для його заповнення

    Програма записує результати своєї роботи в файл LOGFILE.TXTLOGFILE.TXT, що знаходиться в установчому каталозі Access. Вміст цього файлу можна переглянути в будь-якому текстовому редакторі. При тестовому запуску A файл містив наступний текст:

    ********************************

    Procedure Profiling

    8/13/2003 3:29:11 PM

    ********************************

    + Entering procedure: A()

        + Entering procedure: B

            + Entering procedure: C

                + Entering procedure: D

                – Exiting procedure : D                 101 msecs.

                + Entering procedure: D

                – Exiting procedure : D                 100 msecs.

            – Exiting procedure : C                     301 msecs.

            + Entering procedure: C

                + Entering procedure: D

                – Exiting procedure : D                 100 msecs.

                + Entering procedure: D

                – Exiting procedure : D                 100 msecs.

            – Exiting procedure : C                     300 msecs.

        – Exiting procedure : B                         701 msecs.

        + Entering procedure: B

            + Entering procedure: C

                + Entering procedure: D

                – Exiting procedure : D                 100 msecs.

                + Entering procedure: D

                – Exiting procedure : D                 100 msecs.

            – Exiting procedure : C                     300 msecs.

            + Entering procedure: C

                + Entering procedure: D

                – Exiting procedure : D                 100 msecs.

                + Entering procedure: D

                – Exiting procedure : D                 101 msecs.

            – Exiting procedure : C                     301 msecs.

        – Exiting procedure : B                         701 msecs.

    – Exiting procedure : A()                          1513 msecs.

    Щоб організувати подібний хронометраж у своєму додатку, виконайте такі дії.

    1.Імпортіруйте модуль basProfiler в свій додаток. У модулі містяться процедури, що забезпечують ініціалізацію та ведення стека процедур.

    2.Включіте виклик процедури acbProInitCallStack в код, що виконується при запуску програми. Але якщо в рішенні з розділу «Створення глобального стека процедур» можна було обійтися без виклику процедури ініціалізації, на цей раз acbProInitCallStack викликається кожного разу, коли потрібно провести хронометраж, або стек буде працювати невірно. При виклику в процедуру acbProInitCallStack передаються три параметри пов’язані до логічного типу (True або False). Опис цих параметрів наведено в табл. 7.2.

    Таблиця 7.2. Параметри процедури acbProInitCallStack
















    Ім’я параметра


    Використання


    blnDisplay


    Прапор виклику вікна повідомлення при виникненні помилки


    blnLog


    Прапор ведення журналу


    blnTimeStamp


    Прапор збереження виміряних інтервалів часу в журналі


    3.Процедура ініціалізує кілька глобальних змінних і, якщо має вестися журнал викликів, записує в файл заголовок журналу. Типовий виклик acbProInitCallStack виглядає так:

    acbProInitCallStack False, True, True

    4.Каждая функція або процедура програми повинна починатися з виклику процедури acbProPushStack, яка зберігає в стеку отриманий параметр і поточний час. При кожному виклику acbProPushStack передається один аргумент – ім’я поточної процедури. У нашому прикладі за іменами функцій слідують круглі дужки, а імена процедур вказуються без дужок; втім, на роботу програми це не впливає. Всі процедури повинні завершуватися викликом процедури acbProPopStack, яка витягує ім’я процедури із стека і реєструє поточний час.

    5.Чтоби дізнатися ім’я поточної процедури в будь-якій точці програми, викличте функцію acbProCurrentProc. Функція аналізує верхній елемент стека і повертає знайдену рядок.

    6.Результати хронометражу записуються в файл LOGFILE.TXT (в каталозі бази даних Access), і їх можна переглянути в будь-якому текстовому редакторі. Якщо ви уважно виконали всі наведені інструкції, для кожної функції або процедури у файлі будуть присутні записи входу і виходу. Вкладені рівні виділяються відступом, а точки входу і виходу позначаються різними знаками (точка входу – знаком +, а точка виходу – знаком -).

    Коментар


    Модуль basProfiler, імпортований з бази даних 07-03.MDB, містить весь код хронометражу. Він містить п’ять точок входу, перерахованих в табл. 7.3.

    Таблиця 7.3. П’ять точок входу модуля basProfiler




























    Ім’я процедури


    Призначення


    Параметри


    acbProInitStack


    Ініціалізація стека



    acbProPushStack


    Занесення нового елемента в стек


    Рядок, що заносяться в стек


    acbProPopStack


    Видалення елемента зі стека



    acbProCurrentProc


    Читання імені поточної процедури



    acbProLogString


    Запис рядки у файлі журналу


    Рядок, що записується в журнал


    В цілому хронометраж працює практично так само, як операції з простим стеком процедур з розділу «Створення глобального стека процедур». До речі, автори спочатку розробили це рішення, а потім скоротили його до більш простого прикладу. В цей приклад входить код запису файлу на диск, а також код збору хронометражних даних. Основні відмінності описані нижче.

    Якщо в рішенні з розділу «Створення глобального стека процедур» дані стека зберігалися в простому масиві рядків, для хронометражу також необхідно зберігати час запуску і завершення кожної процедури. Стек реалізується у вигляді масиву структур acbStack, що визначаються наступним чином:

    Type acbStack

        strItem As String

        lngStart As Long

        lngEnd As Long

    End Type

    Dim maStack(0 To acbcMaxStack) As acbStack

    В Access існує функція Timer, яка повертає кількість секунд, що пройшли з півночі, але її роздільної здатності недостатньо для хронометражу процедур в Access Basic. Замість неї краще використовувати функцію Windows TimeGetTime, функціяTimeGetTime, яка повертає кількість мілісекунд, які минули з моменту запуску Windows. Функція TimeGetTime обнуляє повертається значення через 48 днів, тоді як функція Timer скидається щодня – якщо вам знадобиться виконати тривалу операцію, timeGetTime дозволить виміряти проміжок часу тривалістю більше одного дня (а також працювати з інтервалами, що переходять за північ). Звичайно, якщо операція виконується більше доби, мілісекунди точність навряд чи істотна, але це інше питання. Код basProfiler викликає timeGetTime для отримання поточного «часу» при кожному занесенні або видалення елемента з стека. Після включення такого оголошення в глобальний модуль ви можете вільно викликати timeGetTime у своєму додатку:

    Public Declare Function timeGetTime _

      Lib “winmm.dll” Alias “timeGetTime” () As Long

    У модулі basTestProfiler функція TimeGetTime також використовується в процедурі Wait. Ця процедура організовує затримку заданої тривалості (в мілісекундах), при цьому всередині циклу виконується команда DoEvents, щоб система Windows могла виконати свою роботу:

    Public Sub Wait(intWait As Integer)

        Dim lngStart As Long

        lngStart = acb_apiTimeGetTime()

        Do While acb_apiTimeGetTime() < lngStart + intWait

            DoEvents

        Loop

    End Sub

    Код basProfiler відкриває і закриває вихідний файл при кожному записі. Це уповільнює роботу програми, але зате гарантує, що при виникненні збоїв з якоїсь причини файл журналу завжди буде містити найсвіжішу інформацію. Якщо ви ніколи не використовували Access для прямого запису текстових файлів, подивіться, як це робиться.

    Процедура acbProWriteToLog спочатку перевіряє, чи відбувалися помилки при запису в журнал (тобто чи встановлений прапор mfLogErrorOccurred). При наявності помилок процедура нічого не намагається записувати в файл, тому що помилки можуть бути пов’язані зі збоями на диску. Якщо все було нормально, процедура отримує вільний файловий ідентифікатор, відкриває файл журналу для приєднання даних, записує інформацію і закриває файл. Нижче наведено код процедури acbProWriteToLog:

    Private Sub acbProWriteToLog(strItem As String)

        Dim intFile As Integer

     

        On Error GoTo HandleErr

     

    “Якщо в поточному сеансі сталася ХОЧ ОДНА помилка,

    “Вийти з процедури.

        If mfLogErrorOccurred Then Exit Sub

     

        intFile = FreeFile

        Open acbcLogFile For Append As intFile

        Print #intFile, strItem

        Close #intFile

       

    ExitHere:

        Exit Sub

     

    HandleErr:

        mfLogErrorOccurred = True

        MsgBox Err & “: ” & Err.Description, , _

          “Writing to Log”

        Resume ExitHere

    End Sub

    Як і в розділі «Створення глобального стека процедур», стековий механізм профілірованіеведенія журналу приносить користь лише в тому випадку, якщо при вході і виході з кожної процедури виконуються виклики acbProPushStack і acbProPopStack. Якщо процедура містить кілька точок виходу, подумайте, чи не можна їх об’єднати. Якщо це неможливо, простежте за тим, щоб перед кожною точкою виходу з процедури виконувався виклик acbProPopStack.

    В процесі аналізу журналу стає зрозуміло, що у витратах часу на виконання кожної процедури повинно враховуватися час роботи всіх процедур, що викликаються з неї. Наприклад, у нашому прикладі функція A викликає процедуру B, з якої викликаються процедури C і D. Час виконання A склало 1513 мс, але ця величина відповідає інтервалу між викликами acbProPushStack і acbProPopStack у функції A, і в неї входить час виконання процедур B, C і D. Не варто сприймати це як серйозну проблему, але слід пам’ятати, що використаний механізм не дозволяє «зупиняти годинник» при вкладених виклики процедур.

    У модулі basProfiler передбачена ще одна відкрита точка входу, acbProLogString. У наведеній програмі вона не викликається, але ви можете використовувати її в своїх програмах. Процедура отримує рядок і зберігає її у файлі журналу. Наприклад, наступна команда записує в журнал рядок «This is a test»:

    acbProLogString “This is a test”

    Багатозадачність в коді Access Basic


    Проблема


    Якщо в програмі VBA використовується цикл, який виконується довше однієї-двох секунд, Access немов «завмирає». Неможливо переміщати вікна на екрані, а клацання мишею в Access ігноруються до тих пір, поки програма не вийде з циклу. Чому це відбувається? І що можна зробити, щоб система могла працювати?

    Рішення


    Ймовірно, ви вже помічали, що простий фрагмент коду VBA здатний паралізувати роботу Access. Підтримка многозадачностьмногозадачности в 32-розрядних версіях Windows допомагає лише в тому випадку, якщо вона підтримується в додатках. Виявляється, виконання коду Basic не дозволяє виконуватися коду Access, тому багатозадачна природа Windows тут не допоможе. Якщо ваша програма містить дуже довгі цикли, ви повинні спеціально подбати про те, щоб на час поступатися управління Windows і дозволяти системі виконувати свою роботу. У VBA це завдання вирішується за допомогою команди DoEvents. При правильному використання команди DoEvents додаток-«монополіст», переважна багатозадачні можливості Access, перетворюється в нормальне додаток, що дозволяє Access нормально працювати під час виконання коду VBA.

    Відкрийте базу даних 07-04.MDB і запустіть форму frmDoEvents (рис. 7.5). На формі перебувають три кнопки, кожна з яких змінює ширину написи «Watch Me Grow!» Від 500 до 3500 твіпов з одиничним приростом (На малюнку видно лише частину напису). Ширина написи змінюється в наступному циклі:

    Me.lblGrow1 = 500

    For intI = 0 To 3000

        Me.lblGrow1.Width = Me.lblGrow1.Width + 1

    “Без виклику Repaint екран не оновлюється

        Me.Repaint

    Next intI

     

    Рис. 7.5. Тестова форма frmDoEvents під час виконання

    Щоб на практиці познайомитися з наслідками команди DoEvents, виконайте вправу.

    1.Щелкніте на кнопці Run Code Without DoEvents. Процедура, пов’язана з цією кнопкою, змінює ширину написи в циклі без передачі управління Access. Спробуйте клацнути на інший кнопці форми, перемістити або змінити розміри активного вікна під час виконання циклу. Ви побачите, що в процесі збільшення написи жодна з цих операцій не виконується. Після того як напис перестає рости, Access обробляє всі дії, які ви намагалися виконати під час циклу.

    2.Попробуем виконати аналогічний цикл з командою DoEvents. Клацніть на другий кнопці, Run Code With DoEvents1. На цей раз під час виконання програми можна переміщати активне вікно або змінювати його розміри, а також клацати на кнопках форми. Дана можливість буде протестована на наступному кроці.

    3.Во час збільшення написи кілька разів швидко клацніть на кнопці Run Code With DoEvents1. При кожному натисканні Access запускає новий примірник процедури обробки події Click, собитіеClick, і кожен примірник продовжує незалежно збільшувати напис. Перед нами приклад рекурсії, тобто декількох викликів процедури, що стартують до завершення попереднього виклику. При кожному виклику події Click використовується невелика частина стекової пам’яті Access (області пам’яті, призначеної для зберігання параметрів і змінні; локальниелокальние переменниелокальних змінних процедур). Теоретично при дуже великому кількості викликів ця пам’ять може бути витрачена. Починаючи з Access 95 і далі, ця проблема практично ніколи не виникає, але в Access 2 переповнення стека було цілком звичайним явищем. На наступному кроці продемонстровано рішення цієї проблеми.

    4.Щелкніте на третій кнопці, Run Code with DoEvents2. Поки напис продовжує збільшуватися, знову клацніть на кнопці. На цей раз клацання не мають ніякого ефекту. Процедура обробки події Click перевіряє, чи виконується вона в даний момент, і якщо виконується – скасовує запуск свого нового екземпляра. Подібна перевірка вирішує проблему рекурсіярекурсівних викликів DoEvents, командаDoEvents.

    Коментар


    Процедура, пов’язана з першою кнопкою, абсолютно не піклується про інтереси Windows та інших програм. При натисканні на кнопці виконується наступний код:

    Private Sub cmdNoDoevents_Click()

        Dim intI As Integer

     

        Me.lblGrow1.Width = 500

        For intI = 0 To 3000

            Me.lblGrow1.Width = Me.lblGrow1.Width + 1

    “Без виклику Repaint екран не оновлюється.

            Me.Repaint

        Next intI

    End Sub

    Оскільки процедура не дозволяє Windows виконати «свою роботу», в неї необхідно включити виклик методу Me.Repaint, що забезпечує перемальовування форми після кожної зміни. Щоб зрозуміти сенс цього рядка, закоментуйте її і знову клацніть на першій кнопці – ви побачите, що форма перемальовується лише після завершення всієї операції.

    Процедура другої кнопки працює аналогічно, але у неї в цикл включена команда DoEvents. Виклик Me.Repaint стає необов’язковим, оскільки передача управління по команді DoEvents дозволяє Windows обробити запити на перемальовування, що знаходяться в черзі. Під час виконання цього циклу переміщається покажчик миші, а також працюють інші програми. Процедура обробки події Click для другої кнопки виглядає так:

    Private Sub TestDoEvents()

        Dim intI As Integer

        Me.lblGrow1.Width = 500

        For intI = 0 To 3000

            Me.lblGrow1.Width = Me.lblGrow1.Width + 1

            DoEvents

        Next intI

    End Sub

    Private Sub cmdDoEvents1_Click()

        TestDoEvents

    End Sub

    Як згадувалося на кроці 2, недолік цього коду полягає в тому, що ніщо не заважає користувачеві в процесі виконання запустити його заново, якщо клацнути на тій же кнопці під час роботи циклу, процедура запускається знову. На початку виконання будь-якої процедури VBA Access завжди зберігає інформацію про процедуру та її локальних змінних в спеціальній області пам’яті, званої стеком. Стек має фіксовані розміри, що обмежує кількість одночасно виконуваних процедур. Якщо швидко клацати на цій кнопці багато разів підряд, можна викликати переповнення стека Access.

    Навряд чи вам вдасться відтворити цю проблему за допомогою нашої невеликої програми, оскільки розмір стека, який в Access 2 складав всього 40 Кбайт, в Access 95 був збільшений до 1 Мбайт. Щоб переповнити такий блок пам’яті, доведеться дуже швидко клацати на кнопці протягом дуже довгого часу. Втім, у більш складній ситуації і при передачі великих обсягів даних в параметрах процедур така проблема все ж може виникнути.

    Проблема з паралельним виконанням процедури вирішується в процедурі третьої кнопки форми. Перш ніж входити в цикл, ми перевіряємо, чи не виконується поточна процедура в даний момент, і якщо виконується, процедура просто завершує роботу. Процедура події Click третьої кнопки виглядає так:

    Private Sub cmdDoEvents2_Click()

        Static blnInHere As Boolean

     

        If blnInHere Then Exit Sub

        blnInHere = True

        TestDoEvents

        blnInHere = False

    End Sub

    Статична мінлива blnInHere є прапором попереднього запуску процедури. Якщо змінна blnInHere дорівнює True, значить, процедура працює в даний момент, тому ми просто повертаємо управління. Якщо змінна blnInHere дорівнює False, процедура присвоює прапору True і потім викликає процедуру TestDoEvents. Після виходу з TestDoEvents1 процедура cmdDoEvents2_ Click знову скидає прапор blnInHere, дозволяючи подальші виклики.

    Багато програмістів недостатньо добре розуміють сенс команди DoEvents. Що б ця команда ні повинна була робити з точки зору програміста, в Access 95 і пізніших версіях вона всього лише на час передає управління Access, дозволяючи обробити всі повідомлення, що знаходяться в черзі. Команда ніяк не впливає на роботу ядра Access, не може використовуватися для штучного уповільнення або синхронізації програми (якщо вона не базується на обробці повідомлень Windows). У коді VBA команда DoEvents передає управління операційній системі і отримує його назад лише після того, як будуть опрацьовані всі необроблені події, а також клавіші в черзі SendKeys, очередьSendKeys. Access ігнорує виклики DoEvents:


  • в призначених для користувача процедурах, обчислює значення поля запиту, форми або звіту;
  • в призначених для користувача процедурах, що створюють дані для заповнення списків або полів зі списками.

    Як показує друга кнопка на формі, рекурсивний виклик процедури з командою DoEvents може заподіяти неприємності. Простежте за тим, щоб це не відбувалося у вашому додатку (як у випадку з третьої кнопкою).

    Програмне додавання рядків у списку або полі зі списком


    Проблема


    Завдання заповнення списку або поля зі списком з джерела даних в Access вирішується елементарно. Проте в деяких ситуаціях в списки доводиться заносити дані, які не зберігаються в таблиці. В Visual Basic і інших середовищах VBA, включаючи Access 2002 і вище, це робиться просто: достатньо скористатися методом списки; додавання строкAddItem, методAddItem, але в попередніх версіях Access списки не підтримують цей метод. Як включити в список рядок, не зберігається в таблиці?

    Рішення


    Дійсно, до появи Access 2002 списки (а також поля зі списками) не підтримували методу AddItem, звичного для програмістів Visual Basic. Щоб спростити заповнення списків і полів зі списками приєднаними даними, розробникам Access довелося відмовитися від спрощеного методу занесення вільних даних в списки. Існує два способи, що дозволяють обійти це обмеження при заповненні списків і полів зі списками Access: самостійне побудова властивості RowSource в додатку і визначення функції функція зворотного визоваобратного виклику. Перший варіант просто реалізується, але працює тільки в елементарних ситуаціях. З іншого боку, функція зворотного виклику підходить для будь-яких ситуацій, хоча і реалізується не настільки тривіально. У наведеному рішенні продемонстровані обидва способи.

    Звичайно, виникає важливе питання – навіщо вдаватися до цих штучних засобів при заповненні списків і полів зі списками? Якщо дані завжди можна перенести в елемент з таблиці, запиту або висловлювання SQL, навіщо створювати собі труднощі? Відповідь проста: в деяких випадках заздалегідь невідомо, з якими даними вам належить працювати, причому ці дані не зберігаються в таблицях. Можливі й інші варіанти, наприклад, при заповненні елемента вмістом масиву, якщо ви не хочете зберігати дані в проміжній таблиці. До появи Access 2002 програмісти були змушені або створювати функцію зворотного виклику для заповнення списку, або самостійно змінювати властивість елемента RowSource. Починаючи з Access 2002, багато завдань щодо заповнення списків вирішуються методом AddItem.

    Нижче описані всі три способи модифікації вмісту списку або поля зі списком під час роботи програми. Перший спосіб заснований на модифікації значення властивості RowSource за умови, що властивості Тип джерела рядків (RowSourceType) задається значення Список значень (Value List). У другому способі список заповнюється функцією зворотного виклику. Нарешті, останній спосіб заснований на використанні методу AddItem.

    Заповнення списку методом AddItem


    1.Відкрийте базу даних 07-05.MDB і запустіть форму frmAddItem.

    2.Ізменіте вміст списку, встановивши перемикач Days або Months в групі зліва. Спробуйте обидва варіанти і змініть кількість стовпців у списку. На рис. 7.6 представлена ​​форма з висновком назв місяців в три колонки.

     

    Заповнення списку з модифікацією властивості RowSource


     

    1.Відкрийте базу даних 07-05.MDB і запустіть форму frmRowSource.

    2.Ізменіте вміст списку, встановивши перемикач Days або Months в групі зліва. Спробуйте обидва варіанти і змініть кількість стовпців у списку. На рис. 7.6 представлена ​​форма з висновком назв місяців в три колонки.

     

    Рис. 7.6. Форма frmRowSource з висновком назв місяців в три колонки

     

    Заповнення списку функцією зворотного виклику


     

    1.Відкрийте базу даних 07-05.MDB і запустіть форму frmListFill.

    2.Виберіте в першому списку день тижня. У другому списку виводяться числа, на які припадає заданий день (найближче і три наступні), як показано на рис. 7.7.

    3.СВОЙСТВО Тип джерела рядків (RowSourceType) відповідного елемента має містити ім’я функції (без знака рівності і без круглих дужок). Функції, що викликаються таким чином, повинні підкорятися жорстким вимогам, описаним в наступному розділі. На рис. 7.8 представлено вікно властивостей для списку frmListFill, у якого у властивості Тип джерела рядків (RowSourceType) зазначено ім’я функції зворотного виклику.

     

    Рис. 7.7. Списки форми frmListFill заповнюються функціями зворотного виклику

     

    Рис. 7.8. Вікно властивостей для функції заповнення списку

    Коментар


    У цьому розділі розглядаються два способи заповнення списків на програмному рівні. У тексті йдеться тільки про списки, але аналогічна методика застосовна і до полів зі списками. Можливо, вам буде простіше стежити за викладом, якщо ви відкриєте модулі форм, описаних нижче.

    Виклик методу AddItem


    Починаючи з Access 2002, для додавання нових елементів до списку можна використовувати метод AddItem елемента (а видалення елементів здійснюється парним методом RemoveItem, якому при виклику передається номер або текст елемента). Цей спосіб набагато простіше всіх інших, тому в першу чергу слід віддавати перевагу саме йому.

    При установці перемикача в групі Fill Choice виконується наступний обробник:

    Private Sub grpChoice_AfterUpdate()

        Dim strList As String

        Dim intI As Integer

        Dim varStart As Variant

     

        lstAddItem.RowSourceType = “Value List”

     

    “Очищення списку

        lstAddItem.RowSource = vbNullString

        lstAddItem.ColumnCount = 1

        grpColumns = 1

     

        Select Case Me.grpChoice

    Case 1 “Дні

    “Обчислити дату останньої неділі

                varStart = Now – WeekDay()

    “Перебір всіх днів тижня

                For intI = 1 To 7

                    lstAddItem.AddItem Format(varStart + intI, “dddd”)

                Next intI

     

    Case 2 “Місяці

                For intI = 1 To 12

                    lstAddItem.AddItem Format(DateSerial(2004, intI, 1), “mmmm”)

                Next intI

        End Select

     

        Me.txtFillString = lstAddItem.RowSource

    End Sub

    На початку процедури властивості RowSourceType задається текст “Value List”:

    lstAddItem.RowSourceType = “Value List”

    Це дуже важливий момент: якщо властивість RowSourceType задано невірно (в режимі конструктора або в програмі), викликати методи AddItem і RemoveItem не вдасться.

    Далі програма очищає формат списку:

    lstAddItem.RowSource = vbNullString

    lstAddItem.ColumnCount = 1

    grpColumns = 1

    Потім в залежності від обраного перемикача програма заповнює список ListBox назвами днів тижня або місяців:

        Select Case Me.grpChoice

    Case 1 “Дні

    “Обчислити дату останньої неділі

                varStart = Now – WeekDay()

    “Перебір всіх днів тижня

                For intI = 1 To 7

                    lstAddItem.AddItem Format(varStart + intI, “dddd”)

                Next intI

     

    Case 2 “Місяці

                For intI = 1 To 12

                    lstAddItem.AddItem Format(DateSerial(2004, intI, 1), “mmmm”)

                Next intI

        End Select

    Насправді програма просто маніпулює властивістю RowSource. Щоб наочно продемонструвати, що відбувається, ми відображаємо властивість RowSource в текстовому полі на формі:

    Me.txtFillString = lstAddItem.RowSource

    УВАГА

    Хоча на перший погляд здається, що в список дійсно додаються нові елементи, насправді програма просто змінює значення властивості RowSource елемента. Отже, в цьому варіанті діють ті ж обмеження, що і при ручному завданні властивості (див. наступний розділ). Зокрема, розмір властивості RowSource не може перевищувати максимального значення, яке в Access 2002 дорівнювало 2048 символам (Але може бути збільшено в майбутніх версіях).

    Модифікація властивості RowSource


    Якщо ви працюєте в Access 2002 і пізніших версіях, швидше за все, вам не доведеться використовувати цю методику. З іншого боку, у більш ранніх версіях Access вона дозволяє легко заповнювати незв’язані списки. Задаючи властивості Тип джерела рядків (RowSourceType) значення Список значень (Value List), можна передати перелік рядків, розділених символом крапки з комою (;), які будуть використовуватися для заповнення списку. Включаючи цей перелік у властивість Джерело рядків (RowSource), ви наказуєте Access послідовно виводити елементи списку у всіх заповнюваних рядках і стовпцях списку. Оскільки дані вводяться безпосередньо у вікні властивостей, їх максимальний обсяг обмежується максимальною довжиною властивості, що вводиться у вікні властивостей (його конкретне значення залежить від версії Access).

    Властивість RowSource можна в будь-який момент модифікувати, задавши в ньому новий список елементів, розділених символом крапки з комою. При цьому слід враховувати властивість Число стовпців (ColumnCount), оскільки Access заповнює список спочатку по рядках, а потім по стовпцях. У цьому неважко переконатися, змінивши значення властивості Кількість стовпців (ColumnCount) списку на формі frmRowSource.

    Форма створює список днів тижня або назв місяців в залежності від стану перемикачів на формі. Основна робота виконується наступним фрагментом:

    Select Case Me.grpChoice

    Case 1 “Дні

    “Обчислити дату останньої неділі.

            varStart = Now – WeekDay(Now)

    “Перебір всіх днів тижня.

            For intI = 1 To 7

                strList = strList & “;” & Format(varStart + intI, “dddd”)

            Next intI

               

    Case 2 “Місяці

            For intI = 1 To 12

                strList = strList & “;” & _

                          Format(DateSerial(1995, intI, 1), “mmmm”)

            Next intI

    End Select

     

    “Видалити зайві символи”; “на початку.

    strList = Mid(strList, 2)

    Me.txtFillString = strList

    В залежності від стану групи grpChoice мінлива strList містить або перелік днів тижня виду


    або перелік місяців:


    Листопад; грудня

    Після побудови рядка залишається переконатися в тому, що властивості RowSourceType задано правильне значення, і задати нове значення RowSource:

    lstChangeRowSource.RowSourceType = “Value List”

    lstChangeRowSource.RowSource = strList

    Якщо ви збираєтеся використовувати метод, заснований на модифікації властивості RowSource, обов’язково пам’ятайте про його головному обмеження: довжина рядка, що містить всі елементи списку, обмежується максимальним кількістю символів, що вводяться в вікні властивостей.

    Якщо ви ще не перейшли на Access 2002, максимальна довжина властивості RowSource дорівнює 2048 символів. Якщо обсяг даних перевищують цей поріг, цей спосіб вам не підійде. В Access 2002 і пізніших версіях подібної проблеми бути не повинно, оскільки максимальна довжина властивості RowSource помітно збільшена. Втім, у цих версіях все одно краще використовувати метод AddItem.

    Заповнення списку функцією зворотного виклику


    Наступна методика заснована на створенні спеціальної функції Access Basic, яка надає інформацію, необхідну для заповнення списку. На жаль, у підручниках з Access ця можливість документована недостатньо добре. Рішення, засноване на використанні функції зворотного виклику, надзвичайно гнучко, і при цьому реалізувати його зовсім нескладно.

    Ідея проста: ви передаєте Access функцію, яка повинна повертати інформацію про вміст заповнюваного елемента. Access «задає питання» про кількість рядків, кількості шпальт, ширині і форматі стовпців, а також запитує самі дані. Ваша функція повинна відреагувати на всі запити та надати відомості, на підставі яких Access заповнить елемент даними. Це єдиний приклад того, як в Access програміст визначає функцію, яка не викликається з конкретних точок його програми. Access викличе функцію, коли виникне необхідність в інформації для заповнення елемента (тому функція називається функцією зворотного виклику). Форма frmFillList використовує дві такі функції для заповнення двох списків.

    Щоб функція могла взаємодіяти з Access, вона повинна отримувати п’ять параметрів, що інтерпретуються певним чином. У табл. 7.4 перераховані ці параметри з короткими описами. Імена параметрів вибираються довільно, у таблиці вони наведені тільки для зручності, однак порядок передачі параметрів має принципове значення – параметри повинні передаватися в порядку їх перерахування до табл. 7.4.

    Таблиця 7.4. Обов’язкові параметри для функцій зворотного виклику




























    Аргумент


    Тип даних


    Опис


    ctl


    Control


    Посилання на заповнюється елемент


    varId


    Variant


    Унікальне значення, ідентифікує заповнюється елемент (присвоюється програмою). Хоча ідентифікатор дозволяє використовувати одну функцію зворотного виклику з декількома елементами, зазвичай ця можливість не виправдовує клопоту, пов’язаних з її реалізацією


    lngRow


    Long


    Поточна заповнюється рядок (нумерація починається з нуля)


    lngCol


    Long


    Поточний заповнюється стовпець (нумерація починається з нуля)


    intCode


    Integer


    Ознака запитуваної інформації


    В останньому параметрі intCode Access вказує, яку інформацію повинна повернути функція. Ваша програма повинна зреагувати на запит і повернути відповідне значення. У табл. 7.5 перераховані допустимі значення intCode у вигляді констант з короткими описами, а також значення, які функція повинна повернути Access при отриманні кожного запиту.

    Таблиця 7.5. Значення параметра intCode, їх інтерпретація і повертаються значення












































    Константа


    Опис


    Значення, що повертається


    acLBInitialize


    Ініціалізація даних


    Ненульове значення, якщо функція може заповнити список; Null або 0 в іншому випадку


    acLBOpen


    Відкриття елементу


    Ненульовий унікальний ідентифікатор, якщо функція може заповнити список; Null або 0 в іншому випадку


    acLBGetRowCount


    Отримання кількості рядків


    Кількість рядків у списку; -1, якщо величина невідома (див. нижче)


    acLBGetColumnCount


    Отримання кількості стовпців


    Кількість стовпців у списку (не може бути дорівнює 0)


    acLBGetColumnWidth


    Отримання ширини стовпця


    Ширина стовпця, заданого параметром lngCol (в твіпах); -1, щоб використовувати ширину за замовчуванням


    acLBGetValue


    Отримання виведеного значення


    Значення, що відображається на перетині рядка і стовпця, заданих параметрами lngRow і lngCol


    acLBGetFormat


    Отримання формату стовпця


    Форматна рядок для стовпця, заданого параметром lngCol


    acLBClose


    Не використовується



    acLBEnd


    Завершення (при закритті форми)



    Практично всі функції зворотного виклику, використовувані при заповненні списків, будуються по одному зразку, тому ви можете взяти за основу функцію ListFillSkeleton. Функція отримує правильний набір параметрів і містить команду Select Case з окремими секціями Case для всіх значень intCode, що використовуються на практиці. Вам залишається лише змінити ім’я функції і зробити так, щоб вона повертала корисні дані. Код функції ListFillSkeleton наведено нижче.

    Function ListFillSkeleton(ctl As Control, varId As Variant, _

      lngRow As Long, lngCol As Long, intCode As Integer) As Variant

       

        Dim varRetval As Variant

     

        Select Case intCode

            Case acLBInitialize

    “Ініціалізація

                varRetval = True

     

            Case acLBOpen

    “Унікальний ідентифікатор

                varRetval = Timer

     

            Case acLBGetRowCount

    “Кількість рядків у списку

     

            Case acLBGetColumnCount

    “Кількість стовпців у списку

     

            Case acLBGetValue

    “Значення на перетині вказаного рядка і стовпця

     

            Case acLBGetColumnWidth

    “Ширина стовпця в твіпах (не обов’язково)

     

            Case acLBGetFormat

    “Формат стовпця (не обов’язково)

     

            Case acLBEnd

    “Завершальні дії, якщо вони потрібні

    “(Не обов’язково, крім звільнення пам’яті

    “Використовуваного масиву)

     

        End Select

        ListFillSkeleton = varRetval

    End Function

    Нижче приведена функція ListFill1 форми frmListFill1, що заповнює перший список на формі. Згідно з даними, переданим функцією, список складається з двох стовпців, другий стовпець ховається (його ширина дорівнює 0 твіпов). Кожен раз, коли Access викликає цю функцію з параметром intCode, рівним acLBGetValue, функція обчислює і повертає нову дату. Нижче наведено повний код функції ListFill1.

    Private Function ListFill1( ctl As Control, varId As Variant, _

      lngRow As Long, lngCol As Long, intCode As Integer)

       

        Select Case intCode

            Case acLBInitialize

    “Ініціалізація

                ListFill1 =

    Схожі статті:


    Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

    Коментарів поки що немає.

    Ваш отзыв

    Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

    *

    *