Мир объектов Excel 2000

Циклические вычисления чисел Фибоначчи

Эта задача интересна тем, что она отчетливо демонстрирует ситуацию, когда начальный отрезок ряда чисел вычисляется по "своим" формулам и лишь, начиная с третьего числа, идут вычисления по рекуррентным соотношениям. Для того чтобы в каждый момент иметь не весь ряд вычисленных чисел, а только последние полученные значения, нам потребуются три ячейки таблицы Excel. Заметьте, двумя ячейками не обойтись. Формулы в двух первых ячейках используют конструкцию IF, позволяющую на первом шаге вычислений задать соответствующую константу, а затем перейти к рекуррентному соотношению. Третья ячейка необходима, как дополнительная память при пересылке значений. Для реализации задачи я выполнил следующие действия:
  • В ячейку Fprev занес IF - формулу: "=ЕСЛИ(Fprev=0; 1; Fib)"
  • В ячейку Fib занес IF - формулу: "=ЕСЛИ(Fib=0; 1; Fnext)"
  • В ячейку Fnext ввел формулу: "=Fprev + Fib"
  • Эти формулы порождают на первом шаге вычислений тройку чисел (1,1,2), на втором - (2,3,5) и т.д.
  • Напомним, что важен порядок расположения этих формул на рабочем листе. Они должны идти сверху вниз, слева направо.

  • Взгляните, как выглядит на рабочем листе решение задач по нахождению корня уравнения и вычисления чисел Фибоначчи, использующее циклические вычисления.
    Циклические вычисления чисел Фибоначчи

    увеличить изображение
    Рис. 2.2.  Циклические вычисления и итерационные процессы

    Циклические вычисления и нахождение корней уравнения

    Покажем, как можно использовать циклические вычисления на примере задачи нахождения корня уравнения методом Ньютона. Для простоты я начну с квадратного уравнения, а позже рассмотрю и более "серьезные" уравнения. Итак, рассмотрим квадратное уравнение: X2 -5X+6 =0. Найти корень этого (и любого другого уравнения) можно, используя всего одну единственную ячейку Excel. Для этого достаточно включить режим циклических вычислений и ввести в произвольную ячейку с именем, скажем X, рекуррентную формулу, задающую вычисления по Ньютону:
    = X - F(X)/F1(X),
    где F и F1 задают соответственно выражения, вычисляющие функцию и производную. Для нашего квадратного уравнения после ввода формулы в ней появится значение 2, соответствующее одному из корней уравнения. А как получить второй корень? Обычно, это можно сделать путем изменения начального приближения. В нашем случае начальное приближение не задавалось, итерационный процесс вычислений начинался со значения, хранимого в ячейке X по умолчанию и равного нулю. Как же задать начальное приближение в циклических вычислениях? Возникшая проблема не связана с данной конкретной задачей. Она возникает всегда в циклических вычислениях, - до начала цикла надо задать начальные установки. В рекуррентных соотношениях всегда есть некоторый начальный отрезок. Решать задачу задания начальных установок в каждом случае можно по-разному. Я продемонстрирую один прием, основанный на использовании функции ЕСЛИ. Вот как выглядит "настоящее" решение этой задачи, использующее 4 ячейки, две из которых нужны по существу дела, а две используются для повышения наглядности процесса вычислений:
  • В ячейку с именем Xinit я ввел начальное приближение.
  • В ячейку Xcur, в которой и будет идти циклический счет, ввел формулу: = ЕСЛИ(Xcur =0; Xinit; Xcur - (6- Xcur *(5- Xcur))/(2* Xcur -5))
  • В две другие вспомогательные ячейки я поместил текст этой формулы и формулу, задающую вычисление функции в точке Xcur, позволяющую следить за качеством решения.
  • Заметьте, что на первом шаге вычислений, функция IF (ЕСЛИ) поместит в ячейку Xcur начальное значение, а затем уже начнет счет по формуле на последующих шагах.
  • Чтобы сменить начальное приближение, недостаточно изменить содержимое ячейки Xinit и запустить процесс вычислений. В этом случае вычисления будут продолжены, начиная с последнего вычисленного значения. Чтобы обнулить значение, хранящееся в ячейке Xcur, нужно заново записать туда формулу. Для этого достаточно выбрать ячейку и выделить текст формулы непосредственно в окне ее редактирования. Щелчок по Enter начнет вычисления с новым начальным приближением.


  • Циклические вычисления

    Если зависимые ячейки Excel образуют цикл, то говорят, что имеют место циклические ссылки (circular references). В обычном режиме Excel обнаруживает цикл и выдает сообщение о возникшей ситуации, требуя устранить циклические ссылки. Следуя обычной семантике, он не может провести вычисления, так как циклические ссылки порождают бесконечные вычисления. Есть два выхода из этой ситуации, - устранить циклические ссылки или изменить настройку в машине вычислений так, чтобы такие вычисления стали возможными. В последнем случае, естественно, требуется, чтобы число повторений цикла было конечным. Excel допускает переход к новой семантике, обеспечивающей проведение циклических вычислений. Вручную, для этого достаточно на вкладке Вычисления (меню Сервис, пункт Параметры) включить флажок Итерации и при необходимости изменить число повторений цикла в окошке "Максимум итераций". Можно также задать точность вычислений в окошке "Максимальное изменение", что также приводит к ограничению числа повторений
    цикла. По умолчанию максимальное число итераций и точность вычислений соответственно имеют значения 100 и 0,0001. Понятно, что включить циклические вычисления и задать значения параметров, определяющих окончание цикла, можно и программно.
    Укажем, особенности семантики циклических вычислений:
  • Формулы, связанные циклическими ссылками, вычисляются многократно.
  • Запись формул на листе определяет порядок их вычисления. Формулы вычисляются сверху вниз, слева направо.
  • Число повторений цикла определяется параметрами, заданными на вкладке Вычисления. Цикл заканчивается при достижении максимального числа итераций или, когда изменения значений во всех ячейках не превосходят заданной точности.

  • В каких же ситуациях требуется прибегать к циклическим вычислениям? Это, возможно, следует делать, когда речь идет о реализации итерационного процесса, вычислениях по рекуррентным соотношениям. У нас уже были примеры реализации итерационных процессов, например, вычисление суммы ряда, задающего экспоненту, в которых не применялись циклические ссылки. Платой за это было использование дополнительных ячеек таблицы Excel. Правда, появлялись и новые возможности, - возможность построить график, проанализировать процесс сходимости и т.д. Тем не менее, программисту, привыкшему к традиционным языкам, и привыкшему "с детства" экономить на переменных, может показаться странным предложенное решение задачи о нахождении корня уравнения, где на экран выводятся результаты всех приближений. В Excel экономия ячеек не главная задача. Тем не менее, при реализации итерационных процессов можно, конечно, и в Excel иметь одну единственную ячейку X, значение которой изменяется, начиная от начального прибли жения до искомого результата. Это в большей степени соответствует понятию переменной в языках программирования.

    Функции с побочным эффектом и неявная передача данных

    Возможность написать функцию с побочным эффектом или неявной передачей данных является одной из основных причин вычисления всех пользовательских функций при пересчете электронной таблицы. Давайте приведем примеры, проясняющие ситуацию. С этой целью я написал три функции:
  • ПравильнаяФункция( X As Variant) As Variant. Это пример хорошей, правильно построенной функции. Через параметр X ей передается значение некоторой ячейки рабочего листа (объект Range). В качестве результата она возвращает значение функции,- в нашем примере результат является копией входного параметра X.
  • ПобочныйЭффект(X As Variant, Y As Variant) As Variant. В данной функции помимо вычисления результата изменяется и значение параметра Y. Поскольку по умолчанию параметр передается по ссылке (By Ref), то это должно было бы привести к побочному эффекту и изменить содержимое ячейки рабочего листа, переданной в качестве параметра Y. Мы увидим, что этого, однако, не происходит.
  • НеявнаяПередача(X As Variant) As Variant В данной функции результат зависит не только от входного параметра X, но и от значения другой, неявно используемой ячейки рабочего листа.

  • Вот как выглядят описания наших функций:
    Public Function ПравильнаяФункция(X As Variant) As Variant 'При вызове функции в формуле рабочего листа ей может быть передан объект 'Range - отдельная ячейка или диапазон. Возвращаемый результат 'также является объектом Range. Если передается и возвращается массив, 'то, естественно, функция должна вызываться в формуле над массивами.
    ПравильнаяФункция = X End Function Public Function ПобочныйЭффект(X As Variant, ByRef Y As Variant) As Variant 'Также, как и ПравильнаяФункция данная функция возвращает 'в качестве результата переданный ей параметр X. ПобочныйЭффект = X
    'Побочным эффектом является изменение параметра Y, переданного по ссылке. 'Однако заметьте, это изменение не затрагивает ячеек рабочего листа! Y = X
    'Попытка явного изменения значений ячеек рабочего листа 'также не приводит к успеху. В этом случае и функция не возвращает 'правильный результат. Ее результат в этом случае - #ЗНАЧ. 'Range("C4") = 777

    Const mes1 = "Если объект Range, переданный " Const mes2 = " в качестве второго аргумента функции ПобочныйЭффект" Const mes3 = " хранит значение, несовпадающее с первым аргументом, " Const mes4 = " то побочный эффект отсутствует!" MsgBox (mes1 & vbCrLf & mes2 & vbCrLf & mes3 & vbCrLf & mes4) End Function

    Public Function НеявныеДанные(X As Variant) As Variant 'Передача данных из рабочего листа в функцию, 'минуя параметры, является возможной! Dim R As Range Set R = Range("C4") MsgBox ("В ячейке C4 хранится значение " & R.Value) НеявныеДанные = X.Value + R.Value End Function

    Вот как выглядит рабочий лист Excel, на котором вызываются эти функции:

    Функции с побочным эффектом и неявная передача данных

    Рис. 2.1.  Побочный эффект и неявная передача данных

    Анализируя полученные результаты, обратим внимание на следующие моменты:

  • Функция, которую я назвал "ПравильнаяФункция", при вызове ее из рабочей формулы получает объект Range в качестве своего параметра X, и возвращает объект Range в качестве результата выполнения функции. Объект Range может быть единственной ячейкой, и в этом случае функция может вызываться в обычной рабочей формуле. Если же функция получает массив ячеек и возвращает массив, то она должна вызываться в формуле над массивами. На рабочем листе я продемонстрировал оба способа вызова функции. В ячейку D4 я записал формулу "=ПравильнаяФункция(B4)". Поскольку функция в качестве результата возвращает переданный ей аргумент, то значение, записанное ячейку D4, будет совпадать со значением 17, хранящемся в ячейке B4. Затем я в ячейки D5:E5 записал формулу над массивами "{=ПравильнаяФункция(B4:C4)}". В результате эта же функция позволяет скопировать диапазон ячеек.
  • В функции ПобочныйЭффект, хотя Y и получает "правильное" значение переменной X, но это никак не сказывается на значении ячейки рабочего листа, переданной в качестве параметра Y, хотя параметр передается по ссылке. Так что можно полагать, что передача объектов Range рабочего листа Excel в функцию всегда происходит по значению, а не по ссылке.
  • Всякая попытка явно или неявно изменить значения ячеек рабочего листа в процессе работы функции рабочего листа помимо возвращаемого функцией результата оканчивается неуспехом. Более того, попытка явно изменить значение в ячейке приводит к тому, что результат функции становится неопределенным. Так что побочный эффект во всех его проявлениях запрещен. Изменить содержимое листа можно только, возвращая результат работы вызываемых функций в формулах рабочего листа.
  • В функции НеявныеДанные создается локальный объект Range. Он получает значение одной из ячеек рабочего листа, и это значение влияет на результат, возвращаемый функцией. Тем самым становится возможной неявная передача данных, минуя аппарат формальных параметров. Заметьте, что Excel не может обнаружить такой способ зависимости между ячейками. Мы специально отразили существующие, по мнению Excel зависимости. Как видите, Excel не подозревает, что ячейка D10 зависит от ячейки C4. Именно возможность неучтенных зависимостей заставляет Excel полностью проводить вычисления всех наличествующих пользовательских функций.
  • Ни функции с побочным эффектом, ни функции с неявной передачей данных не вызывают никаких предупреждающих сообщений.



  • Const mes1 = "Если объект Range, переданный " Const mes2 = " в качестве второго аргумента функции ПобочныйЭффект" Const mes3 = " хранит значение, несовпадающее с первым аргументом, " Const mes4 = " то побочный эффект отсутствует!" MsgBox (mes1 & vbCrLf & mes2 & vbCrLf & mes3 & vbCrLf & mes4) End Function

    Public Function НеявныеДанные(X As Variant) As Variant 'Передача данных из рабочего листа в функцию, 'минуя параметры, является возможной! Dim R As Range Set R = Range("C4") MsgBox ("В ячейке C4 хранится значение " & R.Value) НеявныеДанные = X.Value + R.Value End Function

    Вот как выглядит рабочий лист Excel, на котором вызываются эти функции:

    Функции с побочным эффектом и неявная передача данных

    Рис. 2.1.  Побочный эффект и неявная передача данных

    Анализируя полученные результаты, обратим внимание на следующие моменты:

  • Функция, которую я назвал "ПравильнаяФункция", при вызове ее из рабочей формулы получает объект Range в качестве своего параметра X, и возвращает объект Range в качестве результата выполнения функции. Объект Range может быть единственной ячейкой, и в этом случае функция может вызываться в обычной рабочей формуле. Если же функция получает массив ячеек и возвращает массив, то она должна вызываться в формуле над массивами. На рабочем листе я продемонстрировал оба способа вызова функции. В ячейку D4 я записал формулу "=ПравильнаяФункция(B4)". Поскольку функция в качестве результата возвращает переданный ей аргумент, то значение, записанное ячейку D4, будет совпадать со значением 17, хранящемся в ячейке B4. Затем я в ячейки D5:E5 записал формулу над массивами "{=ПравильнаяФункция(B4:C4)}". В результате эта же функция позволяет скопировать диапазон ячеек.
  • В функции ПобочныйЭффект, хотя Y и получает "правильное" значение переменной X, но это никак не сказывается на значении ячейки рабочего листа, переданной в качестве параметра Y, хотя параметр передается по ссылке. Так что можно полагать, что передача объектов Range рабочего листа Excel в функцию всегда происходит по значению, а не по ссылке.
  • Всякая попытка явно или неявно изменить значения ячеек рабочего листа в процессе работы функции рабочего листа помимо возвращаемого функцией результата оканчивается неуспехом. Более того, попытка явно изменить значение в ячейке приводит к тому, что результат функции становится неопределенным. Так что побочный эффект во всех его проявлениях запрещен. Изменить содержимое листа можно только, возвращая результат работы вызываемых функций в формулах рабочего листа.
  • В функции НеявныеДанные создается локальный объект Range. Он получает значение одной из ячеек рабочего листа, и это значение влияет на результат, возвращаемый функцией. Тем самым становится возможной неявная передача данных, минуя аппарат формальных параметров. Заметьте, что Excel не может обнаружить такой способ зависимости между ячейками. Мы специально отразили существующие, по мнению Excel зависимости. Как видите, Excel не подозревает, что ячейка D10 зависит от ячейки C4. Именно возможность неучтенных зависимостей заставляет Excel полностью проводить вычисления всех наличествующих пользовательских функций.
  • Ни функции с побочным эффектом, ни функции с неявной передачей данных не вызывают никаких предупреждающих сообщений.


  • Инструментальное средство Excel - "Решатель"(Solver)

    Для транспонирования, умножения и обращения матриц Excel имеет стандартные функции: ТРАНСП, МУМНОЖ и МОБР. Но в инструментарии Excel, доступном пользователю, есть более мощное средство, позволяющее в общем случае решать задачи нелинейного программирования, - Решатель (Solver). Как частный случай, он позволяет, например, найти решение нелинейных уравнений, которые рассматривались в задачах 3 и 4 предыдущей главы, и для решения которых я применял метод Ньютона. Частным случаем для Решателя является и решение систем линейных уравнений.

    Общая постановка задачи

    Задачи, которые можно решить с помощью Решателя, в общей постановке формулируются так:
    Найти:
    x1, x2, … xn
    такие, что:
    F(x1, x2, ….xn) -> {Max; Min; =Value}
    при ограничениях:
    GI(x1, x2, …xn) -> {<= Value; >= Value; = Value} I = 1…N
    Искомые переменные - ячейки рабочего листа Excel - называются регулируемыми (adjustable) ячейками. Целевая функция F(x1, x2, …xn), называемая иногда просто целью, должна задаваться в виде формулы в ячейке рабочего листа. Эта формула может содержать функции, определенные пользователем, и должна зависеть от регулируемых ячеек. В момент постановки задачи определяется, что делать с целевой функцией. Возможен выбор одного из вариантов:
  • найти максимум целевой функции F(x1, x2, …xn);
  • найти минимум целевой функции F(x1, x2, …xn);
  • добиться того, чтобы целевая функция F(x1, x2, …xn) имела фиксированное значение: F(x1, x2, ….xn) = A.

  • Функции GI(x1, x2, …xn) называются ограничениями. Их можно задать как в виде равенств, так и неравенств. На регулируемые ячейки можно наложить дополнительные ограничения: положительности и/или целочисленности, тогда искомое решение ищется в области положительных и/или целых чисел.
    Под эту постановку подпадает самый широкий круг задач оптимизации, в том числе решение различных уравнений и систем уравнений, задачи линейного и нелинейного программирования. Такие задачи обычно проще сформулировать, чем решить. И иногда для решения конкретной оптимизационной задачи требуются специально для нее сконструированные методы. Решатель имеет в своем арсенале мощные универсальные методы решения подобных задач: метод обобщенного градиента, симплекс-метод, метод ветвей и границ. Так что в более или менее простых случаях можно надеяться на успех. Кстати, помимо решения, делается и дополнительный анализ. Например, для задач линейного программирования делается анализ на чувствительность, позволяющий понять, насколько полученное решение нечувствительно к изменению ограничений. Предусмотрена и возможность управления процессом поиска решения.

    Параметры, управляющие работой Решателя

    Рассмотрим возможности управления работой Решателя, задаваемые в окне Параметры (Options):
  • Максимальное время (MaxTime) - ограничивает время, отведенное на процесс поиска решения. По умолчанию задано 100 секунд, что обычно достаточно для задач небольшой размерности, имеющих около 10 ограничений. Для задач большой размерности придется это значение увеличивать.
  • Предельное число итераций (Iterations) - еще один способ ограничения времени поиска путем задания максимального числа итераций. По умолчанию задано 100, но это число можно увеличивать до 32767. Чаще всего, если решение не получено за 100 итераций, надежд получить его при увеличении этого значения мало. Лучше попытаться изменить начальное приближение и запустить процесс поиска заново.
  • Относительная погрешность (Precision) - задает точность выполнения ограничений. Иногда проще изменить ограничение, отодвинув границу, чем пытаться выполнить ограничения с высокой точностью.
  • Сходимость (Convergence) - задается десятичной дробью, меньшей единицы, позволяя остановить процесс поиска при сходимости решения к неподвижной точке, когда относительные изменения в течение последних 5 итераций не превышают заданную дробь.
  • Линейная модель (Assume Linear Model) - этот флажок следует включать, когда целевая функция и ограничения - линейные функции. Эта дополнительная информация позволяет Решателю упростить процесс поиска решения.
  • Неотрицательные значения (Assume Non-Negative) - этим флажком можно задать ограничения на переменные, что позволит искать решения в положительной области значений, не задавая специальных ограничений на их нижнюю границу.
  • Показывать результаты итераций (Show Iteration Results) - флажок, позволяющий включить пошаговый процесс поиска, показывая на экране результаты каждой итерации. В сложных ситуациях, когда Решатель не находит решения автоматически, рекомендуется включать этот флажок, так как иногда можно найти точку, от которой процесс поиска уклонился в сторону.
  • Автоматическое масштабирование (Use Automating Scaling) - флажок автоматического изменения масштаба следует включать, когда масштаб значений входных переменных и целевой функции и ограничений отличается, возможно, на порядки. Например, переменные задаются в штуках, а целевая функция, задающая суммарную стоимость, измеряется в миллионах рублей.
  • Относительная погрешность (Tolerance) - задается в процентах. Указанное значение имеет смысл только для задач с целочисленными ограничениями. Решатель в таких задачах вначале находит оптимальное не целочисленное решение, а потом пытается найти ближайшую целочисленную точку, решение в которой отличалось бы от оптимального не более чем на указанное данным параметром количество процентов. Если такая точка найдена, Решатель сообщает об успехе. При большом допуске (по умолчанию 5%) может быть потеряно лучшее целочисленное решение, правда, отличающееся от найденного Решателем в пределах допуска. Для целочисленных задач допуск имеет смысл уменьшить, что я и сделал при решении транспортной задачи. Хочу еще раз обратить внимание на эту особенность решения задач целочисленного программирования. Если значение параметра Tolerance задать большим, то Решатель может остановиться раньше времени, не найдя лучшего целочисленного решения. Если же его взять малым, то наилучшее целочисленное решение будет отличаться от оптимального нецелочисленного решения на величину большую, чем ту, которая задается параметром Tolerance. В этом случае формально решение заканчивается неуспехом, поскольку найденное решение не удовлетворяет всем требованиям. Конечно, параметр Tolerance играет служебную роль, и "умный" Решатель, найдя наилучшее целочисленное решение, должен был бы уведомлять, что решение найдено, но ограничение по Tolerance не выполнено. Этого, однако, не происходит. Мы еще столкнемся с этой ситуацией при рассмотрении следующей задачи.
  • Сохранить модель (Save Model) - командная кнопка; позволяет открыть диалоговое окно, где можно указать имя сохраняемой модели. Имеет смысл использовать эту возможность, когда на рабочем листе несколько моделей, так как единственная модель запоминается автоматически.
  • Загрузить модель (Load Model) - позволяет загрузить одну из сохраненных моделей.
  • Есть еще несколько более специальных параметров, которыми можно управлять, варьируя процедурами, применяемыми в процессе поиска. К ним следует прибегать в тяжелых ситуациях, когда решение найти не удается.


  • Пользовательские функции и массивы рабочего листа

    Учитывая важность проблемы передачи массивов рабочего листа в пользовательскую функцию, кратко сформулируем основные выводы еще раз:
  • Пользовательская функция, вызываемая в формуле рабочего листа, может принимать один или несколько массивов в качестве своих входных параметров. Эти параметры должны иметь тип Variant.
  • На выходе функция может сформировать только один массив, который можно передать в рабочий лист. Этот массив не может быть значением одного из параметров функции - только результатом этой функции, т. е. побочный эффект не допускается. Всякая попытка вызвать функцию с побочным эффектом обнаруживается динамически (но не в период трансляции) и приводит к ошибке при вычислении результата.
  • Пользовательская функция, возвращающая массив рабочего листа, должна иметь тип Variant.

  • Ряд важных вопросов требует специального рассмотрения. Вот лишь некоторые:
  • Что собой представляют формулы, вызывающие пользовательские функции, когда те получают массивы рабочего листа как параметры, но их результатом является скаляр? Вызываются ли они как формулы с массивами или как обычные формулы?
  • Я уже сказал, что в пользовательскую функцию можно передать массив рабочего листа, для чего необходимо, чтобы соответствующий формальный параметр имел тип Variant. Но Variant является универсальным типом, а посему можно передавать не только отдельные ячейки и массивы. Что можно передавать в качестве фактического параметра, как при этом построить обработку? Ответы на эти вопросы я дам при рассмотрении следующей задачи.


  • Пользовательские функции, принимающие сложный объект Range

    Известно, что объект Range может представлять несмежную область и являться объединением нескольких интервалов ячеек. Иначе говоря, один объект Range может задавать несколько массивов рабочего листа. Можно ли такой объект передать пользовательской функции и, если да, то как его обрабатывать? Ответ: "можно", хотя соответствующий формальный параметр следует описывать особым образом. Процедуры и функции VBA допускают произвольное число параметров, это достигается за счет того, что один, последний по счету формальный параметр может иметь спецификатор ParamArray. В этом случае данный параметр задает фактически массив параметров с произвольным числом элементов. Именно эта техника и применяется для передачи в пользовательскую функцию сложного объекта Range, представляющего не один, а произвольное число массивов. У такой функции последний параметр должен иметь спецификатор ParamArray и быть массивом типа Variant.
    Рассмотрим предыдущую задачу, немного усложнив ее, полагая, что при проверке кандидата на "медианность" используется произвольное число массивов. Вот как выглядит функция, решающая эту задачу:
    Public Function IsMedianaForAll(Cand As Variant, _ ParamArray M() As Variant) As Integer 'Эта функция осуществляет те же вычисления, что и функция IsMediana 'Важное отличие состоит в том, что аргумент M может быть 'задан сложным объектом Range 'или представлять объединение массивов. Dim Pos As Integer, Neg As Integer Pos = 0: Neg = 0 Dim Elem As Variant For Each Elem In M 'Анализ типа параметра Elem If TypeName(Elem) = "Range" Then For i = 1 To Elem.Rows.Count For j = 1 To Elem.Columns.Count If Elem.Cells(i, j) > Cand Then Pos = Pos + 1 ElseIf Elem.Cells(i, j) < Cand Then Neg = Neg + 1 End If Next j Next i
    ElseIf TypeName(Elem) = "Variant()" Then 'TypeName is "Variant()" 'Это массив, но для него, к сожалению, не всегда корректно работают, 'например, функции границ: LBound, UBound. Dim Val As Variant For Each Val In Elem If Val > Cand Then Pos = Pos + 1 ElseIf Val < Cand Then Neg = Neg + 1 End If Next Val Else MsgBox ("При вызове IsMedianaForAll один из аргументов" & _ vbCrLf & "не является массивом или объектом Range!") End If Next Elem IsMedianaForAll = Pos - Neg

    End Function

    Комментируя работу этой функции, отмечу:

  • Формально эта функция по- прежнему имеет два параметра Cand и M. Правда, теперь они поменялись местами, и параметр M стал последним. Фактически у этой функции теперь произвольное число параметров, поскольку параметр M, сохранив тип Variant, стал массивом параметров. Спецификатор ParamArray подчеркивает, что это специальный массив с произвольным числом элементов.
  • Для работы с массивом M используется цикл типа For Each. В цикле выделяется очередной элемент Elem типа Variant, а дальше используется уже знакомый по функции IsMediana алгоритм проверки элемента Cand.
  • Разбор случаев делается независимо для каждого из элементов массива M. Это значит, что на входе могут быть заданы одновременно как массивы рабочего листа, так и массивы констант Visual Basic.


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

    Пользовательские функции, принимающие сложный объект Range

    увеличить изображение
    Рис. 2.6.  Вызов функции IsMedianaForAll, допускающей сложные объекты Range

    Проанализируем четыре сделанных вызова:

  • =IsMedianaForAll(7;M;N). В этом вызове наш кандидат - число 7 - проверяется по отношению к объединению двух массивов рабочего листа, заданных своими именами M и N. Формальных параметров у функции два, а фактических при вызове задается три. Два последних можно рассматривать как сложный объект Range, представляющий несмежную область ячеек, задающую объединение вектора M и матрицы N. С программистской точки зрения, можно полагать, что передается массив с произвольным числом элементов, где каждый из них в свою очередь является массивом. Такой фактический параметр является допустимым значением формального параметра нашей функции, имеющего спецификатор ParamArray.
  • =IsMedianaForAll(4,5;N;M). В этом вызове мало нового в сравнении с предыдущим. Изменен порядок следования массивов N и M, изменен кандидат, - им стало число 4.5, не входящее ни в один из массивов. Как показывает результат, это число является медианой объединенных массивов.
  • =IsMedianaForAll(7; {4;7;2}; {9;12;5}). Здесь в роли аргументов выступают массивы Visual Basic, заданные в виде констант, заключенных в фигурные скобки. Фактическое значение параметра M в этом случае представляет массив из двух элементов, каждый из которых в свою очередь является массивом.
  • =IsMedianaForAll(7; {4;7;2}; {9;12;5}; M). Ситуация в этом вызове сложнее, так как число аргументов возросло, но, что более важно, среди них есть как массивы Visual Basic, так и массив рабочего листа - вектор M. Тем не менее, все работает правильно.


  • "Пользовательские" и "обычные" функции VBA

    Под пользовательской функцией VBA я понимаю функцию, которая может быть вызвана в формулах рабочего листа Excel. Обычные функции VBA могут вызываться в функциях и процедурах VBA. Возникает естественный вопрос, может ли одна и та же функция одновременно быть пользовательской и обычной? Этот же вопрос может быть сформулирован и по-другому, есть ли особая специфика в пользовательских функциях? Ответ прост - особой специфики нет, и одна и та же функция может вызываться как в формулах, так и в процедурах VBA. Практически не возникает проблем, когда аргументами функции и результатом являются скалярные значения. Когда же, как в случае с MultMatr, аргументами и результатом являются массивы, то возникают определенные трудности. Эти трудности преодолимы, примером тому служит функция MultMatr. Попробуем разобраться, в чем состоят эти трудности. Когда функции нужно предать массив, то в пользовательских функциях при вызове им передаются объекты Range, обычным функциям - пер еменные, описанные, как массивы VBA. Поэтому для обеспечения универсального характера функции в ее теле необходимо производить разбор случаев, определяя тип параметра. В результате растет объем функции, а, следовательно, усложняется ее понимание. Еще одна сложность связана с результатом вычислений. Никаких проблем нет для формулы над массивами, вызывающей пользовательскую функцию, - результат, записывается в область, выделенную при вызове формулы. Обычные функции VBA, как правило, не возвращают массив в качестве результата. Если результатом работы является массив, то при программировании на VBA создается процедура, а не функция. Дело в том, что в VBA присваивания над массивами запрещены, потому просто невозможно присвоить массиву значение обычной функции, возвращающей массив в качестве своего результата. Как же, спросите Вы, MultMatr может использоваться в качестве обычной функции? Только за счет маленьких хитростей и универсального типа Variant, который может быть чем угодно, в том числе и массивом. При вызове MultMatr как обычной функции в процедуре VBA результат вызова присваивается переменной типа Variant, - это допустимо. Затем уже с этой переменной можно работать как с массивом, - это тоже допустимо, что я и продемонстрирую чуть позже. Таким образом, всегда можно написать функцию так, чтобы она служила и как пользовательская и как обычная функция. Другой вопрос, стоит ли это делать. В таком обобщении есть свой резон, поскольку в таких случаях при вызове пользовательской функции ей можно передавать в качестве аргументов не только объекты Range, но и массивы констант, что было продемонстрировано при рассмотрении функции IsMedianaForAll. Заметьте, однако, что в функцию MultMatr передать массивы констант невозможно. Причина этого в том, что для двумерных массивов констант функции UBound и LBound работают некорректно.
    Подводя итог, замечу, что, когда приходится работать с массивами, разумнее иметь два варианта - пользовательскую и обычную функцию. Чтобы отчетливее продемонстрировать разницу между обычными и пользовательскими функциями, я написал обычную процедуру MultMatr1, выполняющую умножение матриц. Вот ее текст:
    Public Sub MultMatr1(A() As Variant, B() As Variant, C() As Variant) 'Умножение матриц. 'Процедуру можно вызывать в обычных VBA функциях и процедурах, 'передавая ей в качестве параметров массивы VBA. Dim i As Integer, j As Integer, k As Integer Dim N As Integer, M As Integer, Q As Integer Dim P As Integer, NC As Integer, PC As Integer Dim msg1 As String, msg2 As String Dim Uncor1 As Boolean, Uncor2 As Boolean Dim Elem As Variant Uncor1 = True: Uncor2 = True msg1 = " При вызове MultMatr некорректно задана размерность" _ & " перемножаемых матриц!" & vbCrLf & _ "Число столбцов матрицы A = " & M & vbCrLf & _ "Число строк матрицы B = " & Q msg2 = " При вызове MultMatr некорректно задана размерность" _ & " матрицы результата!" & vbCrLf & _ "Число строк матрицы C = " & NC & vbCrLf & _ "Число столбцов матрицы C = " & PC

    'Проверка корректности задания размерности N = UBound(A, 1): M = UBound(A, 2) Q = UBound(B, 1): P = UBound(B, 2) NC = UBound(C, 1): PC = UBound(C, 2) If (Q = M) Then ' Размерность исходных матриц задана корректно Uncor1 = False If NC = N And PC = P Then 'Размерность результата задана корректно Uncor2 = False 'Построение произведения матриц AB =A*B For i = 1 To N For j = 1 To P Elem = 0 For k = 1 To M Elem = Elem + A(i, k) * B(k, j) Next k C(i, j) = Elem Next j Next i Else 'некорректно задана размерность If Uncor1 Then MsgBox (msg1) If Uncor2 Then MsgBox (msg2) End If End If End Sub

    От функции MultMatr она отличается тем, что в ней опущен разбор случаев и проводится более тщательная проверка корректности размерностей аргументов. Конечно, она ни в коем случае не может быть использована как пользовательская функция, но зато работать с ней в процедурах и функциях VBA с ней не то чтобы проще, но естественнее. Чтобы почувствовать разницу, я продемонстрирую тестовую процедуру, в которой вызываются, как функция MultMatr так и процедура MultMatr1.

    Public Sub MultTest() Dim A(1 To 2, 1 To 2) As Variant Dim B(1 To 2, 1 To 2) As Variant Dim C(1 To 2, 1 To 2) As Variant Dim C1 As Variant Dim item As Variant Dim i As Integer, j As Integer A(1, 1) = 1: A(1, 2) = 2: A(2, 1) = 3: A(2, 2) = 4 B(1, 1) = 1: B(1, 2) = 2: B(2, 1) = 3: B(2, 2) = 4 'Переменной типа Variant присваивается массив C1 = MultMatr(A, B) For i = 1 To UBound(C1, 1) For j = 1 To UBound(C1, 2) Debug.Print C1(i, j) Next j Next i 'Здесь С - массив и работаем с ним, как с массивом. Call MultMatr1(A, B, C) For i = 1 To UBound(C, 1) For j = 1 To UBound(C1, 2) Debug.Print C(i, j) Next j Next i 'Вызов тестовой функции, возвращающей массив. C1 = ResArray(A) For Each item In C1 Debug.Print item Next item End Sub

    Public Function ResArray(A() As Variant) As Variant 'Возвращает в качестве результата, 'переданный ей массив ResArray = A End Function

    Как видите, функция MultMatr, успешно работающая в роли пользовательской функции, с тем же успехом может выполнять и роль обычной функции. Так что я выполнил поставленную задачу, создав "универсальную" функцию. Но, возможно, предпочтительнее в процедурах VBA работать с MultMatr1, не прибегая к переменным типа Variant. Обратите внимание на небольшую тестовую функцию ResArray, которую я написал, чтобы в явной форме продемонстрировать способ возвращения массива в функциях VBA.

    Решатель и программирование

    Правильная постановка оптимизационной задачи, грамотное использование Решателя требует от пользователя определенной математической культуры. В ряде случаев эту часть работы следует взять на себя программисту и организовать программный вызов Решателя, оставив за пользователем задание исходных данных. Все, что можно сделать вручную, можно сделать и на VBA и даже несколько больше, используя стандартный набор функций VBA для работы с Решателем. Кратко охарактеризую четыре основные функции:
  • SolverOk(SetCell, MaxMinVal, ValueOf, ByChange) - позволяет поставить задачу оптимизации. Она выполняет работу, эквивалентную установке параметров в диалоговом окне при вызове Решателя. Параметр SetCell задает ячейку, содержащую формулу с целевой функцией. Три возможных значения параметра MaxMinVal указывают, что с ней делать (максимизировать, минимизировать или пытаться зафиксировать ее значение, которое при этом задается параметром ValueOf). Последний по счету параметр ByChange задает диапазон регулируемых ячеек - переменных оптимизационной задачи.
  • SolverAdd(CellRef, Relation, FormulaText) - позволяет добавлять ограничения в модель. Первый параметр CellRef задает левую часть ограничения в виде ссылки на ячейку или их диапазон. Пять возможных значений параметра Relation (1…5) определяют отношение, связывающее левую и правую часть ограничения (<=, =, >=, int, bin). Два последних отношения накладывают на переменные ограничения целочисленности и двоичности. Двоичные переменные могут принимать только два значения 0 и 1. Параметр FormulaText задает правую часть ограничений, он не должен задаваться, если отношение задано как int или bin.
  • SolverOptions(MaxTime, Iterations, Precision, AssumeLinear, StepThru, Estimates, Derivates, Search, IntTolerance, Scaling, Convergence, AssumeNonNeg) - используя эту функцию, можно установить параметры Решателя, которые вручную устанавливаются в окне Options. Мы не будем их описывать, так как большинство из них мы фактически рассмотрели. Но на одном из них остановимся. Булев параметр StepThru имеет значение True, если включается пошаговое исполнение с прерыванием после выполнения каждой итерации. При программировании можно передать Решателю макрос, который будет выполняться при каждом таком прерывании.
  • SolverSolve(UserFinish, ShowRef) - запускает Решатель. Ее действие эквивалентно выбору кнопки Solve. Возможный булев параметр UserFinish равен True, если результаты не выводятся на дисплей. Если параметр опущен или равен False, результаты появятся в диалоговом окне Решателя, предназначенном для их вывода. Параметр ShowRef представляет строку, задающую имя макроса, который должен выполняться в паузах между итерациями. Он задается, только если параметр StepThru равен True.


  • Решение систем линейных уравнений, умножение и обращение матриц

    Задачи, перечисленные в заголовке, возникают достаточно часто в различных сферах деятельности, требующих применения математического аппарата. По этой причине в библиотеке Excel есть встроенные функции, позволяющие решить эти задачи. О встроенных функциях умножения матриц МУМНОЖ (MMULT) и транспонирования матриц МТРАНСП (MTRANSP) я уже упоминал, есть и функция для нахождения обратной матрицы - МОБРАТ (MINVERSE). Зная обратную матрицу и умея умножать матрицы, найти решение системы уравнений не представляет труда. Но поскольку умение решать эти задачи входит в круг начального образования программиста, то я полагаю уместным рассмотреть создание собственных аналогов этих функций на VBA. Заодно это позволит рассмотреть некоторые важные моменты в создании пользовательских функций, вызываемых в формулах рабочего листа. Многое мы уже знаем. Знаем, как написать пользовательскую функцию, какие ограничения накладываются на ее параметры с тем, чтобы ее можно было вызывать из формул рабочего листа Excel, передавая ей в качестве фактических параметров массивы рабочего листа. Знаем, как анализировать тип переданных данных. Знаем, как такая функция может вернуть массив и изменить содержимое рабочего листа. В последующих примерах я еще раз коснусь всех этих вопросов, а, кроме того, появятся и другие вопросы, на которые стоит обратить внимание.

    Решение системы линейных уравнений методом простой итерации

    Циклические вычисления можно проводить и над массивами. В качестве примера такой задачи рассмотрим итеративный способ решения системы линейных уравнений AX = B. Если применить метод простой итерации, то вектор решений X определяется следующим рекуррентным соотношением:
    XK+1 = XK -(AXK -B) k= 1…N
    Чтобы запустить процесс вычислений, нужно задать начальное приближение - вектор X1. Для сходимости процесса необходимо, но не достаточно, чтобы норма матрицы А была меньше 1. Иногда достаточно соответствующим образом масштабировать матрицу А и вектор В. Но нас конечно интересуют другие вопросы,- как реализовать на Excel это итеративное соотношение над векторами и матрицами, не прибегая к программированию на VBA и используя циклические вычисления. Покажем, что решение получить ненамного сложнее, в сравнении с применением схемы Ньютона для одного уравнения. Я сделал следующее:

  • Ввел матрицу А и вектор В, предварительно нормировав их. Заметим, что нужно правильно выбирать ориентацию векторов. В данном случае мне было удобнее представлять вектор В строкой. Вектор получил имя Veb.

  • Затем определил вектор с именем Vxinit, задающий начальное приближение.

  • Затем определил еще один вектор с именем Vxcur. Это вектор решений, его значения будут изменяться в цикле, пока не закончится итерационный процесс. Формула для вычислений будет определяться написанным выше рекуррентным соотношением, и представлять собой формулу над массивами. Учитывая то, что говорилось ранее о рекуррентных соотношениях, формула должна быть IF - функцией. Это позволит использовать на первом шаге начальное приближение, а потом уже вести вычисления по рекуррентной формуле. Приведем теперь саму формулу над массивами, вычисляющую рекуррентно вектор Vxcur:
    {=ЕСЛИ(Vxcur=0; Vxinit; Vxcur - Axhor + Veb)}
    Здесь, как и ранее, используются нулевые значения, как признак начала процесса. Заметим, что, конечно, было бы лучше, если бы существовала возможность явной инициализации переменных при циклических вычислениях, а так наши действия немного напоминают фокус. Вектор Axhor, введенный в формулу - это вспомогательный вектор, равный произведению матрицы А на текущий вектор Vxcur. Если процесс вычислений сходится, и Vxcur сходится к решению системы уравнений, то вектор Axhor будет сходиться к вектору Veb.


  • Опишем подробнее формирование вспомогательного вектора Axhor. Содержательно, он представляет произведение матрицы на вектор. Но произведение матрицы на вектор дает вектор столбец, а в рекуррентном соотношении необходим вектор строка. По этой причине я формирую вначале вектор столбец Axver:
    Axver = A*Vxcur
    Конечно, для умножения матрицы на вектор можно воспользоваться стандартной функцией, но я хочу показать, как это делается с использованием более простых средств. Для этого достаточно написать формулу, вычисляющую его первый элемент:
    {=SUM(A38:B38*Vxcur)}
    и затем скопировать ее по столбцу. Обратите внимание, вектор, на который умножается матрица, должен быть строкой, а вектор - результат - столбцом. Строка матрицы задается в относительных адресах и при копировании меняется. Вектор, на который умножаются строки, задается своим именем, а значит абсолютным адресом, не изменяющимся при копировании. Каждая формула, задающая элемент вектора, является формулой над массивами.

  • Получив вектор столбец Axver, задающий нужное произведение, можно перейти к получению строки - Axhor, представляющей результат транспонирования вектора Axver. Для транспонирования я использовал стандартную функцию Transpose. Сама задача транспонирования и эта функция подробно будет рассмотрена чуть позже. Формула над массивами, определяющая вектор Axhor имеет вид:
    {=ТРАНСП(Axver)}

  • Задав все вектора и все формулы, я получил решение системы линейных уравнений. Как ни странно, но даже столь плохой метод, как метод простой итерации сошелся к решению. Так, начав с начального приближения (1,1), я получил решение (1.6, 2.4) с заданной точностью.

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

  • В заключение темы о циклических вычислениях покажем, как выглядят построенное решение на рабочем листе Excel:
    Решение системы линейных уравнений методом простой итерации

    увеличить изображение
    Рис. 2.3.  Циклические вычисления и действия над матрицами


    Опишем подробнее формирование вспомогательного вектора Axhor. Содержательно, он представляет произведение матрицы на вектор. Но произведение матрицы на вектор дает вектор столбец, а в рекуррентном соотношении необходим вектор строка. По этой причине я формирую вначале вектор столбец Axver:
    Axver = A*Vxcur
    Конечно, для умножения матрицы на вектор можно воспользоваться стандартной функцией, но я хочу показать, как это делается с использованием более простых средств. Для этого достаточно написать формулу, вычисляющую его первый элемент:
    {=SUM(A38:B38*Vxcur)}
    и затем скопировать ее по столбцу. Обратите внимание, вектор, на который умножается матрица, должен быть строкой, а вектор - результат - столбцом. Строка матрицы задается в относительных адресах и при копировании меняется. Вектор, на который умножаются строки, задается своим именем, а значит абсолютным адресом, не изменяющимся при копировании. Каждая формула, задающая элемент вектора, является формулой над массивами.

  • Получив вектор столбец Axver, задающий нужное произведение, можно перейти к получению строки - Axhor, представляющей результат транспонирования вектора Axver. Для транспонирования я использовал стандартную функцию Transpose. Сама задача транспонирования и эта функция подробно будет рассмотрена чуть позже. Формула над массивами, определяющая вектор Axhor имеет вид:
    {=ТРАНСП(Axver)}

  • Задав все вектора и все формулы, я получил решение системы линейных уравнений. Как ни странно, но даже столь плохой метод, как метод простой итерации сошелся к решению. Так, начав с начального приближения (1,1), я получил решение (1.6, 2.4) с заданной точностью.

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

  • В заключение темы о циклических вычислениях покажем, как выглядят построенное решение на рабочем листе Excel:
    Решение системы линейных уравнений методом простой итерации

    увеличить изображение
    Рис. 2.3.  Циклические вычисления и действия над матрицами

    Транспортная задача

    В офисной деятельности очень часто приходится анализировать варианты, стараясь найти оптимальное решение. К сожалению, математические методы оптимизации в реальной офисной практике применяются сравнительно редко. Причины этого я обсуждать здесь не буду. Наша цель другая, - показать, как решаются такие задачи. Поэтому я подробно останавливаюсь на возможностях Решателя, полагая, что это средство Excel позволяет легко и просто решать самые разные оптимизационные задачи, возникающие в ходе офисной деятельности.
    Рассмотрим более серьезную оптимизационную задачу - транспортную. Вот одна из возможных содержательных постановок этой задачи:
    Имеется N складов, на каждом из которых хранится готовая продукция. Пусть:
    P1, P2, …PN
    - объемы продукции, хранящейся на каждом складе. Пусть теперь есть M магазинов, от которых поступила заявка на завоз этой продукции:
    Q1, Q2, …QM
    - требуемые объемы продукции для каждого магазина. Транспортные расходы на перевозку единицы продукции из склада I в магазин J задаются матрицей:
    || TI,J || I = 1…N; J = 1…M
    Требуется найти оптимальный план перевозок,- матрицу ||XI,J||, минимизирующую суммарные транспортные расходы при естественных ограничениях: все заявки магазинов должны быть удовлетворены, и со склада нельзя увезти продукции сверх того, что там имеется. В формальной постановке эта задача имеет вид:
    Транспортная задача
     
    Транспортная задача
    XI,J* TI,J -> Min I  J
    При ограничениях:
    Транспортная задача
    XI,J <= PI I = 1…N; J
    Транспортная задача
    XI,J = QJ J =1…M; I
    XI,J > = 0; I = 1…N; J = 1…M;
    В качестве примера я рассмотрел транспортную задачу для 2 складов и 5 магазинов.
  • В ячейки C4:C5 записал объемы продукции, имеющиеся на 2 складах.
  • В ячейки E5:I5 - заявки на продукцию, поступившие от магазинов.
  • В ячейки B8:F9 - матрицу транспортных расходов, задающую расходы на перевозку из I-го склада в J-й магазин единицы продукции.
  • В ячейки B13:F14 - план перевозок - матрицу, задающую количество товара, перевезенного из I-го склада в J-й магазин. Начальное распределение плана задано по принципу "каждой сестре по серьге", равномерно распределив всю имеющуюся на складе продукцию по магазинам. Эти ячейки являются регулируемыми и Решатель должен найти более подходящее решение, изменив значения в этих ячейках.
  • В ячейку D15 - записал целевую функцию: {=СУММ(B8:F9*B13:F14)}
  • В ячейки D17:H17 записал ограничения, задающие требование о точном выполнении заявки каждого магазина. Как обычно, я записал соответствующую формулу в первую из этих ячеек: {=СУММ(B13:B14) - E5}

    Затем скопировал ее. При копировании формула автоматически меняется, задавая нужное ограничение. Правда, нужно следить при этом за правильной ориентацией данных. Например, в данном случае формулу нужно копировать в строку, а не в столбец.
  • Затем задал следующую группу ограничений. Эти ограничения отвечают тому естественному условию, что со склада нельзя увести больше продукции, чем там имеется. Формула, помещенная в ячейку D18, имеет вид: {= С4 - СУММ(B13:F13)}

    Эта формула скопирована уже по столбцу в ячейку D19. Подготовительный этап завершен - можно вызывать Решатель.



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

    Транспортная задача

    увеличить изображение
    Рис. 2.21.  Окно Решателя при решении транспортной задачи

    Прежде чем дать команду на решение задачи, я провел настройку параметров в окне Options. В частности я включил флажки, указывающие на линейность модели и положительность переменных. Кроме того, я увеличил точность решения целочисленной задачи, задав в окне Tolerance значение в 1% вместо 5%, принятых по умолчанию.

    Транспортная задача

    Рис. 2.22.  Настройка в окне параметров Решателя при решении транспортной задачи

    Осталось щелкнуть кнопку "Solve" и получить оптимальный план перевозок. Вы можете проанализировать, насколько оптимальный план отличается от равномерного распределения, предложенного в качестве первоначального варианта, и как уменьшились транспортные расходы:

    Транспортная задача

    увеличить изображение
    Рис. 2.23.  Решение транспортной задачи

    Задача 10 Нахождение медианы

    Постановка задачи: Для массива M и элемента Cand вычислить разность между двумя числами - числом элементов массива M, больших и меньших Cand.
    Это вариация задачи о медиане - "среднем" элементе - массива. Медиану можно определить, например, таким алгоритмом: упорядочив массив, взять элемент, находящийся в середине. Есть и более эффективные алгоритмы. Но я решил ограничиться более простой задачей - проверкой на "медианность". Заметим: если все элементы массива M различны и число их нечетно, то, взяв медиану в качестве Cand, искомая в задаче 10 разность даст значение 0. В общем случае, близость искомой разности к нулю является мерой близости параметра Cand к медиане массива M. Но займемся программистскими аспектами этой задачи. У функции, ее реализующей, на входе - массив, а на выходе - скаляр. Мы хотели бы, чтобы эта функция могла вызываться в формулах рабочего листа, а в качестве фактического параметра ей могли быть переданы как объект Range, так и массив чисел. Вот как я реализовал эту функцию, назвав ее IsMediana:
    Public Function IsMediana(M As Variant, Cand As Variant) As Integer 'Дан массив M и элемент Cand. В качестве результата возвращается 'разность между числом элементов массива M, больших и меньших Cand. 'Тем самым можно определить близость Cand к медиане массива M. Dim Pos As Integer, Neg As Integer Pos = 0: Neg = 0 'Анализ типа параметра M If TypeName(M) = "Range" Then For i = 1 To M.Rows.Count For j = 1 To M.Columns.Count If M.Cells(i, j) > Cand Then Pos = Pos + 1 ElseIf M.Cells(i, j) < Cand Then Neg = Neg + 1 End If Next j Next i IsMediana = Pos - Neg
    ElseIf TypeName(M) = "Variant()" Then 'TypeName is "Variant()" 'Это массив, но не совсем настоящий, для него не определены, 'например, функции границ: LBound, UBound. Dim Val As Variant For Each Val In M If Val > Cand Then Pos = Pos + 1 ElseIf Val < Cand Then Neg = Neg + 1 End If Next Val IsMediana = Pos - Neg Else MsgBox ("При вызове функции: IsMediana(M,Cand)" & _ vbCrLf & "M не является массивом или объектом Range!") End If

    End Function
    Прокомментируем работу функции IsMediana.
  • Функции, чьи аргументы имеют универсальный тип Variant, целесообразно строить по принципу разбора случаев. Алгоритм может изменяться в зависимости от типа фактического параметра, задаваемого в момент вызова.
  • Стандартная функция TypeName(V) возвращает в качестве результата конкретный тип параметра V. Это очень мощная функция VBA, способная разобраться не только с внутренними типами самого языка, но и типами объектов, возвращаемых благодаря Автоматизации (Automation). В частности, функция может определить, имеет ли объект тип Range, File, Sheet или Application.
  • В процессе работы функции IsMediana(M,Cand) разбираются три возможных случая: M - объект Range, M - массив типа Variant, M имеет любой другой тип.
  • В случае, когда функция IsMediana вызывается в формуле рабочего листа Excel, то в качестве фактического параметра ей передается объект Range - интервал ячеек этого листа. Следовательно, функция TypeName возвратит строку "Range" в качестве результата. При обработке этого случая организуется цикл по числу строк и столбцов объекта Range, используя свойство Cells этого объекта.
  • Во втором случае обработка основана на том, что функции передан массив Visual Basic типа Variant. Это возможно, когда при вызове в формуле нашей функции ей передается массив констант. Ниже я приведу примеры подобного вызова. Для таких массивов не определены функции границ UBound и LBound. Поэтому обработка в этом случае основана на использовании цикла For Each.
  • В третьем случае, когда наш параметр не является ни массивом констант, ни объектом Range, в качестве результата по умолчанию выдается 0. Но выдается также и окно сообщений с предупреждением о возникшей ситуации.

  • Посмотрим, как это выглядит на экране, и разберем примеры нескольких различных вызовов функции IsMediana в формулах рабочего листа:
    Задача 10 Нахождение медианы

    увеличить изображение
    Рис. 2.5.  Вызов функции IsMediana в формулах рабочего листа
    На рабочем листе сформированы два массива: вектор M, вытянутый в виде столбца, и прямоугольная матрица N. Вектор M записан в ячейках C6:C11, матрица N - в F5:I6. В ячейки E8:E16 я поместил формулы, вызывающие функцию IsMediana. Они не являются формулами над массивами, несмотря на то, что параметром может быть массив рабочего листа. Важно, что результат - скаляр. Если бы результат, возвращаемый функцией, был массивом, формулу следовало бы вызывать как формулу над массивами. Для скалярного результата это не так.
    Хочу обратить внимание на то, что Excel не последователен в этом вопросе. Пользовательская функция, написанная на VBA, аргументы которой являются массивами, но результат которой есть скаляр, может вызываться, как я сказал чуть выше, в обычных формулах рабочего листа. Что касается вызова встроенных функций, то тут ситуация сложнее. Если функции, возвращающей скаляр, передается один массив, то она может вызываться как обычная функция. Если же ей передается несколько массивов, то такой вызов уже должен быть формулой над массивами. Вот классический пример двух вызовов:
    =СУММ(A1:D1) {=СУММ(A1:D1 +A2:D2)}


    Первый вызов работает нормально, а второй должен быть обязательно формулой над массивами, чтобы результатом действительно была сумма всех элементов двух объектов Range.
    Но вернемся к рассмотрению нашей функции IsMediana. В двух первых вызовах функции IsMediana, записанных в ячейках E8, E9, ей передается в качестве параметров имя массива рабочего листа "M" и разные кандидаты: 7 и 8. Они оба годятся на роль медианы этого массива. В следующих двух вызовах проверяются кандидаты на медиану массива N. Как видите, 4 ближе к медиане, чем 3. Следующие два вызова в ячейках E12 и E13 демонстрируют возможность задания диапазона ячеек в момент вызова, что позволяет, например, работать с частью массива. В двух последующих вызовах вообще не используются в качестве входных данных элементы рабочего листа. Входным параметром M является массив констант, заключенный в фигурные скобки, элементы которого разделяются символом ";". В этих случаях фактический параметр уже не является объектом Range, а имеет тип массива с элементами Variant. Поэтому и функция IsMediana будет работать по-другому, в отличие от предыдущих вызовов. Разбор случаев при данном вызове, зависящий от результата, возвращаемого функцией TypeName, приведет к выбору второго варианта. Наконец, вызов, записанный в формуле ячейки E16, демонстрирует случай, когда входной параметр M - обычное число и, следовательно, не является ни объектом Range, ни массивом. Как следствие, разбор случаев в функции IsMediana приводит к третьему варианту и появлению на экране окна сообщений.

    Задача 11 Произведение матриц

    Постановка задачи: Найти произведение прямоугольных матриц A*B
    Из того, что мы узнали ранее, следует, какой вид может иметь заголовок пользовательской функции, решающей эту задачу. Два входных параметра функции должны быть типа Variant. Этот же тип должен быть у возвращаемого функцией значения. Конечно, это не единственно возможное решение. Можно было бы иметь один входной параметр, используя спецификатор ParamArray. Такой способ был бы единственно возможным, если обобщить постановку и попытаться создать функцию, которая должна перемножать произвольное число матриц. Но при умножении двух матриц естественнее иметь и два соответствующих им параметра. Поэтому заголовок получился таким:
    Function MultMatr(A As Variant, B As Variant) As Variant
    Я хочу показать Вам, как написать общую функцию, достаточно широкого назначения. Ее можно будет вызывать в формулах над массивами рабочего листа, передавая ей в качестве фактических параметров A и B массивы рабочего листа (объекты Range). Но не только объекты Range, но и массивы констант будут допускаться в качестве одного или обоих аргументов. Результат работы функции будет записан в массив, выделенный в момент вызова формулы над массивами. Более того, я хочу, чтобы эту же функцию можно было вызывать в обычных функциях и процедурах VBA, передавая в момент вызова массивы VBA в качестве аргументов. Все это, естественно, утяжелит нашу функцию, но позволит мне обсудить отличия "обычных" и "пользовательских функций. С учетом этих замечаний наша функция выглядит так:
    Public Function MultMatr(A As Variant, B As Variant) As Variant 'Умножение матриц. 'Эта функция может вызываться в формулах рабочего листа Excel. 'В этом случае входные параметры являются объектами Range. 'Функцию можно также вызывать в обычных VBA функциях и процедурах, 'передавая ей в качестве параметров массивы VBA.
    Dim AB() As Variant Dim i As Integer, j As Integer, k As Integer Dim N As Integer, M As Integer, P As Integer, Q As Integer Dim Correct As Boolean Dim msg1 As String, msg2 As String Dim Elem As Variant Correct = True

    'Определение размерностей матриц If TypeName(A) = "Range" Then N = A.Rows.Count: M = A.Columns.Count ElseIf TypeName(A) = "Variant()" Then N = UBound(A, 1): M = UBound(A, 2) Else: Correct = False End If If TypeName(B) = "Range" Then P = B.Rows.Count: Q = B.Columns.Count ElseIf TypeName(A) = "Variant()" Then P = UBound(B, 1): Q = UBound(B, 2) Else: Correct = False End If ' Проверка корректности задания размерности If Correct And (P = M) Then 'Размерность задана корректно ReDim AB(1 To N, 1 To Q) 'Построение произведения матриц AB =A*B For i = 1 To N For j = 1 To Q Elem = 0 For k = 1 To M Elem = Elem + A(i, k) * B(k, j) Next k AB(i, j) = Elem Next j Next i MultMatr = AB Else 'Некорректно заданы аргументы или размерность If Not Correct Then msg2 = " При вызове MultMatr некорректно заданы аргументы!" _ & vbCrLf & "По крайней мере, один из них не является" _ & vbCrLf & "ни массивом, ни объектом Range" MsgBox (msg2) Else msg1 = " При вызове MultMatr некорректно задана размерность" _ & " перемножаемых матриц!" & vbCrLf & _ "Число столбцов в первом сомножителе = " & M & vbCrLf & _ "Число строк второго сомножителя = " & P MsgBox (msg1) End If End If End Function
    Сделаем несколько замечаний.
  • Из-за того, что фактические параметры могут иметь разную природу, приходится анализировать тип параметра, используя уже упоминавшуюся функцию TypeName.
  • В зависимости от того, массивом или объектом Range является параметр, по-разному определяются границы массивов.
  • Если хотя бы один из аргументов не принадлежит ни одному из перечисленных типов, вычисления прерываются с выдачей предупреждающего сообщения.
  • Еще одна проверка, которую я счел обязательной, - проверка на корректность задания размеров перемножаемых матриц. Конечный пользователь может легко ошибиться и не соблюсти обязательное требование при умножении матриц: "число столбцов матрицы A должно совпадать с числом строк матрицы B". В этом случае результат не будет получен, и будет выдано предупреждающее сообщение. Если же пользователь неверно выделит область памяти под результирующую матрицу, вычисления будут идти. Правда, если эта область урезана по отношению к требуемой, часть результатов будет потеряна. Если же область выделена с избытком, выводятся "лишние" результаты, полученные путем копирования.
  • Заметьте, сам процесс вычисления результирующей матрицы выполняется одинаково для обоих типов аргументов.
  • Результат получается в динамическом массиве, который на последнем шаге работы и становится значением функции.
  • Функцию MultMatr я использовал двояко, - вызывая ее в формулах над массивами в рабочем листе Excel и в обычной процедуре VBA, когда мне понадобилось получить произведение двух матриц, представленных обычными массивами VBA.

  • Взгляните, как выглядят результаты некоторых экспериментов по умножению матриц на рабочем листе Excel:
    Задача 11 Произведение матриц

    увеличить изображение
    Рис. 2.7.  Умножение матриц
    На рабочем листе я расположил три матрицы разной размерности и дал им имена MatrA, MatrB и MatrC соответственно. Затем, вызывая MultMatr, я получил произведения MatrA*MatrB и MatrB*MatrC, - все выполнилось корректно. Попытка использовать MultMatr для умножения массива констант на матрицу - {1,2; 2,3}*MatrC закончилась неуспехом, поскольку, как я говорил ранее, для массивов констант некорректно работает функция Ubound. При попытке умножения MatrA*MatrC, как и положено, выдалось предупреждающее сообщение о несоблюдении правила размерности перемножаемых матриц.

    Задача 12 Решение системы линейных уравнений и обращение матриц

    Постановка задачи: Найти решение системы линейных уравнений AX = B
    Чтобы одним выстрелом "убить двух зайцев", рассмотрим эту задачу в более общей постановке. Будем полагать, что B - это не вектор правых частей, а матрица из m столбцов, каждый из которых задает правую часть уравнения. Тем самым мы будем решать m систем линейных уравнений с одной и той же левой частью и разными правыми частями. Переход от решения одной системы уравнений к решению m систем сам по себе является важным достоинством рассматриваемого алгоритма. Но более важно то, что здесь же становится возможным находить обратную матрицу для A. Достаточно задать в качестве матрицы правых частей B единичную матрицу E, и тогда матрица X будет обратной к A.
    Заголовок функции, решающей эту задачу, имеет такой же вид, как и при умножении матриц и отличается только именем функции:
    Function SLEQ(A As Variant, B As Variant) As Variant
    Эта функция имеет два входных параметра, - матрицу коэффициентов системы уравнений и матрицу правых частей, и возвращает как результат матрицу X. Конечно, реализация этой функции требует больших усилий, чем умножение матриц. Мне пришлось написать пять процедур, вызываемых в процессе вычислений функции SLEQ. Но сама идея алгоритма достаточно прозрачна. Вначале строится расширенная матрица AB путем присоединения справа матрицы B к матрице A. Затем линейными преобразованиями над ее строками матрица A переводится в единичную матрицу E. Эти же преобразования переводят матрицу B в решение X. Если в качестве B задать единичную матрицу, X будет представлять обратную матрицу. Если B - вектор из одного столбца, то речь идет об обычной системе линейных уравнений. В промежуточном случае B представляет прямоугольную матрицу, и тогда одновременно получа
    R± ется решение для m систем линейных уравнений. Теперь приведу текст функции SLEQ и прокомментирую его, а затем уже приведу процедуры, вызываемые по ходу работы. Вот текст основной функции, вызываемой в формуле над массивами рабочего листа Excel:
    Public Function SLEQ(A As Variant, B As Variant) As Variant 'Решение системы линейных уравнений: AX = B 'A - матрица коэффициентов, B - матрица правых частей. 'X - матрица решений. 'Для каждого столбца B1 матрицы B соответствующий столбец X1 'является решением системы AX1 = B1. 'Если B - единичная матрица E, то X - матрица, обратная к A. Dim i As Integer, j As Integer Dim N As Integer, M As Integer Dim msg1 As String, msg2 As String msg1 = " При вызове SLEQ система уравнений линейно зависима!" msg2 = " При вызове SLEQ некорректно задана размерность системы уравнений!" 'Проверка корректности задания размерности СЛУР. N = A.Rows.Count If (A.Columns.Count = N) And (B.Rows.Count = N) Then 'Размерность задана корректно. M = B.Columns.Count ReDim AB(1 To N, 1 To N + M) 'Построение расширенной матрицы. For i = 1 To N For j = 1 To N AB(i, j) = A.Cells(i, j) Next j Next i For i = 1 To N For j = 1 To M AB(i, N + j) = B.Cells(i, j) Next j Next i 'Цикл линейных преобразований над матрицей AB. Dim k As Integer Dim Independence As Boolean k = 1: Independence = True Do Independence = FindMax(AB, k, i) If Not Independence Then Exit Do Call Change(AB, k, i) Call Normalization(AB, k) Call Linear(AB, k) k = k + 1 Loop While k <= N If Not Independence Then MsgBox (msg1)

    Постановка задачи: Найти решение системы линейных уравнений AX = B
    Чтобы одним выстрелом "убить двух зайцев", рассмотрим эту задачу в более общей постановке. Будем полагать, что B - это не вектор правых частей, а матрица из m столбцов, каждый из которых задает правую часть уравнения. Тем самым мы будем решать m систем линейных уравнений с одной и той же левой частью и разными правыми частями. Переход от решения одной системы уравнений к решению m систем сам по себе является важным достоинством рассматриваемого алгоритма. Но более важно то, что здесь же становится возможным находить обратную матрицу для A. Достаточно задать в качестве матрицы правых частей B единичную матрицу E, и тогда матрица X будет обратной к A.
    Заголовок функции, решающей эту задачу, имеет такой же вид, как и при умножении матриц и отличается только именем функции:
    Function SLEQ(A As Variant, B As Variant) As Variant
    Эта функция имеет два входных параметра, - матрицу коэффициентов системы уравнений и матрицу правых частей, и возвращает как результат матрицу X. Конечно, реализация этой функции требует больших усилий, чем умножение матриц. Мне пришлось написать пять процедур, вызываемых в процессе вычислений функции SLEQ. Но сама идея алгоритма достаточно прозрачна. Вначале строится расширенная матрица AB путем присоединения справа матрицы B к матрице A. Затем линейными преобразованиями над ее строками матрица A переводится в единичную матрицу E. Эти же преобразования переводят матрицу B в решение X. Если в качестве B задать единичную матрицу, X будет представлять обратную матрицу. Если B - вектор из одного столбца, то речь идет об обычной системе линейных уравнений. В промежуточном случае B представляет прямоугольную матрицу, и тогда одновременно получа
    R± ется решение для m систем линейных уравнений. Теперь приведу текст функции SLEQ и прокомментирую его, а затем уже приведу процедуры, вызываемые по ходу работы. Вот текст основной функции, вызываемой в формуле над массивами рабочего листа Excel:
    Public Function SLEQ(A As Variant, B As Variant) As Variant 'Решение системы линейных уравнений: AX = B 'A - матрица коэффициентов, B - матрица правых частей. 'X - матрица решений. 'Для каждого столбца B1 матрицы B соответствующий столбец X1 'является решением системы AX1 = B1. 'Если B - единичная матрица E, то X - матрица, обратная к A. Dim i As Integer, j As Integer Dim N As Integer, M As Integer Dim msg1 As String, msg2 As String msg1 = " При вызове SLEQ система уравнений линейно зависима!" msg2 = " При вызове SLEQ некорректно задана размерность системы уравнений!" 'Проверка корректности задания размерности СЛУР. N = A.Rows.Count If (A.Columns.Count = N) And (B.Rows.Count = N) Then 'Размерность задана корректно. M = B.Columns.Count ReDim AB(1 To N, 1 To N + M) 'Построение расширенной матрицы. For i = 1 To N For j = 1 To N AB(i, j) = A.Cells(i, j) Next j Next i For i = 1 To N For j = 1 To M AB(i, N + j) = B.Cells(i, j) Next j Next i 'Цикл линейных преобразований над матрицей AB. Dim k As Integer Dim Independence As Boolean k = 1: Independence = True Do Independence = FindMax(AB, k, i) If Not Independence Then Exit Do Call Change(AB, k, i) Call Normalization(AB, k) Call Linear(AB, k) k = k + 1 Loop While k <= N If Not Independence Then MsgBox (msg1)


    'Возвращение результата. For i = 1 To N For j = 1 To M AB(i, j) = AB(i, j + N) Next j Next i ReDim Preserve AB(1 To N, 1 To M) SLEQ = AB Else 'Некорректно задана размерность. MsgBox (msg2) End If
    End Function
    Дам теперь необходимые пояснения:
  • В реализации функции можно выделить три основные части: построение расширенной матрицы, цикл линейных преобразований и формирование результирующей матрицы. Для построения расширенной матрицы вводится динамический массив AB. Его заполнение достаточно очевидно. Окончательный результат представляет лишь часть расширенной матрицы и формируется на месте исходной матрицы B. Чтобы выделить эту часть, я использую возможности динамического массива: переопределяю его размерность и затем, используя спецификатор Preserve, сохраняю нужные результаты. Таким образом, новый массив не вводится, а сжимается существующий до нужного размера. Правда, это сжатие возможно лишь для последнего измерения. Поэтому предварительно нужные результаты переписываются в начало массива AB.
  • С содержательной точки зрения основу алгоритма составляет цикл, в котором над матрицей выполняются линейные преобразования. Алгоритм реализует схему Гаусса с выбором главного элемента в столбце. На каждом шаге цикла линейными преобразованиями над строками матрицы ее очередной k-й столбец приводится к единичному столбцу с единицей на диагонали матрицы и остальными нулевыми элементами. Вначале вызывается функция FindMax, которая в k-ом столбце находит максимальный по модулю элемент, расположенный ниже диагонали. Процедура Change меняет строки местами, так чтобы найденный максимальный элемент стал диагональным. Процедура Normalization нормирует k-ю строку делением всех ее элементов на элемент, расположенный на диагонали. Сам этот элемент тем самым становится равным 1. Процедура Linear выполняет линейные преобразования над строками, вычитая из каждой строки k-ю строку, умноженную на подходящий коэффициент, так чтобы сделать нулевыми все элементы k-го столбца, расположенные выше и ниже диагонали.
  • Вначале проверяется корректность задания размерности матриц A и B. Подобная проверка проводилась при умножении матриц. Вторая проверка позволяет обнаружить линейную зависимость строк или столбцов матрицы A. В случае линейной зависимости система уравнений не имеет единственного решения, а обратную матрицу вычислить невозможно.
  • Данная функция является чисто пользовательской, и я ориентируюсь лишь на один способ передачи параметров, - в виде Range-объектов.



  • Приведу теперь тексты процедур, вызываемых в теле функции SLEQ, и начну с функции FindMax:
    Public Function FindMax(A() As Variant, ByVal Num As Integer, _ Ind As Integer) As Boolean 'В столбце с номером Num матрицы A, начиная с диагонального элемента, 'ищется максимальный по абсолютной величине элемент, 'и вычисляется его индекс Ind. 'Функция возвращает true, если элемент отличен от нуля. Dim i As Integer, Elem As Variant Elem = Abs(A(Num, Num)): Ind = Num For i = Num + 1 To UBound(A, 1) If Abs(A(i, Num)) > Elem Then Elem = Abs(A(i, Num)): Ind = i End If Next i FindMax = Not (Elem = 0)
    End Function
    Прежде всего, заметьте, что в теле пользовательской функции SLEQ вызываются обычные процедуры и функции. Помимо своей основной задачи - нахождения максимального по модулю элемента столбца и определения его индекса Ind, -булева функция FindMax позволяет определить, является ли система уравнений линейно зависимой. Если найденный элемент равен 0, то и все остальные элементы равны 0, а это и есть признак линейной зависимости. Эта проверка предохраняет нас от возможного деления на 0. Конечно, разумнее выполнять не строгую, а слабую проверку на 0, полагая, что система "почти линейно зависима" (плохо обусловлена), если вычисляемый нами элемент близок к 0. Но это уже вычислительные, а не программистские аспекты, которые здесь обсуждать не место. Следующие две процедуры Change и Normalization совсем простые:
    Public Sub Change(A() As Variant, _ ByVal Ind1 As Integer, ByVal Ind2 As Integer) 'Перестановка строк с индексами Ind1 и Ind2 матрицы A Dim i As Integer, Elem As Variant If Not (Ind1 = Ind2) Then For i = LBound(A, 2) To UBound(A, 2) Elem = A(Ind1, i) A(Ind1, i) = A(Ind2, i) A(Ind2, i) = Elem Next i End If End Sub
    Public Sub Normalization(A() As Variant, Ind As Integer) 'Нормировка строки с индексом Ind матрицы A 'делением на диагональный элемент. Dim i As Integer, Elem As Variant Elem = A(Ind, Ind): A(Ind, Ind) = 1 For i = Ind + 1 To UBound(A, 2) A(Ind, i) = A(Ind, i) / Elem Next i

    Постановка задачи: Используя Решатель, найти

    Постановка задачи: Используя Решатель, найти решение уравнения: F(x) = a*x^4 + b*x^3 + c*x^2 + d*x + e =0,
    В задачах 3 и 4 предыдущей главы для нахождения корней этого уравнения я применил метод Ньютона, и сам определял процесс построения решения. Покажу теперь, как воспользоваться Решателем. Наша задача без труда укладывается в общую постановку. Целевая функция - уравнение F(x). Требуется найти такое значение регулируемой ячейки x, чтобы функция F(x) приняла фиксированное значение, равное 0. Никаких ограничений на переменную x не накладывается. Так что все, что нужно, - записать в одну из ячеек рабочего листа переменную x, в другую - формулу, задающую функцию F(x), и вызвать Solver (Решатель) из меню Сервис. Вот как это выглядит на рабочем листе:
    Постановка задачи: Используя Решатель, найти

    увеличить изображение
    Рис. 2.9.  Вызов Решателя для поиска корней нелинейного уравнения
    Как видите, в диалоговом окне Решателя:
  • в окне "Установить ячейку цели" (Set Target Cell) задана ссылка на ячейку, содержащую формулу для вычисления значения функции F(x);
  • включен переключатель "Значение" (Value of) и задано значение 0 в его окошке;
  • в окне регулируемых ячеек (By Changing Cells) задана ссылка на ячейку, содержащую x; кстати, можно щелкнуть командную кнопку "Догадка" (Guess), и Решатель автоматически попытается сформировать весь список регулируемых ячеек, от которых зависит целевая функция;
  • так как в нашем случае ограничений задавать не нужно, то больше ничего делать не нужно и можно запустить процесс поиска решения щелчком командной кнопки "Решение" (Solve).

  • Полученное решение зависит от выбора начального приближения, которое задается в ячейке x перед обращением к Решателю. Вот так выглядит окно Решателя при успешном завершении поиска решения:
    Постановка задачи: Используя Решатель, найти

    увеличить изображение
    Рис. 2.10.  Окно Решателя при успешном поиске решения
    Наряду с появлением уведомления об успехе поиска в окне "Отчеты" (Reports) предлагается выбрать любые из трех возможных отчетов по результатам работы. Кроме того, можно сохранить полученные результаты или восстановить первоначальные значения. Можно также запомнить созданную постановку задачи. Вот как выглядит отчет "Результаты" (Answer) в нашем случае:
    Постановка задачи: Используя Решатель, найти

    Рис. 2.11.  Отчет "Результаты", уведомляющий о результатах решения

    Постановка задачи: Используя Решатель, найти

    Постановка задачи: Используя Решатель, найти решение системы линейных уравнений AX = B
    Решение системы уравнений как линейных, так и нелинейных нетрудно сформулировать как оптимизационную задачу в постановке, требуемой Решателем. Пусть имеется система уравнений:
    F1(X) = 0; F2(X) = 0; … Fn(X) = 0;
    Для Решателя эта задача естественным образом формулируется так:
  • вектор переменных X представляет массив регулируемых ячеек;
  • функции FI(X) задают ограничения типа равенств;
  • в качестве целевой функции можно выбрать, например, Ф(X) = SUM(FI(X)). При этом можно выбирать любой критерий для целевой функции, устремляя ее к минимуму, максимуму или требуя нулевое значение для Ф(X).

  • В качестве примера я использовал те же данные, что и в задаче 12. Взгляните, как выглядит постановка этой задачи в окне Решателя:
    Постановка задачи: Используя Решатель, найти

    увеличить изображение
    Рис. 2.14.  Постановка задачи в окне Решателя
    А вот как выглядит решение, найденное Решателем:
    Постановка задачи: Используя Решатель, найти

    Рис. 2.15.  Решение системы линейных уравнений, найденное Решателем


    Постановка задачи: Используя Решатель, найти решение системы линейных уравнений AX = B
    Решение системы уравнений как линейных, так и нелинейных нетрудно сформулировать как оптимизационную задачу в постановке, требуемой Решателем. Пусть имеется система уравнений:
    F1(X) = 0; F2(X) = 0; … Fn(X) = 0;
    Для Решателя эта задача естественным образом формулируется так:
  • вектор переменных X представляет массив регулируемых ячеек;
  • функции FI(X) задают ограничения типа равенств;
  • в качестве целевой функции можно выбрать, например, Ф(X) = SUM(FI(X)). При этом можно выбирать любой критерий для целевой функции, устремляя ее к минимуму, максимуму или требуя нулевое значение для Ф(X).

  • В качестве примера я использовал те же данные, что и в задаче 12. Взгляните, как выглядит постановка этой задачи в окне Решателя:
    Постановка задачи: Используя Решатель, найти

    увеличить изображение
    Рис. 2.14.  Постановка задачи в окне Решателя
    А вот как выглядит решение, найденное Решателем:
    Постановка задачи: Используя Решатель, найти

    Рис. 2.15.  Решение системы линейных уравнений, найденное Решателем
    Нужно понимать, что когда используется Решатель, многое зависит еще и от того, как сформулирована задача. Решатель допускает различные постановки одной и той же задачи, а также имеет ряд параметров, позволяющих управлять процессом решения. Так, при решении системы линейных уравнений можно, например, рассматривать задачу как задачу минимизации следующей функции:
    Ф(X) = F12 (X) + F22 (X) + … +Fn2 (X) -> Min
    При минимизации этой функции можно и не задавать ограничения. Понятно, что решение системы исходных уравнений является точкой минимума целевой функции Ф(X). На следующих рисунках показана постановка задачи и ее решение для этого варианта постановки:
    Постановка задачи: Используя Решатель, найти

    увеличить изображение
    Рис. 2.16.  Второй вариант постановки задачи
    Постановка задачи: Используя Решатель, найти

    увеличить изображение
    Рис. 2.17.  Решение, найденное Решателем, во втором варианте
    Как видите, в обеих постановках Решатель успешно находит решение системы линейных уравнений. Чтобы убедиться, что "проклятие размерности" не так уж и страшно Решателю, я провел еще один эксперимент, выбрав задачу большей размерности, и стал искать решение системы из пяти линейных уравнений. На рабочем листе Excel я задал:
  • матрицу коэффициентов этой системы - myA;
  • вектор правых частей - myB;
  • вектор переменных - SolX5
  • в ячейках Myl1-Myl5 формулы, задающие соответствующие линейные уравнения. Фактически, я ввел одну формулу в ячейку myl1: {=СУММ(B30:F30*SolX5) -I30}


    Затем скопировал ее в соседние ячейки. При копировании автоматически изменяется диапазон ячеек, который указывает на следующую строку матрицы коэффициентов, и изменяется ссылка на ячейку, задающую правую часть уравнения.
  • В ячейки с именами MYST и MYST1 я ввел формулы для целевых функций, соответствующих двум различным постановкам: =Myl1 + Myl2 + Myl3 + Myl4 + Myl5; = Myl1* Myl1 + Myl2* Myl2 + Myl3* Myl3 + Myl4* Myl4 + Myl5* Myl5;
  • В первом случае целевая функция линейна, но задаются дополнительные ограничения. Во втором - минимизируется квадратичная функция при отсутствии ограничений. Диалоговое окно Решателя в момент задания его параметров при оптимизации первой целевой функции выглядит так:

  • Постановка задачи: Используя Решатель, найти

    увеличить изображение
    Рис. 2.18.  Постановка задачи линейного программирования
    Обратите внимание на некоторые детали постановки задачи. В окне изменяемых ячеек я задал имя вектора решений - SolX5. Вектор ограничений распространяется на весь диапазон уравнений, но формально задан в виде одного векторного ограничения. Перед тем, как приступить к решению, я изменил настройку параметров Решателя. Взгляните, как выглядит окно Options (Параметры), в котором производится настройка:
    Постановка задачи: Используя Решатель, найти

    Рис. 2.19.  Настройка параметров Решателя
    Как видите, возможностей управлять решением достаточно много, и я еще позже поговорю о них подробнее. Сейчас же замечу, что я оставил без изменения максимальное время, отводимое на решение - 100 секунд, максимальное число итераций - 100, требуемую точность выполнения ограничений - 0, 0000001. Единственное, что я сделал, это включил флажок Linear, указав, что в данной постановке Решатель имеет дело с задачей линейного программирования, поскольку все ограничения и целевая функция линейно зависят от переменных задачи.
    Точное решение нашей системы уравнений задается вектором (1, 2, 3, 4, 5). Решатель нашел это решение.
    Постановка задачи: Используя Решатель, найти

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

    Задача 9 Транспонирование матриц

    Постановка задачи: Транспонировать матрицу A: B= AT
    Наша конкретная задача - транспонирование матрицы на новое место - решается сравнительно просто, поскольку для этого есть стандартная функция ТРАНСП(X). В английской версии и при программировании на VBA она называется Transpose. Можно, например:
  • ввести прямоугольную матрицу в область рабочего листа, скажем, в ячейки B34:D35;
  • дать имя этой области, например, "MatrixA";
  • выделить область ячеек с другой ориентацией для транспонированной матрицы, например A37:B39;
  • ввести в эту область формулу над массивами: "{=ТРАНСП(MatrixA)}"

  • Можете убедиться, эти действия приводят к желаемому результату:
    Задача 9 Транспонирование матриц

    Рис. 2.4.  Два решения в задаче транспонирования матрицы
    Хорошо, что есть функция ТРАНСП. Но можно ли справиться с этой классической задачей, не прибегая к вызову стандартной функции? Эта задача имеет различные вариации: матрица может быть квадратной или прямоугольной, транспонировать ее можно на месте (для квадратных матриц) или получать результат, как новую матрицу. При транспонировании на месте возникают циклические ссылки, для прямоугольных матриц появляется проблема различной ориентации исходной матрицы и результата. Но даже в самом простом случае транспонирования квадратной матрицы на новое место при построении собственного решения написать одну или несколько формул в ячейках рабочего листа, решающих эту задачу для матриц произвольного размера, не удается. В отличие от приведенного в предыдущей главе примера с сигнальной матрицей здесь требуется оперировать с индексами элементов, а формулы над массивами этого не допускают. Выход в том, чтобы написать подобную функцию на VBA, вызов которой приведет к желаемому эффекту. Давайте создадим собственную функцию Trans, работающую, как и стандартная функция ТРАНСП. У функции Trans один аргумент. При вызове в качестве фактического параметра ей передается массив рабочего листа (объект Range), заданный в виде диапазона ячеек или своим именем. Этот массив (прямоугольная матрица) транспонируется, и новая матрица возвращается как результат. Она должна быть записана в выделенную в момент вызова область ячеек, отведенных для транспонированной матрицы. Функция Trans, как и функция ТРАНСП, должна вызываться в формуле над массивами и, следовательно, связывается с каждой ячейкой выделенной области.
    Итак, с программистской точки зрения, нам нужно написать функцию от одного параметра, тип которого должен допускать при вызове массивы рабочего листа (объекты Range) и такой же массив возвращать как результат функции. Каким же должен быть тип параметра и тип функции? Если Вы внимательно следили за предыдущими примерами, то понимаете, что таковым является тип Variant - этот универсальный тип успешно работает в данной ситуации. Остальное - дело техники. Вот текст функции Trans, решающий нашу задачу:
    Public Function Trans(A As Variant) As Variant 'Функция предназначена для транспонирования массивов 'рабочего листа Excel - объектов Range. 'При работе с переменной A используются свойства объекта Range

    Dim B() As Variant Dim i As Integer, j As Integer 'Динамическому массиву В выделяется память ReDim B(1 To A.Columns.Count, 1 To A.Rows.Count) As Variant For i = 1 To A.Rows.Count For j = 1 To A.Columns.Count B(j, i) = A.Cells(i, j) Next j Next i 'Массив B возвращается в качестве результата функции Trans. Trans = B End Function
    Нам остается сделать несколько замечаний:
  • Параметр и функция имеют тип Variant, согласуемый как с массивом, так и объектом Range.
  • Чтобы получить транспонированную матрицу, нам понадобился динамический массив B. Оператор ReDim в процедуре Trans позволяет массиву получить память, - его размеры можно определить, зная свойства объекта Range, задающего входной массив A.
  • Операция транспонирования реализуется двойным циклом.
  • На последнем шаге работы функции ее значением становится сформированный массив B. Он и будет записан в выделенную область памяти рабочего листа.

  • Заметьте, на предыдущем рисунке показаны оба варианта решения задачи транспонирования матрицы с использованием стандартной функции ТРАНСП и собственной функции Trans.
    Хочу еще раз обратить внимание на то, чего делать нельзя. Например, в данной задаче многим программистам хотелось бы получать результат в качестве побочного эффекта, введя функцию:
    ТрансТакНельзя(A As Variant, B As Variant) As Boolean
    Эта функция получала бы два массива A и B - входной и результирующий. Транспонированная матрица (массив B) получалась бы как побочный эффект. Результатом работы функции могло бы быть значение True в случае успеха работы и False в противном случае. Такая схема, возможная в других языках и в том числе на VBA недопустима, когда функция вызывается в Excel. Пользовательские функции, вызываемые в формулах ячеек рабочего листа, не допускают побочного эффекта, приводящего к изменению массивов на рабочем листе. Единственная возможность - передать массив как результат пользовательской функции, вызываемой в формуле над массивами. Об этой особенности Excel я уже говорил ранее, но не грех напомнить еще раз об этой важной специфике машины вычислений Excel.

    Задача о краске

    Мне нравится рассказывать об этой задаче, которая принадлежит В. Очкову, и взята "из жизни" в период середины 90-х годов, когда бартер играл основную роль при расчетах. С тех пор ситуация в экономике существенно изменилась в лучшую сторону, хотя до нормы еще далеко. При описании содержательной постановки я хочу сохранить весь внешний "антураж". Заметьте, цены в те годы примерно на 4 порядка отличались от нынешних. Суть задачи такова. Коллектив программистов под руководством В. Очкова написал программу и продал ее за 14 миллионов лакокрасочному предприятию. "Живых" денег предприятие не платило, но могло в пределах указанной суммы расплатиться краской - своей продукцией. Институт, в котором работал В. Очков, нуждался в краске и готов был оплатить привезенную краску. Краска выпускалась в двух видах тары - больших и малых банках (барабанах), емкость которых составляла 55 и 15 литров, а стоимость пустых барабанов составляла - 30 и 24 тысячи рублей. Литр краски стоит 14 600 рублей. Спрашивается, сколько больших и сколько малых барабанов следует взять, если учесть, что институт заинтересован получить как можно больше краски, а создатель программы скорее заинтересован в том, чтобы как можно меньше денег оставить "лакокрасочникам". Типичная оптимизационная задача!
    Для решения этой задачи (кстати, это задача с целочисленными переменными) автор использовал Решатель. Благо Excel уже тогда можно было найти повсюду, в том числе и в бухгалтерии лакокрасочного завода. В поставленной Решателю задаче требовалось максимизировать объем получаемой краски при естественном ограничении на сумму имеющихся денег и ограничений целочисленности на искомые переменные - число больших и малых барабанов. Используя установки параметров, принятые по умолчанию, найденный Решателем "оптимальный" ответ гласил, следует взять 2 малых и 16 больших барабанов с краской. Это давало 910 литров краски, но оставляло предприятию 186000 рублей. Возможно, остаток денег был большой, но В. Очков не поверил Решателю и стал искать другой ответ. И нашел лучшее решение. Оно таково: следует взять 15 больших и 6 малых барабанов. При этом краски будет больше (915 л), а денег, заработанных автором, у завода останется значительно меньше (47 000 руб.). Надеюсь, Вы понимаете, что второе решение было получено за счет разумного управления параметрами Решателя. Я напомню, что когда решается задача с целочисленными ограничениями на переменные, вначале ищется решение без учета таких ограничений. Здесь такое оптимальное решение можно найти самому, поскольку понятно, что максимизации объема можно добиться, закупая краску в больших банках. Наша сумма денег позволяет взять примерно 16,8 больших барабанов, т. е. около 924 л. Но брать неполные банки нельзя и, следовательно, нужно искать целочисленное значение. В окрестности найденного оптимального решения Решатель ищет ближайшую целочисленную точку. Значение целевой функции в ней не должно отличаться более чем на величину, заданную параметром Tolerance. Напомним, по умолчанию это значение равно 5%, что в данном примере немало: 46,2 л. Первое решение, выданное Решателем, вполне укладывается в допустимые рамки. Если уменьшить значение параметра Tolerance до 1%, Решатель найдет другое, действительно оптимальное целочисленное решение.
    История с "оптимальными" решениями имеет продолжение. Рассмотрим еще одно решение с другой целевой функцией, в котором минимизировалась сумма денег, оставляемая заводу. В этом решении следует взять 37 малых и только 6 больших барабанов. Тогда заводу останется только 11 000 руб., так что краска будет закуплена почти на все заработанные автором деньги. Правда, краски будет меньше, - всего 885 литров. Но! Когда это решение было показано тем, кому предназначалась краска, они также признали его наилучшим. С большими барабанами больше возни, и краски на переливах из большого барабана теряется больше. Если ввести дополнительные ограничения на потери, то нормальное решение 37 и 6 может быть получено и при максимизации объема закупаемой краски. Обратите внимание, как, решая, по сути, одну и ту же оптимизационную задачу, можно найти принципиально разные решения. Многое зависит от удачной постановки, выбора критериев, ограничений. Немаловажное значение имеет и умение управлять параметрами алгоритмов оптимизации.
    Давайте и мы решим эту задачу. Приведу два решения с различными целевыми функциями. Я не буду подробно рассказывать, в какие ячейки помещаются исходные данные, какие формулы записаны в ячейках. Все это, как и результаты, показано на рисунке:
    Задача о краске

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

    увеличить изображение
    Рис. 2.25.  Минимизация остатка денег

    Задачи с массивами и программирование

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

    Мир объектов Excel 2000

    Адресация ячеек

    До сих пор в наших примерах мы использовали так называемую относительную адресацию ячеек таблицы в формате А1. Имя ячейки в этом формате строится из имени столбца (их 256 - A, B,…Z, AB, …AZ,….HZ, IA, …IV) и номера строки (1..65536). Адреса ячеек в этом формате, как мы видели при рассмотрении свойства Range, являются относительными. В этом случае начало координат, задающее нумерацию строк и столбцов, связывается с объектом, вызвавшим Range. Относительные ссылки вещь весьма полезная. Но иногда наряду с относительными ссылками требуются и абсолютные ссылки. Excel достаточно гибок в этом вопросе и предоставляет возможность независимых абсолютных ссылок на строку и столбец. Признаком абсолютной ссылки является знак "$", предшествующий имени строки и (или) столбца. Так что адреса: Z10, Z$10, $Z10, $Z$10 в зависимости от контекста могут именовать одну и ту же или четыре разные ячейки. Адрес ячейки на рабочем листе является лишь частью полного адреса, который, в общем случае, включает адрес листа и адр ес книги. При задании полного адреса имя листа сопровождается знаком "!". Имя книги должно заключаться в квадратные скобки. Эта книга должна быть открыта. Вот пример адресации:
    'Example 10 Debug.Print Range("$A$3").Value Debug.Print Range("Лист2!$A$3").Value Debug.Print Range("[BookOne.xls]Лист2!$A$3").Value
    В первой строке приведена абсолютная ссылка на ячейку А1 текущего рабочего листа (Лист2), во второй - ссылка на эту же ячейку, но имя включает и имя листа, в третьей - дано полное имя, включающее имя книги.

    Для задания адресов ячеек помимо

    Для задания адресов ячеек помимо формата A1 можно использовать и другой, так называемый R1C1 - формат. Он называется так, поскольку адрес задается индексом строки (Row) и индексом столбца (Column). И здесь ссылки бывают абсолютными и относительными. В абсолютных ссылках указываются действительно индексы ячейки, в относительных - их смещение по отношению к активной ячейке. Смещение в относительных ссылках задается в квадратных скобках и со знаком, указывающим направление смещения по отношению к активной ячейке. Вот пример, в формулах которого использовались ссылки формата R1C1:

    'Example 11 Range("A3") = 11 Range("A4") = "=R1C1+5" Range("A5:A6") = "=R[-2]+R[-1]"

    Здесь следует обратить внимание на два обстоятельства:

  • При вызове Range его параметры можно задавать только в формате А1. Поэтому в левой части мы сохранили "старый способ" адресации. В формулах целесообразнее применять адресацию в формате R1C1, чтобы явно подчеркнуть относительный характер ссылок, задаваемых в формулах.
  • Если, как часто бывает, вычисления в формулах, распространяются на диапазон, связанный с одним столбцом или одной строкой, то можно задать ссылку, используя только один индекс. В данном примере в последней строке было задано смещение по строкам, поскольку столбец остается неизменным и его можно не указывать


  • Источники данных и структура объекта Chart

    Диаграмма Excel, предоставляя пользователям широкий спектр возможностей по отображению данных, не может быть просто устроена, - она имеет достаточно сложную внутреннюю структуру. Соответственно, такую же сложную структуру имеет и объект Chart. Прежде чем переходить к деталям, давайте рассмотрим общую картину. В диаграмме можно выделить:
  • общую область, занятую диаграммой. Понятно, что можно менять размеры, внешний вид и другие характеристики общей области.
  • Область данных, в которой и строится собственно диаграмма.
  • Источник данных и данные этого источника. Эти объекты играют центральную роль, и о них я расскажу подробнее.
  • Заголовок диаграммы и другие подписи.
  • Оси.
  • Легенду.

  • Всем этим понятиям соответствуют целый ряд объектов, их свойств и методов. Рассмотрим более подробно источники данных и организацию данных при построении диаграммы. Одним из самых простых и наиболее употребительных источников данных является таблица Excel. Вот как выглядит таблица, используемая для построения диаграмм, показанных на рис. 3.11-3.14:
    Источники данных и структура объекта Chart

    Рис. 3.15.  Таблица, являющаяся источником данных для построения диаграмм
    Взгляните еще на одну таблицу Excel и диаграмму, для которой данная таблица выступает источником данных:
    Источники данных и структура объекта Chart

    Рис. 3.16.  Диаграмма с источником данных
    Для того чтобы можно было построить диаграмму, необходимо иметь, по крайней мере, один ряд данных. Напомню специальные термины, применяемые при построении диаграмм. Ось X называются осью категорий и значения, откладываемые на этой оси, называются категориями. Значения отображаемых в диаграмме функций и гистограмм составляют ряд данных. Ряд данных, представляющий последовательность числовых значений, является одним из центральных понятий в построении диаграмм. Этому понятию соответствует объект Series. Поскольку при построении диаграммы, как правило, используется несколько рядов данных, то важную роль играет понятие совокупности рядов данных, которой с объектной точки зрения соответствует коллекция - объект SeriesCollection. Замечу сразу, что все ряды в одной коллекции должны иметь одну и ту же размерность. Excel позволяет строить смешанные диаграммы, где на одной диаграмме одновременно строятся несколько различного типа диаграмм, например, графики и гистограммы. В такой смешанной диаграмме каждому типу соответствует своя группа данных. Группы одной диаграммы составляют коллекцию ChartGroups, элементы которой принадлежат классу ChartGroup. Итак, каждое значение, отображаемое на диаграмме, является членом некоторого ряда данных, ряды объединятся в коллекции. Коллекция рядов связывается с группой, каждая из которых позволяет отобразить диаграмму определенного вида. Группы объединяются в коллекцию групп. В общем случае, каждый ряд данных может иметь свой источник - свое положение, например диапазон ячеек некоторой рабочей книги Excel.
    Чаще всего, хотя и не всегда, стараются делать так, чтобы у диаграммы был один источник данных, в роли которого выступает таблица Excel. Каждая из таблиц, показанных на двух предыдущих рисунках, служит единственным источником данных для соответствующих диаграмм. Поскольку построенные диаграммы не являются смешанными, то все данные таблицы составляют одну группу - одну совокупность рядов данных. Заметьте, удобно, как это сделано и в наших примерах, чтобы ряды в таблице были именованными. Если нужно задать значения категорий, то они должны составлять первый ряд таблицы, который должен быть неименованным, по крайней мере, в тех случаях, когда он состоит из числовых значений. Это позволяет отличить ряд категорий от обычных рядов данных. Значения категорий можно не задавать, тогда по умолчанию в этой роли выступает начальный отрезок натурального ряда - 1, 2, 3 и так далее. Заметьте также, в качестве рядов данных можно рассматривать как строки, так и столбцы таблицы. Можно построить диаграмму в предположении, что строки таблицы являются рядами данных, а можно полагать, что таковую роль играют столбцы. В наших примерах в первой таблице, отражающей динамику продаж, в качестве рядов использовались именованные столбцы таблицы; фамилии дилеров, осуществляющих продажу, выступают в качестве имен столбцов источника данных. Во второй таблице именованные строки задают ряды данных. Обе таблицы в качестве первого ряда задают значения, располагаемые на оси категорий.
    Тип диаграммы влияет на ее структуру и предъявляет определенные требования к рядам данных. Так, для построения круговой диаграммы всегда используется только один ряд данных, для трехмерных диаграмм необходимо не менее трех рядов с учетом значений оси категорий. При построении двумерной диаграммы, отображающей графики, показанные на рис. 3.16, используются три ряда данных - один из них задает значения координат на оси X, два других - соответствующий значения двух функций.
    Хотя детальное обсуждение объектной модели Chart еще предстоит, хочу, предваряя это обсуждение привести пример программного построения диаграммы, где в качестве нескольких источников данных будут использоваться различные диапазоны ячеек рабочего листа Excel, а не единая таблица. По ходу дела в этом примере встретятся многие объекты, упоминавшиеся в этом обзоре, - Series, SeriesCollection и другие.
    Public Sub CreatingChart() 'Эта процедура строит диаграмму 'Добавить новую диаграмму на лист Dim MySh As Worksheet, MyChO As ChartObject, MyCh As Chart Dim Gr As ChartGroup, SC As SeriesCollection, Sr As Series Dim CG As ChartGroups Set MySh = ThisWorkbook.Worksheets(4) Set MyChO = MySh.ChartObjects.Add(50, 520, 320, 150) Set MyCh = MyChO.Chart

    ' Добавить ряды данных Set SC = MyCh.SeriesCollection SC.Add Source:=MySh.Range("B26:F27"), RowCol:=xlRows SC.Add Source:=MySh.Range("B29:F29"), RowCol:=xlRows 'Установить тип диаграммы для рядов данных Set Sr = MyCh.SeriesCollection(1) Sr.ChartType = xlColumnClustered Set Sr = MyCh.SeriesCollection(2) Sr.ChartType = xlColumnClustered Set Sr = MyCh.SeriesCollection(3) Sr.ChartType = xlLineMarkers

    'Добавить свою ось для графика Sr.AxisGroup = xlSecondary

    'Изменить ось категорий MyCh.Axes(xlCategory).CategoryNames = _ Range("C31:F31") 'Array("янв.", "февр.", "март", "апр.")

    'Работа с группами Set CG = MyCh.ChartGroups Set Gr = CG(2) Debug.Print CG.Count Debug.Print ; Gr.SeriesCollection(1).Name

    'Изменить внешний вид - цвет графика With Sr.Border .ColorIndex = 30 .Weight = xlThin .LineStyle = xlContinuous End With Sr.MarkerBackgroundColorIndex = xlColorIndexNone Sr.MarkerForegroundColorIndex = 30 End Sub

    Прокомментируем работу этой программы:

  • На первом шаге ее работы создается сам объект Chart, представляющий диаграмму, встроенную в рабочий лист. Как обычно, для создания этого объекта вначале создается контейнер.
  • Теперь задаются ряды данных, и формируется коллекция SeriesCollection. Ряды создаются методом Add этой коллекции. Обратите внимание, для создания трех рядов данных используются два различных диапазона ячеек рабочей книги.
  • Для каждого ряда задается тип диаграммы, отображающей этот ряд. Для первых двух рядов, задающих планируемые и фактические продажи продукции, выбрано представление в виде гистограммы, для третьего ряда - график, задающий изменение цен.
  • Стандартные значения оси категорий меняются на названия месяцев, взятые из некоторого диапазона ячеек. Закомментированная строка показывает, что для этой цели может использоваться и массив строковых значений. Здесь также демонстрируется работа с объектом Axis - элементом коллекции Axes, - задающим ось категорий.
  • Следующий шаг демонстрирует работу с группами. Поскольку создана смешанная диаграмма с двумя разными типами, то и групп будет две. Отладочная печать показывает, что групп действительно две, и ряд, связанный со второй группой имеет имя "Цена".
  • На заключительном шаге работы изменяется цвет графика и маркеров, отмечающих точки на графике.
  • В заключение осталось взглянуть, как выглядит построенная диаграмма.



  • Источники данных и структура объекта Chart

    увеличить изображение
    Рис. 3.17.  Программно построенная диаграмма с несколькими источниками данных

    В заключение этого параграфа следует сказать, что сегодня Excel интенсивно используется для проведения анализа данных, хранящихся в различных базах и хранилищах данных, в том числе для анализа OLAP - кубов, представляющих многомерные источники данных. Как правило, в этом случае для анализа таких данных в Excel строится сводная таблица. Источником данных для нее являются внешние источники - хранилища данных, а она сама является источником данных для построения сводной диаграммы, интерактивно изменяющейся в процессе работы со сводной таблицей. О сводных таблицах и сводных диаграммах - этом мощном инструменте анализа данных более подробный разговор пойдет в других главах этой книги.

    После этих общего обзора перейдем к более детальному рассмотрению диаграмм с объектной точки зрения.

    Изменения в объектной модели объекта WorkSheet

    Изменения объектной модели не обошли стороной и рассматриваемый нами объект WorkSheet. Многие объекты, встроенные в объект WorkSheet, как, например, уже упоминавшийся объект QueryTable приобрели новые свойства и методы. Два новых свойства появились и у самого объекта Worksheet. Если новое терминальное свойство DisplayRightToLeft вряд ли представляет интерес для российских программистов, поскольку связано с правосторонними языками, то свойство-участник Scripts, возвращающее коллекцию объектов класса Script, представляет несомненный интерес. Каждый элемент этой коллекции задает блок script-кода, используемого в возможных сценариях при публикации рабочей книги в интернет.

    Как получить объект Chart

    Объект Chart задает диаграмму, расположенную на листе рабочей книги или на отдельном листе диаграммы. В зависимости от типа листа получение этого объекта ведется по-разному. Я уже об этом говорил, но хочу теперь систематизировать приводимые ранее сведения. Рассмотрим все возможные случаи:
  • Диаграмма встроена в рабочий лист. На рабочем листе может находиться несколько встроенных диаграмм. Коллекция ChartObjects задает совокупность объектов - контейнеров, содержащих эти диаграммы. Зная индекс или имя диаграммы, можно получить доступ к нужному элементу этой коллекции, а свойство Chart позволяет получить объект, задающий диаграмму. Так что вызов ThisWorkbook.Worksheet(3).ChartObjects(1).Chart
    вернет объект Chart, задающий первую диаграмму, расположенную на третьем рабочем листе текущей рабочей книги. Я хотел выделить этот объект, вызвав его метод Select, но напрямую метод не вызывается, так что пришлось применить обходной маневр, активизировав вначале контейнер, а затем выделив область диаграммы:
    ThisWorkbook.Worksheets(3).ChartObjects(1).Activate ActiveChart.ChartArea.Select
    Так что заметьте, в этом случае приходится часто использовать оба объекта - ChartObjects и Chart для решения возникающих задач.
  • Диаграмма расположена на отдельном листе диаграммы. Специальные листы диаграмм рабочей книги составляют, как уже говорилось, коллекцию Charts. Хочу обратить внимание на два момента. Хотя коллекция представляет собой совокупность специальных листов рабочей книги, элементы этой коллекции являются не столько листами, сколько объектами Chart, задающими диаграммы. Эта ситуация не является типичной для коллекций. Связано это с тем, что на таком листе располагается только один объект Chart. Именно поэтому лист отождествляется с диаграммой. Вызов, который я сделал в отладочном окне Immediate: ThisWorkbook.Charts(1).Select ?ActiveChart.ChartTitle.Text
    выделил в текущей рабочей книге первый лист с диаграммой, а, следовательно, сделал активной и саму диаграмму. После чего стало возможным получить заголовок диаграммы. Заметьте, в данной ситуации метод Select работает без проблем. Для получения диаграммы я использовал коллекцию Charts, но заметьте, эта коллекция является частью коллекции Sheets, поэтому тот же результат можно получить, используя эту коллекцию:

    ThisWorkbook.Sheets(2).Select ?ActiveChart.ChartTitle.Text
  • Активная диаграмма. Добраться до объекта Chart, задающего активную диаграмму, можно с использованием свойства ActiveChart, как показано в только что приведенных примерах. Еще раз хочу обратить внимание на то, что активизация листа диаграммы приводит и к активизации самой диаграммы. При работе с такими диаграммами можно использовать с тем же успехом и свойство ActiveSheet. Что же касается встроенных диаграмм, то там необходимо предварительно активизировать соответствующий контейнер.
  • Несколько диаграмм на одном листе диаграммы. Excel позволяет указать один и тот же лист диаграммы, как место расположения нескольких диаграмм. В этом случае новая диаграмма накладывается на область, отведенную первой диаграмме. Руками можно работать с несколькими диаграммами на таком листе, например, изменяя их параметры. Но программная работа возможна только с одним объектом, определяющим первую диаграмму. Более того, если, используя MacroRecorder, записать макрос, повторяющий работу руками с несколькими диаграммами одного листа, то этот макрос не будет корректно выполняться при его запуске. Суммируя это, рекомендую в своей работе придерживаться стратегии - одна диаграмма на листе диаграмм. Это обеспечит корректную программную работу с такими листами.


  • Коллекция Charts и объект Chart

    Напомню, совокупность всех листов рабочей книги задается объектом Sheets. Этот объект, задающий коллекцию, представляет все листы рабочей книги независимо от их типа. Основных типов листов в рабочей книге два - WorkSheet и Chart, соответственно имеются и две коллекции для этих типов листов. Коллекцию WorkSheets мы уже рассмотрели, перейдем теперь к рассмотрению коллекции Charts.

    Коллекция Charts

    Эта коллекция является частью коллекции Sheets, - ее элементами являются объекты класса Chart, представляющие специальный тип листов рабочей книги - листы, содержащие диаграммы. Всякий раз, когда руками или программно создается диаграмма, ее можно встроить либо в рабочий лист, либо расположить на отдельном листе. Если предпочтение отдается второму варианту, то в коллекции Charts появляется новый элемент. Обратите внимание, все элементы этой коллекции являются объектами Chart, но не всякий объект Chart принадлежит этой коллекции.
    Коллекция Charts содержит тот же набор свойств и методов, что и коллекция WorkSheets. Она содержит типичный набор свойств: Application, Count, Creator, Parent, Item. Кроме этих свойств имеется менее типичное свойство для коллекций свойство Visible и два свойства VpageBreaks и HpageBreaks, возвращающие одноименные коллекции, элементы которых задают вертикальное и горизонтальное деление листа на страницы, которые могут быть распечатаны. Обо всех этих свойствах я подробно рассказывал при описании коллекции WorkSheets.
    Все методы, которые есть у коллекции WorkSheets, есть и у коллекции Charts. Вот эти методы: Add, Copy, Delete, Move, PrintOut, PrintPreview, Select. Я их уже описывал, поэтому повторяться не буду. Заметьте, отсутствует только один метод FillAcrossSheets, копирующий диапазон ячеек рабочего листа. Понятно, что для листов диаграмм этот метод неприменим.

    Коллекция Workbooks и объект Workbook

    Документы, с которыми пользователь работает в Excel, называются рабочими книгами. Каждая рабочая книга представляется объектом WorkBook, а их коллекция - Workbooks.

    Коллекция Workbooks

    Коллекция Workbooks, содержащая все открытые рабочие книги, имеет обычные для коллекций свойства Application, Count, Creator, Parent и Item. У нее всего 4 метода:
  • Add([Template]) As Workbook - добавляет новую книгу в коллекцию. Новая книга создается на основе шаблона, заданного параметром Template. Если он опущен, то используется шаблон по умолчанию. Метод возвращает в качестве результата созданную рабочую книгу.
  • Open(Filename As String, [UpdateLinks], [ReadOnly], [Format], [Password], [WriteResPassword], [IgnoreReadOnlyRecommended], [Origin], [Delimiter], [Editable], [Notify], [Converter], [AddToMru]) As Workbook - открывает существующую книгу. При открытии можно задавать параметры, управляющие свойствами открываемого документа. Обязательным параметром является только первый, задающий имя файла, содержащего открываемую книгу.
  • Close -закрывает все книги коллекции. При закрытии той или иной книги может появиться диалоговое окно с предложением сохранить сделанные изменения.
  • OpenText(Filename As String, [Origin], [StartRow], [DataType], [TextQualifier As XlTextQualifier = xlTextQualifierDoubleQuote], [ConsecutiveDelimiter], [Tab], [Semicolon], [Comma], [Space], [Other], [OtherChar], [FieldInfo], [TextVisualLayout], [DecimalSeparator], [ThousandsSeparator]) - открывает текстовый файл, содержащий таблицу, и создает рабочую книгу Excel, преобразуя текстовую таблицу в таблицу Excel. Это бывает важно, если таблицу, подготовленную в одном из текстовых редакторов, нужно импортировать в формат Excel. Обязательным параметром является имя файла, содержащего таблицу, подготовленную в текстовом редакторе. Остальные параметры задают характеристики таблицы. Конечно, для копирования таблицы, подготовленной в редакторе Word, проще использовать буфер и стандартную технику "Копировать" - "Вставить".


  • Коллекция WorkSheets и объект WorkSheet

    Каждая рабочая книга состоит из листов. Совокупность всех листов рабочей книги задается объектом Sheets. Этот объект, задающий коллекцию, представляет все листы рабочей книги независимо от их типа. При программной работе часто полезно иметь дело с подколлекциями коллекции Sheets, содержащими листы только одного определенного типа. Основных типов листов в рабочей книге два - WorkSheet и Chart, соответственно имеются и две коллекции для этих типов листов. К рассмотрению коллекции WorkSheets мы сейчас и переходим.

    Коллекция WorkSheets

    Эта коллекция является частью коллекции Sheets, - ее элементами являются объекты класса WorkSheet, представляющие рабочие листы - листы электронных таблиц. По умолчанию при создании каждой новой рабочей книги в ее состав включаются три таких листа. С объектной точки зрения это означает, что при создании новой книги автоматически создается коллекция WorkSheets, содержащая три элемента. Как и всякая коллекция в Excel и Office 2000 данная коллекция содержит типичный набор свойств: Application, Count, Creator, Parent, Item. Кроме этих свойств имеется менее типичное свойство для коллекций свойство Visible, которое позволяет сделать видимыми или невидимыми рабочие листы книги. У коллекции WorkSheets есть еще два свойства VpageBreaks и HpageBreaks, возвращающие одноименные коллекции, элементы которых задают вертикальное и горизонтальное деление рабочего листа на страницы, которые могут быть распечатаны. Дело в том, что рабочий лист Excel имеет большие размеры по ширине и длине, так что его полностью нельзя увидеть ни на экране дисплея, ни при выводе на печать. Поэтому при печати часто приходится делить рабочий лист, вставляя разрывы по горизонтали и вертикали. Коллекции VpageBreaks и HpageBreaks содержат объекты, задающие эти разрывы.
    Методов у коллекции WorkSheets немного, и практически все они типичны для коллекций:
  • Function Add([Before], [After], [Count], [Type]) As Object - позволяет добавить новый рабочий лист в книгу, возвращая соответствующий объект в качестве результата. Добавленный лист становится активным. Параметры Before и After позволяют указать, куда поместить добавленный лист, - перед или после листа, который до выполнения операции был активным. Параметр Count позволяет одновременно добавить несколько листов, задавая число этих листов. Параметр Type обычно не указывается, ранее он позволял добавлять листы макросов в версии Excel4.
  • Sub Copy([Before], [After]) - метод Copy вызывается объектом WorkSheets, чаще всего, для создания копии рабочей книги. В этом случае параметры метода не задаются. При копировании отдельной страницы параметры указывают, куда поместить ее копию. Понятно, что только один из этих параметров может быть указан в момент вызова метода.
  • Sub Delete() - удаляет коллекцию рабочих листов.
  • Sub FillAcrossSheets(Range As Range, [Type As XlFillWith = xlFillWithAll]) - область, заданная параметром Range, копируется в соответствующее место всех рабочих листов. Тип копирования задается вторым параметром, можно, например, копировать формулы, по умолчанию копируется все содержимое области, заданной параметром Range. Копируемый объект, естественно, должен быть частью одного из рабочих листов коллекции. Вот простой пример, демонстрирующий применение этого метода: Public Sub CopyRange() 'Копирование объекта Range первого листа 'на все листы рабочей книги. With ThisWorkbook .Worksheets.FillAcrossSheets (.Worksheets(1).Range("A7:C11")) End With End Sub
  • Sub Move([Before], [After]) - используется для перемещения листов. К коллекции его лучше не применять.
  • Sub PrintPreview([EnableChanges]), Sub PrintOut([From], [To], [Copies], [Preview], [ActivePrinter], [PrintToFile], [Collate], [PrToFileName]) - используются для предварительного просмотра коллекции рабочих листов перед ее печатью и для печати коллекции.
  • Sub Select([Replace]) - используется для выделения листов коллекции.

  • Вот еще один небольшой пример на применение методов:
    Public Sub MoveAndOthers() 'Перемещение листов и другие операции. With ThisWorkbook.Worksheets .Select .PrintPreview (True) 'Метод Move к коллекции лучше не применять! '.Move End With End Sub
    Как Вы понимаете, большинство методов - Copy, Move, Select и другие - коллекция WorkSheets "унаследовала" от своих потомков. Чаще всего эти методы применяются к отдельным листам, а не ко всей коллекции в целом. Нам придется еще с ними столкнуться, при рассмотрении методов объекта WorkSheet. Прежде, чем перейти к изучению этого объекта, скажу только, что коллекция WorkSheets, также как и все другие коллекции, событий не имеет.

    Методы - "незнакомцы"

    Рассмотрим теперь методы, которые нам ранее не встречались. В большинстве случаев эти методы отражают специфику Excel. У рабочего листа их не так и много. Вот эти методы:
  • Calculate - проводит вычисления формул рабочего листа. Обычно свойство EnableCalculation включено и вычисления идут автоматически. Но при выключенном свойстве необходимо применять этот метод для инициирования вычислений.
  • ClearArrows - удаляет стрелки трассировки. Эти стрелки можно установить для просмотра зависимостей при вычислениях. Стрелки соединяют ячейки А и В, если формула, записанная в В ссылается на А. Для программного задания трассировки используются методы ShowDependents и ShowPrecedents, но, заметьте, это методы объекта Range, а не рабочего листа. Вот как выглядит трассировка зависимостей, связывающих ячейки при вычислении ряда, задающего вычисление функции ex.

  • Методы -

    увеличить изображение
    Рис. 3.7.  Отображение зависимостей ячеек при вычислениях в Excel

    ' Запрос на вычисление функции. Mes = "Задайте функцию и аргумент - получите значение" NameOfCell = InputBox(Prompt:=Mes, _ Title:="Ввод функции", Default:="SIN(3)") Val = Evaluate(NameOfCell) MsgBox ("Значение функции " & NameOfCell & " = " & Val) End Sub

    На рисунках показаны окна, которые открывались в процессе диалога с пользователем при вычислении значения выражения:

    Методы -

    Рис. 3.8.  Ввод выражения, заданного строкой

    Методы -

    Рис. 3.9.  Вычисление выражения интерпретатором формул

  • PivotTableWizard - создает сводную таблицу. Работу со сводными таблицами я рассмотрю в последующих разделах этой книги.
  • ResetAllPageBreaks - восстанавливает исходную разбивку рабочего листа на страницы, которая возможно была изменена.
  • SetBackgroundPicture(Filename) - устанавливает графический фон для рабочего листа или листа диаграмм. Картинка для фона берется из файла, имя которого задает параметр FileName.
  • ShowDataForm - показывает форму данных, связанную с данным рабочим листом. Несколько слов о том, что собой представляет форма данных. Начать нужно, по-видимому, с определения понятия список данных. Excel позволяет связывать с рабочим листом один список данных, представляющий небольшую реляционную базу данных - таблицу, состоящую из именованных столбцов. Форма данных - инструментальное средство для работы с этой таблицей. Форма позволяет добавлять и изменять записи списка. Форма строится автоматически по заголовкам списка и число полей формы совпадает с числом столбцов. Над списком определены разные операции, в частности, возможна фильтрация данных. Но пока при рассмотрении свойств и методов оставим в стороне все, что связано с работой над списком. Об этом предстоит отдельный и подробный разговор.


  • Методы объекта Application

    Методов у объекта Excel.Application меньше, чем свойств, но и их около полусотни. Дадим краткий обзор, опять-таки, объединяя их по возможности в группы:
  • Метод ActivateMicrosoftApp(Index As xlMSApplication) позволяет активировать приложение Microsoft, заданное соответствующей константой в аргументе метода. Если приложение уже выполняется, то активируется текущий вариант. В противном случае открывается экземпляр приложения, и затем приложение активируется. Константы позволяют задать все основные приложения Office 2000, а также FoxPro, Project и некоторые другие приложения Microsoft.
  • Группа методов - DeleteCustomList, DeleteChartAutoFormat, AddCustomList, AddChartAutoFormat - позволяет удалять и добавлять пользовательские списки и пользовательские форматы к тем спискам и форматам, которые используются в самом приложении Excel.
  • Группа из пяти DDE-методов позволяет обеспечить динамический обмен данными между приложениями в соответствии со стандартом DDE. Сохранена для поддержки совместимости с предыдущими версиями Excel.
  • Методы, запускающие вычисления - Calculate, CalculateFull, приводят к перевычислению рабочих страниц всех рабочих книг. Метод CheckSpelling запускает проверку орфографии во всех рабочих книгах. Метод Evaluate(Name) преобразует имя объекта в сам объект. Эти методы объединяет то, что все они, по существу, являются методами объектов более низкого уровня иерархии - объектов Workbook, WorkSheet, Chat. Объект Application "наследует" эти методы у своих потомков, что позволяет распространять действие метода на все открытые рабочие книги. Следует понимать, что пользоваться вызовом этих методов объектом Application стоит в очень редких случаях. Опять-таки, можно говорить о некоторой излишней перегрузке объекта Application.
  • Группа Get-методов - GetCustomListContents, GetCustomListNum, позволяет вернуть содержимое пользовательского списка, получить его номер. Методы GetOpenFileName, GetSaveAsFileName позволяют получить имя файла, выбранное пользователем, открывая по ходу дела соответствующее диалоговое окно.
  • Группа On-методов, позволяющих запустить на выполнение некоторый макрос. Метод OnKey(Key, Procedure) позволяет запустить макрос, заданный вторым параметром метода, при нажатии пользователем комбинации клавиш, заданной первым параметром метода. Метод OnTime(EarliestTime, Procedure As String, [LatestTime], [Schedule]) позволяет запустить макрос, заданный вторым параметром метода, в указанное время. О схожем методе OnTime рассказывалось при описании методов объекта Word.Application. Методы OnRepeat(Text As String, Procedure As String) и OnUndo(Text As String, Procedure As String) позволяют указать макросы и текст, который будет появляться в пунктах "Повторить Выполнение"
    Методы объекта Application
    и "Отменить Выполнение"
    Методы объекта Application
    из меню Правка. Когда пользователь выберет соответствующий пункт меню, то запускается макрос, указанный вторым параметром метода. Вот простой пример на применение этих методов:
  • Public Sub RepeatAndUndo() 'Создание пунктов Повторить и Отменить в меню Правка Call Application.OnRepeat("Hello", "Test") Call Application.OnUndo("7 to A1", "Write7")

    End Sub

    Public Sub Test() MsgBox ("Hi!") End Sub

    Public Sub Write7() Range("A1") = 7 End Sub

    Процедура RepeatAndUndo создает соответствующие пункты меню Правка, а процедуры Test и Write7 будут вызываться при выборе пользователем этих пунктов меню. Замечу, что реально особой пользы от применения этих методов не вижу, так как при любых действиях пользователя произойдет обновление этих пунктов меню.

  • Методы Repeat и Undo близки по духу к рассмотренным только что методам. Они позволяют повторить или отменить последнее действие пользователя при его работе вручную.
  • Еще одним важным методом, позволяющим запускать макрос на выполнение, является метод Run(Macro, Arg1, Arg2, …). Метод Run позволяет выполнить макрос (процедуру или функцию) проекта рабочей книги или функцию из DLL или XLL. Макрос, запускаемый на выполнение, может находиться в той же рабочей книге, что и макрос, вызвавший метод Run, но может принадлежать и другой рабочей книге. В этом случае, естественно, проекты должны быть связаны по ссылке и в проекте, который вызывает макрос другого проекта, должна быть установлена ссылка на вызываемый проект. При вызове макросу могут быть передано произвольное число аргументов, все они передаются по значению, так что, заметьте, нельзя передать макросу сам объект, а только его значение, задаваемое свойством Value. Метод Run в свою очередь возвращает значение, являющееся результатом выполнения макроса. Приведу простой пример, демонстрирующий все особенности вызова метода Run:


  • Проекту документа BookOne я дал имя BookOneProject. В этом проекте объявлена глобальная переменная

    Option Explicit Public GlobalZ As Variant

    В модуль с именем ModuleOne этого проекта я поместил описание процедуры PlusXY и функции Plus1. Они выполняют простые и понятные без комментариев действия.

    Public Function Plus1(ByVal X As Integer) As Integer Plus1 = X + 1 End Function

    Public Sub PlusXY(ByVal X As Integer, Y As Integer) GlobalZ = X + Y End Sub

    В этом же модуле находится и процедура testrun, демонстрирующая вызовы метода Run.

    Public Sub testrun() 'Запуск на выполнение функции и процедуры, 'находящихся в том же проекте Dim z As Integer z = Application.Run("Plus1", 7) Debug.Print "z = ", z z = Application.Run("PlusXY", 5, 7) Debug.Print "GlobalZ = ", GlobalZ, "z = ", z End Sub


    Вот результаты ее выполнения:

    z = 8 GlobalZ = 12 z = 0

    В проекте другой рабочей книги Excel с именем BookTwo я установил ссылку на проект BookOneProject и в один из модулей поместил процедуру testrun1, вызывающую макросы проекта BookOneProject:

    Public Sub testrun1() 'Запуск на выполнение функции и процедуры, 'находящихся в другом проекте BookOneProject, 'на который установлена ссылка. Dim z As Integer z = Application.Run("BookOneProject.Module1.plus1", 7) MsgBox ("z= " & z) Call Application.Run("BookOneProject.Module1.plusXY", 5, 7) MsgBox ("GlobalZ = " & BookOneProject.GlobalZ) End Sub

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

  • Метод Goto([Reference], [Scroll]) не выполняя макроса, позволяет перейти к его рассмотрению. Другое, может быть, основное назначение метода состоит в том, чтобы перейти в заданную точку рабочей книги Excel. Чтобы перейти к рассмотрению макроса, параметр Reference должен быть строкой, задающей имя макроса. Для перехода в заданную область документа параметр Reference задается объектом Range. Булев параметр Scroll, имеющий значение true, обеспечивает прокрутку области так, чтобы заданная точка находилась в левом верхнем углу области просмотра. Главное, на что стоит обратить внимание, - метод Goto позволяет осуществлять переходы между документами. Вот пример макросов из документа BookTwo, осуществляющих соответственно переходы к заданной области и макросу документа BookOne.

    Public Sub GotoRange() 'Переход к заданной области другого документа Application.Goto Workbooks("BookOne.xls").Worksheets("Лист1").Range("A20"), True End Sub


    Public Sub GotoMacro() 'Переход к заданному макросу в другом проекте Application.Goto "BookOneProject.Module1.testrun" End Sub
  • Метод MacroOptions ([Macro], [Description], [HasMenu], [MenuText], [HasShortcutKey], [ShortcutKey], [Category], [StatusBar], [HelpContextID], [HelpFile]) - это еще один метод, связанный с макросами. Он позволяет задать для макроса, указанного первым параметром, различные характеристики - описание, горячие клавиши, раздел справки, связанный с данным макросом, и другие свойства.
  • Метод RecordMacro([BasicCode], [XlmCode]) - также предназначен для работы с макросами. Он позволяет добавить некоторый программный код в макрос, создаваемый инструментом MacroRecorder. В момент вызова метода MacroRecorder должен быть включен и записывать макрос в модуль, не являющийся активным, другими словами, нельзя произвести запись в тот модуль, макрос которого вызвал метод RecordMacro.
  • Метод Wait(Time) As Boolean - это последний из описываемых мной методов объекта Excel.Application, входящих в большую группу методов, предназначенных для работы с макросами. Он позволяет организовать задержку вычислений на заданное время, указанное параметром метода. В приведенном ниже примере метод используется, чтобы открыть и показать пользователю некоторую форму, а затем закрыть ее по истечении заданного времени. Этот прием можно использовать в играх, целью которых является проверка внимательности. Вот текст соответствующего макроса:

    Public Sub WaitSomeTime() 'Открывает форму на ограниченное время MsgBox ("Форма будет показана на 10 секунд!") FlyForm.Show Application.Wait (Now + TimeValue("0:00:10")) FlyForm.Hide End Sub


  • Взгляните, как выглядит сама форма.

    Методы объекта Application

    Рис. 3.1.  Форма FlyForm, открытая на "мгновение"

    Привожу рисунок этой формы только для того, чтобы пояснить, какая цель преследуется в этом примере. Я предполагал, что при открытии формы пользователь должен успеть в предоставленное ему время ввести два числа в поля X и Y , нажать кнопку, производящую вычисления и запомнить полученный результат. Однако мои намерения не осуществились, и вот по каким причинам. Если форма имеет статус модальной формы, то выполнение макроса приостанавливается до той поры, пока пользователь не закроет форму. Так что в этом случае у пользователя время на работу с формой не ограничено. Это я понимал. Если же форма имеет статус немодальной формы (свойство ShowModal = False), то форма действительно будет открыта в течение 10 секунд. Но в этом случае пользователь не сможет работать с этой формой, вводить значения в поля ввода и нажимать командную кнопку. Хуже всего то, что при попытке ввода значений в поля формы они фактически будут попадать в произвольное место программного текста и порти ть сам проект. Так что следует быть осторожным в подобной ситуации.

  • Метод Help([HelpFile], [HelpContextID]) позволяет вызвать справочное руководство, указав при необходимости и соответствующий раздел в этом руководстве. Можно вызывать как стандартную справочную систему, - в этом случае не нужно задавать аргументы при вызове метода, либо, что чаще бывает, собственную справочную систему. Первый параметр метода задает имя файла, хранящего справочное руководство. Этот файл может иметь уточнение "chm" , если руководство подготовлено с помощью инструментария HTML Help Workshop, или иметь уточнение "htm", если справочная система создана с помощью инструментария Microsoft WinHelp.
  • Методы Intersect(Arg1 As Range, Arg2 As Range, …)As Range и Union(Arg1 As Range, Arg2 As Range, …)As Range возвращают в качестве результата объект Range, задающий прямоугольную область, представляющую соответственно пересечение или объединение областей аргументов, которых должно быть не менее двух и не более 30.
  • Метод InputBox, по существу, эквивалентен одноименной функции из библиотеки VBA [2, стр. 346]и позволяет организовать диалог с пользователем и принять введенное им значение. Функция InputBox является одной из наиболее широко применяемых функций, и примеров ее вызова приводилось достаточно много. Не обойтись без нее и в примерах этой книги. Что вызывать метод InputBox объекта Application или функцию InputBox библиотеки VBA - дело вкуса.
  • Метод Volatile([Volatile]) позволяет включить или выключить принудительное вычисление для функций, вызываемых в формулах рабочего листа. Метод вызывается непосредственно в функции, которую предполагается пометить. Булев параметр Volatile помечает функцию, как принудительно вычисляемую, если он имеет значение true. Это значение является значением параметра по умолчанию.


  • Я рассмотрел большую часть методов объекта Application. Замечу, что в предыдущей версии этих методов было значительно больше, поскольку многие функции Excel - математические и прочие были доступны на этом уровне. Теперь, как и положено, все они находятся в специальном контейнере WorkSheetFunction.

    Методы объекта Chart

    Мы не будем рассматривать методы, которые так или иначе уже встречались. Рассмотрим только основные методы, определяющие новое поведение объекта Chart:
  • Sub ChartWizard([Source], [Gallery], [Format], [PlotBy], [CategoryLabels], [SeriesLabels], [HasLegend], [Title], [CategoryTitle], [ValueTitle], [ExtraTitle]) Этот метод позволяет построить или модифицировать существующую диаграмму. В отличие от мастера диаграмм (ChartWizard), который вызывается при построении диаграммы вручную, метод не является интерактивным, более того он не позволяет задать все возможные свойства. С его помощью можно выполнить основную работу, а детали строятся с использованием других свойств и методов объекта Chart. В одном из ранее приведенных примеров (процедуре AddChart) я демонстрировал применение этого метода для программного построения диаграммы. Дадим краткое описание параметров метода, все из которых являются необязательными:
  • Source - объект Range, содержащий исходные данные для построения новой диаграммы. Если параметр опущен, то метод позволяет отредактировать существующую диаграмму - либо выделенную диаграмму рабочего листа, либо диаграмму активного листа диаграмм.
  • Gallery - задает тип диаграммы и может быть одной из следующих констант: xlArea, xlBar, xlColumn, xlLine, xlPie, xlRadar, xlXYScatter, xlCombination, xl3DArea, xl3DBar, xl3DColumn, xl3DLine, xl3DPie, xl3DSurface, xlDoughnut, или xlDefaultAutoFormat.
  • Format - задает формат для данного типа диаграммы. Каждому типу диаграммы соответствует некоторое число возможных форматов. Параметр задает номер формата, по умолчанию выбирается первый формат данного типа.
  • PlotBy - соответствует терминальному свойству PlotBy.
  • CategoryLabels и SeriesLabels - целые, указывающие число строк или столбцов с метками категорий и рядов данных в области, заданной параметром Source. Указывать эти числа нужно на единицу меньше фактического значения.
  • Остальные параметры позволяют добавить легенду, задать название диаграммы и ее осей - они совпадают с соответствующими терминальными свойствами.
  • Sub SetSourceData(Source As Range, [PlotBy]) Устанавливает источник данных диаграммы. Второй параметр соответствует терминальному свойству PlotBy.
  • Sub ApplyCustomType(ChartType As XlChartType, [TypeName]) Метод позволяет модифицировать диаграмму, применив к ней новый тип - стандартный или настраиваемый. Если этот тип стандартный, то тогда первый параметр полностью его определяет. Его возможные значения совпадают со значениями соответствующего терминального свойства ChartType. Если же тип настраиваемый, то первый параметр должен иметь одно из следующих значений: xlBuiltIn, xlUserDefined или xlAnyGallery. В этом случае второй параметр задает имя типа диаграммы.
  • Function Export(Filename As String, [FilterName], [Interactive]) As Boolean Позволяет экспортировать диаграмму, преобразуя ее в графический формат. Первый параметр задает имя файла, в который будет записана диаграмма в графическом формате, второй - задает имя графического фильтра в том виде, как оно записано в системном регистре. Булев параметр Interactive должен иметь значение True, если мы хотим вызвать диалоговое окно в процессе фильтрации. Функция Export возвращает значение True в случае успешного завершения работы.
  • Sub GetChartElement(X As Long, Y As Long, ElementID As Long, Arg1 As Long, Arg2 As Long). Представьте себе, что пользователь щелкнул кнопку мыши где-то над диаграммой. Обработав это событие, можно получить координаты курсора мыши - X и Y. Если теперь вызвать метод GetChartElement с этими координатами, то он вернет значение параметра ElementID - идентификатор элемента диаграммы и значения двух параметров, связанных с этим элементом. Конечно, параметры зависят от типа того элемента, чьи координаты X и Y заданы.
  • Function Location(Where As XlChartLocation, [Name]) As Chart. Передвигает диаграмму в новое местоположение. Параметр Where имеет следующие значения: xlLocationAsNewSheet, xlLocationAsObject, или xlLocationAutomatic.

    В первом случае диаграмма помещается на новый лист диаграммы и параметр Name задает имя этого листа. Во втором случае диаграмма помещается как встроенный объект и Name задает имя рабочего листа. Вот пример, в котором диаграмму, построенную на рабочем листе книги "BookOne" мы переносим на отдельный лист диаграмм:

    Public Sub MoveChart() Workbooks("BookOne").Worksheets("Sheet1").ChartObjects(4) _ .Chart.Location Where:=xlLocationAsNewSheet, Name:="Динамика продаж" End Sub
  • Sub SetSourceData(Source As Range, [PlotBy]) Позволяет установить новый источник данных. Параметры в пояснениях не нуждаются.


  • В заключение приведем процедуру, создающую трехмерную диаграмму по данным нашего примера с дилерами и продажами:

    Public Sub Chart3D() Workbooks("BookFour").Activate With Worksheets("Лист3") Dim myRange As Range Set myRange = .Range("C23:F27") Dim myChart As ChartObject 'Создаем контейнер объекта Chart Set myChart = .ChartObjects.Add(myRange.Left - 100, _ myRange.Top + myRange.Height, 400, 300) End With 'Определяем параметры объекта Chart With myChart.Chart .ChartType = xl3DColumn .SetSourceData Source:=myRange, PlotBy:=xlRows .Location Where:=xlLocationAsObject, Name:="Лист3" End With With ActiveChart .HasTitle = True .ChartTitle.Characters.Text = "Динамика продаж" End With 'Диалог с пользователем If MsgBox("Хотите изменить угол зрения?", vbYesNo) = vbYes Then ActiveChart.RightAngleAxes = True End If End Sub

    Дадим некоторые комментарии к этой программе:

  • В качестве источника данных выступает известная по предыдущим примерам таблица Excel.
  • Я ввел объекты myRange и myChart класса ChartObjects. Это позволило задать нужные размеры и положение диаграммы, привязанное к таблице данных.
  • Ранее при построении диаграмм я использовал метод ChartWizard и работу с коллекциями SEriesCollection. Теперь продемонстрирован еще один способ, когда задаются свойства и методы объекта Chart, в частности, для указания источника данных используется метод SetSourceData.
  • Свойство ChartType позволяет указать тип диаграммы, а метод Location определяет ее, как встроенный объект.
  • В диалоге с пользователем изменяется угол зрения, задаваемый булевым свойством RightAngleAxes.
  • Задав еще заголовок диаграммы, и приняв остальные свойства диаграммы по умолчанию, я получил диаграмму, представленную на рис.3.14.


  • Методы объекта Range

    Объект Range имеет около 80 различных методов. Есть ли пересечение с методами объекта Worksheet? Есть, но оно незначительно. Общих методов примерно 10%. К таким методам относятся методы общего назначения: Activate, Calculate, CheckSpelling, Copy, Delete, PasteSpecial, PrintOut, Select. Замечу, что объект Range имеет общие методы не только с объектом Worksheet, но и со старшим в иерархии объектом Workbook. Так описанный ранее метод Run, позволяющий запускать макросы, есть и у объекта Range. Более 20 методов общего назначения входят в следующие группы, которые я лишь назову, не приводя подробного описания:
  • Clear - 7 методов этой группы проводят разнообразную чистку содержания, комментариев и прочих деталей в области объекта Range.
  • Copy, Fill - 9 методов этих близких групп выполняют копирование объекта Range и заполнения некоторого диапазона результатами копирования.
  • Find - 3 метода позволяют проводить разнообразный поиск в области объекта Range.
  • Show - 4 метода этой группы предназначены для отображения данных на экране дисплея.
  • Sort - 2 метода выполняют сортировку данных в области объекта Range.

  • Остается еще более 40 методов, которые я не стану сейчас описывать, надеясь, что большинство из них, по крайней мере, самые важные в работе программиста появятся при рассмотрении большого числа задач в последующих главах. В качестве примере дам краткую характеристику лишь трех из них:
  • Sub AutoFill(Destination As Range, [Type As XlAutoFillType = xlFillDefault]) - метод из группы Fill позволяет заполнить диапазон, заданный параметром Destination, используя значения объекта Range и тип заполнения, определенный параметром Type. Диапазон назначения Destination должен включать в себя исходный объект Range, вызвавший метод.
  • Sub AutoFormat([Format As XlRangeAutoFormat = xlRangeAutoFormatClassic1], [Number], [Font], [Alignment], [Border], [Pattern], [Width]) - метод из группы методов форматирования включает автоматическое форматирование диапазона. Тип форматирования определяет первый параметр, остальные параметры имеют булевы и позволяют включать или выключать те или иные возможности форматирования. По умолчанию они включены. Если исходный диапазон представляет одну ячейку, то форматируется весь текущий регион, содержащий эту ячейку.
  • Sub DataSeries([Rowcol], [Type As XlDataSeriesType = xlDataSeriesLinear], [Date As XlDataSeriesDate = xlDay], [Step], [Stop], [Trend]) - метод из группы методов, предназначенных для работы с данными. Позволяет создать автоматически ряд данных в указанном диапазоне, удовлетворяющий определенному закону построения. Первый параметр имеет два возможных значения: xlRows и xlColumns, указывающих, как будут заполняться данные - по строкам или столбцам. Второй параметр задает тип заполнения ряда данных. Остальные параметры позволяют управлять процессом заполнения данных. Вот небольшой пример на применение этого метода:
  • Public Sub WorkWD() 'Работа с методом DataSeries Dim Sh As Worksheet Dim myr As Range Set Sh = ThisWorkbook.Worksheets(1) Set myr = Sh.Range("C21:C32") myr.Cells(1, 1) = "31-Jan-2001" myr.DataSeries Type:=xlChronological, Date:=xlMonth Set myr = Sh.Range("D21:D32") myr.Cells(1, 1) = 320 myr.DataSeries Type:=xlDataSeriesLinear, Step:=320
    End Sub
    Взгляните на два ряда данных, полученных в результате выполнения данной процедуры:
    Методы объекта Range

    Рис. 3.22.  Автоматическое построение рядов данных
    В следующих главах этой книги, посвященных работе с документами Excel, я рассмотрю по ходу дела применение многих свойств и методов как объекта Range, так и других объектов Excel. А сейчас поставлю точку в рассмотрении объектной модели Excel.

    End Sub

    Взгляните на два ряда данных, полученных в результате выполнения данной процедуры:

    Методы объекта Range

    Рис. 3.22.  Автоматическое построение рядов данных

    В следующих главах этой книги, посвященных работе с документами Excel, я рассмотрю по ходу дела применение многих свойств и методов как объекта Range, так и других объектов Excel. А сейчас поставлю точку в рассмотрении объектной модели Excel.

    Методы объекта Range
    Методы объекта Range
    Методы объекта Range
    © 2003-2007 INTUIT.ru. Все права защищены.

    Методы объекта Workbook

    Дадим теперь краткую характеристику основным методам объекта Workbook. Мы уже говорили о том, что создаются и открываются рабочие книги методами коллекции Workbooks - Add, Open и OpenTextFile. А вот закрываются и сохраняются, используя собственные методы. С них мы и начнем описание методов:
  • Save, SaveAs, SaveCopyAs - позволяют сохранить рабочую книгу, без ее закрытия и удаления из коллекции Workbooks. При первом сохранении следует применять метод SaveAs, чтобы задать имя файла, в котором книга сохраняется. Метод имеет и другие параметры - формат хранения, пароль, статус и другие характеристики. Последний из этой группы методов создает копию рабочей книги.
  • Close - выполняет те же функции, что и Save, но одновременно закрывает книгу и удаляет ее из коллекции.
  • Activate - активизирует рабочую книгу.
  • Route - направляет рабочую книгу по сети всем участникам совместной разработки. Список участников и другие характеристики задаются в свойствах объекта RoutingSlip. Вот пример процедуры, в которой определяется круг участников работы, после чего им пересылается текущая книга.
  • Public Sub BookRoute() 'Регистрация исполнителей совместной разработки 'в объекте RoutingSlip. 'Посылка книги совместно работающим исполнителям. With ThisWorkbook .HasRoutingSlip = True With .RoutingSlip .Delivery = xlOneAfterAnother .Recipients = Array("Илья Биллиг", _ "Михаил Дехтярь") .Subject = "Collaboration Test" .Message = "Это книга Excel, пересылаемая в качестве примера" .ReturnWhenDone = True End With .Route End With
    End Sub
    Заметьте, для того, чтобы при пересылке книги не задавались лишние вопросы, все исполнители, включенные в список Recipients, также как и сам автор документа, должны быть включены в адресную книгу. Согласно установленному порядку книга будет послана первому исполнителю, указанному в списке, и далее будет пересылаться по заданному списком маршруту. После отсылки книги свойство Routed автоматически будет установлено как True.
  • AcceptAllChanges, RejectAllChanges - принимает или отвергает все изменения, сделанные участниками совместной разработки документа при разделенном доступе.
  • RefreshAll - обновляет сводные таблицы и все области, содержащие внешние данные.
  • PurgeChangeHistoryNow (Days,SharingPassword) - удаляет из истории изменений все те, чей срок хранения превосходит число дней, заданных параметром Days. Второй параметр задает общий пароль.
  • Protect, ProtectSharing, Unprotect, UnprotectSharing - методы, включающие и выключающие пароли личные и общие рабочей книги.
  • ExclusiveAccess - если книга открыта с разделяемым доступом, то этот метод дает пользователю, вызвавшему его, исключительное право доступа - изменения, сделанные всеми остальными пользователями должны сохраняться в отдельных файлах.
  • ChangeFileAccess(Mode, WritePassword, Notify) - изменяет статус доступа. Новый статус задается параметром Mode, который может принимать одно из двух значений: xlReadWrite и xlReadOnly. Если файл снабжен паролем и получает статус для записи и чтения, то второй параметр WritePassword задает пароль на запись. Если булев параметр Notify имеет значение True, то пользователь получает уведомление, когда файл недоступен.
  • AddToFavorites - добавляет в папку Favorites ярлычок рабочей книги.
  • PivotCaches - возвращает коллекцию областей памяти, отводимых сводным таблицам данной рабочей книги. Элементами этой коллекции являются объекты PivotCache. Каждой сводной таблице - объекту PivotTable отводится своя память (кэш), которую и задает объект PivotCache.
  • RunAutoMacros - запускает на выполнение все автомакросы данной книги.
  • LinkSources([Type]), ChangeLink(Name As String, NewName As String, [Type As XlLinkType = xlLinkTypeExcelLinks]), OpenLinks(Name As String, [ReadOnly], [Type]), LinkInfo(Name As String, LinkInfo As XlLinkInfo, [Type], [EditionRef]), UpdateLink([Name], [Type]) - группа методов, позволяющих работать со ссылками. Ссылки могут быть четырех типов:
  • на другие рабочие книги Excel ,
  • на документы, связанные по протоколу OLE (например, документы Word, на которые ссылается рабочая книга) или протоколу DDE,
  • на издателей книги при совместной работе над книгой и ее публикации на сервере,
  • на подписчиков книги, опубликованной на сервере и доступной для подписчиков.


  • Метод LinkSources позволяет получить все ссылки типа, заданного его параметром. Если параметр не указан, то будут выданы ссылки на книги Excel. Следующий метод Change позволяет изменить ссылку, Open - открыть документ по заданной ссылке, Info - получить некоторую информацию о документе, Update - обновить ссылки.

    Вот пример работы с этой группой методов объекта Workbook:

    Public Sub LinkDocs() 'Установление связей рабочей книги Dim BookLinks As Variant Dim i As Integer With ThisWorkbook ' связи с другими книгами Excel. BookLinks = .LinkSources(xlExcelLinks) If Not IsEmpty(BookLinks) Then Debug.Print "Существуют ссылки на рабочие книги Excel!" For i = LBound(BookLinks) To UBound(BookLinks) Debug.Print "Ссылка" & i & " : ", BookLinks(i) If BookLinks(i) = "BookTwo" Then .OpenLinks BookLinks(i) .ChangeLink BookLinks(i), "BookOne" End If Next i Else: Debug.Print "Ссылки на рабочие книги отсутствуют!" End If

    BookLinks = .LinkSources(xlOLELinks) If Not IsEmpty(BookLinks) Then Debug.Print "Существуют ссылки на OLE - документы!" For i = LBound(BookLinks) To UBound(BookLinks) Debug.Print "Ссылка" & i & " : ", BookLinks(i) Next i If .LinkInfo("Word.Document.8|E:\O2000\Remarks.doc!'", _ xlUpdateState, xlOLELinks) = 1 Then Debug.Print "Автоматическое обновление данных с OLE - документами!" End If Else: Debug.Print "Ссылки на OLE - документы отсутствуют!" End If

    BookLinks = .LinkSources(xlPublishers) If Not IsEmpty(BookLinks) Then Debug.Print "Существуют ссылки на издателей документа!" For i = LBound(BookLinks) To UBound(BookLinks) Debug.Print "Ссылка" & i & " : ", BookLinks(i) Next i Else: Debug.Print "Ссылки на издателей отсутствуют!" End If

    BookLinks = .LinkSources(xlSubscribers) If Not IsEmpty(BookLinks) Then Debug.Print "Существуют ссылки на подписчиков документа!" For i = LBound(BookLinks) To UBound(BookLinks) Debug.Print "Ссылка" & i & " : ", BookLinks(i) Next i Else: Debug.Print "Ссылки на подписчиков отсутствуют!" End If End With End Sub


    Вот как выглядят результаты отладочной печати, полученные при работе этой процедуры:

    Существуют ссылки на рабочие книги Excel! Ссылка1 : BookTwo Ссылка2 : E:\O2000\DsCd\Ch11\BookOne.xls Существуют ссылки на OLE - документы! Ссылка1 : Word.Document.8|E:\O2000\Remarks.doc!' Автоматическое обновление данных с OLE - документами! Ссылки на издателей отсутствуют! Ссылки на подписчиков отсутствуют! Новые методы объекта Workbook

    У объекта Workbook в Excel 2000 появились два новых метода:

  • Sub ReloadAs(Encoding As MsoEncoding),
  • Sub WebPagePreview().


  • Оба метода, так или иначе, связаны с общей тенденцией публикации документов Excel в Интернет. Рабочие книги, публикуемые в Интернет и интранет, хранятся, естественно в формате HTML. При их чтении могут возникнуть проблемы с кодировкой. Метод ReloadAs(Encoding As MsoEncoding) позволяет перезагрузить книгу в формате HTML, используя нужную кодировку, заданную параметром метода, значением которого может быть, например, константа msoEncodingCyrillic. Метод WebPagePreview позволяет перед публикацией книги отобразить ее на дисплее в том виде, как будет выглядеть соответствующая Web-страница, открываемая в интернет для работы с рабочей книгой.

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

    Методы объекта WorkSheet

    У объекта WorkSheet методов достаточно много. Часть из этих методов применима ко многим объектам и уже встречалась или еще будет встречаться при описании других объектов. Так что введенные в [3] понятия общности и схожести применимы не только к самим объектам, но и их отдельным свойствам и методам. Поэтому я разделю описание методов на две группы, и начну с более простой группы схожих методов.

    Методы - свойства

    Теперь я хочу рассмотреть еще несколько важных методов объекта WorkSheet, которые я выделил в отдельную группу. Эти методы похожи на свойства. В результате их работы возвращаются объекты. По-видимому, правильно считать, что возвращаемые объекты непосредственно вложены в объект WorkSheet и определяют его структуру также как объекты, возвращаемые свойствами-участниками. Вот почему я называю эти методы свойствами.
    В эту группу методов входят:
  • Function ChartObjects([Index]) As Object- возвращает коллекцию ChartObjects. Если задан параметр Index, то возвращается элемент этой коллекции - объект ChartObject. Возможный параметр Index задает номер или имя возвращаемого объекта. Заметьте, элементами коллекции являются объекты ChartObject, а не объекты Chart. Объект ChartObject является контейнером объекта Chart. Его методы и свойства позволяют управлять внешним видом и размерами встроенной в контейнер диаграммы. Чтобы получить сам объект Chart, следует воспользоваться свойством Chart объекта ChartObject. Не следует путать метод ChartObjects со свойством Charts объекта WorkBook, которое возвращает коллекцию Charts, представляющую страницы с диаграммами рабочей книги. Я напомню, что в Excel диаграммы могут быть встроены в обычный рабочий лист и, следовательно, с объектной точки зрения быть встроенными в объект WorkSheet. С другой стороны, диаграммы могут располагаться на отдельных листах рабочей книги. Такие специальные листы для отображения диаграмм и составляют коллекцию Charts. Элементы этой коллекции - объекты Chart - представляют либо встроенные диаграммы, либо листы с диаграммами. Согласно справочной системе Excel объект Chart, задающий лист с диаграммой, также имеет метод ChartObjects, возвращающий коллекцию контейнеров. Однако, практически работать с этой коллекцией не удается, да и в этом нет никакой необходимости, поскольку сам объект Chart задает и диаграмму, расположенную на листе. Наличие объектов Chart и ChartObject, их коллекций, большого числа различных свойств и методов, возвращающих эти объекты, создает впечатление излишней сложности. Приведу сейчас два примера, демонстрирующих работу с этими объектами:
  • Public Sub WorkWithCharts() 'Работа с встроенными диаграммами Dim CHO As ChartObjects 'коллекция контейнеров Dim ChO1 As ChartObject 'контейнер диаграммы Dim Ch1 As Chart 'встроенная диаграмма With ThisWorkbook Set CHO = .Sheets("Лист2").ChartObjects Set ChO1 = CHO(2) 'Меняем внешний вид диаграммы ChO1.RoundedCorners = True ChO1.Select Debug.Print ChO1.Name 'Получаем диаграмму Set Ch1 = ChO1.Chart Ch1.HasTitle = True Ch1.ChartTitle.Text = "Заказы Февраля" Debug.Print Ch1.Name 'Работа с листами диаграмм Dim Ch2 As Chart, Ch3 As Chart Dim ChO2 As Object Set Ch2 = .Charts(1) 'Лист диаграммы Ch2.HasTitle = True Ch2.ChartTitle.Text = "Заказы Марта" 'Контейнер для листа диаграммы Set ChO2 = .Charts(2).ChartObjects 'Работать с этим контейнером практически невозможно! 'Но особой необходимости в этом нет. 'Set Ch3 = ChO2.Chart 'Ch3.ChartTitle = "Заказы Апреля" End With

    End Sub

    В этом примере я работаю вначале с рабочим листом, на котором расположены две диаграммы. Получив контейнер одной из этих диаграмм - объект ChartObject, я меняю внешний вид диаграммы, закругляя ее края. Затем получаю саму диаграмму - объект Chart и задаю свойства этого объекта, определяя заголовок диаграммы. Попытка аналогичным образом работать с диаграммой, расположенной на отдельном листе, не увенчалась успехом из-за невозможности получить контейнер. В этом случае необходимо работать с самим объектом Chart - элементом коллекции Charts.

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

    Public Sub AddChart() 'Формируется последовательность чисел Фибоначчи. 'Вставляется диаграмма, отражающая график роста этих чисел. Dim myRange As Range Dim MySh As Worksheet Dim CHOS As ChartObjects Dim CHO As ChartObject Set MySh = ThisWorkbook.Worksheets(3) With MySh Set myRange = .Range("A1") With myRange .Value ="Числа Фибоначчи" .Offset(1, 0).FormulaR1C1 = "0" .Offset(2, 0).FormulaR1C1 = "1" .Offset(3, 0).FormulaR1C1 = "=R[-2]C +R[-1]C" .Offset(3, 0).Select Selection.AutoFill Destination:=Range("A4:A10"), _ Type:=xlFillDefault End With 'Добавление диаграммы Set CHOS = .ChartObjects Set CHO = CHOS.Add(50, 50, 250, 200) CHO.Chart.ChartWizard Source:=.Range("A2:A10"), _ Gallery:=xlLine, Title:="Числа Фибоначчи" End With

    End Sub

    В результате работы этой процедуры соответствующий рабочий лист Excel имеет вид:

    Методы - свойства

    Рис. 3.10.  Программно построенная диаграмма

  • Function PivotTables([Index]) As Object возвращает коллекцию PivotTables. Если задан параметр Index, то возвращается элемент этой коллекции - объект PivotTable. Возможный параметр Index задает номер или имя возвращаемого объекта. Объект класса PivotTable определяет сводную таблицу. Эти таблицы играют важную роль при представлении итоговых данных и формировании отчетов. Но о них есть смысл говорить после знакомства с базами данных. Отметив наличие такого объекта, отложив его обсуждение до той поры, пока не встретимся с ним в нужном месте и в нужное время.
  • Function Scenarios([Index]) As Object возвращает коллекцию Scenarios. Если задан параметр Index, то возвращается элемент этой коллекции - объект Scenario. Возможный параметр Index задает номер или имя возвращаемого объекта. Элемент класса Scenario представляет сценарий, используемый при анализе данных электронной таблицы. Я рассмотрю подробно применение сценариев на примерах в последующих главах книги.
  • Function OLEObjects([Index]) As Object возвращает коллекцию OLEObjects. Если задан параметр Index, то возвращается элемент этой коллекции - объект OLEObject. Возможный параметр Index задает номер или имя возвращаемого объекта. Элемент класса OLEObject представляет OLE-объект, встроенный в рабочий лист.


  • Объект Chart

    Трудно перечислить все достоинства Excel. Конечно, на первом месте стоит возможность работы с данными, предоставляемая электронной таблицей Excel, его машиной вычислений и мощной библиотекой встроенных функций. Но на второе место по важности, несомненно, претендуют возможности Excel по графическому отображению данных. Для этого используются диаграммы Excel, позволяющие отображать одни и те же данные в самых различных форматах в зависимости от потребностей пользователя. Excel предоставляет самые широкие возможности для варьирования формой представления данных. Диаграммы могут быть плоскими и объемными, двумерными и трехмерными, круговые и цилиндрические, данные можно отображать в виде графиков или гистограмм различного типа. На одной диаграмме может отображаться несколько групп, где каждая группа содержит один или несколько рядов данных (серий), отображаемых в одном формате. Диаграммы выделяются цветом, имеют оси, сопровождаются заголовком, подписями, легендой. Легенда - это надпись на предмете, например, на монете. На диаграммах, содержащих несколько рядов данных, легенда задает название каждого ряда.
    Видов диаграмм в Excel великое множество. По типу диаграммы делятся на стандартные и нестандартные или настраиваемые (Custom). Стандартных типов - 14, но каждый из них имеет до десяти форматов, так что в общей сложности их около 100. Настраиваемые типы в свою очередь разделяются на встроенные и определенные пользователем. С их помощью на одной диаграмме можно задать комбинацию нескольких стандартных типов. Мы в этой книге не собираемся останавливаться на подробном разборе всех типов, поскольку для программистов, в основном, это понятные вещи. Но несколько слов все-таки сказать следует. По сути, диаграммы предназначены для отображения графиков функций и гистограмм. Если по точкам строится график функции Y=F(X), то, как известно, необходимо задать два множества - аргументов и значений. Например, функция, определяющая объем продаж, осуществленных ее дилерами в первом квартале, имеет аргументами фамилии дилеров (Иванов, Петров, Сидоров), а значениями - объем продаж, выраженный в рублях (100, 200, 150).Множество значений называется в Excel рядом данных, а аргументы называются категориями. Соответственно, ось X (аргументов) называется осью категорий, а ось Y - осью значений. При построении графика можно опустить задание аргументов и тогда по умолчанию за их значения принимается начало натурального ряда чисел - 1, 2, 3 и т.д. Очень часто на одной диаграмме отображается несколько графиков. Если эти графики отражают некоторую тенденцию, например динамику объема продаж во времени, то лучше использовать трехмерную диаграмму, в которой появляется третья ось, чаще всего это ось времени. В Excel она называется осью рядов данных, так как фиксирует изменения ряда данных.
    Понятие гистограммы пришло из теории вероятности. Можно считать, что гистограмма отличается от графика функции тем, что аргументами при построении гистограммы являются интервалы, и значение функции связывается с интервалом. Гистограмма, обычно, отображается в виде прямоугольников, каждый из которых имеет в основании заданный интервал, а высотой - значение функции. В примере с дилерами и продажами, естественно отобразить данные в виде гистограммы, поскольку объемы продаж в реальности связаны с интервалами времени (1-й квартал, 2-й, 3-й, 4-й).
    Поскольку речь идет о графических объектах, то лучше на них взглянуть. На рис. 3.11 - 3.14 показаны различные диаграммы, отражающие данные с дилерами и продажами нашего простенького примера. На первом из этих рисунков показана классическая гистограмма, чаще всего применяемая в подобных ситуациях. На втором рисунке те же данные представлены в виде графиков. Конечно, чаще всего графики используются в инженерных расчетах и математических вычислениях. В экономических задачах предпочтительнее более наглядные формы отображения данных, например, круговые диаграммы, одна из которых показана на третьем рисунке. Наконец, на четвертом рисунке этой серии показана трехмерная диаграмма. Третье измерение - это, чаще всего, ось времени, что позволяет наглядно отобразить динамику объема продаж. Представленные рисунки отражают лишь малую долю тех возможностей, которые предоставляют диаграммы Excel
    Объект Chart

    Рис. 3.11.  Гистограмма, отражающая динамику объема продаж
    Объект Chart

    Рис. 3.12.  Представление данных о продажах в виде графиков
    Объект Chart

    Рис. 3.13.  Круговая диаграмма, отражающая вклад каждого дилера
    Объект Chart

    увеличить изображение
    Рис. 3.14.  Трехмерная диаграмма, отражающая динамику продаж

    Объект Excel Application

    Объект Excel.Application задает приложение Excel. А посему свойства, методы и события этого объекта должны характеризовать приложение в целом. Понятно, что у этого объекта должно быть свойство Workbooks, возвращающее все открытые в приложении рабочие книги, свойство Windows, возвращающее открытые окна, свойства, такие как CommandBars, возвращающие объекты интерфейса, и другие подобные свойства. Методов и событий, характерных для всего приложения в целом, по-видимому, не так уж и много. Так что, казалось бы, структура этого объекта должна быть достаточно простой. Однако реально это не так, - у объекта Excel.Application очень большое число свойств, методов и событий, что не позволяет мне описать их полностью, да и нет в этом особого смысла. Объект Excel.Application, на мой взгляд, явно перегружен, многие его свойства и методы без всякого ущерба можно было бы исключить, поскольку они оперируют с объектами, стоящими на более низких уровнях иерархии и не имеют прямого отношения ко всему приложению в целом. Приведу лишь один пример. Первое по алфавиту свойство ActiveCell возвращает объект, задающий активную ячейку. Понятно, что речь идет об активной ячейке активной страницы активной рабочей книги. Непонятно только, зачем нужно было добавлять это свойство самому приложению. Вполне достаточно, чтобы им обладал объект WorkSheet, задающий страницу книги. Более того, если в момент вызова свойства ActiveCell нет активной страницы с ячейками, то возникнет ошибка, чего не происходит, если активную ячейку вызывает объект WorkSheet. Примеров подобной перегруженности объекта Application можно привести много. Я в своем описании объектов верхнего уровня не всегда буду упоминать такие свойства, полагая, что лучше рассказать о них там, где они необходимы по существу.

    Объект Workbook

    Рабочая книга Excel устроена проще, чем документ Word. Как и положено книге, она состоит из страниц (листов). В терминах объектов это означает, что объект Workbook имеет свойство Sheets, возвращающее объект Sheets - коллекцию листов рабочей книги. Поскольку рабочие книги Excel содержат листы разного типа, то наряду с коллекцией Sheets у объекта Workbook имеются свойства, возвращающие коллекции листов разного типа:
  • Worksheets - рабочих листов;
  • Charts - листы, содержащие диаграммы;
  • Excel4MacroSheets - листы с макросами одной из предыдущих версий Excel. Реально листы такого типа уже не встречаются. Поэтому и в документации они не всегда упоминаются. Я о них также больше говорить не буду. Здесь отдана дань прошлому, когда макросы располагались на листах рабочей книги. Сейчас все макросы находятся в модулях программного проекта.

  • Эти коллекции и составляют в совокупности коллекцию Sheets. Чаще всего приходится работать с каждой из этих коллекции в отдельности, но иногда полезно иметь возможность выполнять операции над всеми листами, независимо от их типа. С некоторым сожалением отмечу, что отсутствует класс Sheet, обобщающий свойства классов Workbook и Chart.
    Теперь, когда состоялось первое знакомство с основными объектами, составляющими рабочую книгу, давайте перейдем к более систематическому обзору всех свойств- участников объекта Workbook.

    Объект WorkSheet

    Объект Worksheet - рабочий лист является элементом коллекции Worksheets. Он представляет основной тип страниц рабочей книги. Именно на этих страницах разворачиваются основные действия в ячейках электронной таблицы. Основу рабочего листа составляет прямоугольная таблица ячеек. Главная особенность электронной таблицы состоит в том, что в ее ячейки можно вводить не только данные, но и формулы. Формулы Excel, также как и обычные математические формулы, также как и выражения в языках программирования, оперируют при вычислении значений константами, переменными и функциями. В электронной таблице роль переменных играют ячейки таблицы. Существует некоторый алгоритм, определяющий порядок, согласно которому вычисляются формулы в ячейках электронной таблицы. При изменении данных таблиц, инициированных пользователем, внешними ссылками или выполнением макросов программного проекта, пересчитываются и формулы. Это делает таблицу живой, - изменение значения одной ячейки приводит, возможно, к пересчету всей таблицы.
    Также как для документов Word работа с текстом является главным занятием пользователей, работающих с документом, так и работа с ячейками - ввод данных и формул в ячейки, и, тем самым, инициирование вычислений лежит в основе работы с рабочим листом. С объектной точки зрения отдельные ячейки электронной таблицы и области, содержащие совокупности этих ячеек, то, что называется объектами Range, являются основными объектами рабочего листа. Но, естественно, рабочий лист состоит не только из объектов Range, есть и другие компоненты. Объектная модель рабочего листа Worksheet достаточно сложна, что отражает, впрочем, сложность изучаемого объекта. Давайте познакомимся с ней поближе.

    Объектная модель Excel

    Прежде всего, несколько слов о том, как устроена объектная модель Excel и других приложений Office 2000. В этой модели объекты связаны между собой отношением встраивания. На нулевом уровне иерархии существует некоторый центральный объект, в который встроены другие объекты, составляющие первый уровень иерархии. В каждый из объектов первого и последующих уровней могут быть встроены объекты следующего уровня. Так это процесс продолжается. Таким образом, объекты в этой модели "толстые", поскольку в них встроено большое число других объектов. В особенности это касается объектов, стоящих на верхних уровнях иерархии.
    Формально встраивание реализуется с помощью свойств объектов. Свойства могут быть как терминальными, не являющимися объектами, и так называемыми свойствами - участниками, которые возвращают объекты при их вызове.
    Давайте перейдем к рассмотрению библиотеки объектов Excel 9.0 и начнем с центрального объекта этой библиотеки - Excel.Application.

    Объекты Range и Selection

    Объекты Range и Selection относятся к группе схожих объектов, встречающихся в различных приложениях Office 2000. Это основные объекты, с которыми приходится работать программисту. В приложении Word есть достаточно четкая логика в том, как создаются объекты Range. Объекты верхнего уровня, например, Document, имеют метод Range, позволяющий создать новый диапазон. Объекты более низкого уровня, например Paragraph, имеют свойство Range, возвращающее диапазон, связанный с объектом. В Excel ситуация другая. Все объекты Excel имеют только свойство Range. Это свойство имеют и верхний в иерархии объект Application и Worksheet и сам объект Range, представляющий объекты нижнего уровня вплоть до ячейки. Синтаксис этого свойства следующий:
    Property Range(Cell1 [,Cell2]) As Range
    С объектом Selection тоже дело обстоит не так просто как в приложении Word, поскольку в Excel нет класса объектов Selection. Объект Selection возникает двояко - либо в результате работы метода Select, либо при вызове свойства Selection. Тип полученного объекта может быть различным и определяется типом выделенного объекта. Чаще всего, объект Selection принадлежит классу Range и тогда при работе с ним можно использовать все свойства и методы объектов класса Range.
    Вернемся к объекту Range, создаваемому, чаще всего, при вызове свойства Range тех или иных объектов Excel. Что может быть задано в качестве параметров Cell1 и Cell2? Давайте рассмотрим этот вопрос подробнее. Прежде всего, нужно понимать, что Range уникальный объект - он может представлять как единственную ячейку таблицы, так и столбец или строку, некоторую связную и не связную прямоугольную область, а также объединение и пересечение всех подобных элементов. Это же касается и объекта Selection. Параметры Cell1 и Cell2 это не просто имена ячеек таблицы. Они имеют непростой синтаксис и в общем случае могут быть достаточно сложными выражениями, позволяющими соответственно вернуть объект Range сложной конфигурации. Если при вызове используется только один параметр, то Cell1 может быть:
  • Именем ячейки, например, - "A1"
  • Диапазоном ячеек, например, - "A1: B5"
  • Выражением над диапазонами, содержащим операции объединения (запятая) и пересечения (пробел), например, - "A1:B5, F1: G8" или "A1:B5 A3:G8"


  • В случае, когда задаются оба параметра - Cell1 и Cell2, то они определяют прямоугольную область, заданную наименьшим левым верхним углом и максимальным правым углом диапазонов, определяемых параметрами. В этом случае параметры могут быть и переменными класса Range.

    Давайте начнем с простых примеров:

    Public Sub WorkWithRS() 'Работа с объектами Range и Selection 'Example 1 Workbooks("BookOne").Activate Worksheets("Лист2").Activate Range("A3") = 5 Range("A4") = "=A3+2" Range("A5:A6") = "=A3+A4"

    End Sub

    Обратите внимание на следующие моменты:

  • Здесь при вызове Range во всех случаях используется только один параметр - и это ячейка или диапазон, заданные в формате "А1".
  • Цепочка вызовов начинается непосредственно с Range, по умолчанию это означает, что речь идет о свойстве Range активного листа. Заметьте, что это должен быть рабочий лист, иначе возникнет ошибка. Так что для полноты картины следовало писать, например Worksheets("Лист2").Range или ActiveSheet.Range.
  • Объекту Range мы присваиваем значения и формулы. Опять - таки здесь используется концепция умолчания, в результате присваивания определяются свойства Value или Formula объекта Range. По-видимому, лучше писать подобные присваивания в таком виде: 'Example 2 Range("B1").Value = 7 Range("B2").Formula = "=B1+2" Range("B3:B4").Formula = "=B1+B2"
  • Когда формула присваивается диапазону ячеек, то переменные в формуле носят относительные имена и изменяются при переходе к очередной ячейке диапазона, так что формула, приписанная ячейке A6, будет иметь вид: "=A4 +A5"


  • Следующий пример демонстрирует важную еще одну важную для понимания относительность ссылок, задаваемых параметром Cell. Этот параметр задает ссылки, относительно объекта Range, вызвавшего свойство Range. Вот пример:

    'Example 3 Dim myRange As Range Set myRange = Range("C1:C4") myRange.Range("A1") = 7 myRange.Range("B1") = 7 myRange.Range("A2") = "=A3+2" myRange.Range("A3:A5") = "=A3+A4"


    Вначале создается объект myRange, заданный диапазоном "C1:C4". Вызов myRange.Range("A1") определяет объект из одной ячейки А1, где адрес вычисляется относительно объекта myRange. Такая ссылка задает ячейку С1 в абсолютных адресах. Заметьте, что можно обращаться к любым ячейкам вне зависимости от того, какую область занимает вызывающий объект myRange, задающий по существу начальную точку отсчета. В нашем примере присваивается значение не только ячейкам A1 - A4, но и ячейкам B1 и A5, не входящих в диапазон, определяемый объектом myRange. Остается еще заметить, что если в левой части операторов присваивания А1 и А2 - это ссылки относительно объекта myRange, то в формулах правой части А1 и А2 привязаны к абсолютным адресам.

    Наш следующий пример демонстрирует работу с объектом Selection:

    'Example 4 Range("D1").Select Selection.Range("A1") = 7 Selection.Range("A2") = "=C1+2" Selection.Range("A3:A4") = "=C1+C2"

    Все сказанное по поводу предыдущего примера имеет место и в данном случае, когда свойство Range вызывается объектом Selection.

    Рассмотрим теперь вызов Range с двумя параметрами:

    'Example 5 Dim myRange1 As Range Set myRange1 = Range("E1", "E6") Debug.Print myRange1.Count myRange1.Range("A1") = 27 myRange1.Range("A2") = "=D1+2" myRange1.Range("A3:A6") = "=D1+D2"

    Здесь в роли параметров метода Range выступают ячейки, первая из них определяет левый верхний, а вторая - правый нижний элемент диапазона объекта Range. Далее с этим объектом работаем также как и в предыдущем примере. В качестве параметров Cell1 и Cell2 могут выступать не только ячейки, но и объекты Range, что и демонстрирует следующий пример. Возвращаемый объект Range в этом случае представляет объединение областей, охватывающее область первого и второго объектов. Вот пример:

    'Example 6 Dim myr1 As Range, myr2 As Range, myr3 As Range Set myr1 = Range("A11:C15") myr1 = 33 Set myr2 = Range("A13:F14") myr2 = 44 Set myr3 = Range(myr1, myr2) Debug.Print myr3.Cells.Count Debug.Print myr3.Cells(1, 1)


    Прямоугольная область, полученная в результате, будет состоять из 30 элементов, и первый элемент этой области будет иметь значение 33, что и отражают результаты отладочной печати. Пример демонстрирует, как можно построить объединение диапазонов, дающее в результате новый непрерывный диапазон, включающий области обоих объектов. Покажем теперь, как построить "настоящее" объединение и пересечение диапазонов. В настоящем объединении могут быть несмежные диапазоны. Оно включает те и только те ячейки, которые принадлежат объединяемым объектам. Чтобы задать объединение или пересечение диапазонов, нужно вызвать Range с одним параметром, Этот параметр в этом случае представляет список элементов, разделенный знаком объединения -"," (запятая) или знаком пересечения - " " (пробел). Каждый элемент списка представляет диапазон, возможно, ячейку. Вот соответствующий пример

    'Example 7 Dim myRange3 As Range 'Пример пересечения Set myRange3 = Range("A6:E6 E1:E6") ' Пересечение состоит из одного элемента E6 Debug.Print myRange3.Count myRange3.Select 'Пример объединения Dim myRange4 As Range Set myRange4 = Range("A6:E6, E1:E6") 'Объединение содержит 11 (!) элементов Debug.Print myRange4.Count myRange4.Select

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

    'Example 8 Dim myRange5 As Range 'Set myRange5 = Range("myRange1, myRange2")

    Наконец, покажем, что в одном Range можно строить сколь угодно сложное объединение и пересечение элементов. Приведем для экзотики такой пример:

    'Example 9 Dim myRange5 As Range Set myRange5 = Range("A6:E6, E1:E6, C1:C6 B5:D5") Debug.Print myRange5.Count myRange5.Select

    Чтобы убедиться, что все построено правильно взгляните на картинку:

    Объекты Range и Selection

    Рис. 3.20.  Экзотический объект Range

    Общие объекты и Excel.Application

    Давайте начнем рассмотрение со свойств объекта Excel.Application , возвращающих уже знакомые нам общие объекты:

    Таблица 3.1. Общие объекты, доступные в Excel.ApplicationСвойство, возвращающее объектНазначение объектаБиблиотека
    AssistantПомощник, позволяющий организовать собственную диалоговую систему.Office
    Answer WizardМастер Ответов, стоящий за спиной Помощника. Может использоваться при создании собственной справочной системы.Office
    Com AddInsКоллекция компонент, общих для приложений Office 2000.Office
    CommandBarsКоллекция инструментальных панелей, без работы с которой не обойтись при создании собственного интерфейса документа Excel.Office
    FileSearchОбъект, используемый при поиске файлов.Office
    Language SettingsОбъект, задающий языковые предпочтения, общие для приложений Office 2000.Office
    DebugОбъект, используемый при отладке программных проектов.VBA
    VBEКорневой объект при работе с программными проектами.VBA

    Все объекты, приведенные в этой таблице, играют важную роль при программной работе с документами Excel, как, впрочем, и с другими документами Office 2000.

    Построение обработчиков событий

    Обработчики событий для объектов Workbook, Worksheet и объектов Chart, задающих листы диаграмм, построить нетрудно. Все эти события по умолчанию включены, поэтому для построения обработчика достаточно перейти в окно проектов, выбрать модуль, обрабатывающий события этого объекта, в окне объектов этого модуля выбрать нужный объект, а затем в окне событий и процедур выбрать из списка имя события. В результате этих действий появится заготовка, содержащая заголовок обработчика события, после чего останется написать код обработчика. Более сложно строятся обработчики событий для объектов Chart, задающих встроенные диаграммы. О том как создаются обработчики событий для тех объектов, у которых события по умолчанию выключены, я подробно рассказал при рассмотрении событий объекта Application. Остается только коротко напомнить схему действий:
  • Вначале нужно создать класс, в котором следует объявить объект Chart с событиями (With Events)
  • Затем нужно объявить глобальный объект созданного класса - объект, уже имеющий события. После этого остается написать инициализирующую процедуру, в которой связывается глобальный объект с объектом, задающим встроенную диаграмму.
  • Поскольку объект Chart с событиями появляется в окне созданного класса, то к нему применяется обычная схема создания обработчиков событий.
  • После запуска инициализирующей процедуры, объект, задающий встроенную диаграмму, будет реагировать на события.

  • Возможно, следует обратиться к началу главы, где все подробно описано на примере работы с объектом Application.

    Пример обработки события Change

    В качестве примера рассмотрим построение обработчиков события Change. Я рассмотрю обработку этого события на двух уровнях - уровне объекта Application, на нижнем уровне - объектом WorkSheet. Рассмотрение этого события представляет практический интерес, поскольку довольно часто в различных задачах приходится следить за изменениями, происходящими с рабочими страницами. С другой стороны, есть несколько важных нюансов, которые следует учитывать в процессе работы с этим событием.
    В моем примере обработчик события Change объекта Application будет следить за всеми изменениями, которые пользователь выполняет на рабочих страницах различных документов Excel. Информация об изменениях будет регистрироваться в журнале изменений, заданным специально спроектированной формой. Как выглядит сама форма, увидим чуть позже, а сейчас замечу, что устроена она очень просто и содержит один список из пяти столбцов, в каждом из которых будут храниться данные о документе, в котором произошли изменения, странице, дате, адресе области изменения и новом значении, записанном в эту область. Вот текст обработчика события, возникающего при инициализации формы:
    Private Sub UserForm_Initialize() 'Задание заголовков столбцов журнала изменений. With Me.ListBox1 .ColumnCount = 5 .AddItem "Книга" .Column(1, .ListIndex + 1) = "Страница" .Column(2, .ListIndex + 1) = "Дата" .Column(3, .ListIndex + 1) = "Адрес" .Column(4, .ListIndex + 1) = "Значение" End With End Sub
    Приведу теперь текст обработчика события Change для объекта Application:
    Private Sub ExApp_SheetChange(ByVal Sh As Object, ByVal Target As Range) 'Запись в журнал всех изменений, проводимых пользователем. Dim RowIndex As Integer MsgBox ("Запись в журнал изменений!") RowIndex = JournalForm.ListBox1.ListCount With JournalForm.ListBox1 .AddItem Sh.Parent.Name .Column(1, RowIndex) = Sh.Name .Column(2, RowIndex) = Now .Column(3, RowIndex) = Target.Address .Column(4, RowIndex) = Target.Cells(1).Value End With End Sub

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

    Рис. 3.4.  Журнал изменений, созданный обработчиком события Change
    В журнале нашли отражение почти все изменения, происшедшие со страницами рабочих книг - с разными ячейками, разными страницами, разными рабочими книгами. Почему не все изменения были зафиксированы в журнале, я скажу чуть позже, специально остановившись более подробно на объяснении этой ситуации. А сейчас давайте рассмотрим специальный обработчик этого же события, предусмотренный для страницы с именем "Лист1" книги BookThree. Вот его текст:
    Private Sub Worksheet_Change(ByVal Target As Range) Static NumChange As Integer Dim Myr As Range NumChange = NumChange + 1 MsgBox ("Пишу Изменения!") Set Myr = ThisWorkbook.Worksheets("Лист2").Range("A1") Myr.Offset(NumChange, 0) = Target End Sub
    Изменения, происходящие с ячейками этой страницы, будут фиксироваться не только в общем журнале изменений, но и заноситься на следующий лист этой же книги. Этот лист, по существу, является журналом изменений данной конкретной страницы. Такова общая схема работы с событием Change. А теперь давайте поговорим о нюансах.

    Смещение и свойство Offset

    Мы только что сказали, что при создании объектов Range нельзя пользоваться смещением - доступен только формат А1. Тем не менее, можно использовать смещение, чтобы переходить от одного объекта Range к другому, например от одной ячейки к другой, отстоящей от первой на определенном расстоянии. Достигается это благодаря свойству Offset объекта Range. Это свойство, или если хотите метод, имеет два параметра: RowOffset и ColumnOffset - смещение по строкам и столбцам, и возвращает новый объект Range, отстоящий от прежнего на заданное расстояние. Вот пример создания нового объекта, смещенного относительно исходного:
    'Example 12 Set myRange = Range("A1:A4") Set myRange1 = myRange.Offset(2, 3) myRange1.Select
    Приведем еще один пример, когда смещение используется при работе с ячейками. Заодно продемонстрируем ряд полезных функций, позволяющих проанализировать тип значения, хранящегося в ячейках таблицы:
    'Example 13 Dim currcell As Range For Each currcell In Range("E1:E6").Cells If Application.WorksheetFunction.IsText(currcell.Value) Then currcell.Offset(0, 1).Formula = "Text" ElseIf Application.WorksheetFunction.IsNumber(currcell.Value) Then currcell.Offset(0, 1).Formula = "Number" ElseIf Application.WorksheetFunction.IsLogical(currcell.Value) Then currcell.Offset(0, 1).Formula = "Logical" ElseIf Application.WorksheetFunction.IsError(currcell.Value) Then currcell.Offset(0, 1).Formula = "Error" ElseIf currcell.Formula = "" Then currcell.Offset(0, 1).Formula = "Пусто" End If Next currcell
    Взгляните, как выглядят значения, хранящиеся в ячейках, и результаты их анализа:
    Смещение и свойство Offset

    Рис. 3.21.  Результаты анализа значений, хранимых в ячейках E1- E6

    События объекта Chart

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

    Таблица 3.9. События объекта ChartСобытиеПоявляется, когда пользователь или программа
    ActivateАктивизировал лист диаграмм. Естественно, его нет у встроенных диаграмм.
    BeforeDoubleClickДважды щелкает кнопкой мыши на диаграмме
    BeforeRightClickЩелкает правой кнопкой мыши на диаграмме
    CalculateДобавил или изменил данные на диаграмме.
    DeactivateАктивизировал новый лист и тем самым деактивировал старый.
    DragOverПеретащил данные, расположив их поверх диаграммы.
    DragPlotПеретащил диапазон ячеек, расположив их поверх диаграммы.
    MouseDownНажал кнопку мыши при позиционировании ее над диаграммой.
    MouseMoveПередвигает указатель мыши по диаграмме.
    MouseUpЗакончил перемещение мыши и освободил кнопку.
    ResizeИзменил размер диаграммы.
    SelectВыделил некоторый элемент диаграммы
    SeriesChangeИзменил значение точки ряда данных.


    События объекта Excel.Application

    Объект Excel.Application может обрабатывать 21 событие, возникающие при работе с теми или иными объектами приложения Excel. Почти половина из этих событий возникает в процессе работы с объектом Workbook, другая половина событий связана с такими объектами, как страница документа (объект Sheet) и окно (объект Window). Практически все события, за исключением одного события NewWorkBook, могут быть обработаны на двух уровнях - объектом Application и объектом Workbook. Но прежде чем поговорить, о том, какие события может обрабатывать объект Application, в каких случаях следует проводить обработку события на уровне объекта Application, а в каких - на уровне объекта Workbook, давайте разберемся, как заставить объект Excel.Application вообще реагировать на события.

    События объекта Workbook

    Со всеми событиями, которые может обрабатывать объект Workbook, мы уже знакомы. Всего таких событий 20, из них 9 событий связаны непосредственно с самим объектом Workbook, 8 - возникают на страницах рабочей книги и связаны также с объектом Sheet, три события связаны с объектом Window. Я напомню, что при возникновении события сообщение о нем операционная система посылает, как правило, нескольким объектам. Все они, каждый по-своему, могут обрабатывать это событие. Подробно обо всем этом рассказано при рассмотрении событий объекта Application.

    События объекта Worksheet

    Со всеми событиями, которые может обрабатывать объект Worksheet, мы уже знакомы. Всего таких событий 8. Я напомню, что при возникновении события сообщение о нем операционная система посылает, как правило, нескольким объектам. Поэтому, когда возникает событие, связанное с рабочим листом, сообщение о нем будет послано и объектам Workbook и Application, стоящим на верхних уровнях иерархии. Все они, каждый по-своему, могут обрабатывать это событие. Подробно обо всем этом рассказано при рассмотрении событий объекта Application. Замечу еще, что объект Worksheet это последний объект в иерархии, для которого определены события, на нижних уровнях иерархии таких объектов нет.

    События, связанные с объектом Sheet

    Ряд событий, которые возникают в процессе работы со страницами той или иной рабочей книги, также могут быть обработаны на уровне объекта Application. Естественно, что эти события могут быть обработаны и на более низких уровнях - как на уровне объекта Workbook, так и на следующем уровне объектами, задающими саму страницу - Worksheet и Chart. Понятно, что на самом верхнем уровне задается обработка, общая для всех страниц всех книг, на следующем уровне обработка, общая для страниц конкретной книги, и на нижнем уровне - обработка, специфическая для данной страницы. В таблице 3.4 дана сводка событий, связанных с объектом Sheet, обрабатываемых на уровне объекта Application.

    Таблица 3.4. События, возникающие при работе с объектом Sheet СобытиеКогда возникаетПараметры события
    SheetActivate(Sh As Object)Страница становится активной.Вновь активированная страница передается обработчику события в качестве параметра.
    SheetBeforeDoubleClick(Sh As Object, Target As Range, Cancel As Boolean)При двойном щелчке левой клавиши мыши на рабочей странице, но до того, как выполнится макрос, задающий реакцию на щелчок. Событие не возникает на Chart-страницах.Первый параметр передает обработчику события объект Sh, задающий рабочую страницу, на которой был произведен двойной щелчок. Второй параметр Target возвращает ячейку (объект Range), ближайшую к указателю мыши в тот момент, когда был произведен щелчок. Если в обработчике события изменить значение параметра Cancel на True, то отменится выполнение макроса, задающего реакцию на двойной щелчок.
    SheetBeforeRightClick(Sh As Object, Target As Range, Cancel As Boolean)Аналогично двойному щелчку, но при нажатии правой клавиши мыши.Параметры сохраняют смысл, описанный для обработчика событий двойного щелчка.
    SheetCalculate(Sh As Object)При перевычислениях рабочей страницы или при любых изменениях данных, отображаемых на диаграмме страницы диаграмм.В зависимости от того, на странице какого типа произошло событие, параметр Sh представляет либо объект Workbook либо объект Chart.
    SheetChange(Sh As Object, Target As Range)При изменениях в ячейках рабочей книги, инициированные пользователем или внешней ссылкой. Не возникает для Chart-страниц.Параметр Sh задает объект WorkSheet - страницу, в ячейках которой произошли изменения. Параметр Target задает область изменения - объект Range.
    SheetDeactivate(Sh As Object)Страница перестает быть активной, поскольку активной становится другая страница.Деактивированная страница передается обработчику события в качестве параметра.
    SheetFollowHyperlink(Sh As Object, Target As Hyperlink)При щелчке по гиперссылке на рабочей странице. Не возникает для Chart-страниц.В качестве параметров обработчику события передаются два объекта, представляющие рабочую страницу и гиперссылку, задающую переход.
    SheetSelectionChange(Sh As Object, Target As Range)При изменении области выделения рабочей страницы. Не возникает для Chart-страниц.Параметр Sh задает объект WorkSheet -страницу, содержащую новую область выделения. Параметр Target задает новую область выделения - объект Range.


    События, связанные с объектом Window

    Объект Application может обработать три события, возникающие в процессе работы с окном - объектом Window. Эти события показаны в следующей таблице.

    Таблица 3.5. События, возникающие при работе с объектом Window СобытиеКогда возникаетПараметры события
    WindowActivate(Wb As Workbook, Wn As Window)Окно рабочей книги становится активным.Рабочая книга и вновь активированное окно передаются обработчику события в качестве параметров.
    WindowDeactivate(Wb As Workbook, Wn As Window)Окно рабочей книги перестает быть активным.Рабочая книга и деактивированное окно передаются обработчику события в качестве параметров.
    WindowResize(Wb As Workbook, Wn As Window)Окно рабочей книги изменяет размеры.Рабочая книга и перестраиваемое окно передаются обработчику события.

    Полагаю, что в дополнительных комментариях и примерах эти события не нуждаются.

    События, связанные с рабочей книгой

    В нижеследующей таблице 1 дана сводка всех событий, которые возникают при работе с рабочими книгами - объектами Workbook, и которые могут быть обработаны объектом Application.

    Таблица 3.3. События, возникающие при работе с объектом WorkbookСобытиеКогда возникаетПараметры события
    NewWorkbook(Wb As Workbook)При создании новой книги. Единственное событие этой группы, которое может обработать только объект Application.Обработчику события передается объект Wb, представляющий вновь созданную книгу.
    WorkbookActivate(Wb As Workbook)Книга становится активной.Вновь активированная книга передается обработчику события в качестве параметра.
    WorkbookAddinInstall(Wb As Workbook)При установке рабочей книги в качестве AddIn.Рабочая книга, представляющая AddIn.
    WorkbookAddinUninstall(Wb As Workbook)Отменяется установка рабочей книги в качестве AddIn. Закрытие книги при этом не происходит.Рабочая книга, представляющая AddIn.
    WorkbookBeforeClose(Wb As Workbook, Cancel As Boolean)При попытке закрыть рабочую книгу, но до того, как она будет закрыта.Параметр Wb задает закрываемую книгу. Параметр Cancel позволяет отменить закрытие, если в обработчике события его значение будет установлено как True.
    WorkbookBeforePrint(Wb As Workbook, Cancel As Boolean)При попытке распечатать содержимое рабочей книги, но до того, как произойдет печать.Параметр Wb задает печатаемую книгу. Параметр Cancel позволяет отменить печать, если в обработчике события его значение будет установлено как True.
    WorkbookBeforeSave(Wb As Workbook, SaveAsUI As Boolean, Cancel As Boolean)При попытке сохранить содержимое рабочей книги, но до того, как произойдет сохранение.Параметр Wb задает сохраняемую книгу. Параметр Cancel позволяет отменить сохранение, если в обработчике события его значение будет установлено как True. Параметр SaveAsUI показывает, как идет сохранение, его значение равно true, если при сохранении открывается диалоговое окно "Сохранить как ".
    WorkbookDeactivate(Wb As Workbook)Книга перестает быть активной, поскольку активной становится другая книга.Деактивированная книга передается обработчику события в качестве параметра.
    WorkbookNewSheet(Wb As Workbook, Sh As Object)При добавлении новой страницы в рабочую книгу.Объект Wb задает книгу, а Sh - страницу, добавленную в эту книгу.
    WorkbookOpen(Wb As Workbook)При открытии уже существующей рабочей книги.Обработчику события передается объект Wb, представляющий вновь открытую книгу.


    Практически все события, происходящие с объектом Workbook, могут быть обработаны на двух уровнях - объектом Application и самим объектом Workbook. Разница лишь состоит в том, что если у объекта Application есть, например, событие WorkbookOpen, то у объекта Workbook есть событие Open. При возникновении данного события операционная система посылает соответствующее сообщение двум объектам - Application и Workbook. Обработчику сообщения WorkbookOpen передается параметр Wb, задающий открываемую книгу. Понятно, что при посылке аналогичного сообщения объекту Workbook передавать этот параметр не имеет смысла, поскольку он и так знает сам себя, так что обработчик события Open объекта Workbook параметров не имеет.
    Возникает естественный вопрос, в каких случаях следует вести обработку события, происходящего с объектом Workbook, на уровне объекта Application. Ответ понятен - в тех случаях, когда обработчик события выполняет действия, общие для всех рабочих книг. В тех же случаях, когда предполагается специфическая обработка события, характерная только для данной конкретной книги, обработчик события связывается с объектом Workbook. Что происходит, если обработка одного и того же события предусмотрена на двух уровнях? В этом случае вначале выполнится обработчик события, связанный с объектом Workbook, - он выполнит специфическую для данной книги обработку, а потом начнет работать обработчик этого же события, связанный с объектом Application, выполняющий ту часть работы, которая является общей для всех рабочих книг.
    Приведу пример, в котором предусмотрена общая для всех рабочих книг обработка события BeforeSave:
    Private Sub ExApp_WorkbookBeforeSave(ByVal Wb As Workbook, _ ByVal SaveAsUI As Boolean, Cancel As Boolean) Dim YesNo As Variant YesNo = MsgBox("Вы действительно хотите сохранить этот документ?", vbYesNo) If YesNo = vbNo Then Cancel = True End Sub
    Предупреждающее сообщение будет появляться для всех рабочих книг. Рассмотрим теперь пример, когда обработка события BeforePrint предусмотрена на двух уровнях:
    Private Sub Workbook_BeforePrint(Cancel As Boolean) 'Обработка события - печать содержимого книги. MsgBox ("Эту книгу - " & ThisWorkbook.Name _ & " печатать запрещено!") Cancel = True End Sub


    Private Sub ExApp_WorkbookBeforePrint( ByVal Wb As Workbook, Cancel As Boolean) 'Обработка события - печать содержимого книги. If Wb.ActiveSheet.Name = "Лист1" Then MsgBox ("Эту страницу книги - " & Wb.Name _ & " печатать запрещено!") Cancel = True End If End Sub
    Специальный обработчик события объекта Workbook книги BookOne запрещает печать только этой книги, а общий для всех книг обработчик того же события, но находящийся в объекте Application, запрещает печать только первого листа, но для всех рабочих книг. Вот как выглядит сообщение, выданное для книги BookOne общим обработчиком при попытке распечатать содержимое первого листа книги:
    События, связанные с рабочей книгой

    увеличить изображение
    Рис. 3.3.  Сообщение, выданное обработчиком события WorkbookBeforePrint

    Создание объекта Application, реагирующего на события

    Для всех приложений Office 2000 соответствующие объекты Application хотя и могут реагировать на события, но появляются как объекты без событий. И нужно приложить некоторые усилия, чтобы создать новый объект Application With Events, который может реагировать на события. Причина возникающих сложностей кроется в логике построения программного проекта, изначально принятой в Office. Дело в том, что обработчики событий, возникающих при работе с теми или иными объектами, следует размещать в специальных модулях - обработчиках событий. Для большинства стандартных объектов Office эти модули создаются автоматически в момент создания основного объекта. Так, например, при создании документа в его проекте автоматически создается модуль, обрабатывающий события этого документа, - для Excel это модуль с именем "ЭтаКнига" (ThisWorkbook). Только в этом модуле и можно создать обработчики событий объекта Workbook и всех объектов, реагирующих на события, например, элементов управления, встраиваемых непосредственно в документ.
    Объект Application, единый для всех рабочих книг, по понятным причинам не вписывается в эту общую схему и для него такой модуль автоматически не создается, потому, естественно, эту работу приходится выполнять программисту.
    Для того чтобы заставить реагировать на события объект Excel.Application, необходимо выполнить четыре шага:
  • Создать модуль, задающий собственный класс для объекта Excel.Application, в котором можно будет размещать обработчики возникающих событий. Устроен этот класс очень просто и состоит в момент создания из одной строчки, поскольку класс не имеет методов и имеет ровно одно свойство, задающее объект Application с событиями. Конечно, позже этот класс будет пополняться обработчиками событий. Вот как выглядит на этапе создания описание этого класса, которому я в своем примере дал имя AppWithEvents. Option Explicit 'Класс, описывающий объект Application With Events. 'Класс не имеет методов, задается одним свойством. Public WithEvents ExApp As Application
  • Следующее, что необходимо сделать - это определить объект вновь созданного класса AppWithEvents. И эта задача решается в одну строчку. Объявление объекта я поместил в раздел объявлений созданного ранее стандартного модуля. Заметьте, необходимо не только объявить объект, но и создать его, используя спецификатор New. Public AppWithEv As New AppWithEvents
  • Третьим шагом является связывание двух объектов Application, стандартного и созданного объекта, умеющего реагировать на события. По-видимому, лучшим местом, где следует осуществить связывание, является обработчик события Open того документа, в котором описан класс AppWithEvents. Тогда после открытия этого документа и вплоть до его закрытия Excel будет реагировать на события, связанные с объектом Excel.Application. Вот как осуществляется связывание: Private Sub Workbook_Open() 'Связывание двух объектов Application - 'стандартного и реагирующего на события. Set AppWithEv.ExApp = Excel.Application End Sub
  • На этом шаге созданный класс AppWithEvents можно расширить, добавив в него обработчики событий объекта Application. Я приведу пока текст только одного обработчика, который обрабатывает событие NewWorkBook, возникающее при создании новой книги. Private Sub ExApp_NewWorkbook(ByVal Wb As Workbook) 'Обработка события - создание новой книги. Static Num As Integer Num = Num + 1 MsgBox ("Число вновь созданных книг - " & Num _ & vbCrLf & "Новая книга - " & Wb.Name _ & " открыта в " & Time) End Sub

  • Вот как выглядит сообщение, появляющееся при открытии новой книги:
    Создание объекта Application, реагирующего на события

    Рис. 3.2.  Сообщение об открытии новой книги

    Сравнение свойств объектов Range и Worksheet

    У этих двух объектов есть целый ряд общих свойств. Вот они:
  • Свойства, возвращающие объект Range: Range, Cells, Columns, Rows. Понятно, что с помощью, например, свойства Range можно выделить некоторую область не только из области заданной рабочим листом, но и из любой подобласти, определенной объектом Range. Это же относится и ко всем другим свойствам, возвращающим объект Range, напоминающий матрешку.
  • Hyperlinks - возвращает коллекцию гиперссылок, принадлежащих области объекта Range.

  • Целый ряд свойств объекта Range возвращают единственный объект, в то время как родительский объект Worksheet возвращает всю коллекцию. Вот эти свойства:
  • Name - для Range возвращается не строка, задающая имя, а объект Name.
  • Comment - комментарий.
  • PivotTable - сводная таблица.
  • QueryTable - таблица запросов.

  • Обратите внимание, на объект Range, возвращающий единственный объект, накладываются определенные требования. Так для того, чтобы вернуть комментарий, необходимо, чтобы объект Range представлял единственную ячейку, содержащую комментарий. Сводная таблица должна содержать верхний левый угол объекта Range. Честно скажу, логика создателей объектной модели не очень понятна. Почему возвращается коллекция гиперссылок, но не возвращается коллекция комментариев или сводных таблиц, которых, вообще говоря, может быть несколько в области объекта Range. На такие вопросы ответов нет, нужно просто знать спецификации. Вот небольшой пример работы со свойством Comment:
    'Example 13 - какой комментарий возвращается? Dim Sh As Worksheet Dim myr As Range, s As String, c As Comment Set Sh = ThisWorkbook.Worksheets(1) Set c = Sh.Comments(1) s = c.Text Debug.Print s Set myr = Sh.Range("C16") myr.Select Set c = myr.Comment s = c.Text Debug.Print s
    Следующую группу составляют похожие свойства. Я отношу к ним следующие свойства:
  • OutlineLevel, - свойство, определенное для объектов Range, представляющих строки или столбцы. Задает уровень структурирования для текущей строки или столбца и связано со свойством Outline объекта Worksheet.
  • PageBreak - разрывы страниц, также устанавливаемые для строк и столбцов. Свойство связано со свойствами HPageBreaks и VPageBreaks.

  • Упомяну еще ряд свойств, так или иначе пересекающихся со свойствами родительского объекта:

  • CurrentArray, CurrentRegion - Первое из этих свойств возвращает весь массив, частью которого является объект Range (ячейка). Второе - возвращает текущий регион, то есть минимальную прямоугольную область, содержащую элементы из Range и окаймленную пустыми строками и столбцами или границами таблицы. Если Range представляет связную область, то текущий регион охватывает Range, для несвязной области он выделяет лишь некоторую его часть.
  • EntireColumn, EntireRow - Эти два свойства возвращают один или несколько столбцов или строк, охватывающих объект Range.
  • End - используется для объекта Range, представляющего одну ячейку. В качестве результата возвращается объект Range, также представляющий одну ячейку в конце региона, содержащего вызывающий свойство объект Range. Вот как, например, можно, используя это свойство, выделить область от искомой ячейки до конца региона:
  • 'Example 14 Set myr = Sh.Range("F12") Sh.Range(myr, myr.End(xlToRight)).Select
  • Dependents, Precedents, DirectDependents, DirectPrecedents - В первых двух случаях возвращается объект Range, содержащий все ячейки, зависимые или предшествующие ячейкам исходного объекта. Результат, обычно, представляет несвязную область. В двух последних случаях возвращаются только непосредственно зависимые и непосредственно предшествующие ячейки. Возможно, стоит определить понятия зависимых и предшествующих ячеек. Если формула в ячейке Y содержит ссылку на ячейку X, то говорят, что Y непосредственно зависит от X, а X непосредственно предшествует Y. Обобщая понятие непосредственной зависимости, мы говорим, Y зависит от X, а X предшествует Y, если существует цепочка ячеек Z1, Z2, …ZK, начинающаяся с X и заканчивающаяся Y, такая, что каждые два соседние элемента цепочки связаны отношением непосредственной зависимости (непосредственного предшествования). Свойство CircularReferences объекта Worksheet, возвращающее ячейки, связанные циклической зависимостью, тоже относится к этой группе свойств. О зависи мых ячейках и примерах использования этих свойств подробный разговор еще предстоит.
  • PivotItem, PivotField - возвращают элементы сводной таблицы, хранящиеся в объекте Range.


  • Свойства и методы объекта Range

    Об объекте Range можно говорить сколь угодно долго - это основа Excel. У него есть большое число свойств и методов, но нет событий, поскольку события связаны с объектами, стоящими не более высоких уровнях иерархии. Заметьте, со многими свойствами объекта Range мы уже знакомы. В этом нет ничего удивительного, поскольку Range задает часть рабочего листа, а свойства части и целого во многом совпадают. Поэтому давайте начнем изучение свойств объекта Range в сравнении с уже знакомыми свойствами объекта Worksheet.

    Свойства объекта Worksheet

    Среди свойств, как всегда, наибольший интерес представляют свойства-участники, возвращающие некоторый отдельный объект или коллекцию в качестве результата. Эти свойства определяют структуру объекта Worksheet, задавая непосредственно вложенные в него объекты.

    Свойства-участники объекта Workbook

    Свойств, возвращающих объекты, у объекта Workbook относительно немного. Поэтому коротко можно рассказать о каждом. При их описании я разделю их на группы:
  • Группа свойств, возвращающих основные объекты рабочей книги. К ним я отношу следующие свойства:
  • Sheets, Charts, WorkSheets, Excel4MacroSheets - все они возвращают объект класса Sheets, но в зависимости от свойства этот объект представляет либо всю коллекцию листов рабочей книги, либо ту ее часть, которая содержит листы типа, заданного именем свойства
  • ActiveSheet - возвращает объект класса Object, содержащий активный лист рабочей книги. Тип активного листа и определяет тип возвращаемого объекта.
  • ActiveChart - возвращает объект класса Chart - это может быть лист-диаграмма, если активный в момент вызова лист принадлежит этому типу, либо диаграмма, встроенная в активный рабочий лист, если и сама диаграмма на этом листе является в свою очередь активной. В противном случае при вызове свойства будет возвращен результат Nothing, свидетельствующий о том, что объект не найден.
  • Группа свойств, возвращающих общие или схожие объекты. Это самая большая группа свойств объекта Workbook, что говорит о том, что общих черт у объекта Workbook больше чем специфических.
  • CommandBars - общий объект, возвращающий коллекцию инструментальных панелей приложений Office 2000.
  • VBProject - общий объект, описывающий программный проект приложений Office 2000. Необходим при программировании "на лету" - программной работе на VBA с программным проектом.
  • HMLProject - общий объект библиотеки Office, описывающий проект в формате HTML. Этот объект используется при программировании на VBScript.
  • WebOptions - объект, схожий во всех приложениях Office 2000, позволяющий задавать параметры Web-страниц. Действие большинства параметров распространяются на все приложения Office 2000.
  • Windows - коллекция окон, связанных с рабочей книгой. Особенностью документа Excel от документов других типов состоит в том, что документ Excel можно одновременно открыть в нескольких окнах, чтобы в каждом из них видеть требуемую часть рабочей книги. Сами же объекты Window схожи во всех приложениях. Эта коллекция является частью более общей коллекции окон, возвращаемой аналогичным свойством объекта Application.
  • RoutingSlip - объект, схожий для всех приложений Office 2000. Он определяет характеристики передачи рабочей книги по сети участникам, совместно работающим над данным документом. При работе с ним должно быть включено булево терминальное свойство HasRoutingSlip.
  • Styles - объект, схожий для приложений Office 2000 - определяет коллекцию применяемых для форматирования стилей. Есть набор встроенных стилей, но в коллекцию можно добавлять и собственные стили, задав в момент добавления соответствующие характеристики стиля - шрифт, размер, рамки и т.д. Обычно стили создаются руками, но, возможно и программное создание или изменение того или иного стиля.
  • BuiltinDocumentProperties, CustomDocumentProperties - оба эти свойства возвращают объект класса DocumentProperties из общей библиотеки Office. Этот класс определяет коллекцию свойств документов Office 2000. Эта коллекция напоминает коллекцию Sheets тем, что ее элементы могут относиться к различным типам. Свойство BuiltinDocumentProperties возвращает ту часть коллекции, которая задает стандартные свойства, заданные самим приложением, CustomDocumentProperties - свойства, определенные пользователем для конкретного документа. Свойства документов, являются схожими для всех приложений Office 2000. С двумя типами свойств работа идет по-разному. Коллекция стандартных свойств определена для каждого типа документа Office 2000, - добавлять или удалять элементы этой коллекции нельзя. В тоже время, коллекция пользовательских свойств изначально пуста и добавление или удаление свойств является основным способом работы с этой коллекцией. Большинство из стандартных свойств автоматически изменяют свои значения в процессе работы с документом, отражая, например, текущие характеристики документа - количество страниц, символов, дату последнего изменения и другие характеристики. Значения других стандартных свойств, также как и всех пользовательских свойств могут быть заданы пользователем или программно.
  • Mailer - схожий для всех приложений Office 2000 почтовый объект, используемый при работе на компьютерах Макинтош.
  • К третьей группе относятся свойства, возвращающие объекты, специфические для рабочих книг Excel. К ним относятся:
  • Names - коллекция имен, которые даны отдельным областям листов рабочей книги. Типичной является ситуация, когда при работе с рабочей страницей, выделяется та или иная область, содержащая определенные данные, и этой области даются имена. При программировании очень часто приходится использовать имена таких объектов. Полагаю, что примеров в свое время будет достаточно. Напомню, что объект Application также имеет свойство Names, возвращающее коллекцию имен, включающую коллекции имен всех открытых рабочих книг.
  • PublishObjects - возвращает одноименную коллекцию, элементы которой принадлежат классу PublishObject. Эти объекты являются новинкой Excel 2000, они задают элементы рабочей книги, опубликованные на Web-странице. С их помощью можно вести обновление Web-страницы при изменениях состояния опубликованных объектов.
  • Container - объект, который содержит рабочую книгу. Типичным примером, когда рабочая книга может быть встроена в другой объект, и являться его частью, служит подшивка - объект Binder. Следует заметить, что в отличие от предыдущей версии подшивки пока не стали частью семейства Office 2000.
  • CustomViews - возвращает одноименную коллекцию, элементы которой задают образ той или иной части рабочей книги в том виде, как он виден на экране дисплея. Каждому такому образу дается свое имя. Ранее я говорил, что для того, чтобы увидеть ту или иную часть рабочей книги, используются окна. Custom View - это другая возможность задать коллекцию образов экрана, отражающих заданные части рабочей книги. В нижеследующем примере первая из приведенных процедур создает объект CustomView, сохраняющий текущий образ рабочей книги. Вторая процедура, которую можно вызвать в подходящий момент, позволяет показать на экране нужную часть рабочей книги в соответствии с сохраненным образом.
  • Public Sub AddCustView() 'Добавить Custom View - образ экрана, 'отражающий текущий вид рабочей книги. ThisWorkbook.CustomViews.Add ("Две кнпки") Debug.Print ThisWorkbook.CustomViews.Count ThisWorkbook.CustomViews(1).Show End Sub
    Public Sub ShowCustView() 'Показать Custom View - вывести на экран, 'заданный вид рабочей книги. Dim CW As CustomView If ThisWorkbook.CustomViews.Count > 0 Then For Each CW In ThisWorkbook.CustomViews If CW.Name = "Две кнопки" Then CW.Show Next CW End If End Sub

    Свойства - участники объекта

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

    Таблица 3.2. Основные свойства - участникиСвойство, возвращающее объектНазначение объекта
    WorkBooksКоллекция открытых в Excel документов - рабочих книг. Основной объект, благодаря которому можно получить доступ к любому документу Excel и далее работать с объектами этой рабочей книги.
    WindowsКоллекция открытых окон во всех рабочих книгах. Дело в том, что одну и ту же рабочую книгу часто полезно открывать в нескольких окнах, что позволяет видеть разные участки рабочей книги. Коллекция Windows позволяет получить доступ к каждому такому окну. Чаще всего, свойство Windows используется при работе с объектом WorkBook, для объекта Application это один из примеров той перегрузки, о которой я упоминал выше.
    WorkSheetFunctionОбъект - контейнер, в котором находятся многочисленные функции Excel, начиная от обычных математических функций и кончая функциями, применяемыми для решения задач статистики, прогноза, работы с датами и прочими.
    AddInsКоллекция компонент, расширяющих возможности решения специальных задач в Excel.
    AutoCorrectЗнакомый по приложению Word объект, позволяющий задавать автоматическую корректировку набираемых текстов в ячейках Excel.
    DefaultWebOptionsОбъект, позволяющий устанавливать параметры для документов Excel, сохраненных в виде Web-страниц. Схож с аналогичным объектом Word.Application, но имеет свою специфику.
    DialogsОбъект Dialogs также как и три предыдущих объекта - AddIns, AutoCorrect, DefaultWebOptions относится к группе схожих объектов, встречающихся в каждом из приложений Office 2000, имеющих много общего, но имеющих и отличия, связанные со спецификой приложения. Также как и в Word, объект Dialogs задает коллекцию стандартных диалоговых окон, которые могут открываться в Excel, позволяя организовать диалог с пользователем.
    NamesОдно из перегруженных свойств, возвращающее коллекцию всех имен, используемых для отдельных ячеек и областей всех открытых документов Excel. Чаще всего, это свойство используется при работе с отдельной рабочей книгой или отдельной страницей.
    ODBCErrorsКоллекция объектов класса ODBCError. Элементы этой коллекции создаются автоматически источником ODBC-данных, если при выполнении запроса на получение данных возникли ошибки. Если ошибок не было, то и коллекция будет пустой.
    OLEDBErrorsКоллекция объектов класса OLEDBError. Аналогично предыдущей коллекции, ее элементы появляются при наличии ошибок в процессе работы с базой данных, когда используется интерфейс OLE DB.
    RecentFilesОбъект, относящийся к группе схожих объектов семейства Office 2000. Он задает коллекцию файлов, хранящих документы Excel последнего использования.


    Конечно, непросто устроен и объект Series, задающий сам ряд данных. Его структура показана на рис. 3.19, а некоторые из основных его свойств были продемонстрированы в предыдущем примере.
  • DataTable- объект, представляющий таблицу данных. Основными методами являются Select и Delete, основные свойства связаны с рамкой, строящейся вокруг таблицы. Вся содержательная работа с данными таблицы ведется через другие объекты (ряды данных). Так что по существу этот объект представляет рамку таблицы данных.
  • Legend - задает легенду диаграммы. O сущности этого объекта мы уже говорили, так что его поведение должно быть понятно.
  • Shapes- эта коллекция нам хорошо знакома. В диаграммах она используется редко, но иногда можно категории изображать рисунками, что повышает эстетику диаграммы.
  • ChartGroups(ChartGroup) - возвращает коллекцию групп. Элементами коллекции являются объекты класса ChartGroup. Каждый такой объект представляет группу, связанную с одним или несколькими рядами данных, отображаемых диаграммой одного и того же типа, о чем я уже подробно рассказывал. Параметр Index позволяет добраться до конкретной группы в коллекции. Поскольку при форматировании одной из группы индексы изменяются, то иногда удобнее пользоваться специальными методами, которые возвращают группы фиксированного типа. Такими методами являются: AreaGroups, BarGroups, ColumnGroups, DoughnutGroups, LineGroups и PieGroups. Эти методы для двумерных диаграмм возвращают коллекцию групп типа, указанного методом. К конкретной группе можно добраться опять - таки с помощью индекса. Для трехмерных диаграмм может быть только одна группа определенного формата. Поэтому есть методы, возвращающие эту единственную группу: Area3DGroup, Bar3DGroup, Column3DGroup, Line3DGroup, Pie3DGroup, SurfaceGroup.
  • Floor, Walls и Corners объекты используются при работе с трехмерными диаграммами. При отображении таких диаграмм для создания иллюзии трехмерного пространства диаграмма отображается на фоне открытого куба, имеющего основание, заданное объектом Floor, и две боковые грани - объекты Walls. Объект Corners задает углы куба. Манипулируя этими объектами, можно, например, развернуть куб или изменить заливку и узор стенок куба, добиваясь большей наглядности изображения.


  • Свойства - участники

    Дадим краткую характеристику свойствам - участникам, входящим в рабочий лист:
  • Range, Cells, Rows, Columns, UsedRange, CircularReference - начнем с группы свойств, возвращающих объект Range. Я уже говорил, что объект Range - это основной объект электронной таблицы. Он позволяет задать, как отдельную ячейку таблицу, диапазоны ячеек, представляющие прямоугольную область таблицы, так и области более сложной конфигурации. Именно объект Range со своими свойствами и методами позволяет осуществлять непосредственную работу, как с отдельной ячейкой, так и с областями ячеек. Об этом объекте я еще много буду говорить, но уже сейчас хочу заметить, что большинство свойств рабочего листа Worksheet, о которых пойдет речь, характерны и для объекта Range, задающего часть рабочего листа. Объект Range возвращается в качестве результата при вызове следующих свойств:
  • Range(Cell1, [Cell2]) As Range - возвращает объект Range, определяемый параметрами свойства. Синтаксис параметров таков, что он позволяет определить достаточно изощренный объект. Я расскажу об этом подробнее чуть позже, когда мы займемся подробным рассмотрением объекта Range.
  • Cells As Range - возвращает коллекцию ячеек электронной таблицы. Вызванное объектом WorkSheet это свойство возвращает всю таблицу ячеек рабочего листа, которая, конечно, представляет собой объект Range. Поскольку Cells одновременно является объектом Range и коллекцией ячеек, то можно использовать индексы, чтобы добраться до отдельного элемента коллекции - ячейки таблицы. Важным свойством Cells обладают и объекты, стоящие на более низких ступенях иерархии, в частности, им обладает и сам объект Range, что позволяет получить коллекцию ячеек для любой заданной области таблицы.
  • Rows As Range и Columns As Range - соответственно возвращают коллекции строк и столбцов таблицы. По индексу можно добраться до отдельной строки или столбцу таблицы. Одновременно эти коллекции являются объектами Range, поскольку задают некоторую область рабочего листа.
  • UsedRange As Range - возвращает используемую область рабочего листа. Как правило, лишь небольшая часть рабочего листа занята данными, формулами, рисунками, диаграммами и графиками. Свойство UsedRange позволяет получить минимальную прямоугольную область, содержащую используемую область рабочего листа.
  • CircularReference As Range - возвращает объект Range, содержащий первую циклическую ссылку, если таковые имеются на рабочем листе. В противном случае возвращается значение Nothing. О циклических ссылках поговорим подробнее чуть позже.


  • Одну и ту же область таблицы - один и тот же объект Range - можно получить разными способами. Приведем пример, показывающий два способа получения ячейки "А1": Debug.Print ActiveSheet.Range("A1") Debug.Print ActiveSheet.Cells(1, 1)
    Оба оператора здесь эквивалентны, но только в одном случае используется свойство Range, в другом - Cells. В следующем примере работа идет над отдельным столбцом и строкой, но, фактически, и здесь действует тот же объект Range: ActiveSheet. Columns(2).Value = "Да" ActiveSheet.Rows(1).Font.Bold = True
  • Shapes - возвращает одноименную коллекцию, элементами которой являются объекты класса Shape. Эта коллекция состоит из объектов самых различных типов. По существу, все, что вставляется в рабочий лист, - рисунки, диаграммы, графики, встроенные и связанные OLE-объекты, элементы управления, размещаемые на рабочем листе, - все это объекты коллекции Shapes. При программировании нам неоднократно придется сталкиваться с этими объектами. После объектов Range объекты Shape представляют наиболее часто встречающийся тип объектов, определяющих суть рабочего листа. Замечу еще, что коллекция Shapes и объекты Shape относятся к схожим объектам, встречающимся во всех приложениях Office 2000. При описании объектов Word нам уже приходилось встречаться с этими объектами.
  • Names - возвращает неоднократно упоминавшуюся одноименную коллекцию. Подобное свойство имеют объекты Application и Workbook, имеет его и объект WorkSheet. Разница состоит только в том, что здесь речь идет об именах, используемых в данном рабочем листе.
  • Comments - возвращает одноименную коллекцию с элементами класса Comment, представляющими комментарии, которые можно привязывать к той или иной ячейке рабочего листа - объекту Range. Замечу, что комментарии несут очень важную нагрузку в создании дружелюбного интерфейса разрабатываемых документов. Их следует широко использовать. Эти объекты также относятся к схожим объектам, и встречаются во всех приложениях Office 2000. Но в Excel комментарии к ячейкам играют особую роль, значительно более важную, чем комментарии в документах Word. В текстовых документах можно использовать различные стили, чтобы непосредственно в тексте документа вставлять авторские замечания, предупреждения и пояснения. Комментарии, как правило, используются в тех случаях, когда не желательно прерывать плавное изложение материала, чтобы работающий с документом мог прочесть основной текст, лишь при желании знакомясь с комментариями. В Excel'е, когда основное содержание листа составляют таблицы, все замечания, пояснения и предупреждения оформляются в виде комментариев к тем или иным ячейкам. Они, например, могут давать подсказку, какие данные располагаются в ячейке, определять их формат и давать другую полезную информацию, необходимую пользователю при работе с таблицами рабочего листа. Есть некоторая разница в программной работе с комментариями в Word и Excel. Вот как выглядит пример введения комментариев в документ Excel: Public Sub AddComments() 'Формируется последовательность чисел Фибоначчи. 'Вставляется комментарий, поясняющий суть чисел. Dim myRange As Range Workbooks("BookThree").Activate With ActiveWorkbook.Worksheets(2) Set myRange = .Range("E1") With myRange .Value = "Числа Фибоначчи" .Offset(1, 0).FormulaR1C1 = "0" .Offset(2, 0).FormulaR1C1 = "1"


    .Offset(3, 0).FormulaR1C1 = "=R[-2]C +R[-1]C" .Offset(3, 0).Select Selection.AutoFill Destination:=Range("E4:E20"), _ Type:=xlFillDefault End With 'Добавление комментария myRange.AddComment "Числа Фибоначчи - это ..." .Comments(1).Visible = False If (.Comments(1).Author = "Vladimir Billig") Then Debug.Print "OK!" End If 'Показ и удаление комментария .Comments(1).Visible = True '.Comments(1).Delete End With
    End Sub
    В этом примере я вначале программно формирую последовательность чисел Фибоначчи, а затем к ячейке, задающей заголовок, добавляю комментарий, поясняющий сущность этих чисел. Заметьте, в Excel в отличие от Word коллекция комментариев не имеет метода Add, - они вводятся специальным методом AddComment объекта Range. Они по-другому показываются, используя свойство Visible, что, пожалуй, более естественно. Заметьте также, что свойство Author можно использовать только для чтения.
  • QueryTables - возвращает одноименную коллекцию с элементами класса QueryTable, каждый из которых представляет таблицу, полученную на основе запроса к внешнему источнику данных. Внешним источником данных может быть база данных, Web-страницы в Интернет. Я уже вскользь упоминал о построении Web-запроса, когда рассматривал внешние ссылки при описании объекта Application. Следует сказать, что, несмотря на то, что объект QueryTable присутствовал и в предыдущей версии Office 97, в нынешней версии этот объект претерпел существенные изменения, и у него появилось множество новых свойств, что, в первую очередь, связано с возможностью построения Web-запросов. Об этом объекте я еще скажу более подробно чуть позже.
  • Hyperlinks - возвращает одноименную коллекцию с элементами класса Hyperlink, - гиперссылками, задающими связи (переходы) ячеек рабочего листа с внешним миром. В качестве гиперссылки может, например, использоваться URL-адрес в Internet. Объекты Hyperlink относятся к группе схожих объектов, встречающихся во всех приложениях Office 2000.
  • Outline - возвращает одноименный объект, задающий структурированное представление рабочего листа. Зачастую данные, представленные на рабочем листе можно структурировать, сжимая или разворачивая их по мере необходимости. Типичной является ситуация, когда данные, отражающие работу некоторого предприятия, представлены по дням, неделям, месяцам, кварталам и годам. При глобальном анализе деятельности предприятия нас могут интересовать только сводные результаты за каждый год, в этом случае нижние уровни структуры будут свернуты, но при необходимости их всегда можно развернуть вплоть до ежедневного анализа. Поскольку таблица двумерная, то возможны два направления свертки данных. Так, например, второе направление может отражать структуру предприятия: цеха, участки, группы, отдельного работника. Метод ShowLevels(RowLevels, ColumnLevels) объекта Outline позволяет показать структуру рабочего листа, где уровни детализации по строкам и столбцам задают параметры метода. Чаще всего, для проведения подобного анализа целесообразнее использовать сводные таблицы - объект PivotTablle.
  • AutoFiter - возвращает одноименный объект, позволяющий производить фильтрацию данных в специального рода Excel-запросах. О фильтрах и их использовании я расскажу подробно и приведу соответствующие примеры.
  • Свойства Next и Previous возвращают следующую и предыдущую страницу рабочей книги.
  • Коллекции HPageBreaks и VpageBreaks, возвращаемые при вызове одноименных свойств, используются для того, чтобы разбить нужным образом электронную таблицу на страницы, задавая горизонтальное и вертикальное разбиение. Это бывает важно при формировании отчетов и вывода результатов на печать. О них уже шла речь, при рассмотрении коллекции WorkSheets.
  • Объект PageSetup позволяет установить параметры страницы при выводе на печать.

  • Подводя итоги, заметим, что рабочий лист помимо того, что он представляет электронную таблицу ячеек, может содержать и другие элементы: диаграммы, рисунки, OLE -объекты. В нем могут быть также расположены сводные таблицы и таблицы, построенные на основе запросов к внешним источникам данных. Некоторые из ячеек рабочего листа снабжаются комментариями и имеют ссылки на внешние адреса. Ячейки и области данных могут иметь имена. Наконец, данные разрешается свернуть и отобразить структуру такого листа с нужной степенью подробности.

    Терминальные и нетерминальные свойства объекта Range

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

    Таблица 3.9. Свойства объекта RangeСвойствоОписание
    Address, AddressLocalsВозвращает строку, задающую ссылку на Range объект. Во втором случае это ссылка в языке пользователя. Эту ссылку можно выдавать в формате A1 или R1C1, как абсолютную или относительную. Вид возвращаемого значения определяют параметры этого свойства (метода).
    AreasПрименимо обычно к объекту Selection и возвращает коллекцию объектов Range в случае, когда Selection (Range) задает несвязную область. Возвращается сам объект Range, если область содержит только один объект.
    BordersВозвращает коллекцию из четырех границ объекта Range. Позволяет выделить цветом и (или) толщиной линии границы объекта.
    Text, CharactersСвойство Text возвращает строку текста, связанного с Range объектом (ячейкой). Имеет статус только для чтения. Если нужно изменить весь текст или его часть, то можно использовать свойство (метод) Characters, два параметра которого: Start и Length позволяют выделить требуемую подстроку текста.
    Column, RowВозвращают соответственно номер первого столбца или первой строки в области объекта Range.
    FontВозвращает объект Font, используемый при написании текста в области объекта Range.
    FormatConditionsВозвращает коллекцию условных форматов, содержащую не более трех элементов - объектов класса
    FormatCondition.Объект Range может иметь до трех условных форматов, выбор каждого из которых зависит от выполнения условия форматирования. Условие определяется параметрами объекта FormatCondition - оператором условия и константой, которая сравнивается со значением выражения, заданного объектом Range. В простейшем случае, когда объект Range задает ячейку, то значение в ячейке сравнивается с заданной константой. Метод Add коллекции позволяет задать новое условие форматирования. Методы Modify и Delete объекта FormatCondition позволяют модифицировать или удалять существующий формат. Параметры формата задаются с использованием объектов Borders, Font и Interior, возвращаемых свойствами объекта FormatCondition.
    Formula, FormulaR1C1, FormulaArray, FormulaLocal, FormulaHidden, FormulaLabel, FormulaR1C1LocalПервое из них позволяет прочесть или задать формулу в формате A1, второе - в формате R1C1, третье -формулу над массивами. Остальные также так или иначе связаны с заданием формул.
    LockedВозвращает значение True, если объект закрыт для модификаций и False, если модификация данного объекта возможно, хотя рабочий лист защищен. Возвращается Null, есди в области объекта Range существуют закрытые и открытые ячейки.
    OffsetОб этом свойстве, возвращающем объект Range, у уже подробно рассказывал.
    StyleСвойство имеет статус "только для чтения" - возвращает объект Style, характерный для объекта Range.
    ValueЗначение указанной ячейки. Если она пуста, то возвращается значение Empty, что можно проверить, вызвав функцию IsEmpty. Если объект Range содержит более одной ячейки, то возвращается массив значений, что можно проверить, вызвав функцию IsArray. Функции IsNumber, IsText позволяют определить тип значения, хранимого в ячейке.


    Терминальные свойства объекта Chart

    Основные терминальные свойства сведены в таблицу.

    Таблица 3.8. Терминальные свойства объекта ChartСвойствоОписание
    ChartTypeПозволяет прочесть или задать тип и формат стандартной диаграммы. Возможные значения задаются константами, которых около сотни. Напомним, что с каждым из 14 стандартных типов связано до 10 форматов.
    AutoScalingБулево свойство, имеющее значение True, когда трехмерная диаграмма автоматически масштабируется так, чтобы совпадать по размеру с двумерной. Свойство RightAngleAxes должно также иметь значение True.
    BarShapeМы ранее говорили, что двумерные гистограммы изображаются в виде прямоугольников. Для изображения трехмерных гистограмм обычно используются параллелепипеды, но можно применять и другие геометрические фигуры. Свойство BarShape задает вид используемой фигуры. Оно имеет следующие значения: xlBox, xlConeToMax, xlConeToPoint, xlCylinder, xlPyramidToMax, или xlPyramidToPoint. Использовать это свойство вряд ли стоит. Все эти фигуры- "изыски от лукавого".
    DepthPercent, HeightPercentСвойства применимы только к трехмерным диаграммам. Позволяют установить глубину и высоту диаграммы в процентах относительно ее ширины.
    DisplayBlanksAsУстанавливает способ интерпретации данных при встрече с пустой ячейкой. Следующие константы: xlNotPlotted, xlInterpolated, или xlZero задают три возможные стратегии- игнорировать ячейку, провести интерполяцию или считать нулем.
    Elevation, Rotation, Perspective, RightAngleAxesМожно попытаться повысить наглядность изображения диаграммы. Свойство Elevation задает возвышение (в градусах) точки, с которой Вы смотрите на диаграмму. Rotation задает поворот вокруг оси Z, а Perspective - перспективу. Булево свойство RightAngleAxes задает "угол зрения".
    GapDepthЗадает в трехмерной диаграмме расстояние между рядами данных. Значение может быть в интервале от 0 до 500.
    HasAxis, HasDataTable, HasLegend, HasTitleБулевы свойства, показывающие, какие элементы диаграммы присутствуют в ней.
    PlotByИмеет два значения: xlColumns и xlRows, указывающие столбцы или строки следует использовать как ряды данных.
    PlotVisibleOnlyБулево свойство, имеющее значение True, если отображаются только данные из видимых ячеек. В противном случае диаграмма отображает все данные, включая скрытые ячейки.
    ProtectContents, ProtectData, ProtectDrawingObjects, ProtectFormatting, ProtectGoalSeek, ProtectionMode, ProtectSelectionБулевы свойства, позволяющие установить защиту соответствующих элементов диаграммы. Часть из них имеет статус "только для чтения".
    ShowWindowБулево свойство, применяемое только к встроенным диаграммам. Имеет значение True, если диаграмма отображается в отдельном окне.
    VisibleНапомним, имеет три значения: True, False и xlVeryHidden.


    Терминальные свойства объекта Workbook

    Терминальных свойств, как обычно, множество. Они проще, чем свойства, задаваемые объектами. Среди них достаточно много булевых свойств, позволяющих включать или отключать то или иное свойство рабочей книги. Я приведу сводку некоторых из этих свойств, позволяющую получить общее представление о том, как можно управлять характеристиками рабочей книги Excel с помощью этих свойств.

    Таблица 3.6. Терминальные свойства объекта WorkbookТерминальные свойстваНазначение свойстваНовые терминальные свойства объекта Workbook в Excel 2000
    AcceptLabelsInFormulaБулево свойство со значением True, если метки могут использоваться в формулах рабочего листа. По умолчанию - True.
    HasRoutingSlipБулево свойство со значением True, если книга может быть направлена по сети другим участникам разработки документа.
    RoutedБулево свойство со значением True, если документ был направлен следующему участнику разработки.
    MultiUserEditingБулево свойство со значением True, если книга открыта для разделяемого доступа.
    AutoUpdateFrequencyЗадает частоту (в минутах), с которой сделанные изменения передаются участникам разделяемого доступа. Если свойство имеет значение 0, то книга будет пересылаться только в момент ее сохранения
    AutoUpdateSaveChangesБулево свойство со значением True, если сделанные изменения автоматически пересылаются всем участникам разработки. Предыдущий параметр должен иметь значение в пределах от 5 до 1440, чтобы это свойство оказало эффект.
    ListChangesOnNewSheetБулево свойство со значением True, если сделанные изменения показываются на отдельной странице при разделенном доступе
    KeepChangeHistoryБулево свойство со значением True, если при разделенном доступе сохраняется история сделанных изменений.
    ChangeHistoryDurationУстанавливает число дней, в течение которых сохраняются изменения в их истории. Старые изменения, срок которых превышает заданную установку, из истории удаляются
    CreateBackupБулево свойство со значением True, если при сохранении книги создается ее резервная копия.
    CodeNameРабочие книги, листы и другие объекты Excel имеют два имени - собственное и кодовое. В момент создания объекта они совпадают, например "Лист1", но затем каждое из них может быть независимо изменено. Кодовое имя может быть изменено только вручную в окне свойств. Программно оно может быть использовано только для чтения. Важно то, что кодовое имя можно использовать для непосредственного именования объекта, что сокращает цепочку вызовов. Так непосредственно можно обратиться к объекту Лист1.Range(myRange)
    FullNameПолное имя рабочей книги, заданное в виде строки, включающее путь к файлу, хранящему книгу. Имеет статус "только для чтения".
    FileFormatСвойство имеет статус "только для чтения" и возвращает константу, задающую формат файла и/или тип рабочей книги
    HasPasswordБулево свойство со значением True, если документ имеет пароль защиты
    SavedБулево свойство со значением True, если не делалось никаких изменений с момента последнего сохранения документа.
    WriteReservedБулево свойство со значением True, если документ закрыт для записи.
    EnvelopeVisibleНовое в Excel 2000 терминальное булево свойство, при включении которого появляется панель для отправки электронной почты и заголовок отправляемого сообщения.
    VBASignedНовое в Excel 2000 терминальное булево свойство, которое показывает, имеет ли программный проект данной книги цифровую подпись. Свойство имеет статус "только для чтения".


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

    Public Sub AllNames() 'Печать имен документа With ThisWorkbook Debug.Print "Свойство Name - ", .Name Debug.Print "Свойство CodeName - ", .CodeName Debug.Print "Свойство FullName - ", .FullName Debug.Print "Свойство Path - ", .Path End With End Sub

    Вот как выглядят результаты в окне отладки, полученные в результате работы этой процедуры:

    Свойство Name - BookOne.xls Свойство CodeName - ЭтаКнига Свойство FullName - E:\O2000\DsCd\Ch11\BookOne.xls Свойство Path - E:\O2000\DsCd\Ch11

    Следующая процедура позволяет по желанию пользователя включить или отключить панель для отсылки почтового сообщения, используя новое свойство EnvelopeVisible:

    Public Sub EnvelopeOn() 'Включение и выключение панели и заголовка почтового сообщения Dim Answer As Long Answer = MsgBox("Включить панель отправки почтового сообщения?", _ vbYesNo) If Answer = vbYes Then ThisWorkbook.EnvelopeVisible = True Else ThisWorkbook.EnvelopeVisible = False End If End Sub

    Терминальные свойства объекта WorkSheet

    Перейдем теперь к рассмотрению основных терминальных свойств объекта Worksheet. Представим их, как обычно, таблицей:

    Таблица 3.7. Терминальные свойства объекта WorkSheetТерминальные свойстваНазначение свойства
    CodeName, Name, IndexСвойство CodeName имеет статус только для чтения и позволяет установить кодовое имя рабочего листа. Мы уже говорили, что рабочие книги, рабочие листы и листы диаграмм наряду с именем имеют и кодовое имя. Свойство Name позволяет задать или изменить имя рабочего листа. Это свойство, также как и свойство Index, имеют многие объекты. Index позволяет по имени объекта получить его порядковый номер в коллекции.
    ConsolidationFunction, ConsolidationOptions, ConsolidationSourcesExcel имеет разные способы агрегирования данных. Мы уже говорили об объекте Outline, позволяющем структурировать данные, представляя их с разной степенью детализации. Другим средством являются сводные таблицы. Еще одну возможность объединения данных дает их консолидация. Как правило, консолидируются однотипные данные, построенные, например, на основе единого шаблона. Можно, например, консолидировать данные, представляющие результаты работы однотипных подразделений. Что реально скрывается за термином "консолидация" определяет функция консолидации - чаще всего это функция Sum, проводящая обычное суммирование. Но это может быть и нахождение среднего или минимального (максимального) значения.
    Свойство ConsolidationFunction предназначенное только для чтения возвращает константу, задающую код функции консолидации: xlAverage, xlCount, xlCountNums, xlMax, xlMin, xlProduct, xlStDev, xlStDevP, xlSum, xlVar, или xlVarP.
    Свойство ConsolidationSources возвращает массив строк, содержащий имена листов, служивших источниками для консолидации данных.
    Свойство ConsolidationOptions возвращает трехэлементный массив булевых переменных, каждая из которых имеет значение True, если одна из трех соответствующих опций установлена. Об опциях и некоторых подробностях консолидации мы еще поговорим при рассмотрении метода Consolidate, которым обладает объект Range.
    ProtectContents, ProtectDrawingObjects, ProtectionMode, ProtectScenariosДля защиты книги или ее листа от случайных изменений используется метод Protect. В момент его вызова можно установить, какие именно элементы будут защищены.
    Имеющие статус " только для чтения" булевы свойства: ProtectContents, ProtectDrawingObjects и ProtectScenarios имеют значение True, если соответственно защищены такие элементы рабочего листа, как ячейки, графические объекты, сценарии. Если свойство ProtectionMode имеет значение True, то это означает, что макросы, представляющие часть пользовательского интерфейса, доступны для изменений. Для того чтобы это свойство было включено, необходимо, чтобы при вызове метода Protect его параметр UserInterfaceOnly был задан и получил значение True.
    EnableAutoFilter, EnableCalculation, EnableOutlining, EnablePivotTable, EnableSelectionБулевы свойства Enable включают или выключают ту или иную опцию.
    EnableCalculation включает автоматическое вычисление формул, всякий раз, когда возникает необходимость, например, изменились данные.
    EnableOutlining включает показ символов структуризации на защищенном листе при условии включения параметра UserInterfaceOnly.
    EnablePivotTable как и предыдущий параметр включает элементы управления сводной таблицей на защищенном листе.
    EnableAutoFilter включает стрелки автофильтрации на защищенном листе.
    EnableSelection не является булевым параметром. В его задачу входит включить или выключить доступ к тем или иным ячейкам защищенного листа. Он имеет три возможных значения: xlNoRestrictions, xlNoSelection, or xlUnlockedCells. В первом случае для выбора доступны все ячейки, во втором - ни одна, в третьем - только "открытые" ячейки, у которых свойство Locked имеет значение False.
    ScrollAreaВозвращает или устанавливает строку, задающую область рабочего листа, где разрешена прокрутка. Ячейки, расположенные вне этого диапазона не могут быть выбраны. Вот пример: Public Sub StrictScrolling() 'Ограничение области скроллинга! Dim S As String S = "A1:G20" With ThisWorkbook.Worksheets(2) 'Установка области прокрутки .ScrollArea = S '.ScrollArea = "" 'Попытка выделить ячейку вне области .Range("B21").Select Selection.Value = 77 End With End Sub
    Заметьте, программно мы смогли выделить и записать значение в ячейку B21, находящуюся вне области прокрутки, но вручную ни эта ячейка, ни другие не будут доступны. В дальнейшем можно переопределить область прокрутки и, чтобы сделать доступными все ячейки, нужно задать пустую строку в качестве значения ScrollArea.
    Замечу, что в отличие от предыдущей версии значение ScrollArea теперь можно задавать не только константой, но и переменной, как в нашем примере.
    StandardHeight, StandardWidthПервое из этих свойств возвращает стандартную (по умолчанию) высоту всех строк, второе - ширину всех столбцов. Разница между ними состоит в том, что первое имеет статус "только для чтения". Вы можете задать ширину всех столбцов, принимаемую по умолчанию. Для всех строк задать высоту нельзя, но это можно делать для каждой отдельно взятой строки, используя ее свойство - RowHeight.
    TransitionExpEval, TransitionFormEntryЭти булевы свойства обеспечивают совместимость с Lotus 1-2-3. Если они имеют значение True, то используются правила вычислений выражений и формулы Lotus 1-2-3.
    VisibleВ общем случае булево свойство, имеющее значение True, если объект виден. Но для рабочего листа имеется третье значение - xlSheetVeryHidden. В этом случае пользователь не сумеет вручную добраться до "очень" скрытого листа. Это можно будет сделать только программно, изменив его значение на True или False.


    Терминальные свойства

    Терминальных свойств много, и понятно почему. Приложение Excel, как и другие приложения Office 2000, могут быть настроены пользователем по своему усмотрению. Эту настройку можно выполнять вручную, а можно и программно. Настройка вручную большей частью проводится из меню Сервис | Параметры, используя возможности, предоставляемые различными вкладками в открывающемся окне параметров. Для программной настройки используются терминальные свойства, - в этом их основное назначение. Естественно, я не буду останавливаться на всех свойствах, - они просты. В ниже приведенном обзоре представлено выборочное описание некоторых групп терминальных свойств:
  • Группа свойств, задающих свойства приложения по умолчанию, - DefaultFilePath, DefaultSaveFormat, DefaultSheetDirection, - путь по умолчанию, формат по умолчанию, направление просмотра текста (слева направо или справа налево), задаваемое для некоторых языков. К этим же свойствам примыкает и ранее упоминавшееся свойство DefaultWebOptions.
  • Группа булевых свойств, позволяющих включить или выключить отображение на экране тех или иных элементов приложения - DisplayAlerts, DisplayCommentIndicator, DisplayFormulaBar, DisplayStatusBar и другие Display-свойства. Первое из этих свойств позволяет управлять выдачей на экран некоторых сообщений в процессе работы макросов, второе - отображать специальный индикатор при показе комментариев. Более часто приходится использовать управление показом панелей формул и статуса. Особенно часто приходится использовать эти свойства, когда документ Excel используется в специальных целях, например, при отображении различных бланков, когда внешний вид документа ничем не напоминает привычную электронную таблицу. Замечу, что используемое в этих случаях свойство DisplayGridLines, позволяющее отключать сетку, принадлежит объекту Windows, а не объекту Application.
  • Группа булевых свойств, позволяющих включить или выключить те или иные свойства - EnableAnimations, EnableAutoComplete, EnableCancelKey, EnableEvents, EnableSound. Первое из этих свойств позволяет управлять анимацией при добавлении или удалении строк и столбцов рабочего листа, второе - автозаполнением ячеек таблицы. Свойство EnableCancelKey не является булевым, оно принимает значения, заданные соответствующим перечислением, и позволяет управлять процессом прерывания программы при нажатии комбинации клавиш Ctrl+Break. Значение xlInterrupt, принятое по умолчанию, позволяет прервать выполнение макроса и перейти в режим отладки, где возможно пошаговое выполнение. Однако с помощью этого свойства можно задать разные режимы, как, например, передачу управления обработчику ошибок в момент прерывания. Пользоваться этим свойством следует осторожно, поскольку при зацикливании может возникнуть ситуация, когда нельзя будет прервать программу, не применяя грубых способов. Свойство EnableEvents позвол яет управлять включением событий объекта Application, а свойство EnableSound управляет включением звука в процессе работы приложений Office 2000.
  • Группа свойств, управляющих размерами главного окна приложения Excel - Height, Width, Left, Top, задающие высоту, ширину окна и координаты верхнего левого угла окна.
  • Многие другие свойства, позволяющие управлять курсором, скроллингом, характеристиками пользователя и многими другими параметрами так или иначе, характеризующими приложение Excel.


  • Внешние ссылки, Web-запросы и событие Change

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

  • А теперь, в подтверждение сказанного приведу некоторые примеры:
  • Хочу обратить Ваше внимание на то, что при написании обработчика события Change объекта Application в качестве журнала изменений я выбрал список, помещенный в пользовательскую форму. Скажу честно, вначале я попытался использовать для этой цели отдельную страницу в одной из книг Excel, но эта попытка оказалась безуспешной и привела к зацикливанию. Дело объясняется тем, что, как Вы понимаете, не только действия пользователя приводят к возникновению события Change. Когда обработчик этого события записывает информацию о возникшем событии в журнал изменений - на одну из страниц рабочей книги, то автоматически возникает событие Change, обработка которого приведет к возникновению нового события и так далее до бесконечности. Этот пример подтверждает сделанный вывод - не только действия пользователя по изменению значений в ячейках рабочей книги приводят к возникновению события Change, - но и программное изменение состояния ячеек, в том числе производимое самой системой, также вызывает событие Change. Замечу в заключение, что вместо формы, для журнала изменений можно было бы выбрать документ Word, таблицу Access, файл, но только не страницу рабочей книги. Обратите также внимание, что для обработчика события Change, связанного с рабочей страницей - объектом WorkSheet, для журнала изменений можно использовать рабочую страницу, но, учтите, только страницу, не заданную данным объектом WorkSheet.
  • А теперь рассмотрим пример изменений значений в ячейках рабочей страницы, не приводящих к возникновению события Change. В книге BookThree в ячейках G1 и H1 на странице Лист1, для которой, как Вы помните, предусмотрен свой обработчик события Change, я записал соответственно формулы: = [BookOne.xls]Лист1!A1 = Лист2!A1 + Лист3!A2

    "Знакомые" методы

    Вначале рассмотрим методы объекта Worksheet, действие которых так или иначе уже было описано. Это позволит нам избежать некоторых подробностей:
  • Activate - активизирует рабочий лист.
  • Delete - удаляет объект. Этот метод, также как и нижеследующие методы Copy, Move, Select и другие рассматривались при описании работы с коллекцией листов. Конечно, именно при работе с отдельным листом эти методы чаще всего применяются.
  • Copy - имеет два варианта. В первом варианте используется без параметров, копируя содержимое рабочего листа в буфер. Во втором варианте Copy(Before, After) создает копию листа, помещая ее перед или после листа, вызвавшего метод. Позиция вставляемого листа задается параметрами метода. Понятно, что только один из двух параметров - Before или After - может быть задан.
  • Move(Before, After) - перемещает рабочий лист, изменяя порядок следования листов в рабочей книге.
  • Paste(Destination, Link) -помещает содержимое буфера на рабочий лист. Возможный параметр Destination указывается только тогда, когда содержимое буфера представляет некоторый диапазон. В этом случае параметр задается объектом Range, определяющим диапазон, в который будет помещено содержимое буфера. Второй возможный булев параметр Link получает значение True в случае, когда устанавливается связь с источником данных. По умолчанию имеет значение False. Одновременно можно задать только один из этих параметров.
  • PasteSpecial(Format, Link, DisplayAsIcon, IconFileName, IconIndex, IconLabel) - также помещает содержимое буфера в область выделения рабочего листа. Разница состоит в том, что метод применяется тогда, когда содержимое буфера хранится в специальном формате, отличном от формата Excel. Чаще всего метод применяется для размещения объектов других приложений. Параметр Format в виде строки задает формат объекта, хранящегося в буфере. Параметр Link имеет тот же смысл, что и в предыдущем случае. Остальная группа параметров используется тогда, когда объект "приклеивается" в виде значка. В этом случае параметр DisplayAsIcon имеет значение True, параметр IconFileName задает имя файла, содержащего значки, IconIndex - индекс значка в файле, IconLabel - текст, связанный со значком. Обратите внимание, что поскольку объект или значок помещается в область выделения, то предварительно такая выделенная область

    Xі должна быть установлена. Рассмотрим пример помещения текста, взятого из буфера, в ячейку нашей тестовой книги BookOne. Сам текст был создан в приложении Word, - я поместил в буфер начало этого абзаца.
  • Public Sub PasteTextFromWord() 'В приложении Word некоторый текст документа 'был сохранен в буфере. В данной процедуре 'текст из буфера помещается в ячейку Excel. With ThisWorkbook.Worksheets(2) 'Установка области выделения .Range("B25").Select .PasteSpecial Format:="Microsoft Word 9.0 Document Object" .Range("B35").Select .PasteSpecial Format:="Microsoft Word 9.0 Document Object", _ DisplayAsIcon:=True End With End Sub

    Вот как выглядит рабочий лист Excel после вставки из буфера текста, скопированного в приложении Word.


    Рис. 3.6.  Рабочий лист Excel с текстом документа Word при копировании из буфера

    Продолжим рассмотрение методов:

  • Select(Replace) - создает объект Selection. Возможный булев параметр Replace имеет значение True, если новый объект заменяет ранее существовавшее выделение, и False, когда происходит расширение области выделения, так, чтобы она охватывала и новый объект.
  • SaveAs - сохраняет изменения, сделанные на рабочем листе в отдельном файле. Мы не будем перечислять все возможные параметры этого метода.
  • PrintPreview и PrintOut - позволяют осуществит предварительный просмотр и печать содержимого рабочей книги
  • CheckSpelling - позволяет проверить правописание содержимого рабочего листа. Имеет ряд возможных параметров, позволяющих задать ряд опций, например, словарь, предоставленный пользователем.
  • Protect и Unprotect - позволяют защитить лист от несанкционированных изменений и снять эту защиту. Первый из методов имеет ряд параметров- Protect(Password, DrawingObjects, Contents, Scenarios, UserIntefaceOnly). Параметр Password задает пароль, а остальные являются булевыми и позволяют включить или отключить защиту той или иной части листа. Значение True у последнего параметра говорит о том, что на защищенном листе пользовательский интерфейс будет защищен, а макросы могут изменяться. Если этот параметр опущен, то и макросы будут защищены. По умолчанию защищены ячейки листа (Contents) и сценарии, но не графические объекты.


  • Мир объектов Excel 2000

    База данных офиса "Родная Речь"

    В качестве тестовой базы данных я рассмотрю базу данных гипотетического офиса "Родная Речь", который в дальнейшем буду называть офисом РР. Офис РР занимается издательской деятельностью и имеет отделы (группы), которые непосредственно готовят и издают книги, занимаются маркетингом и рекламой, распространением и сбытом книг. С издательством сотрудничают авторы, переводчики, книготоргующие организации.

    Фильтрация записей

    Под фильтрацией понимается выделение из таблицы записей, удовлетворяющих условиям запроса. Фильтровать записи можно как вручную, так и программно. В Excel есть два метода фильтрации записей списка: AutoFilter и AdvancedFilter. Первый фильтрует записи на том же месте, где находится сам список. Критерии отбора записей можно задать только для одного поля. В результате его работы исходный список делается невидимым, показываются только отобранные фильтром записи. При работе вручную можно просмотреть эти записи, в случае необходимости скорректировать их или скопировать. При программировании чаще используется метод AdavncedFilter, позволяющий не только фильтровать записи на месте, но и копировать результаты в указанное место. Важнее, что он позволяет задавать довольно сложные условия отбора записей, накладываемые, при желании, на все поля списка. Рассмотрим подробнее работу этих методов. Синтаксис метода AutoFilter таков:
    expression.AutoFilter(Field, Criteria1, Operator, Criteria2, VisibleDropDown)
    Выражение Expression должно возвращать Range-объект, задающий список. Параметры метода имеют следующий смысл:
  • Field - задает порядковый номер поля списка, используемого для отбора записей; критерии отбора накладываются именно на это поле;
  • Criteria1 и Criteria2 - задают два возможных условия, накладываемых на поле. Если заданы оба эти параметра, то параметр Operator может принимать значение xlAnd или xlOr. Каждый из параметров представляет собой строку вида
    <знак операции отношения><значение>",
    где могут быть использованы все обычные знаки операции отношения: " >, >=, <, <=, =, <>". Если опущен знак операции, - подразумевается "равенство"; если опущено значение, а знак операции - "=" или "<>", условие означает проверку поля на пустоту. Если параметры Criteria1 и Criteria2 опущены, то никакие условия не накладываются и выбираются все записи. Чаще всего опускается один параметр - Criteria2. В этом случае могут быть заданы дополнительные условия выборки. Можно выбрать первые N записей, имеющих максимальные или минимальные значения. Параметр Critria1 указывает тогда число N, а значение параметра Operator указывает, задает ли N число записей или число процентов записей, максимальные или минимальные значения которых будут отбираться.
  • Operator - принимает одно из значений: xlAnd, xlOr, xlBottom10Items, xlTop10Items, xlBottom10Percent, xlTop10Percent; первые два используются, если заданы оба критерия, остальные задаются, если задан только первый критерий и он определяет не отношение, а специальные условия выборки записей. По умолчанию значение параметра Operator - xlAnd.
  • VisibleDropDown - булев параметр, значение True которого позволяет сделать видимым поле фильтрации. Для этого подсвечивается стрелка выпадающего списка, связанного с именем поля.
  • Если метод AutoFilter вызывается без параметров, он отключает результаты предыдущей фильтрации и список показывается полностью в обычном виде.

  • Я создал некоторый список и хочу поэкспериментировать с ним:
    Фильтрация записей

    увеличить изображение
    Рис. 4.22.  Список до начала фильтрации его записей

    Я создал некоторый список и хочу поэкспериментировать с ним:

    Фильтрация записей

    увеличить изображение
    Рис. 4.22.  Список до начала фильтрации его записей

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

    Sub Автовыбор() 'Эксперименты с автофильтрацией Range("B1").Select 'Эксперимент 1. 'Выбор 3-х элементов с максимальными значениями 2-го поля. Selection.AutoFilter Field:=2, Criteria1:="3", _ Operator:=xlTop10Items, VisibleDropDown:=True

    'Эксперимент 2. 'Типичное условие выбора с двумя критериями Selection.AutoFilter Selection.AutoFilter Field:=2, Criteria1:="<12", _ Operator:=xlOr, Criteria2:=">30", VisibleDropDown:=False

    'Эксперимент 3. 'Условие выбора эквивалентно True. Выбираются все записи. Selection.AutoFilter Selection.AutoFilter Field:=2, Criteria1:="<>12", _ Operator:=xlOr, Criteria2:="<>35", VisibleDropDown:=True

    'Эксперимент 4. 'Будут выбраны 5 записей из списка Selection.AutoFilter Range("B1").Select Selection.AutoFilter Field:=2, Criteria1:="12", _ Operator:=xlOr, Criteria2:=">30" End Sub

    Процедура подробно прокомментирована и, по-видимому, не нуждается в особых пояснениях. Взгляните на заключительные результаты ее работы:

    Фильтрация записей

    увеличить изображение
    Рис. 4.23.  Результаты фильтрации списка

    Обратите внимание, в результате последней фильтрации из списка выбраны 5 записей, имеющие номера: 2, 3, 4, 7, 8. Только эти записи отображаются на рабочей странице, остальные записи недоступны, пока фильтр остается включенным.

    Я хочу теперь рассказать еще об одном объекте, связанном с автофильтрацией. Напомню, что каждый список следует располагать на отдельном листе, и методы фильтрации могут быть применены только к единственному списку на листе. В терминах объектов это означает, что объект Worksheet имеет свойство AutoFilter, возвращающее одноименный объект. Объект AutoFilter хранит информацию о фильтрах, применяемых к списку. Главное свойство этого объекта - Filters возвращает одноименную коллекцию, элементы которой являются объектами класса Filter. Число элементов в коллекции совпадает с числом полей списка. Каждый из объектов Filter сохраняет параметры фильтра, применяемого к соответствующему столбцу списка. Я покажу на примере, какими свойствами обладают эти объекты. Кроме свойства Filters можно еще отметить и свойство Range объекта AutoFilter, возвращающее, по существу, область, занятую списком.

    Вот пример работы с объектом AutoFilter:

    Public Sub FilterAuto() 'Работа с объектом AutoFilter и результатами фильтрации Dim Myf As AutoFilter, filt As Filter 'Определить объект AutoFilter, связанный со страницей Set Myf = ThisWorkbook.Worksheets("Лист2").AutoFilter Debug.Print "Число фильтров = ", Myf.Filters.Count For Each filt In Myf.Filters Debug.Print "Включен = ", filt.On If filt.On Then Debug.Print "Фильтр:", filt.Criteria1, _ filt.Operator, filt.Criteria2 End If Next filt


    End Sub

    Вот результаты отладочной печати:

    Число фильтров = 4 Включен = False Включен = True Фильтр: =12 2 >30 Включен = False Включен = False

    Как видите, булево свойство On объекта AutoFilter позволяет определить, включен ли фильтр. Свойства Criteria1, Criteria2 и Operator возвращают одноименные параметры фильтра.

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

    Public Sub FilterRes() 'Работа с результатами фильтрации Dim Myf As AutoFilter Dim Myr As Range, Myr1 As Range Dim MyrBeg As Range, MyrEnd As Range Dim cel As Range 'Определить объект AutoFilter, связанный со страницей Set Myf = ThisWorkbook.Worksheets("Лист2").AutoFilter 'Область списка Set Myr = Myf.Range 'Отрезаю строку заголовков списка Set MyrBeg = Myr.Offset(1, 0).Cells(1, 1) Set MyrEnd = Myr.Cells(Myr.Rows.Count, Myr.Columns.Count) Set Myr = Range(MyrBeg, MyrEnd) 'Выделяю область фильтрации Myr.Select Myr.Copy 'Получаю на другом листе результаты фильтрации Set Myr1 = Range("Лист3!F2") Myr1.PasteSpecial Set Myr1 = Myr1.CurrentRegion 'Объект Myr содержит все данные списка, 'Объект Myr1 содержит только отфильтрованные данные. Debug.Print "Число ячеек исходной области = ", Myr.Cells.Count Debug.Print "Число ячеек области фильтрации = ", Myr1.Cells.Count 'Обработка результатов фильтрации For Each cell In Myr1.Cells Debug.Print cell Next cell

    End Sub

    Цель этой программы показать, как можно получить результаты применения фильтра и программно работать с ними. Для этого я первым делом получаю в объекте Myr область, занятую списком, для чего использую метод Range объекта Autofilter. Затем я отрезаю строку, занятую заголовками списка, после чего копирую содержимое объекта Myr в буфер. Заметьте, хотя объект Myr содержит всю область списка, в буфер передается только видимая часть списка - результаты фильтрации. Так что, скопировав результаты из буфера в объект Myr1, я получаю результаты фильтрации и могу с ними работать, так как мне нужно, - для простоты я их просто печатаю. Обратите внимание, результаты я копирую на новый лист рабочей книги, иначе они будут восприниматься как список. Таким образом, в объекте Myr у меня хранятся результаты до фильтрации, а в объекте Myr1 - после фильтрации. Вот результаты отладочной печати:

    Число ячеек исходной области = 32 Число ячеек области фильтрации = 20 Anna 12 5 low Mary 12 7 low Anna 12 5 low Петр 35 18 middle Петр 37 18 high

    Импорт списков Excel в приложении Access

    Приложение Access позволяет импортировать внешние данные, и я воспользуюсь этой возможностью для переноса списков Excel. Я создал в Access новую, пока что пустую базу данных, дал ей имя "dbPPnew" и занялся выполнением операции импорта, выбрав из меню "Файл" команду "Внешние данные | Импорт…". В открывшемся окне Импорта я, как обычно, в окошке "тип файла" выбрал из большого раскрывающегося списка нужный мне тип - Microsoft Excel - затем в поле "Папка" выбрал нужную папку, выбрал файл с книгой Excel, содержащей базу данных и нажал кнопку "Импорт". В результате, Мастер Импорта начинает свою работу. Вот первое окно, которое открывает этот Мастер, предлагая импортировать рабочие листы или именованные диапазоны книги Excel:
    Импорт списков Excel в приложении Access

    Рис. 4.18.  Импорт списков Excel
    Обратите внимание, я предпочел работать с именованными диапазонами, а не с листами книги. Дело в том, что Мастер импорта не слишком интеллектуален и не может разобраться, где на рабочем листе начинается список Excel. Он предполагает, что заголовки полей списка начинаются в первой строке. Я же рабочий лист начинал с некоторого общего заголовка, и только потом уже размещал список. По этой причине, прежде чем заниматься импортом списков, я ввел именованные диапазоны для списков, назвав каждый диапазон по имени списка. Это позволит Мастеру Импорта разобраться с именами полей списка и сделать их именами полей таблицы Access, при условии, что на втором шаге работы Мастера будет включен флажок "Первая строка содержит заголовки столбцов". Я включил этот флажок, а на третьем шаге работы включил переключатель "В новой таблице", поскольку речь идет не о добавлении данных в существующую таблицу, а о создании новой таблицы. Вот как выглядит следующее окно Мастера Импорта, позволяющего уточнить характеристики полей таблицы:
    Импорт списков Excel в приложении Access

    Рис. 4.19.  Мастер Импорта уточняет характеристики полей таблицы
    На следующем шаге можно указать Мастеру, какое поле является ключевым или добавить в таблицу поле счетчика, которое и будет играть роль ключа. Единственная проблема возникает в случае составного ключа, - Мастеру можно задать лишь одно ключевое поле, остальную работу по уточнению состава ключа придется выполнить уже в Access. Наконец, на последнем шаге работы можно указать не только имя таблицы, но и включить два флажка, один из которых вызывает Мастера Анализа таблиц, который позволяет провести проверку эффективности (с точки зрения этого Мастера) качества проектирования таблицы и определить целесообразность ее возможного разбиения на несколько таблиц.
    Импорт списков Excel в приложении Access

    Рис. 4.20.  Последний шаг работы Мастера Импорта
    Я не стал вызывать Мастера Анализа таблиц, но надеюсь, что еще придет его время, и я расскажу подробнее о шагах его работы. Таблица "Книги" была успешно перенесена из Excel в Access. Аналогичным образом можно было бы импортировать и другие списки Excel, преобразуя их в таблицы базы данных Access. Но следующий список "Заказчики" я перенесу из Excel в Access, используя команду "Перенести в MS Access", которая появляется в меню Excel при включенной надстройке "AccessLinks".

    Изменение данных в списке

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

    Экспорт таблиц Access

    Если база данных в Access уже создана, то ее можно без труда экспортировать в Excel. Этим мы сейчас и займемся. Поскольку операции по экспорту можно выполнять по-разному, то я рассмотрю несколько способов. Начну с самого простого, основанного на классическом использовании буфера при переносе тех или иных данных из одного приложения Office 2000 в другое. Вот краткое описание моих действий по переносу таблицы "Сотрудники". Прежде напомню, что к моменту начала переноса таблицы у меня уже создана книга Excel, страницы которой будут хранить базу данных, и первая таблица этой базы была только что создана. Так что мне осталось сделать следующее:
  • Открыть БД офиса РР, сделанную на Access.
  • Открыть таблицу "Сотрудники" в режиме таблицы, выделить и скопировать в буфер ее содержимое.
  • Перейти на страницу "Сотрудники" рабочей книги Excel и выполнить операцию "Специальная вставка" из буфера, вставляя содержимое как текст в формате Unicode.
  • Отформатировать подходящим образом вставленную таблицу. Замечу, что для единообразия использовалось форматирование уже созданной таблицы "Книги".
  • Вся работа заняла несколько минут. Вот как выглядит таблица "Сотрудники" после ее переноса на соответствующий лист рабочей книги:

  • Экспорт таблиц Access

    Рис. 4.15.  Копирование таблицы "Сотрудники" из Access в Excel

    Метод AdvancedFilter

    Перейдем теперь к рассмотрению более полезного для программистов и в любом случае более универсального метода фильтрации записей - AdvancedFilter. Вот его синтаксис:
    Function AdvancedFilter(Action As XlFilterAction, [CriteriaRange], [CopyToRange], [Unique])
    Это метод вызывается объектами Range и возвращает объект Range, задающий список. Параметры метода имеют следующий смысл:
  • Action - может принимать одно из двух значений: xlFilterInPlace, xlFilterCopy.; первое из них указывает, что список фильтруется на месте, второе - задает возможность копирования результатов на новое место. О недостатках фильтрации на месте я уже говорил, поэтому при программировании целесообразно копировать значения, - это облегчает доступ к выбранным записям.
  • CriteriaRange - задает область, в которой можно записать логическую формулу, задающую условие выбора записей. Заметьте, что здесь формула позволяет задать условия, одновременно налагаемые на все поля записи. О том, что собой представляет область критериев и как строится формула, задающая условие, я скажу чуть ниже.
  • CopyToRange - задает область копирования результатов фильтрации.
  • Unique - булев параметр, значение True которого позволяет отобрать только один экземпляр записи в случае, когда она многократно встречается в списке. Если параметр имеет значение False, то выдаются все имеющиеся экземпляры записи.

  • Чтобы понять, как работает метод, нужно четко представлять, что собой представляет область критериев и как в ней формируется условие выбора. Этим мы сейчас и займемся. Для создания области критериев нужно выбрать любую свободную область листа и задать в ней имена полей. Заметьте, в этой области имя одного и того же поля может появиться дважды. На следующем шаге следует задать логическую формулу, накладывающую ограничения на поля выбираемых записей. Для тех, кто знаком с логикой, скажем, что эта формула записана в дизъюнктивной нормальной форме и представляет собой дизъюнкцию конъюнктов. Каждый конъюнкт записывается в отдельной строке под именами полей. Все члены в одной строке соединены знаком конъюнкции "And", а отдельные строки - знаком дизъюнкции "Or". Теперь попробуем сказать то же самое, но проще. Условия выбора можно задавать в нескольких строчках. Если есть две строки и условие в первой из них обозначить через F1, а во второй - F2 , то общее условие будет иметь вид F1 Or F2. Сами условия F1 и F2 могут быть достаточно сложными. Они объединяют знаком "And" элементарные условия, накладываемые на каждое поле в отдельности. Элементарные условия - это известные нам по методу AutoFilter отношения ">, <, = и т. д.". Поэтому в области критериев имена полей могут встречаться дважды, чтобы была возможность задать условие вида "(Поле1 > 10) And (Поле1<= 20)".
    Из сказанного следует, что расширенный фильтр позволяет формировать сложные условия отбора записей из списков. При этом я рассказал еще не обо всех его возможностях. Так, для текстовых полей можно задать образец, которому соответствуют множество записей. При этом действуют обычные в таких случаях правила:
  • Если в образце используется символ "?", то в записи ему соответствует любой символ; образцу "к?т" соответствуют, например, записи с полями "кит" и "кот".
  • Если в образце используется символ "*", то в записи ему соответствует любая последовательность символов. Так, образцу "*он" соответствуют записи "он", "кон" и "Наполеон".
  • Чтобы символы "?" и "*" могли использоваться как обычные, им должен предшествовать символ тильда "~"; образцу "Кто~?" соответствует строка "Кто?".
  • Любому образцу соответствуют все строки, чьим префиксом он является. Так, образцу "Петр" соответствуют строки: Петр, Петрушка, Петрович. Чтобы задать точное соответствие, условие сравнения нужно ввести в виде: ="=Петр".


  • Внимательно взгляните на рисунок, где показан слегка видоизмененный список из предыдущего примера. Здесь же показаны две области, отведенные под критерии, и результаты двух запросов, реализуемых двумя вызовами метода AdvancedFilter:

    Метод AdvancedFilter

    увеличить изображение
    Рис. 4.24.  Результаты работы расширенного фильтра

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

    Sub РасширенныйВыбор() 'Сложное условие фильтрации. Range("МойСписок").AdvancedFilter Action:=xlFilterCopy, _ CriteriaRange:=Range("F7:K14"), _ CopyToRange:=Range("A13:D13"), Unique:=True 'Здесь условие проще. Range("МойСписок").AdvancedFilter Action:=xlFilterCopy, _ CriteriaRange:=Range("F16:G21"), _ CopyToRange:=Range("I16:L16"), Unique:=False End Sub

    Первый вызов метода AdvancedFilter стоит обсудить подробнее. Метод вызывается объектом Range, задающим список. Как мы говорили ранее, список целесообразно именовать. В нашем примере его имя - "МойСписок". Результаты фильтрации копируются в область, заданную параметром CopyToRange. Размер этой области заранее не известен, но знать его и не нужно - достаточно указать диапазон для записи заголовков полей. Под строкой заголовков будут располагаться записи, выбранные при фильтрации. А вот область, отведенную для критериев, следует указать точно. При этом не следует ее именовать, как рекомендовано в документации. Во всяком случае, этого не стоит делать, если предполагается несколько различных запросов или они будут модифицироваться.

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

    ( (Поле3 >=5) And (Поле3 < 7) And (Поле4 = "low") ) OR ( (Поле1 = "Мария") And (Поле4 = "high") ) OR (Поле2 = 37) OR ( (Поле2 > 20) And (Поле3 < 20) And (Поле3 > 15) And (Поле4 ="middle") ) OR ( (Поле1 = "Анна") And (Поле4 = "high") ) OR ( (Поле1 = "Петр") And (Поле4 = "low") ) OR ( (Поле1 = "Алиса") And (Поле2 = 24) And (Поле4 = "low") )


    Формула состоит из 7 дизъюнктов, каждый из них независимо добавляет новые записи в результирующую выборку. Рассмотрим их:

  • первый - выделяет из списка две одинаковые записи со значением Anna в первом поле. Однако, поскольку параметр Unique имеет значение True, выбираться будет только одна уникальная запись.
  • второй дизъюнкт выделяет из списка запись с именем Мария, имеющей в 4-м поле значение high;
  • третий - самый простой: он должен выделить записи, имеющие в поле 2 значение 37; такая запись есть в списке, это запись с именем Петр (high);
  • четвертый дизъюнкт правильно выделяет запись со значением middle -это запись с именем Петр (middle);
  • пятый - выделяет запись с именем Анна (high);
  • шестой дизъюнкт выделяет две записи с именами Петр (low) и Петрович (low);
  • седьмой - выделяет еще одну запись.


  • В результате этого сложного запроса из списка выделяется 8 записей из 9. По сути, выбраны все записи без дублирования. Следующий запрос мы не будем рассматривать подробно. Заметьте лишь, что в запросе изменено значение параметра Unique, и потому оба экземпляра продублированной записи появятся в результирующей выборке.

    Перенос списков из Excel в Access

    Специальная надстройка AccessLinks добавляет в меню Excel команды, позволяющие преобразовать списки Excel в объекты базы данных Access - таблицы, формы, отчеты. Если надстройка AccessLinks еще не подключена, то это следует сделать, выбрав команду "Надстройки" из меню "Сервис" и включив флажок соответствующей надстройки. При включенной надстройке в меню "Данные" появляются три команды: MS Access Form, MS Access Report, Convert to MS Access. Первые две из них позволяют по данным списка Excel построить форму и отчет базы данных Access, я не буду на них останавливаться, поскольку по существу все построение осуществляют известные в Access Мастера построения форм и отчетов. Давайте чуть более подробно рассмотрим лишь третью команду, преобразующую список в таблицу базы данных. Первое окно, которое появляется после вызова этой команды, позволяет указать базу данных Access:
    Перенос списков из Excel в Access

    Рис. 4.21.  Первый шаг Мастера Преобразований списка Excel в таблицу Access
    А далее все возвращается на круги своя и работу продолжает уже знакомый нам Мастер Импорта, который и создает таблицу в базе данных Access. Замечу только, что поскольку работа начинается в Excel, то Excel способен распознать, где начинается список и передать точный список Мастеру Импорта без всяких пустых строк. Это, пожалуй, достаточно важная причина, по которой я рекомендовал бы перенос списков выполнять, используя именно этот способ работы. На этом я и закончу рассмотрение вопросов экспорта - импорта баз данных между Excel и Access.

    Построение форм "Заказчики" и "Книги Редакции"

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

    Рис. 4.3.  Форма "Заказчики"
    Для второй формы избран более "скромный", но более экономичный вид, позволяющий одновременно видеть на экране больше деталей:
    Построение форм

    Рис. 4.4.  Форма "Книги редакции"
    Вот еще одна форма, построенная по таблице "Заказчики", позволяющая просматривать адреса заказчиков:
    Построение форм

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

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

    Построение таблиц "Заказчики" и "Книги"

    Access относится к реляционным базам данных, в которых основной формой представления данных являются таблицы. Таблицы связываются между собой за счет общих полей. Поскольку таблица еще и общечеловеческая форма представления данных, то ее создание интуитивно понятно всем. Запись (строку) таблицы можно рассматривать, как совокупность полей разного типа. Каждый столбец таблицы хранит значения одного поля. Так что каждая таблица задает некоторый набор записей, и каждая запись представляется одной строкой таблицы. Число полей записи однозначно определяет число столбцов таблицы. Access предоставляет различные средства, облегчающие задание структуры таблицы. Полагаю, что даже человек, не имеющий опыта работы с Access, работая в Конструкторе таблиц Access, без труда определит собственную таблицу. Отмечу лишь, что для каждого поля задается имя, выбирается его тип, и задаются другие свойства поля, например, описание. Свойства поля зависят не только от его типа, они включают такие характеристики, как указание на то, является ли поле индексируемым, имеет ли уникальное значение, должно ли обязательно присутствовать в записи. Они определяют также формат и маску, которой должно удовлетворять значение поля, и другие характеристики.
    Я не буду сейчас останавливаться на средствах Access, позволяющих создавать таблицы визуально, они достаточно просты. Для нас сейчас важнее понимать структуру таблиц, входящих в состав базы данных. Начну с определения таблицы "Заказчики", хранящей информацию о заказчиках офиса РР. Каждая ее запись включает следующие поля: Код заказчика, Название, Адрес, Город, Телефон, Директор, Прочее, Email. Вот как выглядит определение таблицы в конструкторе Access, где для каждого поля задаются имя, тип, описание и другие характеристики:
    Построение таблиц

    Рис. 4.1.  Структура таблицы "Заказчики
    Обратите внимание, обязательным условием определения таблицы базы данных является задание ключа. Ключом может быть одно поле или совокупность полей, требуется лишь, чтобы значение ключа было уникальным для каждой записи. Это требование позволяет однозначно найти запись в таблице, зная ее ключ. Требование существования ключа записей всегда выполнимо, поскольку при необходимости есть возможность добавить к исходным полям записи поле счетчика, которое может выступать в роли ключа, автоматически давая записи новый номер при добавлении ее в таблицу. На рисунке 4.1 можно видеть (ключевые поля отмечены соответствующим значком -
    Построение таблиц
    ), что в таблице "Заказчики" в роли первичного ключа выступает поле "Название". Заметьте, ключ можно определить далеко не единственным способом, например, поле "Код заказчика" также могло играть роль первичного ключа.
    Определим теперь структуру таблицы "Книги". Эта таблица содержит информацию о книгах, выпускаемых издательством "РР". Достаточно взглянуть на рисунок, чтобы увидеть, какие поля в нее включены:
    Построение таблиц

    Рис. 4.2.  Структура таблицы "Книги"
    Две введенные таблицы "Заказчики" и "Книги" пока не связаны между собой, - они не имеют общих полей. Такая связь возникнет позже - при расширении базы данных. Она появится, когда заказчики начнут заказывать книги. Позже я добавлю в базу данных таблицу "Заказы", которая будет иметь общие поля, как с таблицей "Книги", так и с таблицей "Заказчики".

    Построение запросов

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

    Рис. 4.7.  Конструирование запроса "Список Заказчиков"
    А так выглядит результат его выполнения:
    Построение запросов

    Рис. 4.8.  Результат выполнения запроса
    Поскольку Access транслирует создаваемые запросы в SQL форму, то предоставляется возможность просматривать и корректировать запросы и в этой форме. Результат трансляции нашего запроса:
    SELECT Заказчики.Название FROM Заказчики ORDER BY Заказчики.Название;
    Приведу примеры еще нескольких запросов. Запрос "Список книг", построенный по таблице "Книги", позволяет для всех книг, данные о которых содержит таблица, создать список из двух полей, - автора и название книги. Список упорядочен по авторам. Приведу SQL-форму этого запроса:
    SELECT Книги.Автор, Книги.Название FROM Книги ORDER BY Книги.Автор;
    Два предыдущих запроса были совсем простые, - при отборе записей не проводилась их фильтрация, не накладывалось никаких ограничений на значения полей записи. Создадим теперь запрос с фильтром. В запросе "Заказчики в Твери" используются два поля таблицы "Заказчики", и в список отбираются только те записи, у которых код города равен "Тверь". При показе результатов код города не выводится:
    Построение запросов

    Рис. 4.9.  Конструирование запроса с фильтром
    SQL-форма этого запроса имеет вид:
    SELECT Заказчики.Название FROM Заказчики WHERE (((Заказчики.Город)="Тверь")) ORDER BY Заказчики.Название;

    Построенный только что запрос позволяет выбрать среди всех заказчиков тех, кто живет в Твери. Такой запрос является частным случаем общего запроса. Значительно чаще в подобных ситуациях строится запрос с параметром, в котором код города является параметром, задаваемым динамически в момент вызова запроса. Визуально запросы с параметрами строятся, как обычные. Чтобы предыдущий запрос превратить в запрос с параметрами, в поле запроса, где задавался город ("Тверь"), достаточно записать в квадратных скобках строку, запрашивающую имя параметра, например: [город?] или [Town]. Скобки - признак того, что задается параметр запроса, они сигнализируют о необходимости добавления элемента в коллекцию Parameters, связанную с запросом. Аналогично создаются запросы с несколькими параметрами.

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

    Построение запросов

    Рис. 4.10.  Запрос с двумя параметрами

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

    SELECT Заказчики.Название FROM Заказчики WHERE (((Заказчики.Город)=[город?]) AND ((Заказчики.Директор)=[директор?])) ORDER BY Заказчики.Название;

    Заметьте, хотя в явном виде предложение Parameters в операторе SELECT не отображается, но соответствующая коллекция Parameters, связанная с запросом формируется.

    Преобразование списков Excel в базу данных Access

    Только что мы рассмотрели различные способы экспорта-импорта таблиц базы данных Access в списки Excel. Пожалуй, чаще приходится выполнять обратную операцию - переноса существующей базы данных Excel в базу данных Access. Необходимость в этом может возникать, например, в тех случаях, когда база данных Excel разрослась настолько, что дальнейшая работа требует более мощного инструментария Access. Иногда списки Excel используются просто для пополнения таблиц существующей базы данных Access.
    Понятно, что обратное преобразование данных Excel в Access выполняется сложнее по той простой причине, что база данных Access устроена значительно сложнее, чем списки Excel. Поэтому нет многих возможностей, которые есть при переносе данных из Access в Excel. Нельзя, например, сохранить таблицу Excel в виде mdb-файла в формате базы данных Access. Тем не менее, существуют, по крайней мере, два способа переноса данных. Первый из них позволяет начать работу по переносу данных в Excel, используя его команду "Перенести в MS Access", являющейся аналогом рассмотренной выше команды Access - "Связи с Office - Анализ в MS Excel". Второй способ использует возможности Access по импорту внешних данных. Я рассмотрю сейчас оба эти способа и выполню обратный перенос созданной базы данных офиса РР в Excel в базу данных Access, создав под другим именем новую копию существующей базы данных.

    Сохранение в базе данных информации о заказах

    Если бы база данных офиса РР хранила данные только о выпускаемых книгах и заказчиках, то полезность ее была бы невысока. Расширим эту базу данных за счет того, что будем хранить в ней данные о заказах, которые делают заказчики на книги, выпускаемые редакцией. Это расширение сразу же придает некий содержательный характер нашему тестовому примеру. Прежде всего, таблицы в базе данных станут взаимосвязанными, как им и полагается быть в любой приличной базе данных. С другой стороны на такой базе данных можно демонстрировать решение ряда типичных офисных задач. Обработка заказов, хранящихся в базе данных, позволяет руководству офиса в дальнейшем принимать обоснованные решения, например, назначать срок и объем последующего тиража, определять объем средств, вкладываемых в рекламную компанию, оценивать деятельность дилеров, осуществляющих заказы. Я предполагаю в последующих главах продемонстрировать некоторые возможности по обработке заказов, а пока давайте сосредоточимся на том, как сохранить данные о заказе в ба
    зе данных.
    Каждый заказ, как правило, содержит требования на разные товары. В нашем случае это означает, что в одном заказе содержится заявка на книги разных авторов. В таких ситуациях целесообразно данные о заказе хранить не в одной, а в двух взаимосвязанных таблицах. Я назвал эти таблицы "Заказы" и "Заказано". В первой из них каждый заказ задается одной записью, хранящей общие сведения о заказе. Во второй таблице одному заказу соответствует несколько записей, - по одной записи на каждый заказываемый товар. Такой способ хранения информации в двух таблицах позволяет всю общую информацию о заказе сохранять лишь один раз. Есть целая наука о том, как правильно проектировать базы данных, но я сейчас не буду ее затрагивать, ограничившись лишь упоминанием о ней. Пока нам достаточно продолжить рассмотрение примера и определить структуру таблиц "Заказы" и "Заказано":
  • Заказы. Эта таблица имеет следующие поля: КодЗаказа, Заказчик, Сотрудник, ДатаЗаказа, Стоимость. Код заказа играет роль ключа и является уникальным полем, автоматически заполняемым в момент записи заказа в базу данных. Следующие два поля совпадают с соответствующими полями таблиц "Заказчики" и "Сотрудники" и, тем самым, связывают эти таблицы. Я неявно предполагаю, что база данных офиса РР уже частично расширена за счет естественного добавления таблицы "Сотрудники". Действительно, было бы странно хранить информацию о заказчиках и не иметь информации о собственных сотрудниках. Поэтому каждый заказ хранит информацию не только о заказчике, но и о сотруднике офиса РР, оформляющего этот заказ. Следующие два поля - ДатаЗаказа и Стоимость (общая стоимость заказа) не требуют особых пояснений. Так выглядит общая информация о заказе.
  • Заказано. Эта таблица имеет следующие поля: КодЗаказа, НазваниеКниги, Количество, Стоимость. Как я уже говорил, в этой таблице одному заказу будет соответствовать, как правило, несколько записей. Каждая запись - строка таблицы - содержит данные об одной из заказываемых книг. Все записи одного заказа будут иметь один и тот же код заказа, совпадающий с кодом заказа таблицы "Заказы". Поле "КодЗаказа" связывает между собой таблицы "Заказы" и "Заказано". Заметьте, повторяющийся код заказа не может быть ключом для таблицы "Заказано". Роль ключа в этой таблице играют два поля - КодЗаказа и НазваниеКниги, которые в совокупности являются уникальными для каждой записи таблицы "Заказано", хотя каждое из них в отдельности уникальным полем не является и может многократно повторяться в записях таблицы. Поля этой таблицы - Количество и Стоимость - задают количество экземпляров заказываемой книги и их суммарную стоимость.


  • Вот как выглядит определение этих таблиц в Конструкторе Access:

    Сохранение в базе данных информации о заказах

    Рис. 4.11.  Определение таблицы "Заказы" в конструкторе

    Сохранение в базе данных информации о заказах

    Рис. 4.12.  Определение таблицы "Заказано" в конструкторе

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

    Сохранение в базе данных информации о заказах

    Рис. 4.13.  Схема связей между таблицами базы данных офиса РР

    Заметьте, все связи между таблицами имеют тип "один ко многим". Так, например, одному коду заказа из таблицы "Заказы" соответствуют несколько записей с аналогичным кодом в таблице "Заказано". Аналогично, одной фамилии (полю ФИО) из таблицы "Сотрудники" соответствует несколько записей в таблице "Заказы", поскольку понятно, что один сотрудник может оформлять множество заказов.


    Вот как выглядит определение этих таблиц в Конструкторе Access:

    Сохранение в базе данных информации о заказах

    Рис. 4.11.  Определение таблицы "Заказы" в конструкторе

    Сохранение в базе данных информации о заказах

    Рис. 4.12.  Определение таблицы "Заказано" в конструкторе

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

    Сохранение в базе данных информации о заказах

    Рис. 4.13.  Схема связей между таблицами базы данных офиса РР

    Заметьте, все связи между таблицами имеют тип "один ко многим". Так, например, одному коду заказа из таблицы "Заказы" соответствуют несколько записей с аналогичным кодом в таблице "Заказано". Аналогично, одной фамилии (полю ФИО) из таблицы "Сотрудники" соответствует несколько записей в таблице "Заказы", поскольку понятно, что один сотрудник может оформлять множество заказов.

    Сортировка списков

    Мы уже рассмотрели вопросы создания списков Excel, представляющих локальную базу данных, рассмотрели взаимное преобразование между списками Excel и объектами базы данных Access. Теперь пришла пора заняться специальными операциями, которые облегчают работу с данными, хранящимися в этой локальной базе данных. При этом я буду рассматривать, как визуальные методы работы со списками, так и программные. Сортировка списков - одна из основных операций, которые выполняются над данными, хранящимися в базе. Эту операцию, конечно, можно выполнять вручную. При программировании следует пользоваться методом Sort объекта Range. Приведем синтаксис этого метода:
    Function Sort([Key1], [Order1 As XlSortOrder = xlAscending], [Key2], [Type], [Order2 As XlSortOrder = xlAscending], [Key3], [Order3 As XlSortOrder = xlAscending], [Header As XlYesNoGuess = xlNo], [OrderCustom], [MatchCase], [Orientation As XlSortOrientation = xlSortRows], [SortMethod As XlSortMethod = xlPinYin])
    Рассмотрим параметры метода и поясним его работу:
  • Key1, Key2 и Key3 - задают имена полей, по которым сортируется список, так что одновременно можно отсортировать список не более чем по трем полям.
  • Order1, Order2 и Order3 задают порядок сортировки - по возрастанию или убыванию независимо по каждому полю; значение для полей по умолчанию - "по возрастанию" (xlAscendinig).
  • Type - задается только при сортировке сводных таблиц и указывает тип сортируемых значений, задаваемых константами: xlSortLabels и xlSortValues.
  • Header - указывает, есть ли в списке строка с именами полей; если его значение - xlGuess, система сама должна определить, есть ли заголовки у полей (для этого они должны отличаться форматом).
  • Значение параметра OrderCustom, отличное от 1, используется, когда список сортируется в порядке, заданном пользовательским списком или некоторым стандартным, специальным списком, например, с названиями месяцев. Приведу пример, поясняющий ситуацию. Представьте себе, что значениями некоторого поля таблицы являются названия основных цветов радуги. Вы хотите отсортировать эти значения в порядке следования цвета в радуге: "красный, оранжевый, …, фиолетовый". Тогда следует создать пользовательский список, задающий порядок следования цвета, и добавить его в коллекцию списков методом AddCustomList: Application.AddCustomList ListArray:=Range("F1:F7")

    Здесь предполагается, что область "F1:F7" содержит значения цветов радуги. При добавлении списка в коллекцию он получит свой номер, который и указывается, как значение параметра OrderCustom.
  • MatchCase (булев) - имеет значение False, если при сортировке регистр не учитывается, большие и малые буквы не различаются.
  • Orientation - задает ориентацию при перестановке сортируемых значений таблицы: сверху вниз (переставляются строки) или слева направо (переставляются столбцы).
  • SortMethod - позволяет выбрать порядок, заданный на символах алфавита. Он может задаваться соответствующей кодовой страницей или являться стандартным алфавитным порядком, в котором цифры предшествуют буквам, а латынь - кириллице.


  • Вот пример вызова этого метода для сортировки списка "Заказчики", показанного на рис. 4.17. Список сортируется по двум полям, - вначале упорядочиваются города, а затем организации каждого города:

    Public Sub Sorting() 'Сортировка таблицы Заказчики по двум полям: 'вначале по полю "город", затем - "организация" Selection.Sort Key1:=Range("D5"), Order1:=xlAscending, _ Key2:=Range("B5"), Order2:=xlAscending, _ Header:=xlGuess, OrderCustom:=1, MatchCase:=False, _ Orientation:=xlTopToBottom End Sub

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

    Создание базы данных в Excel

    Сейчас я рассмотрю создание базы данных офиса РР непосредственно в Excel. Будут созданы те же таблицы с той же структурой, что и в базе, которая только что создавалась в Access. Я рассмотрю разные способы создания этих таблиц, в том числе путем экспорта таблиц Access. Первым делом я создал новую книгу Excel, в которой завел 5 рабочих листов по числу создаваемых таблиц, каждая страница именовалась по названию таблицы базы данных. Затем я перешел к созданию таблицы "Книги" на листе с одноименным названием. В процессе этой работы я следовал рекомендациям, приведенным в предыдущем параграфе. Работа это простая, и не думаю, что требуются хоть какие-либо дополнительные пояснения. Конечно, на этом этапе требуется выполнить подходящее форматирование для ячеек таблицы, но я не буду на этом останавливаться. Форматирование может быть и другим. Взгляните, что у меня получилось, и надеюсь, Вы сумеете сделать эту работу не хуже меня, в особенности с учетом того, что я не обладаю хорошим художественным вку сом.
    Создание базы данных в Excel

    увеличить изображение
    Рис. 4.14.  Таблица "Книги", спроектированная в Excel

    Создание в приложении Access базы данных офиса "РР"

    Для пользователей Microsoft Office 2000 создание базы данных именно в Access самая естественная вещь. Этот параграф может служить предварительным знакомством с Access для тех, кто действительно не знаком с этим замечательным приложением. Создание базы данных в Access помимо прочего обладает двумя несомненными достоинствами:
  • Допускается создание и ведение базы данных простыми и интуитивно понятными средствами в визуальном стиле. Даже человек, далекий от этой весьма специфической области программирования, способен после предварительного непродолжительного знакомства начать создавать свою собственную базу данных.
  • В любом из приложений Office 2000 (Word, Excel, PowerPoint) легко получить доступ к БД Access.

  • В первом приближении БД Access можно рассматривать как совокупность взаимосвязанных таблиц с данными, запросов к ним, форм и отчетов, облегчающих ввод данных в таблицы и вывод информации из них в удобном для конечного пользователя виде, а также программных компонентов, выполняющих различные операции над данными.
    Построение БД офиса РР начну с введения минимально необходимых средств. По мере необходимости база будет расширяться.

    Специальные средства экспорта таблиц Access в Excel

    Вместе с тем Access имеет специальные, развитые средства экспорта таблиц БД и других своих объектов - запросов, форм, отчетов. Таблицу "Заказы" я переместил в Excel, используя следующий способ:
  • В окне базы данных Access выбрал имя таблицы "Заказы".
  • В меню Сервис выбрал команду "Связи с Office" и подкоманду "Анализ в MS Excel". Результатом ее выполнения стало автоматическое создание новой книги Excel с единственной страницей "Заказы", содержащей требуемую таблицу с подходящим форматированием. Заметьте, что команда Связи с Office имеет и другие подкоманды - Слияние с MS Word, Публикация в MS Word, - позволяющие передавать таблицу Access в приложение Word. Все подкоманды команды Связи с Office позволяют выполнять операции не только с таблицами, но и с частями таблиц, запросами, формами, отчетами.
  • Получив на предыдущем шаге нужную мне таблицу в Excel, я использовал далее средства Excel для перемещения таблицы в нужное место имеющейся у меня книги. После чего я применил собственное форматирование, чтобы сохранить единый стиль всех страниц моей книги, хранящей базу данных.

  • Специальные средства экспорта таблиц Access в Excel

    увеличить изображение
    Рис. 4.16.  Экспорт таблицы "Заказы" с использованием команды Анализ в MS Excel
    Наконец, я рассмотрю еще один возможный способ экспорта таблицы Access, основанный на широких возможностях Access по сохранению его объектов в виде файлов самых различных форматов. Например, таблицы, запросы, формы, отчеты можно экспортировать в формат HTML. Таблицы и запросы можно экспортировать в БД, удовлетворяющие стандарту ODBC, их можно экспортировать в текстовые файлы, в ASP-страницы, в базы данных Paradox или dBase и во многие другие форматы. Естественно, среди форматов присутствуют и форматы файлов Excel различных версий. Вот как выглядят мои действия по экспорту таблицы "Заказчики":
  • Открыл БД Access и выбрал нужную мне таблицу "Заказчики".
  • В меню "Файл" выбрал команду "Сохранить как", а затем - "Экспорт".
  • В появившемся диалоговом окне Экспорта выбрал папку, согласился с предложенным именем файла, совпадающим с названием таблицы, в окошке "тип файла" из раскрывающегося списка, задающего многочисленные возможности по экспорту таблицы, выбрал тип "Microsoft Excel 97-2000". Затем включил флажок "Сохранить формат" и щелкнул кнопку OK. Хочу обратить внимание на то, что при выполнении этой операции следует быть осторожным поскольку, если задать имя существующего файла в папке, то все его содержимое заменится новой единственной страницей.
  • В результате этих действий автоматически создается новая книга Excel с заданным именем файла, на единственный лист которой и записывается сохраняемая таблица. Результат этих действий немногим отличается от предыдущего случая, когда использовалась команда Связи с Office.
  • Также как и в предыдущем случае экспорта таблицы, получив файл Excel, я занялся перемещением полученной таблицы, используя средства Excel для перемещения таблицы в нужное место имеющейся у меня книги. После чего применил собственное форматирование, чтобы сохранить единый стиль всех страниц моей книги, хранящей базу данных.

  • Вот окончательный результат моих действий:
    Специальные средства экспорта таблиц Access в Excel

    увеличить изображение
    Рис. 4.17.  Экспорт таблицы "Заказчики"
    Как видите, есть много способов, позволяющих перенести таблицы базы данных Access в списки Excel. Какой из них выбрать - дело вкуса. Скажу честно, я предпочитаю первый способ, когда таблица переносится через буфер без создания промежуточных файлов. То, что при этом может не сохраниться форматирование полей, не представляется особой бедой, поскольку после переноса таблицы в Excel все равно приходиться, как правило, заниматься форматированием, подбирая размеры строк и столбцов, шрифт, цвет и прочие атрибуты.

    Списки Excel, как локальная база данных

    Говоря об Excel и базах данных нельзя не упомянуть и о такой возможности Excel, как ведение собственной локальной базы данных, не использующей никаких внешних источников данных. Небольшие базы данных можно вести непосредственно на Excel. Такую базу данных можно представлять как совокупность таблиц. Таблицы естественны для Excel и их создание не вызывает никаких трудностей. Для поиска данных в таблицах, их отбору, сортировке предусмотрены специальные средства. Когда объем данных невелик и нет необходимости в сложных запросах, требующих одновременной работы с несколькими таблицами, работать с данными в Excel проще и быстрее. Умение вести базу данных на Excel особенно важно для тех, кто не имеет доступа к другим базам данных и работает с версией Office 2000, не содержащей Access.
    Базу данных Excel удобнее всего располагать на нескольких листах по числу таблиц, хранящих данные. На каждом листе обычно располагается одна таблица. Это ограничение накладывает метод AdvancedFilter, без которого, как правило, не обходится обработка данных в таблице. Многие запросы к таблице можно реализовать с помощью этого метода. Таблицу, хранящую данные, в Excel принято называть списком. Список, как и всякая таблица, состоит из строк и столбцов. Столбцы - это поля списка (таблицы БД). Первая строка списка обычно содержит имена полей. Вот несколько правил, которым должны удовлетворять списки, часть из них носит рекомендательный характер:
  • для имен полей следует задать формат (шрифт, цвет фона), отличный от формата ячеек, хранящих значения полей: это позволит системе автоматически отличать имена полей от их значений;
  • значения, хранящиеся в одном столбце (значения одного поля), должны иметь один и тот же тип;
  • рекомендуется именовать лист названием списка, возможно, с некоторым префиксом;
  • области ячеек, отведенной для списка (вместе с первой строкой, хранящей названия полей), целесообразно дать имя; эту область нужно выделить по максимуму, так чтобы она допускала дальнейшее пополнение списка;
  • список отделяется от остальной части рабочего листа пустыми строками и столбцами. Поэтому не рекомендуется, чтобы пустые строки и столбцы встречались в середине списка, т. е. записи, все поля которых пусты, нежелательны, - иногда это может привести к неприятностям.


  • Мир объектов Excel 2000

    Интерфейс ODBC

    Скажу несколько слов об интерфейсе ODBC . Он был создан для того, чтобы дать возможность приложению - Потребителю данных - получать данные из различных систем управления базами данных (СУБД - Data Base Management System - DBMS). При этом концептуальная схема работы такова - приложение через набор соответствующих API-функций, которые, собственно говоря, и составляют интерфейс ODBC, обращается к объекту, называемому Менеджер драйверов (Driver Manager). Последний, опять-таки через тот же набор API- функций обращается к драйверу источника данных. Замечу, что большинство существующих баз данных имеют соответствующие драйверы и, следовательно, являются ODBC-источниками данных. Следует также отметить, что языком запросов в этой модели выступает SQL. Взаимодействие четырех основных объектов: Потребителя, Менеджера Драйверов, Драйвера и Источника данных - и определяет концептуальную модель ODBC.

    Интерфейс OLE DB

    Целевая установка при разработке этого интерфейса была следующей. Решения в сфере бизнеса нужно принимать оперативно. Эти решения должны быть основаны на полной и достоверной информации. Конечно же, основным источником данных для принятия решений является корпоративная база данных, например, MS SQL Server или Oracle. Но только этих данных, как правило, недостаточно для принятия оперативного решения. Необходимо учитывать данные, хранящиеся в личных базах данных, таких как Microsoft Access или FoxPro. Необходимые данные необходимо уметь извлекать из систем электронной почты. Данные могут храниться в индексно-последовательных файлах системы Betrieve, может быть, просто в бинарных файлах, наконец, в документах Office 2000, например, в списках Excel. Все возрастающую роль, как источника данных, играют Web-страницы интернет. Чтобы обеспечить единообразный способ работы с разнообразными источниками данных и создавался интерфейс OLE DB, представляющий некоторое множество интерфейсов, основанных на стандарте COM (Component Object Model).
    Кратко опишу основные понятия (объекты), составляющие концептуальную и объектную модель OLE DB. Процесс работы с данными можно описать в терминах взаимодействия двух объектов - Поставщика данных (Provider) и Потребителя данных (Consumer). Потребителем является приложение, запрашивающее данные и непосредственно вызывающее функции, заданные интерфейсом. Поставщик данных или Провайдер - это приложение, экспонирующее функции интерфейса, - это тот посредник, стоящий между приложением, запрашивающим данные, и источником данных. Функциональные возможности набора COM-интерфейсов могут зависеть от Провайдера, от того, как тот или иной Провайдер реализует функциональность интерфейса для конкретного источника данных. Для наиболее часто используемых источников данных фирмой Microsoft, так и другими фирмами, разработаны ряд Провайдеров, ставших стандартами "де факто".
    Разработчики, создающие Провайдеров, отображают функциональность, присущую конкретному источнику данных в функциональность, определенную интерфейсом. Разработчики, создающие терминальное приложение - Потребителя данных, - вызывают функции, экспонируемые Провайдером, для получения доступа к данным. Позже я чуть подробнее расскажу о различных экземплярах объекта Provider, обеспечивающих связь с различными источниками данных. Провайдеры поддерживают не только непосредственный доступ к данным, но и другие службы (Services) - транзакции, удаленный доступ, структуризацию кэш-памяти (cache) и другие службы. Кэш-память - это промежуточная память, в которой происходит изменение данных, их обновление, удаление и добавление, пока не наступает момент фактического обмена данными между кэш-памятью и источником данных.
    Когда Потребитель связывается с Провайдером и обращается к нему за данными, то он должен вначале создать и инициализировать экземпляр объекта, задающий источник данных данного Провайдера (data source object). С помощью этого объекта можно создать второй центральный объект Провайдера - объект Session. Имея в своем распоряжении объект, задающий сеанс работы, можно уже создать и работать с собственно объектами, определяющими работу с данными - транзакциями, командами, наборами строк (объектами Transaction, Command, Rowset). Говоря о связывании Потребителя и Провайдера, следует упомянуть, что OLE DB определяет возможность использования адресов ресурсов - Uniform Resource Locators (URLs) как альтернативу связывания с помощью строки связывания (Connection String). Адреса URLs могут быть использованы для указания хранилищ данных, строк данных, потоков, наборов строк, объектов Session. Процесс связывания объекта OLE DB с ресурсом, именованным с помощью URL, называется прямым связыванием. При таком способе связывания можно даже и не инициализировать объект Session.
    На этом я закончу беглое знакомство с основными понятиями и концепциями OLE DB. Позже я более подробно остановлюсь на рассмотрении объектов верхнего уровня - объектах ADO, которые транслируются в объекты OLE DB, и объектная модель которых на более высоком уровне повторяет объектную модель OLE DB.

    Методы объекта Command

    У объекта Command всего три метода. Рассмотрим их:
  • Function Execute([RecordsAffected], [Parameters], [Options As Long = -1]) As Recordset. Запускает на выполнение команду, предписанную свойствами CommandText или CommandStream. В качестве результата возвращает либо ссылку на объект Recordset, либо поток, либо Nothing.
    Параметры метода имеют следующий смысл:

  • RecordsAffected - переменная типа Long, возвращающая число записей, затронутых при выполнении операции. Этот параметр не дает числа записей результата. Для этого необходимо использовать свойство RecordCount.
  • Parameters - массив значений параметров типа Variant. Этот аргумент позволяет переопределить все или некоторые из параметров, заданных свойством Parameters объекта Command.
  • Options - значение, указывающее, как Провайдер должен транслировать текст входной строки или потока. Может быть битовой маской одного или более значений из перечислений CommandTypeEnum или ExecuteOptionEnum. Так, если результатом выполнения команды должен быть поток, то значение этого свойства следует задать как adExecuteStream.
  • Sub Cancel(). Прерывает выполнение команды Execute. Подробности уже рассматривались при описании объекта Connection.
  • Function CreateParameter([Name As String], [Type As DataTypeEnum = adEmpty], [Direction As ParameterDirectionEnum = adParamInput], [Size As Long], [Value]) As Parameter. Позволяет создать новый объект Parameter с заданными свойствами. Ссылка на этот объект и является возвращаемым значением. Параметры метода имеют следующий смысл:
  • Name - имя параметра,
  • Type -тип данных объекта Parameter, заданный перечислением DataTypeEnum. Вот лишь некоторые из возможных значений: adArray, adChar, adDate, adCurrency, adIUnknown.
  • Direction - тип самого объекта Parameter, заданный перечислением ParameterDirectionEnum. Он определяет является ли параметром входным или выходным параметром запроса или процедуры, возвращаемым значением хранимой процедуры Вот его возможные значения: adParamInput, adParamInputOutput, adParamOutput, adParamReturnValue, adParamUnknown.
  • Size - значение указывающее максимальную длину значения параметра в символах или байтах.
  • Value - значение типа Variant, указывающее значение объекта Parameter.

  • Метод создает, но не присоединяет автоматически созданный параметр к коллекции параметров. Это позволяет перед присоединением корректно установить необходимые свойства в коллекции Properties. Например, при задании типа данных как adNumeric следует также установить значения свойств NumericScale и Precision.

    Методы объекта Connection

    У объекта Connection 8 методов, позволяющих открыть и закрыть соединение, выполнить команду и прервать ее выполнение, методы, связанные с выполнением транзакции. Давайте рассмотрим описание этих методов:
  • Sub Open([ConnectionString As String], [UserID As String], [Password As String], [Options As Long = -1]). Один из центральных методов, с которого обычно начинается работа с источником данных, и который позволяет установить соединение с Провайдером. Первый параметр метода можно не задавать, если предварительно определить свойство ConnectionString. При задании этого параметра в момент открытия можно переопределить некоторые аргументы, заданные одноименным свойством. Два следующих параметра необходимы при установлении соединения с защищенным паролем источником данных. Последний параметр, задаваемый предопределенной константой, определяет некоторые характеристики устанавливаемого соединения.
  • Sub Close(). Метод закрывает открытое соединение. Он применяется не только тогда, когда надобность в соединении исчезает, а часто для того, чтобы изменить параметры соединения и вновь открыть его методом Open. Напомню, что ряд параметров соединения могут быть установлены только для закрытого соединения. Этим методом обладают целый ряд объектов ADO. Чтобы удалить соответствующий объект из памяти объекту нужно присвоить значение Nothing. Метод Close для этих целей не применяется, - он не удаляет объект.
  • Function Execute(CommandText As String, [RecordsAffected], [Options As Long = -1]) As Recordset. Объект Connection позволяет не только открывать соединение, но и выполнять операции над открытым источником данных. Метод Execute возвращает ссылку на созданный Провайдером объект Recordset, являющийся результатом выполнения команды, заданной первым параметром метода. Параметр CommandText, задающий описание команды, может быть текстом SQL-оператора, именем таблицы или хранимой процедуры, URL-адресом или текстом, специфическим для данного Провайдера. Второй параметр - RecordAffected возвращает число записей, затронутых при выполнении команды. Параметр Options позволяет задать дополнительную информацию для Провайдера, используемую при преобразовании текста параметра CommandText в реальную команду. Поскольку в результате выполнения команды возвращается объект Recordset, обладающий некоторым ограниченным набором возможностей, то для выполнения команд и получения объекта Recordset чаще используют объекты Command или Recordset.
  • Sub Cancel(). Большинство объектов ADO обладают этим методом. При его выполнении завершается асинхронный метод, то есть метод, для которого установлены свойства adAsyncConnect, adAsyncExecute или adAsyncFetch. Для объекта Connection завершаемым методом может быть метод Open либо Execute.
  • Function BeginTrans() As Long, Sub CommitTrans(),Sub RollbackTrans() - три метода для работы с транзакциями. Первый из них запускает новую транзакцию, второй - сохраняет результаты транзакции и завершает ее, третий - позволяет сделать откат, отменяя все изменения, сделанные во время текущей транзакции, сама транзакция при этом завершается. Если Провайдер поддерживает гнездованные транзакции, то повторный вызов метода BeginTrans в уже открытой транзакции начинает новую (гнездованную) транзакцию. Этот метод, вызванный, как функция, возвращает уровень гнездования транзакции. Замечу, что не все Провайдеры поддерживают механизм транзакций, не говоря уже о гнездованных транзакциях. Значение одного из атрибутов свойства Attributes объекта Connection можно задать так, что при закрытии текущей транзакции методами CommitTrans и RollBackTrans будет открываться новая транзакция.
  • Function OpenSchema(Schema As SchemaEnum, [Restrictions], [SchemaID]) As Recordset. Метод позволяет получить информацию от Провайдера о схеме базы данных. В качестве результата возвращается ссылка на объект Recordset, который будет открыт только для чтения со статическим курсором. Столбцы этого объекта содержат описание схемы. Возможные описания задаются спецификациями OLE DB, первый параметр метода определяет, какие именно столбцы появятся в объекте Recordset.


  • Методы объекта Recordset

    У объекта Recordset много свойств, много и методов. С некоторыми из них мы уже знакомы, поскольку они появлялись в примерах. Теперь приступим к их систематическому рассмотрению. С помощью методов можно выполнять все необходимые операции над набором записей - перемещаться по набору, находить нужные записи, создавать новые записи и удалять существующие, менять содержимое записей и передавать состояние набора в базу данных. Начнем наше рассмотрение:
  • Sub AddNew([FieldList], [Values]), Sub Delete([AffectRecords As AffectEnum = adAffectCurrent]). Метод AddNew позволяет добавлять новые записи в набор. Конечно, для применения метода объект Recordset должен иметь статус обновляемого набора, что означает, что для него должен быть установлен соответствующий тип курсора. Можно проверить, допускает ли Провайдер добавление записей. Метод Support, о котором еще предстоит разговор, позволяет выяснить поддерживается ли Провайдером то или иное свойство. В данном случае для того, чтобы проверить возможность создания новых записей в наборе, его следует вызвать, задав в качестве аргумента константу adAddNew.
    Что следует сделать, чтобы набор был обновляемым? Я напомню, что создать объект Recordset можно разными способами. Он создается при вызове методов Execute объектов Connection и Command, его можно создать, вызвав метод Open объекта Recordset. В примерах, которые я приводил ранее, это объект создавался при вызове методов Execute. Но, заметьте, объект, создаваемый таким способом всегда имеет статический тип курсора и не может обновляться. Для создания объекта Recordset, допускающего обновление, всегда нужно применять метод Open, задавая динамический тип курсора в момент открытия.
    Метод AddNew можно вызывать без аргументов. В этом случае созданная новая запись становится текущей, ее поля можно заполнить обычным способом. Для того чтобы содержимое записи было перенесено в базу данных, следует вызвать метод Update. Можно предусмотреть возможность пакетного обновления и использовать метод UpdateBatch для передачи в базу данных группы записей. Приведу пример, в котором в базу данных добавляется новая запись и корректируется значение отдельных полей в некоторых записях:
    Public Sub CreateNewRecords() 'добавление и изменение записей базы данных Dim recExist As Boolean 'Создать соединение CreateConnection 'Создать команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Книги]" Cmd1.CommandType = adCmdText 'Открытие обновляемого объекта Recordset With Rst1 .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic 'Узнаем характеристики набора Debug.Print .Supports(adAddNew) Debug.Print .LockType Debug.Print .CursorType 'Изменение записей recExist = False .MoveFirst Do While Not .EOF 'Обработка текущей записи If !Название = "Офисное программирование" Then recExist = True If ![Год издания] < 2000 And !Цена < 100 Then !Цена = !Цена * 2 .Update End If .MoveNext Loop If Not recExist Then .AddNew !Автор = "Владимир Биллиг" !Название = "Офисное программмирование" ![Год издания] = 2001 ![Число страниц] = 599 !Цена = 150 .Update End If End With End Sub

    Заметьте, здесь метод AddNew, вызываемый без параметров, создал новую запись в наборе, она стала текущей, обычным способом заполнены поля этой записи, затем вызван метод Update для переноса записи в базу данных. Этот же метод использовался для изменения в базе данных значения поля "Цена" ряда записей.

    При вызове метода AddNew можно задавать аргументы. Параметры метода имеют следующий смысл:



  • FieldList - имя поля или массив таких имен. Вместо имен можно задавать порядковые номера полей.
  • Values - значение поля или массив значений. Если аргументы задаются, то должны быть заданы оба аргумента, и они должны быть согласованы обычным образом - по числу элементов в массиве, тип значения должен также соответствовать типу поля.


  • Когда метод AddNew вызывается с аргументами, то создается запись с уже заполненными полями, для нее автоматически вызывается метод Update, так что нет необходимости вызывать его самостоятельно - эту заботу берет на себя система. Приведу пример такого способа добавления записей в набор и базу данных:

    Public Sub CreateNewRecords2() 'добавление и изменение записей базы данных Dim recExist As Boolean 'Создать соединение CreateConnection 'Создать команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Книги]" Cmd1.CommandType = adCmdText 'Открытие обновляемого объекта Recordset With Rst1 .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic

    recExist = False .MoveFirst Do While Not .EOF 'Проверка существования записи If !Название = "Война и мир" Then recExist = True .MoveNext Loop If Not recExist Then .AddNew Array("Автор", "Название", "Год издания", _ "Число страниц", "Цена"), _ Array("Лев Толстой", "Война и мир", 2001, 799, 220) End If End With End Sub

    На эти примеры я еще буду ссылаться при рассмотрении других методов объекта Recordset. Если метод AddNew добавляет записи, то обратный к нему метод Delete позволяет удалять записи из набора и соответственно из базы данных. Я не буду останавливаться на деталях, а продолжу рассмотрение других методов объекта Recordset.
  • Sub Cancel(), Sub CancelBatch([AffectRecords As AffectEnum = adAffectAll]), Sub CancelUpdate(). Эта группа методов позволяет отменить выполнение метода, в частности отменить проделанную работу по обновлению отдельной записи или пакета записей, если в процессе этой работы становится ясно, что вызывать соответствующий метод Update не следует. Метод CancelUpdate обычно вызывается, когда операция Update или Delete завершилась неуспехом, о чем можно узнать, анализируя свойство EditMode, указывающее на незавершенность состояния редактирования.
  • Function Clone([LockType As LockTypeEnum = adLockUnspecified]) As Recordset. Позволяет создать дубликат набора записей. Дубликат может иметь другие права доступа и использоваться, например, только для чтения.
  • Sub Open([Source], [ActiveConnection], [CursorType As CursorTypeEnum = adOpenUnspecified], [LockType As LockTypeEnum = adLockUnspecified], [Options As Long = -1]), Sub Close(). Метод Open является одним из основных способов создания объекта Recordset. Соответствующая объектная переменная, конечно, должна быть создана, а в момент открытия, используя параметры метода, создается реальный набор записей. Первые два параметра задают источник данных и активное соединение. Источником может быть ссылка на объект Command, SQL-оператор, хранимая процедура. В вышеприведенном примере в качестве источника задавался объект Command, заметьте, в этом случае задавать активное соединение не следует, оно определено в объекте Command. Два следующих параметра задают тип курсора и тип доступа к данным. О важности задания этих параметров я уже говорил, - без них не обойтись, при создании обновляемого набора записей. Последний параметр Options должен быть задан в тех случаях, когда первый параметр Source не является ссылкой на объект Command, в этом случае он задает способ интерпретации первого параметра, принимая значения констант из перечислений CommandTypeEnum и ExecuteOptionEnum. Приведу еще один пример открытия объекта Recordset, когда первый параметр не является объектом Command:


    Public Sub CreateRst2() 'Создать соединение CreateConnection ' Открытие объекта Recordset With Rst1 .Open Source:="Select * From [Заказчики]", ActiveConnection:=Con1, _ CursorType:=adOpenStatic, Options:=adCmdText .MoveFirst Do While Not .EOF 'Печать поля записи Debug.Print !Название .MoveNext Loop End With End Sub

    В этом примере источник данных задается SQL-оператором, а последний параметр указывает способ интерпретации, имея чаще всего используемое значение - adCmdText.

    Метод Close позволяет закрыть ранее открытый объект, освобождая ресурсы.
  • Function CompareBookmarks(Bookmark1, Bookmark2) As CompareEnum. Поскольку внутреннее представление закладок зависит от Провайдера, то самому выполнять операции сравнения закладок невозможно. Для этих целей и используется метод CompareBookmarks, позволяющий сравнивать закладки, принадлежащие одному и тому же набору Recordset или набору и его клону. Фактически закладки передаются Провайдеру, который и возвращает результат сравнения. Значением результата является одна из констант перечисления CompareEnum: adCompareEqual, adCompareGreaterThan, adCompareLessThan, adCompareNotComparable, adCompareNotEqual.
  • Sub Find(Criteria As String, [SkipRecords As Long], [SearchDirection As SearchDirectionEnum = adSearchForward], [Start]). Если для выполнения операций сортировки и фильтрации используются свойства объекта Recordset, то для поиска используются его методы. Метод Find позволяет найти в наборе первую строку, удовлетворяющую критерию поиска, где критерий задается первым параметром метода. Критерий ограничен, - позволяет задать условие поиска только по одному полю, он задается строкой, имеющий следующий синтаксис: <Имя поля> Оператор <Значение поля>, где оператор может быть одной из операций сравнения либо оператором Like, задающим сравнение с шаблоном. Параметры SkipRecords и Start позволяют задать начало поиска. Первый из них указывает, сколько записей следует пропустить, начиная от текущей, чтобы начать поиск. Если задан параметр Start, то поиск начинается с записи, заданной этим параметром. Параметр SearchDirection указывает направление поиска, - вперед или назад от начала поиска. Поиск заверша ется, когда найдена запись, удовлетворяющая критерию, или поиск достиг конца набора записей. Так что неуспех можно определить по истинности EOF или BOF в зависимости от направления поиска. В случае успеха искомая запись становится текущей.
  • Function GetRows([Rows As Long = -1], [Start], [Fields]). Этот метод позволяет создать из набора записей переменную VBA - двумерный массив, состоящий из строк и столбцов набора. Первый параметр указывает число строк, переписываемых из набора в массив. Значение по умолчанию этого параметра указывает, что переписываются все строки до конца набора. Если Провайдер поддерживает закладки, то можно задать закладку, как значение параметра Start, определяющую строку, начиная с которой набор будет переписываться в массив. Можно в массив записать выборочные поля набора записей, задав параметр Fields соответствующим образом - в виде массива имен или порядковых номеров переписываемых полей.
  • Function GetString([StringFormat As StringFormatEnum = adClipString], [NumRows As Long = -1], [ColumnDelimeter As String], [RowDelimeter As String], [NullExpr As String]) As String. Если предыдущий метод позволяет преобразовать набор записей в двумерный массив, то метод GetString позволяет конвертировать набор записей в строку. Не буду останавливаться на деталях такого преобразования
  • Sub Move(NumRecords As Long, [Start]), Sub MoveFirst(), Sub MoveLast(),Sub MoveNext(),Sub MovePrevious(). Эта группа методов позволяет организовать перемещение по записям набора. Методы неоднократно появлялись в наших примерах, и я полагаю, семантика их не нуждается в дополнительных пояснениях.
  • Function NextRecordset([RecordsAffected]) As Recordset. Метод Execute объекта Command или метод Open объекта Recordset может выполнять составные команды, возвращающие множество наборов данных. Например, командой может быть составной оператор SQL вида: "Select * From [Книги]; Select * From [Заказчики]". В таких случаях необходимо вызвать метод NextRecordset для получения очередного набора записей. Если множество наборов исчерпано, то метод возвращает значение Nothing. Синтаксически очередной объект может связываться с той же объектной переменной, если старый набор уже не нужен.
  • Sub Requery([Options As Long = -1]). Вызов этого метода эквивалентен последовательности вызовов методов Open и Close. Он позволяет обновить состояние набора, синхронизируя его с текущим состоянием базы данных. Параметр Options определяет специфику выполнения обновления, его возможные значения задаются суммой констант из перечислений ExecuteOptionEnum и CommandTypeEnum. Так, например, значение adAsyncExecute указывает, что обновление будет идти асинхронно и о его завершении можно будет узнать, когда вызовется обработчик события RecordsetChangeComplete.
  • Sub Resync([AffectRecords As AffectEnum = adAffectAll], [ResyncValues As ResyncEnum = adResyncAllValues]). Еще один способ синхронизации данных набора записей и базы данных. В отличие от выше описанного метода Requery запрос повторно не выполняется, - восстанавливается значения только тех записей, которые есть в наборе. Поэтому, если запись набора будет удалена другим пользователем, то возникнет ошибка, информация о которой сохранится в коллекции Errors. Метод полезен при работе со статическим курсором или курсором типа Forward Only.
  • Sub Save([Destination], [PersistFormat As PersistFormatEnum = adPersistADTG]). Позволяет сохранить набор записей в файле или объекте Stream. Параметр Destination задает полный путь к файлу, в котором должен быть сохранен объект Recordset или ссылку на объект Stream. Второй параметр задает формат сохранения - XML или ADTG.
  • Sub Seek(KeyValues, [SeekOption As SeekEnum = adSeekFirstEQ]). Еще один, наряду с методом Find, метод поиска записи в наборе. Напомню, поля в таблицах базы данных могут быть индексированными. Индексы позволяют организовать быстрый поиск нужной записи. Метод Seek позволяет по ключевым значениям индексируемых полей найти запись в наборе и сделать ее текущей. В его первом параметре KeyValues задается массив значений полей, составляющих индекс. Параметр SeekOption задает тип сравнения между столбцами индекса и соответствующими значениями KeyValues.


    Метод используется в сочетании со свойством Index, которое задает соответствующий индекс.

    Поскольку не все Провайдеры поддерживают работу с индексами, то перед вызовом метода полезно, как обычно, вызвать метод Supports с константой adSeek или adIndex, чтобы выяснить возможность такого вызова в используемом контексте. Провайдер базы данных Access не поддерживает работу с индексами и для него единственным методом поиска является метод Find.
  • Function Supports(CursorOptions As CursorOptionEnum) As Boolean. Метод, позволяющий выяснить возможности Провайдера в данном контексте. Параметр CursorOptions задает одно из возможных свойств курсора, метод возвращает значение True, если Провайдер поддерживает это свойство и False в противном случае. Возможные значения параметра являются константами из перечисления CursorOptionEnum. Ряд констант были упомянуты при описании свойств и методов объекта Recordset.
  • Sub Update([Fields], [Values]), Sub UpdateBatch([AffectRecords As AffectEnum = adAffectAll]). Два важных метода, позволяющих проводить обновление отдельной записи или пакета записей в базе данных. Примеры применения метода Update уже приводились.


  • О курсоре

    Прежде чем продолжить рассмотрение свойств, есть смысл посвятить отдельный параграф курсору - важному понятию в модели ADO и при работе с базами данных. Курсор - это элемент базы данных, позволяющий управлять перемещением по записям, обновлением данных, видимостью изменений, сделанных другими пользователями.
    В реляционных базах данных в результате запроса возвращается набор строк таблицы (записей). Приложению, работающему с этим набором в каждый текущий момент необходим не весь набор, а отдельная запись или небольшой блок из записей набора. Для работы с набором записей приложению необходим программный механизм, который будет управлять позициями записей в наборе при его изменении, скроллингом - перемещением вперед и назад по записям набора, разрешением конфликтов при одновременном доступе многих пользователей к одной и той же записи. Все эти службы и предоставляются совокупностью программных компонент, называемых курсорами. Они реализованы в виде библиотеки курсоров, являющейся, обычно, частью базы данных. Название "Курсор" связано с тем, что, так или иначе, курсор указывает на текущую запись в наборе.
    Поскольку от решения вопросов, относящихся к курсорам, зависит эффективность работы с базой данных, и эти решения могут играть определяющую роль, то следует уметь правильно выбирать тип курсора, положение курсора и другие его характеристики. Тип курсора задает, как будет идти скроллинг, динамику изменения набора записей и многое другое. Положение курсора определяет, где будет идти основная работа с ним - на клиентской или серверной стороне. Рассмотрим возможные типы курсоров. Их четыре:
  • Forward Only - допускает перемещение по записям только вперед, не допуская полноценный скроллинг. Он обычно используется тогда, когда необходим только один проход по записям. Его можно использовать и в случаях нескольких проходов, закрывая и заново открывая курсор. После обработки очередной строки освобождаются ресурсы, связанные с ее хранением. По умолчанию этот курсор является динамическим, что означает, что он следит за всеми изменениями, сделанными другими пользователями в показываемых записях. Конечно, это не относится к уже просмотренным записям. Константа adOpenForwardOnly из перечисления CursorTypeEnum задает этот тип курсора.
  • Static - курсор этого типа в отличие от динамического не следит за изменениями, сделанными другими пользователями. Он сохраняет состояние набора записей на момент открытия. В зависимости от реализации статические курсоры могут допускать только чтение записей или возможность их обновления, могут допускать скроллинг или только перемещение вперед. Как правило, статический курсор допускает видимость изменений, сделанных самим приложением. Этот вид курсора обычно применяется, когда необходим скроллинг, но нет необходимости следить за изменениями, сделанными другими пользователями. Константа adOpenStatic задает этот тип курсора.
  • Keyset - курсор этого типа является, обычно, альтернативой курсору Forward Only. Он применяется, когда идет интенсивная работа с отдельными записями набора в произвольном порядке доступа к ним. Этот курсор называется курсором, управляемым набором ключей (keyset driven cursor). Для каждой строки из набора создается ключ, обеспечивающий быстрый доступ к любой строке набора.

    Что касается наблюдения за изменениями в наборе, то этот вид курсора обеспечивает стратегию, промежуточную между статическим и динамическим курсором. Он позволяет проследить за изменениями значений записей, сделанных другими пользователями, и этим он похож не динамический курсор. Но он не позволяет проследить за изменениями в составе набора - добавлению или удалению строк, изменения порядка их следования. При создании ключей состав набора замораживается. Когда запись удаляется из набора, то, поскольку ключ для нее сохраняется, то такая запись будет видна, как пустая запись - "дыра" в наборе. Добавляемые записи будут видны в виде добавлений в конец набора. Константа adOpenKeyset задает этот тип курсора.
  • Dynamic - динамический курсор обнаруживает все изменения, происходящие с набором записей, сделанные как самим приложением, так и всеми параллельно работающими пользователями. Все вставки, обновления или удаления, сделанные всеми пользователями видимы для этого типа курсора. Этот вид курсора выбирается, когда необходимо обеспечить совместную работу, но, нужно понимать, он требует от сервера больших затрат и при большом числе пользователей может существенно замедлить работу с набором данных. Константа adOpenDynamic задает этот тип курсора.


  • Поговорим теперь о такой важной характеристике курсора как его положение (Cursor Location). Константы adUseClient и adUseServer перечисления CursorLocationEnum задают положение курсора. В зависимости от установленного значения все необходимые ресурсы и сама работа с данными ведется на клиентской или серверной стороне. Достоинства работы на стороне клиента состоят в быстром отклике на обращение к записям, не требующим выхода в сеть. Когда работа с записями ведется в основном в режиме их чтения, то это положение курсора наиболее предпочтительно. Некоторым недостатком является то, что при больших наборах клиентскому компьютеру требуются большие ресурсы по памяти для хранения данных. Преимущества работы также теряются, когда интенсивно изменяется состав набора - записи удаляются, добавляются, так как эти изменения должны отражаться и на сервере. Если курсор расположен на серверной стороне, то объем передаваемых данных может быть существенно уменьшен, поскольку вся обра ботка ведется на сервере и клиенту переда ется только необходимые ему записи, а не весь набор. С другой стороны при большом числе активных пользователей каждому из них сервер должен выделить ресурсы, что может быть серьезной нагрузкой на сервер, с которой он может и не справиться. Еще одним недостатков курсора на серверной стороне является то, что в отличие от курсоров на клиентской стороне, не поддерживается работа в пакетном режиме (batch cursor), а возможна работа только с единственной записью.

    Конечно, поддерживают работу на клиентской и серверной стороне разные библиотеки курсоров. При работе на стороне клиента для обеспечения работы с курсором ADO вызывает специальную службу - Microsoft Cursor Service for OLE DB, поддерживающую единую функциональность для различных Провайдеров.

    Для того чтобы обеспечить целостность данных в СУБД имеются специальные механизмы, позволяющие временно закрыть доступ к записям базы другим пользователям, пока один из пользователей корректирует их содержание. С одной стороны, возможность закрытия доступа совершенно необходима в некоторых ситуациях, например, чтобы не продать один и тот же билет на самолет или поезд разным пассажирам. С другой стороны, когда, например, в интернете сотни тысяч пользователей обращаются одновременно к базе данных, то закрытие данных существенно снижает общую производительность системы. Система ADO позволяет указывать Провайдеру, какой тип закрытия данных следует применять при работе с объектами Recordset. Конкретный Провайдер может не поддерживать все типы закрытия, в этом случае он будет поддерживать возможный ближайший тип закрытия. Вернемся теперь к рассмотрению свойств объекта Recordset и продолжим это рассмотрение со свойств, связанных с курсором.

    Объект Command

    Объект Connection позволяет установить соединение с Провайдером источника данных и, вообще говоря, выполнять операции над данными. Но для выполнения команд есть специальный объект. Объект Command позволяет задать команду Провайдеру на выполнение той или иной операции над данными источника. Чтобы разобраться с возможностями этого объекта, давайте начнем с рассмотрения его свойств и методов. Замечу, что этот объект событий не имеет, - они связаны только с объектами Connection и Recordset.

    Объект Connection

    С чего начинается работа с источником данных? Прежде всего, нужно с ним соединиться. Объект Connection, как я уже говорил, позволяет установить соединение с Провайдером источника данных.

    Объект Recordset

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

    Объектная модель ADO

    Рассмотрим объектную модель ADO и начнем с графического представления отношений между объектами в этой модели:
    Объектная модель ADO

    Рис. 5.1.  Отношения между объектами в объектной модели ADO
    Объекты ADO имеют следующее назначение:
  • Command - Определяет ту специальную команду, которую предполагается выполнить над источником данных.
  • Connection - Задает открытое соединение с источником данных.
  • Error - элемент коллекции Errors. Содержит описание ошибки доступа, которая возникла при выполнении Провайдером определенной операции. Список этих ошибок (коллекция Errors), возникших в течение одной операции создается Провайдером данных.
  • Field - элемент коллекции Fields. Поле записи, задает столбец данных, все значения в котором имеют один и тот же тип.
  • Parameter - элемент коллекции Parameters. Представляет параметр или аргумент, связанный с объектом Command, который определяет параметризованный запрос или хранимую процедуру. Параметры передаются запросу или процедуре.
  • Property - элемент коллекции Properties. Представляет характеристики объектов ADO, используемые Провайдером.
  • Record - Представляет единственную запись - строку в наборе записей - объекте Recordset, или каталог или файл в файловой системе.
  • Recordset - Представляет все множество записей таблицы базы данных или результат выполнения некоторой команды. В каждый текущий момент объект Recordset ссылается только на одну запись, называемую текущей, внутри всего набора записей.
  • Stream - Представляет бинарный поток данных, рассматриваемый как последовательность байтов.

  • Хочу обратить внимание на некоторую особенность данной объектной модели. На верхнем уровне иерархии находится целая группа объектов. Здесь нет центрального объекта, как это обычно бывает, в который вложены все остальные объекты. Замечу, что в предыдущей модели DAO такой объект был - это объект DBEngine, задающий некую машину базы данных. В данном случае Microsoft отошла от привычной для Office 2000 практики и отказалась от введения центрального объекта, в который вложены все остальные объекты иерархии.
    Чтобы дать полную характеристику объектов, нужно рассмотреть их свойства, методы и события. Кроме того, нужно понимать отношения, связывающие объекты, а еще хорошо бы понимать, как пользоваться всем этим богатством. Поговорим об этом.

    Обзор возможностей объекта Command и примеры применения

    Подведу теперь некоторые итоги и укажу на те возможности, которые предоставляет объекта Command:
  • Задать описание команды, используя свойства CommandText и CommandStream. Второе из этих свойств позволяет, например, задавать XML-запросы.
  • Связать объект Command с открытым соединением, используя свойство ActiveConnection.
  • Вызывать команду на исполнение, используя метод Execute объекта Command, или для именованного объекта вызывать команду как метод объекта Connection.
  • Формировать объект Recordset как результат выполнения команды.
  • В момент вызова команды передавать параметры хранимой процедуре или параметризованному запросу.
  • Формировать при необходимости объекты Parameter и создавать коллекцию Parameters.
  • Повышать эффективность выполнения команды, используя свойства Prepared и CommandType.

  • Приведу пример создания и работы с объектом Command:
    Public Sub CreateCommands() 'Создание команд, выполняющих операции 'с тестовой базой данных Access Dim Par1 As Object Dim strSQL1 As String, strSQL2 As String Dim strSQL3 As String, strSQL4 As String Dim KeyAuthor As String, KeyName As String Const Кавычка = "'" KeyAuthor = "Б. Гейтс": KeyName = "Дорога в будущее" 'Задание четырех SQL операторов strSQL1 = "Select * FROM [Книги]WHERE [Автор]= " _ & Кавычка & KeyAuthor & Кавычка strSQL2 = "Select * FROM [Книги]WHERE ([Название]=" _ & Кавычка & KeyName & Кавычка & " AND [Автор] = " _ & Кавычка & KeyAuthor & Кавычка & ")" strSQL3 = "Select * FROM [Заказчики в Твери]" strSQL4 = "Select * FROM [зак-из-гор]"
    'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = strSQL1 Cmd1.CommandType = adCmdText Cmd1.Prepared = True 'Cmd1.Name = "AuthorAndBook"
    'вызов команды на исполнение методом Execute Set Rst1 = Cmd1.Execute 'печать результата Rst1.MoveFirst Debug.Print Rst1!Автор Debug.Print Rst1!Название Debug.Print Rst1!Цена Rst1.MoveLast Debug.Print Rst1!Автор Debug.Print Rst1!Название Debug.Print Rst1!Цена

    'Изменение описания команды Cmd1.CommandText = strSQL2 Set Rst1 = Cmd1.Execute 'печать результата Debug.Print Rst1!Автор Debug.Print Rst1!Название Debug.Print Rst1!Цена

    ' Вызов хранимого запроса базы данных Cmd1.CommandText = strSQL3 Set Rst1 = Cmd1.Execute 'печать результата Rst1.MoveFirst Debug.Print Rst1!Название Rst1.MoveLast Debug.Print Rst1!Название

    'Вызов хранимого запроса с параметрами Cmd1.CommandText = strSQL4 Set Par1 = Cmd1.CreateParameter("Town", adBSTR, adParamInput) Cmd1.Parameters.Append Par1 Par1.Value = "Тверь" Set Rst1 = Cmd1.Execute 'печать результата Rst1.MoveFirst Debug.Print Rst1!Название Rst1.MoveLast Debug.Print Rst1!Название

    'печать характеристик Cmd1 Debug.Print "ActiveConnection = ", Cmd1.ActiveConnection Debug.Print "CommandTimeout = ", Cmd1.CommandTimeout Debug.Print "CommandType = ", Cmd1.CommandType Debug.Print "Name = ", Cmd1.Name Debug.Print "CommandText = ", Cmd1.CommandText Debug.Print "Prepared = ", Cmd1.Prepared Debug.Print "Parameters.Count = ", Cmd1.Parameters.Count Debug.Print "Properies.Count = ", Cmd1.Properties.Count Debug.Print "State = ", Cmd1.State Debug.Print "Properties(1).Name = ", Cmd1.Properties(1).Name Debug.Print "Properties(1).Value = ", Cmd1.Properties(1).Value

    End Sub

    Прокомментирую текст этой процедуры:

  • Я выполняю четыре разные команды над базой данных. Описание этих команд я задал в четырех SQL-операторах. Первый из них выбирает из таблицы "Книги" все книги, автор которых задан переменной KeyAuthor, имеющей в данном примере значение "Б. Гейтс". Замечу, что в таблице хранятся сведения о двух книгах этого автора. Во втором операторе запрос уточняется и ищется книга с фиксированным названием. Третий SQL-оператор обращается к стандартному запросу, хранимому в базе данных. Этот запрос выдает в качестве результата набор записей, содержащих заказчиков из города Тверь. Наконец, текст четвертой команды представляет вызов параметризованного запроса, где ищутся заказчики из города, название которого является параметром запроса.
  • Прежде чем вызвать первую команду на исполнение, я формирую свойства объекта Command. Первым делом формирую свойство CommandText, задающее описание команды, но не только его. Я задал свойство ActiveConnection, что совершенно необходимо, а, кроме того, задал свойства Prepared и CommandType. Заметьте, в данном контексте невозможно именовать команду, поэтому мне пришлось закомментировать назначение этого свойства, чтобы избежать появления ошибки.
  • Последовательно изменяя описание команды - свойство CommandText, - я выполнил все четыре команды. Конечно же, прежде чем выполнить четвертую команду, требующую задания параметра в момент ее выполнения, мне пришлось этот параметр создать. Для его создания я вызвал метод CreateParameter и созданный таким образом параметр присоединил к коллекции Parameters. Хочу обратить внимание на одну деталь, переменную Par1 мне пришлось описать как имеющую класс Object, а не класс Parameter, иначе возникало несоответствие типов при попытке присвоения ей ссылки на объект, созданный в результате выполнения метода CreateParameter.
  • Для демонстрации корректности выполнения команд, я печатаю значения записей из набора Rst1, создаваемого при выполнении очередной команды. Заметьте, я использую некоторые операции над объектом Recordset - MoveFirst и MoveLast, позволяющие выбрать нужную запись в наборе. Чуть позже я подробно рассмотрю все свойства и методы этого объекта.
  • В заключение, я печатаю некоторые свойства объекта Command. Из большой коллекции Properties я ограничился печатью количества элементов в этой коллекции, а также имени и значения первого элемента в этой коллекции. Приведу результаты отладочной печати:
  • Б. Гейтс Дорога в будущее 23 Б. Гейтс Бизнес со скоростью мысли 150 Б. Гейтс Дорога в будущее 23 Книжная лавка ООО 'Тверькнига' Книжная лавка ООО 'Тверькнига' ActiveConnection = Provider=Microsoft.Jet.OLEDB.4.0;Password="";User ID=Admin;Data Source=c:\!O2000\DsCd\Ch14\dbPP2000.mdb;Mode=Share Deny None;Extended Properties="";Jet OLEDB:System database="";Jet OLEDB:Registry Path="";Jet OLEDB:Database Password="";Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:New Database Password="";Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False CommandTimeout = 30 CommandType = 1 Name = CommandText = Select * FROM [зак-из-гор] Prepared = True Parameters.Count = 2 Properies.Count = 88 State = 0 Properties(1).Name = Blocking Storage Objects Properties(1).Value = True


    'Изменение описания команды Cmd1.CommandText = strSQL2 Set Rst1 = Cmd1.Execute 'печать результата Debug.Print Rst1!Автор Debug.Print Rst1!Название Debug.Print Rst1!Цена

    ' Вызов хранимого запроса базы данных Cmd1.CommandText = strSQL3 Set Rst1 = Cmd1.Execute 'печать результата Rst1.MoveFirst Debug.Print Rst1!Название Rst1.MoveLast Debug.Print Rst1!Название

    'Вызов хранимого запроса с параметрами Cmd1.CommandText = strSQL4 Set Par1 = Cmd1.CreateParameter("Town", adBSTR, adParamInput) Cmd1.Parameters.Append Par1 Par1.Value = "Тверь" Set Rst1 = Cmd1.Execute 'печать результата Rst1.MoveFirst Debug.Print Rst1!Название Rst1.MoveLast Debug.Print Rst1!Название

    'печать характеристик Cmd1 Debug.Print "ActiveConnection = ", Cmd1.ActiveConnection Debug.Print "CommandTimeout = ", Cmd1.CommandTimeout Debug.Print "CommandType = ", Cmd1.CommandType Debug.Print "Name = ", Cmd1.Name Debug.Print "CommandText = ", Cmd1.CommandText Debug.Print "Prepared = ", Cmd1.Prepared Debug.Print "Parameters.Count = ", Cmd1.Parameters.Count Debug.Print "Properies.Count = ", Cmd1.Properties.Count Debug.Print "State = ", Cmd1.State Debug.Print "Properties(1).Name = ", Cmd1.Properties(1).Name Debug.Print "Properties(1).Value = ", Cmd1.Properties(1).Value

    End Sub

    Прокомментирую текст этой процедуры:

  • Я выполняю четыре разные команды над базой данных. Описание этих команд я задал в четырех SQL-операторах. Первый из них выбирает из таблицы "Книги" все книги, автор которых задан переменной KeyAuthor, имеющей в данном примере значение "Б. Гейтс". Замечу, что в таблице хранятся сведения о двух книгах этого автора. Во втором операторе запрос уточняется и ищется книга с фиксированным названием. Третий SQL-оператор обращается к стандартному запросу, хранимому в базе данных. Этот запрос выдает в качестве результата набор записей, содержащих заказчиков из города Тверь. Наконец, текст четвертой команды представляет вызов параметризованного запроса, где ищутся заказчики из города, название которого является параметром запроса.
  • Прежде чем вызвать первую команду на исполнение, я формирую свойства объекта Command. Первым делом формирую свойство CommandText, задающее описание команды, но не только его. Я задал свойство ActiveConnection, что совершенно необходимо, а, кроме того, задал свойства Prepared и CommandType. Заметьте, в данном контексте невозможно именовать команду, поэтому мне пришлось закомментировать назначение этого свойства, чтобы избежать появления ошибки.
  • Последовательно изменяя описание команды - свойство CommandText, - я выполнил все четыре команды. Конечно же, прежде чем выполнить четвертую команду, требующую задания параметра в момент ее выполнения, мне пришлось этот параметр создать. Для его создания я вызвал метод CreateParameter и созданный таким образом параметр присоединил к коллекции Parameters. Хочу обратить внимание на одну деталь, переменную Par1 мне пришлось описать как имеющую класс Object, а не класс Parameter, иначе возникало несоответствие типов при попытке присвоения ей ссылки на объект, созданный в результате выполнения метода CreateParameter.
  • Для демонстрации корректности выполнения команд, я печатаю значения записей из набора Rst1, создаваемого при выполнении очередной команды. Заметьте, я использую некоторые операции над объектом Recordset - MoveFirst и MoveLast, позволяющие выбрать нужную запись в наборе. Чуть позже я подробно рассмотрю все свойства и методы этого объекта.
  • В заключение, я печатаю некоторые свойства объекта Command. Из большой коллекции Properties я ограничился печатью количества элементов в этой коллекции, а также имени и значения первого элемента в этой коллекции. Приведу результаты отладочной печати:
  • Б. Гейтс Дорога в будущее 23 Б. Гейтс Бизнес со скоростью мысли 150 Б. Гейтс Дорога в будущее 23 Книжная лавка ООО 'Тверькнига' Книжная лавка ООО 'Тверькнига' ActiveConnection = Provider=Microsoft.Jet.OLEDB.4.0;Password="";User ID=Admin;Data Source=c:\!O2000\DsCd\Ch14\dbPP2000.mdb;Mode=Share Deny None;Extended Properties="";Jet OLEDB:System database="";Jet OLEDB:Registry Path="";Jet OLEDB:Database Password="";Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:New Database Password="";Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False CommandTimeout = 30 CommandType = 1 Name = CommandText = Select * FROM [зак-из-гор] Prepared = True Parameters.Count = 2 Properies.Count = 88 State = 0 Properties(1).Name = Blocking Storage Objects Properties(1).Value = True

    Заметьте, число параметров в коллекции Parameters равно 2, а не 1, как должно было бы быть. Это связано с моей недоработкой и недоработкой Microsoft. Перед тем, как начать формировать коллекцию Parameters, я должен был очистить ее содержимое, вызвав метод Delete. Я не сделал этого, поскольку метод Delete не вызывается в данном контексте. По этой причине при повторном запуске процедуры произошло добавление параметра к уже имеющейся коллекции. В данном случае, когда выполняется запрос с одним параметром, это не приводит к ошибке, и я не стал усложнять уже и так довольно длинную процедуру. Но в принципе это серьезная ошибка, которая в другой ситуации может привести к неприятностям, например, если бы в следующей команде я попытался бы выполнить другой параметризованный запрос. Так что обратите внимание на эту ситуацию, и корректно работайте с коллекцией Parameters. Заметьте, проблемы исчезают для локально определенного объекта Command .

    Обзор возможностей объекта Connection

    Подведу теперь некоторые итоги и укажу на некоторые дополнительные возможности объекта Connection. Объект задает сеанс работы с источником данных. В случае, когда речь идет об удаленном источнике и клиент-серверном приложении установление соединения означает физическое подключение к серверу в сети.
    Хотя большинство свойств и методов этого объекта определено для всех стандартных Провайдеров, тем не менее, есть и специфика, определяемая каждым конкретным Провайдером. Перечислю еще раз те возможности, которые предоставляют свойства и методы объекта:
  • Еще до открытия соединения можно установить его конфигурацию, задавая характеристики соединения с помощью таких свойств, как ConnectionString, ConnectionTimeOut, Mode. Первое из этих свойств имеет статус свойства по умолчанию.
  • Можно обращаться на клиентской стороне к службе управления курсором - Cursor Service for OLE DB, задав adUseClient в качестве значения свойства CursorLocation.
  • Можно установить для соединения базу данных по умолчанию, задав свойство DefaultDatabase, аналогично, свойство Provider устанавливает Провайдера по умолчанию.
  • Используя методы Open и Close, можно многократно открывать и закрывать соединение, возможно, меняя его параметры от сеанса к сеансу.
  • Используя метод Execute, можно выполнять те или иные команды, не обращаясь к объекту Command.
  • Дана возможность управлять транзакциями в открытом соединении, в том числе гнездованными транзакциями, если только Провайдер поддерживает эти возможности.
  • В соответствующих обработчиках событий можно проверять корректность задания параметров перед выполнением команд. После их выполнения можно анализировать объекты Error, чтобы понять причины, приведшие к возникновению ошибки по завершении команды.
  • Можно независимо создать несколько объектов Connection, что позволяет запускать одновременно несколько сеансов работы с одним или разными источниками данных.
  • Наконец, важно отметить такую возможность некоторых Провайдеров, как вызов объектом Connection именованных команд и хранимых процедур, точно также как, если бы они были методами этого объекта. Вот схема процедуры, демонстрирующая эту возможность:
  • Sub Fragment() 'Это не исполняемый код, а демонстрационная заготовка Dim cnn As New ADODB.Connection Dim cmd As New ADODB.Command Dim rst As New ADODB.Recordset 'конфигурирование соединения cnn.Open ConnectionString:= "…" cmd.Name = "NameOfMyCommand" cmd.ActiveConnection = cnn 'запуск команды на выполнение, передавая ей параметры и объект Recordset cnn.NameOfMyCommand "parameter", rst 'запуск хранимой процедуры и передача ей параметров cnn.NameOfMyStoredProcedure "parameter" End Sub

    Приведенный пример является лишь иллюстрацией, чтобы он заработал необходимо уточнить опущенные здесь детали. Такие примеры тоже полезны, но, пожалуй, интереснее работающие процедуры. Чтобы проводить эксперименты с объектами ADO, я создал некоторый модуль в программном проекте документа. Приведу сейчас процедуру этого модуля, в которой создается соединение.

    'Модуль TestingADO 'Глобальные переменные Public Con1 As New ADODB.Connection Public Cmd1 As New ADODB.Command Public Rst1 As New ADODB.Recordset Public Strm1 As New ADODB.Stream Public Sub CreateConnection() 'Создание соединения с тестовой базой данных Access Dim strConnStr As String If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение 'Вариант 1 'Конфигурирование соединения Con1 Con1.Provider = "Microsoft.jet.oledb.4.0" Con1.ConnectionString = "Data Source=c:\!O2000\DsCd\Ch14\dbPP2000.mdb" Con1.CursorLocation = adUseClient 'Открытие соединения Con1.Open 'Вариант 2 'strConnStr = "Provider=Microsoft.jet.oledb.4.0;" & _ '"Data Source=c:\!O2000\DsCd\Ch14\dbPP2000.mdb" 'Con1.Open strConnStr 'печать характеристик соединения Debug.Print "Attributes = ", Con1.Attributes Debug.Print "CommandTimeout = ", Con1.CommandTimeout Debug.Print "ConnectionString = ", Con1.ConnectionString Debug.Print "ConnectionTimeout = ", Con1.ConnectionTimeout Debug.Print "CursorLocation = ", Con1.CursorLocation Debug.Print "DefaultDatabase = ", Con1.DefaultDatabase Debug.Print "Mode = ", Con1.Mode Debug.Print "Properies.Count = ", Con1.Properties.Count Debug.Print "State = ", Con1.State Debug.Print "Version = ", Con1.Version End Sub

    Приведу краткий комментарий.

  • Прежде всего, отмечу, что я начал работать с документом Excel, который будет получать данные из базы данных Access, создание которой я описал в предыдущей главе. В проекте этого документа я создал модуль с именем TestingADO, подключил к проекту библиотеку ActiveX Data Objects 2.5 Library, которая у меня на компьютере находится, как обычно, по адресу: "c:\Program Files\Common Files\System\ado\msado15.dll". Имя этой библиотеки, используемое в проектах на VBA, - ADODB.
  • Затем я создал 4 глобальных объекта: Con1, Cmd1, Rst1, Strm1, принадлежащих соответственно классам Connection, Command, Recordset, Stream из библиотеки ADODB.
  • Первая процедура, которую я написал в этом модуле - CreateConnection, проста. В ней идет работа с объектом Con1, устанавливается соединение с базой данных Access и распечатываются характеристики сделанного соединения.
  • Работа начинается с проверки состояния соединения, поскольку попытка открыть уже открытое соединение приводит к ошибке.
  • Я рассматриваю два варианта установления соединения. В первом - предварительно задаются свойства соединения Provider и ConnectionString, затем вызывается метод Open без параметров. Во втором варианте вся необходимая информация указывается при вызове метода Open в передаваемых ему параметрах.
  • Приведу результаты печати свойств соединения после его открытия:
  • Attributes = 0 CommandTimeout = 30 ConnectionString = Provider=Microsoft.Jet.OLEDB.4.0;Password="";User ID=Admin;Data Source=c:\!O2000\DsCd\Ch14\dbPP2000.mdb;Mode=Share Deny None;Extended Properties="";Jet OLEDB:System database="";Jet OLEDB:Registry Path="";Jet OLEDB:Database Password="";Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:New Database Password="";Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False ConnectionTimeout = 15 CursorLocation = 3 DefaultDatabase = Mode = 16 Properies.Count = 94 State = 1 Version = 2.5

    Обратите внимание на длинную строку ConnectionString. Она, конечно, намного длиннее той строки, которую задал я, определив источник данных. Помимо этого, строка содержит многие характеристики соединения, устанавливаемые по умолчанию. Заметьте также, я напечатал лишь число элементов коллекции Properties, не приводя значений всех 94 элементов.

    Обзор возможностей объекта Recordset

    Объект Recordset это основной объект, позволяющий работать с данными, извлеченными и помещаемыми в базу данных. Создается разными способами - методом Execute объектов Connection и Command, либо методом Open объекта Recordset. Содержит набор записей, полученных в результате выполнения запроса. В каждый текущий момент позволяет работать только с одной записью набора - текущей, на которую указывает курсор.
    Методы и свойства объекта позволяют перемещать курсор по набору записей, производить фильтрацию записей, их сортировку, находить нужную запись. Можно изменять значения отдельных полей некоторых записей, добавлять новые и удалять записи из набора. Измененный набор можно сохранить в базе данных. При этом изменения можно делать оперативно - для каждой записи, подвергшейся изменению, или в пакетном режиме.
    Во многом возможность выполнения операций над данными определяет тип и положение курсора, установленные свойствами объекта Recordset. В ADO определены четыре типа курсора и два возможных положения, указывающие, где выполняются операции - на клиентской или на серверной стороне.
    Свою специфику, определяющую возможность выполнения отдельных операций и их особенность, накладывает используемый Провайдер. Свойство Supports позволяет выяснить, поддерживает ли Провайдер те или иные свойства выбранного курсора.
    Свойство Fields является свойством по умолчанию объекта Recordset. Оно возвращает коллекцию полей, элементы которой являются объектами Field, каждый из которых позволяет работать с отдельным полем записи.
    Объект Recordset может быть не только плоским набором записей, но и иметь иерархическую структуру, при которой записи набора могут иметь потомков.
    На этом я завершу описание основных объектов ADO - Connection, Command и Recordset. Но разговор об объектах ADO еще далеко не завершен и будет продолжен в следующей главе.

    Семейство объектов ADO

    Перейдем теперь к подробному рассмотрению объектов ADO - наиболее интересных для VBA-программистов. Более точно, следует говорить о семействе объектов ADO, в которое входят три группы объектов:
  • ADO - ActiveX Data Objects. Иногда, когда говорят об объектах ADO, имеют в виду именно объекты этой группы. Эти объекты, как я уже говорил, являются надстройкой над объектами OLE DB. Их назначение - решать те же задачи, ради которых был создан интерфейс OLE DB, но на более высоком языковом уровне общения. В первую очередь эти объекты создавались для того, чтобы дать возможность создавать клиент-серверные и WEB-ориентированные приложения на VB/VBA и VBScript. Вот три главных объекта, находящихся на верхнем уровне иерархии этой группы объектов:
  • Connection - позволяет установить соединение с Провайдером.
  • Command - позволяет задать команду Провайдеру, определяющую операцию, выполняемую над данными.
  • Recordset - сохраняет результаты выполненной команды и обеспечивает, тем самым, возможность работы с результатами запросов и хранимых процедур.
  • ADOX - ActiveX Data Objects Extensions for Data Definition Language and Security. Объекты этой группы дополняют функциональные возможности объектов первой группы. Они позволяют манипулировать не с данными, а с объектами более высокого уровня - схемами данных. С их помощью можно модифицировать схему данных, создавать, изменять и удалять ее объекты, например таблицы базы данных. Другое назначение этих объектов - управлять безопасностью доступа к данным, - назначать и изменять права доступа к данным, создавать группы пользователей, обладающих определенными правами. Центральными объектами в этой группе, находящимися на верхнем уровне иерархии, являются объекты Catalog, User, Group. Первый из этих объектов задает источник данных, - он соответствует объекту DataBase в модели объектов DAO. Объекты User и Group позволяют управлять безопасностью доступа, как отдельного пользователя, так и пользователей, объединенных в группы.
  • ADO MD - ActiveX Data Objects MultiDimensional. Также как и обычные ADO-объекты, эти объекты являются надстройкой над интерфейсом OLE DB. Их назначение состоит в том, чтобы обеспечить возможность работы с многомерными источниками данных. Для того, чтобы работа с этим объектами была возможной, соответствующий Провайдер должен быть Провайдером многомерных данных (MDP Provider) и удовлетворять специальным спецификациям - OLE DB for OLAP. В этом случае данные представляются не в виде таблиц, а в виде кубов данных. Центральными в этой группе являются объекты CubeDef и CellSet, которые обеспечивают определение куба данных и доступ к множеству ячеек этого куба.

  • Каждая из трех групп ADO-объектов находится в отдельной DLL библиотеке и подключается к программному проекту независимо друг от друга обычным способом - вручную через меню References, либо программно. Замечу, что описание объектов, которое я буду приводить, соответствует версии библиотеки 2.6, - последней на момент написания данного текста, хотя примеры, которые я буду приводить, разработаны на предыдущей версии 2.5.

    События объекта Connection

    Только два объекта ADO обладают событиями - Connection и Recordset. События связываются с выполнением той или иной операции и могут возникать перед выполнением и после завершения операции. При возникновении события операционной системе посылается уведомление, а она, в свою очередь, вызывает соответствующую процедуру обработки этого события. Конечно, программист должен определить эту процедуру в соответствующем месте проекта. Обработка событий, предшествующих выполнению операции, позволяет проверить правильность задания всех параметров и принять окончательное решение о запуске команды на выполнение или на ее прерывание. Обработка событий после завершения операции особенно важна при асинхронном способе выполнения команд, когда выполнение операций над данными выполняется параллельно с выполнением кода программного проекта. В этом случае необходимо получать уведомление о завершении очередной команды и выполнять в обработчике события определенные действия.
    У объекта Connection достаточно много событий - 9, большая часть из которых возникает по завершении той или иной команды. Давайте рассмотрим эти события:
  • Event WillConnect(ConnectionString As String, UserID As String, Password As String, Options As Long, adStatus As EventStatusEnum, pConnection As Connection),
  • Event WillExecute(Source As String, CursorType As CursorTypeEnum, LockType As LockTypeEnum, Options As Long, adStatus As EventStatusEnum, pCommand As Command, pRecordset As Recordset, pConnection As Connection). Первое из этих событий будет возникать перед выполнением соединения, второе - перед выполнением команды Execute или Open. Параметры pConnection, pCommand, pRecordset возвращают указатели на соответствующие объекты, а остальные параметры задают те или иные характеристики соединения или команды, которую предстоит выполнить. Как я уже говорил, в обработчике события можно проверить корректность задания параметров.
  • Event BeginTransComplete(TransactionLevel As Long, pError As Error, adStatus As EventStatusEnum, pConnection As Connection),
  • Event CommitTransComplete(pError As Error, adStatus As EventStatusEnum, pConnection As Connection),
  • Event RollbackTransComplete(pError As Error, adStatus As EventStatusEnum, pConnection As Connection).
  • Event ConnectComplete(pError As Error, adStatus As EventStatusEnum, pConnection As Connection),
  • Event Disconnect(adStatus As EventStatusEnum, pConnection As Connection),
  • Event ExecuteComplete(RecordsAffected As Long, pError As Error, adStatus As EventStatusEnum, pCommand As Command, pRecordset As Recordset, pConnection As Connection), Шесть событий этой группы возникают после завершения операций Connection, Execute или операций, связанных с транзакциями. Событие ConnectComplete возникает после того, как было открыто соединение, событие Disconnect - после закрытия соединения. Во всех случаях обработчику событий передаются указатели на соответствующие объекты и информация, необходимая, для того чтобы оценить, насколько корректно была выполнена та или иная операция. Если значение параметра adStatus равно adStatusOk, то операция окончилась успешно, и нет необходимости анализировать объект Error. Если же этот параметр имеет значение adStatusErrorsOccured, то необходимо анализировать объект Error, а, возможно, и всю коллекцию Errors.
  • Event InfoMessage(pError As Error, adStatus As EventStatusEnum, pConnection As Connection). В отличие от остальных это событие встречается не до и не после выполнения операции соединения, а в момент ее выполнения, и выдает предупреждение о возможных ошибках. Параметры, передаваемые обработчику события, имеют привычный смысл.


  • События объекта Recordset

    Только два объекта ADO обладают событиями - Connection и Recordset. События могут возникать перед началом выполнения той или иной команды, что позволяет проверить возможность ее выполнения и произвести отмену выполнения, не дожидаясь появления ошибки. У объекта Recordset таких событий четыре:
  • Event WillChangeField(cFields As Long, Fields, adStatus As EventStatusEnum, pRecordset As Recordset),
  • Event WillChangeRecord(adReason As EventReasonEnum, cRecords As Long, adStatus As EventStatusEnum, pRecordset As Recordset),
  • Event WillChangeRecordset(adReason As EventReasonEnum, adStatus As EventStatusEnum, pRecordset As Recordset),
  • Event WillMove(adReason As EventReasonEnum, adStatus As EventStatusEnum, pRecordset As Recordset).

  • Эти события возникают перед тем, как выполняемая операция изменит поле записи, саму запись, например при выполнении операций AddNew или Delete, набор записей, например, при выполнении пакетного обновления, или при перемещении курсора на новую запись. Параметры, передаваемые событию, имеют понятный смысл:
  • pRecordset - задает указатель на набор записей,
  • adStatus - определяет состояние, в котором находится операция. Перед началом выполнения операции свойству Status целесообразно присвоить значение adStatusCancel. В обработчике события можно проверить значение этого свойства, если оно имеет значение adStausOk, то операция выполняется нормально.
  • AdReason - определяет выполняемую операцию, являющуюся причиной появления события.
  • cFields и Fields - задают соответственно номер поля в массиве Fields, который содержит поля, подлежащие изменениям.
  • cRecords - номер изменяемой записи.

  • Большая часть событий связана с окончанием выполнения команды. Вот четыре события, которые дополняют события Will:
  • Event FieldChangeComplete(cFields As Long, Fields, pError As Error, adStatus As EventStatusEnum, pRecordset As Recordset),
  • Event RecordChangeComplete(adReason As EventReasonEnum, cRecords As Long, pError As Error, adStatus As EventStatusEnum, pRecordset As Recordset),
  • Event RecordsetChangeComplete(adReason As EventReasonEnum, pError As Error, adStatus As EventStatusEnum, pRecordset As Recordset),
  • Event MoveComplete(adReason As EventReasonEnum, pError As Error, adStatus As EventStatusEnum, pRecordset As Recordset).


  • У всех этих событий помимо уже описанных параметров обработчику события передается еще один параметр pError - указатель на объект Error, позволяющий провести обработку ошибок, если они возникли в ходе выполнения операции.
    У объекта Recordset есть еще три события:
  • Event EndOfRecordset(fMoreData As Boolean, adStatus As EventStatusEnum, pRecordset As Recordset). Событие возникает при выполнении операции MoveNext, когда при перемещении по набору достигнут его конец. В обработчике события можно предусмотреть добавление новых записей в конец набора, и повторить выполнение операции. Заметьте, параметр fMoreData следует установить в этом случае как Variant_True.
  • Event FetchProgress(Progress As Long, MaxProgress As Long, adStatus As EventStatusEnum, pRecordset As Recordset), Event FetchComplete(pError As Error, adStatus As EventStatusEnum, pRecordset As Recordset). Первое из этих событий периодически вызывается, чтобы уведомить сколь много записей было доставлено во время выполнения долгой асинхронной операции. Второе событие возникает по завершении операции доставки записей. Параметр Progress указывает число доставленных записей, а MaxProgress - число ожидаемых для получения записей.


  • Свойства объекта Command

    У объекта Command 9 свойств, из которых 7 - терминальные, а два свойства возвращают коллекции Parameters и Propertiies, что отображено на рис.5.1. Давайте рассмотрим описание всех свойств этого объекта:
  • Property ActiveConnection As Connection. Заметьте, я был не точен, когда говорил, что только два свойства объекта Command возвращают объекты в качестве результата. Свойство ActiveConnection при открытом соединении также возвращает объект Connection, задающий соединение, связанное с командой. Наличие этого свойства отражает связи между объектами. Нельзя выполнить команду, если не установлено соединение. Свойство позволяет получить доступ к объекту Connection, задающему соединение. Здесь есть, однако, некоторая тонкость, из-за которой это свойство имеет двойственную природу. Дело в том, что при закрытом соединении вернуть ссылку на объект невозможно и потому возвращается строка, содержащая описание соединения.
    Свойством ActiveConnection обладают и другие объекты - Recordset и Record.
    Заметьте, следует корректно установить значение этого свойства - на открытый объект Connection или на корректно определенную строку, задающую соединение, еще до того, как будет выполняться метод Execute объекта Command, в противном случае возникнет ошибка.
    Если между двумя выполнениями команды следует изменить соединение, то вначале необходимо свойству ActiveConnection присвоить значение Nothing, в результате Провайдер освободит ресурсы и корректно произведет операцию отсоединения. После чего можно установить новое соединение. Лишь некоторые Провайдеры позволяют проводить изменения в соединении без промежуточного присваивания значения Nothing.
    Изменение соединения оставляет нетронутой коллекцию Parameters, если она заполнялась вручную, значения, устанавливаемые Провайдером, при этом очищаются.
    При закрытии соединения, связанного с командой, значение свойства устанавливается в Nothing. Попытка связать команду с закрытым соединением приводит к ошибке.
  • Property CommandText As String. Устанавливает или возвращает строку, текст которой содержит описание команды для Провайдера. Чаще всего, этот текст содержит описание SQL-оператора, но может быть именем таблицы, относительным URL-адресом или вызовом хранимой процедуры.

    Если свойство Prepered установлено как True и объект Command связан с открытым соединением, то Провайдер транслирует текст команды в исполняемый запрос и сохраняет его в таком виде. Это повышает общую эффективность выполнения при многократных вызовах команды.
    В зависимости от значения свойства CommandType возможно изменение значения свойства CommandText, чтобы привести его в соответствие со спецификой Провайдера.
    Когда необходимо в команде задать ресурс, такой как файл или каталог, то в свойстве CommandText задается URL-адрес ресурса. URL-адрес, использующий http-схему, приводит к автоматическому вызову специального Провайдера - OLE DB Provider for Internet Publishing.
  • Property CommandStream As Stream. Это свойство появилось в последней на момент написания данного текста версии ADO 2.6. Свойства CommandStream и CommandText является взаимоисключающими. Когда устанавливается свойство CommandStream, то свойство CommandText автоматически становится равным пустой строке. Эти два свойства задают альтернативные способы задания описания команды. Свойство CommandText задает описание строкой. Свойство CommandStream устанавливает или возвращает поток, используемый как ввод для объекта Command. Формат потока определяется спецификой Провайдера.
  • Property Dialect As String. Свойство определено в версии ADO 2.6. Оно задает диалект - синтаксис и систему правил, которыми руководствуется Провайдер при разборе входной строки или потока, заданного свойствами CommandText и CommandStream. Свойство содержит GUID, который и задает диалект, используемый Провайдером.
  • Property CommandTimeout As Long. Смысл этого свойства я описывал ранее при рассмотрении объекта Connection. Напомню, значение свойства задает время, в течение которого команда должна быть выполнена. При превышении времени возникнет ошибка.
  • Property CommandType As CommandTypeEnum. Свойство позволяет установить или определить тип объекта Command, который может быть одним из предустановленных перечислением значений. Тип можно и не устанавливать, - значение свойства по умолчанию - adCmdUnknown. Другие возможные значения типа - adCmdFile, adCmdStoredProc, adCmdTable, adCmdTableDirect, adCmdText. Корректное указание типа повышает эффективность исполнения команды, позволяя Провайдеру не выполнять лишней работы по установлению типа. Вместе с тем неверное указание типа будет приводить к появлению ошибки при вызове метода Execute.
  • Property Name As String. Возвращает или устанавливает имя команды. Я уже говорил о способе использования этого свойства и демонстрировал схему его применения при описании объекта Connection. Далее я расскажу о том, что мои попытки задать имя команде потерпели неудачу при работе с Провайдером, обеспечивающим работу с базой данных Access.
  • Property Parameters As Parameters. Является одним из двух свойств-участников объекта Command. В качестве результата возвращает коллекцию параметров, передаваемых Провайдеру, когда команда задает запрос с параметрами или представляет вызов хранимой процедуры, у которой есть параметры. Это свойство является свойством объекта по умолчанию, что, напомню, означает возможность опускать имя свойства при его вызове, так что записи MyCmd.Parameters(i) и MyCmd(i) эквивалентны. Позже я еще буду рассматривать объекты Parameters и Parameter.
  • Property NamedParameters As Boolean. Это булево свойство определено, начиная с версии ADO 2.6. Когда оно имеет значение True, то имена параметров передаются Провайдеру и могут быть использованы при вызовах команды, требующей передачи параметров. При значении False порядок передачи параметров жестко фиксирован.
  • Property Prepared As Boolean. Булево свойство, имеет статус "чтение/запись". Я уже говорил, что установка значения True повышает эффективность исполнения команды при многократных вызовах, за счет того, что Провайдер единожды выполняет подготовительную работу и затем хранит скомпилированную версию команды. Не все Провайдеры поддерживают это свойство.
  • Property Properties As Properties. Еще одно свойство, возвращающее коллекцию объектов. Каждый элемент этой коллекции - объект Property - задает одну из характеристик объекта Command, передаваемых Провайдеру. Этих характеристик, зависящих от Провайдера и объекта довольно много. Вспомните, в предыдущем примере, когда печатались свойства объекта Connection, его коллекция Properties насчитывала 94 элемента.
  • Property State As Long. Я уже рассказывал об этом свойстве, которым обладают многие объекты ADO, и приводил пример работы с ним.


  • Свойства объекта Connection

    У объекта Connection имеется 13 свойств, два из которых являются свойствами-участниками и возвращают коллекции объектов, остальные являются терминальными свойствами. Свойства, являющиеся объектами, показаны на рис.5.1, где приводится объектная модель ADO. Сейчас же давайте рассмотрим все свойства - терминальные и нетерминальные. Приведу краткое описание свойств:
  • Property Attributes As Long. Свойство определяет характеристики соединения и может использоваться как для чтения, так и для записи, что позволяет задать или выяснить, каким набором характеристик обладает объект Connection. Значением свойства является сумма значений устанавливаемых характеристик, значение каждой из которой задается соответствующей константой из перечисления XactAttributeEnum. По умолчанию никакие характеристики не задаются и значение этого свойства равно 0. Следует быть осторожным при задании свойства, поскольку значение, представляющее сумму значений несовместимых констант, приводит к возникновению ошибки. Свойством Attributes обладают и другие объекты ADO - Parameter, Field, Property.
  • Property CommandTimeout As Long, Property ConnectionTimeout As Long. Первым из этих свойств обладают два объекта - Connection и Command. Свойства задают в секундах интервал времени, в течение которого должен выполниться метод Execute, когда он вызывается указанными объектами. Если при выполнении метода произойдет задержка сверх указанного времени, то установление соединения или выполнение команды, заданной методом, будет прервано и возникнет ошибка. По умолчанию значение ConnectionTimeout - максимальное время для установления соединения равно 30 секундам, для выполнения команды - 15 секунд. Оба свойства имеют статус "чтение/запись". Заметьте, что значение первого свойства, установленное для объекта Connection не наследуется объектом Command, связанного с этим соединением. Чтобы корректно установить значения свойств необходимо, конечно представлять каково может быть максимальное время установления соединения и время выполнения той или иной команды.
  • Property ConnectionString As String. Одно из наиболее важных и постоянно используемых свойств. Свойство имеет статус "чтение/запись". Информация, заданная в тексте строки, используется для установления соединения с источником данных. Синтаксически строка соединения представляет пары вида: аргумент = значение, разделенные символом ";" (точка с запятой). ADO поддерживает пять аргументов, но в зависимости от Провайдера ему могут предаваться и другие аргументы, которые никак не обрабатываются средствами ADO и передаются непосредственно Провайдеру. Вот список общих для всех Провайдеров аргументов, поддерживаемых ADO:
  • Provider - имя Провайдера, с которым устанавливается соединение.
  • File Name - имя файла, содержащего предустановленную информацию о соединении, передаваемое провайдеру,.
  • Remote Provider - имя Провайдера, используемое при открытии соединения на клиентской стороне. Используется только при работе со службой RDS.
  • Remote Server - имя сервера (путь), используемое при открытии соединения на клиентской стороне. Используется только при работе со службой RDS.
  • URL - адрес, идентифицирующий такие ресурсы, как файл или каталог. Заметьте, свойства, установленные в строке соединения, могут измениться после открытия соединения, поскольку может произойти трансляция аргументов в форму, предусмотренную Провайдером.
  • Property CursorLocation As CursorLocationEnum. Свойство позволяет сделать выбор между различными библиотеками курсоров, доступных Провайдеру. Обычно применяемые значения констант - adUseClient и adUseServer позволяют выбрать библиотеку на стороне клиента или сервера. Курсоры, возвращаемые методом Execute, наследуют заданную установку. Также поступают и объекты Recordsets, наследуя установку из связанного с ними соединения.
  • Property DefaultDatabase As String. Задает имя базы данных по умолчанию. Свойство имеет статус "чтение/запись".
  • Property Errors As Errors. Свойство имеет статус "только чтение". Возвращает коллекцию объектов Error. Каждый из этих объектов создается автоматически Провайдером, если возникает ошибка при выполнении той или иной команды. Вся коллекция связана только с одной командой. При выполнении новой команды при появлении первой ошибки старая коллекция Errors очищается и начинает создаваться заново. Метод Clear позволяет организовать принудительную чистку коллекции. Заметьте, объекты Error создаются только при выполнении кода Провайдера. Ошибки ADO, возникающие в коде VB/VBA, приводят к появлению события OnError и могут быть обработаны как все динамические ошибки периода выполнения.
  • Property IsolationLevel As IsolationLevelEnum. Свойство имеет статус "чтение/запись". Специальные константы задают так называемый уровень изоляции. По умолчанию значение константы - adXactChaos. При удаленном доступе на клиентской стороне допустимо только значение - adXactUnspecified.
  • Property Mode As ConnectModeEnum. Свойство, значения которого задаются перечислением и определяют статус модификации данных. Это свойство имеют объекты Connection, Record и Stream. Для объекта Connection значение по умолчанию - adModeUnknown, для объекта Record - adModeRead, для Stream возможно то или другое значение в зависимости от того, как объект связан с источником данных. Установить значение этого свойства можно только тогда, когда объект закрыт. Изменить статус уже открытого объекта невозможно.
  • Property Properties As Properties. Второе из свойств объекта Connection, возвращающее в качестве результата объект - коллекцию Properties. Этим же свойством обладают и другие объекты - Command и Recordset. Каждый из объектов Property содержит характеристику, передаваемую Провайдеру.
  • Property Provider As String. Строка, задающая имя Провайдера. Это имя может быть установлено и другим способом, например, как один из аргументов при задании свойства ConnectionString.
  • Property State As Long. Многие из объектов ADO обладают этим свойством, которое имеет статус "только для чтения" и определяет совокупность состояний объекта - открыт он или закрыт, а для объектов выполняющих асинхронный метод - состояние выполнения. Поэтому значение свойства задается суммой значений соответствующих констант. Возможные значения констант следующие: adStateClosed = 0, adStateConnecting = 2, adStateExecuting = 4, adStateFetching = 8, adStateOpen = 1. Если свойство возвращает, например, значение 5, то это означает, что объект открыт, а выполняемый метод, например, Execut находится в активном состоянии.
  • Property Version As String. Свойство имеет статус "только для чтения" и возвращает версию библиотеки ADODB.


  • Свойства объекта Recordset (продолжение)

  • Property CursorLocation As CursorLocationEnum, Property CursorType As CursorTypeEnum, Property LockType As LockTypeEnum - группа свойств, связанных с курсором. Общее представление о курсоре уже дано, так что осталось сообщить лишь небольшие детали. Свойство позволяет задать положение курсора. Два его возможных значения: adUseClient и adUseServer определяют, на какой стороне идет работа с курсором - на клиентской или на серверной стороне. Свойство CursorType позволяют определить или задать тип курсора. Я уже определял четыре возможных значения, доступных при задании типа курсора. Свойство LockType позволяет управлять закрытием доступа. На серверной стороне можно задать одно из трех возможных значений этого свойства: adLockReadOnly, adLockOptimistic, adLockPessimistic. На клиентской стороне возможно только одно значение - adLockBatchOptimistic.
  • Property DataSource As Unknown, Property DataMember As String. Два взаимосвязанных свойства. Первое из них задает источник данных для объекта Recordset. Данные в этом источнике могут представлять совокупность именованных разделов, называемых элементами источника данных - DataMember. Второе свойство и определяет конкретный элемент источника данных. В одной из последующих глав, посвященных компонентам OWC (Office Web Components), я расскажу подробнее и приведу примеры работы с подобными источниками данных и их элементами.
  • Property EditMode As EditModeEnum. Свойство указывает статус редактирования текущей записи. Его возможные значения: adEditAdd, adEditDelete, adEditInProgress, adEditNone указывают, была ли запись добавлена, удалена, операция редактирования записи не завершена или не применялась. Анализируя значение этого свойства, можно принять правильное решение и вызвать, например, метод Update или CancelUpdate, чтобы принять или отменить результаты редактирования.
  • Property Fields As Fields. Одно из немногих свойств, возвращающих объект - коллекцию полей, элементами которой являются объекты класса Field. Каждый такой объект задает одно поле в записи, а вся коллекция - совокупность всех полей. Позже я подробнее расскажу об этих объектах, о том, что можно делать с самой коллекцией, можно ли добавлять и удалять поля, о свойствах и методах, доступных при работе с отдельным полем.
  • Property Filter As Variant. Это свойство позволяет наложить фильтр на набор записей и создать новый набор, состоящий из записей, удовлетворяющих условию фильтра. При задании корректного фильтра текущей записью становится первая запись нового набора, перемещение по набору идет только по отфильтрованным записям, остальные записи как бы скрываются и становятся недоступными до тех пор, пока фильтр не будет отменен. Задать фильтр можно тремя различными способами:
  • Строкой критерия - представляет набор из элементарных условий, связанных логическими операциями AND и OR. Каждое элементарное условие имеет вид: <Имя поля> Operator <Значение>, где Operator является одной из операций сравнения или оператором Like, задающим сравнение с образцом. Образец может включать обычные для образцов метасимволы "?" и "*". Заметьте, на операциях AND и OR не задан приоритет, по этой причине для сложных критериев необходимо правильно задать скобочную запись критерия. Критерий должен представлять дизъюнкцию конъюнктов, это означает, что выражения в скобках должны соединяться знаком AND, а сами скобки знаком операции OR.
  • Массивом закладок - каждая закладка, как я говорил выше, определяет одну запись, а их массив задает все записи, которые должны быть отобраны в результате применения фильтра.
  • Стандартным фильтром - константой из перечисления FilterGroupEnum. Возможные значения в этом перечислении: adFilterAffectedRecords, adFilterConflictingRecords, adFilterFetchedRecords, adFilterPendingRecords, adFilterNone.


  • Приведу пример создания фильтра:
    Public Sub CreateFilter() 'Работа с фильтром
    'Создать соединение CreateConnection 'Создать и выполнить команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "SElect * From [Заказы]" Cmd1.CommandType = adCmdText Cmd1.Prepared = True ' вызов команды на исполнение Set Rst1 = Cmd1.Execute
    'Задать фильтр Rst1.Filter = " [Сотрудник] = 'Петрова О.' AND " _ & "[Заказчик] = 'Книжная лавка'" With Rst1 .MoveFirst Do While Not .EOF 'Обработка текущей записи Debug.Print "Стоимость заказа = ", Rst1!Стоимость .MoveNext Loop End With End Sub
    По-видимому, в дополнительных комментариях текст процедуры не нуждается. Не буду приводить и отладочных результатов, замечу, что все работало правильно, и на печать была выдана стоимость всех заказов, удовлетворяющих условию фильтра. Продолжим рассмотрение свойств:
  • Property Index As String. Устанавливает или возвращает строку, задающую имя индекса. Поля в таблицах базы данных могут быть индексированы. Система индексов позволяет быстрее находить нужные записи. Свойство Index используется в сочетании с методом поиска Seek для быстрого поиска записей в наборе по значениям индексируемого поля. Замечу, что не все Провайдеры позволяют работать с индексами, в частности Провайдер, обеспечивающий работу с базой данных Access, не поддерживает индексы.
  • Property MarshalOptions As MarshalOptionsEnum. Свойство позволяет управлять транспортировкой измененных записей набора на сервер. Применяется оно только на клиентской стороне и позволяет указать, нужно ли передавать серверу все записи или только те записи, которые подверглись изменениям. Свойство имеет всего два возможных значения: adMarshalAll, adMarshalModifiedOnly.
  • Property MaxRecords As Long. Позволяет ограничить число записей, одновременно передаваемых Провайдером от источника данных объекту Recordset. По умолчанию свойство имеет значение 0, указывающее, что ограничений на число передаваемых записей не устанавливается.
  • Property Properties As Properties. Второе из свойств, возвращающее объект - коллекцию Properties объекта Recordset. В этой большой коллекции, насчитывающей около сотни элементов - объектов класса Property собраны характеристики объекта Recordset, меняя которые можно управлять объектом. Многие из них задаются по умолчанию, обращение к ним позволяет выяснить текущие свойства объекта.
  • Property Sort As String. Если свойство Filter позволяет провести фильтрацию набора записей, то свойство Sort позволяет провести сортировку по одному или нескольким полям в порядке возрастания или убывания значений. Часто бывает удобнее сортировку и фильтрацию задавать не в операторе SQL при формировании объекта Recordset, а выполнять эти операции по мере необходимости, работая с уже созданным объектом и используя возможности, предоставляемые его свойствами. Вот пример подобной сортировки:


    Public Sub CreateSortedSet() 'Сортировать полученный набор данных 'Создать соединение CreateConnection 'Создать и выполнить команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Заказчики]" Cmd1.CommandType = adCmdText Cmd1.Prepared = True ' вызов команды на исполнение Set Rst1 = Cmd1.Execute 'Задание критерия сортировки Rst1.Sort = "Город ASC" 'Работа с отсортированным набором With Rst1 .MoveFirst Do While Not .EOF 'Обработка текущей записи Debug.Print "Организация = ", Rst1!Название, Rst1!Город .MoveNext Loop
    End With End Sub
    Для простоты я ограничился сортировкой по одному полю, указав ASC - возрастающий порядок следования значений по этому полю.
  • Property Source As Variant. Свойство позволяет задать или выяснить, что является источником данных для объекта Recordset. При установке значением свойства может быть ссылка на объект Command или строка. Возвращается всегда только строка, если источником данных является объект Command, то строка содержит описание источника - соответствующий SQL-оператор. Источником может быть также имя таблицы или имя хранимой процедуры.
  • Property State As Long, Property Status As Long. Я уже рассказывал о свойстве State, составное значение которого позволяет определить, открыт или закрыт объект и состояние выполняемого метода. Свойство Status в отличие от свойства State характерно только для объекта Recordset и определяет статус текущей записи по отношению к пакетным обновлениям или другим групповым операциям. Также как и для свойства State, его значение представляет сумму значений констант, в совокупности, задающих статус записи. Константы принадлежат перечислению RecordStatusEnum, задающему 18 различных значений, приведу лишь несколько из них: adRecNew, adRecInvalid, adRecUnmodified. Заметьте, как обычно в таких случаях, значения констант задаются степенями числа 2, так что их сумма всегда представляет уникальное значение и однозначно определяет все слагаемые. Программисты говорят в таких ситуациях, что значение свойства задает маску.
  • Property StayInSync As Boolean. Система ADO позволяет создавать иерархические наборы записей, в которых запись объекта Recordset может ссылаться на своих потомков. Такие иерархические наборы поддерживаются специальной службой - Microsoft Data Shaping Service for OLE DB. Булево свойство StayInSync задается только для иерархических наборов и позволяет управлять навигацией по иерархии записей в таком наборе. Значение True указывает, что при изменении позиции родительской записи раздел (Chapter), содержащий потомков, будет указывать на новое положение родителя. При значении False ссылка не меняется, так что потомки получают нового родителя.


  • Свойства объекта Recordset

    Объект Recordset имеет несколько десятков свойств. Попробуем разобраться в них:
  • Property AbsolutePage As PositionEnum, Property AbsolutePosition As PositionEnum, Property PageCount As Long, Property PageSize As Long, Property RecordCount As Long - группа свойств, определяющих нумерацию записей в наборе. Поскольку объект Recordset может определять достаточно большой набор записей, то, иногда целесообразно придать ему определенную структуру и выделить в нем страницы, на каждой из которых находится фиксированное число записей. Исключением является последняя страница, на которой записей, естественно, может быть меньше. Свойство PageSize определяет число записей на странице, по умолчанию значение этого свойства равно 10. Свойство PageCount задает число страниц, AbsolutePage - номер страницы, на которой расположена текущая запись набора. Значением этого свойства является целое в интервале от 1 до PageCount или значение из перечисления PositionEnum. Свойство RecordCount задает число записей в наборе, а свойство AbsolutePosition - порядковый номер записи, значением которого есть целое в и нтервале от 1 до RecordCount или одно из значений перечисления PositionEnum. Это перечисление содержит всего три значения: adPosBOF, adPosEOF, adPosUnknown, первые два из которых определяют позицию в начале и конце набора (перед первой и после последней записи набора), а третье значение задает неопределенную позицию. Заметьте, что пользоваться номерами записей для их идентификации следует с большой осторожностью, поскольку нумерация изменяется, когда в набор добавляются или из него удаляются записи. Для идентификации записей используются закладки, о которых будет сказано чуть ниже. Не все Провайдеры полностью поддерживают эти свойства.
  • Property ActiveCommand As Object, Property ActiveConnection As Variant. Свойства отражают связи между объектами ADO. Значением этих свойств является ссылка на объект Command, породивший набор записей и ссылка на соединение, при котором команда выполнялась. Если соединение закрыто, то выдается строка, описывающая соединение. Если набор записей создавался без явного выполнения команды, то возвращается ссылка на пустой объект Null.
  • Property BOF As Boolean, Property EOF As Boolean. Их имена пришли из соответствующих названий при работе с файлами - Begin Of File (BOF) и End Of File (EOF). Два булевых свойства, позволяющих определить достигнут ли конец набора записей при его последовательном просмотре в том или ином направлении. Вот пример двух классических схем прохода набора записей:

    Public Sub AllRecords() ' Две схемы прохода по набору записей With Rst1 'Схема1: От начала к концу набора .MoveFirst Do While Not .EOF 'Обработка текущей записи Debug.Print Rst1!Название .MoveNext Loop 'Схема2: От конца к началу набора .MoveLast Do 'Обработка текущей записи Debug.Print Rst1!Название .MovePrevious Loop Until .BOF End With End Sub
    Я запустил на выполнение эту процедуру сразу по окончании работы процедуры CreateCommands из предыдущего примера и получил корректные результаты.
  • Property Bookmark As Variant. Часто в наборе записей необходимо иметь некоторое количество выделенных записей и периодически к ним обращаться. Как я уже говорил, номера записей для этой цели не годятся, поскольку они изменяются вместе с изменением самого набора. Для идентификации записей используются закладки, однозначно идентифицирующие запись при всех изменениях набора.
    Когда открывается набор записей, то все записи имеют уникальные закладки. Сохранение закладок - дело рук программиста. Вы можете сохранить закладку в собственной переменной типа Variant, используя значение свойства Bookmark. В тот момент, когда значение этой переменной будет присвоено свойству Bookmark набора записей, автоматически текущей станет запись с указанной закладкой. Вот пример, иллюстрирующий работу с закладками:
    Public Sub CreateBookmarks() 'Работа с закладкой Dim MyBookmark As Variant With Rst1 .MoveFirst Do While Not .EOF 'Обработка текущей записи If Rst1!Название = "Книжная лавка" Then 'Создаю закладку MyBookmark = .Bookmark End If .MoveNext Loop 'переход к записи с закладкой .Bookmark = MyBookmark Debug.Print Rst1!Название End With End Sub
    Заметьте, по окончании цикла, текущая запись не определена, поскольку истинно свойство EOF, и это означает, что указатель сместился за последнюю запись. Но как только свойству Bookmark присвоено значение сохраненной закладки, текущей становится нужная нам запись.
  • Property CacheSize As Long. Я уже говорил, что основная работа с записями базы данных ведется во временной памяти, называемой буфером или кэшем. Несмотря на название, это свойство явно не задает размер памяти буфера. Оно должно быть целым значением в интервале от 1 до Maximum Open Rows. Правая граница интервала задается одноименным свойством из коллекции Properties объекта Recordset. Заметьте, значение 0 приводит к ошибке, по умолчанию значение свойства равно 1. Свойство позволяет указать число записей, помещаемых Провайдером в кэш при одном обращении. Заметьте, идет постоянный своппинг (обмен данными) в процессе работы, поскольку, когда достигнута последняя запись в буфере и нужна следующая запись, будет идти подкачка в буфер очередной порции. Значение свойства настраивается в течение жизни объекта Recordset.


  • Универсальный доступ к данным и ADO

    Microsoft предлагает своим пользователям и, прежде всего, разработчикам корпоративных приложений единую стратегию доступа к данным - Universal Data Access (UDA), не зависящую, в высокой степени, от типа используемых хранилищ данных, применяемых инструментальных средств и языков программирования. В основе этой стратегии лежат компоненты доступа к данным - Microsoft Data Access Components (MDAC). Три базисных интерфейса и, соответственно, три группы объектов определяют UDA и MDAC:
  • Низко уровневый, высоко эффективный по производительности COM-интерфейс баз данных - интерфейс OLE DB (OLE Data Base), позволяющий получать доступ как к реляционным (табличным), так и не реляционным (иерархическим и потоковым) базам данных.
  • Низко уровневый, высоко производительный интерфейс ODBC (Open Data Base Connectivity), специально спроектированный для связи с различными реляционными базами данных и структурированным языком запросов SQL (Structured Query Language).
  • Интерфейс высокого уровня ADO, являющийся надстройкой над интерфейсом OLE DB, позволяющий получать легкий доступ к данным при работе не только на VC, но и на VB/VBA и VB Script.

  • Начиная с Windows 2000, MDAC является частью операционной системы. Microsoft создала специальный Web-узел - Microsoft Universal Data Access Web site, на котором можно найти последние версии компонент и свободно произвести обновление операционной системы от Windows 95 до Windows Me.
    Поскольку для VBA - разработчика, как я уже говорил, наибольший интерес представляет интерфейс верхнего уровня - ADO, то я ограничусь лишь штрихами к портрету низкоуровневых интерфейсов.

    Мир объектов Excel 2000

    ADO и DAO

    До появления объектов ADO при работе с базой данных Access использовались объекты DAO (Data Access Objects), являющиеся предшественниками ADO. Они и сейчас, естественно, могут использоваться в уже разработанных и действующих приложениях. В новых разработках следует отдать предпочтение объектам ADO. Объектные модели ADO и DAO хотя и имеют много общего, но имеют и существенные различия. Так что вряд ли целесообразно переходить к новым объектам в уже работающих приложениях, если это не вызвано особыми причинами. В этом разделе я приведу сравнение объектных моделей и другие сведения, облегчающие переход от DAO к ADO в случае, если такая необходимость возникла.

    Анализ ошибок в обработчике событий

    Я хочу теперь рассмотреть ситуации, когда при выполнении операции по тем или иным причинам возникают ошибки. Мы рассмотрим, как выглядят обработчики ошибок в таких ситуациях, как можно проанализировать содержимое объектов Error, созданных Провайдером и переданных обработчику ошибок, как можно исправить ситуацию и продолжить выполнение.
    Первым делом я смоделировал ситуацию, приводящую к ошибке в момент создания соединения, для чего сменил свойство "Provider", не позаботившись о корректном задании характеристик этого Провайдера.
    Вот как выглядит модернизованная процедура, организующая соединение:
    Public Sub ConnectionWithEvents() 'Создание соединения с тестовой базой данных Access 'Объект с событиями Dim imEvCon As New MyEventsADO ' Связывание объекта с событиями Set imEvCon.EvCon = Con1 Dim strConnStr As String If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение 'Вариант 1 'Конфигурирование соединения Con1 'Con1.Provider = "Microsoft.jet.oledb.4.0" Con1.Provider = "MSDASQL"
    Con1.ConnectionString = "Data Source=c:\!O2000\DsCd\Ch14\dbPP2000.mdb" Con1.CursorLocation = adUseClient 'Открытие соединения On Error GoTo Check Con1.Open Exit Sub Check: 'Повтор операции соединения Resume End Sub
    В этой процедуре следует обратить внимание на следующие моменты:
  • "Правильный" Провайдер, позволяющий работать с базой данных Access закомментирован. Указан Провайдер "MSDASQL", - его вызов приведет к ошибке при установлении соединения. Для удобства восприятия текст этой строки и других строк, требующих внимания, подсвечен.
  • Чтобы иметь возможность написать обработчики событий, возникающих при соединении, введен соответствующий объект imEvCon класса MyEventsADO, о создании которого я говорил выше.
  • Предусмотрена возможность обработки возникающих ошибок. О том, как исправить возникающую ошибку, скажу чуть ниже.

  • В класс MyEventsADO я добавил следующий обработчик события ConnectComplete:
    Private Sub EvCon_ConnectComplete(ByVal pError As ADODB.Error, _ adStatus As ADODB.EventStatusEnum, _ ByVal pConnection As ADODB.Connection) 'Печать параметров обработчика события MsgBox "Обработчик события ConnectComplete! " & vbCrLf & _ "Строка соединения = " & _ pConnection.ConnectionString & vbCrLf & _ "Статус выполняемой операции = " & adStatus 'Печать параметров объекта Error If Not (pError Is Nothing) Then With pError MsgBox "Описание ошибки:" & .Description & vbCrLf & _ "Код Провайдера:" & .NativeError & vbCrLf & _ "Константа, идентифицирующая ошибку:" & .Number & vbCrLf & _ "Имя объекта:" & .Source & vbCrLf & _ "ANSI SQL код:" & .SqlState End With End If If adStatus = adStatusErrorsOccurred Then 'Исправление ошибки pConnection.Provider = "Microsoft.jet.oledb.4.0" End If 'Анализ коллекции Errors Dim myErr As Error Dim MyCon As Connection Set MyCon = pConnection Debug.Print MyCon.Errors.Count For Each myErr In MyCon.Errors Debug.Print myErr.Description Next myErr MyCon.Errors.Clear Debug.Print MyCon.Errors.Count MyCon.Errors.Refresh Debug.Print MyCon.Errors.Count For Each myErr In MyCon.Errors Debug.Print myErr.Description Next myErr End Sub

    Прокомментирую работу этой процедуры:

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

    Анализ ошибок в обработчике событий

    Рис. 6.3.  Окна сообщений, появляющиеся при установлении соединения

    Обратите внимание, статус показывает, что в момент соединения возникла ошибка, поэтому строка соединения короткая и не содержит информации, добавляемой Провайдером.
  • Следующая часть обработчика ConnectComplete работает тогда, когда обработчику передается объект Error. В функции MsgBox, окно которой также показано на рис. 6.3, формируется информация о свойствах объекта Error, в частности, описание ошибки показывает, что ODBC-Провайдер не сумел найти источник данных.
  • В обработчике события "исправляется" ошибка и устанавливается "правильный" Провайдер. Конечно, в реальной ситуации для этого мог понадобиться диалог с пользователем, но я решил упростить демонстрационный пример. В процедуре есть еще одна часть, связанная с выдачей отладочной информации, но о ней чуть позже, а пока скажу, что произойдет по окончании работы обработчика событий. Понятно, что когда управление вернется в процедуру ConnectionWithEvents в оператор Con1.Open, вызвавший событие, то из-за возникновения ошибки в охраняемом блоке управление будет перехвачено и передано на метку Check. Оператор Resume заставит повторить открытие соединения, но теперь уже свойства соединения заданы корректно. Снова работает процедура ConnectComplete, но в ситуации, когда ошибка не возникает. В этот раз будет работать только первая функция MsgBox, которая уведомит о нормальном завершении и выдаст подробную информацию в строке соединения.
  • В заключительной части процедуры ConnectComplete выводится на печать отладочная информация о коллекции Errors объекта Connection. Я специально поместил ее в эту процедуру, чтобы обратить Ваше внимание на тот факт, что при обработке события Complete коллекция Errors объекта Connection еще не сформирована, она создается лишь после завершения работы этой процедуры. Поэтому, как ни странно, но при первом вызове, когда ошибка есть, коллекция Errors пуста, а во втором вызове, когда ошибки нет, коллекция существует, сохраняя историю первого вызова. Заметьте, по этой причине было бы разумнее с содержательной точки зрения поместить эту группу операторов в блок с меткой Check. Иллюстрацией к этому тексту являются результаты отладочной печати:
  • 0 0 0 1 [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified 0 0


    Еще одна ошибка, которую я решил смоделировать, возникает при работе с объектом Recordset, - некорректно формируется значение поля при изменении записи. К уже имеющемуся обработчику события WillChangeField я добавил обработчик события ChangeFieldComplete. Поскольку общая схема экспериментов остается такой же, как и для объекта Connection, то я приведу тексты процедур без дополнительных комментариев. В уже описанной процедуре CreateEvents я заменил оператор

    !Цена = !Цена * 2

    на оператор

    !Цена = "По Договоренности"

    Эта замена приводит к ошибке несоответствия типов. Вот текст обработчика события ChangeFieldComplete:

    Private Sub EvRst_FieldChangeComplete(ByVal cFields As Long, _ ByVal Fields As Variant, ByVal pError As ADODB.Error, _ adStatus As ADODB.EventStatusEnum, _ ByVal pRecordset As ADODB.Recordset) 'Печать параметров обработчика события MsgBox "Номер поля = " & cFields & vbCrLf & _ "Значение поля = " & Fields(cField) & vbCrLf & _ "Позиция записи в наборе = " & _ pRecordset.AbsolutePosition & vbCrLf & _ "Статус выполняемой операции = " & adStatus 'Печать параметров объекта Error If Not (pError Is Nothing) Then With pError MsgBox "Описание ошибки:" & .Description & vbCrLf & _ "Код Провайдера:" & .NativeError & vbCrLf & _ "Константа, идентифицирующая ошибку:" & .Number & vbCrLf & _ "Имя объекта:" & .Source & vbCrLf & _ "ANSI SQL код:" & .SqlState End With End If 'Анализ коллекции Errors Dim myErr As Error Dim MyCon As Connection Set MyCon = pRecordset.ActiveConnection Debug.Print MyCon.Errors.Count For Each myErr In MyCon.Errors Debug.Print myErr.Description Next myErr

    End Sub

    Вот как выглядит серия появляющихся окон функции MsgBox, вызываемой в процедурах: CreateEvents, WillChangeField, ChangeFieldComplete, при попытке изменить поле "Цена":

    Анализ ошибок в обработчике событий

    Рис. 6.4.  Серия окон, появляющихся при попытке изменить значение поля.

    Возможно, полезно взглянуть и на информацию, появляющуюся в окне отладки:

    0 1 Operation was canceled. 1 Operation was canceled. 1 Multiple-step operation generated errors. Check each status value.

    Добавление индексов

    В следующем примере в таблицу "Книги" я добавляю два индекса, один из них связан с полем "Автор", другой - "Название_книги". Приведу процедуру, решающую задачу:
    Public Sub AddIndex() 'Процедура добавляет индексы в таблицу "Книги" Dim myT As ADOX.Table 'таблица Dim myC1 As Column, myC2 As Column 'поля таблицы Dim myInd1 As New Index, myInd2 As New Index 'индексы таблицы 'Установить соединение с базой NewDB CreateConnection Cat1.ActiveConnection = Con1 'Связывание объекта Table с таблицей "Книги" Set myT = Cat1.Tables("Книги") Set myC1 = myT.Columns("Автор") Set myC2 = myT.Columns("Название_книги") 'Формирование индексов myInd1.Columns.Append (myC1) myInd1.Name = "Author" myInd1.PrimaryKey = False myInd1.Unique = False myInd1.Clustered = False
    myInd2.Columns.Append (myC2) myInd2.Name = "Book" myInd2.PrimaryKey = False myInd2.Unique = False myInd2.Clustered = False
    'Добавление индексов Call myT.Indexes.Append(myInd1) Call myT.Indexes.Append(myInd2) 'Отладочная печать Debug.Print "Число индексов =", myT.Indexes.Count For Each myInd1 In myT.Indexes Debug.Print "Имя индекса -", myInd1.Name Next myInd1
    End Sub
    Результаты отладочной печати для нее следующие:
    Число индексов = 3 Имя индекса - Код_книги Имя индекса - Author Имя индекса - Book
    Заметьте, индексов три, поскольку первый из них был создан автоматически в момент добавления ключа в таблицу.
    Думаю, что в особых комментариях данная процедура не нуждается, поскольку работа с индексами ведется по той же схеме, что и при работе с ключами таблицы. Чтобы убедиться, что все работает должным образом, взгляните, как выглядит таблица "Книги", открытая в режиме конструктора:
    Добавление индексов

    Рис. 6.9.  Программно созданная таблица "Книги"

    Добавление полей и ключа

    В следующей процедуре в уже созданную таблицу добавляется новое поле и новый составной ключ. Приведу текст процедуры:
    Public Sub InsertItems() 'Эта процедура добавляет элементы в уже созданную таблицу Dim myT As Table Dim myC As New Column Dim myK As New Key Dim myInd As Index 'Установить соединение с базой NewDB CreateConnection Cat1.ActiveConnection = Con1 'Связывание объекта Table с таблицей "Книги" Set myT = Cat1.Tables("Книги") 'Добавление поля myC.ParentCatalog = Cat1 myC.Name = "Художник" myC.Attributes = adColNullable Call myT.Columns.Append(myC)
    'Добавление составного ключа myK.Columns.Append (myT.Columns("Автор")) myK.Columns.Append (myT.Columns("Название_книги")) myK.RelatedTable = "myT" myK.Type = adKeyUnique myK.Name = "AandB" Call myT.Keys.Append(myK) End Sub
    Все эти добавления в таблицу возможны и проходят без препятствий. Заметьте, добавление составного ключа приведет к добавлению элемента в коллекцию индексов. Взгляните, как выглядит измененная таблица "Книги". Здесь же на рисунке можно увидеть и открытое окно с индексами таблицы, в котором отображается введенный составной ключ.
    Добавление полей и ключа

    увеличить изображение
    Рис. 6.10.  Изменения полей и индексов таблицы "Книги"

    Другие объекты модели ADO

    Свойства, методы и события основных объектов ADO - Connection, Command, Recordset, Error уже рассмотрены. В примерах появлялись и другие объекты - Field, Parameter, Property и их коллекции. Рассмотрим свойства и методы этих и других объектов ADO, но уже с меньшей степенью детализации.

    Другие Провайдеры

    Краткие данные о других Провайдерах приведу в таблице.

    Таблица 6.1. Характеристики Провайдеров Название ПровайдераСтрока соединенияНазначение
    >Microsoft OLE DB Remoting Provider"Provider=MS Remote"Позволяет удаленному пользователю подключиться к источнику данных так, как если бы он был локальным пользователем.
    Microsoft OLE DB Simple Provider (OSP)"Provider=MSDAOSP;Data Source=serverName"Позволяет ADO получить доступ к любым данным, для которых Провайдер написан с использованием специального инструментария - OLE DB Simple Provider Toolkit. Такие источники данных требуют лишь базисных средств поддержки OLE DB. Типичным примером данных являются - XML документы.
    Microsoft OLE DB Persistence Provider"Provider=MSPersist"Этот Провайдер позволяет сохранить в файле данные и всю необходимую информацию об объекте Recordset, в том числе и иерархическом. Затем, при необходимости, объект Recordset может быть восстановлен.
    Microsoft Data Shaping Service for OLE DB"Provider=MSDataShape"Поддерживает конструирование иерархических объектов Recordset.
    Microsoft OLE DB Provider for Microsoft Indexing Service"Provider=MSIDXS;Data Source=myCatalog;Locale Identifier=nnnn;"
    Типично указывается только первый параметр. Второй параметр задает имя каталога специальной поисковой службы - Indexing Service. Если параметр опущен, то по умолчанию используется системный каталог. Параметр Locale Identifier указывает предпочтения при форматировании данных, заданные пользовательским языком.
    Поддерживает программный доступ со статусом "только для чтения" к файловой системе и Web-страницам, индексируемых службой Microsoft Indexing Service.
    Microsoft Active Directory Service Interfaces (ADSI) Provider"Provider=ADSDSOObject;User ID=userName;Password=userPassword;"
    to connect to heterogeneous directory services through ADSI. This gives ADO applications read-only access to the Microsoft Windows NT® 4.0 and Microsoft Windows 2000 directory services, in addition to any LDAP-compliant directory service and Novell Directory Services. ADSI itself is based on a provider model, so if there is a new provider giving access to another directory, the ADO application will be able to access it seamlessly.
    Позволяет связаться с гетерогенными службами каталогов - Windows NT 4.0, Windows 2000, Novell Directory Services, LDAP Directory Services.
    Microsoft Cursor Service for OLE DBСтроки соединения нет, поскольку это специальная служба, вызываемая автоматически в зависимости от расположения курсора.Эта специальная служба обеспечивает функции поддержки курсора для различных Провайдеров данных, что обеспечивает единую функциональность в работе ADO. Вызывается служба автоматически, если соответствующим образом задать положение курсора объектов Connection или Recordset: connection.CursorLocation=adUseClient recordset.CursorLocation=adUseClient


    Коллекции Tables, Procedures, Views, Groups, Users

    С объектной точки зрения все эти коллекции устроены одинаково. Они хранят соответствующие элементы классов: Table, Procedure, View, Group, User. У них есть три метода и два свойства. Для добавления нового элемента в коллекцию применяется метод Append(item), удаления - метод Delete(item), для обновления состояния коллекции, учитывающего текущее состояние базы данных - метод Refresh(). Свойство Item позволяет добраться до нужного элемента коллекции, а свойство Count возвращает число элементов в коллекции.
    Некоторая особенность связана с коллекциями Groups и Users. Коллекция Groups объекта Catalog задает все группы базы данных. С другой стороны, как показано на схеме, отражающей связи между объектами (Рис. 7), коллекция Groups связана и с отдельным объектом User. Коллекция групп, связанная с отдельным пользователем, возвращает все группы, в которые входит этот пользователь. Заметьте, прежде чем добавить новую группу с именем name в коллекцию Groups объекта User, элемент с таким именем уже должен находиться в коллекции Groups объекта Catalog. Совершенно симметрично дело обстоит и с коллекцией Users, которая в объекте Catalog описывает всех пользователей, а в объекте Group - пользователей данной группы. Аналогично, прежде чем добавить пользователя в отдельную группу, он должен быть добавлен в коллекцию Users объекта Catalog .

    Коллекция Errors и объекты Error

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

    Коллекция Fields и объект Field

    Свойство Fields объектов Recordset и Record возвращает одноименную коллекцию, элементами которой являются объекты Field, представляющие содержательно поля записи или столбцы данных, пользуясь табличной терминологией. Об этих объектах уже было сказано немало, так что я постараюсь долго на них не останавливаться.
    У коллекции Fields всего два традиционных для всех коллекций свойства - Count и Item, а вот методов гораздо больше. Наряду с методами, о которых я уже говорил при рассмотрении других объектов ADO, такими как Delete, Refresh, Update, CancelUpdate, имеется два специфических метода:
  • Sub Append(Name As String, Type As DataTypeEnum, [DefinedSize As Long], [Attrib As FieldAttributeEnum = adFldUnspecified], [FieldValue]). Метод позволяет создать новое поле и присоединить его к коллекции. При определении поля задается необходимая информация - имя поля, его тип, определяемый размер данных, атрибуты и, возможно, значение, если речь идет об объекте Record. При работе с полями объекта Recordset они должны быть созданы при закрытом объекте Recordset, после этого объект можно открыть и присвоить значения созданным полям. Присоединять поля к объекту Recordset можно только в том случае, если он закрыт и для него не установлено свойство ActiveConnection. Поскольку в реальных ситуациях эти объекты появляются при выполнении метода Open или Execute, когда соединение с источником данных установлено, то присоединять новые поля к объекту Recordset таким способом не удается. Для изменения структуры базы данных используются обычно объекты ADOX, которые будут рассмотрены ниже. Так что, чаще всего, метод Append применяется при работе с полями объекта Record. Добавлять поле к этому объекту можно и тогда, когда он открыт, при добавлении можно указать и значение поля. После добавления поля следует вызвать метод Update, чтобы сохранить сделанные изменения.
  • Sub Resync([ResyncValues As ResyncEnum = adResyncAllValues]). Позволяет обновить значения всех полей у записей, хранящихся в буфере.


  • Коллекция Parameters и объект Parameter

    Объект Command может определять запрос или хранимую процедуру с параметрами. А посему этот объект имеет свойство Parameters, возвращающее одноименную коллекцию, элементами которой являются объекты Parameter. Содержательно, каждый из этих объектов определяет параметр - входной или выходной, который передается или возвращается при вызове хранимой процедуры. Я уже приводил пример вызова запроса с параметром, где появлялись эти объекты.
    Коллекция Parameters устроена достаточно просто. У нее всего два типичных свойства - Item и Count. У коллекции три метода, которые также появлялись в нашем рассмотрении. Вот эти методы:
  • Sub Append(Object As Object). Метод позволяет присоединить объект Parameter, заданный аргументом, к коллекции. Обычно присоединяемый объект создается методом CreateParameter объекта Command. В процедуре CreateCommands, приведенной в предыдущей главе, продемонстрировано применение методов CreateParameter и Append.
  • Sub Delete(Index). Метод позволяет удалить из коллекции параметр, по имени или порядковому номеру, указываемому в аргументе Index.
  • Sub Refresh(). Позволяет обновить содержимое коллекции, получая информацию о параметрах из хранимой процедуры.


  • Коллекция Properties и объект Property

    Многие из объектов ADO имеют свойство Properties, возвращающее одноименную коллекцию элементов Property. Поскольку каждый объект обладает набором свойств, то возникает естественный вопрос, зачем необходимо еще и свойство Properties, которое тоже задает свойства. Дело в том, что свойства разделяются на встроенные и динамические. Динамические свойства зависят от специфики Провайдера, - они то и составляют коллекцию Properties. Каждый объект этой коллекции описывает то или иное динамическое свойство объекта ADO, зависящее от специфики Провайдера и доступное, естественно, только тогда, когда объект открыт. Добавлять или удалять элементы этой коллекции невозможно, - это прерогатива Провайдера.
    С объектной точки зрения коллекция Properties устроена очень просто - у нее всего два типичных свойства: Item, Count и один метод Refresh. Также просто устроен и объект Property - у него всего четыре свойства: Name, Type, Value, Attributes. Они задают имя, тип, значение и некоторые дополнительные характеристики. Примеры работы с этими объектами уже приводились.
    На этом я завершаю утомительное описание объектов ADO. Но нам предстоит еще хотя бы вкратце познакомиться с объектами ADOX.

    Методы объекта Catalog

    У этого объекта три метода:
  • Function Create(ConnectString As String). Позволяет создать новую базу данных. Параметр ConnectString задает строку соединения. Если создание базы данных прошло успешно, то в качестве результата возвращается объект Connection из библиотеки ADODB, задающий соединение с базой. Этот же объект становится значением свойства ActiveConnection объекта Catalog. Естественно, при выполнении метода возникнет ошибка, если Провайдер не поддерживает создание нового источника данных.
  • Function GetObjectOwner(ObjectName As String, ObjectType As ObjectTypeEnum, [ObjectTypeId]) As String.
  • Sub SetObjectOwner(ObjectName As String, ObjectType As ObjectTypeEnum, UserName As String, [ObjectTypeId]).

  • Эти два метода позволяют определить и, соответственно, установить собственника объекта. Параметр UserName метода Set задает имя собственника, которое возвращает метод (функция) Get. Параметры ObjectName и ObjectType задают имя и тип объекта, для которого устанавливается собственник. Наличие этих параметров связано с тем, что методы применимы не только к объекту Catalog. Тип объекта задается константами из перечисления ObjectTypeEnum. Вот их значения: adPermObjColumn, adPermObjDatabase, adPermObjProcedure, adPermObjTable, adPermObjView. Еще одно возможное значение - adPermObjProviderSpecific указывается тогда, когда объект не принадлежит ни к одному из указанных типов и зависит от Провайдера, в этой ситуации тип задается последним параметром ObjectTypeId.
    Чтобы оживить изложение, давайте перейдем к рассмотрению примеров. Для экспериментов с объектами ADOX я создал новый модуль. Глобальные переменные этого модуля будут определять уже знакомые объекты - Connection, Command, Recordset. Но к ним теперь добавится и новый объект, задающий Catalog.
    Option Explicit 'Модуль TestingADOX 'Глобальные переменные Public Con1 As New ADODB.Connection Public Cmd1 As New ADODB.Command Public Rst1 As New ADODB.Recordset Public Strm1 As New ADODB.Stream Public Cat1 As New ADOX.Catalog
    Вот как выглядит первая процедура этого модуля, создающая новую базу данных:
    Public Sub CreateNewDB() 'Создание новой базы данных и открытие соединения с ней Dim strConnStr As String strConnStr = "Provider=Microsoft.jet.oledb.4.0;" & _ "Data Source=c:\!O2000\DsCd\Ch16\NewDB.mdb" Set Con1 = Cat1.Create(strConnStr) Debug.Print Cat1.ActiveConnection Dim Con2 As Connection Set Con2 = Cat1.ActiveConnection Debug.Print Con2.ConnectionString End Sub

    В этой процедуре я создал новую базу данных Access и установил с ней соединение. Заметьте, я ввел два объекта класса Connection, - один из них определяется при вызове метода Create объекта Cat1 класса Catalog, а второй определяется, используя значение свойства ActiveConnection. Отладочная печать в обоих случаях одинакова и дает следующий результат:

    Provider=Microsoft.Jet.OLEDB.4.0;Password="";User ID=Admin;Data Source=c:\!O2000\DsCd\Ch16\NewDB.mdb;Mode=Share Deny None;Extended Properties="";Jet OLEDB:System database="";Jet OLEDB:Registry Path="";Jet OLEDB:Database Password="";Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:New Database Password="";Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False

    В ней отражены все свойства объекта Connection.

    Методы объекта Field

    У этого объекта имеется всего два метода:
  • Sub AppendChunk(Data). Этот метод позволяет формировать "длинные" значения полей, присоединяя очередную порцию данных к текстовому или двоичному значению поля. При первом вызове метода значение, заданное аргументом Data переписывается в поле и становится его значением, при последующих вызовах значение аргумента Data добавляется к значению, хранимому в поле. Метод не применим к полям объекта Record. Аргумент Data задается переменной типа Variant и содержит добавляемые данные. Заметьте, для того чтобы можно было использовать этот метод бит adFldLong в свойстве Attributes должен быть установлен - иметь значение True.
  • Function GetChunk(Length As Long). Метод позволяет получить все значение или нужную порцию большого текста или двоичного кода, хранящегося в поле. Длина порции устанавливается параметром Length. Возвращаемый результат является переменной типа Variant.

  • Приведу теперь пример, в котором анализируются поля объекта Recordset. По изложенным выше причинам я не стал пытаться вводить новые поля и ограничился получением информации о свойствах уже существующих полей записи. Вот код соответствующей процедуры:
    Public Sub CreateField() 'Процедура печатает информацию о существующих полях записи Dim fld As Field Dim df As Object 'Создать соединение CreateConnection 'Создать команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Книги]" Cmd1.CommandType = adCmdText 'Открытие обновляемого объекта Recordset With Rst1 .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic
    .MoveFirst Do While Not .EOF 'Печать данных о полях записи набора If !Название = "Офисное программирование" Then For Each fld In .Fields With fld Debug.Print "Имя поля ", .Name Debug.Print "Фактический размер поля =", .ActualSize Debug.Print "Определяемый размер поля =", .DefinedSize Debug.Print "Точность", .Precision Debug.Print "Число цифр после запятой в числовых полях -", _ .NumericScale Debug.Print "Тип поля", .Type Set df = .DataFormat If df Is Nothing Then Debug.Print "объект - Формат данных -не определен " End If Debug.Print "Статус", .Status Debug.Print "Значение поля до его изменений ", .OriginalValue Debug.Print "Текущее значение в базе данных", .UnderlyingValue Debug.Print "Текущее значение поля в наборе", .Value Debug.Print "Атрибуты =", .Attributes Debug.Print "Число свойств =", .Properties.Count Debug.Print "Имя и значение первого свойства - ", _ .Properties(1).Name, .Properties(1).Value End With Next fld End If .MoveNext Loop 'попытка изменить характеристики полей 'закрываем объект Recordset .Close Set fld = .Fields("Год издания") With fld .DefinedSize = 8 .Type = adDecimal End With '.Fields.Update

    End With End Sub

    Приведу краткий комментарий:

  • Для выбранной записи из набора в окне отладки я печатаю значения всех характеристик для каждого поля записи.
  • Свойство Data Format возвращает ссылку на пустой объект.
  • Попытка изменить метаданные, путем изменения таких характеристик поля, как его тип или определяемый размер, не приводит к успеху. Хотя при закрытом объекте Recordset изменение значений этих характеристик возможно, но метод Fields.Update в данном контексте не работает, по этой причине он закомментирован.


  • Подведем теперь некоторые итоги. Вот что можно делать с объектом Field:

  • Свойство Name позволяет вернуть имя поля, но не позволяет изменить это имя.
  • Свойство Value позволяет не только получить значение поля, но и изменить его.
  • Свойства Type, Precision, NumericScale, DefinedSize позволяют получить, но не изменить метаданные, задающие соответствующие характеристики поля.
  • Свойство ActualSize позволяет определить фактический размер хранимого в поле значения.
  • Определить, какой тип функциональности поддерживается данным полем, используя свойства Attributes и Properties.
  • Работать с длинными полями, формируя или получая их значения порциями, используя методы AppendChunk и GetChunk.
  • Все свойства, задающие метаданные, доступны для изменения при закрытом объекте Recordset, что может быть полезно при динамическом конструировании форм.


  • Методы объекта Stream

    Рассмотрим теперь методы объекта Stream:
  • Sub Cancel(). Как и для других объектов ADO позволяет прервать выполнение асинхронных операций над объектом Stream.
  • Sub Close(). Закрывает объект Stream.
  • Sub CopyTo(DestStream As Stream, [CharNumber As Long = -1]). Позволяет создать копию потока. Объект, представляющий точку назначения, должен быть открыт. Уточню семантику. Метод копирует, начиная с текущей позиции, символы (байты), число которых задано вторым параметром. Если раньше встретится конец потока, то копирование идет от текущей точки до конца потока. Если в потоке назначения есть символы, то они остаются, следуя после скопированной части. Чтобы произвести их отсечение, следует вызвать метод SetEOS для потока назначения.
  • Sub Flush(). Принуждает оставаться содержимому потока в ADO буфере того базового объекта, с которым ассоциирован объект Stream.
  • Sub LoadFromFile(FileName As String), Sub SaveToFile(FileName As String, [Options As SaveOptionsEnum = adSaveCreateNotExist]). Эти методы позволяют загрузить содержимое файла в поток и сохранить поток в файле.
  • Sub Open([Source], [Mode As ConnectModeEnum = adModeUnknown], [Options As StreamOpenOptionsEnum = adOpenStreamUnspecified], [UserName As String], [Password As String]). Один из основных методов, создающий поток. Первый параметр задает источник данных. Это может быть абсолютный URL-адрес, в этом случае параметр имеет следующий синтаксис "URL = scheme://server/folder". Адрес может задавать узел в структуре дерева каталогов файловой системы или системе электронной почты. Источник может быть ссылкой на открытый объект Record. Объект Stream ассоциируется с потоком по умолчанию этого объекта. В наших примерах использовался именно такой способ создания объекта Stream. Источник может и не указываться, тогда создается объект Stream, не ассоциированный с потоком другого объекта. Данные в него могут поступать, например, при загрузке из файла при вызове метода LoadFropmFile или при создании копии потока.
  • Function Read([NumBytes As Long = -1]), Function ReadText([NumChars As Long = -1]) As String, Sub Write(Buffer), Sub WriteText(Data As String, [Options As StreamWriteEnum = adWriteChar]). Эта группа методов позволяет читать и писать в поток заданное число байтов (символов). Методы Read и Write, работающие с байтами, используют переменную типа Variant, а методы ReadText и WriteText используют переменную типа String. Заметьте, речь идет о потоке, поэтому все операции по чтению и записи идут только в одном направлении - от начала к концу потока. Если требуется несколько проходов, то следует заново переоткрывать поток.
  • Sub SetEOS(). Метод устанавливает значение свойства EOS в True, а, главное, делает текущую позицию концом потока, отсекая все оставшиеся символы. Поскольку методы Write, WriteText, CopyTo не производят отсечения, то, чаще всего, они используются в комбинации с методом SetEOS.
  • Sub SkipLine(). Позволяет пропустить одну строку при чтении текстового файла. Используется в цикле, когда нужно пропустить несколько строк.


  • Модификация характеристик

    К сожалению, в Access попытка программно изменить характеристики полей таблицы или ее ключей и индексов невозможна. Я специально написал процедуру, в которой делается такая попытка:
    Public Sub Modify() 'Изменение характеристик таблицы в Access невозможно! Dim myT As Table Dim myC As Column Dim myK As Key Dim myInd As Index 'Установить соединение с базой NewDB CreateConnection Cat1.ActiveConnection = Con1 'Связывание объекта Table с таблицей "Книги" Set myT = Cat1.Tables("Книги") Set myC = myT.Columns("Название_книги") 'myC.DefinedSize = 150 Set myC = myT.Columns("Число_страниц") 'myC.Type = adVarWChar
    Set myK = myT.Keys("Код_книги") 'myK.Type = adKeyUnique Set myK = myT.Keys("AandB") 'myK.Type = adKeyPrimary End Sub
    Как видите, все операторы, производящие изменения закомментированы, поскольку выполнение любого из них приводит к появлению ошибки. Хотя модификация характеристик полей, ключей и индексов таблицы базы данных Access невозможна, удаление самих элементов происходит без затруднений. Это позволяет при необходимости внесения изменений в характеристики элемента, вначале удалить его, а затем ввести заново с новыми характеристиками. Приведу процедуру, выполняющую удаление поля, ключа и индекса:
    Public Sub Removing() 'Удаление элементов таблицы Dim myT As Table 'Установить соединение с базой NewDB CreateConnection Cat1.ActiveConnection = Con1 'Связывание объекта Table с таблицей "Книги" Set myT = Cat1.Tables("Книги") myT.Columns.Delete ("Художник") myT.Indexes.Delete ("AandB") 'myT.Keys.Delete ("AandB") End Sub
    Заметьте, когда я вводил ключ "AandB", то был создан индекс с этим же именем. Удалив этот индекс, я автоматически удалили и ключ. По этой причине последний оператор процедуры закомментирован.
    На этом я закончу рассмотрение объектов, связанных с таблицами базы данных и займусь другими объектами модели ADOX, которые позволяют работать с запросами, представлениями и пользователями.

    Несколько примеров программной работы с таблицами

    Пора привести пример программного создания таблицы и работы с объектами, связанными с таблицами базы данных. В предыдущих примерах уже была создана программно база данных с именем NewDB. Рассмотрим еще одну процедуру из модуля "TestingADOX", в которой в эту базу данных будет добавлена таблица "Книги", уже появлявшаяся в наших примерах. Но теперь создадим эту таблицу программно, используя возможности объектов ADOX. Вот текст процедуры, решающей эту задачу:
    Public Sub CreateTable() 'Создание таблицы "Книги" и присоединение ее к базе данных NewDB 'Создание объектов - таблицы, ее полей и ключа Dim myT As New ADOX.Table 'таблица Dim myC(1 To 6) As New ADOX.Column 'поля таблицы Dim myK As New ADOX.Key 'ключ таблицы 'Установить соединение с базой NewDB CreateConnection Cat1.ActiveConnection = Con1 'Создание объекта Table 'Создание полей таблицы myC(1).ParentCatalog = Cat1 myC(1).Name = "Код_книги" myC(1).Type = adInteger Call myT.Columns.Append(myC(1))
    myC(2).ParentCatalog = Cat1 myC(2).Name = "Автор" Call myT.Columns.Append(myC(2))
    myC(3).ParentCatalog = Cat1 myC(3).Name = "Название_книги" Call myT.Columns.Append(myC(3))
    myC(4).ParentCatalog = Cat1 myC(4).Name = "Год_издания" Call myT.Columns.Append(myC(4))
    myC(5).ParentCatalog = Cat1 myC(5).Name = "Число_страниц" myC(5).Type = adSmallInt Call myT.Columns.Append(myC(5))
    myC(6).ParentCatalog = Cat1 myC(6).Name = "Цена" myC(6).Type = adCurrency Call myT.Columns.Append(myC(6))
    'Создание ключа myK.Columns.Append (myC(1)) myK.RelatedTable = "myT" myK.Type = adKeyPrimary myK.Name = "Код_книги" Call myT.Keys.Append(myK)
    'Присоединение таблицы myT.Name = "Книги" Call Cat1.Tables.Append(myT) End Sub
    А теперь комментарии к этой процедуре:
  • В процедуре создаются, формируются, а затем добавляются к соответствующей коллекции объекты Table, Column и Key. Работа с ними ведется в полном соответствии со схемой, которую я приводил выше. Заметьте, я ввел массив объектов Column, число элементов которого определяется по числу полей, добавляемых в таблицу.
  • Для каждого поля таблицы я задавал лишь три свойства - имя, тип и ссылку на родительский каталог, оставляя значения остальных полей те, которые приняты по умолчанию. Для текстовых полей я не указывал и тип поля, поскольку это тип, принятый по умолчанию.
  • Замечу, что мои попытки указать тип поля в методе Append в момент присоединения поля к коллекции не увенчались успехом в данном контексте. В этом случае все поля создаваемой таблицы в Access имели один и тот же текстовый тип.
  • При формировании ключа - объекта myK я добавил к его коллекции Columns ключевое поле - уже созданный объект myC(1). Заметьте, добавление в коллекцию Columns объекта Key возможно только после того, как объект добавлен в коллекцию Columns объекта Table.
  • Далее для ключа были заданы его имя, тип и ссылка на родительский объект - таблицу myT, после чего мне осталось только присоединить объект к коллекции Keys.
  • Последним шагом было присоединение таблицы с именем "Книги", чьи свойства Tables и Keys уже сформированы, к коллекции Tables объекта Catalog, для которого установлено соединение с ранее созданной базой данных Access с именем NewDB.


  • Объект Catalog

    Центральный объект модели ADOX, задает каталог базы данных. Его метод Create позволяет создать новый источник данных, а свойства позволяют добраться до элементов, определяющих схему базы данных. Не все Провайдеры поддерживают полный набор возможностей этого объекта, определяемых его свойствами и методами. Так что источник данных и его Провайдер определяют, допустимо ли для него использование того или иного свойства или метода объектов ADOX.

    Объект Column

    Коллекция Columns с элементами Column возвращается при вызове одноименного свойства объекта Table, в этом случае она задает поля таблицы. Но эта коллекция появляется и в других случаях. Напомню, ключи и индексы таблицы могут быть составными и состоять из нескольких полей, по этой причине объекты Key и Index также обладают свойством Columns, которое возвращает коллекции полей, задающих составной ключ или составной индекс. Так что объект Column может задавать поле таблицы, ключа или индекса. Этот объект является центральным объектом в мире, связанном с таблицами. Устроен он достаточно просто, у него нет ни методов, ни событий, - есть только свойства.

    Объект Group

    Группы облегчают управление правами доступа. Проще, быстрее и правильнее с содержательной точки зрения задать права группы, чем задавать их индивидуально для каждого пользователя. Хотя, конечно же, есть пользователи, для которых права задаются индивидуально.
    С объектной точки зрения объект Group, во многом, устроен аналогично объекту User, - у него нет метода ChangePassword, поскольку у групп нет паролей, а свойство Groups заменяет симметричное свойство Users. Таким образом, у объекта Group два свойства: Name, Users и два метода - GetPermissions, SetPermission.

    Объект Index

    Если таблица имеет первичный ключ, то она имеет и индекс, который автоматически создается по данному ключу. Однако индексы могут создаваться и независимо от создания ключевых полей. На индексы не накладывается жесткое условие уникальности значений индекса в таблице. Индексы служат для того, чтобы ускорить операции по поиску и сортировке данных таблицы. Платой за это является увеличение памяти, требуемой для индексов и увеличение времени, требуемое на перестроение индексов, когда записи в таблицу добавляются или удаляются. Для больших, редко обновляемых таблиц, к которым часто приходится обращаться для поиска данных, построение индексов может существенно ускорить работу с базой данных. Для больших, часто обновляемых таблиц задание индексов может приводить к потере эффективности. Поэтому разумное введение индексов требует, как правило, проведения специальных исследований и опыта работы у администратора базы данных.
    Объект Index задает индекс таблицы. Он создается с использованием конструктора New и добавляется в коллекцию Indexes методом Append, в соответствии с ранее приведенной схемой. Как и объекты Table и Key он имеет только свойства. Рассмотрим их:
  • Property Name As String. Свойство по умолчанию, задает имя индекса.
  • Property PrimaryKey As Boolean, Property Unique As Boolean, Property Clustered As Boolean. Булевы свойства, позволяющие определить, является ли индекс первичным ключом, возможным ключом с неповторяющимися значениями индекса, составным индексом, состоящим из нескольких полей.
  • Property Columns As Columns. Для составного индекса возвращает коллекцию полей, входящих в индекс.
  • Property IndexNulls As AllowNullsEnum. Позволяет указать, как обрабатываются значения Null, если они встречаются в поле индекса.
  • Property Properties As Properties. Возвращает коллекцию свойств, специфических для используемого Провайдера.


  • Объект Key

    Напомню, ключом таблицы называется совокупность полей, однозначно идентифицирующая каждую запись таблицы. Если для идентификации записи требуется несколько полей, то такой ключ называется составным. Среди возможных ключей выделяется один, называемый первичным ключом (primary key). Access при построении таблицы, если не задается ее ключ, предлагает включить в число полей специальное поле типа "Счетчик". Это поле объявляется ключевым, а его значения строятся автоматически, чаще всего путем увеличения предыдущего значения счетчика на 1 при каждом добавлении новой записи, что и обеспечивает уникальность значений ключа. Однако, заметьте, среди типов полей, задаваемых перечислением DataTypeEnum, значения, соответствующего полю "Счетчик", нет.
    Таблицы в реляционных базах данных связываются между собой, за счет того, что они имеют общие ключевые поля. Поле в связанной таблице называется внешним ключом, если это поле является частью ключа другой таблицы. Таблица с первичным ключом называется основной или базисной, а таблица, содержащая внешний ключ, связанной или связующей. В предыдущих версиях Access все таблицы обязаны были иметь первичный ключ. В Access 2000 связующие таблицы могут и не иметь первичного ключа. Разумно, однако, задавать ключи для всех таблиц базы данных. Ключ, который не является первичным, имеет статус "уникальный" (Unique).
    Объект Key представляет первичный, внешний или уникальный ключ. Создается объект конструктором New, а добавляется в коллекцию методом Append, в соответствии с приведенной выше схемой. С объектной точки зрения объект Key устроен достаточно просто. Также как и многие другие объекты ADOX, он не имеет ни событий, ни методов, - только свойства. Их немного, - всего 6. Вот их описание:
  • Property Name As String. Задает имя ключа.
  • Property Type As KeyTypeEnum. Задает тип ключа. Возможные значения задаются константами перечисления: adKeyPrimary, adKeyForeign, adKeyUnique, определяющими, соответственно, первичный, внешний и уникальный ключ.
  • Property Columns As Columns. Возвращает коллекцию полей для составного ключа.
  • Property DeleteRule As RuleEnum, Property UpdateRule As RuleEnum. Эти два свойства определяют правила (процедуры), выполняемые при удалении или изменении первичного ключа.
  • Property RelatedTable As String. Задает таблицу, которой принадлежит ключ, что позволяет подняться по иерархии объектов.


  • Объект Procedure и коллекция Procedures

    Объект Procedure представляет собой хранимую процедуру. Хранимые процедуры, текст которых, чаще всего, пишется на SQL, хранятся на сервере в оттранслированном виде, что ускоряет их выполнение. Еще одно немаловажное достоинство состоит в том, что облегчается задача программиста, вызывающего хранимую процедуру, когда он пишет свой код на VBA или VBScript, поскольку ему не нужно вникать в тонкости операторов SQL.
    Коллекция Procedures объекта Catalog включает в себя все хранимые процедуры базы данных. Как и у всех коллекций ADOX у нее три метода: Append, Delete, Refresh и два свойства - Item и Count. Тем не менее, есть особенность в создании объекта Procedure и добавлении его в коллекцию. Прежняя схема, используемая при создании объектов Table, Key, Index для него не применима. Этот объект не может быть создан с помощью конструктора New. Для того чтобы добавить в коллекцию новый объект Procedure, следует создать объект Command, задать текст процедуры, используя свойство CommandText, а затем применить метод Append коллекции Procedures. Заметьте, у этой коллекции метод Append имеет два параметра, - первый задает имя хранимой процедуры, второй объект Command, определяющий, по существу, саму процедуру. Используя объекты Procedure, Procedures и Command можно программно создать хранимую процедуру, модифицировать существующую процедуру или удалить процедуру из коллекции.
    Объект Procedure устроен очень просто, - у него всего 4 свойства:
  • Property Name As String. Задает имя объекта. Имеет статус "только для чтения", что объясняется особенностями создания этого объекта.
  • Property DateCreated As Variant, Property DateModified As Variant. Эти два свойства, также имеющие статус "только для чтения", задают дату создания и последней модификации процедуры.
  • Property Command As Variant. Задает объект Command из библиотеки ADODB. В предыдущей главе этот объект был достаточно подробно описан и приведено большое число примеров работы с ним. Свойство является центральным, поскольку именно объект Command определяет суть хранимой процедуры и позволяет создать процедуру.


  • Объект Record

    Этот объект представляет отдельную запись набора Recordset. Поскольку, однако, объект Recordset не имеет свойства Records, возвращающее коллекцию записей, ни свойства Item, возвращающего отдельную запись, то явным способом получить этот объект из набора записей не удается. Некоторые Провайдеры возвращают объект Record в тех случаях, когда в результате запроса результатом является объект Recordset, состоящий из одной записи. Обычно создается этот объект методом Open, имеющим привычный для объектов ADO синтаксис:
    Sub Open([Source], [ActiveConnection], [Mode As ConnectModeEnum], [CreateOptions As RecordCreateOptionsEnum = adFailIfNotExists], [Options As RecordOpenOptionsEnum = adOpenRecordUnspecified], [UserName As String], [Password As String])
    При открытии задается источник данных, соединение, устанавливаются параметры открытия и при необходимости задаются имя пользователя и пароль доступа к данным. Заметьте, источники могут быть различных типов. Объект Record хотя и подобен набору записей, состоящему из одной записи, но имеет все-таки отличающийся набор свойств и методов. Этот объект часто используют при доставке данных из Интернета, поскольку его значениями могут быть файл или каталог. Некоторые Провайдеры поддерживают объекты Record и Stream как альтернативу объекту Recordset или как дополнительные объекты, позволяющие манипулировать данными, поступающими от Провайдеров. В роли Провайдера часто выступает Microsoft OLE DB Provider for Internet Publishing. Этот Провайдер позволяет использовать объект Record для управления данными, такими как каталоги и папки файловой системы, а также сообщениями в системе электронной почты. Источником данных в этом случае выступает абсолютный или относительный адрес URL, сочетаемый с объектом Connection.
    Этот объект с успехом может использоваться при работе с данными, имеющими иерархическую структуру. С одной стороны его родителем может быть объект Recordset. С другой стороны, объект Recordset может быть его потомком, поскольку метод GetChildren объекта Record возвращает в качестве результата объект Recordset. Чуть позже я приведу пример такого взаимодействия этих двух объектов.
    Большая часть свойств объекта Record совпадает со свойствами объекта Recordset. К таковым относятся свойства: ActiveConnection, Source, Fields, Properties, State. То, что эти два объекта имеют свойство Fields, возвращающее коллекцию полей записей, более всего роднит эти объекты. Еще одно свойство Mode, имеющее значениями константы из перечисления ConnectModeEnum, является общим с объектом Connection. Особых свойств у этого объекта два:
  • RecordType задает тип объекта, его значениями являются константы из перечисления RecordTypeEnum: adCollectionRecord, adSimpleRecord, adStructDoc, имена которых достаточно точно отражают смысл типа объекта.
  • ParentURL определяет URL-адрес родителя объекта Record.


  • Методов у объекта Record немного. О центральном методе Open, позволяющем создать объект я уже сказал. Кроме этого есть типичные для всех объектов методы Close и Cancel. Три метода CopyRecord, MoveRecord и DeleteRecord выполняют типичные операции над этим объектом. О специфическом методе GetChildren скажу чуть подробнее. Его синтаксис:

    Function GetChildren() As Recordset

    Записи возвращаемого объекта Recordset являются потомками текущего объекта Record. Например, потомками объекта Record, представляющего собой каталог, могут быть файлы и подкаталоги, содержащиеся внутри родительского каталога.

    Рассмотрим теперь пример, в котором в роли Провайдера выступает Internet Publishing Provider, а участниками будут объекты Record, Recordset и Stream. Я поочередно создам два объекта Record, в первый раз для чтения текстового файла и передачи этих данных объекту Stream. Во втором случае объект Record будет представлять каталог, а созданный на его основе объект Recordset будет содержать имена файлов этого каталога. Вот процедура, решающая эту задачу:

    Public Sub CreateRecord() 'Создание и работа с объектом Record Dim FirstRec As New Record Dim fld As Field Dim strText As String 'Create Connection with Internet Publishing Provider If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение Con1.Open "Provider = MSDAIPP.DSO; " & _ "Data Source = http://serverva/proba2_2/testtext.txt" 'Объекту Record передается текстовый файл FirstRec.Open ActiveConnection:=Con1 'Поля объекта Record Debug.Print "Число полей:", FirstRec.Fields.Count For Each fld In FirstRec.Fields Debug.Print "Имя поля: ", fld.Name, _ "Значение поля:", fld.Value, "Тип:", fld.Type Next fld

    'Создание объекта Stream на основе объекта Record If Strm1.State = adStateOpen Then Strm1.Close Strm1.Open Source:=FirstRec, Mode:=adModeRead, _ Options:=adOpenStreamFromRecord strText = Strm1.ReadText(100) Debug.Print "Текстовый файл:", strText Con1.Close 'Новое соединение и открытие объекта Record 'Объекту Record передается каталог Con1.Open "Provider = MSDAIPP.DSO; " & _ "Data Source = http://serverva/" FirstRec.Open ActiveConnection:=Con1, _ Options:=adOpenSource, Mode:=adModeRead 'Поля объекта Record Debug.Print "Число полей:", FirstRec.Fields.Count For Each fld In FirstRec.Fields Debug.Print "Имя поля: ", fld.Name, _ "Значение поля:", fld.Value, "Тип:", fld.Type Next fld 'Создание объекта Recordset Set Rst1 = FirstRec.GetChildren Debug.Print Rst1.ActiveConnection Debug.Print Rst1.RecordCount Rst1.MoveFirst Do While Not Rst1.EOF 'Обработка текущей записи Debug.Print Rst1(2).Name, Rst1(2).Value Rst1.MoveNext Loop Con1.Close


    End Sub

    Приведу комментарии к этой программе:

  • При установлении соединения с Провайдером в качестве источника данных я указал текстовый файл, находящийся в виртуальном каталоге Proba2_2. Этот каталог установлен под Internet Information Server на моем компьютере. Соответствующие строки программного текста подсвечены.
  • Создается объект FirstRec класса Record методом Open, которому в качестве параметра передается имя открытого соединения. В этот момент данные из текстового файла передаются объекту FirstRec.
  • Я не стал выводить информацию о всех свойствах открытого объекта, ограничившись лишь наиболее важным свойством Fields. Полей у этого объекта в данном контексте достаточно много - 26. Приведу имена и значения пяти полей:

    RESOURCE_PARSENAME - testtext.txt RESOURCE_PARENTNAME - http://serverva/proba2_2 RESOURCE_ABSOLUTEPARSENAME - http://serverva/proba2_2/testtext.txt RESOURCE_ISHIDDEN - False RESOURCE_CONTENTCLASS - text/plain

    В данном контексте большая часть полей, но не все, определяет характеристики ресурса.
  • Поскольку созданный объект FirstRec задает теперь текстовый файл, то он может в свою очередь послужить источником данных для объекта Stream, который создается, как обычно, методом Open.
  • Вызов метода ReadText глобального объекта Strm1 класса Stream позволяет прочесть нужную порцию символов в обычную строку и распечатать ее, что позволяет убедиться в правильности передачи данных.
  • На следующем шаге объект FirstRec закрывается и заново открывается, но теперь у него другой источник данных и его значением становится каталог.
  • Число полей в данном контексте уменьшилось до 18, все они определяют характеристики ресурса. Приведу имена и значения тех же полей в этом новом контексте:

    RESOURCE_PARSENAME - RESOURCE_PARENTNAME - http://serverva RESOURCE_ABSOLUTEPARSENAME - http://serverva RESOURCE_ISHIDDEN - RESOURCE_CONTENTCLASS -

    В сравнении с предыдущим случаем не для всех полей определены значения, в частности не задан класс контента.
  • Данный объект, представляющий каталог, может использоваться для построения объекта Recordset, записи которого будут содержать сведения о подкаталогах и файлах, хранимых в каталоге. Для создания объекта Recordset используется метод GetChildren, вызываемый объектом FirstRec.
  • Для каждой записи из созданного набора я вывожу на печать имя и значение второго поля. Приведу лишь первые пять строчек:

    RESOURCE_ABSOLUTEPARSENAME http://serverva/_private RESOURCE_ABSOLUTEPARSENAME http://serverva/images RESOURCE_ABSOLUTEPARSENAME http://serverva/win2000.gif RESOURCE_ABSOLUTEPARSENAME http://serverva/web.gif RESOURCE_ABSOLUTEPARSENAME http://serverva/warning.gif


  • На этом я закончу описание примера, в котором успешно взаимодействовали объекты Record, Stream и Recordset. Знакомство с объектом Stream уже состоялось, но давайте, хотя бы вкратце познакомимся с его свойствами и методами.

    Объект Stream

    Этот объект представляет двоичный поток данных или текст. Методы и свойства этого объекта позволяют выполнять операции над этим потоком, позволяя читать, писать, копировать и сохранять данные потока. Объекты Stream обычно используются при работе с данными, сохраненными в XML-формате, при работе с текстовыми файлами и сообщениями электронной почты, во всех случаях, когда данные рассматриваются как поток байтов. Объект Stream может быть получен тремя способами:
  • Заданием URL-адреса, указывающего на объект, обычно файл, содержащий двоичные данные или текст. Этот объект может быть простым документом, объектом Record, представляющим структурированный документ, или папкой.
  • Заданием потока, ассоциированного с объектом Record.
  • Быть созданным непосредственно в приложении, не будучи связанным ни с каким источником данных.


  • Объект Table

    Этот объект определяет таблицу базы данных. Является элементом коллекции Tables, вложенной в объект Catalog. Создается конструктором New и добавляется в коллекцию Tables методом Append. Вот типичная схема программного создания новой таблицы:
    Dim NewTable As New ADOX.Table 'Формирование объекта Table … Call Cat1.Tables.Append(NewTable)
    Чуть позже я приведу полный пример программного создания объекта Table, а пока займемся рассмотрением его свойств, методов и событий. Задача облегчается тем, что у объекта Table нет методов и событий, - есть только свойства, рассмотрением которых сейчас и займемся.

    Объект User

    Объект определяет учетную запись пользователя, позволяет задать имя пользователя, пароль, права доступа к объектам базы данных и группы, в которые входит этот пользователь. У этого объекта два свойства:
  • Property Name As String. Задает имя пользователя. Является свойством по умолчанию.
  • Property Groups As Groups. Возвращает одноименную коллекцию, содержащую все группы, в которые входит данный пользователь.

  • Пароль пользователя и разрешения на право доступа к тем или иным объектам базы данных задаются методами объекта User. Методов всего три:
  • Sub ChangePassword(OldPassword As String, NewPassword As String). Чтобы задать новый пароль, нужно знать и старый пароль. В случае, когда пароль задается в первый раз, старым паролем является пустая строка.
  • Function GetPermissions(Name, ObjectType As ObjectTypeEnum, [ObjectTypeId]) As RightsEnum. Метод (функция) возвращает разрешения на то, что может делать пользователь с объектом базы данных, чье имя задается параметром Name, а тип - параметром ObjectType. Третий параметр указывается лишь в тех исключительных ситуациях, когда речь идет об объектах, специфических для Провайдера. Права пользователя на объект задаются константами из перечисления RightsEnum. Возвращаемое значение может быть суммой отдельных констант и представляет маску, задающую отдельные разрешения. Различных констант в перечислении достаточно много, приведу лишь некоторые из них: adRightCreate, adRightDelete, adRightMaximumAllowed, adRightNone.
  • Sub SetPermissions(Name, ObjectType As ObjectTypeEnum, Action As ActionEnum, Rights As RightsEnum, [Inherit As InheritTypeEnum = adInheritNone], [ObjectTypeId]). Метод позволяет установить разрешения на возможные действия с элементом базы, чье имя задано параметром Name, а тип - параметром ObjectType. Параметр Rights задает маску, определяющую право на те или иные действия, значением параметра является сумма констант из перечисления RightsEnum. Параметр Action задает действия, выполняемые при установке разрешений. Параметр Inherit указывает, как объекты наследуют разрешения.


  • Объект View и коллекция Views

    Объекты View задают так называемые представления. Они подобны объектам Procedure, хранятся на сервере в виде оттранслированных запросов и задают отфильтрованное множество записей. Представления являются элементами таких баз данных как Microsoft SQL Server , а, следовательно, и MSDE. Поскольку с объектной точки зрения объект View имеет те же свойства, что и объект Procedure и соответствующие коллекции устроены одинаково, то я не буду задерживаться на более подробном описании этих объектов.

    Объекты ADOX

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

    Рис. 6.7.  Объектная модель ADOX
    На схеме не отражен тот факт, что объекты Table, Index, Column имеют стандартную коллекцию Properties с элементами Property.
    Заметьте, в этой модели есть центральный объект Catalog, в который и встроены все основные коллекции: Tables, Procedures, Views, Groups, Users. Эти коллекции определяют различные метаэлементы базы данных. Давайте подробно рассмотрим свойства и методы объектов модели.

    Объекты User, Group и их коллекции

    Для того чтобы обеспечить защиту баз данных от неавторизованного доступа, для того чтобы разным пользователям предоставить разные права доступа к тем или иным объектам базы данных - таблицам, запросам, полям таблицы, одним из основных используемых механизмов является введение учетных записей для пользователей и групп пользователей базы данных. На объектном уровне защита баз данных поддерживается объектами User, Group и их коллекциями. Свойства Users и Groups объекта Catalog возвращают одноименные коллекции всех пользователей и всех групп, связанных с базой данных - объектом Catalog. Но поскольку каждая группа содержит одного или нескольких пользователей, то свойством Users обладает и объект Group, для него свойство возвращает всех пользователей, входящих в группу. Симметрично, свойством Groups обладает объект User, для него свойство возвращает все группы, в которые входит данный пользователь.
    Коллекции Users и Groups устроены, как и все коллекции ADOX, у них три метода - Append, Delete, Refresh и два свойства: Item и Count. Создаются объекты и присоединяются к коллекции также согласно общей схеме, - при создании используется конструктор New, для присоединения - метод Append. Замечу, что, наконец-то, хотя бы в ADOX достигнута желаемая общность в организации коллекций.

    Особенности работы с хранимыми процедурами в Access

    В базе данных Access нет понятия хранимых процедур, их роль играют хранимые запросы. Было бы хорошо, если бы Провайдер Microsoft Jet при создании объектов коллекции Procedures создавал вместо этого хранимые запросы. Однако этого не происходит, - запросы не создаются. Поэтому, казалось бы, при работе с базой данных Access создавать программно процедуры невозможно и, главное, бесполезно. Тем не менее, при попытке создать коллекцию Procedures никаких ошибок не возникает и, по крайней мере, одна процедура создается и с ней можно впоследствии работать, вызывая ее на исполнение.
    Я приведу сейчас пример, в котором демонстрируется сам способ создания коллекции Procedures. Этот способ не зависит от Провайдера и базы данных, он может с успехом использоваться при работе с Access MSDE или, например, с Microsoft SQL Server. Но, главная суть примера в том, чтобы показать недокументированную возможность программной работы с хранимой процедурой в Access. Как всегда, приведу вначале текст процедуры:
    Public Sub CreateQuery() 'Создание хранимых процедур Dim myCmd1 As New Command, myCmd2 As New Command 'Установить соединение с базой NewDB CreateConnection Cat1.ActiveConnection = Con1 'SQL-запросы, представляющие тексты хранимых процедур myCmd1.CommandText = "Select [Книги.Автор],[Книги.Название_книги]" & _ "From [Книги]" & _ "WHERE ((([Книги.Год_издания])= 1999))" & _ "ORDER BY [Книги.Автор];" 'Запрос с параметром myCmd2.CommandText = "Parameters [Avtor] Text(50);" & _ "Select [Книги.Автор],[Книги.Название_книги]" & _ "From [Книги]" & _ "WHERE ((([Книги.Автор])= [Avtor]));" 'Удаление процедур Dim i As Integer For i = 0 To Cat1.Procedures.Count - 1 Cat1.Procedures.Delete (i) Next i 'Добавление процедур Debug.Print Cat1.Procedures.Count Call Cat1.Procedures.Append("Books1999", myCmd1) Debug.Print Cat1.Procedures.Count Call Cat1.Procedures.Append("BooksOfAuthor", myCmd2) Debug.Print Cat1.Procedures.Count

    End Sub

    Сама процедура и то, как она выполняется, нуждается в подробных комментариях:

  • Поскольку целью работы, которую я поставил перед собой, являлось создание двух хранимых процедур, то в процедуре создаются два объекта Command.
  • Свойство CommandText этих объектов позволяет задать текст команд, представляющих в данном случае SQL-запросы. Второй из этих запросов представляет запрос с параметром.
  • Успешно выполняется метод Append коллекции Procedures, которому в качестве одного из параметров передаются созданные объекты Command. По завершении работы процедуры создается впечатление, что коллекция Procedures с двумя элементами успешно создана. Однако это не совсем так.
  • Прежде всего, заметьте, что никакие запросы в базе данных Access не появляются. Коллекция процедур действительно создается, но в ней присутствует только один элемент. При добавлении нового элемента он записывается на место ранее существовавшего. Так что свойство Count в конце работы этой процедуры будет возвращать число 1, а сама коллекция будет хранить второй параметрический запрос.
  • Досадной ошибкой, усугубляющей ситуацию, является то, что имена записываемых процедур сохраняются полностью, не забивая друг друга. По этой причине при повторном запуске процедуры, несмотря на то, что все процедуры из коллекции удаляются перед их созданием, возникнет ошибка с выдачей сообщения о том, что есть хранимая процедура с именем "Books1999", хотя коллекция и пуста.


  • Не следует особенно расстраиваться, что хранимые процедуры не работают корректно в Access, - они и не должны работать. Можно, однако, использовать тот факт, что с одной процедурой все-таки работать разрешается. Вот пример работы, демонстрирующий работу с сохраненной в предыдущем примере процедурой с именем BooksOfAuthor, имеющей имя автора в качестве параметра запроса:

    Public Sub WorkWithProcs() 'работа с хранимыми процедурами Dim myCmd As Command 'Установить соединение с базой NewDB CreateConnection Cat1.ActiveConnection = Con1 Set myCmd = Cat1.Procedures("BooksOfAuthor").Command Set Rst1 = myCmd.Execute(, "Пушкин") With Rst1 .MoveFirst Do While Not .EOF Debug.Print .Fields(0), .Fields(1) .MoveNext Loop End With End Sub

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


    End Sub

    Сама процедура и то, как она выполняется, нуждается в подробных комментариях:

  • Поскольку целью работы, которую я поставил перед собой, являлось создание двух хранимых процедур, то в процедуре создаются два объекта Command.
  • Свойство CommandText этих объектов позволяет задать текст команд, представляющих в данном случае SQL-запросы. Второй из этих запросов представляет запрос с параметром.
  • Успешно выполняется метод Append коллекции Procedures, которому в качестве одного из параметров передаются созданные объекты Command. По завершении работы процедуры создается впечатление, что коллекция Procedures с двумя элементами успешно создана. Однако это не совсем так.
  • Прежде всего, заметьте, что никакие запросы в базе данных Access не появляются. Коллекция процедур действительно создается, но в ней присутствует только один элемент. При добавлении нового элемента он записывается на место ранее существовавшего. Так что свойство Count в конце работы этой процедуры будет возвращать число 1, а сама коллекция будет хранить второй параметрический запрос.
  • Досадной ошибкой, усугубляющей ситуацию, является то, что имена записываемых процедур сохраняются полностью, не забивая друг друга. По этой причине при повторном запуске процедуры, несмотря на то, что все процедуры из коллекции удаляются перед их созданием, возникнет ошибка с выдачей сообщения о том, что есть хранимая процедура с именем "Books1999", хотя коллекция и пуста.


  • Не следует особенно расстраиваться, что хранимые процедуры не работают корректно в Access, - они и не должны работать. Можно, однако, использовать тот факт, что с одной процедурой все-таки работать разрешается. Вот пример работы, демонстрирующий работу с сохраненной в предыдущем примере процедурой с именем BooksOfAuthor, имеющей имя автора в качестве параметра запроса:

    Public Sub WorkWithProcs() 'работа с хранимыми процедурами Dim myCmd As Command 'Установить соединение с базой NewDB CreateConnection Cat1.ActiveConnection = Con1 Set myCmd = Cat1.Procedures("BooksOfAuthor").Command Set Rst1 = myCmd.Execute(, "Пушкин") With Rst1 .MoveFirst Do While Not .EOF Debug.Print .Fields(0), .Fields(1) .MoveNext Loop End With End Sub

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

    Построение обработчиков событий и обработка ошибок

    Давайте разберемся с деталями построения обработчиков событий объектов ADO. Прежде всего, рассмотрим, как они создаются, в какой модуль проекта их следует поместить. Хотя объекты Recordset и Connection обладают событиями, но появляются они как объекты без событий. Необходимо предпринять обычные для VBA в таких ситуациях действия, чтобы создать объекты с событиями и написать процедуры, обрабатывающие события, - обработчики событий. В первом томе и других книгах серии "Офисное программирование" я подробно рассказывал о том, как это делается. Напомню, что для решения этой задачи нужно выполнить три шага:
  • Создать на VBA собственный класс с именем, например MyEventsADO, в котором описать вложенные объекты - Connection WithEvents и Recordset WithEvents.
  • Создать обработчики нужных событий для данных объектов в классе MyEventsADO. Заметьте, после объявления переменных With Events в этом классе появится возможность написания обработчиков событий, следуя обычной технологии.
  • В подходящем месте, например в уже существующем модуле TestingADO, создать экземпляры класса MyEventsADO и связать их с глобальными или локальными объектами Rst1 и Con1, задающими набор записей и соединение.

  • Чтобы сделать дальнейшее описание конкретным, давайте рассмотрим создание обработчика события WillChangeField объекта Recordset, которое, напомню, появляется при попытках изменить значение поля в записи набора, но перед тем, как это изменение реально произойдет. Вот как выглядит модуль класса MyEventsADO, содержащий обработчик этого события:
    'Класс MyEventsADO, определяющий события ADO Public WithEvents EvRst As ADODB.Recordset Public WithEvents EvCon As ADODB.Connection
    Private Sub EvRst_WillChangeField(ByVal cFields As Long, _ ByVal Fields As Variant, adStatus As ADODB.EventStatusEnum, _ ByVal pRecordset As ADODB.Recordset) 'Печать параметров обработчика события MsgBox "Номер поля = " & cFields & vbCrLf & _ "Значение поля = " & Fields(cField) & vbCrLf & _ "Позиция записи в наборе = " & _ pRecordset.AbsolutePosition & vbCrLf & _ "Статус выполняемой операции = " & adStatus 'Изменение статуса приведет к появлению ошибки 'при попытке изменить значение поля, обработка которой 'позволит отменить операцию. If IsNumeric(Fields(cField)) Then If Fields(cField) > 75 Then adStatus = adStatusCancel End If End Sub

    Первым делом я включил в обработчике события WillChangeField печать всех его параметров на входе. Обратите внимание, обработчику события передается указатель на сам объект Recordset, поэтому доступна практически любая информация, связанная с этим объектом. Я, например, использовал его, чтобы вывести на печать позицию записи в наборе, поля которой обновляются. Во второй части обработчика я изменяю значение статуса при выполнении некоторого условия на обновляемое поле. Делаю это для того чтобы реализовать возможность отмены изменения значения, когда в результате анализа, проведенного в обработчике, принимается решение о нежелательности выполнения изменений. Заметьте, для этого свойству статус, которое имело значение adStatusOk, уведомляющее о том, что изменения возможны, я присваиваю значение adStausCancel. В этом случае при выходе из обработчика возникнет ошибка в операторе, производящем изменения. Обработав соответствующим образом эту ошибку, я смогу выполнить поставленную задачу.

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

    Public Sub CreateEvents() 'Объект с событиями Dim imEvRst As New MyEventsADO 'добавление и изменение записей базы данных Dim recExist As Boolean ' Связывание объекта с событиями Set imEvRst.EvRst = Rst1 'Создать соединение CreateConnection 'Создать команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Книги]" Cmd1.CommandType = adCmdText 'Открытие обновляемого объекта Recordset With Rst1 .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic 'Изменение записей recExist = False .MoveFirst Do While Not .EOF 'Обработка текущей записи On Error Resume Next If !Название = "Офисное программирование" Then recExist = True If ![Год издания] < 2000 And !Цена < 100 Then MsgBox "Старая цена =" & Str(!Цена) !Цена = !Цена * 2 .Update End If .MoveNext Loop If Not recExist Then .AddNew !Автор = "Владимир Биллиг" !Название = "Офисное программмирование" ![Год издания] = 2001 ![Число страниц] = 599 !Цена = 150 .Update End If End With End Sub


    Обратите внимание, первым делом я объявляю объект imEvRst созданного класса MyEventsADO и связываю его с глобальным объектом Rst1, после чего последний начнет реагировать на события. Сколько раз будет появляться событие WillChangeField? Очевидно, при каждом выполнении оператора !Цена = !Цена * 2, изменяющего значение соответствующего поля записи. Вот как выглядят окошки функции MsgBox, одно из которых открывается перед выполнением этого оператора, а второе в обработчике события в момент выполнения оператора:

    Построение обработчиков событий и обработка ошибок

    Рис. 6.1.  Окна, открываемые перед изменением поля и в момент изменения

    Соответствующие сообщения появляются и при создании новой записи, причем столько раз, сколько полей имеет запись.

    Важную роль в этой процедуре играет и оператор On Error Resume Next. Всякий раз, когда в обработчике события изменяется значение переменной adStatus, возникнет ошибка при выполнении оператора, изменяющего значение поля и послужившего причиной возникновения события. Оператор OnError позволяет в этом случае обойти выполнение оператора и, тем самым, избежать изменения значения, что и хотелось. Взгляните, как выглядит окно, уведомляющее об ошибке, появляющееся, если в процедуре закомментировать оператор On Error:

    Построение обработчиков событий и обработка ошибок

    Рис. 6.2.  Окно, уведомляющее о возникновении ошибки при изменении значения

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


    Первым делом я включил в обработчике события WillChangeField печать всех его параметров на входе. Обратите внимание, обработчику события передается указатель на сам объект Recordset, поэтому доступна практически любая информация, связанная с этим объектом. Я, например, использовал его, чтобы вывести на печать позицию записи в наборе, поля которой обновляются. Во второй части обработчика я изменяю значение статуса при выполнении некоторого условия на обновляемое поле. Делаю это для того чтобы реализовать возможность отмены изменения значения, когда в результате анализа, проведенного в обработчике, принимается решение о нежелательности выполнения изменений. Заметьте, для этого свойству статус, которое имело значение adStatusOk, уведомляющее о том, что изменения возможны, я присваиваю значение adStausCancel. В этом случае при выходе из обработчика возникнет ошибка в операторе, производящем изменения. Обработав соответствующим образом эту ошибку, я смогу выполнить поставленную задачу.

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

    Public Sub CreateEvents() 'Объект с событиями Dim imEvRst As New MyEventsADO 'добавление и изменение записей базы данных Dim recExist As Boolean ' Связывание объекта с событиями Set imEvRst.EvRst = Rst1 'Создать соединение CreateConnection 'Создать команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Книги]" Cmd1.CommandType = adCmdText 'Открытие обновляемого объекта Recordset With Rst1 .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic 'Изменение записей recExist = False .MoveFirst Do While Not .EOF 'Обработка текущей записи On Error Resume Next If !Название = "Офисное программирование" Then recExist = True If ![Год издания] < 2000 And !Цена < 100 Then MsgBox "Старая цена =" & Str(!Цена) !Цена = !Цена * 2 .Update End If .MoveNext Loop If Not recExist Then .AddNew !Автор = "Владимир Биллиг" !Название = "Офисное программмирование" ![Год издания] = 2001 ![Число страниц] = 599 !Цена = 150 .Update End If End With End Sub


    Обратите внимание, первым делом я объявляю объект imEvRst созданного класса MyEventsADO и связываю его с глобальным объектом Rst1, после чего последний начнет реагировать на события. Сколько раз будет появляться событие WillChangeField? Очевидно, при каждом выполнении оператора !Цена = !Цена * 2, изменяющего значение соответствующего поля записи. Вот как выглядят окошки функции MsgBox, одно из которых открывается перед выполнением этого оператора, а второе в обработчике события в момент выполнения оператора:

    Построение обработчиков событий и обработка ошибок

    Рис. 6.1.  Окна, открываемые перед изменением поля и в момент изменения

    Соответствующие сообщения появляются и при создании новой записи, причем столько раз, сколько полей имеет запись.

    Важную роль в этой процедуре играет и оператор On Error Resume Next. Всякий раз, когда в обработчике события изменяется значение переменной adStatus, возникнет ошибка при выполнении оператора, изменяющего значение поля и послужившего причиной возникновения события. Оператор OnError позволяет в этом случае обойти выполнение оператора и, тем самым, избежать изменения значения, что и хотелось. Взгляните, как выглядит окно, уведомляющее об ошибке, появляющееся, если в процедуре закомментировать оператор On Error:

    Построение обработчиков событий и обработка ошибок

    Рис. 6.2.  Окно, уведомляющее о возникновении ошибки при изменении значения

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

    Пример программной работы в Access с пользователями и группами

    База данных Microsoft Jet позволяет устанавливать защиту с использованием механизма учетных записей пользователей и групп пользователей. По умолчанию, когда создается новая база данных, автоматически создаются две типичные группы пользователей - Admins и Users. Первая из них включает администраторов базы, обладающих максимальными правами, другая - "обычных" пользователей. В специальной системной базе данных Access хранится информация о группах, о пользователях, их именах и паролях. Эта база данных хранится в файле с именем System и расширением - mdw. Обычный путь к этому файлу - "c:\Program Files\Microsoft Office\Offce\system.mdw". Фактический путь можно посмотреть и при необходимости изменить в реестре, где он находится в разделе:
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\9.0\Access\Jet\4.0\Engines Параметр, задающий путь, имеет имя SystemDB.
    В нижеследующем примере я ввел в дополнение к существующим группам еще две группы - читателей и писателей - с именами, соответственно, Readers и Writers и четырех новых пользователей, входящих по-разному в эти группы. Группам заданы различные права на доступ к таблицам базы данных. Но, прежде чем давать другие пояснения приведу соответствующий программный код:
    Public Sub CreateUsersАndGroups() 'Cоздание пользователей и групп Dim myUs(1 To 4) As New User 'учетные записи пользователей Dim myGr(1 To 2) As New Group 'учетные записи групп 'Установить соединение с базой NewDB 'и системной базой данных System.mdw CreateConnectionSystemDB Cat1.ActiveConnection = Con1 'Пользователи myUs(1).Name = "Vladimir" myUs(1).ChangePassword "", "Old111" Cat1.Users.Append myUs(1)
    myUs(2).Name = "Nina" Call myUs(2).ChangePassword("", "Best111") Call Cat1.Users.Append(myUs(2))
    myUs(3).Name = "Ilya" Call myUs(3).ChangePassword("", "Prim111") Call Cat1.Users.Append(myUs(3))
    myUs(4).Name = "Yuly" Call myUs(4).ChangePassword("", "Clev111") Call Cat1.Users.Append(myUs(4))

    'Группы myGr(1).Name = "Readers" Call Cat1.Groups.Append(myGr(1)) myGr(1).Users.Append (myUs(1)) myGr(1).Users.Append (myUs(2)) myGr(1).Users.Append (myUs(3)) myGr(1).Users.Append (myUs(4))
    myGr(2).Name = "Writers" Call Cat1.Groups.Append(myGr(2)) myGr(2).Users.Append (myUs(1))
    'Установление прав Call myGr(1).SetPermissions("Книги", _ adPermObjTable, adAccessSet, adRightRead, adInheritBoth) Call myGr(1).SetPermissions("Заказчики", _ adPermObjTable, adAccessSet, adRightRead, adInheritBoth)
    Call myGr(2).SetPermissions("Книги", _ adPermObjTable, adAccessSet, adRightFull, adInheritBoth) Call myGr(2).SetPermissions("Заказчики", _ adPermObjTable, adAccessSet, adRightFull, adInheritBoth)
    Call myUs(3).SetPermissions("Заказчики", _ adPermObjTable, adAccessSet, adRightMaximumAllowed, adInheritBoth) End Sub
    Прежде всего, хочу обратить внимание на главную особенность работы с объектами User, Group и их коллекциями, когда речь идет о Провайдере Microsoft Jet. При установлении соединения необходимо связаться не только с самой базой данных, но и с системной базой данных. По этой причине вызывается специальная процедура, устанавливающая такое соединение:
    Public Sub CreateConnectionSystemDB() 'Создание соединения с базой данных Access - NewDB ' и системной базой данных - System.mdw Dim strConnStr As String If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение
    Con1.Provider = "Microsoft.jet.oledb.4.0" Con1.ConnectionString = _ "Data Source=c:\!O2000\DsCd\Ch16\NewDB.mdb;" & _ "Jet OLEDB:System database =" & _ "c:\Program Files\Microsoft Office\Office\system.mdw" 'Открытие соединения Con1.Open End Sub
    Заметьте, я задаю специфическое для Провайдера свойство "Jet OLEDB:System database" непосредственно в строке соединения, хотя мог бы это сделать, используя коллекцию Properties.
    Возвращаясь к процедуре CreateUsersAndGroups, приведу еще несколько комментариев к ее работе:
  • Создание объектов User и Group, присоединение их к коллекциям идет по традиционной схеме.
  • Лишь после того, как созданы пользователи и группы, создаются коллекции Users для каждой группы. Заметьте, при этом автоматически будут созданы коллекции Groups для каждого объекта User.
  • На последнем шаге я задаю различные права для каждой группы на доступ к таблицам "Книги" и "Заказчики", а также индивидуальные права на работу с таблицей "Заказчики" для одного из пользователей с именем "Ilya".

  • В заключение пара экранных снимков. Взгляните, как выглядит результаты одного из запросов к системной базе данных, после выполнения процедуры CreateUsersAndGroups:
    Пример программной работы в Access с пользователями и группами

    Рис. 6.11.  Результаты запроса к системной базе данных Access
    Пример программной работы в Access с пользователями и группами

    Рис. 6.12.  Показ прав доступа к элементам базы данных Access
    Конечно, все, что я сделал программно, можно сделать и руками, выбрав пункт меню "Сервис | Защита" и открыв соответствующее окно, например, окно разрешений, показанное на рис. 6.12.


    'Группы myGr(1).Name = "Readers" Call Cat1.Groups.Append(myGr(1)) myGr(1).Users.Append (myUs(1)) myGr(1).Users.Append (myUs(2)) myGr(1).Users.Append (myUs(3)) myGr(1).Users.Append (myUs(4))
    myGr(2).Name = "Writers" Call Cat1.Groups.Append(myGr(2)) myGr(2).Users.Append (myUs(1))
    'Установление прав Call myGr(1).SetPermissions("Книги", _ adPermObjTable, adAccessSet, adRightRead, adInheritBoth) Call myGr(1).SetPermissions("Заказчики", _ adPermObjTable, adAccessSet, adRightRead, adInheritBoth)
    Call myGr(2).SetPermissions("Книги", _ adPermObjTable, adAccessSet, adRightFull, adInheritBoth) Call myGr(2).SetPermissions("Заказчики", _ adPermObjTable, adAccessSet, adRightFull, adInheritBoth)
    Call myUs(3).SetPermissions("Заказчики", _ adPermObjTable, adAccessSet, adRightMaximumAllowed, adInheritBoth) End Sub
    Прежде всего, хочу обратить внимание на главную особенность работы с объектами User, Group и их коллекциями, когда речь идет о Провайдере Microsoft Jet. При установлении соединения необходимо связаться не только с самой базой данных, но и с системной базой данных. По этой причине вызывается специальная процедура, устанавливающая такое соединение:
    Public Sub CreateConnectionSystemDB() 'Создание соединения с базой данных Access - NewDB ' и системной базой данных - System.mdw Dim strConnStr As String If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение
    Con1.Provider = "Microsoft.jet.oledb.4.0" Con1.ConnectionString = _ "Data Source=c:\!O2000\DsCd\Ch16\NewDB.mdb;" & _ "Jet OLEDB:System database =" & _ "c:\Program Files\Microsoft Office\Office\system.mdw" 'Открытие соединения Con1.Open End Sub
    Заметьте, я задаю специфическое для Провайдера свойство "Jet OLEDB:System database" непосредственно в строке соединения, хотя мог бы это сделать, используя коллекцию Properties.
    Возвращаясь к процедуре CreateUsersAndGroups, приведу еще несколько комментариев к ее работе:
  • Создание объектов User и Group, присоединение их к коллекциям идет по традиционной схеме.
  • Лишь после того, как созданы пользователи и группы, создаются коллекции Users для каждой группы. Заметьте, при этом автоматически будут созданы коллекции Groups для каждого объекта User.
  • На последнем шаге я задаю различные права для каждой группы на доступ к таблицам "Книги" и "Заказчики", а также индивидуальные права на работу с таблицей "Заказчики" для одного из пользователей с именем "Ilya".

  • В заключение пара экранных снимков. Взгляните, как выглядит результаты одного из запросов к системной базе данных, после выполнения процедуры CreateUsersAndGroups:
    Пример программной работы в Access с пользователями и группами

    Рис. 6.11.  Результаты запроса к системной базе данных Access
    Пример программной работы в Access с пользователями и группами

    Рис. 6.12.  Показ прав доступа к элементам базы данных Access
    Конечно, все, что я сделал программно, можно сделать и руками, выбрав пункт меню "Сервис | Защита" и открыв соответствующее окно, например, окно разрешений, показанное на рис. 6.12.

    Пример работы с объектами ADO при создании Web-документов

    Последний пример в этой главе я хочу посвятить хотя бы беглому знакомству использования объектов ADO в сценариях, написанных на VBScript. Я рассмотрю пример, представляющий фрагмент создания Web-узла. Создаваемый узел является активным, он может реагировать на действия пользователя, используя для этих целей технологию ASP(Active Server Page) страниц. Напомню, эти страницы содержат сценарии, которые собственно и определяют реакцию на действия пользователя. Особенностью ASP-страниц является то, что код сценариев выполняется на сервере и пользователю пересылается статическая страница, содержащая не код, а результат его выполнения.
    В рассматриваемом фрагменте рассматривается типичная для активных Web-узлов ситуация, когда пользователь заполняет некоторую форму и отсылает ее на сервер. ASP-страница обрабатывает данные формы, сохраняет их в базе данных, добавляя новые записи в таблицы, и формирует новую страницу, пересылаемую пользователю. Вот как выглядит страница узла, на которой пользователь заполняет форму:
    Пример работы с объектами ADO при создании Web-документов

    увеличить изображение
    Рис. 6.13.  Web-страница, содержащая форму, отсылаемую на сервер
    Приведу теперь текст ASP-страницы, которая выполняется на сервере:

    vbazu

    BiVANT  

    BiVANT  Книги  Статьи  Учебники  Обучение  Чайничек  Дело_Отца  О нас  Авторы


    <% 'Переменные, задающие поля формы Dim DAvtor, DFam, DName, DVid, DGorod, Dmail, DOtziv, DVopros, DPriob 'Передача значений полей формы DFam= Request.Form("Fam") DName = Request.Form("Name") DVid= Request.Form("Vid") DGorod =Request.Form("Gorod") Dmail= Request.Form("mail") DOtziv= Request.Form("Otziv") DPriob= Request.Form("Priob") DVopros =Request.Form("Vopros") DAvtor = Request.Form("Avtor")
    'Объявление объектов ADO - Connection, Command, Recordset Dim Cconnect, Cmd, Records Dim StrSQL 'строка запроса
    ' Установить связь с базой данных Set Cconnect =Server.CreateObject("ADODB.Connection") Cconnect.ConnectionString= "provider=microsoft.jet.oledb.4.0;" & "data source ='d:\Bivant\svyaz.mdb'" Cconnect.Open
    'Формирование строки запроса StrSQL = "Select * From VoprosOtvet"
    'Создание команды и задание свойств объекта Command Set Cmd =Server.CreateObject("ADODB.Command") Cmd.ActiveConnection = Cconnect Cmd.CommandText = StrSQL Cmd.CommandType =1
    'Работа с RecordSet Set Records =Server.CreateObject("ADODB.Recordset") With Records 'Открытие обновляемого объекта RecordSet .Open Cmd,,2,3
    ' Добавление записи .MoveFirst .AddNew .Fields("Автор")=DAvtor .Fields("Вопрос") = DVopros .Fields("Отзыв") = DOtziv .Fields("Фамилия") =DFam .Fields("Имя") =DName .Fields("Город") = DGorod .Fields("Вид_деят") =DVid .Fields("Почта") =Dmail .Fields("Приобрести") =DPriob .Update End With
    %>
    Спасибо за Ваш интерес к нашей тематике!


    Мы постараемся учесть Ваши пожелания и своевременно ответить на вопрос.




    Приведу теперь комментарии к этому тексту:
  • Текст написан на HTML и представляет содержание ASP-страницы, в задачу которой входит обработать посылаемую пользователем форму. Большая часть текста задает сценарий, написанный на VBScript, и именно эту часть я и буду комментировать. Для удобства восприятия код сценария подсвечен.
  • Язык VBScript является нетипизированным языком, все переменные принадлежат к одному универсальному типу Variant, поэтому для переменных, соответствующих полям формы не задан тип.
  • Объект ASP Request позволяет передать значения из полей формы в переменные VBScript.
  • Затем начинается работа с объектами ADO, которые создаются на сервере методом CreateObject объекта Server.
  • Вся остальная часть работы с объектами ADO - создание соединения с базой данных, формирование команды, набора записей, добавление новой записи к объекту Recordset и обновление базы данных, выполняется на VBScript совершенно аналогично тому, как это делалось на VBA. В результате выполнения этого сценария база данных действительно изменяется и данные, посланные в форме, будут сохранены в соответствующей таблице базы данных.

  • На этом я закончу разговор о компонентах ADO, представляющих универсальный способ взаимодействия с самыми разными источниками данных.


    <% 'Переменные, задающие поля формы Dim DAvtor, DFam, DName, DVid, DGorod, Dmail, DOtziv, DVopros, DPriob 'Передача значений полей формы DFam= Request.Form("Fam") DName = Request.Form("Name") DVid= Request.Form("Vid") DGorod =Request.Form("Gorod") Dmail= Request.Form("mail") DOtziv= Request.Form("Otziv") DPriob= Request.Form("Priob") DVopros =Request.Form("Vopros") DAvtor = Request.Form("Avtor")
    'Объявление объектов ADO - Connection, Command, Recordset Dim Cconnect, Cmd, Records Dim StrSQL 'строка запроса
    ' Установить связь с базой данных Set Cconnect =Server.CreateObject("ADODB.Connection") Cconnect.ConnectionString= "provider=microsoft.jet.oledb.4.0;" & "data source ='d:\Bivant\svyaz.mdb'" Cconnect.Open
    'Формирование строки запроса StrSQL = "Select * From VoprosOtvet"
    'Создание команды и задание свойств объекта Command Set Cmd =Server.CreateObject("ADODB.Command") Cmd.ActiveConnection = Cconnect Cmd.CommandText = StrSQL Cmd.CommandType =1
    'Работа с RecordSet Set Records =Server.CreateObject("ADODB.Recordset") With Records 'Открытие обновляемого объекта RecordSet .Open Cmd,,2,3
    ' Добавление записи .MoveFirst .AddNew .Fields("Автор")=DAvtor .Fields("Вопрос") = DVopros .Fields("Отзыв") = DOtziv .Fields("Фамилия") =DFam .Fields("Имя") =DName .Fields("Город") = DGorod .Fields("Вид_деят") =DVid .Fields("Почта") =Dmail .Fields("Приобрести") =DPriob .Update End With
    %>
    Спасибо за Ваш интерес к нашей тематике!


    Мы постараемся учесть Ваши пожелания и своевременно ответить на вопрос.




    Приведу теперь комментарии к этому тексту:
  • Текст написан на HTML и представляет содержание ASP-страницы, в задачу которой входит обработать посылаемую пользователем форму. Большая часть текста задает сценарий, написанный на VBScript, и именно эту часть я и буду комментировать. Для удобства восприятия код сценария подсвечен.
  • Язык VBScript является нетипизированным языком, все переменные принадлежат к одному универсальному типу Variant, поэтому для переменных, соответствующих полям формы не задан тип.
  • Объект ASP Request позволяет передать значения из полей формы в переменные VBScript.
  • Затем начинается работа с объектами ADO, которые создаются на сервере методом CreateObject объекта Server.
  • Вся остальная часть работы с объектами ADO - создание соединения с базой данных, формирование команды, набора записей, добавление новой записи к объекту Recordset и обновление базы данных, выполняется на VBScript совершенно аналогично тому, как это делалось на VBA. В результате выполнения этого сценария база данных действительно изменяется и данные, посланные в форме, будут сохранены в соответствующей таблице базы данных.

  • На этом я закончу разговор о компонентах ADO, представляющих универсальный способ взаимодействия с самыми разными источниками данных.
    Пример работы с объектами ADO при создании Web-документов
    Пример работы с объектами ADO при создании Web-документов

    Пример работы с объектами ADO при создании Web-документов
    © 2003-2007 INTUIT.ru. Все права защищены.

    Провайдер Internet Publishing

    Полное имя этого Провайдера - Microsoft OLE DB Provider for Internet Publishing. Он позволяет получить доступ к ресурсам Web-серверов, работающих под управлением Internet Information Server. Типичная строка соединения имеет вид:
    "Provider= MSDAIPP.DSO; Data Source=ResourceURL; User ID =userName; Password=userPassword;"
    или
    "URL =ResourceURL; User ID =userName; Password=userPassword;"
    Параметр Data Source | URL задает источник данных - URL файла или каталога, расположенного в Web-парке на сервере. Заметьте, во втором случае, когда используется имя URL для второго параметра, первый параметр, определяющий Провайдера, задавать не следует. Ошибка возникнет и в том случае, если предварительно будет задано свойство Provider.
    Примеры использования этого Провайдера уже были приведены.

    Провайдер Microsoft Jet

    Этот Провайдер позволяет получить доступ к Microsoft Jet базам данных, а, следовательно, к базам данных, созданным в приложении Access. В большинстве приведенных примерах использовался именно этот Провайдер. Типичная строка соединения имеет вид:
    "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=databaseName; User ID =userName; Password=userPassword;"
    В строке соединения Провайдеру могут быть переданы и специфические свойства, например, параметр Jet OLEDB: Database Password позволяет задать пароль к базе данных. В одном из последующих примеров я использую этот способ, чтобы указать имя системной базы данных. Другой способ передачи специфических для Провайдера параметров - через коллекцию Propertieis объекта Connection. Заметьте, большую часть этой коллекции для объектов Connection, Command и Recordset Провайдер формирует динамически.

    Провайдер ODBC

    Полное имя этого Провайдера - Microsoft OLE DB Provider for ODBC. Он является Провайдером по умолчанию для объектов ADO, так что если не задать аргумент Provider в строке соединения, то связь по умолчанию будет осуществляться с этим Провайдером. Этот Поставщик данных позволяет связаться со всеми СУБД с интерфейсом ODBC. Все СУБД, поставляемые Microsoft - Microsoft SQL Server, Microsoft Access (Microsoft Jet database engine), Microsoft FoxPro - обладают этим интерфейсом. Но и СУБД других фирм, например Oracle, обладают, как правило, этим интерфейсом, так что Провайдер ODBC реально позволяет связаться с любой профессиональной базой данных.
    Провайдер ODBC, являясь Провайдером по умолчанию, поддерживает все зависящие от Провайдера свойства и методы объектов ADO. Он поддерживает транзакции, в том числе и гнездованные транзакции. Однако различные СУБД могут обеспечивать различный уровень поддержки транзакций, например, Microsoft Access поддерживает гнездованные транзакции на глубину не более пяти уровней.
    Для этого Провайдера аргумент Provider свойства ConnectionString следует установить как MSDASQL. Типичная строка соединения имеет вид:
    "Provider = MSDASQL; DSN = dsnName; UID = userName; PWD = userPassword;"
    Аргумент DSN (Data Source Name), задает имя источника данных. Это имя должно быть зарегистрировано в Администраторе источников данных ODBC, добраться до которого можно из панели управления. Вот как выглядит окно Администратора, в котором я установил DSN для тестовой базы данных.
    Провайдер ODBC

    Рис. 6.5.  Установка DSN в окне Администратора ODBC
    Приведу пример работы с этой базой данных:
    Public Sub CreateODBCConnect() 'Создание соединения с тестовой базой данных Access Dim strConnStr As String Dim Start As Single, Finish As Single If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение 'Вариант1: Провайдер ODBC Con1.Provider = "MSDASQL" strConnStr = "DSN=dbTestingADO; DATABASE =c:\dbPP2000.mdb;" 'Вариант2: Провайдер Microsoft Jet 'Con1.Provider = "Microsoft.jet.oledb.4.0" 'strConnStr = "Data Source=c:\dbPP2000.mdb;" Start = Timer Con1.Open strConnStr 'Создать команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Книги]" Cmd1.CommandType = adCmdText 'Открытие обновляемого объекта Recordset With Rst1 .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic

    recExist = False .MoveFirst Do While Not .EOF 'Проверка существования записи If !Название = "Война и мир" Then recExist = True . MoveNext Loop If Not recExist Then .AddNew Array("Автор", "Название", "Год издания", _ "Число страниц", "Цена"), _ Array("Лев Толстой", "Война и мир", 2001, 799, 220) End If End With Con1.Close Finish = Timer Debug.Print "Время работы с таблицей:", Finish - Start End Sub

    Хочу обратить внимание на два момента:

  • В предыдущих примерах я работал с базой данных Access, используя Провайдер Microsoft.jet.oledb.4.0, специально разработанный для этой базы. Сейчас же для работы с этой же базой данных я использую общий Провайдер, позволяющий работать с любыми базами, допускающими интерфейс ODBC. База данных Access, конечно же, позволяет такой способ работы. Однако время работы зависит от выбора Провайдера. Этот пример позволяет сравнить два варианта, в каждом из которых устанавливается связь с одним из выше упомянутых Провайдеров. Соответствующие операторы подсвечены в тексте процедуры. Поочередно комментируя операторы, задающие вариант соединения, я запускал процедуру на выполнение и получал время ее выполнения для того или иного Провайдера. Приведу временные характеристики, полученные при двух запусках этой процедуры:

    Время работы с таблицей: 5,878906 Время работы с таблицей: 0,0390625

    Заметьте, время работы с ODBC Провайдером для базы данных Access в данном случае существенно больше, - на два порядка, чем время работы с "родным" Провайдером.

  • При работе с ODBC Провайдером, обратите внимание, помимо DSN я задал аргумент DATABASE, указывающий путь к базе данных. Эта информация является дублирующей, поскольку реальный путь к базе данных задается в момент определения DSN. Тем не менее, это полезно делать, чтобы явно указать, с какой базой данных пользователь намеревается работать.


  • Провайдер ODBC добавляет в коллекцию Properties объектов Connection, Command, Recordset ряд свойств, часть из которых является специфической для данного Провайдера. Некоторые из этих свойств добавляются к не открытым объектам, другие - при открытии объектов. Для всех этих свойств, доступных в ADO, есть аналог в модели OLE DB. Перечисление всех этих свойств заняло бы слишком много места. Чтобы дать некоторое представление, укажу несколько свойств объекта Connection:

  • Accesible Tables - булево свойство, указывающее, имеет ли пользователь разрешение на выполнение запросов (SQL-операторов) над таблицами базы данных.
  • File Usage - указывает, как драйвер воспринимает источник данных, - как файл или как каталог.
  • Special Characters - указывает, какие символы имеют специальный смысл для ODBC драйвера.
  • Stored Procedures - определяет доступность использования хранимых процедур.


  • Провайдер Oracle

    Полное имя этого Провайдера - Microsoft OLE DB Provider for Oracle. Он позволяет получить доступ к базе данных Oracle. Типичная строка соединения имеет вид:
    "Provider= MSDAORA; Data Source=ServerName; User ID =userName; Password=userPassword;"
    Параметр Data Source задает имя сервера. В строке соединения Провайдеру могут быть переданы и специфические свойства, например, параметр OLE DB Services позволяет задать маску, каждый бит которой позволяет включить или выключить соответствующую OLE DB службу. Другой способ передачи специфических для Провайдера параметров - через коллекцию Propertieis объекта Connection. Большую часть этой коллекции для объектов Connection, Command и Recordset Провайдер формирует динамически.
    Этот Провайдер поддерживает работу только со статическим курсором.

    Провайдер SQL Server

    Полное имя этого Провайдера - Microsoft OLE DB Provider for SQL Server. Он позволяет получить доступ к данным Microsoft SQL Server. Типичная строка соединения имеет вид:
    "Provider= SQLOLEDB; Data Source=ServerName; Initial Catalog =databaseName; User ID =userName; Password=userPassword;"
    Параметр Data Source или Server задает имя сервера. Параметр Initial Catalog или Database - имя базы данных на сервере.
    В строке соединения Провайдеру могут быть переданы и специфические свойства, например, параметр Current Language позволяет задать язык, используемый для системных сообщений. Язык должен быть инсталлирован на SQL Server, иначе возникнет ошибка в момент открытия соединения. Другой способ передачи специфических для Провайдера параметров - через коллекцию Propertieis объекта Connection. Большую часть этой коллекции для объектов Connection, Command и Recordset Провайдер формирует динамически.

    Провайдеры

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

    Сравнение объектных моделей ADO и DAO

    В объектной модели DAO есть корневой объект - DBEngine, определяющий машину баз данных. В этот объект вложена коллекция объектов Workspaces, каждый элемент которой - объект Workspace - задает сеанс работы, являясь неким эквивалентом объекта Session в модели OLE DB. В модели ADO сеанс работы связывается с объектом Connection, задающим соединение с базой данных. Заметьте, модель ADO во многом упрощена в сравнении с моделью DAO, - в ней нет даже такого объекта как объект Database, являющегося одним из основных объектов в модели DAO.
    Конечно, главное достижение в модели ADO связано с возможностью работы с разнообразными источниками данных и введением такого понятия как Провайдер. В модели DAO, в которой можно работать только с двумя Провайдерами, отсутствие этого понятия и универсального подхода, характерного для ADO, привело к существенному осложнению самой модели. Модель DAO позволяет в течение одной сессии, или, другими словами, в рабочем пространстве работать либо с источником данных Microsoft Jet, либо с ODBC - источниками данных. С объектной точки зрения это означает, что в объект WorkSpace, задающий рабочее пространство, вкладываются две достаточно разные совокупности объектов. Я ограничусь рассмотрением лишь одной половинки этой модели и рассмотрю модель DAO, связанную с источниками данных Microsoft Jet. Взгляните, как выглядит объектная модель DAO для этого случая:
    Сравнение объектных моделей ADO и DAO

    Рис. 6.6.  Объектная модель DAO для рабочей области Microsoft Jet
    Обратите внимание, чтобы не усложнять схему, на нижних уровнях иерархии ряд объектов опущен. Вот как следует дополнить эту схему:
  • Объект Container содержит коллекцию Documents с элементами Document,
  • Объект QueryDef содержит коллекцию Parameters с элементами Parameter,
  • Объекты OueryDef, Recordset, Relation, TableDef содержат коллекцию Fields c элементами Field.

  • Я уже говорил, что объектная модель DAO намного сложнее, чем модель ADO. Чтобы в полной мере реализовать функциональность, обеспечиваемую объектами DAO, представленными на рис. 6.6, в модели АДО потребуется использовать три группы объектов и, соответственно, подключить три различные библиотеки объектов:
  • Объекты ADO, находящиеся в библиотеке ADODB. Их подробному описанию посвящена предыдущая и эта глава. Объекты этой библиотеки позволяют манипулировать с данными базы данных Access (Microsoft Jet). Объект Connection позволяет связаться с базой данных и заменяет, в определенной степени, объекты Workspace и Database. Объект Command, позволяющий выполнять SQL-операторы, заменяет объект QueryDef, который выполняет эти действия в модели DAO. Наконец, объект Recordset присутствует в обеих моделях, выполняя, в общем, аналогичные действия.
  • Объекты из библиотеки ADOX позволяют работать с метаданными. Эту библиотеку следует подключать, если соответствующие объекты DAO - QueryDef, TableDef используются для этих целей, создавая или модифицируя стандартные запросы и таблицы базы данных. Объекты User, Group и их коллекции, присутствующие в моделях DAO и ADOX, выполняют аналогичную работу по обеспечению безопасности и прав доступа пользователей к данным.
  • Еще одну библиотеку объектов - Microsoft Jet and Replication Objects (JRO) следует подключать, если объекты DAO используются для создания реплик (копий базы данных) и их синхронизации. Ранее, рассматривая объекты ADO, я не упоминал эту библиотеку, поскольку в отличие от остальных объектов ADO, объекты этой библиотеки не носят общий характер, - они могут использоваться только при работе с одним источником данных - Microsoft Jet. Основным объектом модели JRO является объект Replica, задающий репликацию базы данных. Свойства и методы этого объекта используются для создания новой репликации, получения и модификации свойств уже существующих реплик и синхронизации реплик. В модели DAO эти функции выполняет объект Database. Еще один объект JetEngine из модели JRO позволяет выполнять операции по сжатию базы данных и обновлению данных, хранимых в кэш-памяти.


  • Я не буду останавливаться на деталях сходства и различия в моделях ADO и DAO. Отмечу еще только два момента:

  • В модели DAO транзакции связываются с сеансом работы, роль которого играет объект Workspace. Но поскольку в этот объект вложена коллекция объектов Databases, то в транзакции участвуют все открытые базы данных, запоминаются все изменения и при откате восстанавливается состояние всех баз данных. В модели ADO каждая транзакция связана только со своей базой данных, открытой заданным соединением.
  • Модель ADO гораздо компактнее модели DAO. Пожалуй, она проще для понимания, что тоже имеет немаловажное значение. В ряде случаев она требует и меньших ресурсов, например, когда требуется только манипулирование данными, вместо подключения многофункциональной и более объемной библиотеки объектов DAO можно подключить более компактную библиотеку объектов ADO. Главный выигрыш в использовании ADO, конечно же, состоит в универсальном характере объектов этой библиотеки, позволяющих работать с разнообразными источниками данных. Эти объекты можно использовать не только при работе с языками VB и VBA, но и в языке сценариев - VBScript, что позволяет создавать, как обычные приложения, так и Web-приложения, в которых используются источники данных. Один пример использования объектов ADO для создания Web-документа будет приведен в конце этой главы.


  • Свойства и методы коллекции Errors

    У коллекции два свойства:
  • Count - возвращает число элементов коллекции,
  • Item - свойство по умолчанию позволяет по индексу получить необходимый элемент коллекции - объект Error.

  • У коллекции два метода:
  • Clear - позволяет принудительно очистить содержимое коллекции,
  • Refresh - обновляет коллекцию.


  • Свойства и методы объекта Parameter

    Поскольку все свойства и методы объекта Parameter так или иначе уже появлялись в нашем описании, то я их только перечислю, не приводя подробного описания. Вот список свойств этого объекта: Attributes, Direction, Name, NumericScale, Precision, Properties, Size, Type, Value. Метод у этого объекта всего один - AppendChunk, - и он тоже был описан при рассмотрении объекта Field.
    Используя свойства и методы коллекции и самого объекта Parameter можно делать следующее:
  • Свойство Name позволяет устанавливать и возвращать имя параметра, а свойство Value - его значение.
  • Свойства Attributes, Direction, Precision, NumericScale, Size и Type позволяют устанавливать и возвращать различные характеристики параметра.
  • Свойство Direction позволяет указать, является ли параметр входным, выходным, входным-выходным или представляет возвращаемое процедурой значение. Поскольку не все Провайдеры могут установить направление передаваемых параметров, то в таких ситуациях необходимо программно устанавливать значение этого свойства перед тем, как вызывать хранимую процедуру или параметризованный запрос.
  • Метод AppendChunk позволяет передавать параметру порции двоичных или символьных данных в случае длинных значений параметра.
  • Получать доступ к специфическим для Провайдера атрибутам, вызывая свойство Properties.
  • Зная свойства параметров, ассоциированных с хранимой процедурой создать параметр методом CreateParameter и присоединить его к коллекции, используя ее метод Append. Это позволяет избежать лишних вызовов метода Refresh для получения нужной информации от Провайдера и тем самым снизить нагрузку на передачу данных.


  • Свойства объекта Catalog

    У объекта Catalog 6 свойств: ActiveConnection, Tables, Procedures, Views, Groups, Users. Пять последних свойств возвращают одноименные коллекции. А о первом свойстве поговорим чуть подробнее.

    Свойства объекта Column

    Свойства этого объекта определяют различные характеристики поля, включенного в таблицу:
  • Property Name As String. Задает имя поля.
  • Property Type As DataTypeEnum. Задает тип поля. Возможные значения определяются константами из уже упоминавшегося перечисления DataTypeEnum.
  • Property DefinedSize As Long. Задает определяемый размер поля. Заметьте, фактический размер может быть меньше и зависит от значения, хранимого в поле.
  • Property Precision As Long, Property NumericScale As Byte. Эти два свойства определены только для полей, хранящих числовые значения. Они определяют общее число хранимых цифр и число цифр после запятой.
  • Property SortOrder As SortOrderEnum. Записи в таблице могут быть упорядочены по тому или иному полю. Данное свойство определяет порядок сортировки, - по возрастанию или убыванию, что задается его возможными значениями: adSortAscending, adSortDescending. Свойство применимо только для индексируемых полей.
  • Property ParentCatalog As Catalog. Свойство задает родительский каталог, позволяя подняться при необходимости к корневому объекту ADOX. Включение корневого объекта в объекты нижних уровней типично для объектных моделей, используемых Microsoft. Такое включение облегчает программистам работу, упрощая навигацию между объектами.
  • Property RelatedColumn As String. Это свойство применимо только для ключевых полей, оно указывает имя поля, являющегося внешним ключом в связываемой таблице. Для уже присоединенных полей к коллекции Columns свойство имеет статус "только для чтения".
  • Property Attributes As ColumnAttributesEnum. Как и для других объектов ADO, обладающих подобным свойством, данное свойство задает набор дополнительных атрибутов объекта. Значение свойства представляет маску, каждый бит которой определяет, включен или выключен тот или иной атрибут. Возможных атрибутов, которые можно установить, всего два - adColFixed и adColNullable. Они указывают, имеет ли поле фиксированную длину и может ли использоваться значение Null. Значение этого свойства, принимаемое по умолчанию и равное 0, означает, что упомянутые атрибуты не включены.
  • Property Properties As Properties. Задает коллекцию свойств, зависящих от Провайдера.

  • В заключение отмечу, что конкретный Провайдер может не поддерживать всех свойств объекта Column.

    Свойства объекта Error

    У объекта Error нет методов и нет событий. У него есть только свойства:
  • Property Description As String (read-only). Задает описание ошибки, которое можно показать пользователю, если не предвидится другой способ обработки ошибки.
  • Property HelpContext As Long (read-only), Property HelpFile As String (read-only). Эти свойства позволяют обратиться за дополнительными разъяснениями к справочной системе, если в ней содержится более подробная информация об ошибке.
  • Property NativeError As Long (read-only). Код ошибки, специфический для данного Провайдера. Полезен, если есть описание ошибок Провайдера.
  • Property Number As Long (read-only). Номер, однозначно идентифицирующий ошибку. Является константой типа HRESULT. Значения соответствуют, но не совпадают со значениями констант, принадлежащих перечислению ErrorValueEnum. Классификация ошибок достаточно подробная, констант в этом перечислении много. Приведу значения лишь некоторых: adErrCantChangeConnection, adErrCantConvertvalue, adErrDataOverflow, adErrFieldsUpdateFailed, adErrInTransaction. Замечу, что, зная номер Number, не просто разобраться, какой тип ошибки имеет место.
  • Property Source As String (read-only). Задает имя объекта, породившего ошибку. В совокупности свойства Source, Number и Description позволяют проанализировать ошибку и, возможно, в диалоге с пользователем устранить ее причину.
  • Property SQLState As String (read-only). Задает 5-и символьный код, соответствующий стандарту ANSI SQL, определяющий состояние SQL, вызвавшее ошибку.


  • Свойства объекта Field

    Объект Field задает поле записи. С помощью его свойств можно определить характеристики поля - тип, значение, формат и установить эти значения при формировании новых полей, присоединяемых к записи. Рассмотрим все по порядку:
  • Property Name As String. Задает имя поля.
  • Property ActualSize As Long, Property DefinedSize As Long. Первое свойство позволяет определить фактический размер хранимого в поле значения. Оно имеет статус только для чтения. В случае, если ADO не может определить по каким либо причинам истинный размер значения, в качестве результата возвращается константа adUnknown. Второе свойство задает метаданные - размер поля, заданный в момент его определения. Его статус - "чтение/запись". Размер всегда задается в байтах.
  • Property Precision As Byte. Для числовых полей указывает максимальное число цифр, используемых для представления значения.
  • Property NumericScale As Byte. Для числовых полей указывает число цифр после запятой, используемых для представления значения.
  • Property Type As DataTypeEnum. Задает тип поля. Приведу значения некоторых констант из перечисления: adInteger = 3, adVarWChar = 202 (для строковых полей), adCurrency = 6, adLongVarWChar = 203 (для поля Memo).
  • Property DataFormat As Unknown. Это свойство, которое, судя по названию, должно задавать формат данных, отсутствует в версии ADO 2.6. Хотя оно присутствует в предыдущей версии 2.5, но возвращает в качестве результата ссылку на пустой объект, так что не стоит пытаться его использовать.
  • Property Status As Long. Возвращает статус поля, позволяющий, например, определить, было ли поле нормально добавлено или удалено из записи. Значения свойства задаются константами из перечисления FieldStatusEnum. Вот некоторые значения констант: adFieldAlreadyExists, adFieldOK, adFieldPendingInsert, adFieldPendingDelete, adFieldCantCreate. Анализ статуса поля, позволяет определить, что произошло при выполнении операций над тем или иным полем.
  • Property OriginalValue As Variant, Property UnderlyingValue As Variant, Property Value As Variant. Все эти свойства задают значение поля в разных его ипостасях - значение поля в записи до его изменений, значение поля в базе данных, текущее значение поля в записи.
  • Property Attributes As Long. Свойство задает маску, определяющую характеристики поля. Значение представляет сумму констант из перечисления FieldAttributeEnum. Вот лишь некоторые значения этих констант: adFldKeyColumn, adFldLong, adFldIsChapter, adFldUpdatable.
  • Property Properties As Properties. Возвращает коллекцию свойств поля.


  • Свойства объекта Stream

    Объект Stream имеет следующие свойства:
  • Property Charset As String. Строка, задающая множество символов, в которое транслируется данные потока. Свойство имеет смысл только для текстовых файлов, которые по умолчанию должны быть представлены в кодировке Unicode, -значением свойства по умолчанию.
  • Property EOS As Boolean. Аналог свойства EOF для файлов, позволяет проследить за окончанием потока при последовательном чтении его байтов. Принимает значение True, когда свойство Position определяет позицию, лежащую за последним символом потока.
  • Property LineSeparator As LineSeparatorEnum. Константы перечисления задают символы, задающие конец строки в текстовом файле. Возможные значения: adCR, adCRLF, adLF.
  • Property Mode As ConnectModeEnum. Свойство, задающее статус объекта. Им обладают большинство объектов ADO.
  • Property Position As Long. Определяет текущую позицию в потоке. Поскольку над потоком определены операции чтения и записи, то понятно, что должна быть определена текущая позиция (курсор), отмечающая начальную точку при выполнении этих операций.
  • Property Size As Long. Задает размер потока. И позиция и размер потока измеряются в байтах.
  • Property State As ObjectStateEnum. Свойство, задающее состояние объекта. Им обладают большинство объектов ADO.
  • Property Type As StreamTypeEnum. Тип потока задается константой из перечисления. Имеет два возможных значения: adTypeBinary, adTypeText.


  • Свойства объекта Table

    Свойства объекта позволяют определить новую таблицу или модифицировать уже существующую. Свойств у объекта сравнительно немного. Вот они:
  • Три основных свойства: Columns, Keys и Indexes возвращают одноименные коллекции, которые и задают основные элементы таблицы - ее поля, ключи и индексы. Все три коллекции имеют одинаковый и уже описанный набор из трех методов: Append, Delete, Refresh и двух свойств - Item и Count. Создаются элементы коллекций конструктором New, а добавляются в коллекцию методом Append, в полном соответствии с выше приведенной схемой для объекта Table.
  • Property DateCreated As Variant, Property DateModified As Variant. Свойства имеют статус только для чтения и возвращают дату создания и дату последней модификации таблицы.
  • Property Name As String. Задает имя таблицы.
  • Property ParentCatalog As Catalog. Задает родительский каталог, - объект, метод Create которого создал базу данных, которой и принадлежит таблица.
  • Property Properties As Properties. Возвращает коллекцию свойств, стандартную для многих объектов ADO.
  • Property Type As String. Свойство задает тип, но не, как обычно, константой, а строкой, значения которой могут быть, например, "TABLE", "SYSTEM TABLE" или "GLOBAL TEMPORARY".

  • Таким образом, для создания таблицы или ее модификации приходится, главным образом, работать с объектами Column, Key, Index и их коллекциями. Рассмотрим устройство этих объектов.

    Свойство ActiveConnection

    Свойство имеет статус "чтение/запись". Синтаксически определяется следующим образом:
    Property ActiveConnection As Variant
    Позволяет установить ссылку на объект Connection или задать строку, определяющую соединение. Возвращает ссылку на активный объект Connection.

    Мир объектов Excel 2000

    Чистка полей таблицы заказа

    Рассмотрим действие еще одной командной кнопки - "Очистить заказ". Выше, в разделе "Работа с заказом", я говорил, что последнее слово всегда остается за пользователем, и он может вручную редактировать таблицу заказов. В какой - то момент может оказаться, что он хотел бы прекратить работу с заказом, без сохранения его в базе данных. Конечно, для перехода к созданию нового заказа можно в любой момент нажать кнопку "Сформировать заказ", но, вполне естественно, предварительно нажать кнопку "Очистить заказ", чтобы очистить поля бланка. Приведу код соответствующего обработчика события:
    Private Sub ClearOrder_Click() 'Чистка полей заказа ClearBookFields End Sub
    Процедура ClearBookFields, которая выполняет работу по чистке полей, уже вызывалась в другом месте программного проекта. Теперь пришла пора привести ее код:
    Public Sub ClearBookFields() 'Чистка полей документа Dim curField As Range Set curField = Range("A34:G45") curField.ClearContents Set curField = Range("I34:I45") curField.ClearContents
    'Установить свойства элементов управления With ThisWorkbook.Worksheets(1).OLEObjects .Item("LabelNDS").Visible = False .Item("NDS").Visible = False .Item("SaveOrder").Object.Enabled = False End With
    End Sub
    Помимо чистки полей в процедуре делаются невидимыми уже упоминавшиеся OLE-объекты, а главное, выключается командная кнопка "Сохранить заказ". Это и понятно, - раз поля таблицы заказа пусты, - запись в базу данных должна быть запрещена, пока не будет сформирован новый заказ.
    Обратите внимание на один чисто программистский аспект работы этой процедуры, связанный с OLE-объектами. Свойство Visible доступно для всех элементов коллекции OLEObjects. Но чтобы добраться до свойства Enabled, мне предварительно нужно вызвать свойство Object, и только потом у полученного объекта можно установить значение свойства Enabled.
    На этом я заканчиваю рассмотрение интерактивности, добавляемой в раздел заказов нашего документа.

    Чистка полей

    Чтобы закончить рассмотрение вопросов, связанных с приданием интерактивности той части документа, где речь идет о реквизитах заказчика, нам осталось рассмотреть самую простую задачу - чистки соответствующих полей документа. Эта задача является частью более общей задачи чистки всех полей документа, но она может возникать и в процессе работы с разделом "Реквизиты заказчика". Понятно, что с программистской точки зрения особых проблем здесь возникать не может. Тем не менее, приведу текст обработчика события Click командной кнопки "Очистить":
    Private Sub CommandButton4_Click() 'Чистка полей бланка с реквизитами заказчика ClearCustomerFields End Sub Public Sub ClearCustomerFields() 'Чистка полей бланка с реквизитами заказчика Dim curField As Range Set curField = Range("D19:J22") curField.ClearContents Set curField = Range("D21") curField = "tel: Email: " End Sub
    Единственное, о чем стоит упомянуть, это то, что при чистке сохраняется формат, который следует выдерживать при задании новой информации в поле, отведенном для указания телефона и Email адреса.

    Формирование и показ списка отобранных заказчиков

    Эта задача является некоторым усложнением аналогичной задачи, когда показывался список всех заказчиков. Принципиально задача остается той же. После получения набора записей нужно сформировать список для предъявления его пользователю. Усложнения касаются двух моментов:
  • В формировании списка участвуют лишь записи, прошедшие фильтр.
  • Формируется список из нескольких столбцов, каждый из которых содержит тот или иной реквизит заказчика - поле записи.

  • Приведу текст процедуры, которая решает эту задачу:
    Public Sub FormListSelectedCustomers() 'Готовит список заказчиков, удовлетворяющих критериям поиска Dim txt As String Dim i As Integer Dim RowIndex As Integer Dim B1 As Boolean, B2 As Boolean, B3 As Boolean Dim B4 As Boolean, B5 As Boolean 'Число столбцов списка - реквизитов поиска Const ColumnCount = 5
    'Поиск заказчиков в наборе по заданным реквизитам SelectedCustomers.ListBox1.Clear SelectedCustomers.ListBox1.ColumnCount = ColumnCount RowIndex = 0 With Rst1 .MoveFirst Do While Not .EOF B1 = False: B2 = False: B3 = False: B4 = False: B5 = False On Error Resume Next txt = LookCustomer.TextBox1.Text B1 = txt <> "" And VBA.InStr(!Название, txt) txt = LookCustomer.TextBox2.Text B2 = txt <> "" And VBA.InStr(!Адрес, txt) txt = LookCustomer.TextBox3.Text B3 = txt <> "" And VBA.InStr(!Город, txt) txt = LookCustomer.TextBox4.Text B4 = txt <> "" And VBA.InStr(!Телефон, txt) txt = LookCustomer.TextBox5.Text B5 = txt <> "" And VBA.InStr(!Директор, txt) If B1 Or B2 Or B3 Or B4 Or B5 Then 'Текущая запись переносится в список 'Первый столбец SelectedCustomers.ListBox1.AddItem .Fields(1) 'Остальные столбцы For i = 1 To ColumnCount - 1 txt = "" txt = .Fields(i + 1) SelectedCustomers.ListBox1.Column(i, RowIndex) = txt Next i RowIndex = RowIndex + 1 End If .MoveNext Loop End With
    End Sub
    Фильтр, через который проходят все записи, состоит из пяти условий, объединенных операцией "ИЛИ". Каждое условие определяет, задан ли ключ для определенного поля записи, и, если да, то является ли ключевое слово частью поля записи. Записи, прошедшие фильтр, добавляются в список формы SelectedCustomers, при этом поля записи переносятся в соответствующие столбцы списка. Обратите внимание на оператор обработки исключительных ситуаций - On Error Resume Next - позволяющий справиться с возможной ошибкой при выполнении функции InStr, возникающей в ситуации, когда некоторые из полей записи базы данных не определены.
    В заключение, взгляните, как выглядит спроектированная форма SelectedCustomers в процессе работы:
    Формирование и показ списка отобранных заказчиков

    Рис. 7.5.  Форма SelectedCustomers, содержащая список выбранных заказчиков
    Поскольку записей, прошедших фильтр, может быть несколько, то и здесь окончательный выбор остается за пользователем, работающим с документом. Вот что происходит, когда, сделав выбор в списке, он нажимает кнопку "Выбери меня" формы SelectedCustomers:
    Private Sub CommandButton1_Click() 'Данные о реквизитах заказчика переносятся 'из набора записей в поля бланка Счет-фактура FromSelectedListToFields

    End Sub

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

    Public Sub FromSelectedListToFields() 'Данные о заказчике, выбранном из списка, 'ищутся в базе данных и затем переносятся 'из набора записей в поля бланка Счет-фактура Const Кавычка = "'" Dim Key As String Dim strSQL1 As String

    If SelectedCustomers.ListBox1.ListIndex >= 0 Then 'Выбор сделан 'Формирование запроса к базе данных Key = SelectedCustomers.ListBox1.Column _ (0, SelectedCustomers.ListBox1.ListIndex) strSQL1 = "Select * FROM [Заказчики]WHERE [Название]= " _ & Кавычка & Key & Кавычка Cmd1.CommandText = strSQL1 Set Rst1 = Cmd1.Execute 'Перенос данных из набора записей в поля бланка FromRstToFields 'Форма сделала свое дело - форма закрывается SelectedCustomers.Hide Else MsgBox ("Выберите заказчика!") End If End Sub

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


    End Sub

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

    Public Sub FromSelectedListToFields() 'Данные о заказчике, выбранном из списка, 'ищутся в базе данных и затем переносятся 'из набора записей в поля бланка Счет-фактура Const Кавычка = "'" Dim Key As String Dim strSQL1 As String

    If SelectedCustomers.ListBox1.ListIndex >= 0 Then 'Выбор сделан 'Формирование запроса к базе данных Key = SelectedCustomers.ListBox1.Column _ (0, SelectedCustomers.ListBox1.ListIndex) strSQL1 = "Select * FROM [Заказчики]WHERE [Название]= " _ & Кавычка & Key & Кавычка Cmd1.CommandText = strSQL1 Set Rst1 = Cmd1.Execute 'Перенос данных из набора записей в поля бланка FromRstToFields 'Форма сделала свое дело - форма закрывается SelectedCustomers.Hide Else MsgBox ("Выберите заказчика!") End If End Sub

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

    Формирование и показ списка

    Чтобы была возможность показать пользователю список всех заказчиков, я спроектировал форму с именем Customers, в которую поместил два элемента управления - список и командную кнопку. Вот как выглядит эта форма в процессе работы:
    Формирование и показ списка

    Рис. 7.3.  Форма Customers, позволяющая сделать выбор в списке заказчиков
    Решать задачу формирования списка нужно начинать с получения из базы данных набора записей, содержащих названия заказчиков. Возможно, Вы помните, что в спроектированной нами базе данных содержится стандартный запрос с именем "Список заказчиков", решающий именно эту задачу. Уже тогда, при проектировании базы было понятно, что такой запрос может понадобиться. Поэтому теперь единственное, что нужно сделать, - получить набор записей, соответствующий этому запросу. Как это реализовать с помощью объектов ADO, - подробно описывалось в предыдущих главах. Так что осталось взглянуть на текст соответствующей процедуры CreateListCustomers, которая первой вызывается в процедуре Choose:
    Public Sub CreateListCustomers() 'Создание и выполнение команды, 'позволяющей получить данные о заказчиках Dim strSQL1 As String strSQL1 = "Select * FROM [Список заказчиков]" 'задание объекта Command Cmd1.CommandText = strSQL1 'вызов команды на исполнение методом Execute Set Rst1 = Cmd1.Execute End Sub
    Как видите, получить нужный набор записей достаточно просто. Конечно, нужно иметь в виду, что глобальные объекты Con1, Cmd1, Rst1 уже созданы, соединение с базой данных и конфигурирование команды - объекта Cmd1 выполнено при инициализации документа. Так что в данной процедуре осталось задать лишь текст команды, который в данном случае сводится к выполнению стандартного запроса "Список заказчиков". При выполнении команды формируется объект Rst1, который и содержит названия заказчиков.
    Назначение следующей процедуры состоит в том, чтобы данные из набора записей - объекта Rst1 - перенести в список спроектированной формы Customers. Все делается совершенно просто и естественно:
    Public Sub FormListCustomers() 'Перенос данных о заказчиках из набора записей 'в список формы Customers With Rst1 .MoveFirst Customers.ListBox1.Clear Do While Not .EOF 'Текущая запись переносится в список Customers.ListBox1.AddItem .Fields(0) .MoveNext Loop End With End Sub

    Заметьте, схема прохождения набора записей обсуждалась в главе 5, а о работе с элементами управления, в частности, с объектами класса ListBox я подробно рассказывал в предыдущих книгах этой серии, например, в книге [1]. Может быть, стоит лишь напомнить, что запрос "Список заказчиков" возвращает запись, содержащую одно поле с названием организации заказчика. Содержимое этого поля и переносится в список.

    Теперь, когда данные о заказчиках получены, список с названиями организаций сформирован, осталось предъявить форму Customers пользователю. Эту операцию и выполняет метод Show объекта Customers, что отражено в последнем выполняемом операторе процедуры Choose. Форма, появляющаяся на экране и показана на рис. 7.3. В открывшейся форме пользователь может выбрать из списка нужного заказчика, после чего нажать командную кнопку "Выбери меня". Обработчик события Click этой кнопки и отвечает за последующие действия, позволяя решить следующую упоминавшуюся подзадачу - поиск по ключу в базе данных сведений о реквизитах выбранного заказчика. Вот текст этого обработчика:

    Private Sub CommandButton1_Click() 'Данные о заказчике, выбранном из списка, 'ищутся в базе данных и затем переносятся 'из набора записей в поля бланка Счет-фактура FromListToFields

    End Sub

    Содержательные действия выполняет процедура FromListToFields, которая и реализует решение подзадачи по формированию запроса на поиск.

    Формирование заказа

    Какие задачи предполагается решать на этапе формирования заказа? Их несколько:
  • Показать список всех возможных товаров, сведения о которых хранятся в базе данных. Поскольку в нашем случае товаром являются книги, то список будет содержать сведения о книгах, выпускаемых и продаваемых офисом РР. Напомню, что вся информация о книгах хранится в отдельной таблице с именем "Книги".
  • Дать возможность пользователю выбрать из списка все книги, которые он хочет заказать.
  • На основании выбора, сделанного пользователем, автоматически заполнить все возможные поля документа в разделе заказы.

  • Принципиально, решение всех этих задач уже обсуждалось. Мы умеем связываться с базой данных, получать нужный набор записей, формировать по данным этих записей список в некоторой форме, которая предъявляется пользователю с предоставлением ему возможности выбора нужных элементов списка. Обсуждалась также и задача переноса данных из набора записей в поля документа. Так что нам осталось посмотреть, как выглядит реализация, написанная мной для решения этих задач в данной конкретной ситуации.
    Решение всех перечисленных задач инициирует пользователь в тот момент, когда он нажимает кнопку "Сформировать заказ". Рассмотрим работу обработчика события Click, запускаемого при нажатии данной командной кнопки:
    Private Sub FormOrder_Click() 'Формирование заказа OrderForm End Sub
    Как всегда, основную работу выполняет вызываемая в обработчике процедура. Вот ее текст:
    Public Sub OrderForm() 'Формирование заказа! Dim txt As String Dim i As Integer Dim RowIndex As Integer 'Число столбцов списка Const ColumnCount = 4 'Создание набора записей с информацией о книгах Cmd1.CommandText = "Select [Автор],[Название],[Год издания]," & _ "[Цена] From [Книги]" Set Rst1 = Cmd1.Execute 'Формирование списка формы Books Books.ListOfBooks.Clear Books.ListOfBooks.ColumnCount = ColumnCount RowIndex = 0 With Rst1 .MoveFirst Do While Not .EOF On Error Resume Next 'Текущая запись переносится в список 'Первый столбец Books.ListOfBooks.AddItem .Fields(0) 'Остальные столбцы For i = 1 To ColumnCount - 1 txt = "" txt = .Fields(i) Books.ListOfBooks.Column(i, RowIndex) = txt Next i RowIndex = RowIndex + 1 .MoveNext Loop End With 'Показать форму со списком книг Books.Show End Sub

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

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

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

    Формирование заказа

    Рис. 7.9.  Форма Books для выбора книг, представленных в заказе

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

    Дальнейшие действия опять инициируются пользователем. Он производит выбор в списке книг и нажимает командную кнопку с надписью "Выбери нас" в форме Books. Затем в дело вступает обработчик события Click для этой кнопки. Так и реализуется та самая интерактивность, о которой идет речь в этой главе. Вот код этого обработчика:

    Private Sub SelectUs_Click() 'Данные о выбранных книгах переносятся в поля документа SelectedBooks End Sub

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

    Формирование заказа

    увеличить изображение
    Рис. 7.10.  Таблица заказа, сформированная по выбору пользователя

    А теперь рассмотрим, как это делается:

    Public Sub SelectedBooks() 'Данные о выбранных книгах переносятся в поля документа Dim i As Integer, j As Integer Dim curField As Range, curField1 As Range Dim txt As String Dim myNds As Object 'Dim myNds As TextBox 'Чистка полей документа ClearBookFields 'Инициализация Set curField = Range("A34:D34") Set curField1 = Range("E34") With ThisWorkbook.Worksheets(1).OLEObjects Set myNds = .Item("NDS").Object .Item("NDS").Visible = True .Item("LabelNDS").Visible = True


    'Проход по списку With Books.ListOfBooks j = 0 For i = 0 To .ListCount - 1 If .Selected(i) Then 'Книга выбрана 'Перенос данных в поля документа curField.Offset(j) = .List(i, 0) & " '" & _ .List(i, 1) & "'" curField1.Offset(j) = "шт." curField1.Offset(j, 2) = .List(i, 3) curField1.Offset(j, 4) = myNds.Text j = j + 1 End If Next i End With 'Включить кнопку "Сохранить Заказ" .Item("SaveOrder").Object.Enabled = True End With 'Спрятать форму "Книги" Books.Hide End Sub

    Текст процедуры довольно большой, и он нуждается в некоторых дополнительных комментариях:

  • Начну с рассмотрения локальных объектов, описанных в процедуре. Я ввел два объекта Range - curField и curField1. Они используются при формировании таблицы заказа для заполнения полей документа. Один из них задает области, полученные слиянием ячеек, другой - области, состоящие из одной ячейки. Более подробно стоит сказать об объекте myNds класса Textbox. Дело в том, что я разместил на документе еще два элемента управления - два OLE-объекта. Один из них является меткой, другой - полем ввода. Предназначены они для того, чтобы можно было задавать значение ставки НДС, которое по умолчанию будет использоваться при формировании таблицы заказов и проведения необходимых вычислений. Объекты эти я решил сделать видимыми только в момент формирования заказа. В остальное время они будут невидимыми. Объект myNds используется при проведении вычислений в процессе формирования таблицы заказа. Взгляните еще раз на рис. 7.10, - он сделан тогда, когда эти два элемента управления являются видимыми. В окне ввода в этот момент можно задать значение НДС, принимаемое по умолчанию.
  • Прежде чем заполнять поля таблицы заказов данными о новом заказе, следует ее очистить от возможно уже заполненного предыдущего заказа. Эту функцию и выполняет вызываемая процедура ClearBookFields. Поскольку эта процедура вызывается и в других местах, то о ней я скажу подробнее чуть позже и там же приведу ее текст.
  • В разделе инициализации процедуры я устанавливаю значения введенных объектов curField, curField1, myNds, а также делаю видимыми объекты, связанные с НДС. Это позволяет показать пользователю ставку НДС, используемую в расчетах, и дает ему возможность при необходимости изменить значение ставки.
  • Основная работа делается в цикле по списку товаров. Отбирая выбранные пользователем товары, - свойство Selected списка позволяет это сделать, - переношу данные о книгах в поля таблицы заказов. Значение поля НДС берется из OLE-объекта, о котором я говорил чуть выше.
  • С программистской точки зрения стоит обратить внимание на технику использования смещения Offset, позволяющую эффективно работать с объектами Range внутри таблицы.
  • В заключительной части процедуры делаются две вещи - включается командная кнопка "Сохранить заказ" и закрывается форма Books. Форма закрывается, поскольку выбор пользователя обработан, так что в показе формы уже нет необходимости. Командная кнопка "Сохранить заказ" в обычном состоянии документа выключена, поскольку пока заказ не сформирован, пользователь и не должен пытаться сохранить его. Теперь же, после формирования заказа, документ переходит в новое состояние, в котором эта кнопка будет играть основную роль, позволяя сохранить сформированный заказ в базе данных. Но об этом чуть позже. А сейчас поговорим о том, что еще нужно сделать, чтобы закончить формирование заказа.


  • Формирование запроса на поиск

    Выбрав заказчика из списка, пользователь тем самым задает ключ, позволяющий отобрать нужную запись в таблице базы данных "Заказчики". Формирование запроса на поиск в базе данных, когда ключ поиска известен, технически сводится к корректному заданию SQL-оператора. Получение набора записей по динамически сформированному запросу принципиально не сложнее, чем уже рассмотренная задача, когда использовался стандартный запрос. Вот как все это решается в процедуре FromListToFields:
    Public Sub FromListToFields() 'Данные о заказчике, выбранном из списка, 'ищутся в базе данных и затем переносятся 'из набора записей в поля бланка Счет-фактура. Const Кавычка = "'" Dim Key As String Dim strSQL1 As String If Customers.ListBox1.ListIndex >= 0 Then 'Выбор сделан. 'Формирование запроса к базе данных. Key = Customers.ListBox1.List(Customers.ListBox1.ListIndex) strSQL1 = "Select * FROM [Заказчики]WHERE [Название]= " _ & Кавычка & Key & Кавычка 'Изменение описания команды. Cmd1.CommandText = strSQL1 Set Rst1 = Cmd1.Execute
    'Перенос данных из набора записей в поля бланка. FromRstToFields 'Форма сделала свое дело - форма закрывается. Customers.Hide Else MsgBox ("Выберите заказчика!") End If End Sub
    Как видите, все происходит по задуманному плану. Формируется строка с ключом, строка с SQL-запросом, изменяется текст команды у объекта Command и при запуске его метода Execute формируется нужный набор записей, состоящий в данном случае из одной записи, содержащей все реквизиты заказчика.
    Вызов процедуры FromRstToFields позволяет решить последнюю подзадачу в этом пункте по переносу данных из полей текущей записи объекта Rst1 в поля бланка. Последний выполняемый оператор этой процедуры - Customers.Hide прячет форму Customers, которая сделала свое дело и должна быть закрыта.

    Интерактивная форма документа "Счет-фактура" офиса РР

    Я буду рассматривать вопросы создания интерактивных документов на конкретном примере. Надеюсь, он будет достаточно убедительным и позволит продемонстрировать, как многие из проблем, возникающих в процессе создания подобных документов, так и пути решения этих проблем.
    Создание документа начнем не на пустом месте. В качестве такового рассмотрим один из документов офиса "РР", о котором уже шла речь в предыдущих главах, и для которого база данных уже создана. Будем предполагать, что создаваемый документ используется в этом офисе при оформлении заказов на книги, и представляет вариацию типового документа, называемого обычно "Счет-фактура".
    Я напомню, что один из тезисов, пропагандируемых мною в офисном программировании, состоит в том, что программист является членом команды разработчиков, целью которой является создание системы документов. Программный проект, создаваемый программистом, не является самоцелью, - он лишь часть документа. Другие части документа могут быть созданы другими участниками совместной работы без программирования. Об этом я достаточно подробно говорил во всех предыдущих книгах по офисному программированию. В частности, в книге [2] я подробно описал процесс создания бланка "Счет-фактура" офиса РР, который был создан "руками", без программирования. Именно этот документ и послужит нам основой для дальнейшей работы. Вот как он выглядит:
    Интерактивная форма документа

    Рис. 7.1.  Документ "Счет-фактура" в начальной стадии
    Наша цель - придать интерактивность уже созданному документу.
    Программную работу с документом начнем с инициализации. И первый шаг в инициализации - подключение базы данных офиса РР. Это разумно, поскольку, как я уже говорил, интерактивный документ практически всегда взаимодействует с базой данных. Инициализация документа проводится, обычно, в момент открытия документа, а это значит - в обработчике события Open. Вот текст этого обработчика:
    Private Sub Workbook_Open() 'Инициализация документа InitAccount End Sub

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

    'Модуль InterAction 'Обеспечивает интерактивность документа "Счет-фактура" 'Глобальные переменные Public Con1 As New ADODB.Connection Public Cmd1 As New ADODB.Command Public Rst1 As New ADODB.Recordset

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

    Public Sub InitAccount() 'Установить соединение с базой данных CreateConnection 'Конфигурирование команды ConfigCommand 'Очистить поля бланка ClearFields End Sub

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

    Public Sub CreateConnection() 'Создание соединения с базой данных офиса РР Dim strConnStr As String, PathDB As String PathDB = ThisWorkbook.Path & "\dbPP2000.mdb" If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение

    'Конфигурирование соединения Con1 Con1.Provider = "Microsoft.jet.oledb.4.0" Con1.ConnectionString = "Data Source=" & PathDB 'Открытие соединения Con1.Open 'печать характеристик соединения ' Debug.Print "Attributes = ", Con1.Attributes ' Debug.Print "CommandTimeout = ", Con1.CommandTimeout ' Debug.Print "ConnectionString = ", Con1.ConnectionString ' Debug.Print "ConnectionTimeout = ", Con1.ConnectionTimeout ' Debug.Print "CursorLocation = ", Con1.CursorLocation ' Debug.Print "DefaultDatabase = ", Con1.DefaultDatabase ' Debug.Print "Mode = ", Con1.Mode ' Debug.Print "Properies.Count = ", Con1.Properties.Count ' Debug.Print "State = ", Con1.State ' Debug.Print "Version = ", Con1.Version End Sub


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

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

  • Реквизиты покупателя (заказчика).
  • Сотрудники.
  • Заказы.


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

    Интерактивные документы и базы данных

    Под интерактивным документом понимается документ, автоматически изменяющий свое содержание в ответ на действия пользователя. При этом, естественно, предполагается, что интерфейс документа содержит различные элементы управления - меню, командные кнопки, поля ввода, списки, благодаря которым пользователь может управлять изменениями в документе. Интерактивные документы, как правило, обладают еще одной важной особенностью, - они связаны с некоторой базой данных. Именно такие документы мы и будем рассматривать. В процессе работы пользователя с документом происходит обмен данными между базой данных и документом. Это означает, что в ответ на те или иные действия пользователя некоторые данные будут выбираться из базы данных и переноситься в документ, где они становятся доступными для проведения необходимых операций над ними. Справедлив и обратный процесс, - данные, созданные в документе в результате действий пользователя, могут сохраняться в базе данных.
    На три момента хочу обратить внимание:
  • Документы должны храниться достаточно долго. Поэтому без базы данных не обойтись. При этом не всегда обязательно хранить сам документ в той форме, в которой с ним работает пользователь. Часто достаточно хранить в базе данных информацию, связанную с документом. При этом важно следовать следующему принципу. Если информация, размещаемая на документе в момент его создания, уже имеется в базе данных, то ее следует переносить в документ из базы данных, а не создавать заново. Всякую новую информацию, полученную в процессе работы пользователя с документом, следует перенести в базу данных. Этот принцип работы с документами способствует поддержанию полноты и непротиворечивости данных.
  • Чаще всего, именно Excel является тем приложением, где обрабатываются данные, хранящиеся в базах данных. Поэтому многие офисные документы, требующие интерактивность, создаются как документы Excel.
  • Хотя всюду в тексте этой главы я буду говорить о базе данных, следует иметь в виду, что источники данных для документа, в общем случае, могут быть различные, - как структурированные (базы данных), так и не структурированные. Об этом достаточно подробно говорилось в предыдущих главах при рассмотрении объектов ADO.


  • Элемент-невидимка

    Вместо того чтобы удалять элементы управления из электронной формы, сделаем их невидимыми. Тогда они становятся недоступными для пользователя и одновременно не отображаются при печати. С другой стороны, в нужный момент их всегда можно включить. Так что одним щелчком форма из интерактивной преобразуется в бумажную и наоборот. Многие элементы интерфейса обладают прекрасным булевым свойством Visible. Чтобы сделать такой элемент невидимым, нужно просто выключить свойство Visible. Заметьте, что сами элементы управления - объекты класса CommandButton, Image или ComboBox таким свойством не обладают. Но при их добавлении в рабочий лист, они становятся элементами коллекции OLEObjects, каждый из элементов которой обладает этим свойством, что неоднократно использовалось в ранее приведенных макросах. Заметьте, что не только элементы коллекции, но и вся коллекция в целом обладает этим свойством. Это позволяет делать видимой или невидимой всю коллекцию одним оператором.
    Помимо элементов управления я размещал на документе и некоторые объекты WordArt - линии, рамки, позволяющие улучшить оформление листа. При их добавлении в рабочий лист эти объекты становятся элементами коллекции Shapes. Коллекция Shapes не обладает свойством Visible, но ее элементы таким свойством обладают. Я решил при печати избавиться от всяческого "украшательства", и потому выключил видимость и этих элементов.
    Операция выключения видимости объектов, очевидно, обратима. Поэтому на созданной панели инструментальных кнопок расположены две кнопки, позволяющие включать и выключать видимость объектов. Соответственно написаны и два макроса, запускаемые при нажатии этих кнопок. Вот текст макросов OlePlus и OleMinus, включающих и выключающих видимость OLE-объектов и Shape-объектов:
    Public Sub OlePlus() 'Эта процедура делает видимой коллекцию OLE - объектов 'Видимой делается и коллекция Shapes
    Dim Ob As OLEObjects Set Ob = ThisWorkbook.Worksheets(1).OLEObjects If Ob.Count > 0 Then Ob.Visible = True For Each Shp In ThisWorkbook.Worksheets(1).Shapes Shp.Visible = msoTrue Next Shp End Sub
    Public Sub OleMinus() 'Эта процедура делает невидимой коллекцию OLE - объектов 'Невидимой делается и коллекция Shapes Dim Ob As OLEObjects Set Ob = ThisWorkbook.Worksheets(1).OLEObjects If Ob.Count > 0 Then Ob.Visible = False Dim Shp As Shape For Each Shp In ThisWorkbook.Worksheets(1).Shapes Shp.Visible = msoFalse Next Shp End Sub
    Макросы OlePlus и OleMinus связываются с соответствующими инструментальными кнопками созданной панели инструментов.

    Перенос результатов запроса в поля бланка

    Приведу вначале текст процедуры FromRstToFields, а потом уже дам краткий комментарий, поясняющий ее работу:
    Public Sub FromRstToFields() 'Перенос данных из набора записей Rst1 в поля бланка Dim curField As Range VBA.Randomize Set curField = Range("D19") 'Поле с названием организации curField = Rst1!Название curField.Offset(1) = Rst1!Адрес curField.Offset(2) = "tel: " & Rst1!Телефон & _ " Email: " & Rst1!Email curField.Offset(3) = VBA.Int(VBA.Rnd * 999999999 + 100000000) 'Заполнение полей - грузотправитель и грузополучатель Set curField = Range("E25:J25") curField = "Издательство: Родная Речь, " & Range("F9") curField.Offset(1) = Rst1!Название & ", " & Rst1!Адрес End Sub
    Процедура, как и положено, коротенькая, так что текст ее самодостаточен для понимания. Значения полей записи присваиваются свойству Value объектов Range, задающих поля бланка. В последней части процедуры заполняются поля документа, задающие грузоотправителя и грузополучателя, в качестве которых выступает офис РР и выбранный заказчик книг.
    На этом можно было бы закончить комментарий, если бы не необходимость привести некоторые пояснения моим читателям. Дело в том, что я проектировал бланк документа и базу данных в разное время. По этой причине есть определенная несогласованность между реквизитами заказчика, представленными на бланке и реквизитами, хранящимися в таблице "Заказчики" базы данных офиса РР. В частности в базе данных хранятся такие реквизиты, как, например, "Город", "Директор", которые не вынесены на бланк, но нет реквизита "ИНН", присутствующего на бланке. Я не стал приводить в полное соответствие формат бланка документа и базу данных, найдя в этом даже некоторую пользу для целей обучения. Примером того, что некоторые поля документа могут быть вычислимыми, в данном случае является поле "ИНН", которое я формирую случайным образом, используя функции Randomize и Rnd.
    На этом закончу рассмотрение действий, выполняемых при нажатии командной кнопки "Выбрать" и перейду к рассмотрению задач, решение которых осуществляется по нажатию следующей инструментальной кнопки "Найти".

    Подготовка документа к печати

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

  • Чтобы пользователь мог управлять процессом преобразования, создадим панель с инструментальными кнопками, выбор которых и будет решать ту или иную частную задачу преобразования. С создания панели с инструментальными кнопками и начну. Я не буду останавливаться на самом процессе создания панели. Полагаю, достаточно взглянуть на то, как выглядит эта панель:
    Подготовка документа к печати

    Рис. 7.12.  Панель документа с инструментальными кнопками

    Поиск заказчика

    В предыдущем разделе я подробно описал, как можно перенести данные из базы данных в поля документа. Решение было основано на том, что пользователю, заполняющему документ, предъявлялся некоторый список, выбор из которого задавал ключ поиска нужных сведений в базе данных. Иногда приходится более сложным образом задавать ключ поиска. Именно такую ситуацию я хочу сейчас продемонстрировать на примере поиска заказчика, название организации которого может быть неизвестно, а есть лишь некоторые отрывочные сведения. Предположим, что требуется найти заказчика по таким сведениям, как телефон или фамилия директора, или по названию города, или по частично известному названию организации.
    Для решения задачи в такой постановке и служит инструментальная кнопка "Найти". Приведу код обработчика события, возникающего при нажатии этой кнопки:
    Private Sub CommandButton3_Click() 'Поиск заказчика 'Открыть форму для задания реквизитов поиска LookCustomer.Show End Sub
    Вся обработка события сводится к открытию специальной формы, в полях которой можно задавать ключи поиска. Так что первое, с чего я начал при решении этой задачи, это спроектировал форму с именем "LookCustomer", в текстовых полях которой пользователь задает известную ему информацию, которая и служит ключом поиска. Вот как выглядит эта форма в процессе работы с ней:
    Поиск заказчика

    Рис. 7.4.  Форма LookCustomer для задания ключей поиска
    В приведенном на рисунке примере будут отобраны все заказчики, в названии которых содержится слово "лавка" или директором которых является "Рондова", или заказчики, расположенные в Твери. Заметьте, не обязательно задание всех полей формы, достаточно задание хотя бы одного поля. Обращаю внимание и на мое решение о том, что при поиске поля будут объединяться логической связкой "ИЛИ", а не "И".
    Нажатие кнопки "Найти" в форме LookCustomer инициирует начало поиска. Взгляните на код обработчика события Click:
    Private Sub CommandButton1_Click() 'Найти заказчика по заданным реквизитам LookingFor End Sub

    Реальную работу выполняет процедура LookingFor стандартного модуля. Вот ее текст:

    Public Sub LookingFor() 'Найти заказчика по заданным реквизитам If (LookCustomer.TextBox1 <> "") Or (LookCustomer.TextBox2 <> "") Or _ (LookCustomer.TextBox3 <> "") Or (LookCustomer.TextBox4 <> "") Or _ (LookCustomer.TextBox5 <> "") Then 'Критерии поиска заданы. 'Спрятать форму. LookCustomer.Hide 'Создать набор записей с реквизитами заказчиков. CreateCustomers 'Сформировать список заказчиков, удовлетворяющих критериям поиска. FormListSelectedCustomers If SelectedCustomers.ListBox1.ListCount > 0 Then 'Найдены заказчики, удовлетворяющие критериям. SelectedCustomers.Show Else 'Показ всех заказчиков. MsgBox ("Нет записей, удовлетворяющих заданным критериям!" _ & " Будут показаны все заказчики!") Choose End If Else MsgBox ("Задайте значение хотя бы в одном поле!") End If End Sub

    Приведу краткие комментарии к работе этой процедуры:

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

    Public Sub CreateCustomers() 'Создание и выполнение команды, 'позволяющей получить данные о заказчиках Dim strSQL1 As String strSQL1 = "Select * FROM [Заказчики]" 'задание объекта Command Cmd1.CommandText = strSQL1 'вызов команды на исполнение методом Execute Set Rst1 = Cmd1.Execute End Sub
  • После того, как получены данные обо всех заказчиках, идет отбор заказчиков, удовлетворяющих заданным критериям поиска. Эта основная задача решается в процедуре FormListSelectedCustomers. О ней подробнее скажу чуть позже.
  • Если найдутся заказчики, удовлетворяющие заданным условиям поиска, то их список будет показан в специальной форме SelectedCustomers для окончательного выбора между ними. Замечу, что в отличие от ранее приводимого списка, содержащего только названия заказчиков, теперь для выбора предъявляется список из нескольких столбцов, содержащий все основные реквизиты.
  • Если по результатам поиска не будет найден ни один из заказчиков, то вызывается процедура Choose, которая уже рассмотрена ранее. Заметьте, в этом случае будет показан полный список всех заказчиков.


  • Работа с заказом

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

    увеличить изображение
    Рис. 7.11.  Полностью сформированная таблица заказа

    Раздел "Реквизиты заказчика"

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

  • Вот как выглядит теперь этот раздел документа с добавленными кнопками:
    Раздел

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

    Раздел "Сотрудники"

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

  • Для реализации этого подхода я добавил в документ элемент управления класса Combobox, предназначенный для хранения списка сотрудников. Сразу же скажу еще об одном элементе управления, добавленном мною в документ, - класса Label, метке или надписи, предназначенной для хранения текущей даты. Понятно, что без текущей даты не обходится ни один из документов, и эта дата должна создаваться автоматически, используя соответствующую встроенную функцию Date, возвращающую текущую дату в нужном формате. Конечно в документах, создаваемых на основе Excel, для хранения даты можно использовать любую из ячеек рабочего листа, и я применю этот подход чуть позже при формировании листа печати. Сейчас же хочу продемонстрировать использование различных элементов управления, и потому я предпочел для отображения даты ввести отдельный элемент управления. Взгляните, как выглядит интерфейс этой части документа с добавленными элементами управления:
    Раздел

    увеличить изображение
    Рис. 7.7.  Документ с элементами управления для хранения текущей даты и списка сотрудников
    Добавленным элементам управления я дал соответственно имена - AccountDate и ListOfTeam, и установил подходящее форматирование и другие свойства этих элементов. Останавливаться на этих деталях не буду, отмечу только, что для списка свойство Style я установил, равным 0, что запрещает вносить данные в поле ввода списка в момент работы с документом, - разрешен только выбор из заранее сформированного списка.
    Главный вопрос при работе с этими элементами, - как и когда задавать текущую дату и формировать список фамилий на основе сведений из базы данных. Вопрос "когда" решается очевидным способом, - разумнее всего инициализацию отдельных элементов документа проводить в момент общей инициализации, то есть в момент открытия документа. На вопрос "как" проще всего ответить, приведя программный код. Поскольку, благодаря принятой стратегии построения обработчиков событий, текст их остается неизменным, то мне достаточно показать дополнения, сделанные в процедуре InitAccount, вызываемой в обработчике события Open нашего документа:
    Public Sub InitAccount() 'Установить соединение с базой данных CreateConnection 'Конфигурирование команды ConfigCommand 'Очистить поля бланка ClearFields 'Задать дату и создать список сотрудников CreateListOfTeam End Sub

    Сделанные изменения подсвечены, и, как видите, они состоят из вызова процедуры CreateListOfTeam, текст которой сейчас приведу:

    Public Sub CreateListOfTeam() 'Задать дату и создать список сотрудников Dim myDate As Object Dim myCombo As ComboBox 'Установить дату Set myDate = ThisWorkbook.Worksheets(1).OLEObjects("AccountDate").Object myDate.Caption = Date 'Создать список сотрудников Set myCombo = ThisWorkbook.Worksheets(1).OLEObjects("ListOfTeam").Object Cmd1.CommandText = "Select [ФИО] From [Сотрудники]" Set Rst1 = Cmd1.Execute With Rst1 .MoveFirst Do While Not .EOF myCombo.AddItem .Fields(0) .MoveNext Loop End With End Sub

    Приведу краткие комментарии:

  • Оба добавленных в документ элемента управления входят в коллекцию OLEObjects того рабочего листа, на котором расположены элементы. Добраться до них в коллекции можно по данным им именам. Чтобы работать с ними, как с объектами, я создал в процедуре два объекта myDate и myCombo соответствующих классов.
  • Дату я установил, задав свойство Caption объекта myDate, вызвав функцию Date в качестве значения этого свойства.
  • Формирование списка по информации базы данных рассматривалось уже неоднократно, поэтому ничего принципиально нового нет - формируется текст команды, позволяющий читать данные из таблицы "Сотрудники", команда выполняется и создается набор записей. Обычная схема прохождения по набору записей позволяет сформировать по значениям полей записи элементы списка объекта myCombo. Результаты работы можно видеть на рис. 7.7.


  • Давайте теперь перейдем к основному разделу нашего документа, связанному с оформлением самого заказа, и покажем, как добавить интерактивность этой части документа.

    Раздел "Заказы"

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

  • Займемся этими задачами. И первое, с чего начнем, добавим в этот раздел документа три инструментальные кнопки с надписями "Сформировать заказ", "Сохранить заказ", "Очистить Заказ" и именами - "FormOrder", "SaveOrder", "ClearOrder". Вот как выглядит интерфейс документа с добавленными кнопками:
    Раздел

    увеличить изображение
    Рис. 7.8.  Командные кнопки в разделе "Заказы"

    Сохранение реквизитов

    Как я говорил ранее, иногда информация создается при формировании документа и тогда крайне желательно, чтобы она тут же была занесена в базу данных. Эта ситуация достаточно естественно появляется при работе с нашим документом. Действительно, представьте, что появился новый заказчик, о котором нет сведений в базе данных. Тогда данные о нем, - его реквизиты естественно придется вручную занести в соответствующие поля документа. Инструментальная кнопка "Сохранить" поможет перенести эту информацию в базу данных.
    Следует заметить, что решение обратной задачи по переносу данных из документа в базу данных даже проще, чем рассматриваемая ранее прямая задача, и никаких особых новинок здесь не появляется. Вот ее программное решение:
    Private Sub CommandButton2_Click() 'Сохранить реквизиты заказчика CreateNewCustomer End Sub
    Процедура CreateNewCustomer, решающая задачу имеет вид:
    Public Sub CreateNewCustomer() 'добавление записи в базу данных Dim recExist As Boolean Dim curField As Range Dim txtField As String, txtTel As String, txtMail As String Cmd1.CommandText = "Select * From [Заказчики]" Set curField = Range("D19") 'Поле с названием организации txtField = curField.Text 'Открытие обновляемого объекта Recordset With Rst1 If Rst1.State = adStateOpen Then Rst1.Close .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic
    recExist = False .MoveFirst Do While Not .EOF 'Проверка существования записи If LCase(!Название) = LCase(txtField) Then recExist = True .MoveNext Loop If Not recExist Then .AddNew !Название = txtField txtField = curField.Offset(1).Text !Адрес = txtField txtField = curField.Offset(2).Text Call Parsing(txtField, txtTel, txtMail) !Телефон = txtTel !Email = txtMail 'Запрос значений недостающих полей AddingFields.Show .Update Else MsgBox ("В базе данных уже существует" _ & " организация с таким названием!") End If End With End Sub
    На три момента хочу обратить внимание:
  • При добавлении записей в базу данных приходится иметь дело с обновляемым набором записей, который открывается методом Open, а не создается, как это было ранее, при выполнении метода Execute объекта Command. Подробнее об этом я писал в предыдущих главах, посвященных объектам ADO.
  • Наш пример сконструирован так, что он позволяет продемонстрировать некоторые побочные проблемы, возникающие при передаче данных из документа в базу данных. В частности, информация о телефоне и электронном адресе записана в одном поле бланка документа. Поэтому, прежде чем она попадет в соответствующие поля записи базы данных, необходим разбор соответствующей строки. В нашем случае разбор прост, и осуществляет его вызываемая процедура Parsing. Приведу текст этой процедуры:

    Public Sub Parsing( Field As String, Tel As String, Mail As String) 'разбор поля бланка, содержащего телефон и EMail организации Dim Ind1 As Integer Ind1 = InStr(5, Field, "Email") Tel = Mid(Field, 5, Ind1 - 5) Mail = Right(Field, Len(Field) - Ind1 - 5) End Sub
  • Еще одна часто возникающая проблема состоит в том, что запись базы данных может содержать больше информации, чем задается в полях документа. В этом случае целесообразно запросить всю необходимую информацию. Я демонстрирую этот подход на примере полей "Город" и "Директор", которые есть в базе данных, но не вынесены в раздел реквизитов нашего документа. Заметьте, с этой целью, прежде чем обновить запись, вызывается специальная форма, которой я дал имя AddingFields, и которая содержит текстовые поля для занесения необходимой информации. Вот как выглядит эта форма в процессе работы:


  • Сохранение реквизитов

    Рис. 7.6.  Форма AddingFields, позволяющая добавить сведения о полях базы данных

    При нажатии кнопки "OK" формы AddingFields текст из полей формы переносится в поля записи, после чего форма закрывается:

    Private Sub CommandButton1_Click() 'Добавление полей "Город" и "Директор" в запись Rst1!Город = Me.TextBox1.Text Rst1!Директор = Me.TextBox2.Text Me.Hide End Sub

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

    Сохранение заказа

    Теперь, когда заказ полностью сформирован, его следует сохранить в базе данных. Командная кнопка "Сохранить заказ" уже 'горит', и пользователю остается ее только нажать. Но прежде чем приводить программный код, стоит несколько слов сказать о том, как хранятся заказы в базе данных.
    Данные о заказе хранятся в двух таблицах базы данных - "Заказы" и "Заказано", связанных между собой общим полем "Код заказа". Первая из этих таблиц содержит одну запись для каждого заказа, в которой содержится общая информация - о заказчике, о сотруднике, оформляющем заказ, дате заказа и его общей стоимости. Во второй таблице о каждом заказе содержится несколько записей, - по одной записи на каждый заказываемый товар. Теоретическим обоснованием такого представления данных является теория нормализации реляционных баз данных. Но и без теории понятно, что нет необходимости хранить в каждой записи общую информацию. Ее достаточно хранить один раз, с другой стороны, она доступна, найти ее легко, благодаря общему коду заказа, который присутствует в каждой записи.
    С программной точки зрения никаких особых новых проблем при передаче данных из полей документа в таблицы базы данных не возникает. Объекты ADO достаточно просто позволяют создать обновляемый набор записей, примеры этому уже приводились. Тем не менее, полагаю, есть смысл посмотреть на код обработчика события, возникающего при нажатии командной кнопки "Сохранить заказ". Вот он:
    Private Sub SaveOrder_Click() 'Сохранение заказа OrderSave End Sub
    Рассмотрим код процедуры, выполняющей основную работу:
    Public Sub OrderSave() 'Сохранение заказа Dim myDate As Object Dim myCombo As ComboBox Dim curField As Range, curField1 As Range Dim i As Integer Dim BCount As Boolean BCount = True Set curField = Range("A34:D34") Set curField1 = Range("F34") With ThisWorkbook.Worksheets(1).OLEObjects Set myDate = .Item("AccountDate").Object Set myCombo = .Item("ListOfTeam").Object End With 'Проверка заполнения полей "Количество" i = 0 Do While (Not IsEmpty(curField.Offset(i))) BCount = BCount And Not IsEmpty(curField1.Offset(i)) i = i + 1 Loop If BCount Then 'Заполнены поля "Количество" 'Добавление записи в таблицу "Заказы" Cmd1.CommandText = "Select * From [Заказы]" With Rst1 If .State = adStateOpen Then .Close .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic .AddNew !Заказчик = Range("D19").Text !Сотрудник = myCombo.Text !ДатаЗаказа = myDate.Caption !Стоимость = Range("K46") .Update OrderCode = !КодЗаказа

    End With 'Добавление нескольких записей в таблицу "Заказано" Cmd1.CommandText = "Select * From [Заказано]" With Rst1 If .State = adStateOpen Then .Close .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic ' Цикл по строкам таблицы заказов i = 0 Set curField = Range("A34") Set curField1 = Range("F34") Do While (Not IsEmpty(curField.Offset(i))) .AddNew !КодЗаказа = OrderCode !НазваниеКниги = curField.Offset(i) !Количество = curField1.Offset(i) !Стоимость = curField1.Offset(i, 5) .Update i = i + 1 Loop End With 'Выключить кнопку "Сохранить Заказ" With ThisWorkbook.Worksheets(1).OLEObjects .Item("SaveOrder").Object.Enabled = False 'Поле NDS сделать невидимым .Item("NDS").Visible = False .Item("LabelNDS").Visible = False End With 'Номер заказа становится номером счета Range("F16") = OrderCode Else MsgBox ("Задайте значения в полях Количество!") End If End Sub

    Приведу комментарии к тексту процедуры:

  • При формировании общих данных о заказе мне понадобится работать с текущей датой, получить информацию о сотруднике, оформляющем заказ. Для этого и введены объекты myDate и myCombo. Объекты curField и curField1 используются, как и в предыдущей процедуре, при работе с областями ячеек нашего документа.
  • Процедура начинает свою работу с инициализации перечисленных выше локальных объектов. Объекты myDate и myCombo связываются с соответствующими OLE-объектами, curField и curField1 устанавливаются в начальное положение, задавая первую запись в таблице заказов.
  • Еще один шаг работы процедуры, предваряющий основные действия, связан с "защитой от дурака". Я уже говорил, что, прежде чем нажать кнопку "Сохранить заказ", квалифицированный пользователь укажет количество для каждого заказываемого товара. Однако кнопка доступна для нажатия и тогда, когда эти данные не указаны. По этой причине, прежде чем пытаться записать данные о заказе в базу данных, я делаю проверку на заполнение этих полей. Если хотя бы одно из этих полей не заполнено, будет выдано предупреждающее сообщение. Соответствующий код этой части процедуры подсвечен.
  • В основной части работы процедуры вначале создается одна запись таблицы "Заказы". Следует обратить внимание на то, что "Код заказа" является для этой таблицы ключевым полем, имеет тип "Счетчик", и создается автоматически при добавлении каждой новой записи. Поэтому уже после того, как запись добавлена в базу данных, то есть после выполнения метода Update, я запоминаю в переменной OrderCode этот код. Он мне понадобится в дальнейшем для достижения двух целей - во-первых, для включения его в записи таблицы "Заказано", во-вторых, этот код будет одновременно служить уникальным номером нашего документа, который указывается в разделе заголовка документа.
  • Создание записей таблицы "Заказано" идет в цикле по числу заполненных строк таблицы заказов документа. Первая встреченная пустая строка служит признаком окончания цикла, по этой причине в таблице заказов не должно быть купюр.
  • Наконец, в заключительной части работы нашей процедуры, которая также выделена цветом, становится недоступной кнопка "Сохранить заказ". Действительно, заказ уже сохранен, повторное нажатие этой кнопки приведет к повторной записи заказа. Более того, поскольку ключом таблицы является счетчик, то сам Access не может предупредить о том, что подобная запись уже существует. В базе данных появится тот же заказ, но с новым кодом. Эта ситуация крайне нежелательна, по этой причине кнопка сохранения заказа должна быть недоступной до тех пор, пока не будет сформирован новый заказ.
  • Кроме выключения кнопки "Сохранить заказ", в заключительной части процедуры делаются невидимыми элементы управления, связанные с заданием НДС, а также для документа задается номер счета, на основании кода заказа.


  • Создание листа печати

    Теперь, когда некоторая предварительная работа проделана, займемся основной работой по подготовке листа печати. Замечу, что, конечно, нажав кнопки OLE- и Fon-, можно приступить к печати листа, в таком виде, как он получился. После чего, нажать кнопки OLE+ и Fon+, вернув лист в исходное состояние. Однако это не лучший способ, поскольку остались кой-какие лишние детали, а главное при выключении была потеряна важная информация. Поскольку окончательное формирование листа печати является необратимой операцией, не позволяющей вернуться к исходному состоянию, то лучшим выходом является формирование дополнительного листа, специально предназначенного для печати.
    Пятая кнопка на инструментальной панели с именем "КопияП" запускает макрос, готовящий лист печати. Приведу его текст:
    Public Sub CopyForPrinting() 'подготавливает копию листа для печати Dim mySh As Worksheet Dim Ex As Boolean Dim curField As Range With ThisWorkbook Ex = False For Each mySh In .Worksheets If mySh.Name = "ЛистПечати" Then Ex = True: Exit For End If Next mySh If Not Ex Then .Worksheets.Add .ActiveSheet.Name = "ЛистПечати" .ActiveSheet.Move After:=Sheets("Заказ") End If 'Очистить область листа печати Set curField = .Worksheets("ЛистПечати").Range("A1:K56") curField.Clear 'Показ скрытых строк StrShow 'Копирование листа Set curField = .Worksheets("Заказ").Range("A1:K56") curField.Copy .Sheets("ЛистПечати").Activate .ActiveSheet.Range("A1").Select .ActiveSheet.PasteSpecial End With 'запуск макросов OleMinus FonMinus 'Скрыть лишние строки StrHide 'Дополнить нужной информацией AddInformation 'Удалить сетку и другие детали With ActiveWindow .DisplayGridlines = False 'сетка .DisplayHeadings = False 'заголовки .DisplayFormulas = False 'показ формул .DisplayZeros = False 'отображение нулей End With
    End Sub
    В работе процедуры можно выделить несколько этапов:
  • Вначале создается лист печати, если только он уже не был создан.
  • Область листа, отведенная под документ, чистится от предыдущего заполнения. Вызываемая процедура StrShow показывает строки, которые были скрыты при предыдущем заполнении. О скрытии строк я поговорю чуть позже.
  • Затем лист заказа копируется со всеми его атрибутами.
  • Теперь, когда у нас есть копия листа, над ней можно проводить все необходимые операции. Первым делом запускаются макросы OleMinus и FonMinus, удаляющие объекты OLE и Shape, также как и заливку фона.
  • На следующем этапе лист печати сжимается в размерах. Достигается это за счет того, что ряд пустых строк скрываются, - делаются невидимыми. Такими строками являются строки, разделяющие разделы нашего документа, а также незаполненные строки таблицы заказов. Выполняет операцию скрытия строк процедура StrHide. Поскольку эта операция обратима, то я написал и процедуру StrShow, показывающую скрытые строки. Она вызывается при чистке листа.
  • Вызов процедуры AddInformation позволяет дополнить лист печати необходимой информацией. В соответствующие ячейки листа печати заносятся название издательства, фамилия сотрудника, оформляющего заказ, дата заказа.
  • На последнем шаге работы процедуры удаляются ненужные при печати элементы листа - сетка, заголовки строк и столбцов.


  • Приведу теперь тексты процедур StrHide и StrShow:

    Public Sub StrHide() 'Эта процедура позволяет скрыть лишние строки Dim curField As Range, curField1 As Range Dim i As Integer Rows("1:7").Select Selection.EntireRow.Hidden = True Rows("10:14").Select Selection.EntireRow.Hidden = True Rows("17:18").Select Selection.EntireRow.Hidden = True Rows("27:28").Select Selection.EntireRow.Hidden = True Rows("48:49").Select Selection.EntireRow.Hidden = True

    'Скрыть пустые строки в таблице заказов Set curField = Range("A34:D34") Set curField1 = Rows("34:34") For i = 0 To 11 If IsEmpty(curField.Offset(i)) Then curField1.Offset(i).Select Selection.EntireRow.Hidden = True End If Next i End Sub

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

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

    Public Sub StrShow() 'Показ скрытых строк Rows("1:56").Select Selection.EntireRow.Hidden = False End Sub

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

    Public Sub AddInformation() 'Эта процедура дополняет лист печати нужной информацией Dim MyCombo As Object With ThisWorkbook Set curField = .Worksheets("ЛистПечати").Range("D8") curField.Value = "Издательство: Родная Речь" curField.Font.Bold = True curField.Font.Size = 12 Set curField = .Worksheets("ЛистПечати").Range("H16") curField.Value = Date curField.Font.Bold = True curField.Font.Size = 14 Set curField = .Worksheets("ЛистПечати").Range("H29") Set MyCombo = .Worksheets("Заказ").OLEObjects("ListOfTeam").Object curField.Value = MyCombo.Text curField.Font.Bold = True curField.Font.Size = 12 End With End Sub

    В завершение рассказа о подготовке листа печати давайте взглянем на то, что получается при нажатии кнопки "КопияП", ответственной за выполнение этой работы:

    Создание листа печати

    Рис. 7.13.  Лист печати

    На этом и закончу обсуждение того, как придать интерактивность офисным документам.


    В завершение рассказа о подготовке листа печати давайте взглянем на то, что получается при нажатии кнопки "КопияП", ответственной за выполнение этой работы:

    Создание листа печати

    Рис. 7.13.  Лист печати

    На этом и закончу обсуждение того, как придать интерактивность офисным документам.

    Создание листа печати
    Создание листа печати
    Создание листа печати
    © 2003-2007 INTUIT.ru. Все права защищены.

    Включение и выключение фона

    Включение и выключение фона, - простые операции. Достаточно выделить нужную область и установить соответствующие свойства объекта Selection. Вот два макроса, решающие эту задачу. Нетрудно догадаться, с какими кнопками нашей панели они связаны:
    Public Sub FonPlus() 'Заливает область бланка серым цветом Range("A1:K56").Select With Selection .Font.ColorIndex = 1 .Interior.ColorIndex = 15 End With Range("A1").Select End Sub
    Public Sub FonMinus() 'Восстанавливает белый цвет в области бланка Range("A1:K56").Select With Selection .Font.ColorIndex = 1 .Interior.ColorIndex = 2 End With Range("A1").Select End Sub

    Выбор заказчика

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

  • А теперь взгляните, как выглядит обработчик события Click командной кнопки "Выбрать", вызываемый при нажатии этой кнопки:
    Private Sub CommandButton1_Click() 'выбор заказчика из базы данных Choose End Sub Public Sub Choose() 'Данные о заказчиках выбираются из базы данных CreateListCustomers FormListCustomers Customers.Show End Sub
    Сохраняя свой стиль, обработчик события свожу к вызову соответствующей процедуры стандартного модуля. Замечу, что в этом стиле имеется и определенный резон, поскольку процедуру, заданную обработчиком, можно вызывать не только при нажатии командной кнопки. В частности, именно так обстоит дело с процедурой Choose, которая будет вызываться по ходу решения задачи в одной из процедур стандартного модуля.
    Сама по себе процедура Choose не решает полностью задачу выбора заказчика. Две процедуры, вызываемые в процедуре Choose, решают лишь первую подзадачу, завершая свои действия показом формы со списком. Дальнейшие действия определяются пользователем и зависят от его выбора. Но, прежде чем говорить о решении других подзадач, давайте более подробно разберемся с тем, что делается в процедуре Choose.

    Запросы с фильтрацией

    Возможно, Вы обратили внимание на то, как я решал данную задачу по организации поиска нужных мне данных, - вначале получил весь набор записей, а затем организовал их фильтрацию средствами VBA. Возможен и другой способ, в ряде случаев более предпочтительный, - он состоит в том, чтобы фильтрацию выполнять в момент чтения набора записей, задав соответствующим образом оператор SELECT языка SQL. Оператор Select имеет в своем арсенале конструкцию Like, позволяющую задать шаблон поиска и тем самым проводить фильтрацию непосредственно при чтении записей, что может приводить к существенному уменьшению объема набора записей, передаваемого клиенту.
    Давайте подробно рассмотрим этот вариант работы. Полагаю, это будет полезным, как с содержательной, так и с программистской точки зрения, поскольку, например, я сам не сразу разобрался, как корректно следует на VBA задавать конструкцию Like.
    Начнем повторно наше рассмотрение с того момента, когда после задания критериев поиска пользователь нажимает кнопку "Найти" в форме LookCustomer. Я изменил код обработчика события Click для этой кнопки следующим образом:
    Private Sub CommandButton1_Click() 'Найти заказчика по заданным реквизитам 'LookingFor Version2LookingFor End Sub
    Как видите, вместо ранее вызываемой процедуры LookingFor я буду вызывать ее версию, которую и рассмотрим подробно. Начнем с текста:
    Public Sub Version2LookingFor() 'Найти заказчика по заданным реквизитам If (LookCustomer.TextBox1 <> "") Or (LookCustomer.TextBox2 <> "") Or _ (LookCustomer.TextBox3 <> "") Or (LookCustomer.TextBox4 <> "") Or _ (LookCustomer.TextBox5 <> "") Then 'Критерии поиска заданы. 'Спрятать форму. LookCustomer.Hide 'Создать набор отфильтрованных записей с реквизитами заказчиков. CreateFiteredCustomers 'Сформировать список заказчиков, удовлетворяющих критериям поиска. FormListSelectedCustomers If SelectedCustomers.ListBox1.ListCount > 0 Then 'Найдены заказчики, удовлетворяющие критериям. SelectedCustomers.Show Else 'Показ всех заказчиков. MsgBox ("Нет записей, удовлетворяющих заданным критериям!" _ & " Будут показаны все заказчики!") Choose End If Else MsgBox ("Задайте значение хотя бы в одном поле!") End If End Sub

    Я не буду подробно описывать работу этой процедуры, поскольку она, во многом, похожа на своего двойника - процедуру LookingFor. Главное отличие с программной точки зрения состоит в том, что вместо относительно простой процедуры CreateCustomers здесь вызывается более сложная процедура CreateFilteredCustomers. С содержательной точки зрения отличие состоит в том, что первая процедура создает полный набор всех заказчиков, а вторая - набор заказчиков, удовлетворяющих условию фильтра.

    Приведу программный код процедуры CreateFilteredCustomers:

    Public Sub CreateFiteredCustomers() 'Создание и выполнение команды, 'позволяющей получить данные о заказчиках, 'удовлетворяющих фильтру Dim strSQL1 As String 'Вызов функции FormSQLStatement, формирующей строку SQL strSQL1 = FormSQLStatement 'задание объекта Command Cmd1.CommandText = strSQL1 'вызов команды на исполнение методом Execute Set Rst1 = Cmd1.Execute 'Перенос данных из набора записей в список SelectedCustomers.ListBox1.Clear RowIndex = 0 With Rst1 .MoveFirst Do While Not .EOF 'Текущая запись переносится в список 'Первый столбец SelectedCustomers.ListBox1.AddItem .Fields(1) 'Остальные столбцы On Error Resume Next For i = 1 To ColumnCount - 1 txt = "" txt = .Fields(i + 1) SelectedCustomers.ListBox1.Column(i, RowIndex) = txt Next i RowIndex = RowIndex + 1 .MoveNext Loop End With End Sub

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

    То новое, на что хочу обратить внимание, связано с формированием строки SQL. Как положено, я написал специальную функцию FormSQLStatement, которая и решает поставленную задачу. Именно она и интересует нас в первую очередь. Вот ее текст:

    Public Function FormSQLStatement() As String 'Возвращает SQL оператор, фильтрующий записи Dim strSQL As String strSQL = "Select * FROM [Заказчики] WHERE " Dim txt As String Const Кавычка = "'" txt = LookCustomer.TextBox1.Text If txt <> "" Then strSQL = strSQL & "[Название] Like " & _ Кавычка & "%" & txt & "%" & Кавычка & " Or " txt = LookCustomer.TextBox2.Text If txt <> "" Then strSQL = strSQL & "[Адрес] Like " & _ Кавычка & "%" & txt & "%" & Кавычка & " Or " txt = LookCustomer.TextBox3.Text If txt <> "" Then strSQL = strSQL & "[Город] Like " & _ Кавычка & "%" & txt & "%" & Кавычка & " Or " txt = LookCustomer.TextBox4.Text If txt <> "" Then strSQL = strSQL & "[Телефон] Like " & _ Кавычка & "%" & txt & "%" & Кавычка & " Or " txt = LookCustomer.TextBox5.Text If txt <> "" Then strSQL = strSQL & "[Директор] Like " & _ Кавычка & "%" & txt & "%" & Кавычка & " Or " 'Удалить последние пять символов - Or strSQL = Left(strSQL, Len(strSQL) - 4) FormSQLStatement = strSQL End Function


    Поговорим подробнее о работе этой функции. Наша цель состоит в том, чтобы задать сложное выражение WHERE оператора Select, задающее фильтр. По условиям задачи это выражение состоит из нескольких слагаемых - логических условий, соединенных связкой OR (ИЛИ). Число слагаемых формируется динамически и может быть от одного до пяти, в зависимости от того, сколько полей заполнил пользователь в форме LookCustomer. Напомню, что хотя бы одно поле он обязан задать, чтобы можно было говорить о ключе поиска. Все слагаемые формируются по одной схеме, - если задано соответствующее ключевое поле в форме, то формируется и соответствующее ему слагаемое.

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

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

    Замечание 1. Шаблоны не новость ни в математике, ни в программировании. Традиционно, символ * (звездочка) в шаблоне соответствует произвольной строке символов, а символ ? (знак вопроса) соответствует произвольному одиночному символу. Мне совершенно непонятно, почему в шаблонах на VBA нужно было изменять традиции и использовать другие символы - знак %(процент) вместо звездочки и знак _ (подчеркивание) вместо знака вопроса. Это тем более странно, что при формировании запроса с выражением Like непосредственно в Access используются традиционные символы.

    Замечание 2. Экспериментируя с шаблонами, я наткнулся на одну ошибку в Access, связанную, видимо, с локализацией. Ошибка проявляется в следующей ситуации. Пусть в Access с помощью конструктора строится запрос по таблице, у которой первое поле является ключевым и содержит, скажем, название организации. Предположим, что на это поле накладывается фильтр типа Like - S*, где S - буква русского алфавита. Этот фильтр, естественно, означает, что запросу удовлетворяю организации, начинающиеся с буквы S. Так вот, все работает правильно за исключением, когда в качестве начальной буквы задается "Г" или "К". Замечу, что в других полях шаблон "Г*" работает правильно.

    Мир объектов Excel 2000

    Два способа создания объектов PivotCache и PivotTable

    Я уже говорил, что объекты PivotCache и PivotTable можно создавать несколькими способами, и рассмотрел методы, используемые в этих способах. Теперь пришла пора привести соответствующие процедуры, решающие эти задачи. Я приведу две процедуры, в каждой из которых создаются оба эти объекты, но в каждой из них это делается по-разному. Вот код первой из этих процедур:
    Public Sub CreatePivotCacheAndTable() 'Создание объекта PivotCache 'и на его основе - объекта PivotTable 'при заполнении данными используются 'свойства объекта: Connection,CommandText,CommandType Dim myCache As PivotCache Dim DbDir As String, DbPath As String DbDir = ThisWorkbook.Path DbPath = DbDir & "\dbPP2000.mdb"
    Set myCache = ThisWorkbook.PivotCaches.Add(xlExternal) With myCache .Connection = "OLEDB; Provider=Microsoft.jet.oledb.4.0;" & _ "Data Source=" & DbPath .CommandType = xlCmdSql .CommandText = _ "SELECT Заказано.НазваниеКниги, Заказано.Стоимость, " & _ "Заказано.Количество, " & "Заказы.Заказчик, Заказы.Сотрудник, " & _ "Заказы.ДатаЗаказа" & Chr(13) & "" & Chr(10) & _ "FROM `C:\!O2000\DsCd\Ch18\dbPP2000`.Заказано Заказано, " & _ "`C:\!O2000\DsCd\Ch18\dbPP2000`.Заказы Заказы" & _ Chr(13) & "" & Chr(10) & _ "WHERE Заказано.КодЗаказа = Заказы.КодЗаказа" 'Создать отчет сводной таблицы With ThisWorkbook.Worksheets("Лист1") .Activate If .PivotTables.Count > 0 Then 'Очистить область сводной таблицы ClearRegion End If End With .CreatePivotTable TableDestination:=Range("A3"), _ TableName:="Анализ продаж"
    End With End Sub
    Обратите внимание, как создается объект PivotCache, - вначале он добавляется в коллекцию методом Add, но при этом объект не связан еще ни с каким источником данных и, следовательно, данных не содержит. На следующем этапе при заполнении свойств этого объекта - Connection, CommandType и CommandText - осуществляется связь с источником данных и выполняется команда, задающая запрос на получение данных. Соответствующий участок текста процедуры подсвечен.
    Так кэш становится заполненным. После этого, к нему можно привязать и отчет сводной таблицы. В данном варианте для построения объекта PivotTable используется метод CreatePivotTable объекта PivotCache.
    Заметьте, нельзя размещать сводную таблицу в области уже занятой другой сводной таблицей. По этой причине в процедуре проводится соответствующая проверка и область очищается, если она была занята. Приведу текст вызываемой процедуры ClearRegion:
    Public Sub ClearRegion() Dim myr As Range Set myr = ActiveSheet.UsedRange myr.Clear End Sub

    Надеюсь, она не требует особых пояснений. Напомню лишь, что свойство UsedRange возвращает всю область, занятую на рабочем листе. Эта область и очищается.

    Давайте рассмотрим теперь другой способ создания объектов PivotCache и PivotTable. Приведу код процедуры для этого варианта:

    Public Sub CreatePivotCacheAndTableWithADO() 'Создание объекта PivotCache 'и на его основе - объекта PivotTable 'Связь с базой данных осуществляется с использованием 'объектов ADO: Connection,Command,Recordset 'Объект Recordset является основой для построения кэша Dim myCache As PivotCache 'Соединение с базой данных и получение набора записей CreateRecordset Set myCache = ThisWorkbook.PivotCaches.Add(xlExternal) With myCache Set .Recordset = Rst1 'Создать отчет сводной таблицы With ThisWorkbook.Worksheets("Лист1") .Activate If .PivotTables.Count > 0 Then 'Очистить область сводной таблицы ClearRegion End If .PivotTables.Add PivotCache:=myCache, _ TableDestination:=Range("A3"), _ TableName:="Анализ продаж" End With End With End Sub

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

    Работа с объектами ADO заканчивается построением объекта Recordset, задающего набор записей. Теперь остается ссылку на этот объект сделать значением одноименного свойства объекта PivotCache.

    Так кэш становится заполненным. После этого, к нему можно привязать и отчет сводной таблицы. В данном варианте для построения объекта PivotTable используется метод Add коллекции PivotTables.

    Чтобы закончить рассмотрение, остается привести текст вызываемых процедур CreateConnection и CreateRecordset:

    Public Sub CreateConnection() 'Создание соединения с тестовой базой данных Access Dim strConnStr As String If Con1.State = adStateOpen Then Con1.Close 'закрыть соединение 'Конфигурирование соединения Con1 Con1.Provider = "Microsoft.jet.oledb.4.0" Con1.ConnectionString = "Data Source=c:\!O2000\DsCd\Ch14\dbPP2000.mdb" Con1.CursorLocation = adUseClient 'Открытие соединения Con1.Open End Sub


    Public Sub CreateRecordset() 'Связывание с базой данных Access ' получение набора записей Dim strSQL1 As String 'Задание SQL оператора strSQL1 = _ "SELECT Заказано.НазваниеКниги, Заказано.Стоимость, " & _ "Заказано.Количество, " & "Заказы.Заказчик, Заказы.Сотрудник, " & _ "Заказы.ДатаЗаказа" & Chr(13) & "" & Chr(10) & _ "FROM `C:\!O2000\DsCd\Ch18\dbPP2000`.Заказано Заказано, " & _ "`C:\!O2000\DsCd\Ch18\dbPP2000`.Заказы Заказы" & _ Chr(13) & "" & Chr(10) & _ "WHERE Заказано.КодЗаказа = Заказы.КодЗаказа" CreateConnection 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = strSQL1 Cmd1.CommandType = adCmdText Cmd1.Prepared = True

    'вызов команды на исполнение методом Execute Set Rst1 = Cmd1.Execute End Sub

    Процедуры CreateConnection и CreateRecordset нам хорошо знакомы, - их аналоги уже появлялись в главе 15 при рассмотрении объектов ADO. При работе с ними, как и ранее, используются глобальные объекты - Con1, Cmd1, Rst1.

    Функция ЛИНЕЙН

    В общем случае решает задачу линейной множественной регрессии, вычисляя по методу наименьших квадратов вектор оценок параметров. Используется описанная нами выше модель:
    Y = X*a + E
    Синтаксис вызова этой функции:
    ЛИНЕЙН (Известные_значения_Y; Известные_значения_X; Конст; Статистика)
    Параметры функции имеют следующий смысл:
  • Известные_значения_Y - задает вектор измерений.
  • Известные_значения_X - в общем случае матрица значений наблюдаемых параметров. Если речь идет о временном тренде, то элементы X задают моменты времени, в которые проводились измерения. Можно опустить X, если значения элементов составляют последовательность 1, 2, 3 и т. д.
  • Булев параметр "Конст" равен Истина (True), если в линейной записи модели присутствует дополнительно свободный член b, не входящий в вектор параметров a.
  • Булев параметр "Статистика" равен Истина (True), если наряду с оценками параметров вычисляются и статистические характеристики.
  • Результат вычислений этой функции - массив, в общем случае состоящий из 5 строк и n+1 столбцов, где n - это размерность вектора искомых параметров a.
  • an,    an-1, …  a1,  b
  • ?n,    ?n-1, …  ?1,  ?b
  • R*R,   ?Y
  • F,             df
  • Ssreg, Ssresid
  • В первой строке идут оценки параметров a и свободного члена b. Оценки идут в обратном порядке, начиная с an. Они и определяют линию регрессии, позволяя рассчитать прогнозируемое значение Y в любой точке, где заданы значения наблюдаемых параметров.
  • В следующей строке идут среднеквадратические отклонения этих оценок. Выше мы показали, как вычислить полную корреляционную матрицу оценок. Среднеквадратические отклонения являются диагональными элементами этой матрицы. Точнее, на диагонали стоят их квадраты - дисперсии DI = ?I * ?I. Значения ?I позволяют построить доверительный интервал для соответствующих оценок и вынести суждение об их значимости в линейной модели. Как вычисляются эти значения в Excel, нам осталось непонятно, так как алгоритм не описан. Можно лишь заметить, что применяемый алгоритм не всегда корректен с позиций классической математической статистики. Приведем пример. Пусть оцениваются, как часто бывает, два параметра a и b (Y = at +b). Пусть выполнены всего два измерения - Y1 и Y2. Тогда, каковы ни были ошибки в измерениях, линия регрессии пройдет через две наблюденные точки. Excel скажет, что оши

    "µ бок в оценках параметров нет, и выдаст значения ?1 и ?2 , равные 0, хотя ясно, что это не так.
  • Коэффициент детерминации R2 имеет значение в интервале от 0 до 1 и позволяет оценить, насколько хорошо сглаживаются измеренные значения линией регрессии. Он равен 1, если линия регрессии проходит через все измеренные точки. При этом можно полагать, что есть строгая функциональная зависимость между измеряемым значением Y и параметрами ai. Предыдущий пример показывает, что недостаточное количество измерений может приводить к такому же результату. Поэтому и к этому параметру надо относиться с осторожностью. Вычисляется коэффициент детерминации по формуле:

    R2 = Dreg / D

    и представляет отношение дисперсии, объясняемой регрессией, к общей дисперсии. О смысле этих терминов мы скажем чуть ниже.

  • Мы и так увлеклись понятиями математической статистики, потому не будем говорить о том, что означают и как используются параметры ?Y, F и число степеней свободы df.
  • Последние два значения - Ssreg и Ssresid задают дисперсию, объясняемую регрессией, и остаточную дисперсию, представляющую разность между общей дисперсией и Dreg. Обе дисперсии вычисляются "обычным" способом:

    D =
    Функция ЛИНЕЙН
    (YI - E)2 ; Dreg =
    Функция ЛИНЕЙН
    (YI - E)2 ,

    где E - среднее значение измеренных значений, а YI - сглаженные значения, вычисленные из уравнения регрессии.

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

    Функция ТЕНДЕНЦИЯ и другие функции, используемые для прогноза

    В основе всех других функций Excel, используемых для прогноза и регрессионного анализа лежит функция ЛИНЕЙН. Так, если уравнение регрессии уже построено, вычислить значение в новой точке нетрудно. Функция ТЕНДЕНЦИЯ решает эту простую задачу. Она неявно вызывает функцию ЛИНЕЙН и, используя полученные оценки параметров, вычисляет прогнозируемые значения в новых точках. Обращение к ней имеет вид:
    ТЕНДЕНЦИЯ(Известные_Y, Известные_X, Новые_значения_X, Конст)
    Здесь появился один новый параметр, задающий в общем случае матрицу новых значений X. Все остальные параметры имеют тот же смысл, что и в функции ЛИНЕЙН. В результате возвращается вектор прогнозных значений Y, вычисленный в точках, заданных матрицей новых значений X. Каждая ее строка задает одну точку.
    Функция ПРЕДСКАЗ - частный случай функции ТЕНДЕНЦИЯ - используется в линейной модели с двумя параметрами, когда уравнение регрессии имеет вид:
    y = a*x + b
    В этом случае Y и X представляют одномерные массивы данных. Вызов функции таков:
    ПРЕДСКАЗ( x; Известные_Y; Известные_X)
    Здесь x - точка, для которой строится прогноз.
    Мы говорили о возможности построения нелинейного уравнения регрессии, которое простым преобразованием сводится к задаче линейной регрессии. Такое преобразование и осуществляет функция ЛГРФПРБЛ. Формально здесь используется нелинейная модель:
    y = b* a1x1 * a2x2 * … * amxm
    Простым логарифмированием модель сводится к линейной.
    ln(y) = x1* ln(a1) + x2*ln(a2) + … + xm*ln(am) + b
    Функция ЛГРФПРБЛ имеет те же параметры, что и функция ЛИНЕЙН. Обращение к ней:
    ЛГРФПРБЛ (Известные_значения_Y; Известные_значения_X; Конст; Статистика)
    Как работает эта функция, совершенно ясно: она вызывает функцию ЛИНЕЙН, подавая ей на вход не сами измерения Y, а их логарифмы. Полученные оценки достаточно подвергнуть обратному преобразованию - взять экспоненту, и задача решена. Так строится нелинейное уравнение регрессии. Этот нехитрый прием позволяет самому строить новые модели нелинейной регрессии.
    Последняя из стандартных функций этого семейства - РОСТ - непосредственно вычисляет значения прогноза в новых точках, используя результаты вызова функции ЛГРФПРБЛ. РОСТ связана с функцией ЛГРФПРБЛ, как ТЕНДЕНЦИЯ связана с ЛИНЕЙН. Обращение к функции имеет вид:
    РОСТ(Известные_Y, Известные_X, Новые_значения_X, Конст)

    Инструментальная панель "Сводные таблицы"

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


  • Кубы OLAP, сводные таблицы и анализ данных

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

    Метод экспоненциального сглаживания

    В модели устойчивого спроса методы прогноза основаны на скользящем среднем, где вычисляется средневзвешенное значение по результатам предыдущих измерений. Весь вопрос в том, какой временной интервал учитывать и какие веса приписывать данным. Один из простых и лучших методов - экспоненциальное сглаживание, описываемое соотношением:
    Pt =
    Метод экспоненциального сглаживания
    St + (1-
    Метод экспоненциального сглаживания
    )Pt-1
    где St - фактический спрос в момент времени t, а Pt - его оценка, экстраполируемая на будущее. Формула показывает, что оценка является взвешенной суммой последнего полученного значения спроса и предыдущей оценки. Параметром метода, устанавливаемым эмпирически, является весовой коэффициент
    Метод экспоненциального сглаживания
    . Чем меньше
    Метод экспоненциального сглаживания
    , тем большее значение придается прошлым данным. Если же большего доверия заслуживают последние данные,
    Метод экспоненциального сглаживания
    следует увеличивать. Рекомендуемые значения
    Метод экспоненциального сглаживания
    обычно выбираются из интервала 0.1-0.5.
    Метод Чоу адаптивного прогнозирования позволяет подбирать
    Метод экспоненциального сглаживания
    в процессе прогноза. Его суть состоит в том, чтобы одновременно вести три прогноза с разными значениями
    Метод экспоненциального сглаживания
    , например 0.1, 0.15 и 0.2. Если реальный спрос ближе к одной из границ, скажем, верхней, система перестраивается, и новыми значениями
    Метод экспоненциального сглаживания
    будут 0.15, 0.2 и 0.25.

    Методы краткосрочного прогноза

    Применяемые при краткосрочном прогнозе методы основываются на разных моделях поведения спроса. Наиболее часто используются модели:
  • устойчивого (постоянного) спроса;
  • линейно изменяющегося спроса (возрастающего или убывающего);
  • сезонного спроса;
  • комбинации этих моделей.


  • Методы прогнозирования

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

    Многомерные (OLAP) источники данных

    Интерес к кубам OLAP как к одному из ключевых источников данных в последнее время резко возрос. И этому есть понятные объяснения. Сводные таблицы являются одним из основных инструментов анализа данных при работе над документами, как на локальных компьютерах, так и при работе в интрасетях. Эффективность работы со сводными таблицами возрастает, когда источником данных является куб OLAP. Это особенно заметно в тех случаях, когда приходится работать с большими объемами данных. Основная причина в том, что между представлением данных в сводных таблицах и кубах OLAP есть большое сходство. Поэтому большую часть работы по требуемой структуризации данных берут на себя серверы OLAP. Поскольку многие пользователи могут работать с одним и тем же представлением данных, заложенным в кубе OLAP, то сервер единожды выполняет работу, результаты которой используются многократно. Другое достоинство состоит в том, что передавать каждому пользователю можно уже агрегированные данные, что существенно снижает нагрузку на объем передаваемых данных в сети и повышает общую эффективность работы.
    Сам термин OLAP означает On-Line Analytical Processing и отражает тот факт, что сервер, хранящий базы данных OLAP, выполняет определенную аналитическую обработку. Часто в термин OLAP вкладывают и другой смысл, отражающий многомерность структуры хранимых данных. Так что, когда говорят о многомерных хранилищах данных, неявно предполагают, что речь идет о базах данных OLAP. Первичной структурой в этих базах является многомерный куб - гиперкуб. Оси этого куба, как и положено, называются измерениями. С каждой точкой в пространстве этого куба связаны данные. Важной особенностью OLAP-куба является то, что на каждом измерении можно задать иерархию, определяющую способ группирования или классификации элементов, принадлежащих данному измерению. Например, одним из измерений куба, хранящего данные о продажах, может быть измерение "Заказчики", на котором естественным образом можно задать иерархию, определяющую географическое распределение заказчиков по континентам, страна м, регионам, городам. На одном измерении можно задать несколько иерархий. Например, тех же заказчиков можно сгруппировать по профессиональным признакам. Кубы OLAP идеально приспособлены для проведения анализа "в глубину". Так пользователь вначале может проанализировать объем заказов по странам. Заметьте, суммарное число заказчиков может быть очень большим и измеряться десятками тысяч, в то же время число стран будет измеряться единицами, так что объем передаваемой информации будет малым. При необходимости пользователь может для той или иной страны проанализировать распределение по регионам, для некоторых выбранных регионов - по городам, так можно дойти и до конкретного заказчика и проанализировать сделанные им заказы. Внутри куба OLAP можно производить и итоговые вычисления, применяя, например, функции суммирования данных, вычисления среднего, нахождения максимума и другие.
    Отметим, что кубы OLAP могут существовать и вне базы OLAP, как отдельные файлы. В этом случае они могут использоваться и в режиме Offline даже при работе вне сети.

    Объект PivotCache и коллекция PivotCaches

    Коллекция PivotCaches возвращается при вызове одноименного свойства объекта Workbook. Как и большинство коллекций, она устроена достаточно просто. У нее типичные для коллекций свойства: Application, Count, Creator, Parent. Методы также классические - Item и Add. Из всего этого набора заслуживает рассмотрения только метод Add, позволяющий создать новый объект. Вот его синтаксис:
    Expression.Add(SourceType, SourceData)
    Здесь Expression - выражение, возвращающее объект класса PivotCaches. Аргументы метода имеют следующий смысл:
  • SourceType - задает тип источника данных. Значение аргумента может быть одной из следующих четырех констант: xlCosolidation, xlDatabase, xlExternal, xlPivotTable. Каждая из этих констант задает один из четырех возможных типов, которые уже обсуждались, когда речь шла о первом шаге работы Мастера сводных таблиц, смотри, например, рис. 8.1.
  • SourceData - возможный аргумент, который не следует задавать, если первый аргумент имеет значения xlExternal, то есть задает внешний источник данных. Для остальных трех значений первого аргумента параметр SourceData определяет источник данных. Тип этого аргумента, естественно зависит от значения первого аргумента. Так, если первый аргумент определяет базу данных Excel, то аргумент задается объектом Range, определяющим эту базу. Если речь идет о диапазонах консолидации, то аргумент задается массивом из объектов Range. В случае, когда речь идет о другой сводной таблице, как источнике данных, то аргумент задается строкой текста, представляющей имя сводной таблицы.

  • Конечно, более всего, для нас интересен случай, когда источником построения сводной таблицы является база данных. Понятно, что в этом случае метод Add определяет пустой объект PivotCache, еще не заполненный данными, поскольку второй параметр при выполнении метода не задается и источник данных, следовательно, не определен. Как же происходит заполнение объекта PivotCache данными в этом случае? Ответ прост - при установлении свойств объекта. Ситуация во многом напоминает работу с объектом Command из библиотеки ADO. У объекта PivotCache имеются свойства - Connection, CommandType, CommandText, аналогичные одноименным свойствам объекта Command.
    Давайте перейдем к рассмотрению свойств и методов объекта PivotCache.

    Объект PivotTable и коллекция PivotTables

    Если коллекция PivotCaches связана с рабочей книгой, то коллекция PivotTables связана с отдельным листом этой книги. Коллекция PivotTables возвращается при вызове одноименного свойства объекта Worksheet. Обе коллекции устроены одинаково и имеют один и тот же набор свойств и методов. У коллекции PivotTables имеются следующие свойства: Application, Count, Creator, Parent. У нее есть также два метода - Item и Add. Из всего этого набора заслуживает рассмотрения только метод Add, позволяющий создать новый объект. Вот его синтаксис:
    Function Add(PivotCache As PivotCache, TableDestination, [TableName], [ReadData]) As PivotTable
    Чуть выше я рассматривал, как основной способ создания объектов PivotTable, вызов метода CreatePivotTable объекта PivotCache. Я называл этот способ основным по той причине, что создание кэша не является самоцелью, - это не самостоятельный объект. Всегда он создается для того, чтобы связать с ним отчет сводной таблицы - объект PivotTable. Поэтому разумно, создав объект PivotCache тут же вызвать его метод CreatePivotTable, чтобы создать и объект PivotTable.
    Тем не менее, для создания объекта PivotTable есть возможность использовать метод Add коллекции PivotTables. Он выполняет ту же работу, что и метод CreatePivotTable, и имеет тот же набор аргументов. Дополнительно, в качестве первого аргумента, естественно, указывается объект PivotCache, на основе которого создается объект PivotTable.
    Чуть позже я приведу примеры, где будет показано применение обоих способов создания объекта PivotTable.

    Оптимизация и анализ "Что, если ...?"

    Средства оптимизации - мощные инструменты, используемые в анализе "Что, если ...?". Рассмотрим вначале то, что попроще. "Подбор Параметра" позволяет для функции одного параметра F(a) подобрать, если можно, такое значение параметра a^, что функция в этой точке будет иметь заранее заданное значение F* = F(a^). Наш менеджер, найдя наилучший вариант сценария, спросил себя: "Что, если слегка увеличить тираж? Увеличу ли я за счет этого доход до 200 000?" Чтобы ответить на эти вопросы, он выбрал в меню "Сервис" пункт "Подбор параметра". В появившемся окне он задал доход как целевую функцию, 200000 - как желаемое значение дохода, Тираж - как параметр (изменяемую ячейку), значение которого нужно подобрать так, чтобы достичь заданной величины дохода. Но сделать этого невозможно. В такой постановке у задачи решения нет, о чем и было ему сообщено. Менеджеру пришлось умерить свои аппетиты: он повторно вызвал "Подбор параметра
    "µ ", задав теперь значение дохода, равное 180000. Теперь решение удалось найти. Оно достигается при тираже, равном 10900. Менеджер округлил значение тиража до 10500, что принесло увеличение дохода еще на 10000. На этом менеджер и остановился. Найденное им решение практически оптимально.
    Подбор параметра можно осуществлять и программно. Для этого следует вызвать метод GoalSeek объекта Range. Вызывает этот метод объект Range, задающий целевую функцию. Синтаксис метода:
    Function GoalSeek(Goal, ChangingCell As Range) As Boolean
    Параметр Goal задает значение целевой функции, а параметр ChangingCel - изменяемую ячейку (параметр целевой функции), значение которой будет изменяться, в попытке достичь заданного значения целевой функции. Функция GoalSeek возвращает True в случае успеха и False в противном случае. Вот пример процедуры, вызывающей эту функцию:
    Sub ПодборПараметра() Dim Res As Boolean 'Доход - имя целевой ячейки - H49, вычисляющей доход Res = Range("H49").GoalSeek(Goal:=200000, ChangingCell:=Range("J37")) If Res Then Доход = 200000 Else 'Восстанавливаем нормальные значения в ячейках ActiveSheet.Scenarios(3).Show Res = Range("H49").GoalSeek(Goal:=180000, ChangingCell:=Range("J37")) If Res Then Доход = 180000 End If MsgBox ("Ваш Доход = " & Доход) End Sub
    И в заключение этой главы об анализе офисной деятельности и используемых для этого средствах Excel взгляните на окошко, уведомляющее о достигнутом доходе:
    Оптимизация и анализ

    Рис. 8.37.  Уведомление о достигнутом доходе

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

    Оптимизация и анализ

    Рис. 8.37.  Уведомление о достигнутом доходе

    Оптимизация и анализ
    Оптимизация и анализ
    Оптимизация и анализ
    © 2003-2007 INTUIT.ru. Все права защищены.

    Построение модели прогноза продаж книг офиса "РР"

    Рассмотрим теперь применение этих методов для построения модели прогноза продаж книг офиса "РР". Вернемся к таблице, где представлены данные о продажах в течение последних 10 недель. Менеджера интересует прогноз на последующий месяц, и хотя, как мы убедились, он умеет прогнозировать продажи, используя полученное уравнение регрессии, теперь он хочет воспользоваться стандартными функциями прогноза, которые только что были рассмотрены.
    Менеджер начинает работу с визуального анализа данных. Для этой цели он использует возможности визуального представления на диаграмме линии тренда, прогноз значений тренда на требуемый период, возможность задания доверительных интервалов. Эту визуализацию можно сделать как вручную, так и программно. Наш менеджер для таблицы продаж построил три одинаковые диаграммы, на каждой из которых дополнительно вывел:
  • доверительные интервалы;
  • прямолинейный тренд и некоторые его характеристики;
  • полиномиальный тренд и его характеристики.

  • Построение модели прогноза продаж книг офиса

    увеличить изображение
    Рис. 8.28.  Диаграммы, доверительные интервалы и линии тренда
    На первой из диаграмм менеджер дополнительно вывел доверительные интервалы шириной в 2? (среднеквадратичное отклонение). Такой интервал с высокой вероятностью накрывает истинное значение. Правда, построенный интервал не отражает динамики изменения данных и, к сожалению менеджера, слишком велик.
    На второй диаграмме показан прямолинейный тренд, выведено уравнение регрессии и построен прогноз на ближайшие три недели. Таким образом, здесь в визуальной форме отражены результаты вычислений функций ЛИНЕЙН и ТЕНДЕНЦИЯ. На третьей диаграмме - полиномиальный тренд, где линия регрессии задается кубическим полиномом.
    Несколько слов о том, как вручную можно получить диаграммы с трендом и доверительными интервалами. Построив диаграмму, щелкните правой кнопкой в одном из рядов диаграммы и из контекстного меню выберите пункт "Формат рядов данных" и затем вкладку "Y-погрешности". В появившемся окне укажите, отображать ли планки погрешностей, одну или обе, и установите величину погрешности (ее тип, например, стандартное отклонение), количество единиц погрешности. Так визуализируются доверительные интервалы на диаграмме. Для отображения тренда из контекстного меню нужно выбрать пункт "Добавить линии тренда", в появившемся окне - вкладку "Тип" и задать один из 6 возможных типов тренда: линейный, полиномиальный, логарифмический, показательный, экспоненциальный или скользящее среднее. Вкладка "Параметры" позволяет вывести на диаграмму уравнение линии регрессии. Что более важно, тут же можно задать количество интервалов (вперед и назад), для которых будут построены и выведены на график прогнозируемые значения.

    Построение OLAP-куба

    Excel позволяет по данным внешних источников построить не только сводную таблицу, но и многомерный куб данных - OLAP куб. Цель этого построения - использовать в дальнейшем этот куб, как источник данных для сводной таблицы. При больших объемах данных, используемых при построении сводной таблицы, эффективность работы может быть существенно повышена, если сводная таблица использует OLAP куб.
    Работа по построению OLAP куба начинается с построения сводной таблицы. Но в тот момент, когда построен запрос, извлекающий данные из базы данных, можно перейти к построению OLAP куба. Взгляните на рис. 8.7, где показан завершающий шаг построения запроса, - здесь можно включить соответствующий переключатель и перейти к созданию OLAP куба. В примере, где это окно не появлялось, и где запрос создавался непосредственно в Microsoft Query, что показано на рис. 8.10, для построения OLAP куба можно выбрать соответствующий пункт из меню File.
    Итак, построение OLAP куба в Excel начинается так же, как и построение сводной таблицы вплоть до момента завершения построения запроса. В этот момент появляется возможность построить OLAP куб по запросу. Давайте продолжим рассмотрение, начиная с этого момента. Предположим, что в окне, показанном на рис. 8.7, я выбрал третий переключатель, запустив, тем самым, на исполнение нового Мастера - Мастера построения OLAP куба. Взгляните, как выглядит первое окно, открываемое этим Мастером:
    Построение OLAP-куба

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

    Рис. 8.20.  Окно первого шага Мастера построения OLAP куба
    Заметьте, по умолчанию все численные поля являются вычисляемыми, а в качестве функции применяется функция "Сумма". Я оставил предлагаемые установки и перешел к следующему шагу работы. На этом шаге работы Мастер предлагает создать измерения нашего куба:
    Построение OLAP-куба

    Рис. 8.21.  Создание измерений OLAP куба
    Заметьте также, вычисляемые поля стали полями данных. Все оставшиеся поля я перетащил, и они стали измерениями нашего куба. Поле "Дата заказа", имеющее тип даты, автоматически породило иерархию на соответствующем измерении. Понятно, что можно и самому создать иерархию при перетаскивании полей, например, когда речь идет о многократно упоминавшихся полях типа: Страна - Регион - Город. Взгляните на результат создания измерений:
    Построение OLAP-куба

    Рис. 8.22.  Результат создания измерений OLAP куба
    На заключительном шаге работы Мастер предлагает сохранить OLAP куб. Здесь можно выбрать, сохранить ли только определение куба - файл с уточнением "oqy" или сам куб с данными - файл с уточнением "cub". Сохранение самого куба хотя и требует времени, но обеспечивает более быструю работу со сводной таблицей в дальнейшем при использовании куба.
    Построение OLAP-куба

    Рис. 8.23.  Сохранение OLAP куба
    Я предпочел сохранить сам куб. На этом построение куба завершено. Но чтобы рассказ о нем был завершен, следует привести сводную таблицу, построенную на этом кубе. Опускаю детали, их и так было достаточно, приведу окончательный результат:
    Построение OLAP-куба

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

    Построение сводной таблицы вручную

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

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

    Рис. 8.1.  Первое окно Мастера сводных таблиц и диаграмм
    Заметьте, из четырех возможностей задания разных типов источников данных - списков Excel, внешних источников, нескольких диапазонов, другой сводной таблицы - я выбрал внешний источник данных, поскольку, как я уже говорил, буду строить сводную таблицу, используя базу данных Access. Вторая группа переключателей позволяет задать желаемый вид отчета - сводную таблицу или сводную диаграмму, построенную на основе сводной таблицы. Пример с диаграммой приведем чуть позже, а сейчас займемся чисто сводными таблицами. Сделав выбор, остается нажать кнопку "Далее", что заставляет Мастера сделать очередной шаг. Вот окно, открываемое на втором шаге:
    Построение сводной таблицы вручную

    Рис. 8.2.  Окно второго шага Мастера сводных таблиц

    Взгляните на результат моей работы:

    Построение сводной таблицы вручную

    Рис. 8.5.  Выбор полей базы данных для включения их в сводную таблицу

    Нажатие кнопки "Next" заставляет Мастера запросов перейти к очередному шагу. Что будет выполняться на следующем шаге, зависит от того, сумеет ли Мастер запросов извлечь требуемые данные из таблиц базы данных. Если проблем у него не возникает, то Мастер запросов предложит создать фильтр для отбираемых данных. Вот как выглядит соответствующее окно в несколько более простой ситуации, когда не включено поле "Город" в число полей, запрашиваемых для построения сводной таблицы.

    Построение сводной таблицы вручную

    Рис. 8.6.  Запрос на построение фильтров

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

    Построение сводной таблицы вручную

    Рис. 8.7.  Завершающий шаг построения запроса

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

  • вернуть данные в Excel и возвратиться к очередному шагу работы Мастера сводных таблиц и диаграмм,
  • перейти в Microsoft Query и там продолжить работу над запросом,
  • перейти к построению OLAP куба.


  • Обычная практика состоит в том, что выбирается первый пункт, и данные возвращаются в Excel. Мы тоже вернемся в Excel, но чуть попозже, а пока рассмотрим исходную ситуацию, когда поле "Город" включено в запрос. Эта ситуация при построении запроса оказалась чуть более сложной, и у Мастера построения запросов возникли некоторые трудности, - он оказался не в состоянии разобраться в связях между таблицами базы данных, и попросил выполнить эту работу вручную, перейдя в Microsoft Query. Вот появляющееся сообщение о возникших у него трудностях:

    Построение сводной таблицы вручную

    Рис. 8.8.  Сообщение о возникших трудностях у мастера запросов

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

    Построение сводной таблицы вручную

    Рис. 8.9.  Схема данных базы "dbPP2000"

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

    Построение сводной таблицы вручную

    Рис. 8.10.  Установление связей между таблицами

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

    Построение сводной таблицы вручную

    Рис. 8.11.  Новое состояние окна Мастера сводных таблиц на втором шаге

    Заметьте, теперь, в отличие от рисунка 8.2, наряду с уведомлением о получении данных стала доступной кнопка "Далее", которую я и нажал для перехода к последнему шагу работы Мастера:

    Построение сводной таблицы вручную

    Рис. 8.12.  Заключительный шаг работы Мастера сводных таблиц

    На заключительном шаге работы можно указать рабочий лист и ячейку, начиная с которой будет располагаться сводная таблица. Заметьте, наряду с кнопкой "Готово", нажатие которой завершает работу Мастера, в нашем распоряжении есть и другие кнопки, в частности, кнопка "Макет". Вот как выглядит окно макета сводной таблицы:

    Построение сводной таблицы вручную

    Рис. 8.13.  Макет сводной таблицы

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

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

    Построение сводной таблицы вручную

    Рис. 8.14.  Рабочий лист с макетом сводной таблицы и инструментальной панелью

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

    Я в данном примере размещу все поля и приведу некоторые аргументы в пользу выбранного мной варианта размещения полей:

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


  • Применение сценариев для решения задачи менеджера

    Итак, сформулирована задача, в которой имеется результирующая функция, задающая доход. Эта функция зависит от многих параметров. Некоторыми из них можно управлять. Задача, которую должен решить менеджер для оптимизации дохода, заключается в поиске подходящих значений этих параметров. Конечно, можно считать, что наилучшие значения этих параметров менеджер мог бы получить, если бы попытался решать свою задачу как задачу оптимизации. Но здесь не все так просто. Точная формулировка задачи оптимизации потребовала бы дополнительных усилий, трудно было бы сформулировать некоторые ограничения, например, условия накладываемые типографией. Можно отметить и сложность решения оптимизационных задач. Кроме того, часто нет смысла стрелять из пушки по воробьям и искать точное решение в условиях, когда сама модель и ее параметры далеко не точны и отражают лишь суть дела.
    В этих типичных условиях сценарии имеют большое практическое значение. Здесь пользователю предоставляется возможность рассмотреть наиболее разумные варианты. А опытный пользователь в своем деле эксперт - всегда знает, где лежит подходящее решение. Поэтому ему достаточно обычно просмотреть несколько возможных вариантов и выбрать наилучший среди них. Варианты, или, как их называют, сценарии, могут быть предложены разными специалистами. Отчет по результатам применения различных сценариев позволяет обосновать принятое решение.
    Я специально построил довольно сложную результирующую функцию, по ходу вычисления которой приходится выполнять сложные расчеты, в том числе обращаться к процедурам, написанным на VBA. Вообще результаты принятого решения можно оценивать сразу по нескольким критериям, так что функций, выдающих результат, может быть несколько. Все это не мешает применять сценарии.
    Рассмотрим решение задачи менеджера с применением сценариев. Обращаю внимание, что прежде чем обращаться к сценариям предстоит довольно большая работа по формированию на рабочем листе модели исследуемого процесса. В этой модели нужно определить ту функцию, которая подлежит оптимизации, параметры, которые будут изменяться в сценариях. На нашем примере можно убедиться, что это, возможно, достаточно серьезная работа. Но не буду Вас больше пугать, лучше взгляните, как выглядит рабочий лист, на котором эта работа уже проделана:
    Применение сценариев для решения задачи менеджера

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

    =A37 + B37*A40 +C37*A40*A40 +D37*Рек +E37*Кон*Цен

    Эта функция и используется в таблице подстановки, расположенной в ячейках B41:N42.

    Значения используемых в расчетах параметров видны на рисунке.

  • Далее вычисляется период продаж T. В соответствующей ячейке - B49 вызывается пользовательская функция ПериодПродаж:

    =ПериодПродаж(C42:N42; Тир)

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

    Public Function ПериодПродаж(Sails As Variant, Tir As Integer) As Integer 'Вычисляет число месяцев, в течение которых распродан тираж. 'Если тираж не распродан в течение года, возвращается число 13 'Параметр Sails задает продажи по месяцам, Tir - объем тиража Sum = 0 For i = 1 To 12 Sum = Sum + Sails.Cells(i) If Sum >= Tir Then Exit For Next i ПериодПродаж = i End Function
  • Затем вычисляется количество проданных книг по формуле:

    If T < 13 Then N = Тир Else N = SumNI

    где SumNI посчитано заранее вместе с таблицей подстановки. Для реализации данного соотношения в соответствующую ячейку - D49 записана формула:

    =ЕСЛИ(B49<13;J37;O42)
  • На следующем шаге в ячейку - H49, задающую доход, я записал формулу, его вычисляющую:

    =D49*Цен*Себ -G37*Рек -H37*Тир*Себ -I37*B49

    Общую формулу определения дохода я приводил выше.



  • На этом завершается подготовительный этап работы по формированию на рабочем листе нужной модели. Теперь модель определена, - пора задать сценарии. Вручную это делается так. В меню "Сервис" выбирается пункт "Сценарии", а в открывшемся окне Диспетчера сценариев - нужная кнопка. Для первоначального создания сценариев служит кнопка "Добавить". Вот это окно:

    Применение сценариев для решения задачи менеджера

    Рис. 8.32.  Окно диспетчера сценариев

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

    Применение сценариев для решения задачи менеджера

    Рис. 8.33.  Добавление сценария


    =A37 + B37*A40 +C37*A40*A40 +D37*Рек +E37*Кон*Цен

    Эта функция и используется в таблице подстановки, расположенной в ячейках B41:N42.

    Значения используемых в расчетах параметров видны на рисунке.

  • Далее вычисляется период продаж T. В соответствующей ячейке - B49 вызывается пользовательская функция ПериодПродаж:

    =ПериодПродаж(C42:N42; Тир)

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

    Public Function ПериодПродаж(Sails As Variant, Tir As Integer) As Integer 'Вычисляет число месяцев, в течение которых распродан тираж. 'Если тираж не распродан в течение года, возвращается число 13 'Параметр Sails задает продажи по месяцам, Tir - объем тиража Sum = 0 For i = 1 To 12 Sum = Sum + Sails.Cells(i) If Sum >= Tir Then Exit For Next i ПериодПродаж = i End Function
  • Затем вычисляется количество проданных книг по формуле:

    If T < 13 Then N = Тир Else N = SumNI

    где SumNI посчитано заранее вместе с таблицей подстановки. Для реализации данного соотношения в соответствующую ячейку - D49 записана формула:

    =ЕСЛИ(B49<13;J37;O42)
  • На следующем шаге в ячейку - H49, задающую доход, я записал формулу, его вычисляющую:

    =D49*Цен*Себ -G37*Рек -H37*Тир*Себ -I37*B49

    Общую формулу определения дохода я приводил выше.



  • На этом завершается подготовительный этап работы по формированию на рабочем листе нужной модели. Теперь модель определена, - пора задать сценарии. Вручную это делается так. В меню "Сервис" выбирается пункт "Сценарии", а в открывшемся окне Диспетчера сценариев - нужная кнопка. Для первоначального создания сценариев служит кнопка "Добавить". Вот это окно:

    Применение сценариев для решения задачи менеджера

    Рис. 8.32.  Окно диспетчера сценариев

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

    Применение сценариев для решения задачи менеджера

    Рис. 8.33.  Добавление сценария

    В следующем окне задаются значения изменяемых ячеек, устанавливаемых сценарием:

    Применение сценариев для решения задачи менеджера

    Рис. 8.34.  Установка значений параметров, заданных сценарием

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

    Заключительный шаг при работе со сценариями - подведение итогов. Щелкнув в окне Диспетчера кнопку "Отчет" и выбрав один из двух типов отчета, Вы получите итоговый отчет, позволяющий обосновать принимаемое решение:

    Применение сценариев для решения задачи менеджера

    увеличить изображение
    Рис. 8.35.  Отчет по результатам вычисления сценариев

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

    Пример применения функции ЛИНЕЙН в задаче прогнозирования

    Приведем теперь пример применения функции ЛИНЕЙН. Менеджер офиса "РР" решил построить уравнение, прогнозирующее продажи одной из книг. В его распоряжении были данные по продажам этой книги за последние 10 недель. Агенты офиса фиксировали также уровень рекламы и количество конкурирующих товаров (книг на аналогичную тему). Используя функцию ЛИНЕЙН, менеджер построил уравнение множественной регрессии. Взгляните, как выглядит лист рабочей книги Excel, где размещены данные о продажах и где менеджер построил уравнение регрессии, основываясь на этих данных:
    Пример применения функции ЛИНЕЙН в задаче прогнозирования

    увеличить изображение
    Рис. 8.26.  Построение уравнения регрессии по данным продаж
    Менеджер построил это уравнение дважды, получив два уравнения - Y1 и Y2, используя выборки измерений разного объема. Этот полезный прием позволяет понять, насколько полученные коэффициенты критичны к измерениям.
    Наш менеджер достаточно хорошо разбирается в статистике, поэтому он тщательно проанализировал все данные, возвращаемые функцией ЛИНЕЙН для двух ее вызовов. Обратите внимание, массивы результатов работы функции на рисунке подсвечены. Смысл каждого из результирующих параметров выше уже был пояснен и я не буду на этом останавливаться.
    С содержательной точки зрения важен следующий полученный результат. Оба измеряемых параметра - уровень рекламы и число конкурирующих книг - являются статистически значимыми. Большее влияние на уровень продаж оказывает число книг - конкурентов.
    Скажу еще, что менеджер вполне обоснованно решил использовать полученные уравнения как для прогноза будущих продаж, так и для принятия таких решений, как, скажем, повышение уровня рекламы. Чтобы визуально увидеть влияние уровня рекламы на продажи, менеджер использовал полученные уравнения для построения графиков Y1(U) и Y2(U) при фиксированных значениях параметров T и V. Результаты его работы можно увидеть на следующем рисунке:
    Пример применения функции ЛИНЕЙН в задаче прогнозирования

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

    Прогнозирование нестационарных показателей

    Чаще всего среднее значение спроса с течением времени меняется. Такое изменяющееся среднее принято называть трендом. Для краткосрочного прогноза часто достаточно ограничиться линейным трендом. Наиболее распространены две модели линейного тренда. При линейно-аддитивном тренде среднее изменяется на постоянную величину за время dt. В линейно-мультипликативной модели тренд меняется на постоянный процент, например, ежемесячно спрос может возрастать на 2%. Рассмотрим подробнее линейно-аддитивную модель, когда спрос меняется в соответствии с формулой:
    St = a + b* t + et
    Здесь et - ошибка измерения. Если параметры модели a и b постоянны, их оценки можно получить по методу наименьших квадратов. Именно такие оценки реализованы в стандартных функциях Excel, предназначенных для прогнозирования. Однако можно рассматривать методы, когда предполагается, что и сами параметры меняются во времени. Метод, предложенный Холтом, использует ту же идею взвешенного суммирования, примененную в экспоненциальном сглаживании. Вот соотношения для расчета оценки прогноза и оценки параметра b:
    Pt =
    Прогнозирование нестационарных показателей
    *St + (1-
    Прогнозирование нестационарных показателей
    )*(St -1 + bt -1 * dt) bt = ?*(Pt - Pt -1)/ dt + (1-?)* bt -1
    Здесь dt - временной интервал между двумя последними измерениями. Прогнозируемое значение на момент времени t+t1 вычисляется по формуле:
    Ft + t1 = Pt + bt * t1
    Некоторым недостатком метода является необходимость эмпирического задания двух констант
    Прогнозирование нестационарных показателей
    и ?, задающих веса. В методе двойного сглаживания Брауна достаточно ввести одну константу. Прогнозируемое значение здесь вычисляется по формуле:
    Ft + t1 = 2Pt - Qt + bt * t1
    Двойное экспоненциально взвешенное среднее вычисляется из соотношения:
    Qt =
    Прогнозирование нестационарных показателей
    Pt + (1-
    Прогнозирование нестационарных показателей
    )Qt-1
    Оценка коэффициента bt дается формулой:
    bt =
    Прогнозирование нестационарных показателей
    /(1-
    Прогнозирование нестационарных показателей
    )*( Pt -Qt)
    Есть и другие модели краткосрочного прогнозирования тренда, например, методы Бокса-Дженкинса.

    Программирование сценариев

    Рассмотрим теперь объекты, обеспечивающие программное создание сценариев и дальнейшую работу с ними. Вот процедура, решающая все эти задачи:
    Sub ДобавитьСценарии() 'Создание трех сценариев Dim mys As Worksheet Dim Scen As Scenario, Scens As Scenarios Set mys = ThisWorkbook.Worksheets("Лист6") With mys Set Scens = .Scenarios If .Scenarios.Count > 0 Then 'Удаление сценариев For Each Scen In Scens Scen.Delete Next Scen End If With Scens .Add Name:="Минимальный тираж", ChangingCells:=Range("J37:L37"), _ Values:=Array("5000", "0", "1,5") .Add Name:="Максимальный тираж", ChangingCells:=Range("J37:L37"), _ Values:=Array("30000", "5", "2,5") .Add Name:="Нормальный тираж", ChangingCells:=Range("J37:L37"), _ Values:=Array("10000", "2", "2") 'Запуск сценариев на выполнение For Each Scen In Scens Scen.Show Next Scen 'Построение отчета '.CreateSummary ReportType:=xlStandardSummary, _ ' ResultCells:=Range("H49,D49,B49") .CreateSummary ReportType:=xlSummaryPivotTable, _ ResultCells:=Range("H49,D49,B49")
    End With End With End Sub
    Рассмотрим на этом примере основные объекты и методы, связанные с применением сценариев. Рабочие листы (объекты Sheet) включают в свой состав коллекцию Scenarios. Новые элементы в эту коллекцию добавляются, как чаще всего бывает, методом Add. Параметр Name задает имя сценария, ChangingCells - изменяемые ячейки. Обычно эти ячейки располагают подряд, чтобы можно было их указать одним смежным интервалом, но делать так не обязательно - объект Range может задавать и несмежные интервалы. В параметре Comment указывается дополнительная информация, по умолчанию задается автор сценария. Эти данные выводятся в итоговом отчете. Остальные два параметра задают возможность скрытия сценария и его защиты от несанкционированного доступа, - не всегда и не всем требуется объяснять принятое решение.
    Совсем просто запустить сценарий на выполнение, удалить или изменить. Для этого у объектов Scenario есть методы Show, Delete, ChangeScenario. В нашем примере методом Show все три сценария поочередно запускаются на выполнение. Чтобы процедура работала в случаях ее многократного запуска, то сценарии на рабочем листе предварительно удаляются, для чего используется метод Delete.
    Для создания отчета и подведения итогов используется метод CreateSummary. У параметра ReportType, задающего тип отчета, возможны значения xlStandartSummary и xlSummaryPivotTable. С первым отчетом, принимаемым по умолчанию, Вы знакомы, во втором случае отчет определяет сводную таблицу. Параметр ResultCells позволяет задать результирующие ячейки. В отличие от работы вручную я включил в итоговый отчет сведения о трех параметрах: доходе, периоде продажи и количестве проданных книг. Объект Range здесь задает три несмежные ячейки. В процедуру включены два вызова метода CreateSummary, каждый из которых создает свой тип отчета. Один из вызовов, естественно, закомментирован. Поскольку стандартный тип отчета уже приведен, то взгляните, как выглядит сводная таблица, построенная в результате выполнения этой процедуры:
    Программирование сценариев

    Рис. 8.36.  Сводная таблица, программно построенная по результатам выполнения сценариев

    Программное формирование структуры сводной таблицы

    Теперь, когда объекты PivotCache и PivotTable уже созданы, можно приступить к завершающему этапу - формированию структуры отчета сводной таблицы. Это означает, что нужно поля таблицы, содержащиеся в коллекции PivotFields распределить по измерениям. И здесь существует несколько способов выполнения этой работы. В предыдущих версиях я применял метод AddFields, который, правда, имел ряд ограничений. Теперь всю эту работу удобнее выполнять, работая непосредственно с объектами PivotField. Полного описания этих объектов давать не буду, но поясню, какими свойствами я пользовался, на примере формирования отчета сводной таблицы. Процедура, которую я сейчас приведу, полностью решает вопрос программного создания сводной таблицы, начиная от этапа связывания с источником данных, кончая этапом формирования структуры таблицы и группирования ее данных. Вот ее текст:
    Public Sub MyCreatePT() 'Создание отчета сводной таблицы 'Создание кэша и отчета сводной таблицы - объектов PivotCache, PivotTable 'CreatePivotCacheAndTable 'Другой вариант создания кэша - через ADO CreatePivotCacheAndTableWithADO 'Формирование отчета сводной таблицы With ThisWorkbook.Worksheets("Лист1").PivotTables("Анализ продаж") With .PivotFields("ДатаЗаказа") .Orientation = xlRowField .Position = 1 End With With .PivotFields("Сотрудник") .Orientation = xlRowField .Position = 2 End With With .PivotFields("НазваниеКниги") .Orientation = xlColumnField .Position = 1 End With With .PivotFields("Заказчик") .Orientation = xlPageField .Position = 1 End With With .PivotFields("Стоимость") .Orientation = xlDataField .Position = 1 End With With .PivotFields("Количество") .Orientation = xlDataField .Position = 2 End With End With Range("A5").Select Selection.Group Start:=True, End:=True, By:=7, Periods:=Array(False, _ False, False, True, False, False, False) End Sub
    Я приведу несколько комментариев:
  • На первом этапе работы создаются объекты PivotCache и PivotTable, для чего вызываются уже рассмотренные нами процедуры. Реально вызывается одна из этих процедур, вызов другой закомментирован. Какой вариант предпочесть - дело вкуса. О достоинствах этих вариантов я говорил.
  • После создания указанных объектов формируется структура отчета сводной таблицы. Для каждого из полей сводной таблицы задается соответствующее измерение и порядок расположения. Для этого используются свойства объектов PivotField - Orientation и Position. Первое из них задает измерение, второе - порядок в измерении. Добраться до нужного поля позволяет коллекция PivotFields, где в качестве индекса используется имя поля.
  • На заключительном шаге производится группирование данных по полю "Дата заказа". В данном случае я группирую данные по неделям. Скажу несколько слов о методе группирования данных - Group, производящем эту операцию. Он является методом класса Range и, следовательно, может вызываться объектом Selection. Здесь применяется его форма, специально созданная для группирования данных сводной таблицы. При группировании дат булев массив Periods указывает одну из 7 возможных единиц группирования (секунду, минуту, час, день, месяц, квартал, год), а параметр BY задает количество единиц в группе.

  • Единственное, что осталось сделать для завершения рассказа о программном создании отчета сводной таблицы, - это посмотреть на результаты работы нашей процедуры:
    Программное формирование структуры сводной таблицы

    увеличить изображение
    Рис. 8.25.  Программно построенная сводная таблица

    Программное построение диаграмм с доверительными интервалами и трендами

    Как это все программируется? В свое время мы рассказали о Chart-объектах, их свойствах и методах, позволяющих программно строить диаграммы. Сейчас мы на примере покажем объекты, используемые при выводе доверительных интервалов и трендов. Например, эта процедура строит доверительный интервал:
    Sub ДоверительныеИнтервалы() 'Построение доверительных интервалов на диаграммме Dim myChart As Chart Dim mySeries As Series Set myChart = ThisWorkbook.Worksheets("Лист5").ChartObjects(1).Chart Set mySeries = myChart.SeriesCollection(1) mySeries.Select mySeries.ErrorBar Direction:=xlY, Include:=xlErrorBarIncludeBoth, _ Type:=xlErrorBarTypeStDev, Amount:=1 mySeries.ErrorBars.Border.Color = RGB(255, 0, 0) myChart.ChartArea.Select End Sub
    Как видите, я создаю объект Chart, выделяя соответствующий ChartObject объект, встроенный в рабочий лист. Затем создаю объект класса Series, задающий ряд данных. В нашем примере диаграмма включает только один ряд, так что коллекция SeriesCollection содержит единственный элемент.
    Для элемента класса Series вызывается метод ErrorBar, который и строит доверительный интервал. Значение xlErrorBarIncludeBoth параметра Include указывает на необходимость построения двухсторонних границ интервала погрешностей, а тип ошибок задается параметром Type, параметр Amount задает количество единиц в интервале погрешностей. Чем шире интервал, тем выше вероятность попадания истинного значения в указанный интервал. Доверительный интервал выделяется заданным цветом.
    Следующая процедура демонстрирует построение тренда:
    Sub ЛинейныйТренд() 'Построение трендов на диаграммме Dim myChart As Chart Dim mySeries As Series Set myChart = ThisWorkbook.Worksheets("Лист5").ChartObjects(1).Chart Set mySeries = myChart.SeriesCollection(1) mySeries.Select mySeries.Trendlines.Add(Type:=xlLinear, Forward:=3, _ Backward:=0, DisplayEquation:=True, DisplayRSquared:=True).Select mySeries.Trendlines.Add(Type:=xlPolynomial, Order:=3, Forward:=3, _ Backward:=0, DisplayEquation:=True, DisplayRSquared:=True).Select

    myChart.ChartArea.Select End Sub

    На одной диаграмме можно построить несколько трендов различного типа. Поэтому в состав объекта Series входит коллекция TrendLines, элементы которой создаются при вызове метода Add. Параметр Type задает один из 6 возможных типов тренда, параметры Forward и Backward задают интервалы прогнозируемых значений. Соответственно прогноз делается вперед и/или назад. Булев параметр DisplayEquation включает вывод уравнения регрессии в окне диаграммы.

    Процедура строит на одной диаграмме два тренда - линейный и полиномиальный. Заметьте, для полиномиального тренда задается дополнительный параметр Orde, определяющий степень аппроксимирующего полинома.

    Вот как выглядит лист с диаграммой, после того, как отработали рассмотренные нами процедуры:

    Программное построение диаграмм с доверительными интервалами и трендами

    увеличить изображение
    Рис. 8.29.  Программно построенные доверительные интервалы и тренды

    Но вернемся к менеджеру "РР". Визуальный анализ данных показал, что, вряд ли, результаты продаж хорошо согласуются с моделью линейного или полиномиального тренда. Менеджер просмотрел все виды трендов: ни один из них не учитывал в полной мере характер поведения данных. Один из них при прогнозе дает слишком пессимистическую оценку, другой - излишне оптимистическую. Полином третьей степени неплохо описывает поведение данных, но только на интервале наблюдения. Использовать его для целей прогноза, очевидно, невозможно. Увы, такая ситуация типична. Модель, особенно полиномиальная, может хорошо описывать наблюдаемые значения, но не годиться для прогноза.

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

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


    myChart.ChartArea.Select End Sub

    На одной диаграмме можно построить несколько трендов различного типа. Поэтому в состав объекта Series входит коллекция TrendLines, элементы которой создаются при вызове метода Add. Параметр Type задает один из 6 возможных типов тренда, параметры Forward и Backward задают интервалы прогнозируемых значений. Соответственно прогноз делается вперед и/или назад. Булев параметр DisplayEquation включает вывод уравнения регрессии в окне диаграммы.

    Процедура строит на одной диаграмме два тренда - линейный и полиномиальный. Заметьте, для полиномиального тренда задается дополнительный параметр Orde, определяющий степень аппроксимирующего полинома.

    Вот как выглядит лист с диаграммой, после того, как отработали рассмотренные нами процедуры:

    Программное построение диаграмм с доверительными интервалами и трендами

    увеличить изображение
    Рис. 8.29.  Программно построенные доверительные интервалы и тренды

    Но вернемся к менеджеру "РР". Визуальный анализ данных показал, что, вряд ли, результаты продаж хорошо согласуются с моделью линейного или полиномиального тренда. Менеджер просмотрел все виды трендов: ни один из них не учитывал в полной мере характер поведения данных. Один из них при прогнозе дает слишком пессимистическую оценку, другой - излишне оптимистическую. Полином третьей степени неплохо описывает поведение данных, но только на интервале наблюдения. Использовать его для целей прогноза, очевидно, невозможно. Увы, такая ситуация типична. Модель, особенно полиномиальная, может хорошо описывать наблюдаемые значения, но не годиться для прогноза.

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

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

    Программное построение сводных таблиц

    Создать серьезную систему анализа деятельности офиса без программирования невозможно. Наряду с построением сводной таблицы вручную программист должен уметь написать и соответствующую программу. Допустим, Вы разрабатываете в Вашем приложении подсистему "Помощник Аналитика". Конечно, здесь не обойтись без сводных таблиц, и строиться они должны щелчком одной кнопки с дальнейшим диалогом, настроенным на конкретную проблему.
    Рассмотрим объектную модель, используемую при программной работе со сводными таблицами, - основные классы, их свойства и методы. Для краткости я буду называть соответствующие объекты "Pivot-объектами". Нужно заметить, что модель Pivot-объектов достаточно сложная, она включает несколько классов, многие из которых имеют достаточно большой набор свойств и методов. Замечу, что эта объектная модель в Office 2000 претерпела существенные изменения в сравнении с предыдущей версией. Существенное влияние на нее оказал новый подход к доступу данных, основанный на OLE DB и ADO. С этой точки зрения, знакомство с ADO поможет нам и при изучении модели Pivot-объектов.
    Замечу, документация по этим объектам, как мне кажется, оставляет желать лучшего. Она не полна, не всегда точна. Замечу еще, что и использование такого средства, как Macrorecorder, не дает правильного представления о том, как использовать новые возможности Pivot-объектов, поскольку Macrorecorder работает, основываясь на старой модели.
    Два основных класса объектов - PivotCache и PivotTable и две коллекции этих объектов - PivotCaches и PivotTables играют центральную роль в программном создании сводных таблиц. Кроме них существует еще несколько классов, так или иначе связанных со сводными таблицами:
  • PivotField и коллекция PivotFields - определяют поля сводной таблицы,
  • PivotItem и коллекция PivotItems - определяют данные, хранимые в полях сводной таблицы,
  • PivotLayout и коллекция PivotLayouts - определяют расположение полей сводной таблицы,

  • Поговорим об основных объектах. Прежде всего, разберемся с тем, почему понадобились два объекта (два класса), чтобы описать одну сущность - сводную таблицу. Объект PivotCache задает кэш-память, сводную таблицу, хранимую в оперативной памяти, ее данные. Работа с этим объектом, помимо всего прочего, позволяет оптимизировать память, отводимую сводной таблице. Объект PivotTable задает представление сводной таблицы на рабочем листе, задает отчет. До сих пор я не использовал термин "отчет" сводной таблицы, хотя он широко используется, в том числе и в документации. Теперь пришла пора и для этого термина. Действительно, объект PivotTable вполне соответствует понятию отчета, задаваемого сводной таблицей. Таким образом, два объекта PivotCache и PivotTable задают внутренне и внешнее представление сводной таблицы. В основе объекта PivotTable лежит объект PivotCache, являясь источником данных для объекта PivotTable.
    Правильная технология работы при программном создании сводной таблицы состоит в том, чтобы вначале создать объект PivotCache, а затем на его основе создать объект PivotTable. Заметьте, если объект PivotCache создан, но не создан объект PivotTable, ссылающийся на этот кэш памяти, то при закрытии документа память освобождается и объект PivotCache автоматически удаляется перед сохранением рабочей книги.
    Есть некоторая разница и в коллекциях этих объектов. Коллекция PivotCaches связана с самой книгой - объектом Workbook, в то время как коллекция PivotTables связывается с рабочим листом - объектом WorkSheet. А теперь перейдем к деталям и рассмотрим подробнее основные свойства и методы этих классов объектов.

    Программное построение таблиц подстановки

    Ну и несколько слов о том, как построить таблицу подстановки программно. Если подготовительная работа уже выполнена, - созданы заголовки таблицы и записана формула, вычисляющая функцию от двух параметров, то дальнейшее построение таблицы подстановки выполняется одним оператором. Достаточно выделить соответствующую прямоугольную область под таблицу и вызвать метод Table объекта Selection или Range. Метод имеет два параметра, задающих ячейки ввода.
    Вот процедура, которая на другом месте строит таблицу подстановки, аналогичную той, которая показана на предыдущем рисунке 8.30:
    Public Sub Buildtable() 'Программное построение таблицы подстановки Dim myr As Range Set myr = Range("A1:H7") myr.Clear 'Построение заголовков таблицы подстановки Range("E1") = "Уровень рекламы" Range("A5") = "Число конкурентов" Range("C2") = 0: Range("D2") = 1 Range("C2: D2").AutoFill Destination:=Range("C2:H2"), Type:=xlFillDefault Range("B3") = 0: Range("B4") = 1 Range("B3: B4").AutoFill Destination:=Range("B3:B7"), Type:=xlFillDefault Range("B2").Formula = "= 8.318*$B$13 + 16.66*$A$1-26.58*$A$2 +109.06" 'Формирование таблицы Set myr = Range("B2:H7") myr.Table RowInput:=Range("A1"), ColumnInput:=Range("A2")
    End Sub

    Сценарии

    Таблицы подстановки применимы, когда результат, точнее функция, его вычисляющая, зависит максимум от двух параметров. При анализе более сложных моделей, когда результат зависит от большего количества факторов, следует использовать другое средство - сценарии. Заметьте, даже в рассматриваемой нами достаточно простой ситуации уровень продаж зависит от трех параметров - времени, уровня рекламы и числа конкурентов.
    Итак, пусть результирующая функция F(a1,a2, …an) зависит от n параметров. Сценарием будем называть набор значений этих параметров. Добавить новый сценарий в коллекцию означает ввести новый набор значений параметров и связать с ним имя сценария. Ячейки, хранящие параметры, на которые ссылается функция F, называются изменяемыми. При выборе сценария в них будут посланы значения, заданные этим сценарием. После чего будет вычислено соответствующее значение результирующей функции. Результаты вычислений по всем сценариям можно объединить в одной сводной таблице.
    Такова основная идея сценариев. Их можно рассматривать, как некоторое обобщение таблиц подстановки. Как обычно, создать и анализировать сценарии можно вручную или программно. Мы рассмотрим оба способа. Но вначале обсудим задачу, требующую введения сценариев.

    Сезонный спрос

    Сезонные колебания действуют независимо от других факторов и накладываются на ту или иную модель спроса. Проще всего учесть сезонный фактор, используя коэффициенты сезонности. Так, если, как обычно, принять сезонный цикл за год, можно иметь 52 недельных или 12 месячных коэффициентов сезонного спроса. Коэффициент сезонности представляет собой отношение среднего спроса за текущий период ( месяц) к среднему значению за весь период цикла (год). Чтобы оценить значения коэффициентов сезонности, требуются данные за несколько лет. Достоверность результатов обычно можно повысить за счет того, что сезонные циклы одинаковы для разных товаров.
    Если сезонные коэффициенты рассчитаны, то учет сезонности не вызывает трудностей для любой из моделей тренда. Вначале необходимо текущие значения очистить от влияния сезонности делением на соответствующий коэффициент. Затем применить обычный алгоритм прогноза и полученную прогнозную оценку умножить на коэффициент сезонности, соответствующий моменту прогноза.

    Среднесрочный прогноз и методы регрессионного анализа

    Для среднесрочного прогноза обычно применяются методы регрессионного анализа. Хотя ничто не мешает применять их и для краткосрочного прогноза. Они основаны на получении оценок по методу наименьших квадратов. Эти методы и реализованы в стандартных функциях Excel, так что рассмотрим их подробнее. Начнем с наиболее простой модели линейного тренда. В основе модели лежит уже упоминавшееся соотношение:
    Yt = a + b* t + Et
    Это соотношение можно интерпретировать следующим образом. В каждый момент времени t измеренное значение спроса Yt является суммой неизвестной помехи Et и линейной функции времени с неизвестными (ненаблюдаемыми) параметрами a и b. Из-за помех решения, принимаемые на основе измерений, носят вероятностный характер. Найти точные значения параметров a и b в этих условиях невозможно, но, зная выборку Yt, можно вычислить оценки параметров. В статистике оценкой называют любую функцию от измерений. Оценки параметров a и b можно получить по методу наименьших квадратов из условия минимизации квадратичного функционала:
    F(a, b) =
    Среднесрочный прогноз и методы регрессионного анализа
    (Yt - (a +b*t))2
    При этом, когда мы имеем дело с линейной моделью, минимум этого функционала находится аналитически, и в случае двух параметров можно явно выписать конечные соотношения для оценок параметров a и b. В этом одно из преимуществ метода наименьших квадратов. Прямая Yt = в + ^b* t , где a и ^b - оценки параметров, называется линией регрессии и используется для прогнозирования значений Y в произвольные моменты времени t. Конечно, чем дальше отстоит значение t от интервала наблюдений, тем вероятнее, что ошибка прогноза будет увеличиваться.
    Метод наименьших квадратов хорош и с точки зрения статистики. Если предположить, что неизвестные нам помехи распределены по нормальному закону с нулевым математическим ожиданием и, в общем случае, с заданной корреляционной матрицей, то полученные оценки обладают важными свойствами несмещенности, состоятельности и эффективности. Мы не будем давать строгого определения всех этих терминов. Скажем лишь, что в классе несмещенных оценок наши оценки обладают минимальной дисперсией, т. е. минимальным разбросом относительно истинного значения параметров. Чем больше измерений, тем точнее оценки, так как уменьшается интервал, накрывающий истинное значение параметра с заданной вероятностью. Как ни странно, но практика показала, что предположения о характере помех зачастую оправдываются. В теории вероятностей этому факту есть хорошее объяснение. Недаром открытый Гауссом закон распределения называется "нормальным". Все в нашей жизни распределено по гауссиане.
    Обобщим теперь постановку задачи на произвольное количество параметров, полагая теперь, что спрос может быть описан уже не линейной, а полиномиальной функцией времени, например:
    Yt = a0 + a1 t + a2t2 + … + amtm + Et

    Это полиномиальное относительно времени соотношение остается линейным по отношению к неизвестным параметрам. Для простоты перейдем к матричной форме записи соотношений:

    Y = X*a + E

    Здесь Y - вектор измерений, a - вектор параметров, E - вектор ошибок, X - прямоугольная матрица, элементы которой зависят от t и не зависят от параметров a. Для полиномиальной зависимости нетрудно выписать явный вид ее элементов:

    X = || ti j.|| i= 1…n; j = 0..m;

    Число строк этой матрицы определяется моментами времени t1, t2, … tn, в которые производились измерения, а количество столбцов определяется степенью полинома. Квадратичный функционал F(a) в матричной форме имеет вид:

    F(a) = (Y - X*a)T R-1 (Y - X*a)

    Продолжая обобщать постановку задачи, мы ввели корреляционную матрицу R ошибок измерений. В частном случае, когда отсутствует корреляция ошибок измерений и дисперсия их единична, матрица R превращается в единичную матрицу. Другой важный частный случай - диагональный, когда корреляция отсутствует, но дисперсия ошибки меняется от измерения к измерению. Величину, обратную к дисперсии -1/?2 , можно рассматривать как вес измерения. Так что введение этой матрицы позволяет приписать разный вес измерениям, придавая, например, больший вес последним измерениям.

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

    a = I-1 XT R-1 Y

    Здесь I - информационная матрица Фишера, вычисляемая из соотношения:

    I = XT R-1 X

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

    Ra = I-1

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

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

    Yi = a0 + a1 x1i + a2x2i+ … + amxmi + Ei


    В матричной форме записи все соотношения остаются справедливыми, изменяются лишь соотношения для расчета элементов матрицы X. В заключение отметим, что при долговременном прогнозировании предположение о линейности тренда вряд ли справедливо, кривая спроса имеет более сложную форму и не описывается линейной функцией относительно параметров. В этом случае определить аналитически точку минимума квадратичного функционала F(a), обычно уже невозможно. Правда, есть одно важное исключение. Пусть:

    Yi = F(a0 + a1 x1i + a2x2i+ … + amxmI)+ Ei

    и функция G является функцией, обратной к F. Тогда линейная модель по-прежнему имеет место, но уже для преобразованных значений измерений:

    G(YI )= (a0 + a1 x1i + a2x2i+ … + amxmI)+ Ei

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

    Но вовсе не обязательно приводить нашу модель к линейной относительно параметров. Она спокойно может оставаться нелинейной, так как существуют хорошо разработанные численные методы. Более того, можно использовать для минимизации функционала средство самого Excel - Решатель. Думается, найти минимум функционала не столь сложно - сложнее построить адекватную реальной ситуации модель спроса. Здесь надо выяснить, какие параметры, влияющие на спрос, поддаются прямому наблюдению, а какие требуется оценить по результатам наблюдений. Не менее сложно подобрать аналитическое описание кривой спроса с точностью до неизвестных параметров. Таким образом, экономисту, математику и программисту есть где поработать, создавая эффективную систему прогноза. Средства Office 2000, прежде всего Excel, облегчают решение этой задачи, а нередко позволяют получить ее решение на основе встроенных стандартных функций.

    Структура сводной таблицы

    Сводную таблицу можно рассматривать как таблицу с тремя измерениями, в каждой точке которой заданы данные. Четыре оси сводной таблицы носят названия:
  • Оси Строк;
  • Оси Столбцов;
  • Оси Страниц, называемой также осью Фильтров;
  • Оси Данных.

  • Если бы на каждой оси располагались значения одного типа, то эта модель была бы совершенно простой и понятной. Можно было бы рассматривать сводную таблицу, как функцию трех переменных - F(x,y,z), заданную таблицей. Вся сложность сводной таблицы состоит в том, что на каждой оси может располагаться несколько полей, это же верно и относительно данных, - полей данных может быть также несколько. Поэтому, каждая из координат сводной таблицы, также как и значение функции F, представляет собой агрегат довольно сложной структуры. Поля, располагаемые на той или иной оси, получают тип этой оси - поля строк, поля столбцов, поля фильтра, поля данных.
    Важным свойством сводной таблицы является то, что ее структуру можно легко менять в процессе работы с этой таблицей. Поля, располагаемые на оси, жестко не закрепляются, и, при желании, можно в ходе работы изменять структуру таблицы, меняя местами, например, поля строк и столбцов.
    Заметьте, что при таком определении сводной таблицы, ее трехмерность носит довольно условный характер, реально измерений значительно больше, и сводная таблица представляет собой гиперкуб - многомерный куб данных.
    Чтобы пояснить ситуацию со структурой сводной таблицы, приведу простой пример, когда на каждой оси располагается ровно одно поле. Рассмотрим организацию, занимающуюся продажами. При построении сводной таблицы на оси страниц (фильтров) расположим поле "Отделы", на оси строк - поле "Сотрудники", на оси столбцов - поле "Месяцы". Единственное поле данных "Продажи" будет задавать объем продаж. Тогда, если выбрать соответствующий отдел, или, другими словами, включить фильтр по отделам, строки таблицы будут задавать имена сотрудников выбранного отдела, столбцы будут задавать месяцы, а значения на пересечении строки и столбца будут определять объем продаж, совершенных данным сотрудником данного отдела в данном месяце. Для этой таблицы достаточно естественно ввести группирование данных, как по строкам, так и по столбцам. Например, сотрудников отделов можно сгруппировать по лабораториям, а месяцы - по кварталам. Тогда легко получать представление данных с разной степенью детал

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

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

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

    Чаще всего в роли источника данных для построения сводной таблицы выступают базы данных. Это могут быть табличные (реляционные) базы данных, например, Access или Microsoft SQL Server, В последнее время в качестве источников данных стали широко применяться кубы OLAP. Между кубами OLAP и сводными таблицами много общего. Эти объекты, в какой-то мере, близнецы - братья. Кубы OLAP служат для хранения многомерных данных, а сводные таблицы для проведения анализа этих данных.

    Сводные диаграммы

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

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

    Свойства и методы объекта PivotCache

    У объекта PivotCache 23 свойства. Большинство из них я рассмотрю:
  • Connection - позволяет задать соединение с источником данных. Возвращает или устанавливает строку, имеющую разный синтаксис в зависимости от типа источника данных. Строка может задавать:
  • OLE DB установки для связи Excel с OLE DB источниками данных,
  • ODBC установки для связи Excel с ODBC источниками данных,
  • URL, когда Excel связывается с данными Web-страниц,
  • Полный путь, задающий текстовый файл или файл, задающий Web-запрос или базу данных.

  • Строка начинается специальным ключевым словом, указывающим тип источника данных. В зависимости от варианта префикс, начинающий строку соединения, имеет вид - OLEDB; ODBC; URL; TEXT. Префикс заканчивается символом ";" (точка с запятой). В остальном, строка удовлетворяет требованиям, предъявляемым к строке соединения при работе с ADO. Вот пример задания свойства Connection для соединения с базой данных Access с использованием провайдера Microsoft Jet:
    ActiveWorkbook.PivotCaches.Add(SourceType:=xlExternal).Connection = _ "OLEDB; Provider=Microsoft.jet.oledb.4.0;" & _ "Data Source=c:\!O2000\DSCD\Ch18\dbPP2000.mdb"
    Установка значения для свойства Connection не означает непосредственного соединения с источником данных. Необходимо вызывать метод Refresh, чтобы такая связь была в действительности установлена.
  • LocalConnection, UseLocalConnection - эти два свойства используются при работе с сохраненными в отдельном файле OLAP кубами. Когда в качестве источника данных используется OLAP куб, то вместо задания свойства Connection следует использовать свойство LocalConnection, предварительно установив значение True для свойства UseLocalConnection.
  • CommandType, CommandText - два хорошо знакомых по ADO свойства. Первое из них определяет тип команды, а второе значение команды, выполняющей запрос к источнику данных. Первое свойство может иметь четыре значения, заданное константами: xlCmdCube, xlCmdDefault, xlCmdSQL, xlCmdTable. В зависимости от установленного значения свойство CommandText задает:
  • Имя куба для OLAP кубов,
  • Текст команды, учитывающий специфику и требования провайдера,
  • Текст SQL-запроса,
  • Имя таблицы.
  • MemoryUsed As Long - свойство имеет статус "только для чтения", возвращает количество байтов памяти занятой в текущий момент под кэш. Если объект PivotTable не присоединен к объекту PivotCache, то возвращается значение 0.
  • OptimizeCache - булево свойство, при установке значения True, кэш будет оптимизироваться при его конструировании. Для OLE DB источников данных свойство имеет статус "только для чтения" и имеет значение по умолчанию - False.
  • QueryType - свойство имеет статус "только для чтения", возвращает константу типа xlQueryType, которая определяет тип запроса, используемого Excel для заполнения кэша.
  • Recordset - очень важное и полезное свойство при программной работе со сводными таблицами. Оно позволяет вернуть или установить хорошо знакомый объект Recordset, задающий набор записей, используемый при построении кэша. Тем самым появляется возможность программного создания и наполнения данными объекта PivotCache. Связь с источником данных можно организовать средствами ADO и получить объект Recordset. После чего остается только установить свойство объекта PivotCache. Зачастую, это более эффективный способ работы с источником данных. Пример такого способа работы будет приведен.
  • RecordCount - как обычно, задает число записей в наборе Recordset.
  • RefreshDate, RefreshName, RefreshOnFileOpen, RefreshPeriod - свойства, задающие различную информацию, связанную с обновлением данных.


  • Рассмотрим теперь методы объекта PivotCache. Их немного - всего три:
  • Function CreatePivotTable(TableDestination, [TableName], [ReadData]) As PivotTable. Этот метод (функция) создает объект PivotTable, основанный на данном кэше - объекте PivotCache. Это основной способ создания и появления объектов PivotTable.
  • Аргумент TableDestination представляет объект Range, задающий область построения сводной таблицы. Аргумент задает ячейку в левом верхнем углу этой области. Напомню, что объект PivotTable связан с определенным листом рабочей книги, поэтому аргумент должен определять и нужный рабочий лист, в противном случае будет выбран активный лист рабочей книги.
  • Аргумент TableName задает имя сводной таблицы - имя объекта PivotTable, которым можно пользоваться при работе с коллекцией PivotTables.
  • Булев аргумент ReadData позволяет установить способ чтения записей в кэш. Он имеет значение True, если в кэш читаются все записи.
  • Sub Refresh(). Обновляет кэш текущим состоянием источника данных.
  • Sub ResetTimer(). Восстанавливает значение таймера. Это может быть важно, когда используется свойство RefreshPeriod, задающее период времени между последующими обновлениями источника данных.

  • На этом я закончу рассмотрение свойств и методов объекта PivotCache. Примеры создания этого объекта приведу чуть позже, после рассмотрения объекта PivotTable, поскольку создавать эти объекты, тесно связанные между собой, следует в одной процедуре.

    Свойства и методы объекта PivotTable

    Объект PivotTable, задающий отчет сводной таблицы - его внешнее представление устроен, естественно, более сложно. У него значительно больше свойств, чем у объекта PivotCache, - их 54, да и методов в четыре раза больше. Замечу, что для программного создания сводной таблицы достаточно использовать лишь малую часть из этого набора. Большая часть этих свойств и методов необходима, если Вы хотите программно поддерживать работу пользователя со сводной таблицей.
    Давайте рассмотрим основные свойства этого объекта:
  • ColumnFields([Index]) As Object, DataFields([Index]) As Object, PageFields([Index]) As Object, RowFields([Index]) As Object. Все эти свойства имеют статус "только для чтения" и возвращают коллекцию или отдельный элемент коллекции, если указан индекс. Возвращаемые объекты задают поля сводной таблицы по соответствующему измерению - поля столбцов, данных, страниц или строк. Вне зависимости от измерения все возвращаемые объекты принадлежат единому классу PivotField или PivotFields для коллекций.
  • ColumnRange, DataLabelRange, DataBodyRange, PageRange, RowRange - возвращают объект Range, задающий соответствующую область. Вот простенькая процедура, поочередно выделяющая указанные области сводной таблицы:
    Public Sub SelectRange() ThisWorkbook.Worksheets("Лист1").Activate Range("A3").Select ActiveCell.PivotTable.ColumnRange.Select ActiveCell.PivotTable.DataLabelRange.Select ActiveCell.PivotTable.DataBodyRange.Select ActiveCell.PivotTable.PageRange.Select ActiveCell.PivotTable.RowRange.Select End Sub
  • ColumnGrand, RowGrand - булевы свойства, имеющие значение True, если сводная таблица подводит итоги по столбцам и строкам.
  • CubeFields - для сводных таблиц, основанных на OLAP кубе, возвращает одноименную коллекцию, задающую поля куба. Каждый объект этой коллекции содержит свойства поля.
  • HiddenFields([Index]) As Object, VisibleFields([Index]) As Object - коллекции спрятанных и видимых полей. Для сводных таблиц, основанных на OLAP кубах спрятанных полей нет - все поля являются видимыми.
  • ErrorString As String, DisplayErrorString As Boolean. Первое из свойств позволяет задать строку, представляющую сообщение об ошибке, второе - позволяет включить или отключить появление этой строки в вычисляемых полях, где возникает ошибка.
  • PivotFormulas As PivotFormulas - возвращает одноименную коллекцию объектов. Каждый элемент этой коллекции является объектом класса PivotFormula и представляет формулу, используемую в вычисляемых полях.


  • На этом я закончу рассмотрение свойств и перейду к рассмотрению методов:
  • Function AddFields([RowFields], [ColumnFields], [PageFields], [AddToTable]). Позволяет добавить поля к соответствующему измерению. Последний булев параметр позволяет указать, будут ли поля добавляться или заменять существующий набор полей. В предыдущей версии
  • Function CalculatedFields() As CalculatedFields. Возвращает одноименную коллекцию вычисляемых полей.
  • Sub Format(Format As xlPivotFormatType). Производит форматирование сводной таблицы. Аргумент Format задает один из возможных типов форматирования.
  • Function GetData(Name As String) As Double. Позволяет получить данные из отдельной ячейки сводной таблицы. Аргумент Name задает поля таблицы, однозначно определяющие ячейку. Он имеет достаточно сложный синтаксис, на деталях которого останавливаться не буду.
  • Function PivotCache() As PivotCache - возвращает объект PivotCache, связанный с отчетом.
  • Function PivotFields([Index]) As Object - возвращает одноименную коллекцию, а при указании индекса элемент этой коллекции, задающий поле сводной таблицы. В качестве индекса можно использовать имя поля. Возвращаемые объекты принадлежат классу PivotField. Позже в примере я продемонстрирую работу с этими объектами при программном формировании структуры сводной таблицы.
  • Sub PivotTableWizard([SourceType], [SourceData], [TableDestination], [TableName], [RowGrand], [ColumnGrand], [SaveData], [HasAutoFormat], [AutoPage], [Reserved], [BackgroundQuery], [OptimizeCache], [PageFieldOrder], [PageFieldWrapCount], [ReadData], [Connection]). Этим методом, но не в виде процедуры, а в виде функции обладает и объект Worksheet. Вызванный этим объектом метод позволяет создать объект PivotTable. В предыдущих версиях Office этот способ был основным для создания подобных объектов. Теперь надобность в нем практически отпала. Метод моделирует работу Мастера сводных таблиц и имеет многочисленные аргументы, позволяющие определить сводную таблицу. Поскольку, как я сказал, теперь не следует пользоваться этим методом, то я не буду останавливаться на деталях его описания.
  • Function RefreshTable() As Boolean - обновляет данные сводной таблицы и возвращает значение True, если обновление прошло удачно.
  • Function ShowPages([PageField]) - создает новый отчет для каждого элемента в поле страниц. Каждый отчет создается на отдельной странице.


  • Таблицы подстановок, Сценарии и Поиск решения

    Таблица подстановок - одно из средств анализа данных. Вот первая задача, которая приводит к построению таблицы подстановок. Рассмотрим набор функций, зависящих от одного и того же параметра: F1(a), F2(a), …Fm(a). Пусть каждая из этих функций задается формулой Excel. Пусть также требуется проанализировать зависимость этих функций от значений параметра a. Обычно нас интересуют результаты для конечного набора значений параметра - a1, a2, …an. В этом случае все, что нужно для анализа, - это построить прямоугольную таблицу размерности n*m, элементами которой будут значения Fj(ai). Excel позволяет без особого труда построить такую таблицу. Таблицы подстановок упрощают решение этой задачи.
    Чтобы вручную построить такую таблицу, надо записать в столбец значения ai, в строку, расположенную на одну ячейку выше и правее, записать формулы Fj(Ain). Все формулы должны ссылаться на одну и ту же ячейку Ain - ячейку ввода. Можно, конечно, значения параметра записать в строку, а формулы в столбец. Основное требование к расположению формул и значений параметра состоит в том, чтобы они определяли прямоугольную область таблицы и воспринимались как заголовки ее строк и столбцов. Проделав эту подготовительную работу, достаточно выделить прямоугольную область, занятую таблицей, включая заголовки, и выбрать в меню "Данные" пункт "Таблица подстановок". В появившемся окне нужно задать ссылку на ячейку ввода. Заметьте, если значения параметра располагаются в столбец, то ссылку на ячейку ввода нужно задавать в окне строк, а не в окне столбцов. По щелчку кнопки OK таблица значений Fj(ai) будет автоматически построена.
    Другая задача, приводящая к таблице подстановок, состоит в том, что рассматривается только одна функция F(a,b), но теперь зависящая от двух параметров. Элементами таблицы являются значения этой функции F(ai, bj). В роли заголовков строк и столбцов выступают значения ai и bj. Для записи формулы осталось одно свободное место - ячейка в левом верхнем углу таблицы. Формула, записанная в нее, ссылается теперь на две ячейки ввода - ячейку ввода строки и ячейку ввода столбца. Этим нюансом в расположении заголовков отличаются подготовительные действия по созданию таблицы подстановок в первом и во втором случае. Остальные действия аналогичны. И здесь нужно быть аккуратным в выборе окон при задании ссылок. Мне, например, всегда хочется задавать ссылки в другом порядке.
    Конечно, таблицы подстановки - не столь уж мощное средство анализа данных. Собственно говоря, никакого анализа данных они не выполняют. Это лишь часто используемое средство, облегчающее построение таблицы данных для ее визуального анализа. Таблицу анализирует сам пользователь, исходя из содержательных соображений. Обычно, только он может понять, какие значения параметров a и b наиболее подходят для его целей. Хотя, конечно, может существовать и формальный алгоритм выбора из таблицы наилучшего значения.
    А теперь покажем, как менеджер офиса использует таблицу подстановки в своем анализе. Ранее он построил уравнение регрессии, в котором продажи зависят от уровня рекламы и количества конкурирующих книг. Менеджер уже использовал его для прогноза состояния продажи книги и даже построил соответствующие графики, демонстрирующие прогнозируемую зависимость продаж от уровня рекламы. Однако графики были построены для фиксированного числа конкурирующих книг. Менеджер не определил еще окончательно, каков будет уровень рекламы в точке прогноза, и тем более он не знает точного значения количества конкурентов. Поэтому он решает построить таблицу подстановки, чтобы оценить все реально возможные варианты. Вот что у него получилось:
    Таблицы подстановок, Сценарии и Поиск решения

    увеличить изображение
    Рис. 8.30.  Таблица подстановки, используемая в анализе "Что, если ...?"
    Эта таблица позволяет ему получить ответы на вопросы: каков прогноз на продажу книг на следующий месяц и что будет, если появится новый конкурент, и что будет, если повысить уровень рекламы?

    Встроенные функции Excel и прогнозирование

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

    Задача менеджера

    Наш менеджер должен принять важные решения в связи с выходом новой книги. Он должен определить тираж книги, установить уровень рекламы и назначить цену, точнее коэффициент надбавки по отношению к себестоимости книги. А целью менеджера, является, естественно, получение максимального дохода от выпуска книги.
    Дадим имена параметрам, которыми может управлять менеджер: Тир, Рек и Цен. Менеджер хотел бы подобрать их значения так, чтобы оптимизировать доход от выпуска книги. Для начала он решил ограничиться тремя возможными вариантами (сценариями). Приведем их названия и соответствующие значения параметров:
  • Сценарий(1). МинимальныйТираж - (Тир = 5000, Рек = 0, Цен = 1,5)
  • Сценарий(2). НормальныйТираж - (Тир = 10000, Рек = 2, Цен = 2)
  • Сценарий(3). МаксимальныйТираж - (Тир = 30000, Рек = 5, Цен = 2,5)

  • Рассмотрим теперь, как доход связан с параметрами, управляемыми менеджером. Конечно, можно было бы написать совсем простую функцию. Чтобы научиться работать со сценариями, вид функции не важен. Но мы усложним задачу и напишем нечто правдоподобное. Доход зависит от продаж, а чтобы их прогнозировать, желательно иметь соответствующую модель. Наш менеджер уже построил модель продаж для среднесрочных прогнозов. Но сейчас ему нужна общая модель, подходящая для полного (годового) цикла продаж. Обобщая данные по продажам выпущенных книг, менеджер построил такую модель и получил соотношение, позволяющее рассчитать ожидаемое количество проданных книг в каждом месяце в течение года с момента выхода книги. Вот общий вид этого соотношения:
    Продажи книг =a0+a1*t+a2*t^2+a3*Рек+a4*Кон*Цен
    Зависимость продаж книги во времени можно описать квадратичным полиномом с отрицательным коэффициентом a2 при t^2. Это соответствует тому, что вначале спрос на книгу растет, достигает пика на рассматриваемом временном интервале и идет на убыль. Но на спрос влияют и другие факторы. Так, элемент a3*Рек отражает увеличение спроса, вызванное улучшением рекламы. Высокие надбавки на цену по отношению к себестоимости снижают спрос при наличии конкурирующих книг: коэффициент a4 всегда отрицательный. Оценки параметров a0, a1, a2, a3 и a4 менеджер получил по результатам измерений, используя функцию ЛИНЕЙН.
    Второе ключевое соотношение связывает доход с количеством проданных книг с учетом произведенных затрат на их выпуск:
    Доход = N * Цен * Себ - b1*Рек - b2*Тир*Себ -b3*T

    Здесь N - это проданное количество книг, T - время продажи (в месяцах), Себ - себестоимость книги. Доход, согласно этому соотношению, зависит от количества проданных книг и той надбавки (Цен), которую менеджер решил установить на цену. Расходы определяются затратами на выпуск всего тиража, затратами на рекламу и затратами на продажу в течение периода T. Чтобы модель получила законченный вид, скажем, как считаются N и T.
    N = min(Продажи книг(tI), Тир)
    Это соотношение отражает тот очевидный факт, что при хорошем спросе весь тираж может быть распродан быстрее, чем за год, и тогда N совпадает с Тир. В случае неудачи за год будет продано N книг, возможно существенно меньше, чем полный тираж. Вместе с N считается и T - количество месяцев, за которое удалось распродать весь тираж. Заметьте, я не привожу соотношение для расчета T. Хотя алгоритм его расчета очевиден, его не удается описать одной формулой, и мне пришлось реализовать его программно отдельной процедурой с именем "ПериодПродаж", текст которой приведу чуть позже.

    Мир объектов Excel 2000

    Формальная постановка задачи

    Введем обозначения:
  • n - число этапов, на которых принимаются решения о вложении денег в тот или иной проект. В нашем примере такие решения принимаются каждый месяц и потому n= 6.
  • m - число проектов. В примере m =4.
  • Pi - проекты, где i = 1 …m.
  • Ri - величина риска проекта Pi.
  • qi - число этапов проекта Pi.
  • Si j - сумма денег, вкладываемая в проект Pi на j -м этапе. Si j - это и есть наши искомые переменные, значения которых предстоит найти в ходе решения задачи. Заметьте, что здесь i = 1 ... m, j = 1 ... qi
  • N - общее число искомых переменных, которое определяется формулой N =
    Формальная постановка задачи
    qi
  • K - первоначальный капитал фонда.
  • Rc- допустимый средний риск.
  • Tc -допустимая средняя длительность проекта.

  • Перейдем теперь к формулировке оптимизационной задачи:
    Необходимо минимизировать первоначальный капитал фонда
    K => min
    при выполнении четырех групп ограничений:
    Ограничения баланса: Bi = 0 i = 1…n
    Ограничения среднего риска: Ri <= Rc i = 1…n
    Ограничения средней длительности проекта: Ti <= Tc i = 1…n
    Ограничения на положительность значений: Si j >= 0 i = 1 ... n, j = 1 ... qi
    Первую группу ограничений составляют ежемесячные ограничения баланса. Все имеющиеся к началу месяца сбережения следует вложить в те или иные проекты. Деньги " в чулке" хранить нельзя. Поэтому сумма денег, полученных в конце месяца с учетом дивидендов и выплаты долговых обязательств, равна сумме денег, вкладываемых в инвестиционные проекты на следующем месяце. Конечно, чтобы такая возможность всегда существовала, необходимо, чтобы среди проектов был "безопасный" проект с наименьшим риском и минимальным сроком вложения. Параметры такого проекта должны быть заведомо меньше задаваемых значений среднего риска и средней продолжительности. Существование такого проекта гарантирует существование решения задачи для любых исходных данных. В нашем примере это первый проект. Понятно, что таким проектом может считаться вложение денег в Сбербанк.
    Отметим еще краевые балансовые ограничения. В начале первого месяца сумма вложений в инвестиционные проекты равна начальному капиталу фонда. В конце последнего месяца сумма полученных денег равна сумме, которую следует выплатить для погашения долга. Эти ограничения вытекают из предназначения временного целевого фонда.
    Я не стану выписать балансовые ограничения в явном виде, поскольку, с одной стороны, они достаточно понятны, с другой стороны их формальная запись для общего случая довольно громоздка из-за динамического характера решаемой задачи. Ограничусь тем, что приведу краевые уравнения баланса и уравнение для одного из месяцев. Вот как выглядит уравнение баланса на начальном этапе:
    K = S1 1 + S2 1 + S3 1 + S4 1

    Это уравнение задает целевую функцию - начальный капитал K, значение которого предстоит определить. Уравнение баланса говорит о том, что весь начальный капитал должен быть вложен в инвестиционные проекты.

    Баланс по завершении работы фонда имеет вид:

    1.015* S1 6 + 1.035* S2 3 + 1.06* S3 2 + 1.11* S4 1 = Capital

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

    Баланс после истечения двух месяцев с учетом выплаты аванса имеет вид:

    1.015* S1 2 + 1.035* S2 1 - D1 = S1 3 + S2 2

    Вторую группу составляют ежемесячные ограничения среднего риска. Если обозначить через Li k сумму инвестиций i -го проекта в k - м месяце, то эти ограничения в общем случае имеют вид:

    m                 m

    Формальная постановка задачи
    Li,k * Ri 
    Формальная постановка задачи
     Rc *
    Формальная постановка задачи
    Li,k

    i                 i

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

    R1* S1 4 + R2* S2 2 + R3* S3 2 + R4* S4 1 <= Rc*(S1 4 + S2 2 + S3 2 + S4 1)

    Третью группу составляют ежемесячные ограничения средней длительности проектов. Общая формула имеет вид:

    m                   m

    Формальная постановка задачи
    Li,k * Ti,k 
    Формальная постановка задачи
     Tc *
    Формальная постановка задачи
    Li,k

    i                   i

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

    T1 4* S1 4 + T2 4* S2 2 + T3 4* S3 2 + T4 4* S4 1 <= Tc*(S1 4 + S2 2 + S3 2 + S4 1)

    В нашем примере: T1 4 = 1; T2 4 =1; T3 4 = 3; T4 4 = 3.

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

    Оптимизация инвестиций

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

    Планирование инвестиций с учетом риска

    Приведу содержательную постановку задачи, которая была предложена моим коллегой - доцентом экономического факультета - Шукурьяном С. И.
    АОЗТ "Риск" заключило контракт на покупку оборудования на общую сумму 750000$. В соответствии с условиями контракта аванс в размере 150000$ необходимо заплатить через 2 месяца, а оставшуюся сумму через 6 месяцев после заключения контракта. Для обеспечения выплат руководитель "Риска" создал временный целевой фонд и назначил его руководителя. Начальный капитал фонда составляет сумму, меньшую, чем та, которую предстояло заплатить в конце контракта. Остальные деньги руководитель фонда должен был обеспечить за счет грамотной инвестиционной политики и получения соответствующих дивидендов. Кроме того, руководитель "Риска" поставил жесткие ограничения на среднюю величину допустимого ежемесячного риска и среднюю ежемесячную длительность вложения денег. Задать величину требуемого начального капитала должен был руководитель фонда, но, естественно, руководство хотело, как можно меньше денег вложить в начальный капитал фонда.
    Руководитель фонда после предварительных консультаций с экспертами отобрал 4 возможных проекта, которые разумно было инвестировать. И хотя число инвестиционных проектов сравнительно невелико, подобрать наилучшее или даже просто приемлемое решение "вручную" оказалось совсем не просто. И тогда руководитель фонда обратился к программистам с целью помочь ему в решении задачи. Так появился помощник MasterF.
    Программист начал с формализации постановки задачи, стараясь сделать ее на начальном этапе как можно более простой. Начнем с инвестиционных проектов. Каждый из них имеет следующие характеристики:
  • Дату начала и длительность.
  • Число этапов, на каждом из которых производится выплата денег. Для этапа указана его длительность и сумма, выплачиваемая в конце этапа.
  • Минимальная и максимальная сумма, которую можно вложить в проект.
  • Степень риска проекта, заданная независимыми экспертами.

  • Данные о четырех отобранных инвестиционных проектах представлены в таблице:

    Таблица 9.1. Описание инвестиционных проектовНазвания проектовМесяцыРиски123456
    A1.0151.0151.0151.0151.0151.0151
    B1.0351.0351.0354
    C1.061.069
    D1.117

    Как следует из этой таблицы, проект B, например, состоит из трех этапов, длительность каждого - 2 месяца. В конце этапа прибыль составляет 3,5% от суммы, вложенной на начало этапа. Аналогичный смысл имеют данные и для других проектов. Никаких ограничений на суммы, вкладываемые в проекты, в данном случае не накладывается. Последний столбец таблицы задает риски каждого проекта.

    Проект MasterF. Реализация

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

    Работа с проектами

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

    Справка

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

    Страница "Ограничения"

    В начальный момент эта страница почти пуста. Вот как она выглядит:
    Страница

    увеличить изображение
    Рис. 9.5.  Страница формирования оптимизационной задачи в начальный момент работы
    Как видите на странице две надписи и, соответственно, две командные кнопки - "Нажми меня" и "Решатель". По нажатию первой из этих кнопок автоматически создаются переменные задачи, формируется целевая функция и ограничения задачи. Все эти данные размещаются на странице в полном соответствии с требованиями решателя Solver, и в таком виде, чтобы пользователь сумел понять и оценить правильность работы своего помощника. Конечно же, это наиболее серьезная часть той работы, которую выполняет MasterF. Чтобы дать некоторое представление о том, как решаются задачи, возникающие на этом этапе, я приведу лишь одну процедуру, задающую первое балансное ограничение:
    Public Sub FormFirstBalance() 'Формирование краевого балансного ограничения первого этапа Dim Myr As Range, i As Byte Dim Bound As String, NameVar As String Set Myr = Worksheets("Ограничения").Range("Bounds").Offset(1, 0) 'Формирование формулы, задающей краевое ограничение Bound = "=" For i = 1 To ProjectsNumber If ProjectStages(i) > 0 Then NameVar = "Sum_" & i & "_1" Bound = Bound & NameVar & "+"
    End If Next i Bound = Left(Bound, Len(Bound) - 1) 'Формирование трех ячеек рабочего листа, содержащих 'имя ограничения, левую и правую часть. Myr.Offset(0, 1).Value = Bound Myr.Offset(0, 1).Name = "Bal0" If MaxMin = 1 Then 'Краевое условие задает ограничение Myr.Value = "Balance0" Myr.Offset(0, 2).Value = Capital Myr.Offset(0, 2).Name = "Bar0" Else 'Краевое условие задает целевую функцию Myr.Value = "Goal" 'Переменная Goal - содержит имя ячейки, задающей цель или ограничение. 'Используется при вызове Решателя Goal = "Bal0" End If End Sub
    Вот несколько моментов, на которые следует обратить внимание:
  • Имена переменных я строю по определенным правилам, - в них используется имя проекта и имя этапа. Это позволяет мне динамически строить формулу, задающую краевое ограничение.
  • Для размещения ограничения на рабочем листе, я использую три ячейки. В первой из них помещаю имя ограничения. Заметьте, все ограничения именованы, что позволяет пользователю проанализировать работу, которую выполнил MasterF. Во второй и третьей ячейках размещаются левая и правая часть ограничения.
  • В зависимости от постановки задачи формируемое уравнение может задавать цель или быть первым балансным ограничением.


  • Всем, кого решение оптимизационных задач на Excel интересует в большей степени, рекомендую обратиться к полному коду проекта. А сейчас давайте взглянем на результаты работы, которую выполнил MasterF по формированию переменных и полной системы ограничений:

    Страница

    увеличить изображение
    Рис. 9.6.  Автоматическое формирование переменных и ограничений задачи

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

    Страница "ПостановкаЗадачи"

    Вот внешний вид этой страницы в тот момент, когда пользователь начинает с ней работать:
    Страница

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

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

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

    Страница "Решение"

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

    увеличить изображение
    Рис. 9.7.  Отчет о решении
    Как следует из отчета, чтобы уплатить на втором месяце 150000, и получить по окончании шести месяцев сумму в 600000, необходимо иметь начальный капитал в размере 684628$. В первый месяц этот капитал следует поровну распределить между двумя проектами - A и C. На третьем и пятом месяце деньги следует вкладывать в проект B. Проект D в решении не используется - у него слишком большая длительность, так что ограничения на среднюю длительность не позволяют использовать этот проект.
    Я надеюсь, что при желании Вы сумеете самостоятельно реализовать подобный проект, и уж, по крайней мере, разобраться в программном коде моей реализации проекта. Хочу отметить, что проект был написан мной довольно давно, так что при работе с базой данных я использовал объекты DAO, а не ADO. Но, может быть, в этом есть свое достоинство.

    Страница "Титул"

    Вот как выглядит титульная страница нашего документа:
    Страница

    увеличить изображение
    Рис. 9.1.  Титульная страница MasterF
    Три командные кнопки - Работа с проектами, Оптимизация инвестиций и Справка - осуществляют переход к соответствующей службе помощника.

    Мир объектов Excel 2000

    Библиотека функций

    Практически сохранена библиотека функций Excel, допустимых при построении формул. Следует сказать, что чрезвычайно обширная библиотека функций Excel является его гордостью. Компонент Spreadsheet может также гордиться своей библиотекой. В ее состав входят функции, разбитые на 10 основных категорий:
  • Database - так называемые D-функции, предназначенные для работы с базами данных.
  • Data and Time - для работы с датами и временем.
  • Engineering - функции Бесселя и другие функции, применяемые в инженерных расчетах.
  • Financial - финансовые функции.
  • Information - функции, позволяющие получить тип данных и другую полезную информацию.
  • Logical - логические функции.
  • Lookup and Reference - функции поиска данных и перехода по ссылкам.
  • Math - общематематические функции.
  • Statistical - статистические функции.
  • Text - функции, предназначенные для работы со строковыми данными.

  • Важно, однако, понимать, что не все функции, имеющиеся в Excel, реализованы, может быть реализовано только 95% функций, например, нет функции Transpose (транспонирования) из класса математических функций, Trend - из класса статистических функций, Asc - информационной функции и некоторых других функций. Более полную информацию о функциях, включенных в состав библиотеки, можно найти в справочной системе, сопровождающей компонент.

    Дополнительные свойства объекта SpreadSheet

    Рассмотрим те свойства, которые приобрел объект SpreadSheet. Многие из них связаны с главным предназначением этого объекта - обеспечением возможности интерактивной работы с электронной таблицей на Web-страницах.
  • ActivePane - это свойство заменяет свойство ActiveWindow и возвращает объект Pane - активное подокно, в котором размещается электронная таблица.
  • AllowPropertyToolbox - булево свойство, позволяющее включать или отключать панель свойств в период выполнения.
  • Управление видимыми размерами электронной таблицы. Ряд свойств позволяют управлять видимыми размерами. Свойство ViewableRange позволяет задать объект Range, определяющий область электронной таблицы, допустимую для просмотра пользователем. Остальная область будет для него скрыта. Свойства MaxWidth, MaxHeight определяют максимальные размеры видимой части электронной таблицы по ширине и высоте. Они могут быть заданы либо в пикселях, либо в процентах от величины контейнера, содержащего электронную таблицу. Наконец, булево свойство AutoFit позволяет наилучшим способом отобразить допустимую для просмотра область. Если его значение задано, как True, то электронная таблица будет отображать допустимую для просмотра область со скроллингом или без него в зависимости от числа строк и столбцов области и значений MaxWidth и MaxHeight. Вот пример установки этих свойств в обработчике события OnLoad:

    Этот код я добавил в конец тега html-кода, полученного при сохранении рабочего листа книги Excel с именем BookFour в виде интерактивной Web-страницы. Событие OnLoad возникает при загрузке Web-страницы в браузер, соответственно вызывается обработчик этого события. Поэтому при открытии страницы будут установлены параметры, задающие область просмотра и то, как она будет отображаться. Вот как выглядит эта страница, открытая в Internet Explorer:

    Дополнительные свойства объекта SpreadSheet

    увеличить изображение
    Рис. 10.4.  Управление отображением области, допустимой для просмотра

    Заметьте, если уменьшить значение параметров MaxWidth и MaxHeight, например, до 50%, то область отображения электронной таблицы уменьшится, и появятся полосы, позволяющие осуществлять скроллинг внутри области.
  • Свойство BuildNumber задает номер версии компонента.
  • Булево свойство CanUndo со статусом "только для чтения" возвращает значение True, если предыдущее действие может быть отменено.
  • Работа с именованными константами в VBScript. Свойство Constants позволяет справиться с проблемой типизированных констант в VBScript. Язык VBScript, как известно, не типизирован. С другой стороны, константы, используемые при работе с объектами, типизированы и принадлежат типам, задаваемым соответствующими перечислениями. Свойство Constants позволяет обойти ограничение VBScript и дает возможность работы с именованными константами, принадлежащим различным перечислениям. Это свойство задает набор из всех именованных констант VB. После вызова свойства достаточно указать через точку имя константы, чтобы работать с ней в VBScript также как и в VB /VBA . Вот небольшой пример кода на использование трех последних свойств:

    Dim myc 'Задание набора констант Set myc = .Constants .Range("D5:F8").Borders.Weight = myc.owcLineWeightThick .Range("D5:F8").Borders.Color = "Green" .TitleBar.Caption = "OWC SpreadSheet Build - " & .BuildNumber .TitleBar.Font.Color = "Green" If .CanUndo Then .TitleBar.Font.Color = RGB(255,125,125)

    Этот код я добавил в предыдущую процедуру Window_onLoad. Не буду приводить рисунка, демонстрирующего изменения внешнего вида электронной таблицы. Замечу, что все отработало должным образом. Свойство CanUndo вернуло значение True, так что цвет в заголовке окончательно был установлен с использованием функции RGB.
  • Управление данными. Я уже говорил о вводе-выводе данных в электронную таблицу. Сейчас я более подробно расскажу о свойствах компонента SpreadSheet, предназначенных для управления вводом и выводом данных. Свойство CSVData позволяет задать строку, являющуюся источником данных для ввода в ячейки электронной таблицы. Данные в строке должны разделяться символом "," (запятая). Свойство позволяет, как получать данные, так и возвращать строку данных. Свойство CSVURL также устанавливает или возвращает данные, разделенные символом "запятая", но источник данных в этом случае задается URL-адресом. Свойство HTMLData позволяет получать или возвращать данные, представленные в HTML-таблице. Свойство HTMLURL позволяет работать с данными такого же формата, но источник данных задается URL-адресом. Это может быть, например, адрес Web-страницы, на которой расположена HTML-таблица. Источником может быть и страница документа Excel 2000, сохраненная как интерактивная Web-страница. Если одновременно заданы несколько из этих свойств, то возникает дилемма, откуда брать данные. Свойство DataType позволяет решить данную проблему. Четыре возможных значения этого свойства совпадают с именами четырех рассмотренных только что свойств, задающих источники данных. Значение DataType однозначно определяет, каким свойством следует пользоваться для доставки данных.
  • Display-свойства. Серия этих свойств позволяет отображать те или иные элементы электронной таблицы - заголовки столбцов и строк, сетку, вертикальную и горизонтальную полосу прокрутки, панель свойств и панель инструментов, заголовок таблицы. Вот их перечисление: DisplayColHeaders, DisplayRowHeaders, DisplayGridlines, DisplayHorizontalScrollBar, DisplayVerticalScrollBar, DisplayPropertyToolbox, DisplayToolbar, DisplayTitlebar. Конечно же, всеми этими элементами можно управлять и в объектной модели Excel, но там они не являются свойствами объекта Application.
  • Enable-свойства. Серия этих свойств позволяет включать или отключать те или иные возможности электронной таблицы - автоматическое вычисление, возможность отката. Вот эти свойства: EnableAutoCalculate, EnableUndo.
  • ScreenUpdating - булево свойство, позволяющее отключить перерисовку экрана при обновлении данных. Полезно пользоваться этим свойством при программном обновлении данных. До начала обновления установить это свойство, как False, затем обновить все необходимые данные, затем установить для свойства значение True, чтобы перерисовать экран, когда все данные изменились.


  • Практически я рассмотрел все свойства объекта SpreadSheet. Как видите, их немало, и есть новые свойства, весьма полезные при программной работе с интерактивными документами.

    Другие свойства

    Конечно же, объект Chart обладает свойствами, позволяющими при отображении диаграмм показывать не только саму диаграмму, но и все ее основные элементы оси категорий и значений, заголовок и легенду. Сохранена также такая важная возможность, как показ линии тренда с прогнозом будущих и предыдущих значений, не представленных данными диаграммы. Можно также показывать на диаграмме интервалы, задающие возможную погрешность данных. Следует отметить, что не сохранена возможность показа текущих справок при подведении курсора к той или иной области диаграммы. Однако при желании, эту возможность можно ввести в собственном решении, используя программную настройку и те возможности, которые предоставляет объектная модель компонента Chart.
    На следующем рисунке показана уже знакомая диаграмма с данными об объемах продаж, просматриваемая в Internet Explorer. На диаграмме показана линия тренда с прогнозом продаж, которые можно ожидать от одного из продавцов в следующем периоде. Для данных другого продавца показаны пределы возможной погрешности.
    Другие свойства

    увеличить изображение
    Рис. 10.6.  Диаграмма с линией тренда и интервалом погрешностей

    Форматирование

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

    Гиперссылки

    Компонент поддерживает переход по гиперссылкам, задающим адреса (URL) в Интернете. Достаточно напечатать в любой ячейке строку, начинающуюся с http://, как она будет воспринята в качестве гиперссылки и щелчок по этой ячейке задаст переход к соответствующей странице в Интернете. Другой способ состоит в том, чтобы задать в ячейке таблицы формулу, вызывающую функцию Hyperlink.

    Инструментальная панель

    Инструментальная панель расположена в верхней части компонента Pivot Table и содержит 19 командных кнопок, часть из которых уже знакома нам по интерфейсу компонента SpreadSheet, часть - по работе с инструментальной панелью сводной таблицы в Excel. Вот краткое описание этих кнопок:
  • Инструментальная панель
    - кнопка с логотипом Office отображает информацию о компоненте, задавая номер инсталлированной версии. Имеет ссылку, позволяющую перейти на узел технической поддержки пользователей программных продуктов Microsoft.
  • Инструментальная панель
    - позволяет скопировать выделенную часть сводной таблицы в буфер, а затем содержимое буфера перенести в то или иное приложение. Таким образом, можно осуществлять передачу данных от компонента Pivot Table в приложения, допускающие работу с буфером. Заметьте, что в отличие от компонента SpreadSheet, здесь нет кнопки Paste, так что обратная передача данных не допускается. Очевидно, что наличие этой возможности противоречило бы самой сути сводных таблиц, поскольку проводимый анализ не связан с изменением данных в процессе анализа. Данные можно изменять только в самом источнике.
  • Инструментальная панель
    - знакомые нам кнопки позволяют проводить сортировку выделенных данных сводной таблицы в порядке возрастания или убывания. Последняя из этой группы кнопок включает или выключает фильтр, заданный полями фильтра сводной таблицы.
  • Инструментальная панель
    - кнопка автосуммирования позволяет произвести вычисление итогов для выбранного поля, применяя одну из возможных функций. Серый цвет кнопки означает, что тип поля не позволяет подводить итоги или провайдер не обеспечивает выполнения такой функции. Замечу, что OLAP-провайдер не допускает автосуммирования, поскольку подобные вычисления проводятся на стороне сервера, а не на клиентской машине. Таким образом, при работе с кубами OLAP эта кнопка всегда будет оставаться серой.
  • Инструментальная панель
    - позволяет включать или выключать промежуточные итоги для выбранного поля. Эта кнопка работает и для OLAP-провайдера.
  • Инструментальная панель
    - эта группа кнопок позволяет перемещать поля между измерениями, перенося поле соответственно на ось строк, столбцов, фильтра или в область данных.
  • Инструментальная панель
    - эта пара кнопок также позволяетперемещать поля, но внутри одного измерения, меняя их порядок следования.
  • Инструментальная панель
    - включает или выключает иерархию, допустимую для данного измерения.
  • Инструментальная панель
    - обновляет список полей сводной таблицы, с учетом всех изменений, которые могли произойти в источнике данных.
  • Инструментальная панель
    - создает новую рабочую книгу Excel и копирует туда сводную таблицу с сохранением ее интерактивности. Это позволяет сохранить в Excel структуру сводной таблицы, выбранную пользователем. Замечу, что данные не передаются в Excel, а поступают туда из источника данных. Возможность экспорта сводной таблицы в Excel, конечно же, увеличивает мощь компонента Pivot Table.
  • Инструментальная панель
    - открывает инструментальную панель свойств, набор кнопок которой зависит от контекста, - какой из элементов сводной таблицы является выделенным.
  • Инструментальная панель
    - открывает окно, содержащее список полей, доступных для конструирования сводной таблицы в интерактивном режиме.
  • Инструментальная панель
    - открывает справочную систему по компоненту Pivot Table.


  • Интерфейс компонента Pivot Table

    Давайте поговорим теперь о том, что может делать пользователь, работая с компонентом Pivot Table в интерактивном режиме. Вот основные действия, доступные пользователю:
  • Изменение структуры сводной таблицы. Пользователь может перетаскивать поля, меняя их местами не только внутри одного измерения, но и перемещая их между измерениями. Так что поле, которое было полем строки, может по желанию пользователя стать полем столбца или полем фильтра.
  • Группировать и разгруппировывать данные полей. Иерархии, заданные для измерений, позволяют проводить анализ "в глубину" с необходимой пользователю степенью детализации. О сути этого анализа я говорил ранее.
  • Фильтровать данные. Задавая те или иные значения полей на оси фильтров, можно выполнять требуемую фильтрацию данных.
  • Выполнять команды, определенные инструментальной панелью. Инструментальная панель компонента Pivot Table содержит набор командных кнопок, запускающих выполнение определенных команд.
  • Выполнять расширенный набор команд. Помимо команд, вынесенных на инструментальную панель, пользователю доступен и расширенный набор команд, заданный специальной панелью свойств. Набор команд, появляющийся на этой панели, зависит от контекста, с которым работает пользователь. При выборе того или иного элемента сводной таблицы, например, поля данных или поля фильтра, на панели свойств будет отображаться набор команд, доступных для выполнения при работе с выбранным элементом сводной таблицы.


  • Интерфейс

    Как можно видеть на рис. 10.1, в верхней части компонента SpreadSheet располагается панель инструментальных кнопок, каждая из которых позволяет выполнять определенное действие над данными электронной таблицы. Опишем коротко назначение этих кнопок.
  • Интерфейс
    - кнопка с логотипом Office отображает информацию о компоненте, задавая номер инсталлированной версии. Имеет ссылку, позволяющую перейти на узел технической поддержки пользователей программных продуктов Microsoft.
  • Интерфейс
    - позволяет производить "откат" - поочередно отменяя последние выполненные действия.
  • Интерфейс
    - хорошо известные кнопки, позволяющие выполнять операции "Вырезать", "Копировать", "Вставить".
  • Интерфейс
    - кнопка автосуммирования, позволяющая выполнять одну из самых распространенных операций над данными.
  • Интерфейс
    - эта группа кнопок выполняет операции по сортировке и фильтрации данных, о которых я говорил чуть выше.
  • Интерфейс
    - кнопка, позволяющая осуществлять экспорт данных электронной таблицы SpreadSheet в Excel, создавая новую рабочую книгу Excel. Я еще буду говорить об этой возможности в следующем параграфе.
  • Интерфейс
    - открывает справочную систему по компоненту SpreadSheet.
  • Интерфейс
    - эта кнопка открывает инструментальную панель, элементы которой задают расширенные возможности управления свойствами компонента SpreadSheet. Вот как выглядит эта панель в раскрытом виде:

  • Интерфейс

    Рис. 10.2.  Инструментальная панель управления свойствами компонента SpreadSheet

    Как можно видеть на рис. 10.1, в верхней части компонента SpreadSheet располагается панель инструментальных кнопок, каждая из которых позволяет выполнять определенное действие над данными электронной таблицы. Опишем коротко назначение этих кнопок.
  • Интерфейс
    - кнопка с логотипом Office отображает информацию о компоненте, задавая номер инсталлированной версии. Имеет ссылку, позволяющую перейти на узел технической поддержки пользователей программных продуктов Microsoft.
  • Интерфейс
    - позволяет производить "откат" - поочередно отменяя последние выполненные действия.
  • Интерфейс
    - хорошо известные кнопки, позволяющие выполнять операции "Вырезать", "Копировать", "Вставить".
  • Интерфейс
    - кнопка автосуммирования, позволяющая выполнять одну из самых распространенных операций над данными.
  • Интерфейс
    - эта группа кнопок выполняет операции по сортировке и фильтрации данных, о которых я говорил чуть выше.
  • Интерфейс
    - кнопка, позволяющая осуществлять экспорт данных электронной таблицы SpreadSheet в Excel, создавая новую рабочую книгу Excel. Я еще буду говорить об этой возможности в следующем параграфе.
  • Интерфейс
    - открывает справочную систему по компоненту SpreadSheet.
  • Интерфейс
    - эта кнопка открывает инструментальную панель, элементы которой задают расширенные возможности управления свойствами компонента SpreadSheet. Вот как выглядит эта панель в раскрытом виде:

  • Интерфейс

    Рис. 10.2.  Инструментальная панель управления свойствами компонента SpreadSheet


    Элемент Chart не имеет собственного интерфейса. Поэтому если поместить этот элемент в единственном числе на Web-странице или в каком-либо другом допустимом контейнере, то у пользователя не будет никаких возможностей взаимодействия с этим элементом. По существу, в этом случае все сведется лишь к статическому просмотру диаграммы. В этом тоже есть некоторый смысл, поскольку диаграмма при перерисовке будет отображать последние изменения источника данных. Возможны два пути решения проблемы создания интерактивной диаграммы:
  • Программное создание интерфейса. При построении собственных решений такой путь может оказаться предпочтительным, так как позволяет в полной мере дать пользователю необходимые средства графического представления данных, учитывающих специфику решаемой задачи.
  • Размещение вместе с диаграммой источника данных. По этому пути пошел Microsoft, встраивая компоненты OWC в Office 2000. При сохранении диаграммы, как интерактивной Web-страницы, на эту страницу помещается не только компонент Chart, но и два других компонента - SpreadSheet и Data Source. Первый из них задает источник данных для построения диаграммы, второй - служит мостиком для передачи данных от компонента SpreadSheet к компоненту Chart. Достоинством такого подхода является то, что пользователь получает возможность изменять данные в таблице компонента SpreadSheet и тут же увидеть, как эти изменения отражаются на диаграмме. Пользователь может также использовать интерфейс компонента SpreadSheet, но он по-прежнему не сможет изменить, например, тип отображаемой диаграммы. В собственном решении можно сочетать достоинства обоих подходов.


  • Как компонент PivotTable взаимодействует с источником данных OLAP

    Одним из главных вопросов, требующих решения при работе со сводными таблицами, является организация связи с источником данных. Как я уже говорил, при создании в Excel такого инструмента анализа данных, как сводная таблица, с самого начала предполагалось, что основным источником данных для них будут внешние источники - базы данных. Естественно, что возможность использования в качестве источника данных таблиц самого Excel подразумевалась сама собой, и была гарантирована в первую очередь. Три основных типа внешних источников данных могут быть использованы сводными таблицами Excel:
  • Табличные - реляционные базы данных различного типа - MS Access, MS SQL Server и другие.
  • Многомерные хранилища - прежде всего, кубы OLAP.
  • XML-потоки - данные, поступающие из интернет.

  • Компонент Pivot Table сохранил все эти возможные источники данных. Более того, еще одним источником данных может быть DataSource - четвертый компонент, входящий в состав OWC. Для связи с базами данных используется интерфейс OLE DB - стандарт, разработанный Microsoft. Для связи с OLAP источниками данных Microsoft разработала модификацию этого стандарта - OLE DB for OLAP, которая позволяет взаимодействовать с многомерными хранилищами данных аналогично тому, как это взаимодействие происходит при работе с табличными базами данных. Этот стандарт поддерживается как базами данных от Microsoft, так и многими другими многомерными хранилищами данных.
    Взаимодействие компонента с источником данных начинается с того, что указанный при соединении провайдер источника данных определяет механизм этого взаимодействия. Для OLE DB for OLAP провайдера используется интерфейс TCP/IP. После того как соединение установлено, в список полей компонента - Pivot Table Field List, который будет более подробно рассматриваться ниже - передается структура OLAP-куба, его поля, измерения, а также иерархии, связанные с измерениями. Когда пользователь начинает в интерактивном режиме оперировать со сводной таблицей или когда это делается программным путем, компонент Pivot Table генерирует соответствующий MDX-запрос (MultiDimensional Expression), определенный спецификациями интерфейса OLE DB for OLAP. Этот запрос передается на сервер, там выполняется, и результаты выполнения пересылаются клиенту. Поскольку серверу передается по существу один запрос, а результаты содержат агрегированные данные, то объем передаваемой информации сводится к минимуму, что и обеспечивает высокую эффекти вность работы при использовании OLAP-источников данных.

    Каких свойств не имеет объект SpreadSheet

    По понятным причинам объект SpreadSheet устроен намного проще, чем объект Application, а посему и многих свойств объекта Application он не имеет. Вот, например, чего у него нет:
  • Ряда Active-свойств, возвращающих активную диаграмму, рабочую книгу, принтер или окно.
  • Свойств AddIns и ComAddIns, возвращающих соответствующие коллекции. Объект SpreadSheet может работать только с COM объектами.
  • Свойств, возвращающих и позволяющих работать с такими объектами, как AnswerWizard, Assistant, Dialogs, FileSearch, CommandBars, Charts, Workbooks, Sheets, Windows и рядом других мощных объектов Excel.
  • У объекта Application намного богаче совокупность булевых Display- свойств, позволяющая в Excel управлять отображением на экране тех или иных элементов, например, строки статуса.
  • Аналогично обстоит дело и с Enable-свойствами. У объекта Application их значительно больше, они позволяют, например, включить или отключить звук или анимацию.

  • Я не буду подробно останавливаться на всех возможностях объекта Application. Перейдем к рассмотрению тех свойств объекта SpreadSheet, которых нет у объекта Application.

    Компонент Chart

    Говоря о замечательных свойствах приложения Excel, на первое место я поставил его машину вычислений. Второе место по справедливости занимает способность Excel представлять данные в наглядной графической форме. Казалось бы, что представление данных, хранящихся в таблицах, столь же информативно, как и их отображение в виде графиков и гистограмм. Более того, понятно, что информация, хранимая в больших таблицах Excel или в различных хранилищах данных, значительно более полна в сравнении с той, которая может быть отображена в тех или иных графиках и гистограммах. Однако для человека графическая форма представления информации является более приемлемой, позволяя ему значительно быстрее воспринять информацию, обработать ее, осознать тенденции процессов, показанных на графиках, что, в конечном итоге, позволяет быстро принять необходимые решения. Компонент Chart реализует практически в полном объеме все возможности Excel по визуализации данных - представлению их в графической форме.

    Компонент Data Source

    Компонент Data Source (DSC), оставаясь невидимым, выполняет большую работу по связыванию с различными внешними источниками данных, построению и выполнению команд по их доставке. В свою очередь он служит, как правило, источником данных для других компонент OWC - SpreadSheet, Chart, Pivot Table. Этот компонент несет основную нагрузку по доставке данных и при работе со страницами доступа данных в Access (Access 2000 Data Access Pages).
    Так как DSC реализует стандартный интерфейс источников данных - IDataSource Interface, - определенный и поддерживаемый Internet Explorer (начиная с версии 5) и Visual Basic (начиная с версии 6), то эти контейнеры воспринимают его как правильный источник данных. Это означает, что при размещении компонента DSC на Web-странице или на форме Visual Basic он может служить источником данных для других элементов, расположенных на этой странице или форме.
    Замечу, что не только DSC, но и другие компоненты OWC могут служить источниками данных и удовлетворяют интерфейсу IDataSource. Так, компонент SpreadSheet может поставлять данные компонентам Chart и Pivot Table, а последний, в свою очередь, может поставлять данные для компонента Chart.
    Источники данных имеют достаточно сложную структуру и состоят из наборов данных, называемых элементами (членами) источника - Data Member. Каждый такой элемент задает некоторый набор данных - Recordset, который может использоваться при связывании источника данных с тем или иным элементом, расположенным на Web-странице. Для компонента SpreadSheet в качестве элемента источника - Data Member - может использоваться любой диапазон его ячеек. Для компонента DSC элементы источника следует определять. Эти определения элементов составляют специальную коллекцию - RecordsetDefs.
    В детали объектной модели этого компонента я углубляться не буду. Вместо этого я приведу пример, в котором на Web-странице строится круговая диаграмма по данным внешнего источника - базы данных Access. Посредником в связывании данных будет выступать компонент Data Source. На этом примере я постараюсь прояснить и роль Data Member, и роль RecordsetDefs.
    Прежде чем перейти к примеру, хочу отметить следующее. Надеюсь, понятно, что поскольку элемент является невидимым, то никакого интерфейса у него нет, так что общение с ним возможно только на программном уровне. Поэтому в данном разделе я приведу достаточно большой кусок программного кода. Следует заметить также, что элемент действительно невидим в момент выполнения, например при просмотре Web-страницы в Internet Explorer. Однако в режиме проектирования, например, при открытии страницы в приложении FrontPage, местоположение элемента отмечается небольшим значком с изображением ключа -
    Компонент Data Source
    .

    Компонент PivotTable

    В предыдущей главе, посвященной анализу офисной деятельности, подробно рассматривались сводные таблицы - один из самых мощных инструментов, позволяющий эффективно анализировать большие объемы данных, хранящихся в различных источниках. Сводные таблицы позволяют анализировать информацию под разными углами зрения, получать различные срезы, сортировать, фильтровать, группировать данные нужным образом. Сводные диаграммы, тесно связанные со сводными таблицами, позволяют тут же отобразить в графической форме полученную информацию.
    Конечно же, иметь подобный инструмент при работе с документами в интрасетях чрезвычайно важно. И такой инструмент был создан - среди компонентов OWC имеется компонент PivotTable, позволяющий создать интерактивный Web-документ, содержащий сводную таблицу, с которой можно практически работать также, как в обычном Excel.

    Компонент Spreadsheet

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

    Компоненты OWC и офисное программирование

    Подводя итоги, отмечу, что включенные в состав Office 2000 компоненты OWC выполняют важную работу, позволяя проводить полноценный анализ данных, возможный ранее только в Excel, как при работе в интрасетях, так и при построении собственных решений на базе различных контейнеров, допускающих работу с COM-объектами.
    Как и всегда при работе в мощной среде Office 2000, многие задачи, требующие использования этих компонентов, могут решаться "руками" - по существу, несколькими нажатиями кнопки мыши. Однако, возможно, самое главное достоинство этой среды, в том числе и компонентов OWC, заключается в том, что можно произвести тонкую настройку решения, учитывающую специфику задачи. Когда стандартных решений становится недостаточно, за дело берется программист, который, работая с имеющимися в его распоряжении объектами среды, может создать собственное решение, существенно расширяющее стандартные возможности среды.
    Объектная модель компонент OWC весьма разнообразна и предоставляет программисту широкие возможности. Программная работа по созданию решений, использующих компоненты OWC, по существу, является важной частью офисного программирования.

    Компоненты OWC, работающие на стороне сервера

    Одно из достоинств компонентов OWC заключается в том, что они могут работать на стороне сервера. Причины, по которым целесообразно перенести их работу на сервер, могут быть самыми разными. Вот три основные:
  • У клиента нет компонентов OWC и он не располагает лицензией на Office 2000, необходимой при легальной работе с компонентами.
  • Клиент открывает Web-страницы в браузере, не являющемся контейнером COM-объектов, например, клиент использует обозреватель NetScape.
  • В ряде случаев эффективнее перенести всю работу на сервер, а клиенту пересылать по запросу или в момент возникновения определенных событий моментальные снимки диаграмм, электронных и сводных таблиц.

  • Обычно Com-объекты работают либо как видимые объекты, либо как невидимые объекты (in memory). Компоненты OWC спроектированы так, что для них возможны оба способа работы. На стороне сервера они работают, конечно же, как невидимые объекты, поскольку там нет клиента, который мог бы с ними работать в интерактивном режиме. Они создаются программно, с использованием метода CreateObject, вызываемого объектом Server. Вот как может выглядеть код, создающий на стороне сервера компонент Chart, или, более точно, основной его объект cspase:
    Set m_cspace = Server.CreateObject("OWC.Chart")
    После того как объект получен, остальная часть работы с этим объектом на сервере ведется так же, как и на стороне клиента. Завершающим аккордом на стороне сервера является преобразование построенной диаграммы или сводной таблицы в графический образ, который и пересылается клиенту. Для этой цели используется метод ExportPicture, которым обладают компоненты OWC.

    Копирование и работа с буфером

    Одними из основных операций, которые чаще всего выполняют пользователи, работающие в интерактивном режиме с электронной таблицей, - это операции "Копировать" - "Вставить", использующие буфер. Поскольку есть ряд отличий от выполнения их в Excel, то на некоторые моменты хочу обратить внимание.
  • При копировании ячейки копируется все - формула, значение, формат. Эти элементы нельзя копировать независимо, так что известная по Excel команда Paste Special отсутствует. Никаких дополнительных операций, возможных в Excel в момент копирования, здесь нет.
  • При копировании формул абсолютные ссылки остаются фиксированными, а относительные ссылки на ячейки изменяются нужным образом в момент вставки копируемой формулы в другую ячейку. При перемещении формул такого изменения не происходит.
  • При вставке в ячейку копируемые или перемещаемые данные полностью заменяют содержимое ячейки.
  • Копируемые данные хранятся в буфере в формате HTML до тех пор, пока в буфер не поступит очередная порция данных. В Excel это не так.
  • В Excel выделенная ячейка имеет "хвостик", потянув за который можно скопировать содержимое ячейки в соседние ячейки, при этом относительные ссылки в формулах изменяются. Здесь такая операция невозможна. Нет и специального выделения копируемой или перемещаемой области данных.

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

    Машина вычислений

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

    Методы объекта Range

    Что касается методов объекта Range, то их чуть более 20, и практически все они наследованы от объекта Excel.Range. Из специфических методов можно упомянуть лишь два:
  • LoadText - позволяет загружать данные из текстового файла в заданную область. Об этом методе я уже говорил при описании объекта SpreadSheet.
  • FreezePanes - "замораживает" подокно. Вот небольшой пример. В предыдущей процедуре можно видеть следующие строчки:
  • 'Замораживание подокна myr.FreezePanes myc.ssFreezeTop
    В результате действия этого оператора произошло расщепление окна, и окружающее сверху подокно было заморожено, так что, например, прокрутка не оказывает действие на это подокно - оно всегда видимо. Заметьте, на рисунке 10.5 видна черта, отделяющая подокна.
    Я уже говорил о том, что можно делать с помощью методов объекта Range. Напомню, - можно выполнять фильтрацию, сортировку и поиск данных в пределах области. Можно осуществлять чистку содержимого и формата, удалять и вставлять столбцы и строки и ряд других подобных операций.
    На этом я закончу рассмотрение объектной модели компонента SpreadSheet. Я специально достаточно подробно рассмотрел эту модель, чтобы показать, что объекты OWC построены также как и все остальные объекты Office 2000, и работа с этими объектами ведется аналогичным образом. Для других компонентов OWC я не буду уже столь подробно разбирать объектную модель, а ограничусь кратким обзором и примерами.

    Методы объекта SpreadSheet

    Методов у объекта SpreadSheet немного - всего 9. Три из них схожи с методами объекта Excel.Application. Два из этих методов - Union и Intersect задают объединение и пересечение областей электронной таблицы, возвращая соответствующий объект Range в качестве результата. От одноименных методов объекта Application отличаются числом возможных аргументов. У методов объекта SpreadSheet аргументов два - Range1 и Range2, которые и задают области, пересечение или объединение которых ищется. Методы объекта Application позволяют проводить эти операции одновременно над большим числом аргументов. Третий общий метод Undo также имеет некоторые различия в семантике. Для объекта SpreadSheet он позволяет не только отменить последнее действие, сделанное, например, пользователем в процессе работы с интерфейсными объектами, но и отменить действия последнего выполняемого программного блока, помеченного специальными метками "BeginUndo" и "EndUndo".
    Из 6 методов, специально созданных для объекта SpreadSheet, методы:
  • BeginUndo, EndUndo - позволяют произвести разметку программного блока, чтобы можно было при необходимости отменить его выполнение, вызвав метод Undo.
  • LoadText - позволяет загрузить в электронную таблицу данные из текстового файла. В отличие от свойства CSVURL, позволяющего решать схожую задачу, ограничителем данных в текстовом файле может выступать не только символ "запятая", но и другие ограничители - пробелы, символы табуляции и другие.
  • Refresh - позволяет провести обновление данных, перезагружая их из источника и перерисовывая объект - электронную таблицу.
  • UpdatePropertyToolbox - позволяет обновить значения свойств, заданных панелью свойств.
  • AddIn - добавляет Addin к объекту, позволяя работать с его свойствами и методами.


  • Методы объекта

    Объект имеет 5 методов: Calculate, Eval, ShowAllData, Export, Scroll, первые три из которых в этом списке унаследованы от объекта WorkSheet модели Excel.
    Метод Export(FileName, ExportAction) позволяет экспортировать электронную таблицу. Второй аргумент метода указывает нужно ли открыть таблицу в Excel или сохранить ее в файле, имя которого задается первым аргументом метода.
    Метод Scroll(Range) производит горизонтальную и вертикальную прокрутку так, чтобы область, заданная аргументом попала в левый верхний угол электронной таблицы.

    Объект SpreadSheet.Range

    Объект Range является основой основ в объектной модели Excel. Также обстоит дело и в модели компонента SpreadSheet. Десятки свойств и методов различных объектов этой модели возвращают в качестве результата объект Range. Заметьте, коллекции Cells, Rows, Columns, возвращаемые одноименными свойствами, являются одновременно объектами Range. Такие свойства как Range, Selection, ViewableRange, VisibleRange, такие методы как Union, Intersection и многие другие - возвращают объект Range.
    Модель этого объекта хорошо отработана еще в Excel и потому не удивительно, что объект SpreadSheet.Range унаследовал большую часть свойств объекта Excel.Range. Конечно, часть свойств исходного объекта была потеряна - исчезла возможность работы с формулами над массивами, возможность хранения зависимостей, работы с циклическими ссылками, но основные свойства объекта Excel.Range сохранились. Заметьте, у объекта SpreadSheet.Range почти не появилось новых свойств, которых не было бы у объекта Excel.Range. Достаточно сказать, что из 46 свойств объекта SpreadSheet.Range 45 свойств перешли по наследству от объекта Excel.Range. Появилось лишь одно новое свойство - HTMLData, о котором уже шла речь при рассмотрении объекта SpreadSheet. Для объекта Range это свойство имеет статус "только для чтения" и позволяет вернуть HTML-таблицу, хранящуюся в ячейках области, заданной объектом Range.
    Поскольку объект Excel.Range описан мной достаточно подробно, то я не буду останавливаться на деталях описания аналогичных свойств объекта SpreadSheet.Range , и даже не буду всех их перечислять. Скажу только, что свойства объекта позволяют работать с коллекциями ячеек, строк и столбцов области, отдельными ячейками, границами области, используемым шрифтом, формулами и значениями, хранимыми в ячейках, их форматами и многим другим.
    Приведу пример программной работы с объектами Range. Заметьте, эти объекты уже появлялись в ранее приведенной процедуре - обработчике события OnLoad объекта Window. Но еще один пример не помешает. К Web-странице, показанной на рис. 10.4, я добавил командную кнопку. Вот описание соответствующего тега:



    Далее я написал обработчик события Click для этой кнопки, в котором создаю и работаю с объектами Range нашей электронной таблицы, размещенной на этой странице. Вот VBScript код этого обработчика:

    Sub WorkWithRange()

    'Работа с областями электронной таблицы Dim myc 'Задание набора констант Dim myr, myr1, myr2, myr3, myr4, myr5, myr6 'объекты Range Dim strFormula 'Строка, задающая формулу

    With BooKFour_1787_WebCalc Set myc = .Constants Set myr2 = .Range("E12:G14") myr2.Borders.Weight = myc.owcLineWeightMedium myr2.Borders.Color = "Blue" 'Изменяю область, доступную для просмотра .ViewableRange = "A1:G20" 'Создаю объекты Range для программной работы с ними Set myr = .Range("A11:G20") With myr .Font.Size = 12 .Font.Bold = True Set myr1 = .Cells(2,5) myr1.Value = "Строки и столбцы" Set myr3 = myr1.Offset(1) myr3.Value = myr.Rows.Count Set myr4 =myr1.Offset(1,1) myr4.Value = myr.Columns.Count Set myr5 = myr1.Offset(2) strFormula = "=" & myr3.Address & "+" & myr4.Address myr5.Formula = strFormula Set myr6 = myr1.Offset(2,2) strFormula = "=" & myr3.Address & "*" & myr4.Address myr6.Formula = "=E13 * F13" 'Замораживание подокна myr.FreezePanes myc.ssFreezeTop End With End With

    End Sub

    В этом примере я создаю большое число объектов Range. В процессе работы с этими объектами создаются объекты Font и Borders, как результат вызова соответствующих свойств. Здесь же появляются коллекции Cells, Columns, Rows, являющиеся, как я уже говорил, объектами Range. Я использую различные свойства и методы объектов Range, - в частности, свойства ViewableRange, Address, Value, Formula, методы Offset и FreezePanes.

    Взгляните, как выглядит страница в Internet Explorer после нажатия в окне браузера командной кнопки с именем "Работа с объектом Range":

    Объект SpreadSheet.Range

    увеличить изображение
    Рис. 10.5.  Программная работа с областями электронной таблицы

    Объект SpreadSheet.WorkSheet

    Получить объект WorkSheet можно двумя способами - можно спуститься вниз по иерархии, вызвав свойство ActiveSheet корневого объекта SpreadSheet, можно подняться вверх по иерархии, вызвав свойство Parent объекта Range.

    Объектная модель и программирование

    Начну с некоторого обзора, позволяющего дать общее представление об объектной модели этого компонента. Прежде всего, замечу, что корневым объектом в этой модели является не объект Chart, а объект - ChartSpace, задающий пространство диаграммы, в котором можно разместить несколько объектов Chart. Содержательно это означает, что компонент ChartSpace играет роль листа книги, который может одновременно отображать несколько диаграмм. Для программиста важно то, что объект ChartSpace имеет свойство Charts, возвращающее коллекцию объектов Chart и для добавления элементов в эту коллекцию используется стандартный для коллекций метод Add.
    Особую роль среди методов объекта ChartSpace играет метод ExportPicture, позволяющий экспортировать пространство диаграммы со всем ее содержимым в графический файл в формате GIF. Такой прием применяется при размещении компонент OWC на серверной стороне, о чем я еще скажу особо. Из свойств объекта Chart упомяну свойство Type, позволяющее задать тип диаграммы. Говоря о методах этого объекта, назову метод SetData, который используется для того, чтобы установить связь с разнообразными источниками данных и направить их в те или иные области диаграммы. Одноименный метод объекта Series, входящего в коллекцию серий данных - SeriesCollection объекта Chart, - позволяет загружать данные отдельной серии. Из событий объекта Chart нельзя не назвать событие DataSetChange. Это событие зажигается, когда связанный источник данных изменяет свои данные. При возникновении события компонент Chart автоматически перестраивается, чтобы соответствовать обновленным данным. Но, если обновление связано с изменением структуры диагра ммы, например, появлением новой серии, то для подобных обновлений следует написать собственный обработчик события.
    В заключение обзора хочу представить схему, отражающую устройство основных объектов модели - ChartSpace, WCChart, WCSeries:
    Объектная модель и программирование

    Рис. 10.7.  Схема устройства основных объектов модели

    Третьим объектом, устройство которого показано на рисунке 10.6, является объект WCSeries, задающий одну группу (серию) данных, используемых для построения диаграммы. Как можно видеть, соответствующие свойства возвращают объекты, позволяющие программно работать не только с самими данными, но и строить тренды и доверительные интервалы для данной серии данных.

    Я не буду дальше погружаться в описание деталей построения объектов, а ограничусь примером программного построения диаграммы на Web-странице. Я продолжил работу с HTML-документом, показанным на рис. 10.5. Я использовал этот документ для демонстрации некоторых возможностей работы с компонентом SpreadSheet и написал, как Вы помните обработчик события, связанного с нажатием соответствующей командной кнопки, расположенной на документе. Теперь я добавлю к документу еще одну командную кнопку, по нажатию которой будет строиться диаграмма, источником данных для которой будет компонент SpreadSheet, уже расположенный на этом документе. Чтобы пример был интереснее, я добавлю к документу еще один управляющий элемент - выпадающий список, элементы которого будут задавать тип диаграммы. Тем самым пользователь будет иметь возможность не только построить саму диаграмму, но и перед построением задать ее тип.

    Все эти действия я выполнил в приложении FrontPage. Не буду останавливаться на деталях выполнения этого этапа работы. Приведу лишь описание тега , в котором я определил компонент Chart:



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

    Объектная модель и программирование

    Рис. 10.8.  Начальное состояние Web-страницы

    А вот как выглядит эта страница, после того, как из списка выбран тип "диаграмма" и нажата командная кнопка "Создать диаграмму":

    Объектная модель и программирование

    увеличить изображение
    Рис. 10.9.  Web-страница с программно построенной диаграммой

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

    Sub CreateChart() 'Создание диаграммы Dim myc 'Константы Dim cht 'Объект, задающий диаграмму Dim Ax 'Объект, задающий ось диаграммы Dim Ser 'Объект, задающий серию данных Dim strDebug 'Строка, используемая при отладке With myChartSpace 'strDebug="step 1 " 'MsgBox(strDebug) Set myc = .Constants 'размеры области диаграммы .width = "100%" .height = "300" 'Источник данных If .ChartDataSources.Count = 0 Then .DataSource = BooKFour_1787_WebCalc Else .DataSource.Refresh End If 'Добавляем Объект Chart в коллекцию Set cht = .Charts(0) 'strDebug="step 2 " 'MsgBox(strDebug & .Charts.Count) 'Устанавливаем его параметры With cht 'По умолчанию - тип диаграмма .Type = myc.chChartTypeColumnClustered strDebug = "ColumnClustered"


    If SelType.Value = "График" Then .Type = myc.chChartTypeLineMarkers strDebug = "LineMarkers" End If 'MsgBox("Тип диаграммы - " & strDebug) 'Легенда и заголовок диаграмы .HasLegend = True .HasTitle = True .Title.Caption = "Дилеры и Объемы продаж" 'Заголовки осей Set Ax = .Axes(myc.chAxisPositionLeft) Ax.HasTitle = True Ax.Title.Font.Italic = True Ax.Title.Font.Bold = True Ax.Title.Caption = "Объем Продаж" Set Ax = .Axes(myc.chAxisPositionBottom) Ax.HasTitle = True Ax.Title.Font.size = 14 Ax.Title.Font.Bold = True Ax.Title.Caption = "Кварталы -2001" 'Установка данных .SetData myc.chDimCategories, 0, "C5:C8" .SetData myc.chDimSeriesNames, 0, "D4:F4" .SeriesCollection(0).SetData myc.chDimValues, 0, "D5:D8" .SeriesCollection(1).SetData myc.chDimValues, 0, "E5:E8" .SeriesCollection(2).SetData myc.chDimValues, 0, "F5:F8" 'Работа с серией данных. Определение тренда Set Ser = .SeriesCollection(1) Ser.TrendLines.Add Ser.TrendLines(0).Type = myc.chTrendLineTypeExponential Ser.TrendLines(0).Order = 2 Ser.TrendLines(0).IsDisplayingEquation = True

    End With End With End Sub

    Хотя процедура снабжена комментариями, но целесообразно дать дополнительные пояснения:

  • Прежде всего, я определяю объекты, с которыми предстоит работать - cht, Ax, Ser и другие. Хотя VBScript и бестиповый язык, но объявлять переменные все равно целесообразно - это правильный стиль программирования.
  • Затем начинается работа с корневым объектом ChartSpace. Заметьте, имя MyChartSpace, которым я пользуюсь, - это ID объекта, заданное при определении соответствующего тега object, приведенного выше. Я использую свойства width и height, чтобы задать подходящую область пространства диаграмм. При первоначальном определении объекта в теге его размеры задавались минимальными, теперь область расширяется до подходящих размеров.
  • В качестве источника данных задается объект SpreadSheet, расположенный на этой же Web-странице. Заметьте, я выполняю проверку того, не установлена ли уже связь с этим источником. Дело в том, что командная кнопка может нажиматься многократно и, следовательно, процедура может работать многократно, а попытка связи с уже присоединенным источником приводит к ошибке. Так что, когда связь уже установлена, то вызывается только метод Refresh для обновления данных источника.
  • Обратите внимание, при создании объекта cht класса WCChart я не использую метод Add для добавления нового элемента в коллекцию, поскольку один элемент этой коллекции создается автоматически. Конечно, если бы я хотел одновременно показать несколько диаграмм, без использования этого метода не обойтись.
  • На следующем этапе идет работа с уже созданным объектом cht, - задаются параметры, определяющие его элементы.
  • Первым делом, я определяю тип строящейся диаграммы. Для этого используется значение, выбранное пользователем из выадающего списка. Имя списка - SelType - задается его ID, определенным при размещении элемента на Web-странице. Конечно, я без труда мог бы дать пользователю больший набор возможных типов, что лишь слегка бы увеличило размер нашей процедуры.
  • Затем я создаю заголовок, включаю легенду и даю заголовки осям диаграммы. При этом приходится работать с различными объектами - Title, Legend, Axes, Axis и другими.
  • Наиболее важный момент - это определение данных. В данном случае я определяю три серии данных и, соответственно, задаю три элемента коллекции SeriesCollection. Поскольку источником данных является электронная таблица - объект SpreadSheet, то мне достаточно указать диапазон ячеек, где находятся данные серии.
  • На последнем шаге демонстрируется работа с отдельной серией. Для этой серии задается полиномиальный тренд.


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

    Объектная модель и программирование

    увеличить изображение
    Рис. 10.10.  Программное построение диаграммы другого типа

    Объектная модель компонента SpreadSheet и программирование

    Для программиста компонент SpreadSheet является обычным объектом, свойства и методы которого он может вызывать в своих программах, также как и проводить обработку событий, происходящих в процессе работы пользователя с компонентом. Замечу, что объектная модель компонента SpreadSheet во многом напоминает соответствующую часть объектной модели Excel. Программирование позволяет построить собственное решение, в полной мере использующее возможности этого компонента.
    Прежде чем перейти к деталям, хочу напомнить об одной особенности построения объектной модели компонент OWC - объектные модели всех компонент построены независимо, нет объекта, объединяющего их, в который все они были бы вложены. Каждый из четырех рассматриваемых нами объектов является корневым объектом в соответствующей модели.
    Я приведу сейчас описание основных элементов объектной модели компонента SpreadSheet, проводя по ходу дела сравнение с соответствующими объектами Excel. Напомню, что описание объектной модели Excel я дал в главе 11. Справочная система по объектам OWC доступна и находится в файле msowcvba.chm, путь к которому обычно следующий: C:\ProgranFiles\Microsoft Office\Office\1033.
    Давайте вспомним, как устроена объектная модель Excel, На верхнем уровне здесь находится объект Application, в который среди прочего вложена коллекция рабочих книг - Workbooks, каждая из которых содержит коллекцию листов книги - Sheets. Листы книги могут быть разного типа, определяющее значение играют рабочие листы - объекты Worksheet. Одним из главных объектов рабочего листа, с которым чаще всего приходится работать программисту, является объект Range.
    В объектной модели компонента SpreadSheet на верхнем уровне расположен сам объект SpreadSheet, он в определенном смысле играет роль объекта Application и во многом наследует его свойства и методы. Конечно же, никакой коллекции рабочих листов, не говоря уже о рабочих книгах, в объект SpreadSheet не вложено, в него встроен лишь объект Worksheet, задающий единственный рабочий лист, связанный с компонентом. Этот объект во многом устроен также как и его тезка в Excel, в частности, в него встроен объект Range, позволяющий программисту работать с областями рабочего листа, связанного с компонентом SpreadSheet.

    Объекты SpreadSheet и Excel Application. Сравнительный анализ

    Давайте рассмотрим свойства, общие для объектов SpreadSheet и Excel Application. Их сводка дана в следующей таблице:

    Таблица 10.1. Общие свойства объектов SpreadSheet и Excel ApplicationСвойстваНазначение
    ActiveCell, ActiveSheetАктивная ячейка и активная страница. Свойство ActiveSheet для объекта SpreadSheet играет значительно более важную роль, чем для объекта Application, поскольку именно оно возвращает центральный элемент этой модели - объект WorkSheet - тот единственный рабочий лист, который задает электронную таблицу.
    Cells, Columns, RowsКоллекции ячеек, строк и столбцов
    DisplayScrollBarБулево свойство, позволяющее включать или отключать скроллинг. У объекта Spreadsheet есть два свойства, позволяющие отдельно управлять вертикальным и горизонтальным скроллингом.
    EnableEventsБулево свойство, позволяющее включать события объекта.
    MoveAfterReturn, MoveAfretReturnDirectionБулево свойство, позволяющее указать, будет ли изменяться активная ячейка после ввода в нее данных и нажатия клавиши Enter, и свойство, задающее направление перемещения к новой активной ячейке.
    RangeСвойство, возвращающее объект Range. Аргументы свойства позволяют задать требуемую область рабочего листа.
    SelectionСвойство, возвращающее выделенный (активный) объект. Если выделена область в рабочем листе, то этот объект принадлежит классу Range.
    VersionСвойство, возвращающее версию.

    Общих свойств не столь уже и много. Может быть, более интересно понять, каких свойств объекта Application не имеет объект SpreadSheet, и какие дополнительные свойства он приобрел.

    Область данных

    С каждым компонентом Spreadsheet связывается один рабочий лист - одна электронная таблица. Ее максимальный размер 65536 строк на 702 столбца (с именами от A до ZZ). Заметьте, что в Excel 2000 при том же числе строк число столбцов меньше почти в три раза - всего 256. Однако не стоит особенно обольщаться возможным большим размером листа, так как тут же возникает проблема времени его загрузки при работе в сети.
    Поскольку с каждым компонентом связывается только один лист, то при публикации на Web нельзя сохранить в интерактивном режиме сразу всю рабочую книгу Excel, состоящую из нескольких рабочих листов. Приходится независимо сохранять каждый рабочий лист или часть этого листа на отдельной Web-странице.

    Обмен данными

    Как и все элементы управления ActiveX, компонент SpreadSheet не имеет собственных средств, позволяющих открыть или сохранить этот элемент - команды Open и Save не являются частью интерфейса таких элементов. Выполнение этих функций - прерогатива контейнеров, содержащих эти элементы. Тем не менее, у объекта SpreadSheet есть целый набор способов, позволяющих ему общаться с внешним миром, - передавая и получая данные.

    Office 2000 и OWC

    Поскольку компоненты OWC встроены в Office 2000, то для большинства пользователей Office работа с компонентами скрыта. Пользователь, работающий с документами Excel или Access, может использовать эти компоненты, даже не подозревая о том, что он их использует. Конечно же, опытный пользователь - программист, знающий объектную модель компонентов и умеющий программировать на VBScript, может произвести настройку компонентов OWC, расположенных на Web-странице, включив дополнительные свойства, учитывающие специфику работы с его документом.
    Включение компонентов OWC в состав Office 2000 привело к появлению новых возможностей по публикации документов в Excel 2000. В связи с этим существенно изменился внешний вид и функции диалогового окна "Сохранение документа". Также как и в Word, здесь появилась панель с кнопками (ярлыками), позволяющая уточнить расположение сохраняемого документа. В частности, кнопка "Web Folders" открывает Web-папки, хранящиеся на сервере, и позволяет публиковать документ (Web-страницу) непосредственно в папку на сервере, тем самым мгновенно делая его доступным всем участникам совместной работы над документом в интерсети. В этом же диалоговом окне можно указать, следует ли публиковать всю рабочую книгу или отдельный лист, нужно ли добавлять интерактивность публикуемой странице. Командная кнопка "Опубликовать" этой панели открывает новое диалоговое окно "Публикация Web-страницы", в котором можно более детально задать элемент рабочей книги, предназначенный для публикации, а также вид и
    нтерактивности, придаваемой публикуемому элементу.
    В зависимости от типа публикуемых данных на Web-странице автоматически располагаются различные Web-компоненты. Я не буду сейчас останавливаться на деталях этого процесса, но надеюсь, что из сказанного уже очевидно, что, работая в Excel, создавать интерактивные Web-страницы с встроенными компонентами OWC достаточно просто, - нужно лишь грамотно задавать параметры при сохранении страниц рабочей книги Excel или отдельных элементов этих страниц. Аналогичная ситуация имеет место и при работе в Access, где также могут автоматически создаваться интерактивные страницы с расположенными на них компонентами OWC.
    Вот как выглядит в Internet Explorer соответствующая Web-страница, полученная при сохранении в Excel интерактивной диаграммы, расположенной на рабочем листе книги Excel.
    Office 2000 и OWC

    увеличить изображение
    Рис. 10.1.  Интерактивная диаграмма и рабочий лист с источником данных при просмотре в Internet Explorer
    Заметьте, в этом случае на Web-страницу выносится как сама диаграмма, так и рабочий лист с источником данных этой диаграммы. В данном конкретном случае диаграмма отображает поквартальную динамику объема продаж, выполненных группой дилеров, с подведением итогов за год. При просмотре в Internet Explorer этой Web-страницы можно ввести новые, уточненные данные, например, за 3-й и 4-й кварталы - тут же машина вычислений пересчитает итоги, тут же перестроится и диаграмма.
    Все это становится возможным благодаря тому, что на Web-странице в момент ее сохранения в Excel автоматически размещаются три компонента OWC - Chart, Data Source и SpreadSheet. Как я уже говорил, невидимый объект Data Source "стоит за спиной" объекта Chart, обеспечивая передачу ему данных. Вот часть HTML-кода, отвечающая за появление этих компонентов на Web-странице:

    Компоненты вставляются как объекты. Параметр classid позволяет найти их, если они зарегистрированы на компьютере пользователя, просматривающего соответствующую Web-страницу. Параметр codebase позволяет найти их, если они еще не зарегистрированы. Контент, передаваемый компоненту, задается тегом param этих объектов. Я не привожу достаточно длинное значение параметра value этого тега. По правде говоря, можно обойтись и без задания этого контента, что будет видно из последующих примеров.
    Надеюсь, понятно, что для того чтобы работа с этой страницей стала возможной, на компьютере пользователя должны присутствовать компоненты OWC или они должны быть доступны для загрузки с источника, указанного параметром codebase.
    Давайте поговорим теперь более подробно о компонентах.

    Панель свойств

    Панель свойств задает дополнительный набор команд, которые можно выполнять над тем или иным элементом. Вот какой вид имеет эта панель при выборе поля строки:
    Панель свойств

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

    Пример использования DSC

    В наших примерах неоднократно появлялась база данных "Офис РР". В главе 18 она использовалась при построении OLAP-куба и сводной таблицы. Теперь я воспользуюсь ею и построенным OLAP-кубом, чтобы создать Web-страницу, на которой будет строиться круговая диаграмма, отражающая вклад каждого из сотрудников в общее дело добывания заказов. На страницу будут помещены два компонента OWC - Chart и Data Source. Компонент DSC будет посредником в передаче данных от базы данных к диаграмме.
    Наша страница не будет интерактивной, так как сам элемент Chart, как вы помните, не имеет собственной инструментальной панели. И я не буду выносить сами данные на страницу, передавая их, например, в элемент SpreadSheet. Не буду я и программно добавлять интерфейс элементу Chart. Тем не менее, этот пример кажется мне достаточно интересным, так как позволяет продемонстрировать, как самому создать страницу с расположенными на ней элементами OWC. А главное, он показывает роль DSC как посредника при передаче данных. Вот полный HTML-текст Web-страницы, созданной в редакторе FrontPage:
    MDB-база данных -> Data Source -> Chart

    Связывание: MDB-база данных -> Data Source -> Chart




    0 10 10 ">


    А вот как выглядит эта страница, открытая в Internet Explorer:
    Пример использования DSC

    увеличить изображение
    Рис. 10.13.  Web-страница с компонентами Data Source и Chart
    Главное в этом коде - сценарий, написанный мной на VBScript. Почти половину программного текста составляют комментарии, поясняющие все этапы работы. Тем не менее, я добавлю еще несколько общих замечаний:
  • На странице нет ничего, кроме заголовка, вставки двух компонентов OWC - Chart и Data Source - и, естественно, script кода.
  • При открытии страницы в Internet Explorer будет вызываться обработчик этого события - процедура Window_onLoad. В ней и выполняется нужная нам работа по связыванию базы данных и компонентов.
  • На первом шаге работы этой процедуры DSC связывается с базой данных и получает от нее данные. На втором шаге - вызывается процедура BindChartToDsc, строящая диаграмму, источником данных для которой уже служит сам DSC.

  • Говоря о работе DSC, можно отметить:
  • С использованием свойства этого компонента - ConnectionString - устанавливается связь с базой данных.


    DSC.ConnectionString = "provider=microsoft.jet.oledb.4.0; data source=" & sDBPath
    Соответствующая строка, как обычно, состоит из двух частей и задает провайдера базы данных и путь к самой базе. В данном случае используется провайдер Jet для связи с базой данных Access.
  • Затем добавляется новый элемент в коллекцию RecordsetDefs, определяющий набор данных - Data Member источника данных. DSC.RecordsetDefs.AddNew myQ, DSC.Constants.dscCommandText, "ChartData"
    Этот набор получает имя ChartData. Команда, определяющая действия по доставке данных, в данном случае задается текстом SQL-запроса к базе данных. Сам текст запроса записан в строку, заданную переменной myQ. В процессе выполнения запроса данные группируются, происходит суммирование стоимости всех заказов, выполненных тем или иным сотрудником.
  • На следующем шаге вызывается процедура BindChartToDsc, строящая диаграмму, которой передается информация о DSC и его наборе данных - ChartData. Замечу еще, что для тех, кто привык работать с ADO, нет ничего нового в синтаксисе и семантике работы с DSC.

  • Несколько слов о работе процедуры BindChartToDsc. Отмечу главное:
    set cspace.DataSource = dsc cspace.DataMember = sRSName
    так устанавливается связь объекта cspace, - основного объекта, задающего пространство диаграммы, - с источником данных и набором данных внутри этого источника. Остальные операторы внутри процедуры выполняют рутинную работу по формированию различных областей диаграммы.
    Надеюсь, этот пример позволяет ощутить возможности компонента DSC и его роль в получении данных от различных источников с последующей их передачей другим компонентам OWC.

    Сортировка, фильтрация, поиск

    Как я уже говорил, Excel является многофункциональным приложением, и над данными, располагаемыми на его страницах, можно выполнять самые разнообразные операции. Компонент SpreadSheet полной функциональностью Excel, конечно, не обладает; тем не менее, ряд важных дополнительных функций включен в его состав.
  • Сортировка. Сортируемые данные должны представлять список, - столбцы списка должны иметь заголовки, все элементы столбца иметь один и тот же тип, не допускаются пропуски элементов (пробелы). Разрешена сортировка данных, как в возрастающем, так и в убывающем порядке. Можно поочередно сортировать данные по нескольким столбцам, начиная со столбца с наиболее низким приоритетом.
  • Фильтрация. Фильтрация означает отбор данных списка, удовлетворяющих определенным критериям. В Excel разрешена фильтрация списка на месте - AutoFilter и расширенная фильтрация с возможностью задания сложных критериев и размещением результатов на другом месте. У компонента SpreadSheet допустим только один вариант - AutoFilter, и критерии фильтрации существенно упрощены в сравнении с аналогичным вариантом Excel. Замечу, что спрятанные фильтром данные не сортируются, не копируются и не перемещаются. Они как бы отсутствуют до той поры, пока не будет снят фильтр.
  • Поиск. Поиск можно вести как во всей электронной таблице, так и в выделенной области, задав соответствующее условие поиска в виде строки текста.


  • Создание интерактивных сводных таблиц в Excel

    Самый простой способ создать интерактивную Web-страницу, содержащую сводную таблицу, - использовать для этой цели Excel. Здесь давно существовала возможность создать сводную таблицу по данным реляционной базы данных, например, Microsoft Access или Microsoft SQL Server. В Excel 2000 в процессе создания сводной таблицы можно параллельно создать и OLAP - куб, который и будет служить источником данных для этой таблицы. После того, как сводная таблица в Excel получена, ее можно сохранить в виде интерактивной Web-страницы, на которой будет помещен компонент PivotTable. При публикации можно присоединить и сводную диаграмму. Замечу, что фактически на Web-странице Excel разместит два компонента - PivotTable и Data Source. Источником данных для компонента Data Source может служить, например, OLAP-куб, а уж затем данные будут переданы компоненту PivotTable. Взгляните, как выглядит подобная Web-страница со сводной таблицей, открытая в Internet Explorer:

    Создание интерактивных сводных таблиц в Excel

    увеличить изображение
    Рис. 10.11.  Интерактивная Web-страница со сводной таблицей

    Список полей

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

    Ссылки

    В формулах Excel могут использоваться как внутренние, так и внешние ссылки. Компонент Spreadsheet не поддерживает внешних ссылок. Более того, поскольку каждый рабочий лист сохраняется как независимая Web-страница, то ссылки на ячейки других рабочих листов той же книги считаются внешними и не поддерживаются. В самом приложении Excel ссылки между страницами одной книги считаются внутренними.
    Что касается внутренних ссылок, то для них могут использоваться различные форматы - абсолютные адреса ($A$1) и относительные - (A1). Формат относительных ссылок RC, задающий смещение по строкам и столбцам (Row - Column), считается устаревшим и не поддерживается. Использование в формулах именованных диапазонов также, к сожалению, не поддерживается. При сохранении листа Excel именованные диапазоны преобразуются в абсолютные адреса, поэтому уже написанные формулы будут, конечно, работать, но написать новую формулу в интерактивном режиме, содержащую именованный диапазон, не удастся. Поддержку именованных диапазонов можно обеспечить лишь программным путем. Не поддерживается и обновление Web-запросов.

    Свойства объекта

    Объект SpreadSheet.WorkSheet большинство своих свойств унаследовал от одноименного объекта в стандартной модели Excel. Поэтому мне остается их только перечислить, а за описанием можно обратиться к главе 11, где рассматриваются объекты Excel, в том числе и объект WorkSheet. Вот краткая характеристика подобных свойств объекта:
  • Cells, Columns, Rows, Range - свойства, возвращающие одноименные объекты. Заметьте, этими свойствами обладает и корневой объект SpreadSheet. Упомяну еще одно свойство - ViewableRange, общее для этих двух объектов, которое задает область, допустимую для просмотра. Пример использования этого свойства приводился.
  • AutoFilter, AutoFilterMode, EnableAutoFilter, FilterMode, EnableAutoCalculate, Parent, UsedRange. Большинство свойств этой группы предназначено для работы с фильтрами. Чуть выше в одном из разделов этой главы я останавливался на особенностях фильтрации данных при работе с объектом SpreadSheet.

  • Рассмотрю теперь свойства, имеющие некоторую специфику:
  • Protection - свойство, возвращающее объект Protection, с помощью свойств которого и устанавливается защита. Свойство Enabled объекта Protection устанавливает, включена ли защита или нет. Если она включена, то остальные булевы свойства определяют, что именно защищается. Например, свойство AllowSorting позволяет задать допустимость сортировки данных в электронной таблице, а свойства AllowDeletingRows и AllowInsertingRows - удаление и вставку строк таблицы.
  • FreezePanes - булево свойство, имеющее статус "только для чтения", возвращает значение True, когда электронная таблица содержит "замороженные" подокна.
  • VisibleRange - свойство, возвращающее объект Range, содержащий видимую область электронной таблицы. Заметьте, видимая область может не совпадать с областью, допустимой для просмотра, возвращаемой при вызове свойства ViewableRange.


  • Типы диаграмм

    Также как, во многом, мощь машины вычислений Excel определяется разнообразием функций, составляющих ее библиотеку, так мощь визуализации определяется разнообразием возможных типов диаграмм. Элемент Chart сохранил это многообразие, отказавшись, правда, от объемных, трехмерных изображений, - такие диаграммы будут представляться в обычном двухмерном изображении. На мой взгляд, это небольшая потеря. Зато появилась новая возможность - рисовать графики в полярной системе координат. Для визуализации инженерных расчетов такая возможность во многих случаях может быть весьма существенной.

    Возможности OWC

    Набор OWC разработан той же командой, что создавала приложение Excel, и представляет собой компоненты, реализующие основную функциональность Excel. В набор OWC входят четыре компонента:
  • Spreadsheet. Этот элемент реализует функциональность Excel, как электронной таблицы. Он включает:
  • Машину вычислений, - при изменении значений в одной или нескольких ячейках электронной таблицы вся таблица может быть подвергнута пересчету.
  • Полную библиотеку функций - формулы в ячейках могут ссылаться почти на все функции Excel.
  • Возможность сортировки, фильтрации и поиска данных.
  • Сохранение многих свойств Excel. Так, например, можно закрыть для изменения всю таблицу, разрешив для интерактивного обмена лишь ограниченное число ячеек - параметры управления.
  • Дополнительные по сравнению с Excel свойства, позволяющие организовать взаимодействие с контейнером. В частности, имеется возможность организации обмена данными между электронной таблицей и остальной частью Web-страницы. Возможны взаимные ссылки между ячейками интерактивной электронной таблицы и элементами управления, размещаемыми на Web-странице, например текстовыми полями. В конечном счете, это позволяет передать на сервер итоговые результаты проделанных вычислений.
  • Pivot Table. Функциональность этого компонента включает свойства сводной таблицы Excel с рядом полезных дополнений. Этот элемент позволяет:
  • Производить свертку и развертывание сводной таблицы.
  • Производить динамическую фильтрацию, сортировку и группирование данных.
  • Производить автоматический подсчет сумм.
  • Строить диаграмму, динамически отражающую изменения в таблице.
  • Chart. Этот компонент позволяет не только просматривать график или диаграмму как статический рисунок, но, благодаря связи с источником данных, мгновенно отображать на диаграмме изменения, происходящие с данными источника. Также как и остальные Web-компоненты, объект Chart является полностью программируемым и допускает обращение к его свойствам и методам при выполнении VBScript-кода в Internet Explorer.
  • Data Source. Этот компонент не имеет визуального образа при размещении его на Web-странице. В отличие от остальных трех элементов он не играет самостоятельной роли. Обеспечивая соединение с источником данных с последующей доставкой данных, этот элемент играет роль посредника между источником данных и другими компонентами OWC.


  • Ввод данных

    Ввод данных в электронную таблицу может осуществляться разными способами.
  • Публикация рабочей книги Excel. Когда происходит публикация рабочего листа Excel или его части в виде интерактивной Web-страницы, то создается компонент SpreadSheet с уже заполненным содержанием, источником которого является рабочий лист Excel. Это один из основных способов первоначального ввода данных, применяемый при работе с офисными документами.
  • Непосредственный ввод данных. Данные можно вводить в ячейки электронной таблицы в процессе интерактивной работы пользователя с компонентом SpreadSheet. Замечу, что эти данные сохраняются только на момент сеанса и, если не предпринять специальных мер, будут утеряны при закрытии Web-страницы.
  • Вставка данных из буфера. Данные, находящиеся в буфере, могут быть вставлены в нужную область электронной таблицы нашего компонента. Заметьте, в буфер данные могут попадать в процессе работы с разными приложениями. Таким путем можно перенести в электронную таблицу компонента SpreadSheet, например, таблицу документа Word, не говоря уже о данных различных областей рабочих листов Excel.
  • Вставка данных, находящихся в интрасети. Конечно, документы, используемые для этой цели, должны содержать соответствующие таблицы или списки данных со специальными разделителями. Этот способ требует программной работы с объектом SpreadSheet. Замечу, что объект SpreadSheet среди многих других свойств имеет свойство HTMLURL, которое содержит URL, указывающий источник данных для загрузки. URL должен возвращать HTML-документ, в котором есть, по крайней мере, одна таблица.
  • Ввод данных непосредственно из контейнера. Понятно, что важно иметь возможность обмениваться данными с контейнером, - как принимать от него данные, так и передавать ему данные. В этом случае обеспечивается нормальный способ сохранения результатов работы, выполненных в очередном сеансе. Я продемонстрирую эту возможность на небольшом примере. В опубликованный документ, приведенный на рис. 10.1, я добавил поле ввода. Вот соответствующий HTML-текст:

    При просмотре этого документа в Internet Explorer я ввел в это поле текст "Привет от компонентов OWC!". В одну из ячеек электронной таблицы ввел формулу:
    =Document.myTextField.Value
    Заметьте, здесь используется возможность работы в ячейках электронной таблицы с объектом "Document", задающим контейнер, в данном случае - Web-страницу, просматриваемую в Internet Explorer. Зная ID элементов, располагаемых на этих страницах, можно добраться до их свойств, - в данном примере до свойства Value поля ввода. Надеюсь, понятно, что значением данной формулы будет текст, введенный в поле ввода.


  • Вывод данных

    Операции ввода-вывода данных симметричны. Почти каждому способу ввода данных соответствует свой способ вывода:
  • Экспорт в Excel. Созданную или модифицированную в процессе сеанса работы электронную таблицу можно экспортировать в Excel, создав в момент экспорта новую рабочую книгу. Выполняется эта операция одним щелчком кнопки экспорта данных, о которой я говорил при описании интерфейса элемента SpreadSheet. Очевидно, эта операция является обратной к операции публикации страниц рабочей книги Excel. Экспорт - это один из основных способов сохранения результатов работы с офисными документами в сети.
  • Копирование данных в буфер. При этом данные, перенесенные в буфер, могут затем использоваться в процессе работы с разными приложениями. Таким путем можно перенести часть или всю электронную таблицу компонента SpreadSheet, например, в таблицу документа Word, или в заданную область рабочего листа Excel.
  • Вывод данных непосредственно в контейнер. Я продемонстрирую эту возможность на небольшом примере. В нем данные из некоторой области компонента SpreadSheet переносятся в таблицу, создаваемую на Web-странице. Вот соответствующий код, обеспечивающий эту работу:
  • Итоги продаж Иванов Петров Сидоров

    Надеюсь, Вы понимаете, что я продолжаю работать с документом, приведенным на рис. 10.1. В данном случае в нем создается новая таблица, источником данных для которой является наш компонент SpreadSheet. В параметре таблицы datasrc указывается ID компонента, после точки следует адрес области данных. Префикс # указывает, что источник данных находится на той же странице, что и строящаяся таблица. Вот как выглядит построенная таблица:
    Вывод данных

    увеличить изображение
    Рис. 10.3.  Таблица, построенная по данным компонента SpreadSheet

    XML-источник данных

    В качестве источника данных для компонента Pivot Table можно указать адрес в Internet - URL, который возвращает XML-поток данных. Конечно, данные этого потока должны удовлетворять определенным требованиям. Microsoft разработала стандарт, которому должен удовлетворять XML-поток данных, и провайдера - OLE DB persistence provider, который способен принимать данные в этом формате. В главе, посвященной ADO, упоминался среди прочих и этот провайдер данных.

    Мир объектов Excel 2000

    Как Офис РР организовал обратную связь с читателями

    Вернемся снова к нашему Офису РР, который неоднократно встречался в наших примерах, и посмотрим, как он использует интернет и Web-программирование при разработке своих документов. Конечно же, Офис РР давно уже имеет интернет-магазин для продажи своих книг, более того, он дает возможность своим читателям присылать отзывы и выставлять оценки приобретенным книгам. Чтобы связь с читателями была двусторонней, на страницах узла нашего офиса, читатели могут задавать вопросы авторам, переводчикам и редакторам выпускаемых книг, получать ответы на свои вопросы. Читатели могут также ознакомиться с отзывами, присланными на книги и просмотреть весь спектр выставленных оценок.
    Давайте рассмотрим, как можно организовать обратную связь с читателями с помощью упомянутых мной средств. При этом я основное внимание сосредоточу на вопросах программирования, оставляя в стороне вопросы дизайна и многие другие вопросы, которые возникают в процессе создания коммерческого решения.
    Для тех, кто хочет ознакомиться с реально работающим подобным узлом, советую зайти на узел издательства "Русская Редакция", - www.rusedit.ru. Там, правда, нет возможности в настоящее время задавать вопросы и получать ответы, но возможность заказать нужную книгу и написать отзыв имеется.

    Как придать структуру программному коду

    Я говорил о том, что одной из трудностей Web-программирования в нынешних условиях, является слабая структурированность программного кода, отсутствие столь привычного модульного построения программных проектов. Чтобы как-то справиться с этой проблемой, я для себя создал определенные правила. Приведу их, может быть, они будут полезны и другим:
  • Декларативная часть кода, исполняемого на сервере, записывается в заголовочной части HTML-текста страницы - в конец тега . Декларативной части предшествует специальный комментарий. Вы могли видеть его в тексте приведенного кода.
  • Декларативная часть кода, исполняемого на клиентской стороне, также записывается в заголовочной части сразу после серверного кода, если таковой присутствует. Этому фрагменту кода также предшествует специальный комментарий.
  • Исполняемая часть серверного кода, снабженная комментарием, записывается в начале тега .
  • Исполняемая часть клиентского кода отсутствует. Инициируют выполнение программного кода на стороне клиента процедуры - обработчики событий.

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

    Код, исполняемый на сервере

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


    Ну а теперь, как всегда, некоторые комментарии к этому коду. Работа с базой данных будет вестись, как и положено, через хорошо знакомые объекты ADO. Глобальные переменные Con1, Cmd1, Rst1, задающие эти объекты, уже не один раз встречались в наших примерах. Это же касается и процедуры CreateConnection, устанавливающей соединение с базой данных. В процедуре CreateRstBooks создается набор записей, содержащий данные о книгах. Подобные процедуры также встречались неоднократно при рассмотрении объектов ADO. На одно обстоятельство хочу обратить внимание. Для получения информации из базы данных используется хранимый запрос с именем "список книг". Вот его SQL-текст:

    SELECT Книги.Автор, Книги.Название FROM Книги ORDER BY Книги.Автор;

    При работе с базами данных на Web-страницах рекомендую использовать всюду, где можно, хранимые процедуры. В базе данных Microsoft Access, с которой я работаю во всех примерах данной книги, роль хранимых процедур играют хранимые запросы. Помимо многих других преимуществ такой способ хорош еще и тем, что надежно работает. В то же время при использовании других приемов работы с объектами ADO зачастую возникали ошибки там, где их не должно было бы быть.

    Процедура CreateOptions по данным полученного набора записей программно создает интерфейсный объект - раскрывающийся список и определяет элементы этого списка. Если говорить более точно, то процедура динамически дописывает к имеющемуся HTML-коду страницы новый фрагмент HTML-кода, определяющий тег Select. Метод Write серверного объекта Response выполняет эту задачу. Когда браузер получит страницу от сервера и начнет выполнять присланный HTML-код страницы, то он создаст в дополнение к элементам определенным при проектировании страницы, и динамически созданный сервером элемент SelectBook.

    Мы рассмотрели декларативную часть серверного кода. Теперь давайте взглянем на исполняемый код. Он небольшой и сводится к инициализации переменных и вызову рассмотренных нами процедур. Вот он:



    Из этого кода хочу обратить внимание на оператор, создающий объект Session("UserId"). Реально этот объект создается на начальной индексной странице при регистрации пользователя. Поскольку я не собираюсь описывать индексную страницу, то решил промоделировать ее работу, введением этого оператора, явным образом создавая объект Session и присваивая конкретное значение Id пользователю, работающему со страницей.

    Разбор кода, выполняемого на серверной стороне, закончен, и можно переходить к коду, выполняемому на стороне клиента. Возможно, следовало бы привести полный HTML-код страницы, передаваемой клиенту, но я ограничусь только программным кодом.


    Как видите, это запрос с пятью параметрами. Через параметры полям записи передаются - сам вопрос, дата задания вопроса, название и автор книги, по которой был задан вопрос и идентификатор пользователя, задавшего вопрос.

    Понятно, что в функции SaveQuestion, вызывающей этот запрос, при формировании объекта ADODB.Command главной задачей является корректное формирование коллекции Parameters этого объекта. Дальше остается только вызвать метод Execute, чтобы выполнить запрос. Поскольку в главах, посвященных объектам ADO, я подробно рассматривал все свойства и методы этих объектов, в том числе работу с коллекцией Parameters, то теперь останавливаться на деталях, думаю, нет необходимости.

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

    Код, исполняемый на сервере

    Рис. 11.6.  Страница qomToBase с уведомлением об успешном завершении операции

    Код, исполняемый на стороне клиента

    И на клиентской стороне можно выделить декларативную и исполняемую часть кода. Заметьте, я предпочитаю, чтобы на серверной стороне исполняемая часть кода присутствовала, в то же время на клиентской стороне ее может не быть. Более того, при хорошем программировании, полагаю, ее не должно быть, как не будет ее и в нашем примере. Причины этого, надеюсь, понятны. Дело в том, что обработка событий на стороне сервера хотя и возможна, но не представляется столь желательной. Поэтому исполняемый участок кода и предназначен для инициирования вычислений. Конечно, достаточно одного оператора, вызывающего первую процедуру, чтобы начать сложные вычисления с многочисленными вызовами процедур, представленными в декларативной части кода. При хорошем стиле программирования так, обычно, и бывает.
    Программирование на клиентской стороне в большей степени является программированием, управляемым событиями. Это и понятно - здесь расположены элементы управления, с которыми работает пользователь, а именно он, его действия при работе с документом являются источником возникновения событий. Поэтому основу декларативной части клиентского кода составляют, как правило, процедуры-обработчики событий, возникающих с интерфейсными объектами и другими объектами, расположенными на странице. По этой причине нет необходимости в исполняемом фрагменте кода, хотя он и может присутствовать. Но более естественно для данного стиля программирования для инициирования вычислений иметь процедуру, обрабатывающую событие Load, которая автоматически будет вызываться при открытии страницы. Когда же пользователь начнет работать, то будут вызываться и другие обработчики событий. А теперь взгляните на декларативный код клиентской части нашей страницы. Конечно же, он включает процедуры-обработчики различных событий, возни
    m° кающих с объектами страницы. Вот этот код:


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

    Когда на клиентской стороне открывается страница, возникает событие Load объекта Window и вызывается соответствующая процедура. Действие ее достаточно просто - она обращается к процедуре FromSelectToHiddenFields, которая значение элемента SelectBook преобразует в название книги и фамилию автора и заполняет этими данными скрытые поля формы.

    Эта же процедура вызывается и в обработчике события Change объекта SelectBook. Само же событие возникает тогда, когда пользователь выберет новую книгу из списка. Понятно, что в этот момент следует и обновить значения полей формы.

    Комментировать работу процедуры FromSelectToHiddenFields вряд ли имеет смысл - обычная работа с объектами и строковыми переменными.

    Следующие три процедуры в этом тексте - это обработчики события Click для трех объектов, задающих разные командные кнопки. Напомню, что ожидаемое действие при нажатии этих кнопок аналогично действию при нажатии кнопки Submit - должна быть вызвана некоторая страница узла и этой странице должны быть переданы параметры, необходимые для выполнения на сервере запрашиваемого действия. Все три обработчика устроены одинаково - вызывают процедуру CallNextPage, передавая ей в качестве параметра имя нажатой кнопки.

    Процедура CallNextPage обеспечивает связь с сервером. Вызов метода Navigate объекта Window позволяет достаточно просто решить требуемую задачу. Метод позволяет задать вызываемую страницу сервера и передать ей параметры. Заметьте, вызывается страница qomFromBase, которая предназначена для того, чтобы по запросу пользователя читать данные из базы данных и отображать их в нужном виде на клиентском компьютере. Три параметра передаются этой странице - имя нажатой командной кнопки, фамилия автора и название книги. Первый параметр определяет, по сути, запрос пользователя - хочет ли он просмотреть по выбранной им книге ответы на его вопросы, все вопросы и ответы или все отзывы и оценки.

    Вот и весь программный код, обеспечивающий интерактивность нашей странице. Я рассмотрел создание интерфейса страницы и создание кода, поддерживающего этот интерфейс. На этом я заканчиваю рассмотрение вопросов создания основной страницы нашего узла. Думаю, что она является достаточно типичным примером интерактивной Web-страницы - примером страницы, в которой пользователь запрашивает необходимую ему информацию. Чтобы пример был законченным, нужно еще рассмотреть, как устроены страницы qomToBase и qomFromBase, организующие реакцию на запросы пользователя. Но прежде, чем перейти к их рассмотрению, хочу сказать несколько слов о том, как я придаю структуру программному коду.

    Принципиальные особенности Web-программирования

    Назову три момента, характерные для Web-программирования:
  • "Многомерный" код.
  • Слабая структурированность.
  • Специфика отладки.

  • Что я имею в виду, когда говорю о "многомерности" кода? Дело в том, что код, создаваемый программистом, всегда представляет собой линейный текст. Однако исполнение написанного кода в Web-документе, чаще всего, будет разнесено в пространстве и времени. Как правило, у кода будет два исполнителя - Web-сервер на серверной стороне и Обозреватель на клиентской. Прежде чем страница документа будет послана обозревателю, ее текст читается Web-сервером, который находит относящиеся к нему части кода и выполняет его. Страница с результатами этой работы передается по сети клиенту, и там уже обозреватель, установленный на клиентском компьютере, выполняет свою часть работы, исполняя предназначенный ему код и отображая страницу на экране. После этого за дело берется пользователь, работающий за клиентским компьютером, - он выбирает нужные ему данные из списков, заносит требуемую информацию в поля ввода, нажимает командные кнопки и выполняет доступные команды меню. В ходе обработки возникающих событий обозреватель, как правило, обращается к серверу за получением очередной страницы документа, передавая ему одновременно информацию о том, какой должна быть эта страница в соответствии с запросами пользователя. Сервер находит нужную страницу, выполняет относящийся к нему код с учетом полученной им информации. Далее процесс повторяется. Напомню, что в роли Web-сервера я рассматриваю IIS, в роли обозревателя - Internet Explorer, а страницы, выполняемые на серверной стороне, являются ASP-страницами.
    Сделаю еще несколько замечаний по поводу деталей этого процесса:
  • В ходе работы, выполняемой на серверной стороне, зачастую создается код, который должен выполнять обозреватель по получению страницы. Так что программирование на стороне сервера часто напоминает "программирование на лету".
  • Хотя IIS и IE оба понимают VBScript, но объекты, доступные им, разные. У сервера свой набор объектов, обозревателю же доступна объектная модель отображаемой страницы с центральными объектами Window и Document.
  • Важным моментом во всем этом процессе является организация передачи нужной информации от обозревателя к серверу и корректный анализ полученной информации на серверной стороне.
  • "Многомерность" кода связана не только с тем, что его текст выполняется двумя различными исполнителями. Когда код выполняется на клиентской стороне, то и здесь не все так просто. Дело в том, что код, исполняемый обозревателем, представляет собой смесь HTML-кода и VBScript-кода. Если быть более точным, то родным языком для обозревателя является HTML-код. Возможность выполнять сценарии, то есть исполнять VBScript-код, появилась позже. Для Internet Explorer такой код формально является HTML-кодом, представленным тегом
    Как можно видеть, этот фрагмент практически совпадает с исполняемым кодом предыдущей страницы. Хотя вызываемые процедуры сохранили свои имена и назначение, но имеют, конечно, другое содержание. Поскольку информация на страницу передавалась не так как в предыдущем случае, то и процедура InitVars использует другие методы для извлечения этой информации. Вот код этой процедуры:
    Sub InitVars() Set Con1=Server.CreateObject("ADODB.Connection")
    pUser=Session("UserId") pUser =CLng(pUser) pAction = Request.QueryString("btnName") pAuthor =Request.QueryString("Author") pTitle =Request.QueryString("Title") End Sub 'InitVars

    Основное отличие этой процедуры от своего аналога состоит в том, что используется метод QueryString объекта Request, а не метод Form, как ранее.

    Одноименная процедура SelectAndExec, сохраняя свое назначение, также в ряде деталей отличается от своего аналога:

    Sub SelectAndExec() Select Case LCase(pAction) Case "lookallq" LookQA_All Case "lookmyq" LookQA_my(pUser) Case "lookom" LookOM 'Case Else Response.Write "Else" End Select Response.Write "" & _ " Вернуться " Response.Write "на страницу Читатели" End Sub 'SelectAndExec

    Здесь в операторе Select анализируется, какие данные о книге желает получить пользователь и в зависимости от этого вызывается одна из трех процедур - LookQA_All, LookQA_my, LookOM, которые соответственно позволяют просмотреть ответы на все вопросы, вопросы конкретного пользователя, отзывы и оценки по книге. Поскольку все эти процедуры принципиально устроены одинаково, то я ограничусь рассмотрением первой из них.

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

    Sub LookQA_All() 'Показ всех вопросов 'Получение данных из базы данных CreateConnection 'Получение набора записей CreateRSAllQ 'Перепись данных в таблицу на html-странице strTitle = "Вопросы и ответы к книге '" & pAuthor & _ ":" & pTitle & "'" CreateTHead(strTitle) 'Заголовок таблицы CreateTBody 'Тело таблицы End Sub

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

    Sub CreateRSAllQ() 'Создание команды и набора записей с вопросами и ответами Dim par 'Параметр запроса Set Cmd1=Server.CreateObject("ADODB.Command") Set Rst1=Server.CreateObject("ADODB.Recordset") With Cmd1 'Конфигурирование объекта Command .ActiveConnection = Con1 .CommandText ="QAAll" .CommandType = 4 'adCmdStoredProc Set par= .CreateParameter("repAuthor",202,1,255) par.value=pAuthor .Parameters.Append par Set par= .CreateParameter("repTitle",202,1,255) par.value=pTitle .Parameters.Append par 'Формирование набора записей Rst1.Open Cmd1 End With End Sub 'CreateRSAllQ


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

    PARAMETERS repAuthor Text ( 255 ), repTitle Text ( 255 ); SELECT QA.Question, QA.QDate, QA.Answer, QA.ADate FROM QA WHERE (((QA.Author)=[repAuthor]) AND ((QA.Title)=[repTitle]));

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

    Рассмотрим теперь, как результаты запроса, представленные полученным в процедуре CreateRSAllQ набором записей Rst1, преобразуются в таблицу, отображаемую на экране пользователя. Конечно, для "красивого" отображения необходим хороший дизайн, а, следовательно, и более глубокое знание языка HTML. Я приведу тексты процедур, решающих эту задачу, но особых комментариев давать не стану, хотя знание HTML и XML - это неотъемлемая часть знаний, необходимых Web-программисту.

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

    Sub CreateTHead(Tit) 'Организация заголовка таблицы вопросов и ответов Response.Write "" Response.Write "" 'Организация заголовков столбцов таблицы Response.Write "" Response.Write "" Response.Write "" Response.Write "" Response.Write "" Response.Write "" Response.Write "" Response.Write "" End Sub 'CreateTHead


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

    В следующей процедуре создаются записи этой таблицы. Число строк таблицы, естественно определяется числом записей в наборе, полученном по результатам запроса. Приведу код процедуры CreateTBody:

    Sub CreateTBody() 'Организация тела таблицы вопросов и ответов While Not Rst1.EOF Response.Write "" Response.Write "" Response.Write "" If Rst1.Fields("Answer").ActualSize=0 Then Response.Write "" Else Response.Write "" End If If Rst1.Fields("ADate").ActualSize=0 Then Response.Write "" Else Response.Write "" End If Response.Write "" Rst1.MoveNext Wend Response.Write "
    " & _ "" & _ Tit & "
    Вопрос" Response.Write "Дата вопроса" Response.Write "Ответ" Response.Write "Дата ответа" Response.Write "
    " & _ Rst1.Fields("Question").Value & "" & _ Rst1.Fields("QDate").Value & "" & _ " Пока ответа нет!" & _ Rst1.Fields("Answer").Value & "" & _ Date & "" & _ Rst1.Fields("ADate").Value & "
    " End Sub 'CreateTBody

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

    Страница qomFromBase - страница, где информация, необходимая пользователю, читается из базы данных

    увеличить изображение
    Рис. 11.7.  Страница с вопросами и ответами, выводимая по запросу пользователя

    На этом я заканчиваю описание примера, главы, книги и всей серии "Офисное программирование".

    Страница qomToBase - страница, где информация пользователя записывается в базу данных

    Напомню, эта страница вызывается после нажатия кнопки с заголовком "Послать вопрос и/или отзыв и/или оценку". Кнопка имеет статус Submit. Одновременно странице передаются данные о заданном пользователем вопросе, сделанном отзыве, проставленной оценке, также как и данные о книге, с которой связаны эти характеристики. Серверный код на этой странице должен обеспечить прием параметров и запись полученной информации в таблицы базы данных. Должен быть сформирован HTML-код, уведомляющий пользователя о результатах выполнения его запроса.
    Я решил на этапе проектирования оставить эту страницу пустой - ни интерфейсных объектов, ни даже простого текста на этой странице нет. Все, что на ней появится, когда она откроется на клиентском компьютере, будет определяться результатами выполнения серверного кода. Поэтому, если открыть эту страницу во FrontPage на вкладке Normal, то все будет пусто. Но серверный код в ней, конечно же, присутствует.

    Страница Qpage - страница читателей

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

    

        Базы данных: Разработка - Управление - Excel