Основы офисного программирования и документы Word

         

Анализ свойств проекта


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

Листинг 4.1.

(html, txt)

Сама программа достаточно понятна. В ее первой части я в отладочном режиме печатаю число открытых проектов и в цикле по коллекции VBProjects для каждого из проектов печатаю значения его основных терминальных свойств. Обратите внимание на два обстоятельства. В двух первых строчках программы дважды описана переменная MyProject один раз как VBProject, другой - как Object. Это не случайно. Когда я пишу текст программы, я пользуюсь первым из этих объявлений, задавая раннее связывание. Тогда я получаю нужные мне подсказки о свойствах и методах этого объекта и вложенных в него объектов. Без этих подсказок жизнь программиста осложняется. Когда же я перехожу на выполнение программы, то приходится пользоваться вторым вариантом объявления, поскольку из-за недоработки в Office 2000 нормальная работа с этим объектом в документах Word невозможна, хотя в Excel работают и явные объявления типа VBProject. Заметьте также, что обращение к некоторым свойствам закомментированы. Это связано с тем, что не все заявленные новые свойства и методы реально работают в Office 2000. Следует отметить еще один небольшой, но досадный "жучок", проявляющийся в этом месте. При появлении подсказки ряд существующих свойств и методов не показывается, хотя они фактически работают, например, не показывается свойство Protection и Mode.

Списки свойств и методов объекта VBProject, но и не только этого объекта, просматриваемые в трех разных местах в справочной системе, в окне просмотра (браузере объектов) и при появлении подсказки в момент программирования, к сожалению, могут не совпадать.

Во второй части этой процедуры приводятся некоторые свойства коллекции AddIns - GUID и описание каждого элемента.

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


Листинг 4.2.

(html, txt)

Как и положено, при двух открытых документах коллекция VBProjects содержит три проекта. Проекту моего тестового документа в свое время я дал имя (DocOneProject) и указал для него описание. О коллекции AddIns я уже говорил, но, возможно, Вам будет интересно взглянуть на их полное описание.

Документы и проекты с объектной точки зрения


Объект Document имеет свойство VBProject, возвращающее объект, задающий программный проект данного документа. С другой стороны, объект VBProject является элементом коллекции VBProjects, содержащей проекты всех открытых документов. Коллекция VBProjects, в свою очередь, вложена в объект VBE. Поэтому давайте поднимемся на несколько этажей выше проекта. Я напомню, что корневым объектом, содержащим все объекты VBA, является объект VBE (Visual Basic Environment или Editor). У этого объекта есть только свойства, позволяющие получить доступ к объектам VBA, лежащим на нижних уровнях иерархии. В справочной системе приведена следующая объектная модель VBE:


Рис. 4.1.  Модель объекта VBE

Эта модель далеко не полная. Она отражает только коллекции, входящие на верхнем уровне в объект VBE. Рассмотрим коротко эти коллекции:

Конечно, основной является коллекция проектов - VBProjects. Эту коллекцию составляют все проекты открытых документов приложения. Заметьте, если одновременно открыто N документов Word, то коллекция VBProjects будет содержать не менее чем N+1 элемент, по одному проекту на документ плюс общий для всех проект Normal. Заметьте также, проекты разных приложений, например, Excel и Word входят, к сожалению, в разные коллекции, что затрудняет программную работу в проекте документа Word с проектом Excel. Совместная работа с разными проектами внутри одного приложения не вызывает особых трудностей. Достаточно просто иметь общие переменные, вызывать общие процедуры.О том, как создать собственные надстройки - AddIn, разговор пойдет в последующих лекциях. Сейчас же замечу, что коллекция надстроек, доступных в приложении Word 2000, не пуста. В настоящий момент у меня на компьютере доступны восемь таких надстроек, среди них WinApiViewer, AddIn для построения строк, задающих SQL-операторы, AddIn, реализующий множественный импорт- экспорт объектов за одну операцию и другие.Коллекция окон Windows содержит объекты, задающие окна в Редакторе Visual Basic. Восемь окон входит в эту коллекцию, - окно проекта и окно кода, окно отладки, окно локальных переменных и другие.Коллекция CodePanes содержит подокна, задающие активные окна кода, обычно таких открытых окон в процессе работы одно, но может быть и несколько ведется параллельная отладка нескольких проектов.Коллекция CommandBars содержит, как правило, несколько десятков элементов, задающих инструментальные панели в среде Редактора VBA.


Объект Document имеет свойство VBProject, возвращающее объект, задающий программный проект данного документа. С другой стороны, объект VBProject является элементом коллекции VBProjects, содержащей проекты всех открытых документов. Коллекция VBProjects, в свою очередь, вложена в объект VBE. Поэтому давайте поднимемся на несколько этажей выше проекта. Я напомню, что корневым объектом, содержащим все объекты VBA, является объект VBE (Visual Basic Environment или Editor). У этого объекта есть только свойства, позволяющие получить доступ к объектам VBA, лежащим на нижних уровнях иерархии. В справочной системе приведена следующая объектная модель VBE:


Рис. 4.1.  Модель объекта VBE

Эта модель далеко не полная. Она отражает только коллекции, входящие на верхнем уровне в объект VBE. Рассмотрим коротко эти коллекции:

Конечно, основной является коллекция проектов - VBProjects. Эту коллекцию составляют все проекты открытых документов приложения. Заметьте, если одновременно открыто N документов Word, то коллекция VBProjects будет содержать не менее чем N+1 элемент, по одному проекту на документ плюс общий для всех проект Normal. Заметьте также, проекты разных приложений, например, Excel и Word входят, к сожалению, в разные коллекции, что затрудняет программную работу в проекте документа Word с проектом Excel. Совместная работа с разными проектами внутри одного приложения не вызывает особых трудностей. Достаточно просто иметь общие переменные, вызывать общие процедуры.О том, как создать собственные надстройки - AddIn, разговор пойдет в последующих лекциях. Сейчас же замечу, что коллекция надстроек, доступных в приложении Word 2000, не пуста. В настоящий момент у меня на компьютере доступны восемь таких надстроек, среди них WinApiViewer, AddIn для построения строк, задающих SQL-операторы, AddIn, реализующий множественный импорт- экспорт объектов за одну операцию и другие.Коллекция окон Windows содержит объекты, задающие окна в Редакторе Visual Basic. Восемь окон входит в эту коллекцию, - окно проекта и окно кода, окно отладки, окно локальных переменных и другие.Коллекция CodePanes содержит подокна, задающие активные окна кода, обычно таких открытых окон в процессе работы одно, но может быть и несколько ведется параллельная отладка нескольких проектов.Коллекция CommandBars содержит, как правило, несколько десятков элементов, задающих инструментальные панели в среде Редактора VBA.

Помимо пяти коллекций в объект VBE вложено большое число других объектов, не являющихся коллекциями. Отмечу среди них лишь некоторые объекты.

Объект Events напоминает коллекцию. Каждое из его свойств возвращает объект, тип которого совпадает с именем свойства. Возвращаемые объекты обладают определенным набором событий, которые могут быть подключены в создаваемых надстройках Addins, позволяя реагировать на те или иные действия. Большинство событий связано с добавлением, удалением, переименованием объектов в коллекциях. Так свойство ReferencesEvents объекта Events возвращает объект ReferencesEvents, обладающий двумя событиями - ItemAdded, ItemRemoved, возникающими при добавлении или удалении ссылок - элементов коллекции References.

Три Active-свойства объекта VBE возвращают соответствующие активные объекты - ActiveVBProject, ActiveWindow, ActiveCodePane. Похожим является свойство SelectedVBComponent, возвращающее выбранную компоненту проекта




Помимо пяти коллекций в объект VBE вложено большое число других объектов, не являющихся коллекциями. Отмечу среди них лишь некоторые объекты.

Объект Events напоминает коллекцию. Каждое из его свойств возвращает объект, тип которого совпадает с именем свойства. Возвращаемые объекты обладают определенным набором событий, которые могут быть подключены в создаваемых надстройках Addins, позволяя реагировать на те или иные действия. Большинство событий связано с добавлением, удалением, переименованием объектов в коллекциях. Так свойство ReferencesEvents объекта Events возвращает объект ReferencesEvents, обладающий двумя событиями - ItemAdded, ItemRemoved, возникающими при добавлении или удалении ссылок - элементов коллекции References.

Три Active-свойства объекта VBE возвращают соответствующие активные объекты - ActiveVBProject, ActiveWindow, ActiveCodePane. Похожим является свойство SelectedVBComponent, возвращающее выбранную компоненту проекта

Объект VBProject и коллекция VBProjects




В Office 2000 возможности работы с программными проектами документов существенно расширились. У объекта VBProject и коллекции VBProjects в Office 2000 появилось большое число новых свойств и методов. Замечу, что определенную сложность в изучение этого материала вносит то обстоятельство, что в справочной системе справки по многим объектам, связанным с проектами, отсутствуют или, что еще хуже, содержат неполные сведения, не отражающие действительной картины. Так было и в Office 97, такая же ситуация имеет место и в Office 2000.

Скажем несколько слов о коллекции VBProjects. В Office 97 у этой коллекции имелись три простых свойства Count, Parent, VBE и единственный метод - Item. У нее не было методов Add и Delete, так как проекты были прочно связаны со своими документами и удалялись и появлялись одновременно со своими документами. В Office 2000 у этой коллеции появились три метода для добавления проектов в коллекцию Add, AddFromFile, AddFromTemplate, соответственно появился и метод Remove для удаления элементов коллекции. Помимо этого, коллекция имеет методы SaveAs и FileName для сохранения проектов, появились также и новые свойства, в частности свойство StartProject.

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

Имея коллекцию VBProjects, добраться до отдельного проекта нетрудно, делается это обычным для коллекций способом, Можно, конечно, использовать метод Item, но, проще всего, указать индекс элемента в коллекции, при этом роль индекса может играть и имя проекта. Так что спуститься по иерархии сверху вниз нетрудно: VBE.VBProjects(1) дает ссылку на первый проект коллекции. Также просто и от отдельного проекта перейти вверх по иерархии. Объект VBProject имеет свойство Collection, позволяющее получить коллекцию VBProjects, а его свойство VBE позволяет подняться еще выше по иерархии и добраться до корневого объекта VBE; от корня можно уже спуститься в любую заданную точку. Итак, разобрав, как связан объект VBProject с объектами, вышестоящими в иерархии, давайте более подробно остановимся на его свойствах и методах.

В Office 97 объект класса VBProject имел только свойства и не имел методов. О двух свойствах этого объекта Collection и VBE я уже упомянул, они позволяют подняться по иерархии. Свойства Description и Protection позволяют получить описание проекта и определить, защищен ли он. Свойства HelpFile и HelpContextId позволяют указать справочную систему по проекту, если таковая существует. Наиболее важными, пожалуй, свойствами являются свойства VBComponents и References. Свойство VBComponents возвращает коллекцию компонент проекта: модули, классы и формы, входящие в проект. Имея эту коллекцию, можно перейти к соответствующей компоненте и программно работать с ней. Свойство References позволяет получить доступ к коллекции ссылок на элементы, доступные из данного проекта. Элементы этой коллекции соответствуют ссылкам, отображаемым в пункте References меню Tools в среде редактора VB.

В Office 2000 объект VBProject получил несколько новых свойств, например, свойства: CompatibleOleServer, IsDirty, Type. Первое из них возвращает или устанавливает строку, содержащую совместимый сервер Автоматизации для проекта. Булево свойство IsDirty, как обычно для свойства с таким именем указывает на внесение изменений в проект с момента последнего его сохранения. Свойство Type задает тип проекта, указывающий на то, что проект может относиться, например, к ActiveXDLL или ActiveXControl. Объект VBProject теперь имеет и методы. Метод MakeCompiledFile записывает проект в виде библиотеки DLL, имя которой задается новым свойством BuildFileName. Метод SaveAs позволяет сохранить проект в указанном месте.

Изменения в составе свойств и методов объектов VBProject и VBProjects симптоматичны. Они отражают тенденцию к возрастанию потребностей офисных программистов при работе с программными проектами документов.



Объекты программного проекта. Программирование на лету


Всюду, где только можно, я не уставал повторять, как заклинание, что для офисного программиста целью работы является создание документа, частью которого является программный проект, неразрывно связанный с документом. Более того, в серьезных разработках речь всегда идет о создании системы документов, а, следовательно, и о системе программных проектов, связанных с этими документами, так и с помощью ссылок непосредственно друг с другом. Программные проекты в такой системе могут иметь общий пул памяти, общие процедуры, вызываемые из любого проекта. Я хочу рассмотреть отношения между документами и проектами с объектной точки зрения, рассмотреть основной объект VBProject, задающий проект, его компоненты, его связь с объектом Document и коллекцией VBProjects, хочу рассмотреть вопросы программной работы с этими объектами. Всюду в этом тексте, где речь идет о документах, я буду рассматривать документы Word, а, следовательно, с ними связывать программные проекты. Но нужно понимать, что объектная модель проектов практически одна и та же для всех документов Office 2000. Объекты, о которых пойдет речь, являются общими и находятся в общей библиотеке VBIDE.

В отличие от других документов Office документы Word обладают той особенностью, что с каждым из них связаны два проекта. Один - Normal - стандартный проект, общий для всех документов, а второй - отражает специфику документа. Этот второй проект обычно и называют проектом данного документа, и именно о нем пойдет основной наш разговор. Но прежде несколько слов о проекте Normal. Все документы Word открываются по умолчанию на основе шаблона Normal, частью этого шаблона и является, связанный с шаблоном проект Normal. Все макросы, хранимые в проекте Normal, являются доступными для любого из документов Word. Именно сюда может поместить программист все макросы, стандартные модули или модули классов, которые, по его мнению, будут использоваться всеми документами Word на данном компьютере. Нужно, однако, понимать, что при размещении макроса в проекте Normal, речь должна идти действительно только об общезначимых программных инструментах. В остальных случаях все программные компоненты должны быть связаны с проектом конкретного документа или включены в специальную надстройку - AddIn. Замечу, кстати, что иногда помещение программных компонент, чаще всего макросов, в проект Normal происходит по недоразумению, поскольку по умолчанию местом размещения создаваемых MacroRecorder макросов является именно проект Normal, а не проект данного документа. Так что будьте внимательны в таких ситуациях. Но давайте перейдем к рассмотрению объектной модели проекта. Но вначале несколько слов о том, как связаны на объектном уровне документы с проектами и как связаны между собой проекты разных документов.



Организация взаимодействия в системе проектов


Теперь, когда мы умеем программно включать в системе проектов ссылку на новый программный проект, осталось рассмотреть, как обращаться к элементам этого проекта, его общедоступным переменным, объектам и процедурам. Для обращения к элементу проекта, на который есть ссылка, необходимо указать его полное имя, включающее имя проекта, имя модуля и собственное имя элемента. В моем примере я буду вызывать процедуру с одним и тем же собственным именем TestPrint, которая есть в обоих подключаемых проектах DocTwo и DocThree. Чтобы вызвать процедуру того проекта, который подключен в текущий момент, очевидно, необходимо организовать разбор случаев. Это нетрудно сделать, зная имя (имена) подключенных проектов. Здесь есть правда некоторый подводный камень. Дело в том, что в соответствующем операторе Case только имя одного элемента будет определено, того элемента, который принадлежит подключенному проекту, имена вызываемых элементов из других, неподключенных проектов будут неопределенными, более того, считаться необъявленными, что немедленно приведет к обычной синтаксической ошибке. Из этой ситуации есть простой выход. Но прежде, чем рассказать о нем, давайте взглянем на текст соответствующей процедуры, занимающейся разбором случаев:

Public Sub CallProcFromProject() 'Вызов процедуры подключаемого проекта 'Разбор случаев, какой проект подключен If NoP = "DocTwo" Then Proc2 ElseIf NoP = "DocThree" Then Proc3 End If End Sub

Public Sub Proc2() 'Вызов процедуры проекта 2 DocTwo.DocTwoModule.TestPrint End Sub Public Sub Proc3() 'Вызов процедуры проекта 2 DocThree.DocThreeModule.TestPrint End Sub

Листинг 4.11.

(html, txt)

В нашей простой ситуации разбором занимается простейший оператор IF, который в зависимости от имени проекта вызывает процедуру TestPrint того или иного проекта. Но заметьте, я не стал в текст этой процедуры вставлять явный вызов процедуры проекта, заменив его вызовом внутренней процедуры, являющейся оберткой настоящего вызова. Таким образом, процедура CallProcFromProject является синтаксически корректной, более того она вызывает синтаксически корректную процедуру, если, конечно, разбор работает корректно. Синтаксически некорректные процедуры не вызываются, их существование в проекте не мешает его нормальной работе. Это, конечно, "уловка", но позволяющая достигнуть заданной цели. Хочу обратить внимание на еще один аспект, связанный с работой этой процедуры. Вы, возможно, заметили, эта процедура вызывается в теле процедуры CreateRef, сразу же после создания ссылки на подключаемый проект. Давайте еще раз вернемся к рассмотрению метода AddFromFile, создающего ссылку в коллекции References. Конечно, главным итогом работы этого метода будет не только создание ссылки, но и явное подключение нового документа с его проектом. Это означает, что, если соответствующий документ не был открыт к моменту создания ссылки, то он автоматически откроется. Необходимо иметь в виду еще одно следствие этого процесса, после завершения процедуры, создающей ссылки в связи с подключением нового проекта, происходит сброс выполнения текущего проекта. Это означает, в частности, что все глобальные переменные будут обнулены, и, следовательно, будут потеряны значения переменных, хранящих имена подключаемых проектов. Именно поэтому вызов процедуры CallProcFromProject делается в той же процедуре, в которой новый проект подключается. В противном случае необходимо было повторно запрашивать у пользователя имена подключаемых проектов.

Прежде, чем поставить точку на описании решения рассматриваемой задачи, хочу обсудить еще один важный ее аспект.

Будет ли задача успешно решаться, если подключаемые проекты закрыты для просмотра и защищены паролем?

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

В заключение хочу высказать несколько общих замечаний. Прежде всего, следует отметить, что программное подключение и отключение ссылок может быть полезным не только при работе с подключаемыми программными проектами. Список возможных Com-объектов, подключаемых к документу через меню References велик, и зачастую возникает необходимость в программном их подключении.

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

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


Теперь, когда мы умеем программно включать в системе проектов ссылку на новый программный проект, осталось рассмотреть, как обращаться к элементам этого проекта, его общедоступным переменным, объектам и процедурам. Для обращения к элементу проекта, на который есть ссылка, необходимо указать его полное имя, включающее имя проекта, имя модуля и собственное имя элемента. В моем примере я буду вызывать процедуру с одним и тем же собственным именем TestPrint, которая есть в обоих подключаемых проектах DocTwo и DocThree. Чтобы вызвать процедуру того проекта, который подключен в текущий момент, очевидно, необходимо организовать разбор случаев. Это нетрудно сделать, зная имя (имена) подключенных проектов. Здесь есть правда некоторый подводный камень. Дело в том, что в соответствующем операторе Case только имя одного элемента будет определено, того элемента, который принадлежит подключенному проекту, имена вызываемых элементов из других, неподключенных проектов будут неопределенными, более того, считаться необъявленными, что немедленно приведет к обычной синтаксической ошибке. Из этой ситуации есть простой выход. Но прежде, чем рассказать о нем, давайте взглянем на текст соответствующей процедуры, занимающейся разбором случаев:

Public Sub CallProcFromProject() 'Вызов процедуры подключаемого проекта 'Разбор случаев, какой проект подключен If NoP = "DocTwo" Then Proc2 ElseIf NoP = "DocThree" Then Proc3 End If End Sub

Public Sub Proc2() 'Вызов процедуры проекта 2 DocTwo.DocTwoModule.TestPrint End Sub Public Sub Proc3() 'Вызов процедуры проекта 2 DocThree.DocThreeModule.TestPrint End Sub

Листинг 4.11.

В нашей простой ситуации разбором занимается простейший оператор IF, который в зависимости от имени проекта вызывает процедуру TestPrint того или иного проекта. Но заметьте, я не стал в текст этой процедуры вставлять явный вызов процедуры проекта, заменив его вызовом внутренней процедуры, являющейся оберткой настоящего вызова. Таким образом, процедура CallProcFromProject является синтаксически корректной, более того она вызывает синтаксически корректную процедуру, если, конечно, разбор работает корректно. Синтаксически некорректные процедуры не вызываются, их существование в проекте не мешает его нормальной работе. Это, конечно, "уловка", но позволяющая достигнуть заданной цели. Хочу обратить внимание на еще один аспект, связанный с работой этой процедуры. Вы, возможно, заметили, эта процедура вызывается в теле процедуры CreateRef, сразу же после создания ссылки на подключаемый проект. Давайте еще раз вернемся к рассмотрению метода AddFromFile, создающего ссылку в коллекции References. Конечно, главным итогом работы этого метода будет не только создание ссылки, но и явное подключение нового документа с его проектом. Это означает, что, если соответствующий документ не был открыт к моменту создания ссылки, то он автоматически откроется. Необходимо иметь в виду еще одно следствие этого процесса, после завершения процедуры, создающей ссылки в связи с подключением нового проекта, происходит сброс выполнения текущего проекта. Это означает, в частности, что все глобальные переменные будут обнулены, и, следовательно, будут потеряны значения переменных, хранящих имена подключаемых проектов. Именно поэтому вызов процедуры CallProcFromProject делается в той же процедуре, в которой новый проект подключается. В противном случае необходимо было повторно запрашивать у пользователя имена подключаемых проектов.

Прежде, чем поставить точку на описании решения рассматриваемой задачи, хочу обсудить еще один важный ее аспект.

Будет ли задача успешно решаться, если подключаемые проекты закрыты для просмотра и защищены паролем?

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

В заключение хочу высказать несколько общих замечаний. Прежде всего, следует отметить, что программное подключение и отключение ссылок может быть полезным не только при работе с подключаемыми программными проектами. Список возможных Com-объектов, подключаемых к документу через меню References велик, и зачастую возникает необходимость в программном их подключении.

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

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

Анализ терминальных свойств проектов коллекции


Public Sub WorkWithProjects() ' Анализ терминальных свойств проектов коллекции VBProjects 'Dim MyProject As VBProject Dim MyProject As Object 'Dim MyAddIn As AddIn Dim MyAddIn As Object 'Анализ свойств проектов Debug.Print "Число проектов в коллекции - ", VBE.VBProjects.Count For Each MyProject In VBE.VBProjects With MyProject Debug.Print "Имя Файла - ", .FileName Debug.Print "Имя проекта", .Name Debug.Print "Тип проекта", .Type Debug.Print "Описание проекта", .Description Debug.Print "Статус защиты", .Protection Debug.Print "Статус проекта", .Mode 'Debug.Print "Изменения Вносились? ", .IsDirty Debug.Print "Имя Dll", .BuildFileName 'Debug.Print "Начальный статус", .StartMode End With Next MyProject ' Анализ коллекции AddIns Debug.Print "Число AddIn в коллекции = ", VBE.AddIns.Count For Each MyAddIn In VBE.AddIns Debug.Print "GUID AddIn = ", MyAddIn.GUID Debug.Print "Описание AddIn - ", MyAddIn.Description Next MyAddIn End Sub
Листинг 4.1.
Закрыть окно

doc Имя проекта Project Тип


Число проектов в коллекции - 3 Имя Файла - E:\O2000\DS2000\Ch8\Ch8. doc Имя проекта Project Тип проекта 100 Описание проекта Статус защиты 0 Статус проекта 0 Имя Dll - E:\O2000\DS2000\Ch8\Ch8.DLL Имя Файла - E:\O2000\Book2\Ch1\Normal Имя проекта Normal Тип проекта 100 Описание проекта Статус защиты 0 Статус проекта 0 Имя Dll - E:\O2000\Book2\Ch1\Normal.DLL Имя Файла - E:\O2000\Book2\CD\Ch1\DocOne.doc Имя проекта DocOneProject Тип проекта 100 Описание проекта - Этот проект содержит примеры главы 2 Статус защиты 0 Статус проекта 0 Имя Dll - ProjectDll
Число AddIn в коллекции = 8 GUID AddIn = {7A588DE1-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the VBA String Editor to quickly and accurately build strings for SQL statements or long scripts to embed in VBA code. GUID AddIn = {6961B1FB-3ECD-11D2-B81C-0060089A6839} Описание AddIn - Use the Package and Deployment Wizard to package applications for installation and deployment. GUID AddIn = {0D87BB44-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the Error Handler Add-in to automate creating standardized error handler code with input dialogs that capture basic information and insert standardized error handling code using a customizable template. GUID AddIn = {0D87BB42-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the Code Commenter Add-in to create well-commented code by automatically adding comments and headers to procedures using customizable templates. GUID AddIn = {0D87BADE-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the Multi-Code Import/Export Add-in to import or export multiple objects in a single operation. GUID AddIn = {0D87BB14-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the WinAPI Viewer utility to view and copy Win32 API Constants, Declares, and Types. GUID AddIn = {0D87BAE8-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the VBA Source Code Control Add-in to take advantage of the Microsoft Visual SourceSafe source code control tool from within the Visual Basic for Applications 6.0 IDE. GUID AddIn = {31E27D66-596E-11D2-B826-0060089A6839} Описание AddIn - Use the Code Librarian to reuse and share code in a centralized database.
Листинг 4.2.
Закрыть окно

Dim MyProject As VBProject Dim


Public Sub WorkWithVBProject() 'Компоненты и ссылки проекта ' Dim MyProject As VBProject Dim MyProject As Object 'Dim MyComp As VBComponent Dim MyComp As Object 'Dim MyRef As Reference Dim MyRef As Object Set MyProject = ActiveDocument.VBProject With MyProject 'Печать имени и типа для каждой существующей компоненты проекта Debug.Print "Число компонент проекта - ", .VBComponents.Count For Each MyComp In .VBComponents Debug.Print "Имя компоненты - ", MyComp.Name, "Тип - ", MyComp.Type If MyComp.Name = "NewMacros" Then MyComp.Name = "DocOneMacros" Next MyComp 'Добавление формы - новой компоненты проекта .VBComponents.Add vbext_ct_MSForm
'Печать имени и типа для каждой существующей компоненты проекта Debug.Print "Число компонент проекта - ", .VBComponents.Count For Each MyComp In .VBComponents Debug.Print "Имя компоненты - ", MyComp.Name, "Тип - ", MyComp.Type If MyComp.Name = "NewMacros" Then MyComp.Name = "DocOneMacros" Next MyComp
'Печать имени и типа для каждой существующей ссылки проекта For Each MyRef In .References Debug.Print "Имя ссылки -", MyRef.Name, "Тип -", MyRef.Type Next MyRef
End With End Sub
Листинг 4.3.
Закрыть окно

Число компонент проекта


Число компонент проекта - 5 Имя компоненты - ThisDocument Тип - 100 Имя компоненты - Examples Тип - 1 Имя компоненты - DocOneMacros Тип - 1 Имя компоненты - Examples1 Тип - 1 Имя компоненты - EventsOfApp Тип - 2 Число компонент проекта - 6 Имя компоненты - ThisDocument Тип - 100 Имя компоненты - Examples Тип - 1 Имя компоненты - DocOneMacros Тип - 1 Имя компоненты - Examples1 Тип - 1 Имя компоненты - EventsOfApp Тип - 2 Имя компоненты - UserForm1 Тип - 3 Имя ссылки - VBA Тип - 0 Имя ссылки - Word Тип - 0 Имя ссылки - stdole Тип - 0 Имя ссылки - Normal Тип - 1 Имя ссылки - Office Тип - 0 Имя ссылки - MSForms Тип - 0 Имя ссылки - VBIDE Тип - 0 Имя ссылки - EventSystemLib Тип - 0
Листинг 4.4.
Закрыть окно

Создание переменной


Private Sub Document_New() Const MyPath = "e:\O2000\Book2\Cd\Ch1\" Const Lin1 = "OpenDoc" Dim Beg As Long 'Создание переменной - хранителя информации в новом документе With ActiveDocument.Variables If Not ExistVar("CounterDoc") Then 'Добавляем переменную .Add Name:="CounterDoc", Value:= 0 End If End With With ActiveDocument.VBProject.VBComponents("ThisDocument").CodeModule 'Создание процедуры - события в новом документе Call .CreateEventProc("Open", "Document") 'Определение точки вставки в процедуру 'Beg = .ProcStartLine("Document_Open", 0) Beg = .ProcBodyLine("Document_Open", 0) 'Вставка текста в процедуру Call .InsertLines(Beg + 1, Lin1) End With 'Добавление модуля в проект нового документа ActiveDocument.VBProject.VBComponents.Add (vbext_ct_StdModule) 'Переименование модуля ActiveDocument.VBProject.VBComponents("Module1").Name = "AddedModule" With ActiveDocument.VBProject.VBComponents("AddedModule").CodeModule
'вставка текста процедуры из файла .AddFromFile (MyPath & "AddingModule.bas") End With
End Sub
Листинг 4.5.
Закрыть окно

Name As String) As Boolean


Public Function ExistVar( Name As String) As Boolean 'Определяет наличие переменной Name в коллекции Variables Dim MyVar As Variable ExistVar = False For Each MyVar In ActiveDocument.Variables If MyVar.Name = Name Then ExistVar = True: Exit For End If Next MyVar End Function
Public Sub OpenDoc() 'Использование счетчика Counter для подсчета числа открытий документа Dim myLocal As Integer 'локальная переменная получает значение счетчика With ActiveDocument If ExistVar("CounterDoc") Then myLocal = .Variables("CounterDoc") MsgBox "Число открытий документа " & .Name & vbCrLf & _ myLocal, vbExclamation, "Число открытий документа!" 'Увеличиваем и сохраняем счетчик myLocal = myLocal + 1 .Variables("CounterDoc") = myLocal Else MsgBox "У документа " & .Name _ & " нет счетчика числа открытий", vbExclamation, "Число открытий документа!" End If End With End Sub
Листинг 4.6.
Закрыть окно

NoF As String, NoP As


Public Sub ChooseProject( NoF As String, NoP As String) Const Msg = "Введите имя файла, хранящего документ и его программный проект!" NoF = InputBox(Msg, "Projects", "DocTwo.doc") Const Msg1 = "Введите имя проекта, хранящегося в документе!" NoP = InputBox(Msg1, "Projects", "DocTwo") End Sub
Листинг 4.7.
Закрыть окно

с именем NoF Dim MyPath


Public Sub CreateRef() 'Создание ссылки на проект с именем NoP, хранящийся в файле с именем NoF Dim MyPath As String Dim MyRef As Object Dim NameOfProject As String Dim NameOfFile As String
'Выбор добавляемого проекта Call ChooseProject(NameOfFile, NameOfProject)
'Запоминание глобальных переменных программы NoF = NameOfFile NoP = NameOfProject With ActiveDocument MyPath = .Path 'Вставка ссылки If Not ExistRef(NameOfProject, MyRef) Then .VBProject.References.AddFromFile MyPath & "\" & NameOfFile End If CallProcFromProject Debug.Print "Имя файла -", NoF, "Имя проекта -", NoP End With End Sub
Листинг 4.8.
Закрыть окно

Name As String, Refery As


Public Function ExistRef( Name As String, Refery As Object) As Boolean 'Определяет наличие ссылки с именем Name в коллекции References 'Возвращает ссылку при ее обнаружении Dim MyRef As Object Set Refery = Nothing ExistRef = False For Each MyRef In ActiveDocument.VBProject.References If MyRef.Name = Name Then Set Refery = MyRef ExistRef = True Exit For End If Next MyRef End Function
Листинг 4.9.
Закрыть окно

с именем NoP Dim MyRef


Public Sub RemoveRef() 'Удаление ссылки на проект с именем NoP Dim MyRef As Object Dim NameOfProject As String Dim NameOfFile As String 'Выбор удаляемого проекта Call ChooseProject(NameOfFile, NameOfProject) 'Удаление ссылки If ExistRef(NameOfProject, MyRef) Then ActiveDocument.VBProject.References.Remove MyRef End If End Sub
Листинг 4.10.
Закрыть окно

Разбор случаев, какой проект подключен


Public Sub CallProcFromProject() 'Вызов процедуры подключаемого проекта ' Разбор случаев, какой проект подключен If NoP = "DocTwo" Then Proc2 ElseIf NoP = "DocThree" Then Proc3 End If End Sub
Public Sub Proc2() 'Вызов процедуры проекта 2 DocTwo.DocTwoModule.TestPrint End Sub Public Sub Proc3() 'Вызов процедуры проекта 2 DocThree.DocThreeModule.TestPrint End Sub
Листинг 4.11.
Закрыть окно

Программирование на лету


Сейчас я хочу рассмотреть одну важную тему при работе с программными проектами. Иногда работа программиста с проектом заключается в том, что он программно должен изменить сам текст проекта, добавляя новые модули, новые процедуры и обработчики событий, корректируя текст модуля и его отдельных процедур, создавая или меняя свой проект, как говорят, "на лету". Я уже говорил о том, что, благодаря коллекции VBComponents, можно добраться до каждого модуля проекта, а благодаря свойству CodeModule, получить код модуля. Свойством CodeModule обладает объект VBComponents("NameofModule"), задающий модуль проекта с именем NameOfModule. При вызове этого свойства возвращается объект CodeModule, определяющий код модуля. У этого объекта много важных и полезных свойств и методов, необходимых при программной работе с кодом проекта. Благодаря таким свойствам как CountOfLines, CountOfDeclarationsLines, ProcCountLines, можно узнать число строк в модуле, число строк в разделе объявлений, число строк в процедуре модуля с заданным при вызове именем. Работая со свойством Members, можно получить полную информацию обо всех элементах модуля. Свойства ProcBodyLine и ProcStartLine возвращают номер строки, с которой начинается процедура или предшествующей ей строки. Свойство Lines возвращает заданное число строк процедуры, свойство ProcOfLine возвращает имя процедуры, содержащей заданную при вызове строку. Если свойства объекта CodeModule позволяют проанализировать состав модуля и добраться до каждой из его процедур, то методы объекта позволяют вставлять, заменять и удалять строки кода, так что можно "на лету" провести коррекцию процедуры, удалить или добавить новую процедуру и/или объявление переменной.

Методы AddFromFile и AddFromString позволяют добавить в модуль текст, сохраненный либо в файле, либо непосредственно в строке. Первый метод, как правило, используется для введения больших изменений в модуле, второй при небольших корректировках. Заметим, что если нужно полностью добавить новый модуль, то удобнее пользоваться методом AddFile или AddFromTemplate коллекции VBComponents. Методы InsertLines, DeleteLines и ReplaceLines позволяют вставить, удалить или заменить строки программного текста в указанной точке. Функция CreateEventProc позволяет создавать процедуры указанных событий. Функция Find позволяет осуществлять полномасштабный поиск в модуле.

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

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

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

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


Понятно, что, помимо всего прочего, этот пример демонстрирует программное создание кода проекта документа. Для решения этой задачи нам придется широко использовать свойства и методы объекта CodeModule, так что настал его черед. Вот код обработчика события New, встроенный в шаблон документа с именем DocWithCounter:

Листинг 4.5.

(html, txt)

Процедура довольно хорошо прокомментирована, тем не менее, я позволю обратить Ваше внимание на следующие моменты

Вначале в коллекцию Variables нового документа вставляется переменная Counter. Напомню, что переменные этой коллекции являются частью документа, хранятся вместе с ним и потому время их жизни совпадает с временем жизни документа. Они могут выступать в роли хранителей информации между сеансами работы.Интерес представляет строка:With ActiveDocument.VBProject.VBComponents("ThisDocument").CodeModuleРассмотрим подробнее цепочку вызовов, порождаемую этой строкой. Заметьте, вызов ActiveDocument в обработчике события New некоторого шаблона возвращает новый документ, только что созданный на основе этого шаблона. Вызов VBProject возвращает проект этого документа. Очевидно, что содержательного кода в этом проекте пока нет. Тем не менее, в этом проекте есть модуль со стандартным именем ThisDocument, так что вызов VBComponents("ThisDocument") вернет этот модуль. Вызов CodeModule вернет объект CodeModule, содержащий пока что пустой код модуля, с которым я и начинаю работать.Вызов CreateEventProc("Open", "Document") программно создаст в этом модуле обработчик события Open для объекта Document. Но пока это будет только заготовка обработчика с пустым кодом.В эту заготовку я добавляю свой код. И как всегда, я строю очень простой обработчик события, состоящий из одной строчки ("OpenDoc") - вызова соответствующей процедуры стандартного модуля. Заметьте, я использую вызов метода InsertLines, чтобы вставить заготовленную в виде константы эту строку в тело обработчика события.На следующем шаге я добавляю в проект документа новый модуль, даю ему имя "AddedModule" и из ранее заготовленного файла заполняю текст этого модуля. Но хочу обратить Ваше внимание, в этот момент не только добавятся процедуры, хранимые в этом файле, но и сам модуль получит имя "AddingModule" по имени модуля, экспортированного ранее в этот файл, так что моя работа по созданию имени "AddedModule" оказалась напрасной. Тем не менее, добавление текста модуля из файла проходит успешно. Приведу текст процедур, хранимых в добавляемом модуле:



Листинг 4.6.

(html, txt)

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

Программное добавление и удаление ссылок


Теперь я хочу рассмотреть еще один пример, являющийся ответом на следующий вопрос одного из моих читателей:

Как программно добавляются и удаляются ссылки коллекции References?

Задача Сергея Шершнева (так зовут моего читателя) состояла в том, что, работая с системой документов, ему в зависимости от выбора пользователя необходимо было подключать программные проекты тех или иных документов. Чтобы получить доступ в главном документе к процедурам подключенных проектов, ему необходимо было предварительно программно установить ссылки на подключаемые проекты. Не буду вдаваться во все тонкости его проблем и рассмотрю лишь две конкретные задачи, как программно включить (выключить) ссылку на программный проект и как вызывать процедуры подключенных проектов.

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

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

Public Sub ChooseProject(NoF As String, NoP As String) Const Msg = "Введите имя файла, хранящего документ и его программный проект!" NoF = InputBox(Msg, "Projects", "DocTwo.doc") Const Msg1 = "Введите имя проекта, хранящегося в документе!" NoP = InputBox(Msg1, "Projects", "DocTwo") End Sub


Листинг 4.7.

(html, txt)

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

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

Public Sub CreateRef() 'Создание ссылки на проект с именем NoP, хранящийся в файле с именем NoF Dim MyPath As String Dim MyRef As Object Dim NameOfProject As String Dim NameOfFile As String

'Выбор добавляемого проекта Call ChooseProject(NameOfFile, NameOfProject)

'Запоминание глобальных переменных программы NoF = NameOfFile NoP = NameOfProject With ActiveDocument MyPath = .Path 'Вставка ссылки If Not ExistRef(NameOfProject, MyRef) Then .VBProject.References.AddFromFile MyPath & "\" & NameOfFile End If CallProcFromProject Debug.Print "Имя файла -", NoF, "Имя проекта -", NoP End With End Sub

Листинг 4.8.

(html, txt)

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

Заметьте также, что прежде, чем добавить ссылку, я проверяю возможность существования ее в коллекции ссылок, чтобы исключить ее повторную запись. Булева процедура ExistRef решает эту задачу. Первый параметр NameOfProject является входным и задает имя проекта, ссылку на который мы ищем. Второй параметр - MyRef является выходным и задает объект класса Reference, найденную ссылку в случае успеха поиска. Вот текст этой функции:



Листинг 4.9.

(html, txt)

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

Листинг 4.10.

(html, txt)

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

Программное создание компонент проекта


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

Листинг 4.3.

(html, txt)

Приведу результаты отладочной печати по завершении работы этой процедуры:

Листинг 4.4.

(html, txt)

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

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

Как сохранить в форме программно добавленные элементы управления так, чтобы они появлялись при повторном ее открытии?Как импортировать VBComponent, если компонент с таким именем уже присутствует в программе?

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


Листинг 4.4.

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

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

Как сохранить в форме программно добавленные элементы управления так, чтобы они появлялись при повторном ее открытии?Как импортировать VBComponent, если компонент с таким именем уже присутствует в программе?

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