Опубликован: 13.09.2006 | Уровень: для всех | Доступ: свободно | ВУЗ: Тверской государственный университет
Лекция 4:

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

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >

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

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

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.

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

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

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

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

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

Сейчас я хочу рассмотреть одну важную тему при работе с программными проектами. Иногда работа программиста с проектом заключается в том, что он программно должен изменить сам текст проекта, добавляя новые модули, новые процедуры и обработчики событий, корректируя текст модуля и его отдельных процедур, создавая или меняя свой проект, как говорят, "на лету". Я уже говорил о том, что, благодаря коллекции 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:

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.

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

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

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

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
Андрей Гуменюк
Андрей Гуменюк
Молдова