key does the same thing as selecting File > Print from the menu. Whether you click your mouse or use the associated shortcuts, it is helpful to know what a menu item does. Let’s look at the menu items that are available in VBA.
File Menu Ctrl+M
Import File imports existing form (.frm), module (.bas), and class (.cls) files into our project.
Export File exports forms, modules, and classes from our project to their own .frm, .bas, and .cls files. After these files have been exported, they can be imported into another project. Remove removes forms, modules, and classes from our project. When we attempt to remove an element from our project we are asked if we want to export it (save it) first.
I Menus I
17
Print allows us to print code and/or forms. Close and Return to Microstation hides the VBA environment
and gives Microstation focus.
Edit Menu Undo and Redo are standard Windows menu items. Cut, Copy, and Paste perform
standard Windows Clipboard operations. Clear deletes selected text or
objects. Select All selects all text when in a
Code window or all controls when in a User Form. The Find, Find Next, and Replace menu items perform standard Find and Replace functions. Indent indents the selected code by one tab to the right. Outdent (is
*
Outdent a real word?) shifts the selected code by one tab to the left. List Properties/Methods displays the Properties/Methods list. List Constants works with an API call that utilizes constants. A list of the applicable constants is shown.
To use Quick Info, set the cursor on a variable, an object, or an object’s property or method and then click Quick Info to display the type of object or variable on which the cursor was placed. Parameter Info displays information about the Method the
cursor is over. Complete Word shows the list of Constants and Methods in
VBA so we can select something from the list. Bookmarks sets and removes bookmarks in our code and
moves from bookmark to bookmark. A bookmark is a flag that
18
I Chapter 3: The VBA IDE I lets you quicklyjump to a line of code. Bookmarks are not saved with .mvba projects.
View Menu El When looking at a user form, click Code to jump to the code behind the form. El Object displays the form associated with the code we are looking at. El Click on Definition when the cursor is over the item you want to look at to quickly display where the variable is declared or the method is defined. ElLast Position moves the cursor
to the previous line of code the cursor was in. El Object Browser, Immediate Window, Locals Window, Watch Window, Call Stack, Project Explorer, PropertiesWindow, and Toolbox display a window with the same name. Tab Order displays the Tab Order properties of controls so we
can see the order in which controls receive focus when the user hits the Tab button in the form. Toolbars toggles the display of the Debug, Edit, Standard, and
User Form toolbars. El Click on Microstation to bring the Microstation window to the forefront.
I Menus I
19
Insert Menu Procedure displays the Add Procedure
dialog box to begin new procedures. This dialog box is most useful for creating new Properties for Class Modules. UserForm, Module, and Class Module inserts these new objects into our project.
One way to speed up our development is to reuse code that has already been written. If we place code in ASCII Text Files, insert snippets into our project by clicking File and then selecting the file to insert.
Format Menu
Use the Format menu to perform standard formatting when editing a User Form.
Debug Menu The Debug menu allows us to perform debugging operations on our code. We will cover this functionality in Chapter 9
"Standard VBA Calls".
20
I Chapter 3: The VBA IDE I Run Menu Run Macro, Break into, and Reset code execution by using these menu items in the Run menu. *
Design Mode is a standard VBA
button that does nothing substantive in the Microstation implementation of VBA.
Tools Menu References allows us to add a reference to existing DLLs and type libraries. For example, if we want to work with Microsoft Excel, we can add a reference to the “Microsoft Excel Object Library”.Doing so makes working with Excel in VBA very easy.
1
By default, 14 controls display in the toolbox for use in our forms. We can add more controls by selecting the Additional Controls menu item. Macros displays the Macros dialog box where we can create,
edit, run, delete, and debug macros in our project. Change preferences such as font size, tab width, and grid settings by clicking on Options. The Properties menu item displays the properties for the active VBA project. In the image shown above, the project is named Default. Digital Signature allows us to sign our VBA projects. This assures end users that the code they are going to run is created by a specific company.
Add-Ins Menu Third party developers can create add-Ins for VBA. Add-In Manager displays the Add-In dialog box where we can set properties for available add-ins.
I Menus I
21
Since we will not be discussing Add-Ins anywhere else in this book, here is a snapshot of the manager with an add-in that has been loaded. Addins can be loaded based on the “Load Behavior” settings.
Window Menu These are the standard menu items available in nearly every Microsoft Windows program. L
Help Menu We will cover Help issues in the next chapter. One way to get there is by clicking the Microsoft Visual Basic Help menu item. About Microsoft Visual Basic displays the About dialog box.
22
I Chapter 3: The VBA IDE I
TOOLBARS Toolbars offer a very quick way to issue a command. One click is usually all it takes to get things started. Compare this with at least two clicks to issue the same command using a menu and we can instantly double our CIP (Command Issuing Performance). As a general rule, all commands issued by clicking on a toolbar icon can be issued from the menus. It can take a little while to become familiar with toolbar icons. Until you learn what each icon does, hold your cursor over an icon to see what the icon does.
Standard toolbar
The Standard toolbar is very, very important. Why? Because the only way to save the changes we are making in our VBA project from within VBA is to click the Save button. We cannot Save changes by using the menu. We must use the Save icon in the Standard toolbar. And please, please, please, my friend, save your project often. There are few things worse than spending a couple of hours working on a project only to have something silly like a power outage or a fatal error cause you to lose all of that work. View MivoStatmn
Notice how holding your cursor over an icon displays the icon’s tool tip. We could show each and every button on every toolbar but that would be a bit of a waste because you can move your cursor over the icons to see what they do.
Edit toolbar The Edit toolbar displays functionality found in the Edit menu.
.
Debug toolbar
The Debug toolbar displays the functionality found in the Debug menu.
I Windows I
23
UserForm tool bar The UserForm toolbar exposes functionality found in the Format menu.
WINDOWS Use the toolbars and menu items to display and hide VBA windows. Let’s take a look at the VBA windows we will be working with on a regular basis.
Project Explorer The Project Explorer displays the top-level objects in the loaded projects. In this project we have a form named UserForm1, a module named Module1 and a class module named Class1 . The view shown uses folders to group the common types of objects. Click on the Folder icon in the top of the Project Explorer to turn off Folders and display the objects in alphabetical order.
24
I Chapter 3: The VBA IDE I Object Browser
L
The Object Browser gives us a way to explore the objects (classes) loaded into the current VBA project. In addition to the objects themselves, we see a list of properties, methods, and events associated with each object under the “Members” list. The gray area at the bottom of the window gives us the declaration of the selected method, property, or event. We can display All Libraries or select a specific library. We also have the ability to search the loaded libraries. We will cover the Object Browser in more detail in the next chapter.
I Windows I
25
Properties Window Objects in VBA have names. For example, this user form has the name UserForml. We can use the Properties window to change this form’s name, color, and other properties. The Properties window is used extensively when working with forms and controls on forms.
Watch Window
The Watch window is a favorite among VBA developers. It allows us to watch the value of a variable, object, or property. As we can see above, I added a watch to a variable named MyApp. This variable points to the Microstation Application. Take a look at all of the properties with which we can work.
26
I Chapter 3: The VBA IDE I Locals Window
The Locals window looks a lot like the Watch window. There is one primary difference however. To look at items in the Watch window, you must add a watch to the item. The Locals window automatically displays the variables declared in the active procedure or function along with each variable’s type and value.
Immediate Window
The Immediate window does a couple of things for us. First, it allows us to display text as our code executes. When we use the following code D i m MyApp As A p p l i c a t i o n S e t MyApp
=
Application
D e b u g . P r i n t MyApp.Caption
the caption of the Microstation application is printed to the Immediate window. For this reason, it is also called the Debug window. The other thing the Immediate window does is it allows us to execute code immediately. For example, we can type MsgBox “ L e a r n i n g M i c r o s t a t i o n V B A ”
I Windows I
27
in the Immediate window and press the
Call Stack Window
As we step through our code line-by-line or break into our code as it is executing, the Call Stack window shows us where we are (the top line), where we started (the bottom line), and how we got there (all of the lines in between). There are times when one procedure calls another procedure which calls a function which calls a procedure. Knowing how we arrived inside a procedure or function can help us debug it.
Toolbox Window The Toolbox window displays the standard controls that can be placed on our user forms. It only displays when a user form is the active window in VBA. If we are working with a user form and the toolbox is not visible, click on the Toolbox icon in the Standard toolbar or go to the View > Toolbox menu to display it. All of the windows discussed so far are dockable except for the Toolbox. This means they can be snapped to the bottom, top, right, or left window of the VBA IDE. These windows’ dockable property is set in the
28
I Chapter 3: The VBA IDE I Docking tab of the Options dialog box. To view this, go to the Tools menu in VBA and select Options.
OK
If an attempt to dock a window fails, look at the Docking property of the window in the Options dialog box and turn on Docking for the specific window you want to dock. As with many applications, these windows are docked by dragging the window to the edge where you want the window docked. They are un-docked by dragging the window away from the edge where the window is currently docked.
Other Windows There are a couple of additional windows in VBA we should discuss. As we have already discovered, VBA projects are composed of forms, modules, and classes. Each of these elements has its own windows.
Here is a Form.
I Windows I
29
A single CommandButton has been added to this form. This form window allows us to place controls on it. Remember, the controls are placed from the toolbox to the form. What happens when we are running this form and the user clicks the button? Code is executed. Double-clicking the button takes us to Code window behind the form. Another way to see the code is to right-click on the button and select View Code.
This is the C l i c k E v e n t of CommandButtonl. View Code takes us to the default
event of the control we right-click over. So, here we can see the C1 i c k Event. Are there other events we can work with? How do we see them and write code in them?
Private Sub Comman Dim MyApp As A Set MyApp = Ap Debug.Print My DrawL ine End Sub Sub DrawLine ( ) MsgBox "XI is End Sub
Private Sub CommandButtonl-Click() Dim MyApp As Application Set MyApp = Application Debug.Print MyApp.Caption DrawL ine End Sub Sub DrawLine ( )
Take a look at the top of the Code window. There are two ComboBoxes. The left one contains the controls and objects available in this Code window. The right one contains the events we can work with. Selecting an event
30
I Chapter 3: The VBA IDE I from the right ComboBox takes us into the code for that event. Open Module (Code Module) and Class Module windows by doubleclicking on their icons in the Project Explorer or by right-clicking on the icons and selecting View Code. They look exactly like the Code window shown above. We have spent a few pages discussing the VBA IDE. As we continue learning Microstation VBA, we will discover other facets of the IDE and get into more detail.
The VBA IDE (Visual Basic for Applications Integrated Development Environment) is where we do our programming, i.e. writing code and creating user interfaces. As you become more familiar with this environment, you will be able to develop your programs much more quickly.
Finding Help Finding help can be one of the most difficult aspects of learning a new programming language. Why? If you have a question for someone, you can converse with them until the question is clear. That’s easy. When learning VBA, however, you don’t always know what to ask. For example you might ask, “How do I put something on a form that forces a user to enter a numeric value?” If you could ask a VBA guru this question, you will get a straightforward answer. Working through a Help file for the answer is different. For starters, even if you know what to ask but you don’t know the correct terminology, you won’t find the answer. Distressing? Yes. Frustrating? Definitely. The end of civilization as we know it? No. This book is targeted toward helping you learn Microstation VBA. It is filled with code samples and explanations but it does not contain every answer to every possible question. Here a few things that will provide help when you need it. In this Chapter:
Terminology Help Files TheNet The Object Browser 31
32
I Chapter 4:Finding Help I
TERMINOLOGY Thingy. Dilly Whopper. Whatchamacallit. Gizmo. Whether we are asking a person or a computer, these words will get us nowhere. How can we get a little closer to the right keyword? Let’s begin by looking in VBA. Holding your cursor over a variety of objects displays a Tool Tip. There is a ComboBox icon in the Toolbox. If m we ask about a ComboBox we are more likely to find answers than if we ask about a DropDown. Both combinations of words may make sense to us, but using the correct name for the control gets us closer to finding answers to our questions than using terminology that, although descriptive, is not correct. Since the terminology used in VBA may be foreign to you when getting started, it is a good idea to make notes or highlight areas of this book and other resources when you come across a word or phrase you want to remember or that you may want to be able to find quickly at a later date. For example, if you are asked to provide a string, you may produce a piece of flexible material useful for restricting blood circulation in one’s index finger with the intent of reminding you of something. As for me, I’m just as likely to forget the string is tied around my finger as I am to forget why the string is there in the first place. What does this mean? Before long we are collectively fingerless. Or is it finger-free?Digitless?
I Terminology I
33
What is a string when it comes to VBA? It is a type of variable that can hold text, numbers, and other characters. “This is a test” is an example of a string. Since the word string is very different when dealing with VBA when compared to Benjamin Franklin’s experiments with electricity, highlighting the definition of a string type variable in a book may be helpful. In addition to highlighting existing text, get out a pen or pencil and write in the margins of this book. “Chicken Soup for the VBA Programmer.” “The VBA Word of the Day.” “The VBA Programmers Daily Calendar.” These products may not exist but they could be very helpful. Why? Frequent and regular exposure reinforces retention. If your goal were to obtain a dark tan, you would want regular exposure to the sun. If your goal were to bake bread, it would be out of reach if you rarely stepped inside a kitchen. “Practice makes Perfect,” “No Pain, No Gain,” and “If at first you don’t succeed, try, try again.” We have heard these statements over and over again. Each of them reminds us that frequent and prolonged exposure and practice is usually necessary for success in any endeavor. While you don’t need to write a thousand lines of code every day to learn VBA terminology, 20 lines of code every day will do more than 100 lines of code only on Monday mornings. If you expose yourself to VBA frequently, the terminology becomes familiar and you will be in a better position to find the help you need.
34
I Chapter 4: Finding Help I
HELPFILES One way to display the VBA Help File is to go to the Help menu in VBA and select Microsoft Visual Basic Help.
Visual Basic See i\k Spea!!c>
Welcome to the Visual Basic documentation. Visual Basic includes many documentation tools, each designed to help you learn and use a particular aspect of the product. The documentation provided with Visual Basic includes the following: Visual Basic User Interface Help Look here f o r Help on interface elements of the Visual Basic Editor, such as commands, dialog boxes, windows, and toolbars. Visual Basic Conceptual Topics The Conceptual Help topics include information to help you understand Visual Basic programming. Visual Basic HOW-TO Topics Look in the How To section of Help to find useful common procedures, f o r example, how to use the O b j e c t B r o w s e r or how to set Visual Basic Environment options. Visual Basic Language Reference The Language Reference is the place to find Help on Visual Basic the language: all its methods, properties, statements, functions, operators, and objects.
It is filled with a large amount of information but also gives us the ability to organize our own unique help file by using of the Favorites tab. So, you want a little help with a ComboBox? Let’s begin in the Contents tab and drill down to the ComboBox starting with the Microsoft Forms Reference.
I Help Files I
35
Contents tab
ComboBox Control See Also Events
Example Properties 4pftiimsr
Methods
Combines t h e features of a ListBon and a TextBox. The u s e r can e n t e r a new value, as with a TentBon, o r t h e user can select a n existing value as with a ListBon. Remarks
If a ComboBox is bound t o a data source, then t h e ComboBon inserts t h e value the user enters o r selects into t h a t data source, If a multicolumn combo b o x is bound, then the BoundColumn property determines which value is stored in t h e bound data source,
The list in a ComboBon consists of rows of data. Each r o w can h a v e one o r m o r e columns, which can appear with o r without headings. Some applications do not support column headings, others provide only limited support
It may take a little digging to find what you are looking for using the Contents tab but knowing the correct terminology is a big help. At the top of many help topics, are links for “See Also,” “Example,”“Properties,” “Methods,” “Events,” and “Specifics’: If you are looking for more explanations, “See Also” is very helpful. If you are looking for code to copy and paste, “Example” is the link you want. For information about specific Properties, Methods, and Events, click the appropriate link. The body of help topics often contain hyperlinks to other topics and pop-ups to explain the highlighted text in greater detail. You can print help Help topics by clicking the Print icon at the top of the file.
I Chapter 4: Finding Help I Index tab
CompareMode property Filter function InStrRev function Replacefunction Split function wildcards string conversion String data type String function String keyword String$ function strings aligning character comparing concatenating converting data types fixed-length formats justifying leftmost characters length manipulating matching middle characters removingspaces repeating replacing returningfrom functions reversing rightmost characters searching spaces substrings variable-length
clearing color comparing converting cutting and pasting entering from numbers importing Iooping searchinglieplacing String data type text files inserting TextStream oblect The binary compatibilityDLL or E) The binary compatibilityDLL or E) Then keyword This [Me keyword] Tile Horizontallycommand Tile Vertically command Time function time intervals adding difference Time keyword time stamp Time statement Time$ function Timer function timers times adding converting creating Date data type determining
The Index tab displays a different way to organize help topics. It works much like the index of a book. This is another area where using correct terminology is very helpful. If you enter “string in the keyword textbox, you get a large number of linked topics. Enter “text” and you get a number of unrelated topics (if we are looking for information on the String variable type) but also a link to the “String data type.” So even if you don’t have the exact terminology, getting close to the correct word may link you to the correct topic.
I Help Files I
37
Search tab Use the Search tab to enter a word or series of words to search for in the help topics. It returns a list of all help topics containing the word(s) entered. For example, if you enter “combobox,” you are returned a listing of 67 topics. Some topics are properties such as “List Property.” Other topics are instructional, such as “Ways to put data in a ListBox or ComboBox.” Keep in mind that most help topics are linked to related topics. So, we could begin with a search for “combobox” and read far more than 67 topics by jumping to other help topics.
Toolbox Unable to unload withi... UnderstandingObject... MatchFound, MatchR... Layout Event, OldLeft... Style Property DblClick Event, CanP... ComboBox Control, A,.. Ways to put data in a ... List Property Thingsyou can do wit ... Style Property Example MatchEntryProperty, ... AutoTab Property BoundColumnProperty Text Property CurX Property TextColumn Property Value Property Locked, DropButtonS... DropDown Method Ex... DragBehavior Property Listwidth Property Ex... ListRowsProperty Ex... Linecount Property Add items to a list usin... Listlndex Property Liststyle Property
Visual Basic ... Visual Basic ... Visual Basic ... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo... Microsoft Fo...
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Favorites tab
t data in a Listsox or See Also
C-:oe:,i.c~
with a single column, er an effective technique for adding an indiv o the list. I n a rnulticolurnn ListBo. o r ,however, the List and Column properties offer another technique; you can load the list from a twoI n a ListBox o r the AddItem rn
dimensional
=.
If you find a particularly helpful help topic or one that was difficult to find, add it to your Favorites. The “Current topic” text box displays the
38
I Chapter 4:Finding Help I default topic title. Fortunately, you can change this description to whatever you want it to say. For example, you could change it to “ComboBox - Adding To the List.” Then click the Add button. Favorites is one of the best ways to personalize the standard Visual Basic Help file. Sure, you can print out page after page after page, use a highlighter and make a binder. That is a fine way to catalog what you have learned and create additional reference material. But Favorites is a quick way to find ‘bookmarked’topics and jumps to linked topics. It also keeps the Copy and Paste functionality available. To sum up, VBA stands for Visual Basic for Applications. Thus far in this chapter, we have been discussing how to find help for the “VB” portion of VBA. What about the ‘Y?The Application? Good question. Learning everything about the VBA programming language will give us some good background but will not get us very far when attempting to interact with Microstation.
I Help Files I
39
MicrostationVBA Help File
Microstation V8 Visual Basic for Applications
p Loading and runninga VBA macro Frequentlyasked MicroStationVBA Questions Recording and RevisingMacros Automatingcommon MicroStationtasks Working with MicroStationObjects Working with MicroStationEvents Customizing MicroStationwithvisual Basic Convertingfrom MicroStationBasic toVBA Changes for MicroStationVBXMEdition
Visual Basic and VBA are thoroughly modern objectoriented programming environments used by both professional application developers and casual programmers. Visual Basic is the primary development platform f o r a large number of commercial products, some of which you may use on a daily basis. Visual Basic f o r Applications shares most of Visual
Microstation has its own VBA Help file. It contains MicroStationspecific help for VBA. Searching for the file ‘microstationvba.chm’ on your computer should show us where it is and let you open it.
Vertex List Example
m Adding the Contents of a Fence to a Named Group AnalyzeArc
H ApplicationElement Evample p A m By Length H Area Booleans p p
H
p p
p
Attaching and Searching a Cell Libraiy B spline Curve Point Extraction B spline Surface From Gridded Point Interpolation B spline Surface From Poles B spline Surface From Scattered Point Appioximation Centering theview at the Curroi Changing a Relerence File Path from Absolute to Logical Changing an Attachment to Releience the Oelault Model Changing Coordinate Readout in a S a v d s Event Changing the Update Order ol Attachments
This example illustrates some of the methods of the VelteXL1St interface. The example 15 a simple primitive command t h a t implements event handlers for start, data point, and reset. Because the other event handlers of IP,imitiveCommandEvents do nothing in this example, they have been omitted. Mort o f the logic f o r this example I S in the data point event handler. It tests t o determine if it has a r a v e d reference t o an element. If not, it tries t o locate an element using LocateElement. If it successfully finds an element that rupportr t h e VertexLirt interface, it retriever the list of verticer using GetVerticer. It prints the vertex list, and i a v e s the reference t o the object. On rubsequent data points, it detects that it already h a r an element. It user GetClorertSesment t o get the index of the first vertex o f the segment closest t o the data point. k i n g t h a t information, it prints the range of t h e closest segment. The reset event handler in this example j u s t restarts the command. It invoker CommandState.StartPrImltlve. StartPrimitiue first resets MicroStation’r command
One of the most helpful sections in the Microstation VBA help file is the Examples folder, which contains excellent explanations as well as the code to perform specific tasks.
40
I Chapter 4:Finding Help I Another excellent way to open the Microstation VBA Help file is to select a Microstation-specific API call in the Object Browser or in our code and hit the
THENET Got Net? http://msdn.microsoft.com/isv/technology/vba/default.aspx gets us to Microsoft’s VBA web site. If this URL is difficult to remember, you can also use http://msdn.microsoft.com/vba, which contains links to white papers, Knowledge Base articles, and other reference materials that comprise a wealth of information on VBA programming and its associated topics. Although you are not likely to find much about Microstation’s VBA-specific implementation here, you will find many other examples on how to accomplish specific tasks in the VBA environment.
I The Net I
41
Google, Yahoo, and other search engines can unlock the rest of the Internet’s VBA knowledge for us. Remember, there are a lot of programming languages out there. A search for “Message Box” returns us a large number of web pages to check out, but in addition to VBA results, we will get pages for C#, C++, Java, JavaScript, Fortran, Pascal, and other languages. A quick trip to the Bentley web site and a search for “vba” nets some good information as well. Why not go directly to the source?
~ ~ L ~ C 7I 8279 s ~ - rUP^^ ~ i W8A ~ ~ tine^ ~ The follo:+irig GEOPAK \iBk r w t i n e s are provided tree far voiir use. We at Bentley I?OUI:that you red? prududivir), bsriclits from atitomaliny GEOFCK Civi! Engineering Su!te with VBA.
________________ ~ ~ L ~ C ~ ~Laz;?s ~ _________ r u- C i ~~ en s~
i ~W ~~ rr i~ ~tinbiMl ~~i ~~~ ~ . u V~8 t ~ ~ ~ ~ R Th!s technical Dociiment provides s description #f the variables in MicroStation V8 !Ziiii? Editiun, V8 0
and ‘di3.t) .............................................................. .............................................................
Among
the
Bentley
Discussion Groups we find Bent/ey.microstution.v8.vba. This is a good place to ask questions. And who knows? As you become a VBA guru in your own right, you may be
42
I Chapter 4:Finding Help I able to help others by answering their questions in this Discussion Group.
You can access any of these groups via must papular discussion gruup neimre from a Web browser-, visit htip:i/discus5ion,ien:lry , c o n .
g a n t t o past a test article from a newsreader? Ga to Bm:leg.tcstpcsts. M ~ c ~ ~ S t a ~ ~ ~ n R e ntle ’f n?i c m stati o n v 8 >. ne a rl Y a cce s s I ne A 2 ~
.
THEOBJECT BROWSER We talk to Microstation through its Object Model. The top level of the Microstation’s Object Model is the Application Object. Using the Object Browser in VBA is a great help when you are trying to discover something about an Object Model. As shown, you can restrict browsing to the Microstation DGN library so all Microstation Classes show up in the ListBox on the left. Selecting “Application” in the left ListBox
I The Object Browser I
43
displays its properties, methods, and events in the listbox on the right side of the window.
For example, if you have selected Application in the ListBox on the left and want to do something with the active design file in Microstation, click on ActiveDesignFile in the right-hand ListBox. The description at the bottom of the Object Browser tells us the ActiveDesignFile property of the Application Object returns a DesignFile object. We can now select DesignFile in the Classes list (the listbox on the left) to see the Design File’s properties, methods, and events in the Members list on the right.
44
I Chapter 4:Finding Help I Selecting AddNewLevel in the Members list shows that we need to supply a LevelName when using AddNewLevel. It also shows that AddNewLevel returns a Level Object. One reason the Object Browser is helpful is because you can start at a very general level (Application) and work down through the object model to the object, property, method, or event for which you are looking. We can literally browse through the available objects and APIs using the Object Browser. Other ways to find/get help? Take a Bentley-approved VBA training class or attend the annual Bentley Developer Conference.
Finding help is not always easy. Knowing where to look is the first step. Next, using correct terminology moves us along the path to finding the answers to our questions. Learning to use tools, such as the Object Browser, provides more answers. Keep in mind that the most simple subjects still require effort to learn and retain. VBA is no different. If you allow yourself to become frustrated, the chances of success are diminished. You can learn Microstation VBA. You really can.
Modules, Forms, and Class Modules We have already discussed the basics of how Microstation VBA projects. modules, forms, and classes are used to create programs that improve productivity and accuracy. That much we know. It’s time to learn when to use each of these design elements and how they work together. In this Chapter:
Modules Forms Classes Procedures and Functions
MODULES Code modules are the foundation of every VBA project. We use them to declare variables that can be used from within the code module, by other code modules, by forms, and even by class modules. Windows API functions are declared in modules so the API calls can be used in our project (more on Windows API functions later in the book). Procedures and functions inside modules can be run from the VBA Project 45
46
I Chapter 5: Modules, Forms, and Class Modules I Manager. In fact, code modules are so essential that an initial code module is created every time a new VBA project is created. Procedures written in code modules are the starting point for running code and displaying forms. Enough talking. Let’s write some code. Let’s begin by creating a new VBA project named Chapter 05. Save it in the folder C:\MicroStution VBA. After this new project is created, you can see that a code module named “Module 1” is created automatically. Rename this module modCh05. Continue by creating a new procedure named Main. Inside the code module, type Sub M a i n 0
When you press the
(GenerPI)
Main
Sub Main() End Sub
The next thing we are going to do is enter some code in our new procedure “Main.” Sub M a i n 0 ‘Declare Variables
D i m M y L i n e As L i n e E l e m e n t D i m MyCir As E l l i p s e E l e m e n t D i m C e n P t As P o i n t 3 d D i m L i n e S t As P o i n t 3 d D i m L i n e E n As P o i n t 3 d
D i m R o t M a t r i x As M a t r i x 3 d ‘Create Horizontal Line LineSt.X
=
-1
LineEn.X
=
1
S e t MyLine
=
Application.CreateLineElernent2(Nothing. LineSt, LineEn)
A p p l ic a t i on . A c t i v e M o d e l R e f e r e n c e . A d d E 1 e m e n t M y L i n e ‘Create Vertical Line LineSt.X
=
0: L i n e S t . Y
=
1
I Modules I LineEn.X Set MyLine
47 = =
0: LineEn.Y
=
-1
Application.CreateLineElernentZ(Nothing, L i n e S t . L i n e E n )
Appl ication.ActiveMode1 Reference.AddElement MyLine 'Create Circles Set MyCir = Application.CreateEllipseElement2(Nothing, CenPt, 0.25, 0.25, RotMatrix) Appl ication.ActiveMode1 Reference.AddElement MyCi r Set MyCir = Application.CreateEllipseElement2(Nothing, CenPt, 0.5, 0.5, RotMatrix) Appl ication.ActiveMode1 Reference.AddElement MyCi r End Sub
~
-
The code above may look like a whole lot of gibberish at this point, but it will make much more sense as we continue to learn VBA together. Notice the comments inserted into the code. Remember, comments begin with an apostrophe (I).
Running this code draws two circles and two lines in Microstation that create a target shape that looks like this: The code works great. From now on, any time we need to draw a target with these dimensions centered at (0, 0, 0) we have the code to do it. In mere milliseconds, we can draw this target by running the macro Ma in whenever we wish.
The more hard-coding we do, the less offen we will be able to run our macros.
-7
If we need to draw the target centered at (4,5,0) we can copy and paste the code, and rename the procedure to Mai n2. And then we can create M a i n 3 with different coordinates, then Main4, then M a i n 5 and so on. Right? Well, we could do that but there is a better way. Let's change the way we are doing things a little bit. Instead of having Mai n draw a target at (0, 0, 0), we create a new procedure that draws the target at the X, Y, and Z coordinates we specify. We will do this by creating parameters for the new procedure. Sub DrawTarget(CenX As Double, CenY As Double, CenZ As Double) 'Declare Variables Dim MyLine As LineElement Dim MyCi r As El 1 ipseElement
48
I Chapter 5: Modules, Forms, and Class Modules I D i m C e n P t As P o i n t 3 d D i m L i n e S t As P o i n t 3 d
D i m L i n e E n As P o i n t 3 d D i m R o t M a t r i x As M a t r i x 3 d ‘Create Horizontal Line LineSt.X
=
CenX
L inest .Y
=
CenY
LineSt.Z
=
CenZ
LineEn.X
=
CenX
LineEn.Y
=
CenY
LineEn.Z
=
CenZ
Set MyLine
=
-
1
+ 1
Application.CreateLineElernentZ(Nothing, L i n e S t . LineEn)
A p p l ic a t i on . A c t i v e M o d e l R e f e r e n c e . A d d E 1 e m e n t M y L i n e ‘Create Vertical Line LineSt.X
=
CenX
LineSt.Y
=
CenY
LineSt.Z
=
CenZ
LineEn.X
=
CenX
LineEn.Y
=
CenY
LineEn.Z
=
CenZ
Set MyLine
=
+ 1
-
1
Application.CreateLineElementZ(Nothing, L i n e S t , LineEn)
A p p l ic a t i on . A c t i v e M o d e l R e f e r e n c e . A d d E 1 e m e n t M y L i n e ‘Create Circles CenPt.X
=
CenX
CenPt.Y
=
CenY
CenPt.Z
=
CenZ
S e t MyCi r 0.25,
Appl i c a t i o n . C r e a t e E l l i p s e E l ementZ(Nothing, CenPt, 0.25, R o t M a t r i x )
=
A p p l ic a t i on . A c t i v e M o d e l R e f e r e n c e . A d d E 1 e m e n t MyCi r S e t MyCi r = A p p l i c a t i o n . C r e a t e E l l i p s e E l e m e n t Z ( N o t h i n g , C e n P t , 0.5, 0.5, R o t M a t r i x ) A p p l ic a t i on . A c t i v e M o d e l R e f e r e n c e . A d d E 1 e m e n t MyCi r End S u b
The code from Mai n has been copied and pasted into the same code module. The new pasted procedure is then renamed D r a w T a r g e t . The goal here is to make our code more flexible, so we can draw targets anywhere we specify. Our new procedure, D r a w T a r g e t requires us to specify three parameters named ‘CenX’, ‘CenY’, and ‘CenZ: We declare them as doubles (very precise numbers). Let’s take a look at how we use it.
I Forms I
49
Sub Main0 ‘Draw T a r g e t s DrawTarget 0, 0, 0 DrawTarget 3, 0, 0 DrawTarget -3, 0, 0 DrawTarget 0, 3, 0 DrawTarget 0, -3, 0
End S u b
Our procedure M a i n now draws five targets. More flexible? More powerful? Absolutely. But the coordinates are still hard-coded. This may work at times when we are setting up a page that is to be printed in Microstation. But how can we let the user specify the coordinates? Let’s expand our program a little by introducing a graphical user interface.
User forms provide a graphical user interface (GUI) for our users. We begin by inserting a new User Form. This form has three abels, three text boxes and a command button. We will keep the default names for each of these form elements except for the text boxes. We want to rename the text boxes to txtX, txtY, and txtL This is done in the properties window inside VBA. When a control is selected, we can make changes to the control’s properties in the properties window. As with other windows in VBA, if the properties window is not displayed, show it by going to the menu in VBA, View > Properties Window. After changing the names of the text boxes, change the caption properties of the labels and the command button to reflect the image shown above. After you modify the properties of the controls on the form, change the name of the user form to “frmCh05’: We are going to enter some coordinates in the text boxes. When the user clicks the “Place Target” button, we want to use the entered coordinates and use the D r a w T a r g e t procedure we just created. We need to write some code in the C1 ic k E v e n t of the CommandButton. Double-click on
I Chapter 5: Modules, Forms, and Class Modules I
50
We cun also get here by right-clicking on the button and selecting View Code in the POP-UP
the button when we are designing in our project to be taken into the C l i c k E v e n t oftheCommandButton.
Private Sub CormnandButtonl-Click() DrawTarget CDbl(txtX.Text), CDbl(txtY.Text), CDbl(txtZ.Text) End Sub
menu.
You only need to enter one line of code to use both the D r a w T a r g e t procedure and the values entered into the form’s text boxes. We are almost finished with this little project. We have a procedure that draws targets. We have a form that allows users to enter coordinates. We now need to give the user a way to display the form. We don’t want the user to enter the VBA environment to run this program, so we will make another change to the procedure named Ma in. Sub M a i n 0 ‘ D i s p l a y the Form
frmCh05.show End Sub
Save your VBA program now. It would be a shame to lose this work. Click on the Save button in VBA. Saved? Good. Now run your program and see how well it works. From within Microstation, hold down the
I Forms I
51
The macro named Mai n that is shown is the procedure Main. When it is selected, click the Run button. The form is displayed so we can enter numbers for X, Y, and Z coordinates to place our targets.
Before we continue, let’s review what we have done. We wanted to allow our users to draw a target symbol inside Microstation. The first thing we needed to do was write some code to draw the target correctly. So we put our code in a procedure named Ma in and got the basic code that draws a target shape working by hard-coding everything. When that code was working properly, we made the code more flexible and useful by taking the code out of Mai n and creating a new procedure named D r a wTa r g e t . We provided three parameters that could be used to specify the location of the target. After all of this work was completed, we tested it by modifying the code in Mai n to use the Procedure named D r a w T a r g e t . The next step was creating a user interface (more on using forms in Chapter 10). We used the values from the text boxes for the parameters of DrawTa r g e t (converting the values from a string to a double by using the standard VBA CDbl function). The last step was to change the code in Ma in to display the form. We now have a code module and a form working together using Ma in to display the form. When the user clicks the button, values from the form are parameters in D r a w T a r g e t , which resides in the code module. So, why is D r a w T a r g e t in the code module? Couldn’t it be in the code area of the form? Yes, it could be and the code would still work. However, other modules and other forms would not be able to use D r a w T a r g e t as easily if it had been placed in the form’s code area. One of the things that makes a code module so great is that declared procedures and functions can be easily utilized in other areas of our project. We may have three forms
52
I Chapter 5: Modules, Forms, and Class Modules I that are using code in a code module as well as procedures and functions in a different code module, for example. Our code is working pretty well right now but can we do anything else to make our project more flexible and powerful? That is a good question.
CLASSES You may know that we use classes to create objects, but did you know that by putting a little thought into creating class modules, they can be useful for years to come. How so? In our current project, we can draw a target at any coordinate we specify. That’s pretty powerful and it meets our needs today. What happens, however, if a year from now we find we want to change the target’s size? The procedure DrawTarget only allows entry of three parameters (X, Y, and Z). We could modify the procedure to require four parameters, the last one specifying the size. But this could break parts of our code that are only providing three parameters. We could also make the new parameter optional, but there is a better way. We can create a new class that has X, Y, and Z properties. It will also have a Draw method. When this is in place, we will add a Scale property. We could add a Level property, a Color property, a NumberOfCircles property, etc. We can add these properties today, tomorrow, or next year. It doesn’t matter when we add them. We just need to make sure that when we add them we do so in a way that allows the previous code using the class to continue to work properly without modification. Time to write some code. Let’s add a new class module to our project. Do this by using the VBA menu Insert > Class Module. Name it &Target (using the Properties Window for this). It will have three properties and one method. The most basic way to implement properties for classes is to declare variables as Public in the General Declarations area of the class module. Implement methods by creating procedures in the class module. Begin by defining the properties. P u b l i c X As D o u b l e P u b l i c Y As Double P u b l i c Z As Double
I Classes I
53
Next implement the Draw method. Recall that you can get this finished project on the CD that accompanies this book and open it instead of typing in all of the code. The Draw method was created by copying and pasting DrawTarget and changing ‘CenX’ to ‘X’,‘CenY’ to ‘Y’, and ‘CenZ’ to ‘Z’ to use the X, Y, and Z properties defined in the class module. To make sure we are all on the same page, look at the screen shot of the finished class.
P u b l i c X 85 D o u b l e P u b l i c Y As D o u b l e P u b l i c Z As D o u b l e Sub Draw!)
‘ D e c l a r e FsriSJ31es D i m N y L i n e As L i n e E l e m e n t D i m N y C i r As EllipseElement D i m C e n P c As P o l n t S d D i m L i n e s t As P o l n c 3 d D i m L i n e E n As P o l n t 3 d D i m R O t N a t r i x 83 N a t r l x a d ‘Create H n r i z o n s a l L i o e L1neSt.X = X - 1 L1neSt.Y = Y Llne5t.Z = Z L1neEn.X = X I LineEn.Y = Y L1neEn.Z = Z Set N y L l n e = B p p l i c B t i ~ n . C T e a t e L i n = E l = m = n t 2( N o t h i n g , L l n e S c , L l n e E n ) AppliCatiOn.ACtIveModelRefeTence.*ddEl~m~nt N y L i n e
+
‘ i r e a i r vertzzai Line Line5t.X = X LineSt.Y = Y + 1 L1neSt.Z = Z L1neEn.X = X L1neEn.Y = Y - 1 L1neEn.Z = Z Set N y L i n e = h p p l i c a t i O n . C T e a t e L i n e E l e m e n f 2 ( N o t h i n g , L i n e s t , L l n e E n ) Application.ActiveModelReference.BddEl~m~nt N y L i n e ’ create Circles CenPt.X = X CenPt.Y = Y CenPt.Z = Z Set N y C i r = Application.CreateEllipseElemenrZ(Nothing, C e n P t , 0 . 2 5 , 0.25, R o t N a t r i x ) Application.ActiveModelReference.BddElem~nt N y C i r Set N y C l r = A p p l i c a t i ~ n . C ~ e ~ F e E l l ~ p ~ ~ E( Nl m ~ hml n~ gn, c C2 e n P t , 0 . 5 , 0 . 5 , R o t M a t r i x ) AppliCatiOn.ACtIveModelRefeTence.lddEl~m~nt N y C i r End S m
And here is the code as it should be typed: P u b l i c X As Double P u b l i c Y As Double P u b l i c Z As Double Sub D r a w ( x As D o u b l e , Y As D o u b l e , Z As Double) ‘Declare Variables Dim MyLine A s L i n e E l e m e n t Dim MyCi r A s El 1 i p s e E l e m e n t
54
I Chapter 5: Modules, Forms, and Class Modules I D i m C e n P t As P o i n t 3 d D i m L i n e S t As P o i n t 3 d
D i m L i n e E n As P o i n t 3 d D i m R o t M a t r i x As M a t r i x 3 d 'Create Horizontal Line
1
LineSt.X
=
X
LineSt.Y
=
Y
LineSt.Z
=
Z
LineEn.X
=
X + 1
LineEn.Y
=
Y
-
LineEn.Z = Z S e t MyLine = Application.CreateLineElementZ(Nothing, L i n e S t , L i n e E n ) A p p l ic a t i on . A c t i v e M o d e l R e f e r e n c e . A d d E 1 e m e n t M y L i n e 'Create Vertical Line LineSt.X
=
X
LineSt.Y
=
Y
Z
LineSt.Z
=
LineEn.X
=
X
LineEn.Y
=
Y
+ 1
-
1
LineEn.Z = Z S e t MyLine = Application.CreateLineElementZ(Nothing, L i n e S t , L i n e E n ) A p p l ic a t i on . A c t i v e M o d e l R e f e r e n c e . A d d E 1 e m e n t M y L i n e 'Create Circles CenPt.X
=
X
CenPt.Y
=
Y
CenPt.Z
=
Z
Application.CreateEllipseElement2(Nothing, CenPt, 0.25, 0.25, R o t M a t r i x )
S e t MyCir
=
~
A p p l ic a t i on . A c t i v e M o d e l R e f e r e n c e . A d d E 1 e m e n t MyCi r S e t MyCir
= Application.CreateEllipseElementZ(Nothing, CenPt, 0.5, 0.5, R o t M a t r i x )
A p p l ic a t i on . A c t i v e M o d e l R e f e r e n c e . A d d E 1 e m e n t MyCi r End S u b
-
I Procedures and Functions I
55
Once everything is in place, we can use it as follows:
Private Sub CommandButtonl-Click() ’ DrawTarget CDbl(txtX.Text), CDbl(txtY.Text), CDbl(txtZ.Text) Dim MyTarget As New clsTarget MyTarget.X = CDbl(txtX.Text) MyTarget.Y = CDbl(txtY.Text) MyTarget.2 = CDbl(txt2.Text) MyTarget.Draw End Sub
Instead of using the D r a wTa r ge t procedure we previously created, we can use the &Target class. Comment out the ‘DrawTarget’ line of code. Next declare a variable as a “New clsTarget” then set the X, Y, and Z properties of the object and invoke the Draw method. Class modules are often considered reserved for advanced programmers but this doesn’t need to be the case. As we see here, classes can be implemented very easily and simply. We will discuss classes in greater detail in a later chapter.
PROCEDURES AND FUNCTIONS Modules, forms, and classes each use procedures and functions. Procedures are declared with the “Sub” keyword and functions are declared with the “Function” keyword. Let’s look at a few procedures and functions. Sub D r a w C i rcl e ( ) ‘Declare Variables
D i m MyCi r As E l 1 i p s e E l e m e n t D i m C e n P t As P o i n t 3 d D i m R o t M a t r i x As M a t r i x 3 d ‘Create C i r c l e CenPt.X
=
CenPt.Y
=
CenPt.Z
=
0 0 0
= Application.CreateEllipseElement2(Nothing, CenPt, 0.25, 0.25, R o t M a t r i x )
S e t MyCir
A p p l i c a t i o n . A c t i v e M o d e 1 R e f e r e n c e . A d d E l e m e n t MyCi r
56
I Chapter 5: Modules, Forms, and Class Modules I End Sub
D r a w C i rcl e draws a circle at (0, 0,O) with a radius of 0.25. It can be run by itself without the any other procedure or function. Sub D r a w C i r c l e 2 ( Radi us A s D o u b l e ) ‘Declare Variables D i m MyCir As E l l i p s e E l e m e n t
D i m CenPt As P o i n t 3 d D i m R o t M a t r i x As M a t r i x 3 d ‘Create C i r c l e CenPt.X
=
0
CenPt.Y
=
0
0 Set MyCir = Application.CreateEllipseElementZ(Nothing, CenPt, CenPt.Z
=
-
Radius, Radius, R o t M a t r i x ) Appl ic a t i on . A c t i veModel Reference.AddE1 ement MyCi r End Sub
D r a w C i rcl e2 is a procedure with a single required parameter. D r a w C i rcl e2 cannot be run by itself - it requires another procedure or
function to run it. When it is called by another function or procedure, the required parameter “Radius” must be supplied like this: D r a w C i r c l e2 1 . 5 Sub D r a w C i r c l e 3 ( x As D o u b l e , Y As D o u b l e , Z As D o u b l e , O p t i o n a l Radius As D o u b l e
=
~
1.25)
‘Declare Variables D i m MyCir As E l l i p s e E l e m e n t D i m CenPt As P o i n t 3 d D i m R o t M a t r i x As M a t r i x 3 d
‘Create C i r c l e CenPt.X
=
X
CenPt.Y
=
Y
CenPt.Z
=
Z
Set MyCir
=
Application.CreateEllipseElernentZ(Nothing, CenPt,
Radius, Radius, R o t M a t r i x ) Appl ic a t i on . A c t i veModel Reference.AddE1 ement MyCi r End Sub
I Proceduresand Functions I
57
0 rawCi r c l e3 requires us to provide X, Y, and Z values and gives the option of providing a radius. If we supply a radius, it uses the value we give it. If we do not provide the radius, it used a value of 1.25.
Here is one way to test our procedure DrawCi rcl e3: Sub T e s t D r a w C i r c l e3( 1 D r a w C i r c l e 3 2.25, 2.25, 0 D r a w C i r c l e 3 2.25, 2.25, 0, 1.125 End Sub
The first time we call DrawCircle3 we do not provide the optional parameter. The second time we call it we provide a radius value of 1.125. The first circle will be drawn with a radius of 1.25 (the default value) and the second will be drawn with a radius of 1.125. Once an Optional Parameter is declared in the Procedure, any parameters afrer it must also he optional.
Sub T e s t D r a w C i r c l e 3
0
Drawcircle3
2.25,
2.25,
Drawcircle3
2.25,
2.25,
0 0,
1.125
End Sub
As we call functions and procedures, VBA displays a tip that shows us the parameters for the function or procedure that we are calling. Notice how the radius parameter is enclosed in square brackets. The square brackets tell us that “Radius” is an optional parameter. We are also shown the value of the optional parameter that will be used if we do not supply a value. Another type of parameter that can be declared in a procedure or function is called ParamArray. A ParamArray must be the last parameter declared, because when we supply a value or values to the parameter in code, we can supply any number of values for the parameter. Here is an example of how it is declared and used in code: Sub D r a w C i r c l e 4 ( x As D o u b l e , Y As D o u b l e , Z As D o u b l e , P a r a m A r r a y R a d i i 0 As V a r i a n t ) ‘Declare Variables D i m MyCi r As E l 1 i p s e E l e m e n t D i m C e n P t As P o i n t 3 d
D i m R o t M a t r i x As M a t r i x 3 d D i m I As Long ‘Create Circles CenPt.X
=
X
58
I Chapter 5: Modules, Forms, and Class Modules I CenPt.Y
=
Y
CenPt.Z
=
Z
For I
LBound(Radii
=
To U B o u n d ( R a d i i
Application.CreateEllipseElementZ(Nothing, CenPt, Radii ( I1 , Radii ( I1 , RotMatrix)
S e t MyCir
=
-
A p p l ic a t i o n . A c t i v e M o d e l R e f e r e n c e . A d d E 1 e m e n t MyCi r Next I End Sub
We don’t know how many radius values w ill be provided in the Radii ParamArray. So, we use a For ... Next loop which allows us to look at each one and use it in creating a new circle. Here is an example of how we call a procedure with a ParamArray in code: Sub T e s t D r a w C i r c l
e4( )
D r a w C i r c l e 4 1, 1, 0 , 0 . 2 5 ,
0.5,
0.75,
1, 1 . 2 5 ,
1.5
End Sub
We provide an X of 1, a Y of 1, and a Z of 0. Then we begin providing radius values. After the code is run, we have six new circles in our Microstation design file. Here are the six circles created by T e s t D r a w C i r c l e4.
We have created over 100 lines of code so far in this chapter. The current module now has nine different procedures in it. Five of them can be run by themselves, the others must be called by other procedures or functions.
i
Speaking of functions, let’s examine them in detail. F u n c t i o n P i 0 As D o u b l e
Pi
=
Atn(1)
*
4
End F u n c t i o n
Here is a function named P i . It does not accept any parameters and the type of value it returns is a Double. We specify what value is to be returned by assigning the return value to the name of the function.
I Procedures and Functions I
59
This function, P i , can be used now wherever we need the value of Pi. The procedure DrawCi r c l e 3 allows us to provide the radius of the circle to be drawn. But what do we do if we only know the area of the circle we want drawn? We can calculate the radius if we know the area but we need the value of Pi to do so. Rather than hard-coding a value of “3.14159” for Pi, we can use the P i function we just created. Sub T e s t P i 0
D i m C i r c l e A r e a As D o u b l e D i m C i r c l e R a d i u s As D o u b l e CircleArea
=
C i r c l e R a d i us
3.5 =
Sqr(Ci rcleArea / P i )
DrawCi r c l e 3 2 . 5 ,
0 , C i r c l eRadi us
2.5,
End Sub
We calculate the radius of the circle based on a given area. We then use that value in the radius parameter of the procedure D r a wC ir c 1 e3. The function P i we just created does not have any parameters. It does not need them because the calculation is always the same. Let’s look at a few additional functions that come in handy from time to time. They are named RTD (Radians To Degrees) and DTR (Degrees To Radians). F u n c t i o n R T D ( A n g l e 1 n R a d i a n s As D o u b l e ) As D o u b l e RTD
=
AngleInRadians
*
180 / P i
End F u n c t i o n F u n c t i o n D T R ( A n g l e 1 n D e g r e e s As D o u b l e ) As D o u b l e DTR
=
AngleInDegrees
*
P i / 180
End F u n c t i o n
These two functions perform calculations that are very common to those of us who use Microstation. They are shown here for instructional purposes only because Microstation’s VBA implementation has a function named D e g r e e s that converts radians to degrees and a function named Ra d ia n s that converts degrees to radians. The functions DTR and RTD (as well as the functions D e g r e e s and R a d i a n s ) have one required parameter. Here is how we use them: Sub D r a w A r c l ( ) D i m MyArc As A r c E l e m e n t
D i m C e n P t As P o i n t 3 d D i m R o t M a t r i x As M a t r i x 3 d
60
I Chapter 5: Modules, Forms, and Class Modules I S e t MyArc 1.5,
=
Application.CreateArcElementZ(Nothing, C e n P t , 1 . 5 ,
-
R o t M a t r i x , DTR(45). DTR(90))
Application.ActiveModelReference.AddE1ement
MyArc
End Sub
C r e a t e A r c E l ernent2 requires several parameters. One of them is the Start Angle. Another is the Sweep Angle. Both parameters require the value to be given in radians. Many of us don’t normally think in radians, we think in degrees. So, we can use the DTR function shown above to convert from degrees (which we think in) to radians (which the function is expecting).
Here is the arc created by the above code. It begins at 45 degrees and has a sweep of 90 degrees.
Returning an Array Functions return a value, right? Yes. But functions can actually return more than one value through the use of an array. As we w ill discuss more in the next chapter, but for now know that an array is a variable that contains more than one value and that we can return an array in a function. Here’s what it looks like: F u n c t i o n P o l a r P o i n t ( X As D o u b l e , Y As D o u b l e , Z As D o u b l e ,
The underscore (J character allows one line ofcode to span multiple lines.
A n g l e As D o u b l e , D i s t a n c e As D o u b l e ) As V a r i a n t
D i m XChange As D o u b l e D i m YChange As D o u b l e XChange
=
Cos(Ang1e)
YChange
=
Sin(Ang1e)
* *
Distance Distance
D i m P P o i n t ( 0 To 2) As D o u b l e
PPoint(0)
=
X + XChange
PPoint(1)
=
Y + YChange
PPoint(2)
=
Z
Polarpoint End F u n c t i o n
=
PPoint
-
I Procedures and Functions I
61
The Po 1 a r P o in t function allows us to define a starting point (X, Y, and Z), an angle, and a distance. In return, we are given the resulting X, Y, and Z elements of the coordinate as an array. We return an array by declaring the return type of the function as a variant. As we will learn in the discussion on variables, a variant can hold any type of value, object, or array of values or objects. We declare an array of doubles within the function and then we assign the array variable to the function name. Here’s one way to test the P o l a r P o i n t function. Sub
TestPolarPointO D i m S t a r t C e n As P o i n t 3 d D i m C e n P t As P o i n t 3 d
D i m R o t M a t r i x As M a t r i x 3 d D i m X As V a r i a n t StartCen.X
=
StartCen.Y
=
2 2
StartCen.Z
=
0
S e t MyCir = Application.CreateEllipseElement2(Nothing, S t a r t C e n , 1, 1, R o t M a t r i x )
Application.ActiveModelReference.AddE1ement
-
MyCir
D i m R o t A n g l e As D o u b l e
For RotAngle
X
=
=
0 To 3 6 0 S t e p 3 0
PolarPoint(StartCen.X, D T R ( R o t A n g l e ) , 4)
CenPt.X
=
X(0)
CenPt.Y
=
X(1)
CenPt.Z
=
X(2)
StartCen.Y,
StartCen.Z,
~
S e t MyCir = Application.CreateEllipseElement2(Nothing, C e n P t , 1, 1, R o t M a t r i x )
Application.ActiveModelReference.AddElement Next RotAngl e End Sub
MyCir
-
62
I Chapter 5: Modules, Forms, and Class Modules I What do we get when we run T e s t P o l ar P o i n t ?
Returning 'Types' Thus far we have written functions that return either a single value or an array of values. You can also return types. Microstation VBA has a 'Point3d' type with three properties: X, Y, and Z. Let's copy and paste the PolarPoint function and make use of this type. F u n c t i o n P o l a r P o i n t 2 ( x As D o u b l e , Y As D o u b l e , Z As D o u b l e , A n g l e As D o u b l e , D i s t a n c e As D o u b l e ) As P o i n t 3 d
-
D i m XChange As D o u b l e D i m YChange As D o u b l e
XChange
=
Cos(Ang1e)
YChange
=
Sin(Ang1e)
* *
Distance Distance
D i m P P o i n t ( 0 To 2 ) As D o u b l e PolarPoint2.X
=
X
PolarPoint2.Y
=
Y
PolarPoint2.Z
=
Z
+ XChange + YChange
End F u n c t i o n
Instead of returning an array as in the previous example, we are returning a Point3d type. Here is an example that uses the Po 1 a r Po in t 2 function: Sub T e s t P o l a r P o i n t 2 ( )
D i m S t a r t C e n As P o i n t 3 d D i m C e n P t As P o i n t 3 d D i m R o t M a t r i x As M a t r i x 3 d D i m X As V a r i a n t
StartCen.X
=
2
I Procedures and Functions I StartCen.Y
=
2
StartCen.Z
=
0
63
S e t MyCir = Application.CreateEllipseElement2(Nothing, S t a r t C e n , 1, 1, R o t M a t r i x )
~
A p p l i c a t i o n . A c t i v e M o d e 1 R e f e r e n c e . A d d E l e m e n t MyCi r
D i m R o t A n g l e As D o u b l e For RotAngle CenPt
=
0 To 3 6 0 S t e p 3 0
= PolarPointZ(StartCen.X, D T R ( R o t A n g l e ) , 4)
StartCen.Y,
StartCen.Z,
S e t MyCir = Application.CreateEllipseElement2(Nothing, C e n P t , 1, 1, R o t M a t r i x )
~
~
A p p l i c a t i o n . A c t i v e M o d e 1 R e f e r e n c e . A d d E l e m e n t MyCi r Next RotAngl e End Sub
Returning Objects One additional return type is worth mentioning. In addition to returning values and types, a function can return objects. Here is one example. F u n c t i o n GetExcel WS() As O b j e c t D i m E x c e l A p p As O b j e c t
Set ExcelApp
=
S e t GetExcelWS
Getobject(, =
"Excel . A p p l i c a t i o n " )
ExcelApp.activesheet
End F u n c t i o n
This function gets the active worksheet in Microsoft Excel. Excel must be running for this function to work correctly. How do we use it? Let's take a look. Sub TestGetExcel WS()
D i m MyWS As O b j e c t D i m C e l l l As D o u b l e D i m C e l l 2 As D o u b l e D i m C e l l 3 As D o u b l e
S e t MyWS Celll C e 112
C e 1 13 End Sub
=
GetExcelWS
=
MyWS.Range("B2") My W S . Ra n g e ( " C 2 " )
=
My W S . R a n g e ( " D 2 " )
=
64
I Chapter 5: Modules, Forms, and Class Modules I This procedure gets the values of three cells in Excel. It really is very easy to get data from or write data to Excel. We will discuss more on working with Microsoft Excel later in this book.
ByVal and ByRef We have seen how we can provide parameters when we call procedures and functions. By default, values are passed by reference. The other way values can be passed is by value. What do these mean? Sub
GetThreeVals(x
As D o u b l e , Y As D o u b l e , Z A s D o u b l e )
X = l Y = 2
z = 3 End Sub
This procedure accepts three parameters. Inside the code, we use the parameter’s names and assign values to them. It is important to understand this is because using variables directly in this manner will change the values in the function or procedure that calls this procedure. Sub
TestGetThreeValsO D i m A As Double D i m B As Double D i m C As Double A
=
B
=
C
=
100
200 300
GetThreeVals A , B, C End Sub
Here we have variables A, B, and C. We assign values of 100, 200, and 300 respectively. Then we use these variables (A, B, and C) when we call G e t T h r e e V a l s. Since the procedure G e t T h r e e V a l s has its parameters declared without the keyword “ByVal”, the values are passed into the procedure “ByRef”. ByRef means the values of the parameters may be modified inside the procedure. And if they are modified in the procedure, the variables will maintain these values outside of the procedure. So, before the line of code “GetThreeVals A, B, C” is executed, the values of A, B, and C are 100, 200, and 300. After G e t T h r e e V a l s is executed, the values of A, B, and C are 1,2, and 3.
I Procedures and Functions I
65
GetThreeVal s changes the values of the parameters that are passed in. This can be a powerful feature if it is used correctly. It can also cause a great deal of confusion if it is not understood. Suddenly, variables that were holding one value could hold another value. If we do not want a function or procedure to change the values of the variables passed as parameters, there are a couple of ways we can do this. The first technique requires discipline on our part. The second technique is a more definite method. Sub G e t T h r e e V a l s P ( X As D o u b l e , Y As D o u b l e , Z A s D o u b l e ) Dim dblX As Double Dim dblY As Double Dim d b l Z As Double dblX = X dblY = Y dblZ = Z dblX = 1 dblY = 2 dblZ = 3 End Sub
Instead of manipulating the parameters directly, we place the values of the parameters into variables declared inside the procedure. Then we manipulate these local variables. This keeps us from changing the parameters. Another way to maintain the integrity of the parameters passed into our functions and procedures is to declare them explicitly as “ByVal”. Sub G e t T h r e e V a l s 3 ( B y V a l X As D o u b l e , ByVal Y A s D o u b l e , ByVal Z A s D o u b l e ) X = l Y = 2 z = 3 End Sub
-
Taking the additional step of declaring a parameter as ByVal guarantees the integrity of the parameters.
66
I Chapter 5: Modules, Forms, and Class Modules I Declaring Variables Variables are used extensively throughout our code. Variables are declared with a name and a type. We will learn more about this in the next chapter. What is important to understand now is that variables have a scope. There is a pre-determined amount of time when a variable can be used. The variable‘s scope depends on where it is declared and what keywords (if any) are used when it is declared. There are two places where variables can be declared. One place is inside the procedures and functions in which they will be used. We have seen numerous examples of this so far. The other place we declare variables is in the General Declarations area of code modules, forms, and class modules.
D i m S t a r t P o i n t X A s Double P r i v a t e S t a r t P o i n t Y A s Double P u b l i c S t a r t P o i n t Z A s Double
In the General Declarations area of this code module, I declared three variables as the same type (as doubles) but used different keywords to declare them: Dim, Private, and Public. D i m S t a r t P o i n t X as Double
Declaring a variable with “Dim” in the General Declarations area of a code module or form code area means the variable is only available for use from within that module, form, or class module. StartPointX was declared by using the “Dim” keyword so, again, it can only be used from within the module or form in which it is declared. ‘Private’ has the same effect as using ‘Dim: You can use the variable within any function or procedure in the code area in which it is declared. One function can set its value and another function in the same module, form, or class can read the value. ‘Public’variable declarations behave differently depending on where the declarations are made.
I Review I
67
Forms - Publicly declared variables are in scope when the form is in scope (usually only when it is displayed). Other areas of a project can access the variable through the form’s name. For example, UserForml.TestVariable
=
4.5
We can use the variable Testvariable only by addressing it through the form and the form must be ‘in scope’ for this to work. Modules - Publicly declared variables are in scope for all areas within the same project. Classes - Publicly declared variables are seen as read/write properties for the class.
Option Explicit By default, if we attempt to use a variable that is not declared, it inherits the type of ‘Variant: We can force ourselves to declare variables by using “Option Explicit” in the General Declarations area of modules, forms, and classes. In this example, we have declared “Option Explicit” in the General Declarations area. When we attempt to run the macro test shown above we get an error.
To avoid this error, we need to declare X as a double, integer, or long. More on variable types in the next chapter.
e
REVIEW Write code as procedures, functions, or inside user form events. In procedures and functions utilize required and optional parameters.
B In functions you can return values, arrays, types, and objects.
I Chapter 5: Modules, Forms, and Class Modules I In procedures and functions you can make changes to the variables passed into them as parameters if the parameters are declared as “ByRef”.Declare a parameter as “ByVal” to keep the variable’s value from changing. Declare variables in procedures, functions, and events or in the General Declarations area. The scope of these variables depends on where they are declared and what keywords accompany the declaration.
Variables 1+N+3=7 What is N?N is a variable. In the above equation it represents a number. If we were to solve for N we would get a value of 3. “Learning Microstation VBA “ & N & “ Easy.” What is N? N is a variable. In the above equation it represents a string of characters. What string of characters does it represent? ‘‘Is’: In this Chapter:
Standard VBA Variable Trpes Microstation-Specific Variable Trpes Assigning Values and Setting Objects Arrays Constants Variable Names Option Explicit Using Variables 69
E
70
I Chapter 6: Variables I STANDARDVBA VARIABLE TYPES A variable is a name that represents a value or an object. The examples above show variables with a name of N. In one instance the variable holds a numeric value. In the other it holds a string of characters. In general, we know in advance what type of value or object a variable will be representing. Since we know this, we specify what type of variable we will use by declaring it. D i m N as I n t e g e r N = 7
-
3
1
-
Here, we declare the variable N as an integer. This means it will be a whole number between -32,768 and 32,767. D i m N As S t r i n g
N
=
"IS"
MsgBox " L e a r n i n g M i c r o s t a t i o n V B A
"
& N &
"
Easy."
Here we declare N as a string. A string is a group of characters. After a variable is assigned a value, you can use it in the place of a number, text, or some other type of value or object. We will use variables extensively throughout this book. Let's examine some of the more common types of variables available to us.
Integer D i m PageNumber a s I n t e g e r PageNumber
=
2
We said that an integer is a whole number between -32,768 and 32,767. If we need a variable to hold a value greater than or less than the range of an integer, we must declare it as something different.
Long D i m M y S a l a r y as Long MySalaray
=
123456
A long is a whole number between -2,147,483,648 and 2,147,483,647. These numbers are much larger than those in the range of an integer. It
I Standard VBA Variable Types I
71
requires more memory to allow for this greater range of numbers so we should only use it when we need it.
Double D i m HoursToLearnVBA as D o u b l e HoursToLearnVBA
=
36.25
A double is also called a double precision floating point number. What does that mean? It means the precision available for a double is twice the precision available for single (also a variable type but not used as much) and the decimal point can float to allow for greater precision of small numbers or larger numbers with less precision. It is important to understand the “floating point” portion of the description. If we expect an extremely large number to be extremely accurate, we may not only be disappointed but we could have less accurate results than we expected.
Consider this next macro, V a r i a b l eTestC. It has a variable named N declared as a double in which the numbers “1234567890123456789” have the decimal in a different position each time with the last two numbers shown are ‘46: VBA rounds the ‘456’ number to ‘46’ because a double variable is given a specific amount of memory in which to keep its value. When we attempt to put more in it than it can handle, it rounds the number to something it can hold. Sub
VariableTestCO D i m N As Double N
=
1.23456789012346
N
=
12.3456789012346
N
=
123.456789012346
N
=
1234.56789012346
N
=
12345.6789012346
N
=
123456.789012346
N
=
1234567.89012346
N
=
12345678.9012346
N
=
123456789.012346
N
=
1234567890.12346
N
=
12345678901.2346
72
I Chapter 6: Variables I N
=
123456789012.346
N
=
1234567890123.46
N
=
12345678901234.6
End Sub
Doubles can hold very precise numbers but as the value of the number increases, the precision decreases. This is something to keep in the back of your mind as you develop applications.
Boolean D i m ICanLearnThis as Boolean
ICanLearnThis
=
True
A Boolean data type can hold one of two values: True or False.
Date D i m XMReleaseDate as Date XMReleaseDate
=
“5/19/2006 8:OO:OO AM”
A Date data type holds a Date/Time value.
String D i m MyLevelName a s S t r i n g M y L e v e l Name
=
“ u t i 1E l e c t r i c i t y ”
A string data type contains text. Letters, numbers, and other characters we on our computer keyboards can be held inside this variable. We have seen that numeric variable types have ranges of values. This is because their data types have a predefined amount of memory set aside for each variable. Strings are no different. So how many characters can be held inside a string variable? Approximately 2 billion (2,000,000,000). That is a lot of characters.
Object D i m M y E x c e l A p p as O b j e c t
S e t MyExcelApp
=
Getobject(, “Excel . A p p l i c a t i o n ” )
Object type variables point to objects. The variable MyExcelApp, for example, could point to an instance of Microsoft Excel, an application. Microsoft Excel is an object with specific properties, methods, and
I MicroStation-SpecificVariable Types I
73
events. Others objects have their own unique properties, methods, and events. When we declare a variable as an object, it is a generic object without any previous knowledge of its properties, methods, or events. Only after we set the variable to an object does it know what kind of an object it is as well as its other attributes.
Variant D i m P o i n t A r r a y as V a r i a n t
Variables declared as a variant can hold any type of value, point to any type of object, or even contain an array of values.
MICROSTATION-SPECIFIC VARIABLE TYPES The variable types we have discussed are standard VBA variable types. They can be used in Microstation VBA, in Excel VBA, in Word VBA, or in Access VBA. Let’s consider some of the variable types specific to Microstation that we will regularly use.
A ppIicat ion D i m MSApp As A p p l i c a t i o n S e t MSApp
=
Application
The application variable type points to the Microstation application. It is the top level object when dealing with Microstation. A few of the things we can do from a variable declared and set to the Microstation application are: Get the ActiveDesignFile property Get the ActiveModelReferenceproperty Get the Activesettings object and its properties Get the ExecutingVBProject object and its properties Get the UserName property Get the left, top, width, and height properties
74
I Chapter 6: Variables I DesignFile D i m MyDGN As D e s i g n F i l e S e t MyDGN
=
Application.ActiveDesignFile
The DesignFile object refers to a Microstation DGN file. Top-level DGN properties and collections are available to us via the DesignFile object. Get and set the Author, Client, Comments, Company, Keywords, Manager, Subject, and Title Properties Get the FormatMajorVersion and FormatMinorVersion properties Get the Levels collection Get the Models collection Get the Name and Path properties
ModelReference D i m MyModel As M o d e l R e f e r e n c e S e t MyModel
=
Appl i c a t i o n . A c t i v e M o d e 1 Reference
The ModelReference object is where the rubber meets the road. When we draw inside a file, we do it through the ModelReference object. When we want to find out what is in a file, we do it through the ModelReference object. We will work extensively with this object throughout this book.
Level D i m M y L e v e l As L e v e l S e t MyLevel
=
A p p l i c a t i o n . A c t i v e D e s i g n F i 1e . L e v e l s ( l )
Levels allow us to divide our designs into groups of objects. We usually group our objects based on their specific type of geometry or annotation. Road centerlines may be placed on one level, Lot numbers on another, and title block lines on another, etc. Here are a few of the properties we can get and set from the level object: Description ElementColor ElementLineStyle
I Microstation-SpecificVariable Types I
75
ElementLineWeight IsActive IsDisplayed IsFrozen El IsLocked
Name Number Plot
LineElement D i m M y L i n e As L i n e E l e m e n t
Set MyLine
=
Application.CreateLineElementZ(Nothing,
Point3dFromXYZ(O, 0 , O ) , Point3dFromXYZ(4,
5 , 6))
A p p l ic a t i o n . A c t i veModel R e f e r e n c e . A d d E l e m e n t M y L i n e
A LineElement is created with a start point and an end point. After it is created, we can add it to our model.
EllipseElement D i m M y C i r c l e As E l l i p s e E l e m e n t D i m R o t M a t r i x As M a t r i x 3 d Set MyCircle
=
CreateEllipseElementZ(Nothing,
Point3dFromXYZ(O, 0 , O ) , 1 . 5 ,
1.5,
-
RotMatrix)
A p p l i c a t i o n . A c t i v e M o d e 1 R e f e r e n c e . A d d E l e m e n t MyCi r c l e
Lines, circles, and arcs form the basis of much of the geometry found in our Microstation files. From Microstation’s perspective, circles are essentially ellipses with equal major and minor radii. The code shown above draws a circle centered at (0, 0,O) with a radius of 1.5.
ArcElement D i m MyArc As A r c E l e m e n t D i m R o t M a t r i x As M a t r i x 3 d
S e t MyArc
=
CreateArcElementZ(Nothing,
-
Point3dFromXYZ(O, 0 , O ) , 1.75,
1.75,
76
I Chapter 6: Variables I RotMatrix, Radians(45).
Radians(90))
Application.ActiveModelReference.AddElement
MyArc
We are given several ways to create a new LineElement, EllipseElement, and ArcElement. In this example, we create a new ArcElement by providing a center point, major and minor radii, a start angle, and a sweep angle.
Text Element D i m M y T e x t As T e x t E l e m e n t D i m R o t M a t r i x As M a t r i x 3 d S e t MyText
=
C r e a t e T e x t E l e m e n t l ( N o t h i n g , " M i c r o S t a t i on V B A " ,
P o i n t 3 d F r o m X Y Z ( O , 0 , 0). R o t M a t r i x )
Application.ActiveModelReference.AddE1ement
MyText
The TextElement object needs the text to display and a starting point. When it is created we can set other properties such as the color, level, and textstyle (which includes font, size, etc.). We use many more types of objects when programming Microstation in VBA and there is much more to learn about the objects we have just introduced. They will be covered in greater detail as we continue to learn Microstation VBA.
ASSIGNINGVALUES AND SETTING OBJECTS Value-type variables and object-type variables are declared similarly. When giving the variables values or setting them to objects, there is one major difference. D i m LevelName As S t r i n g
L e v e l Name
=
"Easement"
D i m E a s e m e n t L e v e l As L e v e l
S e t EasementLevel = A c t i v e D e s i g n F i l e . A d d N e w L e v e 1 ( L e v e l Name)
Here we have two variables. One is declared as a string and the other as a Level. We assign a value to the LevelName variable by stating the variable by name, using an equal sign, and then the value we want it to have. When we use the Level object, we use the keyword 'Set' to set the variable to an
I Arrays I
77
object. We only use ‘Set’ when we are setting a variable to an object. After it is set to an object we can address it by its name without using Set.
ARRAYS When we think about an array in Microstation, we think about taking an element and copying it multiple times. An array in VBA is a single variable name with multiple elements in it. D i m S t a r t P o i n t ( 0 t o 2 ) as D o u b l e
StartPoint(0)
=
4.5
StartPoint(1)
=
5.6
StartPoint(2)
=
6.7
In this example, we declared a variable as an array with three elements numbered 0, 1, and 2. We can address the elements individually by specifying their index within the array. In the next example, I created an array where each Point3D type has an X property, a Y property, and a Z property. Notice how I addressed each element in the array by its index (0 and 1) and then addressed the X, Y, and Z properties. Sub ArrayTestA() D i m M y V e r t i c i e s ( 0 To
1) A s P o i n t 3 d
D i m M y L i n e As L i n e E l e m e n t
MyVerticies(O).X
=
1
MyVerticies(O).Y
=
2
MyVerticies(O).Z
=
3
MyVerticies(l).X
=
4
MyVerticies(l).Y
=
5
MyVerticies(l).Z
=
6
S e t MyLi ne
=
C r e a t e L i neEl e m e n t l ( N o t h i ng, M y V e r t ic i es)
ActiveModel Reference.AddElement MyLine End S u b
78
I Chapter 6: Variables I CONSTANTS A constant is similar to a variable with one significant difference: a constant value does not change. C o n s t PI As Double
=
3.14159
You can declare constants as public in the General Declarations area of a code module, as shown above, from within individual procedures and functions. Constants are useful any time you need a value that does not change. For example, if you are writing a program that labels line lengths in a design file, you could specify a constant for the distance the text is to be offset from the line. A constant can also provide a units conversion factor, such as from inches to cubits. Another reason to use a constant is for calculations that make routine references to specific values, such as a ShimWidth value in multiple calculations. Instead of using a value of 0.6 when we make the calculations, we can declare a constant with a name of Shimwidth and assign it a constant value of 0.6. This makes our code easier to read and allows us to change our Shimwidth value in only one place (where the constant is declared) instead of wherever the value is used.
VARIABLE NAMES Thousands of pages of text have been devoted to naming variables. The best place to start this discussion is with the rules imposed on us by VBA. Variables must begin with an alpha character (A through Z). Variable names cannot contain spaces. Name characters are A-Z, 0-9, and - (underscore). Variable names must be 255 characters or less. Variable names cannot be keywords (such as ‘Dim’,‘New’,‘Left’, ‘Right’,‘Form’,‘Public’). Letters used in variable names can be uppercase or lowercase.
I Variable Names I
79
Based on the rules already identified, here are a few variable declarations that work: D i m m y L i n e As L i n e E l e m e n t D i m t x t e M y T e x t As T e x t E l e m e n t
D i m s t r N a m e As S t r i n g D i m d b l S t a r t X As D o u b l e D i m intLevelNumber As I n t e g e r D i m p t 3 d S t a r t P o i n t As P o i n t 3 d
Each of the declarations shown above are legitimate variable declarations. They follow the rules. The first, myLine, is slightly different than the others. Each of the other declared variables begins with characters that identify the type of variable. strName says the variable type is a string. dblStartX says we are working with a double type variable. It is important to know if a project requires using variable naming conventions. A naming convention is an additional set of rules on how to name variables. For example, one convention may state that each variable name begin with three characters followed by an underscore (-) character, then a name consisting of no more than seven characters. Another convention may not use an underscore. Yet another convention may require that the scope of the variable be identified inside the variable’s name. As mentioned, many pages have been devoted to the topic of variable naming conventions, so we will not spend much time here on the subject. You should understand that naming a variable myLine or IineMyLine or line123 does not cause your program to work any differently than naming it elemline-LineA. Naming conventions can extend beyond variable names. Procedure names, function names, and control names can also be within the scope of a naming convention. Here is a link to a web page that discusses variable naming conventions:
http://msdn.microsoft.com/library/en-us/modcore/html/ deconVariab1eNames.asp Another way to become familiar with naming conventions is to search online for “variable naming convention” or “Hungarian Notation”.
80
I Chapter 6: Variables I Case Sensitivity Consider the following variable declarations: D i m myLine As LineElement
D i m M Y l i n e As L i n e E l e m e n t
Since VBA is not concerned with capitalization in variable names, the two variables declared above are the same. When we use a variable inside VBA, VBA automatically changes the capitalization to that used in the declaration. Some programmers use this to make sure they do not introduce typos into their code. They may always use a capital letter somewhere in the variable name when they declare it and then type all lower-case when they use it. When a line of code is complete and the cursor is moved to the next line of code, VBA automatically updates the capitalization of the variable that had been typed in lowercase.
OPTION EXPLICIT We have spoken for a while about variable types and declaring variables. There are many arguments as to why we should declare our variables. However, VBA does not force us to do so. It is possible to use a variable even if it is not formally declared. When we do this, the variable takes on the characteristics of a specific variable type when it is first used. For example, it will be a ‘Variant Double’ if its value is 1.23456. Or it will become a ‘Variant String’ if its value is “owhatafooliam’: One way we can make sure we declare our variables is to use “Option Explicit” in the
I Using Variables I
81
General Declarations area of our modules. Another way is to go to the VBA menu Tools > Options.
OK
Checking the ‘Require Variable Declaration’ button causes VBA to enter “Option Explicit” for us.
USING VARIABLES After a variable is declared and a value is applied to it or it is set to an object, the variable can be used any time the value is needed. Sub V a r i a b l e T e s t D O
D i m MySalary A s Double D i m MyHourly A s Double MySalary
=
1234567
MyHourly
=
MySalary / 52 / 40
MsgBox “My Hourly R a t e i s ” & FormatCurrency(MyHourly, 2, v b f a l s e , v b f a l s e , vbTrue)
~
End Sub
Here we have two variables. One of them (MySalary) is given a value of 1234567. We then use MySalary to calculate MyHourly. We then use the standard VBA function Formatcurrency to convert the variable MyHourly to a two-decimal place currency value
82
I Chapter 6: Variables I and concatenate “My Hourly Rate is” with the result of the Format C u r rency function.
REVIEW Variables are names that hold values or refer to objects. Variables declared within a function, procedure, or event are local to that function and cannot be used outside of it. Variables declared in the General Declarations area of a form or code module can be used from within the form or code module in which they are declared. Variables declared as ‘Public’ inside a code module can be used anywhere in the VBA project. Variables declared as ‘Public’ in class modules become read/write properties of that class module. We will use variables extensively throughout this book. After all, without variables everything would be static - nothing could change. Lines would always be drawn from the same point to the same point and text would always be inserted at the same point and would always say the same thing.
7
Working With Text We work with text every day. This book is composed of text: words, phrases, sentences, paragraphs. The ability to work with text is invaluable in our programming. Recall that the type of variable that deals with text is a String. Sub
TextWorkOl() D i m B o o k T i t l e As S t r i n g B o o k T i t l e = “ L e a r n i n g M i c r o s t a t i o n VBA” MsgBox U C a s e ( B o o k T i t 1 e ) MsgBox L C a s e ( B o o k T i t 1 e ) MsgBox L e f t ( B o o k T i t l e , 12) MsgBox R i g h t ( B o o k T i t l e , 12)
End S u b
In this example, we have a variable named BookTitle that is declared as a String. It is given a value of “Learning Microstation VBA”. Four different functions are then used with the variable BookTitle as a parameter and the result displays in four MessageBoxes.
83
84
I Chapter 7: Working With Text I
VBA STRINGFUNCTIONS Let's take a look at each of the VBA functions that deal with text (Strings) one-by-one.
UCase Function UCase(Stri ng)
The U Ca s e function converts the supplied string to upper case. Sub TextWorkOP( D i m s t r N e w L e v e l As S t r i n g s t r N e w L e v e l = I n p u t B o x ( " E n t e r New L e v e l N a m e : " ) strNewLevel = UCase(strNewLeve1 A p p l ic a t i on . A c t i v e D e s i g n F i 1 e.AddNewLeve1 s t r N e w L e v e 1 End Sub
sidewalk
In this example we use an InputBox to allow the user to enter the name of a new level. We then convert it to upper case and use it to add a new level (AddNewLevel) to the active design file.
All Levels Filters
Function LCase(Stri ng)
The LCase function converts the supplied string to lower case. Sub TextWork03( D e b u g . P r i n t LCase("LCase Lowers C a p i t a l L e t t e r s . " ) End Sub
I VBA String Functions I Debug.Print is used to place text in the Immediate Window. It is often used to display text to aid in debugging our applications. To view the lmmediate Window, go to the VBA menu View > Immediate Window.
85
lcase lowers c a p i t a l l e t t e r s .
In this example we used text directly in the function instead of assigning the text to a variable.
StrConv F u n c t i o n S t r C o n v ( S t r i n g , C o n v e r s i o n As V b S t r C o n v ,
-
[ L o c a l e I D As L o n g ] )
StrConv is used to convert the provided string through a variety of parameters. The constant most used with StrConv is 'vbProperCase'. Sub T e x t W o r k 0 4 ( ) D i m B o o k T i t l e As S t r i n g B o o k T i t l e = " l e a r n i n g m i c r o s t a t i o n vba" MsgBox S t r C o n v ( B o o k T i t l e , vbProperCase) End Sub
This example uses the vbProperCase constant to capitalize the first letter of each word.
WeekDayName, WeekDayNumber F u n c t i o n We e k d a y N a m e(W eekday As L o n g , [ A b b r e v i a t e As B o o l e a n = F a l s e ] , C F i r s t D a y O f W e e k A s VbDayOfWeek = vbUseSystemDayOfWeek1) As S t r i n g
-
Each day of the week (Sunday through Saturday) has a number assigned to it. The WeekdayName function takes that number and converts it to the day's name. Sub T e x t W o r k 0 5 ( ) D i m TodaysDate A s Dat e D i m WeekDayNumber A s Long TodaysDate = N o w WeekDayNumber = Weekday(T0daysDate) MsgBox WeekdayName(WeekDayNumber) MsgBox WeekdayName(WeekDayNumber, T r u e ) End Sub
86
I Chapter 7: Working With Text I The first parameter supplied to WeekdayName is the day number. The second parameter, 'Abbreviate', is optional with a default of false. When we supply a value of true, the WeekDayName is abbreviated.
P
MonthName F u n c t i o n MonthName(Month As Long, [ A b b r e v i a t e As B o o l e a n = F a l s e ] ) As S t r i n g
The MonthName function is similar to the WeekdayName function but as the name implies, it returns the name of the month instead of the name of the day. Sub TextWork05B() D i m MonthNum As L o n g F o r MonthNum = 1 To 1 2 D e b u g . P r i n t MonthName(MonthNum1 N e x t MonthNum End Sub
January February March April
Wau June July August September October November December
LTrim, RTrim, Trim Function LTrim(String) Function RTrim(Stri ng) Function T r i m ( S t r i ng)
Use the Tr im functions to remove spaces from the beginning, end, and both beginning and end of a string. Sub TextWorkOG(
1
D i m S t r i n g T o T r i m As S t r i n g S t r i n g T o T r i m = " T r i m F u n c t i o n s T r i m Space C h a r a c t e r s . Debug. P r in t L T r im ( S t r in g T o T r im 1 Debug. P r in t RTr im ( S t r in g T o T r im 1 Debug. P r in t T r im ( S t r in g T o T r im 1 End Sub
"
I VBA String Functions I
87
StrComp Function StrComp(String1, String2, [Compare A s VbCompareMethod = vbBinaryCompare1)
The need to compare two pieces of text is common. Is "Sidewalk" the same as "SIDEWALK"? Not always. Sub TextWork07() D i m s t r N e w L e v e 1 As S t r i n g D i m 1 v l E x i s t L e v e l As L e v e l s t r N e w L e v e 1 = I n p u t B o x ( " E n t e r New L e v e l Name:") F o r Each l v l E x i s t L e v e l I n A p p l ication.ActiveDesignFile. L e v e l s I f S t r C o m p ( s t r N e w L e v e 1 , 1 v l E x i s t L e v e l .Name, v b T e x t C o m p a r e ) = 0 Then MsgBox " T h e l e v e l " & s t r N e w L e v e 1 & " a l r e a d y e x i s t s . " E x i t Sub End I f Next Application.ActiveDesignFile.AddNewLeve1 s t r N e w L e v e 1 End Sub ~
This procedure asks the user for a new level name. It compares the newly-entered name with the name of each existing level name. If it finds a match, a MessageBox displays and we exit the procedure.
StrComp allows us to specify how the provided text is to be compared. In the above example, the constant 'vbTextCompare' returns a value of zero (0) when the characters are the same, independent of the capitalization. With 'vbTextCompare', "SWalk" and "swalk" are the same. Sub TextWork08() Debug. P r i n t Debug. P r i n t Debug.Print Debug.Print End Sub
StrComp("SWa1 k " , StrComp("swa1 k " , StrComp("SWa1 k " , StrComp("swa1 k " ,
"swal "SWal "swal "SWal
k", k", k", k",
vbTextCompare) vbTextCompare) vbBinaryCompare) vbBinaryCompare)
88
I Chapter 7: Working With Text I
0 0 -1 1
StrComp lets us know whether the provided text is the same but it also tells us which text comes before the other. It is often used for sorting text alphabetically. Here is one more example of StrComp, called a bubble sort. It takes an array of strings and sorts them alphabetically. This technique is a little more advanced, so it may be good to return to it after we have learned more VBA programming. Sub Bubbl e S o r t ( 1 D i m s t r N m s ( 0 To 7 ) As S t r i n g strNms(0) = " J e r r y " strNms(1) = "Candice" strNms(2) = "Brandon" strNms(3) = "Kyle" strNms(4) = "Benjamin" strNms(5) = "Jacob" strNms(6) = "Nathan" strNms(7) = " O l i v i a " D i m Madechange As B o o l e a n D i m tmpName As S t r i n g D i m I A s Long Madechange = T r u e W h i l e Madechange = T r u e Madechange = F a l s e F o r I = L B o u n d ( s t r N r n s ) To U B o u n d ( s t r N m s ) - 1 I f S t r C o r n p ( s t r N r n s ( 1 ) . s t r N m s ( 1 + l), V b B i n a r y C o m p a r e ) = 1 Then trnpNarne = s t r N r n s ( 1 ) s t r N r n s ( 1 ) = s t r N r n s ( 1 + 1) s t r N m s ( 1 + 1 ) = tmpName Madechange = T r u e End I f Next I Wend F o r I = L B o u n d ( s t r N m s 1 To U B o u n d ( s t r N m s ) Debug.Print I & " " & strNrns(1) Next I End Sub
Let's break down this procedure into segments.
I VBA String Functions I D i m strNms(0 strNms(0) = strNms(1) = strNms(2) = strNms(3) = strNms(4) = strNms(5) = strNms(6) = strNms(7) =
To 7 ) A s S t r i n g "Jerry" "Candice" "Brandon" "Kyle" "Benjamin" "Jacob" "Nathan" "Olivia"
The first thing we do is declare an array of strings and give each element in the array a value. D i m Madechange A s B o o l e a n D i m tmpName A s S t r i n g D i m I As L o n g Madechange = T r u e
Now we are setting up for the sorting portion of our routine. We want to run through the sorting portion at least once so we set the Madechange variable to True and then immediately begin a While ... Wend routine. W h i l e Madechange = T r u e Madechange = F a l s e F o r I = L B o u n d ( s t r N m s ) To U B o u n d ( s t r N m s ) - 1 I f StrComp(strNms(I), strNms(1 + 11, vbBinaryCompare) = 1 Then tmpName = s t r N m s ( 1 ) s t r N m s ( 1 ) = s t r N m s ( 1 + 1) s t r N m s ( 1 + 1) = tmpName Madechange = T r u e End I f Next I Wend
The above section is the heart of the routine. We continue to look at each value in the strNms array and compare it to the value in the array just after it. If the value we are looking at is alphabetically greater than the one after it, we swap the two elements in the array and set the Madechange variable to True. Only after each value is examined and a swap is not made do we continue with the next segment of our code. F o r I = L B o u n d ( s t r N m s ) To U B o u n d ( s t r N m s ) Debug.Print I & " " & strNms(1) Next I
The last little segment of code prints out the elements in the strNms variable array in their sorted condition.
89
90
I Chapter 7: Working With Text I Come back to this procedure after we have spent a little more time working with VBA and it will be easier to follow. Sorting text is accomplished easily and quickly using a Bubble Sort with the StrComp function.
Len Function Len(Expression1
The Len function tells us how many characters are in a string. Sub TextWork09( D i m L e v e l N a m e As S t r i n g L e v e l N a m e = I n p u t B o x ( " E n t e r new l e v e l name: " & (Must b e 8 c h a r a c t e r s ) " ) I f L e n ( L e v e l N a m e 1 <> 8 T h e n MsgBox " T h e l e v e l name m u s t b e 8 c h a r a c t e r s . T r y a g a i n . " End I f End Sub ~
In this example, we ask the user for a new level name. We also request that the name be eight characters long. After the value is entered, we use the Len function to check the length. If it is not eight characters (<> means not equal to), we ask the user to try again.
F u n c t i o n L e f t ( S t r i n g , L e n g t h As L o n g )
The Left function allows us to provide a string and specify how many characters we want returned to us beginning with the first character (left) of the string. Sub T e x t w o r k l o ( D i m F i l e P a t h As S t r i n g D i m F i l e D r i v e As S t r i n g F i l e P a t h = A p p l ic a t i o n . A c t i v e D e s i g n F i 1 e . P a t h F i l e D r i v e = L e f t ( F i 1 e P a t h . 1) MsgBox " T h e c u r r e n t f i l e i s o n t h e " & F i l e D r i v e & End Sub
"
drive."
Here, we get the path of the active design file. We then look at the first character of the FilePath variable and put it into the FileDrive variable. A MessageBox then displays the FileDrive variable with some other text.
Right F u n c t i o n R i g h t ( S t r i n g , L e n g t h As L o n g )
I VBA String Functions I
91
You guessed it. The R i gh t function is a companion to the L e f t function. It gives us the number of characters we ask for beginning with the last (right) character. Sub T e x t W o r k l l O D i m FileName As S t r i n g F i 1 eName = D i r ( " C : \ P r o g r a m F i 1 e s \ B e n t l e y \ M i c r o S t a t i o n \ * . * " , vbArchive) W h i l e F i l e N a m e <> " " MsgBox F i l e N a m e & " i s a " & R i g h t ( F i l e N a m e , 3 ) & " f i l e . " FileName = D i r Wend End Sub
This procedure displays all of the files in the C:\frogrurn Files\Bentley\MicroStution folder and their file extensions in message boxes. Since there are quite a few, it will take a long time to click the OK button on each message box.
HINT: When a program is executing, we can break into the execution of the code by holding down the Control key (
F u n c t i o n M i d ( S t r i n g , S t a r t As Long, [ L e n g t h ] )
The M i d function allows us to specify a string and the index of the starting character we want to have returned to us. We have the option of specifying how many characters we want to have returned or we can leave the Length parameter empty and have Mid return all of the characters following the specified 'Start' character index. Sub
TextWorkl2()
D i m B o o k T i t l e As S t r i n g B o o k T i t l e = " L e a r n i n g M i c r o S t a t i o n VBA" D e b u g . P r i n t M i d ( B o o k T i t l e , 3 , 6) D e b u g . P r i n t M i d ( B o o k T i t l e , 6) Debug.Print Mid(BookTitle, InStr(1, BookTitle, End Sub
"
")
+ 1)
92
I Chapter 7: Working With Text I We used the M i d function three times in the above example, each time a little arning differently. The first time we asked M i d i n g M i c r o s t a t i o n VBA M i c r o s t a t i o n VBA to begin at the third character and to return six characters in all. The second time we asked for the sixth character and each character after it. Notice how we leave out the Length parameter entirely. The third time we did not hard-code the beginning character. We used the I nSt r function to look for the first space in the variable BookTitle and added one (1) to the character number so we began with the character after the first space. The length is not provided so we get everything after the space.
RepIace F u n c t i o n R e p l a c e ( E x p r e s s i 0 n As S t r i n g , F i n d As S t r i n g ,
-
R e p l a c e As S t r i n g , [ S t a r t A s Long = 11, [ C o u n t A s Long = -11, [ C o m p a r e A s VbCompareMethod = v b B i n a r y C o m p a r e 1 ) As S t r i n g
The Replace function allows us to provide a string, a character or characters to look for, and a replacement for the character(s) we are looking for. Sub T e x t W o r k l 3 0 D i m F i l e P a t h As S t r i n g D i m F i l e p a t h 2 As S t r i n g F i l e P a t h = A p p l i c a t i o n . A c t i v e D e s i g n F i l e . F u l l Name Filepath2 = Replace(FilePath, ” \ ” , “//‘I) MsgBox F i l e P a t h & v b C r & “ t u r n s i n t o ” & v b C r & F i l e p a t h 2 End Sub
In this example we look for a backslash then replace each one found with two forward slashes.
InStr Function InStr(CStart1, [ S t r i n g l l , CString21, [ C o m p a r e As VbCompareMethod
=
vbBinaryCompare1)
I n St r helps us identify where a character or group of characters appear in a string. For example, if we look in the string “ABCD” for string “C”, InStr returns the number 3 because “C” is the third character in
I VBA String Functions I
93
"ABCD': If the character(s) we are looking for is not found, I nS tr returns a value of zero (0). Sub
TextWorkl4()
D i m F u l l N a m e As S t r i n g D i m F i r s t s p a c e As Long D i m F i r s t N a m e As S t r i n g FullName = I n p u t B o x ( " E n t e r y o u r f u l l name.") F i r s t s p a c e = I n S t r ( 1 , FullName, " " ) F i r s t N a m e = L e f t ( F u l l N a m e , F i r s t s p a c e - 1) MsgBox " Y o u r f i r s t name i s " & F i r s t N a m e & " . " End Sub
Here is another simple example of the use of the I n St r function. We ask the user to enter hidher full name. We look for the first space in the entered name, then get everything beginning from the start of the FullName up to the character before the Firstspace. Sub
TextWorklS()
D i m F i l e P a t h As S t r i n g D i m F i r s t F o l d e r P a t h As S t r i n g D i m S e c o n d B a c k S l a s h As Long F i 1 e P a t h = A c t i v e D e s i g n F i l e . F u l l Name SecondBackSlash = I n S t r ( 4 , F i l e P a t h , " \ " ) F i r s t F o l d e r P a t h = L e f t ( F i l e P a t h , SecondBackSl a s h ) MsgBox F i l e P a t h & v b C r & F i r s t F o l d e r P a t h End Sub
Often when we use I nSt r we begin looking at the first character. This is why the number one (1) appears as the first parameter. In this example, however, we begin by looking at the fourth character. This is because we know that the third character of a file path will likely be a backslash. The goal with TextWorkl5 is to get the path ofthe root folder of the active DGN file. One more example: Sub
TextWorklG() Dim Dim Set Dim Set Dim
T e x t E l e m As T e x t E l e m e n t MyMod As Model R e f e r e n c e MyMod = Application.ActiveModelReference MyElems As E l e m e n t E n u m e r a t o r MyElems = MyMod.GetSelectedElements MyElem As E l e m e n t
94
I Chapter 7: Working With Text I W h i l e MyElems.MoveNext S e t MyElem = M y E l e m s . C u r r e n t S e l e c t Case MyElem.Type Case m s d E l e m e n t T y p e T e x t S e t T e x t E l e m = MyElem I f I n S t r ( 1 , T e x t E l e m . T e x t , " [ B Y ] " ) > 0 Then TextElem.Text = Replace(TextElem.Text, " [ BY I" , " J KW " 1 T e x t E l em. R e w r i t e End I f End S e l e c t Wend End Sub
In this example we look at each selected element in the active file. If we find a text element selected, we use I n St r to see if a particular string is in the text element. If it is, I n S t r returns a number greater than zero (0). When we know the search string is inside the text element, we use the Replace function to replace "[BY]" with "JKW". This is one way to perform 'Search and Replace' operations on our files. You could use this when you need to place the name of a contractor in your file but the file is created before the contract is awarded. Simply use a tag, such as [CONTRACTOR],then replace it later with the name of the contractor.
InStr Rev F u n c t i o n I n S t r R e v ( S t r i n g C h e c k As S t r i n g , S t r i n g M a t c h As S t r i n g , [ S t a r t A s Long = -11, [Compare A s VbCompareMethod = v b B i n a r y C o m p a r e 1 ) As Long
I n S t r Rev, as the name implies, looks at the end of a string first instead of the beginning. This is the reverse of I n S t r which begins looking at the beginning. Here is one way to use it: Sub
TextWorkl7( 1
D i m F i l e P a t h As S t r i n g D i m F o l d e r N a m e As S t r i n g FilePath = ActiveDesignFile. Path F o l d e r N a m e = M i d ( F i l e P a t h , I n S t r R e v ( F i 1 e P a t h . " \ "+I 1) MsgBox " T h e c u r r e n t f i l e i s i n t h e " & F o l d e r N a m e & "folder." End Sub ~
I VBA String Functions I
95
We get the path of the current file, then use the I n S t r Rev function inside a Mi d function to get the location of the first backslash we find. Since we don't want to display the folder name beginning with the backslash, we add one (1) in our Mid function to get the characters following the backslash.
Split and Join F u n c t i o n S p l i t ( E x p r e s s i o n As S t r i n g , [ D e l i m i t e r ] , [ L i m i t As Long = -11, [Compare As VbCompareMethod = vbBinaryCompare1) Function Join(SourceArray,
-
[ D e l i m i t e r ] ) As S t r i n g
Spl i t is used to take a String and split it into a text array. J o i n takes an array and joins it into a String. We specify a delimiter for each call. Let's take a look at one way to use Spl i t and J o i n. Sub
TextWorkl8()
D i m F i l e P a t h As S t r i n g D i m NewTextFilePath As S t r i n g D i m x S p l i t As V a r i a n t F i 1 e P a t h = A c t i v e D e s i g n F i l e . F u l l Name x S p l i t = Spl i t (F i l e P a t h , " \ " ) x S p l it(UBound(xSp1 i t ) )= x S p l i t ( U B o u n d ( x S p 1 it ) 1 & " . e x t r a c t " NewText F i 1 ePa t h = J o in ( x S p l it , " \ " ) Open N e w T e x t F i l e P a t h F o r O u t p u t A s #1 P r i n t 81, F i l e P a t h C l o s e 81 End Sub
In this example we want to create a new ASCII text file with an extension ".extract" in the same folder as our active design file. Spl i t the path of the active design file into an array using the S p l i t function. Specify the backslash as the delimiter. When you step through the code, this is what we have after the string is Spl i t:
6d
NewTextFilePath XSPlit(0) XSPlit(1) XSPlit(2) XSPlit(3)
""
"C:" "Microstation VBA" "docs" "chapter07.dgn"
String VariantMring(0 to 3) String String String String
The variable FilePath contains the path to the active design file. The variable xSplit is an array Spl i t from FilePath using the backslash (\) as
96
I Chapter 7: Working With Text I the delimiter. Take the last element in the array (using the UBound function) and add a new file extension of “.extract” to it. Next, Join the array with the backslash (\) as the delimiter into the variable NewText FilePath. Lastly, create an ASCII Text file using the NewTextFilePath variable as the file name. Inside this new file print the contents of the FilePath variable. Here is what the file looks like when it is opened in Notepad.
c:\Microstation
vBA\docs\chaptero7.dgn
Asc and Chr F u n c t i o n A s c ( S t r i n g As S t r i n g ) As I n t e g e r F u n c t i o n Chr(CharCode As Long)
The characters on our computer keyboards have an ASCII number associated with them. Upper case ‘‘X has its own unique number different from lower case “a’: Some characters not on our keyboards have their own ASCII number. You get the ASCII number of a character with the Asc function. If you know the ASCII number of a character, you can use the C h r function to get the character. Sub
TextWorkl9( 1
D i m s t r c h a r a c t e r As S t r i n g F o r I = 0 To 2 5 5 D e b u g . P r i n t I & vbTab & C h r ( 1 ) Next I End Sub
I VBA String Functions I
97
This code takes each ASCII character from 0 to 255 and prints it to the Debug (Immediate) window. Now, why would we want to do this? Because some of the ASCII characters that are not on your keyboard can come in handy. For example, character number 169 is the copyright symbol. Character number 174 is the registered symbol. Character 176 is the degrees symbol. Character 177 is the plus/minus symbol used for geometric tolerances.
169 170 171 172 173 174 175 176 177
0 a
<<
-
8 -
f
Now that you know that character 169 is the copyright symbol, you can use it in a message box: Sub
TextworkPo() D i m s t r C o p y r i g h t N o t i c e As S t r i n g strCopyrightNotice
" L e a r n i n g M i c r o s t a t i o n VBA
=
"
&
-
Chr(169) & "2005" MsgBox S t r C o p y r i g h t N o t i c e End S u b
The As c function does the opposite of the C h r function. You provide a character and get back the ASCII number - something you might do when creating graphical user interfaces (GUIs). P r i v a t e S u b T e x t B o x l L K e y P r e s s ( B y V a 1 K e y A s c i i As MSForms.Return1nteger) S e l e c t Case K e y A s c i i Case A s c ( " 0 " ) To A s c ( " 9 " ) Case E l s e KeyAscii
=
0
End S e l e c t End S u b
The preceding code is the K e y p r e s s E v e n t of a TextBox. The K e y p r e s s E v e n t gives the ASCII number of the character the user attempted to type into the TextBox. In this example, if the KeyAscii property contains a number 0 through 9, then do nothing. Otherwise give the KeyAscii
98
I Chapter 7: Working With Text I property a value of zero (0). The net result is that only numbers 0 to 9 can be entered into the TextBox.
FormatCurrency Function ForrnatCurrency(Expression, C N u r n D i g i t s A f t e r D e c i m a l As Long = -11, C I n c l u d e L e a d i n g D i g i t As V b T r i S t a t e = vbUseDefaul t l , C U s e P a r e n s F o r N e g a t i v e N u r n b e r s As V b T r i S t a t e = vbUseDefaul t l , CGroupDigi t s As V b T r i S t a t e = vbUseDefau1 t l ) As S t r i n g
Use FormatCurrency to take a number or string then display it as currency. In some countries, such as the U.S., this places a dollar symbol in front of it. Other parameters include the grouping numbers with commas, etc.
Sub TextWork21( 1
D i m M y S a l a r y As D o u b l e D i m M y s a l a r y 2 As D o u b l e MySalary
=
Mysalary2
=
123456.78 0.1234
MsgBox FormatCurrency(MySa1ary. 2 , v b f a l s e , v b f a l s e , v b T r u e ) MsgBox FormatCurrency(MySalary, 0 , v b f a l s e , v b f a l s e , v b T r u e ) MsgBox FormatCurrency(MySalary2, 2 , v b f a l s e , v b f a l s e , v b T r u e ) MsgBox FormatCurrency(MySalary2, 2 , v b T r u e , v b f a l s e , v b T r u e )
End Sub
FormatNumber F u n c t i o n ForrnatNumber(Expression, C N u r n D i g i t s A f t e r D e c i m a l As Long = -11, C I n c l u d e L e a d i n g D i g i t As V b T r i S t a t e = vbUseDefaul t l , C U s e P a r e n s F o r N e g a t i v e N u r n b e r s As V b T r i S t a t e = vbUseDefaul t l , CGroupDigi t s As V b T r i S t a t e = vbUseDefau1 t l ) As S t r i n g
I VBA String Functions I
99
FormatNumber looks the same as F o r m a t C u r r e n c y . The main difference is that F o r m a t c u r r e n c y places a currency character in front of the number, whereas FormatNumber returns only a formatted number. Sub T e x t W o r k 2 2 ( )
D i m M y S a l a r y As D o u b l e D i m M y s a l a r y 2 As D o u b l e MySal a r y Mysalary2
=
123456.78
=
0.1234
MsgBox F o r m a t N u m b e r ( M y S a l a r y , 2, v b f a l s e , v b f a l s e , v b T r u e ) MsgBox F o r m a t N u m b e r ( M y S a l a r y , 0 , v b f a l s e , v b f a l s e , v b T r u e ) MsgBox F o r m a t N u m b e r ( M y S a l a r y 2 , 2 , v b f a l s e , v b f a l s e , v b T r u e ) MsgBox F o r m a t N u m b e r ( M y S a l a r y 2 , 2 , v b T r u e , v b f a l s e , v b T r u e ) End Sub
FormatDateTime F u n c t i o n FormatDateTime(Expression, CNamedFormat As VbDateTimeFormat = v b G e n e r a l D a t e 1 ) As S t r i n g
-
Use F o r m a t D a t e T i m e to specify a datehime and how format it. Here are your options and the results: Sub T e x t W o r k 2 3 ( ) D i m DateToFormat As Date
DateToFormat
=
"1/1/2005 4:45 PM"
MsgBox F o r m a t D a t e T i m e ( D a t e T o f o r m a t ,
vbGeneralDate)
MsgBox F o r m a t D a t e T i m e ( D a t e T o f o r m a t , v b L o n g D a t e ) MsgBox F o r m a t D a t e T i m e ( D a t e T o f o r m a t , v b L o n g T i m e ) MsgBox F o r m a t D a t e T i m e ( D a t e T o f o r m a t , v b S h o r t D a t e ) MsgBox F o r m a t D a t e T i m e ( D a t e T o f o r m a t , End Sub
vbShortTime)
100
I Chapter 7: Working With Text I
Format Function Format(Expression, [Format], C F i r s t D a y O f W e e k A s VbDayOfWeek = v b S u n d a y 1 , C F i r s t W e e k O f Y e a r A s VbFi r s t W e e k O f Y e a r = v b F i r s t l l a n l l )
We already have examples of specific types of formatting: Fo rmat C u r ren cy, Fo r ma t N umb e r, Fo rmat D a t eT ime. These functions work great for standard formatting situations. However, VBA does not provide a FormatPhoneNumber function. So, how do we take ten digits and turn them into a fully formatted phone number? Sub T e x t W o r k 2 4 0 D i m MyPhone As S t r i n g My P hone
=
"800 5 5 5 1212 "
MsgBox F o r m a t ( My Phone,
"
(IHHI) l ~ l ~ l ~ - l1~ l ~ l ~ l ~ "
End Sub
The Format parameter in the Format function allows a great deal of flexibility. See the VBA Help file for more formatting options.
Use the Ampersand (&) symbol to concatenate strings. I use the ampersand extensively in this book to take multiple strings and combine them into one.
vbCr We have a few constants available for use with strings, such as vbCr constant, which is for a Carriage Return. It is similar to pressing the
I Review I
101
vbTab Use the vbTab constant to simulate the user pressing the
REVIEW Strings refer to text. Letters, numbers, and other characters combine to form a single piece of text. This section focused on working with these strings of characters. You learned to capitalize, make lowercase, get the left-most or right-most characters, split them, join them back together, format them, and a number of other things. Take time to work through all of the examples accompanying each of the functions. Remember, you can step through the code one line at time by using the
Working With Numbers For hundreds and even thousands of years, the world’s greatest mathematicians attempted to calculate an accurate value for Pi. When asked about this value today, we casually state 3.14159 or something close to it. A2 + B2 = C2. This formula is second nature to children on our school’s playgrounds today, whereas not long ago the equality of the sum of the squares of the sides of a right triangle to the square of the hypotenuse was a great unknown. While software development, in general, deals in large part with logic, add Microstation to the mix and the need to manipulate numbers increases exponentially. After all, what is a line? The shortest distance between two points. Each of these points is composed of three numeric values: an X, a Y, and a Z. To draw a circle at the midpoint of that line in VBA, we need to calculate the line’s midpoint. How do we do that? Find the phone number of a math teacher? Not so, my friend.
NUMERIC FUNCTIONS VBA makes working with numbers a breeze. It doesn’t do all of the work for us, but we can do a great deal with very little pain.
103
I Chapter 8: Working With Numbers I Addition 1 + 1 = 2. We learned this many, many years ago. The plus symbol (+) is used in VBA to add numbers. Take a look: Sub
TestAdditionSubtractionO D i m S e l P t As P o i n t 3 d D i m C e n P t As P o i n t 3 d
D i m CadMsg As C a d I n p u t M e s s a g e D i m T e x t E l e m As T e x t E l e m e n t D i m R o t M a t r i x As M a t r i x 3 d
S e t CadMsg
Application.CadInputQueue.Get1nput
=
Do W h i l e T r u e S e l e c t Case C a d M s g . I n p u t T y p e
msdCadInputTypeDataPoint
Case
Sel P t
=
CadMsg. P o i n t
E x i t Do End S e l e c t Loop CenPt
=
CenPt.X
SelPt =
CenPt.X
+ 1
S e t TextElem = Appl i c a t i o n . C r e a t e T e x t E 1 e m e n t l ( N o t h i n g , CenPt, R o t M a t r i x )
"l",
~
ActiveModel Reference.AddElement TextElem CenPt
=
CenPt.Y
SelPt =
CenPt.Y
+ 1
S e t TextElem = Appl i c a t i o n . C r e a t e T e x t E 1 e m e n t l ( N o t h i n g , CenPt, R o t M a t r i x )
"Z",
-
ActiveModel Reference.AddElement TextElem CenPt
=
CenPt.X
SelPt =
CenPt.X
-
1
S e t TextElem = Appl i c a t i o n . C r e a t e T e x t E 1 e m e n t l ( N o t h i n g , CenPt, R o t M a t r i x )
"3",
~
ActiveModel Reference.AddElement TextElem CenPt
=
CenPt.Y
SelPt =
CenPt.Y
-
1
S e t T e x t E l e m = Application.CreateTextElementl(Nothing, CenPt, R o t M a t r i x ) ActiveModel Reference.AddElement TextElem End Sub
"4", ~
I Numeric Functions I
105
We let the user select a point in Microstation. We then use the selected point as a basis for the insertion of each of the text elements we add to the model. We add 1 to the X element of the selected point to get the location for the text ‘‘1”.We add 1 to the Y element of the selected point to get the location for the text “2’: Points 3 and 4 require us to subtract from the X and Y respectively.
Subtraction 10 - 3 = 7. Use the minus symbol (-) to subtract values in VBA, as in the example in the procedure TestAdditionSubtraction.
Mu1tip1ication 2 X 6 = 12. Use the asterisk (*) symbol to multiply in VBA. The previous reference works when in a math book but in VBA it is written 2 * 6 = 12. Sub TestMultiplicationO Dim DistanceInInches A s Double Dim DistanceInMM A s Double DistanceInInches = CDbl (InputBox(”Enter distance in inches:”)) DistanceInMM = DistanceInInches * 25.4
106
I Chapter 8: Working With Numbers I MsgBox D i s t a n c e I n I n c h e s & DistanceInMM &
"
"
i s equal t o
"
&
-
Millimeters."
End Sub
Multiplying the entered value by 25.4 converts the entered value from inches to millimeters.
Division There are two ways to divide numbers in VBA. No, not long division and short division. The first method returns a very precise number. When you want precision (and you usually do), use the forward slash ( / ) symbol like this: 5 / 2 = 2.5 F u n c t i o n T o M i l e s ( D i s t a n c e 1 n F e e t as D o u b l e ) A s D o u b l e T o M i l e s = D i s t a n c e I n F e e t / 5280 End F u n c t i o n
The function ToMi 1 es allows us to supply a distance in feet that returns the distance in miles. Actually, the return value is in decimal miles. Another way to divide numbers is using the backslash symbol (\). This returns a whole number instead of a decimal number. 5 \ 2 = 2.
Squares and Exponents Remember A2 + B2 = C2? The little twos shown above the A, B, and C square the values of A, B, and C. In VBA, we write the expression like this: A^2 + B^2 = C"2. The caret symbol allows an exponent. If you were to cube (raise to the power of 3) a number, you would use DA3. Function
GetCircleArea(Circ1eRadius A s
D o u b l e ) As D o u b l e
D i m P i As D o u b l e
*
4
GetCircleArea
=
Pi
=
Atn(1)
End F u n c t i o n
Pi
*
CircleRadius
2
I Numeric Functions I
107
Square Root Use the Sqr function to get the square root of a number. Here's one way to use it: Sub G e t L i n e L e n g t h O D i m SelElem As Element D i m LineElem As LineElement
D i m S e l Elems As E l e m e n t E n u m e r a t o r S e t S e l Elems
=
ActiveModelReference.GetSelectedElements
W h i l e SelElems.MoveNext Set SelElem
=
SelElems.Current
S e l e c t Case S e l E l e m . T y p e Case m s d E l e m e n t T y p e L i n e S e t L i n e E l em
=
S e l E l em
D i m S t P t As P o i n t 3 d D i m EnPt As P o i n t 3 d
StPt
=
LineElem.StartPoint
EnPt
=
LineElem.EndPoint
LineLength (StPt.Y
=
-
Sqr((StPt.X - EnPt.X) EnPt.Y) 2)
MsgBox " L i n e f o u n d w i t h l e n g t h o f
A
"
2 +
~
& LineLength
End S e l e c t Wend End Sub
Pythagorean's theorem is used in this example. We get the change in X and the change in Y of the selected line. We square these values, add them together, then get the square root of the total.
Sine, Cosine, Tangent The S i n , Cos, and Tan functions require an angle in radians. Sub T e s t S i n C o s O D i m XChange A s D o u b l e
D i m YChange As D o u b l e D i m P i As D o u b l e D i m H y p L e n g t h As D o u b l e D i m HypAngleDegrees As Double D i m HypAngleRadians As Double Pi
=
Atn(1)
*
4
108
I Chapter 8: Working With Numbers I HypLength
=
C D b l ( I n p u t B o x ( " E n t e r Hypotenuse L e n g t h : " ) )
HypAngleDegrees
=
CDbl ( I n p u t B o x ( " E n t e r A n g l e : " ) )
HypAngleRadians
=
HypAngleDegrees
YChange
=
HypLength
XChange
=
HypLength
* *
*
P i / 180
Sin(HypAng1eRadians) Cos(HypAng1eRadians)
Debug. P r in t " T e s t S i n Cos ( 1 " Debug.Print "HypLength
=
"
& HypLength
Debug.Print "HypAngleDegrees
=
"
& HypAngleDegrees
Debug.Print "HypAngleRadians
=
"
& HypAngleRadians
D e b u g . P r i n t "XChange
=
"
& XChange
D e b u g . P r i n t "YChange
=
"
& YChange
End Sub
To calculate the change in X and the change in Y,we need hypotenuse length and an angle, as long as we have access to the Sin and Cosine of the angle. After getting the user input, convert the supplied angle in degrees to radians. Then use the angle in radians with the S i n and Cos functions to give change in X and change in Y TestSinCos ( ) HypLength = 1 0 HypAngleDegrees = 3 0 HypAngleRadians = 0 , 5 2 3 5 9 8 7 7 5 5 9 8 2 9 9 XChange = 8 . 6 6 0 2 5 4 0 3 7 8 4 4 3 9 YChange = 5
Let's use the T a n function now. The first example supposes you know the leg of the triangle along the X axis. Sub
TestTanl( 1 D i m XChange As D o u b l e D i m YChange As D o u b l e D i m P i As D o u b l e
D i m H y p A n g l e D e g r e e s As D o u b l e D i m H y p A n g l e R a d i a n s As D o u b l e Pi
=
Atn(1)
XChange
=
*
4
CDbl(InputBox("Enter X Side Length:"))
HypAngleDegrees
=
CDbl ( I n p u t B o x ( " E n t e r A n g l e : " ) )
HypAngleRadians
=
HypAngleDegrees
YChange
=
Tan(HypAng1eRadians)
Debug.Print "TestTanl( 1"
*
*
P i / 180
XChange
I Numeric Functions I Debug.Print
"XChange
109 =
"
& XChange
Debug. P r i n t " H y p A n g l e D e g r e e s
=
"
& HypAngl eDegrees
Debug.Print
"HypAngleRadians
=
"
& HypAngleRadians
Debug.Print
"YChange
=
"
& YChange
End Sub
TestTanlO XChange = 4 HypAngleDegrees = 36.8699 HypAngleRadians = 0.643501149881057 YChange = 3.00000025679859
Sub T e s t T a n 2 ( ) D i m XChange As D o u b l e
D i m YChange As D o u b l e D i m P i As D o u b l e D i m H y p A n g l e D e g r e e s As D o u b l e D i m H y p A n g l e R a d i a n s As D o u b l e Pi
=
Atn(1)
YChange
=
*
4
CDbl(InputBox("Enter Y Side Length:"))
HypAngleDegrees
=
CDbl ( I n p u t B o x ( " E n t e r A n g l e : " ) )
HypAngleRadians
=
HypAngleDegrees
XChange
=
*
P i / 180
YChange / T a n ( H y p A n g 1 e R a d i a n s )
Debug. P r i n t " T e s t T a n Z ( ) " Debug.Print
"YChange
=
"
& YChange
Debug. P r i n t " H y p A n g l e D e g r e e s
=
"
& HypAngleDegrees
Debug. P r i n t " H y p A n g l e R a d i a n s
=
"
& HypAngleRadians
Debug.Print
"XChange
=
"
& XChange
End Sub
TestTan2 ( ) YChange = 3 HypAngleDegrees = 36.8699 HypAngleRadians = 0.643501149881057 XChange = 3.99999965760191
110
I Chapter 8: Working With Numbers I
The values used for the previous examples make use of the right triangle:
4 As we write code, it is common to make little mistakes along the way. The world calls these “bugs” but we could call them “creative programming.” The net result is the same: the code doesn’t work. It is helpful to test our calculations with numbers that give us predictable results.
Arc Tangent S i n, Cos, and Tan help when we know the angle involved. If we do not know the angle, we can get the angle by using A t n (ArcTangent). ________________
Sub TestATan( 1
D i m P i As D o u b l e D i m A n g l e D e g r e e s As D o u b l e D i m A n g l e R a d i a n s As D o u b l e Pi
=
Atn(1)
*
4
AngleRadians
=
Atn(3 / 4)
AngleDegrees
=
AngleRadians / P i
*
180
MsgBox A n g l e D e g r e e s End Sub
Absolute Value The Abs function gives us the Absolute Value of the supplied number. Sub TestAbs() D e b u g . P r i n t “The a b s o l u t e v a l u e o f 4 i s D e b u g . P r i n t “The a b s o l u t e v a l u e o f - 5 i s End Sub
& Abs(4)
” ”
& Abs(-5)
I Numeric Functions I
111
The a b s o l u t e v a l u e o f 4 is 4 The a b s o l u t e v a l u e o f -5 is 5
Convert to Integer, to Long, to Double, and Value We have discussed declaring types of variables. VBA gives us the ability to convert values from one type to another. One of the most common conversions is from a string to a number. YChange
=
CDbl(InputBox(”Enter Y Side Length:”))
Above we use the CDbl function to convert the results of the InputBox to a double. Sub
TestCInt() Debug.Print CInt(4.56) Debug.Print CInt(4.23) Debug.Print C I n t C 4 . 5 6 ) Debug.Print C I n t C 4 . 2 3 )
End Sub
5 4 -5 -4
When converting from a double to an integer, something needs to be done with the decimal portion of the number because an integer is a whole number. It is important that you understand how this works. C In t arrives at an integer by rounding the number. Take a look at the code in “TestCInt” and the results shown in the Immediate window.
CLng The C Ln g function works just like the C In t function, except it converts the provided number to a long. You could ask, “If CLng does the same thing as CInt, which one should I use?” That is a good question. Remember, that a long number can be significantly larger than an integer. To use C I n t on a number such as 40,000.123 would create an overflow error. C I n t and C Ln g are often used when assigning a value to a
112
I Chapter 8: Working With Numbers I variable. So, if you assign a value to a variable declared as an integer, you should use CInt. If you are assigning a value to a variable declared as a long, use CLng. Sub T e s t C L n g ( 1 D e b u g . P r i n t CLng(40000.56) D e b u g . P r i n t CLng(40000.23) Debug.Print CLng(-40000.56) Debug.Print CLng(-40000.23)
40001 40000 -40001 -40000
End Sub
The F i x function looks like it works the same as the CInt or the CLng function. It returns a number without the decimal portion of the number. However, it works a little differently. Let's look at the results of the code below. Sub T e s t F i x ( ) Debug. P r i n t F i x ( 4 0 0 0 0 . 5 6 ) Debug.Print Fix(40000.23) Debug.Print Fix(-40000.56) Debug.Print Fix(-40000.23) End Sub
The F i x function simply drops the decimal portion of the provided number. It does not do any rounding. Fix can return numbers that fall within the integer and long range.
40000 40000 -40000 -40000
C Db 1 converts the supplied parameter to a double. Sub T e s t D o u b l e ( 1 D i m L i n e L e n g t h As D o u b l e
LineLength End Sub
=
CDbl ( I n p u t B o x ( " E n t e r t h e l i n e l e n g t h : " ) )
I Numeric Functions I
113
CInt, CLng, and CDbl work well if the supplied parameter is numeric, providing the number 3.14159 works with any of these functions. However, if you pass the parameter as 2.5", an error pops up. The Val function has the ability to give us the numeric value of a supplied parameter. The best way to understand how it works is to run some code and look at the results. Sub
TestVal ( 1 Debug. P r i n t V a l ( " 4 . 5 " " " ) Debug.Print Val("4.5
inches")
Debug.Print Val ( " $ 5 , 0 0 0 " ) Debug.Print Val ( " 4 5 d e g r e e s " ) D e b u g . P r i n t V a l ( " A p p r o x . 5280 f e e t " ) Debug.Print Val("23 f e e t 12 inches") End Sub
Notice that when the parameter supplied to the Val function begins with a numeric value, Val returns all of the numeric characters until it finds a non-numeric character and returns the numeric values it found. rn
IsNumeric Many of the functions we have just reviewed return numeric values. I s N u m e r i c returns a Boolean value (True or False). It looks at the parameter and determines if it is numeric. Sub
TestIsNumeric( 1 Debug. P r in t I s Nume r ic ( " 4 . 5 "
" "
)
Debug. P r in t I s Nume r ic ( " 4 . 5 in c h e s " ) Debug. P r i n t I s N u m e r i ~ ( " $ 5 , 0 0 0 " ) Debug . P r in t Is N ume r ic ( " 4 5 d e g r e e s " ) Debug.Print
IsNumeric("Approx.
Debug.Print
IsNumeric("23 f e e t 12 inches")
End Sub
5280 f e e t " )
114
I Chapter 8: Working With Numbers I IsNurneric looks at the entire parameter
and determines if it is numeric. If any portion of the parameter is not numeric, we get a false value returned. Notice how the dollar sign ($) is a numeric sign.
Round C I n t and C Lng round decimal numbers to whole numbers. The Round
function lets us specify how many numbers we want to appear after the decimal point. Take a look: Sub TestRound() Debug.Print Round(3.14159, 4) Debug.Print Round(3.14159, 3) Debug.Print Round(3.14159, 2) Debug.Print Round(3.14159, 1) Debug.Print Round(3.14159, 0) Debug.Print Round(1.455, 2) Debug.Print Round(1.455, 1) Debug.Print Round(l.4, 0) Debug.Print Round(l.5, 0) End S u b
L
Mod - Find the Remainder The Mod function gives the remainder value of two numbers, but you use it quite differently than most other functions. Where most functions call the function then provide parameters separated by commas,. in the Mod function you supply the numerator, call Mod, then supply the denominator. Sub TestModl( 1 Debug.Print Debug.Print Debug.Print Debug.Print End S u b
5 Mod 2 7 Mod 3 23 Mod 7 280 Mod 2
I Numeric Functions I
115
Sgn - Show me a sign Is a number positive or negative? Or is it neither? The Sgn function returns a value of - 1,0, or 1 depending on whether the supplied value is negative, zero, or positive. Sub TestSgn( 1 Debug.Print SgnC4.5) Debug.Print Sgn(0) Debug.Print Sgn(4.5) End Sub
Rnd and Randomize Once in a while you need to generate a random number. This example shows how to create a random number between a lower and higher number. The result is a random point cloud consisting of 300 points between (25,25) and (50,50). Sub TestRnd( 1
D i m I As L o n g D i m Lower As Long D i m H i g h e r As Long D i m P o i n t C e n ( 0 To
1) As P o i n t 3 d
D i m P o i n t E l e m As P o i n t S t r i n g E l e m e n t Lower Higher
=
25 50
=
Randomize For I
=
1 To 3 0 0
PointCen(O).X
=
Round((Higher
-
Lower +
PointCen(O).Y
=
Round((Higher
-
Lower +
PointCen(O).X
PointCen(l).X
=
PointCen(l).Y
=
Set PointElem
=
1) * R n d ( l ) , 2) 1) * R n d ( l ) , 2)
PointCen(O).Y ~
Application.CreatePointStringElementl(Nothing, PointCen, True)
ActiveModelReference.AddElement Next I End Sub
PointElem
~
116
I Chapter 8: Working With Numbers I Order of Operations 2 + 5 * 8 / 1 2 + 13 = ? (2 + 5) * 8 / (12 + 13) = ? 2 + (5 * 8 / (12 + 13)) = ?
Each of these expressions returns a different result. The numbers are the same and the operations are the same but the results are different. The order in which numeric operations are carried out is important to understand. Multiplication and division come first, addition and subtraction come second. If there is any question, place parenthesis around the operations you want grouped to make it clear how VBA should calculate your expressions.
Many software developers can work for extended periods of time without using mathematical functions. When we are programming Microstation, however, we are always using numeric functions. We can add, subtract, multiply, and divide. We can use other functions that aid in the location of elements in Microstation or compute lengths, angles, etc.
Standard VBA Calls While introducing various concepts, we used a number of standard VBA calls without discussing them, so let's cover them now. Again, you can use these VBA calls with other VBA-enabled applications such as Microsoft Excel.
MESSAGE BOXES We used MessageBoxes to display some text with an OK button. By default, the code pauses until the user clicks the OK button. Sub T e s t M e s s a g e B o x l O MsgBox "Your h a r d d r i v e w i l l now b e f o r m a t t e d . " End Sub
This is just what we all want to see: A MessageBox informing us something drastic is about to happen and all we have is an OK button to click on. You can specify the prompt of the MessageBox (the text that shows up) as well as which buttons display.
117
118
I Chapter 9: Standard VBA Calls I Sub T e s t M e s s a g e B o x 2 0 D i m MsgResp As VbMsgBoxResul t
MsgResp
=
MsgBox("Unab1e t o open f i l e . " ,
MsgResp
=
MsgBox("Format H a r d D r i v e ? " , vbOKCancel )
VbAbortRetryIgnore)
MsgResp
=
MsgBox("New L e v e l A d d e d . " ,
MsgResp
=
MsgBox("Not Connected t o I n t e r n e t . " ,
MsgResp
=
MsgBox("Do y o u want t o c o n t i n u e ? " , vbYesNo)
MsgResp
=
M s g B o x ( " C o n t i n u e Reading F i l e ? " , vbYesNoCance1 )
vbOK0nly) vbRetryCance1)
S e l e c t Case MsgResp Case VbMsgBoxResult.vbAbort ' P l a c e Code Here Case VbMsgBoxResul t.vbCance1 ' P l a c e Code Here Case V b M s g B o x R e s u l t . v b I g n o r e ' P l a c e Code Here Case VbMsgBoxResul t . v b N o ' P l a c e Code Here Case VbMsgBoxResul t.vbOK ' P l a c e Code Here Case VbMsgBoxResul t . v b R e t r y ' P l a c e Code Here Case VbMsgBoxResul t . v b Y e s ' P l a c e Code Here End S e l e c t End Sub
Y
The ability to have more than an OK button makes the MessageBox much more powerful. Now, however, you are asking a question of the user. Yes? No? Retry? Abort? Ignore? Cancel? OK? When you ask a question, you need an answer. So, we use the MessageBox as a function and get its return value.
I MessageBoxesI
119
In the example above, which is only for illustration purposes, each MessageBox returns a value into the variable MsgResp that tell us which button the user clicked. We use a Select Case statement to determine the button pressed, then execute code based on the button. The Select Case Statement is placed after the "YesNoCancel" MessageBox so previous button clicks are not considered, only the "YesNoCancel" MessageBox.
Here is a Microsoft Windows MessageBox that appears when you attempt to change a file's extension (say from .txt to .dgn). Note the Yes and No buttons. We now know how to specify buttons but what about the exclamation point in the triangle? How do we do that? When you display a MessageBox, use constants that specify which buttons to display, such as vbOKCancel, vbYesNo, and vbOKOnly. These constants have numeric values. There are other constants that specify which icon to display, such as vbExclamation. When you add the constant specifying the buttons to display with the constant for the icon to display, VBA displays the buttons and the icon in the MessageBox. Sub T e s t M e s s a g e B o x 3 0 D i m MsgResp A s V b M s g B o x R e s u l t MsgResp
= M s g B o x ( " U n a b 1 e t o open f i l e . " , vbAbortRetryIgnore + v b c r i t i c a l )
MsgResp = M s g B o x ( " F o r m a t H a r d D r i v e ? " , vbOKCancel + v b E x c l a m a t i o n ) MsgResp
=
MsgBox("New L e v e l A d d e d . " ,
-
-
vbOKOnly
+ vbInformation)
MsgResp = MsgBox("Do y o u w a n t t o c o n t i n u e ? " , vbYesNo + vbQuestion) End Sub
-
120
I Chapter 9: Standard VBA Calls I
When using these icon constants with button constants, Windows displays the buttons and the icon and plays specific sounds. Let's look at another available parameter when using the MessageBox. Sub TestMessageBox40 MsgBox " T e s t i n g T i t l e " , v b c r i t i c a l , " T i t l e Goes H e r e " MsgBox " T e s t i n g T i t l e " ,
, " T i t l e Goes H e r e "
End Sub
The Title parameter displays at the top of the MessageBox. It is the third parameter. The MessageBox only has one required parameter, the prompt. So, to display a prompt and a title and the default button, place a comma after the prompt, a space, another comma, and then the prompt. When you bypass an optional parameter, leave the parameter blank and use commas to indicate that you are providing the next parameter(s).
INPUTBOX InputBoxes let users enter text. If a user clicks the Cancel button or enters nothing and clicks the OK button, the I nputBox returns an empty string. An empty string is denoted in VBA as two quotation symbols with no other character between them ("").
I InputBox I
121
S u b TestInputBoxl( 1 Dim InpRet As String I n p R e t = I n p u t B o x ( " E n t e r Level Name:") Debug.Print "User entered & InpRet End S u b "
The I nputBox has additional parameters we can use. We will discuss four of them here. S u b TestInputBoxP( 1 Dim InpRet As String I n p R e t = I n p u t B o x ( " E n t e r Level Name:", "Level C r e a t o r " , " S t r i p i n g " , 0, 0) Debug.Print "User entered & InpRet End S u b "
OK
Looking at the code and the result reveals most of the new parameters. After the prompt and title, a default value for the InputBox is provided, then the X, Y location where the InputBox is displayed. The X and Y values are in pixels and are system-dependent. This means if you use 0,O as your coordinates, the InputBox displays in the upper-left corner of the monitor independent of where the Microstation window is placed. Be careful with the X and Y location parameters because it is possible to place the InputBox entirely off screen. It would surely confuse the user if he could not see the InputBox and the code is waiting for a click on a button or the
122
I Chapter 9: Standard VBA Calls I Now! The Now function gives the current system date and time. This is useful to make a datehime stamp. Now returns a Date type value. Sub T e s t N o w ( ) MsgBox Now End Sub
DateAdd Now tells us the current datehime. DateAdd allows us to look into the future or into the past. Here are a few examples of how to use DateAdd: Sub T e s t D a t e A d d ( )
D i m NowDate As D a t e NowDate
=
Now
D e b u g . P r i n t NowDate & v b T a b & D a t e A d d ( " d " , 4 , NowDate) ' D a y D e b u g . P r i n t NowDate & v b T a b & D a t e A d d ( " h " , 4 , NowDate) ' H o u r D e b u g . P r i n t NowDate & v b T a b & D a t e A d d ( " n " , 4 , NowDate) ' M i n u t e D e b u g . P r i n t NowDate & v b T a b & D a t e A d d ( " s " , 4 , NowDate) ' S e c o n d D e b u g . P r i n t NowDate & v b T a b & D a t e A d d ( " m " , 4 , NowDate) ' M o n t h D e b u g . P r i n t NowDate & v b T a b & D a t e A d d ( " w " , 4 , NowDate) 'Week D e b u g . P r i n t NowDate & v b T a b & D a t e A d d ( " y y y y " , 4 , N o w D a t e ) ' Y e a r D e b u g . P r i n t NowDate & v b T a b & D a t e A d d ( " q " , 1, N 0 w D a t e ) ' Q u a r t e r End Sub
5/28/2005 5/28/2005 5/28/2005 5/28/2005 5/28/2005 5/28/2005 5/28/2005 5/28/2005
11:40:54 11:40:54 11:40:54 11:40:54 11:40:54 11:40:54 11:40:54 11:40:54
AM AM AM AM AM AM AM AM
6/1/2005 11:40:54 AM 5/28/2005 3:40:54 PM 5/28/2005 11:44:54 AM 5/28/2005 11:40:58 AM 9/28/2005 11:40:54 AM 6/1/2005 11:40:54 AM 5/28/2009 11:40:54 AM 8/28/2005 11:40:54 AM
In the above example, we declare a variable as a Date then set its value to Now. We could use the function Now in each DateAdd function. Because Now changes from second to second, it is a good idea to set a variable to Now and then use that variable throughout a procedure to make sure you are basing all of your calculations on the same datehime. Use a positive
I InputBox I
123
number as the second argument to move the result into the future. Use a negative number to return a value in the past.
If you have two dates and want to know the time interval between them, use DateDiff. Use the same interval parameters with DateAdd and DateDi ff. Sub TestDateDi f f ( ) D i m NowDate A s D a t e NowDate
=
Now
D e b u g . P r i n t " D a y s " & vbTab & D a t e D i f f ( " d " , NowDate, " 1 / 1 / 3 0 0 0 " ) D e b u g . P r i n t " H o u r s " & vbTab & D a t e D i f f ( " h " , NowDate, " 1 / 1 / 3 0 0 0 " ) Debug. P r i n t " M i n u t e s " & vbTa b & D a t e D i f f ( " n " , NowDa t e , "1/ 1/3000" ) Debug. P r i n t "Seconds
& vbTa b & D a t e D i f f ( " s " , NowDa t e , "1/ 1/3000" )
"
Debug. P r i n t " M o n t h s " & v bTa b & Da t e D i f f ( " m " , Now D a t e , " 1/ 1 / 3 0 0 0 " ) D e b u g . P r i n t "Weeks" & vbTab & D a t e D i f f ( " w " , NowDate, " 1 / 1 / 3 0 0 0 " ) Debug. Pr in t " Y e a r s " & vbTa b & D a t e D i f f ( " y y y y " , NowDa t e , " 1/ 1 / 3 0 0 0 " ) Debug. Pr in t " Q u a r t e r s
"
& vbTa b & D a t e D i f f ( " q " , NowDa t e , " 1/ 1 / 3 0 0 0 " )
End S u b
The Y2K scare was nothing more than a scare for most of us. This example looks forward to Y3K. How many days, hours, minutes, seconds, months, weeks, years, and quarters before the dreaded January lst, 3000 comes our way? Only 31,386,398,632 seconds.
Days 363269 Hours 8718445 Minutes 523106644 Seconds 3 13 8 63 9 8 632 Months 11936 Weeks 51895 Years 995 Quarters 3979
The interval is the first parameter. The next two parameters are the dates we are looking at. If the first date comes before the second date, we get a positive return value. If the first date comes after the second date, we are returned a negative value.
124
I Chapter 9: Standard VBA Calls I Timer The T i me r function tells us how many seconds have transpired since midnight. This can be useful when testing our applications to find bottlenecks in the code. If you are working late at night, however, be careful. At the strike of midnight, the timer function returns a value of 0 (zero) and starts counting seconds all over again. Sub T e s t T i m e r ( ) MsgBox T i m e r End Sub
FileDateTime FileDateTime gives the datehime the specified file was last modified. Sub T e s t F i 1 e D a t e T i m e ( 1 D i m e x e D a t e As D a t e
exeDate = FileDateTime ( " C : \ P r o g r a m F i 1 e s \ B e n t l e y \ M i c r o s t a t i o n \ u s t a t i on . e x e " ) MsgBox " M i c r o S t a t i o n D a t e / T i m e :
"
& exeDate
End Sub
F i 1 elen tells the size (in bytes) of a given file. Sub T e s t F i 1e L e n ( )
D i m e x e S i z e As L o n g exeSize
=
FileLen
( " C : \ P r o g r a m F i 1 e s \ B e n t l e y \ M i c r o s t a t i o n \ u s t a t i on . e x e " ) MsgBox " M i c r o S t a t i o n S i z e :
"
& exesize
End Sub
ruse M k D i r to create a new directory. All parent directories must exist for MkDi r to work. For example, to make a directory (also called a folder)
I InputBox I
125
named c:\MicroStation VBA\Chapters\09\Samples but the Chapters directory does not exist, you must create the “Chapters” directory, then the “09” directory, then the “Samples” directory. Sub TestMkDi r ( 1 MkDi r “ c : \ M i c r o s t a t i o n V B A \ S o u r c e C o d e ” End Sub
RmDir RmDi r removes a directory from the file system. The directory must be
empty, otherwise an error occurs. r(1
Sub TestRmDi
R m D i r “ c :\ M i c r o s t a t i o n VBA\Source Code” End Sub
Dir The D i r function allows us to look for files and folders (directories). The first time you use it, specify a path and file name (wildcards are acceptable). D i r only returns one file/folder at a time. If you are looking for a group of files or folders, call D i r again and leave the parameters empty. When D i r returns an empty string (””), you know it has returned all of the file or folder names requested. In addition to specifying a file or folder pathhame to look for, you can specify the type of file/folder. Since there is a great deal that you can do with the D i r function, we will look at several examples and the results of the code. Sub T e s t D i r l ( )
D i m R o o t P a t h As S t r i n g D i m D i r R e t u r n As S t r i n g RootPath
=
DirReturn
=
“C:\Program
Files\Bentley”
Dir(RootPath &
Whi 1 e D i r R e t u r n
<>
”\*.*”,
vbDirectory)
” ”
Debug.Print RootPath & ” \ ” & D i r R e t u r n DirReturn Wend End Sub
=
Dir
126
I Chapter 9: Standard VBA Calls I
Our first example retrieves Directories under the "C:\frogrurn Files\Bentley" directory. Notice how the first directory is named "I' and the second, "..? This occurs with all calls when looking for directories and they should be ignored in your code. They refer to the current folder and the parent folder. Sub T e s t D i
r2( I
D i m R o o t P a t h As S t r i n g D i m D i r R e t u r n As S t r i n g
RootPath
=
DirReturn
=
"C:\Program
Files\Bentley\MicroStation" "\*.*"I
Dir(RootPath &
While DirReturn
<>
"I'
Debug.Print RootPath & " \ " & D i r R e t u r n DirReturn
=
Dir
Wend End Sub
Now we are looking in the "C:\frogrurn Files\Bentley\MicroStution" folder for all files (*.*). Writing file names to the Immediate window works well for demonstration but is not practical. Let's do another variation this time putting the file names into a variable. Sub T e s t D i
r3( I
D i m R o o t P a t h As S t r i n g D i m D i r R e t u r n As S t r i n g
I InputBox I
127
D i m D g n F i l e s O As S t r i n g ReDim D g n F i l e s ( 0 ) A s S t r i n g Root Path DirReturn
= =
"
C : \ M i c r o St a t ion V B A\ Docs " Dir(RootPath & "\*.dgn")
Whi 1 e D i r R e t u r n
<>
" "
DgnFiles(UBound(DgnFi1es))
=
RootPath & " \ " & D i r R e t u r n
ReDim P r e s e r v e D g n F i l e s ( U B o u n d ( D g n F i 1 e s ) DirReturn
=
+ 1)
Dir
Wend ReDim P r e s e r v e D g n F i l e s ( U B o u n d ( D g n F i 1 e s )
-
1)
End S u b
DgnFiles(0) DgnFiles(1) DgnFiles(2) DgnFiles(3) DgnFiles(4) DgnFiles(5)
"C: WlicroStation VBAIDocskhapter03.dgn" "C: WlicroStation VBAIDocskhapter04.dgn" "C: WlicroStation VBAIDocskhapterO6.dgn" "C: WlicroStation VBAIDocskhapter07.dgn" "C: WlicroStation VBAIDocskhapter08.dgn" "C: WlicroStation VBAIDocskhapterO9.dgn"
We look in the directory "C:\MicroStution VBA\Docs" for files with the extension.dgn. Place the paths of these files into a dynamic array variable named DgnFiles. When the code gets to the "End Sub" line of code, six files have been found and placed into the array. You could write additional code to work with the files before "End Sub".
WARNING: The K i 1 1 function ispermanent. Files that are 'Killed'are
not sent to the recycle bin. They are destroyed totally and completely. Use with extreme caution. S u b T e s t K i 11 ( ) K i l l "C:\MicroStation
VBA\Docs\killtest.txt"
End S u b
This code kills a file named C:\MicroStution VBA\Docs\killtest. txt. The ability to delete a file is useful and necessary but must be used with caution.
128
I Chapter 9: Standard VBA Calls I
Beep beeps. It offers a quick, audible clue to the user as our code executes. Although useful to draw the user's attention to the program, it can become annoying to have an application beep every time a user does something. Sub TestBeep( Beep End
Sub
Savesetting Working with the Windows registry can save settings the user has set in our software. Microsoft has created a registry path for VBA program settings that we can easily write to, edit, and delete. Sub T e s t s a v e s e t t i n g ( ) S a v e s e t t i n g " L e a r n i n g M i c r o s t a t i o n VBA",
"Chapter
9",-
" S a v e s e t t i n g " , "It Works" End
Sub
After this code is run, the necessary registry folders are added and a registry entry named "SaveSetting is created with a value of "It Works':
Getsetting When a setting is in the registry, we can get it by using Get Se t t in g. Sub T e s t G e t S e t t i n g ( D i m R e g S e t t i n g As S t r i n g Regsetting
=
G e t S e t t i n g ( " L e a r n i n g M i c r o s t a t i o n VBA".
"Chapter 9 " .
~
"Savesetting") D e b u g . P r i n t "The Key S a v e s e t t i n g v a l u e i s End
Sub
" " "
& RegSetting &
" " " "
I InputBox I
129
The Key Savesetting value i s "It Works"
Deletesetting We can save and get settings and we can delete them. As with any other API call that deals with the removal of files or data, be careful with this one. S u b TestDeleteSettingl() D e l e t e s e t t i n g " L e a r n i n g M i c r o s t a t i o n VBA", "Chapter 9", " S a v e s e t t i n g " End S u b
When the above code is run, the Key "Savesetting is deleted. S u b TestDeleteSetting2() D e l e t e s e t t i n g " L e a r n i n g M i c r o s t a t i o n VBA",
"Chapter 9"
End S u b
TestDel eteSetti n g 2 deletes the Registry Section "Chapter 9': S u b TestDeleteSetting3() D e l e t e s e t t i n g " L e a r n i n g M i c r o s t a t i o n VBA" End S u b
TestDel eteSetti ng3 deletes the entire "Learning Microstation VBA" Application Name from the Registry and all of its sub-entries.
GetAllSettings GetAl 1 Settings, as the name implies, gets all keys under the specified app name and section and places them into a multi-dimensional array. S u b TestGetSettings( 1 D i m A l l s e t t i n g s As V a r i a n t A l l s e t t i n g s = GetAllSettings("Learning M i c r o s t a t i o n V B A " , " C h a p t e r 9") End S u b
-
130
I Chapter 9: Standard VBA Calls I
AIISettings(0) AIISettings(0,O) AIISettings(0,l) AIISettings(1) AIISettings(1 ,0) AIISettings(1 ,I) AIISettings(2) AIISettings(2,O) AIISettings(2,l)
"SaveSetting" "k Works" "SaveSettingZ" "k WorksZ" "SaveSettingS" "k WorksS"
Adding a watch to the Allsettings variable allows us to see the structure and values of the results of the GetAllSettings call.
READINGAND WRITING TO ASCII FILES Give me a text file and I will move the world. Or is it supposed to be a "Lever"? The ability to read and write ASCII text files gives us powerful leverage in our programming efforts. Many programs and databases can read and write these files. So, what are ASCII files? An ASCII text file is composed entirely of ASCII characters. It can be opened in Notepad or Wordpad and is readable by humans. Sub
TestWri teASCI IA( ) Open " C : \ o u t p u t . t x t "
F o r O u t p u t As
81
P r i n t #l, " F i r s t l i n e . " P r i n t #l, " S e c o n d l i n e . " C l o s e #1
F i r 5 t 1 ine. Second l i n e .
End Sub
Here is our file in Notepad. Let's examine the code now. First, identify a file with which to work. The above example works with the file C:\output.txt. You can use any file extension but be careful. If you write a new ASCII file and supply a file extension of.dgn, Microsoft Windows will think it is a Microstation file and attempt to open it with Microstation when you double-click on it. You have two options to use when writing files. Use the "Output" keyword, which means the file will be created if it does not exist or it will
I Reading and Writing to ASCII Files I
131
be overwritten if it does exist. Or use "Append if you want to add to the end of an existing file or create a new file if one does not already exist. When you open a file, a number is assigned to it. That number is then used whenever you read from or write to the file. In the above example, we are using a file number "#1". The number symbol must be in front of the number each time it is used. Next, write some text to the file. In the above example we use the P r i n t function. You can also use the Write function (an example appears below) but it gives slightly different results. Last, C1 ose the file. Sub
TestWriteASCIIBO Open " C : \ o u t p u t . t x t " F o r O u t p u t As
#I
111, " F i r s t l i n e . " W r i t e 111, "Second l i n e . "
Write C l o s e 81
" F i r 5 t 1 i ne. " "second l i n e . "
End Sub
The W r i t e function places quotation marks at the beginning and end of each line which may be helpful if you need it. Sub
TestWri teASCI IC( 1 Open " C : \ o u t p u t . t x t " F o r Append As Print Print Close
#I
111, " A n o t h e r l i n e 1." 111, " A n o t h e r l i n e 2 . "
111
End Sub
F i r 5 t 1 ine. Second l i n e . A n o t h e r l i n e 1. A n o t h e r 1 ine 2 .
Use "For Append" when opening a file to add text to an existing file. The above screen shot is the result of running TestWri teASCI IA and then running
TestWriteASCIIC.
FreeFile It is important to provide VBA a file number that points to the file in which you want to work. In previous examples where I used "#1" as a file number, the code works fine because the examples are simple. If your programs open multiple files simultaneously, you could become
132
I Chapter 9: Standard VBA Calls I confused as to which number should be used. This is where F r e e F i 1 e comes in handy. Sub
TestWriteASCIIDO D i m F F i l e A As L o n g D i m F F i l e B As L o n g FFileA
=
FreeFile
Open " C : \ o u t p u t a . t x t "
F o r A p p e n d As # F F i l e A
P r i n t #FFileA,
" A n o t h e r l i n e 1."
P r i n t #FFileA,
"Another l i n e 2."
FFileB
=
FreeFile
Open " C : \ o u t p u t b . t x t " F o r A p p e n d As # F F i l e B P r i n t IIFFileA, "Another l i n e 3." P r i n t BFFileB, "Another l i n e 3." P r i n t BFFileA, "Another l i n e 4." P r i n t BFFileB, "Another l i n e 4." C1 o s e # F F i 1 eB C1 o s e # F F i 1 eA End Sub
The above example works with two files at the same time. When you use F r e e F i 1 e, assign the return value to a variable. In this example, I used FFileA and FFileB as our variable names. Be careful if you use F r e e F i 1 e for multiple variables as we have done here. If you assign FFileA and FFileB file numbers with F r e e F i 1 e one right after another, they will both hold the same value. F r e e F i 1 e returns a different number only after a file has been opened. So, use F r e e F i 1 e, open the file it was used for, then use it again to open the next file. This keeps us from getting the same number and accidentally reading from or writing to the same file when we meant to read/write to two separate files. Here is a more advanced application of writing to ASCII Text Files. An XML document is an ASCII text document with specific formatting. Our next example creates a Microsoft Excel XML document that contains all of the EXE files in the "C:\Windows\System32" folder, as well as the date and time the file was last modified. After the XML file is written, you can open it in Microsoft Excel.
I Reading and Writing to ASCII Files I Sub
133
TestWriteASCIIEO D i m F F i l e A As Long
D i m e x e F i l e As S t r i n g FFileA
FreeFile
=
Open " c : \ e x e f i l e s . x m l "
F o r O u t p u t As I I F F i l e A
P r i n t B F F i l e A , " "
< ? ms o - a p p 1 ic a t ion
P r in t {IFF i1 e A,
"
P r i n t BFFileA,
-
"
xm 1 n s= u r n : s c h ema s - m i c r o s o f t "com:office:spreadsheet"">"
P r in t ]IF F i 1 eA , P r i n t IIFFileA, e x e F i 1e
p r o g id= " " Exc e 1 . S h e e t " " ? > " " "
=
" "
"
&
"
~
"
" "
"
D ir ( " C : \ W in d ow s \ Sys t e m 3 2 \ * . e x e " 1
<>
While exeFile
" "
P r i n t BFFileA,
"
P r i n t BFFileA,
-
"
& exeFile &
"
~
F i 1 eDa t e T i me ( " C : \ W in d o w s \ S y s t e m 3 2 \ " < / D a t a > < / C e l 1> " P r i n t BFFileA, exeFile
=
"
Dir
Wend P r i n t IIFFileA,
"
P r i n t IIFFileA,
"
P r i n t IIFFileA, "" Close FFileA End Sub
Excel is particular about the formatting of the XML document, so if you encounter problems copying this code from this book, open and run the code on the included CD. Here is a portion of the results of this XML file shown in Excel.
"
&
-
& e x e F i 1e 1 &
-
134
I Chapter 9: Standard VBA Calls I Reading from ASCII Files It is easy to write to ASCII files. Reading them is just as easy. Let's take it one line at a time. Sub
ReadASCI IA( 1 D i m F F i l e As L o n g D i m T e x t L i n e As S t r i n g
FFile
=
FreeFile
Open " C : \ M i c r o S t a t i o n W h i l e EOF(FFi1e)
=
VBA\TextPoints.txt"
F o r I n p u t As # F F i l e
False
L i n e I n p u t B F F i 1e , T e x t L i n e Debug.Print TextLine Wend C1 o s e # F F i 1 e End Sub
1.5,2.5,O,Note 1 34.2,54.12,O,Note 2 43.2,l. 43, 0,Note 3 22.3,33.4,O,Note 4
1.5,2.5,O,NOte 1 34.2,54.12,O,Note 2 43.2,1.43,O,NOte 3 22.3,33.4,O,Note 4
The example above left uses the Immediate window to show each line in the file we read. Above right is the file in Notepad. Use L i ne I n p u t and the file number to read a text file one line at a time. Continue reading until you reach the End Of File (EOF). It's time to expand on this example. Sub
ReadASCIIBO D i m F F i l e As L o n g D i m T e x t L i n e As S t r i n g D i m T e x t p o i n t As P o i n t 3 d
D i m X S p l i t As V a r i a n t D i m T e x t E l e m As T e x t E l e m e n t D i m R o t M a t As M a t r i x 3 d FFile
=
FreeFile
Open " C : \ M i c r o S t a t i o n W h i l e EOF(FFi1e)
=
VBA\TextPoints.txt"
False
L i n e I n p u t # F F i 1e , T e x t L i n e XSplit
=
Split(TextLine,
",")
F o r I n p u t As # F F i l e
I Controlling Code Execution I
135
TextP0int.X = XSplit(0) TextP0int.Y = XSplit(1) TextP0int.Z = XSplit(2) Set TextElem = Application.CreateTextElementl(Nothing, XSplit(3), TextPoint, RotMat) ActiveModel Reference.AddElement TextEl em
~
Wend C1 ose BFFi 1 e End S u b
We expanded “ReadASCIIA.” Now, instead of writing the information from the text file to the Immediate window, let’s create new text elements at the X, Y,Z location specified in each line of text.
Here are our notes placed exactly where the ASCII file specified.
N
4
ot CONTROLLINGCODEEXECUTION It is essential that we know how to loop through code multiple times and execute code based on certain conditions.
136
I Chapter 9: Standard VBA Calls I For
...Next
When you know how many times to loop through a particular block of code, use a For ... Next statement. Here’s a simple example: Sub ForNextA( 1 D i m I As Long For I = 1 To 10 ActiveDesignFile.AddNewLeve1 “NewLevel
& I
”
Next I End
Sub
After this code is run, 10 new levels are created named “NewLevel 1” through “NewLevel 10’: For ... Next requires a variable. This example uses a variable named I declared as a long. The first time you create a new level, I holds a value of 1 (one). The next time, I holds a value of 2 (two). This continues from 1 to 10. I eventually holds a value of 11 (eleven) which, since it is out of the range specified, exits the For ... Next loop and then VBA continues to execute the code below the For ... Next loop. Sub F o r N e x t B O D i m I A s Long For I = 1 To 1 0 S t e p 2 ActiveDesignFile.AddNewLeve1 “NewLevelB
”
& I
Next I End
Sub
I added an optional parameter to our For ... Next statement. It is a Step parameter. By default, For ... Next loops increase the index parameter by a value 1 (one) each time it is run. When this code is run, however, “I” gets values of 1, 3, 5,7, 9, then ends with a value of 11 and exits the loop because we are using “Step 2’: Sub F o r N e x t C O D i m I A s Long For I = 1 0 To 1 S t e p - 1 ActiveDesignFile.AddNewLeve1 “NewLevelC Next I End
Sub
”
& I
I Controlling Code Execution I
137
I just changed our Step parameter to -1. This means "I" gets the following values: 10, 9, 8, 7 ,6 , 5,4, 3, 2, 1 and then has a value of 0 and exits the loop because 0 is outside the bounds of the loop. Sub F o r N e x t D ( )
Dim
X
As D o u b l e
D i m Y As D o u b l e D i m I n s P t As P o i n t 3 d D i m C e l l E l e m As C e l l E l e m e n t
For
X
=
0 To 1 0 S t e p 0 . 2 5
For Y
=
0 To 1 0 S t e p 0 . 2 5
1nsPt.X
=
X
1nsPt.Y
=
Y
Set CellElem
=
Application.CreateCellElement3("Column",
-
InsPt, True) ActiveModel Reference.AddElement C e l l Elem Next Y Next
X
End Sub
This routine requires the definition of a cell named "Column" prior to running it. Embed one For ... Next statement inside another. The Step statement increases the variable by 0.25 each time it is executed. Note that I declared our index variables as double so they can hold decimal values.
While.. .Wend When using Whi 1 e ... Wend we are uncertain how many times we need to repeat a block of code. The code between the While and Wend
I Chapter 9: Standard VBA Calls I statements continues to execute as long as the While statement is true. Here is a portion of a procedure we already looked at in this chapter. While EOF(FFi1e) = False Line Input #FFi 1 e , TextLi ne XSplit = Split(TextLine, " , " ) TextP0int.X = XSplit(0) TextPoint .Y = XSpl i t(l) TextP0int.Z = XSplit(2) Set TextElem = Application.CreateTextElementl(Nothing, XSplit(31, TextPoint, RotMat) TextEl em.TextSty1 e. Hei ght = 4 TextElem.TextStyle.Width = 4 ActiveModel Reference.AddE1 ement TextElem Wend
-
When we open a file to read it, we may find 1 line, 10 lines, 100 lines, or any other number of lines to read in the file. So we keep looking at the EOF (End of File) condition of the file. While we have not reached the End Of File, we execute the code between the While and Wend lines.
.
Do.. Loop Do ... Loop is very similar to the While ... Wend statement. However, it is more versatile. Here is one example: Sub TestDoWhi 1 eA( 1
Dim CadMsg As CadInputMessage Dim InsPt As Point3d Dim Cell El em As Cell Element Do While True Set CadMsg = CadInputQueue.Get1nput Select Case CadMsg.InputType Case msdCadInputTypeDataPoint InsPt = CadMsg.Point Exit Do End Select Loop Set CellElem = Application.CreateCellElement3("Column", InsPt, True) ActiveModelReference.AddElement Cell Elem End Sub
-
I Controlling Code Execution I
139
One of the great things about a Do ... Loop statement is we can use “Exit Do” to get out of the loop at any time. This example allows the user to select a point in Microstation. When that happens, we capture the point and exit the Do Loop. Then we use the captured point to insert a new cell. Here is another variation of the above procedure. In this next example, the code will continue inserting cells until the user hits a key on the keyboard. Sub TestDoWhi 1 eB( )
D i m CadMsg As C a d I n p u t M e s s a g e D i m I n s P t As P o i n t 3 d D i m C e l l E l e m As C e l l E l e m e n t
Do W h i l e T r u e S e t CadMsg
=
CadInputQueue.Get1nput
S e l e c t Case C a d M s g . I n p u t T y p e Case m s d C a d I n p u t T y p e D a t a P o i n t InsPt
=
CadMsg.Point
Set CellElem = Application.CreateCel1 Element3(”Column”, InsPt, True)
A c t i v e M o d e l R e f e r e n c e . A d d E l e m e n t C e l l Elem Case msdCadInputTypeCommand E x i t Do End S e l e c t Loop End Sub
When using “Do While True”, we remain in the loop until we either “ E x i t Do” or “ E x i t Sub” or “ E x i t Function”. “ E x i t Do” to get out of the loop and continue to execute the code in the procedure or function. Use “ E x i t Sub” and “ E x i t Function” to exit the procedure or function. Sub TestDoWhi 1 eC( )
D i m T e x t T o P l a c e As S t r i n g D i m L i n e N u m b e r As Long D i m N o t e P t As P o i n t 3 d D i m CadMsg As C a d I n p u t M e s s a g e D i m T e x t E l e m As T e x t E l e m e n t D i m R o t M a t As M a t r i x 3 d
Do W h i l e T r u e
140
I Chapter 9: Standard VBA Calls I S e t CadMsg
=
CadInputQueue.GetInput
S e l e c t Case C a d M s g . I n p u t T y p e
msdCadInputTypeDataPoint
Case
NotePt
=
CadMsg.Point
S e t TextElem
=
Application.CreateTextElernentl(Nothing,
-
“B N o t e ” , N o t e P t , R o t M a t ) A c t i v e M o d e l Reference.AddE1 ement T e x t E l e m E x i t Do End S e l e c t Loop TextToPl ace LineNumber
“ T h e f o l l o w i n g n o t e s s u p e r c e d e a1 1 p r i o r n o t e s .
= =
”
1
Do N0tePt.Y
=
N0tePt.Y
Set TextElem
=
-
0.375
Application.CreateTextElementl(Nothing, LineNumber &
‘I.
”
& TextToPlace,
~
~
NotePt, RotMat) A c t i v e M o d e l Reference.AddE1 ement T e x t E l e m LineNumber TextToPlace
= =
LineNumber
+ 1
InputBox(”Enter Note:”)
Loop W h i l e TextToPl ace
<>
“I’
End Sub
This procedure uses two separate Do Loop statements. Let’s focus on the second one. When using “Do” by itself, the code inside the loop executes at least once. Then, rather than placing the conditional statement at the beginning of the “Loop”,place the conditional statement at the end of the “Loop.” This example allows the user to select a point. We automatically enter “# Note” where the user selected the point and enter “1. The following notes supersede all prior notes.” below the “# Note” text. Now that we added a header and a standard note, we allow the user to begin entering additional notes. Each additional note is placed 0.375 units below the prior note. When the user presses the OK button without entering anything in the InputBox, the Loop completes because TextToPlace is an empty string and the “Loop condition is no longer true.
I Controlling Code Execution I
141
.
For Each.. Next Some objects are in collections. For example, each document has a Levels collection composed of Level objects. Use For Each ... Next statements to look at each object in a collection. Sub T e s t F o r N e x t A ( )
D i m d g n L e v e l As L e v e l F o r Each d g n L e v e l I n A c t i v e D e s i g n F i l e . L e v e 1 s D e b u g . P r i n t d g n L e v e l .Name Next End Sub
When we use For Each ... Next, we specify a variable to use for each object and then the collection to look in when we begin the For Each ... Next statement.
NewLevelC 3 NewLevelC 2 NewLevelC 1
.
If.. Then Use If.. . Then statements to execute a particular block of code only if a specific condition evaluates to a value of true. Sub T e s t I f T h e n A ( 1
D i m L e v e l N a m e As S t r i n g L e v e l Name
=
I n p u t B o x ( " E n t e r L e v e l Name ( 3 l e t t e r s o n l y ) " )
I f Len(Leve1Name)
=
3 Then
A c t i v e D e s i g n F i l e . A d d N e w L e v e 1 L e v e l Name End I f End Sub
If the user enters something with three characters, add the new level. A very simple implementation of an If.. . Then statement. Sub T e s t I f T h e n B ( 1 D i m L e v e l N a m e As S t r i n g L e v e l Name
=
I n p u t B o x ( " E n t e r L e v e l Name ( 3 l e t t e r s o n l y ) " )
I f Len(Leve1Name)
3 Then A c t i v e D e s i g n F i l e . A d d N e w L e v e 1 L e v e l Name
Else
=
142
I Chapter 9: Standard VBA Calls I MsgBox L e v e l N a m e & " characters."
"
has
"
& Len(Leve1Name) &
-
End I f End Sub
In this example, look at the number of characters and create the new level if it is three characters in length. Also add an "Else" statement to handle situations when the length is not equal to three. Display a MessageBox showing the number of characters entered when the entry has anything other than three characters in it. Sub T e s t I f T h e n C ( )
D i m L e v e l N a m e As S t r i n g LevelName
=
I n p u t B o x ( " E n t e r L e v e l Name ( 3 l e t t e r s o n l y ) " )
I f Len(LevelName1
=
3 Then
ActiveDesignFile.AddNewLeve1 L e v e l Name E l s e I f Len(LevelName1
>
3 Then
A c t i v e D e s i g n F i 1 e.AddNewLeve1 L e f t ( L e v e l Name, 3 ) Else MsgBox L e v e l N a m e & " h a s " & L e n ( L e v e 1 N a m e ) & " characters."
-
End I f End Sub
TestIfThenC introduces an E l s e I f statement. You can use E l s e I f statements inside I f ... Then statements and provide a secondary I f statement. You can use multiple E 1 s e I f statements before an E 1 s e statement.
Select Case Imagine asking a user to enter a level name then looking at the first character of the level name. You could use an I f ... Then statement with multiple E l s e I f statements or a S e l e c t Case statement. Sel e c t Case lets us provide the condition and then multiple possible matches for the condition. Sub T e s t S e l e c t C a s e A ( )
D i m L e v e l N a m e As S t r i n g LevelName
=
I n p u t B o x ( " E n t e r L e v e l Name:")
S e l e c t Case U C a s e ( L e f t ( L e v e 1 N a m e . 11) Case " A "
I Controlling Code Execution I
143
A c t i v e D e s i g n F i l e . A d d N e w L e v e 1 " A _ " & L e v e l Name Case " B " Case
A c t i v e D e s i g n F i l e . A d d N e w L e v e 1 "B-B-" OUC", WE"
& L e v e l Name
A c t i v e D e s i g n F i l e . A d d N e w L e v e 1 "CDE-"
& L e v e l Name
WDW,
Case E l s e MsgBox " N o t a v a l i d l e v e l name." End S e l e c t End Sub
In this example, look at the first character of the level name entered. There are multiple possible blocks of code we may want to execute based on the first character. If the first character is not A, B, C, D, or E, display a MessageBox and do not add a new level. If the first character does meet our criteria, prepend characters to the entered level name as you add the level name.
Error Handling In a perfect world with perfect developers, errors would never occur. We, however, are not perfect, so errors do pop up once in a while. VBA gives us some tools to deal with errors. Sub
TestErrorHndA( 1 On E r r o r GoTo e r r h n d
D i m L i n e L e n g t h As Double LineLength
=
CDbl ( I n p u t B o x ( " E n t e r L i n e L e n g t h : " ) )
E x i t Sub errhnd: S e l e c t Case E r r . N u m b e r Case 13 ' T y p e M i s m a t c h MsgBox " L i n e L e n g t h s m u s t b e n u m e r i c . "
Err . C l e a r End S e l e c t End Sub
In TestErrorHndA, ask the user for a line length. As you write code assume the user knows to enter a numeric value but if the user enters
144
I Chapter 9: Standard VBA Calls I something like “10 meters’: you run into problems. If you don’t handle the error, the user sees this:
As you become more experienced in programming, you are better able to anticipate potential data entry problems and other issues that cause errors. So, if you do not handle the Type Mismatch error when asking the user to enter a length, he sees an unhandled error MessageBox. If the users click the Debug button, he is taken to the code that shows the line where the error occurred. However, if you handle the error as shown in the above macro, the user sees this:
When handling errors, you can display MessageBoxes to let the user know an error occurred, or you can handle the specific error so the user does not know anything happened. Now, let’s take another look at the code in detail. The first thing to do in the procedure is state: On E r r o r GoTo e r r h n d
This tells VBA that if an error is encountered jump to the area of code labeled “errhnd’: Here it is: errhnd: S e l e c t Case E r r . N u m b e r Case 1 3 ‘ T y p e M i s m a t c h MsgBox “ L i n e L e n g t h s m u s t b e n u m e r i c . ”
Err.Cl e a r End S e l e c t
I Controlling Code Execution I
145
Each error has a number associated with it. Use a Select Case statement to handle different types of errors differently. In this example, only look at error number 13. If any other error occurs, it is not handled by our Select Case statement and the procedure finishes with End Sub. So, how do we know what error numbers we need to deal with? This is an excellent question. Let’s go back to TestSelectCaseA covered a few pages ago. Run that macro and enter “aad: Everything should run fine. Run it again and enter “aad: What happens?
If you enter the same level name twice, the code attempts to create a duplicate level. We see this MessageBox which gives us some good information. First, it tells us the error number. -2147221504 and a description that “Level name is duplicate”.That is good to know because we can add that number in the error handling portion of our code. We can also hit the Debug button to go to the line of code in question to see exactly where the error occurs. Sub
TestErrorHndB( 1 On E r r o r GoTo e r r h n d
D i m LineLength As Double LineLength
=
CDbl ( I n p u t B o x ( ” E n t e r L i n e L e n g t h : ” ) )
E x i t Sub errhnd: S e l e c t Case Err.Number Case 13 ‘ T y p e M i s m a t c h MsgBox “ L i n e L e n g t h s m u s t be n u m e r i c . ” Err . C 1 e a r Resume N e x t End S e l e c t End Sub
146
I Chapter 9: Standard VBA Calls I TestErrorHndB isidenticalto TestErrorHndAexceptforoneline.Addinga Resume Next statement in TestErrorHndB executes our procedure to
continue the line after the error occurred. Sub T e s t E r r o r H n d C ( ) On Error GoTo e r r h n d
D i m L i n e L e n g t h As D o u b l e LineLength
CDbl ( I n p u t B o x ( " E n t e r L i n e L e n g t h : " ) )
=
E x i t Sub errhnd: S e l e c t Case Err.Number Case 1 3 ' T y p e Mismatch MsgBox " L i n e L e n g t h s must be n u m e r i c . " Err.Cl e a r
Resume End S e l e c t End Sub
Here is another slight modification that uses a Resume statement instead of Resume Next. Resume asks VBA to again try the line of code where the error occurred, whereas Resume Next ignores the line of code where the error occurred and moves to the next line. Sub T e s t E r r o r H n d D ( ) On Error Resume N e x t D i m L i n e L e n g t h As D o u b l e
LineLength
=
CDbl ( I n p u t B o x ( " E n t e r L i n e L e n g t h : " ) )
End Sub
Instead of attempting to trap errors as they occur, you can tell VBA to ignore errors altogether and move to the next line using "On Error Resume Next". Although "On Error Resume Next" appears to be somewhat sloppy (and it can be), it can be useful. Consider this next procedure: Sub T e s t E r r H n d E ( ) On Error Resume N e x t
D i m MyExcel As O b j e c t S e t MyExcel
=
I f Err.Number
G e t o b j e c t ( , "Excel . A p p l i c a t i o n " )
<>
0 Then
I Controlling Code Execution I
147
Err.Cl e a r
S e t MyExcel
=
CreateObject("Exce1 . A p p l i c a t i o n " )
End I f On E r r o r GoTo e r r h n d MyExcel . V i s i b l e
=
True
MsgBox MyExcel . A c t i v e S h e e t . N a m e E x i t Sub errhnd: MsgBox "Error
"
& Err.Number & " has o c c u r r e d . " & vbCr & v b c r i t i c a l , " E r r o r I n TestErrHndE"
Err.Oescription,
Err.Cl e a r End S u b
In this example, we use On Error Resume anticipating the potential for a specific error. S e t MyExcel
=
Next because we are
G e t o b j e c t ( , "Excel . A p p l i c a t i o n " )
G e t o b j e c t assumes the object we are getting has already been created by starting Excel (in this example). If Excel is not started, however, we normally get an error when using G e t o b j e c t . Use the Err object to get error numbers and descriptions. After calling Getobject, check the Err.Number value. If it is non-zero, an error occurred. Use Err.Clear to clear the previous error from memory.
Then we move to the next method of working with Excel, using C r e a t e o b j e c t which launches the Excel application. On Error GoTo e r r h nd tells VBA to move to the "errhnd area if an error is encountered. Set the Visible property of the Excel application to true. This can cause an error if Excel did not start, most often because it isn't installed. If Excel is running and visible, display the name of the active sheet in a MessageBox. This can throw an error because even though Excel is running, an Excel workbook (As file) file is not open. Now to the errhnd section of our code. A review of the above suggests that a number of things could cause errors. Showing the error number and description in a MessageBox lets us know which error has been raised.
148
I Chapter 9: Standard VBA Calls I Sub
TestErrorHndF() On E r r o r GoTo e r r h n d
D i m L i n e L e n g t h As D o u b l e On E r r o r GoTo 0 LineLength
=
CDbl ( I n p u t B o x ( " E n t e r L i n e L e n g t h : " ) )
E x i t Sub errhnd: MsgBox " E r r o r
"
& Err.Number &
Err.Description,
"
has o c c u r r e d . " & vbCr &
-
v b c r i t i c a l , " E r r o r I n TestErrHndE"
Err . C l e a r End Sub
"On Error G o t o 0" (that's a zero after Goto) tells VBA to ignore the previous "On Error" statements and continue as if there is no error handling. This comes in handy because you will see an error dialog box showing the error number and description plus a Debug button.
Clicking Debug takes you to the line of code that has the problem. Once you find and fx the bug, you can comment out the "On Error G o t o 0" line so your Error Handling code is at work again. We covered a number, although not a comprehensive list, of useful and commonly used VBA calls. You can use the Object Browser in VBA to display all VBA calls natively available to us. We will discuss the Object Browser later in this book. Here is a snapshot of what you will see if you filter on the VBA Reference, the DateTime Class, and the D a t e D i f f member.
Cglobalsa Collection ColorConstants Constants ...Conversion .................................................. ;DateTime ..................................................... Errobject Filesystem Financia I FormShowConstar Global Information Interaction KeyCodeConstantr Math
I Review I
149
After selecting an item in the Object browser, you can get additional information and, at times, sample code, by pressing the
DateDiff Function See Also
Example Speri!ics
Returns a Variant (Long) specifying the number of time intervals between two specified dates. Syntax DateDiff(interva1, d a t e f , date2[. firstdayofweek[. firstweekofyear]]) The DateDiff function syntax has these named arsuments:
interval
Required. Strins exmession that is the interval of time you use to calculate the difference between datef and date2.
date1 , date2
Required; Variant (Date).Two dates you want to use in the calculation.
firstda yofweek
Optional. A constantthat specifies the first day of the week. If not specified, Sunday is assumed.
firstweekofyear
Optional. A constant that specifies the first week of the year. If not specified, the first week is assumed to be the week in which January 1occurs.
Settings The interval arsument has these settings:
Many procedures and functions are built into VBA. You do not need to write a function that tells the current date and time because we have the Now function. Similarly, you do not need to write complex code that stores your application information in the Windows registry as you can use the Savesetting and Getsetting procedures.
10
Visual Interface It is time to begin working with the “Visual” side of VBA. Let’s consider the form shown. It is composed of labels (Level, Cells, X, Y, and Z), two combo boxes (with SIDEWALK and column selected), three text boxes (for X, Y, and Z values), and two CommandButtons labeled Insert and Cancel. Creating a good graphical user interface (GUI) can be one of the most challenging elements of software development. Anyone can throw buttons on a form, but making the interface user friendly and intuitive takes thought, effort, and being open to the ideas of others. Let’s discuss the form shown above. The goal is to allow the user to insert cells on a specific level at a specific point. Which should come first? The Cell Name ComboBox? If we know which cell we want to insert, perhaps it should appear first. What if the list of cells is dependent on the selected level? This would keep us from inserting a cell on the wrong level. So, perhaps the level should be first. Then we have the insertion point. Perhaps it would be best if the insertion point appeared after the level and cells combo boxes. And perhaps we should
151
152
I Chapter 10: Visual Interface I allow the user to pick the insertion point in addition to being able to enter the insertion point by hand. Let’s move things around a little. How does this look? Better? OK. We have the visual elements arranged now. After you create the basic interface, you can begin writing code behind the interface. We’ll get into that a little later. First, let’s talk about the controls we can add to our user forms. The toolbox shows us the controls we can place on our forms. Except for the pointer arrow, each of the items shown are visual elements we can use in our interface design.
PROPERTIES,
METHODS, AND EVENTS
Controls have properties, methods, and events. A property describes how a control looks or behaves. Methods tell controls to do something. For example, using the ComboBox ‘XddItem” method adds an item to its list. Most events occur when the user interacts with our GUI. For example, when a user clicks a button, the click event of the button is triggered and executes any code we place in that event.
Properties You can set properties at design time (that is while you are designing your interface and writing code) or at run-time (when the program is being run). Control properties are modified at design time by using the Properties window.
I Properties, Methods, and Events I
153
You can display or hide the Properties window. If a CommandButton is selected but you cannot see the Properties window, right-click on the button and select “Properties” from the context menu or press the
At run-time, properties can be set as follows: CommandButtonl.Enabled
=
True
We enabled the control named “CommandButtonl” by setting its Enabled property to true. We can also get property values at run-time. M s g B o x CheckBoxl.Value NewLevel Name = txtLevel Name.Text
Notice how we begin by addressing the control by name, typing a period, and then typing the property to work with. After you press the period key, VBA shows a list of the available properties and methods.
Private Sub CommandButtonl-Click ( ) M s g B o x CheckBoxl. V a l u e N e w L e v e l N a m e = t x t L e v e l N a m e .t e End Sub TexfAlign TextLength TOP Value Visible Width
In the above example, I typed the name of a TextBox txtLevelName and the period key so the helper window displays the available properties and methods. As you begin typing the name of the property or method, VBA automatically selects the first matching item in the list. When the “txtLevelName.te”has been typed, if you press the
154
I Chapter 10: Visual Interface I of code. Letting VBA finish our sentences lets us develop applications very rapidly.
Control Events Now, let’s look at using control events. You write code for control events in the form’s code area, which looks identical to a code module but is a little different. All controls currently inserted into the form are itemized in the left-hand ComboBox. When a control is selected in the left ComboBox (CommandButtonl is selected below), we can then select an event in the right-hand ComboBox (the click event is selected below).
Private Sub CommandButtonl-Click ( ) MsgBox CheckBoxl. Value End Sub
To work with a different event, simply drop down the procedure ComboBox (on the right) and select another event. As we can see here, there are quite a few events from which to choose.
I Common Control Properties I
155
When you select an event not previously selected, VBA fills in the framework of the event for us.
Private Sub CommandButtonl-KeyPress(ByVa1
KeyAscii A s MSForms.Return1nteger)
I
End Sub
This is Keypress event occurs when a key is pressed. Some events pass parameters we can look at, such as the Keypress event passing the “KeyAscii” parameter. We can use this parameter as a variable to see which key was pressed.
P r i v a t e S u b CommandButtonl-MouseDown(ByVa1 B u t t o n A s I n t e g e r , ByVal S h i f t AS I n t e g e r , ByVal x AS s i n g l e , ByVal Y AS s i n g l e ) End Sub
Here the MouseDown event tells which mouse button was pressed (The button parameter), the state of the
COMMONCONTROLPROPERTIES Before discussing each control individually, let’s talk about properties and events that nearly all controls have in common.
156
I Chapter 10: Visual Interface I Name What’s in a name? We work with controls by addressing them by name. We then identify the property we want to get or set, or the method we want to use. The name and property (or method) are separated by a period. Take a look: Labell.Caption
=
“ E n t e r L e v e l Name:”
Change the Caption property of the control named “Labell” by calling the control by name, typing a period, and then typing “Caption” which is the property we want to modify When you want to SET a property value, the “Control.Property” is on the left-hand side of the equal sign and the value you are assigning it is on the right-hand side, as shown. To GET the “Control.Property’: put it on the right-hand side of the equal sign and place a variable on the left-hand side of the equal sign like this: XVal
=
txtXValue.Text
The variable XVal now holds the value of the Text property of the txtXValue control. Since we are discussing control names, we should say a word or two about naming conventions. Control names follow the same rules as variable names. They must begin with a letter, cannot contain spaces, etc. Some naming conventions suggest that TextBox names should begin with “txt”, Labels should begin with “lbl”, ComboBoxes should begin with “cmb‘: etc. As with variable naming, if a convention needs to be followed, you should follow it. If not, at least name the controls something that makes sense. By default, controls are named such as “TextBoxl”,“TextBox2’: “TextBox3” and so on.
Left, Top All controls have a Left property and a Top property. These properties dictate where to place the control on the form. The top left corner of the form is (0,O). So, if a TextBox is given a Left value of 0 and a Top value of 0, it appears in the upper left corner of the form.
I Common Control Properties I
157
Width, Height All controls have Width and Height properties. These properties determine the size of the control. We should consider the size and shape of the controls we use. Just because a TextBox can have a width of 20 and a height of 20 doesn't mean it should. If a TextBox is a set to be a singleline TextBox, it may make little sense to have its height greater than is necessary to display a line of text. If on the other hand, you want to display a square CommandButton, make the width and height properties the same.
Visible Why would you want to place a control on a form and then set its Visible property to false? Controls are to be seen, right? There are times when you may want to make a control visible or invisible based on other conditions. Setting a control's Visible property to false makes it invisible at run-time but it is still visible at design-time. The Visible property can be changed at run-time between true and false as needed.
Enabled When a control has its Enabled property set to true, you can interact with the control at run-time. When Enabled is false, the control turns gray and you we are unable to interact with it. The Enabled property does not affect the visibility of the control, only the interaction.
TabStop Pressing the
Ta blndex The TabIndex property determines the order in which controls receive focus as you Tab from control to control.
158
I Chapter 10: Visual Interface I
Use the Tag property to do as you see fit. One thing you can do with the tag is assign it a default value for a TextBox and give the user the ability to click a “Load Defaults” button, causing the Tag property to populate the Text property.
Cont rolTipText The ideal interface gives the user the controls necessary to perform the proper functions without needing to refer to a user manual each time the program is used. You could place a lengthy Label next to each control explaining details about why the control is there and how to use it, but this would clutter the interface. Hold the mouse cursor over a control for more than a second or two to make VBA display the control tip text.
This example shows the ControlTipTextproperty value. An experienced user learns that the level name should be four characters long, while new users benefit from having a little help on what they should enter. Enough with the common control properties. Let’s talk about each of the standard controls one at a time, highlighting its primary properties, methods, and events. Also shown will be its icon in the toolbox.
Labe1 Labels help users know what to enter or select. Properties of note are as follows: Caption
The text displayed to the user.
Font
The font used to display the Caption.
TextBox The TextBox allows users to enter text or display text, often in singleline mode so text is displayed in one line. You can stretch a TextBox vertically to display multiple lines.
I Common Control Properties I
159
Properties Text Locked ”True’: users cannot. When set to ”True’; text in the TextBox can be selected and copied to the Windows Clipboard even though the text cannot be changed by the user. When Locked, you change the text property MaxLength asking for a Canadian postal code, you could set the MaxLength property to 6,to enter “V4A5M2‘; but would be prohibited from entering “85302-1 234’: MultiLine and WordWrap
next line. When False text is scrolled on one line.
Passwordchar what is being typed, supply a password character. Enter an asterisk (“1 in this property to make an asterisk appear each time a keyboard key is pressed. This can keep others from seeing what is being typed (if they are looking over our shoulder), but is not highlysecure. It takes very little code for a seasoned developer to find out exactly what is behind the password characters displayed on screen.
160
I Chapter 10: Visual Interface I
Keypress
Gives the ASCII code of the character pressed on the keyboard. Use this to restrict users from entering specific characters, such as allowing only numeric values to be entered.
KeyDown, KeyUp
Gives the keyboard key pushed down and released as well as the state of the
COMBOBOX Use ComboBoxes to allow users to drop down a list of items to choose from, or depending on the Style property, users can type into a ComboBox if the item is not listed.
Properties Text
Text selected or entered in the ComboBox.
Style
O=Combo (select from list or type into a ComboBox) or 2=List (user must select from list). Index of the selected item in ComboBox. -1 = Nothing selected. 0 = First item in list is selected. 1 = Second item in list is selected and so on.
Number of items in the ComboBox.
I ListBox I
161
Methods Addltem
Adds a List Item to the ComboBox.
Remove1tem Clear
Clears all items from the ComboBox list.
Click
Occurs when user clicks on an item in the ComboBox.
Change
Occurs when the selected item changes. This is different from the Click event because it is possible to change the selected item in the ComboBox by using arrow keys and other keyboard keys.
LISTBOX Use ListBoxes to allow one or more items to be selected from a list. ComboBoxes are similar but limit selection to one item at a time and, of course, ListBoxes do not “drop down”.
Properties Text
Text of the Selected Item in the List.
MultiSelect Selected of the item we want to check on. ColumnCount
Addltem Removeltem Clear
Same as ComboBox
162
I Chapter 10: Visual Interface I
Same as ComboBox
Click Change
CHECKBOX CheckBoxes allow us to specify the selection of an item. Multiple CheckBoxes can be on one form and behave independently from one another. Using a pizza order analogy, you could use a CheckBox to specify each topping.
Properties Caption TrippleState
True or False. When true, CheckBox has possible values of true, false, or null. When Triplestate is false, possible values are either true or false.
Value
Events Click
When user clicks on a CheckBox, the value is set to either true or false. Click events do not fire when the user clicks the CheckBox and the value is set to Null (in Triplestate mode).
Change
OPTIONB m o ~ Use OptionButtons when you want the user to make a single choice between several possible items, such large, medium, or small. You could use three OptionButtons for each selection.
I Toggle Button I
163
Properties Caption Group Name selected. To allow a user to select "Large': "Medium': "Small" for a size and to allow them to select "Red': "White", "Blue" for the color, use two group names for each group of OptionButtons.
Events Click
See CheckBox Click Event.
Change DblClick
This event is triggered when a user double-clicks the
TOGGLE BUTTON The toggle button looks like a CommandButton but it behaves more like a CheckBox. When selected, it looks indented. You typically see toggle buttons used to specify whether a font is bolded, underlined, or italicized.
Properties Caption
Text displayed on the Toggle Button.
TrippleState Value
Events Click Change DblClick
See OptionButton.
164
I Chapter 10: Visual Interface I FRAME Frames are control containers. This means that controls can be placed inside of them. If a frame’s Visible property is set to false, all controls inside it become invisible. When a frame is moved, all controls in it move with it. Use frames to organize groups of controls.
Properties Caption
The Caption shown in the upper left-hand corner of the
Visible
Use CommandButtons to give users something to click on, such as these (commonly captioned): “OK”,“Cancel”,“Print”,“Open”,and “Close’:
Properties Caption
Text displayed in the Button.
TakeFocusOnClick
Determines whether the Button receives focus when the user clicks the button or if focus remains with the previously selected control.
Events Click
Triggered when the user clicks the button.
TABSTRIP Use tab strips to present “Tab” selections. Do not confuse these with the MultiPage Control even though they look alike. Tab strips are not control containers. Rather tab strip buttons are a cross between toggle buttons and OptionButtons. Only one tab on a tab strip can be selected at any given time.
I MultiPage I
165
Properties MultiRow
When set to True, multiple rows are displayed when the number of tabs exceeds the width of the Tab Strip. When set to False, the Tabs are all displayed on one
Selectedltem
Which tab is selected?
Style TabOrientation
Methods Ta bs.Add
Used to add Tabs to the Tab Strip.
Events Change
Triggered when the active Tab changes.
MULTI PAGE The MultiPage control is a control container where each page has its own collection of controls. Right-click a tab and select a function to add, rename, delete, and reorder pages.
Properties Value Pages.Count MuItiRow
Methods Pages.Add
Used to add pages to the MultiPage control.
166
I Chapter 10: Visual Interface I Events Change
SCROLLBAR Scroll Bars allow the user to select and change numeric values. The rectangle that moves as the value changes is called the Thumb.
Properties The amount the Value property changes when the user clicks inside the ScrollBar area.
Largechange Smallchange
user clicks on the outside Scroll Bar Arrows. Min Scroll Bar is Horizontal. The Maximum Value the Scroll Bar can have.
Max Value
Events Change
Triggered when the value changes by clicking in the Large Change area or on the Small Change arrows and after the Thumb is released.
Scroll Label, for example, and do not implement the Scroll event, the Label will not change until the Thumb is dropped. The Scroll event allows us to see the number change as the
SPINBUTTON Use the spin button to allow users to change numeric values. It is similar to the scroll bar but does not have a Thumb.
167
Properties Delay
Time in milliseconds after the user begins holding down a button before the value begins scrolling up or down Minimum Spin Button Value.
Max
I
Small Change Value
The Value of the Spin Button.
Change SpinUp SpinDown
Use the image control to display images in your interface. Acceptable file formats are .bmp, .gif, .jpg, .wmf, and .ico.
Properties Borderstyle
0 = None, 1 = Single.
Picture
The file to display in the Image Control.
PictureSizeMode PictureTiling for no tiling.
USERINTERFACE EXERCISES We started this chapter by discussing the importance of creating a useful and intuitive interface, then introduced the standard controls. Now let’s create a few interfaces to demonstrate the properties, methods, and events we covered. To accomplish this, you will insert a few new forms
168
I Chapter 10: Visual Interface I in a new project. Begin by inserting one new form and working with it. After it is finished, insert another new form, and so forth. Here is the first interface you are going to work with. Add the controls beginning at the top and working down. The first controls are two ComboBoxes. By default they are inserted with the names ComboBox 1 and ComboBox2. Change the names to cmbLevels and cmbCells. Next, insert two labels and place them on the left-hand side of the ComboBoxes. Make the caption properties for these labels “Level” and “Cells”. The next section is a group of controls inside a frame. Insert the frame and change the frame caption to “Insertion Point”. Then insert the text boxes, labels, and CommandButton. Change the TextBox names to txtX, txtY, and txtL Name the CommandButton cmdPick and the caption “Pick”. The last controls you will add are two CommandButtons named cmdlnsert and cmdCancel with captions of “Insert” and “Cancel’: Placing controls in the form is only the beginning. The Levels ComboBox needs to be filled with all of the levels in the active drawing and the Cells ComboBox needs to be filled with all of the cells available. Fill these ComboBoxes before the user sees the form. To accomplish this, go to the Initialize Event of the User form. Right-click on the form and select View Code in the pop-up menu. By default we are taken to the Click event of the form. Select I n it i a 1 ize in the Procedure ComboBox. Private Dim Dim Dim For
Sub UserFormpInitializeO MyLevel As Level MyCellEnum As CellInformationEnumerator MyCell As Cell Information Each MyLevel In ActiveDesignFile.Levels cmbLevels.AddItem MyLevel .Name
Next Set MyCellEnum = Application.GetCellInformationEnumerator(True,
True)
I User Interface Exercises I
169
Whi 1 e MyCell Enum.MoveNext Set MyCell = MyCellEnum.Current cmbCells.AddItem MyCell.Name Wend End Sub
Here is the code in the Initialize Event of the UserForm. It should look like this:
Private Dim Dim Dim For
Sub UserForm-Initialize 0 MyLevel As Level MyCellEnum As CellInformationEnumerator MyCell As CellInformation Each MyLevel I n ActiveDesignFile.Leve1s crnbLevels.Add1tern MyLevel.Narne
Next Set MyCellEnum = Application. G e t C e l l I n f o r r n a t i o n E n u r n e r a t o r (True, True) While MyCellEnum.MoveNext Set MyCell = MyCel1Enum.Current crnbCells.AddItern MyCell.Narne Wend End S u b
We are using the AddItem method to populate the Levels and Cells ComboBoxes with the names of the levels and cells in the ActiveDesignFile. Now that code is in place to populate the ComboBoxes, press
170
I Chapter 10: Visual Interface I Right-click on the top TextBox and select View Code. This takes us to the Change e v e n t of the TextBox by default. Selecting K e y p r e s s in the Procedure ComboBox takes us to the K e y p r e s s e v e n t .
Private Sub txtX-Keypress (ByVal KeyAscii As MSForms. ReturnInteger) Select Case KeyAscii Case Asc("0") To Asc("9") Case Asc ( " . " ) I f InStr(1, txtX.Text, " . " ) > 0 Then KeyAscii = 0 End I f Case Else KeyAscii = 0 End Select End Sub
"KeyAscii" is passed to us in the Keypress event. It tells us the ASCII value of the character that was pressed. If the ASCII code is for the numbers 0 through 9, the code will do nothing to the KeyAscii parameter. This allows the value to be entered into the TextBox just as it was typed. Next look at the period symbol. If there is already a period in the TextBox, set KeyAscii to zero which keeps another period from being entered. If a period is not in the TextBox, do nothing to the KeyAscii parameter so the period can be added. If any other KeyAscii value is encountered, set the KeyAscii parameter to zero (0) which causes the event to act as though nothing was pressed. We need to put the same code into the K e y p r e s s events of the txtY and txtZ controls. Simply copy and paste the code. One little change is all it takes. When looking for a period, use the control name. After copying and pasting the Select Case code, change the name of the control in the InStr function to match the control of the Keypress event. P r i v a t e Sub t x t X L K e y P r e s s ( B y V a 1
-
K e y A s c i i As M S F o r r n s . R e t u r n 1 n t e g e r ) S e l e c t Case K e y A s c i i Case A s c ( " 0 " ) To A s c ( " 9 " ) Case A s c ( " . " )
If InStr(1, txtX.Text,
"."I
>
0 Then
I User Interface Exercises I KeyAscii End If Case Else KeyAscii = 0 End Select End Sub
=
171 0
Private Sub txtYLKeyPress(ByVa1 KeyAscii As MSForms.Return1nteger) Select Case KeyAsci i Case Asc("0") To Asc("9") Case Asc(".") If InStr(1, txtY.Text, " . " ) > 0 Then KeyAscii = 0 End If Case Else KeyAscii = 0 End Select End Sub Private Sub txtZLKeyPress(ByVa1 KeyAscii As MSForms.Return1nteger) Select Case KeyAsci i Case Asc("0") To Asc("9") Case Asc(".") If InStr(1, txtZ.Text, " . " ) > 0 Then KeyAscii = 0 End If Case Else KeyAscii = 0 End Select End Sub
Let's handle the Cancel button next. When the user clicks the Cancel button, we want to close the form. We have been right-clicking on controls and selecting View Code to get into the events of the controls. Double-click on the Cancel button now. This is another way to get into the form's code area. Private Sub cmdCance1-Click0 Unload Me End Sub
172
I Chapter 10: Visual Interface I One line of code is all it takes to close the form. That's it for the easy functionality; now for something more difficult. When a user clicks the CommandButton, insert the selected cell on the selected level at the entered insertion point. Private Sub cmdInsert-Cl ick( If cmbLevels.Text = Then MsgBox "Please select a level . " Exit Sub End If If cmbCells.Text = Then MsgBox "P1 ease select a cell . Exit Sub End If Dim InsPt As Point3d Dim Cell El em As Cell Element 1nsPt.X = CDbl(txtX.Text) 1nsPt.Y = CDbl(txtY.Text) 1nsPt.Z = CDbl(txtZ.Text) Set CellElem = CreateCellElement3(cmbCells.Text, InsPt, True) Cell Elem. Level = ActiveDesignFi 1 e. Level s(cmbLeve1 s .Text) ActiveModelReference.AddElement Cell Elem End Sub " "
"I'
"
Before inserting, make sure the user selected a level and a cell to insert. Use the txtX, txtY, and txtZ text boxes to get X, Y, Z values for the cell origin. After creating the cell element, set its layer to the value of the cmblevels ComboBox Text property. The last thing to do is add the element to the active model. We have only one button left, the "PICK" button used for selecting the cell origin. What do we want it to do? The button should be used to allow the user to select a point in Microstation instead of entering the X, Y, and Z values by hand. To make the program work even better, if the user has already selected the level and cell, we will insert the cell at the selected point automatically. This keeps the user from needing to click the "Insert" button after clicking the "PICK" button.
I User Interface Exercises I
173
In short, we want the user to click inside Microstation. This could present a problem because, by default, VBA forms are modal. That is, the form is active and thus prevents us from interacting with Microstation until the form is unloaded. To get around this potential problem, display the form as modeless. Sub DoCellInsertionO frmCellInsertion.Show vbModeless End Sub
Place “DoCellInsertion” in a code module where it will be used to display our form. Now for the “PICK” CommandButton, we want the user to pick a point. If the Level and Cell ComboBoxes are not empty, insert the selected cell on the selected level at the selected point. Private Sub cmdPick-Click0 Dim MyMsg As CadInputMessage Dim MyClue As CadInputQueue Dim SelPt As Point3d Dim CellElem As CellElement On Error GoTo errhnd Set MyClue = Application.CadInputQueue Do Set MyMsg = MyQue.GetInput Select Case MyMsg.InputType Case m s d C a d I n p u t T y p e D a t a P o i n t SelPt = MyMsg.Point txtX.Text = Se1Pt.X txtY.Text = Se1Pt.Y txtZ.Text = Se1Pt.Z If cmbLevels.Text <> And cmbCells.Text <> Set Cell El em = CreateCellElement3(cmbCells.Text, SelPt, True) Cell El em. Level = ActiveDesignFi 1 e. Level s(cmbLeve1 s .Text) ActiveModel Reference.AddElement Cell Elem End If Exit Do Case Else Exit Do ” ”
” ”
~
-
Then
174
I Chapter 10: Visual Interface I End S e l e c t Loop E x i t Sub errhnd: Err . C l e a r
End Sub
Let’s look through the code slowly. First, we declare some variables. That’s the easy one. Second, we begin listening to the Input Queue. If the user picks a point, we do the following: Place the selected X, Y, and Z point elements into the three text boxes. If both the Levels ComboBox and Cells ComboBox are not empty, insert the selected cell at the selected point and then change its Level property to reflect the selected level. If any other Input occurs or an error occurs, we exit the procedure. If there is any concern about typing in all of the code shown for this project, the VBA Project named ChapterlO.mvba can be found on the CD included with this book.
POINT LISTREADER This program concentrates on the ListBox control. Use the AddItem method to add items to the ListBox. Then use the List property to place values in the other columns of the ListBox. Use “Remove” to allow the user to manually remove items from the ListBox. We read a text file to get the points into the ListBox of our interface. The text file looks like this:
I Point List Reader I
175
Each line in the text file gives us the X, Y, Z elements of the text insertion as well as the label we want placed at the X, Y, Z point.
Here is the interface.
We are using two labels, one TextBox named txtPointFile, a ListBox named IstPoints, and four CommandButtons named btnRead, btnRemove, btnPlotPoints, and btnCancd. ListBoxes, by default, use only one column. We want four columns, so set the ‘ColumnCount’property to 4.Specify the width of each column in the ColumnWidths property using the value “60 pC60 pC60 pt;60 pt’: Set the last property, “MultiSelect”,to “2 - fmMultiSelectExtended’:This allows the user to select multiple items in the list by using the
176
I Chapter 10: Visual Interface I 1stPoints.AddItem PointSplit(0) 1 stPoints. List(1 stPoints. ListCount
lstPoints.List(1stPoints.ListCount lstPoints.List(1stPoints.ListCount
~
~
1 , 1) 1, 2) 1 , 3)
= = =
PointSpl i t(l) PointSplit(2) PointSplit(3)
End If Wend End Sub
When the user clicks the Read button, we open the file specified in the TextBox “txtPointFile” for input (this means we are going to read the file). Since we have not reached the End Of File, we read the next line from the file, split it into its elements, and add the elements to the ListBox. Notice how we use “AddItem” to add the X component of the point. AddItem is only used to add items to the first column of the ListBox. Each additional column’s value is set by using the List property. When using ‘List: specify the line index and the column, then give it the value you want to put into the column. The Remove button is meant to remove any items selected in the ListBox. Since multiple items can be selected at once, be careful as you remove the items. Private Sub btnRemove-Cl ick( Dim I As Long For I = 1stPoints.ListCount T o 1 Step - 1 If lstPoints.Selected(1 - 1) Then 1stPoints.RemoveItem I - 1 End If Next I End Sub
By beginning at the last item in the list and working to the first, you avoid potential problems as you remove selected items. Private Dim Dim Dim Dim Dim For
Sub btnPlotPoints-Click0 TextIns As Point3d TextVal As String I As Long PT As TextElement RotMat As Matrix3d I = 1 T o 1stPoints.ListCount Text1ns.X = lstPoints.List(1 Text1ns.Y = lstPoints.List(1
-
1 , 0) 1 , 1)
I Write Out File I
177
Text1ns.Z = lstPoints.List(1 - 1 , 2 ) Set PT = Application.CreateTextElementl(Nothing, lstPoints.List(1 - 1 , 3 ) , TextIns, RotMat) ActiveModel Reference.AddElement PT Next I End Sub
The ‘btnPlotPoints’ button looks at each item in the list and from it we get the X, Y, and Z elements of the text origin as well as the text to display. When the user clicks the ‘Cancel’button, execute the following code: Private Sub btnCance1-Click0 Unload frmPointList End Sub
That’s it for the buttons in the form. Now, how do we display the form in the first place? In a code module, we place the following code: Sub D o P o i n t L i s t R e a d e r O frmPoi ntLi st. Show End Sub
The macro 0o Po int L is t Re a d e r is now used to display the form and all of the great functionality we have just put in.
WRITE OUT FILE The next macro we are going to write utilizes CheckBoxes and OptionButtons. Here’s the visual interface:
178
I Chapter 10: Visual Interface I We have seven CheckBoxes named chklevels, chklinestyles, chkTextStyles, chkViews, chkAuthor, chkSubject, and chkTitle. We also have two OptionButtons named optASCII and optHTML. To round things out, we have two CommandButtons named cmdOK and cmdCancel.
We want to allow the user to select any of the "Items To Write". CheckBoxes are perfect for this. As for the file format, only one selection should be made. This is why we use OptionButtons.
Before we get into the code, it is important to understand that this program is not as simple as the previous ones. Let's plan before we jump in. We want to write to two file formats: ASCII and HTML. This meets our needs today but what about tomorrow? We should think about future uses as we develop applications to allow for scalability. We could place all of the code in the C1 ic k e v e n t of the cmdOK button, however, breaking the code into more manageable chunks makes it easier for us to add file formats tomorrow. Let's look at three procedures for writing the file sections: headers, lines, and footers. Sub P r i n t H e a d e r ( H e a d e r 1 n As S t r i n g , F i l e N u m As L o n g , O p t i o n a l C o l u m n s As L o n g ~
I f optASCII.Value
=
P r i n t #FileNum,
=
1)
T r u e Then "['I
E l s e I f optHTML.Value
& HeaderIn & =
"I"
T r u e Then
P r i n t BFileNum, " < t a b l e width=660>" P r i n t B F i 1 eNum, " < t r > < t d c o l s p a n = " & C o l u m n s & " a1 i g n = c e n t e r > < b > " & H e a d e r I n & " < / t d > < / t r > " End
f
End Sub
Use an I and E 1 s e I f statement to handle the two file formats for today. Another E l seI f statement is all it takes to add another file format tomorrow. Sub P r i n t L i n e ( L i n e 1 n As S t r i n g , F i l e N u m As L o n g ) I f optASCII.Value
=
P r i n t #FileNum,
T r u e Then LineIn
I Write Out File I
179
E l s e I f optHTML.Value
=
T r u e Then
D i m X S p l i t As V a r i a n t
D i m I As L o n g XSplit
Split(LineIn,
=
P r i n t #FileNum,
vbTab)
"" "
F o r I = L B o u n d ( X S p 1 i t ) To U B o u n d ( X S p 1 i t ) P r i n t #FileNum, vbTab & " < t d > " & X S p l i t ( 1 ) & " < / t d > " Next I P r i n t #FileNum,
"
End I f End Sub
Use the procedure Pri n t L i ne for each of the selected items found. Use another I f and E 1 s e I f statement for the file formats. Sub P r i n t F o o t e r ( F i 1 e N u m As L o n g ) I f optHTML.Value
=
T r u e Then
P r i n t BFileNum, " < / t a b l e > "
& vbCrLf
End I f End Sub
We only need to print a footer if the HTML option is selected. Now it is time to look at the C1 ic k E v e n t of the cmdOK button. There are two sections in the C1 ic k Event. The first sets up the export. The second section is a series of If ... Then statements, each directly related to a CheckBox. Here it is: P r i v a t e Sub c m d O K L C l i c k 0 D i m M y F i l e As S t r i n g D i m F F i l e As L o n g D i m m y L e v e l As L e v e l
D i m m y L S t y l e As L i n e S t y l e D i m m y T S t y l e As T e x t S t y l e D i m m y V i e w As V i e w FFile
=
FreeFile
I f optASCII.Value MyFile
=
=
T r u e Then
"c:\output.txt"
E l s e I f optHTML.Value MyFile
=
=
T r u e Then
"c:\output.htm"
End I f Open M y F i l e F o r O u t p u t As # F F i l e P r i n t H e a d e r " F I L E NAME",
FFile, 1
I Chapter 10: Visual Interface I P r i n t L i n e A c t i v e D e s i g n F i 1 e . F u l l Name, F F i 1 e PrintFooter FFile
If chkLevels.Value
=
T r u e Then
P r i n t H e a d e r "LEVELS",
FFile, 3
F o r Each m y L e v e l I n A c t i v e D e s i g n F i l e . L e v e l s P r i n t L i n e myLevel.Name & v b T a b &
-
myLevel . D e s c r i p t i o n & vbTab &
-
myLevel .E l ementCol o r , F F i l e Next PrintFooter FFile End I f
If chkLineStyles.Value
=
T r u e Then
P r i n t H e a d e r " L I N E STYLES",
FFile, 2
ActiveDesignFile.LineStyles
F o r Each m y L S t y l e I n
P r i n t L i n e myLStyle.Name & vbTab & myLStyle.Number.
~
FFile
Next PrintFooter FFile End I f
If chkTextStyles.Value
=
T r u e Then
P r i n t H e a d e r "TEXT STYLES", F o r Each m y T S t y l e I n
FFile, 3
ActiveDesignFile.TextSty1es
P r i n t L i n e myTStyle.Name & vbTab &
-
m y T S t y l e . C o l o r & vbTab &
~
myTStyle.BackgroundFillColor, F F i l e Next PrintFooter FFile End I f
I f chkViews.Value
=
T r u e Then
PrintHeader "VIEWS",
FFile, 5
F o r Each myView I n A c t i v e D e s i g n F i 1 e . V i e w s P r i n t L i n e myView.0rigin.X
& vbTab &
myView.0rigin.Y
-
& vbTab &
m y V i e w . 0 r i g i n . Z & vbTab &
~
myView.CameraAng1e & v b T a b &
-
myview. CameraFocal L e n g t h , F F i l e Next
I Write Out File I
181
PrintFooter FFile End I f I f chkAuthor.Value
=
T r u e Then
P r i n t H e a d e r "AUTHOR",
FFile
PrintLine ActiveDesignFi le.Author,
F F i 1e
PrintFooter FFile End I f I f chkSubject.Value
=
T r u e Then
P r i n t H e a d e r "SUBJECT",
FFile
PrintLine ActiveDesignFi le.Subject,
F F i 1e
PrintFooter FFile End I f If chkTitle.Value
=
T r u e Then
PrintHeader "TITLE",
FFile
P r i n t L i n e A c t i v e D e s i g n F i l e . T i t l e , F F i 1e PrintFooter FFile End I f C1 o s e # F F i 1 e End Sub
We have saved the easiest event for last. P r i v a t e Sub c r n d C a n c e l L C l i c k 0 Unload frrnWriteDgnSettings End Sub
We add the following procedure to a Module to display the Form. Sub D o W r i t e O u t F i l e O frrnWriteDgnSettings.Show
End Sub
182
I Chapter 10: Visual Interface I ZOOM AND PAN Here is a little program that provides real-time interactive panning and zooming of the views of the ActiveDesignFile.
I use the MultiPage control here. This provides tabs and unique interfaces on each tab. I also use a few labels, a ComboBox, and three scroll bars with Min values of -500 and max values of 500. When the form is initialized, I populate the ComboBox with the View indexes. I also set an initial value for the Pan scroll bars. When you right-click on an existing tab in the MultiPage, you access controls to add tabs (select “New Page”), to rename, delete, or move the order of the pages. This is what the MultiPage looks like when a right-click is performed on an existing page.
Let’s start with the Initialize event of the User form. Private Dim Dim For Next
Sub UserForm-Initialize0 ViewCen As Point3d MyView As View Each MyView In ActiveDesignFile.Views cmbViews.AddItem MyView.Index
I Zoom And Pan I
183
cmbViews.ListIndex = 0 ViewCen = ActiveDesignFile.Views(l).Center scrX.Value = ViewCen.X scrY.Value = ViewCen.Y End Sub
Here is the Initialize event of the UserForm. We add each View’s Index to the ComboBox named cmbviews. Select the first element by assigning the ListIndex value to 0. The last step is to get the current center of view 1 and apply the X and Y values to the scroll bars srcX and sr cY.
Scroll bars have two events with which we will be working. The first, Change event, is triggered each time the value of the scroll bar changes except for when the Thumb is being scrolled. The scroll event is triggered as the Thumb is dragged between the min value and max value. We are going to create two procedures for performing the zoom and pan operations: Sub SetZoom(ZoomVa1 ue As Long, OldZoomVal ue As Long) ActiveDesignFile.Views(cmbViews.Text).Zoom 1 + (ZoomValue - OldZoomValue) / 100 ActiveDesignFile.Views(cmbViews.Text).Redraw End Sub Sub SetPan(XPan As Long, YPan As Long) Dim Vieworigin A s Point3d View0rigin.X = XPan View0rigin.Y = YPan View0rigin.Z = 0 ActiveDesignFile.Views(cmbViews.Text).Center ActiveDesignFile.Views(cmbViews.Text).Redraw End Sub
=
Vieworigin
When we use the zoom method of a view, providing a number greater than 1 zooms in. A number less than 1 zooms out. The Zoom Method zooms relatively. If we provide a zoom factor of 1.1 three times, the view zooms in each time. Subtract the previous value from the current value and divide the result by 100. Add that value to the number 1. This allows us to zoom in and out as we move the scroll bar left and right. After performing the zoom, issue a Redraw to see the result of the zoom.
184
I Chapter 10: Visual Interface I Panning is performed by adjusting the view’s center. You can see the code that is used to zoom in and out. Now let’s look at the events that call these procedures. P r i v a t e Sub s c r Z o o m - C h a n g e 0 SetZoom scrZoom.Value, scrZoom.Tag
=
scrZoom.Tag
scrZoom.Value
End Sub P r i v a t e Sub s c r Z o o m - S c r o l l 0 SetZoom scrZoom.Value, scrZoom.Tag
=
scrZoom.Tag
scrZoom.Value
End Sub
The Change and Scroll events for the scroll bar named scrZoom is shown above. The code inside these events is the same. The Tag property (as discussed previously) is there for whatever use we have for it. Here is one way: use the tag to store the previous value. After we call SetZoom, we set the tag value. Now, let’s talk about panning. We are using two scroll bars to set the X and Y elements of the view’s center. P r i v a t e Sub s c r X L C h a n g e 0 SetPan s c r X . V a l u e ,
scrY.Value
End Sub P r i v a t e Sub s c r X L S c r o l 1 ( 1 SetPan s c r X . V a l u e ,
scrY.Value
End Sub P r i v a t e Sub s c r Y - C h a n g e 0 SetPan s c r X . V a l u e ,
scrY.Value
End Sub P r i v a t e Sub s c r Y - S c r o l l SetPan s c r X . V a l u e , End Sub
(1 scrY.Value
I Review I
185
REVIEW We will use user interfaces in a number of areas in the remainder of this book as we learn more about Microstation VBA. Keep the following points in mind: All controls have properties, methods, and events. Address a control's properties and methods by the control name, typing a period, typing the property or method, and then providing parameters when required. At run-time, events are triggered as the user interacts with your interface. Display user forms using the Show method. Use the Initialize event to set values and populate controls prior to the form being displayed.
11
The Microstation Object Model - Objects Objects are the basis for much of our VBA programming. Object Models are hierarchal structures of objects. Rather than examine in this chapter all of the objects, we will look at the tools available to work with the Microstation Object Model. After we look at the tools, we will look at some of the Objects frequently used when working with Microstation VBA. In this Chapter:
The Object Browser Auto List Members
i l The Microstation VBA Help File El Adding Watches
The Microstation Object Model
187
I Chapter 11:The Microstation Object Model - Objects I
188
THEOBJECT BROWSER One of the best tools to work with Object Models is the Object Browser. Object Browser
Click on the Object Browser toolbar button to display the Object Browser. The Object Browser can also be displayed by using the VBA menu View > Object Browser or by pressing the
eglobals> AccuDrawHints ACSManaaet ApplicationElement ApplicationObjectConnector ArcElement AreaPattern Attachment Attachments AuxiliaryCoordinateSystemElement B s p Ii n e BsplineCuns BsplineCunsElement Bspline Surface BsplineSurfaceElement CadlnputMessage CadlnputQueue
ACSManager ActiveDesignFile ActiveModelReference ActiveSettings ActiveWorkspace AddAttachmentEventsHandler AddChangeTrackEventsHandler AddLevelChangeEventsHandler AddModalDialogEventsHandler AddModelActivateEventsHandler AddModelChatigeEventsHandler AddSaveAsEventsHandler AddViewUpdateEventsHandler AppendXDatum ApplyHorizontalScalingFixForEMF ApplplerticalScalingFixForEMF AssembleComplexStringsAndShapes Atn2
The Object Browser has two combo boxes at the top. The top-most combo box allows us to narrow the classes to a specific Library. In the image above, the MicroStationDGN Library has been selected. The only classes now shown belong to the MicroStationDGN Library. When we select ?Application” in the Classes ListBox, the “Members of ‘Application”’ show up in the Members ListBox. The Members ListBox displays the Properties, Methods, and Events of the selected Class.
Name OnDesignFileClosed 0nDe s ig nF i Ie 0pene d 0penDe s ig nF iIe 0penDe s ignF iIe F orP ro gra m Path Pi
I The Object Browser I
189
Three primary member types are shown in the Members ListBox. First are Properties. “Name” and “Path” are properties of the Application Object. Methods “OpenDesignFile”, “OpenDesignFileForProgram” and “Pi” belong to the Application Object. Events “OnDesignFileClosed and “OnDesignFileOpened” also belong to the Application Object.
When we select a member in the list, we are shown the Declaration for the selected member at the bottom of the Object Browser. E
The Declaration shows us the Parameters for the Function or Procedure as well as the return value type of Functions. In addition to clicking on the Classes and Members we are familiar with, we can search Object Models using the Object Browser.
Notice the cursor over the Hide/Show Search Results button in the Object Browser. A search for “text” in the MicroStationDGN Trpe Library results in numerous results. So, if we do not know the specific Class or Member we need, we can use the Object Browser to search for it.
190
I Chapter 11:The Microstation Object Model - Objects I
AUTO LIST MEMBERS VBA gives us help as we write our code at design time. Dim M y A p p A s A p p l i c a t i o n Set M y A p p = A p p l i c a t i o n M s g B o x myapp. End S u b
The “List Members” list displays as we work in VBA. Once the list displays, we can use the arrow keys and page up/down keys to scroll through the list. If we select “ActiveDesignFile”at this time and press the period key, we see the following: Dim MyApp As Application Set MyApp = Application MsgBox myapp.ActiveDesignFi1e. End S u b
The ‘Xuto List Members” list allows us to ‘drill down’ through an Object Model.
MICROSTATION VBA HELPFILE If we see something in the Object Browser and would like to see more detail on it, we can select it in the Object Browser and press the
I Microstation VBA Help File I
191
Object, Property, Method, or Event that was selected in the Object Browser.
Once in the Microstation VBA Help File, we can click on the Index tab and type ‘Xpplication Structure” in the ‘Search‘ box. Selecting ‘Xpplication Structure” from the Index list displays the Microstation Application Object structure. Select “Application Object” from the list to display a description of the object with hyperlinks to Properties, Methods, Events, Example Code, and See Also which displays a list of associated objects.
192
I Chapter 11 :The Microstation Object Model - Objects I
ADDINGWATCHES We have introduced adding Watches previously. Adding a watch to a variable is an excellent way to see its Properties. Some of the Properties are actually other objects that we can continue to traverse by expanding the item in the tree. Others in the list are Collections of Objects that we can examine in the Watch window.
ACSManager ActiveDesignFile ActiveModelReference ActiveSetLings Activeworkspace AltachedCellLibrary Bspline CadlnputQueue Caption CommandState CurrerdGraphicGreup Cursorhiormatnn ExecutingVBProject FullName HasActiveDesignFile HasAdiveModelReference Height IsAcademicVersian IsCellLibraryAttached IsRegidered IsSerialized KeyinArguments LeitPosnion MdlLib Messagecenter Name Path ProcesslD RasterManager StandardsCheckerCordroller TapPosition UserName VBE Version Visible Wdth
"chapter11.dgn (20 - V8 DON) - Microslation V8 XM Edition" 1
ACSManagerlACSManageI DesignFileDesignFile ModelReferenceYodelRefi Settingslsetlings WorkspacelWorkspace CellLibrary BsplineiBspline CadlnputQueuelCadlnputOl String CommandStatelCommandS Long
cwsormiormatiOnlCursorlr "C:Wogram Files~entleyVulicroStatien~~~ion exe" True True 1208 False False True True -4
'"ustation" '"C:.DrogramFiles~entleyVulicroStation" 3160
-4 '"Administrator" "Version 08.09.00.92Wlndows x 8 6 True 1608
ObjedNBProjed String Boolean Boolean Long Boolean Boolean Boolean Boolean String Long MdlLibraryYdlLibrary MessageCerderYessageC String String Long RasterManagerIRasterMan StandardsCheckerCordrollt Long String ObjectNBE String Boolean Long
THEMICROSTATION OBJECT MODEL Let's begin looking at the Microstation Object Model by examining the Application Object.
I The Microstation Object Model I
193
Application Object The Application Object points to the Microstation Application.
Accessors Sub TestAppl i c a t i o n A o
D i m M y A p p A s New A p p l i c a t i o n M s g B o x MyApp.Path End S u b
Sub TestAppl i c a t i o n B 0
Dim MyApp As Application Set MyApp = Application M s g B o x MyApp.Path End S u b
Both examples shown here result in the variable MyApp pointing to the Microstation Application Object. Once a variable is pointing to the Application, we can use that variable to manipulate the Application Object through its Properties and Methods. The Application Object is always available through the exposed Object named “Application”.This means when we are in VBA, we can use the Object named Application at any time. In addition to accessing the Application’s properties and methods, additional objects and collections under the Application object can be accessed by traversing the object model. Do this by typing “Application’: the period key, and then the next level of the Object Model. Sub TestAppl i c a t i o n C 0
M s g B o x Application.Path End S u b
In this example, we have not declared any variables or set any variables. We just use the Object named ?Application”because it is always exposed to us. A comprehensive list of objects in the Microstation Object Model is available on the CD that accompanies this book. It is not feasible to give the entire Object Model here in print but you will get an understanding as to how large the Object Model is. Let’s take a look at a selection of the Properties and Methods of a few of the Objects we deal with on a regular
194
I Chapter 11:The Microstation Object Model - Objects I basis in Microstation. Some read-only properties are marked with “{read-only}”.
Application Property ACSManager As ACSManager {read-only} Property ActiveDesi gnFi 1 e As Desi gnFi 1 e {readonly1 Property ActiveModelReference As ModelReference {read-only} Property Activesettings As Settings {readonly1 Property Activeworkspace As Workspace {readonly1 S u b AddAttachmentEventsHandler(EventHand1er As IAttachmentEvents) S u b AddChangeTrackEventsHandler(EventHand1er As IChangeTrackEvents) S u b AddLevelChangeEventsHandler(EventHand1er As ILevelChangeEvents) S u b AddModalDialogEventsHandler(EventHand1er As IModal Dial ogEvents) S u b AddModelActivateEventsHandler(EventHand1er As IModelActivateEvents) S u b AddModelChangeEventsHandler(EventHand1er As IModelChangeEvents) S u b AddSaveAsEventsHandler(EventsHand1er As ISaveAsEvents) S u b AddViewUpdateEventsHandler(EventHand1er As IViewUpdateEvents) S u b AppendXDatum(XData0 As XDatum, Type As MsdXDatumType, Value As Variant) Function ApplyHorizontal Scal i n g F i x F o r E M F ( P i x e 1 C o o r d i n a t e As Double) As Long Function Applyvertical Scal i n g F i x F o r E M F ( P i x e 1 C o o r d i n a t e As Double) As Long Function Assembl eCompl exStri ngsAndShapes (Chai nab1 eEl eme n t s 0 As ChainableElement, CGapTolerance As Double = -11 As El ementEnumerator
I The Microstation Object Model I
195
F u n c t i o n A t n 2 ( Y As D o u b l e , X As D o u b l e ) As Double Sub A t t a c h c e l l L i b r a r y ( C e l 1 L i b r a r y N a m e As S t r i n g , CConvertFromV7 As MsdConversionMode
=
msdConversionModeAlwaysl) P r o p e r t y A t t a c h e d C e l 1 L i b r a r y As C e l l L i b r a r y { read-on1y 1 P r o p e r t y B s p l i n e As B s p l i n e { r e a d - o n l y } F u n c t i o n B y C e l l C o l o r O As Long F u n c t i on B y C e l l L i n e S t y l e ( ) As L i n e S t y l e F u n c t i on B y C e l l L i neWei g h t ( As Long F u n c t i o n B y L e v e l C o l o r O As Long F u n c t i on By L e v e l L i n e S t y l e ( As L i n e S t y l e F u n c t i o n B y L e v e l L i n e w e i g h t ( ) As Long P r o p e r t y C a d I n p u t Q u e u e As C a d I n p u t Q u e u e { read-on1y 1 P r o p e r t y C a p t i o n As S t r i n g P r o p e r t y Commandstate As Commandstate { r e a d only} Function
ConstructCirclesTangentToThreeElements(E1ement 1 As E l e m e n t , E l e m e n t 2 As E l e m e n t , E l e m e n t 3 As E l e m e n t , Temp1 a t e As E l e m e n t , [ O u t p u t T y p e As
MsdTangentElementOutputType
=
msdTangentCi r c l e s l , CSamplesCount As Long = 1 0 1 ) As E l ementEnumerator Sub Copy D e s i g n F i 1 e ( E x i s t i n g D e s i g n F i 1 eName As S t r i n g , NewDesignFileName As S t r i n g , COverwri t e As Boo1 e a n l ) Function CreateApplicationElement(Application1D As Long, A p p l i c a t i o n D a t a As D a t a B l o c k ) As A p p l ic a t i o n E l ement F u n c t i o n C r e a t e A r c E l e m e n t l ( T e m p 1 a t e As E l e m e n t , S t a r t P o i n t As P o i n t 3 d , C e n t e r p o i n t As P o i n t 3 d , E n d p o i n t As P o i n t 3 d ) As A r c E l e m e n t F u n c t i o n C r e a t e A r c E l ement2(Templ a t e As E l e m e n t , C e n t e r P o i n t As P o i n t 3 d , P r i m a r y R a d i u s As Doubl e , S e c o n d a r y R a d i us As Doubl e , R o t a t i on
196
I Chapter 11 :The Microstation Object Model - Objects I As Matri x3d, StartAngl e As Doubl e, SweepAngl e As Double) As ArcElement Function CreateArcElement3(Template As Element, StartPoint As Point3d, PointOnCurve As Point3d, Endpoint As Point3d) As ArcElement Function CreateArcElement4(Template As Element, StartTangent As Ray3d, Endpoint As Point3d) As ArcElement Function CreateArcElement5(Template As Element, Chord As Segment3d, ArcLength As Doubl e , P1 anePoi nt As Poi nt3d) As ArcEl ement Function CreateAreaPattern(R0wSpacing As Double, ColSpacing As Double, Angle As Double, CellName As String, Scale As Double) As AreaPattern Function CreateBsplineCurveElementl(Temp1ate As Element, Curve As BsplineCurve) As Bspl i neCurveEl ement Function CreateBspl i neCurveEl ementZ(Temp1 ate As El ement, Curve As Interpol ati oncurve) As Bspl i neCurveEl ement Function CreateBsplineSurfaceElementl(Temp1ate As Element, Surface As BsplineSurface) As Bspl ineSurfaceE1 ement Function CreateCellElementl(Name As String, El ements ( ) As -El ement , Ori gi n As Poi nt3d, [ IsPointCell As Bool eanl) As Cell Element Function Createcell El ementZ(Cel1 Name As String, Origin As Point3d, Scale As Point3d, Truescale As Boolean, Rotation As Matrix3d) As Cell El ement Function CreateCe 1 El ement3(Cell Name As String, Origin As Poi nt3d, TrueScal e As Bool ean) As Cell E ement Function CreateCompl exShapeEl ementl (Chai nabl eEl ements( ) As Chai nabl eEl ement, [Fi 1 1 Mode As MsdFi 1 1 Mode = msdFi 1 1 ModeUseActi vel) As Compl exShapeEl ement tN Function CreateCompl exShapeEl ementZ(Chai nabl eEl ements( ) As Chai nabl eEl ement, [Fi 1 1 Mode As MsdFi 1 1 Mode =
I The Microstation Object Model I
197
msdFi 1 l M o d e U s e A c t i v e l , [GapTol e r a n c e As D o u b l e = -11) As Compl exShapeEl ement B Function CreateCompl e x S t r i n g E l e m e n t l ( C h a i n a b l e E l ements ( ) As Chai n a b l e E l e m e n t ) A s Compl e x S t r i n g E l ement Function CreateCompl e x S t r i n g E l e m e n t Z ( C h a i n a b 1 e E l e m e n t s ( ) As Chai n a b l e E l e m e n t , [GapTol e r a n c e As Doubl e = -11) As C o m p l e x S t r i n g E l e m e n t B F u n c t i o n CreateConeEl ementl(Temp1a t e As E l e m e n t , BaseRadi us As Doubl e , B a s e C e n t e r P o i n t As P o i n t 3 d , TopRadi us As Doubl e , T o p C e n t e r P o i n t As P o i n t 3 d , R o t a t i o n A s M a t r i x 3 d ) A s ConeEl ement F u n c t i o n C r e a t e C o n e E l ementZ(Temp1 a t e As E l ement , Radi us As Doubl e , B a s e C e n t e r P o i n t As P o i n t 3 d , T o p C e n t e r P o i n t As P o i n t 3 d ) As ConeEl ement B F u n c t i o n CreateCrossHatchPattern(Space1 A s D o u b l e , Space2 As D o u b l e , A n g l e 1 As D o u b l e , A n g l e2 As D o u b l e ) A s C r o s s H a t c h P a t t e r n F u n c t i o n C r e a t e C u r v e E l e m e n t l ( T e m p 1 a t e As E l e m e n t , P o i n t s 0 As P o i n t 3 d ) As C u r v e E l e m e n t B F u n c t i o n C r e a t e D a t a b a s e L i n k ( M s 1 i n k A s Long, E n t i t y A s Long, L i n k T y p e As M s d D a t a b a s e L i n k a g e , I s I n f o r m a t i o n As Boolean, D i s p l a y a b l e A t t r i buteType As Long) As DatabaseLink F u n c t i o n CreateDesignFile(SeedFi1eName As S t r i n g , NewDesignFileName As S t r i n g , Open As Boo1 e a n ) As D e s i g n F i 1 e B F u n c t i o n CreateDimensionEl ementl(Temp1a t e As Element, R o t a t i o n As M a t r i x 3 d , Type As MsdDimType, C T e x t O r i e n t a t i o n V i e w A s V i e w ] ) A s D i m e n s i o n E l ement F u n c t i o n CreateEllipseElementl~Template As E l e m e n t , P e r i m e t e r P o i n t l As P o i n t 3 d , P e r i m e t e r p o i n t 2 As P o i n t 3 d , P e r i m e t e r P o i n t 3 As P o i n t 3 d , C F i 11 Mode As MsdFi 11 Mode = m s d F i l l M o d e U s e A c t i v e I ) As E l l i p s e E l e m e n t B F u n c t i o n CreateEllipseElement2(Template A s Element, O r i g i n As Point3d, PrimaryRadius As
198
I Chapter 11 :The Microstation Object Model - Objects I Doubl e , S e c o n d a r y R a d i us As Doubl e , R o t a t i on As M a t r i x 3 d , [ F i l l M o d e As M s d F i l l M o d e = msdFi 11 ModeUseActi v e l ) As E l 1 ip s e E l ement F u n c t i o n C r e a t e E l 1 ip t i c a l E l e m e n t l (Templ a t e As E l e m e n t , E l 1 ip s e As E l 1 ip s e 3 d , [ F i 11 Mode As M s d F i l l M o d e = m s d F i l l M o d e U s e A c t i v e 1 ) As Element F u n c t i o n CreateHatchPatternl(Space As D o u b l e , A n g l e As D o u b l e ) As H a t c h P a t t e r n F u n c t i o n CreateLineElementl(Temp1ate As E l e m e n t , V e r t i c e s ( ) As P o i n t 3 d ) As L i n e E l ement F u n c t i o n CreateLineElementZ(Temp1ate As E l e m e n t , S t a r t P o i n t As P o i n t 3 d , EndPoi n t As P o i n t 3 d As L i n e E l ement F u n c t i o n CreateObjectInMicroStation(Prog1D As S t r i n g ) A s Unknown F u n c t i o n C r e a t e P o i n t S t r i n g E l e m e n t l (Templ a t e As E l e m e n t , V e r t i c e s 0 As P o i n t 3 d , D i s j o i n t As B o o l e a n ) As P o i n t S t r i n g E l ement F u n c t i o n C r e a t e S a v e d V i ewEl ement ( V i ewSpeci f i e r A s V a r i a n t , Name As S t r i n g , [ D e s c r i p t i o n As S t r i n g 1 ) A s SavedVi ewEl ement F u n c t i o n CreateShapeElementl(Temp1ate As E l e m e n t , V e r t i c e s ( ) As P o i n t 3 d , [ F i 11 Mode As M s d F i l l M o d e = m s d F i l l M o d e U s e A c t i v e 1 ) As ShapeEl ement F u n c t i o n CreateSharedCellElementl(Name As S t r i n g , E l e m e n t s 0 As - E l e m e n t , O r i g i n As P o i n t 3 d , [ I s P o i n t C e l l As B o o l e a n ] ) As SharedCell Element F u n c t i o n CreateSharedCellElementZ~CellNameAs S t r i n g , O r i g i n As P o i n t 3 d , S c a l e As P o i n t 3 d , T r u e s c a l e As B o o l e a n , R o t a t i o n As M a t r i x 3 d ) As SharedCell Element F u n c t i o n CreateSharedCellElement3(CellName As S t r i n g , O r i g i n As P o i n t 3 d , T r u e S c a l e As Bool ean) As SharedCell Element F u n c t i o n CreateTextElementl(Temp1ate As E l e m e n t , T e x t As S t r i n g , O r i g i n As P o i n t 3 d , R o t a t i o n As M a t r i x 3 d ) As T e x t E l e m e n t
I The Microstation Object Model I
199
il F u n c t i o n CreateTextNodeElementl(Temp1ate As
il
il
il il
il il il il il il
il il
il il
il il il
E l e m e n t , O r i g i n As P o i n t 3 d , R o t a t i o n As M a t r i x 3 d ) As T e x t N o d e E l ement F u n c t i o n CreateTextNodeElementZ(Temp1ate As E l e m e n t , O r i g i n As P o i n t 3 d , R o t a t i o n As M a t r i x 3 d , [ I n c r e m e n t N o d e N u m b e r As B o o l e a n = T r u e ] , [ R e s e r v e d As Unknown]) As T e x t N o d e E l ement P r o p e r t y C u r r e n t G r a p h i c G r o u p As Long { r e a d only} P r o p e r t y C u r s o r I n f o r m a t i o n As CursorInformation {read-only} Function D a t a E n t r y R e g i o n F r o m C r i t e r i a ( S t a r t P o s i t i on As Long, L e n g t h As Long, J u s t i f i c a t i o n As MsdData E n t r y Regi on J u s t if ic a t ion As D a t a E n t r y Regi on F u n c t i o n D e g r e e s ( R a d i a n s As D o u b l e ) As D o u b l e Sub D e l e t e X D a t u m ( X D a t a 0 As XDatum, I n d e x As Long) Sub D e t a c h c e l l L i b r a r y ( ) F u n c t i o n DLongAbs(Va1ue As DLong) As DLong F u n c t i o n DLongAdd(Term1 As DLong, Term2 As DLong) As DLong F u n c t i o n DLongComp(Value1 As DLong, V a l u e 2 As DLong) As Long F u n c t i o n D L o n g D i v i d e ( N u m e r a t 0 r As DLong, D e n o m i n a t o r As DLong) As DLong F u n c t i o n DLongFromDoubl e ( V a 1 ue As D o u b l e ) As DLong F u n c t i o n DLongFromHexString(Va1ue As S t r i n g ) As DLong F u n c t i o n D L o n g F r o m I n t 6 4 ( V a l ue As E m p t y ) As DLong F u n c t i o n DLongFromLong(Va1ue As L o n g ) As DLong F u n c t i o n D L o n g F r o m S t r i n g ( V a 1 u e As S t r i n g ) As DLong F u n c t i o n DLongMod(Numerator As DLong, D e n o m i n a t o r As DLong) As DLong
200
I Chapter 11 :The Microstation Object Model - Objects I l l
Function DLongMultiply(Factor1 As DLong, Factor2 As DLong) As DLong Function DLongNegate(Va1ue As DLong) As DLong Function DLongSubtract(Minuend As DLong, Subtrahend As DLong) As DLong Function DLongToHexString(Va1ue As DLong) As String S u b DLongToInt64(Value As DLong) Function DLongToLong(Va1ue As DLong) As Long Function DLongToString(Va1ue As DLong) As String Function El 1 ipse3dFromEll i pti cal El ement (El ement As El 1 i pti cal El ement) As El 1 i pse3d Property ExecutingVBProject As Object {readonly1 Property Full Name As String {read-only} Function GetCellInformationEnumerator(IncludeSharedCel1 s As Boolean, IncludeFullPath As Boolean) As Cell InformationEnumerator Function GetCExpressionValue(CExpression As String, [MdlApplicationName As String]) As Variant Function GetCExpressionValueAsDLong(CExpression As String, [MdlApplicationName As String]) As DLong Function GetFloodBoundary(CandidateElements0 A s -Element, Template As Element, SeedPoint A s Point3d, [Viewspecifier As Variant], [FindHoles As Boolean = True], [Tolerance As Double = - 1 1 , [FillMode As MsdFillMode = msdFi 1 1 ModeUseActi vel) As El ement Function GetRegionDifference(RegionSolid0 As -El ement, Regi onHol es( ) As -El ement, Templ ate As Element, [FillMode As MsdFillMode = msdFi 1 1 ModeUseActi vel) As El ementEnumerator Function GetRegionIntersection(Regionl0 As -El ement, Regi on2( ) As -El ement, Templ ate As
I The Microstation Object Model I
il
il
il il il il
il il il il il il
il il
il il
il il
il
201
E l ement, [ F i 1 1 Mode As MsdFi 1 1 Mode = m s d F i l l M o d e U s e A c t i v e I ) As E l e m e n t E n u m e r a t o r F u n c t i o n G e t R e g i o n U n i o n ( R e g i o n l ( ) As -Element, Regi o n 2 ( ) As - E l ement, Temp1 a t e As E l ement, [ F i l l M o d e As M s d F i l l M o d e = m s d F i l l M o d e U s e A c t i v e I ) As E l e m e n t E n u m e r a t o r P r o p e r t y H a s A c t i v e D e s i g n F i l e As B o o l e a n { r e a d - o n 1y 1 P r o p e r t y H a s A c t i v e M o d e l R e f e r e n c e As B o o l e a n { r e a d - o n 1y } P r o p e r t y H e i g h t As Long Sub I n s e r t X D a t u m ( X D a t a 0 As XDatum, I n d e x As Long, Type As MsdXDatumType, V a l u e As V a r i a n t ) P r o p e r t y I s A c a d e m i c V e r s i o n As B o o l e a n { r e a d only} P r o p e r t y I s C e l l L i b r a r y A t t a c h e d As Boo1 ean { r e a d - o n 1y } P r o p e r t y I s R e g i s t e r e d As B o o l e a n { r e a d - o n l y } P r o p e r t y I s s e r i a l i z e d As B o o l e a n { r e a d - o n l y } P r o p e r t y K e y i n A r g u m e n t s As S t r i n g P r o p e r t y L e f t P o s i t i o n As Long F u n c t i o n M a t r i x 3 d A d d Z S c a l e d ( M a t r i x O As M a t r i x 3 d , M a t r i x 1 As M a t r i x 3 d , S c a l e 1 As D o u b l e , M a t r i x 2 As M a t r i x 3 d , S c a l e 2 As D o u b l e ) As M a t r i x 3 d F u n c t i o n M a t r i x 3 d D e t e r m i n a n t ( M a t r i x As M a t r i x 3 d ) As D o u b l e F u n c t i o n M a t r i x 3 d E q u a l ( M a t r i x l As M a t r i x 3 d , M a t r i x 2 As M a t r i x 3 d ) As B o o l e a n F u n c t i o n M a t r i x 3 d E q u a l T o l e r a n c e ( M a t r i x 1 As M a t r i x 3 d , M a t r i x 2 As M a t r i x 3 d , T o l e r a n c e As D o u b l e ) As B o o l e a n Function Matrix3dFromAxisAndRotationAngle(Axis As Long, Radians As D o u b l e ) As M a t r i x 3 d Function Matrix3dFromDirectionAndScale(Vector As P o i n t 3 d , S c a l e As D o u b l e ) As M a t r i x 3 d Function Matrix3dFromMatrix3dTimesMatrix3d(A As M a t r i x 3 d , B As M a t r i x 3 d ) As M a t r i x 3 d Function
Matrix3dFromMatrix3dTimesMatrix3dTimesMatrix3d
202
I Chapter 11 :The Microstation Object Model - Objects I ( A As M a t r i x 3 d , B As M a t r i x 3 d , C As M a t r i x 3 d ) As M a t r i x 3 d F u n c t i o n Matrix3dFromPoint3dColumns(XVector As P o i n t 3 d , Y V e c t o r As P o i n t 3 d , Z V e c t o r As P o i n t 3 d ) As M a t r i x 3 d F u n c t i o n Matrix3dFromPoint3dRows(XVector As P o i n t 3 d , Y V e c t o r As P o i n t 3 d , Z V e c t o r As P o i n t 3 d ) As M a t r i x 3 d Function Matrix3dFromRotationBetweenVectors(VectorO As P o i n t 3 d , V e c t o r 1 As P o i n t 3 d ) As M a t r i x 3 d F u n c t i o n M a t r i x 3 d F r o m R o w V a l ues(XO0 As D o u b l e , X O 1 As D o u b l e , XO2 As D o u b l e , X10 As D o u b l e , X11 As D o u b l e , X12 As D o u b l e , X20 As D o u b l e , X21 As D o u b l e , X22 As D o u b l e ) As M a t r i x 3 d F u n c t i o n M a t r i x 3 d F r o m S c a l e ( S c a 1 e As D o u b l e ) As M a t r ix3d F u n c t i o n M a t r i x 3 d F r o m S c a l e F a c t o r s ( X s c a 1 e As D o u b l e , Y s c a l e As D o u b l e , Z s c a l e As D o u b l e ) As M a t r ix3d F u n c t i o n Matrix3dFromTransform3d(Transform As T r a n s f o r m 3 d ) As M a t r i x 3 d Function M a t r i x 3 d F r o m V e c t o r A n d R o t a t i onAngl e ( A x i s As P o i n t 3 d , R a d i a n s As D o u b l e ) As M a t r i x 3 d Function Matrix3dFromXYRotationSkewAndScal e ( X A x i s A n g 1 e As D o u b l e , Y A x i s S k e w A n g l e As D o u b l e , X s c a l e As D o u b l e , Y s c a l e As D o u b l e , Z s c a l e As D o u b l e ) As M a t r ix3d Function Matrix3dGetComponentByRowAndColumn(Matrix As M a t r i x 3 d , Row As Long, Col As L o n g ) As D o u b l e F u n c t i o n Matrix3dHasInverse(Matrix As M a t r i x 3 d ) As B o o l e a n F u n c t i o n M a t r i x 3 d I d e n t i t y O As M a t r i x 3 d F u n c t i o n M a t r i x 3 d I n v e r s e ( F o r w a r d As M a t r i x 3 d ) As M a t r i x 3 d F u n c t i o n Matrix3dIsIdentity(Matrix As M a t r i x 3 d ) As B o o l e a n
I The Microstation Object Model I
203
I4 F u n c t i o n Matrix3dIsOrthogonal(Matrix As M a t r i x 3 d ) As B o o l e a n I4 F u n c t i on M a t r i x3d Is R i g i d ( M a t r i x As M a t r i x 3 d ) As B o o l e a n I4 Fun c t ion M a t r ix3d Is Rot a t e S c a 1 e Ro t a t e ( M a t r ix As M a t r i x 3 d , R o t a t i o n 1 As M a t r i x 3 d , S c a l e F a c t o r s As P o i n t 3 d , R o t a t i o n 2 As M a t r i x 3 d ) As B o o l e a n I4 F u n c t i o n Matrix3dIsSignedPermutation(Matrix As M a t r i x 3 d ) As B o o l e a n I4 F u n c t i o n M a t r i x 3 d I s X R o t a t i onY R o t a t i o n Z R o t a t i onScal e ( M a t r i x As M a t r i x 3 d , RadiansX As D o u b l e , RadiansY As D o u b l e , RadiansZ As D o u b l e , S c a l e As D o u b l e ) As B o o l e a n I4 F u n c t i on M a t r i x 3 d I s X Y R o t a t i o n ( M a t r i x As M a t r i x 3 d , X Y R o t a t i o n R a d i a n s As D o u b l e ) As Boo1 ean I4 F u n c t i o n M a t r i x3dIsXY R o t a t i onS kewAndScal e ( M a t r i x As M a t r i x 3 d , X A x i s A n g l e As D o u b l e , YAxisSkewAngle As D o u b l e , X s c a l e As D o u b l e , Y s c a l e As D o u b l e , Z s c a l e As D o u b l e ) As B o o l e a n I4 F u n c t i o n M a t r i x 3 d M a x A b s ( M a t r i x As M a t r i x 3 d ) As Double I4 F u n c t i o n M a t r i x 3 d M a x D i f f ( M a t r i x l As M a t r i x 3 d , M a t r i x 2 As M a t r i x 3 d ) As D o u b l e I4 F u n c t i o n M a t r i x 3 d R o t a t i o n F r o m C o l umnZ(Norma1 As P o i n t 3 d ) As M a t r i x 3 d I4 F u n c t i o n M a t r i x 3 d R o t a t i onFromPoi n t 3 d O r i g i nXY (Orig i n As P o i n t 3 d , X P o i n t As P o i n t 3 d , Y P o i n t As P o i n t 3 d ) As M a t r i x 3 d I4 F u n c t i o n Matrix3dRotationFromRowZ(Normal As P o i n t 3 d ) As M a t r i x 3 d I4 Sub Matrix3dSetComponentByRowAndColumn(Matrix As M a t r i x 3 d , RowIndex As Long, ColumnIndex As Long, V a l u e As D o u b l e ) I4 F u n c t i o n Matrix3dSumSquares(Matrix As M a t r i x 3 d ) As D o u b l e I4 F u n c t i o n Matrix3dTranspose(Matrix As M a t r i x 3 d ) As M a t r i x 3 d
204
I Chapter 11 :The Microstation Object Model - Objects I F u n c t i o n M a t r i x 3 d Z e r o O As M a t r i x 3 d Function Mdl C r e a t e E l ementFromE1 e m e n t D e s c r P ( E l e m e n t D e s c r P A s L o n g ) As E l e m e n t Function MdlGetDesignFileFromModel RefP(Mode1 RefP As L o n g ) As D e s i g n F i l e Function MdlGetModelReferenceFromModelRefP(Mode1RefP As Long) As ModelReference P r o p e r t y M e s s a g e c e n t e r As M e s s a g e c e n t e r {read-only} P r o p e r t y Name As S t r i n g { r e a d - o n l y } Sub OnDesignFileClosed(DesignFi1eName As String) Sub OnDesignFileOpened(DesignFi1eName As String) F u n c t i o n OpenDesignFile(DesignFi1eName As S t r i n g , [ R e a d o n l y As B o o l e a n ] , C V 7 A c t i o n As M s d V 7 A c t i o n = m s d V 7 A c t i o n A s k U s e r l ) As D e s i gn F i 1 e Function OpenDesignFileForProgram(DesignFi1eName As S t r i n g , [ R e a d o n l y As B o o l e a n ] ) As D e s i g n F i l e P r o p e r t y P a t h As S t r i n g { r e a d - o n l y } F u n c t i o n P i ( ) As D o u b l e Function P1 a n e 3 d I n t e r s e c t s P l a n e 3 d ( I n t e r s e c t i onRay As Ray3d, P1 aneO As P1 ane3d, P1 a n e l As P1 a n e 3 d ) As Boo1 ean Function Plane3dIntersectsRay3d(IntersectionPo n t As P o i n t 3 d , P a r a m e t e r A s Doubl e , P1 ane A s P1 ane3d, Ray A s Ray3d) As B o o l e a n F u n c t i o n P o i n t Z d A d d ( P o i n t 1 As P o i n t 2 d P o i n t 2 As P o i n t 2 d ) As P o i n t 2 d F u n c t i o n PointZdAddZScaled(0rigin As P o i n t Z d , V e c t o r l A s P o i n t Z d , S c a l e l As D o u b l e , V e c t o r 2 A s P o i n t 2 d , S c a l e 2 As D o u b l e ) As P o i n t 2 d F u n c t i o n PointZdAdd3Scaled(Origin As P o i n t Z d , V e c t o r l As P o i n t Z d , S c a l e l As D o u b l e , V e c t o r 2
I The Microstation Object Model I
205
As P o i n t Z d , S c a l e 2 As D o u b l e , V e c t o r 3 As P o i n t Z d , S c a l e 3 As D o u b l e ) As P o i n t 2 d F u n c t i o n P o i n t 2 d A d d S c a l e d ( O r i g i n As P o i n t 2 d , V e c t o r As P o i n t 2 d , S c a l e As D o u b l e ) As P o i n t 2 d F u n c t i o n P o i n t 2 d A r e V e c t o r s P a r a l l e l ( V e c t o r l As P o i n t Z d , V e c t o r 2 As P o i n t 2 d ) As B o o l e a n Function PointZdAreVectorsPerpendicular(Vector1 As P o i n t Z d , V e c t o r 2 As P o i n t 2 d ) As B o o l e a n F u n c t i o n P o i n t Z d C r o s s P r o d u c t ( V e c t o r 1 As P o i n t Z d , V e c t o r 2 As P o i n t 2 d ) As D o u b l e F u n c t i o n P o i n t 2 d C r o s s P r o d u c t 3 P o i n t s ( O r i g i n As P o i n t Z d , T a r g e t l As P o i n t 2 d , T a r g e t 2 As P o i n t 2 d ) As D o u b l e F u n c t i o n P o i n t Z d D i s t a n c e ( P o i n t 0 As P o i n t Z d , P o i n t l As P o i n t 2 d ) As D o u b l e F u n c t i o n P o i n t 2 d D i s t a n c e S q u a r e d ( P o i n t l As P o i n t Z d , P o i n t 2 As P o i n t 2 d ) As D o u b l e F u n c t i o n P o i n t Z d D o t D i f f e r e n c e ( T a r g e t P 0 i n t As P o i n t Z d , O r i g i n As P o i n t Z d , V e c t o r As P o i n t 2 d ) As D o u b l e F u n c t i o n P o i n t Z d D o t P r o d u c t ( V e c t o r 1 As P o i n t 2 d , V e c t o r 2 As P o i n t 2 d ) As D o u b l e F u n c t i o n P o i n t 2 d D o t P r o d u c t 3 P o i n t s ( O r i g i n As P o i n t Z d , T a r g e t l As P o i n t Z d , T a r g e t 2 As P o i n t 2 d ) As D o u b l e F u n c t i o n P o i n t 2 d E q u a l ( V e c t o r l As P o i n t 2 d , V e c t o r 2 As P o i n t 2 d ) As B o o l e a n F u n c t i o n P o i n t 2 d E q u a l To1 e r a n c e ( V e c t o r 1 As P o i n t Z d , V e c t o r 2 As P o i n t Z d , T o l e r a n c e As D o u b l e ) As B o o l e a n F u n c t i o n P o i n t 2 d F r o m X Y ( X As D o u b l e , Y As D o u b l e ) As P o i n t 2 d F u n c t i o n PointZdGetComponent(Point As P o i n t Z d , I n d e x As L o n g ) As D o u b l e F u n c t i o n PointZdInterpolate(Point0 As P o i n t 2 d , S As D o u b l e , P o i n t l As P o i n t 2 d ) As P o i n t 2 d F u n c t i o n P o i n t Z d M a g n i t u d e ( V e c t o r As P o i n t 2 d ) As D o u b l e
206
I Chapter 11 :The Microstation Object Model - Objects I F u n c t i o n PointZdMagnitudeSquared(Vector As P o i n t 2 d ) As D o u b l e F u n c t i o n P o i n t Z d M a x A b s ( V e c t o r As P o i n t 2 d ) As Doubl e F u n c t i o n P o i n t Z d N e g a t e ( V e c t 0 r As P o i n t 2 d ) As Poi n t 2 d F u n c t i o n P o i n t Z d N o r m a l i z e ( V e c t o r As P o i n t 2 d ) As P o i n t 2 d F u n c t i o n P o i n t Z d O n e O As P o i n t 2 d Function Point2dSignedAngleBetweenVectors(Vectorl As P o i n t 2 d , V e c t o r 2 As P o i n t 2 d ) As D o u b l e F u n c t i o n P o i n t Z d S u b t r a c t ( P o i n t 1 As P o i n t Z d , P o i n t 2 As P o i n t 2 d ) As P o i n t 2 d F u n c t i o n P o i n t Z d Z e r o O As P o i n t 2 d F u n c t i o n P o i n t 3 d A d d ( P o i n t l As P o i n t 3 d , P o i n t 2 As P o i n t 3 d ) As P o i n t 3 d F u n c t i o n Point3dAddZScaled(Origin As P o i n t 3 d , V e c t o r l As P o i n t 3 d , S c a l e l As D o u b l e , V e c t o r 2 As P o i n t 3 d , S c a l e 2 As D o u b l e ) As P o i n t 3 d F u n c t i o n Point3dAddZScaledVector3d(Origin As P o i n t 3 d , V e c t o r l As V e c t o r 3 d , S c a l e l As D o u b l e , V e c t o r 2 As V e c t o r 3 d , S c a l e 2 As D o u b l e ) As Poi n t 3 d F u n c t i o n Point3dAdd3Scaled(Origin As P o i n t 3 d , V e c t o r l As P o i n t 3 d , S c a l e l As D o u b l e , V e c t o r 2 As P o i n t 3 d , S c a l e 2 As D o u b l e , V e c t o r 3 As P o i n t 3 d , S c a l e 3 As D o u b l e ) As P o i n t 3 d F u n c t i o n Point3dAdd3ScaledVector3d(Origin As P o i n t 3 d , V e c t o r l As V e c t o r 3 d , S c a l e l As D o u b l e , V e c t o r 2 As V e c t o r 3 d , S c a l e 2 As D o u b l e , V e c t o r 3 As V e c t o r 3 d , S c a l e 3 As D o u b l e ) As P o i n t 3 d F u n c t i o n Point3dAddAngleDistance(Pointl As P o i n t 3 d , A n g l e R a d i a n s As D o u b l e , D i s t a n c e X Y As D o u b l e , Dz As D o u b l e ) As P o i n t 3 d F u n c t i o n Point3dAddPoint3dVector3d(Base As P o i n t 3 d , V e c t o r As V e c t o r 3 d ) As P o i n t 3 d F u n c t i o n P o i n t 3 d A d d S c a l e d ( O r i g i n As P o i n t 3 d , V e c t o r As P o i n t 3 d , S c a l e As D o u b l e ) As P o i n t 3 d
I The Microstation Object Model I
207
F u n c t i o n P o i n t 3 d A d d S c a l e d V e c t o r 3 d ( O r i g i n As P o i n t 3 d , V e c t o r As V e c t o r 3 d , S c a l e As D o u b l e ) As P o i n t 3 d F u n c t i o n Point3dAngleBetweenVectors(Vectorl As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d ) As D o u b l e Function Point3dAngleBetweenVectorsXY(Vectorl As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d ) As D o u b l e F u n c t i o n P o i n t 3 d A r e V e c t o r s P a r a l l e l ( V e c t o r 1 As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d ) As B o o l e a n Function
Point3dAreVectorsPerpendicular(Vectorl As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d ) As B o o l e a n F u n c t i o n Point3dCrossProduct(Vectorl As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d ) As P o i n t 3 d F u n c t i o n P o i n t 3 d C r o s s P r o d u c t 3 P o i n t s ( O r i g i n As P o i n t 3 d , T a r g e t l As P o i n t 3 d , T a r g e t 2 As P o i n t 3 d ) As P o i n t 3 d Function Point3dCrossProduct3PointsXY(Origin As P o i n t 3 d , T a r g e t l As P o i n t 3 d , T a r g e t 2 As P o i n t 3 d ) As D o u b l e F u n c t i o n P o i n t 3 d C r o s s P r o d u c t X Y ( V e c t o r l As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d ) As D o u b l e F u n c t i o n P o i n t 3 d D i s t a n c e ( P o i n t l As P o i n t 3 d , P o i n t 2 As P o i n t 3 d ) As D o u b l e F u n c t i o n P o i n t 3 d D i s t a n c e S q u a r e d ( P o i n t l As P o i n t 3 d , P o i n t 2 As P o i n t 3 d ) As D o u b l e F u n c t i o n P o i n t 3 d D i s t a n c e S q u a r e d X Y ( P o i n t l As P o i n t 3 d , P o i n t 2 As P o i n t 3 d ) As D o u b l e F u n c t i o n P o i n t 3 d D i s t a n c e X Y ( P o i n t l As P o i n t 3 d , P o i n t 2 As P o i n t 3 d ) As D o u b l e F u n c t i o n P o i n t 3 d D o t D i f f e r e n c e ( T a r g e t As P o i n t 3 d , O r i g i n As P o i n t 3 d , V e c t o r As P o i n t 3 d ) As D o u b l e Function Point3dDotDifferenceVector3d(Target As P o i n t 3 d , O r i g i n As P o i n t 3 d , V e c t o r As V e c t o r 3 d ) As D o u b l e F u n c t i o n Point3dDotProduct(Vectorl As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d ) As D o u b l e F u n c t i o n P o i n t 3 d D o t P r o d u c t 3 P o i n t s ( O r i g i n As P o i n t 3 d , T a r g e t l As P o i n t 3 d , T a r g e t 2 As P o i n t 3 d ) As D o u b l e
208
I Chapter 11 :The Microstation Object Model - Objects I F u n c t i o n Point3dDotProduct3PointsXY(Origin As P o i n t 3 d , T a r g e t 1 As P o i n t 3 d , T a r g e t 2 As P o i n t 3 d ) As D o u b l e F u n c t i o n Point3dDotProductXY(Vectorl As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d ) As D o u b l e F u n c t i o n P o i n t 3 d E q u a l ( V e c t o r 1 As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d ) As B o o l e a n F u n c t i o n P o i n t 3 d E q u a l T o l e r a n c e ( V e c t o r 1 As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d , T o l e r a n c e As Double) As Boolean F u n c t i o n P o i n t 3 d F r o m A n g l e D i s t a n c e ( A n g 1 eRadi ans As D o u b l e , D i s t a n c e X Y As D o u b l e , Z As D o u b l e ) As P o i n t 3 d F u n c t i o n P o i n t 3 d F r o m M a t r i x 3 d C o l u m n ( M a t r i x As M a t r i x 3 d , Col As Long) As P o i n t 3 d Function
Point3dFromMatrix3dInverseTimesPoint3d(Matrix As M a t r i x 3 d , P o i n t As P o i n t 3 d ) As P o i n t 3 d Function
Point3dFromMatrix3dInverseTransposeTimesPoint3 d ( M a t r i x A s M a t r i x 3 d , P o i n t As P o i n t 3 d ) As Poi n t 3 d F u n c t i o n Point3dFromMatrix3dRow(Matrix As M a t r i x 3 d , Row As Long) As P o i n t 3 d Function P o i n t 3 d FromMat r i x 3 d T i mes P o i n t 3 d ( M a t r i x As M a t r i x 3 d , P o i n t As P o i n t 3 d ) As P o i n t 3 d F u n c t i o n Point3dFromMatrix3dTimesXYZ(Matrix As M a t r i x 3 d , X As D o u b l e , Y As D o u b l e , Z As D o u b l e ) As P o i n t 3 d Function
Point3dFromMatrix3dTransposeTimesPoint3d(Matri x A s M a t r i x 3 d , P o i n t As P o i n t 3 d ) As P o i n t 3 d Function
Point3dFromMatrix3dTransposeTimesXYZ(Matrix As M a t r i x 3 d , X As D o u b l e , Y As D o u b l e , Z As D o u b l e ) As P o i n t 3 d F u n c t i o n Point3dFromRay3dFractionParameter(Ray A s Ray3d, F r a c t i o n As D o u b l e ) As P o i n t 3 d F u n c t i o n Point3dFromRay3dTangent(Ray As Ray3d) As P o i n t 3 d
I The Microstation Object Model I
209
Function Point3dFromSegment3dFractionParameter(Segment As Segment3d, Fraction As Double) As Point3d Function Point3dFromSegment3dTangent(Segment As Segment3d) As Point3d Function Point3dFromTransform3d(Transform As Transform3d) As Point3d Function Point3dFromTransform3dTimesPoint3d(Transform As Transform3d, Point As Point3d) As Point3d Function Point3dFromTransform3dTimesXYZ(Transform As Transform3d, X As Double, Y As Double, Z As Double) As Point3d Function Point3dFromVector3d(Vector As Vector3d) As Point3d Function Point3dFromXY(Ax As Double, Ay As Double) As Point3d Function Point3dFromXYZ(Ax As Double, Ay As Double, A z As Double) As Point3d Function Point3dGetComponent(Point As Point3d, Index As Long) As Double Function P o i n t 3 d I n P o l y g o n X Y ( P o i n t As Point3d, P o l y g o n V e r t i c e s O As Point3d, [Tolerance As Double = -11) As Long Function Point3dInterpolate(PointO As Point3d, Fracti onparameter As Double, Poi ntl As Point3d) As Point3d Function Point3dIsPointInCCWSector(TestPoint As Point3d, Origin As Point3d, Target0 As Point3d, Targetl As Point3d, UpVector As Point3d) As Boolean Function Point3dIsPointInSmallerSector(TestPoint As Point3d, Origin As Point3d, Targetl As Point3d, Target2 As Point3d) As Boolean Function Point3dIsVectorInCCWSector(TestVector As Point3d, Vector0 As Point3d, Vector1 As Point3d, UpVector As Point3d) As Boolean Function Point3dIsVectorInSmallerSector(TestVector As
210
I Chapter 11 :The Microstation Object Model - Objects I Point3d, Vector0 As Point3d, Vector1 As Point3d) As Boolean Function Point3dMagnitude(Vector As Point3d) A s Double Function Po nt3dMagnitudeSquared(Vector As Point3d) As Double Function Po nt3dMaxAbs(Vector As Point3d) As Doubl e Function Po nt3dNegate(Vector As Point3d) As Poi nt3d unction Point3dNormalize(Vector As Point3d) A s Poi nt3d Function P o i n t 3 d O n e O As Point3d Function Point3dPlanarAngleBetweenVectors(Vectorl As Point3d, Vector2 As Point3d, PlaneNorma1 As Point3d) A s Double Function Point3dPolarAngle(Vector As Point3d) As Double Function Point3dProjectToPlane3d(Point As Poi nt3d, P1 ane As P1 ane3d, CVi ewSpeci f i er As Variant], [UseAuxiliaryCoordinateSystem As Boolean = False]) As Point3d Function Point3dProjectToRay3d(Parameter As Double, Point As Point3d, Ray As Ray3d, [Viewspecifier As Variant], [UseAuxiliaryCoordinateSystem As Boolean = False]) As Point3d Function Point3dRotateXY(Vector As Point3d, Theta A s Double) As Point3d Function Point3dScale(Vector As Point3d, Scale As Double) As Point3d S u b Point3dSetComponent(Point As Point3d, Index A s Long, Value As Double) Function Point3dSignedAngleBetweenVectors(Vectorl As Point3d, Vector2 As Point3d, Orientationvector As Point3d) As Double Function Point3dSmallerAngleBetweenUnorientedVectors~Ve ctorl A s Point3d, Vector2 A s Point3d) As Double
I The Microstation Object Model I
21 1
Function
Point3dSmallerAngleBetweenUnorientedVectorsXY~ V e c t o r 1 As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d ) As Double F u n c t i o n P o i n t 3 d S u b t r a c t ( P o i n t l As P o i n t 3 d , P o i n t 2 As P o i n t 3 d ) As P o i n t 3 d Function Point3dSubtractPoint3dVector3d(Base As P o i n t 3 d , V e c t o r As V e c t o r 3 d ) As P o i n t 3 d F u n c t i o n P o i n t 3 d r i p l e P r o d u c t ( V e c t o r 1 As P o i n t 3 d , V e c t o r 2 As P o i n t 3 d , V e c t o r 3 As P o i n t 3 d ) As Doub e F u n c t i on P o i n t 3 d r i p l e P r o d u c t 4 P o i n t s (Orig i n As P o i n t 3 d , T a r g e t 1 As P o i n t 3 d , T a r g e t 2 As P o i n t 3 d , T a r g e t 3 As P o i n t 3 d ) As D o u b l e F u n c t i o n P o i n t 3 d Z e r o ( ) As P o i n t 3 d F u n c t i o n P o i n t s T o P i x e l s X ( P o i n t C o o r d i n a t e As D o u b l e ) As Long F u n c t i o n P o i n t s T o P i x e l s Y ( P o i n t C o o r d i n a t e As D o u b l e ) As Long P r o p e r t y P r o c e s s I D As Long {read-only} Sub Q u i t 0 F u n c t i o n R a d i a n s ( D e g r e e s As D o u b l e ) As D o u b l e F u n c t i o n R a n g e 3 d C o n t a i n s P o i n t 3 d ( R a n g e As Range3d, P o i n t As P o i n t 3 d ) As B o o l e a n F u n c t i o n Range3dContainsXYZ(Range As Range3d, X As D o u b l e , Y As D o u b l e , Z As D o u b l e ) As Boo1 ean F u n c t i o n Range3dEqual ( R a n g e l As Range3d, Range2 As Range3d) As B o o l e a n F u n c t i o n Range3dEqual To1 e r a n c e ( Range0 As Range3d, R a n g e l As Range3d, T o l e r a n c e As D o u b l e ) As B o o l e a n F u n c t i o n Range3dExtentSquared(Range As Range3d) As D o u b l e F u n c t i o n R a n g e 3 d F r o m P o i n t 3 d ( P o i n t As P o i n t 3 d ) As Range3d F u n c t i o n R a n g e 3 d F r o m P o i n t 3 d P o i n t 3 d ( P o i n t O As P o i n t 3 d , P o i n t 1 As P o i n t 3 d ) As Range3d Function R a n g e 3 d F r o m P o i n t 3 d P o i n t 3 d P o i n t 3 d ( P o i n t O As
212
I Chapter 11 :The Microstation Object Model - Objects I P o i n t 3 d , P o i n t 1 As P o i n t 3 d , P o i n t 2 As P o i n t 3 d )
As Range3d F u n c t i o n Range3dFromRange3dMargi n (Range As Range3d, M a r g i n As D o u b l e ) As Range3d F u n c t i o n Range3dFromXYZ(X As D o u b l e , Y As D o u b l e , Z As D o u b l e ) As Range3d F u n c t i o n Range3dFromXYZXYZ(Xl As D o u b l e , Y 1 As D o u b l e , Z 1 As D o u b l e , X2 As D o u b l e , Y2 As D o u b l e , 22 As D o u b l e ) As Range3d F u n c t i o n R a n g e 3 d I n i t O As Range3d F u n c t i o n R a n g e 3 d I n t e r s e c t ( R a n g e l As Range3d, Range2 A s Range3d) As Range3d F u n c t i o n R a n g e 3 d I n t e r s e c t 2 ( Resul t R a n g e As Range3d, R a n g e l As Range3d, Range2 As Range3d) As B o o l e a n Function
Range3dIsContainedInRange3d(InnerRange As Range3d, O u t e r R a n g e As Range3d) As B o o l e a n F u n c t i o n R a n g e 3 d I s N u l l (Range As Range3d) As Boo1 ean F u n c t i o n Range3dScaleAboutCenter(RangeIn As Range3d, S c a l e As D o u b l e ) As Range3d F u n c t i o n Range3dUni on (Range0 As Range3d, R a n g e l As Range3d) As Range3d F u n c t i o n Range3dUnionPoint3d(Range As Range3d, P o i n t A s P o i n t 3 d ) As Range3d F u n c t i o n Range3dUni onXY Z ( Range As Range3d, X As D o u b l e , Y As D o u b l e , Z As D o u b l e ) As Range3d P r o p e r t y R a s t e r M a n a g e r As R a s t e r M a n a g e r {read-only} Sub Ray3dC1 o s e s t P o i n t (Ray As Ray3d, S p a c e P o i n t As P o i n t 3 d , C1 o s e P o i n t As P o i n t 3 d , C1 o s e F r a c t i o n As D o u b l e ) Sub Ray3dC1 o s e s t P o i n t B o u n d e d ( Ray As Ray3d, S p a c e P o i n t As P o i n t 3 d , C1 o s e P o i n t As P o i n t 3 d , C1 o s e F r a c t i o n As D o u b l e ) Sub Ray3dC1 o s e s t P o i ntBoundedXY (Ray As Ray3d, S p a c e P o i n t As P o i n t 3 d , C1 o s e P o i n t As P o i n t 3 d , C1 o s e F r a c t i o n As D o u b l e )
I The Microstation Object Model I
213
Sub Ray3dC1 o s e s t P o i n t X Y (Ray As Ray3d, S p a c e P o i n t As P o i n t 3 d , C l o s e P o i n t As P o i n t 3 d , C1 o s e F r a c t i on As Doubl e ) F u n c t i o n Ray3dFromPoint3dStartEnd(PointO As P o i n t 3 d , P o i n t l As P o i n t 3 d ) As Ray3d F u n c t i o n Ray3dFromPoint3dStartTangent(PointO As P o i n t 3 d , T a n g e n t As P o i n t 3 d ) As Ray3d F u n c t i o n Ray3dFromSegment3d(Segment As Segment3d) As Ray3d Function Ray3dFromTransform3dTimesRay3d(Transform As T r a n s f o r m 3 d , Ray As Ray3d) As Ray3d F u n c t i o n Ray3dFromXYZXYZStartEnd(XO As D o u b l e , Y O As D o u b l e , ZO As D o u b l e , X 1 As D o u b l e , Y 1 As D o u b l e , Z1 As D o u b l e ) As Ray3d F u n c t i o n R a y 3 d L e n g t h ( R a y As Ray3d) As D o u b l e F u n c t i o n R a y 3 d L e n g t h S q u a r e d ( R a y As Ray3d) As Double F u n c t i o n Ray3dP1 a n e 3 d I n t e r s e c t ( Ray As Ray3d, P l a n e As P l a n e 3 d , P o i n t As P o i n t 3 d , F r a c t i o n As D o u b l e ) As B o o l e a n F u n c t i o n Ray3dRay3dCl o s e s t A p p r o a c h ( Ray0 As Ray3d, R a y l As Ray3d, P o i n t O As P o i n t 3 d , F r a c t i o n 0 As D o u b l e , P o i n t l As P o i n t 3 d , F r a c t i o n 1 As D o u b l e ) As B o o l e a n F u n c t i o n R a y 3 d R a y 3 d I n t e r s e c t X Y (Ray0 As Ray3d, R a y l As Ray3d, P o i n t O As P o i n t 3 d , F r a c t i o n 0 As D o u b l e , P o i n t l As P o i n t 3 d , F r a c t i o n l As D o u b l e ) As B o o l e a n Sub RedrawAllViews([DrawMode As MsdDrawingMode = msdDrawi ngModeNormal1) Sub R e g i s t e r V 8 T o V 7 F i l t e r ( H a n d 1 e r As IConvertV8ToV7) Sub R e m o v e A t t a c h m e n t E v e n t s H a n d l e r ( E v e n t H a n d 1 e r As I A t t a c h m e n t E v e n t s ) Sub R e m o v e C h a n g e T r a c k E v e n t s H a n d l e r ( E v e n t H a n d e r As IChangeTrackEvents) Sub R e m o v e L e v e l C h a n g e E v e n t s H a n d l e r ( E v e n t H a n d e r As IL e v e l C h a n g e E v e n t s )
214
I Chapter 11:The Microstation Object Model - Objects I Sub RemoveModalDialogEventsHandler(EventHand1er As IModal Dial ogEvents) Sub RemoveModelActivateEventsHandler(EventHand1er As IModel Acti vateEvents) Sub RemoveModelChangeEventsHandler(EventHand1er As IModel ChangeEvents) Sub RemoveSaveAsEventsHandler(EventsHand1er As ISaveAsEvents) Sub RemoveViewUpdateEventsHandler(EventHand1er As IViewUpdateEvents) Sub ResetDisplaySet(Sh0wEverything As Boolean) Sub Savesettingso Sub Segment3dClosestPoint(Segment As Segment3d, SpacePoi nt As Poi nt3d, C1 osePoi nt As Point3d, C1 oseFracti on As Doubl e) Sub Segment3dClosestPointBounded(Segment As Segment3d, SpacePoi nt As Poi nt3d, C1 osePoi nt As Point3d, C1 oseFracti on As Doubl e) Sub Segment3dClosestPointBoundedXY(Segment As Segment3d, SpacePoi nt As Poi nt3d, C1 osePoi nt As Point3d, C1 oseFracti on As Doubl e) Sub Segment3dClosestPointXY(Segment As Segment3d, SpacePoi nt As Poi nt3d, C1 osePoi nt As Point3d, C1 oseFracti on As Doubl e) Function Segment3dFromPoint3dStartEnd(PointO As Point3d, Point1 As Point3d) As Segment3d Function Segment3dFromPoint3dStartTangent(PointO As Point3d, Tangent As Point3d) As Segment3d Function Segment3dFromRay3d( Ray As Ray3d) As Segment3d Function Segment3dFromTransform3dTimesSegment3d(Transfo rm As Transform3d, Segment As Segment3d) As Segment3d Function Segment3dFromXYZXYZStartEnd(XO As Double, Y O As Double, ZO As Double, X 1 As
I The Microstation Object Model I
I4 I4
I4
I4
I4
I4
I4
I4 I4 I4 I4 I4
I4 I4 I4 I4 I4
21 5
Double, Y 1 As Double, Z1 As Double) As Segment3d Function Segment3dLength(Segment A s Segment3d) As Double Function Segment3dLengthSquared(Segment As Segment3d) As Doubl e Function Segment3dPlane3dIntersect(Segment A s Segment3d, Plane A s Plane3d, Point As Point3d, Fraction As Double) A s Boolean Function Segment3dSegment3dC1osestApproach(SegmentO As Segment3d, Segmentl As Segment3d, PointO As Point3d, Fraction0 As Double, Pointl As Point3d, Fractionl As Double) As Boolean Function Segment3dSegment3dIntersectXY(SegmentO A s Segment3d, Segmentl A s Segment3d, PointO A s Point3d, Fraction0 A s Double, Pointl A s Point3d, Fraction1 A s Double) A s Boolean Sub SetCExpressionValue(CExpression As String, NewVal ue As Variant, [Mdl Appl i cationName As Stri ngl) Sub SetCExpressionValueAsDLong(CExpression A s String, NewValue As DLong, CMdlApplicationName As String]) Sub ShowCommand([Command As String]) Sub ShowError([Error A s String]) Sub ShowPrompt([Prompt As String]) Sub ShowStatus([Status A s String]) Sub ShowTempMessage(Area As MsdStatusBarArea, Message As String, [Details As String]) Property S t a n d a r d s C h e c k e r C o n t r o l l e r A s StandardsCheckerController {read-only} Sub S t a r t B u s y C u r s o r O Sub S t o p B u s y C u r s o r O Property TopPosition As Long Function Transform3dEqual(Transforml A s Transform3d, Transform2 A s Transform3d) A s Boo1 ean
216
I Chapter 11 :The Microstation Object Model - Objects I Function Transform3dEqualTolerance(Transforml As Transform3d, Transform2 As Transform3d, MatrixTol erance As Doubl e , Poi ntTol erance As Double) As Boolean Function Transform3dFactorMirror(Transform As Transform3d, ResidualTransform As Transform3d, MirrorTransform As Transform3d, Fixedpoint As Point3d, P1 aneNormal As Poi nt3d) As Boo1 ean Function Transform3dFromLi neAndRotati onAngl e( Poi ntO As Point3d, Point1 As Point3d, Radians As Double) As Transform3d Function Transform3dFromMatrix3d(Matrix As Matrix3d) As Transform3d Function Transform3dFromMatrix3dAndFixedPoint3d(Matrix As Matrix3d, Origin As Point3d) As Transform3d Function Transform3dFromMatrix3dPoint3d(Matrix As Matrix3d, Translation As Point3d) As Transform3d Function Transform3dFromMatrix3dTimesTransform3d(Matrix As Matrix3d, Transform As Transform3d) As Transform3d Function Transform3dFromMi rrorPl ane(0rigin As Point3d, Normal As Point3d) As Transform3d Function Transform3dFromPl ane3dToWorl d( P1 ane As Plane3d) As Transform3d Function Transform3dFromPoi nt3d(Transl ati on As Point3d) As Transform3d Function Transform3dFromRowValues(XOO As Double, X O 1 As Double, X O 2 As Double, T x As Double, X10 As Double, X11 As Double, X12 As Double, Ty As Double, X20 As Double, X21 As Double, X 2 2 As Double, Tz As Double) As Transform3d Function Transform3dFromSquaredTransform3d(Transform As Transform3d, PrimaryAxis As Long, SecondaryAxis As Long) As Transform3d Function Transform3dFromTransform3dTimesMatrix3d(Transf
I The Microstation Object Model I
21 7
orm As Transform3d, Matrix As Matrix3d) As Transform3d Function Transform3dFromTransform3dTimesTransform3d(Tra nsforml As Transform3d, Transform2 A s Transform3d) A s Transform3d Function Transform3dFromTransform3dTimesTransform3dTime sTransform3d(Transforml As Transform3d, Transform2 As Transform3d, Transform3 As Transform3d) As Transform3d Function Transform3dFromWorl dToPl ane3d( P1 ane As Plane3d) As Transform3d Function Transform3dFromXYZ(X As Double Y As Double, Z As Double) As Transform3d Function Transform3dGetMatrixComponentByRowAndCo umn (Tr ansform As Transform3d, R o w A s Long, Co A s Long) As Double Function Transform3dGetPointComponent(Transform As Transform3d, R o w As Long) As Double Function Transform3dHasInverse(Transform A s Transform3d) A s Boolean Function T r a n s f o r m 3 d I d e n t i t y O As Transform3d Function Transform3dInverse(In A s Transform3d) As Transform3d Function Transform3dIsIdentity(Transform As Transform3d) As Boolean Function Transform3dIsMirrorAboutPlane(Transform As Transform3d, PlanePoint A s Point3d, P1 aneNormal As Poi nt3d) A s Boo1 ean i l Function Transform3dIsPlanar(Transform As Transform3d, Normal As Point3d) As Boolean Function Transform3dIsRigid(Transform A s Transform3d) A s Boolean
Function Transform3dIsRotateAroundLine(Transform As Transform3d, FixedPoint As Point3d,
218
I Chapter 11 :The Microstation Object Model - Objects I D i r e c t i o n v e c t o r As P o i n t 3 d , R a d i a n s As Doubl e ) As B o o l e a n F u n c t i o n Transform3dIsTranslate(Transform As Transform3d, T r a n s l a t i o n As P o i n t 3 d ) As Boolean Function T r a n s f orm3d Is T r a n s 1 a t e R o t a t eSca 1 e R o t a t e ( T r a n s f orm As T r a n s f o r m 3 d , T r a n s l a t i o n As P o i n t 3 d , R o t a t i o n 1 As M a t r i x 3 d , S c a l e F a c t o r s As P o i n t 3 d , R o t a t i o n 2 As M a t r i x 3 d ) As B o o l e a n F u n c t i o n Transform3dIsUniformScale(Transform A s T r a n s f o r m 3 d , F i x e d p o i n t A s P o i n t 3 d , S c a l e As Double) As Boolean Function
Transform3dIsUniformScaleAndRotateAroundLine~T r a n s f o r m As T r a n s f o r m 3 d t F i x e d P o i n t As P o i n t 3 d , D i r e c t i o n v e c t o r As P o i n t 3 d , R a d i a n s As Doubl e , S c a l e As D o u b l e ) As B o o l e a n Sub
Transform3dSetMatrixComponentByRowAndColumn~Tr a n s f o r m A s T r a n s f o r m 3 d , RowIndex As Long, C o l u m n I n d e x As Long, V a l u e As D o u b l e ) Sub Transform3dSetPointComponent(Transform As T r a n s f o r m 3 d , RowIndex As Long, V a l u e As D o u b l e ) F u n c t i o n T r a n s f o r m 3 d Z e r o O As T r a n s f o r m 3 d F u n c t i o n U p d a t e G r a p h i c G r o u p N u m b e r O As Long P r o p e r t y UserName As S t r i n g {read-only} P r o p e r t y V B E As O b j e c t {read-only} F u n c t i o n V e c t o r 3 d A d d ( V e c t o r l As V e c t o r 3 d , V e c t o r 2 A s V e c t o r 3 d ) As V e c t o r 3 d F u n c t i o n V e c t o r 3 d A d d Z S c a l e d ( 0 r i g i n As V e c t o r 3 d , V e c t o r l As V e c t o r 3 d , S c a l e l As D o u b l e , V e c t o r 2 As V e c t o r 3 d , S c a l e 2 As D o u b l e ) As V e c t o r 3 d F u n c t i o n V e c t o r 3 d A d d 3 S c a l e d ( 0 r i g i n As V e c t o r 3 d , V e c t o r l As V e c t o r 3 d , S c a l e l As D o u b l e , V e c t o r 2 As V e c t o r 3 d , S c a l e 2 A s D o u b l e , V e c t o r 3 A s V e c t o r 3 d , S c a l e 3 As D o u b l e ) As Vector3d F u n c t i o n Vector3dAddScaled(Origin As V e c t o r 3 d , V e c t o r As V e c t o r 3 d , S c a l e As D o u b l e ) As Vector3d
I The Microstation Object Model I
21 9
Function Vector3dAngleBetweenVectors(Vectorl As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As D o u b l e Function Vector3dAngleBetweenVectorsXY(Vectorl As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As D o u b l e F u n c t i o n Vector3dAreVectorsParall e l ( V e c t o r 1 As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As B o o l e a n Function Vector3dAreVectorsPerpendicular(Vectorl As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As B o o l e a n F u n c t i o n V e c t o r 3 d C r o s s P r o d u c t ( V e c t o r l As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As V e c t o r 3 d F u n c t i o n V e c t o r 3 d C r o s s P r o d u c t 3 P o i n t s ( O r i g i n As P o i n t 3 d , T a r g e t 1 As P o i n t 3 d , T a r g e t 2 As P o i n t 3 d ) As V e c t o r 3 d F u n c t i o n V e c t o r 3 d C r o s s P r o d u c t X Y ( V e c t o r l As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As D o u b l e F u n c t i o n Vector3dDistance(Vectorl As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As D o u b l e F u n c t i o n V e c t o r 3 d D i s t a n c e S q u a r e d ( V e c t o r l As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As D o u b l e F u n c t i o n V e c t o r 3 d D i s t a n c e S q u a r e d X Y ( V e c t o r l As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As D o u b l e F u n c t i o n V e c t o r 3 d D i s t a n c e X Y ( V e c t o r l As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As D o u b l e F u n c t i o n V e c t o r 3 d D o t P r o d u c t ( V e c t o r l As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As D o u b l e F u n c t i o n V e c t o r 3 d D o t P r o d u c t X Y ( V e c t o r l As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As D o u b l e F u n c t i o n V e c t o r 3 d D o t P r o d u c t X Y Z ( V e c t o r As V e c t o r 3 d , Ax As D o u b l e , Ay As D o u b l e , Az As D o u b l e ) As D o u b l e F u n c t i o n V e c t o r 3 d E q u a l ( V e c t o r l As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As B o o l e a n F u n c t i o n V e c t o r 3 d E q u a l T o l e r a n c e ( V e c t o r l As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d , T o l e r a n c e As D o u b l e ) As B o o l e a n F u n c t i o n V e c t o r 3 d F r o m M a t r i x 3 d C o l u m n ( M a t r i x As M a t r i x 3 d , Col As L o n g ) As V e c t o r 3 d F u n c t i o n V e c t o r 3 d F r o m M a t r i x 3 d R o w ( M a t r i x As M a t r i x 3 d , Row As L o n g ) As V e c t o r 3 d
220
I Chapter 11 :The Microstation Object Model - Objects I Function Vector3dFromMatrix3dTimesVector3d(Matrix As Matrix3d, Vector As Vector3d) As Vector3d Function Vector3dFromMatrix3dTimesXYZ(Matrix A s Matrix3d, X As Double, Y As Double, Z As Double) A s Vector3d Function Vector3dFromMatrix3dTransposeTimesVector3d~Mat rix As Matrix3d, Vector As Vector3d) As Vector3d Function Vector3dFromMatrix3dTransposeTimesXYZ(Matrix A s Matrix3d, X As Double, Y As Double, Z As Double) A s Vector3d Function Vector3dFromPoint3d(Point As Point3d) As Vector3d Function Vector3dFromTransform3dColumn(Transform As Transform3d, Col As Long) As Vector3d Function Vector3dFromTransform3dRow(Transform As Transform3d, Row As Long) As Vector3d Function Vector3dFromTransform3dTimesVector3d(Transform A s Transform3d, Vector As Vector3d) As Vector3d Function Vector3dFromTransform3dTimesXYZ(Transform As Transform3d, X As Double, Y As Double, Z As Double) As Vector3d Function Vector3dFromTransform3dTranslation(Transform A s Transform3d) As Vector3d Function Vector3dFromTransform3dTransposeTimesVector3d~ Transform As Transform3d, Vector As Vector3d) As Vector3d Function Vector3dFromTransform3dTransposeTimesXYZ~Trans form As Transform3d, X As Double, Y A s Double, Z A s Double) As Vector3d Function Vector3dFromXY(Ax As Double, Ay As Double) As Vector3d
I The Microstation Object Model I
221
Function V e c t o r 3 d F r o m X Y A n g l e A n d M a g n i t u d e ( T h e t a As Double, Magnitude As Double) As Vector3d Function Vector3dFromXYZ(Ax As Double, Ay As Double, Az As Double) As Vector3d Function Vector3dGetComponent(Vector As Vector3d, Index As Long) As Double Function Vector3dInterpolate(VectorO As Vector3d, Fractionparameter As Double, Vectorl As Vector3d) As Vector3d Function Vector3dIsVectorInCCWSector(TestVector As Vector3d, Vector0 As Vector3d, Vectorl As Vector3d, UpVector As Vector3d) As Boolean Function Vector3dIsVectorInCCWXYSector(TestVector As Vector3d, Vector0 As Vector3d, Vectorl As Vector3d) As Bool ean Function Vector3dIsVectorInSmallerSector(TestVector As Vector3d, Vector0 As Vector3d, Vectorl As Vector3d) As Bool ean Function Vector3dMagnitude(Vector As Vector3d) As Double Function Vector3dMagnitudeSquared(Vector As Vector3d) As Double Function Vector3dMagnitudeSquaredXY(Vector As Vector3d) As Double Function Vector3dMagnitudeXY(Vector As Vector3d) As Double Function Vector3dMaxAbs(Vector As Vector3d) As Double Function Vector3dMaxAbsDifference(Vectorl As Vector3d, Vector2 As Vector3d) As Double Function Vector3dNegate(Vector As Vector3d) As Vector3d Function Vector3dNormalize(Vector As Vector3d) As Vector3d Function Vector3dOneO As Vector3d Function Vector3dPlanarAngleBetweenVectors(Vectorl As
222
I Chapter 11:The Microstation Object Model - Objects I V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d , PlaneNorma1 As V e c t o r 3 d ) As D o u b l e F u n c t i o n V e c t o r 3 d P o l a r A n g l e ( V e c t o r As V e c t o r 3 d ) As D o u b l e F u n c t i o n V e c t o r 3 d R o t a t e X Y ( V e c t o r As V e c t o r 3 d , T h e t a As D o u b l e ) As V e c t o r 3 d El F u n c t i o n V e c t o r 3 d S c a l e ( V e c t o r As V e c t o r 3 d , S c a l e As D o u b l e ) As V e c t o r 3 d Function Vector3dSignedAngleBetweenVectors(Vectorl As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d , O r i e n t a t i o n v e c t o r As V e c t o r 3 d ) As D o u b l e Function
Vector3dSmallerAngleBetweenUnorientedVectors~V e c t o r l As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As Doubl e Function
Vector3dSmallerAngleBetweenUnorientedVectorsXY ( V e c t o r 1 As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As Doubl e F u n c t i o n Vector3dSubtract(Vectorl As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d ) As V e c t o r 3 d F u n c t i o n Vector3dSubtractPoint3dPoint3d(Target As P o i n t 3 d , Base As P o i n t 3 d ) As V e c t o r 3 d F u n c t i o n Vector3dTripleProduct(Vectorl As V e c t o r 3 d , V e c t o r 2 As V e c t o r 3 d , V e c t o r 3 As V e c t o r 3 d ) As D o u b l e F u n c t i o n Vector3dUnitPerpendicularXY(Vector As V e c t o r 3 d ) As V e c t o r 3 d F u n c t i o n V e c t o r 3 d Z e r o O As V e c t o r 3 d P r o p e r t y V e r s i o n As S t r i n g { r e a d - o n l y } P r o p e r t y V i s i b l e As B o o l e a n P r o p e r t y W i d t h As Long
ApplicationObjectConnector P r o p e r t y A p p l ic a t i on As A p p l ic a t i on { r e a d only1
Attachment Sub A c t i v a t e 0
I The Microstation Object Model I
223
il Sub AddEl ement( El ement As El ement il Sub AddElements(Elements0 As -Element) il Function AddNewNamedGroup([Name As String], [Description As String]) As NamedGroupElement il Sub AddUserAttributeData(Attribute1D As Long, Attri buteData As DataBl ock) il Property AnyEl ementsSel ected As Bool ean { read-on1y 1 il Property AsAttachment As Attachment {readonly} il Property Attachmentorigin As Point3d {read only} il Property Attachments As Attachments {readonly} il Property AttachName As String {read-only) il Property CanBePl acedAsCel1 As Bool ean il Property Cell Type As MsdCel 1 Type il Property ControlElementCache As Elementcache { read-on1y } il Function C o p y 0 As Attachment il Function CopyEl ement( El ement As El ement, CCopyContext As CopyContextl) As Element il Sub Del eteAl1 XData ( il Function DeleteUserAttributeData(Attribute1D As Long, Index As Integer) As Integer il Sub DeleteXData(App1icationName As String) il Property Description As String il Property DesignFile As DesignFile {read-only} il Property Di spl ayAsNested As Bool ean il Property DisplayFlag As Boolean il Property Di spl ayPriori ty As Long il Property Di spl aysRasterReferences As Bool ean il Function D o u b l e T o W o r k i n g U n i t s ( V a 1 u e As Double) As String il Function El ementCacheContainingFi1 ePosi tion( Fil ePosi tio n As Long, CCacheIndex As Long]) As El ementcache
224
I Chapter 11 :The Microstation Object Model - Objects I Property ElementID As DLong {read-only} Property El ementsLocatab1 e As Bool ean Property El ementssnappabl e As Bool ean Property El ementsVi si bl e As Bool ean Function GetElementByID(Element1D As DLong) As Element Function GetElementByID64(ElementID64 As Empty) As Element Function GetLastValidGraphical E l e m e n t 0 As Element Function GetMasterToReferenceTransformO As Transform3d Function GetNamedGroup(Gr0upName As String) As NamedGroupEl ement Function GetReferenceToMasterTransformO As Transform3d Function G e t S e l ectedElementsO As El ement Enumerator Function G e t S h e etDefinitionO As SheetDefinition Function GetUserAttributeData(Attribute1D As Long) As DataBl ock() Function GetXData(App1icationName As String) As X D a t u m O Function GetXDataApplicationNamesO As String( ( Property G1 obal Li neStyl eScal e As MsdGl obal LineStyl eScal e Property G1 obal Ori gi n As Poi nt3d {read-only} Property Graphical El ementcache As El ementcache {read-only} Function H a s A n y X D a t a O As Boolean Function HasXData(App1icationName As String) As Boolean Property Is3D As Boolean {read-only} Property IsActive As Boolean {read-only} Property IsAttachment As Boolean {read-only} Property IsEl ementSel ected As Bool ean {readonly}
I The Microstation Object Model I
225
P r o p e r t y I s L o c k e d As B o o l e a n P r o p e r t y I s M i s s i n g F i l e As B o o l e a n {read-only} P r o p e r t y I s M i s s i n g M o d e l As B o o l e a n {readonly} P r o p e r t y I s R e a d O n l y As B o o l ean {read-only} P r o p e r t y I s T r u e S c a l e As B o o l e a n {read-only} P r o p e r t y L e v e l As L e v e l P r o p e r t y L e v e l s As L e v e l s {read-only} P r o p e r t y L i n e S t y l e s S c a l ed As B o o l ean P r o p e r t y L o g i c a l D e s c r i p t i o n As S t r i n g P r o p e r t y L o g i c a l N a m e As S t r i n g P r o p e r t y M a n i p u l a t e A s E l ement As B o o l ean P r o p e r t y M a s t e r o r i g i n As P o i n t 3 d {read-only} P r o p e r t y M a s t e r u n i t As M e a s u r e m e n t u n i t F u n c t i o n MdlModel R e f P ( ) As Long Sub M o v e ( 0 f f s e t As P o i n t 3 d , A p p l y T o C l i p E l e m e n t As B o o l e a n ) P r o p e r t y Name As S t r i n g P r o p e r t y NamedGroup As S t r i n g P r o p e r t y N e s t L e v e l As Long P r o p e r t y N e s t o v e r r i d e s As M s d N e s t O v e r r i d e s P r o p e r t y NewLevel D i s p l ay As MsdNewLevel D i s p l ay P r o p e r t y P a r e n t M o d e l R e f e r e n c e As Model R e f e r e n c e {read-only} P r o p e r t y P l o t 3 d As B o o l e a n P r o p e r t y P r e s e n t a t i o n As MsdRenderingMode P r o p e r t y P r i n t C o l o r A d j u s t m e n t As B o o l e a n Sub PropagateAnnotationScaleO F u n c t i o n Range( I n c l u d e A t t a c h m e n t s As B o o l e a n ) As Range3d F u n c t i o n R e a t t a c h ( F i 1 e N a m e As S t r i n g , ModelName As S t r i n g ) As A t t a c h m e n t Sub Redraw(CDrawMode As MsdDrawingMode = msdDrawi ngModeNormal1) Sub RemoveEl e m e n t ( E l ement As E l ement Sub Rep1 a c e E l e m e n t ( 0 l d E l ement As E l e m e n t , NewEl ement As E l e m e n t )
226
I Chapter 11:The Microstation Object Model - Objects I El P r o p e r t y R e v i s i onNumber As S t r i n g El Sub R e w r i t e 0 Sub R o t a t e ( P i v o t As P o i n t 3 d , AboutX As D o u b l e , AboutY As D o u b l e , A b o u t Z As D o u b l e , V i e w s p e c i f i e r As V a r i a n t ) El P r o p e r t y R o t a t i o n As M a t r i x 3 d { r e a d - o n l y } El P r o p e r t y S c a l e F a c t o r As D o u b l e El P r o p e r t y S c a l e M a s t e r U n i t s As Doubl e { r e a d only1 Sub S c a l e U n i f o r m ( 0 r i g i n As P o i n t 3 d , S c a l e F a c t o r As Doubl e , A p p l y T o C l ip E l ement As B o o l ean El F u n c t i o n Scan( [ S c a n C r i t e r i a As E l e m e n t S c a n C r i t e r i a l ) As E l e m e n t E n u m e r a t o r Sub S e l e c t E l ement ( E l ement As E l e m e n t , [ D i s p l ayAsSel e c t e d As B o o l ean = T r u e ] ) El Sub S e t A t t a c h N a m e D e f e r r e d ( F i 1 e S p e c i f i c a t i on As String) El Sub S e t S h e e t D e f i n i t i o n ( N e w D e f i n i t i 0 n As SheetDefinition) El Sub SetXData(App1icationName As S t r i n g , NewXDataO As XDatum) P r o p e r t y S t o r a g e u n i t As M e a s u r e m e n t u n i t El P r o p e r t y S u b u n i t As M e a s u r e m e n t u n i t tN P r o p e r t y S u b U n i t s P e r M a s t e r U n i t As D o u b l e {read-only} El P r o p e r t y T r a n s p a r e n c y As D o u b l e P r o p e r t y Type As MsdModelType El Sub U n s e l e c t A l 1 E l ements ( ) Sub U n s e l e c t E l ement ( E l ement As E l e m e n t ) El P r o p e r t y UORsPerMasterUni t As Doubl e { r e a d only1 P r o p e r t y UORsPerStorageUni t As D o u b l e El P r o p e r t y UORsPerSubUnit As D o u b l e { r e a d - o n l y } P r o p e r t y U p d a t e o r d e r As Long El P r o p e r t y U s e s L i g h t s As B o o l e a n F u n c t i o n W o r k i n g U n i t s T o D o u b l e ( V a 1 ue As S t r i n g ) As D o u b l e
I The Microstation Object Model I
227
Attachments F u n c t i o n A d d ( F i 1 e S p e c i f i c a t i o n As S t r i n g , Model Name As S t r i n g , L o g i c a l Name As S t r i n g , D e s c r i p t i o n As S t r i n g , M a s t e r o r i g i n As P o i n t 3 d , R e f e r e n c e o r i g i n As P o i n t 3 d , [ T r u e s c a l e As Bool ean = T r u e ] , [ D i s p l ay Immedi a t e l y As Bool ean = T r u e ] ) As A t t a c h m e n t F u n c t i o n A d d C o i n c i d e n t ( F i 1 e S p e c i f i c a t i o n As S t r i n g , ModelName As S t r i n g , LogicalName As S t r i n g , D e s c r i p t i o n As S t r i n g , C D i s p l ay Immedi a t e l y As Bool ean = T r u e ] ) As Attachment F u n c t i on AddCoi n c i d e n t 1 ( F i 1 eSpec f i c a t i o n As S t r i n g , ModelName As S t r i n g , Log calName As S t r i n g , D e s c r i p t i o n As S t r i n g , F ags As M s d A d d A t t a c h m e n t F l a g s ) As A t t a c h m e n t F u n c t i o n AddUsingNamedView(Fi1eSpecification As S t r i n g , LogicalName As S t r i n g , D e s c r i p t i o n As S t r i n g , ViewName As S t r i n g , C e n t e r p o i n t As Poi n t 3 d , C D i s p l ay Immedi a t e l y As Bool ean = T r u e ] ) As A t t a c h m e n t F u n c t i o n AddUsingNamedViewl(Fi1eSpecification As S t r i n g , ModelName As S t r i n g , LogicalName As S t r i n g , D e s c r i p t i o n As S t r i n g , ViewName As S t r i n g , C e n t e r p o i n t As P o i n t 3 d , F l a g s As M s d A d d A t t a c h m e n t F l a g s ) As A t t a c h m e n t P r o p e r t y Count As Long {read only} F u n c t i o n F i ndByLogi c a l Name( Log calName As S t r i n g ) As A t t a c h m e n t P r o p e r t y I t e m As A t t a c h m e n t read-only} Sub Remove(AttachmentSpecifier As V a r i a n t )
CadInputMessage Property Property Property only} Property Property Property Property
CommandKeyin As S t r i n g { r e a d - o n l y } C u r s o r B u t t o n As Long { r e a d - o n l y } I n p u t T y p e As MsdCadInputType { r e a d K e y i n As S t r i n g { r e a d - o n l y } P o i n t As P o i n t 3 d { r e a d - o n l y } S c r e e n p o i n t As P o i n t 3 d { r e a d - o n l y } View As View { r e a d - o n l y }
228
I Chapter 11:The Microstation Object Model - Objects I CadInputQueue F u n c t i o n G e t I n p u t ( C T y p e 1 As M s d C a d I n p u t T y p e = m s d C a d I n p u t T y p e A n y 1 , [ T y p e 2 As M s d C a d I n p u t T y p e l , [ T y p e 3 As M s d C a d I n p u t T y p e l , [ T y p e 4 As M s d C a d I n p u t T y p e l ) As C a d I n p u t M e s s a g e Sub SendCommand(Command As S t r i n g , [ T r e a t L i k e K e y b o a r d I n p u t As B o o l e a n ] ) Sub S e n d D a t a P o i n t ( D a t a P 0 i n t As P o i n t 3 d , [ V i e w s p e c i f i e r As V a r i a n t ] , [ Q u a l i f i e r As Long]) Sub S e n d D a t a P o i n t F o r L o c a t e ( E 1 e m e n t T o L o c a t e As E l e m e n t , D a t a P o i n t As P o i n t 3 d , [ V i ewSpeci f i e r As V a r i a n t ] , [ Q u a l i f i e r As L o n g ] ) Sub SendDragPoints(DownPoint As P o i n t 3 d , U p P o i n t As P o i n t 3 d , [ V i e w s p e c i f i e r As V a r i a n t ] , [ Q u a l i f i e r As L o n g ] ) Sub S e n d K e y i n ( K e y i n As S t r i n g ) Sub S e n d L a s t I n p u t O Sub SendMessageToApplication(MdlApp1ication As S t r i n g , Message As S t r i n g ) Sub S e n d R e s e t O Sub S e n d T e n t a t i v e P o i n t ( D a t a P 0 i n t As P o i n t 3 d , V i e w s p e c i f i e r As V a r i a n t )
DesignFile F u n c t i o n AddNewLevel(Leve1Name As S t r i n g ) As Level Sub A t t a c h C o l o r T a b l e(Co1 o r T a b l e As Col o r T a b l e ) P r o p e r t y A u t h o r As S t r i n g P r o p e r t y C l i e n t As S t r i n g Sub C l o s e 0 P r o p e r t y Comments As S t r i n g P r o p e r t y Company As S t r i n g l l F u n c t i o n CustomPropertyExists(Name As S t r i n g ) As B o o l e a n P r o p e r t y D a t e c r e a t e d As D a t e { r e a d - o n l y } P r o p e r t y D a t e L a s t P l o t t e d As D a t e { r e a d - o n l y } P r o p e r t y D a t e L a s t S a v e d As D a t e { r e a d - o n l y }
I The Microstation Object Model I
229
P r o p e r t y D e f a u l t M o d e l R e f e r e n c e As Model R e f e r e n c e {read-only} Sub D e l e t e L e v e l ( L e v e l As L e v e l ) P r o p e r t y D i m e n s i o n s t y l e s As D i m e n s i o n s t y l e s { read-on1y 1 P r o p e r t y E d i t o r As S t r i n g { r e a d - o n l y } F u n c t i o n E x t r a c t C o l o r T a b l e ( As Col o r T a b l e P r o p e r t y Fence As Fence { r e a d - o n l y } F u n c t i o n FindSavedView(NamePattern As S t r i n g , C P r e v i o u s l y F o u n d S a v e d V i e w As S a v e d V i e w E l e m e n t l , [Namespace As S t r i n g ] ) As SavedVi ewEl ement P r o p e r t y F o n t s As F o n t s { r e a d - o n l y } P r o p e r t y F o r m a t As M s d D e s i g n F i l e F o r m a t { r e a d only} P r o p e r t y F o r m a t M a j o r V e r s i o n As Long { r e a d only} P r o p e r t y F o r m a t M i n o r V e r s i o n As Long { r e a d only} P r o p e r t y F u l l N a m e As S t r i n g { r e a d - o n l y } F u n c t i o n G e t C u s t o m P r o p e r t y ( N a m e As S t r i n g ) As Variant F u n c t i o n GetElementByID(Element1D As DLong) As Element F u n c t i o n GetElementByID64(ElementID64 As Empty) As E l e m e n t F u n c t i o n G e t L a r g e s t E l e m e n t I D ( ) As DLong Sub G e t L a r g e s t E l e m e n t I D 6 4 0 P r o p e r t y I s A c t i v e As B o o l e a n {read-only} P r o p e r t y Keywords As S t r i n g P r o p e r t y L a s t S a v e d B y As S t r i n g P r o p e r t y L e v e l s As L e v e l s {read-only} P r o p e r t y L i n e s t y l e s As L i n e s t y l e s {read-only} P r o p e r t y Manager As S t r i n g F u n c t i o n M d l F i l e O b j P O As Long F u n c t i o n MdlModel R e f P ( As Long P r o p e r t y M o d e l s As M o d e l R e f e r e n c e s { r e a d - o n l y } P r o p e r t y Name As S t r i n g { r e a d - o n l y }
230
I Chapter 11 :The Microstation Object Model - Objects I P r o p e r t y NonModel E l e m e n t c a c h e As E l e m e n t c a c h e {read-only} P r o p e r t y P a t h As S t r i n g { r e a d - o n l y } P r o p e r t y R e v i s i o n N u m b e r As S t r i n g { r e a d - o n l y } Sub R e w r i t e L e v e l s ( ) Sub S a v e 0 Sub SaveAs ( NewFi 1 eName As S t r i n g , [ O v e r w r i t e As B o o l e a n = F a l s e ] , CNewFormat As MsdDesignFileFormat =
msdDesignFileFormatCurrentl) Sub S e t C u s t o m P r o p e r t y ( N a m e As S t r i n g , V a l u e As Variant P r o p e r t y S u b j e c t As S t r i n g P r o p e r t y T a g S e t s As T a g S e t s { r e a d - o n l y } P r o p e r t y T e x t s t y l e s As T e x t s t y l e s { r e a d - o n l y } P r o p e r t y T i t l e As S t r i n g P r o p e r t y T o t a l E d i t i n g T i m e As Long { r e a d - o n l y } P r o p e r t y ViewGroups As ViewGroups { r e a d - o n l y } P r o p e r t y Views As Views { r e a d - o n l y }
Element Sub A d d D a t a b a s e L i n k ( L i n k T 0 A d d As D a t a b a s e L i n k ) F u n c t i o n A d d T a g ( T a g D e f i n i t i 0 n As T a g D e f i n i t i o n ) As T a g E l e m e n t F u n c t i o n A d d T a g s ( T a g S e t As T a g S e t ) As TagEl e m e n t ( Sub A d d U s e r A t t r i b u t e D a t a ( A t t r i b u t e 1 D As Long, A t t r i b u t e D a t a As D a t a B l o c k ) F u n c t i o n A p p a r e n t C o l o r ( V i e w As V i e w ) As Long F u n c t i o n A p p a r e n t L i n e S t y l e ( V i e w As V i e w ) As L ineStyl e F u n c t i o n A p p a r e n t L i n e W e i g h t ( V i e w As V i e w ) As Long P r o p e r t y AsAppl ic a t i o n E l ement As Appl i c a t i o n E l e m e n t { r e a d - o n l y } P r o p e r t y A s A r c E l e m e n t As A r c E l e m e n t { r e a d only} P r o p e r t y AsAuxiliaryCoordinateSystemElement As AuxiliaryCoordinateSystemElement { r e a d - o n l y }
I The Microstation Object Model I
231
Property AsBsplineCurveElement As BsplineCurveElement {read-only} Property AsBsplineSurfaceElement A s BsplineSurfaceElement {read-only} Property AsCell Element As Cell Element {readonly} Property AsChainabl eEl ement A s ChainableElement {read-only} Property AsCl osedEl ement As C1 osedEl ement { read-on1y 1 Property AsCompl exEl ement A s Compl exEl ement { read-on1y } Property AsCompl exShapeEl ement As Compl exShapeEl ement {read-only} Property AsCompl exStringEl ement A s Compl exStringEl ement {read-only} Property AsConeElement As ConeElement {readonly} Property AsCurveElement A s CurveElement { read-on1y } Property AsDimensionElement As Di mensi onEl ement {read -on1y} Property AsDroppabl eEl ement A s Droppabl eEl ement {read-only} Property AsEllipseElement As EllipseElement { read-on1y 1 Property AsEll ipti cal El ement A s Elliptical Element {read-only} Property AsIntersectabl eEl ement As Intersectabl eEl ement {read-only} Property AsLineEl ement A s LineEl ement {readonly} Property AsMul ti LineEl ement As Multi LineElement {read-only} Property AsNamedGroupElement A s NamedGroupElement {read-only} Property AsPl anarEl ement As P1 anarEl ement { read-on1y 1 Property AsPointStringElement A s PointStringElement {read-only}
232
I Chapter 11 :The Microstation Object Model - Objects I Property AsPossi blyPl anarEl ement As PossiblyPl anarElement {read-only} Property AsSavedVi ewEl ement As SavedViewElement {read-only} Property AsShapeElement As ShapeElement {read-only} Property AsSharedCell Defi ni ti onEl ement As SharedCell DefinitionElement {read-only} Property AsSharedCell Element As SharedCell Element {read-only} Property AsTagElement As TagElement {readonly1 Property AsTextElement As TextElement {readonly1 Property AsTextNodeEl ement As TextNodeEl ement {read-only} Property AsTraversabl eEl ement As Traversabl eEl ement {read-only} Property AsVertexList As VertexList {readonly1 Property Cache As Elementcache {read-only} Property CacheIndex As Long {read-only} Property C1 ass As MsdEl ementCl ass Function C l o n e 0 As Element Property Color As Long Function ConstructVertexList(To1erance As Double) As P o i n t 3 d O Property DateLastModified As Date {read-only} S u b Del eteAl1 Tags ( ) S u b Del eteAl1 XData ( ) S u b DeleteTag(Tag As TagElement) S u b DeleteTagSet(TagSet As TagSet) Function DeleteUserAttributeData(Attribute1D As Long, Index As Integer) As Integer S u b DeleteXData(App1icationName As String) S u b DrawToFile(Fi1eName As String, Width As Long, Height As Long, CDrawBackGround As Boolean = False]) Property Fi 1 ePosi ti on As Long {read-only}
I The Microstation Object Model I
233
I4 F u n c t i o n GetContainingNamedGroupsO As NamedGroupEl e m e n t ( 1 I4 F u n c t i o n GetDatabaseLinks([DatabaseType As M s d D a t a b a s e L i n k a g e l , [ E n t i t y N u m b e r As L o n g ] ) As D a t a b a s e L i n k ( ) I4 F u n c t i o n G e t P i c t u r e ( W i d t h As Long, He g h t As Long, CDrawBackGround As B o o l e a n = Fa s e l l As Unknown I4 F u n c t i o n G e t R e l a t e d E l e m e n t s ( L o c k e d As 3001 e a n , C T r a v e r s e T y p e As MsdMemberTraverseType = msdMemberTraverseCopy1, [ N e w T r a v e r s a l As B o o l ean = T r u e 1 ) As E l e m e n t E n u m e r a t o r I4 F u n c t i o n G e t T a g ( T a g S e t As T a g S e t , TagName As S t r i n g ) As T a g E l e m e n t I4 F u n c t i o n G e t T a g s O As T a g E l e m e n t O I4 F u n c t i o n G e t U s e r A t t r i b u t e D a t a ( A t t r i b u t e 1 D As L o n g ) As D a t a B l o c k ( I4 F u n c t i o n GetXData(App1icationName As S t r i n g ) As XDatumO I4 F u n c t i o n GetXDataApplicationNamesO As String00 I4 P r o p e r t y G r a p h i c G r o u p As Long I4 F u n c t i o n H a s A n y D a t a b a s e L i n k s ( [ D a t a b a s e T y p e As M s d D a t a b a s e L i n k a g e l , [ E n t i t y N u m b e r As L o n g ] ) As B o o l e a n I4 P r o p e r t y HasAnyTags As B o o l e a n { r e a d - o n l y } I4 F u n c t i o n H a s A n y X D a t a O As B o o l e a n I4 F u n c t i o n HasXData(App1icationName As S t r i n g ) As B o o l e a n I4 P r o p e r t y I D As DLong { r e a d - o n l y } I4 P r o p e r t y I D 6 4 As Empty { r e a d - o n l y } I4 P r o p e r t y I n D i s p l a y S e t As B o o l ean I4 P r o p e r t y I s A p p l i c a t i o n E 1 ement As B o o l ean {read-on Y } I4 P r o p e r t y I s A r c E l ement As B o o l ean { r e a d - o n l y } I4 P r o p e r t y IsAuxiliaryCoordinateSystemElement As B o o l ean r e a d - o n 1 y } I4 P r o p e r t y I s B s p l i n e C u r v e E l ement As B o o l ean {read-on Y }
234
I Chapter 11:The Microstation Object Model - Objects I El P r o p e r t y I s B s p l i n e S u r f a c e E l e m e n t As Boolean {read-only} P r o p e r t y I s C e l l Element As Boolean { r e a d - o n l y } P r o p e r t y I s C h a i n a b l e E l e m e n t As Boolean { r e a d only} P r o p e r t y IsCl osedEl ement As Bool e a n { r e a d only1 P r o p e r t y IsCompl exEl ement As Bool e a n { r e a d only} P r o p e r t y IsCompl exShapeEl ement As Bool e a n {read-only} P r o p e r t y IsCompl e x S t r i ngEl ement As Bool e a n { read-on1y } P r o p e r t y IsComponentElement As Boo e a n { r e a d only1 P r o p e r t y IsConeEl ement As Bool e a n r e a d - o n 1 y } P r o p e r t y I s C u r v e E l ement As Bool e a n { r e a d - o n 1 y } P r o p e r t y I s D i m e n s i o n E l e m e n t As Boo e a n { r e a d only} P r o p e r t y I s D r o p p a b l eEl ement As Bool e a n { r e a d only1 P r o p e r t y I s E l 1 i pseEl ement As Bool e a n { r e a d only} P r o p e r t y I s E l 1 i p t i c a l El ement As Bool e a n {read-on Y} P r o p e r t y I s G r a p h i c a l As Boolean { r e a d - o n l y } P r o p e r t y I s H i d d e n As Boolean P r o p e r t y I s I n t e r s e c t a b l eEl ement As Bool e a n { r e a d - o n Yl P r o p e r t y I s L i n e E l e m e n t As Boolean { r e a d - o n l y } P r o p e r t y I s L o c k e d As Boolean P r o p e r t y I s M o d i f i e d As Boolean { r e a d - o n l y } P r o p e r t y IsMul t i Li neEl ement As Bool e a n { r e a d only} P r o p e r t y IsNamedGroupEl ement As Bool e a n {read-only} P r o p e r t y IsNew As Boolean { r e a d - o n l y } P r o p e r t y I s P l a n a r E l ement As Bool e a n { r e a d only1
I The Microstation Object Model I
235
Property IsPointStringEl ement As Bool ean { read-on1y 1 Property IsPossi blyPl anarEl ement A s Bool ean { read-on1y } Property IsSavedViewElement As Boolean {readonly} Property IsShapeElement A s Boolean {read-only} Property IsSharedCell DefinitionElement As Boolean {read-only} Property IsSharedCell Element A s Bool ean { read-on1y } Property IsSnappable As Boolean Property IsTagElement A s Boolean {read-only} Property IsTextElement As Boolean {read-only} Property IsTextNodeElement A s Boolean {readonly} Property IsTraversabl eEl ement As Bool ean { read-on1y 1 Property IsValid A s Boolean {read-only} Property IsVertexList As Boolean {read-only} Property Level A s Level Property Linestyle As Linestyle Property Lineweight A s Long Function Mdl El ementDescrP( [Detach As Bool eanl) As Long Sub Mirror(Point1 A s Point3d, Point2 A s Poi nt3d) Sub Mirror3d(PlanePointl As Point3d, Planepoint2 As Point3d, PlanePoint3 As Point3d) Property Model Reference A s Model Reference { read-on1y } Sub Move(0ffset As Point3d) Sub Parti a1 Del ete( Parti a1 1 A s El ement, Partial2 As Element, Point1 As Point3d, Point2 As Poi nt3d, Sel ector A s Poi nt3d, Vi ewSpeci fi er As Variant) Property Range As Range3d {read-only) Sub Redraw(CDrawMode A s MsdDrawingMode = msdDrawi ngModeNormal1)
236
I Chapter 11:The Microstation Object Model - Objects I Sub RemoveAll D a t a b a s e L i n k s ( ) F u n c t i o n RemoveDatabaseLink(DatabaseType As M s d D a t a b a s e L i n k a g e , [MSLinkNumber As L o n g ] , [ E n t i t y N u m b e r As L o n g ] ) As Long Sub R e w r i t e 0 Sub R o t a t e ( P i v o t As P o i n t 3 d , AboutX As D o u b l e , AboutY As D o u b l e , A b o u t Z As D o u b l e ) Sub R o t a t e A b o u t Z ( P i v o t As P o i n t 3 d , A n g l e As Doubl e 1 Sub S c a l e A l l ( O r i g i n As P o i n t 3 d , X F a c t o r As D o u b l e , Y F a c t o r As D o u b l e , Z F a c t o r As D o u b l e ) Sub S c a l e U n i f o r m ( 0 r i g i n As P o i n t 3 d , S c a l e F a c t o r As D o u b l e ) Sub SetXData(App1icationName As S t r i n g , NewXDataO As XDatum) P r o p e r t y S u b t y p e As M s d E l e m e n t S u b t y p e { r e a d only1 Sub T r a n s f o r m ( T r a n s f o r m 3 d As T r a n s f o r m 3 d ) P r o p e r t y Type As MsdElementType { r e a d - o n l y } P r o p e r t y URL As S t r i n g { r e a d - o n l y } P r o p e r t y U R L T i t l e As S t r i n g { r e a d - o n l y }
ElementEnumerator F u n c t i o n B u i l d A r r a y F r o m C o n t e n t s O As E l e m e n t 0 F u n c t i o n C l o n e 0 As E l e m e n t E n u m e r a t o r P r o p e r t y C u r r e n t As E l e m e n t { r e a d - o n l y } F u n c t i o n M o v e N e x t O As B o o l e a n Sub R e s e t ( )
Elementscancriteria Sub Sub Sub Sub Sub Sub Sub Sub
E x c l u d e A l 1 C1 a s s e s ( ) E x c l u d e A l 1 Col o r s ( ) Excl udeAl1 Level s ( ) E x c l u d e A l 1 L i n e S t y l es ( ) E x c l u d e A l 1 L i neWei g h t s ( ) Excl udeAl1Subtypes ( ) E x c l u d e A l 1 Types ( ) Excl udeGraphi c a l ( )
I The Microstation Object Model I
237
Sub Excl udeNonGraphi cal ( Sub Incl udeCl ass( El emCl ass A s MsdEl ementCl ass) Sub Incl udeCol or(Co1 orIndex As Long) Sub Incl udeLevel (Level A s Level ) Sub Incl udeLineSty1 e( LineStyl e As LineStyl e) Sub Incl u d e L i n e W e i g h t ( L i n e W e i g h t A s Long) Sub Incl udeOnlyCel1 (Cell Name As String) Sub IncludeOnlyFilePositionRange(Min A s Long, Max As Long) Sub Incl udeOnlyGraphi cGroup(Graphi cGroupNumber As Long) Sub Incl udeOnlyHol e( ) Sub IncludeOnlyInvisibleO Sub Incl udeOnlyLocked() Sub IncludeOnlyModified( Sub Incl udeOnlyModifiedRange(Min A s Date, [Max As Date]) Sub Incl udeOnlyNew( Sub Incl udeOnlyNonP1 anar( ) Sub Incl udeOnlyNonSnappab1 e( Sub Incl udeOnlyOl d( ) Sub Incl udeOnlyPl anar( Sub Incl udeOnlySnappab1 e( ) Sub Incl udeOnlySol id( Sub Incl udeOnlyUnl ocked( ) Sub IncludeOnlyUnmodified( 1 Sub IncludeOnlyUserAttribute(UserAttribute1D As Long) Sub IncludeOnlyVisibleO Sub Incl u d e O n l y W i t h i n R a n g e ( R a n g e A s Range3d) Sub IncludeSubtype(Long As Long) Sub Incl udeType(Type A s MsdEl ementType) Sub R e s e t 0 Level
Sub AddUserAttributeData(Attribute1D A s Long, Attri buteData A s DataBl ock)
238
I Chapter 11 :The Microstation Object Model - Objects I Function DeleteUserAttributeData(Attribute1D As Long, Index As Integer) As Integer Property Description As String Property El ementAccess As MsdLevel El ementAccess Property ElementColor As Long Property El ementLi neStyl e As Li neStyl e Property El ement Li neWei g h t As Long Function GetUserAttr buteData(Attribute1D As Long) As DataBl ock() Property ID As Long read-on1 y } Property IsActive As Bool ean Property IsDi spl ayed As Boolean Property IsDi spl ayedInVi e w As Bool ean Property IsEffectivelyDisplayedInView As Boolean {read-only} Property IsFromLevel Library As Boolean {readonly1 Property IsFrozen As Boolean Property IsInUse As Boolean {read-only} Function IsInUseWithinModel (Model As Model Reference) As Bool ean Property IsLocked As Boolean Property Name As String Property Number As Long Property OverrideColor As Long Property OverrideLi neStyl e As Li neStyl e Property OverrideLineWeight As Long Property ParentLevel As Level Property Plot As Boolean Property UsingOverrideColor As Boolean Property UsingOverrideLineStyle As Boolean Property UsingOverrideLineWeight As Boolean ModelReference Sub Activate0 S u b AddEl ement (El ement As El ement)
I The Microstation Object Model I
239
Sub A d d E l e m e n t s ( E l e m e n t s 0 As - E l e m e n t ) F u n c t i o n AddNewNamedGroup([Name As S t r i n g ] , [ D e s c r i p t i o n As S t r i n g ] ) As NamedGroupElement Sub A d d U s e r A t t r i b u t e D a t a ( A t t r i b u t e 1 D As Long, A t t r i b u t e D a t a As D a t a B l o c k ) P r o p e r t y AnyEl e m e n t s S e l e c t e d As B o o l ean { read-on1y } P r o p e r t y A s A t t a c h m e n t As A t t a c h m e n t { r e a d only} P r o p e r t y A t t a c h m e n t s As A t t a c h m e n t s { r e a d only} P r o p e r t y CanBePl acedAsCel1 As B o o l ean P r o p e r t y C e l l Type As MsdCel 1 Type P r o p e r t y C o n t r o l E l e m e n t C a c h e As E l e m e n t c a c h e { read-on1y 1 F u n c t i o n CopyEl e m e n t ( E l ement As E l e m e n t , CCopyContext As C o p y C o n t e x t l ) As E l e m e n t Sub Del e t e A l 1 X D a t a ( F u n c t i o n DeleteUserAttributeData(Attribute1D As Long, I n d e x As I n t e g e r ) As I n t e g e r Sub DeleteXData(App1icationName As S t r i n g ) P r o p e r t y D e s c r i p t i o n As S t r i n g P r o p e r t y D e s i g n F i l e As D e s i g n F i l e { r e a d - o n l y } F u n c t i o n D o u b l e T o W o r k i n g U n i t s ( V a 1 u e As D o u b l e ) As S t r i n g Function E l ementCacheContainingFi1ePosi t i o n ( F i l ePosi t i o n As Long, CCacheIndex As L o n g ] ) As E l ementcache F u n c t i o n GetElementByID(Element1D As DLong) As Element F u n c t i o n GetElementByID64(ElementID64 As Empty) As E l e m e n t F u n c t i o n G e t L a s t V a l i d G r a p h i c a 1 E l e m e n t ( ) As Element F u n c t i o n GetNamedGroup(GroupName As S t r i n g ) As NamedGroupEl ement F u n c t i o n G e t S e l e c t e d E l e m e n t s ( ) As E l ementEnumerator
240
I Chapter 11:The Microstation Object Model - Objects I El F u n c t i o n G e t S h e e t D e f i n i t i o n O As SheetDefinition El F u n c t i o n G e t U s e r A t t r i b u t e D a t a ( A t t r i b u t e 1 D As L o n g ) As D a t a B l o c k ( ) F u n c t i o n GetXData(App1icationName As S t r i n g ) As XDatumO El F u n c t i o n GetXDataApplicationNamesO As String( )( ) P r o p e r t y G1 o b a l Ori g i n As P o i n t 3 d { r e a d - o n l y } El P r o p e r t y G r a p h i c a l E l e m e n t c a c h e As E l e m e n t c a c h e {read-only} F u n c t i o n H a s A n y X D a t a O As B o o l e a n El F u n c t i o n HasXData(App1 ic a t i onName As S t r i n g ) As B o o l e a n P r o p e r t y I s 3 D As B o o l e a n { r e a d - o n l y } El P r o p e r t y I s A c t i v e As B o o l e a n { r e a d - o n l y } P r o p e r t y I s A t t a c h m e n t As B o o l e a n { r e a d - o n l y } El P r o p e r t y I s E l ementSel e c t e d As B o o l ean { r e a d only} P r o p e r t y I s L o c k e d As B o o l e a n El P r o p e r t y I s R e a d O n l y As B o o l e a n { r e a d - o n l y } P r o p e r t y L e v e l s As L e v e l s { r e a d - o n l y } El P r o p e r t y M a s t e r u n i t As M e a s u r e m e n t u n i t El F u n c t i o n M d l M o d e l R e f P O As Long El P r o p e r t y Name As S t r i n g P r o p e r t y P a r e n t M o d e l R e f e r e n c e As Model R e f e r e n c e {read-only} El Sub PropagateAnnotationScaleO F u n c t i o n Range( I n c l u d e A t t a c h m e n t s As Boo e a n ) As Range3d El Sub RemoveEl ement ( E l ement As E l e m e n t ) Sub Rep1 a c e E l ement ( 0 1 d E l ement As E l ement NewEl ement As E l e m e n t ) El F u n c t i o n S c a n ( [ S c a n C r i t e r i a As E l e m e n t S c a n C r i t e r i a l ) As E l e m e n t E n u m e r a t o r tN Sub S e l e c t E l ement ( E l ement As E l ement , [ D i s p l ayAsSel e c t e d As B o o l ean = T r u e ] )
I Review I
241
Sub S e t S h e e t D e f i n i t i o n ( N e w D e f i n i t i 0 n As SheetDefi n ition) Sub SetXData(App1icationName As S t r i n g , NewXDataO As XDatum) P r o p e r t y S t o r a g e u n i t As MeasurementUni P r o p e r t y S u b u n i t As M e a s u r e m e n t u n i t P r o p e r t y S u b U n i t s P e r M a s t e r U n i t As Doub e { read-on1y 1 P r o p e r t y Type As MsdModelType Sub U n s e l e c t A l 1 E l e m e n t s ( Sub U n s e l e c t E l e m e n t ( E l ement As E l e m e n t ) P r o p e r t y UORsPerMasterUni t As D o u b l e { r e a d only} P r o p e r t y UORsPerStorageUni t As D o u b l e P r o p e r t y UORsPerSubUnit As D o u b l e { r e a d - o n l y } F u n c t i o n W o r k i n g U n i t s T o D o u b l e ( V a 1 u e As S t r i n g ) As D o u b l e
We have just displayed a fraction of the Objects available to us in Microstation. At times it is useful to see a listing (even a partial listing) and browse through the items in it. The Object Browser in VBA is especially helpful when attempting to get a grasp on the Object Model of any Library. VBA includes other tools as well that can aid in our development efforts. These include adding Watches and the AutoList functionality.
.
12
The Microstation Object Model - Enums What is an Enum? Enum is an abbreviation for enumeration, a collection of constants that can be used in our code. Let’s consider the following enumeration:
MSDDESIGNFILEFORMAT msdDesi g n F i 1 e F o r m a t C u r r e n t msdDesi g n F i 1 eFormatDWG = 3 msdDesi g n F i 1 eFormatDXF = 4
msdDesignFileFormatUnknown msdDesi g n F i 1 eFormatV7 msdDesi g n F i 1 eFormatV8
= =
=
0
=
-1
1 2
The enumeration name is “MsdDesignFileFormat”.It has six members with values ranging from - 1 to 4. Each member in an enumeration has a name and a value. The enumeration member “msdDesignFileFormatCurrent” has a value of 0. As we saw in the previous chapter, some properties and methods make use of these enumerations. For example,
243
244
I Chapter 12: The Microstation Object Model - Enums I S u b SaveAs(NewFi1eName As String, [Overwrite As
-
Boo1 ean = Fa1 sel , [NewFormat A s MsdDesignFi 1 eFormat = msdDesignFileFormatCurrent1)
-
The SaveAs method is found under the DesignFile object. When we use it, we can specify a file name, whether an existing file should be overwritten, and the file format to be used. The data type for “NewFileName” is String. The value type for “Overwrite” is Boolean. The value type for “NewFormat” is MsdDesignFileFormat. The “NewFormat” parameter utilizes the “MsdDesignFileFormat” enumeration. As we use the SaveAs method, we see the following: ACtiveDeSignFile. S P Y ~ A S “test.dgn”, True,
While working in VBA, when we come to a parameter that utilizes an enumeration, we see the list of the members of that enumeration. We are not shown the value of each member. Enumerations provide several benefits, with one of the largest being that we can more easily see the desired parameter results as we look at our code. In other words, seeing “msdDesignFileFormatDWG” is clearer than seeing the number 3 in the NewFormat parameter. There are two ways to use enumerators. One is to use the Name.Member format, such as: A c t i v e D e s i g n F i 1e.SaveAs “ t e s t . d g n ” , T r u e ,
~
MsdDesignFileFormat.msdDesignFi1eFormatDWG
The other way is to use the member name without the enumerator name: ActiveDesignFi1e.SaveAs
“test.dgn”,
T r u e , rnsdDesignFileFormatDWG
Names of enumeration members often begin with the enumeration name or a shortened version of the enumeration name. The above examples use the “MsdDesignFileFormat” enumeration with the “msdDesignFileFormatDWG” member. Notice how the enumeration name is used to begin the member name. Occasionally, an abbreviation is used such as with the “MsdCoordinateAccuracy”enumeration. The members of this enumeration begin with “msdAccuracy” instead of the full enumeration name “msdCoordinateAccuracy’: It should be noted
I The Enumeration List I
245
that all Microstation enumerations begin with the three-letter designation “M sd and all member names begin with “msd. Now that we have discussed what enumerations are and how they can be used, let’s examine the enumerations available in Microstation VBA.
THEENUMERATIONLIST MsdACSType msdACSTypeCy1 i ndri cal = 2 msdACSTypeNone = 0 msdACSTypeRectangu1 ar = 1 msdACSTypeSpheri cal = 3 MsdAddAttachmentFlags msdAddAttachmentElementsVisible
=
4
msdAddAttachmentFlagCoincidentWorld msdAddAttachmentFlagNone = 0
msdAddAttachmentFlagTrueScale MsdAngleAccuracy msdAngl eAccuracyO msdAngl eAccuracy1 msdAngl eAccuracy2 msdAngl eAccuracy3 msdAngl eAccuracy4 msdAngl eAccuracy5 msdAngl eAccuracy6 msdAngl eAccuracy7 msdAngl eAccuracy8
= = = = = = = = =
0 1 2 3 4 5 6 7 8
MsdAngleFormat msdFormatDD-0000 = 0 msdFormatDD-MM-SS = 1 msdFormatGradians = 2 msdFormatRadi ans = 3
=
1
=
2
246
I Chapter 12: The Microstation Object Model - Enums I MsdAngleMode msdAngl eModeAzimuth = 1 msdAngl eModeBeari ng = 2 msdAngl eModeConventi onal MsdAttachMode msdAttachNone = 1 msdAttachReference
=
=
0
3
MsdBsplineCurveOffsetCuspType msdBsplineCurveOffsetCuspArc = 4 msdBsplineCurveOffsetCuspChamfer = 1 msdBsplineCurveOffsetCuspJump = 0 msdBsplineCurveOffsetCuspParabola = 3 msdBsplineCurveOffsetCuspPoint = 2 MsdBsplineCurveType msdBspl i neCurveCi rcl e = 3 msdBspl i neCurveCi rcul arArc = 2 msdBsplineCurveEllipse = 5 msdBspl i neCurveEl1 i pti cal Arc = 4 msdBsplineCurveGenera1 = 0 msdBsplineCurveHyperbolicArc = 7 msdBsplineCurveLine = 1 msdBsplineCurveParabolicArc = 6 MsdBsplineParametrizationType msdBspl ineparametri zati onCentri petal = 2 msdBsplineParametrizationChordLength = 1 msdBsplineParametrizationInherited = 3 msdBspl ineparametri zati onUni form = 0 MsdBsplineSurfaceDirection msdBsplineSurfaceU = 0 msdBsplineSurfaceV = 1 MsdBsplineSurfaceType msdBsplineSurfaceCone = 3 msdBsplineSurfaceGenera1 = 0
I The Enumeration List I
247
msdBspl i neSurfaceP1 ane = 1 msdBsplineSurfaceRevolution = 6 msdBsplineSurfaceRightCylinder = 2 msdBsplineSurfaceRuledSurface = 8 msdBsplineSurfaceSphere = 4 msdBsplineSurfaceTabCylinder = 7 msdBsplineSurfaceTorus = 5 MsdCadInputType msdCadInputTypeAny = 5 msdCadInputTypeCommand = 1 msdCadInputTypeDataPoint = 3 msdCadInputTypeKeyin = 4 msdCadInputTypeReset = 2 msdCadInputTypeUnassignedCB = 6 MsdCelIType msdCel1 TypeGraphi c = 0 msdCellTypeMenu = 1 msdCel1 TypePoi n t = 7 MsdChangePropagation msdChangePropagationAlways = 2 msdChangePropagationGroupLock = 0 msdChangePropagationNever = 1 MsdChangeTrackAction msdChangeTrackActionAdd = 2 msdChangeTrackActionAppData = 8 msdChangeTrackActionDelete = 1 msdChangeTrackActionDrop = 6 msdChangeTrackActionMark = 7 msdChangeTrackActionModelAdd = 9 m s d C h a n g e T r a c k A c t i o n M o d e 1 Delete = 10 msdChangeTrackActionModify = 3 msdChangeTrackActionModifyFence = 5 msdChangeTrackActionNewFilePositionAndModify
=
4
248
I Chapter 12: The Microstation Object Model - Enums I MsdCom mandResuIt msdCommandResu1 t3dLi brary2dFi 1 e = 50 msdCommandResu1 t3dOnly = 39 msdCommandResultAcceptQuery = 68 msdCommandResu1 tBadCel1 Name = 47 msdCommandResu1 tCel1 Deleted = 59 msdCommandResu1 tCel1 Exists = 55 msdCommandResu1 tCel1 Li braryNotFound = 1 2 msdCommandResultCellNestingError = 43 msdCommandResultCellNotFound = 44 msdCommandResu1 tEl ementNotFound = 21 msdCommandResu1 tEmptyFence = 27 msdCommandResu1 tFi 1 eReadOnly = 287 msdCommandResultIllegalDefinition = 23 msdCommandResultInvalidReferenceOperation msdCommandResultNeedCharacters = 27 msdCommandResultNoActiveCel1 = 19 msdCommandResu1 tNoCel1 Library = 54 msdCommandResultNoFenceActive = 15 msdCommandResu1 tNoOri gi n = 56 msdCommandResu1 tOffDesignP1 ane = 22 msdCommandResultReferenceNotFound = 7 msdCommandResultSuccess = 0 msdCommandResultUnknownCommand = 16 msdCommandResu1 tVi ewNotFound = 1 8 MsdConversionMode msdConversionModeAlways = 1 msdConversionModeNever = 0 msdConversionModePrompt = 2 MsdCoordinateAccuracy msdAccuracy0 = 1 msdAccuracyl = 2 msdAccuracyl6th = 56 msdAccuracy2 = 3 msdAccuracy3 = 4
=
481
I The Enumeration List I
249
msdAccuracy32nd = 120 msdAccuracy4 = 5 msdAccuracy4th = 8 msdAccuracy5 = 6 msdAccuracy6 = 7 m s d A c c u r a c y 6 4 t h = 248 m s d A c c u r a c y 8 t h = 24 msdAccuracyHa1 f = 0
MsdCoordinateFormat msdMasterUnits = 1 msdSubUnits = 0 msdWorkingUnits = 2
MsdCopyContextLevelOption msdCopyContextLeve1 AlreadyRemapped = 4 msdCopyContextLeve1 B y U s e r P r e f e r e n c e = 0 msdCopyContextLevelCopyAlways = 3 msdCopyContextLevelCopyIfDifferent = 2 msdCopyContextLevelCopyIfNotFound = 1
MsdCopyViewPort
msdCopyViewPortApplyAspectRatio = 2 msdCopyViewPortApplySize = 3 msdCopyViewPortApplySizeAndPosition msdCopyViewPortKeepCurrent = 0
=
4
MsdDatabaseLinkage msdDatabaseLinkageInformix = 1 m s d D a t a b a s e L i n k a g e I n g r e s = 32 msdDatabaseLinkageOdbc = 1 2 8 msdDatabaseLinkageO1 eDb = 256 msdDatabaseLinkageOrac1e = 8 msdDatabaseLinkageXBase = 4
MsdDataEntryRegionJustification msdDataEntryRegionJustificationCenter = 0 msdData E n t r y Regi o n J u s t i f ica t i on L e f t = - 1 msdData E n t r y Regi on J u s t if ic a t ion R i g h t = 1
250
I Chapter 12: The Microstation Object Model - Enums I MsdDesignFileFormat msdDesignFileFormatCurrent msdDesignFileFormatDWG msdDesignFileFormatDXF
= =
= =
0
=
-1
3 4
msdDesignFileFormatUnknown msdDesignFileFormatV7 msdDesignFileFormatV8
=
1 2
MsdDevelopableElementOutputType msdDevel o p a b l eCones = 4 msdDevel o p a b l eConesPl a n a r = 5 msdDevel o p a b l eRul e L i n e s = 0 msdDevel o p a b l eRul e L i n e s P l a n a r = 1 msdDevel o p a b l eShapes = 2 msdDevel o p a b l eShapesPl a n a r = 3
MsdDialogBoxResult msdDi a1 ogBoxResul t A p p l y = 1 msdDi a1 ogBoxResul t C a n c e l = 4 msdDi a1 ogBoxResul t D e f a u l t = 5 msdDi a1 ogBoxResul t H e l p = 1 0 msdDi a1 ogBoxResul t N o = 7 msdDi a1 ogBoxResul t O K = 3 msdDi a1 ogBoxResul t R e s e t = 2 msdDi a1 ogBoxResul t R e t r y = 8 msdDi a1 ogBoxResul t S t o p = 9 msdDi a1 ogBoxResul t Y e s = 6 msdDi a1 ogBoxResul t Y e s T o A l 1 = 11
MsdDimAccuracy msdDimAccuracy0 = 0 msdDimAccuracy1 = 129 msdDimAccuracyl6th = 8 msdDimAccuracy2 = 1 3 0 msdDimAccuracy3 = 1 3 2 msdDimAccuracy32nd = 1 6 msdDimAccuracy4 = 1 3 6 msdDimAccuracy4th = 2
I The Enumeration List I
251
msdDimAccuracy5 = 144 msdDimAccuracy6 = 160 msdDimAccuracy64th = 32 msdDimAccuracy7 = 192 msdDimAccuracy8 = 128 msdDimAccuracy8th = 4 msdDimAccuracyHa1 f = 1 msdDimAccuracySci 1 = 64 msdDimAccuracySci2 = 65 msdDimAccuracySci3 = 66 msdDimAccuracySci4 = 67 msdDimAccuracySci5 = 68 msdDimAccuracySci 6 = 69 msdDimAccuracySci7 = 70 msdDimAccuracySci8 = 7 1
MsdDimAlignment msdDimAl ig n m e n t A r b i t r a r y = 3 msdDimAlignmentDrawing = 1 msdDimAlignmentTrue = 2 msdDimAlignmentView = 0
MsdDimAlternateThresholdComparison MsdDimAlternateThresholdComparisonGreater
1 MsdDimAl t e r n a t e T h r e s h o l d C o m p a r i s o n G r e a t e r O r E q u a 1 MsdDimAlternateThresholdComparisonLess = 0 MsdDimAl t e r n a t e T h r e s h o 1 dCompari sonLessOrEqual
MsdDimAngleMeasure MsdDimAngl eMeasureAng1 e = 1 MsdDimAngl e M e a s u r e A r c L e n g t h
=
0
MsdDimBallAndChainAlignment msdDimBallAndChainAlignmentAuto msdDimBallAndChainAlignmentLeft
=
0
1 msdDimBallAndChainA1 i g n m e n t R i g h t = 2 =
=
=
=
3
2
252
I Chapter 12: The Microstation Object Model - Enums I MsdDimBallAndChainChainType msdDimBall AndChainChainTypeArc = 2 m s d D i m B a l l AndChainChainTypeBSpline
msdDimBallAndChainChainTypeLine
=
m s d D i m B a l l AndChainChainTypeNone
=
MsdDimCustomSymbol msdDimCustomSymbolCharacter msdDimCustomSymbo1 D e f a u l t
=
=
=
3
1 0
1
0
MsdDimDMSPrecisionMode MsdDimDMSPreci s i onModeFixed = 0 MsdDimDMSPreci s i onModeFl o a t i n g = 1
MsdDimLabe1LineFormat 3 5 MsdDimLabel L i neFormatAng1 e O v e r L e n g t h = 1 MsdDimLabel L i n e F o r m a t L e n g t h A b o v e = 2 MsdDimLabel L i n e F o r m a t L e n g t h A n g 1 eAbove = 6 MsdDimLabel L i n e F o r m a t L e n g t h A n g 1 e B e l ow = 7 MsdDimLabel L i n e F o r m a t L e n g t h B e l ow = 4 MsdDimLabel L i n e F o r m a t S t a n d a r d = 0 MsdDimLabel L i neFormatAng1 eAbove MsdDimLabel L i neFormatAng1 e B e l ow
= =
MsdDimMLNoteFrameType msdDimMLNoteFrameTypeBox = 2 msdDimMLNoteFrameTypeLine = 1 msdDimMLNoteFrameTypeNone = 0 MsdDimMLNoteJustification msdDimMLNoteJusti f i c a t i o n c e n t e r = 3 msdDimMLNoteJusti f i c a t i onDynami c = 2 msdDimMLNoteJustificationLeft = 0 msdDimMLNoteJustificationRight = 1
MsdDimNoteHorizontalAttachment msdDimNoteHorizontalAttachmentAuto msdDimNoteHorizontalAttachmentLeft
=
0
=
1
I The Enumeration List I
253
msdDimNoteHorizontalAttachmentRight
=
2
MsdDimNoteLeaderType MsdDimNoteLeaderTypeCurve = 1 MsdDimNoteLeaderTypeLine = 0
MsdDimNoteTextRotation msdDimNoteTextRotationHorizonta1
msdDimNoteTextRotationInline
=
2
msdDirnNoteTextRotationVertica1
=
=
0
1
MsdDimNoteVerticaIAttachment msdDimNoteVertica1AttachmentBottom = 4 msdDimNoteVertica1AttachmentBottomLine = 3 msdDimNoteVerticalAttachmentDynamicCorner = 6 msdDimNoteVerticalAttachmentDynamicLine = 5 msdDimNoteVerti c a l AttachmentMiddl e = 2 msdDimNoteVertica1AttachmentTop = 0 msdDimNoteVerticalAttachmentTopLine = 1 msdDimNoteVertica1Attachmentunderline = 7
MsdDimNoteVerticalJustification msdDimNoteVertica1 J u s t i f i c a t i o n B o t t o m = 2 msdDimNoteVerti c a l J u s t i f i c a t oncenter = 1 m s d D i m N o t e V e r t i c a 1 J u s t i f i c a t onDynamic = 3 m s d D i m N o t e V e r t i c a l J u s t i f i c a t onTop = 0
MsdDimPlacementTextPosition msdDimPlacementTextPos t i o n A u t o = 2 msdDimPlacementTextPos t i o n M a n u a l = 0 msdDimPlacementTextPos t i o n S e m i A u t o = 1
MsdDimRadialMode msdDimRadi a1 M o d e c e n t e r M a r k = 0 msdDimRadi a1 ModeDi a m e t e r = 3 msdDimRadi a1 ModeDi a m e t e r E x t e n d e d = 4 msdDimRadi a1 ModeRadi us = 1 msdDimRadi a1 ModeRadi u s E x t e n d e d = 2
254
I Chapter 12: The Microstation Object Model - Enums I MsdDimStackedFractionAlignment MsdDimStackedFractionAlignmentBottom = 2 MsdDimStackedFractionAlignmentCenter = 1 MsdDimStackedFractionAlignmentTop = 0 MsdDimStackedFractionType MsdDimStackedFractionTypeDiagonal = 2 MsdDimStackedFractionTypeFromFont = 0 MsdDimStackedFractionTypeHorizontal = I
MsdDimStyleProp msdDimStylePropBal1 AndChainAlignment = 101 msdDimStylePropBallAndChainChainTerminator = 1 0 2 msdDimStylePropBal1 AndChainChainType = 1 0 3 msdDimStylePropBallAndChainIsActive = 1 0 4 msdDimSty1 ePropBal1 AndChai nNoDockOnDimLi ne = 1 0 6 msdDimStylePropBal1 AndChainShowTextLeader = 1 0 5 msdDimSty1 ePropExtensi onLi neAngl eChordAl i gn = 2 1 3 msdDimStyl ePropExtensi onLi neCol or = 201 msdDimStylePropExtensionLineExtend = 202 msdDimStyl ePropExtensi onLi neJoi n = 203 msdDimSty1 ePropExtensi onLi neLeft = 204 msdDimStyl ePropExtensi onLi neLi neStyl e = 205 msdDimStylePropExtensionLineOffset = 206 msdDimStyl ePropExtensi onLi neOverrideCo1 or = 207 msdDimSty1 ePropExtensi onLi neOverrideLi neStyl e = 208 msdDimStyl ePropExtensi onLi neOverri deWei ght = 209 msdDimSty1 ePropExtensi onLi neRi ght = 2 1 0 msdDimStyl ePropExtensi onLi neShowAny = 211 msdDimSty1 ePropExtensi onLi neWei ght = 2 1 2 msdDimStylePropGenera1 Alignment = 3 0 1 msdDimStylePropGenera1 CenterMarkSize = 302 msdDimStylePropGenera1 Color = 303 msdDimStylePropGenera1 Dimensionscale = 304 msdDimStyl ePropGenera1 DimStyl eDescri pti on = 305 msdDimStylePropGenera1 DimStyleName = 306 msdDimStylePropGeneralFont = 307
I The Enumeration List I
255
msdDimStyl ePropGenera1 I g n o r e L e v e l Symbol ogy = 308 msdDimStyl ePropGenera1 L i n e S t y l e = 309 msdDimStyl ePropGenera1 O v e r r i d e C o l o r = 310 msdDimStyl ePropGenera1 O v e r r i d e L i n e S t y l e = 311
msdDimStylePropGeneralOverrideWeight = 312 msdDimStyl ePropGenera1 Radi a 1 Mode = 313 msdDimStyl ePropGenera1 Re1 a t i v e D i m L i n e = 314 m s d D i m S t y l e P r o p G e n e r a l S h o w C e n t e r M a r k = 315 msdDimStylePropGeneralStacked = 316 msdDimStylePropGeneralStackOffset = 317 msdDimStyl ePropGenera1 W e i g h t = 318 msdDimStylePropInva1 i d = 0 msdDimStyl ePropMLNoteE1 bowLength
108 msdDimStylePropMLNoteFrameType = 401 m s d D i m S t y l e P r o p M L N o t e H o r A t t a c h m e n t = 407 msdDimStylePropMLNoteJustification = 402 m s d D i m S t y l e P r o p M L N o t e L e a d e r T y p e = 405 m s d D i m S t y l e P r o p M L N o t e L e f t M a r g i n = 410 m s d D i m S t y l e P r o p M L N o t e L o w e r M a r g i n = 411 m s d D i m S t y l e P r o p M L N o t e S h o w L e a d e r = 403 m s d D i m S t y l e P r o p M L N o t e T e x t R o t a t i o n = 406 =
m s d D i m S t y l e P r o p M L N o t e V e r L e f t A t t a c h m e n t = 408 m s d D i m S t y l e P r o p M L N o t e V e r R i g h t A t t a c h m e n t = 409 m s d D i m S t y l e P r o p M L N o t e V e r t i c a 1 J u s t i f i c a t i o n = 404
msdDimStylePropPlacementAnnotationScale = 507 msdDimStylePropPlacementCompatibleV3 = 501 msdDimStyl ePropPl acementLeve1 = 502 msdDimSty1 e P r o p P l acementNotUseMode1 A n n o t a t i o n S c a l e
=
msdDimStyl ePropPl a c e m e n t o v e r r i d e L e v e l = 503 msdDimStyl ePropPl a c e m e n t T e x t P o s i t i o n = 504
msdDimStylePropPlacementUseReferenceScale
=
601 602 msdDimStylePropSymbolDiameterType = 603 msdDimStylePropSymbo1 L o w e r P r e f i x C h a r = 604 msdDimStylePropSymbo1 L o w e r S u f f i x C h a r = 605 msdDimStylePropSymbo1 D i a m e t e r c h a r msdDimStylePropSymbo1 D i a m e t e r F o n t
= =
505
506
256
I Chapter 12: The Microstation Object Model - Enums I msdDimStylePropSymbolMainPrefixChar = 606 msdDimSty1 ePropSymbo1 Mai nSuffixChar = 607 msdDimStyl ePropSymbo1 P1 usMi nusChar = 608 msdDimSty1 ePropSymbo1 P1 usMi nusType = 609 msdDimStyl ePropSymbo1 Prefix = 610 msdDimStylePropSymbolPrefixCellName = 611 msdDimStyl ePropSymbo1 Prefixchar = 612 msdDimSty1 ePropSymbo1 Prefi xFont = 613 msdDimStylePropSymbo1PrefixType = 614 msdDimStylePropSymbo1Suffix = 615 msdDimStylePropSymbo1Suffixcell Name = 616 msdDimStylePropSymbolSuffixChar = 617 msdDimStyl ePropSymbo1 Suffi xFont = 618 msdDimSty1 ePropSymbo1 Suffi xType = 619 msdDimStyl ePropSymbo1 To1 Prefixchar = 620 msdDimSty1 ePropSymbo1 To1 Suffixchar = 621 msdDimStylePropSymbolUpperPrefixChar = 622 msdDimStylePropSymbo1UpperSuffixChar = 623 msdDimStylePropTerminatorArrowCellName = 701 msdDimStylePropTerminatorArrowChar = 702 msdDimStylePropTerminatorArrowFont = 703 msdDimStylePropTerminatorArrowhead = 729 msdDimStylePropTerminatorArrowType = 704 msdDimStylePropTerminatorColor = 705 msdDimStylePropTerminatorDotCellName = 706 msdDimStylePropTerminatorDotChar = 707 msdDimStylePropTerminatorDotFont = 708 msdDimStylePropTerminatorDotType = 709 msdDimStylePropTerminatorFirst = 710 msdDimStylePropTerminatorHeight = 711 msdDimStylePropTerminatorJoint = 712 msdDimStylePropTerminatorLeft = 713 msdDimStylePropTerminatorLineStyle = 714 msdDimStylePropTerminatorMinLeader = 715 msdDimStylePropTerminatorMode = 716 msdDimStylePropTerminatorNoLineThruArrow = 717
I The Enumeration List I m s d D i m S t y l e P r o p T e r m i n a t o r N o L neThruDot = 718 m s d D i m S t y l e P r o p T e r m i n a t o r N o L neThruOrigin = 719 m s d D i m S t y l e P r o p T e r m i n a t o r N o L neThruStroke = 720
msdDimStylePropTerminatorNote = 736 msdDimStylePropTerminatorNoteCel1 Name = 738 msdDimStylePropTerminatorNoteChar = 739 m s d D i m S t y l e P r o p T e r m i n a t o r N o t e F o n t = 740 msdDimStylePropTerminatorNoteType = 737 msdDimStylePropTerminatorOriginCellName = 721 m s d D i m S t y l e P r o p T e r m i n a t o r O r i g i n C h a r = 722 m s d D i m S t y l e P r o p T e r m i n a t o r O r i g i n F o n t = 723 msdDimStylePropTerminatorOriginType = 724 msdDimStylePropTerminatorOverrideColor = 725 msdDimStylePropTerminatorOverrideLineStyle = 726 m s d D i m S t y l e P r o p T e r m i n a t o r O v e r r i d e W e i g h t = 727 m s d D i m S t y l e P r o p T e r m i n a t o r R i g h t = 728 msdDimStylePropTerminatorStrokeCellName = 730 m s d D i m S t y l e P r o p T e r m i n a t o r S t r o k e C h a r = 731 m s d D i m S t y l e P r o p T e r m i n a t o r S t r o k e F o n t = 732 msdDimStylePropTerminatorStrokeType = 733 m s d D i m S t y l e P r o p T e r m i n a t o r W e i g h t = 734 msdDimStylePropTerminatorWidth = 735 msdDimStylePropTextArcLengthSymbo1 = 801 m s d D i m S t y l e P r o p T e x t A u t o L i f t = 802 msdDimStylePropTextCapsule = 804 msdDimStyl ePropTextCo1 or = 805 msdDimStylePropTextDecimalComma = 806 msdDimStylePropTextFont = 808 msdDimStylePropTextFrameType = 837 msdDimStylePropTextHeight = 809 msdDimStyl ePropTextHorizonta1 = 810 msdDimStylePropTextHorizontalMargin = 811 msdDimStylePropTextInlineTextLift = 838 msdDimStylePropTextJustification = 812 msdDimStylePropTextLeadingZero = 813 msdDimStylePropTextLocation = 835
257
258
I Chapter 12: The Microstation Object Model - Enums I msdDimStyl ePropTextOmi tLeadi ngDel imi ter = 815 msdDimStylePropTextOverrideColor = 8 1 6 msdDimStylePropTextOverrideHeight = 8 1 7 msdDimStylePropTextOverrideStackedFractions = 833 msdDimStylePropTextOverrideUnderline = 8 3 4 msdDimStylePropTextOverrideWeight = 818 msdDimStylePropTextOverrideWidth = 8 1 9 msdDimStylePropTextSecLeadingZero = 8 2 0 msdDimStylePropTextShowSecondary = 8 2 1 msdDimStylePropTextStackedFractionAlignment = 8 2 9 msdDimStylePropTextStackedFractions = 8 3 0 msdDimStylePropTextStackedFractionScale = 8 3 2 msdDimStylePropTextStackedFractionType = 831 msdDimStylePropTextSuperscriptMode = 8 3 9 msdDimStylePropTextTextStyle = 8 2 7 msdDimStylePropTextTextStyleID = 8 2 8 msdDimStylePropTextUnderline = 8 2 2 msdDimStylePropTextVerticalMargin = 8 2 4 msdDimStylePropTextVerticalOpts = 8 3 6 msdDimStylePropTextWeight = 8 2 5 msdDimStylePropTextWidth = 8 2 6 msdDimStylePropToleranceAccuracy = 9 1 0 msdDimStylePropToleranceLowerValue = 9 0 1 msdDimStylePropToleranceMode = 9 0 2 msdDimStylePropToleranceSecAccuracy = 9 1 1 msdDimStylePropToleranceShow = 9 0 3 msdDimStylePropToleranceStackEqua1 = 9 0 4 msdDimSty1 ePropTol eranceTextHori zontal Margi n = 9 0 5 msdDimStylePropToleranceTextScale = 9 0 6 msdDimStylePropToleranceTextVerticalMargin = 9 0 7 msdDimStyl ePropTol eranceTextVerti cal Separation = 908 msdDimStylePropToleranceUpperValue = 9 0 9 msdDimStylePropValueAccuracy = 1 0 0 1 msdDimStylePropValueAltAccuracy = 1 0 0 2 msdDimStyl ePropVal ueAl tFormat = 1 0 6 7 msdDimStylePropValueAltIsActive = 1 0 0 3
I The Enumeration List I
259
m s d D i m S t y l e P r o p V a l u e A l t S e c A c c u r a c y = 1004 m s d D i m S t y l e P r o p V a l u e A l t S e c F o r m a t = 1069 m s d D i m S t y l e P r o p V a l u e A l t S e c I s A c t i ve = 1005
msdDimStylePropValueAltSecShowZeroMasterUnit = 1012 msdDimStylePropValueAltSecShowZeroSubUnit = 1081 = 1013 msdDimStylePropValueAltSecThresholdComparison = 1071 msdDimStylePropValueAltShowZeroMasterUnit = 1020 msdDimStylePropValueAltShowZeroSubUnit = 1079 m s d D i m S t y l e P r o p V a l u e A l t T h r e s h o l d = 1021 m s d D i m S t y l e P r o p V a l u e A l t T h r e s h o l dCompari s o n = 1070 m s d D i m S t y l e P r o p V a l ueAngl e F o r m a t = 1023 msdDimStylePropValueAngleLeadingZero = 1024 m s d D i m S t y l e P r o p V a l ueAngl eMeasure = 1025 m s d D i m S t y l e P r o p V a l ueAngl e P r e c i s i o n = 1026 msdDimStylePropValueAngleTrailingZeros = 1027 msdDimStylePropValueDMSPrecisionMode = 1082 msdDimStylePropValueFormat = 1066 m s d D i m S t y l e P r o p V a l u e L a b e l L i n e F o r m a t = 1077 m s d D i m S t y l e P r o p V a l u e N o R e d u c e A l t F r a c t i o n = 1043 msdDimStylePropValueNoReduceAltSecFraction = 1062 m s d D i m S t y l e P r o p V a l u e N o R e d u c e F r a c t i o n = 1042 m s d D i m S t y l e P r o p V a l u e N o R e d u c e S e c F r a c t i on = 1061 m s d D i m S t y l e P r o p V a l u e N o R e d u c e T o l F r a c t i o n = 1044 m s d D i m S t y l e P r o p V a l ueNoReduceTo1 S e c F r a c t i on = 1063 m s d D i m S t y l e P r o p V a l ueOrdDatumVa1 ue = 1057 m s d D i m S t y l e P r o p V a l u e O r d D e c r e m e n t R e v e r s e = 1055 m s d D i m S t y l e P r o p V a l u e O r d F r e e L o c a t i o n = 1065 msdDimStylePropValueOrdUseDatumValue = 1056 m s d D i m S t y l e P r o p V a l ueRoundLSD = 1028 msdDimStylePropValueSecAccuracy = 1029 msdDimStylePropValueSecFormat = 1068 msdDimStylePropValueSecShowTrailingZeros = 1033 msdDimStylePropValueSecShowZeroMasterUnit = 1035 msdDimStylePropValueSecShowZeroSubUnit = 1080 msdDimStylePropValueSecUnitMaster = 1075
msdDimStyl ePropVal ueAl tSecThresho1 d
260
I Chapter 12: The Microstation Object Model - Enums I msdDimStylePropValueSecUnitSub = 1076 msdDimStylePropValueShowTrailingZeros = 1039 msdDimStylePropValueShowZeroMasterUnit = 1041 msdDimStylePropValueShowZeroSubUnit = 1078 msdDimStylePropValueSuperscriptLSD = 1045 msdDimStylePropValueThousandsOpts = 1072 msdDimStylePropValueUnit = 1048 msdDimStylePropValueUnitLabelMaster = 1049 msdDimStylePropValueUnitLabe1 SecMaster = 1050 msdDimStylePropValueUnitLabelSecSub = 1051 msdDimStyl ePropVal ueUni tLabel Sub = 1052 msdDimStylePropValueUnitMaster = 1073 msdDimStylePropValueUnitSec = 1053 msdDimStylePropValueUnitSub = 1074 msdDimStylePropValueUseWorkingUnits = 1054 MsdDimSuperscriptMode MsdDimSuperScriptModeFromFont = 0 MsdDimSuperScriptModeGenerated = 1 MsdDimSymbolType msdDimSymbol TypeCell = 2 msdDimSymbolTypeCharacter = 1 msdDimSymbolTypeDefau1 t = 0 MsdDimTerminatorArrowhead msdDimTerminatorArrowheadClosed = 1 msdDimTerminatorArrowheadFilled = 2 msdDimTerminatorArrowheadOpen = 0 MsdDimTerminatorMode msdDimTerminatorModeAuto = 0 msdDimTerminatorModeInside = 2 msdDimTerminatorModeOutside = 3 msdDimTerminatorModeReversed = 1 MsdDimTerminatorType msdDimTerminatorTypeArrow
=
1
I The Enumeration List I
261
msdDimTermi n a t o r T y p e C i r c l e = 3 msdDimTerminatorTypeDot = 4
msdDimTerminatorTypeNone = 0 msdDimTerminatorTypeNote = 5 msdDimTerminatorTypeOrigin = 3 msdDimTerminatorTypeStroke = 2 MsdDimTextField msdDimTextFie1dLowerLimi t msdDi mText F i e l dMai n = 0 msdDi mText F i e l dMi n u s = 2 m s d D i m T e x t F i e 1 dP1 us = 1 msdDimTextFie1dUpperLimi t
=
1
=
0
MsdDimTextFormat MsdDimTextFormatMU
=
0
MsdDimTextFormatMU-dash-SU
4
=
MsdDimTextFormatMU-Label = 1 Msd D i mText Forma tMU-La be1 -da s h-SU-La be1 MsdDimText FormatMU-Label-SU-Label = 5 MsdDimTextFormatSU = 2
MsdDimTextFormatSU-Label
3
=
MsdDimTextFrameType MsdDimTextFrameTypeBox
1
=
MsdDimTextFrameTypeCapsule MsdDimTextFrameTypeNone
=
2
=
0
MsdDimTextJustification msdDimTextJustificationCenter
=
msdDi m T e x t J u s t i f ic a t i on L e f t = 1 msdDi m T e x t J u s t i f ic a t i o n R i g h t = 3
MsdDimTextLocation MsdDimTextLocationAbove
1
=
MsdDimTextLocationInline MsdDimTextLocationOutside MsdDimTextLocationTopLeft
=
0 2
=
3
=
2
=
6
262
I Chapter 12: The Microstation Object Model - Enums I MsdDimTextOrientation MsdDimTextOri entati onAl i gned = 0 MsdDimTextOri entati onHori zontal = 1 MsdDimThousandsOpts MsdDimThousandsOptsComma = 2 MsdDimThousandsOptsNone = 0 MsdDimThousandsOptsSpace = 1 MsdDimToleranceType MsdDimTol eranceTypeLimi t = 1 MsdDimTol eranceTypeP1 usMi nus
=
0
MsdDimType msdDimTypeAng1 eAxi s = 10 msdDimTypeAng1 eAxi sX = 50 msdDimTypeAng1 eAxi sY = 51 msdDimTypeAng1 eLi nes = 9 msdDimTypeAng1 eLocati on = 7 msdDimTypeAng1 eSi z e = 5 msdDimTypeArcLocation = 8 msdDimTypeArcSi z e = 6 msdDimTypeCenter = 19 msdDimTypeCustomLinear = 1 5 msdDimTypeDiameter = 1 2 msdDimTypeDiameterExtended = 18 msdDimTypeDiameterPara = 1 3 msdDimTypeDiameterPerp = 1 4 msdDimTypeLabe1 Line = 52 msdDimTypeLocateSi ngl e = 3 msdDimTypeLocateStacked = 4 msdDimTypeNone = 0 msdDimTypeNote = 53 msdDimTypeOrdinate = 1 6 msdDimTypeRadi us = 11 msdDimTypeRadiusExtended = 17 msdDimTypeSizeArrow = 1
I The Enumeration List I
263
msdDimTypeSizeStroke = 2 msdDimTypeUseActive = - 1
MsdDimValueAngleFormat msdDimVal ueAngl eFormatCentesima1 = 2 msdDimVa1 ueAngl eFormatDegMinSec = 1 msdDimVal ueAngl eFormatDegrees = 0 msdDimVa1 ueAngl eFormatRadi ans = 3
MsdDimValueAnglePrecision msdDimVal u e A n g l e P r e c i s i o n l P 1 ace = 1 msdDimVa1 u e A n g l e P r e c i s i o n Z P 1 ace = 2 msdDimVal u e A n g l e P r e c i s i o n 3 P l ace = 3 msdDimVa1 u e A n g l e P r e c i s i o n 4 P l ace = 4 msdDimVal u e A n g l e P r e c i s i o n 5 P l ace = 5 msdDimVa1 u e A n g l e P r e c i s i o n G P 1 ace = 6 msdDimVal u e A n g l e P r e c i s i o n W h o l e = 0
MsdDimVerticalTextOptions MsdDimVerti c a l TextOptionsAl ways = 1 MsdDimVertical TextOptionsNever = 0 MsdDimVertical TextOptionsNoFi t = 2
MsdDrawingMode msdDrawingModeErase = 1 msdDrawingModeHilite = 2 msdDrawi ngModeNorma1 = 0 msdDrawingModeTemporary = 3
msdDrawingModeTemporaryErase msdDrawingModeXor
=
4
=
6
MsdElementCachePurpose msdElementCachePurposeContro1 = 2 msdEl ementCachePurposeGraphi c a l = 4 msdElementCachePurposeNonMode1 = 1
MsdElementclass msdElementClassConstruction
=
2
264
I Chapter 12: The Microstation Object Model - Enums I msdEl ementCl asscontructi onRul e = 6 msdEl ementCl assDimensi on = 3 msdEl ementCl assLinearPatterned = 5 msdElementClassPatternComponent = 1 msdEl ementCl assprimary = 0 msdEl ementCl assPrimaryRul e = 4 MsdElementSubtype msdEl ementSubtypeApp1 i cati onEl ement = 20 msdElementSubtypeAuxiliaryCoordinateSystem = 3 msdElementSubtypeNone = -1 msdElementSubtypeUpdateSequenceElement = 33 MsdElementType msdEl ementType44 = 44 msdElementTypeArc = 1 6 msdEl ementTypeBsp1 i neBoundary = 25 msdElementTypeBsplineCurve = 27 msdEl ementTypeBsp1 i neKnot = 26 msdEl ementTypeBsp1 i nePol e = 21 msdElementTypeBsplineSurface = 2 4 msdEl ementTypeBsp1 i neWei ght = 28 msdEl ementTypeCel1 Header = 2 msdEl ementTypeCel1 Li braryHeader = 1 msdEl ementTypeComp1 exshape = 1 4 msdEl ementTypeComp1 exStri ng = 1 2 msdElementTypeCone = 23 msdEl ementTypeConi c = 1 3 msdElementTypeCurve = 11 msdEl ementTypeDesi gnFi 1 eHeader = 9 msdElementTypeDgnStoreComponent = 38 msdElementTypeDgnStoreHeader = 39 msdEl ementTypeDi gSetData = 8 msdEl ementTypeDimensi on = 33 msdEl ementTypeEl1 i pse = 1 5 msdElementTypeGroupData = 5 msdEl ementTypeLeve1 Mask = 99
d
4 4 4
o
N
m II
4
E
0 Q
S a, S
c,
co co co m
II
4
L
II
4
4
n
0 m 4 c, Lo m 0
II
0
II m c, m
n
0 4 4
II
II
CO CO
II
r .
L N d CO a, N c, 0-l S II a, II
m
u
4
0-l
II 0
4
II
C
c,o S
a, S 0 0
0 S
a,
m
a, 1 0
0-l Q c, E S II 0 a, 0 E
a,
0
S
c, c, c, c,
O
0
II
4
a, u)
u
.r
0-l
n
Q
Q
Q
Q
.r
r .
II
u
0
z
Q
Q
r .
co
r .
0
N m N 4 r.m
d C O
I
r
4 N
d
.
4 4 N N
II
L I
r .
0-l
s
z o u .r
m
II
a, c,
Lo
II
N c ,
s
0
m
Q
- 0 0
c f 0
L c v ,
aJv, a J w
a , m 0 S O a, .r L >
L
C O Q
II
4 aJ0-l
II
c, c , 3 3 X X a J a , a, .r .r Q
a J a J a , a, a J a J a ,
m m m a, + + + + + > >
Q Q O ,
L L
a,
E .r Lo v, Lo v, Lo cc Lc m m m o m m m m m a, a, 1 C z a a a a a a a a v, v, Q
Q
Q
w
7
Q
w
7
Q
7
Q
w
Q
a J a J a , a, a, a, a, a, a, a, a,
Q
W
7
E
E
E
E
E
W a J W a J W a J
E
W a J W a J W a J
S
a, E a,
L 0 L L
L 0 L L
a
w
7
m
w
7
s
7
s
w
s
Q
Q
s
h h h h h h h + + + + + + + 4 3 4 3 4 3 Q 4 3 4 3 4 3
Q
s
h h h h h h h h h h h + + + + + + + + + + +
Q
L L c, c, m m z E a,
a,
s
h h + +
w
7
S
w
7
Q
w
7
S
aJ E aJ
Q
7
S
W
Q
a, E a,
S
W
7
Q
aJ E aJ
S
7
Q
W
S
a, E a,
Q
W
7
S
aJ E aJ
Q
7
S
W
Q
S
Q
a, E a,
4 3 4 3 4 3
S
Q
W
7
s
aJ E aJ
s
7
s
W
s
a, E a,
w W
7
w
w
7
s s s a J w a J E E E a J w a J w
W v , E E
-0-0
7
C n c n v , E E E
-0-0-0
7
v, E
-0
w
C n c n v , E E E
-0-0-0
w
C n c n v , E E E
-0-0-0
7
v, E
-0
w
v, E
-0
7
v, E
-0
w
v, E
-0
-
v, E
-0
s s s s s s s a J w a J w a J w a J E E E E E E E a J w a J w a J w a J
v, E
-0
7
W
v, E
-0
w
w
7
v, E
-0
a, E a,
w
7
C n c n v , E E E
-0-0-0
W
w
-
C n c n c n v , E E E E
-0-0-0-0
7
7
v, E
-0
7
w v, E
-0
a J w a J w aJ E E E E E a J w a J w aJ C n c n c n v , E E E E
-0-0-0-0
266
I Chapter 12: The Microstation Object Model - Enums I msdErrorAcsNotFound = -2147220744 msdErrorAcsReplaced = -2147220745 msdErrorAddressNotKnown = -2147220784 msdErrorAddressNotValid = -2147220779 msdErrorAlreadyExists = -2147218310 msdErrorAlreadyInUse = -2147220804 msdErrorAlreadyOpen = -2147218312 msdErrorBadBSplineElement = -2147217996 msdErrorBadCharacterConstant = -2147220794 msdErrorBadContinuity = -2147217986 msdErrorBadElement = -2147218399 msdErrorBadFile = -2147218304 msdErrorBadFloat = -2147220796 msdErrorBadFormat = -2147218309 msdErrorBadHexNumber = -2147220799 msdErrorBadIndex = -2147218370 msdErrorBadKnots = -2147217991 msdErrorBadLineWeights = -2147217990 msdErrorBadModelId = -2147218334 m s d E r r o r B a d M o d e l R e f e r e n c e = -2147218397 msdErrorBadName = -2147218316 msdErrorBadNumber = -2147220800 msdErrorBadOcta1 = -2147220797 msdErrorBadOrder = -2147217994 msdErrorBadParameter = -2147217995 msdErrorBadPeriodicity = -2147217993 msdErrorBadPoles = -2147217992 msdErrorBadRasterFormat = -2147218350 msdErrorBadResourceType = -2147220772 msdErrorBadScanList = -2147218389 msdErrorBadSpi ral Definition = -2147217989 msdErrorBadString = -2147220795 msdErrorBadType = -2147220803 msdErrorBadVersion = -2147218308 msdErrorBadWordsToFollow = -2147218311 msdErrorCacheInUse = -2147218318
I The Enumeration List I msdErrorCacheLoadError = -2147218291 msdErrorCacheNotEnabled = -2147218320 msdErrorCacheNotFilled = -2147218288 msdErrorCacheNotFound = -2147218317 msdErrorCannotCreateFile = - 2 1 4 7 2 1 8 3 2 9 msdErrorCannotDereference = - 2 1 4 7 2 2 0 7 8 7 msdErrorCannotImportSeed = - 2 1 4 7 2 1 8 2 9 2 msdErrorCannotOpenFile = -2147218391 msdErrorCannotOpenSeed = -2147218303 msdErrorCannotSaveFile = -2147218328 m s d E r r o r C e l l E x i s t s = -2147218372 msdErrorCel1 L i b r a r y I s 2 d = -2147218365 msdErrorCellNotFound = -2147218373 msdErrorCellTooLarge = -2147218369 msdErrorCircularDependency = - 2 1 4 7 2 1 9 6 0 4 msdErrorCommandReceived = - 2 1 4 7 2 2 0 7 0 4 msdErrorComplexHeaderRequired = - 2 1 4 7 2 1 8 3 8 7 msdErrorCompressionError = - 2 1 4 7 2 1 8 2 9 6 msdErrorCopyError = -2147218289 msdErrorDiskFul1 = -2147218395 msdErrorDivideByZero = -2147220780 msdErrorDup1 i c a t e L o g i c a l = - 2 1 4 7 2 2 0 7 6 6 msdErrorDuplicateTaskId = -2147218353 m s d E r r o r E l e m e n t F i l l e d = -2147220756 msdErrorElementFrozen = -2147218359 msdErrorElementNotFilled = - 2 1 4 7 2 2 0 7 5 5 msdErrorElementNotFound = -2147218323 msdErrorElementNotPlanar = - 2 1 4 7 2 2 0 7 5 3 msdErrorElementTooLarge = -2147220754 msdErrorEndOfFile = -2147218390 msdErrorException = -2147219504 m s d E r r o r F i l e E x i s t s = -2147218326 msdErrorFileNotFound = -2147218338 msdErrorHasChanges = -2147218298 m s d E r r o r I d E x i s t s = -2147218321 msdErrorIdNotFound = -2147218322
267
268
I Chapter 12: The Microstation Object Model - Enums I msdErrorIllegalCharacter = -2147220793 msdErrorInsufficientInformation = -2147218401 msdErrorInsufficientMemory = -2147218388 msdErrorIntegralNeeded = -2147220777 msdErrorInvalidACSType = -2147220770 msdErrorInvalidButton = -2147220769 msdErrorInvalidCel1 = -2147218371 msdErrorInvalidClip = -2147220761 msdErrorInvalidForFloat = -2147220781 msdErrorInvalidForFunction = -2147220771 msdErrorInvalidForStructure = -2147220782 msdErrorInvalidForType = -2147220778 msdErrorInva1 idLi brary = -2147218368 msdErrorInvalidMaterOrigin = -2147220763 msdErrorInvalidOperationForNested = -2147218302 msdErrorInvalidOperationForNonNested = -2147218301 msdErrorInvalidPatternSpace = -2147220760 msdErrorInvalidReference = -2147220762 msdErrorInvalidReferenceOrigin = -2147220764 msdErrorInvalidSymbo1 = -2147220789 msdErrorLinkageNotFound = -2147218344 msdErrorLoadingInterface = -2147218297 m s d E r r o r M o d e l e r N o t L o a d e d = -2147219703 msdErrorMode1 IdExists = -2147218332 msdErrorModelNameExists = -2147218333 msdErrorMode1 NotEmpty = -2147218342 msdErrorModifyComplex = -2147218392 msdErrorNameNotUnique = -2147218343 msdErrorNameTooLong = -2147218335 msdErrorNeedExponent = -2147220798 msdErrorNeedInteger = -2147220802 msdErrorNoAcsDefined = -2147220746 msdErrorNoBounds = -2147218001 msdErrorNoBSplineHeader = -2147217999 msdErrorNoCel1 Library = -2147218375 msdErrorNoClipVolume = -2147218336
IThe Enumeration List I msdErrorNoFence = -2147218337 msdErrorNoGraphicGroup = -2147220742 msdErrorNoKnots = -2147218003 msdErrorNoLevelMask = -2147220740 msdErrorNoLineWeights = -2147218002 msdErrorNoMatch = -2147218381 msdErrorNoMode1 = -2147218299 msdErrorNoMode1 Information = -2147218331 msdErrorNonClosedElement = -2147220757 msdErrorNonClosedPatternElement = -2147220759 msdErrorNonCoplanarShapes = -2147220750 msdErrorNonSolidPatternElement = -2147220758 msdErrorNoNumberBounds = -2147218000 msdErrorNoOffsetIntersection = -2147217987 msdErrorNoParentMode1 = -2147218330 msdErrorNoPoles = -2147218004 msdErrorNoReferenceSlots = -2147220747 msdErrorNoselectionSet = -2147220748 msdErrorNoSuchMode1 = -2147218294 msdErrorNoSymbo1 = -2147220791 msdErrorNotDesignFIle = -2147220768 msdErrorNotDirectAttachment = -2147220739 msdErrorNotFunction = -2147220775 msdErrorNotLoaded = -2147218300 msdErrorNotLocked = -2147218293 msdErrorNotMember = -2147220785 msdErrorNotOpen = -2147218315 msdErrorNotSingleView = -2147220765 msdErrorNotStructure = -2147220786 msdErrorNotSupported = -2147218348 msdErrorNotValidExpression = -2147220776 msdErrorNu1 lSol ution = -2147220752 msdErrorOldMaterialTable = -2147220749 msdErrorOperationCanceled = -2147218306 msdErrorParasolidError = -2147219703 msdErrorReadOnly = -2147218396
269
270
I Chapter 12: The Microstation Object Model - Enums I m s d E r r o r R e c u r s e L i m i t = -2147217985 msdErrorRenameError = -2147218290 m s d E r r o r R e q u i r e s 3 d F i l e = -2147218400 m s d E r r o r R e s o u r c e N o t F o u n d = -2147218376 m s d E r r o r S h a r i n g V i o l a t i o n = -2147218314 m s d E r r o r S t r u c t u r e N e e d e d = -2147220801 msdErrorSymbo1 N o t R e s o l v e d = - 2 1 4 7 2 1 9 7 0 4 m s d E r r o r S y n t a x E r r o r = -2147220790 m s d E r r o r S y s t e m E r r o r = -2147218363 m s d E r r o r T a g B a d A s s o c i a t i o n = -2147220096 m s d E r r o r T a g B a d R e p o r t F i l e = -2147220098 m s d E r r o r T a g B a d R e p o r t K e y w o r d = -2147220097 msdErrorTagNameTooLong = - 2 1 4 7 2 2 0 0 9 2 m s d E r r o r T a g N o T a r g e t = -2147220095 m s d E r r o r T a g N o t F o u n d = -2147220093 msdErrorTagNotInSet = -2147220101 m s d E r r o r T a g P r e v i o u s l y D e f i n e d = -2147220094 msdErrorTagSetNameLong = -2147220103 msdErrorTagSetNotFound = -2147220102 m s d E r r o r T a g S e t P r e v i o u s l y D e f i n e d = -2147220100 m s d E r r o r T a g S e t T o o B i g = -2147220099 msdErrorTagUndefinedType = -2147220104 msdErrorTimeout = -2147218362 m s d E r r o r T o o C o m p l e x = -2147220783 m s d E r r o r T o o F e w A r g u m e n t s = -2147220773 msdErrorTooFewPoles = -2147217998 msdErrorTooManyArguments = - 2 1 4 7 2 2 0 7 7 4 msdErrorTooManyKnots = -2147217988 msdErrorTooManyOpenFiles = -2147218307 m s d E r r o r T o o M a n y P o l e s = -2147217997 msdErrorTooManySurfaceElements = -2147218346 msdErrorTypesIncompatible = - 2 1 4 7 2 2 0 7 8 8 msdErrorUnboundedSolution = -2147220751 m s d E r r o r U n k n o w n E r r o r = -2147218305 m s d E r r o r U n k n o w n F o r m a t = -2147218295 msdErrorUnsupported = -2147220792
I The Enumeration List I
271
msdErrorUserCanceledAction = -2147218382 msdErrorV7CellLibrary = -2147218327 msdErrorViewGroupNotFound = -2147220741 msdErrorViewNotDisplayed = -2147218374 msdErrorViewNotFound = -2147220743 msdErrorWriteFailed = -2147218393 msdErrorWriteInhibited = -2147218394 msdErrorWrongElementID = -2147218319 MsdFileAccessMode msdFileAccessModeRead = 1 msdFileAccessModeReadWrite
=
3
MsdFillMode msdFi 1 1 ModeFi 1 1 ed = 1 msdFi 1 1 ModeNotFi 1 1 ed = 0 msdFi 1 1 ModeOutl i ned = 2 msdFillModeUseActive = - 1 MsdFontType msdFontTypeMicroStation = 0 msdFontTypeSHX = 1 msdFontTypeUnknown = 3 msdFontTypeWindowsTrueType = 2 MsdGeoReferenceSisterFileType msdGeoReferenceSisterFileTypeHgr = 1 msdGeoReferenceSisterFileTypeNone = 0 msdGeoReferenceSisterFileTypeTwf = 2
MsdGlobalLineStyleScale msdGl obal Li neStyl eScal eBoth = 3 msdGl obal Li neStyl eScal eMaster = 0 msdGl obal Li neStyl eScal eNone = 1 msdGl obal Li neStyl eScal eReference = 2 MsdLevelChangeType msdLevelChangeAfterChangeActive
=
9
272
I Chapter 12: The Microstation Object Model - Enums I msdLevelChangeAfterCreate
=
msdLevel ChangeAfterDel ete
=
2 3
msdLevelChangeBeforeChangeActive
msdLevel ChangeBeforeDel ete
=
18
msdLevelChangeChangeAttribute msdLevelChangeChangeCode = 5
=
msdLevel ChangeChangeDi spl ay
7
=
=
8
msdLevelChangeChangeName = 4 msdLevelChangeChangeParent = 6
msdLevel ChangeTabl eRedo msdLevel ChangeTabl eUndo
= =
15 14
MsdLevelElementAccess msdLevel El ementAccessAl1 = 0 msdLevel El ementAccessLocked = 1 msdLevel El ementAccessReadOnly = 2 msdLevel El ementAccessVi ewOnly = 3 MsdLimits msdLimitsMaxVertices = 5000 msdLimi tsMaxVi ews = 8 MsdMeasurementBase msdMeasurementBaseDegree = 2 msdMeasurementBaseMeter = 1 msdMeasurementBaseNone = 0 MsdMeasurementSystem msdMeasurementSystemEnglish = 1 msdMeasurementSystemMetric = 2 msdMeasurementSystemUndefined = 0 MsdMem berTraverseType msdMemberTraverseCopy = 2 msdMemberTraverseDirectMembers = 4 msdMemberTraverseEnumerate = 3 msdMemberTraverseManipulate = 1 msdMemberTraverseSimple = 0
17
I The Enumeration List I
273
MsdMessageCenterPriority m s d M e s s a g e C e n t e r P r i o r i t y D e b u g = 13 m s d M e s s a g e C e n t e r P r i o r i t y E r r o r = 10 m s d M e s s a g e C e n t e r P r i o r i t y I n f o = 12 m s d M e s s a g e C e n t e r P r i o r i t y N o n e = 14 m s d M e s s a g e C e n t e r P r i o r i t y W a r n i n g = 11
MsdModelChangeType mdl Model C h a n g e A c t i ve = 5 mdlModel C h a n g e B e f o r e A c t i v e
=
mdlModelChangeBeforeCreate
=
mdl Model ChangeBeforeDel e t e
=
11 15 6
mdlModelChangeBeforeName = 12 mdlModelChangeBeforeProperties
=
mdlModel C h a n g e B e f o r e S e t t i n g s mdlModelChangeBeforeUnCreate
=
13
=
9
mdlModelChangeBeforeUnDelete
=
16
14
mdlModelChangeCreate = 1 mdl Model ChangeDel e t e = 2 mdlModelChangeName = 10
mdlModelChangePropagateAnnotationScale mdl Model C h a n g e p r o p e r t i es = 3 mdlModelChangeSettings = 4 mdlModelChangeUnCreate = 7 mdl Model ChangeUnDel e t e = 8
MsdModelType msdModel T y p e D e f a u l t = - 1 msdModelTypeExtraction = 2 msdModel TypeNorma1 = 0 msdModel T y p e S h e e t = 1
MsdNestoverrides msdNestOverridesAlways = 1 msdNestOverridesAsRequired msdNestOverridesNever = 2
=
0
=
17
274
I Chapter 12: The Microstation Object Model - Enums I MsdNewLevelDisplay msdNewLeve1 D i s p l a y A l ways = 1 msdNewLeve1 D i s p l a y F r o m C o n f i g msdNewLeve1 D i s p l a y N e v e r = 2
0
=
MsdRasterBlockType msdRasterB1 ockTypeImage = 4 msdRasterBlockTypeLine = 1 msdRasterBlockTypeStrip = 3 msdRasterB1 o c k T y p e T i 1 e = 2
MsdRasterDisplayOrderCommand msdRasterDisplayOrderCommandBackward = 3 msdRasterDisplayOrderCommandForward = 2 msdRasterDisplayOrderCommandToBack = 1 msdRasterDisplayOrderCommandToFront = 0 MsdRasterDisplayPriorityPlane m s d R a s t e r D i s p l a y P r i o r i t y P l aneBack = 1 msdRasterDi s p l a y P r i o r i tyPl aneFront = 3 msdRasterDi s p l a y P r i o r i tyPl aneVector = 2
MsdRasterModificationType m s d R a s t e r M o d i f i c a t i onTypecC1 ip B o u n d a r y m s d R a s t e r M o d i f i c a t i onTypecC1 ipMask = 4
=
5
msdRasterModificationType-ExtendedInformation = 0 msdRasterModificationType-GeoReferenceInformation = 1 msdRasterModificationType-RasterInformation = 3 m s d R a s t e r M o d i f i c a t i onType-Re1 oad = 6 m s d R a s t e r M o d i f i c a t i onType-Renderi n g I n f o r m a t i on
MsdRasterworldFile msdRasterWor1 d F i 1 eHgr = 1 msdRasterWor1 d F i 1 eNone = 0 msdRasterWor1 d F i 1 eWorl d F i 1 e
MsdReferencesystem msdReferenceSystemDgn
=
2
=
2
=
2
I The Enumeration List I msdReferenceSystemRaster
275
1
=
MsdRenderingMode msdRenderingModeConstantShade
=
msdRenderingModeHiddenLine
3
=
msdRenderingModeParticleTrace
5
=
msdRenderingModeCrossSection
1
=
11
m s d R e n d e r i ngModePhong = 7 m s d R e n d e r i n g M o d e R a d i o s i t y = 10 m s d R e n d e r i ngModeRayTrace = 8
msdRenderingModeRenderWireFrame msdRenderingModeSmoothShade = 6 m s d R e n d e r i ngModeSol i d F i 11
=
msdRenderingModeWireFrame
=
m s d R e n d e r i ngModeWi reMesh
=
=
9
4 0 2
MsdStandardsCheckerReplaceChoice msdStandardsCheckerReplaceChoiceAbort
=
4
msdStandardsCheckerReplaceChoiceFix = 1 msdStandardsCheckerReplaceChoiceMarkIgnored
=
msdStandardsCheckerReplaceChoiceMarkNotIgnored msdStandardsCheckerReplaceChoiceSkip = 0 MsdStandardsCheckerReplaceoptions msdStandardsCheckerReplaceOptionCanFix = 2 msdStandardsCheckerReplaceOptionCanIgnore = 1
MsdStatusBarArea msdStatusBarAreaLeft = 16 msdStatusBarAreaMiddle = 15
MsdTagType msdTagTypeBi n a r y = 5 msdTagTypeCharacter = 1 msdTagTypeDoub1 e = 4 msdTagTypeLongInteger = 3 msdTagTypeShortInteger = 2
2 =
3
276
I Chapter 12: The Microstation Object Model - Enums I MsdTangentElementOutputType msdTangentArcs = 1 msdTangentCi r c l es = 0 msdTangentTri angl es = 2
MsdTangentlnterpolationType msdTangentFromCi r c l e F i t = 1 msdTangentFromCubicFit = 2 msdTangentFromCurve = 0
MsdTextDirection msdTextDi r e c t i onHori z o n t a l = 0 msdTextDi r e c t i onRi g h t T o L e f t = 8 msdTextDi r e c t i o n V e r t i c a l = 4 m s d T e x t D i r e c t i o n V e r t i c a l Mu1 t i L i n e R i g h t T o L e f t
=
MsdTextJustification msdTextJustificationCenterBottom = 8 msdTextJustificationCenterCenter = 7 msdTextJustificationCenterTop = 6 msdTextJustificationLeftBottom = 2 msdTextJustificationLeftCenter = 1 m s d T e x t J u s t i f ic a t i onLeftTop
=
0
msdTextJustificationRightBottom = 14 msdTextJustificationRightCenter = 13 msdTextJustificationRightTop = 12 MsdTextNodeLineSpacingType msdTextNodeLineSpacingTypeAtLeast
=
3
msdTextNodeLineSpacingTypeAutomatic = 1 msdTextNodeLineSpacingTypeExact = 0 msdTextNodeLineSpacingTypeExactFromLineTop MsdV7Action msdV7ActionAskUser = 0 msdV7ActionUpgradeToV8 = 1 msdV7ActionWorkmode = 3
=
2
2
I Review I
277
MsdViews msdViewl = 1 msdView2 = 2 msdView3 = 4 msdView4 = 8 msdView5 = 16 msdView6 = 32 msdView7 = 64 msdView8 = 128 msdViewAll = 255 msdViewNone = 0
MsdXDatumType msdXDatumTypeBinaryData
1004 m s d X D a t u m T y p e C o n t r o l S t r i n g = 1002 msdXDatumTypeDatabaseHandle = 1005 msdXDatumTypeDistance = 1041 msdXDatumTypeIntl6 = 1070 msdXDatumTypeInt32 = 1071 msdXDatumTypeLeve1 = 1003 msdXDatumTypePoint = 1010 msdXDatumTypeRea1 = 1040 m s d X D a t u m T y p e S c a l e F a c t o r = 1042 msdXDatumTypeString = 1000 msdXDatumTypeUnsupported = 0 m s d X D a t u m T y p e W o r l d D i r e c t i o n = 1013 msdXDatumTypeWorldSpaceDisplacement = 1012 msdXDatumTypeWorldSpacePosition = 1011 =
REVIEW As we continue through this book, we will see examples of using enumerations in the code samples. As we pointed out in the objects chapter, the Object Browser is useful in finding and determining how to use enumerations.
13
The Microstation Object Model - Types Thus far we have introduced and discussed concepts such as variables, objects, properties, and methods. We are now going to discuss types. A type is used like a variable but is similar to an object because it holds multiple elements. The best way to demonstrate this is by looking at a type we will use extensively in our Microstation VBA programming. Type P o i n t 3 d
X As D o u b l e Y As D o u b l e Z As D o u b l e End T y p e
The Point3d type has three members: X (which is a Double), Y (which is a Double) and Z (which is a Double). Sub T e s t P o i n t 3 d ( 1 D i m S t a r t p o i n t As P o i n t 3 d D i m E n d p o i n t As P o i n t 3 d D i m MyLine As LineElement
StartP0int.X
=
1.5
StartP0int.Y
=
2.5
279
280
I Chapter 13: The Microstation Object Model - Types I StartP0int.Z
=
EndP0int.X
=
4
EndP0int.Y
=
0
EndP0int.Z
=
0
3.5
= CreateLineElement2(Nothing, S t a r t p o i n t , E n d p o i n t ) ActiveModelReference.AddElement M y L i n e
S e t MyLine End Sub
We declare two variables with a type of “Point3d’: We assign coordinate values to the X, Y, and Z elements of these variables. They are then used with the CreateLineElement2 method. Here is the declaration for “CreateLineElement2”: S u b C r e a t e L i n e E l e m e n t 2 ( T e m p l a t e As E l e m e n t , S t a r t p o i n t As P o i n t 3 d , E n d p o i n t As P o i n t 3 d ) as L i n e E l e m e n t
Notice how this method is asking for two Point3d Types - one for the Start Point and the other for the End Point. Here is a list of the types we have available to us in Microstation VBA: T y p e MsdACSType J u s t i f i c a t i o n As M s d D a t a E n t r y R e g i o n J u s t i f i c a t i on L e n g t h As L o n g S t a r t P o s i t i o n As L o n g End T y p e T y p e MsdACSType H i g h As L o n g Low As L o n g End T y p e Type MsdAddAttachmentFlags C e n t e r As P o i n t 3 d S t a r t As D o u b l e Sweep As D o u b l e V e c t o r 0 As P o i n t 3 d V e c t o r 9 0 As P o i n t 3 d End T y p e Type MsdAngleAccuracy Du As P o i n t 3 d Dv As P o i n t 3 d End T y p e
I The Microstation Object Model -Types I Type MsdAngleAccuracy RowX As P o i n t 3 d RowY As P o i n t 3 d RowZ As P o i n t 3 d End T y p e Type MsdAngleAccuracy B a s e As M s d M e a s u r e m e n t B a s e L a b e l As S t r i n g S y s t e m As M s d M e a s u r e m e n t S y s t e m U n i t s P e r B a s e D e n o m i n a t o r As D o u b l e U n i t s P e r B a s e N u m e r a t o r As D o u b l e End T y p e T y p e MsdAngl e F o r m a t D e t a i l s As S t r i n g Msg As S t r i n g P r i o r i t y As M s d M e s s a g e C e n t e r P r i o r i t y End T y p e T y p e MsdAngl eMode N o r m a l As P o i n t 3 d O r i g i n As P o i n t 3 d End T y p e Type MsdAttachMode
X As D o u b l e Y As D o u b l e End T y p e Type
MsdBsplineCurveOffsetCuspType X As D o u b l e Y As D o u b l e Z As D o u b l e
End T y p e Type
MsdBsplineCurveOffsetCuspType H i g h As P o i n t 3 d Low As P o i n t 3 d
End T y p e
281
282
I Chapter 13: The Microstation Object Model - Types I Type MsdBsplineCurveType
D i r e c t i on As P o i n t 3 d O r i g i n As P o i n t 3 d End T y p e Type MsdBsplineCurveType Duu A s P o i n t 3 d Duv A s P o i n t 3 d Dvu A s P o i n t 3 d Dvv A s P o i n t 3 d End T y p e Type MsdBsplineCurveType E n d p o i n t As P o i n t 3 d S t a r t P o i n t As P o i n t 3 d End T y p e Type M s d B s p l i n e P a r a m e t r i z a t i o n T y p e RowX A s P o i n t 3 d RowY A s P o i n t 3 d RowZ A s P o i n t 3 d T r a n s l a t i o n X As D o u b l e T r a n s l a t i onY As D o u b l e T r a n s l a t i o n Z As D o u b l e End T y p e Type MsdBsplineSurfaceType
X As Double Y As Double Z As Double
End T y p e Type MsdBsplineSurfaceType T y p e A s MsdXDatumType Value As V a r i a n t End T y p e
Each of these types is available to us when we are using Microstation VBA. The “Type”,“End Trpe” declaration as shown is a standard VBA convention. As a matter of fact, we can create our own “Types” inside VBA. Custom T p e s are declared in the General Declarations area of a
I Review I
283
Code Module. For example, if we want a new type named “Point4d’: we would use the code: Type P o i n t 4 d X As D o u b l e Y As D o u b l e Z As D o u b l e A As D o u b l e End Type
If this declaration is made, we can declare variables as follows: Dim MyPoint A s P o i n t 4 d
As with enumerations, types will be used extensively as we continue working with the Microstation VBA.
Types are similar to objects. An object has properties. A type has members which are similar to properties. One of the most common types we use in Microstation is the Point3d type. It has members of X, Y, and Z. Each of these members are declared as Doubles.
The Microstation Object Model - Events Objects, as we have discussed, have properties, methods, and events. We introduced events when we discussed creating a Visual Interface. When a user clicks on a CommandButton, the click event of the CommandButton is triggered. Microstation events are triggered as the user interacts with various aspects of Microstation. When a company (such as Bentley) embeds VBA into their application (in this case, Microstation), the question of how to deal with events is raised. Here is how Microsoft Excel deals with events:
Each worksheet in an Excel workbook has events automatically exposed. Two of them are the Change and the Selectionchange events. These
285
286
I Chapter 14: The Microstation Object Model - Events I events are triggered as a worksheet’s cell value changes and when the user moves from one cell to another. There are two ways we can capture and make use of Microstation events. One is to declare a variable in a class module or a form as an application and using the “WithEvents” keyword. This exposes two events: OnDesi gnFi 1 eOpened and OnDesi gnFi 1 eC1 osed. The majority of Microstation events are accessed through the use of interfaces. Microstation has exposed much more than simple events through the use of interfaces, which are discussed in detail in Chapters 22 through 26.
0NDESIGN FILEOPENED AND 0NDESIGNFILECLOSED Here is a small example of how the OnDesignFileOpened and OnDesignFi 1 eC1 osed events work. We will use a UserForm that is shown modeless. This means the user can still interact with Microstation even though the form is displayed. When the form is initialized, we set the Microstation application object to a variable that has been declared “WithEvents” in the General Declarations area of the UserForm. When we declare a variable “WithEvents’: the events belonging to the object we specify are available to our code. Here’s the program as it is running after a couple of files have been opened (the previous file closes when the new file is opened).
....C:\M/cr.ostation ..... VBAZ\filel .dgn
C:\Microstation VBAZ\Mathcad Model.dgn
C:\Docurnents and Settings\All Users\Application Data\Bentley\WorWpace\Projects\Exarnples\E C:\Microstation VBAZ\filel .dgn
I OnDesignFileOpened and OnDesignFileClosed I
287
Each time a file is opened or closed, the associated event is triggered. We will begin by looking at the code in the code area of the UserForm. Dim WithEvents MyApp As MicroStationDGN.App1ication Private Sub UserForm-Initialize0 Set MyApp
=
Application
End Sub Private Sub MyApp-OnDesignFileOpened(ByVa1 DesignFileName As String)
-
1stOpened.AddItem DesignFileName End Sub Private Sub MyApp-OnDesignFileClosed(ByVa1 DesignFileName As String)
-
1stClosed.AddItem DesignFileName End Sub
Each time the OnDesignFileOpened event is triggered, we add the DesignFileName parameter to the IstOpened ListBox. When a file is closed, it is added to the IstClosed ListBox. We want to display this form as modeless, so we will display it by running the next macro: Sub ShowEventsO frmEvents.Show vbModeless End Sub
The Procedure ShowEvents is placed inside a code module. We can use the OnDesignFi 1 eOpened and OnDesignFi 1 eC1 osed events to log which files have been opened. We are given the file name as a parameter in the event. This basic functionality could be expanded to include capturing the current Date/Time (with the Now Function) as well as the current User (with the Appl ication .UserName property).
288
I Chapter 14: The Microstation Object Model - Events I
REVIEW Events are triggered as users interact with software. Microstation events are primarily exposed through the use of interfaces (covered later). The OnDesignFileOpened and OnDesignFileClosed events can be exposed by declaring the MicroStation.Application object “WithEvents” in a Class Module or UserForm. More information on the use of “WithEvents” can be found in the standard VBA help file.
15
Adding To Documents We have created lines, circles, arcs, and text as we introduced programming topics. Let’s examine the specifics of adding elements and other objects to our design files. We begin with graphical elements and then work on non-graphical elements such as levels. In this Chapter:
Graphical Elements Creating New Documents Security Issues with Creating Data
GRAPHICAL ELEMENTS There are two steps to adding elements to our design files. First we create the element in memory. Then we add the element to our design file. As you will see, there is often more than one way to create the element. We will demonstrate multiple examples of each creation method.
Lines “The shortest distance between two points is a straight line.” If this is true, we should be able to create a line by providing two points, right? 289
290
I Chapter 15: Adding To Documents I Well, that is one way to create a line. We can also provide an array of vertices if we want to draw more than one line. F u n c t i o n CreateLineElementl(Temp1ate As E l e m e n t , V e r t i c e s 0 A s P o i n t 3 d ) As L i n e E l e m e n t F u n c t i o n CreateLineElement2(Template As E l e m e n t , S t a r t p o i n t As P o i n t 3 d , E n d p o i n t As P o i n t 3 d ) As L i neEl ement Sub T e s t C r e a t e L i n e A ( ) D i m S t P t As P o i n t 3 d D i m EnPt As P o i n t 3 d D i m m y L i n e As L i n e E l e m e n t
EnPt.X
=
4 : EnPt.Y
Set myLine
=
=
6 : EnPt.Z
=
8
CreateLineElementZ(Nothing, S t P t , E n P t )
ActiveModel Reference.AddElement myLine End Sub
T e s t C r e a t e L i n e A uses the C r e a t e L i n e E l e m e n t P method to create a new
line element. It does so using a start point and an end point. Sub T e s t C r e a t e L i n e B ( ) D i m S t P t As P o i n t 3 d D i m EnPt As P o i n t 3 d D i m m y L i n e As L i n e E l e m e n t
'Line 1 StPt.X
=
0: S t P t . Y
=
0: S t P t . Z
=
0
EnPt.X
=
4 : EnPt.Y
=
0 : EnPt.Z
=
0
Set myLine
=
CreateLineElementZ(Nothing, S t P t , E n P t )
ActiveModel Reference.AddElement myLine 'Line 2 StPt.X
=
4: S t P t . Y
=
0: S t P t . Z
=
0
EnPt.X
=
4 : EnPt.Y
=
4 : EnPt.Z
=
0
Set myLine
=
CreateLineElementZ(Nothing, S t P t , E n P t )
ActiveModel Reference.AddElement myLine 'Line 3 StPt.X
=
4: S t P t . Y
=
4: S t P t . Z
=
0
EnPt.X
=
0 : EnPt.Y
=
4 : EnPt.Z
=
0
Set myLine
=
CreateLineElementZ(Nothing, S t P t , E n P t )
A c t i v e M o d e l R e f e r e n c e . A d d E l e m e n t myLi ne 'Line 4 StPt.X
=
0: S t P t . Y
=
4: S t P t . Z
=
0
EnPt.X
=
0 : EnPt.Y
=
0 : EnPt.Z
=
0
I Graphical Elements I Set myLine
=
291
CreateLineElementZ(Nothing,
S t P t , EnPt)
ActiveModel Reference.AddElement myLine End Sub
TestCreatelineB creates and adds four lines by using the Createli neEl emen t2 function. As we look at the coordinates used to create the lines we will recognize that we are drawing a square. Let's create the same square by using the Createli neEl ementl Method. Sub
TestCreateLineC( 1 D i m L i n e P o i n t s ( 0 To 4 ) As P o i n t 3 d D i m m y L i n e As L i n e E l e m e n t
LinePoints(O).X
=
0: LinePoints(O).Y
=
LinePoints(l).X
=
4: L i n e P o i n t s ( l ) . Y
=
0
LinePoints(Z).X
=
4: L i n e P o i n t s ( Z ) . Y
=
4
LinePoints(3).X
=
0: L i n e P o i n t s ( 3 ) . Y
=
4
LinePoints(4).X
=
0: L i n e P o i n t s ( 4 ) . Y
=
0
S e t myLi ne
=
0
C r e a t e L i neEl e m e n t l ( N o t h i ng, L i nePoi n t s )
ActiveModel Reference.AddElement myLine End Sub
As we can see in TestCreatelineC, we can supply an array of Point3d types and use a single Createli neEl ementl Method to create four lines.
NOTE: W h e n we declare a numeric variable, a value of zero (0) is automatically assigned to the variable. Knowing this, we can leave the .Z element of each point alone and it will be assigned a value of zero by default. W e could have left out the .Xa n d . Y elements that were to be assigned values of zero as well, but keeping them in makes the code much easier to read. Also, note that we are putting two lines of code on the same line. W e can do this by using the colon (:) symbol. This keeps our vertices on the same line of code and can make it easier to read the code. In our next example, we are going to create a procedure that allows us to specify x, y, z elements for the creation of 3d Lines. We want to be able to provide any number of x, y, z sets of elements so we will use the ParamArray keyword in our parameter declaration. Sub Create3dLines(ParamArray P o i n t E l e m s O As V a r i a n t ) I f (UBound(PointE1ems)
+ 1) Mod 3
<>
0 Then
MsgBox " I n v a l i d number o f p o i n t e l e m e n t s . " , E x i t Sub
vbcritical
I Chapter 15: Adding To Documents I End I f I f UBound(PointE1ems)
+ 1
<
5 Then
MsgBox “ A minimum o f 2 X , Y , Z p o i n t s m u s t b e p r o v i d e d . ” , v b c r i t i c a l
E x i t Sub End I f D i m L i n e P o i n t s O As P o i n t 3 d
ReDim L i n e P o i n t s ( 0 To ( U B o u n d ( P o i n t E 1 e m s )
+ 1) \ 3 ) As P o i n t 3 d
D i m I As L o n g
D i m P o i n t c o u n t e r As L o n g D i m m y L i n e As L i n e E l e m e n t F o r I = L B o u n d ( P o i n t E 1 e m s ) To U B o u n d ( P o i n t E 1 e m s ) S t e p 3
LinePoints(PointCounter).X LinePoints(PointCounter).Y LinePoints(PointCounter).Z Pointcounter
=
=
PointElems(1)
=
PointElems(1
=
PointElems(1
Pointcounter
+ 1) + 2)
+ 1
Next I Set myLine
=
CreateLineElementl(Nothing, L i n e P o i n t s )
A c t i v e M o d e l Reference.AddE1 ement m y L i n e End S u b
This procedure is straightforward but a little more complicated than those we have worked with in the past. Let’s start at the top of the procedure and work our way down to the end. The procedure is named Create3dLines and a single ParamArray parameter is declared. NOTE: Only one ParamArray parameter can be declared in a function
orprocedure and it must be the lastparameter. 3D Points are comprised of x,y, and z elements. Because of this, we need to make sure that we have been given the PointElems array in groups of 3. If the upper-bound value of the parameter is 4, this means 5 elements have been provided. This is a problem because 5 elements do not produce two complete 3-D points. If we pass the “group of 3 elements” test, we need to see if we have been provided at least two points. After all, we cannot create a line from one point. This can be done a number of different ways. One way is to look at the upper-bound (UBound Function) of the PointElems array. If it is less than 5, we know we don’t have enough elements in the array. If it is equal to 5 we know we have two complete 3d Point elements.
I Graphical Elements I
293
4 Since the number of points may be different each time this procedure is used, we need to create a dynamic array of points. We then set the number of points in the array based on the number of PointElems provided.
5 Now we need to populate the X, Y, and Z components of the points based on the elements provided in the ParamArray.
6 Weuse CreateLineElement1,usingthepointscreatedfromthe ParamArray.
7 We add the line to the ActiveModelReference. Since this procedure utilizes parameters, it cannot be run by itself. Here is a test procedure to run our “Create3dLine.S”procedure. Sub TestCreate3dLines(1 C r e a t e 3 d L i n e s 0 , 0 , 0 , 4, 0, 0, 4, 4, 0, 0, 4, 0, 0 , 0 , 0 C r e a t e 3 d L i n e s 0, 0, 0, 4, 4, 0 C r e a t e 3 d L i n e s 0, 4, 0, 4, 0, 0 C r e a t e 3 d L i n e s 0, 4, 0, 4, 0 C r e a t e 3 d L i n e s 0, 4, 0 End S u b
Our test procedure, TestCreate3dLi nes, calls our newly created procedure Create3dLines five times. In the first instance, a square is created from (O,O,O) to (4,0,0) to (4,4,0) to (0,4,0) and back to (O,O,O). The next one draws a line from (O,O,O) to (4,4,0). Next we draw a line from (0,4,0) to (4,0,0). The next two lines are put in to test our ParamArray validation code. We are unable to draw a line from (0,4,0) to (4,O) because the second point is only given two elements (x and y) and we are requiring three elements per point. The last one attempts to draw a line from (0,4,0) to ... to nothing. We cannot draw a line with only one point. Here are the two message boxes in the order in which they appear.
L
Once a line is created, we can make changes to its properties such as its color, level, or linestyle properties.
294
I Chapter 15: Adding To Documents I In Microstation’s Color Table dialog box, if we scroll over the colors in the table we see the color number and the RGB values for each color. In the graphic shown we can see that color number 3 has an RGB value of (255, 0, 0).
Let’s draw a couple of lines and change their color to red (255,0,0). Sub T e s t C r e a t e L i n e D ( ) Dim LinePoints(0 To 11 As Point3d Dim myLine As LineElement LinePoints(O1.X = 0: LinePoints(O1.Y = 0 LinePoints(l1.X = 4: LinePoints(l1.Y = 4 Set myLine = CreateLineElementl(Nothing, LinePoints) myLine.Color = 3 ActiveModel Reference.AddElement myLine LinePoints(O1.X = 0: LinePoints(O1.Y = 4 LinePoints(l1.X = 4: LinePoints(l1.Y = 0 Set myLine = CreateLineElementl(Nothing, LinePoints) myLine.Color = 3 ActiveModel Reference.AddElement myLine End Sub
Two lines are created with their color properties changed to color number 3 (red). Here is another way we could accomplish the same task: Sub T e s t C r e a t e L i n e E ( ) Dim LinePoints(0 To 11 As Point3d Dim myLine As LineElement Dim myLine2 As LineElement LinePoints(O1.X = 0: LinePoints(O1.Y = 0 LinePoints(l1.X = 4: LinePoints(l1.Y = 4 Set myLine = CreateLineElementl(Nothing, LinePoints)
I Graphical Elements I
295
myLine.Color = 3 ActiveModel Reference.AddElement myLine LinePoints(O).X = 0: LinePoints(O).Y = 4 LinePoints(l).X = 4: LinePoints(l).Y = 0 Set myLine2 = CreateLineElementl(myLine, LinePoints) ActiveModel Reference.AddElement myLine2 End S u b
In this example, we added one line of code, removed one line of code, and made a slight change to another line. Here is the line we changed: Set myLi ne2
=
CreateLi neEl ementl(myLi ne, Li nePoi nts)
In the previous work we did with CreateLineElementl, we supplied a value of “Nothing’ in the template parameter. In this example, we provided the variable of the first line we created. This results in the creation of a new line with the same non-geometric properties as the ‘Template’element.
Creating Shapes A shape is a series of lines that are joined together into one element. Here is the declaration for CreateShapeElementl: i l Function CreateShapeElementl(Temp1ate As Element, V e r t i c e s 0 As Point3d, CFillMode As MsdFillMode = msdFillModeUseActive1) As ShapeElement Here is a procedure that creates a triangle. Sub TestCreateShapeA( )
Dim myshape As ShapeElement Dim ShapePoints(0 T o 2) As Point3d ShapePoints(O).X = 0: ShapePoints(O).Y = 0 ShapePoints(l).X = 2: ShapePoints(l).Y = 0 ShapePoints(Z).X = 1: ShapePoints(Z).Y = 1 Set myshape = CreateShapeElementl(Nothing, ShapePoints) ActiveModel Reference.AddElement myshape End S u b
When this code is run, a triangle is created and added to the ActiveModelReference. Notice that we do not need to close the triangle by providing a fourth point at (O,O, 0). Shapes are always closed.
296
I Chapter 15: Adding To Documents I A comparison of the declaration and the use of C r e a t e S h a p e E l e r n e n t l reveals that we did not use the optional FillMode parameter. By default, the FillMode parameter uses the active setting in Microstation. Let’s copy and paste TestCreateShapeA, rename the new procedure to T e s t C r e a t e S h a p e B and supply a FillMode parameter: Sub T e s t C r e a t e S h a p e B O D i m myshape As S h a p e E l e m e n t D i m S h a p e P o i n t s ( 0 To 2) As P o i n t 3 d
ShapePoints(O1.X
=
0: ShapePoints(O1.Y
=
0
ShapePoints(l1.X
=
2: S h a p e P o i n t s ( l 1 . Y
=
0
ShapePoints(2l.X
=
1: S h a p e P o i n t s ( 2 l . Y
=
1
S e t myshape
=
CreateShapeElementl(Nothing, S h a p e P o i n t s ,
-
m s d F i 11 ModeFi 11 e d )
ActiveModelReference.AddElement myshape End S u b
T e s t C r e a t e S h a p e B creates a filled triangle. If the resulting triangle does
not look like it is filled, the fill setting in view attributes may not be selected (Settings > View Attributes). Let’s build on our knowledge of creating shapes. Now we are going to create a function that creates a regular polygon based on a center point, a number of sides, and a radius. The polygon we create will be inscribed within the radius we provide. F u n c t i o n C r e a t e P o l y g o n ( C e n t e r P o i n t As Poi n t 3 d . NumOfSides As L o n g , R a d i u s As D o u b l e ) As S h a p e E l e m e n t D i m myshape As S h a p e E l e m e n t D i m S h a p e P o i n t s O As P o i n t 3 d
ReDim S h a p e P o i n t s ( 0 To NumOfSides
-
1) As P o i n t 3 d
D i m P o i n t I n d e x As L o n g D i m I n c A n g l e As D o u b l e IncAngle
=
3 6 0 / NumOfSides
For P o i n t I n d e x
=
L B o u n d ( S h a p e P o i n t s ) To U B o u n d ( S h a p e P 0 i n t s )
ShapePoints(Point1ndex) = Point3dAddAngleDistance(CenterPoint, R a d i a n s ( I n c A n g 1 e * P o i n t I n d e x ) , R a d i u s , 0) ~
Next Set CreatePolygon
=
CreateShapeElementl(Nothing, S h a p e P o i n t s )
End F u n c t i o n
This is our function. It returns a ShapeElement. Since it utilizes parameters, we need to create a test procedure to run it.
I Graphical Elements I
297
S u b TestCreatePolygon( 1 D i m C P o i n t As P o i n t 3 d D i m m y s h a p e As S h a p e E l e m e n t S e t myshape
=
C r e a t e P o l y g o n ( C P o i n t , 6 , 1)
A c t i v e M o d e l Reference.AddElement myshape End S u b
Our TestC re ate Po 1 y g o n procedure declares a variable as a Point3d. No modification is made to the X, Y,or Z elements of the point so the polygon is created centered around (0, 0,O).
A circle is defined by a center point and a radius or diameter. We create circles in Microstation VBA by using the CreateEll ipseEl ementl and CreateEll ipseEl ement2 methods. il F u n c t i o n CreateEllipseElementl(Temp1ate As E l e m e n t , P e r i m e t e r P o i n t l As P o i n t 3 d , P e r i m e t e r P o i n t 2 As P o i n t 3 d , P e r i m e t e r P o i n t 3 As P o i n t 3 d , C F i l l M o d e As MsdFi 1 1 Mode = msdFi 11 ModeUseActi v e l ) As E l 1 ipseEl ement
il
F u n c t i o n CreateEllipseElementZ(Temp1ate As E l e m e n t , O r i g i n As Poi n t 3 d , P r i m a r y R a d i us As Doubl e , SecondaryRadius As D o u b l e , R o t a t i o n As M a t r i x 3 d , [ F i 11 Mode As MsdFi 11 Mode = msdFi 11 ModeUseActi v e l ) As E l 1 ipseEl ement
I Chapter 15: Adding To Documents I We will begin with C r e a t e E l l i p s e E l ernent2. Sub T e s t C r e a t e C i r c l eA( ) D i m C P o i n t As P o i n t 3 d D i m m y E l l i p s e As E l l i p s e E l e m e n t
D i m r o t M a t r i x As M a t r i x 3 d CP0int.X
=
2.5: C P 0 i n t . Y = 2.5 = CreateEllipseElementZ(Nothing, CPoint, 0 . 5 , 0 . 5 ,
S e t myEllipse
rotMatrix) A c t i v e M o d e l R e f e r e n c e . A d d E l e m e n t myEl1 i p s e End Sub
The center point is set at (2.5,2.5,0) and we are using a radius of 0.5. We supply the same value for the PrimaryRadius parameter as we do for the SecondaryRadius parameter. This results in a circle. If the primary and secondary radii values are different, an ellipse is created. Sub T e s t C r e a t e C i r c l eB( ) D i m C P o i n t As P o i n t 3 d D i m m y E l l i p s e As E l l i p s e E l e m e n t D i m r o t M a t r i x As M a t r i x 3 d D i m C i r R a d As D o u b l e
2.5: C P 0 i n t . Y = 2.5 0.5 To 2 S t e p 0.125 S e t m y E l l i p s e = CreateEllipseElementZ(Nothing, C P o i n t ,
CP0int.X
=
F o r CirRad
=
-
CirRad, CirRad, r o t l v l a t r i x ) A c t i v e M o d e l R e f e r e n c e . A d d E l e m e n t myEl1 i p s e N e x t C i rRad End Sub
T e s t C r e a t e C ir c 1 e B creates a series of corradial circles with radii ranging from 0.5 to 2 in .125 unit increments.
The next procedure allows the user to select the center point of the circle to be drawn. The radius used is 0.5. Sub T e s t C r e a t e C i r c l eC( ) D i m C P o i n t As P o i n t 3 d D i m m y E l l i p s e As E l l i p s e E l e m e n t
D i m r o t M a t r i x As M a t r i x 3 d D i m i n p u t Q u e u e As CadInputQueue D i m i n p u t M e s s a g e As CadInputMessage Set inputQueue
=
CadInputQueue
I Graphical Elements I Set inputMessage
299
=
-
inputQueue.GetInput(msdCadInputTypeDataPoint,
~
msdCadInputTypeAny) Do S e l e c t Case i n p u t M e s s a g e . I n p u t T y p e Case m s d C a d I n p u t T y p e D a t a P o i n t CPoint
inputMessage.point
=
S e t m y E l l i p s e = CreateEllipseElement2(Nothing, CPoint, 0.5, 0.5, r o t M a t r i x )
-
ActiveModelReference.AddElement m y E l l i p s e E x i t Do Case m s d C a d I n p u t T y p e R e s e t E x i t Do End S e l e c t Loop End Sub
The last circle-creating procedure we will write allows the user to select two points. A circle is then drawn through the selected points. Sub T e s t C r e a t e C i rcl eD( 1 D i m C P o i n t As P o i n t 3 d D i m S t P o i n t As P o i n t 3 d D i m E n P o i n t As P o i n t 3 d D i m m y E l l i p s e As E l l i p s e E l e m e n t D i m r o t M a t r i x As M a t r i x 3 d D i m i n p u t Q u e u e As C a d I n p u t Q u e u e D i m i n p u t M e s s a g e As C a d I n p u t M e s s a g e D i m C i r R a d As D o u b l e
Set inputQueue
=
Set inputMessage
CadInputQueue =
~
inputQueue.GetInput(msdCad1nputTypeDataPoint. msdCadInputTypeAny) Do S e l e c t Case i n p u t M e s s a g e . 1 n p u t T y p e Case m s d C a d I n p u t T y p e D a t a P o i n t StPoint
=
inputMessage.point
E x i t Do Case m s d C a d I n p u t T y p e R e s e t E x i t Sub End S e l e c t Loop
-
I Chapter 15: Adding To Documents I Set inputMessage
=
inputQueue.GetInput(rnsdCadInputTypeDataPoint,
~
msdCadInputTypeAny) Do S e l e c t Case i n p u t M e s s a g e . I n p u t T y p e Case msdCadInputTypeDataPoint EnPoint
=
inputMessage.point
E x i t Do Case m s d C a d I n p u t T y p e R e s e t E x i t Sub End S e l e c t Loop
+ (EnP0int.X - StP0int.X) + (EnP0int.Y - StP0int.Y) CP0int.Z = StP0int.Z + (EnP0int.Z - StP0int.Z) C i r R a d = Point3dDistance(StPoint, E n P o i n t ) / 2 S e t m y E l l i p s e = CreateEllipseElementZ(Nothing, CP0int.X
=
StP0int.X
/ 2
CP0int.Y
=
StP0int.Y
/ 2 / 2
CPoint, CirRad, CirRad, r o t M a t r i x )
A c t i v e M o d e l Reference.AddElement myEl1i p s e End Sub
We calculate the center point of the circle by using the selected points. We also use the Microstation VBA P o i n t 3 d D i s t a n c e function to give us the distance between the selected points.
Creating Ellipses We have already used code that could create ellipses but the code created circles because the primary and secondary radii were the same. Let’s look at three examples of creating ellipses. Sub
TestCreateEll ipseA( 1 D i m C P o i n t As P o i n t 3 d D i m m y E l l i p s e As E l l i p s e E l e m e n t
D i m r o t M a t r i x As M a t r i x 3 d CP0int.X
=
2.5:
CP0int.Y
=
2.5
S e t m y E l l i p s e = CreateEllipseElementZ(Nothing, rotMatrix)
ActiveModelReference.AddElement m y E l l i p s e End Sub Sub
TestCreateEll ipseB( 1 D i m M a j o r A l As P o i n t 3 d
D i m M a j o r A 2 As P o i n t 3 d
C P o i n t , 1, 0 . 5 ,
I Graphical Elements I
301
D i m M i n o r A l As P o i n t 3 d D i m m y E l l i p s e As E l l i p s e E l e m e n t
MajorA1.X
=
1: M a j o r A 1 . Y
=
1
MajorA2.X
=
5: MajorA2.Y
=
5
MinorA1.X
=
3: MinorA1.Y
=
2
Set myEllipse
=
CreateEllipseElementl(Nothing, M a j o r A l , MajorAP,
-
M inorAl) ActiveModel Reference.AddElement myEl1 i p s e End S u b
S u b TestCreateEl 1 ipseC() D i m C P o i n t As P o i n t 3 d D i m m y E l l i p s e As E l l i p s e E l e m e n t D i m r o t M a t r i x As M a t r i x 3 d CP0int.X
=
2.5:
CP0int.Y
=
2.5
rotMatrix.RowX.X
=
2
rotMatrix.RowY.X
=
4: r0tMatrix.RowY.Y
=
5
S e t m y E l l i p s e = CreateEllipseElementZ(Nothing, rotMatrix)
C P o i n t , 1, 0 . 5 ,
A c t i v e M o d e l Reference.AddElement myEl1 i p s e End S u b
After running the above procedures, what do we find? Two of the three procedures shown above create ellipses. However, the procedure TestCreateEllipseB createdacircle. Themethod CreateEllipseElementl always creates a circle through the three points provided.
Creating Arcs We have five different ways we can create arcs in Microstation VBA. Function CreateArcElement 1(Template As Element, StartPoint As Point3d, Centerpoint As Point3d, Endpoint As Point3d) As ArcElement Function CreateArcElement2(TemplateAs Element, Centerpoint As Point3d, PrimaryRadius As Double, SecondaryRadius As Double, Rotation As Matrix3d, StartAngle As Double, SweepAngle As Double) As ArcElement Function CreateArcElement3(Template As Element, StartPoint As Point3d, PointOnCurve As Point3d, Endpoint As Point3d) As ArcElement
I Chapter 15: Adding To Documents I 4
Function CreateArcElement4(TemplateAs Element, StartTangent As Ray3d, Endpoint As Point3d) As ArcElement
5 Function CreateArcElement5(TemplateAs Element, Chord As Segment3d, ArcLength As Double, PlanePoint As Point3d) As ArcElement Let’s look at a few ways to use these methods. Sub T e s t C r e a t e A r c A ( D i m C P o i n t As P o i n t 3 d D i m S t P o i n t As P o i n t 3 d D i m E n P o i n t As P o i n t 3 d D i m m y A r c As A r c E l e m e n t
CP0int.X StP0int.X EnP0int.X S e t myArc End
1: C P 0 i n t . Y
= =
= =
1
=
4: S t P 0 i n t . Y
=
1
1: E n P 0 i n t . Y
=
4
CreateArcElementl(Nothing, S t P o i n t , C P o i n t , E n P o i n t )
ActiveModelReference.AddElement m y A r c Sub
Sub T e s t C r e a t e A r c B ( D i m C P o i n t As P o i n t 3 d D i m r o t M a t r i x As M a t r i x 3 d D i m m y A r c As A r c E l e m e n t CP0int.X
=
1: C P 0 i n t . Y
=
1
S e t m y A r c = CreateArcElementZ(Nothing, r o t M a t r i x , 0, P i ) End
CPoint, 0.5, 0.5,
-
ActiveModelReference.AddElement m y A r c Sub
Sub T e s t C r e a t e A r c C o D i m P o i n t A As P o i n t 3 d D i m P o i n t B As P o i n t 3 d D i m P o i n t C As P o i n t 3 d D i m m y A r c As A r c E l e m e n t
P0intA.X
=
1: P 0 i n t A . Y
=
1
P0intB.X
=
2: P 0 i n t B . Y
=
2
P0intC.X
=
1: P 0 i n t C . Y
=
3
= CreateArcElement3(Nothing, PointA, PointB, PointC) ActiveModelReference.AddElement m y A r c End Sub
S e t myArc
I Graphical Elements I
303
Sub T e s t C r e a t e A r c D ( )
D i m m y A r c As A r c E l e m e n t D i m myRay As Ray3d D i m E n d p o i n t As P o i n t 3 d myRay.0rigin.X
=
1
myRay.0rigin.Y
=
1
myRay.Directi0n.X
=
1
myRay. D i r e c t i o n . Y
=
4
EndP0int.X S e t myArc
= =
0: EndP0int.Y
=
2
C r e a t e A r c E l e m e n t 4 ( N o t h i n g , myRay, E n d p o i n t )
A c t i v e M o d e l Reference.AddElement myArc End Sub Sub T e s t C r e a t e A r c E ( )
D i m m y A r c As A r c E l e m e n t D i m mySeg As Segment3d D i m m y p o i n t As P o i n t 3 d
1: m y S e g . s t a r t P 0 i n t . Y
mySeg.startP0int.X
=
mySeg.EndP0int.X
=
4: m y S e g . E n d P 0 i n t . Y
myP0int.X
=
3.5:
myP0int.Y
S e t myArc
=
CreateArcElement5(Nothing,
=
=
3: myP0int.Z
1
=
4 =
0
mySeg, 8 . 5 ,
myPoint)
A c t i v e M o d e l Reference.AddElement myArc End Sub
Creating Text Text is easy to create by using the CreateTextElementl method. I4 F u n c t i o n CreateTextElementl(Temp1ate As E l e m e n t , T e x t As S t r i n g , O r i g i n A s P o i n t 3 d , R o t a t i o n A s M a t r i x 3 d ) As TextElement
Here is an example of creating nine text elements spaced 0.5 units away from each other. Sub T e s t C r e a t e T e x t A ( 1
D i m m y T e x t As T e x t E l e m e n t D i m T e x t P t As P o i n t 3 d D i m r o t M a t r i x As M a t r i x 3 d D i m I As D o u b l e For I
=
1 To 9
TextPt.Y
=
TextPt.Y
-
0.5
304
I Chapter 15: Adding To Documents I S e t myText
=
CreateTextElementl(Nothing, " N o t e
":", TextPt,
"
& I &
-
rotMatrix)
ActiveModelReference.AddElement m y T e x t Next I
End S u b
I
j
. + . _.
j
.
.
I I
!
INote 5: I
i .
I
.
.
.
.
.
.
.
.
.
.
Creating Cells Thus far, all elements we have created have been added to the design file as individual elements. When we begin working with cells, we work with multiple elements as a single cell. We create the elements in the same manner as when we are adding them to our model but instead of adding
I Graphical Elements I
305
the created element to the model we add it to the cell. We have three options for creating cells.
1 Function CreateCellElement1(Name As String, Elements() As -Element, Origin As
Point3d, [IsPointCellAs Boolean]) As
CellElement
2 Function CreateCellElement2(CellName As String, Origin As Point3d, Scale As Point3d, Truescale As Boolean, Rotation As Matrix3d) As CellElement
3 Function CreateCellElement3(CellName As String, Origin As Point3d, Truescale As Boolean) As CellElement Our first example creates a cell named "Box". Four lines are added to an array of elements. This array is used when we create the cell. S u b TeStCreateCell A (
)
D i m m y C e l l As C e l l E l e m e n t
To 3 ) As E l e m e n t
D i m BoxLines(0
D i m O r i g i n p o i n t As P o i n t 3 d
Set BoxLines(0)
=
CreateLineElementZ(Nothing,
~
Point3dFromXYZ(O, 0 , O ) , Point3dFromXYZ(4, Set BoxLines(1)
=
CreateLineElementZ(Nothing,
Point3dFromXYZ(4, Set BoxLines(2)
=
=
O),
4,
O),
~
Point3dFromXYZ(4,
CreateLineElementZ(Nothing,
Point3dFromXYZ(4, Set BoxLines(3)
0,
0 , 0)) 4 , 0))
-
P o i n t 3 d F r o m X Y Z ( O , 4 , 0))
CreateLineElementZ(Nothing,
~
P o i n t 3 d F r o m X Y Z ( O , 4 , O ) , P o i n t 3 d F r o m X Y Z ( O , 0 , 0)) 0riginPoint.X Set myCell
=
=
2: 0 r i g i n P o i n t . Y
=
2
CreateCellElementl("Box", B o x L i n e s , O r i g i n p o i n t )
A c t i v e M o d e l Reference.AddElement myCel1 m y c e l l . Redraw End S u b
TestCreateCellA creates A 4-unit square with an origin of (2,2,0). S u b TestCreateCell B ( 1 D i m m y C e l l As C e l l E l e m e n t
D i m CellElements(0
To 6)
As E l e m e n t
D i m O r i g i n p o i n t As P o i n t 3 d D i m r o t M a t r i x As M a t r i x 3 d Set CellElements(0)
=
CreateLineElementZ(Nothing,
-
I Chapter 15: Adding To Documents I Point3dFromXYZ(O, 0, 0). Point3dFromXYZ(4, 0, 0)) Set CellElements(1) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , Point3dFromXYZ(4, 0, 01, Point3dFromXYZ(4, 4, 0)) Set CellElements(2) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , Point3dFromXYZ(4, 4, 01, Point3dFromXYZ(O, 4, 0)) Set CellElements(3) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , Point3dFromXYZ(O, 4, 0). Point3dFromXYZ(O, 0, 0)) Set CellElements(4) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , Point3dFromXYZ(O, 0, 01, Point3dFromXYZ(4, 4, 0)) Set CellElements(5) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , Point3dFromXYZ(4, 0, 01, Point3dFromXYZ(O, 4, 0)) 0riginPoint.X = 2: 0riginPoint.Y = 2 Set CellElements(6) = CreateEllipseElementZ(Nothing, Originpoint, 1.25, 1.25, rotMatrix) Set myCell = CreateCellElementl("Box2", CellElements, Ori gi nPoi nt) ActiveModelReference.AddElement myCell myCel1 .Redraw ~
~
End Sub
As the number of elements we want in a cell increases, the upper-bound array number increases. Six lines and a circle are used in TestCreateCell B to create a cell named "Box2".
Creating cells is easy to do as we have already seen. Adding the cell to a cell library makes the creation of the cell useful in other files. Sub
TestCreateCell C( ) Dim Dim Dim Dim Set Set Set Set Set Set
myCell As CellElement CellElements(0 To 6 ) As Element Originpoint As Point3d rotMatrix As Matrix3d CellElements(0) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , Point3dFromXYZ(O, 0, 01, Point3dFromXYZ(4, CellElements(1) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , Point3dFromXYZ(4, 0, 0). Point3dFromXYZ(4, CellElements(2) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , Point3dFromXYZ(4, 4, 01, Point3dFromXYZ(O, CellElements(3) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , Point3dFromXYZ(O, 4, 01, Point3dFromXYZ(O, CellElements(4) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , Point3dFromXYZ(O, 0, 0). Point3dFromXYZ(4, CellElements(5) = C r e a t e L i n e E l e m e n t Z ( N o t h i n g , ~
0, 0))
~
4, 0)) 4, 0))
~
0, 0))
4, 0))
I Creating New Documents I
307
Point3dFromXYZ(4, 0, O), Point3dFromXYZ(O, 4, 0)) 0riginPoint.X = 2: 0riginPoint.Y = 2 Set CellElements(6) = CreateEllipseElement2(Nothing, Originpoint, 1.25, 1.25, rotMatrix) Set myCell = CreateCellElementl("Box3", CellElements, Ori gi nPoi nt) ActiveModel Reference.AddElement myCel1 mycell. Redraw Application.AttachCel1 Library "MicroStation VBA.ce1" Application.AttachedCellLibrary.AddCel1 mycell, M BOX^", M BOX^", False End Sub ~
~
Note that we specify the file name of the cell library we want to attach the cell to. We do not specify the full path, only the file name.
CREATINGNEWDOCUMENTS We have drawn lines, circles, ellipses, arcs, text, and cells to the current design file. This assumes we have a file to work with. How do we create new design files?
1 Function CreateDesignFile(SeedFileName As String, NewDesignFileName As String, Open As Boolean) As DesignFile
2
Sub CopyDesignFile(ExistingDesignFileNameAs String, NewDesignFileName As String, [Overwrite As Boolean])
308
I Chapter 15: Adding To Documents I Here are two methods that create new design files. CreateDesignFile allows us to specify whether the new file is to be a 2D or 3D file by specifying the seed document. Let's look at a couple of examples. Sub T e s t C r e a t e D e s i g n F i l e A ( ) D i m m y F i l e As D e s i g n F i l e
A p p l i c a t i o n . A c t i v e D e s i g n F i 1 e.C1 o s e S e t m y F i l e = CreateDesignFile("seedZd", " C : \ M i c r o S t a t i o n V B A \ f i 1e a . d g n " , T r u e ) ~
End Sub
TestCreateDesignFileA creates a new 2D design file. The file path and name are specified. After the CreateDesi gnFi 1 e line of code is executed, the new file is created and opened. It becomes the active document. If the file already exists, a new file is created and overwrites the existing file. Since we don't receive any warning of this, we should check if the file already exists. Sub T e s t C r e a t e D e s i g n F i l e B O
D i m m y F i l e As D e s i g n F i l e D i m m y F i l e N a m e As S t r i n g
myFileName
=
"C:\MicroStation
I f Dir(myFileName1 Set myFile
=
=
" "
VBA\filea.dgn"
Then
CreateDesignFile("seed3d", m y F i l e N a m e , T r u e )
Else MsgBox " T h e f i l e vbCri tical
"
& myFileName &
"
already exists.",
~
End I f End S u b
If the file we want to create exists (we know this by using the Dir function), we inform the user it already exists. If it does not exist, we create a new 3D file. Let's look at one more example: Sub T e s t C r e a t e D e s i g n F i l e C O
D i m m y F i l e As D e s i g n F i l e D i m I As Long
F o r I = 1 To 1 0 Set myFile = CreateDesignFile("seed2d","C:\MicroStation VBA\file" & I & ".dgn", False) Next I End Sub
I Security Issues with Creating Data I How many files does TestCreateDesignFileC Create? It Creten (''1 Each is a new 2D fie and the files are not opened in Microstation (the False Parameter).
309
file1 dgn file2 dgn file3 dgn
34 KB Bentley Microstation Design File 34 KB Bentley Microstation Design File 34 KB Bentley Microstation Design File
file4 dgn file5 dgn file6 dgn file7 dgn file8 dgn file9 dgn file10 dgn
34 KB 34 KB 34 KB 34 KB 34 KB 34 KB 34 KB
Bentley Bentley Bentley Bentley Bentley Bentley Bentley
Microstation Microstation Microstation Microstation Microstation Microstation Microstation
Design File Design File Design File Design File Design File Design File Design File
SECURITY ISSUES WITH CREATING DATA Our ability to create data in Microstation using VBA is dependent on our security settings. VBA is not intended to bypass these security settings. Writing and attempting to run the procedures in this chapter on one machine may result in the intended creation of data. Other machines with different security permissions may cause the code to fail. CAD administrators should be able to provide the appropriate permissions if this becomes a problem.
Simple geometry can be created with the knowledge of only a few Microstation VBA calls. The Object Browser and Microstation VBA help file can be used to find other data creation alternatives and can provide examples of how to use them.
16
Searching In Files Our design files range in complexity from one or two elements to many thousands. The number of elements can vary as well as the element types (lines, circles, arcs, text) and colors. Levels, line styles and classes can differ from element to element. Line weights and transparency can also vary. As we begin searching in our files, we will learn how to discover these properties we find in our files. In this Chapter:
The Basics of Searching Files Using Scancriteria Multiple Combinations of Criteria Reviewing Three Collection Methods Scan Criteria Methods
THEBASICSOF SEARCHING FILES Let’s begin by examining each element found in a file. S u b TestScanAl 1A ( ) Dim Dim
myElement A s Element myEnum A s ElementEnumerator
311
312
I Chapter 16: Searching In Files I S e t myEnum
=
ActiveModelReference.Scan0
W h i l e myEnum.MoveNext S e t myElement
=
myEnum.Current
D e b u g . P r i n t myElement.Type Wend End Sub
9 96 96 97 66 96 96 96 96 66 66 6 4
This procedure prints the type property value of each element in the active model to the Immediate Window. Running the procedure T e s t S c a n A l 1 A results in a list of numbers telling us the type of element found. This number references the msdElementType enumeration.
Here is a listing of the msdElementType enumeration’smembers:
msdXDatumTypeWorldSpacePosition msdEl ementType44 = 44 msdEl ementTypeArc = 16 msdElementTypeBsplineBoundary = 25 msdElementTypeBsplineCurve = 27 msdEl ementTypeBsp1 ineKnot = 26 msdEl ementTypeBsp1 inePol e = 21 msdElementTypeBsplineSurface = 2 4 msdElementTypeBsplineWeight = 28 msdEl ementTypeCel1 Header = 2 msdEl ementTypeCel1 Li braryHeader = 1 msdEl ementTypeComp1 exshape = 1 4 msdEl ementTypeComp1 exstring = 1 2 msdEl ementTypeCone = 23 msdElementTypeConic = 13 msdElementTypeCurve = 11 msdElementTypeDesignFileHeader = 9 msdElementTypeDgnStoreComponent = 38 msdElementTypeDgnStoreHeader = 39 msdElementTypeDigSetData
=
8
I The Basics of Searching Files I
313
msdElementTypeDimension = 33 msdEl ementTypeEl1 ipse = 15 msdElementTypeGroupData = 5 msdEl ementTypeLeve1 Mas k = 99 msdEl ementTypeLeve1 Symbol ogy = 10 msdEl ementTypeLine = 3 msdElementTypeLineString = 4 msdEl ementTypeMatrixDoub1 eData = 103 msdElementTypeMatrixHeader = 101 msd El emen tTypeMa t r i x I n teger Da ta = 102 m s d E l e m e n t T y p e M e s h H e a d e r = 105 msdElementTypeMicroStation = 66 msdEl ementTypeMu1 ti Line = 36 msdElementTypeNamedGroupComponent = 111 msdElementTypeNamedGroupHeader = 110 msdEl ementTypePointString = 22 msdElementTypeRasterComponent = 88 msdElementTypeRasterFrame = 94 msdElementTypeRasterHeader = 87 m s d E l e m e n t T y p e R a s t e r R e f e r e n c e = 90 msdElementTypeRasterReferenceComponent = 91 m s d E l e m e n t T y p e R e f e r e n c e A t t a c h m e n t = 100 msdElementTypeReferenceOverride = 108 msdElementTypeShape = 6 msdEl ementTypeSharedCel1 = 35 msdElementTypeSharedCel1 Definition = 34 msdEl ementTypeSo1 id = 19 msdEl ementTypeSurface = 18 msdEl ementTypeTab1 e = 96 msdEl ementTypeTab1 eEntry = 95 msdElementTypeTag = 37 msdElementTypeText = 17 msdElementTypeTextNode = 7 msdEl ementTypeView = 98 msdEl ementTypeViewGroup = 97 A review of the Immediate window, shown previously, shows the first three unique element types are 9, 96, and 97. Referring to the list above tells us the first three element types found were:
314
I Chapter 16: Searching In Files I msdElementTypeDesignFileHeader msdEl ementTypeTab1 e = 96 msdEl ementTypeViewGroup = 97
=
9
Not exactly lines, circles, or arcs, right? Microstation design files are composed of far more than what we see on the screen as we are working with Microstation. What are the next three element types? 6 6 , 6 , and 4.
msdEl ementTypeMi croStati on = 66 msdElementTypeShape = 6 msdEl ementTypeLineString = 4 Now we’re getting somewhere. We can see shapes and linestrings. We are going to do a lot of copy and paste operations in this chapter. Let’s begin by copying and pasting TestScanAll A as TestScannAll B. Sub
TestScanAll B ( 1 D i m m y E l e m e n t As E l e m e n t D i m myEnum As E l e m e n t E n u m e r a t o r
S e t myEnum
=
ActiveModelReference.Scan0
W h i l e myEnum.MoveNext S e t myElement
=
myEnum.Current
S e l e c t Case m y E l e m e n t . T y p e Case m s d E l e m e n t T y p e A r c D i m m y A r c As A r c E l e m e n t
S e t myArc
=
myElement
Case m s d E l e m e n t T y p e C u r v e
D i m m y c u r v e As C u r v e E l e m e n t S e t mycurve
=
myEl e m e n t
Case msdEl e m e n t T y p e L i n e D i m m y L i n e As L i n e E l e m e n t
Set myLine
=
myElement
Case m s d E l e m e n t T y p e T e x t
D i m m y T e x t As T e x t E l e m e n t S e t myText
=
myEl e m e n t
Case E l s e D e b u g . P r i n t myElement.Type End S e l e c t Wend End Sub
We can make the use of a Sel ect ... Case statement to allow us to perform actions based on the Element.Type property. As we cycle
I The Basics of Searching Files I
315
through each element in our ElementEnumerator we set each element to a generic element object. If we want to work with a LineElement we could do so through the generic element object but declaring a variable as a LineElement makes our programming tasks much easier. Let’s see why this is true. Subiype Transform Type URL URLTitie Vertex
SetXData Subiype Transform Type URL URLTitie
As we are programming, which list would help us most if we are working with a line element? The list on the left gives us a Startpoint property. Lines have start points. The list on the right does not have a Startpoint in the list. If we declare a variable as a LineElement we will see line-specific properties in addition to the standard element properties. Let’s do a little more with the above procedure. Copy and paste it as Tes t S c a n A l 1 C. After doing so, we are going to remove everything inside the Se 1 e c t ... Case statement except for the “Case msdElementTpeText” area. S u b TestScanAl 1 C ( ) D i m myElement As Element D i m myEnum A s E l e m e n t E n u m e r a t o r S e t myEnum
=
ActiveModelReference.Scan0
W h i l e myEnum.MoveNext S e t myElement
=
myEnum.Current
S e l e c t Case m y E l e m e n t . T y p e Case m s d E l e m e n t T y p e T e x t
D i m myText As T e x t E l e m e n t S e t myText myText.Text
=
myElement
=
UCase(myText.Text)
End S e l e c t Wend End S u b
Now, our procedure is only going to react to text elements. And what are we doing to the text element? U C a s e capitalizes everything. The result of this procedure should be the capitalization of all text elements, right?
316
I Chapter 16: Searching In Files I After this code is executed we should find that nothing has changed. How is this possible? The code is capitalizing the text. Let’s take a look at the next procedure and see if we can find what is missing. Sub T e s t S c a n A l l D ( 1 D i m m y E l e m e n t As E l e m e n t D i m myEnum As E l e m e n t E n u m e r a t o r S e t myEnum
=
ActiveModelReference.Scan0
W h i l e myEnum.MoveNext S e t myElement
=
myEnum.Current
S e l e c t Case m y E l e m e n t . T y p e Case m s d E l e m e n t T y p e T e x t
D i m m y T e x t As T e x t E l e m e n t S e t myText myText.Text
=
myEl e m e n t
=
UCase(myText.Text)
myText. R e w r i t e End S e l e c t Wend End Sub
If we don’t rewrite the element to the model, the text element may be modified in memory but the change is not actually made to the design file.
USINGSCANCRITERIA Now, let’s suppose we are working with a large file. It is composed of thousands of elements but only four of them are TextElements. If we run the code shown above, the TextElements will be capitalized to be sure. However, it may take a while because each and every element in the design file is reviewed. Let’s make our code more efficient by working only with text elements. We accomplish this through the use of an Elementscancriteria object. Sub T e s t S c a n F i 1 t e r A ( )
D i m myEnum As E l e m e n t E n u m e r a t o r D i m myFi 1 t e r As New E l e m e n t S c a n C r i t e r i a D i m E l e m e n t c o u n t e r As L o n g myFi 1 t e r . I n c l u d e T y p e msdEl e m e n t T y p e T e x t myFi 1 t e r . I n c l u d e T y p e msdEl e m e n t T y p e T e x t N o d e
I Using Scancriteria I
317
Set myEnum = A c t i v e M o d e l R e f e r e n c e . S c a n ( m y F i 1 t e r ) While myEnum.MoveNext Elementcounter = Elementcounter + 1 Wend MsgBox Elementcounter & elements found." End Sub "
When we include Text and TextNode elements, we should only be counting the number of Text and TextNode elements. After running this code, however, we find that something is not working as expected. On careful examination we find that, by default, Scancriteria includes everything. Before specifying which elements we want to look at, we need to exclude everything and then include those elements with which we want to work. Sub TestScanFi 1 t e r B ( 1 Dim myEnum As ElementEnumerator Dim myFilter As New Elementscancriteria Dim Elementcounter A s Long myFilter.ExcludeA11Types myFil ter.IncludeType msdElementTypeText myFilter.IncludeType msdElementTypeTextNode Set myEnum = A c t i v e M o d e l R e f e r e n c e . S c a n ( m y F i 1 t e r ) While myEnum.MoveNext Elementcounter = Elementcounter + 1 Wend MsgBox Elementcounter & elements found." End Sub "
Now, myEnum only contains Text and TextNode elements. Let's build on T e s t S c a n F i 1 terB by adding a filter for a specific level. Before we look for a specific Level, we must first exclude all levels. If we miss this critical step, we will be retrieving all levels. Sub TestScanFi 1 t e r C ( 1 Dim myEnum A s ElementEnumerator Dim myFilter A s New ElementScanCriteria Dim Elementcounter As Long myFilter.ExcludeA11Types myFi 1 ter. Excl udeAl1 Levels myFil ter.IncludeType msdElementTypeText myFilter.IncludeType msdElementTypeTextNode
I Chapter 16: Searching In Files I myFilter.IncludeLeve1 ActiveDesignFile.Levels("SIDEWALK") Set myEnum = ActiveModelReference.Scan(myFi1ter) While myEnum.MoveNext Elementcounter = Elementcounter + 1 Wend elements found." MsgBox Elementcounter & End Sub "
Let's look over the macro "ScanFilterc': What is being counted here? Text elements and TextNode elements on Level "SIDEWALK': Sub T e s t S c a n F i 1 t e r D ( ) Dim myEnum As ElementEnumerator Dim myFi 1 ter As New El ementScanCri teria Dim Elementcounter As Long myFi 1 ter . Excl udeAl1 Types myFi 1 ter. Excl udeAl1 Levels myFi 1 ter. Excl udeAl1 Colors myFi 1 ter. Incl udeType msdEl ementTypeText myFi 1 ter. Incl udeType msdEl ementTypeTextNode myFi 1 ter. Incl udeLevel ActiveDesignFi le. Level s("S1DEWALK") myFi 1 ter. Incl udeCol or 4 Set myEnum = ActiveModelReference.Scan(myFi1ter) While myEnum.MoveNext Elementcounter = Elementcounter + 1 Wend MsgBox Elementcounter & elements found." End Sub "
We have added one more scan criteria. In addition to looking at the element type and level, we are now looking at the color. If we know a color's index in the document's color table, we can specify it as shown above. Let's look at the next example where we specify an RGB color value to filter for a specific color. We will also add one more item in our scan criteria. Let's add a Linestyle criteria. Sub T e s t S c a n F i 1 t e r F ( ) Dim myEnum As ElementEnumerator Dim myFi 1 ter As New El ementScanCri teria Dim Elementcounter As Long Dim myCol orTabl e As Col orTabl e Dim mycolor As Long Set myColorTable = ActiveDesignFile.ExtractColorTable
I Using Scancriteria I
319
= myColorTable.FindClosestColor(RGB(l92, myFilter.ExcludeA11Types
mycolor
192, 192))
myFi 1t e r . E x c l u d e A l 1 L e v e l s
myFilter.ExcludeA11Colors myFi 1t e r . E x c l u d e A l 1 L i n e S t y l e s myFilter.IncludeType
msdElementTypeLineString
m y F i l t e r . I n c l udeLi n e S t y l e A c t i v e D e s i g n F i 1 e . L i n e S t y l e s ( " ( Hidden 1 " )
m y F i 1 t e r . I n c l u d e L e v e l A c t i v e D e s i g n F i l e . L e v e l s ( "SIDEWALK") m y F i 1 t e r . I n c l u d e C o l o r myCol o r S e t myEnum
=
-
1
ActiveModelReference.Scan(myFi1ter)
W h i l e myEnum.MoveNext Elementcounter
=
Elementcounter + 1
Wend MsgBox E l e m e n t c o u n t e r &
"
elements found."
End Sub
And yet another scan criteria is added in our next procedure: Sub T e s t S c a n F i 1 terG( 1 D i m myEnum As E l e m e n t E n u m e r a t o r D i m m y F i l t e r As New E l e m e n t s c a n c r i t e r i a
D i m E l e m e n t c o u n t e r As L o n g D i m m y C o l o r T a b l e As C o l o r T a b l e D i m m y c o l o r As L o n g Set myColorTable
=
ActiveDesignFile.ExtractColorTab1e
myColorTable.FindClosestColor(RGB(l92, myFilter.ExcludeA11Types mycolor
=
192, 192))
myFi 1t e r . E x c l u d e A l 1 L e v e l s
myFilter.ExcludeA11Colors myFi 1t e r . E x c l u d e A l 1 L i n e S t y l e s
myFilter.ExcludeA11Classes m y F i l t e r . I n c l u d e T y p e msdElementTypeLineString rnyFil t e r . I n c l u d e L i n e S t y l e A c t i v e D e s i g n F i l e . L i n e S t y l e s ( " ( Hidden ) " )
m y F i 1 t e r . I n c l u d e L e v e l A c t i v e D e s i g n F i l e . L e v e l s ( "SIDEWALK") m y F i 1 t e r . I n c l u d e C o l o r myCol o r
-
1
myFil t e r . I n c l u d e c l a s s msdElementClassPrimary S e t myEnum
=
ActiveModelReference.Scan(myFi1ter)
W h i l e myEnum.MoveNext Elementcounter
=
Elementcounter
+ 1
Wend MsgBox E l e m e n t c o u n t e r & End Sub
"
elements found."
320
I Chapter 16: Searching In Files I Now we are adding the “Class”to our scan criteria. Thus far we have excluded everything from our criteria and added in only the criteria we wanted. When we ‘ExcludeAllLevels’,the number of levels we exclude varies from file to file. Now, let’s look at each of the levels in our design file. One specific level will not be added to our scan criteria and everything else will be added. S u b T e s t S c a n F i 1t e r H ( ) D i m myEnum As E l e m e n t E n u m e r a t o r D i m myFi 1 t e r As New E l e m e n t S c a n C r i t e r i a D i m m y L e v e l As L e v e l D i m E l e m e n t c o u n t e r As L o n g myFi 1 t e r . E x c l u d e A l 1 L e v e l s
F o r Each m y L e v e l I n A c t i v e D e s i g n F i l e . L e v e 1 s S e l e c t Case U C a s e ( m y L e v e l . N a m e ) C ase
”
S ID EW A LK”
Case E l s e m y F i l t e r . I n c l udeLevel myLevel End S e l e c t Next S e t myEnum
=
ActiveModelReference.Scan(myFi1ter)
W h i l e myEnum.MoveNext Elementcounter
=
Elementcounter
+ 1
Wend MsgBox E l e m e n t c o u n t e r &
”
elements found.”
End S u b
When dealing with our Elementscancriteria object, everything is within the bounds of the criteria. Since we don’t have the option to remove a specific element type or level, etc., in the above example, we remove all levels and then add back those levels that meet our criteria. In the above example, we are adding all levels except for the “SIDEWALK” level.
I Multiple Combinations of Criteria I
321
MULTIPLE COMBINATIONS OF CRITERIA Thus far we have dealt with elements matching specific criteria in each procedure. What do we do if we want all cells on level “Columns”and all text elements on level “Marks”?Here are three ways to accomplish the same task. S u b TestScanFi 1 t e r J ( 1 D i m myElem As E l e m e n t D i m myEnum As E l e m e n t E n u m e r a t o r D i m myEnum2 As E l e m e n t E n u m e r a t o r
D i m m y F i l t e r As New E l e m e n t s c a n c r i t e r i a D i m m y f i l t e r 2 As New E l e m e n t s c a n c r i t e r i a D i m E l e m e n t c o u n t e r As Long
myFilter.ExcludeA11Types myFi 1t e r . E x c l u d e A l 1 L e v e l s myFil ter.IncludeType
msdElementTypeSharedCel1
m y F i 1 t e r . I n c l u d e L e v e l A c t i v e D e s i g n F i 1 e . L e v e l s ( “COLUMNS”) S e t myEnum
=
ActiveModelReference.Scan(myFi1ter)
W h i l e myEnum.MoveNext Elementcounter
=
Elementcounter + 1
Wend
myFilter2.ExcludeAllTypes myFi 1t e r 2 . E x c l u d e A l 1 L e v e l s m y F i l t e r 2 . I n c l u d e T y p e msdElementTypeText m y F i 1 t e r 2 . I n c l u d e L e v e l A c t i v e D e s i g n F i 1 e . L e v e l s ( “MARKS”) S e t myEnum2
=
ActiveModelReference.Scan(myFilter2)
W h i l e myEnum2.MoveNext Elementcounter
=
Elementcounter
+ 1
Wend MsgBox E l e m e n t c o u n t e r &
”
elements found.”
End S u b
We can use two different enumerator objects with two different scan criteria objects. This is one way to deal with our current scenario. Are there other ways we can accomplish the same goal? S u b TestScanFi 1 t e r K ( 1 D i m myElem As E l e m e n t
D i m myEnum As E l e m e n t E n u m e r a t o r
322
I Chapter 16: Searching In Files I D i m myFi 1 t e r As New E l e m e n t S c a n C r i t e r i a D i m m y C o l l e c t i o n As New C o l l e c t i o n myFi 1 t e r . E x c l u d e A l 1 T y p e s myFi 1 t e r . E x c l u d e A l 1 L e v e l s myFi 1 t e r . I n c l u d e T y p e msdEl e m e n t T y p e S h a r e d C e l 1
myFilter.IncludeLeve1 ActiveDesignFile.Levels("COLUMNS") S e t myEnum
=
ActiveModelReference.Scan(myFi1ter)
W h i l e myEnum.MoveNext S e t myElem
=
myEnum.Current
my C o 1 1 e c t io n . Add my E 1 em
Wend myFi 1 t e r . R e s e t myFi 1 t e r . E x c l u d e A l 1 T y p e s myFi 1 t e r . E x c l u d e A l 1 L e v e l s myFi 1 t e r . I n c l u d e T y p e msdEl e m e n t T y p e T e x t
myFilter.IncludeLeve1 ActiveDesignFile.Levels("MARKS") S e t myEnum
=
ActiveModelReference.Scan(myFi1ter)
W h i l e myEnum.MoveNext S e t myElem
=
myEnum.Current
my C o 1 1 e c t io n . Add my E 1 em
Wend MsgBox m y C o l 1 e c t i o n . C o u n t &
"
e l ements f o u n d .
"
End S u b
This is another way to accomplish the same goal. We apply two separate criteria. As we move through each enumerator, we add the element in the enumerator to a custom collection. This allows us to work with a single collection of objects after each combination of criteria is applied.
I Multiple Combinations of Criteria I
323
Cache
Long
Color DateLastModified
#9L?OL?OO55:26:39PM#
Date
FilePosition
Long
GraphicGroup
4000017 0
HasAnyTags
False
Boolean
lnDisplay Set
True
Boolean
Class
Adding a watch to the variable myCollection shows something like this:
ElementCachelElementCac
18 msdEIementCIassPrimary 0
Cachelndex
MsdEIementCIass Long
Long
IsComponentElement False
Boolean
IsFromAttachment
False
Boolean
IsGraphical
True
Boolean
IsHidden IsLinear
False
Boolean
False
Boolean
IsLocked
False
Boolean
IsModified
True
Boolean
lsNew
True
Boolean
IsSnappable
True
Boolean
IsValid
True
Boolean Levelhevel
Level
LineStylehineSty le
LineStyle Lineweight ModelReference
0
Long ModelReferencehlodelRel
Subtype
Type URL
msdElementTypeSharedCell MsdElementType
URLTiile
String String
iiem 2
VariantmbjecilElement
iiem 3
VariantmbjecilElement
Here is one more way to accomplish the same task. We are going to create a named group and then add the objects we find to the named group. Sub T e s t S c a n F i 1 t e r M ( Dim myEnum As ElementEnumerator Dim myFilter As New Elementscancriteria Dim myGroup As NamedGroupElement Set myGroup = A c t i v e M o d e l R e f e r e n c e . A d d N e w N a m e d G r o u p ( " G r o u p A " ) myFilter.ExcludeA11Types myFi 1 ter. Excl udeAl1 Levels myFil ter.IncludeType msdElementTypeSharedCel1 myFil ter. IncludeLevel ActiveDesignFile Leve s ( CO LU M N S Set myEnum = ActiveModelReference.Scan myFi ter) While myEnum.MoveNext myGroup.AddMember myEnum.Current Wend "
"
324
I Chapter 16: Searching In Files I myFi 1 t e r . Reset myFi 1 t e r . E x c l u d e A l 1 Types myFi 1 t e r . E x c l u d e A l 1 L e v e l s myFi 1 t e r . I n c l udeType msdEl ementTypeText
myFilter.IncludeLeve1 ActiveDesignFile.Levels("MARKS") S e t myEnum
=
ActiveModelReference.Scan(myFi1ter)
W h i l e myEnum.MoveNext myGroup.AddMember myEnum.Current Wend myGroup. Rewri t e MsgBox myGroup.MembersCount &
"
elements found."
End Sub
. REVIEWINGTHREE COLLECTION METHODS Each of the three methods described above have their advantages and disadvantages. For the sake of discussion, we will refer to the methods as multi-criteria, collection, and group. The multi-criteria method provides a straightforward and simple way to get groups of criteria in their own individual enumerators. One benefit to doing things this way is that we have our individual groups of criteria in their own distinct groups. This allows us to work with each group separately if desired. The primary disadvantage is that these individual groups make it more difficult to work with the elements in each group as a whole. The collection method uses only one Scancriteria object and places all objects found into a single custom VBA collection. Doing so allows us to use For Each ... Next statements on the entire collection, remove items from the collection, etc. The group method may provide the best possible results. Each item is placed into a single container. This gives us the same benefit as using a collection. The real benefit to using groups is that when we use "myGroup.Rewrite", the group is added to the design file and can be used by the user with other standard Microstation commands and
I Scan Criteria Methods I
325
functionality. If we do not “rewrite” the group, the elements added to the group do not get added to the group in the design file even though the group itself is in the design file. So, if we want to use a group without rewriting it to the design file, we should remove the group after we have completed our programming tasks.
SCAN CRITERIA
METHODS
The code we have written in this chapter has used several methods of the Elementscancriteria Object. Here is a comprehensive listing of the methods: Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub Sub
Excl udeAl1 C1 asses( ) Excl udeAl1 Colors( ) Excl udeAl1 Levels( ) Excl udeAl1 Li neStyl es( ) Excl udeAl1 LineWeights() Excl udeAl1 Subtypes( ) Excl udeAl lTypes( ) Excl udeGraphical() Excl udeNonGraphica1 ( ) IncludeClass(ElemC1ass As MsdElementClass) IncludeColor(Color1ndex As Long) Incl udeLevel (Level As Level ) IncludeLineStyle(LineSty1e As Linestyle) IncludeLineWeight(LineWeight As Long) IncludeOnlyCell(CellName As String) IncludeOnlyFilePositionRange(Min As Long, Max As Long) IncludeOnlyGraphicGroup(GraphicGroupNumber As Long) Incl udeOnlyHole() IncludeOnlyInvisibleO Incl udeOnlyLocked() IncludeOnlyModifiedO IncludeOnlyModifiedRange(Min As Date, [Max As Date]) Incl udeOnlyNew() Incl udeOnlyNonPlanar() IncludeOnlyNonSnappableO Incl udeOnlyOld() Incl udeOnlyPlanar() IncludeOnlySnappableO
326
I Chapter 16: Searching In Files I Sub I n c l u d e O n l y S o l i d O Sub I n c l u d e O n l y U n l o c k e d O Sub I n c l u d e O n l y U n m o d i f i e d O Sub
IncludeOnlyUserAttribute(UserAttribute1D As L o n g )
Sub I n c l u d e O n l y V i s i b l e O Sub
IncludeOnlyWithinRange(Range As R a n g e 3 d )
Sub I n c l u d e S u b t y p e ( L 0 n g As L o n g ) Sub I n c l u d e T y p e ( T y p e As M s d E l e m e n t T y p e ) Sub R e s e t 0
A review of the Microstation VBA help file explains any of the methods that are not self-explanatory. One method is worth noting: the "IncludeOnlyWithinRange" method. Sub T e s t S c a n F i 1 t e r N ( )
D i m myEnum As E l e m e n t E n u m e r a t o r D i m m y F i 1 t e r As New E l e m e n t S c a n C r i t e r i a D i m m y G r o u p As N a m e d G r o u p E l e m e n t D i m myRange As Range3d
S e t myGroup
=
myRange.Low.X
ActiveModelReference.AddNewNamedGroup("GroupC") =
myRange.High.X
=
1: myRange.Low.Y
=
3: myRange.High.Y
1: myRange.Low.Z =
=
3 : myRange.High.Z
0 =
0
myFilter.Include0nlyWithinRange myRange S e t myEnum
=
ActiveModelReference.Scan(myFi1ter)
W h i l e myEnum.MoveNext myGroup.AddMember m y E n u m . C u r r e n t Wend myGroup. Rewri t e MsgBox m y G r o u p . M e m b e r s C o u n t &
"
elements found."
End Sub
The ability to scan a file from within only a specific area is very powerful. We may look for elements surrounding a point selected by the user, for example. Or we may scan for elements surrounding cells with a specific name. The range we specify is 3D so we can provide a L0w.Z and a High.Z value if we are working on 3D files.
I Review I
327
Each file in Microstation is composed of many objects. Some of these are visible, others are not. Levels, for example, are not graphical elements but are still very important. We should be careful when we scan our files. If we scan with the intent to create new geometry, it is possible to create a problem for ourselves. For example, if we are scanning a file for lines and are drawing new lines over old ones, the new lines may be added to our Scancriteria and we could end up in an endless loop. This chapter covered scanning Microstation files with pre-defined criteria. In the next chapter, a user makes selections in Microstation and then has our code manipulate the selection.
17
Interactive Modification User interaction can be helpful when modifymg files and elements in VBA. When our programs are designed well, they are powerful and flexible. In this Chapter:
Giving users feedback and information Working with selection sets Getting user input Using the send command Employing modeless dialog boxes Applying some real-world applications Interacting with MDL applications
GIVINGUSERS FEEDBACK AND lNFORMATlON When we are working with Microstation in any capacity, three distinct areas at the bottom of the Microstation window give us information and feedback.
329
330
I Chapter 17: Interactive Modification I These areas are called the command, prompt, and status areas. ~~~~~~~
~F~~~~
~~~~~~
When we begin the “Place SmartLine” command, we see the command and the first prompt associated with this command. We are prompted to “Enter first vertex’: After we click the first vertex, we are prompted to “Enter next vertex or reset to complete’: The Status area gives us general feedback on the results of selections and other commands. Let’s see how we can work with these areas to give our users similar feedback and information as they use our programs. Sub TestShowCommandO ShowCommand “ D r a w a L i n e ” ShowPrompt “ S e l e c t F i r s t P o i n t : ” ShowStatus “Draw L i n e b y s e l e c t i n g two p o i n t s . ” End Sub
Three methods are used to show the text we want to display in the command, prompt, and status areas of Microstation. Even though the user can change the size of the command/prompt area, make sure that commands and prompts are visible without requiring users to stretch the area wider. Commands and prompts are not meant to provide comprehensive instructions, but rather, general guidelines. Sub TestShowTempMessageO ShowTempMessage r n s d S t a t u s B a r A r e a L e f t ,
“Message L e f t . ”
ShowTempMessage r n s d S t a t u s B a r A r e a M i d d l e , “ M e s s a g e M i d d l e . End Sub
”
I Giving Users Feedback and Information I
331
Another way we can provide feedback to the user is by sending a “Temporary Message’: We have the option of placing the message in the ‘‘Left Area” or the “Middle Area”. Messages placed in the middle area also appear in the Message Center. Sub ShowTempMessage(AreaAs MsdStatusBarArea, Message As String, [Details As String]) Here is the declaration for ShowTempMessage. It has one optional parameter, “Details’: When we provide a value for this parameter and we have specified “msdStatusBarAreaMiddle” as the location for the message, the detail we provide displays in the Message Center. This is an excellent way to provide a more lengthy message to the user if needed. Sub
TestShowTempMessageCenterO ShowTempMessage m s d S t a t u s B a r A r e a M i d d l e , “ C h a n g e s made t o f i l e : ” ,
“ C h a n g e s w e r e made t o t h e f i l e C : \ t e s t a . d g n . “ T h e s e c h a n g e s w e r e made by t h e m a c r o ” ” ”
End S u b
T e s t S h ow Temp Me s s a g e C e n t e r ” ”
.
”
”
&
-
”
&
-
-
332
I Chapter 17: Interactive Modification I
Circle, Level Default Shaoe. Level Default
The next feedback method we will look at is the Show E r r o r method. The text we supply with this method displays in the command/prompt area. Sub TestShowError() ShowError "Selection o f Cell Failed." End Sub
. WORKING WITHSELECTIONSETS Users can select elements in their files through a variety of methods. Once selected, we can make modifications to the selected elements by using the GetSel ectedEl ements method. Sub TestSel ecti onSetA( ) Dim myElement As Element Dim myElemEnum As ElementEnumerator Set myElemEnum = ActiveModelReference.GetSelectedElements While myElemEnum.MoveNext Set myEl ement = myEl emEnum. Current myElement. Level = ActiveModel Reference. Levels( "A-FURN-FREE") Wend End Sub
We used the ElementEnumerator in a previous chapter. In this example, we get the selected elements and change the level of each element oneby-one. Let's look carefully at the code. Are we missing anything? Sub TestSelectionSetB() Dim myElement As Element Dim myElemEnum As ElementEnumerator Set myElemEnum = ActiveModelReference.GetSelectedElements
I Working With Selection Sets I
333
Whi 1 e myEl emEnum.MoveNext Set myElement = myElemEnum.Current myElement. Level = ActiveModel Reference. Levels("A-FURN-FREE") myEl ement. R e w r i t e
Wend End Sub
If we do not rewrite the element to the design file, element modifications are not persistent. This is critical. You could spend a great deal of time debugging code only to find that changes made to elements are not reflected in your files. Any changes made to elements in files must be rewritten back to the file or they are not permanent. This is by design. Sub
TestSelectionSetC(1 Dim mysettings A s Settings Set mysettings = Application.ActiveSettings If MsgBox("Change Selection to Color & rnySettings.Color & " ? " . vbYesNo) = vbYes Then Dim myElement A s Element Dim myElemEnum A s ElementEnumerator Set myElemEnum = ActiveModel Reference.GetSelectedElements While myElemEnum.MoveNext Set myElement = myElemEnum.Current myElement.Color = mySettings.Color myElement.Rewrite Wend End If "
~
End S u b
changes all selected elements to the active color in Microstation if the user clicks on the Yes button in the MessageBox. We are using the same methodology going through each of the elements in the ElementEnumerator. TestSel ectionSetC
334
I Chapter 17: Interactive Modification I GETTINGUSERINPUT Thus far we have discussed prompting the user with information and working with previously-selected elements. Allowing the user to give us input as our procedures execute makes our interactive modifications more powerful. The Cad Input Queue allows us to capture some of the user's interaction with Microstation. Let's look at a few examples of using the CAD Input Queue. We begin with a very simple example that demonstrates the use of the CAD Input Queue and then move to some real-world examples. Sub TestCadInputA() Dim myCIQ As CadInputQueue Dim myCIM As CadInputMessage Dim I A s Long Set myCIQ = CadInputQueue For I = 1 T o 10 Set myCIM = myCIQ.GetInput Debug.Print myCIM.InputType Next I End Sub
In the above example, we capture ten user interactions and print the InputType to the Immediate Window. The main thing we want to see with this example is the mechanics of how to use the CadInputQueue and the CadInputMessage. Let's make a couple of modifications to the above example to capture only point selections. Sub TestCadInputB() Dim myCIQ As CadInputQueue Dim myCIM As CadInputMessage Dim I A s Long Dim pt3Selection As Point3d Set myCIQ = CadInputQueue For I = 1 T o 10 Set myCIM = myCIQ.GetInput(msdCad1nputTypeDataPoint) pt3Selection = myCIM.Point Debug.Print pt3Selection.X & " , & pt3Selection.Y Next I End Sub "
I Getting User Input I
335
The CadInputQueue captures a number of different types of inputs. When we use the GetInput method we can specify which type of inputs we want to capture. In the above example we are restricting the capture to data point entries. Since we know we are getting a point, we can use the point property of the CadInputMessage object and print the X, Y, and Z elements of the point to the Debug window (Immediate Window). Let’s continue to build on our “TestCadInput” macros. In the next example we will capture points and resets. Sub TestCadInputC( 1 Dim myCIQ A s CadInputQueue Dim myCIM A s CadInputMessage Dim I As Long Dim pt3Selection A s Point3d Set myCIQ = CadInputQueue For I = 1 To 10 Set myCIM = myCIQ.GetInput(msdCadInputTypeDataPoint, msdCadInputTypeReset) Select Case myCIM.InputType Case msdCadInputTypeDataPoint pt3Selection = myCIM.Point Debug.Print pt3Selection.X & ” , & pt3Selection.Y Case msdCadInputTypeReset Exit For End Select Next I End Sub ”
Now, our macro captures up to ten input points or until a reset is initiated by the user. We use Exi t For to exit out of the loop when a reset is detected. We have introduced DataPoint and Reset input types, so what other types are available to us?
msdCadInputTypeCommand = 1 msdCadInputTypeReset = 2 msdCadInputTypeDataPoint = 3 msdCadInputTypeKeyin = 4 msdCadInputTypeAny = 5 msdCadInputTypeUnassignedCB = 6
I Chapter 17: Interactive Modification I When we begin capturing input using the CadInputQueue, our program listens to each of the inputs, then the results of the inputs is entirely in the hands of our program. For example, if we begin capturing inputs, selecting a command from a toolbar sends the command information to our queue but Microstation does not begin acting on the command immediately. Sub
TestCadInputD() D i m m y C I Q As C a d I n p u t Q u e u e D i m m y C I M As C a d I n p u t M e s s a g e D i m I As Long D i m p t 3 S e l e c t i o n As P o i n t 3 d
S e t myCIQ
=
CadInputQueue
F o r I = 1 To 1 0 S e t myCIM
=
myCIQ.GetInput
S e l e c t Case m y C I M . I n p u t T y p e Case msdCadInputTypeCommand D e b u g . P r i n t "Command" & v b T a b & rnyC1M.CornrnandKeyin Case r n s d C a d I n p u t T y p e R e s e t E x i t For Case
msdCadInputTypeDataPoint pt3Selection
=
myCIM.Point
Debug.Print " P o i n t " & vbTab & pt3Selection.X & vbTab &
p t 3 S e l e c t i o n . Y & vbTab &
-
p t 3 S e l e c t i o n . Z & vbTab &
-
rnyCIM.View.Index
& vbTab &
-
myC1M.ScreenPoint.X
& vbTab &
myC1M.ScreenPoint.Y
& vbTab &
myC1M.ScreenPoint.Z Case r n s d C a d I n p u t T y p e K e y i n Debug.Print
" K e y i n " & vbTab & myCIM.Keyin
Case r n s d C a d I n p u t T y p e A n y Debug.Print Case
"Any"
msdCadInputTypeUnassignedCB D e b u g . P r i n t "UnassignedCB" & vbTab & myCIM.CursorButton
End S e l e c t Next I End Sub
~
~
~
-
I Getting User Input I
337
This procedure captures ten inputs or captures until a reset is detected. Point Point Command Command Command Keyin Point Point Command Point
11581.8836494914 9599.68844328587
27463.2962386063 28602.2371305697
0 0
1 1
377 579 0 196 475 0
10796.1239475839 31518.0353275433 0 1 10664.7076908189 30833.5756568922 0 1 MDL KEYIN l v l m a n g r l e v e l m a n a g e r d i a l o g o p e n 11195.8483952442 31019.7486873093 0 1
168 238 0 144 363 0
PLACE S M A R T L I N E PLACE BLOCK I C O N CGPLACE C I R C L E I C O N bogus k e y i n
241 329 0
The results of running this procedure with a variety of inputs.
Points The points selected gives us much more than the X, Y, and Z locations in Microstation. We also see in which view the point was selected and the screen coordinates in X, Y, and Z when the point was selected. The screen X, Y, and Z could be useful for more advanced work such as displaying graphical information in Microstation using the Windows API.
Commands Whenever a legitimate Microstation command is initiated and we are listening using the Cad Input Queue, the input comes across as a command. This is the case no matter whether the command was initiated using menus, toolbars, or the Keyin window.
Keyin If the Keyin window is used to enter a legitimate command, the input is registered as a command and not a keyin. When something is entered in the Keyin window that does not result in a legitimate command, it is registered as a keyin. The example above demonstrates this when “bogus keyin” was entered into the Keyin window.
Unassigned Cursor Buttons An unassigned cursor button generates an UnassignedCB input. We use the CursorButton property of the message to retrieve which cursor button was used.
338
I Chapter 17: Interactive Modification I Reset The Reset Input is triggered when the user initiates a reset. For example, clicking the right mouse button initiates a reset when the user is asked to select a point.
SOME REAL-WORLDAPPLICATIONS Now that we have an understanding of how these inputs work, let’s put them to work in some real-world examples. S u b TestCadInputE() Dim myCIQ As CadInputQueue Dim myCIM As CadInputMessage Dim pt3Start As Point3d Dim pt3End As Point3d Dim myLine As LineElement Set myCIQ = CadInputQueue Set myCIM = rnyCIQ.GetInput(rnsdCadInputTypeDataPoint, rnsdCadInputTypeReset) Select Case rnyCIM.InputType Case msdCadInputTypeReset Exit S u b Case msdCadInputTypeDataPoint pt3Start = rnyCIM.Point End Select Set myCIM = rnyCIQ.GetInput(rnsdCadInputTypeDataPoint, msdCadInputTypeReset) Select Case myCIM.InputType Case msdCadInputTypeReset Exit S u b Case msdCadInputTypeDataPoint pt3End = rnyCIM.Point End Select Set myLine = CreateLineElementZ(Nothing, pt3Start, pt3End) ActiveModel Reference.AddE1 ement myLine myLi ne. Redraw End S u b
TestCadInputE allows the user to select two points. A line is then drawn between these two points. A careful examination of the code, and
I Some Real-World Applications I
339
better yet, running the code, reveals that although the user can select two points and a line is drawn between the points, the user has no way of knowing what to do or what the results of the actions will be. Let's use our knowledge of ShowCommand and ShowPrompt to make the macro more user friendly. Sub
TestCadInputF( 1 D i m myCICl As C a d I n p u t Q u e u e D i m myCIM As C a d I n p u t M e s s a g e
D i m p t 3 S t a r t As P o i n t 3 d D i m p t 3 E n d As P o i n t 3 d D i m m y L i n e As L i n e E l e m e n t S e t myCICl
=
CadInputQueue
ShowCommand " T w o - P o i n t L i n e " ShowPrompt " S e l e c t F i r s t P o i n t : " S e t myCIM
=
myCIQ.GetInput(msdCadInputTypeDataPoint,
~
msdCadInputTypeReset.1 S e l e c t Case m y C I M . I n p u t T y p e Case m s d C a d I n p u t T y p e R e s e t ShowPrompt
S h ow C o mm a n d
" " " "
ShowStatus "Two-Point L i n e Reset."
E x i t Sub Case m s d C a d I n p u t T y p e D a t a P o i n t pt3Start
=
myCIM.Point
End S e l e c t ShowPrompt " S e l e c t Second P o i n t : " S e t myCIM
=
myCIQ.GetInput(msdCadInputTypeDataPoint,
~
msdCadInputTypeReset.1 S e l e c t Case m y C I M . I n p u t T y p e Case m s d C a d I n p u t T y p e R e s e t ShowPrompt
S h ow C o mm a n d
" " " "
ShowStatus "Two-Point L i n e Reset."
E x i t Sub Case m s d C a d I n p u t T y p e D a t a P o i n t pt3End
=
myCIM.Point
End S e l e c t Set myLine
=
CreateLineElementZ(Nothing,
ActiveModel Reference.AddElement myLine m y l i n e . Redraw
p t 3 S t a r t . pt3End1
340
I Chapter 17: Interactive Modification I ShowPrompt S h ow C omm a n d Showstatus "Two-Point Line Drawn." End Sub " "
" "
Now, when this macro is run, the user is prompted at each step. The CadInputQueue can be used for more than just capturing user input. We can use it to execute commands as well. Here is one example: Sub TestCadInputH() Dim myCIQ As CadInputQueue Dim myCIM As CadInputMessage Dim pt3Start As Point3d Dim pt3End As Point3d Dim myLine As LineElement Dim S e l E l e m s O As Element Set myCIQ = CadInputQueue Set myCIM = rnyCIQ.GetInput(rnsdCadInputTypeDataPoint, rnsdCadInputTypeReset) Select Case rnyCIM.InputType Case msdCadInputTypeReset Exit Sub Case msdCadInputTypeDataPoint pt3Start = rnyCIM.point End Select Set myCIM = rnyCIQ.GetInput(rnsdCadInputTypeDataPoint, msdCadInputTypeReset) Select Case myCIM.InputType Case msdCadInputTypeReset Exit Sub Case msdCadInputTypeDataPoint pt3End = rnyCIM.point End Select CadInputQueue.SendDragPoints pt3Start, pt3End
-
-
SelElems = A c t i veModel R e f e r e n c e . G e t S e l e c t e d E l e m e n t s . B u i 1 d A r r a y F r o m C o n t e n t s
If MsgBox("Are y o u sure y o u want to delete & UBound(SelE1ems) + 1 & Elements?", vbYesNo) = vbYes Then Cad1nputQueue.SendCornrnand "DELETE" End If End Sub "
"
~
I Some Real-World Applications I
341
In this example we used the selected points with the SendDragPoints method of the CadInputQueue object to effectively select the elements within the window generated by the two points. We get a count of the number of elements selected and ask the user to verify that the elements are to be deleted through a MessageBox with Yes and No buttons. If the user says “Yes”,we delete the selected elements by sending a Command of “DELETE’: This allows the user to select two points and delete the window between the two points. But we must ask ourselves, does it work well? After the first point is selected, we cannot see where the point had been selected. It would be better if we could see the first selection point like when we draw a line. The next function allows the user to select two points. After the first point is selected, we see the same graphical interface from Microstation, as we when drawing a line using standard Microstation commands, until the second point is selected. This function then returns the two points. Function P o i n t s B y L i n e O A s Point3dO Dim myCIQ As CadInputQueue Dim myCIM As CadInputMessage Dim pt3Start As Point3d Dim pt3End As Point3d Dim selPts(0 To 1) As Point3d Set myCIQ = CadInputQueue Set myCIM = myCIQ.GetInput(msdCadInputTypeDataPoint, msdCadInputTypeReset) Select Case myCIM.InputType Case msdCadInputTypeReset Err.Raise -12345 Exit Function Case m s d C a d I n p u t T y p e D a t a P o i n t pt3Start = myCIM.point End Select CadInputQueue.SendCommand “PLACE LINE” C a d 1 n p u t Q u e u e . S e n d D a t a P o i n t pt3Start Set myCIM = myCIQ.GetInput(msdCadInputTypeDataPoint, msdCadInputTypeReset) Select Case myCIM.InputType Case msdCadInputTypeReset
-
-
342
I Chapter 17: Interactive Modification I E r r . R a i s e -12346 E x i t Function
msdCadInputTypeDataPoint
Case
pt3End
=
myCIM.point
End S e l e c t selPts(0)
=
pt3Start
selPts(1)
=
pt3End
PointsByLine
=
selPts
End F u n c t i o n
After the user selects the first point, we begin the "PLACE LINE" command and supply the command the point the user selected. This creates a rubber-band effect that allows us to see the first point selected and also shows the cursor's coordinates as it waits for the second point to be selected. After the second point is selected, we place the selected points into an array that is used for the return value of the function. If the user issues a reset while the first or second points are entered, we raise an error so the function or procedure that called P o i n t s By L i ne" function will know what happened. We need to remember that the "PLACE LINE" command is still in process as we exit the function. We will handle it in the calling procedure or function as follows: Sub
TestCadInputJ() On E r r o r GoTo e r r h n d
D i m s e l P t s 0 As P o i n t 3 d selPts
=
PointsByLine
CadInputQueue.SendReset
CommandState.StartDefaultCommand Debug.Print selPts(O1.X & " , Debug.Print s e l P t s ( l ) . X & " ,
" "
& selPts(O1.Y
& ",
& selPts(l1.Y & " ,
" "
& selPts(01.Z & selPts(l1.Z
E x i t Sub errhnd: CadInputQueue.SendReset
CommandState.StartDefaultCommand S e l e c t Case E r r . N u m b e r Case - 1 2 3 4 5 'Start Point not selected MsgBox " S t a r t P o i n t n o t s e l e c t e d . " ,
vbcritical
Case - 1 2 3 4 6 'End P o i n t n o t s e l e c t e d MsgBox " E n d P o i n t n o t s e l e c t e d . " ,
vbcritical
I Some Real-World Applications I
343
End S e l e c t End Sub
We use P o i n t s B y L i n e to get two points. Notice the S e n d R e s e t and S t a r t D e f a u l tcornrnand calls. This resets the Place Line command which started when we called "PointsByLine". If the user selects the two points as requested, we display the coordinates of the points in the Immediate Window. If the user does not select one of the points, we know which point selection was aborted based on the error raised in the PointsByLine Function.
Here is a more practical application of our new Po in t s By L in e function: Sub
TestCadInputK( 1 On E r r o r GoTo e r r h n d D i m s e l P t s 0 As P o i n t 3 d D i m p t 3 T e x t P t As P o i n t 3 d D i m m y T e x t As T e x t E l e m e n t
D i m r o t M a t r i x As M a t r i x 3 d sel Pts
PointsByLine
=
Cad1nputQueue.SendReset
CommandState.StartOefaultCommand S e t myText
=
CreateTextElernentl(Nothing, " S t a r t " ,
selPts(0). rotMatrix)
ActiveModel Reference.AddElement myText Set myText
=
CreateTextElementl(Nothing, "End", s e l P t s ( l ) , r o t M a t r i x )
ActiveModel Reference.AddElement myText pt3TextPt.X
=
selPts(O).X
+ (selPts(l).X
-
selPts(O).X)
/ 2
pt3TextPt.Y
=
selPts(O).Y + (selPts(l).Y
-
selPts(O).Y)
/ 2
pt3TextPt.Z
=
selPts(O).Z + ( s e l P t s ( l ) . Z
-
selPts(O).Z)
/ 2
S e t myText
=
CreateTextElernentl(Nothing. "Mid". pt3TextPt, r o t M a t r i x )
ActiveModel Reference.AddElement myText E x i t Sub errhnd: Cad1nputQueue.SendReset
CommandState.StartOefaultCommand S e l e c t Case E r r . N u m b e r Case - 1 2 3 4 5 'Start Point not selected MsgBox " S t a r t P o i n t n o t s e l e c t e d . " ,
vbcritical
Case - 1 2 3 4 6 'End P o i n t n o t s e l e c t e d MsgBox " E n d P o i n t n o t s e l e c t e d . " ,
vbcritical
I Chapter 17: Interactive Modification I End Select End Sub
The framework is the same as the previous example. We use our new Po in t s By L i n e function to get two points while simulating the Place Line command. Once we get the points, we use them to place three new text elements in our file. “Start”, “End” and “Mid” are placed at the start point, the end point, and the calculated mid point. Here is what it looks like in Microstation:
Start
Here is one more function that simulates the “PLACE BLOCK” command, allowing the user to stretch out a rectangle instead of a line. Function P o i n t s B y R e c t a n g l e O As P o i n t 3 d O Dim myCIQ As CadInputQueue Dim myCIM As CadInputMessage Dim pt3Start As Point3d Dim pt3End As Point3d Dim selPts(0 T o 1) As Point3d Set myCIQ = CadInputQueue Set myCIM = myCIQ.GetInput(msdCadInputTypeDataPoint, msdCadInputTypeReset) Select Case myCIM.InputType Case msdCadInputTypeReset Err.Raise -12345 Exit Function Case msdCadInputTypeDataPoint pt3Start = myCIM.point End Select Cad1nputQueue.SendCommand “PLACE BLOCK”
I Some Real-World Applications I
345
Cad1nputQueue.SendDataPoint pt3Start Set myCIM = myCIQ.GetInput(msdCadInputTypeDataPoint, msdCadInputTypeReset.1 Select Case myCIM.InputType Case msdCadInputTypeReset Err.Raise -12346 Exit Function Case msdCadInputTypeDataPoint pt3End = myCIM.point End Select sel Pts(0) = pt3Start selPts(1) = pt3End PointsByRectangle = selPts End Function
-
Andnowaprocedure that uses P o i n t s B y R e c t a n g l e : Sub TestCadInputL( 1 On Error GoTo errhnd Dim s e l P t s 0 A s Point3d sel Pts = PointsByRectangle Cad1nputQueue.SendReset CommandState.StartDefaultCommand Debug.Print selPts(O1.X & Debug.Print selPts(l1.X &
",
".
selPts(O).Y
"
&
"
& selPts(l).Y &
& ",
".
selPts(O).Z
"
&
"
& selPts(l).Z
Exit Sub errhnd: Cad1nputQueue.SendReset CommandState.StartDefaultCommand Select Case Err.Number Case -12345 'Start Point not selected MsgBox "Start Point not selected.", vbcritical Case -12346 'End Point not selected MsgBox "End Point not selected.", vbcritical End Select End Sub
346
I Chapter 17: Interactive Modification I
Gie SEYi
TestCadInputL does not do anything fancy. It just displays the points selected in the Immediate Window. Let’s make better use of Po in t s By Recta n g 1 e by using the selected points as part of a scan criteria in selecting cells in a file. Sub
TestCadInputM() On E r r o r GoTo e r r h n d D i m s e l P t s 0 As P o i n t 3 d D i m L i n e P t s ( 0 To
1) As P o i n t 3 d
D i m L i n e E l e m As L i n e E l e m e n t
D i m myESC As New E l e m e n t s c a n c r i t e r i a D i m myRange As Range3d D i m myElemEnum As E l e m e n t E n u m e r a t o r D i m myElem As E l e m e n t D i m F F i l e As L o n g D i m m y C e l 1 H e a d e r As C e l l E l e m e n t
sel Pts
=
PointsByRectangl e
Cad1nputQueue.SendReset
CommandState.StartDefaultCommand = Range3dFromPoint3dPoint3d(sel P t s ( 0 ) . sel Pts(1))
myRange
myESC. E x c l u d e A l 1 T y p e s
m y E S C . I n c l u d e T y p e msdElementTypeCellHeader myESC.IncludeOnlyWithinRange myRange S e t myElemEnum = ActiveModelReference.Scan(myESC)
I Some Real-World Applications I
347
FFile = FreeFile Open "C:\MicroStation VBA\CellExport.txt" For Output As BFFile Print #FFi le, ActiveDesignFi 1 e. Name Whi 1 e myEl emEnum.MoveNext Set myElem = myElemEnum.Current Set myCel1 Header = myEl em Print BFFile, myCellHeader.Name & vbTab & myCellHeader.0rigin.X & vbTab & myCellHeader.0rigin.Y & vbTab & myCel1 Header. Ori gi n. Z Wend C1 ose BFFi 1 e Exit Sub ~
errhnd: CadInputQueue.SendReset CommandState.StartDefaultCommand Select Case Err.Number Case -12345 'Start Point not selected MsgBox "Start Point not selected.", vbcritical Case -12346 'End Point not selected MsgBox "End Point not selected.", vbcritical End Select End Sub
This macro writes the names and locations of cells in the active model reference that fit within the selected rectangle. Here is the output for the office.dgn file installed with Microstation:
bffice.dan PART GWALL PART URINAL PART URINAL nP&RT
DPART PDOORR RAIL2 RAIL1
14517.3074154912 16233.3074154912 16233.3074154912
15917.6804376836 16991.6774376836 15851.6804376836
0 0 0
The results of the macro differ from file to file and from selection to selection. If fewer cells are selected inside the rectangle, fewer cells w ill be output to the text file.
348
I Chapter 17: Interactive Modification I USINGSENDCOMMAND Thus far we have used Sendcommand with “DELETE’: “PLACE LINE’: and “PLACE BLOCK”.Even though these commands may look familiar to some readers, they may be foreign to others. Each time a menu item is selected or toolbar button clicked, a command is issued to Microstation. How do we know what these commands are? Good question. The Microstation VBA macro recorder can help us to discover command names and how they are used. Let’s try recording a few macros to demonstrate this.
1 From the VBA Project Manager, select the VBA Project in which we are currently working and then click the record button.
L
2 Now, select the Line Command from the toolbar as shown:
L
Place SrnartLine
3
Place Multi-line
3 “Place Line” begins by asking for points between which to draw lines. Select two points in Microstation and then click the right mouse button to issue a reset.
4 Next, stop recording the macro by clicking the “Stop Record” button.
5 After macro recording has stopped, return to the VBA environment to see the new macro. The macros are named automatically so the names may vary from computer to computer. The results of this recorded macro should look similar to this:
I Using Sendcommand I
349
Sub Macro10 Dim startpoint As Point3d Dim point As Point3d, point2 As Point3d Dim lngTemp As Long Start a command CadInputQueue.SendCommand “CGPLACE L I N E CONSTRAINED
”
Coordinates are i n master units startP0int.X = 16735.231975 startP0int.Y = 33020.733029 startP0int.Z = O# Send a data point to the current command p0int.X = startP0int.X p0int.Y = startP0int.Y p0int.Z = startP0int.Z Cad1nputQueue.SendDataPoint point, 1 p0int.X = startP0int.X + 1985.401024 p0int.Y = startP0int.Y - 610.892623 p0int.Z = startP0int.Z CadInputQueue.SendDataPoint point, 1 Send a reset to the current command Cad1nputQueue.SendReset
CommandState.StartDefaultCommand End S u b
This recorded macro reveals a command of “CGPLACE LINE CONSTRAINED”. The coordinates shown are those selected in Microstation as the macro was being recorded. Let’s copy and paste the recorded macro and modify it as follows: S u b Macrol-modi f i edA( ) Dim point As Point3d CadInputQueue.SendCommand “CGPLACE L I N E CONSTRAINED p0int.X = 0: p0int.Y = 0: p0int.Z = 0 CadInputQueue.SendDataPoint point, 1 p0int.X = 4: p0int.Y = 5: p0int.Z = 6 Cad1nputQueue.SendDataPoint point, 1
”
350
I Chapter 17: Interactive Modification I CadInputQueue.SendReset
CommandState.StartDefaultCommand End S u b
We have now stripped down this macro to the bare essentials. The coordinates for the line have been replaced with (0, 0,O) and (4 , 5 , 6 ). Let's record another macro. This time we will record drawing a Block (rectangle). Sub Macro20 D i m s t a r t p o i n t As P o i n t 3 d D i m p o i n t As P o i n t 3 d , p o i n t 2 As P o i n t 3 d D i m l n g T e m p As L o n g '
S t a r t a command
CadInDutQueue.SendCommand "PLACE BLOCK I C O N '
'
"
Coordinates are i n master u n i t s startP0int.X
=
3.196418
startP0int.Y
=
6.071205
startP0int.Z
=
O#
Send a d a t a p o i n t t o t h e c u r r e n t command p0int.X
=
startP0int.X
p0int.Y
=
startP0int.Y
p0int.Z
=
startP0int.Z
CadInputQueue.SendDataPoint p o i n t , 1 p0int.X
=
startP0int.X
+ 2.537984
p0int.Y
=
startP0int.Y
p0int.Z
=
startP0int.Z
-
0.882104
CadInputQueue.SendDataPoint p o i n t , 1 CommandState.StartDefaultCommand End S u b
Here is a stripped-down and modified version of Macro2. S u b Macro2Lmodi f i edA( 1 D i m p o i n t As P o i n t 3 d
CadInputQueue.SendCommand "PLACE BLOCK I C O N p0int.X
=
0
p0int.Y
=
0
"
I Using Sendcommand I
351
p0int.Z = 0 CadInputQueue.SendDataPoint point, 1 p0int.X = p0int.X + 2.5 p0int.Y = p0int.Y - 0.75 CadInputQueue.SendDataPoint point, 1 CommandState.StartDefaultCommand End Sub
In this example, we are basing the second point on the first point. Instead of entering hard-coded coordinates, the second point is relative to the first point. However, even though the placement of the second point is relative to the first point, the first point is hard-coded. Let's make a few more modifications. Sub Macro2Lmodi f i edB( ) Dim point As Point3d Dim myCIQ As CadInputQueue Dim myCIM As CadInputMessage Set myCIQ = CadInputQueue Set myCIM = myCIQ.GetInput(msdCad1nputTypeDataPoint) point = myCIM.point CadInputQueue. Sendcommand "PLACE BLOCK ICON CadInputQueue.SendDataPoint point, 1 p0int.X = p0int.X + 2.5 p0int.Y = p0int.Y - 0.75 CadInputQueue.SendDataPoint point, 1 CommandState.StartDefaultCommand End Sub "
Now the first point used for the block is entirely based on user input. The second point is still relative to the first point.
Recording macros is one way to discover the command names of Microstation commands. The following macro is another way. Sub TestCadInputN( 1 Dim myCIQ As CadInputQueue Dim myCIM As CadInputMessage Dim I As Long Set myCIQ = CadInputQueue F o r I = 1 To 10 Set myCIM = myCIQ.GetInput(msdCad1nputTypeCommand)
352
I Chapter 17: Interactive Modification I Debug.Print myCIM.CommandKeyin Next I End Sub
T e s t Ca d I n p u t N captures ten commands. This is different from recording macros in that we do not get all of the other input, such as point selections, etc. The only thing we capture is the command name.
CGPLACE LINE CONSTRAINED PLACE BLOCK ICON ~ ~ ~ ~ ~ ICON ~ C ~ ~ R Attach Tags WORDPROCESSOR PLACE TEXT ICON PLACE CELL ICON MEASURE DISTANCE ICON DIMCREATE ELEMENT PLACE FENCE ICON
C
L
One additional method of determining command names should be mentioned.
digitizer dimcreate dimension dimstyle displayset
palette particletrace plot popset preview
dialog openfile dialog drawingscale open dialog drawingscale delete erase bogus keyin
The Key-in dialog opens by selecting Key-in from the Microstation Utilities menu. Items can be selected from the list boxes to construct the appropriate key-in. The image shown tells us we can use “DIALOG OPENFILE” as a command. Let’s try it. Sub TestMessageA( 1 CadInputQueue.SendCommand “DIALOG OPENFILE” End Sub
E
I Modeless Dialog Boxes I
353
Running the TestMessageA macro shows us that “DIALOG OPENFILE” is indeed a legitimate command. The Open File dialog box displays and the user can select a file to open.
MODELESS DIALOG BOXES InputBoxes and MessageBoxes allow the user to interact with our code. Their functionality is somewhat limited, however. When our goal can be accomplished with a MessageBox, it should be used. But when we need a richer interface or more dynamic interaction with the user, we need to use Forms. The next four examples are on the CD accompanying this book. Import them one at a time by using the VBA menu File > Import File and selecting the appropriate file from the CD. This imports a new form into the active VBA project.
frmMatchProperties.f rm The first form, “frmMatchProperties. frm”,looks like this: The form looks simple enough. We have a few command buttons, a couple of frames, a handful of check boxes, a label, and four text boxes. Before we look at the code behind the controls, let’s discuss the program’s desired functionality.
354
I Chapter 17: Interactive Modification I Desired Functionality 1 The user can select a Source element in Microstation. After the element is selected, the Select button is clicked and four properties are extracted from the selected element: level, color, linestyle, and lineweight.
2 The user can select which of the properties from the source element are to be changed in the Destination Elements.
3 The user can select any number of elements in MicroStation to be modified based on the selected properties of the source element. This sounds simple enough. Let’s get started. Even though the form can be imported from the CD, we will discuss the entire process of creating the form. The first thing we do is place the controls. As we work with an interface, we will find ourselves resizing and moving the controls to make our interface flow nicely for the user. Captions (when available) can be modified immediately after we add each control. Naming the controls is the next step. Here are the names of the controls with which we will be interacting: El frmMatchProperties El btnSelectSource El chkLevel El txtLevel El El El El El El El El El El El
chkColor txtColor chkLinestyle txtLinestyle chkLineweight txtlineweight btnchange lblcount btnClose fraSource fraDestination
I Modeless Dialog Boxes I
355
As we develop this program, we should be thinking about the future of this program. For example, the code in this form does not do anything to the frames. We could leave their names as “Framel” and “Frame2’: but we may decide to make the Destination frame invisible until the Source Element is selected. Then, after the Source Element is selected, we make the Destination Frame visible. We can do this using “Frame2” as the frame’s name, but naming it “fraDestination”in our code. This tells us exactly what we are making visible or invisible without having to browse through the frames on the form to find out which frame we are affecting.
Control Properties The Locked property of each TextBox should be “True’: We do not want the user arbitrarily typing in values that do not work. The text boxes will be populated by the source element’s properties. The Alignment property of each CheckBox should be “fmAlignmentLeW. This places the caption of the CheckBox on the left of the CheckBox. The ControlTipTextof the Select CommandButton is “Click Here to make the current selection the source element.”. The ControlTipText of the “Change Current Selection” CommandButton should be “Click Here to modify the current selection to match the selected properties from the Source element.’: The ControlTipText of the “Close” CommandButton should be “Click Here to Close the VBA Match Properties Program.” Later we will add code to display this form as modeless. This means the user will be able to interact with Microstation even though the form is displayed. This is important to keep in mind as we look at the code behind the controls.
General DeclarationsArea We have two lines of code in the general declarations area of our code. O p t i o n Expl ic i t
D i m elemsource As Element
356
I Chapter 17: Interactive Modification I Select Button We can only use one element as the source element. When the user clicks the Select button, the first thing we need to do is to discover how many elements have been selected. If only one element has been selected, we can continue. Otherwise, we will display one of two MessageBoxes: one MessageBox if nothing was selected or a second if more than one element was selected. If only one element is selected, do the following:
1 Get the level (if a level is assigned to the element). The level name is placed in the appropriate text box.
2 Get the color and display the number in the appropriate TextBox and change the TextBox's Backcolor property to match the color of the source element.
3 Get and place the linestyle property. 4 Get and place the lineweight property. Now, let's look at the code behind the btnSelectSource-Click event: P r i v a t e Sub b t n S e l e c t S o u r c e c C l i c k 0
D i m m y E l e m e n t s 0 As E l e m e n t D i m myElemEnum As E l e m e n t E n u m e r a t o r D i m myCol o r T a b l e
S e t myElemEnum myElements
=
As C o l o r T a b l e
ActiveModelReference.GetSelectedElements
=
ActiveModelReference.GetSelectedElements.Bui1dArrayFromContents If UBound(myE1ements) = 0 Then Set elemsource
=
myElements(0)
If N o t m y E l e m e n t s ( O ) . L e v e l Is N o t h i n g Then txtLevel .Text End
=
m y E l e m e n t s ( 0 ) . L e v e l .Name
If
Set myColorTable
=
ActiveDesignFile.ExtractColorTable
S e l e c t Case myEl e m e n t s ( 0 ) . C o l o r Case - 1 txtColor.Text
=
"I'
txtColor.BackColor
=
RGB(255, 2 5 5 , 2 5 5 )
t x t L i nestyle.Text = myEl e m e n t s ( 0 ) . L i n e S t y l e . Name txtLineweight .Text Case E l s e
=
myEl e m e n t s ( 0 ) . L i n e w e i g h t
I Modeless Dialog Boxes I
357
txtColor.Text = myElements(0) .Color txtColor.BackColor = myCol orTabl e.GetCo1 orAtIndex(myE1 ements(0) .Col or)
txtLinestyle.Text = myElements(O).LineStyle.Name txtLineweight.Text = myElements(O).LineWeight End Select Else Select Case UBound(myE1ements) Case -1 Ms g B ox
No " "Source " " element selected.", vbcritical, Me.Caption
"
-
Exit Sub Case Else MsgBox "Only one element can be the ""Source""" "element. " , vbCri ti cal , Me. Capti on
c%
Exit Sub End Select End If End Sub Plucing a Break Point in the code ullows us to step through the code line by line. This can help us discover how the program is working or to verifv that it is working as designed.
We are working with the ElementEnumerator a little differently in this instance. Instead of using "MoveNext" and getting the "Current" element, we get an array of elements using B u i 1 dArrayFromContents. If the upper-bound of the array is 0, the array is composed of one element. This is what we want. If the upper-bound of the array is -1, this means the array is empty and nothing was selected prior to clicking the button.
Change Current Selection Button When the user clicks the "Change Current Selection Button': we want to change the selected properties of the selected elements to the source element's properties. We also change the caption of the label to reflect how many elements were modified. We create an array of elements from the enumerator as we did in the previous example. Private Sub btnchange-Click0 Dim m y E l e m e n t s 0 A s Element Dim myElemEnum A s ElementEnumerator Dim I As Long Dim boo1 ElemModified A s Boolean Dim lngModCount A s Long 1blCount.Caotion = "0 Element(s) modified."
358
I Chapter 17: Interactive Modification I Show St a t u s 0 E 1 emen t ( s 1 mod i fi ed . Set myElemEnum = ActiveModelReference.GetSelectedElements myElements = myEl emEnum. Bui 1 dArrayFromContents lngModCount = 0 For I = LBound(myE1ements) T o UBound(myE1ements) bool El emModi fi ed = Fa1 s e If chkLevel.Value = True Then myEl ements( I). Level = el emsource. Level bool El emModi fi ed = True End If If chkColor.Value = True Then myElements(I).Color = elemSource.Color bool El emModi fi ed = True End If If chkLinestyle.Value = True Then myEl ements( I). Li neStyl e = el emSource. Li neStyl e bool El emModi fi ed = True End If If chkLineweight.Value = True Then myElements(I).LineWeight = elemSource.LineWeight bool El emModi fi ed = True End If If boolElemModified = True Then myEl ements( I). Rewrite lngModCount = lngModCount + 1 End If Next I 1blCount.Caption = lngModCount & Element(s) modified." Showstatus 1 ngModCount & El ement(s) modified. End Sub "
"
"
"
"
As we look at each element in the array, we only want to change the properties based on the CheckBox values. We only increase the element modified counter if a change was actually made. It is possible to select a source element and multiple destination elements and have no changes made if each of the CheckBoxes are set to false.
Close Button The Close button unloads the Form.
I Providing User Feedback and Information I
359
Private Sub btnClose-Click0 Unload Me End Sub
PROVIDING USER FEEDBACK AND lNFORMATlON Earlier in this chapter we learned how to provide the user feedback and information through the use of the status bar area in Microstation. If we look at the status bar area, we see that it changes as we move our cursor over various tool bar buttons. Let's mimic this same functionality in VBA by using the MouseMove events of several controls. Private Sub UserForm-MouseMove(ByVa1 Button As Integer, ByVal Shift A s Integer, ByVal X A s Single, ByVal Y A s Single) ShowPrompt End Sub " "
Private Sub f r a D e s t i n a t i o n - M o u s e M o v e ( B y V a 1 Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y A s Single) ShowPrompt End Sub " "
Private Sub f r a S o u r c e c M o u s e M o v e ( B y V a 1 Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) ShowPrompt End Sub " "
As the user moves the cursor around the form and the frames, we do not want to display anything in the prompt because clicking on the form or frame does not do anything. So, we use ShowPrompt with an empty string so nothing displays. Private Sub btnSelectSource-MouseMove(ByVa1 Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) ShowPrompt "Select a single ""Source"" Element:" End Sub Private Sub btnChangecMouseMove(ByVa1 Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
360
I Chapter 17: Interactive Modification I S how P r o m pt
"
Se 1e c t
" "
0e s t in a t io n " " E 1 erne n t s : "
End Sub P r i v a t e Sub b t n C l o s e c M o u s e M o v e ( B y V a 1 B u t t o n As I n t e g e r , B y V a l S h i f t As I n t e g e r , B y V a l X As S i n g l e , B y V a l Y As S i n g l e ) S h o w P r o m p t "C1 o s e " " V B A M a t c h P r o p e r t i e s " " " End Sub
As the user moves the cursor over the command buttons, we want to let the user know what happens if the button is clicked. We already do this with the ControlTipText property of each button but using the prompt more closely reflects Microstation standard functionality.
UserForm Initialize We need to discuss two additional events. The first of these is the UserForm Initialize event. This event is triggered as the form is about to be displayed. P r i v a t e Sub U s e r F o r m - I n i t i a l i z e 0 ShowCommand " V B A M a t c h P r o p e r t i e s : " End Sub
We use ShowCommand to set the Command area to "VBA Match Properties" when the form is first initialized. This command continues to display even though the prompt changes as the cursor is moved over the other controls in our Form.
UserForm Queryclose The Queryclose event is triggered just before the form is terminated. This event allows us to perform clean up operations. It also tells us how the form was asked to close. The CloseMode parameter gives us one of four values (which have corresponding constants). vbFormControlMenu = 0 vbFormCode = 1 El vbAppWindows = 2 vbAppTaskManager = 3 For more information on what each of these values mean, look up "Queryclose Constants" in the Microsoft VBA help.
I Providing User Feedback and Information I
361
In this program we are not concerned with how the form is closed, only that it is closing. Private Sub U s e r F o r m - Q u e r y C l o s e ( C a n c e 1 A s Integer CloseMode A s Integer) ShowPrompt Sh ow C omma n d End Sub " "
" "
Displaying the Form as Modeless The code in the form is based entirely on the user's selection of elements in Microstation. Although it is possible to select elements prior to displaying the form, we actually need the user to make two distinct selections: the source and the destination. The source selection can only be one element. To allow the user to select elements in Microstation while the form is displayed, we need to display the form as modeless. Remember that modeless is the opposite of modal where, when the form has focus, nothing else can be done in Microstation until the form is closed. Modeless means that even while a form is displayed interaction can continue inside Microstation. To display a form as modeless, we must show it as such in a procedure in a code module. Sub T e s t M a t c h P r o p e r t i e s ( 1 frmMatchProperties.Show vbModeless End Sub
The procedure TestMatchProperties, if placed in a code module, is available to the user through the VBA Project Manager or from the Microstation menu Utilities > Macro > Macros or by pressing the
362
I Chapter 17: Interactive Modification I Here is the Match Properties Form in use. Notice the Command and Prompt areas at the bottom. The Match Properties Program is simple and straightforward. We allow the user to make modifications to elements in the Active Model Reference by selecting a source element and then using its properties to change the selected destination elements while using a modeless dialog box.
f rmAl ignText.frm The next form we will import into our VBA project is the frmA/ignText.frm file. This form allows the user to perform text alignment and distribution operations on selected text in Microstation. Since we want to allow the user to select a point to align to, the form needs to be displayed as modeless. This program involves geometric calculations and moving text elements based on those calculations. b
Desired Functionality Selected Text can be aligned Horizontally to the selected or entered “X” value. Text can be aligned Left, Center, or Right. Text can be distributed evenly vertically so equal spacing exists between each text element. Only Text elements can be used, not Text Nodes.
I Providing User Feedback and Information I
363
Frames, command buttons, labels, and text boxes are used in this project. Once again, you can import the form from the CD accompanying this book, but we will discuss building this form as though we were starting with nothing.
Control Placement Place the controls as shown. The Base Point frame and Horizontal Alignment frames contain their respective controls and the “Distribute Vertically” button is by itself. If a “Distribute Horizontally” button existed, we would place both “Distribute” buttons in their own frame. After placing the controls, change captions and text properties as shown above.
Control Names El fraBasePoint
El fraHoriAlign El btnPickBasePoint El txtx
El txtY El btnAlignLeft El btnAlignCenter El btnAlignRight
El btnDistributeVert
Pick Button The code in the “Pick” button’s click event allows the user to select a point in Microstation. The selected point’s X and Y components then display in the text boxes. Private Dim Dim Set Set Do
Sub btnPickBasePoint-Click0 myCIQ A s CadInputQueue myCIM A s CadInputMessage myCIQ = CadInputQueue myCIM = myCIQ.GetInput(msdCadInputTypeDataPoint, msdCadInputTypeReset)
-
364
I Chapter 17: Interactive Modification I S e l e c t Case rnyCIM.InputType Case r n s d C a d I n p u t T y p e D a t a P o i n t pt3BasePoint
=
rnyCIM.Point
txtX.Text
=
pt3BasePoint.X
txtY.Text
=
pt3BasePoint.Y
E x i t Do Case rnsdCadInputTypeReset E x i t Do End S e l e c t
Loop End Sub
We use the CadInputQueue to capture the selection of a point in Microstation. When we initialize the CadInputMessage we specify that we are only looking for datapoints and reset inputs. The Do ... Loop is designed as an eternal loop. This means that without explicitly exiting, the loop continues forever. We use the Do ... Loop code because we can use an Exi t Do command that gets us out of the loop whenever we wish.
X and Y TextBoxes The X and Y TextBoxes are populated with values from points selected by the user through the Pick Button just discussed. In addition to picking the point, we want to allow the user to hand-enter X and Y values. Picking points is nice because we know that the user cannot select an invalid point in Microstation. Allowing data entry can cause problems if we are not careful. What happens, for instance, if the user enters "somewhere around 4.5" in the TextBox? This entry would be far from the numeric value we are counting on. One way to limit the user's entry in these text boxes is to make use of the Keypress event. P r i v a t e Sub t x t X L K e y P r e s s ( B y V a 1 K e y A s c i i A s M S F o r r n s . R e t u r n I n t e g e r ) S e l e c t Case KeyAsci i Case A s c ( " 0 " ) To A s c ( " 9 " ) Case A s c ( " . " ) I f InStr(1, txtX.Text, KeyAscii End I f Case E l s e KeyAscii End S e l e c t End Sub
=
0
=
0
"."I
>
0 Then
I Providing User Feedback and Information I P r i v a t e Sub t x t Y - K e y P r e s s ( B y V a 1
365
K e y A s c i i As M S F o r m s . R e t u r n 1 n t e g e r )
S e l e c t Case K e y A s c i i Case A s c ( " 0 " ) To A s c ( " 9 " ) Case A s c ( " . " )
If InStr(1, txtY.Text, KeyAscii
=
".")
>
0 Then
0
End I f Case E l s e KeyAscii
=
0
End S e l e c t End Sub
The Keypress event tells us the ASCII character code of the keyboard character that was pressed. If we change the KeyAscii parameter to a value of zero (0), it is as though the key was never pressed. So, we look at the KeyAscii parameter and ask ourselves the following questions with the following results: Is the Key Ascii between the numbers 0 to 9? If so, do nothing. Always allow numbers 0 through 9 to be entered. Is the Decimal key pressed? If so, look to see if a decimal is already in the TextBox. If a decimal is already in the TextBox, set KeyAscii to zero. Otherwise, do nothing and allow the decimal to be entered. Case Else (if any other key is pressed), set KeyAscii to zero as though the key was not pressed in the first place. It should be noted that this code only keeps numeric values from being entered from the keyboard. It does not prohibit the user from pasting an invalid entry into the TextBox from the Windows clipboard.
Align Left, Center, and Right The best way to deal with these three alignment methods is one at a time. We have three buttons. We could place code in each of the click events of these buttons to perform the specific type of alignment requested, but this would create a lot of redundant code. We will create a function to take care of all horizontal alignments and provide for a parameter to specify which alignment is to be performed.
366
I Chapter 17: Interactive Modification I The “Alignment Mode” parameter could be defined as a string and we could use “LEFT”, “RIGHT”, or “CENTER” as parameter values. This works. There is a better way, though. To get input from the Cad Input Queue, we can specify which types of input we want by the using an enumeration, which is a list of constants grouped together that usually refer to a specific method or property. We will create our own enumeration to deal with alignments. In the General Declarations area of this form, we declare this enumeration: Enum A1 ignMode =
1
msvbaAlignModeCenter
=
msvbaAlignModeLeft msvbaAl ignModeRight
=
2
3
End Enum
Now when we declare our procedure to align the selected text, it looks like this: Sub A1 ig n S e l e c t e d ( 0 p t i o n a l E l emAl ignMode As A1 ignMode = msvbaAl i g n M o d e L e f t )
We declare the parameter as optional so we can specify “Left” as the default alignment. When we are using the Alignselected method in our code, we see this: Private Sub btnAlignCenter-Click ( ) Align Selected
Enumerations help us make sure that the parameter we are providing is legitimate and make it easier to program because we are shown our options for the parameter. One additional declaration needs to be added to the General Declarations area of our form: D i m p t 3 B a s e P o i n t As P o i n t 3 d
When the user selects a point, we use this variable to store the selection. Here is the code that actually aligns the text left, center, or right: Sub A1 ig n S e l e c t e d ( 0 p t i o n a l E l emAl ignMode As A1 ignMode = msvbaAl i g n M o d e L e f t )
I Providing User Feedback and Information I
367
Dim myElemEnum As ElementEnumerator Dim myElem As Element Dim OriginPt As Point3d Dim myTextElem As TextElement Set myEl emEnum = Acti veModel Reference. GetSel ectedEl ements Whi 1 e myEl emEnum.MoveNext Set myElem = myElemEnum.Current Select Case myElem.Type Case msdElementTypeText Set myTextElem = myElem Select Case ElemAlignMode Case msvbaAlignModeLeft rnyTextElern.Move P o i n t 3 d F r o r n X Y ( p t 3 B a s e P o i n t . X
~
~
myTextElem.Boundary.Low.X, 0) myText El em. Rewrite Case msvbaAlignModeRight rnyTextElem.Move P o i n t 3 d F r o m X Y ( p t 3 B a s e P o i n t . X
~
~
myTextElem.Boundary.High.X, 0) myText El em. Rewrite Case msvbaAlignModeCenter myTextElem.Move P o i n t 3 d F r o m X Y ( p t 3 B a s e P o i n t . X - yTextE1em.Boundary.Low.X - (myTextE1em.Boundary.High.X myTextE1em.Boundary.Low.X) / 2 , 0) myText El em. Rewrite End Select End Select ~
Wend End Sub
When we begin executing this code, we know that in addition to selecting text elements, the user may have selected other types of elements. Since we only want to work with text elements, we use a Sel ect Case statement to parse out the text elements from the others. Next, we use another Se 1 ec t Case statement to move the text element based on the type of alignment specified and the X value of the base point. We rewrite the text element so the change made is permanent in the file. Left, Center, and Right Buttons
When the user clicks the left, center, or right buttons, the click event of the respective button is triggered. Notice how we use our enumeration values when calling “AlignSelected.
368
I Chapter 17: Interactive Modification I P r i v a t e Sub b t n A l i g n L e f t L C l i c k 0 A l i g n s e l e c t e d rnsvbaAlignModeLeft End Sub P r i v a t e Sub b t n A l i g n c e n t e r - C 1 i c k ( 1 A l i g n s e l e c t e d rnsvbaAlignModeCenter End Sub P r i v a t e Sub b t n A l i g n R i g h t - C l i c k 0 A l i g n s e l e c t e d rnsvbaAlignModeRight End Sub
Degrees of Complexity There are three degrees of complexity in this program. The degrees and their tasks are as follows: LOW:
Pick a point, place X and Y components into TextBoxes.
Medium:
Align selected Text Elements Left, Center, or Right.
High:
Vertically Distribute selected Text Elements evenly.
We have already discussed the Low and Medium complexity tasks. It is now time for the High complexity task. This task is not highly complex because it is highly difficult. It simply requires more components for everything to work correctly.
Vertically Distribute Selected Text Evenly Here are four text elements in the file chupterl7-AlignText.dgn. This file is on the CD accompanying this book. The text alignment code we have already discussed takes care of the horizontal alignment. Now we are faced with the task of making the text , look nice vertically. The spacing between Note 1 and Note2 is tight, whereas the spacing between Note2 and Note3 is loose (as it is between Note 3 and Note 4). We want the spacing between each of these text elements to be the same. It is a simple task but a number of considerations must be made before continuing.
I Providing User Feedback and Information I
369
1 After the user selects the text, we want even spacing between the top and bottom elements without those elements moving.
2
On the screen it is readily apparent which element is on top and which is on the bottom. But when we look at the selection in code, we do not know which element is on top and which is on the bottom.
3 On the screen we can see the proper order. But when we look at the selection in code, we do not know the top-down order of the text elements. We will create distinct functions to accomplish each of the following tasks: Discover the minimum and maximum points of the selected text elements. Determine the vertical order in which the text elements appear. Determine the number of selected text elements. After we have these functions in place, we will be able to use them in distributing the selected text elements. Function GetMinMaxY(E1emType A s Long, ElementsIn As Variant) As Point3dO Dim I As Long Dim pt3StartPoint A s Point3d Dim pt3EndPoint A s Point3d Dim myTextElem A s TextElement Dim boolPointsSet A s Boolean bool PointsSet = False F o r I = LBound(Elements1n) T o UBound(Elements1n) Set myElem = ElementsIn(1) Select Case myElem.Type Case msdElementTypeText Set myTextElem = myElem If bool PointsSet = Fa1 se Then pt3StartPoint = myTextElem.Boundary.High pt3EndPoint = myTextElem.Boundary.High boolPointsSet = True End If If myTextE1em.Boundary.High.Y > pt3StartPoint.Y Then ~
I Chapter 17: Interactive Modification I pt3StartPoint.Y = m y T e x t E 1 e m . B o u n d a r y . H i g h . Y End If If myTextE1em.Boundary.High.Y < pt3EndPoint.Y Then pt3EndPoint.Y = m y T e x t E 1 e m . B o u n d a r y . H i g h . Y End If End Select Next I Dim pt3Points(0 T o 1) As Point3d pt3Points(O) = pt3StartPoint pt3Points(l) = pt3EndPoint GetMinMaxY = pt3Points End Function
We have created this function to allow for future use and expansion with other types of Elements. We ask for the element type and the elements to be considered. From these parameters, we discover the Min and Max values and return them as an array of Point3d types. The next task is to sort the elements vertically. This is accomplished by providing the type of element we want to look at and the elements to be considered. We return the elements in their vertically sorted state as an array of elements. Function SortElementsVertical ly(E1emType A s Long, ElementsIn A s Variant) As E l e m e n t 0 Dim I A s Long Dim boolMadeChange As Boolean Dim I n g E l e m I D O As DLong Dim p t 3 B o u n d P t O As Point3d Dim myTextEl em As TextEl ement Dim myTextEl em2 As TextEl ement Dim tmpID As DLong Dim tmpPt As Point3d ReDim lngElemID(0) As DLong ReDim pt3BoundPt(O) As Point3d For I = LBound(Elements1n) T o UBound(Elements1n) Select Case El emType Case msdElementTypeText If ElementsIn(I).Type = msdElementTypeText Then Set myTextElem = ElementsIn(1) lngElemID(UBound(lngElem1D)) = myTextElem.ID
I Providing User Feedback and Information I
371
pt3BoundPt(UBound(pt3BoundPt)) = myTextElem.Boundary.High ReDim P r e s e r v e lngElemID(UBound(lngElem1D) + 1) As DLong ReDim P r e s e r v e pt3BoundPt(UBound(pt3BoundPt) + 1 ) As Point3d End I f End S e l e c t Next I ReDim P r e s e r v e lngElemID(UBound(lngElemID) b o o l Madechange
=
-
1 ) As DLong
True
Whi 1 e b o o l Madechange b o o l Madechange
=
=
True
Fa1 s e
F o r I = L B o u n d ( l n g E l e m 1 D ) To U B o u n d ( l n g E l e m 1 D )
>
+ 1).Y
I f pt3BoundPt(I
tmpID
=
lngElemID(1)
tmpPt
=
pt3BoundPt(I)
lngElemID(1) pt3BoundPt(I)
1
lngElemID(1 + 1)
= =
pt3BoundPt(I + 1)
l n g E l e m I D ( 1 + 1)
=
p t 3 B o u n d P t ( I + 1) boolMadeChange
-
p t 3 B o u n d P t ( I ) . Y Then
=
tmpID
=
tmpPt
True
End I f Next I Wend
D i m E l e m s I n O As Element ReDim E l e m s I n ( 0 To U B o u n d ( l n g E l e m 1 D ) ) F o r I = L B o u n d ( l n g E l e m 1 D ) To U B o u n d ( l n g E l e m 1 D )
Set ElemsIn(1)
=
ActiveDesignFile.GetElementByID(lngElemID~1~~
Next I SortElementsVertically
=
ElemsIn
End F u n c t i o n
There is a lot of code to look at here. After we divide it into four little chunks, it becomes easier to understand.
Variable Declaration D i m I As Long D i m boolMadeChange As Boolean
D i m I n g E l e m I D O A s DLong D i m p t 3 B o u n d P t O As P o i n t 3 d D i m mrTextElem As TextElement
372
I Chapter 17: Interactive Modification I Dim myTextEl em2 As TextEl ement Dim tmpID As DLong Dim tmpPt As Point3d
Two variables are declared as dynamic arrays (by using the empty parenthesis). Dynamic arrays can change in size without losing their values. Other variables are declared as well.
Dynamic Variable Array Population in Preparation for Bubble Sort ReDim lngElemID(0) As DLong ReDim pt3BoundPt(O) As Point3d For I = LBound(Elements1n) T o UBound(Elements1n) Select Case El emType Case msdElementTypeText If ElementsIn(I).Type = msdElementTypeText Then Set myTextElem = ElementsIn(1) lngElemID(UBound(lngElem1D)) = myTextElem.ID pt3BoundPt(UBound(pt3BoundPt)) = myText El em. Boundary. High ReDim Preserve lngElemID(UBound(lngElemID) + 1) A s DLong ReDim Preserve pt3BoundPt(UBound(pt3BoundPt)+ 1) As Poi nt3d End If End Select Next I ReDim Preserve lngElemID(UBound(lngElem1D) - 1) As DLong ReDim Preserve pt3BoundPt(UBound(pt3BoundPt) - 1) As Point3d ~
~
We look at each element provided to us in the ElementsIn parameter to see if it is of the correct type (in our example we are looking for text elements). If it is, we get the text element’s ID property and put it in one of the dynamic array variables and get the element’s Boundary.High point and put it in the other dynamic array variable. We then re-declare the dynamic array variables with the Preserve keyword so we don’t lose the previous values. After we have looked at each of the elements selected, we re-declare the dynamic Array variables decreasing the size of each by 1. Throughout the code above, we add one to the size of the array after populating the upperbound variables so we need to take one
I Providing User Feedback and Information I
373
off after we have finished. Otherwise, we would have an array element with nothing in it.
Bubble Sorting bool Madechange = True Whi 1 e bool Madechange = True bool Madechange = Fa1 se For I = LBound(lngElem1D) T o UBound(lngElem1D) - 1 If pt3BoundPt(I + 1).Y > pt3BoundPt(I).Y Then tmpID = lngElemID(1) tmpPt = pt3BoundPt(I) lngElemID(1) = lngElemID(1 + 1) pt3BoundPt(I) = pt3BoundPt(I + 1) lngElemID(1 + 1) = tmpID pt3BoundPt(I + 1) = tmpPt boolMadeChange = True End If Next I Wend
We have discussed bubble sorting previously. We are looking at the Y values of two points. We want the highest Y values to be at the top of the list. So, if a Y value lower down on the list is higher than the Y value just above it in the list, we switch the two. When a switch is made we set the variable boolMadeChangeto True. This means we will run through the array again. We continue running through the array until a switch is not made. When we find we have not made a switch, the sorting is complete.
Setting the Return Value Dim E l e m s I n O A s Element ReDim ElemsIn(0 T o UBound(lngElem1D)) For I = LBound(lngElem1D) T o UBound(lngElem1D) Set ElemsIn(1) = ActiveDesignFile.GetElementByID(lngElemID~1~~ Next I SortElementsVertically = ElemsIn
We are returning an array of elements for this function. When we did our bubble sort, we swapped the point array values and also the ID array values along with them to keep the IDS matched with their points. Now, we use G e t E l ementByID to get the element back and put the element in the return value array. We separated the IDS and points from the
374
I Chapter 17: Interactive Modification I elements, so we did not need to hold onto large elements as we did our sorting, only smaller points and ID types. Determining the number of text elements is relatively easy compared with the last function we just worked with. We get the element type we want to count, the elements to be counted, and we return the number of elements matching the type contained in the elements passed in. GetSel ectedCount( E l emType As L o n g ,
Function
~
E l e m e n t s I n As V a r i a n t ) As L o n g
D i m I As Long
I = L B o u n d ( E l e m e n t s 1 n ) To U B o u n d ( E l e m e n t s 1 n ) I f E l e m e n t s I n ( I ) . T y p e = ElemType Then
For
GetSelectedCount End
=
GetSelectedCount + 1
If
I
Next
End F u n c t i o n
The previous three functions are written so that they can be expanded in the future. We do not need to write code right now to accommodate these potential future needs. Now we need to make use of these functions in a single procedure to accomplish our “Distribution”task. P r i v a t e Sub b t n D i s t r i b u t e V e r t L C l i c k 0 D i m M y P t s As V a r i a n t
D i m p t 3 S t a r t P o i n t As P o i n t 3 d D i m p t 3 E n d P o i n t As P o i n t 3 d D i m myElemEnum As E l e m e n t E n u m e r a t o r D i m myElem As E l e m e n t D i m m y E l e m s 0 As E l e m e n t Dim
I As Long
D i m m y T e x t E l em As T e x t E l e m e n t D i m l n g S p a c e s As L o n g D i m d b l S p a c e P e r E l e m e n t As D o u b l e D i m s o r t E l e m s 0 As E l e m e n t
S e t myElemEnum
=
ActiveModelReference.GetSelectedElements
myElems
=
MyPts
GetMinMaxY(msdElementTypeText, myElems)
=
myEl emEnum. B u i 1 d A r r a y F r o m C o n t e n t s
= GetSelectedCount(msdElementTypeText, m y E l e m s ) I f l n g S p a c e s > 1 Then
1ngSpaces
dblSpacePerElement
=
(MyPts(O1.Y
-
MyPts(l).Y)
-
1
/ 1ngSpaces
I Providing User Feedback and Information I sortElerns
For I
=
=
375
S o r t E l e r n e n t s V e r t i c a l l y ( r n s d E l e r n e n t T y p e T e x t , rnyElerns)
L B o u n d ( s o r t E 1 e m s ) To U B o u n d ( s o r t E 1 e m s )
S e t myTextElem
=
sortElems(1)
myTextElem.Move Point3dFromXY(O, MyPts(O1.Y dblSpacePerElernent * I rnyTextE1ern.Boundary.High.Y) ~
m y T e x t E l em. R e w r i t e myTextElem.Redraw Next I End I f End Sub
A close look at the above code reveals the use of the three functions we just finished discussing. G e t M i nMaxY, G e t S e l e c t e d c o u n t , and S o r t E l e m e n t s V e r t i c a l l y are used. After we have sorted the elements vertically, we move them so that they are spaced evenly.
Providing User Feedback and Information Let's provide similar functionality to our previous program by supplying the user feedback and information. P r i v a t e Sub UserForm-MouseMove(ByVa1 B u t t o n A s I n t e g e r , B y V a l S h i f t A s I n t e g e r , B y V a l X As S i n g l e , B y V a l Y A s S i n g l e ) S h owS t a t u s " " ShowPrompt
" "
ShowCommand M e . C a p t i o n End Sub P r i v a t e Sub
btnDistributeVert-MouseMove(ByVa1 B u t t o n A s I n t e g e r , B y V a l S h i f t As I n t e g e r , B y V a l X As S i n g l e , B y V a l Y As S i n g l e )
S h owS t a t u s
" "
ShowPrompt " D i s t r i b u t e T e x t V e r t i c a l l y " ShowCommand M e . C a p t i o n End Sub P r i v a t e Sub f r a H o r i A l i g n - M o u s e M o v e ( B y V a 1 B u t t o n As I n t e g e r , B y V a l S h i f t A s I n t e g e r , B y V a l X As S i n g l e , B y V a l Y As S i n g l e ) S h owS t a t u s " " ShowPrompt
" "
ShowCommand M e . C a p t i o n End Sub
I Chapter 17: Interactive Modification I P r i v a t e S u b fraBasePointLMouseMove(ByVa1 B u t t o n A s I n t e g e r , B y V a l S h i f t As I n t e g e r , B y V a l X As S i n g l e , B y V a l Y As S i n g l e ) S how St a t u s " " ShowPrompt
" "
ShowCommand M e . C a p t i o n End S u b P r i v a t e S u b btnPickBasePoint-MouseMove(ByVa1 B u t t o n As I n t e g e r , B y V a l S h i f t As I n t e g e r , B y V a l X As S i n g l e , B y V a l Y As S i n g l e )
S how St a t u s
" "
ShowPrompt " S e l e c t Base P o i n t : " ShowCommand M e . C a p t i o n End S u b P r i v a t e S u b b t n A l i g n R i g h t - M o u s e M o v e ( B y V a 1 B u t t o n As I n t e g e r , B y V a l S h i f t As I n t e g e r , B y V a l X A s S i n g l e , B y V a l Y As S i n g l e )
S how St a t u s
" "
ShowPrompt " A l i g n S e l e c t e d T e x t R i g h t as Base P o i n t " ShowCommand M e . C a p t i o n End S u b P r i v a t e S u b btnAlignLeftLMouseMove(ByVa1 B u t t o n A s I n t e g e r , B y V a l S h i f t As I n t e g e r , B y V a l X As S i n g l e , B y V a l Y As S i n g l e )
S how St a t u s
" "
ShowPrompt " A l i g n S e l e c t e d T e x t L e f t a t Base P o i n t " ShowCommand M e . C a p t i o n End S u b P r i v a t e S u b btnAlignCenter-MouseMove(ByVa1 B u t t o n As I n t e g e r , B y V a l S h i f t As I n t e g e r , B y V a l X As S i n g l e , B y V a l Y As S i n g l e )
S how St a t u s
" "
S h o w P r o m p t "A1 i g n S e l e c t e d T e x t C e n t e r a t B a s e P o i n t " ShowCommand M e . C a p t i o n End S u b
At this point, this code spaces text evenly if the text elements are the same height. The upper-left corner of each element is what we are using to space these text elements. If one text element is larger than the others, it could run into the text below it because we are only considering the spacing between the top-left corners relative to each other, not the topleft corner of one text element with the bottom-left corner of the one above it. We will leave the expansion of this macro to accommodate the text height to the reader of this book.
I Providing User Feedback and Information I
377
f rmExportElements.f rm The frmExportElemen ts.frm User Form accomplishes a simple task: it exports elements on specific levels to a new design file. The task for this project is simple. The interface reflects this. We need to allow the user to select any number of levels, enter a file name for the new file to be created, and then click on the Export button.
Control Names 1stLevels txtFileName btnExport
.
Control Properties 1stLevelsproperty MultiSelect is set to 2 fmMultiSelectExtended 1stLevelsproperty ListStyle property is set to 1 fmListStyleoption When this program begins executing, we need to get the names of all levels of the active design file into the list box. This is very easy to do. Because we are not given level names in alphabetical order, we will employ a bubble sort to put them into the list box in alphabetical order. P r i v a t e Sub U s e r F o r m - I n i t i a l i z e 0 D i m m y L e v e l As L e v e l
D i m LevelNamesO As S t r i n g D i m Madechange A s B o o l e a n D i m tmpName A s S t r i n g D i m I As L o n g ReDim L e v e l N a m e s ( 0 ) F o r Each m y L e v e l I n A c t i v e D e s i g n F i l e . L e v e 1 s L e v e l Names(UBound( L e v e l Names) )
=
m y L e v e l .Name
I Chapter 17: Interactive Modification I ReDim P r e s e r v e
LevelNames(UBound(Leve1Names) + 1)
Next ReDim P r e s e r v e Madechange
LevelNames(UBound(Leve1Names)
-
1)
True
=
W h i l e Madechange Madechange
=
=
True
False
F o r I = LBound(Leve1Names) To UBound(Leve1Names) I f S t r C o m p ( L e v e l N a m e s ( 1 ) . L e v e l N a m e s ( 1 + 1)) tmpName
=
-
1
=
1 Then
LevelNames(1)
LevelNames(1)
=
LevelNames(1
LevelNames(1
+ 1)
Madechange
True
=
=
+ 1)
tmpName
End I f Next I Wend F o r I = LBound(Leve1Names) To UBound(Leve1Names) 1 s t L e v e l s . A d d I t e m L e v e l Names( I) Next I End Sub
When comparing numeric values, we can use greater than 0) and less than (<) comparisons. You can also do this with text but the results are not always what we expect. So, we employ the standard VBA StrCornp function to compare two strings. P r i v a t e Sub b t n E x p o r t - C l i c k (
D i m m y F i l e N a m e As S t r i n g D i m m y N e w F i l e As D e s i g n F i l e D i m I As Long D i m E l e m I D As DLong D i m m y E l e m s 0 As E l e m e n t
D i m myElemEnum As E l e m e n t E n u m e r a t o r D i m m y L e v e l As L e v e l myFileName
=
txtFileName.Text
I f ActiveModelReference.1~30Then C r e a t e D e s i g n F i 1 e " s e e d 3 d " . m y F i 1 eName, Fa1 s e Else C r e a t e D e s i g n F i 1 e " s e e d 2 d " , m y F i 1 eName, Fa1 s e End I f S e t myNewFile
=
OpenDesignFileForProgram(myFi1eName)
D i m m y S e l C r i t e r i a As New E l e m e n t s c a n c r i t e r i a
mySel Cri t e r i a . E x c l u d e A l 1 L e v e l s
I Providing User Feedback and Information I For I
=
379
1 To 1 s t L e v e l s . L i s t C o u n t
If lstLevels.Selected(1
-
1) T h e n
mySel Cri t e r i a . I n c l u d e L e v e l
~
ActiveModelReference.Levels(lstLeve1s.List
(I -
1))
End I f Next I S e t myElemEnum
=
ActiveModel Reference.Scan(mySe1Criteria)
myElemEnum.BuildArrayFromContents
myElems
=
For I
LBound(myE1ems) To UBound(myE1ems)
=
myNewFile.Models(l).CopyElement
myElems(1)
Next I myNewFile.Save MsgBox UBound(myE1ems) + 1 & " elements c r e a t e d i n f i l e " & vbCr & myFileName, v b I n f o r m a t i o n , Me.Caption End Sub
We create a new design file using the CreateDesignFile method. We then open that file using OpenDesignFileForProgram. This function allows us to open and work with a file without the user seeing the file in the Microstation interface. Next, we create an Elementscancriteria object, so we only look for elements on the selected levels. We ExcludeAllLevels and then begin adding in the ones that are selected in the 1stLevels ListBox. As we include the levels in our scan criteria, we could add them to the new design file we just finished creating. This is not necessary as the levels will be created when we copy elements to the new file. However, if we select a level in our interface and it does not have any elements on it, the new design file will not have that level.
Providing User Feedback and Information The code imported with the form contains these events: P r i v a t e Sub UserForm-MouseMove(ByVa1 B u t t o n As I n t e g e r , B y V a l S h i f t As I n t e g e r , B y V a l X As S i n g l e , B y V a l Y As S i n g l e ) ShowPrompt S h owS t a t u s S h ow C omma n d
" " " " " "
End Sub P r i v a t e Sub 1 s t L e v e l s - M o u s e M o v e ( B y V a 1 B u t t o n As I n t e g e r , B y V a l S h i f t A s I n t e g e r , B y V a l X As S i n g l e , B y V a l Y A s S i n g l e )
380
I Chapter 17: Interactive Modification I ShowPrompt S how St a t u s
S h ow C o mm a n d
" " " " " "
End Sub P r i v a t e Sub t x t F i l e N a m e c M o u s e M o v e ( B y V a 1 B u t t o n A s I n t e g e r , B y V a l S h i f t As I n t e g e r , B y V a l X A s S i n g l e , B y V a l Y As S i n g l e ) ShowPrompt
S how St a t u s S h ow C o mm a n d
"I'
" " " "
End Sub P r i v a t e Sub btnExport-MouseMove(ByVa1 B u t t o n A s I n t e g e r , B y V a l S h i f t As I n t e g e r , B y V a l X A s S i n g l e , B y V a l Y As S i n g l e ) ShowPrompt
S how St a t u s S h ow C o mm a n d
"I'
" " " "
End Sub
Nothing is being sent to the prompt, status, or command areas of the status bar in Microstation. As we consider the functionality of this program, what should these values be? We will let each individual answer for themselves. Here is the "VBA Files From Levels" form working. This program does not need to be modeless because this program does not require user interaction inside Microstation while the form is being displayed, but it still needs to be shown from a procedure in a code module. Sub T e s t F i 1 esFromLeve1 s ( ) frmExportElements.Show End Sub
I Providing User Feedback and Information I
381
The above code is placed in a code module so users can execute this program. Here is the interface in use. Any number of levels can be selected and exported to the file name entered.
f rmDFAV.frm The frmDFAKfrm program is used to display attachments of design files.
The user selects a folder by clicking on the Browse button. When selected, the selected folder is browsed for a Microstation .dgn file. Each design file found is added to the File listbox. When the user clicks on a
382
I Chapter 17: Interactive Modification I file in the File listbox, we look into the file for any attachments. All attachments found are added to the Attachments listbox.
Control Names txtFolder btnBrowse lstFiles 1stAttachments We are going to add a few elements in this program we have not used thus far. We could have the user type in a folder. Let's have them select it instead. Here's the Folder Selection dialog box we want:
Local Disk(C:) Key Largo (0:) projects on 'Puny' (V:) PunylRem on 'Punyl' (Y:) Store on 'Dev' (I:) Shared Documents Administrator'sDocuments Guest's Documents jerryw's Documents jkw's Documents
How do we get it? We use the Windows API.
The other thing we want to do is store the settings of our application so the next time we use it we can recall those settings. These settings are stored in the Windows registry. Here is what this looks like:
VBA File Attachment Viewer DeFaults VBA File ReFerence Viewer XM-AddInlW Viewpoint
.(DeFault) ........................
i
Include Subs Path
REG-SZ
(value not set)
REG-SZ REG-SZ
True C:\Microstation VBA
Program Components Retrieve Settings from Registry on Form Initialize Allow User to Select Root Folder Search in Folder for .dgn files
I Providing User Feedback and Information I
383
Search in Folder's Sub Folders for .dgn files When user clicks on file, display Attachments When Program Closes, save settings to Registry
Retrieve Settings from Registry on Form Initialize P r i v a t e Sub U s e r F o r m - I n i t i a l i z e 0 txtFolder.Text
GetSetting("VBA F i l e Attachment Viewer", "Defaults", "Path")
=
I f t x t F o l der .Text
<>
" "
Then
chkSubFolders.Value = GetSetting("VBA F i l e Attachment Viewer", -"Defaults", "Include Subs") Popul a t e F i l e L i s t End I f End Sub
When the form is initialized we look for the saved settings and put them in. If we find a "path" saved in the registry, we set the checkbox value and populate the file list using our PopulateFileList method.
Allow User to Select Root Folder We use the Windows API to allow the user to select a folder. In the general declarations area we declare the following: P r i v a t e Type BrowseInfo hWndOwner As Long p i d l R o o t As Long sDisplayName As S t r i n g s T i t l e As S t r i n g u l F l a g s As Long l p f n As Long l P a r a m As Long i I m a g e As Long End T y p e P r i v a t e D e c l a r e F u n c t i o n SHBrowseForFolder L i b " s h e 1 1 3 2 . d l l "
-
( b B r o w s e As B r o w s e I n f o ) As Long P r i v a t e Declare Function SHGetPathFromIDList L i b " s h e 1 1 3 2 . d l l " ( B y V a l l I t e m A s L o n g , B y V a l s D i r As S t r i n g ) As L o n g
I Chapter 17: Interactive Modification I P r i v a t e Const Bif-ReturnOnlyFSDirs P r i v a t e Const Bif-EditBox
=
2
16
=
P r i v a t e Const Bif-NewDialogStyle P r i v a t e C o n s t Bif-UseNewui
1
=
P r i v a t e C o n s t Bif-DontGoBelowDomain
=
=
64
80
P r i v a t e Const Bif-BrowseForComputer P r i v a t e Const Bif-BrowseForPrinter
=
P r i v a t e Const Bif-BrowseIncludeFiles
4096
8192
=
=
16384
After the constants, types, and functions are declared, we can use them in our code. Here is the click event of the Browse button. P r i v a t e Sub b t n B r o w s e c C 1 i c k ( 1 D i m MyBI A s B r o w s e I n f o D i m F L i s t As L o n g
D i m D i r N a m e As S t r i n g D i m S e l F o l d e r As L o n g DirName
Space(255)
=
MyBI.sTitle
=
" S e l e c t Root F o l d e r : "
MyBI.sDisplayName MyBI.ulFlags FList
=
=
=
Space(255)
Bif-ReturnOnlyFSDirs
SHBrowseForFolder(MyB1)
S H G e t P a t h F r o m I D L i s t ( F L i s t , DirName)
Sel Folder
=
DirName
Left(DirName,
=
I f DirName
<>
" "
I n S t r ( 1 , DirName, C h r ( 0 ) )
-
1)
Then
txtFolder.Text
=
DirName
t x t Fo 1 d e r .T e x t
=
" "
Else End I f PopulateFileList End Sub
PopulateFileList is a procedure that takes the folder in the TextBox and begins looking for .dgn files. Sub
PopulateFileListO 1s t F i l e s . C l e a r D i m m y F o l d e r As F o l d e r D i m myFSO As New F i l e S y s t e m O b j e c t
I f txtFolder.Text S e t myFol d e r
<> =
"I'
Then
myFSO.GetFo1 d e r ( t x t F o l d e r . T e x t )
FilesInFolder myfolder,
"dgn", chkSubFolders, l s t F i l e s
I Providing User Feedback and Information I
385
End I f 1blFiles.Caption
= "Design F i l e s i n Folder " & 1 s t F i l e s . L i s t C o u n t & - " F i l e s Found."
End Sub
Popul a t e F i 1 e l i s t uses the FileSystemObject. This is a Windows component we need to add to our references before we can use it. In VBA, go to Tools > References to display the References dialog box:
When selected, the Microsoft Scripting Runtime provides an easy way to find files and traverse folders and sub-folders. The P opu 1 a t e F i 1 e L i s t method utilizes our F i 1 es I n Fo 1 d e r method. The F i 1 e s I n Fol d e r method is a recursive method, which means it calls itself. Here it is. Sub
Fi 1 esInFol der( F o l d e r I n
As F o l d e r , F i l e E x t e n s i o n As S t r i n g ,
I n c l u d e s u b s As Boolean, L i s t T o P o p u l a t e As L i s t B o x )
D i m m y F i l e As F i l e F o r Each m y F i l e I n F o l d e r I n . F i l e s S e l e c t Case U C a s e ( R i g h t ( m y F i l e . N a m e ,
3))
Case U C a s e ( F i 1 e E x t e n s i o n ) ListToPopulate.AddItem myFile.Path End S e l e c t Next I f Includesubs
=
T r u e Then
D i m subFolder As F o l d e r
386
I Chapter 17: Interactive Modification I For Each subFolder In FolderIn.SubFolders FilesInFolder subfolder, FileExtension, Incl udeSubs, ListToPopul ate Next End If End Sub
-
The first thing Fi 1 esInFolder does is looks for files in the "FolderIn" parameter folder. We look at the file extension to see if it matches the FileExtension parameter. If it does, we add it to the list. After all files have been reviewed, we check if we should also look at sub-folders. If we are not to look at sub-folders, we complete the function and move on. If we are to look at sub-folders, we begin a For ... Each statement to look at each of the sub-folders in the current folder.
Foreachsub-folderwe findusing FilesInFolder,we call F i l e s I n F o l d e r using the sub-folder as the FolderIn parameter. This is why it is recursive. The procedure calls itself. When dealing with recursive procedures or functions, we need to be sure there is a way to finish execution. Otherwise, we could end up with hundreds or thousands of procedures in the call stack with no ending to the execution. Displaying Attachments
When the user selects a file in the Files listbox we get the attachments of the selected file and display them in the Attachments ListBox. Private Sub 1stFiles-C1 ick( 1 1stAttachments.Clear If 1stFiles.Text <> Then Dim myDesFile As DesignFile "I'
Set myDesFile
=
OpenDesignFileForProgram(lstFiles.Text, True)
Dim myAttachment As Attachment For Each myAttachment In myDesFile.DefaultModelReference.Attachments 1stAttachments.AddItem myAttachment.AttachName Next myDesFile.Close End If 1blAttachments.Caption = "Attachments in Selected File & IstAttachments. ListCount & Attachments Found." End Sub "
"
I Interacting with MDL Applications I
387
Before looking at the attachments of a file, we need to open the file. We have two ways to open the file: open it for the user to see and work with or open it so the user does not see the file but our program can work with it. In this example we are using OpenDesignFi 1 eForProgram to open the file because we do not want to open the file in Microstation's editing window each time a file is selected. After the user has reviewed the attachments of the desired files, the user closes the program. When a program is being closed, we want to store the settings so the next time the program is executed we begin with those settings in place. UserForm-QueryClose(Cance1
-
S a v e s e t t i n g "VBA F i l e A t t a c h m e n t V i e w e r " ,
"Defaults",
P r i v a t e Sub
As I n t e g e r , C l o s e M o d e As I n t e g e r )
"
-
Pa t h " , t x t F o 1 d e r . T e x t
S a v e s e t t i n g "VBA F i l e A t t a c h m e n t V i e w e r " , " D e f a u l t s " , " I n c l u d e Subs", - chkSubFolders.Va1ue End Sub
We are saving two settings to the Windows registry. These are the settings read by the initialize event of the form.
INTERACTING WITH MDL APPLICATIONS Let's record a macro where we import an image using the Microstation menu File > Import > Image. Browse to C:\frogrurn Files\Bentley\MicroStution\bentleyb.jpg and place it in Microstation. After the image is placed, stop recording the macro. Before we look at the macro that was created, we should discuss the two methods of interacting with MDL Applications. The first method looks like our previous use of the "SendCommand" method where, after the command begins, we can supply points or other input as needed. The second method, using dialog boxes, requires a class module that handles the events of the dialog box. The Import Image MDL application makes use of a dialog box so a new class is created that is used with the recorded macro. Let's begin by looking at the recorded macro. After we do, this we will look at the class module created by the macro recorder.
388
I Chapter 17: Interactive Modification I Sub Macro50 D i m s t a r t p o i n t As P o i n t 3 d
D i m p o i n t As P o i n t 3 d , p o i n t 2 As P o i n t 3 d D i m l n g T e m p As L o n g D i m m o d a l H a n d l e r As New M a c r o 5 M o d a l H a n d l e r AddModal D i a l o g E v e n t s H a n d 1 e r m o d a l H a n d l e r
' '
The f o l l o w i n g s t a t e m e n t opens modal d i a l o g " S e l e c t Image F i l e " S t a r t a command
Cad1nputQueue.SendCommand "MDL LOAD PLAIMAGE" '
'
Coordinates are i n master u n i t s startP0int.X
=
-6.270784
startP0int.Y
=
23.160278
startP0int.Z
=
011
Send p o i n t s t o s i m u l a t e a d o w n - d r a g - u p a c t i o n p0int.X
=
startP0int.X
p0int.Y
=
startP0int.Y
p0int.Z
=
startP0int.Z
point2.X
=
p 0 i n t . X + 2.938037
point2.Y
=
p0int.Y
point2.Z
=
p0int.Z
-
2.980928
Cad1nputQueue.SendDragPoints p o i n t , p o i n t 2 , 1 RemoveModal D i a1 o g E v e n t s H a n d 1 e r m o d a l H a n d l e r
CommandState.StartDefaultCommand End S u b
The class module created is named Macro5ModalHandler. After declaring a few variables, the macro recorder declares a variable as a "Macro5ModalHandler" and adds the events of this class. Next, the "MDL LOAD PLAIMAGE" command is sent. This displays the dialog box. After the dialog box is shown, the class module handles the entry of the file path and name and closes the dialog box. When the dialog box closes, we are back in the Macro5 procedure, which places the image in the design file by 'dragging' points. The event handler is removed and we finish out the procedure. Let's take a look at the class module created. Implements IModal DialogEvents
I Interacting with MDL Applications I P r i v a t e Sub IModal DialogEvents-OnDialogClosed(ByVa1
389 -
DialogBoxNarne A s S t r i n g , ByVal D i a l o g R e s u l t A s M s d D i a l o g B o x R e s u l t )
End Sub P r i v a t e Sub IModal DialogEvents-OnDialogOpened(ByVa1
-
DialogBoxName As S t r i n g , D i a l o g R e s u l t As M s d D i a l o g B o x R e s u l t )
I f DialogBoxName
=
" S e l e c t Image F i l e " T h e n
CadInputQueue.SendCommand "
-
M D L COMMAND MGDS HOO K , f i1 e L is t-s e t F i 1 t e r Cmd
CadInputQueue.SendCommand
* .c a 1
-
"MDL COMMAND M G D S H O O K , f i l e L i s t - s e t D i r e c t o r y c m d "C:\Program
'
&
"
Files\Bentley\MicroStation\"
CadInputQueue.SendCommand "MDL COMMAND MGDSHOOK,fileList-setFileNameCmd "
"
"
&
b e n t 1 e y b .j p g "
Remove t h e f o l l o w i n g l i n e t o l e t t h e u s e r c l o s e t h e d i a l o g b o x .
D i a l ogResul t End I f
=
m s d D i a l ogBoxResu1 t O K
' S e l e c t Image F i l e
End Sub
Every time the macro Ma c r 05 is run, the same image will be placed in the same place. Let's make a few modifications to the code we have so we can create a more flexible and powerful class module that can be used in future projects. Here is the code for the new class module. It is named clsl magelnsertion. We have added two public variables that act as
properties to this class module. Implements IModalDialogEvents P u b l i c F i l e P a t h As S t r i n g P u b l i c F i l e N a m e As S t r i n g P r i v a t e Sub IModal DialogEvents-OnDialogClosed(ByVa1 D i a l o g B o x N a m e As S t r i n g , B y V a l D i a l o g R e s u l t As MsdDi a1 o g B o x R e s u l t ) End Sub
-
390
I Chapter 17: Interactive Modification I Private Sub IModalDialogEventspOnDialogOpened(ByVal DialogBoxName As String, DialogResult As MsdDialogBoxResult) If DialogBoxName = "Select Image File" Then CadInputQueue.SendCommand "MDL COMMAND MGDSHOOK,fileList-setDirectoryCmd & FilePath CadInputQueue.SendCommand "MDL COMMAND MGDSHOOK,fileListpsetFileNameCmd & Fi 1 eName DialogResult = msdDialogBoxResultOK End If End Sub "
"
The path and filename is no longer hard-coded. This means we can use this class module any time we want to insert an image into a file. This is how it is used: Sub TestImageInsertionO Dim pointl As Point3d, point2 As Point3d Dim modalHandler As New ClsImageInsertion modalHandler. F i l e P a t h
=
"C:\Program
Files\Bentley\MicroStation\"
modalHandler. FileName = "bentleyb. jpg" AddModalDialogEventsHandler modalHandler CadInputQueue.SendCommand "MDL LOAD PLAIMAGE" point1.X = 0: point1.Y = 0: point1.Z = 0 point2.X = 1: point2.Y = 1: point2.Z = 0 CadInputQueue.SendDragPoints pointl, point2, 1 RemoveModalDialogEventsHandler modalHandler CommandState.StartDefaultCommand End Sub
Using FilePath and FileName properties for the class module allows the class module to be used with any file path or name. Previously, the path and name were hard-coded.
The Microstation VBA API is powerful. This power allows us to be creative in how we approach programming tasks. For example, initiating the "PLACE LINE" command to provide the user with a more graphically rich experience when selecting two points can be used even when we are not concerned with drawing a line. Using a modeless form allows the user complete flexibility in working with the Microstation interface while allowing interaction with our own GUI.
18
Interface Essentials What is an interface? The term is used in programming to describe several different things, so lets explain and define the term. We have already worked with user forms and controls to create a graphical user interface, which allows users to interact with controls. The graphics provide an interface to the code of the program. Another type of interface allows us to interact with code in a program but does not have a graphical component. It allows us to interact with the user’s activities in Microstation. For example, when a user selects an element in Microstation, we can capture that activity through the use of an interface named “ILocateCommandEvents’: As the user picks points in Microstation, we can capture those points through the use of the “IPrimitiveCommandEvents”Interface. User interaction with some dialogs in Microstation can be evaluated through the use of the “IModalDialogEvents”. In this Chapter:
Interface basics Class module review Class module lifecycle The dynamics event 391
392
I Chapter 18: Interface Essentials I The Locatecriteria object IPrimitiveCommandEvents interface Optimizing the dynamics event
INTERFACE BASICS The ability to capture user interaction in Microstation is powerful. To harness this power, we create a new class module that implements the interface. For example, to capture point selections in Microstation, we insert a new class module in our VBA project and place the following line in the General Declarations area of the class module: Implements IPrimitiveCommandEvents
Using the “Implements” keyword in a class module means the class module inherits the methods or events of the interface.
When we use the Implements keyword, the name of the interface appears in the object combobox of the class module.
I m p l e m e n t s 1P r i m i t i v e c o
If we select the interface in the object combobox, as shown above, the methods of the interface displays in the methods combobox. As we can see, the IPrimitiveCommandEvents interface exposes six methods or events. They are Cleanup, Datapoint, Dynamics, Keyin, Reset, and Start.
Unlike user form controls, where we pick and choose which events we want to display and work with, each and every method in an interface must be declared, even if we are not going to do anything with them. The easiest way to do this is to select each of the methods in the
I Class Module Review I
393
Methods combobox. Each time we do this, VBA automatically writes the Sub ... End Sub code for us. If we follow this procedure for the IPrimitiveCommandEvents interface, we see the following in the class module: Implements IPrimitiveCommandEvents P r i v a t e Sub
IPrimitiveCommandEvents-Cleanup0
End S u b P r i v a t e S u b I P r i r n i t i v e C o m m a n d E v e n t s D a t a P o i n t ( P o i n t ( P 0 i n t As P o i n t 3 d , B y V a l V i e w As V i e w ) End S u b P r i v a t e S u b I P r i m i t i v e C o m m a n d E v e n t s D y n a m i c s ( P o i n t As P o i n t 3 d ,
~
B y V a l V i e w A s V i e w , B y V a l DrawMode As M s d D r a w i n g M o d e ) End S u b P r i v a t e Sub IPrimitiveCommandEventsKeyin(ByVa1
K e y i n As S t r i n g )
End S u b P r i v a t e Sub
IPrimitiveCommandEvents-Reset0
End S u b P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s S t a r t ( ) End S u b
Now we are ready to enter the code into the events.
CLASSMODULE REVIEW An interface must be exposed using a class module. We already discussed class modules but a quick review is in order.
394
I Chapter 18: Interface Essentials I We create a new class module by using the VBA menu Insert > Class Module. By default, VBA names the new class modules “Classl’: “Class2’: “Class3”,“Class4”,etc. We should rename them to something that helps us understand what the class is. For example, we name a class that writes to files “clsFileWriter’: In a later example, we will name a Class “LCE-Text” to indicate we are working with the ILocateCommandEvents interface (LCE) and that we are doing something with text. After a class module is inserted and named, we begin writing our code. Methods and functions are written very much like they are in code modules. We can create events for our class modules. We can create properties by declaring variables as “Public” in the General Declarations area of the code module. And we can also create properties through the use of “Get” and “Set” (or “Let”)statements. The end result of creating a class is a new object. Classes cannot operate independently. They need other code to initiate them, set their properties, and use their methods. I will demonstrate two ways to call up a class module. The first is to create a new class module named “c1sNetNode’: Here is the code: P r i v a t e Type IPAddr S e t 1 As B y t e S e t 2 As B y t e S e t 3 As B y t e S e t 4 As B y t e End T y p e P u b l i c Name As S t r i n g P r i v a t e I P A d d r e s s As I P A d d r Sub P i n g 0 MsgBox “ P i n g i n g
”
& 1PAddress.Setl &
‘I.”
&
IPAddress.Set.2 &
‘I.”
&
IPAddress.Set3 &
‘I.”
&
IPAddress.Set4,
~
~
, Name
End Sub Sub S e t I P A d d r e s s ( 1 P A As B y t e , I P B As B y t e , I P C As B y t e , I P D As B y t e ) IPAddress.Set1
=
IPA
I Class Module Lifecycle I IPAddress.Set2
=
IPB
IPAddress.Set3
=
IPC
IPAddress.Set4
=
IPD
395
End S u b
This class has one property (Name) and two methods (Ping and SetIPAddress). The SetIPAddress method sets the IP address values of the private variable “IPAddress’: The Ping method displays the entered IP address in a MessageBox and uses the Name property for the MessageBox caption. The first way to call up a class module is to declare a variable as the class type, set the variable to a “New” class type, and then set properties and use methods. S u b T e s t C l sNetNodeA( 1 D i m myNetNode As c l s N e t N o d e
S e t myNetNode
=
New c l s N e t N o d e
m y N e t N o d e . S e t I P A d d r e s s 192, 1 6 8 , 1, 1 myNetNode.Name
=
“Router”
myNetNode. P i n g End S u b
The second way to utilize a class module is to declare a variable as a “New” class type and then begin setting variables and using methods as shown below: S u b T e s t C l sNetNodeB( 1 D i m myNetNode As New c l s N e t N o d e
m y N e t N o d e . S e t I P A d d r e s s 192, 1 6 8 , 1, 1 myNetNode.Name
=
“Router”
myNetNode. P i n g End S u b
The difference between these two ways to declare and initialize class modules is small. The net result is the same however in this example. ti
CLASSMODULE LIFECYCLE When we implement a simple class module, as we did with “clsNetNode’: the class is alive only as long as the variable declared as the
396
I Chapter 18: Interface Essentials I class module is in scope. In the two test procedures above, we declared the variable myNetNode inside the procedures so the clsNetNode Class is only alive inside the procedure where the variable was declared. Variables declared in the General Declarations area as a class module are available to all methods in the module in which it is declared and other modules as well if the variable was declared as “Public”. As soon as a variable declared as a specific class goes out of scope, the object is automatically terminated. This is not the only way a declared class can be terminated. You can terminate a class by setting the variable representing the class to Nothing, as follows: S e t myNetNode
=
Nothing
This explanation of the lifecycle of classes relates to most circumstances where we utilize class modules. One exception to this rule is when we use classes with the StartLocate and Startprimitive methods of the Microstation Commandstate object. When we use StartLocate and Startprimitive, Microstation holds onto the class and notifies it of events until either Microstation is closed down, we use “CommandState.StartDefaultCommand”, or we use one of the “Remove....” methods where applicable. We will see examples of the “Remove....”methods later in this book. It is important to understand the lifecycle of the classes we will be using to implement Microstation interfaces because the code we use to call up these classes will execute and the procedure will end but the class will still be alive because Microstation is keeping it alive. Let’s discuss two Microstation ILocateCommandEvents interface.
interfaces. The first
is the
ILocateCommandEvents The ILocateCommandEvents Interface allows us to have the user select or ‘Locate’ an element. Here are the events exposed through the ILocateCommandEvents interface: Private Sub ILocateCommandEvents-Accept(ByVa1 Element As Element, Point As Point3d, ByVal View As View) Private Sub ILocateCommandEvents-Cleanup()
I Class Module Lifecycle I
397
Private Sub ILocateCommandEvents-Dynamics( Point As Point3d, ByVal View As View, ByVal DrawMode As MsdDrawingMode) Private Sub ILocateCommandEvents-LocateFailedO Private Sub ILocateCommandEvents-LocateFilter(ByVal Element As Element, Point As Point3d, Accepted As Boolean) Private Sub ILocateCommandEvents-LocateResetO Private Sub ILocateCommandEvents-Start() Each event is triggered at a specific time. Some of the events provide information such as which element was located. Whenever we use Microstation’s interface objects, each event or method must be declared, whether we intend to use it or not. Let’s take a look at an example. We begin with capitalizing text elements. The name of this class module is LCE-Text. Here is the code in the class module: Implements ILocateCommandEvents P r i v a t e S e l E l e m e n t As E l e m e n t P r i v a t e S u b ILocateCommandEvents-Accept(ByVa1 E l e m e n t As E l e m e n t , P o i n t As P o i n t 3 d , B y V a l V i e w As V i e w )
D i m e l e m T e x t As T e x t E l e m e n t S e t e l emText
=
Element
elemText.Redraw msdDrawingModeErase elemText.Text
=
UCase(elemText.Text)
e l e m T e x t . R e d r a w msdDrawingModeNorma1 e l emText. R e w r i t e ActiveModel Reference.UnselectAl1 Elements
CommandState.StartDefaultCommand End S u b P r i v a t e Sub ILocateCommandEventsCleanupO End S u b P r i v a t e S u b I L o c a t e C o m m a n d E v e n t s - D y n a m i c s ( P o i n t As P o i n t 3 d , B y V a l V i e w As V i e w , B y V a l DrawMode As M s d D r a w i n g M o d e ) End S u b
398
I Chapter 18: Interface Essentials I P r i v a t e Sub
ILocateCommandEvents-LocateFailedO
I f SelElement I s Nothing
=
F a l s e Then
ActiveModel Reference.Unse1 e c t A l 1 Elements Set SelElement
=
Nothing
End I f
S h ow C o mm a n d
"
CA P T e x t "
ShowPrompt " S e l e c t T e x t t o b e C a p i t a l i z e d " End Sub P r i v a t e Sub I L o c a t e C o m m a n d E v e n t s L o c a t e F i l t e F i l t e r ( B y V a 1 E l e m e n t As E l e m e n t , P o i n t As P o i n t 3 d , A c c e p t e d As B o o l e a n ) ~
Accepted
=
False
I f Element.IsTextElement S e t Sel Element Accepted
=
=
=
T r u e Then
Element
True
ActiveModel Reference.SelectElement Element, True
S h owC omma n d
"
CAP T e x t "
ShowPrompt " C l i c k a g a i n t o c o n f i r m . .
."
End I f End Sub
ILocateCommandEvents-LocateResetO CommandState.StartDefaultCommand
P r i v a t e Sub End Sub P r i v a t e Sub
ILocateCommandEvents-Start0
End Sub
LocateFilter Event The first event we work with is the LocateFilter event. This event gives us the ability to specify whether the element selected meets our criteria. By default, the accepted property is true. If the accepted property remains true, the user is given the opportunity to "Accept" the selection by clicking again in Microstation. When the user "Accepts" the selection, the accept event is triggered and the code inside it is executed. If in the LocateFilter event, the accepted parameter is set to false, the LocateFailed event is triggered. It is common to re-start the interface object if the LocateFilter event returns a false accepted value.
I Class Module Lifecycle I
399
Accept Event Two conditions must exist before the accept event is triggered. First, the LocateFilter event must exit with an accepted property of true. Second, the user must “Accept” the already filtered element by left-clicking in Microstation. A right-click in Microstation, after LocateFilter successfully exits, resets the LocateFilter event but will not exit the interface completely. When these two conditions (LocateFilter and User Acceptance) are met, the code in the Accept event is executed.
LocateReset Event The LocateReset event, the last triggered event in this interface, is triggered when the user issues a reset by right-clicking in Microstation before the LocateFilter Event has been entered or after the LocateFilter event has been entered but the accepted property has been set to false. Remember that the LocateReset event is telling us that the user has requested a reset. It is up to our code to exit the interface by issuing a “CommandState.StartDefaultCommand.
LocateFailed Event The LocateFailed event is triggered when the user clicks to select something but nothing is located. This event could be used to exit out of the interface by using “CommandState.StartDefau1tCommand’:
Start Event The Start event, the first event triggered when utilizing this interface, can be used to set up variables or other objects.
Cleanup Event The Cleanup event is triggered just prior to the LocateReset event. As the name implies, it can be used to clean up variables, objects, or references used by the interface.
Dynamics Event The Dynamics event provides dynamic real-time feedback. An example later in this chapter demonstrates how it is used.
400
I Chapter 18: Interface Essentials I Class Modules do not work by themselves - they need to be created by other code. Here is the procedure that makes use of our new LCE-Text class. Sub t s t L C E - T e x t ( ) CommandState.StartLocate New LCELText S h ow C omm a n d C A P Text ShowPrompt "Select Text to be Capital ized" End Sub "
"
T
Here are the screen shots of the program working. Notice the command and prompts guiding the user.
Here is another variation of the Capitalize Text program. The only difference is the code handling the capitalizing of the text. It is now placed in the LocateFilter event. This means the selected text element is capitalized without waiting for user confirmation. Implements ILocateCommandEvents Private SelElement As Element Private Sub ILocateCommandEvents-Accept(ByVa1 Point A s Point3d, ByVal View As View) End Sub
Element As Element,
I Class Module Lifecycle I P r i v a t e Sub
401
ILocateCommandEvents-Cleanup()
End Sub As P o i n t 3 d . P r i v a t e Sub ILocateCommandEvents-Dynamics(Point B y V a l V i e w As V i e w , B y V a l DrawMode As M s d D r a w i n g M o d e )
-
End Sub P r i v a t e Sub
ILocateCommandEvents-LocateFailedO
I f SelElement I s Nothing
=
F a l s e Then
ActiveModelReference.UnselectAllElements Set SelElement
=
Nothing
End I f
S h ow C o mm a n d
"
CAP T e x t "
ShowPrompt " S e l e c t T e x t t o b e C a p i t a l i z e d " End Sub P r i v a t e Sub I L o c a t e C o m m a n d E v e n t s L o c a t e F i l t e F i l t e r ( B y V a 1 E l e m e n t As E l e m e n t , P o i n t As P o i n t 3 d , A c c e p t e d As B o o l e a n ) Accepted
=
False
D i m e l e m T e x t As T e x t E l e m e n t I f Element.IsTextElement S e t e l emText
=
=
T r u e Then
Element
elemText.Redraw msdDrawingModeErase elemText.Text
=
UCase(elemText.Text)
e l e m T e x t . R e d r a w msdDrawingModeNorma1 e l emText. R e w r i t e
ActiveModelReference.UnselectAllElements CommandState.StartDefaultCommand End I f End Sub P r i v a t e Sub I L o c a t e C o m m a n d E v e n t s L o c a t e R e s e t O
CommandState.StartDefaultCommand End Sub P r i v a t e Sub I L o c a t e C o m m a n d E v e n t s S t a r t ( ) End Sub Here i s t h e code t h a t i n i t i a l i z e s t h e I n t e r f a c e O b j e c t .
402
I Chapter 18: Interface Essentials I Sub tstLCE-Text20 CommandState.StartLocate New LCELTextP S h ow C omm a n d C A P Text ShowPrompt "Select Text to be Capital ized" End Sub "
"
We have not used the Dynamics event mentioned previously. Let's use it now. This code dynamically draws a new text element displaying the distance between the original selection point and the cursor location. This is done real-time. As the cursor moves, the text changes.
Here are two examples of this Interface in action. The first is while the cursor is being dragged after the initial selection. The second is after the mouse button is clicked and the "Distance Text" is placed.
3.087 ,lt.iple contacts1
Lm
o t i on
Here is the code for the Class Module named LCE-DistanceText. Implements ILocateCommandEvents Private selElem As Element Private pt3StPoint As Point3d Private dblDistance As Double P r i v a t e Sub I L o c a t e C o m r n a n d E v e n t s A c c e p t ( B y V a 1 E l e m e n t As E l e m e n t ,
Point As Point3d, ByVal View A s View) Dim txtElem As TextElement Dim rotMatrix As Matrix3d dblDistance = Point3dDistance(Point, pt3StPoint)
I Class Module Lifecycle I Set txtElem
CreateTextElementl(selElem, R o u n d ( d b l D i s t a n c e ,
=
31, P o i n t , r o t M a t r i x ) ActiveModel Reference.AddElement t x t E l e m t x t E l em. R e w r i t e t x t E l em. Redraw
CommandState.StartLocate Me End Sub P r i v a t e Sub
ILocateCommandEvents-Cleanup()
End Sub P r i v a t e Sub I L o c a t e C o m m a n d E v e n t s D y n a m i c s ( P o i n t As P o i n t 3 d ,
~
B y V a l V i e w As V i e w , B y V a l DrawMode As M s d D r a w i n g M o d e ) D i m t m p T x t E l e m As T e x t E l e m e n t D i m r o t M a t r i x As M a t r i x 3 d
dblDistance
Point3dDistance(Point, pt3StPoint)
=
Set tmpTxtElem
=
CreateTextElementl(se1 Elem, Round(dblDistance, 3 ) , Point, rotMatrix)
t m p T x t E l e m . R e d r a w DrawMode ShowPrompt " S e l e c t D i s t a n c e P o i n t : " End Sub P r i v a t e Sub I L o c a t e C o m m a n d E v e n t s L o c a t e F a i l e d O
CommandState.StartLocate Me End Sub P r i v a t e Sub
ILocateCommandEvents-LocateFilter(ByVa1
-
E l e m e n t As E l e m e n t , P o i n t As P o i n t 3 d , A c c e p t e d As Boo1 e a n ) S e t s e l E l em pt3StPoint
=
Element Point
=
CommandState.StartDynamics End Sub P r i v a t e Sub I L o c a t e C o m m a n d E v e n t s L o c a t e R e s e t O
CommandState.StartDefaultCommand End Sub P r i v a t e Sub
ILocateCommandEvents-Start0 T e x t D is t a n c e
S h ow C omma n d
"
"
ShowPromDt " S e l e c t E l e m e n t f o r B a s e P o i n t "
-
-
404
I Chapter 18: Interface Essentials I End
Sub
Here is the procedure that calls the interface through the class: Sub t s t L C E - D i stanceText( ) CommandState.StartLocate New L C E L D i s t a n c e T e x t End Sub
This macro demonstrates using a Dynamics event. A careful review of the LocateFilter event shows the StartDynamics method. Without this method, the Dynamics event would not be triggered. The Dynamics event creates a new text element at the point of the cursor displaying the distance between the original Locate Point and the cursor location.
Locatecriteria When an element is ‘located’, we enter the LocateFilter method. In previous examples we used this method to determine the type of the selected element. This works but if we know the kind of element we want, we can specify this before the selection is made by using LocateCriteria. Implements ILocateCommandEvents P r i v a t e S e l E l e m e n t As E l e m e n t D i m myLC A s L o c a t e c r i t e r i a
Private Sub ILocateCommandEvents-Accept(ByVa1
Element As Element, P o i n t As P o i n t 3 d .
ByVal View As View) End
Sub
Private End
Sub
Private
End
Sub ILocateCommandEventsLCleanupO
Sub ILocateCommandEvents-Dynamics(Point
As P o i n t 3 d , B y V a l V i e w As V i e w , B y V a l DrawMode A s M s d D r a w i n g M o d e )
Sub
Private
Sub ILocateCommandEventsLLocateFailedO
I f SelElement Is N o t h i n g
=
F a l s e Then
I Class Module Lifecycle I
405
ActiveModelReference.UnselectAllElements Set SelElement
=
Nothing
End I f S h ow C o mm a n d
"
CAP T e x t "
ShowPrompt " S e l e c t T e x t t o b e C a p i t a l i z e d " End Sub P r i v a t e Sub I L o c a t e C o m m a n d E v e n t s - L o c a t e F i l t e r ( B y V a 1 E l e m e n t As E l e m e n t , P o i n t As P o i n t 3 d , A c c e p t e d As B o o l e a n )
-
D i m e l e m T e x t As T e x t E l e m e n t S e t e l emText
=
Element
elemText.Redraw msdDrawingModeErase elemText.Text
=
UCase(elemText.Text)
e l e m T e x t . R e d r a w msdDrawingModeNorma1 e l emText. R e w r i t e ActiveModel Reference.UnselectAl1 Elements
CommandState.StartDefaultCommand End Sub P r i v a t e Sub I L o c a t e C o m m a n d E v e n t s L o c a t e R e s e t O
CommandState.StartDefaultCommand End Sub P r i v a t e Sub I L o c a t e C o m m a n d E v e n t s - S t a r t 0 S e t myLC
=
CommandState.CreateLocateCriteria(True)
my LC. E x c l u d e A l 1 T y p e s
myLC.IncludeType (msdElementTypeText)
CommandState.SetLocateCriteria myLC End Sub
We make use of the Locatecriteria object with S e t L o c a t e C r i t e r i a to proactively filter the user's selection. This is preferable to allowing the selection to be made, reviewing the element's properties, and accepting or rejecting the selection inside the LocateFilter event. By using the Locatecriteria object, we know the user has made a legitimate selection by the time we get to the Locatecriteria event. Here are the methods of the Locatecriteria object: Sub ExcludeAllClasses() Sub ExcludeAllLevels()
406
I Chapter 18: Interface Essentials I El Sub ExcludeAllTypes() El Sub ExcludeClass(ElemC1assAs MsdElementClass)
El Sub ExcludeLevel(Leve1As Level) El Sub ExcludeType(TypeAs MsdElementType) El Sub IncludeClass(ElemC1assAs MsdElementClass) El Sub IncludeLevel(Level As Level)
El Sub IncludeOnlyHole() El Sub IncludeOnlyLocked() El Sub IncludeOnlyModified() El Sub IncludeOnlyNew()
El Sub IncludeOnlyNonPlanar() El Sub IncludeOnlyNonSnappable() El Sub IncludeOnlyOld() El Sub IncludeOnlyPlanar()
El Sub IncludeOnlySnappable() El Sub IncludeOnlySolid() El Sub IncludeOnlyUnlocked() El Sub IncludeOnlyUnmodified()
El Sub IncludeType(Type As MsdElementType) The Microstation VBA help file explains the use of each method shown here as well as examples of how they are used.
IPrimitiveCommandEvents We just finished discussing the ILocateCommandEvents interface. Its primary use is selection (or location) of elements in a design file. Use the IPrimitiveCommandEvents object to capture command entry and point selection. Here are the events we have to work with: El Private Sub IPrimitiveCommandEvents-Cleanup()
I Class Module Lifecycle I
407
Private Sub IPrimitiveCommandEvents-DataPoint(Point As Point3d, ByVal View As View) Private Sub IPrimitiveCommandEvents-Dynamics(Point As Point3d, ByVal View As View, ByVal DrawMode As MsdDrawingMode) Private Sub IPrimitiveCommandEvents-Keyin(ByVal Keyin As String) Private Sub IPrimitiveCommandEvents-Reset() Private Sub IPrimitiveCommandEvents-Start() Some of these should look familiar: Start, Reset, Cleanup, Dynamics. We have already used these. Two events we have not worked with are DataPoint and Keyin. Let’s take a look at several examples of how these events work.
PCE-Li n eTest The PCE-LineTest class draws a rubber-band line from the first point selected to the current cursor location. After the second point is selected, we use StartDefaultCommand to exit out of the class: Implements IPrimitiveCommandEvents D i m p t 3 B a s e P o i n t As P o i n t 3 d D i m b o o l S e t As B o o l e a n
P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s _ C l e a n u p O End Sub P r i v a t e Sub IPrimitiveCommandEvents-DataPoint(Point As P o i n t 3 d . ByVal View As View) If boolSet
=
F a l s e Then
pt3BasePoint
=
Point
CommandState.StartDynamics
boolSet
=
True
Else CommandState.StartDefau1tCommand End I f End Sub
408
I Chapter 18: Interface Essentials I P r i v a t e Sub
IPrimitiveCommandEvents-Dynamics(Point
As P o i n t 3 d , B y V a l V i e w As V i e w , B y V a l DrawMode As M s d D r a w i n g M o d e )
D i m m y L i n e E l e m As L i n e E l e m e n t S e t rnyLineElern
=
CreateLineElernentZ(Nothing. pt3BasePoint. P o i n t )
m y L i n e E l e m . Redraw DrawMode End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s K e y i n ( B y V a 1
K e y i n As S t r i n g )
End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s R e s e t ( ) End Sub P r i v a t e Sub
IPrimitiveCommandEvents-Start0
End Sub
Most of the code in this example is in the DataPoint event and the Dynamics event. Remember, we only want two points to be selected. We use the variable boolSet so we know if the first point has been selected. If the base point has not been selected, boolSet equals false and we take the Point parameter and place it in the pt3BasePoint variable, StartDynamics, and change boolSet to true. As the cursor moves in Microstation the Dynamics event is triggered. This \/ happens many times per second. We need to make sure the code in the Dynamics event is not too timeconsuming. In this example, we create a new LineElement between the initial point selected and the current cursor location given to us in the Point parameter.
Interface objects cannot run by themselves. They need code in a code module or a form to call them up. Sub P1 a c e l i n e ( ) CommandState.StartPrimitive New P C E - L i n e T e s t End Sub
I Class Module Lifecycle I
409
Running this code demonstrates the fact that it works. The first point is selected and the line is drawn as the cursor moves in Microstation. After the second point is selected, we exit the object. Normally we would not leave this object as it is. We would do something with the two points. We may draw a line between the two points. Or we could write code to divide the selected points into four equal segments and draw circles at those division points. We will see this in a future example.
PCE-RecTest The next example utilizes the same two point selection we saw in the previous example. However, in this example we draw a rectangle using the two points as bounding points. The only code that differs is the code that generates a shape using the X and Y elements of the points to create a rectangle. The name of this class module is PCE-RecTest. Implements IPrimitiveCommandEvents D i m p t 3 B a s e P o i n t As P o i n t 3 d D i m b o o l S e t As B o o l e a n
P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s C l e a n u p O End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s - D a t a P o i n t ( P o i n t ByVal View As View) If boolSet
=
As P o i n t 3 d .
-
F a l s e Then
pt3BasePoint
=
Point
CommandState.StartDynamics boolSet
=
True
Else
CommandState.StartDefau1tCommand End I f End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s D y n a m i c s ( P o i n t As P o i n t 3 d , B y V a l V i e w A s V i e w , B y V a l DrawMode A s M s d D r a w i n g M o d e ) ~
D i m p t 3 R e c P o i n t s ( O To 3 ) A s P o i n t 3 d
D i m myShapeElem A s S h a p e E l e m e n t pt3RecPoints(O)
=
pt3BasePoint
pt3RecPoints(l).X
=
P0int.X
pt3RecPoints(l).Y
=
pt3BasePoint.Y
410
I Chapter 18: Interface Essentials I pt3RecPoints(2)
=
pt3RecPoints(3).X
.Y
p t 3 Rec Po in t s ( 3 S e t myShapeElem
=
Point =
pt3BasePoint.X
=
P o i n t .Y
CreateShapeElementl(Nothing, p t 3 R e c P o i n t s )
m y S h a p e E l e m . R e d r a w DrawMode End Sub P r i v a t e Sub I P r i r n i t i v e C o r n r n a n d E v e n t s - K e y i n ( B y V a 1
K e y i n As S t r i n g )
End Sub P r i v a t e Sub I P r i r n i t i v e C o r n r n a n d E v e n t s - R e s e t 0 End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s S t a r t O End Sub
Notice how the X and Y elements of each shape vertex is derived from the base point and the current cursor point. Sub P1 a c e R e c ( )
C o m m a n d S t a t e . S t a r t P r i r n i t i v e New PCE-RecTest End Sub
\/
The procedure PlaceRec initiates the PCE-RecTest class module.
After the first point is selected, a rectangle is dragged from the first point to the cursor. Since we are not doing anything with the Reset event, the only way to get out of this interface is to select the second point.
PCE-CircTest The CircleTest class draws a circle with a center at the first selected point to the cursor. Implements IPrimitiveCommandEvents
D i m p t 3 B a s e P o i n t As P o i n t 3 d D i m b o o l S e t As Boolean
I Class Module Lifecycle I P r i v a t e Sub
411
IPrimitiveCommandEvents-Cleanup0
End Sub P r i v a t e Sub IPrimitiveCommandEvents-DataPoint(Point B y V a l V i e w As V i e w )
If b o o l S e t
As P o i n t 3 d .
-
F a l s e Then
=
pt3BasePoint
=
Point
CommandState.StartDynamics boolSet
=
True
Else
CommandState.StartDefaultCommand End
If
End Sub P r i v a t e Sub IPrimitiveCommandEvents-Dynamics(Point As P o i n t 3 d . B y V a l V i e w As V i e w , B y V a l DrawMode A s MsdDrawingMode)
-
D i m myCi r c l e A s E l 1 i p s e E l e m e n t D i m r o t M a t r i x As M a t r i x 3 d
D i m d b l Radius As Double dblRadius
=
Point3dDistance(pt3BasePoint, Point)
S e t m y c i r c l e = CreateEllipseElementZ(Nothing, dbl Radius, dblRadius, r o t M a t r i x )
pt3BasePoint,
myCi r c l e . Redraw DrawMode
End Sub P r i v a t e Sub IPrimitiveCommandEvents-Keyin(ByVa1
K e y i n As S t r i n g )
End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s R e s e t O
CommandState.StartDefaultCommand End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s S t a r t O End Sub
This example makes use of the Reset event. If the user resets the command, we exit the interface object by calling S t a r t D e f a u l tCommand. Sub P1 a c e C i r c ( 1
412
I Chapter 18: Interface Essentials I C o m m a n d S t a t e . S t a r t P r i m i t i v e New P C E - C i r c T e s t End Sub
PCE-Pol yTest The PolyTest example draws a regular polygon circumscribed within an imaginary circle centered at the first point and extending out to the cursor location. We could draw a square, a triangle, or a hexagon. Which should we draw? The PolyTest class can draw any regular polygon because we specify the number of vertices. The code in the class module is clear enough. The way we call up the class module differs from the other examples we have looked at. Let’s begin with the class module: Implements IPrimitiveCommandEvents
D i m p t 3 B a s e P o i n t As P o i n t 3 d D i m b o o l S e t As Boolean P u b l i c V e r t i c e s As L o n g P r i v a t e Sub
IPrimitiveCommandEvents-Cleanup0
End Sub P r i v a t e Sub IPrirnitiveCommandEventsDataPoint(Point(P0int A s P o i n t 3 d , B y V a l V i e w As V i e w ) If boolSet
=
F a l s e Then
pt3BasePoin t
=
Point
CommandState.StartDynamics boolSet
=
True
Else
CommandState.StartDefaultCommand End I f End Sub
I Class Module Lifecycle I
413
P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s - D y n a m i c s ( P o i n t As P o i n t 3 d . B y V a l V i e w As V i e w , B y V a l DrawMode As M s d D r a w i n g M o d e ) D i m p t 3 P o l y P o i n t s O As P o i n t 3 d
ReDim p t 3 P o l y P o i n t s ( O To V e r t i c e s
-
1) A s P o i n t 3 d
D i m myShapeElem A s S h a p e E l e m e n t
D i m I As L o n g D i m d b l B a s e A n g l e As D o u b l e dblBaseAngle For I
=
=
Atn((P0int.Y - pt3BasePoint.Y) / (P0int.X - pt3BasePoint.X))
0 To V e r t i c i e s
-
~
1
p t 3 P o l y P o i n t s ( I ) = Point3dAddAngleDistance(pt3BasePoint, dblBaseAngle + Radians(360 / V e r t i c i e s * I), Point3dDistance(pt3BasePoint, P o i n t ) , 0) ~
~
Next I S e t myShapeElem
=
CreateShapeElementl(Nothing, p t 3 P o l y P o i n t s )
myShapeEl em. Redraw DrawMode End Sub P r i v a t e Sub IPrimitiveCommandEvents-Keyin(ByVa1
K e y i n As S t r i n g )
End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s R e s e t ( )
CommandState.StartDefaultCommand End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s S t a r t ( ) End Sub
Take note that the vertices variable is declared as a public variable in the General Declarations area of the class module. This allows it to act as a property of the class module. Sub P1 a c e P o l y ( 1 D i m m y P o l y T e s t A s New P C E - P o l y T e s t
myPolyTest.Verticies
=
8
CommandState.StartPrimitive m y P o l y T e s t End Sub
As we mentioned previously, we make use of the PCE-PolyTest class a little differently than we did to the previous classes. In this example we declare a variable as a New PCE-PolyTest. We need to do this so we can
414
I Chapter 18: Interface Essentials I specify the number of vertices we want drawn before we start the “StartPrimitive” activities. After the class is initiated and the vertices property is set, we use the variable myPolyTest with the “StartPrimitive” method to begin the capture of PrimitiveCommandEvents.
The number of vertices specified is used to calculate the angle used to project each vertex of the regular polygon drawn.
PCE-PointStringTest Each PrimitiveCommandEvent interface example we have used up to this point has been based on the user’s selection of two points. We drew a line between two points. We drew a rectangle using the two points as opposing corners. We drew a circle using two points. We drew a polygon using the two points. The PCE-PointStringTest class allows for selection of more than one point. In fact, there is nothing that prohibits the user from selecting an endless number of points. Implements IPrimitiveCommandEvents D i m p t 3 B a s e P o i n t As P o i n t 3 d D i m p t 3 P o i n t s O As P o i n t 3 d D i m b o o l S e t As Boolean
P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s C l e a n u p O End Sub P r i v a t e Sub
IPrimitiveCommandEvents-DataPoint(Point A s P o i n t 3 d , B y V a l V i e w As V i e w )
If boolSet
=
F a l s e Then
-
I Class Module Lifecycle I
415
pt3BasePoint
=
Point
pt3Points(O)
=
Point
ReDim P r e s e r v e pt3Points(UBound(pt3Points)
+ 1)
CommandState.StartDynamics boolSet
=
True
Else
pt3Points(UBound(pt3Points)) = P o i n t ReDim P r e s e r v e pt3Points(UBound(pt3Points)
+ 1)
End If End Sub P r i v a t e Sub IPrimitiveCommandEvents-Dynamics(Point As P o i n t 3 d . B y V a l V i e w A s V i e w , B y V a l DrawMode A s M s d D r a w i n g M o d e )
D i m m y p o i n t s t r i n g As P o i n t S t r i n g E l e m e n t
pt3Points(UBound(pt3Points)) = P o i n t S e t m y p o i n t s t r i n g = CreatePointStringElementl(
-
Nothing, pt3Points, False) m y p o i n t s t r i n g . Redraw DrawMode End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s K e y i n ( B y V a 1
K e y i n As S t r i n g )
S e l e c t Case UCase( K e y i n ) Case
”
P LC LO S E ”
D i m m y P o i n t S t r i n g As P o i n t S t r i n g E l e m e n t
pt3Points(UBound(pt3Points)) Set mypointstring
=
=
pt3BasePoint
CreatePointStringElementl(Nothing,
~
pt3Points, False)
ActiveModelReference.AddElement
mypointstring
CommandState.StartDefaultCommand End S e l e c t End Sub
IPrimitiveCommandEvents-Reset0 CommandState.StartDefaultCommand
P r i v a t e Sub End Sub
P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s S t a r t ( ) ReDim p t 3 P o i n t s ( 0 ) End Sub
The user is likely to enter more than one or two points when using this example. We could declare a variable to hold up to 10 points or 50 points or 100 points. We could then prompt the user to select “up to 10 points’:
416
I Chapter 18: Interface Essentials I for example. This may work or may be necessary in some circumstances, but when we want any number of points to be allowed, we need a different solution. Declaring the variable pt3Points as a dynamic array (using empty parenthesis when declaring it) allows us to change the size of the array as needed. We change the size with the “Preserve” keyword so VBA changes the size of the array without dumping the existing array elements. Each time a new point is selected, we place the selected point in the upper-bound element of the array and then we immediately increase the array size by one. It is important to increase the array size by one each time a new point is entered because we use the new upper-bound element in the dynamics event. In the previous examples we wanted the user to only select two points. This made it easy for us to exit the class module using “StartDefaultCommand’: We have placed the reset event and it works well. However, we want to allow the user to close the point string and finish the command without having to reset things. How do we do this? One way is to use the Keyin event. P r i v a t e Sub IPrimitiveCommandEventsKeyin(ByVa1
K e y i n As S t r i n g )
S e l e c t Case U C a s e ( K e y i n 1 C ase
”
P LC LDS E ”
D i m m y p o i n t s t r i n g As P o i n t S t r i n g E l e m e n t
pt3Points(UBound(pt3Points)) Set mypointstring
=
=
pt3BasePoint
CreatePointStringElementl(Nothing.
~
pt3Points. False) A c t i veModel Reference.AddE1 ement myPoi n t S t r i n g
CommandState.StartDefaultCommand End S e l e c t End S u b
Here is the code in the Keyin event. If the user enters “plclose”,“PlClose’: “PLClose”,etc., we close the point string by placing the base point in the upper-bound element of the pt3Points variable. We then create a new Pointstring element using the pt3Points variable for the vertices of the point string. We have seen code similar to this but we need to add something we have not done before. In addition to creating the Point
I Class Module Lifecycle I
417
String, we add it to the ActiveModelReference. This makes the Point String a permanent part of the ActiveModelReference.
pIcIose
Multiple points are selected. As the points are selected, we are creating a Pointstring element but we do not add it to the model. We only create it and display it. If at any time the user resets the command, we exit out of the class and the Pointstring disappears. When the user enters “plclose” in the Key-in dialog box and hits
CommandState.StartPrimitive New P C E - P o i n t S t r i n g T e s t , T r u e End S u b
How is this different? We specify that we want to capture key-ins by providing a value of true for the optional parameter “WantKeyins”.The default value of this parameter is false. So, when we want to capture keyins, we must specify a value of true when we use the Startprimitive method.
PCE-Li neTest2 We want to allow the user to select two points. We will then divide the space between the two points into equal length segments and draw
418
I Chapter 18: Interface Essentials I circles at each vertex of these lengths. Let’s begin with the desired interface and then we will discuss the code.
After the first point is selected, we want to draw a rubber-band between the selected point and the cursor location.
After the second point is selected, we draw circles dividing the area between the selected points equally. In this example we specified dividing the space into four equal segments. Here is the code for the class module: Implements IPrimitiveCommandEvents
D i m p t 3 B a s e P o i n t As P o i n t 3 d D i m b o o l S e t As B o o l e a n Pub1 ic 1 n g D i v i s i o n s As L o n g P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s C l e a n u p O End Sub P r i v a t e Sub IPrimitiveCommandEvents-DataPoint(Point B y V a l V i e w As V i e w ) If boolSet
=
F a l s e Then
pt3BasePoin t
=
Point
CommandState.StartDynamics boolSet
=
True
Else
D i m p t 3 E n d P o i n t As P o i n t 3 d D i m d b l L i n e A n g l e As D o u b l e D i m d b l S e g D i s t As D o u b l e D i m D i v P o i n t s O As P o i n t 3 d
As P o i n t 3 d ,
-
I Class Module Lifecycle I
419
D i m I As Long
ReDim D i v P o i n t s ( 0 T o 1 n g D i v i s i o n s pt3EndPoint
=
Point
dblLineAngle
=
Atn((pt3EndPoint.Y
-
(pt3EndPoint.X dblSegDist
=
2 ) As P o i n t 3 d
-
pt3BasePoint.Y)
/
~
pt3BasePoint.X))
-
Point3dDistanceXY(pt3BasePoint.
pt3EndPoint) /
~
lngDivisions F o r I = L B o u n d ( D i v P o i n t s ) To U B o u n d ( D i v P o i n t s ) DivPoints(1)
=
Point3dAddAngleDistance(pt3BasePoint, dblLineAngle, dblSegDist
~
*
(I+
1). 0)
Next I D r a w c i r c l e pt3BasePoint, 0.25 F o r I = L B o u n d ( D i v P o i n t s ) To U B o u n d ( D i v P o i n t s ) D r a w c i r c l e D i v P o i n t s ( 1 ) . 0.25 Next I D r a w c i r c l e pt3EndPoint. 0.25
CommandState.StartDefaultCommand End I f End Sub P r i v a t e Sub D r a w C i r c l e ( C e n P t As P o i n t 3 d , R a d i u s As D o u b l e ) D i m m y E l l i p s e As E l l i p s e E l e m e n t
D i m r o t M a t r i x As M a t r i x 3 d Set rnyEllipse
=
CreateEllipseElementZ(Nothing, CenPt, R a d i u s , R a d i u s , rotMatrix)
ActiveModel Reference.AddElement myEl1 i p s e End Sub P r i v a t e Sub
IPrimitiveCommandEvents-Dynamics(Point As P o i n t 3 d . B y V a l V i e w As V i e w , B y V a l DrawMode As MsdDrawingMode)
D i m m y L i n e E l e m As L i n e E l e m e n t S e t rnyLineElem
=
CreateLineElement2(Nothing, pt3BasePoint. P o i n t )
m y L i n e E l em. Redraw DrawMode End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s K e y i n ( B y V a 1 End Sub
IPrimitiveCommandEvents-Reset0 CommandState.StartDefaultCommand
P r i v a t e Sub End Sub
K e y i n As S t r i n g )
-
420
I Chapter 18: Interface Essentials I P r i v a t e Sub
IPrimitiveCommandEvents-Start0
End Sub
A careful review of the above code reveals a method named “DrawCircle”.We use this each time we want to draw a circle. This keeps the DataPoint event a little cleaner by breaking out a specific and distinct piece of code into its own procedure.
Here are two examples that can be used to work with the PCE-LineTest2 class: Sub P 1 aceLi nePA( ) D i m m y L i n e T e s t 2 As New PCELLineTest.2
myLineTest2.lngDivisions
=
4
Commandstate. S t a r t P r i m i t i v e myLi n e T e s t 2 End Sub
Sub P 1 aceLinePB( ) D i m m y L i n e T e s t 2 As New PCELLineTest.2
myLineTest2.lngDivisions
=
12
Commandstate. S t a r t P r i m i t i v e myLi n e T e s t 2 End Sub
The procedure P1 a c e l i ne2A divides the selected points into four equal segments. P1 a c e l i ne2B divides the selected points into 12 equal segments.
PCE-TestLine3 Our goal up to this point is to learn how to use the IPrimitiveCommandEvents Object. We displayed lines, circles, and polygons as we asked the user to select points. In the most recent example we divided selected points into a specified number of segments and placed circles at the segment points. We are going to expand on the PCELTestLine2 class in the next example. PCELTestLine2 is useful if we want circles drawn at a specific radius at
segment points. If we want to draw squares, we could create a new class, copy and paste the code from PCELTestLine2, then modify the new class to draw squares. We would do the same to draw hexagons. We would create a new class, copy and paste, then modify the code. To draw octagons, we would create a new class, copy and paste, then modify the
I Class Module Lifecycle I
42 1
code. We could create a hundred new class modules, each drawing a different type of element at the division points. Or we could create a new class, copy and paste, then modify the code once so we can use the new class over and over again. PCETestLine3 is based on PCETestLine2, but instead of drawing circles at the division points, we place the points into a variable that the procedure which calls the class can use. Let’s look at some examples of how to use the new class. Then we will look at the class itself. S u b P1 a c e L i ne3A( 1 D i m m y D i v P o i n t s 0 As P o i n t 3 d D i m m y L i n e T e s t 3 A s New P C E L L i n e T e s t 3 D i m I As Long
myLineTest3.1 n g D i v i s i o n s
=
12
CommandState.StartPrimitive m y L i n e T e s t 3 Whi 1 e m y L i n e T e s t 3 . C l a s s C o m p l e t e
=
Fa1 s e
DoEvents Wend myDivPoints For I
=
=
myLineTest3.DivPts
L B o u n d ( m y D i v P 0 i n t s ) To U B o u n d ( m y D i v P o i n t s )
D i m m y E l 1 i p s e As E l 1 i p s e E l e m e n t D i m r o t M a t r i x As M a t r i x 3 d Set m y E l l i p s e
=
C r e a t e E l l i p s e E l e r n e n t Z ( N o t h i n g , rnyDivPoints(1)
0.25,
0.25,
rotMatrix)
ActiveModel Reference.AddElement myEl1 i p s e Next I End S u b
Let’s break this procedure down for discussion.
1 We declare Variables and initiate the PCE-LineTest3 class with the use of the New keyword. D i m m y D i v P o i n t s 0 As P o i n t 3 d D i m m y L i n e T e s t 3 A s New P C E L L i n e T e s t 3
D i m I As Long
2 We set the number of divisions we want by setting the lngDivisions property of the class. myLineTest3.1 n g D i v i s i o n s
=
12
3 We start the PrimitiveCommandEventsobject.
422
I Chapter 18: Interface Essentials I Commandstate. S t a r t P r i m i t i v e myLi n e T e s t 3
4 We look at the ClassComplete property of the PCELLineTest3 class and allow user interaction to continue while the value of the ClassComplete property is false. While
myLineTest3.ClassComplete
=
False
DoEvents Wend
5 We get the points that had been created by the selection of the two points. myDivPoints
=
myLineTest3.DivPts
6 We draw circles at each point in the myDivPoints array with a radius of 0.25. For I
=
L B o u n d ( m y D i v P o i n t s ) To U B o u n d ( m y D i v P 0 i n t s )
D i m m y E l 1 ip s e As E l 1 ip s e E l e m e n t D i m r o t M a t r i x As M a t r i x 3 d Set m y E l l i p s e
=
CreateEllipseElernentZ(Nothing, rnyDivPoints(1).
0.25, 0.25, r o t M a t r i x ) ActiveModel Reference.AddElement myEl1i p s e Next I
That is the code. One of the benefits of making the changes to PCE-LineTest3 is that we are using it to return the division points. After the class returns the points, we can do anything with the points we want to. Here is another example of using PCE-LineTest9 S u b P1 aceLine3B( ) D i m m y D i v P o i n t s 0 As P o i n t 3 d D i m m y L i n e T e s t 3 As New P C E L L i n e T e s t 3 D i m I As L o n g myLineTest3.lngDivisions = 1 6 Commandstate. S t a r t P r i m i t i v e myLi n e T e s t 3 While
myLineTest3.ClassComplete
=
False
DoEvents Wend myDivPoints For I
=
=
myLineTest3.DivPts
L B o u n d ( m y D i v P o i n t s ) To U B o u n d ( m y D i v P 0 i n t s )
-
1
I Class Module Lifecycle I
423
D i m m y L i n e E l e m As L i n e E l e m e n t
Set myLineElern
=
CreateLineElernentZ(Nothing, rnyDivPoints(I),
myDivPoints(1
+ 1))
ActiveModel Reference.AddElement myLineElem Next I End S u b
Follow through the code in P1 a c e l i ne3B. What is it doing with the points returned by the PCE-LineTest3 Class? P1 a c e l i ne3B is drawing lines for each segment identified by the PCE-LineTest3 Class.
Let’s look at one more example: S u b P1 a c e L i ne3C( 1 D i m m y D i v P o i n t s 0 As P o i n t 3 d D i m m y L i n e T e s t 3 As New P C E L L i n e T e s t 3
D i m I As L o n g D i m L i n e B a s e P t As P o i n t 3 d myLineTest3.1 n g D i v i s i o n s
=
16
CommandState.StartPrimitive m y L i n e T e s t 3 Whi 1 e m y L i n e T e s t 3 . C l a s s C o m p l e t e
=
Fa1 s e
DoEvents Wend myDivPoints LineBasePt.X For I
=
=
myLineTest3.DivPts
=
3: LineBasePt.Y
=
4: LineBasePt.Z
=
5
L B o u n d ( m y D i v P o i n t s ) To U B o u n d ( m y D i v P o i n t s )
D i m m y L i n e E l e m As L i n e E l e m e n t
S e t myLineElem
=
CreateLineElement2(Nothing, L i n e B a s e P t ,
-
myDivPoints(1)) ActiveModel Reference.AddElement myLineElem Next I End S u b
What does this code do? Of course, we are using the PCELLineTest3 class. But what are we doing with the returned points?
424
I Chapter 18: Interface Essentials I We draw lines from each segment point to a single base point. In each example where we used the PCE-LineTest3 class, we used a Whi 1 e ... Wend structure to allow the user to select two points. After the two points are selected, the value of “ClassComplete” is no longer false and we make use of the returned points. Each of the examples works well without any modification to the class module. This is the most desirable situation: a class module that can be used in a variety of circumstances without any modifications.
Reviewing the code above shows that the class module has three properties. One is named ‘IngDivisions’, another is named ‘ClassComplete’, and the last one is named ‘DivPts: We have seen examples of how we will use PCE-LineTest3. Let’s take a look at the code behind the class module now. Implements IPrimitiveCommandEvents D i m p t 3 B a s e P o i n t As P o i n t 3 d D i m b o o l S e t As B o o l e a n
P u b l ic 1 n g D i v i s i o n s As L o n g P u b l i c D i v P t s As V a r i a n t P u b l i c C1 assComD1 e t e As Boo1 e a n P r i v a t e Sub IPrimitiveCommandEvents-Cleanup0 End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s D a t a P o i n t ( P o i n t ( P o i n t As P o i n t 3 d , B y V a l V i e w As V i e w ) If boolSet
=
F a l s e Then
pt3BasePoin t
=
Point
CommandState.StartDynamics boolSet
=
True
Else
D i m p t 3 E n d P o i n t As P o i n t 3 d D i m d b l L i n e A n g l e As D o u b l e D i m d b l S e g D i s t As D o u b l e D i m D i v P o i n t s O As P o i n t 3 d D i m I As L o n g
ReDim D i v P o i n t s ( 0 To I n g D i v i s i o n s ) As P o i n t 3 d DivPoints(0)
=
pt3BasePoint
DivPoints(UBound(DivPoints)
=
Point
I Class Module Lifecycle I
425
pt3EndPoint
=
Point
dblLineAngle
=
Atn((pt3EndPoint.Y
-
pt3BasePoint.Y) /
(pt3EndPoint.X dblSegDist
=
-
-
pt3BasePoint.X))
Point3dDistanceXY(pt3BasePoint, p t 3 E n d P o i n t ) /
-
1ngDivisions For I
=
=
+
1 To U B o u n d ( D i v P o i n t s )
-
Point3dAddAngleDistance(pt3BasePoint,
-
LBound(DivPoints)
DivPoints(1)
dblLineAngle, dblSegDist
1
* (I), 0)
Next I DivPts
=
DivPoints
C1 assCompl e t e
=
True
CornmandState.StartDefau1tCommand End I f End Sub P r i v a t e S u b I P r i m i t i v e C o m m a n d E v e n t s D y n a m i c s ( P o i n t As P o i n t 3 d , B y V a l V i e w As V i e w , B y V a l DrawMode As M s d D r a w i n g M o d e ) ~
D i m myLineElem As LineElement S e t myLineElem
=
CreateLineElementZ(Nothing, Point)
pt3BasePoint,
~
m y L i n e E l em. Redraw DrawMode End S u b P r i v a t e Sub IPrimitiveCommandEvents-Keyin(ByVa1
K e y i n As S t r i n g )
End Sub P r i v a t e Sub IPrimitiveCommandEventsReset()
CommandState.StartDefaultCommand End Sub P r i v a t e Sub
IPrimitiveCommandEvents-Start0
End S u b
We have used most of this code before in our previous class PCELLineTest2, but let’s focus on the DataPoint event. Previously, we drew circles. Now we are placing the points into an array. Then we are placing that array into a variable that had been publicly declared as a variant. Declaring a variable as public in a class module allows it to be used like a property of an object. In addition to placing the coordinates into the DivPts variable, we set the ClassCompletevariable to true. This variable, ClassComplete, is read by the procedure that calls
426
I Chapter 18: Interface Essentials I PCELLineTest3 in the Whi 1 e ... Wend structure. Even though we discussed P 1 a c e l i ne3A previously, here is the procedure again: Sub P l a c e L i n e 3 A ( 1
D i m m y D i v P o i n t s 0 As P o i n t 3 d D i m m y L i n e T e s t 3 As New P C E L L i n e T e s t 3 D i m I As L o n g
myLineTest3.lngDivisions
=
12
Commandstate. S t a r t P r i m i t i v e myLi n e T e s t 3 While
myLineTest3.ClassComplete
=
False
DoEvents Wend myDivPoints For I
=
=
myLineTest3.DivPts
L B o u n d ( m y D i v P o i n t s ) To U B o u n d ( m y D i v P o i n t s )
D i m m y E l 1 ip s e As E l 1 ip s e E l e m e n t D i m r o t M a t r i x As M a t r i x 3 d
Set rnyEllipse
=
CreateEllipseElementZ(Nothing, myDivPoints(I), 0.25,
0.25.
-
rotMatrix)
ActiveModel Reference.AddE1 ement myEl1 i p s e Next I End Sub
Once we use Startprimitive, we begin looking at the ClassComplete property and wait until ClassComplete is set to true. When ClassComplete is true, we can get the points from the DivPts property of the class.
Optimizing The Dynamics Event The Dynamics event is very exciting. Each time the mouse moves even the slightest degree, the code within the Dynamics event is executed. What does this mean? Rapid fire VBA code execution! How ‘Rapid Fire’? That’s a good question. One test demonstrated that simply moving the cursor from the left side of a Microstation window to the right side resulted in the Dynamics event executing over 390 times. This implies that you need to be very careful with the code placed in the dynamics event. Simple, fast-executingcode will not cause problems. However, code that attempts complex calculations or performs other time-consuming operations can cause problems. Even though the examples we have
I Class Module Lifecycle I
427
shown in this chapter work well, we may need to minimize processor overhead when working in the dynamics event. P r i v a t e Sub IPrimitiveCommandEvents-Dynamics(Point As P o i n t 3 d . B y V a l V i e w A s V i e w , B y V a l DrawMode A s M s d D r a w i n g M o d e ) D i m myLineElem As LineElement Set myLineElem
=
CreateLineElement2(Nothing, pt3BasePoint. P o i n t )
m y L i n e E l em. Redraw DrawMode End Sub
Each and every time this dynamics event is triggered, we do the following:
1 Declare a variable as a LineElement.
2
Create a LineElement.
3 Redraw the LineElement. Three lines of code are in the event - each line takes up processor time. What we don’t see in the code is that the LineElement goes out of scope when we exit the dynamics event. This takes time because VBA has to dump the memory that had been assigned to the object. Imagine setting aside memory, drawing a line, and then dumping the memory over 390 times just because the cursor moves from the left to right. Let’s compare “PCE-LineTest” with a different implementation of the Dynamics event in “PCE-LineTest4”.
PCE-LineTest Implements IPrimitiveCommandEvents
D i m p t 3 B a s e P o i n t As P o i n t 3 d D i m b o o l S e t As B o o l e a n
P r i v a t e Sub
IPrimitiveCommandEvents-Cleanup0
End Sub P r i v a t e Sub
IPrimitiveCommandEvents-DataPoint(Point
As P o i n t 3 d . ByVal View As View)
If boolSet
=
F a l s e Then
pt3BasePoint
=
Point
CommandState.StartDynamics boolSet
=
True
428
I Chapter 18: Interface Essentials I Else
CommandState.StartDefaultCommand End I f End Sub P r i v a t e Sub
IPrimitiveCommandEvents-Dynamics(Point
As P o i n t 3 d , B y V a l V i e w As V i e w , B y V a l DrawMode As M s d D r a w i n g M o d e )
D i m m y L i n e E l e m As L i n e E l e m e n t Set myLineElem
=
CreateLineElement2(Nothing, pt3BasePoint. P o i n t )
m y L i n e E l e m . Redraw DrawMode End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s K e y i n ( B y V a 1
K e y i n As S t r i n g )
End Sub P r i v a t e Sub
IPrimitiveCommandEvents-Reset0
End Sub P r i v a t e Sub
IPrimitiveCommandEvents-Start0
End Sub
We declare the variable, create the line, redraw it, then terminate it (because it goes out of scope) each time the dynamics event is triggered. Now let’s look at the difference between PCE-LineTest and PCE-LineTest4.
PCE-LineTest4 Implements IPrimitiveCommandEvents D i m p t 3 B a s e P o i n t As P o i n t 3 d D i m b o o l S e t As Boolean D i m m y L i n e E l e m As L i n e E l e m e n t
P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s C l e a n u p O End Sub P r i v a t e Sub IPrirnitiveCommandEventsDataPoint(Point(P0int A s P o i n t 3 d , B y V a l V i e w As V i e w ) ~
If boolSet
=
F a l s e Then
I Class Module Lifecycle I
429
pt3BasePoint
=
Point
Set myLineElem
=
CreateLineElement2(Nothing, Point, Point)
CommandState.StartDynamics boolSet
=
True
Else
CommandState.StartDefau1tCommand End If End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s D y n a m i c s ( P o i n t As P o i n t 3 d , B y V a l V i e w A s V i e w , B y V a l DrawMode A s M s d D r a w i n g M o d e ) ~
m y L i n e E l em. V e r t e x ( 1 )
=
Point
m y L i n e E l em. Redraw DrawMode End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s K e y i n ( B y V a 1
K e y i n As S t r i n g )
End Sub P r i v a t e Sub
IPrimitiveCommandEvents-Reset0
End Sub P r i v a t e Sub I P r i m i t i v e C o m m a n d E v e n t s S t a r t ( ) End Sub
In PCE-LineTest4, we declare the variable for the LineElement in the General Declarations area. We create the line once in the DataPoint event. When we get to the Dynamics event, all we do is change the Endpoint of the line and redraw it. This is a much more efficient way to work with the Dynamics event.
PCE-LineTest and PCE-LineTest4 are simple and small. Although it may not be apparent when using these two classes, PCE-LineTest4 uses significantly less processor time. Minor changes like the one we made for PCE-LineTest4 may not make an immediate dynamic difference in the efficiency of our code, but little efficiencies add up to significant performance benefits. The opposite is true as well. Inefficiencies add up to significant performance degradation.
430
I Chapter 18: Interface Essentials I
REVIEW We implement interfaces through class modules. Each property or method of the interface that we implement must be declared in the class module. When we implement the interfaces discussed in this chapter, we can have greater control, flexibility, and power in our programming as our applicationsbecome more interactive.
19
Using MicroStat ion’s Built-In User Forms One ways to increase your speed in developing applications is to use of existing code, objects, and interfaces. In this Chapter:
Declaring Microstation user form functions The FileOpen dialog The Filecreate dialog The FileCreateFromSeed dialog The OpenAlert dialog The OpenInfoBox dialog
DECLARING MICROSTATION USERFORMFUNCTIONS Access Microstation’s built-in User Form functions by using a DLL (Dynamic Link Library). When functions are wrapped in a DLL, they must be declared before they are used. Make these declarations in the general declarations area of a code module.
43 1
432
I Chapter 19: Using MicroStation'sBuilt-In User Forms I Decl a r e F u n c t i o n mdl D i a1 og-openAl e r t L i b " s t d m d l b l t i n . d l 1 " (ByVal s t r i n g P As S t r i n g ) As Long
Here is the declaration for the OpenAlert dialog box. Let's break up the declaration into its individual parts: Declare Function
TellsVBA we are going to declare a function that is part of a DLL file.
mdlDialog-openAltert
The name of the function we are declaring.
Lib "stdmdlbltin.dll"
The name of the DLL Library the in which the function is contained.
(ByVal stringP as String)
Parametersfor the function. Can be empty 0 or contain one or more parameters.
As Long
The return type of the function.
One of the distinguishing features of a function is that it returns a value. "mdlDialog-openAlert" returns a Long value. Some functions in DLLs need to return more than one value. They do this by changing the values of variables we supply into the function's parameters. Here is the declaration for the FileOpen Function: Decl a r e F u n c t i o n mdl D i a1 o g - f i 1 eOpen L i b " s t d m d l b l t i n . d l 1 " (ByVal
-
-
f i l e N a m e As S t r i n g , ByVal r F i l e H As Long, B y V a l
-
r e s o u r c e I d As Long, ByVal s u g g e s t e d F i 1 eName As S t r i n g , ByVal f i l t e r s t r i n g As S t r i n g ,
-
-
ByVal d e f a u l t D i r e c t o r y As S t r i n g ,
-
ByVal t i t l e s t r i n g As S t r i n g ) As Long
To open a file, we use the FileOpen function. How do we know which file was selected? What if the user clicks the Cancel button? We know which file had been selected through the use of the fileName parameter. The Long return value tells us whether the OK or Cancel button was clicked. Let's look at each of the dialog functions one at a time.
The mdIDia Iog fiIeOpen Funct ion D e c l a r e F u n c t i o n mdlDialog-fileopen " s t d m d l b l t i n . d l 1 " (ByVal -
Lib
-
f i l e N a m e As S t r i n g , ByVal r F i l e H As Long, ByVal r e s o u r c e I d As Long,
-
I Declaring Microstation User Form Functions I ByVal s u g g e s t e d F i 1eName As S t r i n g , ByVal f i l t e r s t r i n g As S t r i n g ,
433 -
-
ByVal d e f a u l t D i r e c t o r y As S t r i n g ,
-
ByVal t i t l e s t r i n g As S t r i n g ) As Long
Our first example prompts the user to select a file. We supply a file extension, a default directory, and a dialog title. S u b T e s t F i 1 eOpenA( 1 D i m s t r F N a m e As S t r i n g D i m l n g f h a n d l e A s Long D i m l n g r i d A s Long D i m r e t V a l A s Long Space(255)
strFName
=
retVal
mdl D i a l o g - f i l e O p e n ( s t r F N a m e ,
=
I, I,
,
" * .d g n
1n g f h a n d l e , 1n g r i d ,
~
, " C : \ M i c r o S t a t io n VBA" , "Open F i 1 e " ) "
~
S e l e c t Case r e t V a l Case 0 'Open strFNarne
=
L e f t ( s t r F N a r n e , I n S t r ( 1 , strFName, C h r ( 0 ) )
MsgBox " F i l e S e l e c t e d : "
& vbCr & strFName
Case 1 ' C a n c e l MsgBox "No F i 1 e S e l e c t e d . End S e l e c t End S u b
"
-
1)
434
I Chapter 19: Using MicroStation’s Built-In User Forms I When the procedure T e s t F i 1 eOpenA is executed, we see the following dialog box:
From this dialog we can see how many of the Procedure Parameters are used. We can see the Title, the Default Directory, the Filter (*.dgn) and we can see that we did not supply a Default File Name because the File Name is blank. Up to this point, the only thing we have done is displayed a dialog box. Now we must ask a few questions. Did the user click the Open button or the Cancel button? S e l e c t Case r e t V a l Case 0 ‘Open Case 1 ‘ C a n c e l End S e l e c t
We look at the return value of the function to see if the Open button was selected (resulting in a return value of 0) or if the Cancel button was selected (returning a value of 1). If the Cancel button was selected, there is little to do because the user Cancelled the operation. In our example we display a MessageBox stating “No File Selected’: If the user clicks the Open button, the next question is “Which file was selected?”
I Declaring Microstation User Form Functions I
435
Use the variable strFName as a parameter when we call the Fi 1 eOpen function. The goal is to fill it with the path and file name that the user selected. When a variable is declared as a string, it becomes an empty string. It is a variable that can contain letters, numbers, and other characters, but it is empty and it remains empty until we fill it with a string. Before supplying the Fi 1 eOpen function (and others like it), we fill the variable we are using with spaces. This allows the function to populate the variable and tell us the path and file name. strFName
=
Space(255)
The Space function fills the variable with the number of spaces specified. If we provide a space-buffered variable to the OpenFi 1 e function, the variable will be filled with the fully qualified path of the file selected. Supplying a variable with 255 spaces in it returns a variable with 255 characters, even if the path and file name are only 20 characters in length. Using the Left function, we get everything to the left of the first Null Character (ASCII Character of 0). strFName
=
L e f t ( s t r F N a m e , I n S t r ( 1 , strFName, C h r ( 0 ) )
-
1)
MsgBox " F i l e S e l e c t e d : " & v b C r & s t r F N a m e
After running the procedure
TestFi 1 eOpenA and selecting a file to open we see the MessageBox:
We can see that we are generating a File Open dialog box. The user selects a file and clicks "Open" but the dialog box does not open the file. It only tells us which file was selected. It is up to us to open the selected file or perform some other operation on it. In our first examples we will only display the file name in a MessageBox. Here is a slight variation on Test Fi 1 eOpen A. Only one change has been made: Sub T e s t F i 1 eOpenB( 1 D i m strFName As S t r i n g D i m l n g f h a n d l e As Long
D i m l n g r i d As Long D i m r e t V a l As Long
436
I Chapter 19: Using MicroStation'sBuilt-In User Forms I strFName
=
retVal
mdl D i a l o g - f i 1 e O p e n ( s t r F N a r n e , 1 n g f h a n d l e , 1 n g r i d ,
"
=
Space(255)
t e st 4 . dgn " ,
" *.d g n
"
-
, " C : \ M i c r oS t a t io n V B A " , "Open F i 1 e " )
S e l e c t Case r e t V a l Case 0 ' O p e n strFName
=
L e f t ( s t r F N a m e , I n S t r ( 1 , strFNarne, C h r ( 0 ) )
MsgBox " F i 1 e S e l e c t e d :
"
-
1)
& vbCr & strFName
Case 1 ' C a n c e l MsgBox "No F i 1 e S e l e c t e d .
"
End S e l e c t End Sub
In this example, we are supplying a default file name of test4.dgn. The dialog box is shown with this default file name in it.
The File Open dialog box is used to allow the user to select existing files. In TestFileOpenB, we specify a default file name of test4.dgn. This file does not exist in the folder shown above. If the user clicks "Open': we see this dialog box: The File Open dialog box does not allow anyone to "Open" a file that does not exist. So, if we get a return value of 0, we can be sure that the file name returned exists.
I Declaring MicroStation User Form Functions I
437
Our previous example used a file extension, also called a file filter, of "*.dgn? Microstation understands that this file extension is a "MicroStation DGN File" and shows this in the "Files of type" combo box. TestFileOpenC uses a file filter of "*.xis': This displays Microsoft Excel files in the dialog box. Sub T e s t F i 1 eOpenC( 1 D i m strFName As S t r i n g D i m l n g f h a n d l e A s Long
D i m l n g r i d As Long D i m r e t V a l As Long strFName retVal
=
=
Space(255)
mdlDialog-fileOpen(strFName,
" *.x l s
"
lngfhandle, lngrid, "", , " C : \ M i c r o S t a t ion VBA" , "Open F i 1 e " )
S e l e c t Case r e t V a l Case 0 'Open strFName
=
L e f t ( s t r F N a r n e , I n S t r ( 1 . strFNarne, C h r ( 0 ) )
MsgBox " F i l e S e l e c t e d : "
~
1)
& vbCr & strFName
Case 1 ' C a n c e l MsgBox " N o F i 1 e S e l e c t e d .
"
'User h i t t h e Cancel B u t t o n End S e l e c t End Sub
Now, instead of displaying Microstation DGN files, Microsoft Excel (XLS files) display.
438
I Chapter 19: Using MicroStation'sBuilt-In User Forms I
Let's look at another example that displays more than one type of file. Sub T e s t F i 1 eOpenD( ) D i m s t r F N a m e As S t r i n g
D i m l n g f h a n d l e As L o n g D i m l n g r i d As L o n g
D i m r e t V a l As L o n g strFName retVal "
=
=
Space(255)
mdl D i a l og-fileOpen(strFName,
* .x 1 s ; * .md b ; * . d b f
"
,
"
1 n g f h a n d l e , l n g r i d , " " ,C : \ M i c r o St a t ion V B A " , "Open F i1 e " )
S e l e c t Case r e t V a l Case 0 ' O p e n strFName
=
L e f t ( s t r F N a m e , I n S t r ( 1 , strFName, C h r ( 0 ) )
MsgBox " F i 1 e S e l e c t e d :
"
-
1)
& vbCr & strFName
Case 1 ' C a n c e l MsgBox "No F i 1 e S e l e c t e d .
"
' U s e r h i t t h e Cancel B u t t o n End S e l e c t End Sub
Now, in addition to Microsoft Excel files, we are allowing for the selection of Access Databases and .dbf database files. The last FileOpen example we will work with not only allows for the selection of a DGN file but opens the file in memory (not in the Microstation window) for program use.
I Declaring Microstation User Form Functions I
439
S u b TestFi 1 eOpenE( 1 D i m s t r F N a m e As S t r i n g D i m l n g f h a n d l e As L o n g D i m l n g r i d As L o n g D i m r e t V a l As Long Space(255)
strFName
=
retVal
mdl D i a l o g - f i l e O p e n ( s t r F N a m e ,
=
I, I,
, "*.dgn",
1n g f h a n d l e , 1n g r i d ,
"C:\MicroStation
VBA",
-
"Open F i l e " )
S e l e c t Case r e t V a l Case 0 ' O p e n strFNarne
=
L e f t ( s t r F N a r n e , I n S t r ( 1 , strFName, C h r ( 0 ) )
-
1)
D i m myDesFile As D e s i g n F i l e Set myDesFile
=
OpenDesignFileForProgram(strFNarne, True)
MsgBox " D o s o m e t h i n g w i t h t h i s f i l e . " myDesFi l e . C 1 o s e Case 1 ' C a n c e l MsgBox "No F i 1 e S e l e c t e d .
"
End S e l e c t End S u b
After the file is opened "ForProgram", we display a MessageBox and then close the file.
The mdlDialogfileCreate Function Just as mdlDialog-fdeOpen does not actually open an existing file, mdlDialogfileCreate does not actually create a new file. It only tells us the file name the user selected or entered. In the event the file already exists, the user is prompted to overwrite the file before we are returned the file name and return value. Here is the declaration for Filecreate. It looks and works very much like the FileOpen function. D e c l a r e F u n c t i o n mdlDialog-filecreate L i b "
s t dmd 1 b 1 t i n .d 1 1 " ( By V a 1
-
f i l e N a m e As S t r i n g , B y V a l r F i l e H As Long, ByVal r e s o u r c e I d As Long,
-
ByVal s u g g e s t e d F i 1eName As S t r i n g , ByVal f i l t e r s t r i n g As S t r i n g ,
-
-
ByVal d e f a u l t D i r e c t o r y As S t r i n g ,
-
-
440
I Chapter 1 9 Using MicroStation's Built-In User Forms I B y V a l t i t l e s t r i n g As S t r i n g ) As Long
-
Let's take a look on how to use the function. Sub T e s t F i 1 e C r e a t e A ( )
D i m s t r F N a m e As S t r i n g D i m l n g f h a n d l e As L o n g D i m l n g r i d As L o n g
D i m r e t V a l As L o n g Space(255)
strFName
=
retVal
mdlDialog-fileCreate(strFName, l n g f h a n d l e , l n g r i d ,
=
11 11
,
" *.d g n
"
,
"
C : \ M i c r o S t a t io n V B A " , " C r e a t e F i 1 e A " 1
S e l e c t Case r e t V a l Case 0 ' O p e n strFName
=
L e f t ( s t r F N a m e , I n S t r ( 1 , strFName, C h r ( 0 ) )
MsgBox " F i 1 e S e l e c t e d :
"
~
1)
& vbCr & strFName
Case 1 ' C a n c e l MsgBox "No F i 1 e S e l e c t e d .
"
' U s e r h i t t h e Cancel B u t t o n End S e l e c t End Sub
The Filecreate dialog has the same look and feel as the FileOpen dialog box.
I Declaring Microstation User Form Functions I
441
The return values of the Create File dialog box work the same as with the File Open dialog box. If an existing file is selected, the user is asked if the file should be overwritten. If a file exists, the only way we are returned the file name and a return value of 0 is if the user clicks "Yes" to over-write the existing file. Entering additional file extensions in the File Filter parameter will display the files associated with the entered extensions.
The mdlDialogfileCreateFromSeed Function When we use the standard Microstation File > New dialog box, we are given the ability to select a seed file. We can provide the same functionality by using mdlDialog-fileCreateFromSeed Function. Decl a r e F u n c t i o n mdl D i a l og-fi 1 eCreateFromSeed L i b "stdmdlbltin.dl1"
(ByVal f i l e N a m e As S t r i n g ,
-
-
ByVal r F i l e H As Long, B y V a l r e s o u r c e I d As Long, ByVal s u g g e s t e d F i 1eName As S t r i n g , ByVal f i l t e r s t r i n g As S t r i n g ,
-
-
ByVal d e f a u l t D i r e c t o r y As S t r i n g ,
-
ByVal t i t l e s t r i n g As S t r i n g , ByVal s e e d F i l e As S t r i n g ,
-
ByVal s e e d D i r e c t o r y As S t r i n g ,
-
ByVal s e e d F i l t e r As S t r i n g ) As Long
Let's test the CreateFromSeed function now S u b T e s t F i 1 eCreateFromSeedA() D i m strFName As S t r i n g D i m l n g f h a n d l e A s Long D i m l n g r i d A s Long D i m r e t V a l A s Long
D i m s t r S e e d F i l e As S t r i n g D i m s t r S e e d D i r As S t r i n g
D i m s t r S e e d F i l t e r As S t r i n g strFName
=
strSeedFile
Space(255) =
"seed2d.dgn"
-
442
I Chapter 19: Using MicroStation'sBuilt-In User Forms I strSeedDir
=
"C:\Documents
-
and S e t t i n g s \ A l l
Users\Appl i c a t i o n
"
&
-
"Data\Documents\Bentley\Workspace\System\Seed\" strSeedFilter retVal
=
=
"*.dgn"
mdlDialogpfileCreateFromSeed(strFName, l n g f h a n d l e , " " , " *. d g n " , " C : \ M i c r o S t a t io n VBA" , 1 n g r id , " C r e a t e F i 1e f r o m S e e d " , s t r S e e d F i 1e , s t r S e e d D i r , strSeedFi1t e r )
S e l e c t Case r e t V a l Case
0 'Open strFName
=
L e f t ( s t r F N a m e , I n S t r ( 1 , strFName, C h r ( 0 ) )
MsgBox " F i 1 e S e l e c t e d :
"
-
1)
& vbCr & strFName
Case 1 ' C a n c e l MsgBox "No F i 1 e S e l e c t e d .
"
'User h i t t h e Cancel B u t t o n End S e l e c t End Sub
Now, in addition to allowing for the selection of a file to create, a seed file selection button is shown in the dialog.
I Declaring Microstation User Form Functions I
443
The mdlDialog-openAlert Function The mdlDialogopenAlert function generates a standard Microstation dialog box which allows the user to select "OK" or "Cancel? Decl a r e F u n c t i o n mdl Dial og-openAl ert L i b " stdmdl bl t i n .d l 1 " (ByVal s t r i n g P As S t r i n g ) As Long
One parameter specifies what to display in the box and the return value tells us whether the OK button or the Cancel button was clicked. S u b TestOpenAl ertA( ) D i m r e t V a l As Long retVal
=
mdlDialog-openAlert("Standard
M e s s a g e Box")
S e l e c t Case r e t V a l Case 3 ' O K
MsgBox " U s e r c l i c k e d ' O K " ' Case 4 ' C a n c e l
MsgBox " U s e r c l i c k e d ' C a n c e l
"'
End S e l e c t End S u b
The mdlDialog-openlnfoBox Function The Information dialog box provides Information and so has no need of a Cancel button. The only button shown is the OK button. It is useful, however, because it follows Microstation's native interface more closely than a MessageBox. D e c l a r e F u n c t i o n mdlDialog-openInfoBox L i b " s t d m d l b l t i n . d l 1 " (ByVal
-
s t r i n g P As S t r i n g ) As Long
Sub TestOpenInfoBoxO D i m r e t V a l As Long
444
I Chapter 19: Using MicroStation'sBuilt-In User Forms I retVal
=
mdlDialog-openInfoBox("This i s a t e s t . " )
S e l e c t Case r e t V a l Case 3 ' O K MsgBox " U s e r c l i c k e d 'OK"' End S e l e c t End S u b
REVIEW After declaring the functions that display standard Microstation dialog boxes, using them is simple. There are other ways to display File Open, File Create-type dialog boxes (such as using the Windows API) but using the standard Microstation dialog boxes is the preferred method when developing in VBA and is so easy to implement.
.
20
Class Modules Class modules have a variety of purposes with these primary beneficial uses: To encapsulate similar functionality into a single object.
To create an object with properties, methods, and events. To create class modules specificallyfor custom collections. Each benefit will be the focus of a section in this chapter. The code we write will target the use of the Microstation Built-in dialog boxes and other Microstation-specificobjects and functionality. In this Chapter:
Encapsulating similar functionality El Creating objects with properties, methods, and events
Using class modules with collections
445
446
I Chapter 20: Class Modules I ENCAPSULATINGSIMILAR FUNCTIONALITY We can design classes with the intent to encapsulate similar functionality in a single class or object so we can reuse our code, which is faster than rewriting code. Let’s begin with a class named clsUStationDialogthat will be used to display the dialog boxes used in the chapter for FileOpen and Filecreate. Before we begin looking at the code, let’s identify what we want this class to do. Display File Open Dialog for Microstation DGN files. Display File Open Dialog for Microsoft Excel files. Display File Open Dialog for ASCII .txt files. Display File Open Dialog for custom file extensions. Property needed for File Name. Property needed for Path Only. Property needed for Path / File Name. Property needed for Size of selected File. Registry Entries to be used to store most recent path. Registry Entries to be used to store most recent file. You have accomplished each of the desired tasks already in this book. The focus of this exercise will be to wrap it all into a single class. We want to display.dgn files, .xls files, and .txt files. We also want to display multiple custom file extensions in the dialog box. We could have an “OpenDGN” method, an “OpenXLS” method, an “OpenTXT” method, and an “OpenCustom” method. The main difference between these methods would be the file extension(s) supplied. So, instead of creating new methods for each file type we may want to browse, we will work with one method, named OpenDi a1 og, that will handle any number of file extensions. Let’s begin by working with the file extensions. There are usually multiple ways to accomplish the same task when working with VBA. We will use a dynamic array in our class to store the desired file extensions. We need to allow the user (in this case, it is us as developers) to add file extensions and clear the file extension list.
I Encapsulating Similar Functionality I
447
'General Declarations
D i m p F i l e E x t s 0 As S t r i n g P r i v a t e Sub C l a s s - I n i t i a l i z e ( ) ReDim p F i l e E x t s ( 0 ) End Sub P u b l i c Sub C l e a r F i l e E x t s O ReDim p F i l e E x t s ( 0 ) End Sub P u b l i c Sub A d d F i l e E x t ( F i 1 e E x t A s S t r i n g ) D i m I As Long
D i m t m p F i l e E x t As S t r i n g t m p F i1 e Ex t
For I
=
=
LC a s e ( Rep 1 a c e ( F i1 e Ex t ,
"
.
"
,
" "
1 To U B o u n d ( p F i 1 e E x t s )
If tmpFileExt E x i t Sub End I f Next I
=
p F i l e E x t s ( 1 ) Then
ReDim P r e s e r v e pFileExts(UBound(pFi1eExts) pFileExts(UBound(pFi1eExts)) = t m p F i l e E x t
+ 1)
End Sub
We declare the variable pFileExts as a dynamic array in the General Declarations area of the class module. When the class is initialized, we redeclare pFileExts with an upper-bound of zero (0). If we use the C1 e a r F i 1 e E x t s method, we redeclare pFileExts to an upper-bound of zero. This clears the list of file extensions because we are always going to leave the first element in the array (index of 0) an empty string. When you attempt to add a file extension, first look at the existing extensions to see if it already exists. If the extension that is being added already exists, exit the procedure doing nothing to the file extension list. If the file extension did not exist, increase the size of your array and place the file extension in the upper-bound element of the array.
448
I Chapter 20: Class Modules I You could write all of the code for this class and then try it out after all of the code has been entered, but it is better to write smaller chunks of code and test them before continuing. Here is the code to test the file extension functionality. It is placed in a code module. S u b T e s t F i 1e E x t s ( 1 D i m MyUSD As New c l s U S t a t i o n D i a l o g My U S D .Add F i 1 e E x t
"
My U S D .Add F i 1 e E x t
"
. dgn . DWg
MyUSD.AddFileExt
"Xls"
" "
MyUSD.ClearFi 1 e E x t s End S u b
As we step through the code, you can see the effect of adding file extensions by adding a watch to the variable MyUSD.
pFileExts(0)
""
pFileExts(1) "dgn" pFileExts(2) "dwg" pFileExts(3) "XIS"
String String String String
The first element in the array is always an empty string. As you add file extensions, remove the period character and add it as lowercase. When you call the C1 ear F i 1 eExts method, remove all elements except for the first element by redeclaring the variable pFileExts with an upperbound index of zero (0). You are able to add file extensions to our class module now. You can also clear the list. Give yourself the ability to discover what and how many file extensions have been added by adding two properties to the class module. The first property is "ExtCount" which tells how many file extensions have been added to the class; the other is "GetExts" which returns an array of all file extensions added to the class. In the past, we created properties for our class modules by declaring a variable as public. This works but there is a better way to work with properties.
I Encapsulating Similar Functionality I
449
The ExtCount property tells how many file extensions have been added to our class. If you declare a variable named ExtCount as public, you will be able to read and write to the variable. This is not good because the property’s value should be based on the actual number of extensions that have been added. You do not want to be able to write to the property since it should be read-only. P r o p e r t y G e t ExtCountO A s Long ExtCount
=
UBound(pFi1eExts)
End P r o p e r t y
Now we are using true property code, because the property “ExtCount” is based entirely on the number of file extensions we added to our class. Specify the Read/Write capabilities of a property using Let and Get statements. If you have a “Property Get” statement without an associated “Property Let” statement, the property is read-only. If you have a “Property Let” statement but do not supply an associated “Property Get” statement, you are creating a write-only property. Write-only properties are uncommon but can be used for storing confidential information such as a password. You may want to be able to write to the property so the class can use it but do not want to be able to read the property. And, when you supply a “Property Get” as well as a “Property Let”,you create a read/write property. Now get the file extensions with the “GetExts” property. This property will be read-only, so use a “Property Get” statement. P r o p e r t y Get G e t E x t s O As S t r i n g 0
I f UBound(pFi1eExts)
=
0 Then
E x i t Property End I f D i m t m p G e t E x t s 0 As S t r i n g
ReDim
tmpGetExts(UBound(pFi1eExts)
-
D i m I As Long For I
=
1 To U B o u n d ( p F i 1 e E x t s )
tmpGetExts(1
-
1)
Next I GetExts End P r o p e r t y
=
tmpGetExts
=
pFileExts(1)
1) As S t r i n g
450
I Chapter 20: Class Modules I First check to see if any file extensions have been added. If the Upperbound element of the array pFileExts is zero (0), nothing has been added so immediately exit the property. Otherwise, create a new temporary dynamic array to hold the file extensions that have been added. Since the first element in the array pFileExts is empty, loop through pFileExts elements beginning with the second element (an index of 1) and loop to the upper-bound element in the array. After you populate your temporary dynamic array, set the values into the property "GetExts" which is returned to the code asking for the property. Here is the code that asks for the GetExts property: Sub T e s t G e t E x t s O D i m MyUSD As New c l s U S t a t i o n D i a l o g D i m F i l e E x t s O As S t r i n g My U S D .Add F i 1 e E x t
"
MyUSD.AddFileExt
"DGN"
. dgn . DWg
My U S D .Add F i 1 e E x t
"
MyUSD.AddFileExt
"Xls"
FileExts
=
"
"
MyUSD.GetExts
End Sub
Notice how we are attempting to add the .dgn file extension twice. If the AddFileExt method is working properly, you see only one dgn extension. Here is a view of a Watch added t o the variable "FileExts".
Three unique file extensions were added and they are properly retrieved by the GetExts property. It is now time to allow the user to set and get the default directory for the File Open dialog box. Make this property read/write using "Property Let" and "Property Get': Declare a variable named pDefFilePath in the General Declarations area of your class.
I Encapsulating Similar Functionality I
451
P r i v a t e p D e f F i l e P a t h As S t r i n g
This variable will be used to store the default path. Begin with the Property Get statement. P r o p e r t y Get D e f a u l t p a t h o As S t r i n g Defaultpath
=
pDefFilePath
End P r o p e r t y
This is easy enough. Place the value stored in the variable pDefFilePath into the property ‘‘Defaultpath‘: Let’s take a look at the “Property Let” statement now for the Defaultpath property. P r o p e r t y L e t D e f a u l t P a t h ( s t r P a t h 1 n As S t r i n g ) pDefFilePath
=
strPathIn
End P r o p e r t y
Here is the Let statement. Take the value supplied to us and place it into the Private variable pDefFilePath. The Let and Get statements work just fine. Let’s try it out. This next code should be placed in a code module. Sub T e s t F i 1 e P a t h A ( 1 D i m MyUSD A s New c l s U S t a t i o n D i a l o g
MyUSD.Defau1 t P a t h
=
“abc:\/?test”
End Sub
If we run the code, “abc:\/?test”is set as the default path in our class. The code worked exactly as designed. It took the value supplied and plugged it in. So, if the code worked, we are in good shape. Right? Wrong. Is “abc:\/?test”a legitimate path? At the time of the printing of this book, it is not. So, what are we to do? When a property is readkit e , we could get away with declaring a variable as public in the class. This allows us to read from and write to the variable, making it behave like a property. But the properties of our objects (classes) must be more than variables we can read from and write to. Before any property is truly implemented, consider whether you need to validate the supplied data. In this example, we need to make sure the path exists. There are several ways to do this. Here is one way.
452
I Chapter 20: Class Modules I Modify your "Property Let Defaultpath" statement. P r o p e r t y L e t D e f a u l t P a t h ( s t r P a t h 1 n As S t r i n g ) I f Dir(strPathIn, vbDirectory) pDefFilePath
=
<>
" "
Then
strPathIn
End I f End P r o p e r t y
Now, instead of blindly applying whatever path we are given, we check to see if it is a valid directory. If it is, apply it to the variable pDefFilePath. If it is not a legitimate path, do nothing. Let's run T e s t F i 1 ePathA and see what happens. We get an error, not because the path does not exist, but because the supplied path is not a potentially legitimate path. Let's create a new testing procedure to verify. Sub T e s t F i 1 e P a t h B ( ) D i m MyUSD As New c l s U S t a t i o n D i a l o g MyUS D . De f a u 1 t Pa t h = " c : \ t e s t 5432 1"
End Sub
Now, even if the path "c:\test54321" does not exist, it is a path that could be created because it meets the rules for drive letter and folder name. So, even though we expect that we will feed our class legitimate paths, we should handle error 52 just in case. P r o p e r t y L e t D e f a u l t P a t h ( s t r P a t h 1 n As S t r i n g ) On E r r o r GoTo e r r h n d I f Dir(strPathIn, vbDirectory) pDefFilePath
=
<>
" "
strPathIn
End I f E x i t Property errhnd: S e l e c t Case E r r . N u m b e r Case 5 2 ' B a d f i l e name o r number Err.Cl e a r
End S e l e c t End P r o p e r t y
Then
I Encapsulating Similar Functionality I
453
Now, even if we supply an illegitimate path, the program will not crash. Let's implement the "DefaultFileName" Property. Property Get D e f a u l t F i l e O A s String Defaul tFi 1 e = pDef Fi 1 eName End Property Property Let DefaultFile(strFile1n A s String) pDefFileName = strFileIn End Property
Let's review what we have accomplished thus far. We have taken care of the file extensions. We can set the default path. We can also set the default file. This is all we need to do to begin work on displaying the FileOpen dialog box. We need to declare the function "mdlDialogfileOpen" in the General Declarations area of the class module as follows: Private Declare Function mdlDialog-fileopen Lib "stdmdlbltin.dl1" (ByVal fileName A s String, ByVal rFileH A s Long, ByVal resourceId A s Long, ByVal suggestedFileName A s String, ~
~
~
ByVal f i l t e r s t r i n g A s S t r i n g , ByVal d e f a u l t D i r e c t o r y A s S t r i n g , -
ByVal titlestring A s String) A s Long
Now that the function is declared, we can use it. Sub O p e n D i a l o g O Dim tmpFilter A s String pRetVal = 1 tmpFilter = " * . " & Join(GetExts, " ; * . " ) pFileNameSelected = Space(255) pRetVal = mdlDialog~file0pen(pFileNameSelected, 0, 0, pDefFileName, tmpfilter, pDefFilePath, "Open File") Select Case pRetVal Case 1 'Cancel Case 0 'Open Dim tmpFile A s String Dim xSplit A s Variant tmpFile = Left(pFileNameSelected, InStr(1, DFileNameSelected, Chr(0)) - 1) ~
454
I Chapter 20: Class Modules I x S p l it
=
"\"I
S p l it ( t m p F i 1 e ,
pFileName
=
xSplit(UBound(xSp1it))
x S p l it ( UBound ( x S p l it p F i 1e P a t h
=
=
" "
J o i n ( x S p l it , " \ "
End S e l e c t End S u b
We need a couple of additional properties for the class: P r o p e r t y G e t S e l e c t e d P a t h O As S t r i n g SelectedPath
=
pFilePath
End P r o p e r t y
As S t r i n g
P r o p e r t y G e t S e l e c t e d F i 1e ( S e l e c t e d F i 1e
=
p F i 1 eName
End P r o p e r t y P r o p e r t y G e t O p e n S u c c e s s O As B o o l e a n S e l e c t Case p R e t V a l Case 1 ' C a n c e l OpenSuccess
=
False
=
True
Case 0 'Open OpenSuccess End S e l e c t End P r o p e r t y
We discussed a great deal of code so far with this class. Let's take a look at the code in its entirety just to make sure we haven't missed anything. 'General Declarations P r i v a t e D e c l a r e F u n c t i o n rndl D i a l o g - f i l e o p e n
L i b "stdrndl b l t i n . d l 1 "
f i l e N a m e A s S t r i n g , B y V a l r F i l e H As L o n g , B y V a l
(ByVal
~
r e s o u r c e I d As L o n g , B y V a l s u g g e s t e d F i l e N a m e A s S t r i n g , ByVal f i l t e r s t r i n g As S t r i n g , ByVal d e f a u l t D i r e c t o r y As S t r i n g ,
B y V a l t i t l e s t r i n g As S t r i n g ) As L o n g P r i v a t e p F i l e P a t h As S t r i n g P r i v a t e p F i l e N a m e As S t r i n g P r i v a t e p D e f F i l e P a t h As S t r i n g P r i v a t e p D e f F i l e N a m e As S t r i n g P r i v a t e P F i l e N a m e S e l e c t e d As S t r i n g P r i v a t e p R e t V a l As L o n g
-
I Encapsulating Similar Functionality I
455
Private p F i l e E x t s 0 A s String Property Get S e l e c t e d P a t h O A s String SelectedPath = pFilePath End Property Property Get S e l e c t e d F i l e O A s String Sel ectedFi 1 e = pFi 1 eName End Property Property Get O p e n S u c c e s s O A s Boolean Select Case pRetVal Case 1 'Cancel OpenSuccess = False Case 0 'Open OpenSuccess = True End Select End Property Sub O p e n D i a l o g O Dim tmpFilter A s String pRetVal = 1 tmpFilter = " * . " & Join(GetExts, " ; * . " ) pFileNameSelected = Space(255) pRetVal = mdlOialog~file0pen(pFileNameSelected, 0, 0, pDefFileName, tmpfilter, pOefFilePath, "Open File") Select Case pRetVal Case 1 'Cancel Case 0 'Open Dim tmpFile A s String Dim xSplit A s Variant tmpFile = Left(pFileNameSelected, InStr(1, pFileNameSelected, Chr(0)) - 1) xSplit = Split(tmpFile, " \ " ) pFileName = xSplit(UBound(xSp1it)) xSplit(UBound(xSp1it)) = p Fi 1 e Pa t h = J o i n ( xSpl i t , \ ) End Select End Sub " " "
"
456
I Chapter 20: Class Modules I Property Get D e f a u l t F i l e O As String DefaultFile = pDefFileName End Property Property Let DefaultFile(strFi1eIn As String) pDefFileName = strFileIn End Property Property Get D e f a u l t P a t h O As String Defaultpath = pDefFilePath End Property Property Let DefaultPath(strPath1n As String) On Error GoTo errhnd If Dir(strPathIn, vbDi rectory) <> Then pDefFilePath = strPathIn End If Exit Property errhnd: Select Case Err.Number Case 52 'Bad file name or number Err.Cl ear End Select End Property "I'
Property Get E x t C o u n t O As Long ExtCount = UBound(pFi1eExts) End Property Property Get G e t E x t s O As S t r i n g 0 If UBound(pFi1eExts) = 0 Then Exit Property End If Dim t m p G e t E x t s 0 As String ReDim tmpGetExts(UBound(pFi1eExts) - 1) A s String Dim I A s Long For I = 1 T o UBound(pFi1eExts) tmpGetExts(1 - 1) = pFileExts(1) Next I GetExts = tmpGetExts End Property
I Encapsulating Similar Functionality I
457
P r i v a t e Sub C l a s s - I n i t i a l i z e ( ) ReDim p F i l e E x t s ( 0 ) End Sub P u b l i c Sub C l e a r F i l e E x t s O ReDim p F i l e E x t s ( 0 ) End Sub P u b l i c Sub A d d F i l e E x t ( F i 1 e E x t As S t r i n g )
D i m I As Long D i m t m p F i l e E x t As S t r i n g t m p F i1 e Ex t
=
LC a s e ( Rep 1 a c e ( F i1 e Ex t ,
"
.
"
,
" "
F o r I = 1 To U B o u n d ( p F i 1 e E x t s )
If t m p F i l e E x t
=
p F i l e E x t s ( 1 ) Then
E x i t Sub End I f Next I ReDim P r e s e r v e pFileExts(UBound(pFi1eExts) pFileExts(UBound(pFi1eExts)) = t m p F i l e E x t
+ 1)
End Sub
The code that uses this class module is in TestShowDialogA. Sub T e s t S h o w D i a l o g A ( ) D i m MyUSD As New c l s U S t a t i o n D i a l o g
MyUSD.AddFileExt "dgn" MyUSD.Defau1tPath MyUSD. D e f a u l t F i 1 e
= =
"c:\" " t e s t .dgn"
MyUSD.OpenDialog S e l e c t Case MyUSD.OpenSuccess Case T r u e MsgBox M y U S D . S e l e c t e d P a t h & M y U S D . S e l e c t e d F i l e End S e l e c t End Sub
Let's try a variation on T e s t S h o w D i a1 ogA just to make sure everything is working properly. Sub TestShowDi a1 ogB( 1
D i m MyUSD As New c l s U S t a t i o n D i a l o g MyUSD.AddFileExt "dgn"
458
I Chapter 20: Class Modules I MyUSD.AddFileExt "dwg" My U S D .Ad d F i1 e Ex t
"
dx f "
MyUSD.Defau1 t P a t h
=
" c : \ M i c r o S t a t i o n VBA"
MyUSD . D e f a u l t F i 1 e
=
"
t e s t .d g n "
My U S D . Open D i a 1 o g
S e l e c t Case MyUSD.OpenSuccess Case T r u e MsgBox M y U S D . S e l e c t e d P a t h & M y U S D . S e l e c t e d F i 1 e End S e l e c t End S u b
Each file extension added to the class displays in the FileOpen dialog box.
Our focus in this section is grouping specific types of functionality into a single class module. We already added functionality for Fileopen, now let's add Filecreate functionality. First declare the Function md 1 D ia 1 o g-f i1 eC r ea t e in the General Declarations area of the class module. Private Declare Function mdlDialog-filecreate Lib " s tdmd 1 b 1 ti n . d 1 1 " ( By V a 1 fileName A s String, ByVal rFileH As Long, ByVal resourceId As Long, ByVal suggestedFileName A s String, -
-
I Encapsulating Similar Functionality I
459
ByVal f i l t e r s t r i n g As S t r i n g ,
-
ByVal d e f a u l t D i r e c t o r y A s S t r i n g ,
-
ByVal t i t l e s t r i n g As S t r i n g ) As Long
Next, create a new method in the class module using variables and properties that have already been declared. Sub CreateDialog() D i m t m p F i l t e r As S t r i n g pRetVal
=
tmpFilter
1 =
"*."
& Join(GetExts,
pFileNameSelected pRetVal
=
=
"; *.")
Space(255)
mdlDialogpfileCreate(pFileNameSelected, 0 , 0,
pDefFileName, t m p f i l t e r , p D e f F i l e P a t h , "Create F i l e " ) S e l e c t Case p R e t V a l Case 1 ' C a n c e l Case 0 'Open D i m t m p F i l e As S t r i n g
D i m x S p l i t As V a r i a n t tmpFile
Left(pFileNameSelected, I n S t r ( 1 ,
=
pFileNameSelected, Chr(0)) xSplit
=
Split(tmpFile,
pFileName
=
=
1)
xSplit(UBound(xSp1it))
xSplit(UBound(xSp1it)) p F i 1 e Pa t h
-
"\")
=
J o in ( x S p l it ,
" " "
\")
End S e l e c t End S u b
It is now time to test the CreateDialog method of our class. S u b TestShowDi a1 ogC( 1 D i m MyUSD As New c l s U S t a t i o n D i a l o g MyUSD.AddFileExt "dgn" MyUSD.DefaultPath
=
"c:\"
MyUSD . D e f a u l t F i 1 e
=
" t e s t .d g n "
MyUSD.CreateDialog S e l e c t Case MyUSD.OpenSuccess Case T r u e MsgBox M y U S D . S e l e c t e d P a t h & M y U S D . S e l e c t e d F i 1 e End S e l e c t End S u b
460
I Chapter 20: Class Modules I TestShowDialogC is almost an identical copy of TestShowDialogA. The only difference is we are using CreateDialog instead of OpenDialog. Everything else is the same. Copy and paste TestShowDialogB to create TestShowDialogD and make the same change. Sub T e s t S h o w D i a l o g D ( ) D i m MyUSD As New c l s U S t a t i o n D i a l o g
MyUSD.AddFileExt "dgn" MyUSD.AddFileExt "dwg" My U S D .Ad d F i1 e Ex t
"
MyUSD.Defau1 t P a t h MyUS D . De f a u 1 t F i 1 e
dx f " =
" c : \ M i c r o S t a t i o n VBA"
=
"
t e s t .d g n "
MyUSD.CreateDialog S e l e c t Case MyUSD.OpenSuccess Case T r u e MsgBox M y U S D . S e l e c t e d P a t h & M y U S D . S e l e c t e d F i l e End S e l e c t End Sub
Onceagain, theonlychangeusing CreateDialog insteadofOpenDialog. Let's expand onTestShowDialogA in anewprocedure TestShowDialogE. Sub T e s t S h o w D i a l o g E ( ) D i m MyUSD As New c l s U S t a t i o n D i a l o g
MyUSD.AddFileExt "dgn" MyUSD.Defau1 t P a t h MyUS D . De f a u 1 t F i 1 e
=
=
"c:\" " t e s t .d g n "
My U S D . Open D i a 1 o g
S e l e c t Case MyUSD.0penSuccess Case T r u e MsgBox "Open
" & MyUSD.SelectedPath & MyUSD.SelectedFile
-
Case F a l s e I f M s g B o x ( " C r e a t e a new f i l e ? " , v b Y e s N o )
=
vbYes T h e n
MyUSD.CreateDialog I f MyUSD.0penSuccess MsgBox " C r e a t e
"
=
T r u e Then
& MyUSD.Se1ectedPath &
~
MyUSD.SelectedFile End I f End I f
I Encapsulating Similar Functionality I
461
End S e l e c t End S u b
What are we doing in Tes tShowDi a1 og E? We prompt the user to select a file to open. If the user selects an existing file, we display the file name in a MessageBox. If the user clicks the Cancel button, the MyUSD.0penSucces.s property will be False, so we to ask if the user wants to create a new file. If the user answers “No”,do nothing. If the user answers “Yes”,display the Filecreate dialog box and allow the user to create a new file. If the user successfully creates a file using the Filecreate dialog box, display the file path and name in a MessageBox. Notice how easy it is to add F i 1 e C r e a t e functionality to the class module and to use the C r ea t e 0ia 1 og method in the procedure. When you outline functionality to include in class modules, you can expand them quickly and easily. From this point on, any time you need to use the FileOpen or Filecreate dialog box, you can use the class you just created. But how can you use it in future projects? Do you need to copy and paste the code each time you want to use it? There is a better way. From the VBA Project Window (not to be confused with the VBA Project Manager), rightclick on the class and select Export File.
462
I Chapter 20: Class Modules I Select a location and save the file.
Now, as you create a new VBA Project, use the VBA menu File > Import. Select the class just exported and it will be imported into the new project.
This concludes the discussion on creating class modules to wrap similar functionality into a single object or class. Let’s look at another way to use classes.
CREATING OBJECTS WITH PROPERTIES,
METHODS, AND
EVENTS Microstation gives us a large number of classes (or objects) with which to work. For example, we used the LineElement object., which has properties such as the StartPoint and Endpoint, but not a Midpoint. Nor does it have ChangeInX, ChangeInY, or ChangeInZ properties. Let’s
I Creating Objects with Properties, Methods, and Events I
463
create a new clsLineElem Class and include a few properties that are not a part of the Microstation Object Model.
To simplify matters, we will have our Start and End Point properties implemented by declaring two variables as public within the General Declarations area of the class module. To make sure things are working correctly, create a test procedure to work with the class. Create a Midpoint property next. S u b T e s t N e w L i neA( ) D i m myLE As New c l s L i n e E l e m
End S u b
When you run this macro, you will see the following MessageBox:
What is the problem? In the General Declarations area we declared two variables as public with a type of Point3d. The Point3d variable type is not a standard VBA variable type, so we cannot declare it as we have. Change the declaration to private and change the name as follows: P r i v a t e p S t a r t P o i n t As P o i n t 3 d P r i v a t e pEndPoint As P o i n t 3 d
Now when you run TestNewLineA, we do not get the error. So how do we implement the StartPoint and Endpoint properties? After the pStartPoint and pEndPoint variables are declared in the General
464
I Chapter 20: Class Modules I Declaration area, we could implement the StartPoint and Endpoints properties like this: P r o p e r t y L e t S t a r t P o i n t ( S t P t As P o i n t 3 d ) pStartPoint
=
StPt
End P r o p e r t y P r o p e r t y G e t S t a r t P o i n t o As P o i n t 3 d StartPoint
pStartPoint
=
End P r o p e r t y P r o p e r t y L e t E n d P o i n t ( E n P t As P o i n t 3 d ) pEndPoint
=
EnPt
End P r o p e r t y P r o p e r t y G e t E n d p o i n t 0 As P o i n t 3 d Endpoint
=
pEndPoint
End P r o p e r t y
Let’s test the properties by creating a pr0ceLa-e in a C O L ~module. Sub T e s t N e w L i n e B ( 1 D i m myLE A s New c l s L i n e E l e m myLE.StartPoint
=
myLE.EndPoint
Point3dFromXYZ(10, 10, 10)
=
Point3dFromXYZ(4, 4, 4 )
End Sub
The code runs without any errors. To make sure, place a Stop (or Break Point) on the End Sub line, which allows us to add a watch to the variable myLE. Here’s what we get: Now that the StartPoint and Endpoint properties seem to be working, let’s move on to the Midpoint property. The Midpoint property is calculated from the StartPoint and Endpoints. Since it is calculated, make it read-only by implementing a “Property Get” without an associated “Property Let’:
I Creating Objects with Properties, Methods, and Events I
465
P r o p e r t y G e t M i d p o i n t 0 As P o i n t 3 d D i m t r n p P o i n t As P o i n t 3 d
trnpP0int.X
=
S t a r t P 0 i n t . X + (EndP0int.X
-
StartP0int.X)
/ 2
trnpP0int.Y
=
S t a r t P 0 i n t . Y + (EndP0int.Y
-
StartP0int.Y)
/ 2
trnpP0int.Z
=
S t a r t P 0 i n t . Z + (EndP0int.Z
-
StartP0int.Z)
/ 2
Midpoint
tmpPoint
=
End P r o p e r t y
A walk through the TestNewLineB procedure displays the results of the Midpoint property.
The calculations seem to be working correctly. Let’s add a few more properties: ChangeInX, ChangeInY, and ChangeInZ. P r o p e r t y G e t C h a n g e I n X O As D o u b l e ChangeInX
=
pEndP0int.X
-
pStartP0int.X
End P r o p e r t y P r o p e r t y G e t C h a n g e I n Y O As D o u b l e ChangeInY
=
pEndP0int.Y
-
pStartP0int.Y
End P r o p e r t y P r o p e r t y G e t C h a n g e I n Z O As D o u b l e ChangeInZ
=
pEndP0int.Z
-
pStartP0int.Z
End P r o p e r t y
Add two more properties: the LineAngleRads and LineAngleDegs properties: P r o p e r t y G e t L i n e A n g l e R a d s O As D o u b l e LineAngleRads
=
Atn((pEndP0int.Y
-
pStartP0int.Y) /
466
I Chapter 20: Class Modules I (pEndP0int.X
-
pStartP0int.X))
End P r o p e r t y P r o p e r t y Get L i n e A n g l eDegs( LineAngleDegs
=
As D o u b l e
Degrees(LineAngleRads1
End P r o p e r t y
Enough with properties. Let’s look at creating methods. The first one will be DrawLine. Sub D r a w L i n e ( ) D i m L i n e E l e m As L i n e E l e m e n t
Set LineElem
=
CreateLineElement2(Nothing,
-
pStartPoint, pEndPoint) ActiveModel Reference.AddElement
LineElem
End Sub
The DrawLine method creates a line element based on the pStartPoint and pEndPoint variables. The line is then added to the active model in Microstation. Now for an example, add the procedure TestNewLi neD to a code module: S u b TestNewLineD( D i m myLE As New c l s L i n e E l e m
P o i n t 3 d F r o m X Y Z ( O , 0 , 0)
myLE.StartPoint
=
myLE.EndPoint
=
P o i n t 3 d F r o m X Y Z ( 1 0 , 1 0 , 0)
=
P o i n t 3 d F r o m X Y Z ( - l O , 1 0 , 0)
my LE . Draw L i n e
myLE.EndPoint my LE . Draw L i n e
End S u b
Set the Start and End Point values, then we use the DrawLine method. That is simple enough. Try creating another method for our class module . This is a method named “DrawLinePerp”.It draws a line perpendicular to the one defined by the pStartPoint and pEndPoint properties of the class through the midpoint of the line. S u b DrawLinePerp( D i m P e r p S t As P o i n t 3 d D i m P e r p E n As P o i n t 3 d
D i m P e r D M i d As P o i n t 3 d
I Creating Objects with Properties, Methods, and Events I
467
D i m L i n e A n g As D o u b l e D i m L i n e L e n g t h As D o u b l e
L i neAng
=
L i n e A n g l eRads
PerpMid
=
Midpoint
LineLength PerpSt
=
Point3dDistance(pStartPoint, pEndPoint)
=
Point3dAddAngleDistance(PerpMid, LineAng + P i / 2,
-
L i n e L e n g t h / 2 , 0) PerpEn
=
Point3dAddAngleDistance(PerpMid, LineAng
-
P i / 2,
-
L i n e L e n g t h / 2 , 0)
D i m L i n e E l e m As L i n e E l e m e n t Set LineElem
=
CreateLineElementZ(Nothing,
~
PerpSt, PerpEn) ActiveModel Reference.AddElement
LineElem
End Sub
Now, to test the DrawLinePerp method, create a new procedure in a code module. Sub TestNewLineE() D i m myLE As New c l s L i n e E l e m
myLE.StartPoint
=
myLE.EndPoint
P o i n t 3 d F r o m X Y Z ( 8 , 8 , 0)
=
P o i n t 3 d F r o m X Y Z ( O , 0 , 0)
myLE.DrawLine myLE. D r a w L i n e P e r p End Sub
Two lines are drawn. One from (0, 0, 0) to (8, 8, 0) and another perpendicular to the first one through the mid-point of the first one. Add another method to the class module. First the code, then the explanation: Sub DrawCi r c l e ( )
D i m C i r c E l e m As E l l i p s e E l e m e n t D i m R o t M a t r i x As M a t r i x 3 d
CreateEllipseElementZ(Nothing, M i d p o i n t , pEndPoint) / 2, Point3dDistance(pStartPoint, p E n d P o i n t ) / 2, R o t M a t r i x )
Set CircElem
=
Point3dDistance(pStartPoint,
ActiveModel Reference.AddElement C i rcElem End Sub
0r a wC ir c 1 e draws a circle through the end points of the LineElem object.
468
I Chapter 20: Class Modules I One of the great things about the class module is that we can add methods and properties whenever we see a need. At this point, the three methods in our class module may be all we need in the application right now. Later, if the next application needs a method named “DrawLineSegments” to specify the number of line segments between the start point and end point, we can add it. We can add any number of methods and properties to our class module but we need to be careful. Be even more cautious when modifying existing methods and Properties. For instance, we have a property named StartPoint declared as a Point2d type that works in many circumstances. Changing it to a Point3d type might seem as though it would support more methods and properties. But changing a variable from a Point2d to a Point3d may cause code already making use of the Class Module to fail because functions or procedures in the existing code expect a Point2d type. Careful planning helps avoid making changes to class module properties and methods after they are in use. We covered properties and methods, but what is an event? An event is triggered when a specific activity takes place. We usually write code in events to react to user interaction. Let’s create an event in our class to be triggered whenever a line is created and drawn in Microstation. In the General Declarations area of the class, the following code creates an event: Pub1 i c E v e n t L i n e A d d e d ( A d d e d L i n e As L i n e E l e m e n t )
An event named “LineAdded is now part of the class. But how do we trigger this event? Sub D r a w L i n e ( )
D i m L i n e E l e m As L i n e E l e m e n t Set LineElem
=
CreateLineElement2(Nothing,
-
pStartPoint, pEndPoint) ActiveModel Reference.AddElement
R a i s e E v e n t L i n e A d d e d ( L i n e E l em) End Sub
LineElem
I Creating Objects with Properties, Methods, and Events I
469
Whenever you want to trigger an event, use the RaiseEvent statement. Supply the variable LineElem as a parameter, so when you capture the event in code that uses this class module, you are given the line element that was drawn in Microstation. Now that we have an event declared and raised in our class module, what’s the next step? How do we get code into the event so that when a line is added, we can do something? That is a good question. To use the event we just created, declare a variable as the class clsLineElem in either another class module or a form “WithEvents”. It is easier to demonstrate the use of this event by using a form. Insert a new form into your VBA Project. Declare a variable “WithEvents” in the General Declarations area of the form’s code as a clsLineElem Object.
Dim WithEvents myLE As clsLineElem
When you declare a variable this way, the variable’s events are available like the events of a CommandButton. The object myLE (it uses the variable name, not the class name) is now available in the Object ComboBox of the form’s code.
As clsLineElem
470
I Chapter 20: Class Modules I After selecting myLE in the Object ComboBox, you can see the clsLineElem Events in the Procedure ComboBox.
Private S u b myLE-LineAdded (AddedLine As LineElement) End S u b
Now use the event created in the clsLineElem class. Let’s review the four components required to make use of an event.
1 Declare the event with “Public Event” in the class module. 2
Raise the event with “RaiseEvent”in a class module property or method.
3 Declare a variable “WithEvents” as the class in the General Declarations area of a class or form.
4 Enter code in the event in the class or form.
USING CLASS MODULES WITH COLLECTIONS We used class modules to wrap functionality and to create new objects through the use of properties, methods, and events. You can also use classes with custom collections. A collection is a container that holds objects. Custom VBA collections can contain any type of object. Let’s look at an example of creating a new collection to hold Microstation Level objects. Sub
TestCol l e c t i o n A ( ) D i m m y C o l l As New C o l l e c t i o n D i m m y L e v e l As L e v e l
For Each myLevel I n A c t i v e M o d e l R e f e r e n c e . L e v e l s myCol1 .Add myLevel Next End S u b
I Using Class Modules with Collections I
471
The declaration for the Add method of a collection is: Sub Add(Item, [Key],
[Beforel,
[After])
In our first example, we added Levels to a collection by supplying the Item (a Level in this case) to the Item parameter of the Add method. We did not specify a key or a position. We will look at the Key, Before, and After parameters later. Add a Watch to the collection myColl, then step through the TestColl ecti onA procedure line byline by pressing the
Code Description ElementAccess ElementColor ElementLineSty le ElementLineWeight ID lsAd ive IsDispIay ed IsFromLevelLibrary IsFrozen IsInUse IsLocked Name Number OverrideCoIor OverrideLineStyle OverrideLineWeight ParentLevel Plot Using0verrideCoIor UsingOverrideLineStyle UsingOverrideLineWeight bem 2 bem 3
1
msdLevelElementAccessAll 0 0 1 True True False False False False "Level 1"
1 0 0 Nothing True True True True
Long String MsdLevelElementAccess Long LineStylehineSty le Long Long Boolean Boolean Boolean Boolean Boolean Boolean String Long Long LineStylehineStyle Long Level Boolean Boolean Boolean Boolean VariantlObjecthevel VariantlObjecthevel
Each added object shows up as an item in the collection. You can see the type of object in the Type column. All of the object's properties display with their values.
Accessing Objects in a Collection After objects have been added to a collection, you can access them different ways. S u b TestCollectionBO D i m m y C o l l As New C o l l e c t i o n D i m m.yLeve1 As L e v e l
472
I Chapter 20: Class Modules I F o r Each m y L e v e l I n A c t i v e M o d e l R e f e r e n c e . L e v e l s m y C o l 1 .Add m y L e v e l Next 'Now t h a t t h e c o l l e c t i o n i s p o p u l a t e d , ' a c c e s s t h e o b j e c t s w i t h F o r Each
. .
Next
D i m m y L e v e l 2 As L e v e l
F o r Each m y L e v e l 2 I n m y C o l l D e b u g . P r i n t myLevel2.Name Next End Sub
One way is to use the For Each ... Next statement. The example above populates a collection with the levels in the active model. Then you access each object in the collection using Fo r Each ... Next. When objects are added to a collection, the properties, methods, and events of the objects are live. That is they are not static variables holding values. Be careful when accessing the objects that you do not modify properties inadvertently. Use For Each ... Next for easy access to each object in the collection. Here is another way to access objects in a collection: Sub TestCol l e c t i o n C ( ) D i m m y C o l l As New C o l l e c t i o n
D i m m y L e v e l As L e v e l F o r Each m y L e v e l I n A c t i v e M o d e l R e f e r e n c e . L e v e l s m y C o l 1 .Add m y L e v e l Next 'Now t h a t t h e c o l l e c t i o n i s p o p u l a t e d , 'access t h e objects using t h e i t e m Index.
D i m m y L e v e l 2 As L e v e l D i m I As Long For I
=
1 To m y C o l l . C o u n t
Set myLevel2
=
m y C o l 1 (I 1
D e b u g . P r i n t myLevel2.Name Next End Sub
We are still cycling through each item in the collection but now we are accessing each item by addressing it by the item's index in the collection. Sub TestCol l e c t i o n D ( )
I Using Class Modules with Collections
473
D i m m y C o l l A s New C o l l e c t i o n D i m myLevel As Level
F o r Each m y L e v e l I n A c t i v e M o d e l R e e r e n c e . L e v e l s m y C o l 1 .Add m y L e v e l Next 'Now t h a t t h e c o l l e c t i o n i s p o p u l a t e d , 'access t h e objects using t h e i t e m Index. D i m myLevel2 As Level
D i m I As Long F o r I = m y C o l l . C o u n t To 1 S t e p -1 Set myLevel2
=
myColl(1)
Debug. Pr in t my L e v e 1 2 . Name Next End Sub
Now, instead of addressing the items from the top of the list down, address the items from the bottom up by using a For ... Next statement using "Step - 1". Let's take another look at the Collection Add method declaration: Sub Add(Item, [Key],
[Before],
[After])
If you add objects to a collection and provide a key (a unique string in the collection), you can address objects through their key as well as their index. Here is an example of adding levels to the collection myColl and using the level's name as the item's key in the collection. Sub T e s t C o l l e c t i o n F O D i m m y C o l l A s New C o l l e c t i o n D i m myLevel As Level
F o r Each m y L e v e l I n A c t i v e M o d e l R e f e r e n c e . L e v e l s m y C o l 1 .Add m y L e v e l , m y L e v e l .Name Next 'Now t h a t t h e c o l l e c t i o n i s p o p u l a t e d , ' a d d r e s s a L e v e l b y i t ' s Key. D i m myLevel2 As Level
Set myLevel2
=
myColl("Defau1t")
MsgBox m y L e v e l 2 . N u m b e r End S u b
474
I Chapter 20: Class Modules I We know each Microstation DGN file has a level named “Default”. Because we may not know what its index in the collection will be, access the level in the collection through the object’s key. Keys must be unique strings. In other words, no two objects in a collection can have the same key. Keys are not case-sensitive so you cannot have a key of “test” and a key of “TEST” in the same collection.
Removing Objects from a Collection You can remove objects from a collection using its key or its index. When removing by index, objects with an index higher than the object removed will decrease their indexes by one. For this reason, to clear an entire collection, begin with the highest index and work to the lowest (always an index of 1) using a “Step -1” statement. Here is an example of populating a collection with levels, then removing all of the Items using the Remove method. Sub T e s t C o l l e c t i o n E O D i m m y C o l l As New C o l l e c t i o n
D i m m y L e v e l As L e v e l F o r Each m y L e v e l I n A c t i v e M o d e l R e f e r e n c e . L e v e l s m y C o l 1 .Add m y L e v e l Next ‘Now t h a t t h e c o l l e c t i o n i s p o p u l a t e d , ‘remove t h e o b j e c t s u s i n g t h e i t e m Index.
D i m I As Long F o r I = m y C o l l . C o u n t To 1 S t e p - 1 m y C o l 1 .Remove I Next End Sub
Using Custom Class Modules Let’s use the clsLineElem class now. Sub TestCol l e c t i o n G ( ) D i m myLE As c l s L i n e E l e m D i m XYA As D o u b l e D i m XYMin As D o u b l e
D i m XYMax As D o u b l e
I Using Class Modules with Collections I
475
D i m m y C o l l As New C o l l e c t i o n
XYMin
=
XYMax
=
For XYA
0 10 =
XYMin T o XYMax
S e t myLE
=
New c l s L i n e E l e m
myLE.StartPoint
=
myLE.EndPoint
P o i n t 3 d F r o m X Y ( X Y A , XYMax)
=
P o i n t 3 d F r o m X Y ( X Y A , XYMin)
m y C o l 1 . A d d myLE S e t myLE
=
Nothing
S e t myLE
=
New c l s L i n e E l e m Point3dFromXY(XYMin, XYA)
myLE.StartPoint
=
myLE.EndPoint
Point3dFromXY(XYMax,
=
XYA)
m y C o l 1 . A d d myLE S e t myLE
=
Nothing
Next XYA F o r Each myLE I n m y C o l l myLE.DrawLine Next End S u b
In this example, we create multiple clsLineElem objects and add them to a collection. Then we use a For Each ... Next statement to access each clsLineElem object in the collection and use the D r a w l i n e method of each object in the collection. After the macro IS run, we should see a grid like this:
Notice that if we do not use the DrawLine Method on each clsLineElem Class, none of the lines will be drawn. Let’s take a look at another example of using the clsLineElem class in a collection.
476
I Chapter 20: Class Modules I Sub
TestCollectionHO D i m myLE As c l s L i n e E l e m
D i m XYA As D o u b l e D i m XYMin As D o u b l e D i m XYMax As D o u b l e D i m x C l o s e As D o u b l e D i m y C l o s e As D o u b l e D i m m y C o l l As New C o l l e c t i o n
XYMin
=
XYMax
=
F o r XYA
0 10 =
XYMin To XYMax
S e t myLE
=
New c l s L i n e E l e m
myLE.StartPoint
=
myLE.EndPoint
P o i n t 3 d F r o m X Y ( X Y A , XYMax)
=
P o i n t 3 d F r o m X Y ( X Y A , XYMin)
m y C o l 1 .Add myLE S e t myLE
=
Nothing
S e t myLE
=
New c l s L i n e E l e m
myLE.StartPoint
=
myLE.EndPoint
P o i n t 3 d F r o m X Y ( X Y M a x , XYA)
=
P o i n t 3 d F r o m X Y ( X Y M i n , XYA)
m y C o l 1 .Add myLE S e t myLE
=
Nothing
N e x t XYA xClose
=
CDbl ( I n p u t B o x ( " E n t e r X V a l u e : " ) )
yClose
=
CDbl ( I n p u t B o x ( " E n t e r Y V a l u e : " ) )
F o r Each myLE I n m y C o l l I f myLE.StartP0int.Y S e t myLineElem
=
=
m y L E . E n d P 0 i n t . Y Then
CreateLineElementZ(Nothing,
rnyLE.StartPoint,
~
Point3dFrornXY(xClose, y C l o s e ) )
A c t i v e M o d e l Reference.AddE1 ement myLineElem S e t myLineElem
=
CreateLineElementZ(Nothing,
rnyLE.EndPoint.
-
Point3dFrornXY(xClose. y C l o s e ) )
A c t i v e M o d e l Reference.AddE1 ement myLineElem End I f Next End Sub
In this procedure, we create a collection of clsLineElem objects. This is the same code that generated the grid in a previous example. Then we look at each clsLineElem in the collection and look at the Y components of the Start and End Points. If they are equal, we know we are dealing with a horizontal line. For each myLineElem Object that is horizontal,
I Using Class Modules with Collections I
477
we draw a line between the entered X and Y values and the Start and End Points of the myLineElem object.
2.875
7.32
The user is prompted to enter an X and Y value. Then we draw lines between the entered X, Y pair and the Start Point and End Point of each clsLineElem object in the collection.
478
I Chapter 20: Class Modules I This example demonstrates how we can place multiple objects in a collection, then use the collection to evaluate objects within the collection.
REVIEW Classes can encapsulate similar functionality, create objects with unique properties, methods, and events, and group similar objects for a variety of purposes into collections. The more you implement classes in your programs, the more you are following the ideals of object oriented programming.
21
VBA for CAD Managers VBA is not just for programmers and not just for Microstation users -. it is a powerful tool for CAD Managers as well. In this Chapter:
Using VBA for maintaining standards Using VBA to facilitate cross-company standards Using VBA to track time in drawings Auto-loading and auto-running macros Protecting projects Distributing VBA projects Working in high-security mode
USING VBA FOR MAINTAINING STANDARDS Because standards differ from company to company, VBA can be an important part of customizing and maintaining standards for your individual company. Let’s talk about some ways to use VBA to review DGN files and report problems.
479
480
I Chapter 21:VBA for CAD Managers I NOTE: For more information on maintaining standards, look at the Standards Checker Interface which provides powerful functionality with built-in reporting capabilities. The first thing to create is a procedure that looks for unsupported levels. S u b FindUnsupportedLevels() D i m G o o d L e v e l s ( 0 To 4 ) As S t r i n g D i m G o o d L e v e l s J As S t r i n g D i m m y L e v e l As L e v e l
GoodLevels(0)
=
"ROADWAY"
GoodLevels(1)
=
"SIDEWALK"
GoodLevels(2)
=
"PAINT"
GoodLevels(3)
=
"ELECTRIC"
GoodLevels(4)
=
"GAS"
GoodLevelsJ
UCase("-"
For
=
& J o i n ( G o o d L e v e l s , "-")
& "-")
Each myLevel I n A c t i v e D e s i g n F i l e . L e v e 1 s I f I n S t r ( 1 , GoodLevelsJ,
"-" &
UCase(myLevel.Name) &
Debug.Print "Unsupported Level Found:
"
"-"I
=
0 Then
& myLevel .Name
End I f Next End S u b
Five supported levels are specified. Join the array of level names separated by tilde characters (-). Then look in the joined string using the InStr function. If the level is not found in the supported level name string, print the level name to the Immediate Window.
Unsupported Unsupported Unsupported Unsupported Unsupported Unsupported Unsupported Unsupported Unsupported Unsupported
Level Level Level Level Level Level Level Level Level Level
Found: Found: Found: Found: Found: Found: Found: Found: Found: Found:
Level 1 L e v e l 20 L e v e l 23 L e v e l 38 L e v e l 39 Level 4 0 Level 4 1 L e v e l 42 L e v e l 47 Default
I Using VBA for Maintaining Standards I
481
This works well for finding un-supported levels. What do you do if a file is supposed to have levels but they aren't there? Look for missing levels next. Sub
FindMissingLevelsO D i m G o o d L e v e l s ( 0 To 4 ) As S t r i n g D i m L e v e l F o u n d ( 0 To 4 ) As B o o l e a n D i m m y L e v e l As L e v e l D i m I As L o n g G o o d L e v e l s (0)
=
"ROADWAY"
GoodLevels(1)
=
"SIDEWALK"
GoodLevels(2)
=
"PAINT"
GoodLevels(3)
=
"ELECTRIC"
GoodLevels(4)
=
"GAS"
GoodLevelsJ
UCase("-"
=
& J o i n ( G o o d L e v e l s , "-"I
& "-"I
F o r Each m y L e v e l I n A c t i v e D e s i g n F i l e . L e v e l s For I
=
L B o u n d ( G o o d L e v e 1 s ) To U B o u n d ( G o o d L e v e 1 s )
I f StrComp(GoodLevels(I), myLevel .Name, vbTextCompare)
LevelFound(1)
=
=
0 Then
True
End I f Next I Next For I
=
L B o u n d ( G o o d L e v e 1 s ) To U B o u n d ( G o o d L e v e 1 s )
I f L e v e l F o u n d ( I)= Fa1 s e T h e n D e b u g . P r i n t " M I S S I N G LEVEL:
"
& GoodLevels(1)
End I f Next I End Sub
Above, the code is ready to report missing levels to the Immediate Window. When is the last time a CAD Manager turned in a report consisting of a screen capture of the Immediate Window in VBA? Let's add some code to copied and pasted versions of the two above procedures to write to an ASCII text file. Sub
FindUnsupportedLevel s B ( ) D i m G o o d L e v e l s ( 0 To 4 ) As S t r i n g D i m G o o d L e v e l s J As S t r i n g D i m m y L e v e l As L e v e l D i m F F i l e As L o n g
I Chapter 21:VBA for CAD Managers I GoodLevels(0)
=
"ROADWAY"
GoodLevels(1)
=
"SIDEWALK"
GoodLevels(2)
=
"PAINT"
GoodLevels(3)
=
"ELECTRIC"
GoodLevels(4)
=
"GAS"
GoodLevelsJ
UCase("-"
FFile
=
=
& J o i n ( G o o d L e v e l s , "-")
& "-")
FreeFile For Output As # F F i l e
Open " C : \ M i c r o S t a t i o n VBA\LevelsUnSupported.txt" For Each myLevel I n A c t i v e D e s i g n F i l e . L e v e l s I f I n S t r ( 1 , GoodLevelsJ,
"-" &
UCase(myLevel.Name) &
"-")
=
0 Then
P r i n t #FFi 1 e, A c t i veDesi gnFi 1 e. Path & A c t i v e D e s i g n F i 1 e. Name & vbTab & " U n s u p p o r t e d L e v e l Found:
"
-
& m y L e v e l .Name
End I f Next C1 o s e # F F i 1 e End Sub Sub F i n d M i s s i n g L e v e l s B O D i m G o o d L e v e l s ( 0 To 4 ) As S t r i n g D i m L e v e l F o u n d ( 0 To 4 ) As B o o l e a n
D i m m y L e v e l As L e v e l D i m I As L o n g D i m F F i l e As L o n g GoodLevels(0)
=
"ROADWAY"
GoodLevels(1)
=
"SIDEWALK"
GoodLevels(2)
=
"PAINT"
GoodLevels(3)
=
"ELECTRIC"
GoodLevels(4)
=
"GAS"
GoodLevelsJ
UCase("-"
FFile
=
=
& Join(GoodLevels,
'I-")
&
'I-")
FreeFile
Open " C : \ M i c r o S t a t i o n V B A \ L e v e l s M i s s i n g . t x t " For Output As # F F i l e For Each myLevel I n A c t i v e D e s i g n F i l e . L e v e 1 s F o r I = L B o u n d ( G o o d L e v e 1 s ) To U B o u n d ( G o o d L e v e 1 s ) I f StrComp(GoodLevels(I), myLevel.Name,
vbTextCompare)
=
0 Then
L e v e l F o u n d ( I)= T r u e End I f Next I Next F o r I = L B o u n d ( G o o d L e v e 1 s ) To U B o u n d ( G o o d L e v e 1 s )
I f L e v e l F o u n d ( I)= Fa1 s e T h e n P r i n t # F F i l e , A c t i v e D e s i g n F i l e . Path & ActiveDesignFile.Name & v b T a b & "MISSING LEVEL: " & G o o d L e v e l s ( I)
I Using VBA for Maintaining Standards I
483
End I f Next I C1 o s e # F F i 1 e End S u b
You are writing to an ASCII file. Place a tab between the filepathhame to make the file tab-delimited. Why tab-delimited? Because it is easy to import the file into Microsoft Excel and other programs capable of reading tab-delimited ASCII files. What's next? Consider the pain involved in opening hundreds of DGN files and running this macro one-by-one. VBA is supposed to solve these types of problems and make life easier and more pain-free. Make a small change to your procedure to append the ASCII file when it is opened. When you open a file for output, the existing file (if it exists) is overwritten. When you open a file for append, the existing file (if it exists) is appended to and created if the file did not previously exist. Sub
FindUnsupportedLevelsC(Fi1eToQuery
As D e s i g n F i l e )
D i m G o o d L e v e l s ( 0 To 4 ) As S t r i n g D i m G o o d L e v e l s J As S t r i n g D i m m y L e v e l As L e v e l D i m F F i l e As L o n g G o o d L e v e l s (0)
=
"ROADWAY"
GoodLevels(1)
=
"SIDEWALK"
GoodLevels(2)
=
"PAINT"
GoodLevels(3)
=
"ELECTRIC"
GoodLevels(4)
=
"GAS"
GoodLevelsJ
UCase("-"
FFile
=
=
& Join(GoodLeve1s. "-"I
& "-"I
FreeFile
Open " C : \ M i c r o S t a t i o n
V B A \ L e v e l s U n S u p p o r t e d . t x t " F o r Append
~
As # F F i l e F o r Each m y L e v e l I n F i 1 eToCluery. L e v e l s I f I n S t r ( 1 . GoodLevelsJ.
P r i n t #FFile,
"-" &
UCase(myLevel.Name) &
"-")
=
0 Then
F i l e T o Q u e r y . P a t h & FileToCluery.Name &
vbTab & "Unsupported Level Found:
"
End I f Next C1 o s e B F F i 1 e End S u b
S u b FindMissingLevelsC(Fi1eToQuery As D e s i g n F i l e )
& myLevel .Name
-
484
I Chapter 21:VBA for CAD Managers I D i m G o o d L e v e l s ( 0 To 4 ) As S t r i n g D i m L e v e l F o u n d ( 0 To 4 ) As B o o l e a n
D i m m y L e v e l As L e v e l D i m I As L o n g D i m F F i l e As L o n g GoodLevels(0)
=
"ROADWAY"
GoodLevels(1)
=
"SIDEWALK"
GoodLevels(2)
=
"PAINT"
GoodLevels(3)
=
"ELECTRIC"
GoodLevels(4)
=
"GAS"
GoodLevelsJ
UCase("-"
FFile
=
=
& Join(GoodLevels,
'I-")
&
'I-")
FreeFile
O p e n "C:\MicroStation VBA\LevelsMissing.txt" F o r A p p e n d As #FFile F o r E a c h m y L e v e l I n F i 1e T o Q u e r y . L e v e l s F o r I = L B o u n d ( G o o d L e v e 1 s ) To U B o u n d ( G o o d L e v e 1 s )
If StrComp(GoodLevels(I), myLevel.Name, v b T e x t C o m p a r e )
=
0 Then
L e v e l F o u n d ( I)= T r u e End I f Next I Next F o r I = L B o u n d ( G o o d L e v e 1 s ) To U B o u n d ( G o o d L e v e 1 s ) I f L e v e l F o u n d ( I)= Fa1 s e T h e n P r i n t I l F F i l e , F i l e T o Q u e r y . P a t h & Fi1eToQuery.Name & v b T a b & "MISSING LEVEL:
"
~
& G o o d L e v e l s ( I)
End I f Next I C1 o s e # F F i 1 e End Sub
In addition to opening a fde for Append, we are supplying a parameter to these procedures now. This allows us to specify a DesignFile object to query for levels. How do we open hundreds of files in a single directory? Before continuing, add a reference to our VBA Project. In the VBA menu, choose Tools > References and select "Microsoft Scripting Runtime" from the alphabetized list. Once a reference has been made to the Microsoft Scripting Runtime, we can use the File System Object to traverse files in a folder. Sub
DoFilesInFolderO D i m MyFSO As New F i l e S y s t e m O b j e c t
I Cross-Company Standards I
485
Dim myFolder A s Folder Dim myFile A s File Set myFolder
=
MyFSO.GetFolder(”C:\Documents
and S e t t i n g s \ ” &
-
“A1 1 U s e r s \ A p p l ic a t i o n D a t a \ D o c u m e n t s \ B e n t l e y \ W o r k S p a c e \ ”
& -
” Projects \ Examp 1 e s \ Ar c h i te c t u r a 1 \ Dgn ” For Each myFile In myFolder.Files Select Case UCase(Right(myFi le.Name, 3 ) Case ” D G N ” Dim myDGNFile As DesignFile
S e t myDGNFile
=
OpenDesignFileForProgram(myFi1e.Path)
F i n d U n s u p p o r t e d L e v e l s C myDGNFile F i n d M i s s i n g L e v e l s C myDGNFile
myDGNFile.Close End Select Next End S u b
The procedure DD F i 1 e s I n F o l d e r takes a hard-coded file path and opens each DGN file in the path “ForProgram”.This means it is not opened in the Microstation editor window but we can manipulate the files using VBA. The example shown opens files added to our hard drives when Microstation is installed. In less than one second, seven files are opened, levels are identified, and text files are written. Use the examples shown here to spark your creativity. There are other elements, in addition to levels, you could use to maintain standards. Indeed, an entire book could cover examples of verifying a multitude of criteria. Since this is not the focus of this book, I will leave additional functionality to your imagination. For more related information, see Chapter 30 “Batch Processing”.
CROSS-COMPANYSTANDARDS Two companies need to work with each other’s files but one company has a level named “STREET” while the other has one named “Level 20’: How can VBA help companies work with different standards? The procedure L e v e l SpecA translates one standard to another for level names. When the procedure finds “Level 20’: it changes the name to “STREET’: The other level name mappings are easy to see.
I Chapter 21:VBA for CAD Managers I
S u b L e v e l SpecA( 1 D i m m y L e v e l As L e v e l D i m I As L o n g For I
=
1 To ActiveDesignFile.Leve1s.Count
S e t myLevel
=
ActiveDesignFile.Levels(1)
S e l e c t Case m y L e v e l . Name Case " L e v e l 20" m y L e v e l .Name
=
"STREET"
ActiveDesignFile.Levels.Rewrite Case " L e v e l 2 1 " m y L e v e l .Name
=
"SIDEWALK"
ActiveDesignFile.Leve1s.Rewrite Case " L e v e l 2 3 " m y L e v e l .Name
=
"GUTTER"
ActiveDesignFile.Levels.Rewrite Case " L e v e l 3 8 " m y L e v e l .Name
=
"STRIPING"
ActiveDesignFile.Leve1s.Rewrite Case " L e v e l 3 9 " m y L e v e l .Name
=
"SEWER"
ActiveDesignFile.Levels.Rewrite Case " L e v e l 4 0 " m y L e v e l .Name
=
"PHONE"
ActiveDesignFile.Leve1s.Rewrite Case " L e v e l 4 1 " m y L e v e l .Name
=
"ELECTRIC"
ActiveDesignFile.Levels.Rewrite Case " L e v e l 4 2 " m y L e v e l .Name
=
"NATGAS"
ActiveDesignFile.Leve1s.Rewrite Case " L e v e l 4 7 " m y L e v e l .Name
=
"FIBER"
ActiveDesignFile.Leve1s.Rewrite End S e l e c t Next I
End S u b
After this procedure is run, the design file meets the company's standards for level names. We can work on the file with our customdeveloped VBA tools. Our designers and drafters do not need to refer to "cheat-sheets'' to remember which level name goes with which level
I Cross-Company Standards I
487
name. They see what they are accustomed to seeing and are much more productive as a result. Before returning the file to the originating company, it would be polite for us to set the Level names back to what they had been. S u b L e v e l SpecB( )
myLevel A s Level I A s Long For I = 1 T o ActiveDesignFile.Leve1s.Count Set myLevel = ActiveDesignFile.Levels(1) Select Case myLevel .Name C a se ST RE ET myLevel .Name = "Level 20" ActiveDesignFile.Leve1s.Rewrite Case S I D EWA L K X myLevel.Name = "Level 21" ActiveDesignFile.Levels.Rewrite C a se G UTT E R myLevel .Name = "Level 23" ActiveDesignFile.Leve1s.Rewrite Case ST R I P I N G myLevel .Name = "Level 38" ActiveDesignFile.Levels.Rewrite Case S EWER myLevel .Name = "Level 39" ActiveDesignFile.Leve1s.Rewrite Case P H 0N E myLevel .Name = "Level 40" ActiveDesignFile.Leve1s.Rewrite Case E L ECT R I C myLevel .Name = "Level 41" ActiveDesignFile.Levels.Rewrite C a se N AT GA S myLevel .Name = "Level 42" ActiveDesignFile.Leve1s.Rewrite Case F I B E R " myLevel .Name = "Level 47" ActiveDesignFile.Levels.Rewrite End Select Next I
Dim Dim
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
End S u b
"
"
488
I Chapter 21:VBA for CAD Managers I Does the code work? Of course! It works great! It is fast! It is amazing! It is a lot of hard-coded mapping that will be difficult to maintain! What happens when another level name translation is introduced? You have to change the code and re-distribute the VBA Projects to everyone who uses it. Right? Maybe not. Let's think about what we can do to create a level mapping file that tells what level to look for and what its new name should be. If this chapter were about working with databases, we could do the mapping in a database. If this chapter were about working with Microsoft Excel, we could do the mappings in Excel. But since these topics will be covered later, we will read from a simple ASCII text file. Now for the file format for our level mapping file. Each line in the file represents one mapping. Each line contains two fields separated by a tab. The first field will be the old level name, the second field will be the new level name.
Level Level Level Level Level Level Level Level Level
20 21 23 38 39 40 41 42 47
STREET SIDEWALK GUTER STRIPING SEWER PHONE ELECTRIC NATGAS FIBER
Let's look at the code that makes use of our LevelMap file. S u b L e v e l SpecFromFi 1 e ( ) D i m m y F i l e As S t r i n g D i m O l d L e v e l O As S t r i n g D i m NewLevel
0 As S t r i n g
D i m F F i l e As L o n g D i m t x t I n As S t r i n g D i m x S p l i t 0 As S t r i n g ReDim O l d L e v e l (0) ReDim NewLevel (0) D i m m y L e v e l As L e v e l
Dim
I A s Long
D i m J As Long myFile FFile
= =
"C:\MicroStation FreeFile
VBA\LevelMap.txt"
I Cross-Company Standards I
489
Open m y F i l e F o r I n p u t As # F F i l e W h i l e EOF(FFi1e)
=
False
Line Input #FFile,
txtIn
I f I n S t r ( 1 , t x t I n , vbTab) xSplit
=
> 0
Then
S p l i t ( t x t I n , vbTab)
OldLevel(UBound(01dLevel ) )
=
xSplit(0)
NewLevel(UBound(NewLeve1))
=
xSplit(1)
ReDim P r e s e r v e O l d L e v e l ( U B o u n d ( O l d L e v e 1 ReDim P r e s e r v e NewLevel (UBound(NewLeve1
+ 1) + 1)
End I f Wend ReDim P r e s e r v e O l d L e v e l ( U B o u n d ( O l d L e v e 1 )
-
1)
ReDim P r e s e r v e NewLevel ( U B o u n d ( N e w L e v e 1 )
-
1)
C1 o s e # F F i 1 e F o r I = 1 To Active0esignFile.Levels.Count S e t myLevel For J
=
=
ActiveDesignFile.Levels(1)
L B o u n d ( O l d L e v e 1 ) To U B o u n d ( O l d L e v e 1 1
I f StrComp(OldLevel(J), m y L e v e l .Name
=
myLevel.Name)
=
0 Then
NewLevel(J)
A c t i v e D e s i g n F i l e . L e v e l s . Rewri t e E x i t For End I f Next J Next I End Sub
Here is the code. It does not matter how many levels are in the text file. There can be 5 or 5,000 -the code doesn’t change.
490
I Chapter 21:VBA for CAD Managers I
TRACKING TIME Time is money, right? How many times have we heard that? Perhaps the reason we have heard it so many times is because it is true. One benefit of learning VBA, is that you can do things many times faster with VBA than without it. The concern about spending time in a drawing or working on a project is different from person to person and is often defined by the relationship we have with the drawing.
Drafters A drafter may look at the time spent in a drawing as the basis for how much money will be paid for the work in the drawing. “The more time spent in a drawing means more money in the paycheck.” Another drafter may look at the time spent in a drawing as an indication of productivity. “I am twice as productive as any other drafter.”
Managers A manager may look at the amount of time spent in a drawing as an indication that a drafter or designer needs additional training. Or perhaps a drafteddesigner needs to teach others in the company to be more productive. Another manager may look at the amount of time spent in a drawing in terms of progress on a project.
Accountants An accountant may look at the time spent in a drawing in terms of how much money to invoice a customer. Another accountant may look at the time spent in a drawing for considering raises and setting salaries. It doesn’t matter what role we play in a company, the basics of tracking time is the same. And if we are working hard (until our backs ache and our tired muscles knot), accurate time reporting will always be on our side.
I Tracking Time I
491
So what are the basics of tracking time? Who did what and when? Any time Microstation is open, you can find out who is logged into the computer. Any time you want to capture data, you can get the current date and time. So the only question you need to answer is "what?".What events do you want to capture?
Previously, we discussed using interfaces to capture user input, element selection, etc. You could log each and every command started by the user. It may be helpful at some point to do a usability study on how Microstation is used, but that is probably overkill for what we are attempting to accomplish here. You could capture File Open and File Closed events. This would be useful to know would be insufficient or misleading, especially for billing. What if someone opens a file at 4:59 PM on Friday then leaves for the weekend? Better to capture something else while the file is open to know if the file is being worked on. Let's try watching the View Update event. This event is not triggered so often that logging information will be a performance problem. To capture events, you need a new class module. Name it clsTimeTrack. Here is the code in the class module: Imp1 e m e n t s I V i e w U p d a t e E v e n t s D i m W i t h E v e n t s MSApp As A p p l i c a t i o n
P r i v a t e Sub C l a s s - I n i t i a l i z e ( )
D i m F F i l e As Long FFile
=
FreeFile
Open " C : \ M i c r o S t a t i o n P r i n t BFFile,
VBA\TimeTrack.txt"
F o r Append As I I F F i l e
" I N I T " & v b T a b & Now
C1 o s e B F F i 1 e S e t MSApp
=
Application
End Sub P r i v a t e Sub C l a s s - T e r m i n a t e 0 D i m F F i l e As Long
FFile
=
FreeFile
Open " C : \ M i c r o S t a t i o n
VBA\TimeTrack.txt"
P r i n t I I F F i l e , "TERM" & v b T a b & Now C1 o s e # F F i 1 e
F o r Append As B F F i l e
I Chapter 21:VBA for CAD Managers I End Sub P r i v a t e Sub IViewUpdateEvents-AfterRedraw(TheViews0 T h e M o d e l s O As M o d e l R e f e r e n c e , B y V a l DrawMode As M s d D r a w i n g M o d e ) I f DrawMode
=
As V i e w ,
msdDrawingModeNorma1 T h e n
D i m F F i l e As L o n g
FFile
=
FreeFile
Open " C : \ M i c r o S t a t i o n V B A \ T i m e T r a c k . t x t "
For A p p e n d As # F F i l e
P r i n t I I F F i l e , " R E D R " & v b T a b & Now & v b T a b & Application.UserName
& vbTab &
ActiveDesignFile.Name C1 o s e # F F i 1 e End I f End Sub P r i v a t e Sub IViewUpdateEvents-BeforeRedraw(TheViews0 T h e M o d e l s O As M o d e l R e f e r e n c e , B y V a l DrawMode As M s d D r a w i n g M o d e )
As V i e w ,
End Sub P r i v a t e Sub MSApp-OnDesignFi 1 eC1 o s e d ( B y V a 1 D e s i g n F i 1 eName As S t r i n g )
D i m F F i l e As L o n g
FFile
=
FreeFile
D i m X S p l i t O As S t r i n g
XSplit
=
Split(DesignFileName, " \ " )
Open " C : \ M i c r o S t a t i o n P r i n t #FFile,
VBA\TimeTrack.txt"
F o r A p p e n d As # F F i l e
"CLDS" & v b T a b & Now & v b T a b &
Application.UserName
& vbTab &
-
XSpl it ( U B o u n d ( X S p 1 i t ) C1 o s e # F F i 1 e End Sub
MSApp-OnDesignFileOpened(ByVa1 D i m F F i l e As L o n g
P r i v a t e Sub
D e s i g n F i l e N a m e As S t r i n g )
D i m X S p l i t O As S t r i n g XSplit FFile
= =
Split(DesignFileName, " \ " ) FreeFile
Open " C : \ M i c r o S t a t i o n Print #File,
V B A \ T i m e T r a c k . t x t " F o r A p p e n d As # F F i l e
" O P E N " & v b T a b & Now & v b T a b &
Application.UserName
& vbTab &
XSpl it ( U B o u n d ( X S p 1 i t )
-
I Tracking Time I
493
C1 o s e B F F i 1 e End S u b
Let’s look at the events we are capturing: Class-l nitialize
Triggered when the class is first opened.
C Ia ss-Te rminate
Triggered when the class is terminated.
IViewUpdateEvents-AfterRedraw
A member of the IViewUpdateEvents interface. Add this class as an EventsHandler.This event is triggered when a view is redrawn.
OnDesignFileClosed
A member of the Microstation application object. When a file is closed, we are given the name of the file.
OnDesignFileOpened
A member of the Microstation application object. When a file is
opened, we are given the name of the file.
As the code suggests, we write our time logging data into an ASCII text f i l e and give each captured event a four-letter abbreviation, such as “INIT’: “TERM”, “REDR’: “CLOS’: and “OPEN”. This tells us what happened at the date and time specified. A tab character separates each field in the text file. Since the code i s written in a class module, use code in a code module t o call up the class so the class can capture the events. S u b TestTimeTrack( 1 S e t myME
=
New c l s T i m e T r a c k
AddModel A c t i v a t e E v e n t s H a n d l e r myME End S u b
494
I Chapter 21:VBA for CAD Managers I
Here is the output of the Class:
. Make a small change to log the full path of the DGN file. Instead of splitting the file name given to us, use the parameter “DesignFileName” in your print statement.
AUTO-LOADAND AUTO-RUN Few things strike more terror into the eyes of computer users than telling them that software is tracking their computer usage. If you test the code above, you will see that it works but you don’t want the user to be responsible for turning it on. To be effective, load and execute without any user intervention.
Loading a project automatically is the easy part. In the VBA Project Manager (Utilities > Macro > Project Manager), click in the Auto Load column to toggle the AutoLoad status of any project. The next time Microstation opens, any VBA project marked as AutoLoad will be loaded. Now that our project is AutoLoading, how do we automatically run a procedure? Sub O n P r o j e c t L o a d ( ) TestTimeTrack End Sub
I Auto-Load and Auto-Run I
495
The O n P r o j e c t L o a d feature in Microstation VBA is easy to use. Enter the procedure named “OnProjectLoad()”, then anything placed in the procedure will be executed when Microstation starts. Of course, even though On P r o j e c t L o a d is an incredible feature, use it with restraint. VBA does not ask what the code inside O n P r o j e c t L o a d is doing. It just begins executing it. If we have code that begins processing data, opening and closing files and a host of other things, it could cause problems. The result could be that, when a user starts Microstation, it is executing code placed in an O n P r o j e c t L o a d procedure in an AutoLoad Project but looks as though Microstation has locked. After saving your project, exit out of Microstation. Opening Microstation again causes the INIT event to be logged into your logging file. So, at this point the code works on your development machine. Imagine being so excited about this new project that you immediately place it on a shared drive so everyone can begin using it today. You go from machine to machine, load the project, and set it to AutoLoad. You don’t tell the users why you are doing this, but ask them to shut down and restart Microstation. One by one, you do this but all of a sudden something happens.
Windows allows us to share a folder and we can also share a file. But when more than approximately 20 people attempt to open a file at the same time, Windows begins to complain. So how do you support 100 users? Place the VBA Project on their individual machines? No. Please, no.
MS-VBA-OPEN-I
N-MEMORY
The MS-VBA-OPEN-IN-MEMORY configuration variable allows us to specify when VBA Projects should be opened in memory instead of maintaining a handle on the project file. By default, this variable is not defined because opening the file from disk is not normally a problem.
496
I Chapter 21:VBA for CAD Managers I Let’s discuss the acceptable values for this variable, define it, and set its value. Opens all VBA Projects in memory. This removes the 20-user
To add the MS-VBA-OPEN-IN-MEMORY variable, go to the Microstation Workspace > Configuration menu item, then scroll down to the Visual Basic for Applications category
You can see configuration variables already in use by Microstation that pertain to VBA.
I Auto-Load and Auto-Run I
497
Click the New button to add a new configuration variable.
t
The name of the variable is MS-VBA-OPEN-IN-MEMORY. We discussed the values: here enter “readonly” as the value to manage which files will be opened in memory by setting the ReadOnly flag in Windows. Click the OK button to return to the Configurations dialog box. Click OK in the Configurations dialog box to save the configuration.
Click “Yes” to save the configuration file. Files marked as “ReadOnly” will be opened in memory.
498
I Chapter 21:VBA for CAD Managers I Marking a file as ReadOnly is simple. First unload it in the VBA Project Manager. After the file closes, browse to it in Windows Explorer, rightclick on the file, and select Properties.
OK
The Read-only CheckBox can be selected or deselected. Do not select it at this time because you have more changes to make to this project. It is a good idea when you finish a VBA project to make it “Read-only’: Click the OK or Cancel button to exit out of the File Properties dialog box. Open the file again using the VBA Project Manager to continue.
PROTECTING PROJECTS Normally, we discourage writing passwords on pieces of paper. Why? Because we don’t want anyone to find the paper and discover the password. Although the concept of protecting password is correct, forgetting a password to a protected project can be ... can be ... hmmmmm, well, devastating. Password-protecting a finished project is a good idea. Whether we are managers or marketers, we don’t want our hard-earned code to be available to just anyone.
I Protecting Projects I
499
1 In the VBA menu, select Tools > Project Properties.
The Properties menu item is different from project to project.
2 Click the Properties menu item to display the VBA Project Properties window.
OK
Two tabs appear. Use the first, General, to give your project a name and description. The Help and Compile areas are outside of the scope of this book so we won’t discuss them here.
500
I Chapter 21:VBA for CAD Managers I Use the Protection tab to set the Project’s password. Projects set as “Locked for viewing can be loaded and procedures can be executed from the VBA Project Manager but they cannot be opened in the VBA environment for modifications unless the correct password is supplied.
3 Lock your current project for viewing by selecting the CheckBox and entering a password. Use the super-secret password “dorami’: Entering a password and clicking the OK button locks the project for viewing.
4 After saving the project, unload it and then reload it using the VBA Project Manager. When you get back into VBA after loading your project, you see the project in the project list with the project tree compressed.
6
M
5 Click on the Plus symbol to expand the project results in a request for the password. Now what was that password?Was it written down somewhere?Ah, there it is: “dorami’:
After successfully entering the password, you again have access to the code in your project. Remember, you cannot modify projects opened as Read Only or opened in memory.
I Distributing VBA Projects I
501
DISTRIBUTING VBA PROJECTS VBA projects are contained in a single .mvba file. This makes them easy to distribute. Just e-mail a file to anyone in the world who has the same version of Microstation and they can use your program. Or can they? From the VBA menu, select Tools > References to display the References dialog box. A portion of the References dialog box: IAS Helper COM Component 1.0 Type Library
Each selected reference refers to a .dll or .exe file with functionality for use in our programming. The top three items appear in every Microstation VBA project. We added the Microsoft Scripting Runtime library earlier in the chapter. Before distributing projects, look at the references added to the project because their absence on someone else’s computer will cause problems. For example, while working with Microsoft Excel we add a reference to the “Microsoft Excel 11.0 Object Library’: This helps us develop more quickly and accurately as we work with Excel. The program works great on our development machine but, when placed on a computer without Microsoft Excel installed, strange things happen (and not just with
502
I Chapter 21:VBA for CAD Managers I regard to the Excel code). Functions such as UCase, LCase, Trim, etc., display errors telling us their library is not loaded.
So, when distributing VBA Applications, make sure you know exactly which references are selected and let users know what they need to have installed for your program to work correctly.
WORKING IN HIGHSECURITYMODE There are benefits to starting Microstation in High Security mode. Here are two:
1 attempting to enter the VBA area by clicking Utilities > Macro > Visual Basic Editor results in the following error:
2
attempting to open an unsigned project results in this error:
Why are these benefits? We can’t run our code. Because it is easy to write code that can damage Microstation DGN files and the Windows system as well. High Security Mode prevents unsigned projects from being loaded and executed. Of course the next question is,
I Review I
503
“How do I sign my Projects?” That is a good question. The answer, however, will not be provided here because explaining how to sign a VBA Project in this book would result in every VBA Project being signed and would defeat the purpose of using High Security Mode as well as the purpose of signing VBA projects. The main thing to keep in mind is that High Security means high security - VBA projects we create will not run in Microstation when Microstation is in High Security Mode.
From a CAD manager’s point of view, VBA can do a lot to aid in maintaining CAD standards. Productivity is important to maintain and, at times, measure. Securing VBA projects means getting productivity from the VBA projects and keeping wandering eyes from playing with your code. Auto-load and auto-run code is powerful and easy to implement. The most difficult thing is knowing when to use these powerful features to your best advantage.
22
Microstation File-Based Events Events give us the opportunity to execute code when a specific thing happens. For example, we can write a file name to an ASCII text file each time a file is opened. The event is the called OnDesi gnFi 1 e0pened. This chapter deals with Microstation File-Based Events. To illustrate the use of the events we discuss in this chapter, we will use some very powerful programming techniques. Don’t allow the use of these techniques to overshadow the fact that we are discussing file-based events. We looked at two of these events in Chapter 14 already but will address them again here. In this Chapter:
The “OnDesignFileOpened” Event The “OnDesignFileClosed” Event The “ISaveAsEvents-BeforeRemap” Event The “ISaveAsEvents-AfterRemap” Event The “ISaveAsEvents-AfterSaveAs” Event
505
506
I Chapter 22: Microstation File-Based Events I
ONDESIGNFILEOPENED The OpenDesi gnFi 1 eOpened event is part of the Microstation Application Object. Each time a DesignFile is opened, the OnDesi gnFi 1 eOpened event is triggered. Let’s begin by adding some very simple code to this event. To begin with, this event is only available when a variable is declared as an “Application”type of object using the “WithEvents”keyword. Let’s create a new Class Module and name it clsS aveAs. D i m W i t h E v e n t s myMS As A p p l i c a t i o n
Where is this declaration made? In the General Declarations area of the Class Module clsSaveAs. When the variable myMS is declared, the events of this Object display in the Procedure ComboBox in the Class Module.
D i m WithEvents m y M S A s Application
Selecting OnDesi gnFi 1 eOpened in the ComboBox results in the automatic declaration of the OnDesi gnFi 1 eOpened Event. P r i v a t e Sub
myMSpDnDesignFileDpened(ByVal D e s i g n F i l e N a m e As S t r i n g )
D i m f f i l e As L o n g ffile
=
FreeFile
Open “ C : \ M i c r o S t a t io n V B A \ F i 1 eDpen . t x t ” F o r A p p e n d As {if f i1 e P r i n t { i f f i l e , Now & v b T a b & D e s i g n F i l e N a m e Close S i f f i l e End Sub
Here is the code in the OnDesi gnFi 1 eOpened event. It is very simple. We are writing the datehime and the file name to an ASCII Text file at C:\MicroStation VBA\FileOpen. txt. Writing the code is simple. But we cannot execute the code in an event in the same manner as we do when we place code in a Code Module. We will discuss how we get a Class “up and running later in the chapter.
I OnDesignFileClosed I
507
ONDESIGN FILECLOSED This event is triggered when a file is closed. We could write code to write to an ASCII file just as we did with the OnDesignFi 1 eOpened event. But there are far more powerful and useful things we can do. Let’s do some brainstorming on what we could do with the OnDesi gnFi 1 eC1 osed event. A user closes a file in Microstation. Is it the end of the day? Is this the last time the file will be opened this week? What changes were made while the file was open? Who was using the file when it was closed? What time was it closed? Had the file been opened earlier in the day? We don’t have space in this book to write 100 different applications making use of the On Des i gn Fi 1 eC 1 osed event. We have space for exactly one example. When a file is closed, it may be useful to “capture”the file at the time it is closed. Of course, we could copy the file to a folder. This would accomplish the task but could take up more hard drive space than we want to dedicate to this purpose. If we copy the file but it is zipped (compressed into a .zip file), we would save some disk space. If we place the file in an existing zipped folder or file, this may be even more helpful. So, how do we compress multiple Microstation DGN files into a single zip file? VBA is not supposed to be that powerful. Right? Well, we could spend money on a third-party DLL or ActiveX Control. But do we want to spend money when we don’t need to?
Windows XP introduced the use of Compressed (zipped) Folders. A Compressed (zipped) Folder is essentially a zip file (.zip extension) that Windows treats as a folder. Files can be copied to and pasted from a Compressed (zipped) Folder using standard Windows functionality. Let’s leverage this new Windows XP functionality so we can zip files using VBA. Before we write any code we need to add a Reference to the “Microsoft Shell Controls and Automation” object in VBA (Tools > References). When we do so, we have access to some powerful features developed by Microsoft for developers (in this case, we are the developers). The Shell32.dll Object can do a lot of things. We will only scratch the surface as we discuss a couple of events in this chapter.
508
I Chapter 22: Microstation File-Based Events I As we will see later in this chapter, we will find it is easy to add files to an existing zip file (compressed folder). But if the file does not exist, we need to create it.
Nearly all files we use on a daily basis have two identifying features. The first is the file extension. For example, when we see a file with a .dgn file extension, we instinctively refer to it as a Microstation file. But there is nothing keeping us from changing the file extension of a .txt file to .dgn. So, even though the file extension is a good indication as to what type of file we are looking at, we do not have any guarantees. The second identifying feature found in most files is a file header. A file header consists of a specific number of bytes at the beginning of a file that helps programs to verify the file type. A zip file (or a compressed zipped folder) header consists of 24 bytes. How do we know this? Creating a new zipped folder in Windows and opening the file in a Hex Editor shows us the byte values of each byte in the file. The first four byte values in a zip file are 80, 75, 5, and 6. The next 20 bytes have byte values of 0 (zero). So, we can create an empty zip file by writing Chr(80), Chr(75), Chr(5), Chr(6), and then 20 Chr(0) values. This writes a zip file header to a file and once this is done, Windows XP recognizes the file not only because of the file extension but also because of the file header. Other zip file readerdwriters also recognize the file as a legitimate zip file. Of course, a zip file is not very useful if it is empty.
Once the zip file is created (if it did not already exist), we can copy files to the 'file/folder' by using the Shell library. The procedure Copy F i 1 e T o Z i p F i 1 e is placed in the Class Module, not the Code Module. S u b CopyFileToZipFile(ZipFi1e As S t r i n g , F i l e T o C o p y As S t r i n g , C o p y F i l e A s As S t r i n g )
D i m f f i l e As Long D i m m y s h e l l As New S h e l l D i m z i p F o l d e r As S h e 1 1 3 2 . F o l d e r 3
ffile
=
FreeFile
If Dir(ZipFi1e)
=
" "
Then
Open Z i p F i l e F o r O u t p u t As ] I f f i l e P r i n t { I f f i l e , Chr(80) & Chr(75) & Chr(5) & Chr(6) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) &
~
-
-
I OnDesignFileClosed I
509
Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) Close ]Iffile End If Set zipFolder = myshell .Namespace(ZipFile) If StrComp(FileToCopy, CopyFileAs) <> 0 Then Fi 1 eCopy Fi 1 eToCopy, Copy Fi 1 eAs zipFolder.CopyHere CopyFileAs Ki 1 1 Copy Fi 1 eAs Else zipFolder.CopyHere CopyFileAs End If End Sub
The first thing we do is check to see if the .zip file exists. If not, we create the file and print to the file using standard File I/O (Input/Output) commands we have already used multiple times in this book. Our function Copy Fi 1 eToZipFi 1 e allows us to specify the file we want to copy and the file name we want to use inside the zip file. We will see why this (the ability to specify the file name used by the .zip file) is important later. The variable myshell is declared as a “New Shell” object. This exposes the Referenced Object’s Methods, Properties, and Events. We use the zip file name to get a folder using the Namespace method of the Shell Object. If the FileToCopy variable is not the same as the CopyFileAs variable, we copy FileToCopy to CopyFileAs, and then we Copy the CopyFileAs file to the zip File. The last thing we do is Kill (delete) the file. If, however, the FileToCopy variable is the same as the CopyFileAs variable, we simply copy the design file to the zip file without doing any other copying or killing (that function sounds so violent, doesn’t it?).
NOTE: Among others, the web page at http://www.codeproject.com/ csharp/DecompressWinShellAPICS.asp describes the use of the Shell Object to work with Windows XP’S Compressed (zipped)folders.
The CopyFileToZipFile procedure is now ready to be used by the OnDesignFileClosed event. Private Sub myMS-OnDesignFileClosed(ByVa1 DesignFileName As String) CopyFi 1 eToZipFi le “C:\MicroStation VBA\Fi 1 eC1 osed.zip” ,
510
I Chapter 22: Microstation File-Based Events I DesignFileName, DesignFileName &
'I."
& CLng(Timer
* 1000)
End Sub
We use the Copy F i 1 e T o Z i p F i 1 e procedure by specifying the zip file we want the design file copied into, the File name (full path) to use as the source file, and a Destination file. The Source and Destination files can be the same. So, why would we want to provide a different filename for the zip file? The reason we want the ability to supply different file names is that we may want the same design file (tusksdgn for example) to be in the same zip file multiple times. We accomplish this by appending a number to the end of the file. What number? In this example, we are using the Timer function which tells us the number of decimal seconds that have transpired since midnight and multiplying it by 1,000 which gives us the number of milliseconds since midnight. This gives us a fairly good degree of confidence that we will not attempt to copy the same file with the same numeric appendage into the same zip file.
ISAVEASEVENTS INTERFACE The ISaveAsEvents Interface includes ISaveAsEvents-BeforeRemap, ISaveAsEvents-AfterRemap, and ISaveAsEvents. The ISaveAsEvents-BeforeRemap event is a member of the ISaveAsEvents Interface. To use members of an interface, we declare the interface in our Class Module. Here is the declaration we need to put into the Class Module we have been working with: Implements ISaveAsEvents
As with previous discussions on Interfaces, we need to make sure that all events (methods) of the Interface are declared before we begin putting code into any of them. Private Sub ISaveAsEvents-BeforeRemap( ByVal TheDesignFile As DesignFile, ByVal SavedFormat As MsdDesignFileFormat, ByVal DestinationFilename As String) ~
End Sub
I ISaveAsEvents Interface I P r iv a t e S u b I S a v e As Eve n t s-A f t e r Re ma p (
511 -
B y V a l T h e D e s i g n F i l e As D e s i g n F i l e ,
-
ByVal SavedFormat As M s d D e s i g n F i l e F o r m a t , ByVal D e s t i n a t i o n F i lename As S t r i n g ) End Sub P r i v a t e Sub I S a v e A s E v e n t s - A f t e r S a v e A s O End Sub
Three events belong to the ISaveAsEvents Interface. They are shown above in the order in which they are executed. Since the A f t e r S a v e A s event is the last event executed, this is where we place the code that actually does something. But before we do so, let’s take a look at the event. In the B e f o r e R e m a p and A f t e r R e m a p events, we are told what the DesignFile, SavedFormat, and DestinationFilename parameter values are. The AfterSaveAs event does not tell us any of this. So, we need to capture the data when it is given to us. We will declare three variables in the General Declarations area of the Class Module to hold the values given to us in the A f t e r Remap event so we can use the information in the A f t e r S a v e As event. D i m F i l e s a v e d As S t r i n g D i m F i l e F o r m a t As L o n g D i m F i l e p r e v i o u s As S t r i n g
Now that the variables are declared, they can be used in the A f t e r Remap event as follows: P r iv a t e S u b I S a v e As Eve n t s-A f t e r Re ma p (
-
B y V a l T h e D e s i g n F i l e As D e s i g n F i l e ,
-
ByVal SavedFormat As M s d D e s i g n F i l e F o r m a t , ByVal D e s t i n a t i o n F i lename As S t r i n g ) F i 1e S a v e d F i 1eFormat
=
Fileprevious End Sub
D e s t i n a t i o n F i lename
=
SavedFormat =
TheDesignFile.Ful1Name
512
I Chapter 22: Microstation File-Based Events I OK. We have captured the DesignFile going into the SaveAs command as well as the file format the file is going to be saved as, and the DestinationFilename.What can we do with this?
In a previous example, we added design files to a zip file when the design files were closed. This was a powerful example of the ease with which great functionality can be implemented with only a few lines of code in VBA (and of course, a little knowledge mixed in for good measure). Can we top that functionality in the A f t e r S a v e A s event? It’s time to brainstorm again. The possibilities are endless. Each function we discuss could be performed on the “TheDesignFile”parameter or the “DestinationFilename”parameter. We could FTP the file to a server half-way around the world. We could e-mail the file to different people based on which project the file is in. We could open the file and extract information and place it in a database. Each of these examples would be useful and could be easily implemented. Let’s do something else, though. When the user performs a SaveAs, the file that had been opened is given a new file name and optionally, saved as a different file type. The scenario we will work with right now is, when a file experiences a “SaveAs” and is saved as an AutoCAD .dwg file, we w ill take the original design file (it could already be an AutoCAD .dwg file) and we will prepare it to be written to a CD. Once again, in this example, we are using functionality introduced in Windows XP. Windows XP allows us to copy and paste files to a CD or DVD writing drive. When we do this, the files are placed into a temporary storage location until we decide to actually write them to a CD. When we ‘Send’ a file to our CD writer, we see a message informing us we have files waiting to be written to CD:
So, how do we get files into this temporary storage location? Where is it? Let’s use the Shell Object we just finished working with in the last example in this example as well.
A new procedure needs to be created in the Class Module we used in the previous example. This procedure will be named CopyFi 1 e t o C D and it
I ISaveAsEvents Interface I
513
will take one parameter, the file that is to be copied to the CD. Here’s the code. A discussion of the code follows. S u b CopyFileToCD(Fi1eToCopy A s String)
Dim myshell A s New Shell Dim cdFolder A s She1132.Folder3 Set cdFolder = myShell.Namespace(59) If Not cdFolder Is Nothing Then cdFol der .CopyHere Fi leToCopy, 0 End If End S u b
The first thing we need to do is find out where the temporary storage location is for the user that is logged in. Now, how are we going to do that? We can discover this by supplying a number of 59 to the Namespace Method of the Shell Object. If we do this and the returned folder is not “Nothing (in other words, if the folder is found), we copy the supplied file into the cdfolder. That’s all there is to it. One of the great things about this procedure is that we don’t need to purchase CD writing add-ins to make this work. If the user has Windows XP, we can make use of this procedure. Now, we need to remember that the Copy F i 1 eToCD procedure does not actually burn the CD. It only copies the file to the staging area to burn the CD. When we are ready to burn the CD, we place a CD-R or CD-RW into the CD burner, select the CD drive in Windows Explorer, and then go to the Explorer menu File > Write these files to CD
New
These menu picks begin the process of writing the files in the temporary CD folder to the CD. So, we now have code that prepares files for being written to a CD. How do we use it? We will use it in the ISaveAsEvents-AfterSaveAs event. Private S u b ISaveAsEvents-AfterSaveAsO Select Case Fi leFormat Case msdDesignFileFormatCurrent Case msdDesignFileFormatDWG CopyFi 1 eToCD Fi 1 ePrevi o u s
514
I Chapter 22: Microstation File-Based Events I Case m s d D e s i g n F i 1 e F o r m a t D X F Case m s d D e s i g n F i 1 e F o r m a t U n k n o w n Case m s d D e s i g n F i 1 e F o r m a t V 7 Case m s d D e s i g n F i 1 e F o r m a t V 8 End S e l e c t End
Sub
The A f t e r S a v e A s event is the last event to execute when a SaveAs is executed by the user. But the event itself does not tell us what the source file name, destination file name, or format is. We collect this information in the A f t e r R e m a p event so we can use it in the A f t e r S a v e A s event. The example above shows how to use the Copy F i 1 eToCD procedure we created but it only executes it when the SaveAs command was used to save an AutoCAD .dwg file. We could place the same line of code under other “Case” statements to accommodate other file formats or we could get rid of the S e l e c t Case structure altogether and use Copy F i 1 eToCD every time a SaveAs occurs. We have addressed the functionality in our new Class Module clsSaveAs in a couple of sections. Let’s take a look at the entire Class Module from beginning to end. Remember, the variable declarations appear in the General Declarations area of the Class Module. Particular attention should be paid to the use of the variables FileSaved, FileFormat, and FilePrevious. Here is the Class Module code from beginning to end: D i m W i t h E v e n t s myMS As A p p l i c a t i o n ImDlements ISaveAsEvents
D i m F i l e S a v e d As S t r i n g D i m F i l e F o r m a t As L o n g D i m F i l e P r e v i o u s As S t r i n g Private
Sub C l a s s - I n i t i a l i z e ( 1
S e t myMS End
=
A p p l ic a t i o n
Sub
Private
Sub ISaveAsEvents-AfterRemap( B y V a l T h e D e s i g n F i l e As D e s i g n F i l e ,
-
I ISaveAsEvents Interface I
515
B y V a l S a v e d F o r m a t As M s d D e s i g n F i l e F o r m a t ,
-
B y V a l D e s t i n a t i o n F i l e n a m e As S t r i n g ) F i 1eSaved
=
F i 1e F o r m a t
=
Fileprevious
D e s t i n a t i o n F i lename SavedFormat =
TheDesignFile.FullName
End Sub P r i v a t e Sub
ISaveAsEvents-AfterSaveAsO
S e l e c t Case F i l e F o r m a t Case
msdDesignFileFormatCurrent
Case m s d D e s i g n F i l e F o r m a t D W G Copy F i 1 eToCD F i 1 e P r e v i ous Case m s d D e s i g n F i l e F o r m a t D X F Case m s d D e s i g n F i l e F o r m a t U n k n o w n Case m s d D e s i g n F i l e F o r m a t V 7 Case m s d D e s i g n F i l e F o r m a t V 8 End S e l e c t End Sub P r i v a t e Sub
ISaveAsEvents-BeforeRemap(
B y V a l T h e D e s i g n F i l e As D e s i g n F i l e , B y V a l S a v e d F o r m a t As M s d D e s i g n F i l e F o r m a t , B y V a l D e s t i n a t i o n F i l e n a m e As S t r i n g ) End Sub P r i v a t e Sub
myMS-OnDesignFileClosed(ByVa1 D e s i g n F i l e N a m e As
C o p y F i 1e T o Z i p F i l e " C : \ M i c r o S t a t i o n
String)
VBA\Fi 1 eC1 o s e d . z i p " ,
DesignFileName, DesignFileName &
"." &
CLng(Timer
*
1000)
End Sub P r i v a t e Sub
myMS-OnDesignFileOpened(ByVa1
-
D e s i g n F i l e N a m e As
String)
D i m f f i l e As Long ffile
=
FreeFile
Open " C : \ M i c r o S t a t i o n Print ]Iffile, Close ] I f f i l e End Sub
VBA\FileOpen.txt"
F o r Append As B f f i l e
Now & v b T a b & D e s i g n F i l e N a m e
516
I Chapter 22: Microstation File-Based Events I S u b C o p y F i l e T o C D ( F i 1 e T o C o p y As S t r i n g ) D i m m y s h e l l As New S h e l l
D i m c d F o l d e r As S h e 1 1 3 2 . F o l d e r 3 Set cdFolder
=
myShell.Namespace(59)
I f N o t c d F o l d e r Is N o t h i n g Then c d F o l d e r . C o p y H e r e F i 1 eToCopy , 0 End I f End S u b
S u b C o p y F i l e T o Z i p F i 1 e ( Z i p F i 1 e As S t r i n g , F i 1 eToCopy As S t r i n g , C o p y F i l e A s As S t r i n g ) D i m f f i l e As L o n g D i m m y s h e l l As New S h e l l D i m z i p F o l d e r As S h e 1 1 3 2 . F o l d e r 3
ffile
=
FreeFile
If Dir(ZipFi1e)
=
"I'
Then
Open Z i p F i l e F o r O u t p u t As ] I f f i l e P r i n t { i f f i l e , Chr(80) & Chr(75) & Chr(5) & Chr(6) & Chr(0) & Chr(0) & Chr(0) & Chr(0) &
-
Chr(0) & Chr(0) & Chr(0) & Chr(0) &
-
Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) &
-
~
~
Chr(0) & Chr(0) & Chr(0) & Chr(0) C1 o s e Siffi 1 e End I f Set zipFolder
=
myshell .Namespace(ZipFile)
I f StrComp(FileToCopy,
CopyFileAs)
<>
0 Then
F i 1 eCopy F i 1 eToCopy , Copy F i 1 eAs zipFolder.CopyHere
CopyFileAs
K i 11 C o p y F i 1 eAs Else zipFolder.CopyHere
CopyFileAs
End I f End S u b
Thus far, we have written a lot of code but we have been unable to run it because the code is contained in a Class Module. Let's discuss how to make use of the Class Module code. The code in a Class Module must be called up by code in a Code Module or UserForm. We will use a Code Module for this example.
I Review I
517
Sub TestSaveAs() D i m mySaveAs As New c l s S a v e A s
A d d S a v e A s E v e n t s H a n d l e r mySaveAs End S u b
This is pretty simple. We declare a variable as a clsSaveAs (the name of the Class Module we just finished working in) and add it to the SaveAsEventsHandler. When this is done, the clsSaveAs Object is brought into memory and begins responding to the SaveAs events as well as the OnDesignFileOpened and OnDesignFileClosed events.
VBA can be a powerful development environment. In this chapter, we zipped files (compressed them) and prepared files for burning to a CD. Windows XP helped us accomplish these tasks. The same tasks can be accomplished in other Operating Systems but would be programmed differently. Independent of the Operating System or the desired functionality,we now have the knowledge to execute code when the user performs a SaveAs or when a file is opened or closed. Before beginning any project, we need to take time to brainstorm on what functionality we want in the project. We should think about the best possible functionality, the ultimate program. Limiting ourselves because we aren’t sure how we can accomplish a task or two or three or more is self-limiting. Even the oldest dogs can learn new tricks - in this case, VBA tricks.
23
Responding to Microstation Attachment Events The next four chapters (this one included) continue to deal with responding to events in Microstation. This chapter reviews the IAttachmentEvents Interface. Five events are exposed. In this Chapter:
The IAttachmentEvents Interface AfterAttach AfterDetach AttachmentModified BeforeAttach BeforeDetach
519
520
I Chapter 23: Responding to Microstation Attachment Events I
THEIATTACHMENTEVENTS INTERFACE Let's create a new Class Module named clsAttachmentEvents. Each event implemented by the IAttachmentEvents Interface must be declared. Adding simple Debug.Print statements inside each event helps us to understand the order in which the events are triggered. P r i v a t e Sub
IAttachmentEvents-AfterAttach(ByVa1
-
T h e A t t a c h m e n t As A t t a c h m e n t ) Debug.Print "AfterAttach" End Sub P r i v a t e Sub
IAttachmentEvents-AfterDetach(ByVa1 T h e A t t a c h m e n t As A t t a c h m e n t )
Debug.Print "AfterDetach" End Sub P r i v a t e Sub IAttachmentEvents-AttachmentModified(ByVa1 T h e A t t a c h m e n t As A t t a c h m e n t ) Debug . P r in t
"
A t t a c h m e n t Mod if ie d "
End Sub P r i v a t e Sub
IAttachmentEvents-BeforeAttach(Fi1eName As S t r i n g ,
-
A1 1 o w A t t a c h m e n t As Boo1 e a n ) Debug.Print "BeforeAttach" End Sub P r i v a t e Sub
IAttachmentEvents-BeforeDetach(ByVa1 T h e A t t a c h m e n t As A t t a c h m e n t )
Debug.Print "BeforeDetach" End Sub
Two of the events refer to attaching a reference file and two refer to detaching a reference file.
AFTERATTACH P r i v a t e Sub
IAttachmentEvents-AfterAttach(ByVa1 T h e A t t a c h m e n t As A t t a c h m e n t )
End Sub
I AfterAttach I
521
After a DesignFile is attached to the current design file, the AfterAttach Event is triggered. When this event occurs, we can be certain the file is actually attached and is available for processing. The parameter “TheAttachment”is provided in the event so we can begin working with the attachment immediately. The “TheAttachment” Parameter is declared as an Attachment type of Object. Perhaps understanding this object a little better will help us to know what we can do with this event. The list is several pages long so we will just take a look at a few of the properties and methods. I3 Sub AddElement(E1ement As Element)
B Sub AddElements(Elements()As -Element) B Property AttachName As String {read-only} I3 Property Description As String I3 Property DesignFile As DesignFile {read-only}
B Property DisplayFlag As Boolean B Property Is3D As Boolean {read-only] I3 Property IsActive As Boolean {read-only} I3 Property IsReadOnly As Boolean {read-only}
B Property IsTrueScale As Boolean {read-only} I3 Property Level As Level I3 Property Levels As Levels {read-only} I3 Property LogicalDescription As String
B Property LogicalName As String B Property Masterorigin As Point3d {read-only} I3 Sub Move(0ffset As Point3d, ApplyToClipElement As Boolean) I3 Property Name As String
B Function Reattach(Fi1eNameAs String, ModelName As String) As Attachment I3 Sub Redraw([DrawMode As MsdDrawingMode = msdDrawingModeNormal])
522
I Chapter 23: Responding to Microstation Attachment Events I Sub RemoveElement(Element As Element) Sub ReplaceElement(OldE1ementAs Element, NewElement As Element) Property RevisionNumber As String Sub Rewrite() El Sub Rotate(Pivot As Point3d, Aboutx As Double, AboutY As Double, AboutZ As Double, Viewspecifier As Variant)
Property Rotation As Matrix3d {read-only} Property ScaleFactor As Double Function Scan([Scancriteria As ElementScanCriteria])As ElementEnumerator Sub SelectElement(E1ementAs Element, [DisplayAsSelectedAs Boolean = True]) Property Transparency As Double As we look at these properties and events, let's think about how we could make use of them AFTER the attachment is attached. Let's look at the DesignFile Property. When an Attachment takes place, we can get the DesignFile of the Attachment and get the DesignFile's Path. Let's add a Date/Time Stamp with the Attachment's file name to the Active Model. P r i v a t e Sub
IAttachmentEvents-AfterAttach(ByVa1 T h e A t t a c h m e n t As A t t a c h m e n t )
D i m T x t P t As P o i n t 3 d
TxtPt.X
=
0
TxtPt.Y
=
-100
D i m R o t M a t r i x As M a t r i x 3 d D i m m y T e x t As T e x t E l e m e n t
CreateTextElementl(Nothing, N o w & " . . . " & TheAttachment.DesignFile.Path, T x t P t , R o t M a t r i x ) ActiveModelReference.AddElement m y T e x t S e t myText
=
End S u b
Each time an Attachment takes place, we are writing the Path of the Attachment's DesignFile as Text to the ActiveModelReference. As the code stands right now, the text will be added to the same place whether
I AfterAttach I
523
it is the first attachment or the hundredth. We could add code to change the position of the Text Insertion Point, etc., but we will move to another example that makes use of the AfterAttach Event. P r i v a t e Sub
IAttachmentEvents-AfterAttach(ByVa1 T h e A t t a c h m e n t As A t t a c h m e n t )
D i m F F i l e As L o n g FFile
=
FreeFile
Open " C : \ M i c r o S t a t i o n
VBA\AttachrnentLog.txt"
F o r Append A s # F F i l e
P r i n t # F F i l e , Now & v b T a b & A c t i v e D e s i g n F i 1 e . P a t h TheAttachment.DesignFi1e.Path
& vbTab &
-
C1 o s e B F F i 1 e End Sub
If we follow the code shown directly above, we will see that we are writing the ActiveDesignFile's Path and the Attachment's Path to an ASCII text file named Attuchrnentlog.txt. This log file can be used to track file dependencies. One more example demonstrates additional use of the Attachment's Properties: P r i v a t e Sub IAttachmentEvents-AfterAttach(ByVa1
-
T h e A t t a c h m e n t As A t t a c h m e n t )
D i m P t A As P o i n t 3 d D i m P t B As P o i n t 3 d D i m myRange As Range3d D i m MyRec As L i n e E l e m e n t D i m L i n e P t s ( 0 To 4 ) As P o i n t 3 d
myRange
=
TheAttachment.Range(True)
PtA
=
myRange.High
PtB
=
myRange.Low
LinePts(O1.X
=
PtA.X:
LinePts(O).Y
=
PtA.Y
LinePts(l1.X
=
PtB.X:
LinePts(l).Y
=
PtA.Y
LinePts(21.X
=
PtB.X:
LinePts(Z).Y
=
PtB.Y
LinePts(31.X
=
PtA.X:
LinePts(3).Y
=
PtB.Y
LinePts(4l.X
=
PtA.X:
LinePts(4).Y
=
PtA.Y
S e t MyRec
=
Application.CreateLineElementl(Nothing,
LinePts)
A c t i v e M o d e l R e f e r e n c e . A d d E l e m e n t MyRec End Sub
This example draws a rectangle around the outer rectangular boundary of the Attachment.
524
I Chapter 23: Responding to Microstation Attachment Events I We have looked at three examples of using the AfterAttach Event. These three examples cannot exist in the Class simultaneously. The example we want to see working should remain un-commented but the remainder of the AfterAttach examples should be commented out in the code. As with other Interfaces, we need to add a Class to the Event Handler. The Class we are working in now is named clsAttachmentEvents. Here's the code that adds the Class to the Event Handler: Sub
TestAttachmentsA( 1 D i m myAE As New c l s A t t a c h m e n t E v e n t s
AddAttachmentEventsHandler
myAE
End Sub
AFTERDETACH Two events relate to the Detaching of Attachments. One is Before Detach and the other is After Detach. As the names imply, Before Detach occurs prior to After Detach. The After Detach event is the last opportunity we have to do anything with the Attachment. Let's try some code that could be used to notify someone that an Attachment has been detached. P r i v a t e Sub
IAttachmentEvents-AfterDetach(ByVa1
-
T h e A t t a c h m e n t As A t t a c h m e n t ) Shell "c:\Program F i l e s \ I n t e r n e t Explorer\iexplore.exe " " "
.
.
.
h t t p : / / www t r a c kmy d g n d r a w in g s c om / 1 o g a s p? f i1 en a me=" &
ActiveDesignFi1e.Name
& "&attachment=" &
The A t t a c h m e n t . Des ig n F i 1 e . Name &
" " " "
"
&
~
-
, vbMaximizedFocus
End Sub
The She1 1 function is a standard VBA function. In this example, we use it to execute Internet Explorer, going to a fictitious web site with the ActiveDesignFile.Name and the "TheAttachmentDesignFi1e.Name". If the log.asp file can read these parameters in the HTTP request, we now have a web server tracking files as they are detached.
I AttachmentModified Event I
525
A~ACHMENTMODIFIED EVENT The AttachmentModified Event gives us the same information as the AfterAttach and AfterDetach events. We are given the parameter "TheAttachment" with which to work. This event may be useful to track the fact that changes have been made to an attachment but will not help us with the nature of the modification. We would need to look at additional events to get more detailed information on that. Private Sub IAttachmentEvents-AttachmentModified( ByVal TheAttachment As Attachment) 0e b u g . P r i n t At t a c hme n t M o d i fi e d End Sub "
"
BEFOREA~ACH EVENT Private Sub IAttachmentEvents-BeforeAttach(Fi1eName As String, AllowAttachment As Boolean)
The BeforeAttach Event is triggered before an Attachment takes place. We are supplied with the FileName and are given the opportunity to cancel the attachment by setting the AllowAttachment parameter to False. Private Sub I A t t a c h m e n t E v e n t s - B e f o r e A t t a c h ( F i 1 e N a m e As String, A1 1 owAttachment As Boo1 ean) If GetAttr(Fi1eName) And vbReadOnly Then MsgBox "Attaching Read-only Files is Prohibited." A1 1 owAttachment = Fa1 se End If End Sub
In this example, if the file that is being attached is set as ReadOnly, we show a MessageBox stating the attaching read-only files is prohibited. After the MessageBox, we set the AllowAttachment parameter to False. This means the user is not shown the Attachment dialog box. If an attempt is made to attach a file and the file is not read-only, we do nothing and the Attach File procedure continues.
526
I Chapter 23: Responding to Microstation Attachment Events I
BEFOREDETACH EVENT P r i v a t e Sub
IAttachmentEvents-BeforeDetach(ByVa1 T h e A t t a c h m e n t As A t t a c h m e n t )
The last event we need to discuss is the BeforeDetach Event. Once again, an Attachment Object is provided to us as a parameter of the event. This event is triggered just prior to the AfterDetach Event. We do not have the ability to keep a 'detachment' from occurring. So, one of the few things we can do is log the fact that the detachment took place. P r i v a t e Sub
IAttachmentEvents-BeforeDetach(ByVa1 T h e A t t a c h m e n t As A t t a c h m e n t )
D i m F F i l e As
FFile
=
Long
FreeFile
Open " C : \ M i c r o S t a t i o n
P r i n t #FFile,
VBA\DetachrnentLog.txt"
F o r Append As # F F i l e
Now & v b T a b & A p p l i c a t i o n . U s e r N a m e & v b T a b & TheAttachment.DesignFile.Ful1Name
C1 o s e # F F i 1 e End Sub
In this example, we are writing to a log file. We capture the Date and Time, the User that is detaching the file, and the full name of the file being detached.
REVIEW The ability to attach files to an existing design file is powerful. It allows us to design more quickly and with fewer errors. Accuracy is improved because we can look at an entire design at one time. Do the walls line up with the foundation? Using the IAttachmentEvents Interface allows us to intercept events so we can track which files are being attached, detached, and modified.
24
Model Events The last chapter dealt with Attachment Events. This one deals with Model Events. Two separate interfaces expose Model-related events. The Interfaces are named “IModelActivateEvents” and “IModelChangeEvents’: To simplify matters, we will implement both Interfaces in the same Class Module. The Class Module will be named CISModelEvents.
In this Chapter:
The AfterActivate Event The BeforeActivate Event The Modelchange Event Implementing each of the Interfaces in a single Class Module makes it easy to identify the order in which these events are triggered. Here is the code in our Class Module clsModelEvents: Implements IModelActivateEvents Implements IModelChangeEvents P r i v a t e Sub I M o d e l A c t i v a t e E v e n t s A f t e r A c t i v a t e ( B y V a 1 TheModel As M o d e l R e f e r e n c e ) Debug.Print “AfterActivate:
”
& vbTab & vbTab &
TheModel.DesignFile.Name & v b T a b & TheModel.Name
527
~
I Chapter 24:Model Events I End Sub
Private Sub IModelActivateEvents-BeforeActivate(ByVa1
TheModel As
-
Model R e f e r e n c e ) D e b u g . P r i n t " B e f o r e A c t i v a t e : " & vbTab & TheModel . D e s i g n F i l e . N a m e & v b T a b & T h e M o d e l .Name End Sub P r i v a t e Sub IModelChangeEvents-ModelChange(ByVa1 T h e M o d e l As M o d e l R e f e r e n c e , B y V a l Change As M s d M o d e l C h a n g e T y p e ) D e b u g . P r i n t "Change:
"
& vbTab & vbTab & vbTab &
-
TheModel.DesignFi1e.Name & v b T a b & TheModel.Name &
"
-
"
&
ModelChange(Change1 End Sub F u n c t i o n M o d e l C h a n g e ( C h a n g e 1 n As M s d M o d e l C h a n g e T y p e ) As S t r i n g S e l e c t Case C h a n g e I n 'Active
MsdModelChangeType.mdlModelChangeBeforeActive
Case
M o d e l Change
=
"BeforeActive"
MsdModelChangeType.mdlModelChangeActive
Case
M o d e l Change
=
"Active"
'Create
MsdModelChangeType.mdlModelChangeBeforeCreate
Case
Mode 1 Change
=
"
Be f o r eC r ea t e "
MsdModelChangeType.mdlModelChangeCreate
Case
M o d e l Change
=
"Create"
'Delete
MsdModelChangeType.mdlModelChangeBeforeDelete
Case
M o d e l Change
=
"BeforeDelete"
MsdModelChangeType.mdlModelChangeDelete
Case
M o d e l Change
=
"Delete"
'Name Case
MsdModelChangeType.mdlModelChangeBeforeName M o d e l Change
Case
=
"BeforeName"
MsdModelChangeType.mdlModelChangeName M o d e l Change
'Properties
=
"Name"
-
I Model Events I
529 MsdModelChangeType.mdlModelChangeBeforeProperties
Case
Modelchange
=
"BeforeProperties"
MsdModelChangeType.mdlModelChangeProperties
Case
Mode 1 C h a n g e
=
"
P r o p e r t ie s "
'Settings
MsdModelChangeType.mdlMode1ChangeBeforeSettings
Case
Modelchange
=
"BeforeSettings"
MsdModelChangeType.mdlModelChangeSettings
Case
Modelchange
=
"Settings"
' UnCreate
MsdModelChangeType.mdlModelChangeBeforeUnCreate
Case
Modelchange
=
"BeforeUnCreate"
MsdModelChangeType.mdlModelChangeUnCreate
Case
Model Change
=
"UnCreate"
'UnDelete
MsdModelChangeType.mdlModelChangeBeforeUnDelete
Case
Modelchange
=
"BeforeUnDel e t e "
Case MsdModel ChangeType.md1 Model ChangeUnDel e t e Model Change '
=
"UnDel e t e "
PropagateAnnotationScale
Case
-
MsdModelChangeType.mdlModelChangePropagateAnnotationScale M o d e l c h a n g e = "PropagateAnnotationScale" End S e l e c t End F u n c t i o n
The Function "Modelchange" receives a constant value and 'converts' it to text. This makes it easier to see what is taking place. As with other Interfaces, the Class Module must be instantiated in a Code Module: P r i v a t e myME As c l s M o d e l E v e n t s
Sub AddEventsO RemoveEvents S e t myME
=
New c l s M o d e l E v e n t s
AddModel A c t i v a t e E v e n t s H a n d l e r myME
530
I Chapter 24:Model Events I AddModelChangeEventsHandler
myME
End Sub
Sub R e m o v e E v e n t s ( I f myME I s N o t h i n g
=
F a l s e Then
RemoveModelActivateEventsHandler myME RemoveModelChangeEventsHandler myME S e t myME
=
Nothing
End I f End Sub
When a Model is switched in a design file, we see the following results in the Immediate Window: BeforeActivate: Change: AfterActivate: Change : BeforeActivate: Change : AfterActivate: Change :
f langedvalve f langedvalve f langedvalve f langedvalve f langedvalve f langedvalve f langedvalve f langedvalve
.dgn .dgn .dgn .dgn .dgn .dgn .dgn .dgn
Untitled Sheet Untitled Sheet - BeforeActive Untitled Sheet Untitled Sheet - Active1 Model-1 Model-1 - BeforeActive Model-1 Model-1 - Active
As we can see here, the order of the events are: IModelActivateEventsBeforeActivate
IModelChangeEvents-Modelchange ( W i t h B e f o r e A c t i v e E v e n t ) IModelActivateEvents-AfterActivate IModelChangeEvents-Modelchange ( W i t h A c t i v e E v e n t )
Each event supplies us with a ModelReference so we know which model is about to be Activated. The Modelchange Event is triggered twice when the active Model is being changed. First, we get a Modelchange Event with the “BeforeActivate” constant and then a Modelchange Event with an “Active” Constant.
As with other events we have discussed, Model Events are implemented through an Interface. Doing something as simple as adding Debug.Print statements can often lead us to understanding how, when, and why events are triggered. A little creativity and some VBA know-how can result in the creation of powerful solutions.
.
25
Level Events Levels are extremely important to the organization of a design file. Since they are so important, an Interface is provided just for Level Events. The ILevelChangeEvents Interface exposes only one event. This event, however, handles twelve types of changes. They are: AfierChangeActive Aftercreate AfterDelete BeforeChangeActive BeforeDelete ChangeAttribute Changecode ChangeDisplay ChangeName ChangeName Changeparent TableRedo Tableundo 531
532
I Chapter 25: Level Events I The change type names are fairly self-explanatory.We had better look at the actual event before we continue. P r i v a t e Sub
ILevelChangeEvents-LevelChanged(
-
B y V a l C h a n g e T y p e As M s d L e v e l C h a n g e T y p e , B y V a l T h e L e v e l As L e v e l , B y V a l TheModel As M o d e l R e f e r e n c e ) End Sub
Three parameters are passed to us in the Levelchanged event. The first gives us the type of change. This is provided to us in the form of an Enumeration Constant. Values for the ChangeType parameter are: MsdLevelChangeType.msdLevelChangeAfterChangeActive = 9 MsdLevelChangeType.msdLevelChangeAfterCreate
=
2
M s d L e v e l ChangeType.msdLeve1 C h a n g e A f t e r D e l e t e
=
3
MsdLevelChangeType.msdLevelChangeBeforeChangeActive M s d L e v e l ChangeType.msdLeve1 C h a n g e B e f o r e D e l e t e
=
MsdLevelChangeType.msdLevelChangeChangeAttribute MsdLevelChangeType.msdLevelChangeChangeCode
M s d L e v e l ChangeType.msdLeve1 C h a n g e C h a n g e D i s p l a y =
MsdLevelChangeType.msdLeve1ChangeChangeParent
17
=
8
5
=
MsdLevelChangeType.msdLevelChangeChangeName
=
18
7
=
4 =
M s d L e v e l ChangeType.msdLeve1 ChangeTabl eRedo
=
15
M s d L e v e l ChangeType.msdLeve1 ChangeTabl eUndo
=
14
6
Let’s begin by using a Function to ‘convert’ the constant provided to a String and a simple Debug.Print statement. This allows us to implement the Interface and experiment with it to see when and how Level Events are handled. Implements I L e v e l ChangeEvents ‘ b e f o r e c h a n g e a c t i v e shows t h e o l d l e v e l name P r i v a t e Sub
ILevelChangeEvents-LevelChanged(
B y V a l C h a n g e T y p e As M s d L e v e l C h a n g e T y p e , B y V a l T h e L e v e l As L e v e l ,
-
-
B y V a l TheModel As M o d e l R e f e r e n c e ) D e b u g . P r i n t TheLevel.Name & vbTab & T h e M o d e l . D e s i g n F i l e . N a m e & v b T a b & TheModel.Name & v b T a b & GetChangeType(ChangeType)
II
533
End Sub F u n c t i o n G e t C h a n g e T y p e ( C h a n g e 1 n As M s d L e v e l C h a n g e T y p e ) As S t r i n g S e l e c t Case C h a n g e I n Case
MsdLevelChangeType.msdLevelChangeAfterChangeActive GetChangeType
Case
GetChangeType Case
=
"Aftercreate"
=
"
A f t e r De 1 e t e "
MsdLevelChangeType.msdLevelChangeBeforeChangeActive GetChangeType
Case
"AfterChangeActive"
MsdLevelChangeType.msdLevelChangeAfterDelete G e t C h a n g e T y pe
Case
=
MsdLevelChangeType.msdLevelChangeAfterCreate
=
"BeforeChangeActive"
MsdLevelChangeType.msdLevelChangeBeforeDelete GetChangeType
=
"BeforeDelete"
Case MsdLevelChangeType.msdLeve1ChangeChangeAttribut.e GetChangeType Case
"ChangeDisplay"
=
"ChangeName"
=
"Changeparent"
MsdLevelChangeType.msdLevelChangeTableRed0 G e t C h a n g e T y pe
Case
=
MsdLevelChangeType.msdLevelChangeChangeParent GetChangeType
Case
"Changecode"
MsdLevelChangeType.msdLevelChangeChangeName GetChangeType
Case
=
MsdLevelChangeType.msdLevelChangeChangeDisplay GetChangeType
Case
"ChangeAttribute"
MsdLevelChangeType.msdLevelChangeChangeCode GetChangeType
Case
=
=
"
C h a n g e T a b 1 e Red o "
MsdLevelChangeType.msdLevelChangeTableUnd0 G e t C ha n g e T y p e
=
"
C ha n geTa b 1 e U n d o "
End S e l e c t End F u n c t i o n
The following code is placed in a Code Module. It is used to call up the Class "clsLevelEvents". P r i v a t e myLC As c l s L e v e l E v e n t s Sub AddEvents( 1 S e t myLC
=
New c l s L e v e l E v e n t s
A d d L e v e l C h a n g e E v e n t s H a n d l e r myLC End Sub Sub RemoveEventsO
534
I Chapter 25: Level Events I I f myLC I s N o t h i n g
=
F a l s e Then
R e m o v e L e v e l C h a n g e E v e n t s H a n d l e r myLC S e t myLC
=
Nothing
End I f End S u b
Running AddEvents adds the Class clsLevelEvents to the LevelChangeEventsHandler. Now, if we make modifications to Levels in Microstation design files, we will see lines added to the Immediate Window reflecting the events. Since we can only have one Levelchanged Event in a single Class Module, the best way to implement this Event is through the use of a Select Case Statement. P r i v a t e Sub
ILevelChangeEvents-LevelChanged(
B y V a l C h a n g e T y p e As M s d L e v e l C h a n g e T y p e , B y V a l T h e L e v e l As L e v e l ,
-
-
B y V a l TheModel As M o d e l R e f e r e n c e ) S e l e c t Case C h a n g e T y p e
Case M s d L e v e l C h a n g e T y p e . m s d L e v e 1 C h a n g e A f t e r C h a n g e A c t i v e
MsdLevelChangeType.msdLevelChangeAfterCreate Case MsdLevelChangeType.msdLevelChangeAfterDelete Case
Case M s d L e v e l C h a n g e T y p e . m s d L e v e 1 C h a n g e B e f o r e C h a n g e A c t i v e Case Case Case Case Case Case Case Case
MsdLevelChangeType.msdLevelChangeBeforeDelete MsdLevelChangeType.msdLevelChangeChangeAttribute MsdLevelChangeType.msdLevelChangeChangeCode MsdLevelChangeType.msdLevelChangeChangeDisp1ay MsdLevelChangeType.msdLevelChangeChangeName MsdLevelChangeType.msdLevelChangeChangeParent MsdLevelChangeType.msdLevelChangeTableRed0 MsdLevelChangeType.msdLevelChangeTableUnd0
End S e l e c t End
Sub
Now we are ready to populate our Select Case statement to deal with the Level Changes. It should be said that we do not have the ability to prohibit changes from taking place in this Interface. We can only react to the events. Let’s take a look at some of the more useful events in this Interface:
I The Active Event I
535
THEACTIVEEVENT Case MsdLevelChangeType.msdLevelChangeAfterChangeActive Dim FFile As Long FFile = FreeFile Open "c:\levelactivated.txt" For Append As BFFile Print #FFile, Now & vbTab & TheLevel.Name & vbTab & TheModel.Name & vbTab & TheModel .DesignFile.FullName C1 ose #FFi 1 e ~
A simple log is kept of when the Active Level is changed. We log the datehime, name of the Level, name of the Model, and the Design File's full name.
THEAFTERCREATE EVENT Case MsdLevelChangeType.msdLevelChangeAfterCreate Dim FFile2 A s Long FFile2 = FreeFile Open "c:\levelcreated.txt" For Append As #FFileP Print #FFile2, Now & vbTab & Application.UserName & vbTab & TheLevel.Name & vbTab & TheModel.Name & vbTab & TheModel .DesignFile.FullName C1 ose #FFi 1 e 2 ~
~
Knowing that a Level was created is helpful. Knowing who created it can be critical. This event is triggered after a Level is created. A log file is created/appended showing a Date/Time Stamp, the User who created the Level and other miscellaneous information.
THEAFTERDELETE EVENT Case MsdLevelChangeType.msdLevelChangeAfterDelete MsgBox "Level & TheLevel .Name & has been deleted." "
"
By the time this Event is triggered, very little can be done with the Level. We can still get its name, however.
536
I Chapter 25: Level Events I
THEBEFORECHANGEACTIVE EVENT Case
MsdLevelChangeType.msdLevelChangeBeforeChangeActive Debug. P r i n t " L e v e l
" " " " " ' I
& TheLevel.Name & i s about t o be d e a c t i v a t e d . " ~
The name of this event may suggest that we are being told which Level is about to become active. Not so. We are only told which Level is about to become deactivated. We do not know which Level is about to become activated until the AfterChangeActive Event.
THEBEFOREDELETE EVENT Case
MsdLevelChangeType.msdLevelChangeBeforeDelete Debug.Print "Before Delete:
"
& T h e L e v e l .Name & v b C r & -
vbTab & TheLevel . D e s c r i p t i o n
Before a Level is deleted, we can gather a little more information. After it is deleted, the only thing we can get is the Name property. In this example, we are pulling the Description and the Name.
THECHANGEA~RIBUTE EVENT Case MsdLevelChangeType.msdLeve1ChangeChangeAttribute D e b u g . P r i n t "Change A t t r i b u t e :
"
& T h e L e v e l .Name & vbCr & -
vbTab & T h e L e v e 1 . D e s c r i p t i o n & vbCr & vbTab & T h e L e v e l . I s A c t i v e & vbCr &
-
-
vbTab & TheLevel .E l ementCol o r
Levels have a large number of Attributes. In this example, we are only looking at four of them.
Every model makes use of levels. They are critical to organizing our design files and models. Knowing when levels are modified can be helpful especially when we are dealing with standards.
26
Change Track Events The last Interface exposing Microstation events that we will look at in this book is the “Change Track Events” Interface. This Interface exposes four events: BeginUndoRedo, ElementChanged, FinishUndoRedo, and Mark. We will look at two of them. In this Chapter: The BeginUndoRedo Event The Elementchanged Event
BEGINUNDOREDOEVENT P r i v a t e Sub IChangeTrackEvents-BeginUndoRedo( B y V a l A f t e r U n d o R e d o As E l e m e n t , B y V a l B e f o r e U n d o R e d o As E l e m e n t ,
-
B y V a l A c t i o n As M s d C h a n g e T r a c k A c t i o n ,
-
B y V a l I s U n d o As B o o l e a n ) End S u b
The BeginUndoRedo Event is triggered before any Undo or any Redo action takes place. The parameter ‘AfterUndoRedo’ points to the element with all of its properties after the Undo or Redo action takes place. The ‘BeforeUndoRedo’parameter points to the element with all of 537
538
I Chapter 26: Change Track Events I its properties before the Undo or Redo action takes place. The ‘Action’ parameter tells us which type of action has taken place. ‘IsUndo’helps us know whether the action was an Undo or a Redo. Let’s take a look at some code in this event: Private Sub IChangeTrackEvents-BeginUndoRedo( ByVal AfterUndoRedo As Element, ByVal BeforeUndoRedo As Element, ByVal Action As MsdChangeTrackAction, ByVal IsUndo As Boolean) Debug.Print AfterUndoRedo.Level.Name & vbTab & BeforeUndoRedo.Level.Name & vbTab & Action & vbTab & IsUndo End Sub
In this example we are writing the Level Names of the elements that are modified, Action, and IsUndo parameters to the Immediate Window. For example, if we changed the Level of an element, and then issued an Undo and then a Redo, we would see the following lines in the Immediate Window: New Level ( 0 ) Default 3 Default New Level (0) 3
True False
We can see the After and Before Level names, the Trpe (3), and the fact that the first action was an Undo and the next one was a Redo (based on True and False values). The Level names and Undo/Redo values make perfect sense. But what about the number 3? It tells us the Type of event. Right? The value points to an item in the MsdChangeTrackAction enumeration. 2 msdChangeTrackActionAppData = 8 msdChangeTrackActionDelete = 1 msdChangeTrackActionDrop = 6 msdChangeTrackActionMark = 7 msdChangeTrackActionModelAdd = 9 msdChangeTrackActionMode1 D e l e t e = 10 msdChangeTrackActionAdd
=
I Element Changed Event I
539
msdChangeTrackActionModify = 3 msdChangeTrackActionModifyFence
=
5
msdChangeTrackActionNewFilePositionAndModify
=
4
We can see here that we were performing Undo/Redo actions on a modification to an element. We should keep in mind that the constants contained in this enumeration are used in other areas of the Change Track Events Interface as well as the BeginUndoRedo Event.
ELEMENTCHANGEDEVENT P r i v a t e Sub
IChangeTrackEvents-ElementChanged( B y V a l A f t e r c h a n g e As E l e m e n t , B y V a l B e f o r e c h a n g e As E l e m e n t ,
-
B y V a l A c t i o n As M s d C h a n g e T r a c k A c t i o n ,
-
C a n t B e U n d o n e As B o o l e a n ) End Sub
Whenever an element is changed, the Elementchanged event is triggered. The parameters provided in this event are very similar to the BeginUndoRedo event. We are given a reference to the element before and after the change is made, the type of action, and whether the action can be undone. Let’s take a look at several implementations of the Elementchanged event: P r i v a t e Sub
IChangeTrackEvents-ElementChanged( B y V a l A f t e r c h a n g e As E l e m e n t , B y V a l B e f o r e c h a n g e As E l e m e n t ,
-
-
B y V a l A c t i o n As M s d C h a n g e T r a c k A c t i o n , C a n t B e U n d o n e As B o o l e a n ) D e b u g . P r i n t BeforeChange.Level.Name AfterChange.Level.Name
& vbTab &
& vbTab &
A c t i o n & vbTab & CantBeUndone End Sub
-
-
540
I Chapter 26: Change Track Events I The code is simple and straight forward. When an element is modified, we are given the element before and after it is modified, the type of action, and whether or not the action can be undone.
D e f a u l t New L e v e l (0) 3 New L e v e l (0) New L e v e l New L e v e l (1) Default 3
False (1) 3 False
False
As we can see here, an element’s level was changed from “Default” to “New Level (0)” to “New Level (1)” and back to “Default’: Each action type is 3 and the actions can be undone because the “CantBeUndone” parameter is passed to us with a value of False.
The Change Trpe is 3. A review of the constants in the MsdChangeTrackAction enumerator reveals that we are dealing with a “msdChangeTrackActionModify”action. Let’s build upon the event by adding a little more code: P r i v a t e Sub
IChangeTrackEvents-ElementChanged( B y V a l A f t e r c h a n g e As E l e m e n t , B y V a l B e f o r e c h a n g e As E l e m e n t ,
-
-
B y V a l A c t i o n As M s d C h a n g e T r a c k A c t i o n , C a n t B e U n d o n e As B o o l e a n ) S e l e c t Case
Application.CommandState.CommandName
Case “ D e l e t e E l e m e n t ” Debug.Print “ D e l e t e Element” D e b u g . P r i n t vbTab & BeforeChange.Level.Name
& vbTab &
~
A c t i o n & vbTab & CantBeUndone Case E l s e Debug.Print
Application.CommandState.CommandName
D e b u g . P r i n t vbTab & BeforeChange.Level.Name AfterChange.Level.Name
& vbTab &
A c t i o n & vbTab & CantBeUndone End S e l e c t End Sub
-
& vbTab &
-
I Element Changed Event I
541
Taking a peek at the CommandState.CommandName property can give us a hint as to what was taking place just prior to the element’s change.
E l e m e n t Selection New L e v e l (0) D r a g Selection New L e v e l (0) E l e m e n t Selection Default Default D e l e t e Element New L e v e l (0)
New L e v e l
(0)
4
False
New L e v e l
(0)
3
False
3
False
1
False
We can see here that we encountered a “msdChangeTrackActionNewFilePositionAndModify” (4), a “msdChangeTrackActionModify” (3), and a “msdChangeTrackActionDelete”(1). In the above example, we were basing our actions on the CommandName property of the CommandState object. When we saw a “Delete Element” CommandName, we only showed the Level of the ‘Beforechange’object because after an item is deleted, the ‘Afterchange’object is set to “Nothing’ - the object has been deleted. Basing our reactions to events on a CommandName may work in some circumstances but basing our reactions on the “Action” produces more reliable results. P r i v a t e Sub IChangeTrackEvents-ElementChanged( B y V a l A f t e r c h a n g e As E l e m e n t , B y V a l B e f o r e c h a n g e As E l e m e n t ,
-
B y V a l A c t i o n As M s d C h a n g e T r a c k A c t i o n ,
-
C a n t B e U n d o n e As B o o l e a n ) S e l e c t Case A c t i o n Case r n s d C h a n g e T r a c k A c t i o n A d d Case r n s d C h a n g e T r a c k A c t i o n A p p D a t a Case r n s d C h a n g e T r a c k A c t i o n D e l e t e Case m s d C h a n g e T r a c k A c t i o n D r o p Case m s d C h a n g e T r a c k A c t i o n M a r k Case m s d C h a n g e T r a c k A c t i o n M o d e l A d d Case r n s d C h a n g e T r a c k A c t i o n M o d e l D e l e t e Case r n s d C h a n g e T r a c k A c t i o n M o d i f y Case r n s d C h a n g e T r a c k A c t i o n M o d i f y F e n c e Case
msdChangeTrackActionNewFilePositionAndModify
542
I Chapter 26: Change Track Events I End S e l e c t End Sub
We can now build on this framework. Let’s take a look at a few examples.
P r i v a t e Sub
IChangeTrackEvents-ElementChanged( B y V a l A f t e r c h a n g e As E l e m e n t ,
-
~
B y V a l B e f o r e c h a n g e As E l e m e n t ,
~
B y V a l A c t i o n As M s d C h a n g e T r a c k A c t i o n ,
-
C a n t B e U n d o n e As B o o l e a n ) S e l e c t Case A c t i o n Case m s d C h a n g e T r a c k A c t i o n A d d Case
msdChangeTrackActionAppData
Case m s d C h a n g e T r a c k A c t i o n D e l e t e Case m s d C h a n g e T r a c k A c t i o n D r o p Case m s d C h a n g e T r a c k A c t i o n M a r k Case m s d C h a n g e T r a c k A c t i o n M o d e 1 Add Case m s d C h a n g e T r a c k A c t i o n M o d e l D e l e t e Case m s d C h a n g e T r a c k A c t i o n M o d i f y S e l e c t Case A f t e r C h a n g e . T y p e Case
MsdElementType.msdElementTypeTextNode, MsdElementType.msdElementTypeText I f A f t e r c h a n g e . L e v e l . Name
<>
-
“TEXT” T h e n
Afterchange. Level = A c t iveDes ig n F i 1 e L e v e l s ( “ T E X T ” 1
.
A f t e r c h a n g e . Rewri t e End I f End S e l e c t Case m s d C h a n g e T r a c k A c t i o n M o d i f y F e n c e Case
msdChangeTrackActionNewFilePositionAndModify
End S e l e c t End Sub
When an element is modified, we check to see if it is a TextNode or Text element. If it is one of these types of elements, we check to see what level the element is on. If it is not on the “TEXT” level, we place it on the “TEXT” level and Rewrite it so the change is ‘saved’to the file.
I Element Changed Event I
543
Example 2 P r i v a t e Sub I C h a n g e T r a c k E v e n t s - E l e m e n t C h a n g e d ( B y V a l A f t e r c h a n g e As E l e m e n t , B y V a l B e f o r e c h a n g e As E l e m e n t ,
-
B y V a l A c t i o n As M s d C h a n g e T r a c k A c t i o n ,
-
C a n t B e U n d o n e As B o o l e a n ) S e l e c t Case A c t i o n Case r n s d C h a n g e T r a c k A c t i o n A d d D e b u g . P r i n t "Add:
"
& A f t e r C h a n g e . T y p e & vbTab &
-
A f t e r C h a n g e . I D . H i g h & vbTab & AfterChange.ID.Low Case m s d C h a n g e T r a c k A c t i o n A p p D a t a Case r n s d C h a n g e T r a c k A c t i o n D e l e t e Case r n s d C h a n g e T r a c k A c t i o n D r o p Case r n s d C h a n g e T r a c k A c t i o n M a r k Case m s d C h a n g e T r a c k A c t i o n M o d e l A d d Case m s d C h a n g e T r a c k A c t i o n M o d e l D e l e t e Case m s d C h a n g e T r a c k A c t i o n M o d i f y Case r n s d C h a n g e T r a c k A c t i o n M o d i f y F e n c e Case r n s d C h a n g e T r a c k A c t i o n N e w F i l e P o s i t i o n A n d M o d i f y End S e l e c t End Sub
Add: 1 5 0 Add: 6 0 Add: 4 0
136 137 138
Knowing what type of element is added can come in handy. Let's add a function so we can see the type of element as a description instead of a number. We w ill modify the Event to make use of this new function. P r i v a t e Sub I C h a n g e T r a c k E v e n t s - E l e m e n t C h a n g e d ( B y V a l A f t e r c h a n g e As E l e m e n t , B y V a l B e f o r e c h a n g e As E l e m e n t , B y V a l A c t i o n As M s d C h a n g e T r a c k A c t i o n , C a n t B e U n d o n e As B o o l e a n ) S e l e c t Case A c t i o n Case m s d C h a n g e T r a c k A c t i o n A d d
-
I Chapter 26: Change Track Events I D e b u g . P r i n t "Add:
"
& GetType(AfterChange.Type1
& vbTab &
AfterChange.ID.High & vbTab & AfterChange.ID.Low Case msdChangeTrackActionAppData Case m s d C h a n g e T r a c k A c t i o n D e l e t e Case m s d C h a n g e T r a c k A c t i o n D r o p Case m s d C h a n g e T r a c k A c t i o n M a r k Case m s d C h a n g e T r a c k A c t i o n M o d e 1 Add Case msdChangeTrackActi onModel Delete Case m s d C h a n g e T r a c k A c t i o n M o d i f y Case m s d C h a n g e T r a c k A c t i o n M o d i f y F e n c e Case msdChangeTrackActionNewFilePositionAndModify End Select End Sub Function GetType(Type1n As MsdElementType) As String Select Case TypeIn Case msdElementTypeArc GetType = "Arc" Case msdElementTypeBsplineBoundary GetType = "BSpl ineBoundary" Case msdElementTypeBsplineCurve GetType = "BSpl ineCurve" Case msdEl ementTypeBsp1 i neKnot GetType = "BSpl ineKnot" Case msdEl ementTypeBsp1 inePol e GetType = "BSpl inePol e " Case msdElementTypeBsplineSurface GetType = "BSpl inesurface" Case msdEl ementTypeBsp1 ineWeight GetType = "BSpl ineweight" Case msdElementTypeCellHeader GetType = "Cell Header" Case msdEl ementTypeCel1 Li braryHeader GetType = "Cell Li braryHeader" Case msdElementTypeComplexShape GetType = "Compl exshape" Case msdEl ementTypeComp1 exStri ng GetType = "Compl exstring" Case msdElementTypeCone GetType = "Cone"
I Element Changed Event I Case r n s d E l e m e n t T y p e C o n i c GetType
=
"Conic"
Case r n s d E l e m e n t T y p e C u r v e G e t T y pe
=
"
C urve "
Case r n s d E l e m e n t T y p e D e s i g n F i l e H e a d e r GetType
=
"DesignFileHeader"
Case r n s d E l e m e n t T y p e D g n S t o r e C o m p o n e n t GetType
=
"DgnStoreComponent"
Case r n s d E l e m e n t T y p e D g n S t o r e H e a d e r GetType
=
"DgnStoreHeader"
Case r n s d E l e m e n t T y p e D i g S e t D a t a GetType
=
"DigSetData"
Case r n s d E l e m e n t T y p e D i m e n s i o n G e tT y p e
=
"
D imen s ion "
Case r n s d E l e m e n t T y p e E l l i p s e GetType
=
" E l 1i p s e "
Case rnsdElementTypeGroupData GetType
=
"GroupData"
Case r n s d E l e m e n t T y p e L e v e l M a s k GetType
=
"LevelMask"
Case rnsdEl e m e n t T y p e L e v e 1 Symbol o g y GetType
=
" L e v e l Symbology"
Case r n s d E l e m e n t T y p e L i n e GetType
=
"Line"
Case r n s d E l e m e n t T y p e L i n e S t r i n g Ge t T y p e
=
"
L in e St r in g "
Case r n s d E l e m e n t T y p e M a t r i x D o u b l e D a t a Ge t T y p e
=
"
M a t r ix Do u b 1 e Da t a "
Case r n s d E l e m e n t T y p e M a t r i x H e a d e r Ge t T y p e
=
"
M a t r ix He a d e r "
Case r n s d E l e m e n t T y p e M a t r i x I n t e g e r D a t a Ge t T y p e
=
"
M a t r ix I n t e ge r Da t a "
Case rnsdElementTypeMeshHeader GetType
=
"MeshHeader"
Case r n s d E l e m e n t T y p e M i c r o S t a t i o n GetType
=
"MicroStation"
Case rnsdElementTypeMu1 t i L i n e GetType
=
"Mu1 t i L i n e "
Case rnsdElementTypeNamedGroupComponent GetType
=
"NamedGroupComponent"
Case rnsdElementTypeNamedGroupHeader
545
I Chapter 26: Change Track Events I GetTy p e = Named G ro u p H e a d e r Case msdElementTypePointString GetTy p e = Poi n t St r i n g Case msdElementTypeRasterComponent GetTy p e = Ra st e rC omp o n e n t Case msdEl ementTypeRasterFrame GetTy p e = Ra st e r Frame Case m s d E l e m e n t T y p e R a s t e r H e a d e r GetTy p e = Ra st e rH e a d e r Case m s d E l e m e n t T y p e R a s t e r R e f e r e n c e GetTy p e = Ra st e r Ref e re n c e Case msdElementTypeRasterReferenceComponent GetType = " R a s t e r R e f e r e n c e C o m p o n e n t " Case msdElementTypeReferenceAttachment GetType = "ReferenceAttachment" Case m s d E l e m e n t T y p e R e f e r e n c e O v e r r i d e GetType = Referenceoverride" Case msdElementTypeShape GetType = "Shape" Case msdElementTypeSharedCel1 GetType = "SharedCell Case msdEl ementTypeSharedCel1 Definition GetType = "SharedCell Definition" Case msdEl ementTypeSo1 id GetType = "Sol id" Case msdElementTypeSurface Ge tTy pe = Su rfa c e Case msdEl ementTypeTab1 e GetType = "Tabl e " Case msdEl ementTypeTab1 eEntry GetType = "Tabl eEntry" Case msdElementTypeTag GetType = "Tag" Case msdElementTypeText GetType = "Text" Case msdElementTypeTextNode GetType = "TextNode" Case msdEl ementTypeView GetType = "View" Case msdElementTypeViewGroup GetType = "ViewGroup" "
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
I Element Changed Event I
547
End S e l e c t End F u n c t i o n
Add: Add: Add: Add: Add: Add: Add: Add: Add: Add:
Linestring Line 0 Line 0 Line 0 Line 0 Curve 0 Shape 0 Ellipse Ellipse Text 0
139 140 141 142 143 144 145 0 146 0 147 148 0
Example 3 When
an
element
is
deleted,
the
action
recorded
'msdChangeTrackActionDelete'. P r i v a t e Sub I C h a n g e T r a c k E v e n t s - E l e m e n t C h a n g e d ( B y V a l A f t e r c h a n g e As E l e m e n t , B y V a l B e f o r e c h a n g e As E l e m e n t ,
-
-
B y V a l A c t i o n As M s d C h a n g e T r a c k A c t i o n , C a n t B e U n d o n e As B o o l e a n ) S e l e c t Case A c t i o n Case r n s d C h a n g e T r a c k A c t i o n A d d Case r n s d C h a n g e T r a c k A c t i o n A p p D a t a Case m s d C h a n g e T r a c k A c t i o n D e l e t e D e b u g . P r i n t GetType(BeforeChange.Type1 &
"
Deleted"
Case m s d C h a n g e T r a c k A c t i o n D r o p Case r n s d C h a n g e T r a c k A c t i o n M a r k Case r n s d C h a n g e T r a c k A c t i o n M o d e l A d d Case r n s d C h a n g e T r a c k A c t i o n M o d e l D e l e t e Case m s d C h a n g e T r a c k A c t i o n M o d i f y Case m s d C h a n g e T r a c k A c t i o n M o d i f y F e n c e Case End S e l e c t End Sub
msdChangeTrackActionNewFilePositionAndModify
is
548
I Chapter 26: Change Track Events I
Text Deleted Shape D e l e t e d E l l i p s e Deleted Line Deleted Line Deleted
When an element is deleted, we only have access to the 'Beforechange' element.
We have had several examples that deal with the 'Action' parameter instead of counting on the 'CommandName: Our next example makes use of both. P r i v a t e Sub
IChangeTrackEvents-ElementChanged( B y V a l A f t e r c h a n g e As E l e m e n t , B y V a l B e f o r e c h a n g e As E l e m e n t ,
-
B y V a l A c t i o n As M s d C h a n g e T r a c k A c t i o n ,
-
C a n t B e U n d o n e As B o o l e a n )
D i m m y L e v e l As L e v e l D i m L e v e l c o u n t As L o n g
S e l e c t Case A c t i o n Case m s d C h a n g e T r a c k A c t i o n A d d Case
msdChangeTrackActionAppData
Case m s d C h a n g e T r a c k A c t i o n D e l e t e Case m s d C h a n g e T r a c k A c t i o n D r o p Case m s d C h a n g e T r a c k A c t i o n M a r k Case m s d C h a n g e T r a c k A c t i o n M o d e 1 Add Case m s d C h a n g e T r a c k A c t i o n M o d e l D e l e t e Case m s d C h a n g e T r a c k A c t i o n M o d i f y Case m s d C h a n g e T r a c k A c t i o n M o d i f y F e n c e Case
msdChangeTrackActionNewFilePositionAndModify GetType(AfterChange.Type)
S e l e c t Case
Case " T a b 1 e " S e l e c t Case
CommandState.CommandName
Case "New L e v e l " Levelcount
=
ActiveDesignFile.Leve1s.Count
I Activating the ChangeTrackEvents Interface I Set myLevel
=
549
~
ActiveDesignFile.Levels(Leve1Count
D e b u g . P r i n t m y L e v e l .Name &
"
-
1)
Added."
End S e l e c t End S e l e c t End S e l e c t End Sub
New New New New New New New New New New
Level Level Level Level Level Level Level Level Level Level
(27) (28) (29) (30) (31) (32) (33) (34) (35) (35)
Added. Added. Added. Added. Added. Added. Added. Added. Added. Added.
The 'NewFilePositionAndModify' change type tracks modifications to the design file and non-element objects such as Levels. In this example, we use the CommandName property of the Commandstate object in addition to the 'Action' parameter so we know when a Level is added or deleted from the file. For more information on Level modifications, review the ILevelChangeEvents interface.
ACTIVATINGTHE CHANGETRACKEVENTS INTERFACE As with other Interfaces, we implement each of the Interface's events in a Class Module and then add the Event Handler. The code that adds the Event Handler is run from within a Code Module as follows: P r i v a t e myCTE As C l s C h a n g e T r a c k E v e n t s Sub AddEvents( 1 RemoveEvents S e t myCTE
=
New C l s C h a n g e T r a c k E v e n t s
A d d C h a n g e T r a c k E v e n t s H a n d l e r myCTE End Sub Sub RemoveEventsO
I f myCTE I s N o t h i n g
=
F a l s e Then
550
I Chapter 26: Change Track Events I RemoveChangeTrackEventsHandler myCTE
Set m y C T E
=
Nothing
End I f End S u b
The ChangeTrackEvents Interface can be used for a thousand different purposes. Fortunately, it normally takes only a little bit of experimentation before we can implement it and begin getting the results we need.
I
27
Non-Graphical Info Databases
=
Databases allow us to store a great deal of information in a single ‘storehouse’. They can be used to store a variety of data regarding elements in our design files. When this information is stored in a database instead of in our design files, the design file is smaller in size and the information is available outside of the Microstation environment. This chapter focuses on Microstation’s ‘Database Link‘ functionality. A later chapter discusses additional ways we can work with databases. In this Chapter:
How Microstation ‘links’ elements to Databases How to create a Microsoft Access Database from scratch to ‘link‘ to a Design File Making use of UDL (Universal Data Link) files Linking elements in Microstation to a Database using VBA Creating Database Records using SQL Creating a User Interface to view Database Information
551
552
I Chapter 27: Non-Graphical Info - Databases I HOW MICROSTATION ‘LINK’ ELEMENTS TO DATABASES Microstation allows us to ‘link‘ an element to a database record by attaching a “DatabaseLink” object to the element. Databases contain Tables. Each Table contains Rows (or records). Each Row contains Columns. The “DatabaseLink” object contains the information necessary for Microstation to find the Table and Row that is linked to the Element. The DatabaseLink Properties that store these pieces of information are the “EntityNumber” and the “Mslink” properties.
At the top of this image, we see a DatabaseLink object that is attached to an element in a design file. Let’s begin by taking a look at the DatabaseLink‘s EntityNumber Property. The EntityNumber of a DatabaseLink helps us discover which Table the DatabaseLink is pointing to. The EntityNumber value is numeric. It does not tell us which Table to look in, it only tells us how to find which Table to look in. Each database that is used with DatabaseLinks has an “mscatalog” table. The “mscatalog” table is used to match up the DatabaseLink.EntityNumber property with the database Table we need to look in. When we find a matching “entitynum” value in the “mscatalog” table, the “tablename” field tells us which table we should look in for the record.
I How Microstation ‘link elements to Databases I
553
Now that we know which Table we need to look in, we begin looking in that Table for a matching “mslink” value. In the above example, a DatabaseLink object was found attached to an element in Microstation. The EntityNumber of the DatabaseLink object is 18. In the “mscatalog table, the EntityNumber 18 points us to the Table named “parcel”.In the “parcel” Table, a value of 531 was found in the “mslink” field and this provides the match between the Element in Microstation to a record (or Row) in the database. This is how Microstation links elements to database records. If more than one record is linked to an element, multiple DatabaseLink objects will be attached to the element in Microstation. Now, we have discussed the theory behind linking elements in Microstation to records in databases. Let’s look at an actual example.
1 Open the file ...\€xurnp/es\Civi/\Dgn\cogo.dgn. This file is installed with Microstation.
2 Next, zoom into the upper-left hand corner of the file.
If we look at this file and the illustration shown above, we will see that we were looking at Parcel Number 13 on Hayden Drive.
554
I Chapter 27: Non-Graphical Info - Databases I 3 Now, let’s open the database file
...\Examples\Database\Oledb\Examples\Access\gis.mdb in Microsoft Access. This is the database that is attached to this file. A review of the “mscatalog”table reveals the database tables in this Access Database that Microstation can work with. Opening the “parcel” table reveals the records we just looked at. So far we have looked into how Microstation ‘links’ elements in design files to records in databases. More information can be found in Microstation’s help file by searching for “Database”.A large number of help topics will appear. Much of what displays relates to using Microstation’s database tools. But we are going to be concentrating on VBA programming tools in this chapter.
DATABASE FROM SCRATCH
CREATING A
Microstation supports Oracle, ODBC, OLEDB, and SQL Server (through the BUDBC Database Server Selection). We are going to use an OLEDB connection with a Microsoft Access database. OLEDB drivers are installed as part of the Microsoft Windows operating system and we will use these drivers to not only communicate with the database but create a new database. Let’s begin by creating a new Microsoft Access database. Since this is a book about programming, we will demonstrate how this can be done even if Microsoft Access is not installed on our computers. We will begin with the code and then offer an explanation. Sub C r e a t e D B ( 1 D i m m y C a t As New A D O X . C a t a l o g D i m d b L o c a t i o n As S t r i n g
D i m C o n n S t r i n g As S t r i n g dbLocation ConnString
= =
“C:\MicroStation
VBA\DatabaseLinkTest.mdb”
“Provider=Microsoft.Jet.OLEDB.4.O;Data Source=” & dbLocation
myCat.Create ConnString End Sub
This code makes use of the “Microsoft ADO Ext. 2.8 for DDL and Security” Reference. Before executing the procedure, we need to add this Reference in VBA. This code creates a new database. The database is empty; there are no tables in it. Creating Tables is our next step. There
I Creating a Database from Scratch I
555
are many ways we can add Tables to an existing database. We will discuss three of them here. One way to add Tables to an existing database is to use the same ADOX Reference we just used to create the database. Sub C r e a t e D B P ( 1 D i m m y C a t As New A D O X . C a t a l o g D i m d b L o c a t i o n As S t r i n g D i m C o n n S t r i n g As S t r i n g
d b L o c a t i on ConnString
= =
" C : \ M i c r o S t a t i on V B A \ D a t a b a s e L i n k T e s t .mdb" "Provider=Microsoft.Jet.OLEDB.4.O;Data
dbLocation myCat.ActiveConnection
=
ConnString
D i m m y T a b l e As New T a b l e D i m m y c o l u m n As ADOX.Column
myTable.Name
=
"Lots"
' m s l i n k Column my Ta b 1 e . C o 1 umn s .A p p e n d
"
ms 1 in k " , a d I n t e g e r
'Owner Column S e t mycolumn
=
myCol umn. Name
New ADOX.Column "Owner"
=
myColumn.Type
=
adVarWChar
myCol umn . A t t r i b u t e s myCo1umn.DefinedSize
= =
a d C o l Nu1 1 a b l e
50
myTable.Columns.Append mycolumn ' S o l d Column S e t mycolumn
=
myCol umn. Name
New ADOX.Column "Sold"
=
myColumn.Type
=
adBoolean
myTable.Columns.Append mycolumn ' D a t e S o l d Column S e t mycolumn
=
myCol umn. Name
=
New ADOX.Column "DateSol d"
myColumn.Type
=
adDate
myCol umn . A t t r i b u t e s
=
a d C o l Nu1 1 a b l e
myTable.Columns.Append mycolumn ' A c r e s Column
Source=" &
556
I Chapter 27: Non-Graphical Info - Databases I Set myCol umn = New ADOX.Co1 umn myCol umn. Name = "Acres" myCol umn .Type = adDoubl e myColumn.Attributes = adColNullable myTable.Co1 umns.Append myCol umn ' SaleAmount Column Set myCol umn = New ADOX.Co1 umn myCol umn. Name = "Sal eAmount" myColumn.Type = adcurrency myColumn.Attributes = adColNullable myTable.Co1 umns.Append myCol umn
myCat.Tables.Append myTable myCat.ActiveConnection = Nothing End Sub
When we create the "mslink" column, we do so by appending the Columns collection 'in-line'. When we create a column 'in-line: we do not have as much control over the properties of the column (field). For example, we may or may not want a field (column) to be required. If we do not want a column to be required, we can specify the Field's Attributes property as 'adColNullable'. This means the column can have a value of 'null'. The second way we can create a table is to use the 'XctiveX Data Objects Library" Reference. A new Reference needs to be added before we can run the procedure CreateDB3. When we open the References dialog box to add "ActiveX Data Objects" as a Reference, we may see several Libraries to choose from. We should select the highest version of the library available. In this example, we will execute an SQL Statement on the database to create a new Table named "Expenses': Here is the example: Sub C r e a t e D B 3 ( ) Dim myDB As New ADODB.Connection Dim dbLocation As String Dim ConnString As String dbLocation = "C:\MicroStation V B A \ D a t a b a s e L i n k T e s t . m d b " ConnString = " P r o v i d e r = M i c r o s o f t . J e t . O L E D B . 4 . O ; D a t a Source=" dbLocation
&
I Creating a Database from Scratch I
557
myDB.0pen C o n n S t r i n g myDB.Execute " C r e a t e T a b l e Expenses ( m s l i n k C o u n t e r ,
"
&
-
" L o t L i n k Long, D e s c r i p t i o n C h a r ( 1 0 0 ) , Amount D o u b l e ) " End Sub
When we created the mslink field we gave it a 'Counter' type. This causes the field to be an "auto-number'' field that automatically assigns a numeric value to this field each time a new record is created. The value for this field begins with one (1) and increases by one each time a new record is created. This "auto-number'' field type is not available in all databases but it is available in Microsoft Access databases so we will use it in this example. The last method we will discuss makes use of the Microstation Visual SQL Query Builder. The Query Builder can be used to get information out of databases that are connected to Microstation but can also be used to execute standard SQL statements on a connected database. Before we use the Query Builder we must connect to the database. Before Microstation can connect to a database, it needs to know a few pieces of information. First, Microstation needs to know what type of database it will be 'talking' to. Next, Microstation needs to know where the database is located. With these two basic pieces of information, Microstation can begin talking to a database. So, how does Microstation know what type of database we have and where it is located? One of the best ways to provide this information is through the use of a UDL file. In Microstation, select the menu Settings > Database > Connect.
558
I Chapter 27: Non-Graphical Info - Databases I The Database Connection dialog box opens:
After selecting “OLEDB” from the Database Server, select “New” from the Database Source combo box. E
When we select “New”, a new UDL file is created and we are asked to enter the UDL file parameters.
ConnectivityService Provider MediaCatalogDB OLE DB Provider MediaCatalogMergedDBOLE DB Provider MediaCataloaWebDB OLE DB Provider Microsoft ISLM 1.1 OLE DB Provider MicrosoftJet 3.51 OLE DB Provider Microsoft OLE DB Provider For Data Minim Services Microsoft OLE DB Provider for Indexing Service Microsoft OLE DB Provider for Internet Publishing Microsoft OLE DB Provider for ODBC Drivers Microsoft OLE DB Provider for OL4P Services Microsoft OLE DB Provider for OL4P Services 8.0 Microsoft OLE DB Provider for Oracle Microsoft OLE DB Provider for Outlook Search Microsoft OLE DB Provider for SQL Server Microsoft OLE DB Simple Provider
What two critical pieces of information do we need to provide to Microstation? First, the type of Database. We specify the type of Database by selecting which OLE DB Provider (database driver) to use. When we are working with a Microsoft Access database, we select “Microsoft Jet 4.0 OLE DB Provider” from the list. Clicking the Next button allows us to continue.
I Creating a Database from Scratch I
559
The Connection tab gives us the ability to provide information regarding the location of the database we want to connect to. The interface in the Connection tab varies based on the selection in the Provider tab. When we are working with a “Jet” (Microsoft Access) Database, we can type in the file name or browse for the file by clicking the button as shown. If we are working with a SQL Server database, we are asked to select the SQL Server Name (it could be any server on the network that has SQL Server installed) and then select which Database on the selected server we want to connect to. Parameters are also provided to allow us to enter a User name and Password if the database we are connecting to requires these parameters. After we click the Browse button we can select the Access database we have just created. After selecting the file, the file path displays in the UDL Properties dialog box.
560
I Chapter 27: Non-Graphical Info - Databases I The TestConnection button is used to see if we have provided the UDL file with enough information to connect to the selected database.
f:
If we have provided the enough information, we will see a MessageBox stating “Test connection succeeded: This is the MessageBox we want to see. From time to time, however, a different MessageBox displays.
For example, if we selected the wrong ‘Provider’ for the selected database, we will see an error message. If the database we have selected is open ‘exclusively’somewhere else, we will see an error. After clicking OK in the Test Connection MessageBox, we next click OK in the UDL dialog box. If all goes well, we will see this MessageBox: Y
I Creating a Database from Scratch I
561
Microstation depends on a table named “MSCatalog” to associate DatabaseLink objects with database records. Since we have not created an MSCatalog table in the database we just attached to Microstation, we are notified that this table needs to be created and are given a hint as to how we can do so from within Microstation. Let’s follow the directions specified in this MessageBox by going to the menu Settings > Database > Setup. When we do so we are shown this MessageBox:
L
Let’s have Microstation create the MSCatalog Table by clicking OK. Next, we are shown the Database Setup dialog box. We will deal with this in a little while. For now, close it by clicking the OK button. When Microstation creates the MSCatalog Table, it may need to detach and then re-attach to the database so we may see the UDL file properties again. If this happens, we need to make sure that the correct ‘Provider’ and ‘Connection’ settings are made prior to closing the UDL File Properties dialog box. OK. We have just performed a number of steps to connect our DatabaseLinkTestmdb database to Microstation. Now that the database is connected, we can create a new Table by using the SQL Builder (Settings > Database > Query Builder).
562
I Chapter 27: Non-Graphical Info - Databases I As has been already stated, the Visual SQL Query Builder dialog can be used to get information from a connected database. When we are retrieving information from a database, we often use the “Select” SQL statement. So, the box we are going to type in is titled “SQL Select Statement”. But we can do much more than simply ‘select’ database records in this TextBox. When we type SQL statements in the “SQL Select Statement” TextBox and click the Execute button, the statement entered is executed by the connected database. Let’s try creating a new Table named “History” by executing a SQL Statement. The History Table will be used to store historic ownership data. CREATE TABLE History [rnslink Integer, Owner Char[SO], PurchaseDate Date, SoldDate Date]
CREATE TABLE H i s t o r y ( m s l i n k I n t e g e r , Owner C h a r ( 5 0 ) . P u r c h a s e D a t e Date, SoldDate Date)
Here is the statement we will use to create our new “History” Table. After clicking the Execute button, the new Table is created (as long as the SQL statement is correct). How do we know if it has been created? We can click the Tables button.
Expenses
:%
mscatalog
If the History table was not created, it will not show up in the “Select Table” list and we would know that the SQL Statement we used needs to be modified until it works properly.
I Making Use of UDL Files I
563
Double-clicking on “History” in the list and then clicking the Close button displays the “History”table in the Query Builder dialog box.
Here w e can see the fields created in the History table.
rnslink Owner PurchaseDate SoldDate
MAKING USE OF UDL FILES Thus far, we have discussed the mechanism behind MicroStation’s linking of design elements to database records and have created a new Access Database. During the creation of the Access Database, we created a new UDL file. A UDL file has two primary components. The “Provider” and the “Connection” components are used to specify the database driver and database location. One of the great things about UDL files is that if the location of our database changes, we can make one change in one file (the UDL file) and the programs we wrote and the Microstation links we created continue to work perfectly. We can even ‘scale up’ the database platform we are using from Microsoft Access to SQL Server or Oracle and our programs continue to function with only a couple of simple changes to the UDL file. Let’s take one more look at a procedure we ran earlier: Sub C r e a t e D B 3 ( 1 D i m myDB As New A D O D B . C o n n e c t i o n
D i m d b L o c a t i o n As S t r i n g D i m C o n n S t r i n g As S t r i n g d b L o c a t i on
=
ConnString
=
“ C : \ M i c r o S t a t i on V B A \ D a t a b a s e L i n k T e s t .mdb” “Provider=Microsoft.Jet.OLEDB.4.O;Data
Source=” &
-
dbLocation myDB.0pen C o n n S t r i n g myDB.Execute “ C r e a t e T a b l e Expenses ( m s l i n k C o u n t e r ,
”
&
~
“ L o t L i n k L o n g , D e s c r i p t i o n C h a r ( 1 0 0 ) , Amount D o u b l e ) ” End Sub
564
I Chapter 27: Non-Graphical Info - Databases I In this procedure, we provide a “Location” and a “Connection String in the code. This allows us to ‘connect’ to a database at a specific ‘location: It demonstrates that it is possible to work with databases without the use of a UDL file. But what happens if the database’s location or file name changes? We need to change the code if we have hard-coded the database connection information in our programs. If we are the only one using the program, this may not seem like much of a problem. But if multiple people are using our program, it is much easier to change a UDL file than it is to change code, especially if we have ‘Locked’ our code in VBA. And even the most basic computer user can be instructed to double-click on a UDL file and browse for a different database. We will discuss UDL file usage in greater detail in a later chapter. But for now, we should just keep in mind that using a UDL file provides the perfect combination of power and flexibility.
LINKINGMICROSTATION ELEMENTSTO DATABASE RECORDS The process of linking a Microstation Element to a Database Record is very simple. Here is the declaration for “CreateDatabaseLink”: Function
CreateDatabaseLink(Ms1ink
As L o n g , E n t i t y As L o n g , L i n k T y p e As M s d D a t a b a s e L i n k a g e , I s I n f o r m a t i o n As B o o l e a n , DisplayableAttributeType As L o n g ) As D a t a b a s e L i n k ~
~
~
And here is an example using CreateDatabaseLink: Sub D a t a b a s e L i n k A ( ) D i m myElem As E l e m e n t
D i m m y L i n k As D a t a b a s e L i n k S e t myElem = CommandState.GetLocatedElement(True) S e t rnyLink = C r e a t e D a t a b a s e L i n k ( 1 , 1, r n s d D a t a b a s e L i n k a g e O l e D b , T r u e , 0) myEl e m . A d d D a t a b a s e L i n k m y L i n k
myElem. R e w r i t e End Sub
-
I Creating Database Records using SQL I
565
After the DatabaseLink is created, we add it to an element in Microstation. It should be noted that Database Links can be created and added to elements without a database even being attached. In the example above, we create a Database Link with an MSLink of 1 (one) and an Entity of 1 (one). Remember, the Entity Property points to the Table we are to look in (by referencing the Entitynum field in the MSCatalog Table) and the Mslink Property points to the record in the Table. This code assumes that the MSCatalog Table has a record with an “Entitynum” of 1 and that the table it references has a record with an “MSLink” of 1. The following code looks at the selected element in Microstation and displays each DatabaseLink attached to it with the Database Link‘s properties in a MessageBox: Sub G e t D a t a b a s e L i n k s A ( 1
D i m myElem A s E l e m e n t D i m m y l i n k s 0 As DatabaseLink D i m m y L i n k As D a t a b a s e L i n k D i m I As L o n g
S e t myElem
=
CommandState.GetLocatedElement(True)
myLi n k s
=
For I
L B o u n d ( m y L i n k s ) To U B o u n d ( m y L i n k s )
=
myEl em. G e t D a t a b a s e L i n k s
Set myLink
=
myLinks(1)
MsgBox m y L i n k . D a t a b a s e T y p e & v b C r &
-
myLink.DisplayableAttributeType & v b C r & myLink.EntityNumber & vbCr & m y L i n k . I s I n f o r m a t i o n & vbCr &
-
~
my L i n k . Ms 1 in k
Next I End Sub
CREATING DATABASE RECORDSUSING SQL Thus far we have successfully created a new database, added new Tables to the new database, and know how to attach DatabaseLinks to Elements in Microstation. The only thing we are missing in our
566
I Chapter 27: Non-Graphical Info - Databases I database is data! We need to know how to add records to the database we created. We do this by using the same SQL Query Builder we used to add a Table to the database. Let’s add a record to the “Lots” Table. INSERT INTO Lots [mslink, OwnerIVALUES [l, ‘Jones Family’]
The SQL Statement is: INSERT INTO L o t s ( m s l i n k , O w n e r ) VALUES ( 1 , ‘ J o n e s F a m i l y ’ )
In this example, we create a new record in the table “Lots”.The Fields “mslink” and “Owner” are given values of “1” and “Jones Family” respectively. Additional fields and values can be added by placing them in the appropriate places. For more information on SQL Statements, review the “AdditionalSources” section at the end of this book.
CREATING A USER INTERFACE TO VIEW DATABASE
INFORMATION Let’s capitalize on what we now know about Database Links by creating a User Interface to view and edit “Lot” information.
Jones Family
Here is our interface. The TextBoxes and CheckBox will be used to display associated information from the fields in the table “Lots”.
Here is the code that is behind the interface: C o n s t U D L F i l e As S t r i n g
=
“C:\Documents
and S e t t i n g s \ A l l U s e r s \ ” &
-
&
-
“ A p p l i c a t i o n Oata\Oocuments\Bentley\WorkSpace\” “Projects\Exampl es\Database\Ol edb\Udl \ ” & “DatabaseLinkTest.ud1 ” ~
D i m M S L i n k I D As L o n g
I Creating a User Interface to view Database Information I
567
Private Sub btnClose-Click0 Unload Me End Sub Private Sub btnUpdate-Click0 Dim myDB As New ADODB.Connection Dim myRS As New ADODB.Recordset myDB.0pen "File name=" & UDLFile myRS.Open "Select * from Lots Where MSLink = & MSLinkID, myDB, adOpenForwardOnly, adLockOptimistic If myRS.EOF = False Then If txt0wner.Text <> Then myRS("0wner") = txtOwner .Text Else myRS("0wner") = Null End If "
" "
my RS ( "So1 d
"
)
=
c h kSo 1 d . Va 1 ue
If IsDate(txtDateSold.Text) Then my RS ( Da teSo 1 d ) = CDa te ( txt Da teSol d .Text 1 Else myRS("DateSo1d") = Null End If "
"
If IsNumeric(txtAcres.Text) Then myRS("Acres") = CDbl (txtAcres.Text.1 Else myRS("Acres") = Null End If If IsNumeric(txtSa1eAmount.Text) Then my RS ( "Sa 1 eAmoun t " ) = CDbl (txtSal eAmoun t .Text 1 Else myRS("Sa1eAmount") = Null End If
my RS . Update End If End Sub
I Chapter 27: Non-Graphical Info - Databases I P r i v a t e Sub U s e r F o r m - I n i t i a l i z e 0
D i m myElem As E l e m e n t D i m m y l i n k s 0 As D a t a b a s e L i n k D i m m y L i n k As D a t a b a s e L i n k D i m I As L o n g D i m myDB As New A D O D B . C o n n e c t i o n D i m myRS As New A D O D B . R e c o r d s e t
myDB.Open " F i l e name=" & U D L F i l e S e t myElem my L i n k s
=
=
CommandState.GetLocatedElement(True)
my E l em. G e t Da t a b a s e L i n k s
F o r I = L B o u n d ( m y L i n k s ) To U B o u n d ( m y L i n k s ) Set myLink
=
myLinks(1)
I f myLink.EntityNumber
=
1 Then
myRS.Dpen " S e l e c t * f r o m L o t s Where m s l i n k myLink.Mslink, myDB,
=
"
adOpenForwardOnly, adLockReadOnly
I f myRS.EDF MSLinkID
= =
F a l s e Then myLink.Mslink
I f I s N u l l ( m y R S ( " 0 w n e r " ) ) Then txt0wner.Text
=
" "
t x t Dwne r . T e x t
=
my RS ( "Owner " )
Else End I f c h k S o 1 d .V a 1 u e
=
my RS ( " So 1 d " 1
I f I s N u l l (myRS("DateSo1d")) Then txtDateSold.Text
=
t x t Da t e S o l d . T e x t
=
"I'
Else my RS ( " Da t e S o 1 d " )
End I f I f I s N u l l ( m y R S ( " A c r e s " ) ) Then txtAcres.Text
=
" "
t x t Ac r e s . T e x t
=
my RS ( " A c r e s " )
Else End I f I f I s N u l l (myRS("Sa1eAmount")) Then
&
I Review I
569 txtSaleAmount.Text
=
” ”
t x t Sa 1 e Amoun t .T e x t
=
my RS ( ” Sa 1 e Amoun t ” 1
Else End I f myRS.Close myDB.Close E x i t Sub End I f my RS . C1 o s e
End I f Next I myDB. C1 o s e
End Sub
When the form is initialized, we look at the selected element in Microstation and then get the “Lot” information through the DatabaseLink object. Not only are we displaying what information is in the database, but we allow the user to make changes to the database by changing the values in the TextBoxes and CheckBox. When the user clicks the Update Database button, the database record is updated based on what is entered in the form.
Databases can store a large variety of information. This information is categorized into Tables. Database Tables have fields (or columns) defined in them and each record in a table can have values in these fields. DatabaseLink Objects are used to associate Microstation Elements with Database Records. In order for DatabaseLinks to work with Databases, the Database must have a table named “mscatalog”.The DatabaseLink Objects are associated with the specified table through the MSLink Property. Databases will be discussed in more detail in a later chapter.
28
Tags Tags are useful for storing and displaying information that is associated with elements in a Design File. They are often used to display the same ‘type’ of information from file to file but the data stored in each Tag is different. For example, “Drawn By” is a useful piece of information for every file but the actual value may vary from file to file. The macros presented in this chapter are to be used with the project named “Building which is installed with Microstation. In this Chapter:
Getting Information from Tags based on a Selection Getting All Tags in a File Working with Tagsets Getting All Tags of All Files in a Folder Changing a Tag’s Value Changing multiple Tags in Multiple Files Exporting Tag Information to a File
571
572
I Chapter 28:Tags I GETTINGlNFORMATlON FROM TAGSBASED ON A SELECTION Let's begin our look into the wonderful world of tags by having the user (in this case it is probably us) select a Tag and get some basic information from it. Sub G e t S e l e c t e d T a g A ( 1 D i m myTag As T a g E l e m e n t
D i m myElemEnum As E l e m e n t E n u m e r a t o r S e t myElemEnum = A p p l i c a t i o n . A c t i v e M o d e 1 Reference.GetSelectedElements ~
W h i l e myElemEnum.MoveNext S e l e c t Case m y E l e m E n u m . C u r r e n t . T y p e Case M s d E l e m e n t T y p e . m s d E l e m e n t T y p e T a g S e t myTag
=
myElemEnum.Current
If myTag Is N o t h i n g
=
F a l s e Then
MsgBox m y T a g . V a l u e End
If
End S e l e c t Wend End Sub
In this example, we create an ElementEnumerator to look at each element selected in Microstation. We look at each selected element to see if it is a Tag. If it is a Tag, we display the Tag's value in a MessageBox. The Tag's value is of great importance to us. But there is more to a Tag than its Value. Let's expand the macro to include the Tag's Name. Sub G e t S e l e c t e d T a g B O
D i m myTag As T a g E l e m e n t D i m myElemEnum As E l e m e n t E n u m e r a t o r
S e t myElemEnum = A p p l i c a t i o n . A c t i v e M o d e 1 Reference.GetSelectedElements W h i l e myElemEnum.MoveNext S e l e c t Case m y E 1 e m E n u m . C u r r e n t . T y p e Case M s d E l e m e n t T y p e . m s d E l e m e n t T y p e T a g S e t myTag
=
myElemEnum.Current
If myTag Is N o t h i n g
=
F a l s e Then
MsgBox myTag.TagDefinitionName & vbTab & myTag.Value End End S e l e c t
If
I Getting Information from Tags based on a Selection I
573
Wend End Sub
The Tag’s Name is very important. It can be used to help us know if the Tag’s Value is telling us who created the drawing, who checked the drawing, or who printed the drawing. G e t S e l e c t e d T a g B gives us more information than the previous procedure but can be improved upon. Let’s get the Tag’s TagSetName. Sub G e t S e l e c t e d T a g C O Dim myTag A s TagElement Dim myElemEnum A s ElementEnumerator Set myElemEnum = Application.ActiveModelReference.GetSelectedE1ements Whi 1 e myEl emEnum.MoveNext Select Case myE1emEnum.Current.Type Case MsdElementType.msdE1ementTypeTag Set myTag = myElemEnum.Current If myTag Is Nothing = False Then MsgBox myTag.TagSetName & vbTab & myTag.TagDefinitionName & vbTab & myTag.Value End If End Select Wend End Sub ~
The TagSet tells us to which ‘group’a Tag belongs. For example, does the Tag belong to a Title Block? Does it belong to a Door Schedule? At this point we are getting some very useful information. If four tags are selected, we see four MessageBoxes. If one tag is selected, we see one MessageBox. Let’s expand our code some more to include all Tags that belong to the same TagSet. In our next example, we are going to display Tag information again. And we are going to make use of the selected Tag. But we are going to display all Tags that are siblings to the selected tag. We do this by getting the all Tags belonging to the selected tag’s ‘BaseElement: Sub G e t S e l e c t e d T a g D O Dim myTag A s TagElement Dim s i b T a g s 0 A s TagElement Dim myElemEnum A s ElementEnumerator Dim I As Long
574
I Chapter 28:Tags I S e t myElemEnum
-
=
Appl ication.ActiveMode1
Reference.GetSelectedElements
W h i l e myElemEnum.MoveNext S e l e c t Case m y E 1 e m E n u m . C u r r e n t . T y p e Case
MsdElementType.msdElementTypeTag S e t myTag
=
sibTags
=
myTag.BaseElement.GetTags
For I
L B o u n d ( s i b T a g s 1 To U B o u n d ( s i b T a g s )
=
myElemEnum.Current
MsgBox s i b T a g s ( I ) . T a g S e t N a m e & vbTab &
~
sibTags(I).TagDefinitionName
& vbTab & s i b T a g s ( I ) . V a l u e Next I E x i t Sub End S e l e c t Wend End Sub
We use G e t T a g s on the selected Tag's BaseElement. G e t T a g s fills an array composed of Tags belonging to an Element. We use a For ... N e x t loop to look at each Tag's TagSetName, TagDefinitionName, and Value.
GETTINGALLTAGSIN A FILE We have just seen how we can extract Tag information based on a selection in Microstation. Let's move from the 'selection' method to scanning an entire file for Tag Elements and displaying each tag's information in a MessageBox. Sub GetTagsA( 1 D i m myTag As T a g E l e m e n t D i m myElemEnum As E l e m e n t E n u m e r a t o r D i m myFi 1 t e r As New E l e m e n t S c a n C r i t e r i a myFi 1 t e r . E x c l u d e A l 1 T y p e s myFi 1 t e r . I n c l u d e T y p e msdEl e m e n t T y p e T a g
S e t myElemEnum
=
~
Application.ActiveModelReference.Scan(myFi1ter) W h i l e myElemEnum.MoveNext S e t myTag
=
myElemEnum.Current
MsgBox myTag.TagSetName & v b T a b & m y T a g . T a g D e f i n i t i o n N a m e
& vbTab & m y T a g . V a l u e Wend End Sub
-
I Working with Tagsets I
575
After we create a new Elementscancriteria object, we first exclude all types from the filter and then include the Tag element type. The entire file is ‘scanned’ and only Tag elements are enumerated. There are times when we want the user to select a Tag. There are other times when we may want to process an entire file without the need of user intervention. We have presented these two scenarios here. We will discuss how to extract Tag information into Microsoft Excel in a later chapter.
WORKING WITH TAGSETS Tags are grouped into ‘Tagsets: We have been showing each Tag’s TagSetName property in our previous examples. Let’s take a look at the active design file’s Tagsets. Sub G e t T a g s S e t s A O
D i m myTagSet A s T a g S e t
F o r Each myTagSet I n
Application.ActiveDesignFile.TagSets
MsgBox myTagSet.Name Next End Sub
Now that we know how to identify each TagSet in a file we can look at each TagDefinition in each TagSet in a file. Sub G e t T a g s S e t s B ( )
D i m myTagSet A s T a g S e t D i m myTagDef A s T a g D e f i n i t i o n
F o r Each myTagSet I n
Application.ActiveDesignFile.TagSets
F o r Each myTagDef I n m y T a g S e t . T a g D e f i n i t i o n s MsgBox myTagSet.Name & v b T a b & myTagDef.Name Next Next End Sub
And now we will perform the same basic function, only we will display more Tag information: Sub G e t T a g s S e t s C ( )
D i m myTagSet A s T a g S e t D i m myTagDef A s T a g D e f i n i t i o n
576
I Chapter 28:Tags I F o r Each myTagSet I n A p p l ic a t i o n . A c t i v e D e s i g n F i 1 e . T a g s e t s F o r Each myTagDef I n m y T a g S e t . T a g D e f i n i t i o n s MsgBox "SetName: "Prompt:
& myTagSet.Name & v b C r &
"
"TagName:
~
& myTagDef.Prompt & vbCr &
"
"Default:
~
& myTagDef.Name & v b C r &
"
"
~
& myTagDef. D e f a u l t V a l ue
Next Next End Sub
GETTINGALLTAGSOF ALL FILESIN A FOLDER As we have already seen, getting all Tags in a File can be accomplished with only a few lines of code. But taking time to open each file in a specific folder and then running a macro such as G e t T a g s S e t s C can take a little time. Especially if there are hundreds or even thousands of files. Let's take a look at some code that will open each design file in a folder and 'export' its Tag information by printing it to the Immediate Window in VBA. Of course, writing the information to a file would be much more useful. We will see an example of that later.
Before we proceed we need to add a Reference to the "Microsoft Scripting Runtime': The File System Object will be used to help us easily traverse all files in a folder. Sub G e t F o l d e r T a g s ( ) D i m myDGN As D e s i g n F i l e
D i m myFSO As New Scripting.FileSystemObject D i m m y F o l d e r As S c r i p t i n g . F o l d e r D i m m y F i l e As S c r i p t i n g . F i l e D i m myTagSet As T a g S e t D i m myTagDef As T a g D e f i n i t i o n D i m T a r g e t T a g s e t As S t r i n g
D i m myTag As T a g E l e m e n t D i m myElemEnum As E l e m e n t E n u m e r a t o r D i m myFi 1 t e r As New E l e m e n t S c a n C r i t e r i a " T i t l eB1 o c k "
TargetTagset
=
Set myFolder
rnyFSO.GetFolder("C:\Docurnents
=
and S e t t i n g s \ " &
"A1 1 U s e r s \ A p p l i c a t i o n D a t a \ " & "
B e n t 1 e y \ W o r kS p a c e \ P r o je c t s \ " &
"Examples\Building\Dgn")
~
~
I Getting All Tags of All Files in a Folder I
577
F o r Each m y F i l e I n m y F o l d e r . F i l e s S e l e c t Case m y F i l e . T y p e Case “ B e n t l e y M i c r o s t a t i o n D e s i g n F i l e ” S e t myDGN
=
Application.OpenDesignFileForProgram( myFile.Path,
~
True)
F o r Each m y T a g S e t I n myDGN.TagSets S e l e c t Case UCase(myTagSet.Name) Case U C a s e ( T a r g e t T a g s e t 1 myFi 1t e r . E x c l u d e A l 1 T y p e s m y F i 1t e r . I n c l u d e T y p e m s d E l e m e n t T y p e T a g S e t myElemEnum = myDGN.Mode1 s ( 1 ). S c a n ( m y F i 1 t e r ) ~
Whi 1 e myEl emEnum.MoveNext S e t myTag
=
myElemEnum.Current
D e b u g . P r i n t myFile.Name & vbTab & rnyTag.TagSetName & vbTab &
~
myTag.TagDefinitionName; v b T a b & myTag.Value & vbTab &
-
-
~
vbTab & myTag.ID.High & vbTab & myTag. I D . Low Wend End S e l e c t Next myDGN. C1 o s e
End S e l e c t Next End Sub
This example opens each design file in the specified folder “for program”. This means the file is opened in memory and is not displayed in Microstation. Files can be opened and manipulated very quickly when they do not need to be ‘rendered’ to the screen. One piece of information we are extracting in this example that we hadn’t extracted before is the High and Low elements of the ID property. This ID property is very important because it provides a unique identifier for an Element in Microstation and it persists from session to session. So, storing the ID property of an Element in a database, for example, would allow us to quickly and easily identify the Element in Microstation hours, days, weeks, or months after we first worked with it.
578
I Chapter 28:Tags I CHANGING A TAG'SVALUE Now we are going to create and use a Procedure named ChangeTag. It uses the High and Low elements of the ID property to get and then set a Tag's value. Here's the procedure: Sub C h a n g e T a g ( 1 D H i g h As L o n g , IDLow As L o n g , N e w V a l u e As S t r i n g ) D i m T a g I D As DLong D i m myTag As T a g E l e m e n t
TagID.High
IDHigh
=
TagID.Low
=
S e t myTag
=
IDLow
Application.ActiveDesignFile.GetElementByID(Tag1D)
myTag.Value
=
NewValue
myTag.Rewrite End Sub
When we have an ID, we can get its element by using the G e t E l emen t By1D procedure. After we set the Tag's value based on the ID, we Rewrite the Tag element. This procedure cannot be executed by itself. It needs something else to 'call' it. Let's take a look at a procedure that does just that: Sub T e s t C h a n g e T a g A O D i m myTag As T a g E l e m e n t D i m myEnum As E l e m e n t E n u m e r a t o r
S e t myEnum
=
ActiveModel Reference.GetSe1 ectedElements
W h i l e myEnum.MoveNext S e l e c t Case m y E n u m . C u r r e n t . T y p e Case
MsdElementType.msdElementTypeTag ChangeTag m y E n u m . C u r r e n t . I D . H i g h , my En urn. C u r r e n t . I D . Low,
"
-
ABC "
End S e l e c t Wend End Sub
In the "real world, we would be storing the High and Low elements of the ID property in a database or in some other storage mechanism. In our example here, we get the ID property from selected elements. Then we use our new procedure C ha ngeTa g to change the Tag's value.
I Changing multiple Tags in Multiple Files I
579
CHANGING MULTIPLE TAGSIN MULTIPLE FILES As we learn more about VBA programming we discover that we can make significant changes on a large scale in a short period of time. So, we can do a lot of good or we can do a lot of damage with our code. When we begin working with multiple files using VBA, extreme caution should be taken to make sure that the changes we are making are good.
Changing a tag in a single file is not nearly as exciting as changing multiple tags in multiple files. Nor is it as dangerous. The procedure we are going to create, ChangeTag2, allows us to specify four parameters. Sub ChangeTag2(FileName A s String, TagSet A s String, TagName A s String, NewValue As String) Dim myDGN A s DesignFile Dim myFilter A s New ElementScanCriteria Dim myElemEnum A s ElementEnumerator Dim myTag A s TagElement Dim myModel Ref A s Model Reference Set
myDGN
=
Application.OpenDesignFileForProgram(FileName,
False)
myFilter.ExcludeA11Types myFil ter.IncludeType msdElementTypeTag For Each myModelRef In myDGN.Models Set myElemEnum = myModelRef.Scan(myFi1ter) While myElemEnum.MoveNext Set myTag = myElemEnum.Current If StrComp(myTag.TagSetName, TagSet) = 0 Then If StrComp(myTag.TagDefinitionName, TagName) = 0 Then myTag.Value = NewValue myTag.Rewrite End If End If Wend Next myDGN.Save myDGN. C1 ose End Sub ~
In this procedure, we open the specified file “ForProgram”,scan the file for Tags with a specific TagSet and TagName and set its value. Here’s a procedure that makes use of ChangeTag2.
580
I Chapter 28:Tags I Sub
TestChangeTagB( 1 Dim Dim Dim Set
myFSO As New Scripting.Fi1eSystemDbject myFolder As Scripting.Fo1der myFile As Scripting.File myFolder = myFSO.GetFolder("C:\Docurnents and Settings\" & "A1 1 Users\Appl ication Data\" & Bent 1 ey \ W o r kSp a c e \ P r o je c t s\ & "Examples\Building\Dgn") For Each myFile In myFolder.Files Select Case myFile.Type Case "Bentley MicroStation Design File" C hangeTag2 my Fi 1 e . Path , "Tit 1 eB1 oc k" , "Checked By", "J Winters" End Select Next "
"
-
End Sub
Running the above procedure changes each design file in the specified folder and saves it. All Tags named "Checked By" in the "TitleBlock" TagSet are given a value of "J Winters". Powerful? Yes. Dangerous? Potentially. Let's be careful so the programming we do is a benefit to our employers instead of the derailing of our careers.
EXPORTINGTAGlNFORMATlON TO A FILE All of the MessageBoxes and Debugprint statements will not do us any good because the data extracted and displayed is not in a format that can be saved. We will begin with a simple ASCII Text file.
b11300ae201-elevat,onr.dgn
b51300ae201-elevaCl~ns.dgn b l l 3 0 0 a e 2 0 1 - e l e v a t l o m . dgn bii300ae201-elevationr.dgn b5130011201-11 evacl o m . dgn b l l 3 0 0 a e 2 0 1 - e l e v a t l o m . dgn b51300ae201-elevaCl~ns.dgn b l l 3 0 0 a e 2 0 1 - e l e v a t l o m . dgn b r i 300ae201-el e v a t i o m . dgn b5130011201-11 evacl o m . don b.1300ae301-rect,onr.dgn~
b ~ ~ 3 0 0 a e 3 0 1 - 5 e COn5 ? ~ .dgn
T1 t l e E l o c k Tl?leBlOck T1 t l e E l OCk Titleslock T1 r l e e l OCk T1 t l e E l OCk Tl?leBlOck T1 t l e E l OCk T i t l e s l ock T1 r l e e l OCk T1tleElock T1 ? l e B l o c k
Dellgn F l l e R e f PrOIIC? Flle Ref
0
a
Eentley
0
11576
OR=.
*E101
0
11570
NO.
0
Rev. 1.0 a l o b NO 651300 a 5cal e 1: 1 0 0 0 Title 5 0 ~ 4 t hE l e v a t i o n checked B u Dellgn F l i e R e f P r O I I C ? F11 e R e f
11574
11568
Date Drawn E y
11573
11569 11566 11567 0
11572
0
11575
0
4312
a
4316
Here is the output we want to create. It is the same information that we had been printing to the Immediate Window: Sub
ExportFolderTagsO
I Exporting Tag Information to a File I
581
D i m myDGN As D e s i g n F i l e
Scripting.FileSystem0bject
D i m myFSO As New
D i m m y F o l d e r As S c r i p t i n g . F o 1 d e r D i m m y F i l e As S c r i p t i n g . F i l e D i m myTagSet As T a g S e t D i m myTagDef As T a g D e f i n i t i o n D i m T a r g e t T a g s e t As S t r i n g D i m myTag As T a g E l e m e n t
D i m myElemEnum As E l e m e n t E n u m e r a t o r D i m m y F i l t e r As New E l e m e n t s c a n c r i t e r i a D i m F F i l e As Long FFile
=
FreeFile
Open " C : \ M i c r o S t a t i o n
VBA\Tags. t x t " F o r O u t p u t As B F F i 1 e
"TitleBlock"
TargetTagset
=
S e t rnyFolder
myFSO.GetFolder("C:\Docurnents
=
and S e t t i n g s \ " &
-
"A1 1 U s e r s \ A p p l i c a t i o n D a t a \ " &
"Bentley\WorkSpace\Projects\" & "Examples\Building\Dgn")
~
~
F o r Each m y F i l e I n m y F o l d e r . F i l e s S e l e c t Case m y F i l e . T y p e Case " B e n t l e y M i c r o s t a t i o n D e s i g n F i l e " S e t myDGN
=
Application.OpenDesignFileForProgram(
~
myFile.Path, True) F o r Each myTagSet I n myDGN.TagSets S e l e c t Case UCase(myTagSet.Name) Case U C a s e ( T a r g e t T a g s e t 1 m y F i 1t e r . E x c l u d e A l 1 T y p e s m y F i l t e r . I n c l u d e T y p e msdElementTypeTag S e t myElemEnum
=
myDGN.Models(l).Scan(myFilter) Whi 1 e myEl emEnum.MoveNext S e t myTag
=
myElemEnum.Current
P r i n t #FFile, m y F i l e . N a m e & v b T a b & myTag.TagSetName & vbTab &
-
~
myTag.TagDefinitionName; v b T a b & m y T a g . V a l u e & vbTab & vbTab & m y T a g . I D . H i g h & vbTab & m y T a g . I D . L o w
-
~
Wend End S e l e c t Next myDGN.Close
-
582
I Chapter 28:Tags I End S e l e c t Next C1 o s e # F F i 1 e End Sub
Writing to a Text file is simple. Of course, an ASCII .txt file is useful for reviewing in Notepad but isn't formatted. Let's modify the above example and instead of creating a .txt file we will create an .htm file. Sub ExportFolderTagsToHTMLO
D i m myDGN As D e s i g n F i l e D i m myFSO As New
Scripting.FileSystem0bject
D i m m y F o l d e r As S c r i p t i n g . F o l d e r D i m m y F i l e As S c r i p t i n g . F i l e
D i m m y T a g S e t As T a g S e t D i m m y T a g D e f As T a g D e f i n i t i o n D i m T a r g e t T a g s e t As S t r i n g D i m myTag As T a g E l e m e n t D i m myElemEnum As E l e m e n t E n u m e r a t o r D i m m y F i 1 t e r As New E l e m e n t S c a n C r i t e r i a
D i m F F i l e As L o n g FFile
=
FreeFile
Open " C : \ M i c r o S t a t i o n
VBA\Tags. h t m " F o r O u t p u t As # F F i l e
" < t a b l e width=660 border=l>"
& vbCr
P r i n t SIFFile, vbTab & " < t r > < t d > < / t d > < / t r > "
& vbCr
P r i n t SIFFile,
" T i t l eB1 o c k "
TargetTagset
=
Set myFolder
myFSO.GetFolder("C:\Documents
=
and S e t t i n g s \ " &
-
"A1 1 U s e r s \ A p p l i c a t i o n D a t a \ " & "
B e n t 1 e y \ W o r kS p a c e \ P r o j e c t s \ " &
~
~
"Examples\Building\Dgn") For Each m y F i l e I n m y F o l d e r . F i l e s S e l e c t Case m y F i l e . T y p e Case " B e n t l e y M i c r o s t a t i o n D e s i g n F i l e " P r i n t I I F F i l e , " < t r > < t d colspan=5>" m y f i l e . Path & " < / t d > < / t r > "
&
~
& vbCr
P r i n t # F F i l e , vbTab & " < t r > < t d > T a g S e t Name" & " < t d > T a g Name" & "Val ue " &
~
"ID High " & "
S e t myDGN
< t d> ID =
-
-
~
Low< / t d>< / t r > "
Application.OpenDesignFileForProgram(
-
I Exporting Tag Information to a File I
583 myFile.Path,
True)
F o r E a c h m y T a g S e t I n myDGN.TagSets S e l e c t Case UCase(myTagSet.Name) Case U C a s e ( T a r g e t T a g s e t . 1 m y F i 1t e r . E x c l u d e A l l T y p e s myFi 1t e r . I n c l u d e T y p e m s d E l e m e n t T y p e T a g S e t myElemEnum
=
-
myDGN.Models(l).Scan(myFilter) W h i l e myElemEnum.MoveNext S e t myTag
=
P r i n t #FFile,
myElemEnum.Current vbTab & " < t r > < t d > " &
myTag.TagSetName & " < / t d > " & " < t d > " & myTag.TagDefinitionName & "" &
~
-
~
" < t d > " & myTag.Value & " < / t d > " & " < t d > " & myTag.ID.High & " < / t d > " & " < t d > " & myTag. ID.Low & " < / t d > < / t r > " & vbCr Wend End S e l e c t Next myDGN.Close End S e l e c t Next P r i n t BFFile, "" C1 o s e B F F i 1 e End Sub
584
I Chapter 28:Tags I A little HTML code is all it takes to display the data we are exporting into a more visually pleasing and better organized format. And since web browsers can be found on most computers independent of the operating system, HTML is a standard format that can be read by nearly everyone.
REVIEW Tags contain useful information. The ability to access tags through VBA gives us control over not only tags in the active design file but in every file in a specific folder and so forth. Reading values and changing them is easy to do as we have just seen. Exporting tag data into ASCII files allows us to work with the data or view it in other programs such as Notepad and in a web browser. We will provide an example of extracting Tag information into Microsoft Excel in a later chapter. Tag information could also be extracted to a database or used as the body of an e-mail. We are only limited by our imagination.
.
29
XML Imagine for a moment that we are tasked with the responsibility of developing a new method of describing and housing data. This method of describing data must be flexible enough to handle a great variety of data models, data types, etc. For example, it must be able to describe a car, a family of people, and a farm. What would we come up with? Hopefully we would come up with something like XML because XML is designed to handle a variety of data structures and types. In this Chapter: El WhatisXML?
XML File Structure Reading XML Files With VBA
WHATIS XML? XML is an abbreviation for Extensible Markup Language. XML documents often have a file extension of “xml”.Even though XML files conform to a common specification, the data it contains can vary greatly from file to file and from ‘structure’to ‘structure’. Companies and organizations create their own XML Document Definitions to store their own type of data with their own data 585
I Chapter 29: XML I structures. For example, there are XML documents to store financial transactions, another to store GIS information, and yet another to store architectural bills of material. XML formats have been devised to store genealogy, sports statistics, and cooking recipes. Programs such as Microsoft Excel and Microsoft Access can export their data to XML files.
XML FILESTRUCTURE The XML File Structure is what allows it to be so flexible and powerful. Here is a small snippet of an XML file: < d a t a r o o t xmlns:od="urn:schemas-microsoft-com:officedata" generated="2005-12-28TO8:57:09">
9< / c 1 t-n o>
RD
< c i ty>KNOXVI LLE
< b 1 o c k-n
urn>0 < / b 1 o c k-n urn>
<1 ot-num>O
H I L L S UNIT l < / s u b d - n a m e >
I Reading XML Files I
587
This XML file is an export from a Microsoft Access database. To be more precise, it is an export from the gismdb file installed with Microstation. The “Parcel” table was exported to an XML file. XML files are being used more and more in place of traditional databases. And looking at the structure, it is easy to see why. We can see Nodes for City, State, Zip-Code, County, District, Parc-Area, Perimeter, and so forth.
READINGXML FILES There are several ways we can read an XML file. We could use standard VBA file Input/Output calls, reading each line of the XML file and parsing it. But there is a better way. Microsoft has given us some tools to work with XML files. Adding a Reference to a Microsoft XML DLL file is the first step.
Each computer has different versions of the Microsoft XML libraries. In the example above, v6.0 is the highest version so we will use it in our examples here. S u b ReadXMLFi 1 e ( 1 D i m myXML As New MSXML2.DOMDocument D i m m y X E l e m As M S X M L 2 . I X M L D O M E l e m e n t D i m m y X R e c o r d As M S X M L 2 . I X M L D O M E l e m e n t D i m m y X F i e l d As M S X M L 2 . I X M L D O M E l e m e n t
588
I Chapter 29: XML I myXML.async
=
False
myXML.validateOnParse
=
False
myXML. Load " c : \ M i c r o S t a t i o n V B A \ p a r c e l . x m l " S e t myXElem
=
myXML.documentElement
F o r Each myXRecord I n m y X E l e m . c h i l d N o d e s Debug . P r i n t "****NEW
RECORD****"
F o r Each m y X F i e l d I n m y X R e c o r d . c h i 1 d N o d e s Debug. P r i n t m y X F i e l d . baseName &
"
..
"
& myXField.Text
Next Next End Sub
Our first example loads the XML file and then prints each record with its children (the fields) in the Immediate window.
E
The procedure ReadXMLFi 1 e starts at the beginning of the file and looks at each item until it reaches the end. If we only want to display the record with a mslink of 531, we could use the following code: Sub ReadXMLFi 1 eB( 1
D i m myXML As New MSXML2.DOMDocument D i m myXElem A s MSXML2. IXMLDOMElement D i m myXRecord As MSXML2.IXMLDOMElement D i m m y X F i e l d As MSXML2.IXMLDOMElement
myXML.async
=
False
I Reading XML Files I
589
myXML.validate0nParse = False myXML. Load "c:\MicroStation VBA\parcel .xml Set myXElem = myXML.documentElement For Each myXRecord In myXElem.childNodes I f myXRecord.childNodes(O).Text = "531" Then For Each myXField In myXRecord.childNodes Debug.Print myXField. baseName & " . . " & myXField.Text Next End I f Next End Sub "
In this example, we look at each record in the XML file and when we find one with a mslink of 531, we print the record's fields to the Immediate window. The code works to be sure, but it is not very efficient. Let's take a look at this next example. Instead of looking at each and every record in the XML file, we are going to get only the "mslink" properties, look at them, and then print each field in the Immediate window. Sub ReadXMLFi 1 eC( ) Dim myXML As New MSXML2.DOMDocument Dim myXElem As MSXML2.IXMLDOMElement Dim myXList As MSXML2.IXMLDOMNodeList Dim myXRecord As MSXML2.IXMLDOMElement Dim myXField As MSXML2.IXMLDOMElement myXML.async = False myXML.validate0nParse = False myXML. Load "c:\MicroStation VBA\parcel .xml" Set myXElem = myXML.documentElement Set myXLi s t = myXML. getEl ementsByTagName("ms1 ink") For Each myXRecord In myXList I f myXRecord.Text = "531" Then For Each myXField In myXRecord.parentNode.childNodes Debug. Pri n t myXFiel d. baseName & " . . " & myXFiel d.Text Next End I f Next End Sub
590
I Chapter 29: XML I The code runs faster than the previous example. It is better than the previous example but we can improve. We are going to look for the first node that has a "mslink" value of 531. Sub ReadXMLFi 1 eD( 1
D i m myXML As New MSXML2.DDMDocument D i m m y X F i e l d As MSXML2.IXMLDDMElement D i m myXNode As MSXML2.IXMLDOMNode
myXML.async
=
False
myXML.validateOnParse
=
False
myXML. L o a d " c : \ M i c r o S t a t i o n V B A \ p a r c e l .xml
"
S e t myXElem
=
myXML.documentElement
S e t myXNode
=
myXML.selectSingleNode("//parcel [ m s l i n k = 5 3 1 1 " )
For Each m y X F i e l d I n rnyXNode.childNodes D e b u g . P r i n t rnyXField.baseNarne &
" .. "
& rnyXField.Text
Next End Sub
Now we are getting somewhere. Now we are letting the Microsoft XML Library do the work for us. It is fast, efficient, and very easy to modify It doesn't matter how deep the "parcel" object is in the XML file. This code finds the first instance where the "mslink" property of the "parcel" object is equal to 53 1. If we take a look at the screen capture shown previously, we will see that there are a good number of fields we have to work with. One of them is "parc-value". We could modify the code above, looking for "parc-value" instead of "mslink" and find a parcel valued at 250,000. But the code would only work if a parcel was valued at exactly 250,000. Let's examine the code that will return all parcels (not only one) with a value greater than or equal to $250,000. Sub ReadXMLFi 1 eE( 1 D i m myXML As New MSXML2.DOMDocurnent D i m m y X L i s t As MSXML2.IXMLDOMNodeList D i m m y X F i e l d As MSXML2.IXMLDOMElernent
D i m myXNode As MSXML2.IXMLDDMNode myXML.async
=
False
myXML.validateOnParse
=
False
myXML. L o a d " c : \ M i c r o S t a t i o n V B A \ p a r c e l .xml Set myXList
=
"
myXML.selectNodes("//parcel [parc~value>=2500001")
Debug. P r in t myX L i s t . L e n g t h &
"
Found.
"
I Reading XML Files I
591
For Each myXNode In myXList Debug. Print "*****PARCEL*****" For Each myXField In myXNode.childNodes Debug.Print myXField.baseName & " . . " & myXField.Text Next Next End Sub
The code is beginning to look a little like an SQL statement. What it really is, is an XPath statement. A careful comparison between the screen capture shown here and one a few pages ago shows us that this one is missing the "group-no" field and the "house-num" field. In a previous example, we used childNodes(0) to get the "mslink" field. We addressed the "mslink" field by an index number instead of its name. We can see here that some records do not have all fields represented, so we should be extremely careful when addressing fields (or nodes) by their index. It is far better to address a field by its name.
This next example displays information about each record where the parcel area is greater than or equal to 1 Acre. Sub ReadXMLFi 1 eF( ) Dim myXML A s New MSXML2.DOMDocument Dim myXList A s MSXML2.IXMLDOMNodeList Dim myXField As MSXML2.IXMLDOMElement Dim myXNode As MSXML2.IXMLDOMNode
592
I Chapter 29: XML I myXML.async
=
False
myXML.validateOnParse
=
False
myXML. L o a d " c : \ M i c r o S t a t i o n V B A \ p a r c e l . x m l " S e t myX L is t
=
my X M L . s e 1 e c t N o d e s ( " / / p a r c e 1 [ p a r c-a r e a >=4356 0 1 "
Debug.Print myXList.Length &
"
P a r c e l s Found
>=
1 Acre."
F o r E a c h myXNode I n m y X L i s t Debug.Print myXNode.selectSingleNode(".//rnsl i n k " ) . T e x t & vbTab & rnyXNode .s e l e c t S i n g l eNode ( . //owner" 1. T e x t & vbTab & myXNode.selectSingleNode(".//parc-area") . T e x t
~
"
~
Next End Sub
In the above example, we use "selectNode.4' to select multiple nodes meeting the criteria specified. We then look at each Node that is found and get its (the node's) "mslink", "owner': and "parc-area" values. Note that we are using ".//" with a period (.) instead of "//". When we use a period we are stating that we want to look relative to the current context. If we did not use the period in the above example, it would find the first "mslink", "owner", and "parc-area" nodes in the XML document. Let's look at one more example of reading this Access-exported XML document. Sub ReadXMLFi 1 eG( 1 D i m myXML As New MSXML2.DOMDocument D i m m y X L i s t As MSXML2.IXMLDOMNodeList D i m m y X F i e l d As MSXML2.IXMLDOMElement
D i m myXNode As MSXML2.IXMLDOMNode myXML.async
=
False
myXML.validateOnParse
=
False
myXML. L o a d " c : \ M i c r o S t a t i o n V B A \ p a r c e l .xml Set myXList
=
"
myXML.selectNodes("//parcellparc-area>=43560 " & " a n d p a r c-a r e a <=871 2 0 1 " )
Debug.Print myXList.Length &
"
P a r c e l s Found Between 1 and 2 Acres."
F o r E a c h myXNode I n m y X L i s t Debug.Print
myXNode.selectSingleNode(".//mslink").Text vbTab & myXNode.selectSingleNode(".//owner").Text vbTab & myXNode. s e l e c t S i n g l e N o d e ( " . / / p a r c - a r e a " )
Next End Sub
& & .Text
I Reading XML Files I
593
Now we have expanded the filter to only return nodes with “parc-are$ values between 1 and 2 Acres. We are once again exporting the “mslink‘: “owner”,and “parc-area” node values to the Immediate Window. 60 Parcels Found Between 1 and 2 Acres. 10 CRESTWOOD DEV INC 86657 36 WINSTON HAROLD J & JERRY M 46475 68 RODGERS CADILLAC INC 61295 71 SANFORD GEORGE B 58874 76 VISSER REAL ESTATE INVESTMENTS 53599 87 J B F COMPANY & WILBURS 63518 90 STEPHENS WILLIAM J & KATHERINE WLLANE 44372 94 DANIEL REALTY CORP S EASLEY-MCCALEB & 59003 100 WAL-MART PROPERTIES INC 65364 107 KINGTON CURTIS H & PATRICIA 52386 110 TED E & MCKINNEY JAMES MICHAEL 56333 111 ADKINS BOYD L & HARRINGTON GLENARD F 50360 113 HUGLEY DAVID B & PEGGY C S UNION SECURITY MORTGAGE 114 ADKINS BOYD L & HARRINGTON GLENARD F 47912 139 CAIN LILLIE MAE LSD OMEGA ENTPRS INC 74120 144 CAIN LILLIE MAE 46608 149 CAIN LILLIE MAE LEASED WESTSIDE INC 50262 156 SCHUBERT M A TR 69869 164 FIRST AMERICAN NATIONAL BANK 82497 184 CAIN LILLIE MAE LEASED OMEGA ENTPRS INC 59782 187 ROBERTS LARRY S & SUZANNE 75045
44071
As was mentioned previously, XML documents have a specific structure. The XML file we have been working with is an export from Microsoft Access. Let’s take a look now at an XML file exported from Microsoft Excel. I8
594
I Chapter 29: XML I The "towerdat.xls" spreadsheet is installed with Microstation. When exported as a "Spreadsheet XML? document, we can traverse the data using the Microsoft XML Reference. Sub ReadXMLFi 1 e l ( 1
D i m myXML As New MSXMLP.DDMDocument D i m myWSheets As MSXML2.IXMLDDMNodeList D i m myWSheet As MSXMLP.IXMLDOMNode D i m myRows As MSXML2.IXMLDOMNodeList D i m myRow As MSXMLP.IXMLDOMNode
D i m m y c e l l s As MSXML2.IXMLDDMNodeList D i m m y C e l l As MSXMLP.IXMLDDMNode myXML.async
=
False
myXML.validateOnParse
=
False
myXML. Load " c : \ M i c r o S t a t i o n V B A \ t o w e r d a t . x m l S e t myWSheets
=
"
myXML.getElementsByTagName("Worksheet")
F o r Each myWSheet I n myWSheets S e t myRows
=
myWSheet.selectNodes(".//Row")
F o r Each myRow I n myRows S e t myCel1s
=
myRow.se1 e c t N o d e s ( " . / / C e l l " )
F o r Each m y C e l l I n m y c e l l s Debug.Print myCell.Text Next Next Next End Sub
The structure for a Microsoft XML file is different than that of a Microsoft Access Database XML export. After declaring the variables we are going to use and Loading the XML file, we get all Worksheets in the file. Then we look at each Worksheet and get all Rows for the worksheet. Then we get all Cells in each Row and look at each Cell. The result of this first Excel XML macro is that the values (stored in the Text property) of each Cell is shown in the Immediate Window.
I Reading XML Files I
Even though XML provides a standard mechanism for storing and retrieving information, the structures are different front format toformat. Tlte XMLfile specification can be read in its entirety at http:// www.w3.0rgl XML.
595
Cable Tension 1200 17 color
weight/ft 2 13 towers
We see the values all right. But how do we know which Row and Column we are looking at when we get a value? To answer this question, we must understand a little more about the Worksheet XML structure. Each and every Cell could have an “Address” property. This may be convenient for us as developers but it would cause the size of the XML file to increase. So, Index properties are used when needed to identify where a specific Row or Cell is located. The first Row found in the Worksheet has an Attribute named “Index”. The “Index” tells us the Row number we are looking at. The first Cell in each Row also has an Attribute named “Index’: The Cell’s Index tells us in which Column the Cell is located. So, between the Index of the Row and the Index of the Cell we know exactly where the first Cell in the first Row is located. The second Cell in the first Row is a different matter. If the first Cell is in Column 2 and the second Cell does not have an Index Attribute, the second Cell listed is in Column 3. The next Cell in the same Row is in Column 4 if it does not have an Index Attribute. If the next Cell has an Index Attribute value of 8, it is located in Column 8. Indexes are only used on the first Cell and whenever a cell is not directly adjacent to another Cell. The second Row works in a similar manner. If the second Row in the XML file has an Index Attribute, the Row number is specified in the Index Attribute. If an Index Attribute is not supplied, the Row is directly below the previous Row. A slight modification of the original code allows us to track the Row and Column of the Cell. S u b ReadXMLFi 1 e 2 ( )
I Chapter 29: XML I D i m myXML As New MSXMLP.DOMDocument
D i m myWSheets As MSXML2.IXMLDOMNodeList D i m myWSheet As MSXMLP.IXMLDOMNode D i m myRows As MSXML2.IXMLDOMNodeList D i m myRow As MSXMLP.IXMLDOMNode D i m m y c e l l s As MSXML2.IXMLDOMNodeList D i m m y C e l l As MSXMLP.IXMLDOMNode
D i m myRowAtt As M S X M L 2 . I X M L D O M A t t r i b u t e D i m myCol A t t As MSXMLP. I X M L D O M A t t r i b u t e D i m m y A t t s As MSXML2.IXMLDOMNamedNodeMap D i m CurRow As L o n g D i m C u r C o l As L o n g
myXML.async
False
=
myXML.validateOnParse
=
False
myXML.Load " c : \ M i c r o S t a t i o n V B A \ t o w e r d a t . x m l " S e t myWSheets
=
myXML.getElementsByTagName("Worksheet")
F o r Each myWSheet I n myWSheets CurRow
=
1
CurCol
=
1
S e t myRows
=
myWSheet.selectNodes(".//Row")
F o r Each myRow I n myRows Set myAtts
=
S e t myRowAtt
myRow.Attributes =
myAtts.getNamedItem("ss:Index")
I f N o t myRowAtt I s N o t h i n g Then CurRow
=
myRowAtt.Text
=
CurRow + 1
Else CurRow End I f CurCol
=
1
S e t myCel1s
=
myRow.se1 e c t N o d e s ( " . / / C e l l " )
F o r Each m y C e l l I n m y c e l l s Set myAtts
=
S e t myCol A t t
myCell.Attributes =
myAtts.getNamedItem("ss: I n d e x " )
I f N o t m y C o l A t t I s N o t h i n g Then CurCol
=
myColAtt.Text
=
CurCol + 1
Else CurCol End I f
I Review I
597 D e b u g . P r i n t “ R ” & CurRow & “ C ” & CurCol &
”=”
& mycell . T e x t
Next Next Next End S u b
In the above Procedure, ReadXMLFi 1 e2, we are printing the Row and Column of the Cell as well as the Text. We do this by looking for Index Attributes of Rows and Cells. When found, we use them as the current values for the variables “CurRow” and “CurCol? When they are not found we increase the “CurRow” and “CurCol” values by 1 (based on whether we are looking at a Row Object (when we increase the “CurRow” variable) or a Cell Object (when we increase the “CurCol” variable).
. Here is the output of ReadXMLFi 1 e2. Notice that some ofthe Cells we find have an Empty String (””) for their Text property.
REVIEW XML is a technology that has been talked about for years and is becoming used more widely. The ability to read XML files is not only useful now but will become more critical in the days to come.
Since each xml Document Definition varies, a little time may be needed to learn how to ‘traverse’a new XML file type.
30
Batch Processing The ability to rapidly process 10, 100, or even 1000 files is as simple as it is powerful. In this Chapter:
Processing Files listed in an ASCII File Processing All Files in a Folder Processing All Files in a Folder and SubFolders Creating a User Interface for File Selection Logging Batch File Processing
PROCESSING FILESLISTEDIN AN ASCII
FILE
It is not uncommon to have a list of files that need processing in an ASCII text file. Here is an example:
. 599
600
I Chapter 30: Batch Processing I The first example we will examine opens this file ProcessThese.txt and reads each line in it. Each line in the file displays in a MessageBox. Sub
ProcessASCIIo D i m F i l e T o O p e n As S t r i n g D i m B a t c h F i l e As S t r i n g
D i m F F i l e As L o n g FFile
=
FreeFile
BatchFile
=
" C : \ M i c r o S t a t i o n VBA\BatchProcessing\ProcessThese.txt"
Open B a t c h F i l e F o r I n p u t As B F F i l e W h i l e EOF(FFi1e)
=
False
L i n e I n p u t # F F i 1 e , F i 1 eToDpen MsgBox F i 1 eToDpen Wend End Sub
Now that we have the basics in place, let's build on them. Sub
ProcessASCI IB( ) D i m F i l e T o O p e n As S t r i n g D i m B a t c h F i l e As S t r i n g D i m F F i l e As L o n g FFile
=
BatchFile
FreeFile =
" C : \ M i c r o S t a t i o n VBA\BatchProcessing\ProcessThese.txt"
Open B a t c h F i l e F o r I n p u t As B F F i l e W h i l e EOF(FFi1e)
=
False
L i n e I n p u t # F F i 1 e , F i 1 eToDpen A p p l ic a t i on. D p e n D e s i g n F i 1 e F i 1 eToDpen, T r u e MsgBox "Do S o m e t h i n g " Wend End Sub
Now, instead of just showing the file name in a MessageBox, we are opening each file in Microstation. We are doing so with the "Read Only" parameter set to True. This is useful when we need to extract information from files but don't want to make any changes to the file. Note that at this point we are only opening the files, we are not closing them.
I Processing Files Listed in an ASCII File I
601
Let's make a change to the ASCII file format. After the path and name of the file we will have lines specifying which Levels we want to process. Each "Level" line in the file should begin with a Tab and then should have the Level name. After adding Level names to the file, we will perform a SaveAs to the file with a new filename of ProcessTheseLevels.txt.
C : \ M i c r O s t a t i o n uBA\BatchProcessing\File Level A Level B
A.dgn
c r o s t a t i o n uBA\BatchProcessing\File Level A
C.dgn
c r o s t a t i o n uBA\BatchProcessing\File Level B
E.dgn
Level c Level 0 Level E Level F
<:\Mi
Level c Level E
<:\Mi
Level Level
0 F
Now, instead of opening each file and displaying the file name in a MessageBox we will open each file and display each Level name in a MessageBox. Sub
ProcessASCIICo D i m FileToOpen As S t r i n g
D i m B a t c h F i l e As S t r i n g D i m s t r L I N As S t r i n g D i m F F i l e As Long FFile
=
FreeFile
BatchFile = "C:\MicroStation
VBA\BatchProcessing\ProcessTheseLevels.txt"
Open B a t c h F i l e F o r I n p u t A s # F F i l e W h i l e EOF(FFi1e)
=
False
Line Input #FFile,
strLIN
S e l e c t Case L e f t ( s t r L I N , 1) Case v b T a b MsgBox "Do s o m e t h i n g t o L e v e l
"
&
~
Re p 1 a c e ( s t r L IN , v b T a b ,
" "
1
Case E l s e FileToOpen
=
strLIN
Application.OpenDesignFile F i l e T o O p e n , T r u e End S e l e c t Wend End Sub
602
I Chapter 30: Batch Processing I The Procedure ProcessASCIIC works very well as long as the files specified in the file exist. What happens if we attempt to open a file that does not exist?
. Prior to attempting to open any file, we should make sure the file exists. Sub ProcessASCI ID( ) Dim FileToOpen As String Dim BatchFile As String Dim strLIN As String Dim FFile As Long Dim FileIsOpen As Boolean FFile = FreeFile BatchFile = "C:\MicroStation V B A \ B a t c h P r o c e s s i n g \ P r o c e s s T h e s e L e v e l s . t x t " Open BatchFile For Input As #FFile While EOF(FFi1e) = False Line Input BFFile, strLIN Select Case Left(strLIN, 1) Case vbTab If FileIsOpen = True Then MsgBox "Do something to Level & ) Rep 1 ace ( st r L I N , v bT a b , End If Case Else FileToOpen = strLIN If Dir(FileTo0pen) = Then FileIsOpen = False Else FileIsOpen = True Application.OpenDesignFile FileToOpen, True End If End Select Wend End Sub "
~
" "
"I'
I Processing All Files in a Folder I
603
The D i r Function is a standard VBA function that returns the name of a file if it exists. If the file does not exist, the D i r Function returns an empty string (""). In this example, if the file does not exist, we pass it up and move to the next file. We could display a MessageBox stating that the file doesn't exist but that could be problematic. When we are Batch Processing files, we may start processing on Friday at 4 5 5 PM expecting the processing to take place over the weekend. If we attempt to process a file that does not exist and an error dialog box shows up, the MessageBox could be sitting there from Friday at 505 PM until we come into the office on Monday morning. We will look at logging our Batch Processing later in this chapter. Logging the fact that a requested file does not exist would be much better than displaying a MessageBox and waiting for user input.
PROCESSING ALL FILESIN A
FOLDER
When we have an ASCII file to tell us exactly which files to open, everything is clear. We know where the files are (or where they are supposed to be). Quite often, what we need to do, however, is open every design file in a specific folder. There are several ways we can get all of the file names in a folder. We could use the Visual Basic D i r Function. Let's try something different. For our example here, we will use the Microsoft Scripting Runtime Reference. After adding this reference, the following code executes: Sub ProcessInFol derA() Dim myFSO As New Scripting.FileSystem0bject Dim myFolder A s Scripting.Folder Dim myFile As Scripting.File Dim RootFolder As String RootFolder = "C:\MicroStation VBA\BatchProcessing" Set myFol der = myFSO.GetFolder(RootFo1 der) For Each myFile In myFolder.Files Select Case UCase(myFile.Type) Case "BENTLEY MICROSTATION DESIGN FILE" Debug. Print myFi le. Path End Select
604
I Chapter 30: Batch Processing I Next End Sub
Each file in the RootFolder is examined. In this example we are looking at the “Type” of file. The file “Type” is associated with the file extension. We can see the Type of a file when looking at our files using Windows Explorer. Another way to find design files is to look at the file extension. Sub
ProcessInFolderB() D i m myFSO As New Scripting.FileSystem0bject D i m m y F o l d e r As S c r i p t i n g . F o l d e r D i m m y F i l e As S c r i p t i n g . F i l e D i m R o o t F o l d e r As S t r i n g
RootFolder
“C:\MicroStation
=
S e t my F o l d e r
=
VBA\BatchProcessing”
my FSD. G e t F o l d e r ( R o o t F o l d e r )
F o r Each m y F i l e I n m y F o l d e r . F i l e s S e l e c t Case UCase( R i g h t ( m y F i 1 e . Name, 3 ) )
C a se
”
DG N ”
Debug. P r i n t m y f i l e . P a t h End S e l e c t Next End Sub
Instead of looking at the “Type”, we are now looking at the last three letters in the file name. If we find a DGN file, we know we are looking at a Microstation design file and we print it to the Immediate (Debug) window. We are off to a good start. Instead of printing the Path of each DGN file in the RootFolder to the Debug window, we should open the file in Microstation and ‘Process’ it. The procedure P r o c e s s I n F o l derB works well as long as the files we want to process are in the folder “C:\MicroStution VBA\ButchProcessing”.Of course, we wouldn’t want to change our code every time we need to process files in a different folder, now would we? Sub
ProcessInFol derC() D i m myFSO As New
Scripting.FileSystem0bject
D i m m y F o l d e r As S c r i p t i n g . F o l d e r
D i m m y F i l e As S c r i p t i n g . F i l e D i m R o o t F o l d e r As S t r i n g RootFolder
=
I n p u t B o x ( ” E n t e r Root F o l d e r : ” )
I Processing All Files in a Folder I
605
Set myFol der = myFSO.GetFolder(RootFolder) For Each myFile In myFolder.Files Select Case UCase(Right(myFi le.Name, 3) Case D G N Debug. Print myFi le. Path End Select Next End Sub "
"
This is a step in the right direction. We are using an InputBox to allow the user to type in the RootFolder path. It is a little tedious but gets us out of having hard-coded paths in our Procedure. What would be great is allowing the user to browse for and select the RootFolder. But how can we do this? When we use VBA we have access to a wealth of resources at our finger tips. Let's make use of one of these resources right now. We have seen that adding References to DLLs and Libraries gives us instant access to powerful functionality. The Reference we want to add now is called "Microsoft Shell Controls and Automation". After we do this we can run the next Procedure: Sub P i c k A F o l d e r O Dim myshell A s New Shell32.Shell Dim myRootFolder A s She1132.Folder3 Set myRootFolder = myshell .BrowseForFolder(O, " P i c k " , 0) MsgBox myRootFolder.Self .Path End Sub
606
I Chapter 30: Batch Processing I Can it be any easier? Add a simple reference and a few lines of code and users can select any folder on their computer or anywhere on their network. Let’s put it to work now in conjunction with our Batch Processing code. Sub ProcessInFolderD( 1 Dim myFSO As New Scripting.FileSystem0bject Dim myFolder As Scripting.Folder Dim myFile As Scripting.File Dim myshell As New Shell3P.Shell Dim myRootFolder As She1132.Folder3 Set myRootFolder = myshell .BrowseForFolder(O, “Pick”, 0) Set my Fol der = my FSO. Get Fol der (my Root Fol der . Sel f . Path) For Each myFile In myFolder.Files Select Case UCase( Right(myFi 1 e. Name, 3)) Cas e ” D G N ” Debug. Print myfile. Path End Select Next End Sub
Now, instead of asking the user to type in the Root Folder, the user selects the folder. Each DGN file in the selected folder displays in the Immediate Window.
PROCESSING ALL FILESIN A FOLDERAND SUBFOLDERS We now want to allow the user to select a folder and ‘process’ all files in the selected folder as well as all files in the selected folder’s subfolders. Before we look into what is required to do this, we need to discuss a programming technique called “Recursive Execution’: Normally, one procedure or function calls another procedure or function. When a function or procedure calls itself, however, it is termed “recursive’: Let’s take a look at an example that once again uses the “Microsoft Scripting Runtime” and “Microsoft Shell Controls and Automation” References. Sub ProcessInFolderE( 1 Dim myFSO As New Scripting.FileSystem0bject Dim myFolder As Scripting.Folder Dim myshell As New Shell3P.Shell
I Processing All Files in a Folder and SubFolders I
607
Dim myRootFolder A s She1132.Folder3 Set myRootFolder = myshell .BrowseForFolder(O, " P i c k " , 0) Set my Fo 1 de r = my F S O . Get F o 1 de r ( my Ro o t F o 1 d e r . S e 1 f . Path ProcessFi 1 esAndSubs myFol der End Sub Sub P r o c e s s F i 1 esAndSubs( FolderIn A s Scripting. Folder) Dim myFile A s Scripting.File Dim mySubFolder As Scripting.Fo1der For Each myFile In FolderIn.Files Select Case UCase(Right(myFile.Name, 3)) Case D G N Debug. Print myFi le. Path End Select Next For Each mySubFolder In FolderIn.SubFo1ders ProcessFilesAndSubs mySubFolder Next End Sub "
"
P r o c e s s I n F o l d e r E should lookvery familiar. We are allowing the user to select a folder. Once the folder is selected, we call the P r o c e s s F i 1 esAndSubs procedure, using the variable myFolder to supply the Folderln parameter.
Now we are inside P r o c e s s F i 1 e s AndSubs. After declaring a couple of variables, we examine each file in the "FolderIn" folder. If the right three characters of the file name are DGN, we print the file path to the Immediate Window (also called the Debug Window). After we finish looking at each of the files in the supplied folder, we begin looking at each of the subfolders in the supplied "FolderIn" folder. For each subfolder we find, we use it as the argument for the "FolderIn" parameter in P r o c e s s F i 1 esAndSubs, the procedure we are alreadyin. This is what makes this procedure recursive. We are already in the procedure
608
I Chapter 30: Batch Processing I and we ask VBA to begin executing the procedure again. This time, we are using a different folder as the “FolderIn”parameter.
Here is the output of this Procedure:
E
As you can see here, we began by processing the files in C:\MicroStution VBA\ButchP rocessing. After this, we began looking at the subfolders of our Root Path. First we found “BatchA”.We process the files in “BatchA” and then begin looking at the subfolders of “BatchA’: Each time we find a subfolder we look at the files of that subfolder and then look for subfolders in that subfolder. It can sound a little confusing. We can see here that we found two subfolders named “BatchA-1” and “BatchA-2” under the path C:\MicroStution VBA\ButchProcessing. The net result of using the code shown above in this manner is that every file in every subfolder as well as those in the root folder are identified. Once they are identified, they can be processed.
CREATING A USER INTERFACE FOR FILESELECTION We know how to read an ASCII file. We know how to process all files in a specific folder (and its subfolders). What happens if we only want to process specific files in a folder? We could make a modification to procedures already shown in this chapter to display a MessageBox asking if the user wants to process a file before doing so. Although this would give the user a little choice, the user would have to sit in front of the computer answering “Yes”,“Yes”,“No”,“Yes”,“No”,“No”.Choice? Yes. Batch Processing?Not really We are going to create a user interface that allows the user to select the files to be processed. After the files are selected, we will have a GO button that will begin the processing. Two ListBoxes will be in the GUI. One will display all files in the currently selected folder. The other will
I Creating a User Interface for File Selection I
609
hold the files that have been selected for batch processing. We want the user to be able to choose which folder to select from. Buttons will be used to move "Selected" or "All" files from one ListBox to the other. We will also allow the user to double-click a file to move it from one ListBox to the other.
Here is the code behind the interface. Private S u b btnBrowsecC1 i c k ( 1 Dim myFSO A s New Scripting.FileSystem0bject Dim myFolder A s Scripting.Folder Dim myFile A s Scripting.File Dim myshell A s New Shell32.Shell Dim myRootFolder A s She1132.Folder3 Set myRootFolder = myshell .BrowseForFolder(O, " P i c k " , 0) If myRootFolder Is Nothing Then Exit S u b Set myFolder = myFSO.GetFolder(myRootFolder.Se1f.Path) txtCurrentFolder.Text = myRootFolder.Self.Path 1 s t Fi 1 es I n Fol der .C1 ear For Each myFile In myFolder.Files Select Case U C a s e ( R i g h t ( m y F i l e . N a m e , 3)) Case D G N If IsFileIn(myFile.Path, IstFilesToProcess) = False Then "
"
610
I Chapter 30: Batch Processing I 1 s t F i 1 e s I n F o l d e r . A d d I t e m myFi l e . P a t h End I f End S e l e c t Next End Sub P r i v a t e Sub
btnGo-Click0
D i m I A s Long
F o r I = 1 To
1stFilesToProcess.ListCount lstFilesToProcess.List(1
Debug.Print
-
1)
Next I End Sub P r i v a t e Sub
btnIntoAllLC1 ick( 1
D i m I As Long
1stFilesInFolder.ListCount lstFilesInFolder.Selected(1 - 1) = T r u e
F o r I = 1 To Next I
MoveSelection I s t F i l e s I n F o l d e r , 1stFilesToProcess End Sub P r i v a t e Sub
btnOutAl1-C1 ick()
D i m I As Long
1stFilesToProcess.ListCount lstFilesToProcess.Selected(1 - 1) = T r u e
F o r I = 1 To Next I
MoveSelection IstFilesToProcess,
1stFilesInFolder
End Sub P r i v a t e Sub
btnIntoSelectedLClick0
MoveSelection I s t F i l e s I n F o l d e r , 1stFilesToProcess End Sub P r i v a t e Sub
btnOutSi ngl e-C1 i ck( 1
M o v e S e l e c t i o n 1s t F i 1e s T o P r o c e s s , 1s t F i l e s I n F o l d e r End Sub P r i v a t e Sub
IstFilesInFolder-DblC1
i c k ( B y V a 1 C a n c e l As MSForms. R e t u r n B o o l e a n )
MoveSelection I s t F i l e s I n F o l d e r , 1stFilesToProcess End Sub
I Creating a User Interface for File Selection I
611
P r i v a t e S u b I s t F i l e s T o P r o c e s s - D b l C l i c k ( B y V a 1 C a n c e l As MSForms.ReturnBoo1ean)
-
MoveSel e c t i o n 1 s t F i l e s T o P r o c e s s , 1 s t F i 1 e s I n F o l d e r End S u b F u n c t i o n I s F i l e I n ( F i 1 e P a t h A s S t r i n g , ListToCheck A s L i s t B o x ) As Boolean D i m I As L o n g IsFileIn
=
False
F o r I = 1 To L i s t T o C h e c k . L i s t C o u n t
StrComp(ListToCheck.List(1
If
IsFileIn
=
-
l), F i l e p a t h )
=
0 Then
True
Exit Function End I f Next I End F u n c t i o n F u n c t i o n M o v e S e l e C t i O n ( L i s t B o x F r o m As L i s t B o x , ListBoxTo As L i s t B o x ) D i m I As L o n g F o r I = 1 To L i s t B o x F r o m . L i s t C o u n t
I f ListBoxFrom.Selected(1
-
1)
=
If IsFileIn(ListBoxFrom.List(1 =
ListBoxTo.AddItem
T r u e Then - 11, L i s t B o x T o ) F a l s e Then
ListBoxFrom.List(1
Else End I f End I f Next I For I
=
L i s t B o x F r o m . L i s t C o u n t To 1 S t e p - 1
I f ListBoxFrom.Selected(1
-
1)
ListBoxFrom.RemoveItem I End I f Next I End F u n c t i o n
-
=
1
T r u e Then
-
1)
612
I Chapter 30: Batch Processing I
A few items worthy of mention that may not be apparent at first glance: We set the MultiSelect property of the ListBoxes to ‘Extended’ so more than one item can be selected and the user can use the
I Logging File Batch Processing I
613
helpful because we can be sure that the path in the TextBox actually exists because it is being selected by the user, not typed in on the keyboard.
LOGGINGFILEBATCHPROCESSING Log files are nothing new. They tell us what has been done, what hasn’t been done, what is being done, and what is about to be done. Here are some of the ways we can track our Processing Activities: Using a ‘Log File’; for example, an ASCII file with a “.log file extension. Track Processing Activities in a Database. Store Processing Information in the Registry. ‘Log’ activities over the Internet. E-mail Transaction Logs to various recipients.
Using a Log File Using a Log File is perhaps the easiest method of logging activities. Some of the benefits of using a Log File are: .log files are easily found and read by using Notepad. .log files can reside on the local computer or on the network. .log files can be backed up with other resources. .log files are not dependent on other resources such as Internet connectivity. .log files can be used so batch processing programs can ‘recover’ in case of power outages, file corruption, etc. There are a few questions we need to answer when working with .log files. First of all, where will the file be located? It could be in a fmed hard-coded folder such as C:\Log Files. It could be located in the same path as the .mvba file. It could be located on a network share such as Z:\Batch Processing\Log Files. Saving files to the same folder where the .mvba file is located is very useful. If we are running a macro, we know an .mvba file exists. If
614
I Chapter 30: Batch Processing I the .mvba file has been saved, it has an actual path. If we can find that path, we can write a .log file to the path and we can easily find it. Sub T e s t L o g F i 1 e ( ) W r it e T o Log " D p e n i n g C : \ t e s t . d g n " End Sub Sub W r i t e T o L o g ( L o g F i 1 e T e x t As S t r i n g ) D i m m y V B E As O b j e c t D i m x S p l i t As V a r i a n t
D i m F F i l e As L o n g S e t myVBE xSpl i t
=
=
Application.VBE
S p l it ( m y V B E . A c t i v e V B P r o j e c t . F i l e n a m e , " \ " )
xSplit(UBound(xSp1it)) FFile
=
=
"BatchProcess.log"
FreeFile
Open J o i n ( x S p l i t , P r i n t #FFile,
"\"I
F o r Append As B F F i l e
LogFileText
C1 o s e # F F i 1 e End Sub
What happens when we have multiple users working from the same .mvba file? Yes, the log file w ill be appended to but we could have a problem if one computer attempts to open it when it is already opened by another computer or user. Let's look at an example that creates a unique .log file for each computer name. This makes use of a Windows API function named Getcomputer Name. 'General D e c l a r a t i o n s Area P u b l i c D e c l a r e F u n c t i o n GetComputerName L i b " k e r n e 1 3 2 " A l i a s "GetComputerNameA" ( B y V a l l p B u f f e r As S t r i n g , n S i z e As L o n g ) As L o n g ~
Sub T e s t L o g F i 1 eB( 1 W r it e T o LogB " D p e n i n g C : \ t e s t . d g n " End Sub Sub W r i t e T o L o g B ( L o g F i 1 e T e x t As S t r i n g )
D i m m y V B E As O b j e c t D i m x S p l i t As V a r i a n t D i m F F i l e As L o n g
-
I Logging File Batch Processing I S e t myVBE x S p l it
=
= Application.VBE Spl i t ( m y V B E . A c t i v e V B P r o j e c t . Filename, “ \ “ I
xSplit(UBound(xSp1it)) FFile
=
615
=
“BatchProcess-”
&
ThisComputerName
&
“.log”
FreeFile
Open J o i n ( x S p l i t , ” \ ” ) P r i n t BFFile,
F o r Append As # F F i l e
LogFileText
C1 o s e B F F i 1 e End Sub Function
ThisComputerNameO
ThisComputerName
=
As S t r i n g
Space(255)
GetComputerName T h i s C o m p u t e r N a m e , L e n ( T h i s C o m p u t e r N a m e ) ThisComputerName
=
Left(ThisComputerName, I n S t r ( 1 , ThisComputerName, C h r ( 0 ) )
-
1)
End F u n c t i o n
Now, each computer running this code has its own Batchprocess .log file. We could base the log file on the “UserName”that is logged into the computer but this could cause problems because we may log onto multiple computers using the same “UserName” that has been established specifically for Batch Processing. It is more likely that the Computer Name on the Network will be unique from machine to machine rather than the user logged into each computer.
Tracking Activities with a Database Tracking activities with a database can be extremely useful. Databases can be queried easily for information. Reports can be made from databases. An .asp file can be created and run from a web browser to provide real-time tracking of batch processing. The benefits are numerous. We will be jumping into databases later in this book so we will not provide any examples right now. Let’s keep logging activities in mind, though, when we get into databases.
Storing Information in the Registry The Windows Registry has become a common storage medium for all sorts of information. Whenever we see a “RecentlyOpened File” list it is likely stored in the Registry. Software serial numbers, installation dates, installation locations, and file associations are among some of the other
616
I Chapter 30: Batch Processing I things we will find there. Although we don't want to add great volumes of information to the Registry, we can add some logging information such as "CurrentFile" and "LastFile': Sub Wri t e T o R e g i s t r y ( ) Savesetting "MicroStation VBA", " La s t F i1 e " , " C : \ F i 1 eA . d g n "
"Batch Processing",
Savesetting "MicroStation VBA", "Batch Processing", " C u r r e n t F i 1e " , " C : \ F i 1 e B .d g n "
~
~
End Sub
Writing information to the Registry is simple. Reading information from the Registry is just as simple. Sub R e a d F r o m R e g i s t r y ( 1 D i m L a s t F i l e As S t r i n g
D i m C u r r e n t F i l e As S t r i n g LastFile
=
GetSetting("MicroStation VBA", "Batch Processing" ,
CurrentFile
=
"
~
L a s t F i 1e " )
GetSetting("MicroStation VBA",
-
"Batch Processing", "CurrentFile") Debug. P r i n t L a s t F i 1 e Debug. P r i n t C u r r e n t F i 1 e End Sub
Logging Activities over the Internet The Internet is used for all sorts of activities. Normally we use it to get information. It can also be used to transmit information. For example, if we open a web browser to http://www.google. com/search?q=MicroStation
Google returns thousands upon thousands of pages related to MicroStation.
I Logging File Batch Processing I
617
If, however, we entered h ttp://www.microstationlogging.com?filename=fiIea.dgn this fictitious web site could log the file, the datehime, the IP Address, etc. But we don't want the user to have to type in the URL every time a file is processed. Right? Let's have our software send the request. It is time for another Reference. "Microsoft Internet Controls" S u b LogToWeb()
Dim LocalFile A s String Dim myInet A s New InternetExplorer Local File = "filea.dwg" myInet.Navigate "http://www.microstationlogging.com?filename=" & Local Fi 1 e While myInet.Busy DoEvents Wend
-
End S u b
The code consists of only a few lines. LogToWeb navigates to the specified URL, in this case, the fictitious website, www.rnicrostation/ogging.com. If this were a real website, and if the default page at that web site received the parameter "filename", it could log the file name sent to it into a database on the web server. Logging information to the Web has its advantages. For one, it doesn't matter where in the world the individual who is running the program is. If the computer is connected to the Internet, it can have its activities logged. Web servers are specifically designed for high traffic, high volume situations so we could have hundreds or even thousands of people using this site to log their activities without causing any problems. Others may hit the site to see real-time statistics as to what is happening, who is working and who isn't, average time per file, etc. The sky's the limit. And it is all possible by making a simple Reference in VBA and then navigating to a specific URL. This same technique can be used to get information from a website. Although this is not a chapter on Internet Technologies, a small example won't hurt.
I Chapter 30: Batch Processing I Sub
GetFromWebO D i m I As Long D i m L o c a l F i l e As S t r i n g D i m m y I n e t As New I n t e r n e t E x p l o r e r D i m TheURL As S t r i n g D i m x S p l i t A As V a r i a n t D i m x S p l i t B As V a r i a n t
D i m x S p l i t C As V a r i a n t D i m x S p l i t D As V a r i a n t D i m T r a i n i n g D a t e As D a t e D i m T r a i n i n g L o c a t i o n As S t r i n g D i m T r a i n i n g c o s t As S t r i n g D i m M s g B o x T e x t As S t r i n g
The U R L
=
"
h t t p : / / b e n t 1 e y in s t it u t e . b e n t 1 e y . c om/
"
&
~
"courseinfo.aspx?course=TRC001810-1/0001" m y I n e t . N a v i g a t e TheURL
<>
While myInet.ReadyState
READYSTATE-COMPLETE
DoEvents Wend xSplitA = Split(myInet.Document.body.innerHTML, x S p l it B = S p l it ( x S p l it A ( 11 , "
"InPersonLabClasses")
F o r I = L B o u n d ( x S p l i t B 1 To U B o u n d ( x S p 1 i t B ) xSplitC
=
Split(xSplitB(I1,
I f IsArray(xSplitC1
I f UBound(xSplitC1 x S p l it D
=
=
>=
=
=
"
<
"
)
C D a t e ( Rep1 a c e ( x S p 1 it D (
S p l it ( x S p l it C ( 2 ) ,
TrainingLocation x S p l it D
3 Then
S p l it ( x S p l it C ( 11 ,
T r a i n i ngDate x S p l it D
"")
T r u e Then
=
=
"<
"
0) ,
"
.
"
,
" / ") )
)
xSplitD(0)
S p l i t ( x S p l it C ( 3 ) ,
"<" )
Trainingcost
=
MsgBoxText
MsgBoxText & T r a i n i n g D a t e & vbTab &
=
xSplitD(0) -
T r a i n i n g c o s t & vbTab & T r a i n i n g L o c a t i o n & vbCr End I f End I f Next I MsgBox M s g B o x T e x t , v b I n f o r m a t i o n , End Sub
" M i c r o S t a t i o n VBA T r a i n i n g "
I Logging File Batch Processing I
619
The Bentley Institute sponsors VBA Training from time to time. We can find training dates, locations, and costs on their web site. This macro is designed to display this information in a MessageBox.
As the time of this writing, three classes are scheduled. Their dates, costs, and locations are shown here in this MessageBox. The code is designed to find specific HTML tags and annotation to ‘drill down’ to the information we want. The HTML for this page may change in the future, but this shows how easy it is to create our own information gathering tools using VBA.
E-mailing Transaction Logs E-mail. It seems as though we can’t get away from it. At one time it was restricted to our computers at work or at home. Now it follows us around on PDAs, cell phones, and Blackberry devices. So, can we make use of this technology using VBA? Of course we can, with one string attached. This string, of course, is a Reference.
If the Reference does not show up in the list, clicking the Browse button and browsing to C:\Windows\system32\cdosys.d//does the trick.
620
I Chapter 30: Batch Processing I CDO. What does it stand for? Collaborative Data Objects. In plain English, e-mail. Sub T e s t E m a i 1 ( )
D i m m y M a i l As New CDD.Message myMa i1 . T o
=
myMa i1 . From
"
b a t c h@mic r o s t a t ion1 o g g i n g . com"
=
"
myMail . S u b j e c t
b a t c h@mic r o s t a t ion1 o g g i n g . com" =
m y M a i l .HTMLBody
=
" M i c r o s t a t i o n VBA B a t c h Process Log" " < b > F i l e name: f i l e a . d g n < / b > < b r > " "Computer:
"
&
-
& ThisComputerName & "
" &
-
"Date: 1/1/2005" myMai 1 . C o n f i g u r a t i o n . F i e l ds. I t e m ( " h t t p : //schemas . m i c r o s o f t . corn/" & -
"cdo/configuration/sendusing")
=
myMai 1 . C o n f i g u r a t i o n . F i e l ds. I t e r n ( " h t t p : //schemas . m i c r o s o f t . corn/" &
"cdo/configuration/smtpserver")
2
~
-
=
" y o u r s m t p s e r v e r . com" rnyMail.Configuration.Fields.Item("http://schernas.rnicrosoft.com/" &
"cdo/configuration/smtpserverport")
~
25
=
myMai 1 . C o n f i g u r a t i o n . F i e l d s . U p d a t e myMai 1 . S e n d End Sub
That's about as difficult as it gets. With the right reference and a little bit of code, our program is now capable of e-mailing batch processing information to an e-mail address. In order for this code to work correctly, the "To" property should be set to a legitimate e-mail address and the SMTPServer Field should be set to a legitimate SMTP server. And how difficult is it to attach a file to this e-mail? Sub T e s t E m a i 1 2 ( 1
D i m m y M a i l As New CDD.Message myMa i1 .To = " b a t c h@mic r o s t a t io n 1 o g g i n g . com" myMa i1 . From
=
"
myMail . S u b j e c t myMai 1 .HTMLBody
b a t c h@mic r o s t a t io n 1 o g g i n g . com" = =
" M i c r o s t a t i o n VBA B a t c h Process Log" " < b > F i 1 e name: f i 1 ea . d g n < / b > < b r > " "Computer:
"
&
-
& ThisComputerName & "
" &
"Date: 1/1/2005"
myMail.Configuration.Fields.Item~"http://schemas.microsoft.com/" &
"cdo/configuration/sendusing")
=
&
rnyMail.Configuration.Fields.Item("http://schernas.rnicrosoft.com/"
"cdo/configuration/smtpserver")
-
2
=
-
~
I Review I
621
" y o u r s r n t p s e r v e r . corn" myMail.Configuration.Fields.Item("http://schemas.microsoft.com/" & "cdo/configuration/smtpserverport") = 2 5 ~
rnyMail.Configuration.Fie1ds.Update
.
.
myMa i1 AddAtta chment "C :\ t e s t dgn"
rnyMai 1 . S e n d End Sub
One line of code. AddAttachment. That's it.
The task of Batch Processing is accomplis,,ed relatively easily. We need to know which files we want to process. This can be discovered by using an ASCII file, having the user select a folder to process, or selecting individual files within a folder. Next, we need to do the processing of the files. Along the way, we can Log our activities through a variety of methods. Once our code is in place, it matters very little whether we need to process 5 files or 5,000 files. Our productivity and accuracy can increase exponentially compared to manually opening each file one by one.
31
The Standards Checker The Standards Checker helps us to keep our files in line with established standards. Using this functionality as it is installed with Microstation can do a fairly good job performing basic standards checking. Add the power and flexibility of VBA and nothing can hold us down. Before we take any more time on the Standards Checker we should note that unhandled errors that occur in our Standards Checker routines can cause severe errors. Some may even shut down Microstation completely. So, we need to take care when working with Standards Checker code. We should make sure we save our VBA Projects often and verify that any DGN files open in Microstation are also saved. In this Chapter:
Basics of implementing the Standards Checker Standards Checker Settings Checking for Standards Standards Checker Reporting Automatically Loading Custom Standards Checker Add-Ins
623
624
I Chapter 31: The Standards Checker I
BASICSOF IMPLEMENTING THE STANDARDS CHECKER The Standards Checker is implemented by using the IStandardsChecker Interface. As with other Interfaces we have implemented, a Class Module is used and our code is written within the bounds of Events. ‘General D e c l a r a t i o n s Implements IStandardsChecker
P r i v a t e Sub IStandardsChecker-AddedCheckerToStandardsCheckerApps( B y V a l A p p l i c a t i o n X M L N o d e As O b j e c t ) End Sub
P r i v a t e P r o p e r t y Get IStandardsChecker-CallForEachModelO As B o o l e a n End P r o p e r t y
P r i v a t e Sub
IStandardsChecker-CreateSettings()
End Sub
P r i v a t e Sub
IStandardsChecker-DeleteSettings()
End Sub
P r i v a t e P r o p e r t y G e t I S t a n d a r d s C h e c k e r - D e s c r i p t i o n 0 As S t r i n g End P r o p e r t y
P r i v a t e P r o p e r t y Get IStandardsChecker-DialogStringO End P r o p e r t y
As S t r i n g
I Basics of Implementing the Standards Checker I P r i v a t e Sub I S t a n d a r d s C h e c k e r - E d i t S e t t i n g s ( B y V a 1 I s R e a d O n l y As B o o l e a n )
625 -
End Sub
P r i v a t e P r o p e r t y Get I S t a n d a r d s C h e c k e r - F o u n d s e t t i n g s o
As B o o l e a n
End P r o p e r t y
P r i v a t e Sub IStandardsChecker-GetFixDetail(Fixes0 As S t r i n g , ByVal S e l e c t e d F i x As Long, F i x P r o p e r t i e s L a b e l As S t r i n g , F i x p r o p e r t i e s o As S t r i n g )
-
End Sub
P r i v a t e P r o p e r t y Get
IStandardsChecker-Hassettings0 As B o o l e a n
End P r o p e r t y
P r i v a t e P r o p e r t y Get
IStandardsChecker-IdentityStringO As S t r i n g
End P r o p e r t y
P r i v a t e Sub I S t a n d a r d s C h e c k e r - R u n C h e c k ( B y V a 1
-
ModelToCheck As ModelReference, ByVal F i r s t M o d e l As Boolean, ByVal O p t i o n s As Long) End Sub
P r i v a t e P r o p e r t y Get I S t a n d a r d s C h e c k e r - V e r s i o n S t r i n g O
As S t r i n g
End P r o p e r t y
Six events and seven properties are implemented. As the user begins the Standards Checker by selecting Utilities > Standards Checker > Check, the properties in our Class Module are used in the Standards Checker user interface. Let’s implement a Standards Checker that does nothing other than implementing the most basic properties and uses
626
I Chapter 31: The Standards Checker I Debug.Print statements so we can see the order in which the events take place.
Standards Check A The following code is placed
in
a Class Module named
clsStandChe&A. O p t i o n E x p l ic i t Implements IStandardsChecker P r i v a t e Sub I S t a n d a r d s C h e c k e r - A d d e d C h e c k e r T o S t a n d a r d s C h e c k e r A p p s ( B y V a l A p p l i c a t i o n X M L N o d e As O b j e c t ) D e b u g . P r i n t "AddedCheckerToStandardsCheckerApps" End Sub P r i v a t e P r o p e r t y Get IStandardsChecker-CallForEachModelO
As B o o l e a n
Debug.Print " C a l l ForEachModel" IStandardsChecker-Call ForEachModel
=
True
End P r o p e r t y P r i v a t e Sub
IStandardsChecker-CreateSettings()
Debug.Print "CreateSettings" End Sub P r i v a t e Sub
IStandardsChecker-DeleteSettings()
Debug.Print "DeleteSettings" End Sub P r i v a t e P r o p e r t y G e t I S t a n d a r d s C h e c k e r - D e s c r i p t i o n 0 As S t r i n g Debug. P r in t
"
Desc r ip t io n "
IStandardsChecker-Description
=
"VBA StandChk A Desc"
End P r o p e r t y P r i v a t e P r o p e r t y Get IStandardsChecker-DialogStringO Debug. P r in t
"
As S t r i n g
D i a 1 o g S t r in g "
IStandardsChecker-Dialogstring
=
"VBA StandChk A D i a l "
End P r o p e r t y P r i v a t e Sub I S t a n d a r d s C h e c k e r - E d i t S e t t i n g s ( B y V a 1 IsReadOnly A s Boolean) Debug. P r in t " E d i t Se t t in g s " End Sub
I Basics of Implementing the Standards Checker I
627
P r i v a t e P r o p e r t y Get I S t a n d a r d s C h e c k e r - F o u n d S e t t i n g s O
As B o o l e a n
Debug. P r i n t " F o u n d S e t t i n g s " End P r o p e r t y P r i v a t e Sub
IStandardsChecker-GetFixDetail(Fixes0 B y V a l S e l e c t e d F i x As L o n g ,
As S t r i n g ,
-
-
F i x P r o p e r t i e s L a b e l As S t r i n g ,
-
F i x p r o p e r t i e s o As S t r i n g ) Debug.Print
"GetFixDetail"
End Sub P r i v a t e P r o p e r t y Get
IStandardsChecker-HasSettingsO
As B o o l e a n
Debug. P r i n t " H a s S e t t i n g s "
IStandardsChecker-Hassettings
=
True
End P r o p e r t y P r i v a t e P r o p e r t y Get Debug . P r in t
"
IStandardsChecker-IdentityStringO
As S t r i n g
I d e n t ityS t r in g "
IStandardsChecker-IdentityString
=
"VBA-SC-A"
End P r o p e r t y P r i v a t e Sub I S t a n d a r d s C h e c k e r - R u n C h e c k ( B y V a 1 M o d e l T o C h e c k As M o d e l R e f e r e n c e , B y V a l F i r s t M o d e l As B o o l e a n ,
-
-
B y V a l O p t i o n s As L o n g ) Debug . P r in t
"
I d e n t ityS t r in g "
End Sub P r i v a t e P r o p e r t y Get Debug. P r in t
"
IStandardsChecker-VersionStringO As S t r i n g
Ve r s ion St r in g "
IStandardsChecker-Versionstring
=
"1.0.0.0"
End P r o p e r t y
Now we need to write some code that will add the above Class Module to the Standards Checker in Microstation. This code is located in a Code Module. O p t i o n E x p l ic i t P r i v a t e S t a n d C h k As c l s S t a n d C h e c k A Sub Addchecker()
628
I Chapter 31: The Standards Checker I Removechecker S e t StandChk
=
New c l s S t a n d C h e c k A
StandardsCheckerController.AddStandardsChecker S t a n d C h k , 1 0 0 0 End Sub
Sub Removechecker()
I f N o t StandChk Is N o t h i n g Then
StandardsCheckerController.RemoveStandardsChecker S t a n d C h k End I f S e t StandChk
=
Nothing
End Sub
When we run Addchecker, the Class Module is instantiated and it is added to Microstation’s Standard Checker dialog box.
When we run the Standards Checker, we can see how the properties returned in the Class Module are used. In this view we can see “VBA StandChk A Dial” which comes from the D i a 1 o g S t r i n g property. Prior to creating the Class Module, the “VBA Check A” Setting in the Standards Checker Settings Configuration dialog box was created. We can select the “VBA StandChk A Dial” CheckBox and can click the Settings button. When we do so, events are triggered and their associated Debug.Print statements are executed. At this point we are able to get our own Standards Checker into the Standards Checker dialog. A good start. But nothing to write home about. Before we continue let’s talk about the entire Standards Checker process.
I Basics of Implementing the Standards Checker I 1 A Class Module is created that implements the IStandardsChecker Interface.
2
A Procedure declares a variable as the Class Module and then adds it using Add St a nda r ds C h ec ke r.
3 When the user clicks Utilities > Standards Checker > Configure, the Events and Properties are triggered in the following sequence: IdentityString DialogString Hassettings CalIForEachModel Foundsettings IdentityString DialogString
The next dialog we see will vary based on the Hassettings property. If we return a value of True in this property, we see a Settings button.
4 After Settings are made, we can select our new custom Standards Checker Add-in.
629
630
I Chapter 31: The Standards Checker I If, however, the HasSettings property returns a value of False, we do not get a Settings button and the Standards Checker we created is enabled and ready to be selected.
f
If available, the user clicks the Settings button. The Editsettings event is triggered with a "IsReadOnly" parameter value of False. When we are given a value of False we know that we should allow the user to change the settings for our Standards Checker. At this point we can display a dialog box that allows the user to select various settings for our Standards Checker. After the Editsettings event exits execution, the Standards Checker we created is enabled and can be selected. Each time the Standards Checker we created is selected or deselected, the IdentityString Property is retrieved. When the user clicks the OK button, the Standards Checker we created is selected. Let's implement a Settings dialog box now. In order for a Settings dialog box to display, we need to have the following code in our StandardsCheckerclass module: P r i v a t e P r o p e r t y Get IStandardsChecker-HasSettingsO
As Boolean
Debug.Print "HasSettings"
IStandardsChecker-Hassettings
=
True
End P r o p e r t y
Private Sub I S t a n d a r d s C h e c k e r - E d i t S e t t i n g s ( B y V a 1 IsReadOnly A s Boolean) Debug. P r in t " E d i t Se t t in g s "
I Standards Checker Settings I
631
UserForml.Show End S u b
Of course, the Debug.Print statements are only here to help us see the order in which the events are triggered. The important things are to set the IStandardsChecker-Hassettings to True and to display a UserForm in the Editsettings event. At this point, we are ignoring the IsReadOnly parameter of the Editsettings event. So, what settings are we going to allow the user to set? By default, Microstation has settings for Levels, Text Styles, Dimension Styles, Element Templates, and Line Styles. What else is there to check?
STANDARDSCHECKERSETTINGS Standards Checker Interfaces can make use of ‘Settings: IStandardsChecker-HasSettingsO IStandardsChecker-Hassettings = T r u e
P r i v a t e P r o p e r t y Get
As B o o l e a n
End P r o p e r t y
If the Hassettings property is True, the Settings button displays in the Standards Checker Settings dialog box (Utilities > Standards Checker > Configure). When the Settings button is clicked, the Editsettings event is triggered. P r i v a t e Sub IStandardsChecker-EditSettings(ByVa1 Boo1 e a n )
I s R e a d O n l y As
UserForml.Show End S u b
OK. Now when the Settings button is clicked we display a UserForm named “UserForml’: Let’s design a Graphical User Interface that resembles the one shown here:
632
I Chapter 31: The Standards Checker I This example is going to allow for the entry and storing of settings relating to Room Labels in a design file. We need to allow the selection of the following properties: Room Labels are Written in a File The File Contains only Legitimate Room Labels The Path of the File Should the Standards Checker 'Fix' the Errors it encounters? The interface is fairly simple and straight forward. Entering the data is the easy part. How will we 'store' the settings that are made? We have several options: Save the settings in an ASCII text file. Save the settings in the design file so settings are unique to each design file. Save the settings in the Windows Registry. Save the settings as XML in the Settings dgnlib file. We will examine one way we can save Standards Checker settings. Our example here will deal with storing values in the Windows Registry. Another method of storing Standards Checker (storing settings in XML format in a .dgnlib file) settings can be found in the Microstation VBA Help file. In this example we will save our settings in the Windows Registry. Here is the code behind the GUI: P r i v a t e Sub U s e r F o r m - I n i t i a l i z e 0 chkLabelsWritten.Value
=
~
GetSetting("VBA Standards Checker", " S e t t i n g s " ,
"Labels W r i t t e n " ,
-
False) chkCleanFile.Va1ue
=
~
GetSetting("VBA Standards Checker", " S e t t i n g s " ,
chkFixErrors.Value
=
-
GetSetting("VBA Standards Checker",
txtFilePath.Text
=
"Clean F i l e " , F a l s e )
"Settings",
"Fix Errors",
-
GetSetting("VBA Standards Checker",
"Settings",
" F i l e Path",
False)
I Standards Checker Settings I
633
" C : \ M i c r o S t a t ion VBA\ La b e 1 s . t x t " ) End Sub
P r i v a t e Sub b t n O K - C l i c k 0 If txtFilePath.Text
=
" "
Then
MsgBox "A f i l e p a t h m u s t b e e n t e r e d . " E x i t Sub End I f S a v e s e t t i n g "VBA S t a n d a r d s C h e c k e r " , " S e t t i n g s " , "
-
La b e 1 s W r it t e n " . c h k La b e 1 s W r it t e n . Va 1 u e
S a v e s e t t i n g "VBA S t a n d a r d s C h e c k e r " , " S e t t i n g s " " C l e a n F i l e " , c h k C l e a n F i l e . V a l ue S a v e s e t t i n g "VBA S t a n d a r d s C h e c k e r " , " S e t t i n g s " , "
-
F i x E r r o r s " , c h k F i x E r r o r s . Va 1 u e
S a v e s e t t i n g "VBA S t a n d a r d s C h e c k e r " , " S e t t i n g s " , "
F i 1 e P a t h " , t x t F i1 e P a t h . T e x t
U n l o a d Me End Sub
P r i v a t e Sub b t n C a n c e l L C l i c k 0 U n l o a d Me End Sub
When the Settings Form is initialized, we look at the Windows Registry by using the standard VBA call GetSetting and providing a default value. The default value is important because the first time this Settings Form displays, the Registry will not have the 'Settings' information we need. When the user clicks the OK button, we save the properties of the CheckBoxes and the TextBox in the Registry using the SaveSetti ng call. After the values are saved in the Registry, we unload the Form. If the Cancel button is clicked, we unload the Form without saving values to the Registry.
634
I Chapter 31: The Standards Checker I CHECKING FOR STANDARDS
Where we are at this point At this point, we can add a custom Standards Checker that displays in the Standards Checker dialog boxes. We can also display and save settings made when the Settings button is clicked in the Standards Checker Settings dialog box. So, how do we actually ‘Check‘ our document when we are ready to do so? The first thing we need in place is a file to check. Let’s open the file BS1300AE7 0 7-P/an.dgn. It is installed with Microstation.
Now we are ready to check this file. We are going to look at a procedure that checks for, reports, and fures (when set to do so) situations relating to the Room Label Tags in the file BS/300AE107-P/an.dgn. It is named “RoomLabelChecks” and it is divided into seven segments: Declare Variables Read All Room Label Tags in Drawing Get Settings From Registry Read Room Label File Check for File’s Existence Check for Tags in File Check to make sure Labels in the File have Tags in the DGN file Each ‘segment’ in this procedure is proceeded by a comment matching the bulleted items above. We ‘check‘ our file by making use of the RunC hec k event in the IStandardsChecker Interface. P r i v a t e Sub I S t a n d a r d s C h e c k e r - R u n C h e c k ( B y V a 1 M o d e l T o C h e c k As M o d e l R e f e r e n c e , B y V a l F i r s t M o d e l As B o o l e a n , B y V a l O p t i o n s As L o n g ) RoomLabelChecks ModelToCheck End Sub
And now for the procedure “RoomLabelChecks”:
I Checking for Standards I
635
Sub R o o m L a b e l C h e c k s ( M o d e 1 T o S c a n As M o d e l R e f e r e n c e ) 'Declare Variables
D i m myElementEnum As E l e m e n t E n u m e r a t o r D i m m y s c a n c r i t e r i a As New E l e m e n t S c a n C r i t e r i a D i m m y T a g s 0 As S t r i n g ReDim m y T a g s ( 0 ) A s S t r i n g D i m m y T a g s F i l t e r 0 As S t r i n g
D i m RoomTag As T a g E l e m e n t D i m I As L o n g D i m A u t o F i x As B o o l e a n D i m L a b e l s I n F i l e As Boolean D i m L e g i t i m a t e L a b e l s As Boolean D i m m y F i l e R o o m s 0 As S t r i n g ReDim m y F i l e R o o m s ( 0 ) As S t r i n g
D i m F F i l e As L o n g D i m F i l e N a m e As S t r i n g D i m L i n e I n As S t r i n g
' R e a d A l l Room L a b e l T a g s i n D r a w i n g
myScanCriteria.ExcludeAllTypes myScanCriteria.IncludeType m s d E l e m e n t T y p e T a g S e t myElementEnum = ModelToScan.Scan(myScanCriteria) Whi 1 e myEl ementEnum.MoveNext myElementEnum.Current
S e t RoomTag
=
S e l e c t Case
UCase(RoomTag.TagDefinitionName)
Case " R O O M LABEL" myTagsFilter
F i l t e r ( m y T a g s , RoomTag.Value, T r u e , VbBinaryCompare)
=
I f UBound(myTagsFi1ter)
=
myTags(UBound(myTags))
-1 T h e n =
RoomTag.Value
ReDim P r e s e r v e m y T a g s ( U B o u n d ( m y T a g s ) + End I f End S e l e c t Wend I*********
I f UBound(myTags)
>=
1 Then
ReDim P r e s e r v e m y T a g s ( U B o u n d ( m y T a g s ) End I f
-
1)
1)
636
I Chapter 31: The Standards Checker I 'Get Settings from Registry FileName
GetSetting("VBA Standards Checker", " F i l e Path", -
=
"
AutoFix
=
=
-
C : \ M i c r o S t a t io n V B A \ La be1 s . t x t "
GetSetting("VBA Standards Checker", "Fix Errors", " Fa 1 s e "
LabelsInFile
"Settings",
"Settings",
GetSetting("VBA Standards Checker", " S e t t i n g s " ,~ W r it t e n " , " Fa 1 s e " ) " La b e 1 s
LegitimateLabels
-
~
= GetSetting("VBA Standards Checker", "Settings", " C 1 ea n F i 1 e " , " Fa 1 s e " )
-
' R e a d Room L a b e l F i l e FFile
FreeFile
=
If D i r ( F i 1 e N a m e )
<>
"I'
Then
Open F i l e N a m e F o r I n p u t As I I F F i l e W h i l e EDF(FFi1e)
=
False
myFileRooms(UBound(myFi1eRooms)) myFileRooms(UBound(myFi1eRooms) + 1)
Line Input {IFFile, ReDim P r e s e r v e Wend
I f UBound(myFi1eRooms) ReDim P r e s e r v e
>=
1 Then
myFileRooms(UBound(myFi1eRooms)
-
1)
End I f C1 o s e # F F i 1 e End I f
'Check f o r F i l e ' s E x i s t e n c e
If D i r ( F i 1 e N a m e )
=
Debug.Print
I f AutoFix
"I'
Then
"The F i l e =
"
& FileName &
"
does n o t e x i s t . "
T r u e Then
Open F i l e N a m e F o r O u t p u t As # F F i l e F o r I = L B o u n d ( m y T a g s ) To UBound(myTags) P r i n t #FFile,
myTags(1)
Next I C1 o s e # F F i 1 e D e b u g . P r i n t "The F i l e " & FileName & " has been c r e a t e d . " End I f End I f
-
I Checking for Standards I
637
'Check f o r Tags i n F i l e
If LabelsInFile
=
T r u e Then
F o r I = L B o u n d ( m y T a g s ) To U B o u n d ( m y T a g s 1 myTagsFilter
Filter(myFi1eRooms. myTags(I1, True, vbBinaryCompare)
=
I f UBound(myTagsFi1ter)
=
-1 T h e n
D e b u g . P r i n t "Tag " & myTags(1) & " n o t found i n f i l e . "
I f AutoFix
=
-
-
T r u e Then
Open F i l e N a m e F o r A p p e n d As B F F i l e P r i n t #FFile,
myTags(1)
Debug.Print "Label " & myTags(1) & " added t o F i l e . "
-
C1 o s e # F F i 1 e End I f End I f Next I End I f
' C h e c k t o make s u r e L a b e l s i n t h e F i l e h a v e ' T a g s i n t h e DGN f i l e
If LegitimateLabels I f Dir(Fi1eName)
=
T r u e Then
=
" "
Then
Debug.Print " F i l e
"
& FileName &
"
does n o t e x i s t . "
Else F o r I = L B o u n d ( m y F i 1 eRooms) To U B o u n d ( m y F i 1 eRooms) m y T a g s F i 1 t e r = F i 1 t e r ( m y T a g s , m y F i 1 eRooms( I ) , True, vbBinaryCompare) ~
I f UBound(myTagsFi1ter)
=
-1 Then
D e b u g . P r i n t " F i l e Room " & myFileRooms(1) & " i s n o t i n t h e DGN F i l e . " ~
End I f Next I End I f End I f End Sub
The Procedure is rather lengthy. Let's discuss each segment now.
638
I Chapter 31: The Standards Checker I 1 Declare Variables. This section is fairly self-explanatory.We use a couple of dynamic arrays because before the code is executed, we do not know how many Room Labels we will find in the drawing or in the file.
2 Read All Room Label Tags in Drawing. We create a Scancriteria filtering on Tags. We then look to see which Tags in the drawing are “Room Labels’: When we find one, we assign it to the last element in the dynamic array myTags and then increase the size of the array, preserving the existing values.
3 Get Settings From Registry. Placing the Standards Checker Settings into variables means we only need to read them once and our code is easier to read than having multiple Get Se t t in g calls for the same Registry entry.
4
Read Room Label File.
We specify a file name in the Settings dialog box. This file is to contain all Room Label values in our DGN file. If the file exists, we read the file, placing each line of the file into its own element in a dynamic array.
5
Check for File’s Existence.
If the file in which we are placing Room Label values does not exist, we create it and populate it if the ?AutomaticallyFix Errors” setting is True.
6 Check for Tags in File. We look at each Room Label Tag found in the drawing file and check for its presence in the file. If the Room Label Tag is not found in the file, we add it to the file (if AutoFix is True) and report the ‘error’ to the Debug (Immediate) Window.
7 Check to make sure Labels in the File have Tags in the DGN file. It is possible that the ASCII File we are looking at has Room Label values in it that are not in the drawing. This could be due to data entry errors or may be the result of having deleted a Room Label from the drawing. In either case, if we find a Room Label in the ASCII file that does not have an associated Room Label value in the drawing, we ‘report’ the problem by printing to the Immediate Window.
I Standards Checker Reporting I
639
STANDARDSCHECKERREPORTING Let’s expand our Standards Checker now and use some of the built-in error reporting functionality. We are going to add a custom Enumeration in the General Declarations area of the Class Module and one Procedure to the body of the Class Module. All code changes are shown in bold. We will use the enumeration “ErrorType”to help describe the nature of the Error we are reporting. As we see here, we have seven elements in the enumeration. P r i v a t e Enum E r r o r T y p e NoError
=
0
NotInFile
=
NotInDGN
=
1
2
FixedNotInFile FixedNotInDGN
11
= =
12
NotFixedNotInFile NotFixedNotInDGN
=
=
21
22
End Enum
When we find something we want to ‘report’, we use the Procedure Rep0 r t E r r o r. We specify which type of error we encountered as well as the Room Number that was not found, corrected, or not corrected. P r i v a t e Sub R e p o r t E r r o r ( E r r 0 r As E r r o r T y p e , RoomNumber As String)
D i m mySCC As StandardsCheckerController S e t mySCC
=
StandardsCheckerController
S e l e c t Case E r r o r Case ErrorType.NotInDGN mySCC.Tota1 Problems
=
mySCC.Tota1 Problems + 1
=
mySCC.Tota1 Problems
Case E r r o r T y p e . N o t I n F i l e mySCC.Tota1 Problems
+
1
+
1
Case E r r o r T y p e . F i x e d N o t I n D G N mySCC.FixedProblems
=
mySCC.FixedProblems
Case ErrorType.FixedNotInFi1e mySCC.FixedProblems
=
mySCC.FixedProblems + 1
Case ErrorType.NotFixedNotInDGN mySCC.IgnoredProblems
=
mySCC.IgnoredProblems
Case ErrorType.NotFixedNotInFile
+
1
640
I Chapter 31: The Standards Checker I mySCC.IgnoredProb1ems End Select End S u b
=
mySCC.IgnoredProb1ems + 1
The only thing we are doing differently in Roomlabel Checks is that we are using the Rep0 r t E r r o r procedure when we encounter an 'error' or when we 'fx' the 'error'. Sub RoomLabel C h e c k s ( M o d e 1 T o S c a n As Model R e f e r e n c e ) 'Declare Variables
D i m myElementEnum As E l e m e n t E n u m e r a t o r D i m m y S c a n C r i t e r i a As New E l e m e n t S c a n C r i t e r i a D i m m y T a g s 0 As S t r i n g
ReDim m y T a g s ( 0 ) As S t r i n g
D i m m y T a g s F i l t e r 0 As S t r i n g D i m RoomTag As T a g E l e m e n t D i m I As Long D i m A u t o F i x As B o o l e a n D i m L a b e l s I n F i 1 e As B o o l e a n D i m L e g i t i m a t e L a b e 1 s As B o o l e a n
D i m m y F i l e R o o m s 0 As S t r i n g ReDim m y F i l e R o o m s ( 0 ) As S t r i n g
D i m F F i l e As L o n g D i m F i l e N a m e As S t r i n g D i m L i n e I n As S t r i n g
' R e a d A l l Room L a b e l T a g s i n D r a w i n g myScanCri t e r i a. E x c l udeAl1Types
myScanCriteria.IncludeType m s d E l e m e n t T y p e T a g S e t myElementEnum = ModelToScan.Scan(myScanCriteria) W h i l e myElementEnum.MoveNext myElementEnum.Current
S e t RoomTag
=
S e l e c t Case
UCase(RoomTag.TagDefinitionName)
Case " R O O M LABEL" m y T a g s F i l t e r = F i l t e r ( m y T a g s , RoomTag.Value, T r u e , vbBinaryCompare)
I f UBound(myTagsFi1ter)
=
myTags(UBound(myTags))
- 1 Then =
RoomTag.Value
ReDim P r e s e r v e m y T a g s ( U B o u n d ( m y T a g s ) + End I f End S e l e c t
1)
-
I Standards Checker Reporting I
641
Wend
>=
I f UBound(myTags)
1 Then
ReDim P r e s e r v e m y T a g s ( U B o u n d ( m y T a g s )
-
1)
End I f 'Get Settings from Registry FileName
=
GetSetting("VBA Standards Checker", " F i l e Path", "
AutoFix
=
"Settings",
C : \ M i c r o S t a t io n V B A \ La be1 s . t x t " 1
GetSetting("VBA Standards Checker", " F ix E r r o r s " , " Fa 1 s e "
LabelsInFile
"Settings",
= GetSetting("VBA Standards Checker", "Settings", "Labels Written", "False")
LegitimateLabels
-
=
-
-
GetSetting("VBA Standards Checker", " S e t t in gs " , " C 1 ea n F i 1 e " , " Fa 1 s e " )
-
' R e a d Room L a b e l F i l e FFile
FreeFile
=
I f Dir(Fi1eName)
<>
Then
" "
Open F i l e N a m e F o r I n p u t As # F F i l e W h i l e EOF(FFi1e)
=
False
L i n e I n p u t # F F i l e , myFi l e R o o m s ( U B o u n d ( m y F i 1 eRooms) 1 ReDim P r e s e r v e m y F i l e R o o m s ( U B o u n d ( m y F i 1 e R o o m s ) +
1)
Wend
I f UBound(myFi1eRooms)
>=
1 Then
ReDim P r e s e r v e m y F i l e R o o m s ( U B o u n d ( m y F i 1 e R o o m s )
-
1)
End I f C1 o s e B F F i 1 e End I f
'Check f o r F i l e ' s E x i s t e n c e
I f Dir(Fi1eName)
=
" "
Then
0 e b u g . P r i n t "The F i l e
I f AutoFix
=
"
& FileName &
"
does n o t e x i s t . "
T r u e Then
Open F i l e N a m e F o r O u t p u t As B F F i l e For I
=
L B o u n d ( m y T a g s 1 To UBound(myTags)
P r i n t B F F i l e , myTags(1) Next I C1 o s e # F F i 1 e
I Chapter 31: The Standards Checker I D e b u g . P r i n t "The F i l e " & FileName & " has been c r e a t e d . "
-
End I f End I f
'Check f o r Tags i n F i l e
If LabelsInFile
=
T r u e Then
F o r I = L B o u n d ( m y T a g s 1 To U B o u n d ( m y T a g s ) myTagsFilter = Filter(myFi1eRooms. myTags(I), True, vbBinaryCompare)
I f UBound(myTagsFi1ter)
=
-
-1 Then
ReportError NotInFile, myTags(1) D e b u g . P r i n t "Tag
If AutoFix
"
& myTags(1) &
"
n o t found i n f i l e . "
T r u e Then
=
ReportError FixedNotInFile, myTags(1) Open F i l e N a m e F o r A p p e n d As # F F i l e P r i n t B F F i l e , myTags(1) Debug.Print "Label "
" & myTags(1) & added t o F i l e . "
-
C1 o s e B F F i 1 e
Else ReportError NotFixedNotInFile, myTags(1) End I f End I f Next I End I f
' C h e c k t o make s u r e L a b e l s i n t h e F i l e h a v e 'Tags i n t h e DGN f i l e
I f LegitimateLabels I f Oir(Fi1eName)
=
T r u e Then
=
" "
Then
0ebug.Print " F i l e
"
& FileName &
"
does n o t e x i s t . "
Else F o r I = L B o u n d ( m y F i 1 e R o o m s ) To U B o u n d ( m y F i 1 e R o o m s ) m y T a g s F i l t e r = F i l t e r ( m y T a g s , myFileRooms(I), True, vbBinaryCompare)
I f UBound(myTagsFi1ter)
=
-1 T h e n
ReportError NotInDGN, myFileRooms(1) ReportError NotFixedNotInDGN, myFileRooms(1) D e b u g . P r i n t " F i l e Room " & m y F i l e R o o m s ( 1 ) & " i s n o t i n t h e DGN F i l e . "
~
I Standards Checker Reporting I
643
End I f
Next I End I f End I f End S u b
So, we have added a few lines of code. What does this get us?
After the Standards Checker is run, we are shown:
At this point in the development of our Standards Checker ‘Xdd-In’: we are not reporting anything to the Report File. If we click Yes in the Standards Check Complete dialog box, we will see a report.
Y
I Chapter 31: The Standards Checker I We have just touched the surface on reporting 'problems' found in our Standards Checker program. Let's build on it now by adding some code to the ReportError Procedure. P r i v a t e Sub R e p o r t E r r o r ( E r r 0 r A s E r r o r T y p e , RoomNumber A s S t r i n g )
D i m mySCC As StandardsCheckerController D i m myProb As StandardsCheckerProblem D i m myRep As S t a n d a r d s C h e c k e r R e p o r t
S e t mySCC
=
StandardsCheckerController
S e t myRep
=
mySCC.Report
S e l e c t Case E r r o r Case E r r o r T y p e . N o t I n D G N mySCC.TotalProblems S e t myProb
=
=
mySCC.TotalProblems + 1
myRep.AddProblem("Room "
i s NOT i n D G N . " ,
"
& RoomNumber &
-
" V B A CheckA", F a l s e )
Case E r r o r T y p e . N o t I n F i l e mySCC.TotalProblems S e t myProb
=
=
mySCC.TotalProblems
"
+ 1
& RoomNumber & i s NOT i n f i l e . " , " V B A CheckA", F a l s e )
myRep.AddProblem("Room
"
-
Case E r r o r T y p e . F i x e d N o t InDGN mySCC. F i x e d p r o b l e m s
=
mySCC. F i x e d p r o b l e m s
+ 1
Case E r r o r T y p e . F i x e d N o t I n F i l e mySCC. F i x e d p r o b l e m s
=
mySCC. F i x e d p r o b l e m s
+ 1
Case E r r o r T y p e . N o t F i x e d N o t I n D G N mySCC.IgnoredProblems Case
=
mySCC.IgnoredProblems + 1
ErrorType.NotFixedNotInFi1e mySCC.IgnoredProblems
=
mySCC.IgnoredProblems
+ 1
End S e l e c t End Sub
Now, when we view the report generated after the Checker finishes, we see the number of Problems that were identified. And if we expand the
I Standards Checker Reporting I
645
listing below the File Name we will see the specific Problems we added above.
Let's add alittle more to the Procedure ReportError. Private String) Dim Dim Dim Set Set
Sub ReportError( Error As ErrorType, RoomNumber As mySCC As S t a n d a r d s C h e c k e r C o n t r o l l e r myProb As StandardsCheckerProblem myRep As StandardsCheckerReport mySCC = S t a n d a r d s C h e c k e r C o n t r o l l e r myRep = mySCC.Report
Select Case Error Case ErrorType.NotInDGN mySCC.TotalProblems = mySCC.TotalProblems + 1 Set myProb = myRep.AddProblem("Room & RoomNumber & is NOT in DGN.", "VBA CheckA", False) "
"
Case ErrorType.NotInFile mySCC.TotalProblems = mySCC.TotalProblems + 1 Set myProb = myRep.AddProblem("Room & RoomNumber & is NOT in file.", "VBA CheckA", False) "
"
Case ErrorType.FixedNot1nDGN mySCC.FixedProblems = mySCC.FixedProblems + 3
-
646
I Chapter 31: The Standards Checker I S e t myProb
=
myRep.AddProblem("Room "
i s NOT i n DGN.",
"
& RoomNumber &
-
"VBA CheckA", T r u e )
Case ErrorType. FixedNotInFile mySCC.FixedProblems = mySCC.FixedProblems + 1 S e t myProb
=
myRep.AddProblem("Room "
i s NOT i n f i l e . " ,
"
& RoomNumber &
-
" V B A CheckA", T r u e )
Case E r r o r T y p e . N o t F i x e d N o t I n D G N S e t myProb
=
myRep.AddIgnoredProb1 em("Room RoomNumber &
"
i s NOT i n DGN.".
mySCC.IgnoredProblems
=
"
&
~
~
"VBA CheckA", UserName, Now)
mySCC.IgnoredProblems + 1
Case ErrorType.NotFixedNotInFile S e t myProb
=
myRep.AddIgnoredProb1 em("Room
RoomNumber & i s NOT i n f i l e . " ,
"
&
~
~
"
mySCC.IgnoredProblems
=
"VBA CheckA".
UserName. Now)
mySCC.IgnoredProblems + 1
End Select End Sub
Now, in addition to adding a "Problem" to the report stating that the problem was not fured (the False parameter in Addproblem), we are also adding Fixed Problems and Ignored Problems. Here is the report now:
Now, in addition to seeing Problems that have not been fixed, we can see problems that have been fixed and those that have been ignored.
I Automatically Loading Custom Standards Checker Add-Ins I
647
AUTOMATICALLY LOADINGCUSTOMSTANDARDS CHECKERADD-INS Standards Checker functionality can be automatically loaded through two mechanisms. Both of these mechanisms make use of the O n P r o j e c t L o a d Procedure that can be placed in a Microstation VBA Project. The O n P r o j e c t L o a d Procedure is executed whenever a VBA Project (.mvba file) is opened. The code in this Procedure is the same for both ‘autoload’mechanisms: Sub OnProjectLoad( 1 Addchecker End S u b
So, the O n P r o j e c t L o a d Procedure is the same for both ‘autoload’ mechanisms. What are the two mechanisms?
1 The first is selecting ‘Xuto-Load in the VBA Project Manager:
L
When a VBA project is set up to ‘Xuto-Load’:the project is loaded when Microstation is started. When the project is loaded, O n P r o j e c t L o a d is executed and the Standards Checker Add-in we created is loaded as well.
2
The other method deals with a configuration variable named MS-STANDARDCHECKERAPPS. If we add the VBA Project file (.mvba file) to this variable, the VBA project will be loaded when the Standards Checker is initialized. This is preferable to the method described above because Standards Checker code is not loaded and added unless we specificallydecide to do something with the Standards Checker.
648
I Chapter 31: The Standards Checker I
REVIEW The Standards Checker Interface provides us the ability to create our own custom “Standards Checking programming. The Microstation VBA documentation includes additional examples of how to further implement the IStandardsChecker Interface.
32
Using the Windows API We added References to things such as the “Microsoft Scripting Runtime”, “Microsoft ActiveX Data Objects”, “Microsoft CDO for Windows 2000” and so forth. When we did this, we had instant access to functionality not natively exposed to VBA. Although working with the Windows API is not as simple and straight forward as adding a Reference, the process is fairly painless and the results can be powerful. In this Chapter:
Declaring API Calls Declaring Trpes
B Utilizing API Calls
DECLARING API CALLS Windows API calls can be declared in the General Declarations area of a Code Module. Once declared, the API calls are used just as we would use any other Function or Procedure. Here is an example:
649
650
I Chapter 32:Using the Windows API I P u b l i c D e c l a r e F u n c t i o n Beep L i b “ k e r n e 1 3 2 ” (ByVal dwFreq As Long, B y V a l d w D u r a t i o n As L o n g ) A s Long
The Function name in the above declaration is “Beep”.It is an amazing API call that beeps. It beeps as long and as high (or as low) as we ask it to beep. API Functions and Procedures are found inside DLL (Dynamic Link Library) files. This one is inside the kerne132.dllfile. Let’s try it out, shall we? After declaring the Beep function in the General Declarations area of a Code Module, we can use it as follows: S u b TestBeep( 1 Beep 4 0 0 0 , 2 5 0 Beep Beep
2000, 2 5 0 1000, 2 5 0
Beep 5 0 0 , 2 5 0 End S u b
Four beeps are heard, each lasting 1/4 of a second (250 milliseconds) and each at a different frequency. The higher the frequency the higher the beep. Each beep is half the frequency of the previous frequency. This results in four ‘notes’, each note is one octave lower than the previous. The Beep API Function is not the most useful API function known to man but for the moment, we are focusing on how to declare the functions. We will see plenty of examples utilizing more powerful and more useful API functions later. Many Windows API calls are declared as Functions. This means they return a value. Often times, the value they return tells us whether the API call worked or if an error was encountered. In addition to specifying the Function’s Name, Location (which DLL file it appears in), and the parameters, Windows API calls often have an ‘Alias: The Alias is important when declaring an API function but we do not use it in our code - we use the Function or Procedure name.
DECLARING TYPES Some Windows API calls make use of “Types’: A ‘Type’ is similar to an Object in that it has specific ‘properties’ or ‘members: Often times we declare a variable as one of these “Types” and then set some of its
I Utilizing API Calls I
651
properties. After the properties are set we may use it as a Parameter in an API call. API Types are declared in the General Declarations area just as API Functions. Public Declare Sub GetSystemInfo Lib "kerne132" (IpSystemInfo As SYSTEMLINFO) Public Type SYSTEM-INFO dwOemID A s Long dwPageSize A s Long 1pMinimumApplicationAddress A s Long 1pMaximumAppl icationAddress As Long dwActiveProcessorMask As Long dwNumberOfProcessors As Long dwProcessorType A s Long dwAllocationGranularity A s Long dwReserved A s Long End Type
The GetSystemInfo API call makes use of a "SYSTEM-INFO" type. Once declared we can use the Procedure and Trpe in a macro: Sub TestSystemInfo() Dim mySystemInfo As SYSTEMLINFO GetSystemInfo mySystemInfo MsgBox mySystemInfo.dwNumberOfProcessors & End Sub
"
Processors."
We will see examples of more Types declared as we look at more API examples.
UTILIZING API CALLS There are hundreds of API calls available for our use. Those presented here are not in any particular order and they are not necessarily related to one another in any way. One call may deal with the Logical Drives on the computer where the other may deal with the screen resolution. In any case, those listed here should prove helpful to many readers.
652
I Chapter 32:Using the Windows API I GetLogicalDrives P u b l i c Declare Function GetLogicalDriveStrings L i b "kerne132" A l i a s "GetLogical DriveStringsA" ( B y V a l n B u f f e r L e n g t h As L o n g , B y V a l l p B u f f e r As S t r i n g ) As L o n g ~
Sub T e s t G e t L o g i c a l D r i v e s t r i n g s o D i m D r i v e L e t t e r s As S t r i n g
D i m x S p l i t As V a r i a n t D i m I As L o n g DriveLetters
=
Space(255)
GetLogical Drivestrings Len(DriveLetters) , DriveLetters xSplit For I
=
=
Split(DriveLetters, Chr(0)) L B o u n d ( x S p l i t . 1 To U B o u n d ( x S p 1 i t )
MsgBox " D r i v e
"
& xSplit(1) &
"
-
2
Found."
Next I End Sub
Often times, when we are using Windows API calls and provide a String to a Function, and the Function is going to give the String a value, we must first 'buffer' the string with Spaces. The DriveLetters variable is an example of this. When the variable DriveLetters enters the Function GetLogicalDriveStrings,itgoesinwith255spacecharactersinit.When it comes out, we are given a series of drive letter characters separated by a Null Character (Chr(0)). We subtract two (2) from the upper-bound index of the xSplit array and this gives us the drive letters found on our system. When T e s t G e t L o g i c a l D r i v e S t r i n g s is run, we see MessageBoxes with the drive letters of each Drive on the system. All drives are returned, those physically attached as well as mapped network drives.
GetDriveType We can use G e t L o g i c a l D r i v e S t r i n g s to get the drive letters on our system. But how do we know what type of drives they are? Hard Drive? CD-ROM Drive? Floppy Drive? We can use G e t D r i veType. This example also uses G e t L o g i c a 1 Dr iveS t r ings. P u b l i c D e c l a r e F u n c t i o n GetDriveType L i b "kerne132" A l i a s " G e t D r i v e T y p e A " ( B y V a l n D r i v e As S t r i n g ) As L o n g P u b l i c Const DRIVE-CDROM
=
5
-
I Utilizing API Calls I
653
P u b l i c Const DRIVE-FIXED
3
=
Pub1 i c C o n s t DRIVE-RAMDISK P u b l i c C o n s t DRIVELREMOTE
6
=
4
=
P u b l i c C o n s t DRIVELREMOVABLE
=
2
Sub T e s t D r i v e T y p e ( 1 D i m D r i v e L e t t e r s As S t r i n g D i m x S p l i t As V a r i a n t
D i m I As Long DriveLetters
=
Space(255)
G e t L o g i c a l Dri v e S t r i n g s L e n ( D r i v e L e t t e r s ) , Dri v e L e t t e r s xSplit For I
= =
Split(DriveLetters,
Chr(0))
L B o u n d ( x S p 1 i t ) To U B o u n d ( x S p 1 i t )
Debug.Print
"Drive
& xSplit(1) &
"
"
-
2
is a
"
&
-
ReturnDriveType(CStr(xSplit(1))) Next I End Sub Function
ReturnDriveType(DriveLetter
As S t r i n g ) As S t r i n g
D i m l n g D r i v e T y p e As Long 1n g D r i veType
=
GetDriveType(Dri veLetter)
S e l e c t Case 1 n g D r i v e T y p e Case D R I V E - C D R O M ReturnDriveType
=
"CD/DVD D r i v e "
=
"Hard D r i v e "
=
"RAM D i s k "
=
"Mapped D r i v e "
=
"Removable D r i v e "
=
1ngDriveType
Case D R I V E L F I X E D ReturnDriveType Case DRIVE-RAMDISK ReturnDriveType Case DRIVE-REMOTE ReturnDriveType Case DRIVELREMOVABLE ReturnDriveType Case E l s e ReturnDriveType End S e l e c t End F u n c t i o n
Drive Drive Drive Drive
C:\ i s a D:\ i s a E:\ i s a 2:\ i s a
Hard Drive CD/DVD Drive Removable Drive Mapped Drive
I Chapter 32:Using the Windows API I This computer has a Hard Drive (C), a CD/DVD Drive (D), a Removable Drive (E) which happens to be a Flash Drive, and a Mapped Drive (Z).
GetcomputerName Public Declare Function
GetComputerName
L i b "kerne132"
A l i a s " G e t C o m p u t e r N a m e A " ( B y V a l l p B u f f e r As S t r i n g ,
-
n S i z e As L o n g ) As L o n g Sub
TestGetComputerNameO D i m CompName As S t r i n g CompName
=
Space(255)
GetComputerName CompName, Len(CompName) CompName
=
L e f t ( C o m p N a m e , I n S t r ( 1 , CompName, C h r ( 0 ) )
-
1)
MsgBox CompName End Sub
Knowing the name of the computer on which our code is running is a useful piece of information. Once again, we use a Buffered String. We look for the Null Character Chr(0) and get everything to the left of it.
GetVersionEx What Operating System is the computer running? We need only ask GetVersionEx to find out. P u b l i c D e c l a r e F u n c t i o n GetVersionEx L i b " k e r n e 1 3 2 " A l i a s " G e t V e r s io n Ex A " ( 1 p V e r s io n In f o r ma t io n As 0 S V E R S I 0 N I N FO ) As L o n g P u b l i c T y p e OSVERSIONINFO d w O S V e r s i o n I n f o S i z e As L o n g d w M a j o r V e r s i o n As L o n g d w M i n o r V e r s i o n As L o n g d w B u i l d N u m b e r As L o n g d w P l a t f o r m I d As L o n g s z C S D V e r s i o n As S t r i n g
*
128
End T y p e P u b l i c Const
VERLPLATFDRMLWIN32LWINDDWS
P u b l i c C o n s t VERLPLATFORMLWIN3PLNT
=
2
=
3
-
I Utilizing API Calls I Sub
655
TestOSVersionO D i m m y V e r I n f o As O S V E R S I O N I N F O D i m s t r S e r v i c e P a c k As S t r i n g
myVerInfo.dwOSVersionInfoSize
=
148
GetVersionEx myVerInfo S e l e c t Case m y V e r I n f o . d w P l a t f o r m I d Case VER-PLATFORM-WIN32-WINDOWS S e l e c t Case m y V e r I n f o . d w M i n o r V e r s i o n Case 0 MsgBox "Windows 9 5 " Case 1 0 MsgBox "Windows 9 8 " Case 90 MsgBox "Windows M E " End S e l e c t Case VERLPLATFORMLWIN3ZLNT S e l e c t Case m y V e r 1 n f o . d w M a j o r V e r s i o n Case I s
<=
4
MsgBox "Windows NT B u i l d " & myVerInfo.dwBui1dNumber Case 5 S e l e c t Case m y V e r 1 n f o . d w M i n o r V e r s i o n Case 1 strServicePack
=
myVer1nfo.szCSDVersion
strServicePack = Left(strServicePack, I n S t r ( 1 , s t r S e r v i c e P a c k , C h r ( 0 ) ) - 1) MsgBox "Windows X P B u i l d " & myVerInfo.dwBui1dNumber & vbCr & s t r S e r v i cePack
-
Case 2 MsgBox "Windows .NET S e r v e r B u i l d myVerInfo.dwBuildNumber
"
&
-
Case E l s e MsgBox "Windows 2000 B u i l d myVerInfo.dwBuildNumber
"
&
-
End S e l e c t End S e l e c t End S e l e c t End Sub
A MessageBox displays the Operating System and in some cases the Build Number and Service Pack.
I Chapter 32:Using the Windows API I
At times we need to temporarily pause program execution. The Sleep function allows us to specify how many milliseconds to 'sleep: Publ i c D e c l a r e Sub s1 eep L i b " k e r n e 1 3 2 " (ByVal d w M i l l i s e c o n d s As Long) Sub T e s t S l eep( ) D i m S t a r t T i m e As D a t e
D i m EndTime As D a t e StartTime
=
Now
Sleep 1000 EndTime
=
Now
& FormatDateTime(StartTime, v b L o n g T i m e ) & vbCr & "End T i m e : & FormatDateTime(EndTime, vbLongTime)
MsgBox " S t a r t T i m e :
"
~
"
End Sub
In this example, we get the current time into a variable named "StartTime", Sleep for 1 second (1,000 milliseconds), and then get the current time into a variable named "EndTime? The last thing we do is display the StartTime and EndTime in a MessageBox.
FindExecutable The same .pdf file may be opened with Adobe Acrobat Reader 7 on one computer and Adobe Acrobat 6 on another computer. Which program is registered to open a .pdf file? Which program is registered to open a.jpg file? F i n d E x e c u t a b l e tells us the path to the program that is registered to open a particular file type. P u b l ic D e c l a r e F u n c t i o n F i ndExecutabl e L i b " s h e 1 1 3 2 . d l 1 " A l i a s "FindExecutableA" -
~
( B y V a l l p F i l e As S t r i n g , B y V a l 1 p D i r e c t o r y As S t r i n g , B y V a l l p R e s u l t As S t r i n g ) As Long Sub T e s t F i n d E x e c u t a b l e ( 1 D i m s t r F i l e N a m e As S t r i n g D i m s t r D i r N a m e As S t r i n g
D i m s t r E x e F i l e As S t r i n g s t r F i 1 eName = " e u l a . p d f " s t r D i rName
=
" C : \ P r o g r a m F i 1e s \ B e n t l e y \ M i c r o S t a t i o n "
strExeFile
=
Space(255)
F i n d E x e c u t a b l e s t r F i 1 eName, s t r D i rName, s t r E x e F i l e
I Utilizing API Calls I strExeFile
Left(strExeFile, InStr(1, strExeFile, Chr(0))
=
-
1)
MsgBox s t r Exe F i 1 e End Sub
GetDiskFreeSpace GetDiskFreeSpace gives us information about the provided disk name. Pub1 ic D e c l a r e F u n c t i o n GetDi s kFreeSpace L i b " k e r n e l 32" A1 ia s " G e t D is k F r e e S p a c e A " -
~
(ByVal 1pRootPathName As S t r i n g , 1 p S e c t o r s P e r C l u s t e r As Long, -
1 p B y t e s P e r S e c t o r As L o n g , 1 p N u m b e r O f F r e e C l u s t e r s As L o n g ,
~
1 p T o t a l N u m b e r O f C l u s t e r s As L o n g ) As L o n g Sub TestFreeDiskSpace( 1 D i m myspace As Long
D i m 1 n g S e c t o r s P e r C l u s t e r As L o n g D i m 1 n g B y t e s P e r S e c t o r As L o n g D i m 1 n g F r e e C l u s t e r s As L o n g D i m 1n g T o t a l C l u s t e r s A s Long D i m FreeBytes As Double D i m T o t a l B y t e s As Double
D i m P e r c e n t F r e e As D o u b l e GetDiskFreeSpace " C : \ " , IngBytesPerSector,
lngSectorsPerCluster, IngFreeClusters, 1ngTotalClusters
F r e e B y t e s = FormatNumber(lngFreeC1usters / 1 0 0 0 * 1 n g B y t e s P e r S e c t o r * l n g S e c t o r s P e r C l u s t e r , 2) T o t a l B y t e s = FormatNumber(lngTotalC1usters / 1 0 0 0 1 n g B y t e s P e r S e c t o r * l n g S e c t o r s P e r C l u s t e r , 2) PercentFree
=
Round((FreeBytes / T o t a l B y t e s )
MsgBox "Free B y t e s : &
" T o t a l Bytes: "
"
" & FormatNumber(FreeBytes, K B " & vbCr & -
"
& FormatNumber(TotalBytes,
KB" & v b C r &
-
"Percent Free: %" & PercentFree End Sub
*
-
*
~
1 0 0 , 2)
2, F a l s e , F a l s e , T r u e )
-
2, False, False, True) &
-
658
I Chapter 32:Using the Windows API I
c
GetSystemMetrics Knowing information about the computer on which our code is running is very helpful. We can use GetSystemMetrics to return a wealth of information. Public Declare Function
GetSystemMetrics
L i b "user32"
( B y V a l n I n d e x As Long) As Long P u b l i c C o n s t SMKCMETRICS
=
44
P u b l i c C o n s t SMKCMOUSEBUTTONS P u b l i c C o n s t SM-CXBORDER
=
Publ i c C o n s t SM-CXCURSOR
=
P u b l i c C o n s t SM-CXDLGFRAME
13
P u b l i c C o n s t SMKCXDOUBLECLK
Publ i c C o n s t SM-CXFULLSCREEN P u b l i c C o n s t SM-CXHSCROLL
=
SM-CXDLGFRAME
=
16
21
=
P u b l i c C o n s t SM-CXHTHUMB
=
P u b l i c Const S M K C X I C O N
11
10
P u b l i c Const S M K C X I C O N S P A C I N G P u b l i c Const S M K C X M I N
=
=
38
28
P u b l i c C o n s t SM-CXMINTRACK
=
P u b l i c C o n s t SM-CXSCREEN
=
0
P u b l i c Const S M K C X S I Z E
30
=
34
P u b l i c Const SMKCXSIZEFRAME
=
P u b l i c C o n s t SMKCXVSCROLL
2
P u b l i c C o n s t SM-CYBORDER P u b l i c C o n s t SM-CYCAPTION P u b l i c C o n s t SM-CYCURSOR
36
=
32
=
=
7
=
P u b l i c Const SMKCXFIXEDFRAME P u b l i c Const SMKCXFRAME
43
=
5
=
=
6 4
= =
P u b l i c C o n s t SMKCYDLGFRAME
SM-CXFRAME
14 =
P u b l i c C o n s t SMKCYDOUBLECLK P u b l i c Const SMKCYFIXEDFRAME
8
=
37
=
SM-CYDLGFRAME
-
I Utilizing API Calls I
659
P u b l i c C o n s t SM-CY FRAME
33
=
P u b l i c C o n s t SM-CYFULLSCREEN P u b l i c C o n s t SM-CYHSCROLL P u b l i c C o n s t SM-CYICON
12
=
P u b l i c C o n s t SM-CYICONSPACING P u b l i c C o n s t SM-CYMENU
P u b l i c C o n s t SM-CYKANJIWINDOW =
29 =
P u b l i c C o n s t SM-CYSIZE
31
=
P u b l i c C o n s t SM-CYVSCROLL
18
20
= =
SM-CYFRAME
=
9
P u b l i c C o n s t SM-DBCSENABLED =
=
1
P u b l i c C o n s t SM-CYSIZEFRAME
P u b l i c C o n s t SM-DEBUG
39
35
=
P u b l i c C o n s t SM-CYSCREEN
P u b l i c C o n s t SM-CYVTHUMB
=
15
=
P u b l i c C o n s t SM-CYMINTRACK P u b l i c C o n s t SM-CYMIN
17
=
3
=
42
=
22
P u b l i c C o n s t SM-MENUDROPALIGNMENT P u b l i c C o n s t SM-MOUSEPRESENT
=
P u b l ic C o n s t SM-SWAPBUTTON
23
=
=
40
19
Sub TestSystemMetrics( 1 MsgBox " S c r e e n R e s o l u t i o n :
"
& vbCr &
GetSystemMetrics(SM-CXSCREEN) & GetSystemMetrics(SM-CYSCREEN)
"
X
"
&
MsgBox "Mouse B u t t o n s : " & GetSystemMetrics(SM-CMOUSEBUTTONS) End Sub
The Procedure TestSystemMetri cs tells us the screen resolution and how many buttons are on the mouse connected to the computer.
GetTickCount How long has it been since the computer was started? GetTickCount answers this question in milliseconds. P u b l ic D e c l a r e F u n c t i o n GetTi ckCount L i b " k e r n e l 3 2 " A1 ia s " G e t T i c k C o u n t " ( ) As L o n g Sub T e s t T i c k C o u n t ( 1 D i m S t a r t T i c k s As L o n g D i m E n d T i c k s As L o n g
StartTicks
=
GetTickCount
660
I Chapter 32:Using the Windows API I S l e e p 2000 EndTicks
=
GetTickCount
MsgBox S t a r t T i c k s & v b C r & E n d T i c k s End Sub
GetUserName Who is logged onto the computer? Pub1 ic D e c l a r e F u n c t i o n GetUserName L i b " a d v a p i 3 2 . d l 1 " A1 ia s
-
"GetUserNameA" (ByVal l p B u f f e r A s S t r i n g , n S i r e A s Long) A s Long Sub TestUserNameO D i m UserName As S t r i n g D i m x S p l i t As V a r i a n t
UserName
=
Space(255)
GetUserName UserName, L e n ( U s e r N a m e 1 xSplit
=
UserName
Split(UserName, Chr(0)) =
xSplit(0)
MsgBox UserName End Sub
GetUser Name is useful for logging who is performing what function. If we get the ComputerName as well, we will know Who, Where, What, and we can know When by using the Now Function. As for the question "Why': Microsoft is still working on that API call.
NOTE: The MicroStation Application Object has its own UserName property so we don't need this API call ifwe are using MicroStation VBA.
GetWindowsDirectory Where is Windows Installed? C:\Winnt? C:\Windows? GetWindowsDirectorytells us. P u b l i c D e c l a r e F u n c t i o n GetWindowsDirectory L i b "kerne132" A l i a s
~
-
" G e t W i n d o w s D i r e c t o r y A " ( B y V a l l p B u f f e r As S t r i n g , B y V a l n S i z e As L o n g ) As L o n g Sub TestWindowsDi r (1
D i m W i n d o w s D i r As S t r i n g
-
I Utilizing API Calls I
661
D i m x S p l i t As V a r i a n t WindowsDir
=
Space(255)
GetWindowsDirectory WindowsDir, xSpl i t
=
Len(WindowsDir)
S p l it ( W i n d o w s D i r , C h r ( 0 ) )
WindowsDir
=
xSplit(0)
MsgBox W i n d o w s D i r End S u b
Knowing where Windows is installed can be helpful if we are looking for a particular file (such as in the System32 directory) or if we want to add our own files or folders under the Windows directory.
Logonuser Security is on everyone's mind. How do I know that changes being made on someone's machine are being made by the person that is logged on? How do I know someone else didn't slide into Fred's cubicle while Fred is at lunch only to goof up a file? We know how to get the current user. Let's take a look at how we can ask the user for a password and with the Windows API validate that the password entered matches that of the password on the system. P r i v a t e Declare F u n c t i o n Logonuser L i b "Advapi32" A l i a s "LogonUserA" ( B y V a l 1 p s z U s e r n a m e A s S t r i n g , B y V a l l p s z D o m a i n As S t r i n g , B y V a l l p s z P a s s w o r d A s S t r i n g , B y V a l d w L o g o n T y p e As L o n g ,
~
~
~
B y V a l d w L o g o n P r o v i d e r A s L o n g , p h T o k e n A s L o n g ) As L o n g Function
CurrentUserName( 1
As S t r i n g
D i m UserName A s S t r i n g D i m x S p l i t As V a r i a n t UserName
=
Space(255)
GetUserName UserName, L e n ( U s e r N a m e ) xSplit
=
UserName
Split(UserName, =
Chr(0))
xSplit(0)
CurrentUserName
=
UserName
End F u n c t i o n
Sub
TestLogonUser( 1 D i m S u c c e s s f u l As Long D i m PasswordEntry As S t r i n g PasswordEntry = I n p u t B o x ( " R e - E n t e r Password:", "Logon V a l i d a t i o n " ) Successful = L o g o n U s e r ( C u r r e n t U s e r N a m e , " " , PasswordEntry, 2 , 0 , 0)
662
I Chapter 32:Using the Windows API I S e l e c t Case S u c c e s s f u l Case 1 MsgBox " Y o u h a v e b e e n v a l i d a t e d . " ,
vbInformation
Case E l s e MsgBox " I n v a l i d Username/Password C o m b i n a t i o n . " ,
vbcritical
End S e l e c t End Sub
This example makes use of an InputBox for entering the password. Although this is not necessarily the best way to ask for a password (because it is visible to anyone looking over one's shoulder), the code still demonstrates the use of the API call. We have a Function named CurrentUserName that returns the current user name. Notice that we leave the Domain an empty string. If we were on a domain, we would want to specify it in the provided parameter. Logonuser. Very powerful.
MessageBeep Feedback is good. Right? In addition to visual feedback we can provide audible feedback through a variety of methods. The MessageBeep function plays the .wav file currently applied in the "Sounds and Audio Devices" section of the Control Panel. These are the sounds we hear when different MessageBoxes display, only we get the sound without the MessageBox. Pub1 i c D e c l a r e F u n c t i o n MessageBeep L i b " u s e r 3 2 " ( B y V a l wType As L o n g ) As L o n g P u b l i c C o n s t MB-OK
=
&HO&
P u b l i c C o n s t MB-ICONSTOP
=
&H10&
P u b l i c C o n s t MB-ICONQUESTION
=
P u b l i c C o n s t MB-ICONEXCLAMATION P u b l i c C o n s t MB-ICONASTERISK
Sub TestMessageBeepO MessageBeep MB-OK Sleep 500 MessageBeep MB-ICONSTOP Sleeo 500
=
&H20& =
&H30&
&H40&
-
I Utilizing API Calls I
663
MessageBeep MB-ICONQUESTION Sleep 500 MessageBeep MBLICONEXCLAMATION Sleep 500 MessageBeep MB-ICONASTERISK End Sub
Playsound Here is another API call that deals with audible feedback. Playsound allows us to specify which .wav file is to be played and how it is to be played. P l a y s o u n d L i b "winmrn.dl1" A l i a s " P l a y S o u n d A " (ByVal lpszName As String, ByVal hModule As Long,
Public Declare Function
~
ByVal dwFlags As Long) As Long Public Const SND-SYNC = &HO Public Const SND-ASYNC = & H 1 Public Const SNDLLOOP = &H8 Sub T e s t P l aySoundA() PlaySound "C:\Windows\Media\chimes.wav", 0, SND-SYNC PlaySound "C:\Windows\Media\chord.wav", 0, SND-SYNC End Sub
This first example uses the "SND-SYNC" flag. This means the chirnes.wuv file plays until it is finished and then the chordwuv file plays. Sub T e s t P l aySoundB() PlaySound "C:\Windows\Media\chimes.wav", Sleep 150 PlaySound "C:\Windows\Media\chimes.wav", Sleep 200 PlaySound "C:\Windows\Media\chimes.wav", Sleep 250 PlaySound "C:\Windows\Media\chimes.wav", Sleep 300 PlaySound "C:\Windows\Media\chimes.wav", End Sub
0, SNDLASYNC 0, SNDLASYNC
0, SND-ASYNC 0, SNDLASYNC 0, SNDLASYNC
When we use the "SND-ASYNC" flag, the specified file begins playing and the code continues executing without waiting for the file to finish
664
I Chapter 32:Using the Windows API I playing. By placing "Sleep" calls between each line of code, we hear 150 milliseconds of chimes.wuv and then we hear 200 milliseconds, then 250 milliseconds, and so forth. P1 aySound can play only one .wav file at a time, so each time it is called, any file already being played is stopped and the new file is played. The next example we will look at would be extremely annoying if we didn't know how to turn it off. We use the "SND-LOOP" flag in conjunction with the "SND-ASYNC" flag to play the sound over and over. The sound continues to play even after the procedure has finished executing. Even if the VBA Project is unloaded, the sound continues to play over and over and over. Not until Microstation is closed down or T e s t P l aySoundD is executed does the sound stop. Sub T e s t P l aySoundC( PlaySound
"C:\Windows\Media\chord.wav", 0 , SNDLLOOP + S N D L A S Y N C
End Sub Sub T e s t P l aySoundD( PlaySound v b N u l l S t r i n g , 0, 0 End Sub
ShellExecute In a previous example, we were able to discover which application was registered to open a specific file. Shel 1 E x e c u t e actually opens the file using the application registered to handle the file. P u b l i c Declare Function ShellExecute L i b "she1132.dll"
Alias
" S h e l 1 E x e c u t e A " ( B y V a l hwnd As L o n g , B y V a l l p o p e r a t i o n As S t r i n g , B y V a l l p F i l e As S t r i n g ,
-
ByVal l p P a r a m e t e r s As S t r i n g , ByVal l p D i r e c t o r y As S t r i n g , B y V a l nShowCmd As L o n g ) As L o n g Sub T e s t S h e l l E x e c u t e A O Shel 1Execute 0, " O P E N " , "Greenstone. bmp", End Sub Sub T e s t S h e l l E x e c u t e B ( ) Shel 1Execute 0, " O P E N " ,
" ' I ,
"C:\Windows",
vbMinimizedNoFocus
-
I Utilizing API Calls I "Greenstone.bmp",
665 "",
"C:\Windows", vbMaximizedFocus
End Sub
We show two examples here. One executes the program and opens the file 'minimized' and the other executes the program and opens the file 'maximized'. In a previous chapter we worked through an example that created an .html file. We could use She1 1 Execute to display the file immediately after it is created. This is much better than creating the file and then asking the user to find the file and double-click on it.
SHGetFileInfo SHGetFileInfo can be used for a variety of things. One thing it can do is tell us what kind of a file we are looking at. Public Type SHFILEINFO hIcon As Long iIcon As Long dwAttributes As Long szDisplayName As String * 255 szTypeName As String * 80 End Type Public Public Public Public Public Public Public Public Public Public Public Public Public Public Public
Const Const Const Const Const Const Const Const Const Const Const Const Const Const Const
SHGFI-ATTRIBUTES = &H800 SHGFILEXETYPE = &HZ000 SHGFILDISPLAYNAME = &HZ00 SHGFILICON = &H100 SHGFI-ICONLOCATION = &H1000 SHGFI-LARGEICON = &HO SHGFI-LINKOVERLAY = &H8000 SHGFILOPENICON = &H2 SHGFILPIDL = &H8 SHGFILSELECTED = &H10000 SHGFI-SHELLICONSIZE = &H4 SHGFI-SMALLICON = &H1 SHGFI-SYSICONINDEX = &H4000 SHGFILTYPENAME = &H400 SHGFILUSEFILEATTRIBUTES = &H10
Sub TestGetFileInfoO
666
I Chapter 32:Using the Windows API I D i m m y F I A s SHFILEINFO SHGetFileInfo "C:\test.dgn", SHGFI-DISPLAYNAME
SHGFI-ATTRIBUTES,
+
SHGFILICON
+
myFI, Len(myFI1,
-
SHGFILTYPENAME
Debug. P r i n t R e p l a c e ( m y F 1 . s z D i s p l ayName, C h r ( O ) , Debug. P r in t R e p l a c e ( my F I . s z T y p e Name, C h r ( 0 ) ,
" "
" ' I )
)
End Sub
The file name and the file type display. test. dgn Bentley MicroStation Design File
We have touched on a handful of Windows API calls here. There are hundreds more. Some API calls deal with the display; they allow us to 'draw' to specific windows. Other API calls deal with reading and writing files. Yet others deal with System Memory matters. Entire books and websites have been dedicated to the topic of Windows API calls. Windows API calls are one of the reasons why we can say "Yes, you can do that with VBA".
Using Third Party ActiveX Controls and DLLs We all know there is no reason to keep reinventing the wheel. Making use of existing resources speeds the development process and can result in a more “bug-free’’application. In this Chapter:
Using ActiveX Controls UsingDLLs
USINGACTIVEXCONTROLS ActiveX Controls are used in our Graphical User Interface development. We have already used TextBoxes, ComboBoxes, CommandButtons, Labels, and other controls.
667
668
I Chapter 33: Using Third Party ActiveX Controls and DLLs I When we insert a UserForm in our VBA Project, the Toolbox dialog box normally displays. Right-clicking on the Toolbox allows us to add Additional Controls.
c
. As we scroll through the list of items, we can see a great variety of Controls. The list on each computer will be different because Controls are added when software is installed. Some of the controls shown in the image above are installed when Visual Basic 6.0 is installed. The fact that a Control is shown in the list does not mean we can make use of it. Some Controls require a License to use. Let’s take a look at a few Controls that are available for us to use.
I Using ActiveX Controls I
wow
2005
669
For demonstration purposes, we will use the “Calendar Control 11.0” Control in our example. This control is installed with Microsoft Office. Select it from the list of Available Controls and click the OK button.
Once it shows up in the Toolbox we can place it on our Form along with other controls we may want to use. Clicking on the button displays the selected date. P r i v a t e Sub btnDisplaySelection-Click0 MsgBox C a l e n d a r l . V a l u e End Sub
If an application is being written that makes use of a non-standard ActiveX Control, we need to make sure that we can successfully deploy the application to other computers. In this example, we would need to distribute the.mvba file (the Microstation VBA Project file) as well as the Calender Control (the .ocx file). But giving someone an .ocx file is not enough. The Control needs to be registered on the user’s computer. Registering an ActiveX control is quite simple. From the Start button in Windows, click Run and they type “cmd” in the window and then click the OK button. A Command Window should open. When it does, we simply need to type: r e g s v r 3 2 “ c : \ p r o g r a m f i 1e s \ M i c r o s o f t O f f i c e \ O f f i c e l l \ m s c a l .o c x ”
670
I Chapter 33: Using Third Party ActiveX Controls and DLLs I and press the
C:\>regsur32
"c :\Program P i l e s \ M i c r o s o f t Off ice\Off i c e i i \ m s c a l . o c x "
f
As has already been mentioned, the ActiveX Control should be tested on machines where it was not previously installed to make sure the application developed can be successfully deployed to others. There are many places on the Internet one can go to find ActiveX Controls. And many sites allow free evaluations of the Controls prior to purchase. In addition to purchasing ActiveX Controls, we can create our own ActiveX Controls using Visual Basic 6.
USINGEXISTINGDLLs There are two fundamental ways to use DLLs in our programming. Each method of using DLLs have been used in this book already. Method 1: Declaring DLL Functions such as was done in the Windows API chapter. Method 2: Adding a Reference to the DLL and using it. Since we have already devoted a chapter to using the Windows API, we will turn our attention to adding References. We have added References to a number of different DLLs but have done so in the interest of discussing specific topics. Now, we are going to look at a number of References in greater detail.
I Using Existing DLLs I
671
The first Reference we are going to look at is the “Microsoft Shell Controls and Automation” Reference. So, we have just added a new Reference to our VBA Project. Perhaps we haven’t used it before. Perhaps we have used it a little bit. How can we find out about what this Reference will do for us? The first thing we should do is take a peek at the Object Browser and filter on the Reference we just added.
She1132
Clicking on an Object displays the Members of the Object. This is one of the best ways to discover what a particular Reference can do for us. Let’s take a look at some code now. Sub TestShel 1A(
)
D i m myshell A s New Shell32.Shell D i m myFolder A s She1132.Folder3 D i m myfolder2 A s She1132.Folder3 D i m myItem A s Shell32.FolderItem
Set myFolder = myshell .BrowseForFolder(O, “Browse”, 0) MsgBox myFolder.Se1f.Path
672
I Chapter 33: Using Third Party ActiveX Controls and DLLs I F o r Each m y I t e m I n m y F o l d e r . I t e m s D e b u g . P r i n t myItem.Name & v b T a b & m y I t e m . T y p e Next End Sub
First, we are asked to select a folder.
After displaying the path of the selected folder in a MessageBox, we display each item in the Folder in the Immediate Window.
assemblies File Folder atl7l.dll DLL File automationdgn. dll DLL File bdtidoc.htm KiTL Document
And we see not only the name of the object but its Trpe as well. One of the great things about the Shell32 BrowseForFolder method is that we can specify a Root Folder. Sub
TestShell B ( 1 D i m m y s h e l l As New S h e l l 3 2 . S h e l l
D i m m y F o l d e r As S h e 1 1 3 2 . F o l d e r 3 D i m m y f o l d e r 2 As S h e 1 1 3 2 . F o l d e r 3 D i m m y I t e m As S h e l l 3 2 . F o l d e r I t e m Set myFolder
=
myshell .BrowseForFolder(O,
"Browse",
"C:\Program F i l e s \ B e n t l e y " )
For
Each m y I t e m I n m y F o l d e r . I t e m s S e l e c t Case m y I t e m . T y p e Case " F i 1 e F o l d e r " Debug.Print
"Folder:
"
& myItem.Name
0,
-
I Using Existing DLLs I
673
End Select Next End S u b
Now, when this is run, the user must select either the RootFolder or a folder in the Root Folder's path. Documentation Licensing Microstation
This is especially helpful when we ask the user to select something from a Project folder. It is faster for the user because we begin in the correct folder. It is good for us because we know the user will be selecting something from the root folder path. Here are a few more examples. They are all harmless, even the ShutdownWindows call. When we use ShutdownWindows, we should be prompted as to what we want to do. Simply click Cancel and everything will be OK. It would be a good idea to save open documents before using this call just to be safe. S u b Testshell C( ) Dim myshell A s New Shell32.Shell myShell.FindComputer End S u b S u b Testshell D ( )
Dim myshell A s New Shell32.Shell myshell .ShutdownWindows End S u b S u b Testshell E ( )
Dim myshell A s New Shell32.Shell myshell .Open "C:\Program Files\Bentley" End S u b
674
I Chapter 33: Using Third Party ActiveX Controls and DLLs I
pi
“Open” displays the contents of the provided path in an Explorer window.
Microsoft Scripting Runtime We have already made use of the Microsoft Scripting Runtime. Let’s explore it in greater detail. The primary Object we will be working with is the File System Object. Our first example will be to look for all “Bentley Microstation Design File” files in a specific folder: Sub TestFSOA( 1 Dim myFSO As New Scripting.Fi1eSystemDbject Dim myFolder As Scripting.Folder Dim myFile As Scripting.File S e t rnyFol d e r
=
myFSO.GetFo1 d e r ( ” C : \ P r o g r a m F i 1 e s \ B e n t l e y \ M i c r o S t a t i o n ” )
For Each myFile In myFolder.Files Select Case myFile.Type Case “Bentley Microstation Design File” Debug.Print myFile.Name End Select Next End Sub
The Immediate Window displays the names of the files matching the specified criteria. In addition to looking at files in a folder, we can get the subfolders in a given folder and all of its subfolders as well. Our next example provides
I Using Existing DLLs I
675
a comprehensive listing of all folders under a 'root folder: This utilizes a technique called 'recursive execution: (See also Chapter 30.) Sub T e s t F S O B ( )
D i m myFSO A s New Scripting.FileSystem0bject D i m myFolder As S c r i p t i n g . F o 1 d e r D i m mySubFolder As S c r i p t i n g . F o 1 d e r S e t myFol d e r
=
myFSO.GetFolder("C:\Program
F iles\Bentl ey")
F o r Each m y F o l d e r I n m y F o l d e r . S u b F o 1 d e r s TraverseFolders myFolder Next End Sub Sub T r a v e r s e F o l d e r s ( F o l d e r I n As S c r i p t i n g . F o l d e r ) D i m NextSubFolder As S c r i p t i n g . F o l d e r
Debug.Print FolderIn.Path F o r Each N e x t S u b F o l d e r I n F o l d e r I n . S u b F o l d e r s TraverseFolders NextSubFolder Next End Sub
C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program C:\Program
Files\Bentley\Documentation Files\Bentley\Licensing Files\Bentley\MicroStation Files\Bentley\MicroStation\assemblies Files\Bentley\MicroStation\assemblies\ECFramework Files\Bentley\MicroStation\assemblies\ECFramework\extensions Files\Bentley\MicroStation\assemblies\JSpace Files\Bentley\MicroStation\assemblies\JSpace\AddIns Files\Bentley\MicroStation\assemblies\JSpace\managed Files\Bentley\MicroStation\assemblies\JSpace\resource Files\Bentley\MicroStation\assemblies\JSpace\unmanaged Files\Bentley\MicroStation\config Files\Bentley\MicroStation\config\appl Files\Bentley\MicroStation\config\database Files\Bentley\MicroStation\config\system Files\Bentley\MicroStation\docs
This technique is useful when performing batch operations. The File System Object can be used to get Drive information as well as file and folder information. Sub TestFSOC() D i m myFSO A s New
Scripting.FileSystem0bject
D i m myDrive As S c r i p t i n g . D r i v e D i m I As Long
676
I Chapter 33: Using Third Party ActiveX Controls and DLLs I F o r I = A s c ( " A " ) To A s c ( " Z " )
I f m y F S O . D r i v e E x i s t s ( C h r ( 1 ) ) Then Set myDrive
=
myFSO.GetDrive(Chr(1) &
I f myDrive.DriveType
=
Debug. P r i n t " D r i v e :
& myDrive.DriveLetter
"
Debug.Print
" S h a r e Name:
Debug.Print
"Volume:
Debug.Print
Debug.Print
" : \ ' I )
Remote Then
"
& myDrive.ShareName
& myDrive.Vo1umeName " T o t a l Space: " & F o r m a t N umbe r ( my D r iv e . T o t a 1 S i z e , 0, False, False, True) & " Bytes" "
" "
End I f End I f Next
I
End Sub
Drive: 2 Share Name: \ \ D e v \ S t o r e Volume: STORE T o t a l Space: 81,956,655,104 Bytes
This example gets all "Network Drives" and displays information about each drive. For our next example, we will read an ASCII File using the File System Object. Sub TestFSOD( 1
D i m myFSO As New Scripting.FileSystem0bject D i m m y F i l e As S c r i p t i n g . F i l e D i m myTS As S c r i p t i n g . T e x t S t r e a m D i m s t r W h o l e F i l e As S t r i n g
D i m x S p l i t As V a r i a n t Set myFile
=
myFSO.GetFile(
-
" C : \Program F i 1 e s \ B e n t l e y \ M i c r o S t a t i o n \ c o n f i g \ m s c o n f i g . c f g " )
S e t myTS
=
myFile.OpenAsTextStream
strWholeFile xSplit
=
myTS . C1 o s e End Sub
=
myTS.ReadAl1
S p l i t ( s t r W h o l e F i 1 e . vbCr)
I Using Existing DLLs I
677
TestFSOD opens the rnsconfigdg file. We use the "ReadAll" method of the Textstream Object. We then split the file into lines by looking for Carriage Return characters in the file. Last, but not least, we close the file.
E
If we add a Watch to the variable xSplit we will see that the file has been successfully read and split into the variable xSplit. We can now look at each line in the file one by one. Now we are going to use the File System Object to 'write' a file. We will create a small HTML file. Sub T e s t F S O E ( ) D i m myFSO A s New
Scripting.FileSystem0bject
D i m m y F i l e As S c r i p t i n g . F i l e D i m myTS As S c r i p t i n g . T e x t S t r e a m S e t myTS
=
my F S O . C r e a t e T e x t F i 1 e ( " C : \ t e s t . h t m " , T r u e 1
my T S . W r it e L in e
"
< ht m 1>
"
myTS.WriteLine vbTab & " < t a b l e width=200 b o r d e r = l > " myTS.WriteLine vbTab & " " m y T S . W r i t e L i n e v b T a b & " < t r > < t d a1 i g n = c e n t e r > l < / t d > " "Number Name
m y T S . W r i t e L i n e v b T a b & " < t r > < t d a1 i g n = c e n t e r > Z < / t d > " "
myTS.WriteLine vbTab & " < / t a b l e > " myTS.WriteLine vbTab & " < / h t m l > " myTS. C1 o s e End Sub
&
~
< t d> J e r r y < / t d>< / t r > &
"
~
< t d > C a n d ic e < / t d > < / t r >
"
678
I Chapter 33: Using Third Party ActiveX Controls and DLLs I
Nothing fancy, just a simple HTML file. We have written ASCII Text files before but we have used standard VBA calls such as “Open” and “Print”. The last File System Object object we will look at is the Dictionary Object. This object allows us to add item pairs (Key and Item) to a ready-made ‘collection’. Sub TestFSOF( 1
D i m myFSO As New F i l e S y s t e m D b j e c t D i m myDir As S c r i p t i n g . F o l d e r D i m m y D i c t i o n a r y As New D i c t i o n a r y D i m I A s Long
S e t myDi r
=
myFSO.GetFo1 d e r ( “ C : \ P r o g r a m F i 1 e s \ B e n t l e y ” )
R e c u r s i v e F o l d e r myDir, m y D i c t i o n a r y For I
=
1 To m y D i c t i o n a r y . C o u n t
Debug.Print myDictionary.Keys(1
-
1) & v b T a b & - 1)
~
myDictionary.Items(1 Next I End Sub Sub
RecursiveFolder(Folder1n
As S c r i p t i n g . F o l d e r ,
~
D i c t i o n a r y I n As S c r i p t i n g . D i c t i o n a r y ) D i m m y F i l e As S c r i p t i n g . F i l e D i m m y S u b D i r As S c r i p t i n g . F o l d e r
F o r Each m y F i l e I n F o l d e r I n . F i l e s
I f UCase( R i g h t ( m y F i 1 e . Name, 4)
=
“.CHM”
Then
D i c t i o n a r y I n . A d d m y F i 1 e . Name, myFi l e . P a t h End I f Next
I Using Existing DLLs I
679
F o r Each m y S u b D i r I n F o l d e r I n . S u b F o 1 d e r s RecursiveFolder mySubDir, D i c t i o n a r y I n Next End S u b
TestFSOF begins in the folder C:\frogrurn Fi/es\Bent/ey and looks for all.chm (help) files. When one is found it is added to a Dictionary Object. After all files are found, each Key and Item in the Dictionary is printed to the Debug window (Immediate Window). basic-help.chm C:\Program Files\Bentley\Documentation\basic-help.chm micLoStation.chm C:\Program Files\Bentley\Documentation\microstation.chm micLostationvba.chm C:\Program Files\Bentley\MicroStation\microstationvba.chm Leadme-microStation.chm C:\Program Files\Bentley\MicroStation\readme-microstatic vba-concept. chm C: \Program Files\Bentley\MicroStation\vba-concept.chm
The FileSystemObject is extremely useful. It can be used to get file, folder, and drive information. It can be used to read and write ASCII Text Files. It can also be used to 'catalog' files by using the Dictionary Object.
Microsoft Speech Object Library The Microsoft Speech Object Library is another example of powerful functionality at our fingertips with only adding a Reference. Sub TestSpeechA( 1
D i m m y v o i c e A s New S p e e c h L i b . S p V o i c e myVoice.Speak "MicroSteyshen V ,
B, A, I s Great!"
End S u b
How difficult would it be to add voice / speaking capabilities to our software without this Reference? Granted, we can't always spell things as we normally do, we need to spell words phonetically (or is that funeticly?). We have already used a Windows API call to play a .wav file. When we use the Speech Object, we can 'play' volumes of instructions without needing to install volumes of .wav files. We can even have our Speech Object 'speak' dynamically, giving instructions and feedback. as our application runs.
680
I Chapter 33: Using Third Party ActiveX Controls and DLLs I Microsoft CDO for Windows 2000 Library Even if Windows XP is installed, the "CDO for Windows 2000 Library" Reference should be available. If it is not displayed in the References list, browsing for C:\Windows\System32\cdosys.d//will allow us to add it. Sub TestCDOA( 1
D i m myMsg As New CDD.Message D i m F i e l d B a s e As S t r i n g FieldBase myMsg.To
"http://schemas.microsoft.com/cdo/configuration/"
=
"[email protected]"
=
myMsg.From
=
"[email protected]"
myMsg.Subject myMsg. HTMLBody
=
=
" T e s t i n g CDO E m a i l " "VBA
For
MicroStation"
m y M s g . C o n f i g u r a t i o n . F i e l d s . I t e m ( F i e l dBase & " s e n d u s i n g " )
=
m y M s g . C o n f i g u r a t i o n . F i e l d s . I t e m ( F i e l dBase & " s m t p s e r v e r " ) " s m t p . y o u r s e r v e r . com"
=
myMsg. C o n f i g u r a t i o n . F i e l d s . I t e m ( F i e l d B a s e & " s m t p s e r v e r p o r t " )
=
2 ~
25
myMsg.Configuration. F i e l ds.Update myMsg.Send End Sub
Sending e-mail using VBA isn't difficult. It does take a few lines of code, but this is because there are a few settings that need to be made. Once the settings are in place, the e-mail is sent.
VBA For UcroStation
Here is the e-mail that is sent using CDO. And how difficult is it to add an attachment to the e-mail? Sub TestCDOB( 1 D i m myMsg As New CDO.Message D i m F i e l d B a s e As S t r i n g
I Using Existing DLLs I
681
Fi el dBase = "http: //schemas .mi crosoft. com/cdo/confi gurati on/" rnyMsg.To = "[email protected]" myMsg.From = "[email protected]" myMsg.Subject = "Testing C D O Email" myMsg.HTMLBody = "VBA
For MicroStation" rnyMsg.Configuration. Fields. Item( FieldBase & "sendusing") = 2 rnyMsg.Configuration. Fields. Item( FieldBase & "srntpserver") = "smtp.yourserver. corn" myMsg.Configuration. Fields.Item(Fie1dBase & "smtpserverport") = 25 myMsg.Configuration.Fie1ds.Update myMsg .AddAtta chment "C :\ t e s t .htm"
rnyMsg.Send End Sub
VEA For MicroStabon
There are thousands of reasons why we would like to send an e-mail using VBA. It's fortunate that sending an e-mail is so easy. The To, From, and SMTPServer properties will need to modified to match individual settings and servers.
DSO OLE Document Properties Reader 2.0 All of the References we have discussed so far are installed with Windows XP. Not so with DSO. Following the link: http://support.microsoft.com/kb/22435 7/
reveals another link to "Download the DsoFileSetup': This is what we want. Why? MicroStation design files are OLE Documents. Another term used to describe this type of file is "Structured Storage" Documents. What does this mean?
I Chapter 33: Using Third Party ActiveX Controls and DLLs I In MicroStation, go to the File > Properties menu item. When the File Properties dialog box opens, click on the Summary tab.
6
These properties may look familiar. They are found in Microsoft Word and Excel files, among others. We can read and write these properties in.dgn files using VBA when a design file is open. The DSOFile Reference allows us to 'open' any Structured Storage File to read file properties or write/create them even if the application that created the file is not installed. Let's take a look. Sub TestDSOA( 1
Dim myDSO As New D S O F i l e . 0 1 e D o c u r n e n t P r o p e r t i e s myDSO.Open "C:\test.dgn", True MsgBox m y D S 0 . S u r n r n a r y P r o p e r t i e s . A u t h o r myDSO.Close End Sub
The 'Author' of the file C:\test.dgn is "Bentley Systems, Inc.? It should be stated here that if we attempt to read or write properties of a file that is open in an application we may be prohibited from doing so. Our next example 'writes' properties to the testdgn file. Sub TestDSOB( 1
Dim myDSO As New D S O F i l e . 0 1 e D o c u r n e n t P r o p e r t i e s my DSO .Open C : \test . dgn , Fa 1 se myDSO.SummaryProperties.Author = "Jerry Winters" myDSO.SummaryProperties.Category = "MicroStation VBA" myDSO.SummaryProperties. Keywords = "VBA" myDSO.Save myDSO.Close "
"
I Using Existing DLLs I
683
End Sub
If we ‘write’properties to a file, we must save it before we close it.
Here are the results in Windows’ Explorer Properties diaIog box:
Summary
There are many ‘SummaryProperties’; many more than are displayed in this window. Not all apply to Microstation .dgn files and some are not supported by Microstation. The next macro displays the values of each ‘Summaryproperty’. Sub TestDSOC() Dim myDSO A s New DSOFile.0leDocumentProperties my DSO . Open “C : \test .dgn ” , True Debug.Print my0SO.SummaryProperties.ApplicationName Debug.Print my0SO.SummaryProperties.Author Debug.Print myDSO.SummaryProperties.ByteCount Debug.Print myDSO.SummaryProperties.Category Debug.Print myDSO.SummaryProperties.CharacterCount Debug.Print myDS0.SummaryProperties.CharacterCountWithSpaces Debug.Print my0SO.SummaryProperties.Comments Debug.Print myDSO.SummaryProperties.Company Debug.Print myDSO.SummaryProperties.DateCreated Debug.Print myDS0.SummaryProperties.DateLastPrinted Debug.Print myDSO.SummaryProperties.0ateLastSaved
I Chapter 33: Using Third Party ActiveX Controls and DLLs I D e b u g . P r i n t myDSO.SummaryProperties.HiddenS1ideCount Debug.Print myDS0.SummaryProperties.Keywords D e b u g . P r i n t myDSO.SummaryProperties.LastSavedBy Debug. Pri n t myDSO. S u m m a r y P r o p e r t i e s . L i neCount Debug.Print Debug.Print
myDSO.SummaryProperties.Manager myDSO.SummaryProperties.MultimediaC1ipCount
Debug.Print myDS0.SummaryProperties.NoteCount D e b u g . P r i n t myDS0.SummaryProperties.PageCount D e b u g . P r i n t myDSO.SummaryProperties.ParagraphCount Debug.Print myDS0.SummaryProperties.PresentationFormat Debug.Print
myDSO.SummaryProperties.RevisionNumber
Debug.Print myDSO.SummaryProperties.SharedDocument Debug.Print
myDS0.SummaryProperties.SlideCount
Debug.Print myDS0.SummaryProperties.Subject Debug.Print Debug.Print
myDSO.SummaryProperties.Template myDSO.SummaryProperties.Thumbnai1
Debug. P r i n t m y D S O . S u m m a r y P r o p e r t i e s . T i t l e Debug.Print myDSO.SummaryProperties.Tota1EditTime Debug.Print myDS0.SummaryProperties.Version Debug.Print myDS0.SummaryProperties.WordCount End Sub
Some of the Summary Properties shown here are read-only. For example, we cannot modify the 'DateCreated' property. Not all files are 'Structured Storage' files, for example, a .txt file created in Notepad. Even though it is not a Structured Storage file, when it resides on an NTFS hard drive, some 'Summary Properties' are available to us. We should be careful with non-Structured Storage files, though. It may look as though we are entering file properties. But if we e-mail the file or place it on a non-NTFS drive, the file properties do not follow the file. This is because the properties are not stored in the file, they are stored with (actually along side) the file. Sub TestDSOD( 1
D i m myDSO As New DSOFile.0leDocumentProperties myDSO.Open " C : \ t e s t . t x t " , T r u e MsgBox m y D S O . S u m m a r y P r o p e r t i e s . A u t h o r myDSO.Close End Sub
I Using Existing DLLs I
685
TestDSOD looks very much like TestDSOA, only we are opening a different file. Test.txt is a standard ASCII file. While in Windows Explorer, rightclick on the file and select Properties to display a few tabs.
Sample .txt File
If we compare this dialog box with the one a couple of pages ago we will discover that this dialog does not have a Custom tab. This indicates to us that the file is not a Structured Storage File and that the properties shown are due to NTFS functionality. So, we can visually discern the difference between NTFS properties and Structured Storage Properties. And the macro TestDSOD runs even though the file is not "Structured Storage': But how do we know whether a file is OLE (Structured Storage) or not? S u b TestDSOE()
Dim myDSO A s New DSOFile.0leDocumentProperties 'First we try a txt file myDS0.0pen "C:\test.txt", True If myDSO.IsOleFile = False Then MsgBox "The file is not Structured Storage." Else MsgBox "Structured Storage File Found.'' End If
I Chapter 33: Using Third Party ActiveX Controls and DLLs I myDSO.Close 'Now f o r t h e D G N F i l e myDSO.Open " C : \ t e s t . d g n " ,
I f myDSO. I s O l e F i 1 e
=
True
Fa1 s e T h e n
MsgBox " T h e f i l e i s n o t S t r u c t u r e d S t o r a g e . " Else MsgBox " S t r u c t u r e d S t o r a g e F i 1 e F o u n d .
"
End I f myDSO.Close End S u b
The "IsOleFile" property lets us know whether the file is an OLE Document (Structured Storage) or a non-OLE Document. This is important to know because non-OLE Documents are limited in their use of properties. In addition to SummaryProperties (standard properties), we can create, read, and write Custom Properties. S u b TestDSOF( 1 D i m myDSO As New DSOFile.0leDocumentProperties my D S O . Open
"
C : \ t e s t . d g n " , Fa 1 s e
I f myDSO. I s O l e F i 1 e
=
T r u e Then
myDS0.CustomProperties.Add " F o r B o o k " ,
-
" L e a r n i n g M i c r o S t a t i o n VBA" myDSO.Save End I f myDSO.Close End S u b
In this example, we are adding a Custom Property to the specified file IF the file is an OLE File (Structured Storage). After the Custom Property is
I Using Existing DLLs I
687
added, it shows up in the Windows Explorer Properties dialog box under the Custom Tab.
........................
;For ..................Book .......
:
;
Learning MicroStation VBA
The ability to add Custom File Properties is powerful. We may want to store information regarding the number of Cells in a design file in a custom property. If we do, VBA programming (from within Microstation or any other VBA environment) can read and write the property. This is true even if Microstation is not installed. A link was provided to the DS0File.exe download page earlier. The file is also located on the CD that accompanies this book.
For additional information regarding the reading and writing of File Properties, Microsoft’s website has documentation and code examples on it. Searching for “Dsofile” on the Internet provides a large number of results as well.
688
I Chapter 33: Using Third Party ActiveX Controls and DLLs I
REVIEW One of the powers of VBA is that we are not limited to the calls directly exposed by VBA. We can make use of other programming components developed by others to speed development and augment functionality. We have discussed a few References that, when added, can add significant power to the software we develop. Opening the References dialog box in VBA, adding a Reference, and opening the Object Browser is a great way to familiarize ourselves with the functionality exposed by any Reference on our computers.
34
Working With Excel Microsoft Excel is used for a great variety of things. A large number of us use it, even though we may use it differently. Many of us use it for calculations. Others use it for generating charts and graphs. Others use it to balance their checkbook. In the examples in this chapter we will be writing our code in Microstation’sVBA environment. In a later chapter we will write code in Excel’s VBA environment. In this Chapter
Connecting to Excel Workbooks, Worksheets, Ranges, and Cells Tag Extraction into Excel
CONNECTING TO EXCEL There are three ways to ‘connect’ to Excel. We will begin by using ‘‘Getobject’:
Getobject Sub TestExcelA(
)
689
690
I Chapter 34: Working With Excel I Dim myExcel As O b j e c t S e t myExcel = G e t o b j e c t ( , “ E x c e l . A p p l i c a t i o n ” ) End Sub
G e t o b j e c t ‘gets’ an existing instance of Microsoft Excel.
If Excel is not running, we see an error when w e attempt to ’get’ Excel.
f
When we see this error, we know we just attempted to “Get” Excel and Excel was not running. If Excel is running, the macro TestExcel A runs without anyproblems. But what does TestExcel A do? We declare a variable, myExcel as an Object. Then we Set the variable to the return value of GetObj e c t . After the variable myExcel is set, it is the Microsoft Excel Application. Everything we do to the variable myExcel impacts Excel. When we declare a variable as an ‘Object’, we are performing “Late Binding. This means that before the Object is Set, the Object doesn’t know who or what it is. When we declare a variable as a specific type of object, we are performing “Early Binding”. Adding a Reference to the Microsoft Excel Object Library does wonders for our programming efforts.
OK
Once a Reference is made, we can declare variables as specific types of Objects. Sub T e s t E x c e l B ( 1 D i m myExcel A s Excel .Application S e t myExcel = G e t o b j e c t ( , “ E x c e l . A p p l i c a t i o n ” ) End Sub
I Connecting to Excel I
691
The code looks very similar to TestExcelA but the difference is enormous. In TestExcel B we are performing “Early Binding. The variable myExcel knows it is an Excel.Application because it is declared as an Excel.Application. In addition to knowing what it is, the variable myExcel knows what it can do. For example, when we type ‘myexcel’ and then press the period key, a list pops up telling us what the object ‘myexcel’ can do. From this variable we can get the Activesheet, the Activewindow, the ActiveWorkbook, and a host of other things.
Early Binding not only improves the speed of software development because of Intellisense, but it improves performance as well.
Create0bject If Microsoft Excel is not running or if we want to create a new instance of the Excel.Application, we can use C r e a t e o b j e c t . Sub T e s t E x c e l D ( ) Dim myExcel As Excel .Application Set myExcel = CreateObject(”Exce1 .Application”) myExcel .Visible = True myExcel .Workbooks.Add End Sub
T e s t Excel D creates a new instance of Excel. It does not matter whether or not Excel had been running, a new instance of Excel is created. C r e a t e o b j e c t can be useful if multiple instances of Excel are running. When we use Get 0 b j ec t, we do not know beforehand which instance of Excel we will ‘get’. When we use C r e a t eO b j e c t , we know exactly which “Excel.Application” we are using because it is a new instance created.
692
I Chapter 34: Working With Excel I New When a Reference to the “Excel Library” has been added to our VBA project, VBA understands what an Excel.Application Object is. If a Reference has been added, we can use the New keyword to ‘create’ an Object. Sub T e s t E x c e l D 2 ( )
D i m m y E x c e l As New E x c e l . A p p l i c a t i o n m y E x c e l .V i s i b l e
=
True
myExcel .Workbooks .Add End Sub
Using New instead of Createobject can be useful because we do not need to supply the Class “Excel.App1ication”as we do with C r ea t eO b j ec t. And why is this useful? Because it is possible to have multiple versions of an application that has been exposed to VBA and using the New keyword will ‘create’the version that is referenced.
Getobject, Createobject, and New are the methods we use to ‘connect’to Microsoft Excel. When we add a Reference to the Microsoft Excel Object Library, we can declare variables as specific types of objects (Early Binding). VBA helps us as we work with these variables by performing syntax checking and also helps us know what properties, methods, and events we can utilize.
WORKBOOKS, WORKSHEETS,
RANGES,A N D CELLS
When we use Getobject, Createobject, or New, we are getting the Excel.Application Object. Directly under this object is the Workbooks
I Workbooks, Worksheets, Ranges, and Cells I
693
Collection (among other things). Workbooks are composed of Worksheets and Worksheets are composed of Ranges and Cells.
This Workbook has three Worksheets. They are named “SimpleGrid”, “ComplexGrid” and “Sheet3’: We are currently looking at “SimpleGrid”. The Activesheet of this Excel.Application is “SimpleGrid’: The Activecell of this Excel.Application has the Address “Al”. Its Row and Column is (1, 1) respectively. Let’s take a look at the Worksheets collection and Worksheet Objects. Sub T e s t E x c e l E ( ) D i m m y E x c e l As E x c e l . A p p l i c a t i o n D i m mySheetA As W o r k s h e e t D i m mySheetB As W o r k s h e e t
D i m mySheetC As W o r k s h e e t D i m mySheetD As W o r k s h e e t S e t myExcel
Getobject(,
=
S e t mySheetA
=
S e t my S h e e t B
=
S e t my Sh e e t C S e t mySheetD
=
“Excel . A p p l i c a t i o n ” )
myExcel.ActiveSheet my Exc e 1 . A c t iv e W o r k b o o k . W o r k s h e e t s ( ” S imp 1 e G r id ” 1
.
.
my Exce 1 A c t iv e W o r k bo o k W o r k s h ee t s ( ” C om p l exG r id ” 1
=
myExcel.ActiveWorkbook.Worksheets(”Sheet3”)
D e b u g . P r i n t mySheetA.Name D e b u g . P r i n t mySheetB.Name D e b u g . P r i n t mySheetC.Name D e b u g . P r i n t mySheetD.Name S e t mySheetB
=
S e t mySheetC
=
S e t mySheetD
=
myExcel.ActiveWorkbook.Worksheets(1) myExcel.ActiveWorkbook.Worksheets(2) myExcel.ActiveWorkbook.Worksheets(3)
694
I Chapter 34: Working With Excel I D e b u g . P r i n t mySheetB.Name D e b u g . P r i n t mySheetC.Name D e b u g . P r i n t mySheetD.Name End Sub
We can address Worksheets by getting the Activesheet or through the Worksheets Collection by Name or by Index. Now that we can get the Worksheets, let's see what we can do about getting individual Cells. Sub TestExcel F ( 1 D i m m y E x c e l As E x c e l . A p p l i c a t i o n D i m mySheetA As W o r k s h e e t
S e t myExcel S e t mySheetA
= =
Getobject(, "Excel . A p p l i c a t i o n " )
myExcel.ActiveWorkbook.Worksheets("Simp1eGrid")
MsgBox mySheetA. R a n g e ( " B 1 " ) . T e x t End Sub
One of the ways we can address individual Cells is by getting to them through the Range Object. When we use the Range object, we get the cell by its address. "Al", 'C6': "F9", etc. Using an Address such as "4" is helpful because we can directly relate that to what we see in Excel. The other way we work with Cells in Excel is through the use of the Cells Collection.
Sub TestExcelG() Dim myExcel As Excel.Application Dim mySheetA As Worksheet Set myExcel = Getobject(, "Excel.Application") Set mySheetA = myExce1.ActiveWorkbook.Worksheets ("SimpleGrid") MsgBox mySheetA. Cells ( End Sub
When we work with the Cells Collection we specify the RowIndex and then the ColumnIndex. Row 1 in Excel has a RowIndex of 1 and Column 'X in Excel has a ColumnIndex of 1. We need to make sure we specify the Row before the Column when working with the Cells collection. Sub TestExcel G( 1 D i m m y E x c e l As E x c e l . A p p l i c a t i o n
I Workbooks, Worksheets, Ranges, and Cells I
695
D i m mySheetA As Worksheet
S e t myExcel
G e t o b j e c t ( , "Excel . A p p l i c a t i o n " )
=
S e t my S h e e t A
my Exc e 1 . A c t iv e W o r k b o o k . W o r ks he e t s ( " S imp 1 e G r id "
=
MsgBox m y S h e e t A . C e l l s ( 4 ,
6).Text
End Sub
A MessageBox displays the text found in the cell on the 4th row and 6th column. Getting a Cell based on its Row and Column does not seem as easy as getting it based on its Address. So, why would we go through the trouble of Rows and Columns? Sub T e s t E x c e l H ( )
D i m myExcel As E x c e l . A p p l i c a t i o n D i m mySheetA As Worksheet D i m CurRow As Long D i m CurCol As Long
S e t myExcel F o r CurRow
G e t o b j e c t ( , "Excel . A p p l i c a t i o n " )
=
S e t my S h e e t A
my Exc e 1 . A c t iv e W o r k b o o k . W o r ks he e t s ( " S imp 1 e G r id " 1
=
1 To 7
=
F o r CurCol
=
A s c ( " A " ) To A s c ( " F " )
D e b u g . P r i n t m y S h e e t A . R a n g e ( C h r ( C u r C o 1 ) & CurRow) N e x t CurCol N e x t CurRow End Sub Sub T e s t E x c e l
J()
D i m myExcel As E x c e l . A p p l i c a t i o n D i m mySheetA As Worksheet D i m CurRow As Long D i m CurCol As Long
S e t myExcel F o r CurRow
G e t o b j e c t ( , "Excel . A p p l i c a t i o n " )
=
S e t my S h e e t A =
=
my Exc e 1 . A c t iv e W o r k b o o k . W o r ks he e t s ( " S imp 1 e G r id "
1 To 7
F o r CurCol
=
1 To 6
Debug.Print mySheetA.Cells(CurRow, CurCol) N e x t CurCol N e x t CurRow End Sub
Both TestExcel H and TestExcel J print the values of a grid of cells to the Immediate Window. TestExcelH can do this easily because we are
696
I Chapter 34: Working With Excel I dealing with columns A to F. The same code would work with columns A to Z. But what happens when we get to column “AX? When we work with Range objects, we specify the column with its letter designation of anything from ‘‘A’’to “IV’: Writing code that flows from “Z” to ‘%A” is not difficult but cumbersome. When we use the Cells collection, we simply specify column 27 after we finish with column 26 without worrying about whether we are going from Column Z to AA, AB, and so forth. So, which is best? The Cells Collection or the Range Collection?
As we have discussed, each has its strengths and weaknesses. Providing a Row and Column numerically is easy to do but difficult to ‘translate’the Cell Column to a lettered Column in Excel. What is the lettered Column Name associated with column 21 l? Ranges are great especially when dealing with a relatively small set of data (Columns A through Z particularly) but become more difficult to work with when we get to Columns AA through IV. Ranges, however, can also consist of multiple cells (from A1 through D4 for example). So, that is a definite strength. If we work with Cells (providing numbers for the columns as well as the rows), we can help ourselves a little by changing a setting in Microsoft Excel. Tools > Options in Excel displays the Options dialog box. Clicking on
the General tab allows us to turn on “RlC1 reference style’: When “RlC1 reference style” is turned on, Columns in Excel appear as Numbers instead of letters. The formulas in Cells are modified to use the RlCl style so they will look odd but as far as programming for Microsoft Excel, seeing the Column Number is a lot easier than
I Workbooks, Worksheets, Ranges, and Cells I
697
counting out ‘&A”, XB”, “AC” to find out what the Column Index is of Column “DC?
After selecting “RlCl”, Columns appear as numbers instead of letters:
Cell and Range Addresses Each Cell in Excel has a large number of properties. We will concern ourselves with only a few of them in this book. The first one we will look at is the Address. Sub T e s t E x c e l L ( ) D i m myExcel As E x c e l . A p p l i c a t i o n D i m mySheetA As Worksheet D i m CurRow As Long
D i m CurCol As Long D i m m y C e l l As E x c e l .Range S e t myExcel S e t mySheetA
= =
G e t o b j e c t ( , “Excel . A p p l i c a t i o n ” ) myExcel . A c t i v e W o r k b o o k . W o r k s h e e t s (
S i mpl eG r id ”
698
I Chapter 34: Working With Excel I F o r CurRow
=
1 To 7
For CurCol
=
1 To 6
Set myCell
=
mySheetA.Cells(CurRow, CurCol)
D e b u g . P r i n t m y C e l l . A d d r e s s ( T r u e , T r u e , x l A l ) & vbTab & myCell .Address(True, True, x l R l C 1 ) Next CurCol N e x t CurRow End Sub
We can get the Address of a Cell (declared as a Range object) in RICl the “xlA1” format or the ;;;; “xlRlC1” format. As we have just SDS~ ~ 1 ~ 4 $E$l R1C5 been discussing, the “xlAl” $F$l R1C6 $AS2 R2C1 format gives us the column as a SBSZ RZCZ letter and the row as a number. The “xlRlC1” format gives us both the column and row as numbers as shown in the Immediate Window after running T e s t E x c e l L.
:;::
As we see here, Addresses are returned in one of two ways. “$D$2” and “R2C2” are referring to the same cell in Excel. Addresses are important to understand because we may need to know which cells the user has selected. For example, how do we know if the user has selected the Range “B2” to “B7” or “D2” to “DS’? Sub T e s t E x c e l M ( 1
D i m m y E x c e l As E x c e l . A p p l i c a t i o n D i m m y s e l e c t i o n As E x c e l . Range S e t myExcel
=
GetDbject(, “Excel . A p p l i c a t i o n ” )
Set myselection Debug.Print
=
myExcel . S e l e c t i o n
mySelection.Address(True, T r u e , x l R l C 1 )
End Sub
R2C2: R7C2
In this example, the selection was “B2” through “B7” (2,2 to 7,2).
I Workbooks, Worksheets, Ranges, and Cells I
699
The colon (:) tells us “B2” through “B7” have been selected. There are other ways to select cells in Excel.
When we usually select cells in Excel we select a range of adjacent cells. For example, we may select “D4” through “G12’: It is possible, however, to hold down the
Here we see what we get when the range of “B2” through “B5” are selected AND “D2” AND “D5”. In the previous examples, we knew exactly from which cells we wanted to get values. Our next example is going to display the values of the selected cells. We will begin by assuming that a range of cells is selected instead of individual cells. Sub T e s t E x c e l N ( )
D i m m y E x c e l As E x c e l . A p p l i c a t i o n D i m m y s e l e c t i o n As E x c e l .Range D i m m y s h e e t As E x c e l . W o r k s h e e t D i m S t a r t R o w As L o n g : D i m S t a r t C o l As L o n g D i m EndRow As L o n g : D i m EndCol As Long D i m X As L o n g : D i m Y As Long Dim X S p l i t A As Variant:
S e t myExcel
=
Dim X S p l i t B As V a r i a n t : Dim X S p l i t C As V a r i a n t
Getobject(,
S e t mySel e c t i on
=
“Excel . A p p l i c a t i o n ” )
myExcel . S e l e c t i o n
S e t mysheet X S p 1 it A
=
XSp 1 it B
=
= myExce1.ActiveSheet S p 1 it ( my Se 1 e c t io n .Add r e s s ( T r u e , T r u e , x 1 R 1 C 11 , Sp 1 it ( X Sp 1 it A ( 0 ) , ” C ” )
XSp 1 it C
=
Sp 1 it ( X Sp 1 it A ( 1) ,
”
C” )
StartRow
=
Replace(XSplitB(O), “R”,
StartCol
=
XSplitB(1)
“ ‘ I )
”
:” )
700
I Chapter 34: Working With Excel I End R o w EndCol
For Y
Rep 1 ace ( X S p 1 it C ( 0 1 ,
=
R" ,
" "
1
XSpl it C ( 1 1
=
=
"
S t a r t R o w To EndRow
For X
=
S t a r t C o l To EndCol
Debug.Print mySheet.Cells(Y,
X1.Text
Next X Next Y End Sub
In this example, we discover the beginning Row and Column as well as the ending Row and Column. Then we look at each cell in the range and print the Text property of the cell to the Immediate Window. Sub TestExcel P (
1
D i m myExcel As E x c e l . A p p l i c a t i o n D i m m y s e l e c t i o n As E x c e l . Range S e t myExcel
=
G e t o b j e c t ( , "Excel . A p p l i c a t i o n " )
Set m y s e l e c t i o n
=
myExcel . S e l e c t i o n
D i m m y C e l l As Range
F o r Each m y C e l l I n m y s e l e c t i o n
D e b u g . P r i n t m y C e l l . A d d r e s s & vbTab & m y C e l l . T e x t Next End Sub $B$E $C$2 $D$E $B$3 $C$3
200 300 400 400 600
TestExcel N and TestExcel P are very much alike. They accomplish the same thing. In TestExcel P, we are 'extracting' the address as well. So, if
each of these is doing the same thing, which one is the best one? Fewer lines of code is good. Knowing how to break out the Address is good too. Each has its benefits. One is not necessarily better than the other, they are just different. Here is another macro to consider. Instead of extracting the Address as
'M" style, we will extract it in (Row, Col) style. Sub TestExcel Q( 1 D i m myExcel As E x c e l . A p p l i c a t i o n D i m m y s e l e c t i o n As E x c e l . Range
D i m m y C e l l As Range D i m s t r A d d As S t r i n g
I Workbooks, Worksheets, Ranges, and Cells I S e t myExcel
=
Getobject(,
S e t mySel e c t i on
=
701
"Excel . A p p l i c a t i o n " )
myExcel . S e l e c t i o n
F o r Each m y C e l l I n m y s e l e c t i o n strAdd s t r Ad d
s t r Add
= = =
myCell.Address(True, True, xlRlC1) Rep 1 a c e ( s t r Ad d , " R " , " " ) Rep 1 a c e ( s t r Add, " C " , " , " )
D e b u g . P r i n t " ( " & s t r A d d & " ) " & vbTab & m y C e l l . T e x t Next End S u b (2,2)
200
(2,3) (2,4) (3,2) (3,3)
300
400 400 600
We need to remember that when we are dealing with Excel, we are looking at (Row, Col). The Row comes first. When we are accustomed to dealing with (X, Y) this can take a little getting used to because Excel thinks in terms of (Y, X). So far we have done a lot of 'pulling' from Excel. Let's try doing a little 'pushing' now. We are going to change a cell's value, a cell's formula, and then perform a Copy and Paste operation. Sub T e s t E x c e l R ( )
D i m m y E x c e l As E x c e l . A p p l i c a t i o n D i m m y s h e e t As W o r k s h e e t S e t myExcel
=
Getobject(,
S e t mysheet
=
myExce1.ActiveSheet
"Excel . A p p l i c a t i o n " )
' G i v e B 1 a new V a l u e
mySheet.Range("Bl").Value
=
70
' G i v e B2 a new F o r m u l a mysheet. Range("B2"). Formul a
=
"=B$l*$A2*52"
' C o p y B2 t o t h e Windows C l i p b o a r d m y s h e e t . R a n g e ( " B 2 " ) .Copy ' S e l e c t B2 t h r o u g h F7 myS h e e t . Range ( " B2 " ,
"
F 7 " ) . Se 1 e c t
'Paste copied formula t o selected c e l l s mySheet.Paste ' S e l e c t B2 mySheet.Range("B2") . S e l e c t ' R e s e t C u t / C o p y Mode m y E x c e l .CutCopyMode
=
False
702
I Chapter 34: Working With Excel I End Sub
The code is very straightforward and simple. Comments proceed each line where we make modifications to the Excel file. T e s t E x c e l R is making changes to the ''Activesheet':
Working with Worksheets As we know, multiple Worksheets can be found in a single Workbook. Let's take a look at working with multiple Worksheets. Sub T e s t E x c e l S (
1
D i m m y E x c e l As E x c e l . A p p l i c a t i o n
D i m myWB A s E x c e l . W o r k b o o k D i m myWSA As E x c e l . W o r k s h e e t
D i m myWSB As E x c e l . W o r k s h e e t D i m myWSC As E x c e l . W o r k s h e e t D i m R a d V a l u e As D o u b l e D i m I n c h V a l u e As D o u b l e
D i m F e e t V a l u e As D o u b l e D i m CurRow As L o n g
S e t myExcel S e t myWB
=
=
Getobject(, "Excel . A p p l i c a t i o n " )
myExcel . A c t i v e W o r k b o o k
S e t myWSA
=
S e t myWSB
=
S e t myWSC
=
myWB.Worksheets("Sheetl"1 myWB.Worksheets("Sheet2") myWB.Worksheets("Sheet3")
' M e r g e H e a d e r Rows
1 .M e r g e T r u e D 1" 1 . Me r g e T r u e my W SC . Ra n g e ( " A 1" , " D 1" 1 . Me r g e T r u e myWSA. Range ( " A 1 " ,
"
my W S B . Ra n g e ( " A 1" ,
"
D1"
'Add T i t l e s myWSA.Range("Al"1.Value
=
"Degrees"
myW SA . Range ( " A 1 " 1 . Ho r iz o n t a 1 A1 ignmen t myWSB . Range ( " A 1 " 1 . V a l u e = " I n c h e s " my W S B . Ra n g e ( " A 1" 1 . H o r iz o n t a 1 A 1 ig n me n t
myW S C . Range ( " A 1 " 1 . Va 1 u e
=
"
=
x l Center
=
x 1C e n t e r
=
x 1C e n t e r
Feet "
my W SC . Ra n g e ( " A 1" 1 . H o r iz o n t a 1 A 1 ig n me n t
'Add Values myWSA.Range("A2"1.Value
=
"Degrees"
myWSA. Range ( " B2 " 1 . V a l u e
=
"
Radi ans "
I Workbooks, Worksheets, Ranges, and Cells I CurRow
=
703
3
For RadValue
=
0 To 360 S t e p 5
myWSA.Range("A" myWSA.Range("B"
CurRow
=
& CurRow)
=
& CurRow).Value
CurRow
RadValue =
RadValue
*
Atn(1)
*
4 / 180
"
* 2.54"
"
/ 5280"
+ 1
N e x t RadVal u e CurRow
=
3
myWSB.Range("A2")
=
"Inch"
myWSB. R a n g e ( " B 2 " )
=
"Centimeter"
For Inchvalue
=
1 T o 36
myWSB.Range("A" myWSB.Range("B"
CurRow
=
& CurRow)
=
& CurRow).Formula
InchValue =
"=A"
& CurRow &
CurRow + 1
Next I n c h V a l ue CurRow
=
3
myWSC. R a n g e ( " A 2 " )
=
"Feet"
myWSC.Range("B2")
=
"Miles"
For FeetValue
=
0 To 20000 S t e p 1000
myWSC.Range("A"
& CurRow)
=
myWSC.Range("B" & C u r R o w ) . F o r m u l a
CurRow
=
CurRow
FeetValue =
"=A"
& CurRow &
+ 1
Next FeetVal ue End S u b
T e s t E x c e l S is making use of three different Worksheets in the same Workbook. We do not need to switch to the actual Worksheet before we can use it, we simply address it by its name (Sheetl, Sheet2, Sheet3).
Excel is used to house a great deal of information. One thing it can be used for is holding X, Y, and Z coordinates for points. Let's take a look at our first example.
704
I Chapter 34: Working With Excel I We use the Excel Function RAND to help us generate a random number. This helps us generate points that fall within a specific area.
Level
Here are the formulas:
The X and Y values are set to be randomly generated between -50 and 50 whereas the Z value will be calculated to be between -25 and 25. The RAND function re-calculates the values whenever the Worksheet is recalculated. The values don’t ‘stick‘, so one person’s values will differ from another person’s values.
I Workbooks, Worksheets, Ranges, and Cells I
705
Now that we have seen the formulas, let's take a look at the values generated.
1
For this example, the number of rows is not fxed. We can have anywhere from 1 data row to 65,536 rows. The code we will work with begins by looking on Row 2 and continues executing until it finds a row where Column A is empty. Sub T e s t E x c e l T ( ) Dim myExcel As Excel .Application Dim myWSA As Excel .Worksheet Dim CurRow As L o n g CurRow = 2 Set myExcel = Getobject(, "Excel .Application") Set my W SA = my Ex c e 1 .Act i v e W o r kb o o k .W o r ks h e e t s ( Sh e e t 1 1 While myWSA.Cells(CurRow, 1) <> Debug.Print myWSA.Cells(CurRow, 1) & & myWSA.Cells(CurRow, 2) & & myWSA.Cells(CurRow, 3 ) CurRow = CurRow + 1 Wend myWSA.Calculate End Sub "
"
" "
'I,
"
'I,
"
~
~
706
I Chapter 34: Working With Excel I If we have a specific number of rows we want to extract, we could use a Fo r I ... Next structure. But since the number of rows may vary, we use a Whi 1 e ... Wend structure. The last line of code forces the Worksheet to recalculate, which generates new random numbers for us. Let's build upon T e s t Excel T by drawing inside Microstation. Sub TestExcel U( 1 D i m m y E x c e l As E x c e l . A p p l i c a t i o n D i m myWSA As E x c e l . W o r k s h e e t D i m CurRow As L o n g D i m m y p o i n t As P o i n t 3 d D i m m y T e x t N o d e As T e x t N o d e E l e m e n t D i m m y R o t M a t r i x As M a t r i x 3 d
CurRow
=
2
S e t myExcel
=
S e t myWSA
m y E x c e l . A c t i v e w o r k b o o k . Works h e e t s ( " S h e e t l " )
=
GetDbject(, "Excel . A p p l i c a t i o n " )
W h i l e m y W S A . C e l l s ( C u r R o w , 1)
<>
" "
myP0int.X
=
m y W S A . C e l l s ( C u r R o w , 1)
myP0int.Y
=
m y W S A . C e l l s ( C u r R o w , 2)
myP0int.Z
=
myWSA.Cells(CurRow, 3 )
S e t myTextNode
=
mypoint,
CreateTextNodeElementl(Nothing,
~
my R o t M a t r ix )
m y T e x t N o d e . A d d T e x t L i n e CurRow
-
2
ActiveModelReference.AddElement m y T e x t N o d e CurRow = CurRow + 3 Wend myWSA.Calculate End S u b
Our Worksheet has a column for "Level': In the above example we are not making use of it. Let's build upon TestExcelU now and place the TextNode on a specific Level. If we attempt to place an Element on a Level that does not exist, we will get an error. Let's create a new Function named Checklevel that creates a Level if it does not exist. We will use this Function inside T e s t Excel V. F u n c t i o n CheckLevel ( L e v e l N a m e As S t r i n g ) As L e v e l On E r r o r Resume N e x t S e t CheckLevel
=
ActiveDesignFile.Levels(Leve1Name)
I Tag Extraction I
707
If E r r . N u m b e r <> 0 Then S e t CheckLevel End
=
A c t i v e D e s i g n F i l e . A d d N e w L e v e 1 ( L e v e l Name)
If
Err.Cl e a r End F u n c t i o n Sub T e s t E x c e l V ( ) D i m m y E x c e l As E x c e l . A p p l i c a t i o n
D i m myWSA As E x c e l . W o r k s h e e t D i m CurRow As Long D i m m y p o i n t As P o i n t 3 d D i m m y T e x t N o d e As T e x t N o d e E l e m e n t D i m m y R o t M a t r i x As M a t r i x 3 d
CurRow
=
2
S e t myExcel
=
S e t my W S A
my Ex c e 1 .A c t iv e W o r k b o o k . W o r k s h e e t s ( " S h e e t 1"
=
Getobject(,
W h i l e myWSA.Cells(CurRow,
"Excel . A p p l i c a t i o n " )
1)
<>
" "
myP0int.X
=
m y W S A . C e l l s ( C u r R o w , 1)
myP0int.Y
=
myWSA.Cells(CurRow, 2 )
myP0int.Z
=
myWSA.Cells(CurRow, 3 )
S e t myTextNode
=
CreateTextNodeElementl(Nothing,
~
mypoint, myRotMatrix) myTextNode.AddTextLine
CurRow
-
2
CStr(myWSA.Cells(CurRow, 4)) m y T e x t N o d e . L e v e 1 = CheckLevel(myWSA.Cells(CurRow, CheckLevel
ActiveModelReference.AddElement CurRow
=
CurRow
4))
myTextNode
+ 1
Wend myWSA.Calculate End Sub
Notice how the Function C h e c k L e v e l returns a Level Object. We use this Level Object after we create the TextNode so the TextNode appears on the correct Level.
TAGEXTRACTION When we discussed Tags in a previous chapter we stated that we would see an example of extracting Tag information into Microsoft Excel. We will begin by modifying the macro E x p o r t F o l derTagsToHTML. In this
708
I Chapter 34: Working With Excel I macro, we had created an HTML document that displays the Tag information of files in a specific folder. Sub T e s t E x c e l W ( 1
Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim
myDGN As DesignFile myFSO As New Scripting.FileSystemDbject myFolder As Scripting.Folder myFile As Scripting.File myTagSet As TagSet myTagDef As TagDefinition TargetTagset As String myTag As TagElement myElemEnum As ElementEnumerator myFi 1 ter As New El ementScanCri teri a
'New D e c l a r a t i o n s
D i m myExcel As E x c e l . A p p l i c a t i o n D i m myWS As E x c e l .Worksheet D i m CurRow As Long 'New Code S e t myExcel
New E x c e l . A p p l i c a t i o n
=
myExcel . V i s i b l e
=
True
myExcel .Workbooks.Add S e t myWS
=
CurRow
2
=
myExcel.ActiveSheet
TargetTagset
=
S e t rnyFolder
rnyFSO.GetFolder("C:\Docurnents and S e t t i n g s \ " &
=
"Titl eB1 ock" ~
" A l l Users\Application Data\" & B e n t 1 e y \ W o r kS pa c e \ P r o j e c t s \ & "Examples\Building\Dgn") "
"
~
For Each myFile In myFolder.Files Select Case myFile.Type Case "Bentley Microstation Design File" ' F i l e Name and Merge C e l l s myWS.Cells(CurRow, rnyWS.Range("A" rnyWS.Range("A"
1)
=
myFile.Path
& CurRow & " : F " & CurRow. "F"
& CurRow).MergeCells , xlThick
'Header CurRow
=
CurRow
+
1
=
& CurRow) . B o r d e r A r o u n d -
True
I Tag Extraction I & CurRow)
myWS. Range("B"
myWS.Range("B"
& CurRow)
myWS. Range("C"
myWS.Range("C"
"Tag S e t Name"
=
=
myWS.Range("E" myWS.Range("F" CurRow
=
& CurRow).Font.Bold
& CurRow)
myWS.Range("F" CurRow
+
=
True
=
True
=
True
=
True
=
True
"Tag V a l u e "
& CurRow).Font.Bold & CurRow) = " I D H i g h "
myWS.Range("D" myWS.Range("E"
=
"Tag Name"
& CurRow).Font.Bold
& CurRow)
myWS.Range("D"
=
& CurRow).Font.Bold
"ID Low"
& CurRow).Font.Bold 1
'Open t h e F i l e S e t myDGN
=
Application.OpenDesignFileForProgram( myFile.Path,
-
True)
F o r Each myTagSet I n myDGN.TagSets S e l e c t Case UCase(myTagSet.Name) Case U C a s e ( T a r g e t T a g s e t 1 rnyFi 1 t e r . E x c l u d e A l 1 T y p e s myFi 1 t e r . I n c l u d e T y p e rnsdElementTypeTag S e t myElemEnum = rnyDGN.Mode1 ~
s(l) . S c a n ( r n y F i 1 t e r )
Whi 1 e rnyEl ernEnurn.MoveNext S e t myTag
=
myElemEnum.Current
' W r i t e t o Excel myWS.Cells(CurRow,
2)
=
TargetTagset
myWS.Cells(CurRow, 3) = m y T a g . T a g D e f i n i tionName
4)
myWS.Cells(CurRow, myWS.Cells(CurRow, myWS.Cells(CurRow, CurRow Wend End S e l e c t Next myDGN. C1 o s e
End S e l e c t Next End S u b
=
CurRow
5)
6)
+
=
=
1
myTag.Value
myTag.ID.High
=
myTag.ID.Low
710
I Chapter 34: Working With Excel I The code here is very similar to that in Chapter 28. We open each file “ForProgram”,and extract Title Block Information from the Tags in the files. We are doing a little bit of formatting as well. We merge a few cells where the file name is, and draw a border around it. We also change the font.Bold property of the ‘headers’to True.
Getting the Tag Name and Value are helpful in a variety of areas. But getting the ID values (both High and Low) are helpful as well. Now that the Tag information is inside Excel, we can make changes to the Tags in Excel and then run a Macro to update the .dgn files.
If, for example, the Job Number is changed from “BSI300” to “BSI300A’: we could make the change in Excel and run the macro T e s t E x c e l X to update the BSI300AE3O1-Elevations.dgnfile. Sub TestExcel X ( 1 D i m myDGN As D e s i g n F i l e D i m myTag As T a g E l e m e n t
I Tag Extraction I Dim Dim Dim Dim
711
myExcel As Excel .Application myWS As Excel .Worksheet CurRow A s Long FileRow A s Long
Set myExcel = Getobject(, "Excel .Application") Set myWS = myExce1.ActiveSheet CurRow = myExcel.ActiveCell.Row FileRow = CurRow W hi 1 e myWS .Cell s ( Fi 1 e Row, 1 ) .Mergecell s = Fa1 se FileRow = FileRow - 1 Wend Set myDGN = Application.OpenDesignFileForProgram( myWS.Cel1 s ( Fi leRow, 1). Fa1 se) Dim myID As DLong myID.High = myWS.Cells(CurRow, 5 ) myID.Low = myWS.Cells(CurRow, 6) Set myTag = myDGN.GetElementByID(my1D) myTag.Value = myWS.Cells(CurRow, 4) myTag. Rewrite myDGN.Save myDGN. C1 ose End S u b
Once we extract Tags from Microstation, we want to allow the user to change the values in Excel. The currently selected Cell in Excel is very important in this macro because it tells us which Tag to modify. The Row of the currently selected Cell in Excel is retrieved. We look upward until we find a row where column A (Column 1) is merged. When we find a merged Column we know that we have found the row where the file name is stored. This is the file in which the selected Tag (actually the selected Cell in Excel) is located. We open the file, get the Tag Element by using the 1D.High and ID.Low values, change the Tag's value to reflect what is in Excel, Rewrite the Tag Element to the DGN file, and then save and close the .DGN file. This is all done in a very short period of time and the user does not see the DGN file open because we are using OpenDesignFi 1 eForProgram. If we open the file, we can see that the Tag Element has been modified to reflect the value in Excel.
712
I Chapter 34: Working With Excel I These macros are very powerful because they allow us to extract data from Microstation files, and ‘modify’ them at any time without using Microstation, and then ‘upload’ the modifications back into the Microstation file. We could build on this macro to update all Tags in the Excel file. But we will allow the reader to do this.
REVIEW Any area we have already discussed relating to Element Creation, Data Extraction, etc., can be used in conjunction with Microsoft Excel. We have used Excel to extract data from Microstation, to create data inside Microstation, and to modify data inside Microstation. Those who use Microsoft Excel in conjunction with Microstation will find that many manual, time-consuming, tedious, error-prone tasks can be accomplished with the marriage of these two great technologies.
Working With Databases (ActiveX Data Objects) Access, Oracle, DB2, SQL Server. When there’s data to be stored, there is no shortage of Database platforms to store it. Rather than spending time on each Database, we will devote our time to learning about ActiveX Data Objects because with ActiveX Data Objects, we can ‘talk’ to each of these database products. In this Chapter:
Primer on ActiveX Data Objects UDL File Basics Connections and Recordsets Structured Query Language Essentials Extending ActiveX Data Objects Examining Database Schema Excel Files as Databases
713
714
I Chapter 35: Working With Databases (ActiveX Data Objects) I PRIMER ON ACTIVEXDATAOBJECTS ActiveX Data Objects is a technology created by Microsoft that allows for simple yet powerful communication with Databases. Which Databases? Virtually any Database that is ‘open: And what does ‘open’ mean? Microsoft Access is ‘open’. SQL Server is ‘open’. Oracle is ‘open’. Mainstream databases are ‘open’. We can work with them through standard VBA / ADO (ActiveX Data Objects) calls. Some databases, however, are proprietary and as such, are not designed to allow software developers to work with them. So, in general, most databases available to us can be accessed through the use of ActiveX Data Objects. ActiveX Data Objects allow us to work with databases through the use of several key Objects. Before using “ActiveXData Objects”,we must add a Reference to it. Let’s do so right now.
P
When we look in the References dialog box in VBA, we may see a number of different ‘XctiveX Data Objects” libraries. In general, it is best to select the highest Library version available. In this example, Version 2.8 is the highest one. After adding a Reference to the “Microsoft ActiveX Data Objects 2.8 Library”,new Objects and Constants are exposed and available for our use. We can see them by looking at the Object Browser and filtering on the “ADODB”Library.
I Primer on ActiveX Data Objects I
715
Selecting the “Connection” Object displays the Methods, Properties, and Events.
Execute ExecuteComplete InfoMessage IsolationLevel Mode Openschema Properties Provider RollbackTrans
When we select the “Open” method, we can see the Open declaration. A few Objects worth examining for a moment are “Connection”, “Recordset’: and “Field. We will be working with these Objects and their Methods and Properties. Look for Methods such as Open, Update, Execute, and AddNew. In a nutshell, ActiveX Data Objects allow us to open a Database, query its records, modify its records, add new records, etc. Before doing much with ActiveX Data Objects, however, we need to have a database to work with.
The USGS (United States Geographic Survey) maintains a system named the “Geographic Names Information System” (GNIS). The Geographic Names from several states have been imported into a Microsoft Access Database named flucefointsmdb. This file is located on the CD accompanying this book. This w ill be the first database we work with in this chapter.
716
I Chapter 35: Working With Databases (ActiveX Data Objects) I Here is a screen capture of some of the data in this database. Notice the Fields (Columns) and each Record (Rows).
14029
UDL FILEBASICS ActiveX Data Objects gives us a framework to interact with databases. We can use Connection Strings to specify the location of the database we want to work with and which driver should be used to connect to the database. Connection Strings are often times hard-coded. This means that the Connection String appears in our code and we must change the code if any portion of the Connection String changes. Connection Strings can also be read from configuration files or from the Windows Registry. Another way to specify the driver and database location is through the use of a UDL file. The steps to creating a UDL file are very simple. They are described in Windows’ Help File and can be found by typing ‘udl’ in the search box.
Using Data Link You can use the Data Link program t o c ile that can be referenced f o r f u t , you can open the Data Link pr( T o open Data Link
I UDL File Basics I
717
1 Open Windows Explorer.
2 Go to Tools > Folder Options.
. 3 In the View tab, uncheck the “Hide extensions for known file types’: 4
Right-click in the folder where the UDL file is to be created and select New > Text Document. View Arrange Icons By Refresh Customize This Folder...
Undo Rename
5
A file such as New Text Document.txt is displayed and is ready to be renamed.
I Chapter 35: Working With Databases (ActiveX Data Objects) I
718
6 Trpe the name of the UDL file followed by the extension “.udl” and press
0 KB
Text Document
7 When asked if you are sure the file extension should be changed, click the Yes button. The new UDL file is created. Now we need to open the UDL file and tell it which driver we want to use (based on the database we are connecting to) and where the database is located.
8 Open the UDL file by double-clickingit in Windows Explorer.
E
9 In the Provider tab, select “Microsoft Jet X.X OLE DB Provider’: 10 In the Connection tab, browse to the P/ucePoints.rndb file. 11 Click the OK button.
I Connections, Recordsets, and More I
719
Congratulations. Once the basics of UDL file creation are understood, UDL files can be created very quickly. When we are in the Provider tab, the list of available ‘Providers’ may vary from computer to computer. A review of the Providers list shows which databases we can work with. In the screen capture previously shown, we can see a driver for Oracle, SQL Server, Visual Fox Pro, Jet, and ODBC among others. Any provider with ‘Jet’in its name is referring to the driver used to open a Microsoft Access database.
CONNECTIONS,
RECORDSETS,AND MORE
Now that we have a Database (PlacePoints.mdb) and a UDL file (PlacePoints.ud1) we can begin working with Connections and Recordsets in our code. The Connection Object is ‘connected’ to the database. It is the first step to working with the data in the database. Let’s look at our first example: Sub
TestConnectionA( 1 D i m myDB As New A D O D B . C o n n e c t i o n myDB.0pen “ f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\PlacePoints.udl”
myDB. C1 o s e
End S u b
In our first example, we open a Connection by using the UDL file we just created and then we immediately close the database connection. Notice how the variable myDB is declared. It is declared as a “New ADODB.Connection’: In addition to declaring a Connection as a “New ADODB.Connection”, we can declare it as an “ADODB.Connection” and then set the variable to a “New ADODB.Connection” as we see in TestConnectionB. Sub
TestConnectionB( 1 D i m myDB As A D O D B . C o n n e c t i o n S e t myDB
=
New A D O D B . C o n n e c t i o n
myDB.0pen “ f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\PlacePoints.udl”
myDB. C1 o s e
End S u b
Instead of Declaring the Connection as New, we set it to a New ADODB.Connection on the following line. Aside from the fact that the
720
I Chapter 35: Working With Databases (ActiveX Data Objects) I second example uses an additional line of code, the difference between the two methods is negligible. Opening and immediately Closing a Connection does not help us at all. Let's do something with the Connection before we close it now. Sub T e s t C o n n e c t i o n C ( ) D i m myDB As A D O D B . C o n n e c t i o n
S e t myDB
=
New A D O D B . C o n n e c t i o n
myDB.Open " f i 1 e name=C: \ M i c r o s t a t i o n VBA\Pl a c e p o i n t s . u d l myDB. E x e c u t e " C r e a t e T a b l e P1 a c e N o t e s
&
"
"
~
"(UniqueID Counter, PlaceID Long)" myDB.Close End Sub
The Execute Method allows us to execute SQL statements on the Connection Object. In the above example, we create a new Table named "PlaceNotes" with two fields. Here are a few additional examples of using the Execute Method.
TestConnecti onD adds two more fields to the PlaceNotes table. Sub T e s t C o n n e c t i o n D ( )
D i m myDB As A D O D B . C o n n e c t i o n S e t myDB
=
New A D O D B . C o n n e c t i o n
myDB.Open " f i 1 e name=C: \ M i c r o s t a t i o n VBA\Pl a c e p o i n t s . u d l myDB. E x e c u t e "A1 t e r T a b l e P1 a c e N o t e s
"
&
"
-
"Add NoteBy C h a r ( 5 0 ) . N o t e D a t e D a t e " myDB.Close End Sub
T e s t C o n n e c t i on E adds a record to the PlaceNotes table. Sub T e s t C o n n e c t i o n E ( )
D i m myDB As A D O D B . C o n n e c t i o n S e t myDB
=
New A D O D B . C o n n e c t i o n
myDB.Open " f i 1 e name=C: \ M i c r o s t a t i o n VBA\Pl a c e p o i n t s . u d l myDB.Execute " I n s e r t I n t o P l a c e N o t e s
"
&
-
" ( P l a c e I D , N o t e B y , N o t e D a t e ) VALUES
"(1, 'JKW',
"'
"
&
-
& Now & " ' 1 "
myDB.Close End Sub
TestConnecti onF creates an additional field in PlaceNotes.
"
I Connections, Recordsets, and More I Sub
721
TestConnectionF( 1 D i m myDB As ADODB.Connection
S e t myDB
=
New ADODB.Connection
myDB.Open " f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\PlacePoints.udl"
myDB. E x e c u t e "A1 t e r T a b l e P1 a c e N o t e s Add T h e N o t e Memo" myDB. C1 o s e
End Sub
T e s t C o n n e c t i onG updates all records in PlaceNotes where the NoteBy field is 'JKW' by setting the "TheNote" field value to 'Reviewed'. Sub
TestConnectionG( 1 D i m myDB As ADODB.Connection
S e t myDB
=
New ADODB.Connection
myDB.Open " f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\PlacePoints.udl"
myDB.Execute "Update PlaceNotes S e t TheNote "
Where N o t e B y
=
=
'Reviewed'
"
&
'JKW"'
myDB. C1 o s e
End Sub
This chapter is not meant to be a comprehensive tutorial on SQL (Structured Query Language). It is good, however, to introduce some of the functionality available to us by using standard SQL statements in an Execute statement with the Connection Object. Opening a Connection is time-consuming. Of course, it doesn't take weeks, days, hours, or minutes. But it can take a second or two. If we open and close a Connection to a database every time we want to work with it, we will experience a performance hit. In some instances, we may want to open a Connection once and keep it open until the application is terminated. We can look at the Connection's State property to determine whether it is open or closed. Sub
TestConnectionH( 1 D i m myDB As ADODB.Connection
S e t myDB
=
New ADODB.Connection
myDB.Open " f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\PlacePoints.udl"
S e l e c t Case m y D B . S t a t e Case a d s t a t e c l o s e d MsgBox " C o n n e c t i o n i s C1 o s e d .
"
Case a d s t a t e c o n n e c t i n g MsgBox " C o n n e c t i o n i s C o n n e c t i n g . " Case a d S t a t e E x e c u t i n g
722
I Chapter 35: Working With Databases (ActiveX Data Objects) I MsgBox "Connection is Executing." Case adStateFetching MsgBox "Connection is Fetching." Case adstateopen MsgBox "Connection is Open. End Select myDB.Close End Sub "
TestConnectionH opens a Connection and then looks at each of the possible States by using a Sel e c t Case statement. In TestConnectionJ, we are looking at a real-world example of how we would use the State property. We first look at the variable myDB (which should have been declared in the General Declarations area of the Code Module or User Form) to see if it is closed. If it is closed, we open it by using a UDL file. Sub T e s t C o n n e c t i o n J ( ) If myDB.State = adstateclosed Then myDB.Open "file name=C:\MicroStation VBA\PlacePoints.udl" End If MsgBox "Use the Connection Object Here" End Sub
It should be noted here that in TestConnectionJ we are not declaring the variable myDB or setting it to a New AD0DB.Connection. We are simply checking to see if it is closed. If so, we open it. In order for this procedure to work correctly, the variable myDB must be declared in such a way that it is available to this procedure (Public in a Code Module or in the General Declarations area of the module in which this procedure is located) and instantiated (set to as a New ADODB.Connection). For example, 'General Declarations Dim myDB as New ADDDB.Connection
Now that we can 'connect' to a database by using a UDL file, let's take a look at the Connection's ConnectionString property. Sub T e s t C o n n e c t i o n K ( ) Dim myDB As ADDDB.Connection Set myDB = New ADDDB.Connection
I Connections, Recordsets, and More I
723
myDB.0pen "file name=C:\MicroStation VBA\Pl acePoints. udl Msg Box Rep1 ace (my DB .Connect i on S t r i n g , ; , vbC r 1 myDB. C1 ose End Sub "
"
"
The Connection String is rather lengthy and is delimited with semicolon characters. In the above example, we replace the semicolon (;) with a Carriage Return so we can more clearly see the Connectionstring.
When we use a UDL file, the Connection String reflects the settings of the UDL file. Although we have been depending on the UDL file, it is possible to open a database and work with it without the use of a UDL file by providing the Connectionstring when we Open the Connection. Sub TestConnectionL( 1 Dim myDB As ADODB.Connection Dim ConnectionStringVals(0 T o 2) As String Set myDB = New ADODB.Connection ConnectionStringVals(0) = "Provider=Microsoft.Jet.OLEDB.4.0" ConnectionStringVals(1) = "User ID=Admin" ConnectionStringVals(2) = "Data Source=" & "C:\Microstation VBA\PlacePoints.mdb" myDB.0pen J o i n ( C o n n e c t i o n S t r i n g V a 1 s , " ; " ) MsgBox myDB.State myDB. C1 ose End Sub ~
In TestConnectionL we are opening the same database as we were by using the UDL file f/ucefoints.ud/ but we do so by opening the Connection with a Connectionstring instead of using the UDL file.
724
I Chapter 35: Working With Databases (ActiveX Data Objects) I Recordsets The Connection Object is used to 'connect' with the database. The Recordset is used to 'connect' with the Records in the database. Sub T e s t R e c o r d s e t A ( 1 D i m myDB As A D O D B . C o n n e c t i o n D i m myRS As New R e c o r d s e t
S e t myDB
=
New A D O D B . C o n n e c t i o n
myDB.Open " f i l e n a m e = C : \ M i c r o S t a t i o n myRS.Open " S e l e c t
*
VBA\PlacePoints.udl"
f r o m P o i n t s Where C o u n t y
=
'Ventura"',
-
myDB, a d O p e n D y n a m i c , a d L o c k O p t i m i s t i c
W h i l e myRS.EOF
=
False
Debug . P r in t my RS ( " Des c r ip t io n " 1 myRS . M o v e N e x t
Wend myRS.Close myDB.Close End Sub
Windmill Canyon Womens Improvement Club of Hueneme Wood Canyon Wood Creek Park Wood Ranch 1027 D a m Wood Ranch Golf Club Wood Ranch Reservoir Woodside Linear Park Woodside Park World University Xahaagua (historical) Xocotoc (historical) Yerba Buena Beach Yerba Buena School Ypuc (historical )
In our first example, we use the Recordset Object to return all fields in all records where the field 'County' has a value of 'Venturd Even though we are getting all fields (by using the asterisk (*) in the SQL Select statement), we only display the Description of each record in the Immediate Window. We will cover SQL statements later in this chapter. For now, we are going to keep our attention on the Recordset Object. In the procedure T e s t R e c o r d s e t A we can see that we use a Whi 1 e ... Wend statement and we look at the EOF (End of File) property. As long as the
I Connections, Recordsets, and More I
725
EOF property is False, we continue to print the Description of the current record and then move to the next record. MoveNext is the method that moves the Recordset to the next record. The Open Method is used to specify which data we want returned, which Connection Object to get it from, what type of cursor we want to use, and what type of record locking we want as we retrieve the data. Which data do we want? We specify which data we want by using a SQL statement. The Connection Object in the Open method of the Recordset Object points to an active Connection. When we get to the Cursor Type and Locking Method, we should understand what each does and when we may want to use them. The descriptions shown here are taken directly from Microsoft's website. Cursor Type Constants: adOpenDynamic = 2: Uses a dynamic cursor. Additions, changes, and deletions by other users are visible, and all types of movement through the Recordset are allowed, except for bookmarks, if the provider doesn't support them. adOpenForwardOnly = 0: Default. Uses a forward-only cursor. Identical to a static cursor, except that you can only scroll forward through records. This improves performance when you need to make only one pass through a Recordset. adOpenKeyset = 1: Uses a keyset cursor. Like a dynamic cursor, except that you can't see records that other users add, although records that other users delete are inaccessible from your Recordset. Data changes by other users are still visible. adopenstatic = 3: Uses a static cursor, which is a static copy of a set of records that you can use to find data or generate reports. Additions, changes, or deletions by other users are not visible. Lock Type Constants: El adLockBatchOptimistic = 4:Indicates optimistic batch updates. Required for batch update mode.
adLockOptimistic = 3: Indicates optimistic locking, record by record. The provider uses optimistic locking, locking records only when you call the Update method.
726
I Chapter 35: Working With Databases (ActiveX Data Objects) I adLockPessimistic= 2: Indicates pessimistic locking, record by record. The provider does what is necessary to ensure successful editing of the records, usually by locking records at the data source immediately after editing. adLockReadOnly = 1: Indicates read-only records. You cannot alter the data. The Cursor Type has bearing on a couple of Recordset Properties. Sub T e s t R e c o r d s e t B ( 1
D i m myDB As A D D D B . C o n n e c t i o n D i m myRS As New R e c o r d s e t S e t myDB
=
New A D O D B . C o n n e c t i o n
myDB.Open " f i l e n a m e = C : \ M i c r o S t a t i o n myRS.Open " S e l e c t
*
VBA\PlacePoints.udl"
f r o m P o i n t s Where C o u n t y
=
'Ventura"',
-
myDB, a d O p e n F o r w a r d O n l y , a d L o c k R e a d O n l y
MsgBox m y R S . R e c o r d C o u n t my RS . C1 o s e
myDB. C1 o s e
End Sub
The Recordcount can be a very useful property. When used with 'adOpenDynamic' and 'adOpenForwardOnly', it always returns a value of -1, however. When we use 'adOpenKeyset' and 'adopenstatic', the Recordset Property gives us the number of records retrieved in the Recordset. For example, when we run T e s t R e c o r d s e t B , we see a Recordcount of -1 no matter how many records we have retrieved due to the Cursor Type specified in the Open statement of the Recordset. Sub TestRecordsetC( 1
D i m myDB As A D D D B . C o n n e c t i o n D i m myRS As New R e c o r d s e t S e t myDB
=
New A D D D B . C o n n e c t i o n
myDB.Open " f i l e n a m e = C : \ M i c r o S t a t i o n myRS.Open " S e l e c t
*
VBA\PlacePoints.udl"
f r o m P o i n t s Where C o u n t y myDB, a d o p e n s t a t i c ,
MsgBox m y R S . R e c o r d C o u n t myRS.Close myDB.Close End Sub
T e s t R e c o r d s e t C displays a Recordcount of 2253.
=
'Ventura"',
adLockReadOnly
-
I Connections, Recordsets, and More I
727
Sub T e s t R e c o r d s e t D ( )
D i m myDB As ADODB.Connection D i m myRS As New R e c o r d s e t S e t myDB
=
New ADODB.Connection
myDB.0pen " f i l e n a m e = C : \ M i c r o S t a t i o n
*
myRS.Open " S e l e c t
myDB,
W h i l e myRS.EOF
=
VBA\PlacePoints.udl"
f r o m P o i n t s Where C o u n t y
=
'Ventura'",
-
adOpenDynamic, a d L o c k O p t i m i s t i c
False
Debug . P r in t my RS ( " De s c r ip t ion " ) myRS. M o v e N e x t
Wend myRS .Move F i rs t
W h i l e myRS.EOF
=
False
D e b u g . P r i n t myRS("Cel1Name") myRS. M o v e N e x t
Wend my RS . C1 o s e myDB. C1 o s e
End Sub
T e s t R e c o r d s e t D usesthe M o v e F i r s t methodofthe Recordset Object. This
allows us to begin at the top of the Recordset and look through the records again, possibly looking for different information.
The Find Method Now we are going to move on to the F i n d method of the Recordset Object. It moves the cursor of the current Recordset to the first record below the current record that matches the criteria. It allows us to search inside of the Recordset that has already been populated using a Select Statement. Since it is possible that the Recordset's Cursor is somewhere in the middle of the Recordset, it is a good idea to use the MoveFi r s t method of the Recordset so we always begin 'finding' from the top of the Recordset. To make this work well, we are also Ordering the Recordset by the CellName Field. This places all records with the same CellName together in the Recordset. Sub T e s t R e c o r d s e t E ( ) D i m myDB As ADODB.Connection D i m myRS As New R e c o r d s e t
S e t myDB
=
New ADODB.Connection
728
I Chapter 35: Working With Databases (ActiveX Data Objects) I myDB.Open " f i 1 e name=C: \ M i c r o s t a t i o n V B A \ P l a c e p o i n t s . u d l
*
myRS.Open " S e l e c t
f r o m P o i n t s Where C o u n t y
=
"
'Ventura'
"
&
-
"Order by CellName", myDB, adOpenDynamic, a d L o c k O p t i m i s t i c myRS. F i n d " C e l l Name
=
W h i l e myRS("Cel1Name") Debug . P r in t
"
' L i o n Canyon ' " =
" L i o n Canyon" "
L i on Canyon :
& my RS ( " De s c r ip t ion " )
myRS . M o v e N e x t
Wend myRS.MoveFirst myRS.Find "CellName
=
W h i l e myRS("Cel1Name") Debug. P r in t
"
'Oxnard"' =
Oxna r d :
"Oxnard" "
& my RS ( " Desc r ip t ion " )
myRS . M o v e N e x t
Wend myRS.MoveFirst
' F i 1 1m o r e ' " W h i1 e my RS ( " C e l l Name" = " F i 1 1 m o r e "
myRS. F i n d " C e l l Name
Debug . P r in t
"
=
F i 1 1m o r e :
"
& my RS ( " Des c r ip t ion " )
myRS . M o v e N e x t
Wend myRS.Close myDB.Close End Sub
In this example, we are only looking for records where the County = 'Venturd We use "Order by CellName" so the Recordset is 'sorted' by the CellName field. Then we use the F i n d method to find the first record where the CellName is 'Lion Canyon'. After looking at each 'Lion Canyon' Cell, we move on to looking for the first 'Oxnard' cell. And then we do the same with 'Fillmore'.
The GetString Method Our next example demonstrates the use of the Get S t ri n g method of the Recordset Object. Sub T e s t R e c o r d s e t F ( 1 D i m myDB As A D O D B . C o n n e c t i o n
D i m myRS As New R e c o r d s e t S e t myDB
=
New A D O D B . C o n n e c t i o n
myDB.Open " f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\PlacePoints.udl"
myRS.Open " S e l e c t D e s c r i p t i o n , C e l l N a m e f r o m P o i n t s
"
&
-
I Connections, Recordsets, and More I "Where S t a t e
=
729
'CA' and P o i n t T y p e
=
'school
"I,
-
myDB, a d O p e n D y n a m i c , a d L o c k O p t i m i s t i c
Debug. P r in t my RS . G e t S t r in g ( adC 1 ip S t r in g ,
-
1,
"
I
"
, vbC r
my RS . C1 o s e
myDB. C1 o s e
End Sub
G e t s t r i n g gets all of the fields of all of the records retrieved in a
Recordset and places them into one large String. We specify the delimiters that should appear between Fields as well as the delimiter that is to be used between Rows (records). In this example, we have chosen to retrieve all records by using - 1 as the value for the number of records to retrieve. If we used a number such as 5, only the top five records would be returned by Get S t r ing. As for the delimiters, we are separating each field with the Pipe symbol (I) and each row with a Carriage Return. Yiion Elementary SchoollTorrance Yiion Intermediate SchoollInglewood Yulupa Elementary SchoollSanta Rosa Yvonne Harmon Development Center for the HandicappedlSan Bernardino North Zamora Elementary SchoollWoodland Zamorano Elementary SchoollNational City Zela Davis Elementary SchoollInglewood Zion Lutheran SchoollOakland East Zion Lutheran SchoollSan Francisco North Zion Lutheran SchoollFountain Springs Zion SchoollAnaheim Zoeter SchoollSeal Beach Zupanic High SchoollSan Bernardino South
AddNew and Update The AddNew and Update methods of the Recordset Object are used to create new records and update the values given to fields. Here are two short examples. We will see additional uses of AddNew and Update as we continue in this chapter. Sub T e s t R e c o r d s e t H ( )
D i m myDB As A D O D B . C o n n e c t i o n D i m myRS As New R e c o r d s e t S e t myDB
=
New A D O D B . C o n n e c t i o n
myDB.0pen " f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\PlacePoints.udl"
myRS.Open "P1 a c e N o t e s " , myDB, a d O p e n D y n a m i c , a d L o c k O p t i m i s t i c myRS.AddNew myRS("P1aceID")
=
4
730
I Chapter 35: Working With Databases (ActiveX Data Objects) I my RS ( Note By 1 = J KW myRS("NoteDate"1 = Now myRS("TheNote"1 = "New Note" myRS. Update myRS.Close myDB.Close End Sub "
"
"
"
Sub T e s t R e c o r d s e t J ( 1 Dim myDB As ADODB.Connection Dim myRS As New Recordset Set myDB = New ADODB.Connection myDB.Open "file name=C:\MicroStation VBA\PlacePoints.udl" myRS.Open "Select * from PlaceNotes Where PlaceID = l " , myDB, adOpenDynamic, adLockOptimistic While myRS.EOF = False myRS("PlaceID"1 = 14 myRS. Update myRS .MoveNext Wend myRS.Close myDB.Close End Sub
In T e s t R e c o r d s e t H , we add a new record to the table. In T e s t R e c o r d s e t J , we query the database and change the PlaceID value in each record retrieved by the S Q L statement. In both examples, we use the Update method to apply the field values to the database.
SQL ESSENTIALS Now that we have discussed attaching to databases by using the Connection Object and the data inside the database by using the Recordset Object, let's begin looking into the S Q L statements that can be
I SQL Essentials I
731
used with the Connection and Recordset Objects. We will do this by creating several UserForms.
The first Form we create is shown above. When the Form loads, we need to query the database for all distinct State values. These values will be added to the State ComboBox.
Select Statement The Select statement is the basis for many of SQL statements we will use. It allows us to specify which fields we want to retrieve, which tables the fields come from, how to order the records, how to group the fields, etc. To get the distinct States in the Points table, we use: Select D i s t i n c t State from Points
The Recordset will be populated with a record for each distinct value found in the State field. In our example here, we will place each State field's value in the ComboBox named cmbState. P r i v a t e Sub U s e r F o r m - I n i t i a l i z e 0 D i m myDB As New ADODB.Connection D i m myRS As New ADODB.Recordset
myDB.Open " f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\Pl a c e P o i n t s . u d l
myRS.Open " S e l e c t D i s t i n c t S t a t e f r o m P o i n t s " , myDB W h i l e myRS.EOF
=
False
cmbState.AddItem myRS("State") myRS. M o v e N e x t
Wend my RS . C1 o s e
"
732
I Chapter 35: Working With Databases (ActiveX Data Objects) I myDB.Close End Sub
When the Form is shown, each unique State is added to the combo box. The data for this example is being taken from a database created from information on the USGS website: http://geonumes.usgs.gov/stuteguz/ index.html. Although all 50 United States were available, only two were used. One, California, is a fairly large dataset, and the other, Utah, is a much smaller dataset. So, in our example here, only two states will be displayed: CA and UT. When the user selects a State from the cmbState ComboBox, we want to populate the cmbCounty ComboBox with all of the Counties in the selected State. But before adding anything to the cmbCounty ComboBox, we use the Clear method on it as well as on the cmbPointTrpe and 1stDescription controls. If we didn’t Clear the ComboBoxes, County names would continue to be added to cmbCounty each time a State was selected so that the cmbCounty ComboBox would no longer display only the Counties from the selected State.
Where When we use the ‘Where’ statement, we begin providing the criteria specifying which records we want to retrieve. In this example, we want only records Where the State field is equal to the selected State in the cmbStateComboBox. Since the State field is a String (Text), we use the Apostrophe (’) to begin and end the value.
Order By The Order By statement allows us to specify how we want to sort the Recordset’s records. Multiple fields can be specified. We use ‘XSC” for Ascending and “DESC” for a Descending sort. P r i v a t e Sub c m b S t a t e - C l i c k ( 1 D i m myDB A s New A D O D B . C o n n e c t i o n D i m myRS A s New A D O D B . R e c o r d s e t
cmbCounty.Clear cmbPoi n t T y p e . C1 e a r 1 s t D e s c r i D t i o n . C1 e a r
I SQL Essentials I
733
myDB.0pen " f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\PlacePoints.udl"
rnyRS.Open " S e l e c t D i s t i n c t County f r o m P o i n t s Where S t a t e = & & cmbState.Text & " ' Order by County A S C " , myDB W h i l e myRS.EOF = F a l s e "
9,
9
~
9,
cm b C o u n t y .Add It em my R S ( " C o u n t y " ) myRS. M o v e N e x t Wend my RS . C1 o s e
myDB. C1 o s e
End Sub
OK, now when the user clicks a State, the Counties in the database show up in the frmCounty ComboBox. When the user clicks on a County, what should happen? Let's populate the cmbPointType ComboBox with only those Point Types that appear in records with the selected State and the selected County. P r i v a t e Sub c m b c o u n t y - C l i c k 0
D i m myDB As New A D O D B . C o n n e c t i o n D i m myRS As New A D O D B . R e c o r d s e t
cmbPointType.Clear 1s t D e s c r i p t i o n . C l e a r myDB. Open " f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\Pl a c e P o i n t s . u d l
myRS.Open " S e l e c t D i s t i n c t PointType from P o i n t s Where S t a t e ""'
& cmbState.Text & " ' and
"County
=
"'
"
&
=
"
&
-
"
& -
-
& cmbCounty.Text & " '
"
" O r d e r by P o i n t T y p e ASC", myDB W h i l e myRS.EOF
=
False
cmbPointType.AddItem myRS("PointType") myRS. M o v e N e x t Wend my RS . C1 o s e myDB. C1 o s e
End Sub
When the user clicks on the PointTrpe ComboBox, we see all the Descriptions that match all of the selected criteria in the ComboBoxes. We place the UniqueID in the second column of the Listbox (but hide the column so it is not visible to the end user).
734
I Chapter 35: Working With Databases (ActiveX Data Objects) I P r i v a t e Sub c m b P o i n t T y p e c C l i c k 0 D i m myDB As New ADODB.Connection
D i m myRS As New ADODB.Recordset 1 s t D e s c r i p t i o n . C1 e a r myDB.Open " f i 1 e name=C: \ M i c r o s t a t i o n V B A \ P l a c e p o i n t s . u d l myRS.Open " S e l e c t D e s c r i p t i o n , U n i q u e I D f r o m P o i n t s Where S t a t e ""I
& c m b S t a t e . T e x t & " ' and
"County
=
"'
"and PointType
& cmbCounty.Text =
"'
=
&
"
&
" I
"
&
& cmbPointType.Text & " '
"Order by D e s c r i p t i o n ASC", W h i l e myRS.EOF
&
"
"
=
~
"
& -
myDB
False
1 s t D e s c r i p t i o n . A d d I t e m myRS( " D e s c r i p t i o n " ) 1s t D e s c r i p t i o n . L i s t ( 1 s t D e s c r i p t i o n . L i s t c o u n t my RS ( " Un iq u e ID "
-
1, 1)
=
myRS . M o v e N e x t
Wend myRS.Close myDB.Close End Sub
All of the above code forms the framework for allowing the user to select Places from the database. At this point, the Form looks like this:
CA
Selecting a State of "CA", a County of "Los Angeles", and a Point Type of "tower" displays all Points meeting this criteria. Let's move onto the buttons now. We will begin with the Report button. When this button is clicked and a 'Place' is selected, we create an ASCII Text file with all of the selected place's field values from the Database.
I SQL Essentials I
735
First we will look at the resulting file, then we will look at the code.
Dni q u e m 71090 USGS-ID 1 6 5 4 6 6 1 state CA De5cription Hau5er Mi CrOWaVe S t a t i o n Poi n t T y p e tower c o u n t y Lo5 Angele5 ;37 TyPeA
We have already seen how we can get specific field values by addressing them by name. We could do this to create the report. However, the goal is to export all field names and values. To accomplish this, we use the Fields Collection of the Recordset Object.
3432491 LOnDMS 1 1 8 1 2 5 6 1 L a t u e c 34.54694 L o m e c -118.21556 Ref-LatDMS Ref-LonDMS Ref-LatDec 0 Ref-LonDec 0 Elevation 0 Population 0 Federalstatus C e l l Name R it t e r Ridge %S
P r i v a t e Sub c m d R e p o r t - C l i c k 0 If 1stDescription.Text
=
" "
Then E x i t Sub
D i m myDB As New ADODB.Connection D i m myRS As New ADODB.Recordset
D i m m y F i e l d As F i e l d D i m F F i l e As Long myDB.Open " f i l e n a m e = C : \ M i c r o S t a t i o n myRS.Open " S e l e c t
*
VBA\Pl a c e P o i n t s . u d l
f r o m P o i n t s Where U n i q u e I D
=
"
&
"
-
lstDescription.List(lstDescription.List1ndex. 1). I f myRS.EOF FFile
=
=
myDB
F a l s e Then
FreeFile
Open " C : \ P l a c e R e p o r t . t x t " F o r O u t p u t As # F F i 1 e F o r Each m y F i e l d I n m y R S . F i e l d s P r i n t #FFile,
myField.Name & vbTab & m y F i e l d . V a l u e
Next C1 o s e B F F i 1 e End I f my RS . C1 o s e
myDB. C1 o s e
End Sub
When we add the Description of each 'place' matching the selected criteria to the Listbox, we also add the UniqueID to the second (hidden) column in the Listbox. We use this value in our query. If we find a record that matches (and we should always find a matching record), we open an ASCII Text file for Output and write each Field Name and Value to the file, and then close the file.
736
I Chapter 35: Working With Databases (ActiveX Data Objects) I Also worthy of note is the query "Select *" in our Select statement. When we use the Asterisk character, we are stating that we want to retrieve all fields in the table. The Report button seems to work fairly well. Now it's time to turn our attention to the Add Note button. When the Add Note button is clicked, we need to display a new UserForm that allows the user to enter the Note information. The Add Note button tells the Add Note Form which record is selected by setting the Tag property of the Add Note Form and then shows the Form. P r i v a t e Sub c m d A d d N o t e - C l i c k 0 I f 1stDescription.Text
=
" "
Then E x i t Sub
f r m A d d N o t e . T a g = 1 s t D e s c r i p t i o n . L i s t ( 1 s t D e s c r i p t i o n ._ L i s t I n d e x , 1) frmAddNote.Show End Sub
Here is the Form frmAddNote
P
When the user clicks the OK button, the following code is executed: P r i v a t e Sub btnOK-C1 i c k ( 1 I f frmAddNote.Tag
=
" "
Then
MsgBox " A d d N o t e n o t e x e c u t e d c o r r e c t l y . " U n l o a d Me End I f I f txtNoteBy.Text
=
"I'
Then
MsgBox " P l e a s e e n t e r N o t e By." E x i t Sub End I f I f txtNote.Text
=
"I'
Then
MsgBox " P l e a s e e n t e r N o t e . " E x i t Sub
I SQL Essentials I
737
End I f
D i m myDB As New ADODB.Connection D i m myRS As New ADODB.Recordset D i m m y F i e l d As F i e l d D i m F F i l e As Long
myDB.0pen " f i l e n a m e = C : \ M i c r o S t a t i o n myRS.Open " S e l e c t myDB,
*
VBA\Pl a c e P o i n t s . u d l
f r o m P l a c e N o t e s Where U n i q u e I D
=
"
0". -
adOpenDynami c , a d L o c k O p t i m i s t i c
myRS.AddNew myRS("NoteBy")
=
myRS("TheNote") my RS ( " P 1 a c e I D " )
txtNoteBy.Text
= =
myRS("NoteDate")
txtNote.Text f rmAdd N o t e .T a g
=
Now
my RS . U p d a t e
MsgBox " N o t e a d d e d .
"
U n l o a d Me End S u b
When we want to add a new record to a table, we have a few options. We can use an "Insert Into" SQL statement with the Connection.Execute method. In this example, however, we open the PlaceNotes table and use the AddNew method of the Recordset Object. This gives us a little more flexibility than we have by using an "Insert Into" statement. When we open the Table, we use a Select statement and look for a UniqueID of 0. Why would we do this? If we want to open the entire Table, we would use something like this: myRS. Open "P1 a c e N o t e s " ,
myDB,
adOpenDynami c , a d L o c k O p t i m i s t i c
but we would take a huge performance hit because we are opening every record in the Table. By intentionally opening a Recordset without any records in it, the Recordset is opened almost immediately because it does not need to retrieve any data. The last button we are going to discuss is the Draw In Microstation button. The Database we are using has Latitude and Longitude values in it, which give us Y and X values of the 'places' in the database. We will use these values to place a Circle and Text Element at the location of the selected 'Places' from the database. First we will look at the code behind the button and then we will look at the results.
738
I Chapter 35: Working With Databases (ActiveX Data Objects) I P r i v a t e Sub btnDraw-C1 i c k ( If
1stDescription.ListCount
=
0 Then E x i t Sub
D i m myDB As New A D D D B . C o n n e c t i o n D i m myRS As New ADDDB.Recordset D i m m y p o i n t As P o i n t 3 d D i m m y c i r c l e As A r c E l e m e n t D i m m y T e x t As T e x t E l e m e n t
D i m R o t M a t r i x As M a t r i x 3 d D i m I As Long myDB.0pen " f i 1 e name=C: \ M i c r o S t a t i o n V B A \ P l a c e p o i n t s . u d l
1stDescription.ListCount I f lstDescription.Selected(1 - 1)
"
F o r I = 1 To
*
myRS.0pen " S e l e c t
=
T r u e Then
f r o m P o i n t s Where U n i q u e I D
lstDescription.List(1
-
=
"
&
-
1, l), myDB
I f myRS.EDF = F a l s e Then my P o i n t . X = my RS ( " Lon Dec " my P o i n t . Y
=
my RS ( " La t Dec "
Set m y c i r c l e
=
CreateArcElementZ(Nothing,
mypoint,
-
0.0025, 0.0025, RotMatrix, 0, 360) ActiveModel Reference.AddElement m y c i r c l e S e t myText
=
CreateTextElementl(Nothing,
~
myRS("Description"), mypoint, RotMatrix) myText.TextStyle.Height myText.TextStyle.Width
= =
0.01 0.01
ActiveModelReference.AddElement myText End I f my RS . C1 o s e
End I f Next I myDB. C1 o s e
End Sub
We use the LonDec and LatDec fields for the X and Y elements of each Circle Center Point and Text Origin. We display the Description field's value as a TextElement in Microstation. In the previous buttons we used, our work was based on the ListIndex property of the Listbox. Since we are drawing in Microstation, we want to allow the software to place multiple points with only one button click. This is why we are looking at the Selected Property of each item in the
I SQL Essentials I
739
ListBox. If an item is Selected, we query the database using the UniqueID hidden in Column 2 of the ListBox. Let's slow down a little here. We are moving through a lot of code. After the code shown above becomes clear (or a little less cloudy), we are going to add one enhancement. We will place the Circle and Text on a Level with the same name as the Point Type. P r i v a t e Sub b t n D r a w - C l i c k 0 If 1stDescription.ListCount
=
0 Then E x i t Sub
D i m myDB As New ADODB.Connection D i m myRS As New ADODB.Recordset D i m m y p o i n t As P o i n t 3 d D i m m y c i r c l e As A r c E l e m e n t D i m m y T e x t As T e x t E l e m e n t
D i m R o t M a t r i x As M a t r i x 3 d D i m I As Long myDB. Open " f i l e n a m e = C : \ M i c r o S t a t i o n
VBA\Pl a c e P o i n t s . u d l
"
F o r I = 1 To 1 s t D e s c r i p t i o n . L i s t C o u n t If
lstDescription.Selected(1
*
myRS.Open " S e l e c t
-
1)
=
T r u e Then
f r o m P o i n t s Where U n i q u e I D
lstDescription.List(1
-
=
"
&
-
1, 11, myDB
I f m y R S . E O F = F a l s e Then my P o i n t . X = my RS ( " Lon Dec " ) my P o in t . Y
=
my R S ( " La t De c " )
Set m y c i r c l e
=
myCircle.Leve1
CreateArcElementZ(Nothing, m y p o i n t , 0.0025, 0.0025, R o t M a t r i x , 0, 360) =
CheckLevel ( c m b P o i n t T y p e . T e x t )
A c t i v e M o d e l R e f e r e n c e . A d d E l e m e n t myCi r c l e S e t myText
=
CreateTextElementl(Nothing,
~
my R S ( " De s c r ip t ion " ) , my P o in t , R o t M a t r ix 1
myText.TextStyle.Height myText.TextStyle.Width
= =
0.01
0.01
= CheckLevel(cmbPointType.Text1 ActiveModelReference.AddElement m y T e x t
myText.Leve1 End I f myRS.Close End I f Next I myDB.Close End Sub
740
I Chapter 35: Working With Databases (ActiveX Data Objects) I F u n c t i o n C h e c k L e v e l ( L e v e 1 N a m e As S t r i n g ) As L e v e l
On E r r o r Resume N e x t S e t C h e c k L e v e l = ActiveDesignFile.AddNewLeve1 ( L e v e l Name) I f E r r . N u m b e r <> 0 Then S e t C h e c k L e v e l = ActiveDesignFile.Levels(Leve1Name) End I f End F u n c t i o n
We only add two lines of code to the Click Event of btnDrawand we add a Function named C h e c k l e v e l . Now, all Places selected are added to the ActiveModelReference on a specific Level. The Level matches the selected “Point Trpe”. As our program stands right now, we have some very good functionality in place. We can get a report based on the selected item in the listbox. We can add a note to the selected item in the listbox and we can draw items selected in the ListBox inside the ActiveModelReference in Microstation.
EXTENDINGACTIVEXDATAOBJECTS As the name implies, ActiveX Data Objects is about more than just Databases, it is all about Data. Data appears in a variety of forms. Databases hold data that can change from time to time, but it is not likely that a database will have entirely different data every 24 hours. One of the things that makes the Internet so powerful is that it is so dynamic. It is changing every second of the day. And although the Internet could be considered one large database, it is probably better typified as a whole lot of Data rather than a large Database. When we look at the Provider tab in a UDL file, we will see a reference to the “Microsoft OLE DB Simple Provider’: What can this do for us? Can we use it to tap into the Data exposed on the Internet? Well, it can be used for some Data on the Internet. Let’s take a look at the “Microsoft OLE DB Simple Provider’: This Provider is used for creating in-memory databases and can also be used for working with XML files. We have dealt with XML files in a previous chapter. Let’s take a look at another way to work with them now. Many web sites are making use of RSS technology. When a button is shown with the initials “RSS” or “XML”,the button links to the site’s RSS
I Extending ActiveX Data Objects I
741
feed. This feed normally contains headlines with links to full articles. In addition to major news networks and other high traffic web sites using this technology, it is likely that smaller web sites and corporate intranets will make use of this technology as well (if not now, in the very near future). For our next example, we will create a new UDL file named RSS.ud/. The Provider is "Microsoft OLE DB Simple Provider': The Data Source for this UDL file will be "MSXML2.DSOControl.2.6': We will use this UDL file and specify the URL of the RSS feed in the Recordset.Open method. RSS files follow a specific document structure. The three primary levels are: Channel Item Item Child
We will use a Recordset for the Channel and the Item and will print the Title, Link, and Description of each Item in the Immediate Window to get things started. Sub ReadRSSA() D i m MyDB As New ADODB.Connection
D i m MyRS As New ADODB.Recordset D i m C h a n n e l RS As New ADODB.Recordset D i m I t e m R S As New ADODB.Recordset MyDB.Open " F i l e n a m e = c : \ M i c r o S t a t i o n MyRS.Open
VBA\rss.udl"
"http://www.wired.com/rss/index.xml",
W h i l e MyRS.EOF
=
S e t ChannelRS
MyDB
False =
MyRS("channel").Value
W h i l e ChannelRS.EOF S e t ItemRS
=
=
False
ChannelRS("item").Value
W h i l e 1temRS.EOF
=
False
Debug . P r in t It em RS ( " t i t 1 e " ) Debug. P r in t v bTa b & ItemRS ( " 1 in k " ) Debug . P r in t v bTa b & ItemRS ( " d e s c r ip t io n " 1 1temRS.MoveNext Wend
I Chapter 35: Working With Databases (ActiveX Data Objects) I
742
Channel RS.MoveNext Wend MyRS .MoveNext
Wend End S u b
When ReadRSSA is executed, the RSS of the wired.com website displays in the Immediate Window. Since RSS files are usually updated fairly frequently, the results shown in the Immediate Window will be different from day to day and may even be different from hour to hour.
L
The technology is great. It only takes 22 lines of code to retrieve the information. So, now that we know the technology is in place and how to access the data, how can we use what we know in a VBA project?
I Extending ActiveX Data Objects I
743
Let’s start a new VBA project. We will name it Chapter 35c.mvba. A reference to “Microsoft ActiveX Data Objects 2.X Library” needs to be added. Then we will insert a User Form.
Here is the Form:
The only control on the Form is a ListBox. The ListBox’s name is IstRSS. It has two columns with widths of 250 points and 0 points respectively. In the first column we will place the Title of each RSS feed item. In the second column (which we hide by giving it a width of 0 points) we will place the URL associated with the
.
744
I Chapter 35: Working With Databases (ActiveX Data Objects) I Title in Column 1. When the user double-clicks on an item in the list, a new web browser opens and displays the story.
When the selected item is double-clicked we use the Shel 1 Execute API command to open the default browser on the system and the selected story appears. Here is the code: P r i v a t e D e c l a r e F u n c t i o n ShellExecute L i b " s h e 1 1 3 2 . d l l " A1 i a s " S h e l 1 E x e c u t e A " ( B y V a l hwnd As L o n g , B y V a l l p o p e r a t i o n As S t r i n g , B y V a l l p F i l e As S t r i n g , B y V a l l p p a r a m e t e r s As S t r i n g , ByVal 1 p D i r e c t o r y As S t r i n g , B y V a l nShowCmd As L o n g ) As L o n g P r i v a t e Sub 1 s t R S S - D b l C l i c k ( B y V a 1 ShellExecute 0, "OPEN",
C a n c e l As M S F o r m s . R e t u r n B o o l e a n )
lstRSS.List(lstRSS.ListIndex,
11,
End Sub P r i v a t e Sub U s e r F o r m - I n i t i a l i z e 0
D i m MyDB As New A D O D B . C o n n e c t i o n D i m MyRS As New A D O D B . R e c o r d s e t D i m C h a n n e l R S As New ADODB.Recordset D i m I t e m R S As New A D O D B . R e c o r d s e t
MyDB.Open
-
" F i l e name=c:\MicroStation
VBA\rss.udl"
" ' I .
" ' I .
0
I Examining Database Schema I
745
MyRS.Open "http://rss.news.yahoo.com/rss/tech", MyDB W h i l e MyRS.EOF
=
S e t ChannelRS
False =
MyRS("channel").Value
W h i l e ChannelRS.EOF S e t ItemRS
=
=
False
ChannelRS("item").Value
W h i l e 1temRS.EOF
=
False
1 s t R S S .Add It e m Rep1 a c e ( ItemRS ( " t it 1 e " 1 , v b L f , " " ) 1 s t RS S . L i s t ( 1 s t RSS . L is tC o u n t - 1 , 1 ) = I tern RS ( " 1 in k " ) 1temRS.MoveNext Wend ChannelRS.MoveNext Wend MyRS. M o v e N e x t
Wend End Sub
Likely, RSS feeds will be used more frequently. As they do, this code will become more important and more useful. For example, a company could create their own RSS feed on their Intranet to display assignments for personnel. What project am I working on today? Open my RSS reader and it tells me.
EXAMININGDATABASE SCHEMA At times, we are faced with the task of using data that we cannot open in a native application. For example, we may have a Visual FoxPro .dbf file but may not have Visual FoxPro. The same could be said of many databases. Access, DB2, etc. ActiveX Data Objects gives us the ability to look at the Schema or in other words, Database Structure of a given database. Sub TestSchemaA( 1
D i m myDB As New ADODB.Connection D i m myRS As New ADODB.Recordset myDB.Open " f i l e n a m e = C : \ M i c r o S t a t i o n S e t myRS
=
VBA\PlacePoints.udl"
myDB.OpenSchema(adSchemaCo1umns)
W h i l e myRS.EOF
=
False
D e b u g . P r i n t myRS("TABLE-NAME")
&
"1"
myRS("COLUMNKNAME") &
"1'' "1''
&
myRS("ISKNULLABLE")
&
&
&
-
746
I Chapter 35:Working With Databases (ActiveX Data Objects) I myRS(”DATA-TYPE”)
&
‘‘1’’
&
-
m y R S ( “CHARACTER-MAXIMUM-LENGTH”
)
myRS . M o v e N e x t
Wend E n d Sub
We still use a Connection Object and a Recordset Object. We still open the database. But after it is open, we use the “OpenSchema”method of the Connection Object. When we get the ‘‘adSchemaColumns”,each record returned has a number of different fields. Among them are TABLE-NAME, COLUMN-NAME, IS-NULLABLE, DATA-TYPE, and CHARACTER-MAXIMUM-LENGTH. These fields are important so that we can understand the structure of each Field and Table contained in the specified Database (specified in the UDL file, that is). The values of each of these fields are printed to the Immediate Window. The Immediate Window does not store an unlimited number of lines in it. So, at times it makes perfect sense to write the values to a Text File.
MSy5Acce55Object5IDatalTruell2813992
MSy5Acce550bject5IIDITruel3I MSy5Acce55XMLIIdlFal5el31 ~~y5~cce55XMLILValueITrue112810 MSy5A~~e55XMLIObjectGuidITrue1721 MSy5Acce55XMLIObjectNameITrue~130165
MSy~Mce55XML\Pr0perty\True\l30\65 ~~y5~~~e55XMLIValuelTrue11301255 Pla~eN0te51NOteBYITrUe~l30~50 ~lace~ote51NoteDatelTruel71 ~ l a c e ~ o t I eP1i a c e r o lTPUe I 3
I
PlaceN0te5~TheN0te~True~l30~0
~ l a c e ~ o t I eUni i q u e r o I Fa1 5 1 I 3 I P o i n t s ICellNai7IelTrue1130130 P0int5~C0~ntylTr~ell30l50 Points ~ D e 5 ~ r i p t i o n ~ T r ~ e ~ l 3 O ~ l O O
~oint51~levationlTrue13I Point5~FederalStatu5ITrue~130~30 Point51LatDecITruel5 I P o i n t s I L a t D M S l T r u e I 1 3 0 I8 Point51LonOecITruel5I Point51LonDMSITruell30l8 Points lPol ntType lTrue I130 I20
P0int5~P0~~latl0nlTruel3l ~oint51~ef-LatDecITrueI5l Point5~Ref~LatDMSITrUe~l30~8
~oint51~ef-LonDecITrueI5l Point5~Ref~LOnDMSITrUe~l30~8
Point51StatelTruell3Ol2 Points ITypeAlTruel3 I Point5lTypeBlTrue113013
Point51UniqueID1Fa15e13I Point51USGS-IDITruel3I
The DATA-TYPE field returns a numeric value that corresponds with ADO Constants. adArray = 8192 (A flag value, always combined with another data type constant, that indicates an array of that other data type.)
I Examining Database Schema I
747
adBigInt = 20 (Indicates an eight-byte signed integer (DBTYPE-18) .) adBinary = 128 (Indicates a binary value (DBTYPE-BYTES).) adBoolean = 11 (Indicates a boolean value (DBTYPE-BOOL).) adBSTR = 8 (Indicates a null-terminated character string (Unicode) (DBTYPE-BSTR) .) adchapter = 136 (Indicates a four-byte chapter value that identifies rows in a child rowset (DBTYPE-HCHAPTER).) adChar = 129 (Indicates a string value (DBTYPE-STR).) adcurrency = 6 (Indicates a currency value (DBTYPE-CY). Currency is a fured-point number with four digits to the right of the decimal point. It is stored in an eight-byte signed integer scaled by 10,000.) adDate = 7 (Indicates a date value (DBTYPE-DATE). A date is stored as a double, the whole part of which is the number of days since December 30, 1899, and the fractional part of which is the fraction of a day.) adDBDate = 133 (Indicates a date value (yyyymmdd) (DBTYPE-DBDATE) .) adDBTime = 134 (Indicates a time value (hhmmss) (DBTYPE-DBTIME).) adDBTimeStamp = 135 (Indicates a datehime stamp (yyyymmddhhmmss plus a fraction in billionths) (DBTYPE-DBTIMESTAMP) .) adDecimal = 14 (Indicates an exact numeric value with a fmed precision and scale (DBTYPE-DECIMAL).) adDouble = 5 (Indicates a double-precision floating-point value (DBTYPE-R8) .) adEmpty = 0 (Specifies no value (DBTYPE-EMPTY).) adError = 10 (Indicates a 32-bit error code (DBTYPE-ERROR) .)
748
I Chapter 35: Working With Databases (ActiveX Data Objects) I adFileTime = 64 (Indicates a 64-bit value representing the number of 100-nanosecond intervals since January 1,1601 (DBTYPE-FILETIME) .) adGUID = 72 (Indicates a globally unique identifier (GUID) (DBTYPE-GUID).) adInteger = 3 (Indicates a four-byte signed integer (DBTYPE-I4).)
adLongVarBinary = 205 (Indicates a long binary value.) adLongVarChar = 201 (Indicates a long string value.) adLongVarWChar = 203 (Indicates a long null-terminated Unicode string value.) adNumeric = 131 (Indicates an exact numeric value with a fixed precision and scale (DBTYPE-NUMERIC).) adpropvariant = 138 (Indicates an Automation PROPVARIANT (DBTYPE-PROP-VARIANT) .) adsingle = 4 (Indicates a single-precision floating-point value (DBTYPE-R4) .) adSmallInt = 2 (Indicates a two-byte signed integer (DBTYPE-12) .) adTinyInt = 16 (Indicates a one-byte signed integer (DBTYPE-I 1).) adUnsignedBigInt = 21 (Indicates an eight-byte unsigned integer (DBTYPE-UI8) .) adUnsignedInt = 19 (Indicates a four-byte unsigned integer (DBTYPE-U14) .) adUnsignedSmallInt = 18 (Indicates a two-byte unsigned integer (DBTYPE-U12) .) adUnsignedTinyInt = 17 (Indicates a one-byte unsigned integer (DBTYPE-UI 1).) adUserDefined = 132 (Indicates a user-defined variable (DBTYPE-UDT) .) adVarBinary = 204 (Indicates a binary value.)
I Excel Files as Databases I
749
adVarChar = 200 (Indicates a string value.) adVarNumeric = 139 (Indicates a numeric value.) adVarWChar = 202 (Indicates a null-terminated Unicode character string.) adWChar = 130 (Indicates a null-terminated Unicode character string (DBTYPE-WSTR).) The Database flucefoints.rndb has a Table named “Points”,and a Table named “PlaceNotes’: When we look into the Schema we can see that Tables named “MSysAccessObjects” and “MSysAccessXML? also display. It is not uncommon for databases to create their own tables for functions such as the indexing of indexed fields. In most cases, it is clear which tables are ‘system’tables and which tables are for our use.
EXCELFILESAS DATABASES Excel files are divided into Rows and Columns. Right? Well, then, it makes perfect sense that we should be allowed to open them by using ActiveX Data Objects. Let’s begin by identifying an Excel file (As) we want to work with. The towerdut.x/s file is installed with Microstation. A search for it on our computer reveals that it is installed somewhere under “Documents and Settings” in a rather lengthy path. Copying it and pasting it into a more simple path makes it easier to use.
750
I Chapter 35: Working With Databases (ActiveX Data Objects) I Let’s create a UDL file named €xce/.ud/. Here are the settings in the UDL file:
Even though the Jet driver is typically used for connecting to Microsoft Access databases, we can use it to connect to Excel. In the Connection tab, we need to browse for the Excel file. By default, the Browse button’s dialog box looks for Microsoft Access Databases (.mdb). We can select the “*.*” option in the “Files of type” combo box and then select the Excel (.As) file. towerdat XIS
All Files [x ‘1
I Excel Files as Databases I
751
When we are working with Microsoft Access files, we are finished entering information into the UDL file at this point. But when we are working with Microsoft Excel files, we need to make one more change.
In the All tab, we need to give the “Extended Properties”, a value of “Excel 8.0’: When we do this, the Jet driver knows it is working with a Microsoft Excel file and so works with it accordingly. If we forget to add this important ‘Extended Property’, we will see errors pop up because without this value, the UDL file will treat the file as a Microsoft Access Database.
752
I Chapter 35: Working With Databases (ActiveX Data Objects) I Now, we want Excel to look as much like a database as possible before we begin. So, let's remove a few rows at the top of the file and the first column so we have a Header Row as shown here:
1 R477
Since working with Excel is new territory, how can we tell what we have to work with? Let's modify our previous "OpenSchemd' procedures to work with the €xce/.ud/file. Sub TestSchemaCO
D i m myDB As New ADODB.Connection D i m myRS As New ADODB.Recordset D i m F F i l e As L o n g name=C : \ M i c r o S t a t io n VBA\ E x c e 1 . u d 1 " " f i1 e
my DB . Open
S e t myRS FFile
=
=
myDB.OpenSchema(adSchemaColumns)
FreeFile
Open " C : \ D b S c h e m a . t x t " W h i l e myRS.EOF
=
P r i n t #FFile,
F o r O u t p u t As I l F F i l e
False
"1''
myRS("TABLE_NAME") &
&
myRS("C0LUMN-NAME") myRS("1S-NULLABLE") myRS("DATA-TYPE")
&
&
''1''
''1" ''1'' &
&
-
&
-
-
myRS( "CHARACTER-MAXIMUM-LENGTH" myRS . M o v e N e x t
Wend C1 o s e # F F i 1 e End Sub
When we run this procedure, a new file is
&
)
-
I Excel Files as Databases I
753
We can see a Table named "TOWERDAT$". That's interesting. When we look at the Excel file, we find a Worksheet named "TOWERDAT". This looks promising. Let's see what we can do with "TOWERDAT$': Sub T e s t D B E x c e l A ( )
D i m myDB As New ADODB.Connection D i m myRS As New ADODB.Recordset D i m m y F i e l d As F i e l d
myDB.0pen " f i l e n a m e = C : \ M i c r o S t a t i o n myRS.Open "CTOWERDAT$I", W h i l e myRS.EOF
=
VBA\Excel.udl"
myDB, adOpenDynamic, a d L o c k O p t i m i s t i c
False
F o r Each m y F i e l d I n m y R S . F i e l d s Deb u g . P r in t my F ie 1 d . N a me &
"
I
"
& my F ie 1 d . V a 1 u e
Next D e b u g . P r i n t vbCr mvRS. M o v e N e x t
X I 18325 Y I22791 2 I514
Wend
DATASETlxy=18325,22791,514 Cable XI18290 Cable Y122929 Cable 21894
End Sub
Now we are getting somewhere. We are able to get to the data in an Excel file without opening Microsoft Excel. For that matter, Excel doesn't even need to be installed on the computer. Let's see what else we can do. Sub T e s t D B E x c e l B ( ) D i m myDB As New ADODB.Connection
D i m myRS As New ADODB.Recordset D i m C e n P t As P o i n t 3 d D i m m y c i r c l e As A r c E l e m e n t D i m R o t M a t r i x As M a t r i x 3 d
myDB.0pen " f i l e n a m e = C : \ M i c r o S t a t i o n myRS.Open "CTOWERDAT$I",
myDB,
VBA\Excel.udl"
-
adOpenDynamic, a d L o c k O p t i m i s t i c W h i l e myRS.EOF
=
False
CenPt.X
=
myRS("X")
Cen P t . Y
=
my R S ( " Y " )
CenPt.Z
=
myRS("Z")
Set mycircle
=
CreateArcElementZ(Nothing,
-
754
I Chapter 35: Working With Databases (ActiveX Data Objects) I CenPt, 4, 4, R o t M a t r i x , 0 , 360) A c t i v e M o d e l R e f e r e n c e . A d d E l e m e n t myCi r c l e myRS . M o v e N e x t
Wend End S u b
Now, we are drawing in Microstation based on data in a Microsoft Excel file that we are accessing via ActiveX Data Objects.
ActiveX Data Objects gives us tools to work with Data. At times this Data is stored in Databases. This Data can be ‘stored’ on the Internet in RSS files. This data can even be stored in a Microsoft Excel file. Independent of where the data is, ActiveX Data Objects can be used to retrieve the data. The process of connecting to data sources is simplified greatly by the use of UDL files. Once connected, the Connection and Recordset Objects can be used to retrieve, manipulate, edit, and add data.
36
Microstation Leveraging Mathcad via VBA Any time a company opens its product for customization, the consumer wins. Mathcad is one such product. Mathcad worksheets can be used to perform calculations and then can ‘hand off’ the information to Microstation through the use of VBA. Of course, Mathcad is not a Bentley product. And the inclusion of Mathcad in this book should not be considered an endorsement in any way. The same should be said of Microsoft Excel and any other third-party products discussed in this book. That having been said, Mathcad like Excel can be customized and channels of communication can be opened between Mathcad and Microstation resulting in an integrated solution. In this Chapter:
A Brief Introduction to Mathcad Adding a Reference and using the Object Browser Basic Macros that communicate with Mathcad Region Objects - The Basis for All Calculations The Mathcad Object Model Driving Microstation Geometry from Mathcad 755
756
I Chapter 36: Microstation Leveraging Mathcad via VBA I
A BRIEFINTRODUCTION TO MATHCAD Mathcad includes functionality that allows us to perform calculations (simple and complex) in a sketchpad type of environment. Variables can be used in these calculations and standard mathematical nomenclature is used so our formulas in Mathcad look just like they do in reference materials we may use. Mathcad provides hundreds of operators and built-in functions for solving technical problems. Mathcad can be used to perform numeric calculations or to find symbolic solutions. It automatically tracks and converts units and operates on scalars, vectors, and matrices. Not only does Mathcad understand Units, but it takes care of all conversions from one unit to the next for us (inches to meters, gallons to liters, etc.). And should we need a unit that does not appear in Mathcad out of the box (such as Hands), we can add it to Mathcad. Additional information about Mathcad can be found by visiting www.mathcad.com.
ADDINGA REFERENCE AND USING THE OBJECT BROWSER Before we attempt to communicate with Mathcad in any way, we need to add a Reference to it in VBA. This is done by using the VBA menu Tools > References.
f
I Adding a Reference and using the Object Browser I
757
Once a Reference has been added, we can use the VBA Object Browser to ‘browse’the Mathcad Object Model.
R e gions
When we filter the Classes by selecting “Mathcad” in the top ComboBox, we can see that we have an Application class in the Mathcad Object Model. This is the top-level class in Mathcad. We can also see that we have an Activeworksheet Object. These two objects should be good enough for us to get started. Let’s begin writing a macro in VBA and we w ill see what we can accomplish. Before we do this, we need to have Mathcad started and a Worksheet open. Let’s begin by using the file “Sample 1.xmcd” which is located on the CD that accompanies this book.
758
I Chapter 36:Microstation Leveraging Mathcad via VBA I
Here is a portion of the Worksheet.
RoughLength := 18R
+ 5in
Jambshin :=
1.
-UI
2
Strikeshin := l i n 2
M d e n g t h := 300cm MhLength := 5Ocm FifiishLength := RoughLength - Jambshin - Strikeshin FifiishLength = "18'4"" FIF
We can see here that we have a variable named "RoughLength", one named "Jambshim", one named "MaxLength", and one named "FinishLength': among others. Let's begin writing a macro in Microstation's VBA environment that will give us the value of the RoughLength variable from Mathcad. Sub T e s t M a t h c a d A ( 1 Dim myMCA As Mathcad.Application Dim myMCW As Mathcad.Worksheet Set myMCA = Getobject(, "Mathcad.Application") Set myMCW = myMCA.ActiveWorksheet End Sub
We saw the Application and Activeworksheet objects in the Object Browser. If we run the macro T e s t M a t h c a d A we will find that the code executes but we are not at the point where we are getting the value of the RoughLength variable in Mathcad.
I Adding a Reference and using the Object Browser I
759
Let’s take another look at the Object Browser in VBA.
L
We know we want to get the value of a variable in the Worksheet. So, we select “Worksheet” in the classes ListBox and begin looking for a Property or Method that gives us the value we are looking for. As we do so, we can see a method named GetVa 1 ue. It uses one parameter, “bstrName as String. This method returns an Object. But what kind of object? Another look at the Object Browser reveals an Object (in the Classes ListBox) named “Value’: Let’s give the Value Object a try. Sub TestMathcadA() D i m myMCA As M a t h c a d . A p p l i c a t i o n D i m myMCW As M a t h c a d . W o r k s h e e t
D i m myMCV As M a t h c a d . V a l u e S e t myMCA
=
Getobject(,
S e t myMCW
=
myMCA.ActiveWorksheet
“Mathcad.Application”)
S e t myMCV
=
myMCW.GetVa1 ue(”RoughLength”)
MsgBox myMCV.AsString & vbCr & myMCV.Type
End Sub
Now, in addition to connecting to the Mathcad Application and the Activeworksheet, we are getting a Value Object based on the parameter RoughLength and displaying its ‘XsString and “Type” properties in a MessageBox.
760
I Chapter 36: Microstation Leveraging Mathcad via VBA I
0.0254
If we look at the RoughLength property in Mathcad we see a value of 18’-5”.But when we ran the macro TesetMathcadA, we were shown a value of 5.6134. Why is this? It must be a units issue. Let’s try dividing 5.6134 by 221 inches and see what we get.
When we see the magic number 0.0254 we know we are dealing with an inches to meters conversion issue. So, when we look at the Object Browser at the function GetValue, we see that it returns an Object. When we use a variable declared as a “Value” Object, we can get the “AsString and ‘‘Tpe” properties of the “Value” object. Is there more we can do here?
When we add a watch to the variable myMCV, we can see the other properties belonging to the Value Object.
AsString lmag Integer Real Type
“5.61 34” 0 6 5.61 34 “Numeric”
String Double Long Double String
The variable myMCV is declared as a “Value”but we can see here that we are actually being returned a “NumericValue”(look in the Type column) Object. Let’s change our code a little by declaring the variable myMCV as a “NumericValue”Object and see what happens. Sub TestMathcadB ( ) Dim myMCA As Mathcad.App1ication Dim myMCW As Mathcad. Worksheet Dim myMCV As Mathcad.NumericValue Set myMCA = Getobject (, “Mathcad.Application“) Set myMCW = myMCA.ActiveWorksheet Set myMCV = myMCW. GetValue ( “RoughLength“) msgbox mymcv. End Sub Ass!!lns ........ lmag Integer Real Type
I Adding a Reference and using the Object Browser I
761
Now, when we begin writing code to display properties in a MessageBox we can see that we have more properties to work with because the variable was declared as a “NumericValue” object. Sub TestMathcadB()
D i m myMCA As M a t h c a d . A p p l i c a t i o n D i m myMCW As M a t h c a d . W o r k s h e e t D i m myMCV As M a t h c a d . N u m e r i c V a l u e
S e t myMCA
=
Getobject(,
S e t myMCW
=
myMCA.ActiveWorksheet
S e t myMCV
=
myMCW.GetValue(”RoughLength”)
“Mathcad.Application”)
MsgBox m y M C V . A s S t r i n g & v b C r & myMCV.Imag & v b C r &
~
myMCV.Integer & vbCr & myMCV.Rea1 & v b C r &
~
-
-
myMCV.Type End Sub
Now we are getting somewhere. In this MessageBox, we can see the ‘XsString and “Real” property values of 5.6134, an “Integer” property value of 6, and a “Tpe” property of “Numeric”. Let’s try making one additional change to our code. S u b TestMathcadC() D i m myMCA As M a t h c a d . A p p l i c a t i o n D i m myMCW As M a t h c a d . W o r k s h e e t D i m myMCV As M a t h c a d . S t r i n g V a 1 u e
S e t myMCA
=
Getobject(,
S e t myMCW
=
myMCA.ActiveWorksheet
“Mathcad.Application”)
S e t myMCV
=
myMCW.GetValue(”RoughLength”)
MsgBox m y M C V . A s S t r i n g & v b C r & myMCV.Imag & v b C r &
myMCV.Integer & vbCr & myMCV.Rea1 & v b C r & myMCV.Type End Sub
-
-
~
-
762
I Chapter 36: Microstation Leveraging Mathcad via VBA I Instead of declaring myMCV as a “NumericValue” we declare it as a “StringValue’: What happens?
Instead of getting the value of the Mathcad variable “RoughLength” we see a Trpe mismatch error dialog box. Why is this? Because we are dealing with a Numeric value, not a String value. Lessons learned: When attempting to retrieve a variable value from Mathcad and there is a degree of uncertainty as to what type of value we will be getting, we should declare our Value variable as a “Value” object. We can then look at the ‘‘Tpe” property and use the appropriate Valuespecific object. Here is an example of this: Sub T e s t M a t h c a d D ( 1
D i m myMCA As M a t h c a d . A p p l i c a t i o n D i m myMCW As M a t h c a d . W o r k s h e e t D i m myMCV As M a t h c a d . V a l u e D i m myMCNV As M a t h c a d . N u m e r i c V a l u e D i m myMCSV As M a t h c a d . S t r i n g V a 1 u e S e t myMCA
=
Getobject(, “Mathcad.Application”)
S e t myMCW S e t my MC V
=
myMCA.ActiveWorksheet
=
my M C W . G e t V a 1 u e ( ” Rough L e n g t h ” 1
S e l e c t Case myMCV.Type Case “ N u m e r i c ” S e t myMCNV
=
myMCV
MsgBox m y M C N V . A s S t r i n g & v b C r & myMCNV.Imag & v b C r &
~
myMCNV.Integer & vbCr & myMCNV.Rea1 & v b C r &
-
~
~
myMCNV.Type Case “ S t r i n g ” S e t myMCSV
=
myMCV
MsgBox m y M C S V . A s S t r i n g & v b C r & myMCSV.Type & v b C r &
~
~
I Basic Macros that Communicate With Mathcad I
763
myMCSV. V a l u e
End S e l e c t End Sub
We begin by using a generic “Value” object. Then we look at the Value.Type property. If it is “Numeric’: we use a “Mathcad.NumericValue” object. If it is a “String”, we use a “Mathcad.Stringvalue”object.
BASICMACROS THAT COMMUNICATE WITH MATHCAD The more familiar we get with Mathcad, the more we realize how well it can handle very complex calculations. But even though the calculations Mathcad handles can be complex, communicating with Mathcad is not complex at all. Let’s take a look at a few macros that communicate with Mathcad in a variety of different areas. These macros continue to make use of the “Sample 1”worksheet. Sub T e s t M a t h c a d E ( ) D i m myMCA As M a t h c a d . A p p l i c a t i o n
D i m myMCW As M a t h c a d . W o r k s h e e t D i m myMCV As M a t h c a d . V a l u e D i m myMCNV As M a t h c a d . N u m e r i c V a 1 u e S e t myMCA
=
Getobject(, “Mathcad.Application”)
S e t myMCW
=
myMCA.ActiveWorksheet
S e t myMCV
=
myMCW.GetValue(”RoughLength”)
S e t myMCNV
=
myMCV
MsgBox “ R o u g h L e n g t h :
”
& myMCNV.Rea1 / 0 . 0 2 5 4 &
”
Inches.”
End Sub
TestMathcadE displays the RoughLength variable in a MessageBox after
converting it to Inches.
c
Our previous examples dealt with pulling information from Mathcad. Let’s try changing a variable’s value, recalculating the Worksheet, and then pulling a value. Sub T e s t M a t h c a d F ( )
D i m myMCA As M a t h c a d . A p p l i c a t i o n D i m myMCW As M a t h c a d . W o r k s h e e t
764
I Chapter 36: Microstation Leveraging Mathcad via VBA I D i m myMCNV As M a t h c a d . N u m e r i c V a l u e
S e t myMCA
=
Getobject(, "Mathcad.Application")
S e t myMCW
=
myMCA.ActiveWorksheet
myMCW.SetValue " J a m b S h i m " , 0 . 3 7 5
*
0.0254
myMCW. R e c a l c u l a t e
S e t myMCNV
my MCW . G e t Va 1 u e ( " F i n is h L e n g t h " )
=
MsgBox " F i n i s h L e n g t h :
"
& myMCNV.Rea1 / 0 . 0 2 5 4
End Sub
When this macro runs, we see a MessageBox with a value in it 1 -In 2 but the calculation seems to be off. When we look at the Mathcad Worksheet we can see that the JambShim variable has a problem with the value we attempted to give it. Why is this happening? Values assigned to Variables through ActiveX Automation are calculated prior to those defined in the Mathcad Worksheet. So, when we attempted to Set a Value for JambShim, in addition to the value shown in the Worksheet, Mathcad attempted to use a value from the SetValue call and this caused a problem. So, we know why the error is occurring. How do we fx it? One solution is to remove the "1/2 in" value assigned to the JambShim variable so the duplicate value assignment is no longer taking place. And instead of assigning JambShim a value of "0.375 * 0.0254'; we are going to give it a value of "3/8': Next, we are going to change the formula for the FinishLength variable to 'convert' the JambShim value to Feet and Inches. To best illustrate this change, we will look at the code and then at the Worksheet. Sub T e s t M a t h c a d F 2 ( )
D i m myMCA As M a t h c a d . A p p l i c a t i o n D i m myMCW As Mathcad.IMathcadWorksheet2 D i m myMCNV As M a t h c a d . N u m e r i c V a 1 u e S e t myMCA
=
Getobject(, "Mathcad.Application")
S e t myMCW
=
myMCA.ActiveWorksheet
myMCW.SetVa1 u e " J a m b S h i m " ,
"3/8"
myMCW. R e c a l c u l a t e
S e t myMCNV
=
my MCW . G e t Va 1 u e ( " F i n is h L e n g t h " 1
MsgBox " F i n i s h L e n g t h : End Sub
"
& myMCNV.Rea1 / 0 . 0 2 5 4
I Basic Macros that Communicate With Mathcad I
Jambshim:=
765
I
Strikeshim := li, 2
M d e n g t h := 300cm MhLength := 5Ocm FifiishLength := RoughLength - FIF(JambShim) - Strikeshim
The macro TestMathcadF2 now works correctly because we are allowing the value for JambShim to be set in our code and we are using the Mathcad function FI F to convert the provided value (which is 3/8) into Feet and Inches. GetVal ue, SetVal ue, and R e c a l c u l a t e form the basis for taking existing
Mathcad Worksheets and making changes and retrieving calculations from them. As long as we make sure Mathcad is being given variable values from only one source and that the correct unit conversion is being performed on the values we set, Mathcad formulas and our own VBA programming will give us consistent, correct results time after time. Each Value, Variable, Formula, and Calculation are stored in Mathcad Worksheets as a “Region’: Sub TestMathcadG() D i m myMCA As M a t h c a d . A p p l i c a t i o n D i m myMCW As M a t h c a d . W o r k s h e e t D i m myMCR As M a t h c a d . R e g i o n
S e t myMCA
=
Getobject(,
S e t myMCW
=
myMCA.ActiveWorksheet
“Mathcad.Application”)
MsgBox myMCW.Regions.Count F o r Each myMCR I n myMCW.Regions MsgBox myMCR.Type Next End S u b
The Region.Type property is an Enumeration named “MCRegionType’: Here are their values: mcBitmapRegion
=
mcMathRegion
1
=
2
766
I Chapter 36: Microstation Leveraging Mathcad via VBA I mcMetafileRegion mcOLERegion
=
mcTextRegion
=
3
4
=
0
In the “Sample 1” Worksheet, we see a lot of “mcMathRegion”Regions. An “mcMathRegion”is a Region that involves numeric calculations and variable assignments. So, even if a variable is holding a String value, it qualifies as a MathRegion.
REGIONOBJECTS - THEBASISFOR ALLCALCULATIONS We just looked at the Region objects of a Worksheet. The Region object gives us some very basic information such as a Type, and the Region’s location on the Worksheet through X and Y properties. But how are calculations stored in Mathcad? Considering some of the complex calculations that Mathcad can make, it is not difficult to imagine that it would take more than a simple property to store this potentially complex calculation information. And that is correct. In addition to declaring a variable as a “Region”, we can declare a variable as a “IMathcadRegion2’: When we do, additional properties are available for our use.
:._I_~...~.,...~.,.I.
.,...
1,.“1~.,,~ ~ .I.,.
.,...
1,.“1~.,,~ ~ .I.,.
1 1 . “ ~ ~
liMathcadRegion2 .......................................................................................... iMathcadWorksheet2 iMathcadWorksheets2 iMetadata MathcadOid Mathinteface MatrixVaiue MCAppOption
I Region Objects -The Basis for All Calculations I Here are the Properties for the IMathcadRegion2 Object. One of them is the "MathInterface" property. When we click on "MathInterface" in the Classes list, we see the following:
The MathInterface Object has a property named "XML': That sounds interesting. Let's try running the following macro: Sub T e s t M a t h c a d H ( ) Dim myMCA As Mathcad.Application Dim myMCW As Mathcad.Worksheet Dim myMCR As Mathcad.IMathcadRegion2 Dim myMCI As Mathcad.MathInterface Set myMCA = Getobject(, "Mathcad.Application") Set myMCW = myMCA.ActiveWorksheet For Each myMCR In myMCW.Regions Set myMCI = myMCR.MathInterface Debug.Print myMCI.XML Next End Sub
When we run this macro, the XML property for each MathInterface of each IMathcadRegion2 Object is written to the Immediate Window. Here is an example of what is written when we run T e s t M a t h c a d E:
I Chapter 36: Microstation Leveraging Mathcad via VBA I < m l :mu1 t s t y l e = " a u t o - s e l e c t " / >
< m l : i d xml : s p a c e = " p r e s e r v e " > f t < / m l : i d >
This is the data that is associated with the RoughLength variable in our Worksheet. Let's take a look at a few more: < m l : d e f ine xml ns : m l = " h t t p : / / s c hemas. mat h s o f t . com/ma t h 2 0 " > < m l : i d xml : s p a c e = " p r e s e r v e " > J a m b S h i m < / m l : i d >
< m l : d e f ine xml ns : m l = " h t t p : / / s c hemas. mat h s o f t . com/ma t h 2 0 " > < m l : i d xml : s p a c e = " p r e s e r v e " > S t r i keShim
< m l : d e f ine xml ns : m l = " h t t p : / / s c hemas. mat h s o f t . com/ma t h 2 0 " > < m l : i d xml : s p a c e = " p r e s e r v e " > M a x L e n g t h < / m l : i d >
< m l : i d xml : s p a c e = " p r e s e r v e " > c m < / m l : i d > < / m l : d e f ine>
I The Mathcad Object Model I
769
We can see here that the calculation for the FinishLength variable in our Worksheet is the RoughLength minus the Feet and Inch value of the JambShim variable minus the Strikeshim.
THEMATHCAD OBJECT MODEL Let's take a look at a couple of the Mathcad Objects we can work with in Mathcad. As with most ActiveX Automation Object Models, Mathcad's top object is the Application Object.
A ppIicat ion Act iveWind ow
Height
Act iveWorksheet
Left
TOP Version
Application
Name
Visible
C IoseAlI
Parent
Width
DefaultFilePath
Path
Windows
FuIIName
Quit
Worksheets
770
I Chapter 36:Microstation Leveraging Mathcad via VBA I In addition to the Application Object, Mathcad has an IMathcadApplication2 Object. Creating additional Application-type Objects is usually done to preserve compatibility with the older parts of an Object Model while introducing new properties and methods. Of note here are the G e t o p t i o n Method, the HWND property (often used in Windows API programming), and the S e t O p t i on Method.
IMathcadA ppIicat ion2 Active
Height
ActiveWindow
HWND
TOP Version
Activeworksheet
Left
Visible
Application
Name
Width
C IoseAlI
Parent
Windows
Defau ItFilePath
Path
Worksheets
FullName
Quit
Getoption
Setoption
For more detailed information regarding the Mathcad Object Model, the Object Browser in VBA gives us the names and members of Objects, Properties, Constants, and Enumerations.
I Driving Microstation Geometry from Mathcad I
771
DRIVING MICROSTATION GEOMETRY FROM MATHCAD We have seen a few examples of getting and setting Mathcad variables using VBA. Now, let’s look at an example of how Mathcad can be used to drive the creation of our Microstation geometry. Width := 1R + 5m
HoleDia := 1 2 m
Height _=2R i. 2m
M d p a c m g := 5Om OutsideBufFer := 5Om
CalculateQumtity(0verQ :=
Overd - OutsideBufFer.2 HoleDia i. M d p a c m g
QtyWidth := bc(CdculateQumtity(Width)) + 1 QtyHeight := bc(CdculateQumtity(Height)) Actu&pach&OverQ :=
4
1
Overd - OutsideBufFer.2 - HoleDia QtyWidth
Spacinfln := ActualSpacin&Width) SpacrngYIn := Actudpacm&Height) HoleDiaIn := HoleDia OutsideBufFerIn := OutsideBufFer Widthln := Width Heighffn := Height
+.
Widthln = 17in Heighffn = 26m QtyWidth = 6 QtyHeight = 10 Spacinfln = 1.705in SpacingYIn = 3.205m HoleDiaIn = 0.472m OutsideBufFerIn = 1.969in
Our goal is to draw a 2D plate in Microstation with a hole pattern. The holes in the X Plane must be equally spaced and the holes in the Y Plane must also be equally spaced (but the X Spacing and the Y Spacing can differ). We want to maintain a ‘buffer’ around the outside of the plate. We want to be able to specify the Width and Height of the plate, the Hole
772
I Chapter 36: Microstation Leveraging Mathcad via VBA I Diameter, the Maximum spacing between the holes, and the ‘buffer’area around the outside of the plate. We could look at our goal and decide to perform all of the calculations inside our code. But there are several advantages to using Mathcad in a situation like this.
1 When using Mathcad, we can allow the parameters for our plate to be entered using a large variety of units. For example, the Hole Diameter can be entered in millimeters and the width and height can be entered in decimal feet. The ‘buffer’can be entered in yards and the Maximum Spacing can be entered in cubits. The units used to enter the design parameters are almost irrelevant because Mathcad takes care of the unit conversions for us. Furthermore, when we retrieve the value of any variables in the Worksheet, we can specify in what units we want the value returned.
2 We can make changes to the calculations in Mathcad without making changes to our code. So, different design rules could be implemented in Mathcad and as long as we are ‘outputting’the same parameters, our code will continue to operate flawlessly.
3 Mathcad is capable of performing calculations using standard mathematical notation that can be extremely challengingto perform in VBA. As we look at our Mathcad Worksheet we can see two types of statements entered. One of them uses “X = 5” notation and the other uses “X := 5” notation (notice the colon). The difference between these types of notation are significant. When we see “:=” in Mathcad we know a formula with/for calculations is being entered. When we see “=” (without the colon) we know we are displaying the value of a variable or the result of a formula. Later on, we will see why the distinction between these two ‘types’of statements are important. Separating the ‘logic’from the ‘presentation’of the resulting information is a sensible thing to do in this instance. What we need now is code that retrieves the results from Mathcad and uses them to draw the plate with the hole pattern in Microstation. In a previous example in this chapter we used the GetVa 1 ue method of the Mathcad Worksheet Object to get a Mathcad variable’s value. We performed a conversion of the value in VBA from meters to inches. Since we already have Mathcad taking care of our unit conversions for
I Driving Microstation Geometry from Mathcad I
773
us, it makes sense to use Mathcad's converted values instead of performing the unit conversions in our code.
By default, Mathcad stores all length type values in meters. GetVa 1 u e retrieves values in this default unit of measure. Rather than retrieving values in Mathcad with G e t v a l u e , we can retrieve values in the units shown in our Mathcad Worksheets by looking at the IMathcadRegion2 Object of each Region. When we look at Mathcad Region objects, we can see that there are several types of regions. The two we are primarily interested in at this point are the "define" and "eval" types. The information regarding these Regions is stored using XML formatting. Here are some samples of the two types of Regions we are interested in:
Here we can see a "define" Region and an "eval" Region. Notice how the "eval" Region has an " i d value of "WidthIn" and a "real" value of "17". These are the Region values of interest to us. When we want to retrieve a Region from Mathcad, it is best to retrieve all Regions in the Worksheet at once in a separate function and then parse them to find the values we are looking for. We will begin with a Function named G e t A l 1 Regions. Function GetAllRegionsO As S t r i n g On Error GoTo e r r h n d Dim myMCA As Mathcad.IMathcadApplication2 Dim myMCW As M a t h c a d . I M a t h c a d W o r k s h e e t 2
774
I Chapter 36: Microstation Leveraging Mathcad via VBA I D i m myMCR As M a t h c a d . I M a t h c a d R e g i o n 2 D i m m y M C I As M a t h c a d . M a t h I n t e r f a c e
D i m D o m S t r i n g s O As S t r i n g ReDim D o m S t r i n g s ( 0 ) S e t myMCA
=
Getobject(, "Mathcad.Application")
S e t myMCW
=
myMCA.ActiveWorksheet
D i m myRegs As M a t h c a d . R e g i o n s
S e t myRegs
=
myMCW. R e g i o n s
F o r Each myMCR I n myRegs S e t myMCI
=
myMCR.MathInterface
' D e b u g . P r i n t myMCI.XML ' D e b u g . P r i n t myMCI.UnitsXML
DomStrings(UBound(DomStrings)) = myMCI.XML ReDim P r e s e r v e DomStrings(UBound(DomStrings) + 1) DomStrings(UBound(DomStrings)) = m y M C I . U n i t s X M L ReDim P r e s e r v e DomStrings(UBound(DomStrings) + 1) Next GetAllRegions
=
J o i n ( D o m S t r i n g s , vbCr & vbCr & vbCr)
E x i t Function errhnd: MsgBox E r r . N u m b e r & v b C r & E r r . D e s c r i p t i o n End F u n c t i o n
G e t A l l R e g i o n s returns a String comprised of Region XML data separated by three (3) Carriage Return characters. We use these
characters to Split the return value back into Regions in the procedure G e t E v a l s. G e t E v a l s takes the returned String from G e t A l 1 R e g i o n s and extracts the
Eval Regions, returning only the Name and Value of each Eval Region separated by an Equal Sign (=). F u n c t i o n G e t E v a l s O As V a r i a n t
D i m myDOM As New DOMDocument60 D i m myDOM2 As New DOMDocument60 D i m myNode As IXMLDOMNode D i m m y N o d e L i s t As IXMLDOMNodeList
D i m EvalName As S t r i n g D i m E v a l V a l u e As D o u b l e D i m A l l R e g i o n s As S t r i n g D i m x S p l i t 0 As S t r i n g D i m E v a l A r r a y O As S t r i n g
I Driving Microstation Geometry from Mathcad I
775
ReDim E v a l A r r a y ( 0 ) As S t r i n g A1 1 R e g i o n s xSplit
=
GetAll Regions
=
Split(AllRegions,
vbCr & vbCr & vbCr)
F o r I = L B o u n d ( x S p 1 i t ) To U B o u n d ( x S p 1 i t ) If xSplit(1)
<>
" "
Then
myD0M.loadXML x S p l i t ( 1 ) I f myDOM.firstChi1d.baseName
=
" e v a l " Then
Eval Name = rnyDOM.getE1ementsByTagNarne("rn1 : i d " ) (0).Text Eval Value = myDOM.getElementsByTagName("m1 : r e a l " ) (0).Text, D e b u g . P r i n t E v a l Name Debug.Print Eval Value EvalArray(UBound(Eva1Array)) ReDim P r e s e r v e
=
EvalName &
"="
&
EvalArray(UBound(Eva1Array)
EvalValue
+ 1)
End I f End I f Next
I
I f UBound(Eva1Array)
>
0 Then
ReDim P r e s e r v e E v a l A r r a y ( U B o u n d ( E v a 1 A r r a y )
-
1)
End I f GetEvals
EvalArray
=
End F u n c t i o n
Now that we have the Region Values we can begin the process of making use of the values and drawing the plate and its hole pattern in Microstation. Sub
DrawFromMathcadO D i m A l l E v a l s O As S t r i n g D i m P a r t w i d t h As Double
D i m P a r t H e i g h t As D o u b l e D i m O u t s i d e B u f f e r As D o u b l e D i m H o l e D i a As D o u b l e D i m S p a c i n g X As D o u b l e
D i m S p a c i n g Y As D o u b l e D i m QtyX As D o u b l e
D i m QtyY As D o u b l e D i m F i l t e r R e t u r n O As S t r i n g AllEvals
=
GetEvals
FilterReturn Partwidth
=
=
Filter(A11Evals. "WidthIn=")
CDbl(Replace(FilterReturn(O),
~
776
I Chapter 36: Microstation Leveraging Mathcad via VBA I "
W i d t h I n=" ,
" "
))
FilterReturn = Filter(A11Evals. "HeightIn=") PartHeight = CDbl (Replace(FilterReturn(O), He i g h t I n=" , ~
" "
"
FilterReturn = Filter(A11Evals. "OutsideBufferIn=") OutsideBuffer = CDbl (Replace(FilterReturn(O), "OutsideBufferIn=", " " ) ) FilterReturn = Filter(A11Evals. "HoleDiaIn=") HoleDia = CDbl (Replace(FilterReturn(0). H o 1 e D i a I n=" , " "
"
FilterReturn = Filter(A11Evals. "SpacingXIn=") SpacingX = CDbl (Replace(FilterReturn(O), "SpacingXIn=", " " ) ) ~
FilterReturn = Filter(A11Evals. "SpacingYIn=") SpacingY = CDbl (Replace(FilterReturn(0). "SpacingYIn=", " " ) ) FilterReturn = Filter(A11Evals. "QtyWidth=") QtyX = CDbl(Replace(FilterReturn(0). 11 Q tyW i d t h=" , " "
"
FilterReturn = Filter(A11Evals. "QtyHeight=") QtyY = CDbl(Replace(FilterReturn(O), Qty Hei g h t=" , ~
" "
"
DrawPart Partwidth, PartHeight, OutsideBuffer, Hol eDi a , Spaci ngX, Spaci ngY, QtyX, QtyY End Sub
~
~
The Array AllEvals is 'Filtered' to get only the parameter we want. When we find it, we get the value associated with the parameter by replacing the parameter name and the equal sign with an empty string and then converting the remaining text (the numeric value) to a Double by using the standard VBA CDbl function. Each of these parameter values are placed into their own variable. These variables are then used to call a procedure named DrawPart.
I Driving Microstation Geometry from Mathcad I Sub D r a w P a r t ( W i d t h As D o u b l e , H e i g h t As D o u b l e ,
777
-
O u t B u f f e r As D o u b l e , H o l e D i a As D o u b l e , S p a c i n g X As D o u b l e , S p a c i n g Y As D o u b l e ,
~
~
Q t y W i d t h As D o u b l e , Q t y H e i g h t As D o u b l e ) D i m m y L i n e As L i n e E l e m e n t D i m myCi r c l e As E l 1 i p s e E l e m e n t
D i m XPos As D o u b l e D i m YPos As D o u b l e D i m R o t M a t r i x As M a t r i x 3 d Set myLine
CreateLineElementZ(Nothing, O), -
=
Point3dFromXY(O,
P o i n t 3 d F r o m X Y ( W i d t h , 0)) ActiveModel Reference.AddElement myLine Set myLine
CreateLineElementZ(Nothing, O), -
=
Point3dFromXY(Width,
Point3dFromXY(Width, H e i g h t ) ) ActiveModel Reference.AddElement myLine Set myLine
=
CreateLineElementZ(Nothing, Point3dFromXY(Width, H e i g h t ) ,
-
Point3dFromXY(O, H e i g h t ) ) ActiveModel Reference.AddElement myLine Set myLine
=
CreateLineElementZ(Nothing, Point3dFromXY(O, H e i g h t ) ,
~
P o i n t 3 d F r o m X Y ( O , 0)) ActiveModel Reference.AddElement myLine XPos
=
For X
OutBuffer
=
+ HoleDia / 2
1 To QtyWidth
YPos
=
For Y I***
OutBuffer
=
+ HoleDia / 2
1 To Q t y H e i g h t Draw t h e C i r c l e
Set mycircle
=
***
CreateEllipseElementZ(Nothing,
Point3dFromXY(XPos, YPos), HoleDia / 2, H o l e D i a / 2, R o t M a t r i x ) ActiveModel Reference.AddElement m y c i r c l e
-
778
I Chapter 36: Microstation Leveraging Mathcad via VBA I YPos
=
YPos
+ SpacingY
Next Y XPos
=
XPos
+ SpacingX
Next X
End S u b
The code is in place. Our Mathcad Worksheet is open and the design criteria has been entered. The only thing to do now is run the procedure DrawFromMathcad and see how the calculations we entered into Mathcad look. 0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
O
a
O
O
O
0
0
0
0
0
0
0
O
O
O
O
0
O
I Driving Microstation Geometry from Mathcad I
779
When we run DrawFromMathcad and take a look at the results in Microstation, we can see that something is not right. The horizontal spacing appears to be correct but the vertical spacing is not. A careful review of our formulas in Mathcad reveals that Actualspacing function is using the QtyWidth Mathcad variable we defined. Since the QtyWidth and the QtyHeight variables will likely have different values, we need to add one more parameter to the Actual Spacing function and provide the value when we calculate the SpacingXIn and SpacingYIn values. ActualSpacm&Overdl1,Pty) :=
O v e r d - OutsideBufFer.2 - HoleDia
QW- 1
Spacmfln := ActualSpacm&Width,QtyWidth) SpacingYIn := ActualSpacm&Height,PtyHeight)
Now that we have added a “Qty” parameter in the Actualspacing function, and provided the parameter in the Spaci ngXIn and Spaci ngY In formulas, the values in the Mathcad Worksheet are updated automatically. We can erase the drawn elements in Microstation and run the DrawFromMathcad procedure again.
780
I Chapter 36: Microstation Leveraging Mathcad via VBA I
0
0
0
0
0
0
0
Q
0
Q
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
Q
0
0
0
a
0
0
Q
0
0
Q
0
0
0
Now the spacing looks correct in Microstation. Let’s add a few dimensions to verify that we are meeting our design criteria as established in Mathcad.
I Driving Microstation Geometry from Mathcad I
781
The Buffer area looks right. But the spacing between the holes isn’t meeting our criteria. A review of the Formulas and Functions in Mathcad shows us that the Cal cul a t e Q u a n t it y Function has one more error in it. We need to calculate the quantity of items, not the number of spaces. So, we will add 1 to the formula so we are retrieving the number of items that fit in the space, instead of the number of spaces. CalculateQuantitdOveraU) :=
Overall - OutsideBufFer-2 - HoleDia HoleDia f MaxSpacing
+I
We fix the formula in Mathcad and run the macro from within Microstation’sVBA environment and the plate is drawn again. Adding a few dimensions to it shows us the result.
782
I Chapter 36: Microstation Leveraging Mathcad via VBA I
1.
0
Now, it looks like the spacing between the holes is meeting our criteria of “MaxSpacing = 50mm”. Changing the Width, Height, Hole Diameter, and OutsideBuffer in Mathcad and running the macro again should yield similar results - a plate drawn in Microstation with holes spaced evenly (in the respective X and Y planes) and spaced within the MaxSpacing rule.
REVIEW Someone once said, “Give me a lever and I will move the World.” A study of the effects of various types of levers shows that with a lever we can move objects that would be impossible to move without the use of the lever. Mathcad and other applications that allow us to communicate with them through ActiveX Automation serve as levers. They allow us to accomplish tasks previously error prone and time consuming with little or no effort. This chapter is by no means a comprehensive reference on the use of Mathcad or its API. An entire book could be devoted to that topic. The goal here is to introduce and demonstrate the ease with which we can take engineering data and calculations from Mathcad and use them in our design work within Microstation.
37
Accessing Data from Exter naI A ppIicat ions We have controlled Microstation from within Microstation’s VBA environment. We have also controlled Microsoft Excel from within Microstation’s VBA environment. It would stand to reason, then, that we can control Microstation from Excel’s VBA environment. And we can. In this Chapter:
ActiveX / COM Basics References, Early Binding, and Late Binding Getobject, Setobject, and ‘New’ What does ‘WithEvents’do for us? When to run macros from within Excel and when to run them from within Microstation Controlling Microstation from within Excel
783
784
I Chapter 37: Accessing Data from External Applications I
ACTIVEX/ COM BASICS What do ‘XctiveX Automation” and “COM” have in common? When viewed in a ‘common’ light, they have everything in common because they are often referred to as the same thing. In essence, when we talk about ActiveX Automation and/or COM, we are talking about a mechanism used to allow one application to ‘talk’to another application. We are talking about one application ‘driving’the other one or ‘deriving’ information from the other one. For years, application vendors have attempted to come up with ways for their customers to customize their software. A large variety of methods have been devised to accomplish this. Dynamic Data Exchange (DDE) was a method whereby one application could send messages to the other. Other software developers have come up with clever ways to simulate the user’s interaction with the software including mouse movement and clicking on the application’s buttons as well as simulating keyboard entry in the other application. And yet others have invented scripting mechanisms that would read a file and follow instructions contained in the file. ActiveX Automation gives us the means to programmatically control applications through the use of the Application’s object model. We traverse the Object Model until we arrive at the Object that exposes the Properties, Methods, and Events with which we want to work. Application Objects have Object Names and Class IDS.Which is easiest to work with? Let’s take a look at the MicroStationDGN Application. Would we rather use “MicroStationDGN.Application” or “{ 121~46499bb6- 1ld4-81ce-0050049e89cb}”?We refer to Classes by their Names instead of by their IDSfor the obvious reason. “MicroStationDGN.Application”,“Excel.Application’: and “Word.Application”are some examples of Class Names we may know. These top-level Objects serve as a starting point and allow us to drill down to Documents, Worksheets, Paragraphs, and the like.
Some Applications have exposed COM Interfaces even though they have not implemented or licensed Microsoft’s VBA environment. So, the fact that an Application does not have VBA embedded into it does not necessarily mean we have no way of communicating with it. The application may very well be open to customization through COM (ActiveX Automation). One example is Internet Explorer.
I References, Early Binding, and Late Binding I
785
Sub RunInternetExplorer( 1 Dim myInet As Object Set myInet = CreateObject("1nternetExplorer.Application") myInet.Visible = True myInet.Navigate2 " h t t p : / / w w w . b e n t l e y . c o m " MsgBox "At Bentley's Website." myInet.Navigate2 " h t t p : / / w w w . m i c r o s o f t . c o m " MsgBox "At Microsoft's Website." End Sub
In the above example, we are 'connecting' to Internet Explorer by creating a new instance of it, making it visible, and displaying two websites in it. Internet Explorer has a COM interface so we can control it using VBA code in Microstation's VBA environment.
REFERENCES, EARLYBINDING, AND LATEBINDING We have added many References in this book so far. And why do we do this? And what can we add a Reference to? In VBA, go to the menu Tools > References.
We have seen this before. Let's talk a little more about what we are actually seeing.
786
I Chapter 37: Accessing Data from External Applications I When we look at the References dialog box, we are looking at References that can be added to our project. These References are stored in files usually having one of three different file extensions: .olb, .tlb, and .dll. If we click the Browse button, we see the Add Reference dialog box.
3corn-dmi 1025 1028 1031 1033 1037
1041 1042 1054 2052 3076
aPPm mt
If we need to add a Reference and it doesn't show up in the References dialog box we can add it by browsing to it and adding it in the Add Reference dialog. Type Libraries can do two things for us. First, they give us the Object Model - the structure of the Objects with their Properties, Methods, and Events. With this comes features such as Intellisense - the lists and tip text boxes that appear as we are writing code. The other thing they do is provide easy access to the Objects and make it possible to perform Early Binding. Early Binding? Late Binding? Binding is the process of taking a variable and setting it to an Object. When we speak of "Early Binding", we mean we declare a variable as a specific type of Object. A variable bound early performs better, allows for intellisense on the variables, and performs type checking as we develop our code. Let's review a snippet of code we wrote earlier: Sub RunInternetExplorer() Dim myInet As Object Set my I net = C r ea t eO b j ec t ( " I n t e r n e t Exp 1 o re r .App 1 i c a t i on " ) myInet.Visible = T r u e myInet.Navigate2 "http://www.bentley.com" MsgBox "At Bentley's Website." myInet.Navigate2 "http://www.microsoft.com"
I References, Early Binding, and Late Binding I
787
MsgBox "At Microsoft's Website." End S u b
Are we doing early binding or late binding here? Late binding. Why? Because we declare the variable mylnet as an Object. Declaring a variable as an Object provides for a generic Object that could be an Internet Explorer Application, a Line Element, or a Database Connection. The variable does not know what type of Object it is until it is Set with code such as: Set myInet
=
CreateObject("InternetExplorer.App1ication")
After this line of code is run, the variable mylnet knows what type of Object it is. When we use late binding, Intellisense is not active because the variable does not know what type of object it is. Sub RunInte rnetExplo re r ( ) Dim myInet As Object Set myInet = Createobject ("1nternetExplorer.Application") myInet.
In the above graphic, when we type 'myInet.' (with a period after the variable name), Intellisense does not show up to help us. If, however, we declare mylnet as an "InternetExplorer",we have Intellisense to help us if we have also added the correct Reference, which in this case is "Microsoft Internet Controls': Sub RunInte r n e t E x p l o r e r ( ) D i m myInet As I n t e r n e t E x p l o r e r S e t myInet = C r e a t e o b j e c t ("1nternetExplorer.Application": myInet.
Now, when we type "myInet." we get Intellisense helping us because we have declared the variable mylnet as a specific type of Object and it is Referenced correctly. Code written that uses Late Binding does work. But code written that uses Early Binding works better, is faster to develop, and easier to debug.
I Chapter 37: Accessing Data from External Applications I GETOBJECT, SETOBJECT, AND NEW When we want to work with an application through ActiveX Automation, we must ‘connect’ to it before we do anything else. There are three ways we do this. For the next few examples, we are going to develop our code in Microsoft Excel’s VBA environment. We are also going to add a Reference to the “Bentley Microstation DGN #.# Object Library’: Sub ConnectToMi c r o S t a t i onA( 1 Dim myMSAppCon As MicroStationDGN.Application0bjectConnector Dim myMSApp As MicroStationDGN.App1ication Set myMSAppCon = Getobject(, “MicroStationDGN.Application0bjectConnector”) Set myMSApp = rnyMSAppCon.App1ication MsgBox myMSApp.Caption Set myMSApp = Nothing Set myMSAppCon = Nothing End Sub
When this code is run, we will either see something like this:
or something like this:
In the first MessageBox, we see Microstation’s Caption. In the second one, we are told “ActiveX component can’t create object’: If we see the first MessageBox, we know the code worked. If we see the second, we know it didn’t work. Why? Because Getobject is used to Get an existing
I Getobject, Setobject, and New I
789
instance of an Object. In this example, Microstation must be running before we can use G e t o b j e c t . Here is our next example. It makes use of C r e a t e 0 b j e c t . Sub
ConnectToMicroStationBO D i m myMSAppCon As MicroStationDGN.ApplicationDbjectConnector D i m myMSApp As MicroStationDGN.Application S e t myMSAppCon = C r e a t eO b j e c t ( " M i c r oS t a t i on DG N .A p p 1 ic a t i on 0 b je c t Con n e ct o r " 1 S e t myMSApp
=
myMSAppCon.Application
myMSApp.Visible
=
True
MsgBox myMSApp.Caption S e t myMSApp
=
Nothing
S e t myMSAppCon
=
Nothing
End Sub
Now, instead of getting an existing instance of Microstation, we create a new instance by using C r e a t e o b j e c t . If we run this macro multiple times, we will end up with multiple instances of Microstation running.
m
A look at the Taskbar shows that ConnectToMicroStationB has been run four times. G e t o b j e c t gets an existing instance of an application. C r e a t e o b j e c t creates a new instance of an application. The New keyword can also be used to create a new instance of an application. And there are two ways to use it. Sub
ConnectToMicroStationCO D i m myMSAppCon As New
~
MicroStationDGN.Application0bjectConnector D i m myMSApp As MicroStationDGN.App1ication S e t myMSApp
=
myMSAppCon.Application
myMSApp.Visible
=
True
MsgBox myMSApp.Caption S e t myMSApp
=
Nothing
790
I Chapter 37: Accessing Data from External Applications I S e t myMSAppCon
=
Nothing
End Sub
We can use the New keyword when we declare an Object-type variable. ConnectToMi c r o S t a t i onC causes a new Microstation application to start. The keyword ‘New’ can also be used as follows: S u b ConnectToMi c r o S t a t i o n D ( 1 D i m myMSAppCon As M i c r o S t a t i o n D G N . A p p l i c a t i o n O b j e c t C o n n e c t o r D i m myMSApp As M i c r o S t a t i o n D G N . A p p l i c a t i o n S e t myMSAppCon
=
New
~
MicroStationDGN.ApplicationDbjectConnector S e t myMSApp
=
myMSAppCon.Application
myMSApp.Visible
=
True
MsgBox myMSApp.Caption S e t myMSApp
Nothing
=
S e t myMSAppCon
=
Nothing
End Sub
In this example, we declare the variable myMSAppCon and then we use the code to create a new instance of Microstation. S e t myMSAppCon
=
New M i c r o S t a t i o n D G N . A p p l i c a t i o n O b j e c t C o n n e c t o r
We have worked with code that gets an existing instance of Microstation as well as code that creates a new instance of Microstation. The code works. Now let’s discuss when we might want to use these mechanisms.
When to use Getobject, Createobject, and New Let’s begin with C r e a t e o b j e c t and New. There are three reasons we may want to create a new instance of Microstation:
1 We need to work with Microstation and would be happy to work with an existing instance if one existed, but Microstation is not currently running. In this case, we probably used GetOb j e c t but received an error so we use either C r e a t e o b j e c t or New (as used in ConnectToMi c r o S t a t i onD) so we can work with Microstation.
2 An instance of Microstation is running but we do not want to interrupt that session with our code so we create a new instance of Microstation.
I Getobject, Setobject, and New I
791
3 Multiple instances of Microstation are or may be running. If we use Getobject, we cannot be certain which instance of Microstation we will 'attach'to, so we use C r e a t e o b j e c t or New and we are 'attached'to
a new instance of Microstation. The second and third circumstances just described are self-explanatory. The first, however, should be demonstrated with some code: Sub
ConnectToMicroStationE() D i m myMSAppCon As MicroStationDGN.ApplicationObjectConnector D i m myMSApp As MicroStationDGN.Application ' A t t a c h t o e x i s t i n g o r c r e a t e new i f no e x i s t i n g M i c r o s t a t i o n On E r r o r Resume N e x t S e t myMSAppCon = G e t o b j e c t ( , "MicroStationDGN.App1 i c a t i o n O b j e c t C o n n e c t o r " ) I f Err.Number
=
4 2 9 Then
S e t myMSAppCon = CreateObject("MicroStationDGN.Application0bjectConnector") Err.Cl e a r
End I f On E r r o r GoTo 0 S e t myMSApp
=
myMSAppCon.Application
myMSApp.Visible
=
True
MsgBox myMSApp.Caption S e t myMSApp
=
Nothing
S e t myMSAppCon
=
Nothing
End Sub
In this example, we are fully aware that Microstation may not be running. We use the statement "On Error Resume Next" so if we do encounter an error, the code will not stop executing. Immediately after attempting to Getobject, we take a look at the Err.Nurnber to see if we encountered an error number of 429 (this is the error number we will get if we attempt to use G e t o b j e c t and the Object we are trying to get does not exist). In the above example, if we do see Error 429, we use C r e a t e o b j e c t and clear the Error. Then we use "On Error GoTo 0" to reset the error handler in VBA.
792
I Chapter 37: Accessing Data from External Applications I
WHAT DOES 'WITHEVENTS' DO FOR US? We use the keyword W i t h E v e n t s when we want to communicate with another application and want to be notified when certain events take place. Let's create a new User Form in Microsoft Excel's VBA environment. We will also add a Listbox to the Form. After this is done, we will enter the following code: Dim myMSAppCon As MicroStationDGN.ApplicationObjectConnector Dim WithEvents myMSApp As MicroStationDGN.App1ication Private Sub UserForm-Initialize0 Set myMSAppCon = Getobject(, "MicroStationDGN.App1 icationObjectConnector") Set myMSApp = myMSAppCon.Application End Sub Private Sub myMSApppOnDesignFileClosed(ByVal DesignFileName As String) ListBoxl.Add1tem "CLOSED & DesignFileName End Sub "
Private Sub myMSApp-OnDesignFileOpened(ByVa1 DesignFileName As String) ListBoxl.AddItem "OPENED & DesignFileName End Sub "
When we declare the variable myMSApp, we do so with the W i t h E v e n t s keyword. This adds the variable myMSApp to the Object ComboBox in the Code area of the Form. When myMSApp is selected in the Object ComboBox in the code area of the Form, the events associated with the Object the variable represents display in the Procedure ComboBox.
I Run Macros from Excel or Microstation? I
793
When the form is run, it attaches to an existing instance of Microstation and each time a file is closed or opened, the code in the appropriate event is executed.
W i t h E v e n t s allows u s to capture events of external applications and
respond to them. In this example, we are adding the names of the files opened and closed in Microstation to a Listbox created in Excel’s VBA environment. The possibilities are endless as to what we can do when we are notified of the events taking place in Microstation. It should be stated here that the same W it h Ev en t s keyword can be used with reference to the Microsoft Excel.Application Object. We can capture events taking place in Excel from within MicroStation’s VBA environment.
RUN MACROS FROM EXCELOR MICROSTATION? When do we run the macros from within Excel and when do we run them from Microstation? If we can control Microstation from Excel and can control Excel from within Microstation, when do we write code in Microstation and when do we write it in Excel? That is a good question. There are several things that we should consider. They are not listed in order of importance because the importance of any particular item probably depends on the situation in which we find ourselves at the moment. For the sake of discussion, we will use Microstation and Microsoft Excel as the two applications in which we are developing. Is Microstation installed on this computer? If we develop in MicroStation’sVBA environment, we know the code will be run on a computer with Microstation installed on it. If we develop
794
I Chapter 37: Accessing Data from External Applications I in Excel, the code is housed in Excel and it is possible that Microstation is not even installed. Communication over the COM pipeline takes time. Not a lot of time, necessarily, but it does take time. If we have a lot of data in Excel and we need to do a lot of work with that data in Excel, an application may take less time to run if it is run from within Excel. However, if a majority of the processing is done in Microstation, the code should be developed inside Microstation’s VBA environment. l l
Are we using G e t o b j e c t or C r e a t e o b j e c t ? Microsoft Excel is used by many developers and at times, instances of Excel are running in the background (even though the application is not visible). And we do not know if it is running in the background unless we look for “EXCEL.EXE” in the Processes tab of the Windows Task Manager. Often times, data will be stored in Excel and an Application will run from within Microstation. The concept is, the user opens the file he/she wants to use in Excel and GetOb j e c t is used inside Microstation’s VBA environment to read the data. However, if an instance of Excel is running in the background, it (the instance running in the background) may be the instance of the Excel.Application object that GetOb j e c t returns and the program will either crash or it uses data other than what the user is expecting. By the way, when using C r e a t e o b j e c t , we should always set the Visible property to True so we don’t end up with applications running in the background.
Choosing the best tool for the job should always be near the top of the list of things to consider before developing an application. We have listed several things worthy of consideration and there are others that may pop up that are specific to a project or task.
CONTROLLING MICROSTATION FROM WITHIN EXCEL We know that Microstation VBA projects are stored in.mvba files. Excel, however, is a little different. When we write VBA code in Excel, the code is stored in a Microsoft Excel Workbook (.As file). Excel VBA programming can also be stored in an Excel Add-In (.Aa file). We will discuss Add-Ins later in this chapter.
I Controlling Microstation from within Excel I
795
Let’s create a new Excel Workbook. We will then get into Excel’s VBA environment by clicking Tools > Macro > Visual Basic Editor. Next, let’s insert a new Module.
(General)
(Occlarations)
VBAProlect (Bookl) McrosoFt Excel Oblectr Sheet1 (Sheetl) Sheet2 (Sheet2) Sheet3 (Sheet3) ThisWor!&ook Modules
The VBA environment in Excel should look a lot like the VBA environment in Microstation. One thing that is different, however, is the Project Window. “Microsoft Excel Objects” are available to us. What are they? These Excel Objects allow us to write code directly into events pertaining to specific Worksheets or the Workbook. So, we have a Code Module inserted. And we want to write code that communicates with Microstation. What is our next step? We should add a Reference to the “BentleyMicrostation DGN #.# Object Library”.
With the Reference in place, it is time to write some simple code that communicates with Microstation. Sub XLMSAO Dim myMSAppCon As MicroStationDGN.ApplicationDbjectConnector Dim myMSApp As MicroStationDGN.App1ication Dim myLevel As Level Dim CurRow As Long Set myMSAppCon = Getobject(, “Mi croStati onDGN .Appl i cati on0bjectConnector”) Set myMSApp = myMSAppCon.Application CurRow = 2 For Each myLevel In myMSApp.ActiveDesignFile.Leve1s Sheetl.Cells(CurRow, 1) = myLevel .Number ~
796
I Chapter 37: Accessing Data from External Applications I Sheetl.Cells(CurRow, CurRow
=
2)
=
myLevel.Name
CurRow + 1
Next End Sub
When we develop in Excel, some Objects are natively available to us without any effort on our part simply because we are in Excel. We don’t have to declare them or instantiate them, we just use them. In the above example, “Sheetl” is one of these object variables. We declare a variable for the Level and for the Current Row but we don’t need to declare a variable for “Sheetl’: Here are the results o f running ”X LMSA”:
......................
.?iE~!.;!l!i~erra!~.....
.GiBuilding_SiteTerrain .,...... .......... . !jEx~.;!ln4T_e_!ra!nMesh
.?iFrame ....i......... .BiBuildiniSiteMesh .,...... ........... .4iLinks . , ~ ........+.......... !Default
i
The Level number and Name are placed into Excel’s “Sheetl’: Now, instead of reading Levels from Microstation’s ActiveDesignFile we are going to create Levels based on what is in Excel. Let’s create a new design file in Microstation before we continue.
..
4IFrame . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
~
!!eM “”h Level..
?i!Ij!!B.
. . .
. . .
/Links Level
When we run XLMSB with the above data in Excel, we get new Levels created in the new .dgn file. Here is the code: Sub XLMSBO D i m myMSAppCon As M i c r o S t a t i o n D G N . A p p l i c a t i o n 0 b j e c t C o n n e c t o r
D i m myMSApp As M i c r o S t a t i o n D G N . A p p l i c a t i o n D i m m y L e v e l As L e v e l D i m CurRow As L o n g S e t myMSAppCon
=
Getobject(,
-
“MicroStationDGN.App1 i c a t i o n O b j e c t C o n n e c t o r ” ) S e t myMSApp
=
myMSAppCon.Application
I Controlling Microstation from within Excel I
797
CurRow = 2 While Sheetl.Cells(CurRow, 1) <> Set myLevel = myMSApp.ActiveDesignFile.AddNewLeve1 (Sheetl.Cells(CurRow, 2)) myLevel.Number = Sheetl.Cells(CurRow, 1 ) myLevel.Description = Sheetl.Cells(CurRow, 3 ) CurRow = CurRow + 1 Wend End Sub ” ”
After the above code is run, we can see the results in the Level Manager: Deiault ExistingTerrain Building SiteTerrain ExistingTerrainMesh Frame BuildingSiteMesh
D 1 2 3
4 5
Existing Terrain Level Building Site Terrain Level ExistingTerrainMeshLevel Frame Level BuildingSiteMeshLevel
Ckaptei37- ... Chapter37- ... Chapter37- ... Chapter37- ... Chapter37- ... Chapter37- ...
Master Master Master Master Master Master
Running Excel Macros After we develop code in Excel’s VBA environment, we want to give users the ability to use our macros but not to modify our code. Excel’s VBA environment (as with MicroStation’s VBA environment) allows us to ‘lock down’ our projects by going to the Project Properties Protection tab and selecting “Lock project for viewing’: But how do we execute macros in Excel when the project is ‘locked down’ or if we do not want our users entering the VBA environment to execute macros? Let’s begin by creating a custom button in Excel.
798
I Chapter 37: Accessing Data from External Applications I 1 In Excel, right-click on any Toolbar button and click Customize. The Customize dialog box opens.
2
Click the New button to begin the steps to create a new toolbar in Microsoft Excel.
3 Name it “Microstation VBA” and click OK. And now for a warning: the new toolbar is very small because it does not have any buttons or menu items in it.
4 Next, change to the Commands tab and select “Macros”in the Categories Listbox. This brings up two ‘Commands’- “Custom Menu Item” and “Custom Button’:
I Controlling Microstation from within Excel I 5
799
Drag and drop “Custom Menu Item” into the new toolbar we just created.
There is a temptation at this point to click the Close button because we have successfully dragged a new Menu Item to our new Toolbar. But key to proceeding now is to keep the Customize dialog box open.
6 Next, right-click on the new Menu Item and take a look at the context menu. P
800
I Chapter 37: Accessing Data from External Applications I 7 The Name property specifies what the menu item will display. At this point it is set to “Custom Menu Item’: Let’s change it to “&Get DesignFile Levels’:
8 Another right-click on the menu item allows us to ‘Xssign Macro’:
9 We can select a macro and click the OK button. This assigns the selected macro to the button. From this point on, any time the button is clicked, the macro X LMSA is run in Excel. Now that we have renamed and assigned the Menu Item we can run it after we close the Customize dialog box.
10 The macro we are going to run populates cells in Sheetl. Before we proceed, let’s delete the data in Sheetl. After we do this we can click the Get DesignFile Levels button just to make sure everything works correctly.
11 Now we are going to save the Excel file and close it. After the workbook is closed we will begin a new workbook. Then we will click the Get DesignFile Levels button again. What happens next depends on your Security settings in Excel. We may see something like this:
I Controlling Microstation from within Excel I
801
When we assigned a macro to the new menu item, it was assigned to a macro in a specific file. That link to the file carries over even after the workbook containing the macro is closed. If Book7.xls is closed and we go back to the Assign Macro dialog, we see the following:
So, even if we close the Excel file in which the macro is written, when we click the button that links to the macro, Excel opens the workbook so it can run the macro. If we 'Enable Macros' (if we are prompted to do so), the macro is run and the Levels are entered. But where are they entered? They are entered into the Book7.xls file even though we may have had a different workbook open when we clicked the button. Let's take another look at the code that is running: Sub XLMSAO
D i m myMSAppCon As MicroStationDGN.ApplicationObjectConnector D i m myMSApp As MicroStationDGN.Application D i m m y L e v e l As L e v e l D i m CurRow As L o n g
S e t myMSAppCon
=
Getobject(,
~
" M i c r o S t a t i onDGN . A p p l ic a t i o n 0 b j e c t C o n n e c t o r " ) S e t myMSApp CurRow
=
=
myMSAppCon.Application
2
myMSApp.ActiveDesignFile.Leve1s 1) = m y L e v e l .Number S h e e t l . C e l l s ( C u r R o w , 2) = m y L e v e l .Name CurRow = CurRow + 1
F o r Each m y L e v e l I n
Sheetl.Cells(CurRow,
Next End Sub
I Chapter 37: Accessing Data from External Applications I So, why are the Levels being written to Bookl.x/s instead of in the new Workbook? Because we are writing to "Sheetl" directly and this object is pre-defined as belonging to the workbook in which the code is written.
Let's copy and paste the macro XLMSA and rename it to XLMSC. Now we will make a couple of changes to it so the Levels are written to the workbook that is open when the menu item is selected: Sub X L M S C O Dim myMSAppCon As MicroStationDGN.ApplicationObjectConnector Dim myMSApp As MicroStationDGN.Application Dim myLevel As Level Dim CurRow As Long Set myMSAppCon = Getobject(, "MicroStationDGN.App1 i c a t i o n D b j e c t C o n n e c t o r " ) Set myMSApp = myMSAppCon.Application CurRow = 2 For Each myLevel In rnyMSApp.ActiveDesignFi le. Levels ActiveSheet.Cells(CurRow, 1) = myLevel .Number ActiveSheet.Cells(CurRow, 2) = myLevel .Name CurRow = CurRow + 1 Next End Sub
The changes are small but the significance of the changes are not. Let's now assign XLMSC as the macro to run when the menu item is selected. After we do so, the workbook Book7.xls is still opened when the menu item is selected but the Levels are placed into the worksheet that was active when the menu item was clicked. So, the code is working better but to have our Workbook open inside Excel each time someone clicks the button in Excel can be improved. It makes perfect sense that prior to running a macro, the file in which the macro resides should be opened. But when we click a menu item we expect code to run, not workbooks to open and dialog boxes to be shown asking us to enable macros. So, what is to be done?
I Controlling Microstation from within Excel I
803
1 Let’s open Book7.xls in Excel. Then save the file Bookl.x/s as an Excel Add-in. Let’s use the filename MicroStutionVBA Add-/n.x/u.
2
Saving a Workbook (.xls file) as an Add-In (.xla file) performs a sort of “SaveAs Copy” in that the original .xls file remains open. So, after we save the file as, we can close down the original Book7.xls file and begin a new workbook.
3 Next go to the Excel menu and select Tools > Add-Ins.
.
804
I Chapter 37: Accessing Data from External Applications I 4
Let’s browse to the new .xla file we just created.
L
5 After selecting the file, click OK and the file displays in the AddIns dialog and is selected. This loads the file into memory (we can work with the file in VBA but not in the Excel Worksheet interface) so when the menu item is clicked, the code is already loaded and ready to run.
6 To finish the Add-In process, click OK in the Add-Ins dialog box. b
7 Now, the Menu Item we added to our custom toolbar is still pointing to Book7.xls. We want it to run the macro in the Add-In file we just created. Let’s go back to the Assign Macro dialog by right-clicking on the Menu Item, selecting Customize, right-clicking on the Menu Item again, and selecting Assign Macro.
I Review I
805
8 We don’t want to run the macro in Book7.xls.We want to run the macro in the Add-In. But macros in Add-Ins don’t show up in the list of available macros, so just type the macro name into the Assign Macro dialog and click OK.
6
9 After clicking OK in the Assign Macro dialog, click the Close button in the Customize dialog box and we can test the Menu Item. When we click the Get DesignFile Levels button now, the macro is executed immediately because the Add-In is already loaded. If we change from “Sheetl” to “Sheet2” and click the menu item again, the Levels are displayed in “Sheet2’: Remember, in our code, we are writing to the ‘XctiveSheet” now so the active sheet in Excel will receive the Level information. When we created the Menu Item, we named it “&Get DesignFile Levels’: The Ampersand (&) character specifies which character will be underlined in the Menu Item. The underlined character means we can hold down the
REVIEW Nearly all of the code that has been written to this point in this book can be run from within Excel’s or Microstation’s VBA environment. When we develop in Excel, we add a Reference to the “Bentley Microstation DGN #.# Object Library’: When we develop in Microstation and wish to work with Excel, we add a Reference to “Microsoft Excel #.# Object Library’: We use GetOb j e c t to get existing instances of the application we wish to attach to and C r e a t e o b j e c t or New if we want to create a new instance of the application with which we want to work.
.
38
Writing VB6 Applications We have written a lot of code in VBA. Now it’s time to write some code in Visual Basic 6. In this Chapter:
B Differences between VBA and VB6 B VB6 Project Basics Controlling Microstation with VB6 Compiling and Distributing Applications
DIFFERENCES BETWEEN VBA AND VB6 VBA and VB6 have a great deal in common. Of course, they both make use of the language, “Visual Basic”. Projects are broken out into Code Modules, User Forms, and Class Modules. Each environment allows References to be added to the project. There are also some differences. In VBA, Code Modules, Classes, and User Forms are all contained in a single .mvba file. In VB6, Code Modules, Classes, and User Forms each have their own file (.bas, .cls, and .frm 807
808
I Chapter 38: Writing VB6 Applications I files) and a single Project file (.vbp) which brings them all together. Microstation VBA projects must be run from within Microstation’s VBA environment. Excel’s VBA projects must be run from within the Excel VBA environment. VB6 projects are compiled into executable programs (.exe) and can be run independent of any other application. VB6 projects can also be compiled into DLL files and ActiveX Controls (.ocx files). Since VBA Projects are self-contained, changes made to Code Modules, Classes, and User Forms in a VBA project remain in the project. Multiple VB6 projects can utilize the same Code Module, Class Module, or User Form file. So changes made to VB6 resources may be reflected in multiple VB6 projects. This is a powerful feature but be careful that changes made to a resource file (.bas, .cls, .frm) in one project do not negatively impact any other project. A VBA project can be distributed by providing a single .mvba file. VB6 projects compile to a single file (.exe, .dll, .ocx) but are best distributed using a Setup program that installs DLLs and other resources (such as the Visual Basic Runtime Libraries) that may be needed for the program to run correctly. Since VBA programs must be run from within the host application, we know the host application is on the user’s computer. Since VB6 compiled programs can run independent of any host application, it is possible that someone could install a program designed for Microstation on a computer without Microstation installed. Even after VBA programs are distributed, it is easy to debug them because debugging can take place and source code can be stepped through on the user’s machine (as long as the code is not ‘Locked’). After a VB6 program is compiled, the code cannot be stepped through or viewed on the user’s machine. The VBA environment is installed with applications such as Microstation and Microsoft Excel. VB6 must be purchased and then installed to use it.
I VB6 Project Structure I
809
VB6 PROJECT STRUCTURE We have already identified the fact that VB6 Projects are similar to VBA projects in that they make use of Code Modules, Class Modules, and User Forms. When we begin a new VB6 Project, there are a number of different types of VB6 projects we can create. We will begin by creating a “Standard EXE’:
L
810
I Chapter 38: Writing VB6 Applications I When we begin a new VBA project, a Code Module is created by default. When we begin a new “Standard EXE” project in VB6, a User Form named “Forml” is created by default.
Projectl (Projectl)
The IDE of VB6 is nearly identical to that of VBA. Here, we can see the Project, Properties, Toolbox, and User Form. Let’s take a couple of controls from the Toolbox and place them on the Form. We will place a ComboBox, a Label and a CommandButton on the Form. At this point, we do not need to do anything to the project. We are simply going to compile the program by going to the VB6 menu and select File > Make Projectl .exe.
Combo1
I VB6 Project Structure I
811
The Make Project dialog box displays and here we specify a file location and a file name. VB98 'MSXM AddIn
RainyDayCode Addin Setup SurnrnaryInForrnation Template ,Wizards
XlsXrnl CLEXE CVPACK.EXE ........................ LINK.:EXE.; VB6.EXE VISDATA.EXE
The file froject7.exe is created and it can be executed whether Microstation is running or not, or whether Microstation is installed or not.
L
When we run our new program, we see the Form we created. It can be Maximized or Minimized. It can be re-sized to any size. There are Form Properties that control the use of these Form sizing functions but by default, Forms are inserted into a project with the ability to Minimize, Maximize, and Resize.
When we save our project, we are asked to supply the names and locations for two files: Form l.frm and Project7.vbp. This differs from VBA where we are only asked to supply the name and location of a single .mvba file. Just to review, we have created three distinct files at this time. We created a .frm file (User Form), a .vbp file (VB6 Project File) and an .exe file (Compiled Executable). When we are ready to distribute our application to others, of these three files, the only file we need to distribute is the .exe file.
812
I Chapter 38: Writing VB6 Applications I Let’s ‘Remove’ the current project we are working on and create a new project in VB6. We remove projects by either shutting down VB6 entirely or by going to the File menu in VB6 and selecting File > Remove Project.
When we create a new project, a new User Form is created for us again. We are not going to use this new Form so we will remove it by going to the Project Window and right-clicking on the Form, then selecting Remove
Projectl (Projectl)
Forml.
After we remove the new default Form, we can see that we now have a project named “Projectl” with nothing in it. Let’s add the Form from the previous project by going to the menu and selecting Project > Add File.
VB6
When we select the Form we saved previously and click Open, it is added to the Project. Double-clicking on the Form in the “Forms”folder opens it so we can edit it.
I VB6 Project Structure I Remember, we are not adding the previous Project, only the Form (a part of the previous Project).
813
Combo1 Combo2
Let’s add another Combobox and Label to the Form and change the size of the Form. The first Project we created was named “Projectl’: This is the name VB 6 gave it. We want to name this one “Project2’: We do this by going to the VB6 menu and selecting Project > Projectl Properties.
Proiectl
Let’s talk about this dialog box. We can see the Project Name is being shown as “Projectl’: Let’s change this to “Project2’: The Startup Object selection is critical. At this point the setting is “Sub Main’: This means when we run this program either at design time or runtime, it looks for a procedure named “Main” in a Code Module. For our example here, we don’t want to start with “Sub Main” because we don’t have a “Sub Main”. We want to select “Forml” from the list. The Startup Object ComboBox contains the names of all User Forms that can be used as Startup Objects. If we had Forms named “Form2” and “Form3” in this Project they would show up in this list as well.
814
I Chapter 38: Writing VB6 Applications I Now that these changes have been made, the Project Properties dialog box should look like this:
When we click OK we can now save our Project. When we click the Save button, we are asked to name and specify a save location for the Project file. VB6 Project1,vbp
The name of the .vbp file defaults to the Project Name set in the Project Properties dialog box. In this case, we will accept the default name of froject2.vbp. We are not asked for the location or name of the Form because it had already been saved in our previous Project. The .frm file is saved with the new controls on it and with its new size properties. Let’s remove Project2 by going to the VB menu and selecting File > Remove Project2. Now, we will open Project1 again.
I VB6 Project Structure I
815
When we open “Projectl” again and look at “Forml” it reflects the changes made when it was being used in “Project2”. This ‘linking’ of program design elements into projects is powerful. But as with most things that are powerful, we should be careful so we don’t abuse it either intentionally or unintentionally. If, for example, we forget that Form1 is being used in two projects, we may make modifications to it in “Project2”that causes it to stop working properly in “Projectl’’. Now, we compiled Projectl before we began Project2. We made changes to Form1 when we were in Project2. What happens when we execute ProjectI.exe? Do we get the new Form or do we get things as they were when we compiled Projectl? Answer: when we compile a program, everything is compiled into the executable as they are at the time the project is compiled. The executable program does not change to reflect modifications made to its design elements. If, however, we open and recompile Projectl, the new executable (.exe) reflects the changes that were made up to the point that we re-compiled the project. This exercise teaches us a few very important principles. Multiple VB Projects (.vbp files) can make use of the same resource file (.frm, .bas, .cls, etc.). Compiled .exe files are static. Changes made to design elements compiled into .exe files are not reflected in the .exe file unless it is re-compiled. By default, compiling an .exe file does not result in saving source .frm, .bas, or .cls files. It is possible to create an entire program using VB6 and compile it without saving any of the source files to disk. Although potentially dangerous, this can be useful when we want to make a fairly significant change ‘just to see what happens’ and we don’t want to lose our ‘working’ code. We can make changes, run it in design mode or compile it, and ‘see what happens’. Only after we know the code works properly do we save our project. On the other hand, if we want VB6 to automatically save our project each time we compile our project, we can change a Tools > Options > Environment Tab > When a program starts setting. Thus far we have created two “Standard EXE” projects. There are two other note-worthy projects and they are:
816
I Chapter 38: Writing VB6 Applications I ActiveX DLL - Allows us to create a ‘program’that can NOT be executed by itself but other programs can ‘connect’to it. An example of an ActiveX DLL would be the “Microsoft ActiveX Data Objects Library’: We cannot ‘execute’it so it runs as an independent program but we can ‘connect’to it to allow easy access of databases. ActiveX Control - Allows us to create our own ‘Controls’that show up in the Toolbox in VBA and VB6. An example of an ActiveX Control is a ComboBox that automatically displays the list of Levels in a .dgn file. We would begin with the basic ComboBox and would write code to populate it. Once completed and compiled (to an .ocx file) we would be able to drop it onto a Form in Microstation’s VBA environment and it would automatically populate with Levels.
CONTROLLING MICROSTATION WITH VB6 We have created a couple of VB6 projects already. We have placed a few controls on a Form and have compiled one of the projects. But the application “Projectl”,even though compiled, does nothing for us. We can click on the ComboBox or on the Button but nothing happens. We can Minimize, Maximize, and Resize the Form but this does not really do anything for us. Let’s look into the steps necessary for us to begin controlling Microstation from our application “Projectl’: The first step to help us to communicate with Microstation is to add a Reference. In VBA we do this with the menu items Tools > References. In VB6 we do this with the menu items Project > References. The list that appears will likely differ from computer to computer because each computer has different software installed on it. The list is
I Controlling Microstation with VB6 I
817
in alphabetical order and we want to browse for “Bentley Microstation DGN #.# Object Library”.
Once selected we can work with Microstation much easier than we could before it was selected. Now, we want to populate the ComboBoxes in our Form before it is displayed. We will write some code in the “Form Load” event (in VBA we would use the Form Initialize Event). Now for the code in the Form Load event: P r i v a t e Sub F o r m - L o a d 0 D i m m y M S t a t i o n C As D i m m y M S t a t i o n As
MicroStationDGN.Application0bjectConnector MicroStationDGN.App1ication
D i m m y L e v e l As M i c r o S t a t i o n D G N . L e v e 1 D i m m y C e l l As M i c r o S t a t i o n D G N . C e l 1 I n f o r m a t i o n D i m myCellInfoEnum As MicroStationDGN.CellInformationEnumerator
S e t myMStationC
=
Getobject(,
-
“ M i c r o S t a t i o n D G N . A p p 1 icationObjectConnector”) Set myMStation
=
myMStationC.App1ication
’ Populate Levels F o r Each m y L e v e l I n myMStation.ActiveDesignFile. L e v e l s Combol.AddItem myLevel.Name Next ’ Populate Cell s
I f myMStation.IsCel1LibraryAttached Set myCellInfoEnum
=
=
T r u e Then
~
myMStation.GetCellInformationEnumerator(True,
True)
818
I Chapter 38: Writing VB6 Applications I While myCellInfoEnum.MoveNext Set myCell = myCellInfoEnum.Current ComboP.AddItem myCell.Name myCellInfoEnum.MoveNext Wend End If End S u b
Frame
Let’s review the code now. We know we want to connect to Microstation. We do this by using Getobject and use the MicroStationDGNApplicationObjectConnector as the Class we ‘Get: Then we get the Application Object from the ApplicationObjectConnector. Once we have the Application of the ApplicationObjectConnector, we are able to work with Microstation in the same way we do in VBA. We place the name of each Level in the ActiveDesignFile into “Combol’: If a CellLibrary is attached, we add the name of each Cell in the Library to “Combo2”. And what happens when the user clicks the Button? Private Dim Dim Dim Dim Set
Sub CommandlLCl ick( myMStationC As MicroStationDGN.ApplicationObjectConnector myMStation As MicroStationDGN.App1ication myCell As CellElement CellInsPt As Point3d myMStationC = GetDbject(, “MicroStati onDGN .Appl i cati onDbjectConnector”) Set myMStation = myMStationC.Application Set myCell = myMStation.CreateCel1Element3(Combo~.Text, CellInsPt, True) myCell.Leve1 = myMStation.ActiveDesignFile.Levels(Combo1.Text) myMStation.ActiveModelReference.AddElement myCell
End S u b
When the CommandButton is clicked, we insert the selected cell at (0, 0, 0) on the selected level.
I Controlling Microstation with VB6 I
819
Each time we use Getobject, it takes time to connect to Microstation. Let's make a few modifications to our code so we don't need to connect to the MicroStationDGN.ApplicationObjectConnector every time we click the button. 'General Declarations Area of Form Dim myMStationC As MicroStationDGN.Application0bjectConnector Dim myMStation As Mi croStationDGN.App1 i cation
Private Dim Dim Set
Sub Commandl-Click0 myCell As CellElement CellInsPt As Point3d myCell = myMStation.CreateCel1Element3(Combo~.Text,CellInsPt, True) myCell.Leve1 = myMStation.ActiveDesignFile.Levels(Combo1.Text) myMStation.ActiveModelReference.AddElement myCell End Sub Private Dim Dim Dim
Sub Form-Load0 myLevel A s Mi croStati onDGN. Level myCell As MicroStationDGN.Cel1 Information myCellInfoEnum As MicroStationDGN.CellInformationEnumerator Set myMStationC = Getobject(, " M i croStati onDGN .Appl i cati on0bjectConnector") Set myMStation = myMStationC.App1ication ' Populate Levels Fo r Each my Le ve 1 I n my MS t a t i on .Act i ve Des i gn Fi 1 e . Leve 1 s Combol.AddItem myLevel.Name Next ' Populate Cell s If myMStation.IsCellLibraryAttached = True Then Set myCellInfoEnum = -
myMStation.GetCellInformationEnumerator(True,
While myCellInfoEnum.MoveNext Set myCell = myCellInfoEnum.Current ComboP.AddItem myCell.Name myCellInfoEnum.MoveNext Wend End If End Sub
True)
820
I Chapter 38: Writing VB6 Applications I By declaring the variables myMStationC and myMStation in the General Declarations area of the Form, these variables are accessible in other areas of the Form such as the CommandButton Click Event as long as they are initially Set in the Form Load event. We can perform a test run of our Application while still within VB6. Clicking the Start button in the VB6 toolbar, using the VB6 menu Run > Start, or pressing the
Level 1 Level 2 Level 3 Level 4 Level 5 Level 6 Level 7 Level 8 Level 9 Level 10 Level 11 Level 12
I Controlling Microstation with VB6 I
821
When we select a drive and a folder, the DGN files in the selected folder display in the File list. When a file is selected, the Levels in the file are shown in the Listbox. The Listbox is available in VBA. The Drive Listbox, Directory Listbox, and File Listbox are not. We can see the Drive, Directory, and File Listboxes in the Toolbox. These are three of the Controls that are available to us in VB6 but are not available in the Standard VBA Toolbox. Let's take a look at the code now that we know what the program is going to do: 'General D e c l a r a t i o n s Area D i m m y M S t a t i o n C As
MicroStationDGN.Application0bjectConnector
D i m m y M S t a t i o n As M i c r o S t a t i o n D G N . A p p 1 i c a t i o n
P r i v a t e Sub F o r m - L o a d 0 S e t myMStationC
=
Getobject(,
-
"MicroStationDGN.App1icationObjectConnector") Set myMStation
=
myMStationC.App1ication
End Sub P r i v a t e Sub D r i v e l - C h a n g e 0 Dirl.Path
=
Drivel.Drive
L i s t l .C1 e a r End Sub P r i v a t e Sub D i r l - C h a n g e ( ) F i 1e l . P a t h
=
D i r l .Path
L i s t l .C1 e a r End Sub P r i v a t e Sub F i l e l - C l i c k 0 D i m m y L e v e l As M i c r o S t a t i o n D G N . L e v e 1
D i m myDF As M i c r o S t a t i o n D G N . D e s i g n F i l e S e t myDF
=
myMStation.OpenDesignFileForProgram(
Dirl.Path & "\" & Filel.FileName, L i s t l .C1 e a r F o r Each m y L e v e l I n m y D F . L e v e l s
True)
822
I Chapter 38: Writing VB6 Applications I L i s t l . A d d I t e m m y L e v e l .Name Next myDF.Close End S u b
When the Form is Loaded, we attach to Microstation. When the Drive changes, we update the Directory Listbox to reflect the change. When the Directory Listbox changes, we update the File Listbox to reflect the change. When the user clicks on a .dgn file in the File Listbox, we open it ‘ForProgram’ and add its Levels into the Listbox. Opening a file by using OpenDesignFi 1 eForProgram opens the file in memory without opening it in Microstation’s editor. Since we are not going to be making any changes to the .dgn file, we open it as Readonly Let’s name the Form “Form3” and the Project “Project3’: After saving the Project, we can compile it and run it.
Creating an ActiveX Control in VB6 VBA gives us a number of standard Controls. Listbox, Textbox, Combobox, Checkbox, CommandButton. When we work with Microstation, we may see user GUI controls we wish we had in VBA. One of the benefits of VB6 is that we can create our own controls in VB6 and then use them in VBA. An entire book could be written on this topic alone (actually, entire books have been written on this topic). So, we w ill create a simple control that can be used in VB6 or in VBA. This control w ill display the Models and Levels in the Active Design File. This w ill be done with a TreeView control. Here are the steps to creating this control:
I Controlling Microstation with VB6 I
823
1 Create a new “ActiveXControl” project in VB6.
2
3
Change the Name of the Control to “msvba-mo deltree”. Right-click on the Toolbox,
ACCeZK3_._.
8
T-
-- --
#gnabls. _ _ . _ . _ . ;!?!re. .
i ! !False
click “Components’: and AutoRedraw select “MicrosoftWindows Common Controls 6.0” in the list. Then click the OK button. Appearance_ _ . _ . .. ?!:
824
I Chapter 38: Writing VB6 Applications I 4 Add a TreeView control to the UserControl area and name it “tvl”. 5
Change the “Style”property of the TreeView to “7 tvwTreelinesPlusMinusPictureText’:
6 Add a Reference to “Bentley Microstation DGN 8.9 Object Library” in the Control Project (Project > References). We now have the framework for our new Control.
Sample Node Sample Node Sample Node Samole Node
So, we have a Control named ‘msvba-modeltree’ with a TreeView control on it named ‘tvl: What do we want this control to do?
Display the Models in the Active Design File in the Tree View under a top Node of “Models”. Display the Levels in the Active Design File in the Tree View under a top Node of “Levels’: Create an Event named “SelectionChanged” when the user clicks on a Node in the Tree View and return the Node object that is selected. Institute a method named “GetModel” and another named “GetLevel”that returns the Model or Level Object that is selected in the Tree View. Allow the user (actually the programmer) to resize the control and have the Tree View change size accordingly. Here is all of the code in the Control: ‘General D e c l a r a t i o n s Area
I Controlling Microstation with VB6 I
825
Dim myMSApp As MicroStationDGN.App1ication Dim myMSAppCon As MicroStationDGN.Application0bjectConnector Event SelectionChanged(Se1ectedNode As Node) Private Dim Dim Dim Set
Sub UserControl-Initialize0 myModel As M i c r o S t a t i o n D G N . M o d e 1 R e f e r e n c e myNode As Node myLevel As MicroStationDGN.Leve1 myMSAppCon = Getobject(, "MicroStationDGN.App1 i c a t i o n O b j e c t C o n n e c t o r " ) Set myMSApp = myMSAppCon.Application 'Models Set myNode = tvl .Nodes .Add(, , "tvlModel s " , "Models") For Each myModel In myMSApp.ActiveDesignFile.Mode1s tvl.Nodes.Add "tvlModels". tvwChild. "modL" & myModel .Name. myModel .Name Next myNode.Sorted = True ' Level s Set myNode = tvl.Nodes.Add(, , "tvlLevels", "Levels") For Each myLevel In myMSApp.ActiveDesignFi 1 e. Levels ~
tvl.Nodes.Add "tvlLevels", tvwChild, "lvl-"
&
myLevel .Name,
myLevel .Name Next myNode.Sorted End Sub
=
True
Private Sub UserControl-Resize() tvl.Width = UserControl.Width - tvl.Left * 2 tvl.Height = UserControl.Height - tvl.Top * 2 End Sub Private Sub t v l C l i c k 0 RaiseEvent SelectionChanged(tvl.Selected1tem) End Sub Function G e t L e v e l O As MicroStationDGN.Leve1 Dim xSplit As Variant xSpl i t = Spl it(tvl.SelectedItem. Full Path, " \ " I Select Case UCase(xSplit(0)) Case MODE LS "
"
-
826
I Chapter 38: Writing VB6 Applications I Set GetLevel
Cas e
"
=
Nothing
LEV E LS "
I f UBound(xSp1it)
>
Set GetLevel
0 Then =
myMSApp.ActiveDesignFile.Levels(xSplit(1)) Else Set GetLevel
=
Nothing
End I f End S e l e c t End F u n c t i o n F u n c t i o n G e t M o d e l O As M i c r o S t a t i o n D G N . M o d e l R e f e r e n c e
D i m x S p l i t As V a r i a n t "\") x S p l i t = S p l it ( t v 1 . S e l e c t e d I t e m . F u l l P a t h , S e l e c t Case U C a s e ( x S p l i t ( 0 ) )
Cas e
"
MO D E LS "
I f UBound(xSp1it)
>
S e t GetModel
0 Then =
myMSApp.ActiveDesignFile.Models(xSplit(l1) Else S e t GetModel
=
Nothing
End I f Cas e
"
LEV E LS "
S e t GetModel
=
Nothing
End S e l e c t End F u n c t i o n P r i v a t e Sub U s e r C o n t r o l L T e r m i n a t e O S e t myMSApp
=
S e t myMSAppCon
Nothing =
Nothing
End Sub
Now, let's take a look at the Events in which we have placed this code to make sure we are clear on what is happening.
Private Sub UserControl-Initialize0 When the Control is Initialized, we get the Microstation Application Object and the Models and Levels of the ActiveDesignFile. We place the Model and Level names in the Tree View named 'tvl:
I Controlling Microstation with VB6 I
827
Private Sub UserControl-Resize() When the Control is resized, we change the size of the Tree View based on the Control’s width and height and the Tree View’s Left and Top properties.
Private Sub tvl-Click() When the user clicks on a Node in the Treeview, we raise the Sel e c t i onC h a nged Event and provide this event with the Node that is selected in the Treeview. Note that the Event Sel e c t i onchanged is declared in the General Declaration area of the Control.
Function GetLevelO As MicroStationDGN.Leve1 When the Control is added to a Form in VB6 or in VBA, we can use the G e t l e v e l method of our Control and the Control returns the selected Level or ‘Nothing’ if the selected node in the Tree View is not a Level.
Function GetModelO As MicroStationDGN.ModelReference Similar to GetLevel, GetModel returns a Model Object if one is selected in the Tree View.
Debugging ActiveX Control Projects We have just created the framework for a new ActiveX Control. How can we test it to see if it works? We can compile it, and then insert it into a new VB6 or VBA project. But that would force us to re-compile each time we find a bug or other error. It would also keep us from stepping through the code to aid in our development. VB6 gives us the ability to create a Project Group. Project Groups are, as the name suggests, a group of individual VB6 projects that are opened in the same session of VB6. Before we create a new Group, we should save our ActiveX Control Project. And before we save our Project, we need to give it a name in the Project > Properties dialog. We will name it “msvbaControls’: When we save an ActiveX Control project, we save the Project (.vbp file) and the Control in the Project (.ctl file). Let’s save the Project and Control with the file name “msvba-modeltree”.
828
I Chapter 38: Writing VB6 Applications I Now we are ready to add a new Project to our Control’s Project, thus creating a Project Group. Select File > Add Project. (Don’t click “New Project”;we need to Add a Project.) Select “Standard EXE’: Go to the VB6 menu and select File > Save Project Group. We are asked for a Project Group file name. Save the project as “msvba-modeltree’: We are also asked to give the new Project and Form a file name. Save the new Project and Form as “testingmodeltree’: x
When we look at the Project window now in VB6 we will see that we have two projects loaded. When we are working in a Project Group, one of the projects is set as the “Start Up Project’: Controls cannot be ‘executed’ by themselves so we will set our new ‘testingmodeltree’ project as the Start Up Project. We do this by right-clicking on the Project and selecting “Set as Start Up‘: Now, in the Project window, double-click on the Control we just created so that it displays. Next, we are going to close the Control by clicking the Close button at the top of the window.
Sample Node Sample Node Sample Node Sample Node
I Controlling Microstation with VB6 I
829
Why did we do this? Because we cannot add the Control in the new Project we created when the Control is open. We do not need to unload the Project, we only need to close the Control window. Now, when we select our new Project’s Form and look at the Toolbox we should see our Control in the list. Clicking on our new control and dragging it into our new Form initializes the Control.
Models Levels
When we run our new Project, the Form is displayed and the Tree View is available.
P
At this point, we have created a new ActiveX Control and have added a new Project that makes use of the Control. Let’s add a little bit of code now to the new Project so we can test the Event and Methods of the Control. We also need to add a Reference to the “Bentley Microstation DGN Object Library’:
830
I Chapter 38: Writing VB6 Applications I P r i v a t e Sub msvba-model t r e e l - S e l e c t i o n C h a n g e d ( S e l e c t e d N o d e As MSComctl L i b . Node) D i m m y L e v e l As M i c r o S t a t i o n D G N . L e v e 1 D i m myModel As M i c r o S t a t i o n D G N . M o d e l R e f e r e n c e
MsgBox S e l e c t e d N o d e . T e x t & v b C r & S e l e c t e d N o d e . F u l l P a t h S e t myLevel
=
msvba-model t r e e 1 . G e t L e v e l
If m y L e v e l Is N o t h i n g
=
F a l s e Then
MsgBox m y L e v e l .Name & v b T a b & m y L e v e l .Number, End
, “LEVEL”
If
= msvba-modeltreel.GetMode1 If myModel Is N o t h i n g = F a l s e T h e n
S e t myModel
MsgBox myModel .Name & vbTab & myModel . D e s c r i p t i o n , , “MODEL” End
If
End Sub
Here is the “SelectionChanged” event we created in our Control. We display the selected node’s Text property and its FullPath property. Then we use the GetLevel and GetModel methods.
Compiling our ActiveX Control Before we compile our ActiveX Control into an .ocx file, we must set the ActiveX Control Project as the “Start U p Project. After we do this we can select File > Make msvba-modeltree.ooc from the menu in VB6. Let’s compile it into the Control Project’s folder so we know where it is. Compiling an ActiveX Control is a little different than compiling a Standard EXE. Of course, the file extension is different, but the Control is also “registered” on the development computer. This registration step must be done manually or with a Setup program on any computer where the control is to be used. We use the command “RegSvr32.exe” in the Command Window to register the control. In Windows, go to the Start button and click the Run shortcut. When presented with the Run dialog box, type “cmd” and click OK.
Now, type:
I Controlling Microstation with VB6 I regsvr32.exe “C:\MicroStation
831
VBA\msvba-mode1tree.o~~’’
and press the
This registration step is necessary on any computer where we want to make use of the Control. If this registration step is not performed, the Control will not be available. Now that we have compiled our ActiveX Control, let’s take a look at it inside MicroStation’sVBA environment. After inserting a new Form in a Microstation VBA Project, right-click in the Toolbox and select Additional Controls. m
MSCustornLog Control MSDTHostCtrl Class MSDTHostCtrl Class MSDVDAdrn Class MSFlexGriMizard Subwizard Msie Control MSNCSALog Control MsnMusicStatusUi Class MSODBCLog Control MSREdit Class
Our new Control should show up in the list. When selected, it displays in the Toolbox inside VBA. Drag and drop the Control on to the Form and size it. Then press the
832
I Chapter 38: Writing VB6 Applications I A MessageBox may display warning us of the use of this ActiveX Control. After getting past this dialog box, the Form is displayed and the Control is populated with the Models and Levels in the Active Model Reference. Creating ActiveX Controls is fairly simple and straight forward. We have created one that interacts with Microstation. So, the next time we see a User Interface in Microstation or another application we wish we could use, we can create our own control complete with its own Properties, Methods, and Events. These custom controls can be used from within VB6 and in VBA.
Creating ActiveX DLLs We just finished creating an ActiveX Control. ActiveX Controls allow us to create custom user interface Controls based on existing controls, some of which are not normally available in VBA. Now we are going to look at creating an ActiveX DLL. ActiveX DLLs are compiled .dll files that we can access through VB and VBA applications. They are often used to ‘wrap‘ functionality into a single .DLL file and are called a ‘wrapper’when this is done. They can be created to isolate specific logic and calculations from general application logic. Let’s create a new ActiveX DLL file now by starting a new VB6 Session and selecting “ActiveX DLL” as the type of project we want to create.
I Controlling Microstation with VB6 I
833
When we create a new “Standard EXE” project, a new Form is created by default. When we create a new “ActiveX DLL”, a new Class Module is created by default. The names of the Project (set in the Project Properties window) and the Class Modules are extremely important when creating an ActiveX DLL. When we look at the Microstation Object Model we can see a top-level Object named MicroStationDGN. Just under this Object is the Application Object. If we were to create a structure similar to this we would name our project “MicroStationDGN” and the Class Module “Application? Since we don’t want to confuse our new DLL‘s Object Model with that of Microstation’s, we will use a different naming convention. Let’s name our Project “msvba-WinAPI” and the Class Module “System”.
I Chapter 38: Writing VB6 Applications I
In addition to naming the Project in the Project Properties dialog box, the Version Compatibility should be set to "Project Compatibility". The code we place in the Class Module named "System" is entered similar to code entered into any Class Module. Here is the code in total: P r i v a t e D e c l a r e F u n c t i o n GetSystemMetrics L i b "user32" ( B y V a l nIndex As L o n g ) As L o n g
~
P r i v a t e D e c l a r e F u n c t i o n GetComputerName L i b " k e r n e 1 3 2 " A l i a s " G e t C o m p u t e r N a m e A " ( B y V a l l p B u f f e r As S t r i n g , n S i z e As L o n g ) As L o n g
-
P r i v a t e D e c l a r e F u n c t i o n GetDiskFreeSpace L i b "kerne132" A l i a s "GetDiskFreeSpaceA" ( B y V a l 1pRootPathName As S t r i n g , 1 p S e c t o r s P e r C l u s t e r As L o n g , 1 p B y t e s P e r S e c t o r As Long,1 p N u m b e r O f F r e e C l u s t e r s As L o n g , 1 p T o t a l N u m b e r O f C l u s t e r s As L o n g ) As L o n g P r i v a t e D e c l a r e F u n c t i o n G e t L o g i c a l D r i v e S t r i n g s L i b "kerne132" A l i a s " G e t L o g i c a l D r i v e S t r i n g s A " ( B y V a l n B u f f e r L e n g t h As L o n g ,
-
~
-
~
B y V a l l p B u f f e r As S t r i n g ) As L o n g P r i v a t e D e c l a r e F u n c t i o n Logonuser L i b "Advapi32" A1 i a s "LogonUserA" ( B y V a l 1 p s z U s e r n a m e As S t r i n g , B y V a l 1 p s z D o m a i n As S t r i n g , B y V a l 1 p s z P a s s w o r d As S t r i n g , B y V a l d w L o g o n T y p e As L o n g , B y V a l d w L o g o n P r o v i d e r As L o n g , p h T o k e n As L o n g ) As L o n g Private Declare Function ShellExecute L i b "she1132.dll" A l i a s "She1 1 E x e c u t e A " (ByVal hwnd As Long, B y V a l l p o p e r a t i o n As S t r i n g , ~
-
-
-
-
~
-
-
I Controlling Microstation with VB6 I ByVal ByVal ByVal ByVal
835
l p F i l e As S t r i n g , l p p a r a m e t e r s As S t r i n g , 1 p D i r e c t o r y As S t r i n g , nShowCmd As L o n g ) As Long
C o n s t SM-CXSCREEN
=
0
C o n s t SM-CYSCREEN
=
1
Function Version() As String Version
=
App.Major &
"." &
App.Minor
End F u n c t i o n
Sub ScreenSize(ByRef x As Long, ByRef y As Long) x
=
GetSystemMetrics(SMLCXSCREEN)
y
=
GetSystemMetrics(SM-CYSCREEN)
End S u b
Function ComputerNameO As String D i m CompName As S t r i n g
CompName
=
Space(255)
GetComputerName CompName, Len(CompName) ComputerName
=
Left(CompName,
I n S t r ( 1 , CompName, C h r ( 0 ) )
-
1)
End F u n c t i o n
Function DiskFreeSpace(DriveLetterAs String, DiskTotalAs Variant) - As Variant D i m S e c t o r s P e r C l u s t e r As V a r i a n t D i m B y t e s P e r S e c t o r As V a r i a n t
D i m F r e e C l u s t e r s As V a r i a n t D i m T o t a l c l u s t e r s As V a r i a n t GetDiskFreeSpace D r i v e L e t t e r , S e c t o r s P e r C l u s t e r , BytesPerSector, FreeClusters, Totalclusters DiskTotal
=
BytesPerSector
DiskFreeSpace FreeCl u s t e r s
=
*
SectorsPerCluster
BytesPerSector
*
*
End F u n c t i o n
Function LogicalDrives()As Variant D i m L D r i v e s O As S t r i n g
ReDim L D r i v e s ( 0 ) As S t r i n g D i m D r i v e B u f f As S t r i n g
D i m X S p l i t As V a r i a n t D r i v e B u f f = Space(255) GetLogicalDriveStrings
Totalclusters
SectorsPerCluster
Len(DriveBuff),
DriveBuff
*
836
I Chapter 38: Writing VB6 Applications I XSplit
Split(DriveBuff, Chr(0))
=
ReDim L D r i v e s ( 0 To U B o u n d ( X S p 1 i t ) For I
=
-
2)
L B o u n d ( L D r i v e s 1 To U B o u n d ( L D r i v e s )
LDrives(1)
=
XSplit(1)
Next I LogicalDrives
=
LDrives
End F u n c t i o n
Function CheckLogon(UserNameAs String, Password As String) As Boolean CheckLogon
=
LogonUser(UserName,
I"',
P a s s w o r d , 2 , 0 , 0)
End F u n c t i o n
Sub WinOpenFile(Fi1eName As String) ShellExecute 0, "OPEN",
FileName,
I"',
I"',
0
End Sub
Testing an ActiveX DLL is similar to testing an ActiveX Control. We Add a new Project (Add Project, not New Project) to our existing project, creating a Project Group. Then in the new "Standard EXE" project, we add a Reference to the ActiveX DLL we just finished creating:
Visual Basic For Applications Visual Basic runtime oblects and procedures Visual Basic oblects and procedures OLE Automation
Once the Reference is added, we use it as we have many other References in this book. If we add a CommandButton to the form in the new testing EXE project and go to the Click event of the button we can enter the following code: P r i v a t e Sub CommandlLCl i c k (
D i m myMsVBA As New m s v b a L W i n A P I . S y s t e m D i m X S As Long D i m Y S As Long D i m X D r i v e s As V a r i a n t
D i m I As L o n g XDrives
=
myMsVBA.LogicalDrives
MsgBox J o i n ( X D r i v e s ,
'I,
" 1 , , " D r i v e s On S y s t e m "
I Compiling and Distributing Applications I For I
=
837
LBound(XDrives) To UBound(XDrives)
MsgBox X D r i v e s ( 1 ) & v b C r &
-
myMsVBA.DiskFreeSpace(CStr(XDrives(I)),
01, ,
~
" D r i v e Free Space" Next I MsgBox myMsVBA.ComputerName,
, " C o m p u t e r Name"
myMsVBA.ScreenSize X S , Y S MsgBox XS & " ,
"
& YS,
, "Screen R e s o l u t i o n "
Msg B o x my MsVBA . C h e c k L o g o n ( "Admi n is t r a t o r " , " a d m i npwd " , , " LOG 0 N "
~
myMsVBA. W i nOpen F i 1 e " c : \ a b c d e f g . p d f " End Sub
We have discussed previously the fact that we can place code into Class Modules and use them in our code. When we compile the Class into an ActiveX DLL, it makes the code much easier to use. Multiple VB and VBA projects can now 'attach' to our ActiveX DLL. Just as with the ActiveX Control, this DLL file must be registered by using Regsvr32.exe before it can be used on computers other than the one on which we are developing the DLL. When we compile the DLL file in VB6, VB6 registers it for us. This ActiveX DLL can be used by other VB6 Applications as well as VBA Applications developed in Microstation VBA.
COMPILING AND DISTRIBUTING APPLICATIONS When we are writing code in a Microstation VBA project (mvba file), we can simply give our users the file and if we are not using custom ActiveX Controls, in most circumstances, everything will work well. When we are writing code in VB6, we need to compile the Application. And even after we compile the application, there is usually more that we need to do. Let's first examine compiling VB6 programs and then we will deal with distributing our applications written in VB6.
838
I Chapter 38: Writing VB6 Applications I CornpiIing A ppIicat ions When it is time to compile an Application in VB6, select File > Make
...
The text we see after the word “Make” differs based on the name of the project and what type of project we are working in. In addition to seeing “Make” and a project name we may also see “Make Project Group”. Make Project Group is used to compile all Applications in the current Group. So, compiling an application is very simple. We can see the menu items here. But when should we compile?
It is possible to create an entire application without compiling until the time comes that we are ready to distribute it. In the case of an ActiveX Control and an ActiveX DLL, we can Add a Project to the Project Group and test the Control or DLL without compiling. If, however, we want to test a Control or DLL in another application such as Microstation’s VBA environment, we need to compile it. When it comes to Standard EXE projects, compiling a project may be useful during the development process to test performance. When we run a program in Design Mode (when we can break into the application, step through code, etc.), VB6 applications run much slower in design mode than when they are compiled. By default, compiling a VB6 application causes a more thorough check of our code by the compiler. Errors in Modules, Forms, Procedures, Functions, etc., that were not identified when we ran the program in Design Mode will surface when we compile a project. Compiling a VB6 project usually results in an .exe file (Standard EXE), an .ocx (ActiveX Control) or a .dll (ActiveX DLL) file. It would appear as though we could e-mail or distribute this one file to anyone in the world and our program would work on their computer just as it does on ours. And this may work from time to time but this is not always the case.
I Compiling and Distributing Applications I
839
Distributing VB6 Applications When we speak of distributing VB6 Applications, we mean not only placing files on another’s computer but also registering controls and DLLs and otherwise ‘setting up‘ their computer.
Standard EXE Applications When a Standard EXE Application written in VB6 is to be distributed, there are at least two files that should be distributed: the EXE file and the VB6 Virtual Machine file (msvbvm60.dll). Without the VB6 Virtual Machine file, VB6 Applications cannot run. The VB6 Virtual Machine DLL is installed on a large number of computers but there is no guarantee that it is installed on every computer where our software is installed, so it should be distributed with our application. In addition to these two essential files, other considerations must be made. For example, let us suppose that we have created an application that makes use of the Internet Transfer Control for sending files to an FTP server. This control (an.ocx file) is installed with VB6. It is extremely useful. With it we can create a powerful internet-enhanced program. But if we give our program (an EXE file) to someone and they do not have this ActiveX Control on their computer, our powerful program will go nowhere. It will do nothing but display error MessageBoxes. When we are looking for files on which our application is dependent, there are two places where we need to look. The first is in the References dialog box.
840
I Chapter 38: Writing VB6 Applications I Here we can see a large number of References. The top four shown are standard to any VB6 program. We don’t need to worry about them. The other References that are selected are the ones that should concern us. When an item in the References list is selected, its full path is shown after the “Location” label. The other area we should look into is the Toolbox. If, for example, we are using a TreeView control, we need to make sure that the file containing the TreeView control is distributed.
Right-click on the Toolbox and select “Components”.This brings up the Components dialog box. When we select “Selected Items Only”, only those Controls that have been ‘selected’ are displayed. The fact that a Control has been selected does not mean it has been inserted into our project. If it has not been inserted, the Control does not need to be distributed with our Application. Right? If we attempt to ‘un-check‘ an item that is inserted, we will see this MessageBox: Any files that are “un-checkable” are not actually used in our program so we do not need to distribute them.
-
I Compiling and Distributing Applications I
841
OK. We know which files we need from the References and the Controls area. We know that we need to Register DLLs and ActiveX Controls on the host computer. How do we best do this?
We have already used the RegSvr32.exe registration process. We could place a series of these registration commands in a batch file (.bat file). We could then compress these source files and registration batch file into a zip file. Although this solution would certainly work, it is far from elegant and user friendly. Let’s examine the solution that is shipped with VB6: the Microsoft Package and Deployment Wizard. It is found in the “Microsoft Visual Basic 6.0” Start menu. API Text Viewer
Microsoft Visual Studio .NET 2003
MicrosoR Visual Basic 6.0
M i r m m f t Weh PI ihlichinn
Before running the Package and Deployment Wizard, it is a good idea to close down VB6.
1 The first step to using the Package and Deployment Wizard is to select which Visual Basic Project (.vbp) file we want to distribute. Then we click on the Package button.
842
I Chapter 38: Writing VB6 Applications I 2 The next dialog presents the choice between a “Standard Setup Package” and a “Dependency File’: We will select a Standard Setup Package.
Dependency File
3 We are next asked to select a folder in which to create the Setup.exe file.
4 After selecting a folder, we are asked to verify which files we are to include in the Setup file. This step takes care of determining which files need to be distributed with our Application. So, if we are going to use the Package and Deployment Wizard, we do not need to worry about looking at the References and Controls we have added.
I Compiling and Distributing Applications I
843
This step is also important because we can Add files to our Setup package that may not be added automatically. For example, if we have written a User Manual and created a PDF file of it, we can Add the file in this step and it will be compressed with the other project files and ‘installed’ on the user’s computer.
SETUP1.EXE SHELLLNK.TLB ST6UNST.EXE VB6 Runtime and OLE Automation VB6STKIT.DLL
5 After verifying which files are to be included in the Setup file, we are asked whether we will be distributing the setup as a single file or if it is to be placed on multiple floppy disks (outdated but still an option).
844
I Chapter 38: Writing VB6 Applications I 6 Next, we supply the title for the Setup package:
7 Shortcuts can be placed in the Start Menu or in other places.
8 The files that have been included in the setup package must be placed somewhere. By default, DLLs and OCX files are placed in the
I Compiling and Distributing Applications I
845
System32 folder. We can, however, change the location of these files in the next dialog box:
9 We are not going to specify our project’s testing executable as a ‘Shared File’ so we will continue through the next dialog box without selecting our project’s executable file.
k
846
I Chapter 38: Writing VB6 Applications I 10 The last dialog box we have to deal with allows us to specify a Script name.
Standard Setup Package 1
After we click the Finish button, our setup file is created. After the Setup program is complete, a report is generated and we are given the option of saving the report or closing the wizard.
.
.
I Review I
847 So, we have completed the Package and Deployment Wizard. And what do we have to show for it? Early on in the packaging process, we were asked for a location to place our deployment files. When we browse to the location we selected, we see the files created by the Wizard.
We are given a froject7.CAB file, a setupexe file, and a SEWELST file. The .CAB file contains all of the files that are to be distributed and they (all of the files) are compressed into a single file. The .LST file specifies where each of these files are to be saved. It can be modified after the CAB and setupexe file is created. As for the setupexe file, its only purpose is to extract the files in the .CAB file and place them in the folder specified in the .LST file. It also registers components as instructed in the .LST file. The Support folder contains all of the files in their un-compressed state. The Microsoft Package and Deployment Wizard does a fairly good job at packaging up programs developed in VB6. There are, of course, other products that create installation programs as well. One of these is Macrovision’s Installshield Express.
REVIEW VB6 has an environment that looks and feels a lot like VBA. In addition to creating stand-alone programs, we can create our own custom ActiveX Controls as well as ActiveX DLLs. When we finish our programming and compiling, we can distribute our application by using the Microsoft Package and Deployment Wizard which ships with VB6. Once we ‘attach‘ to Microstation, developing in VB6 is nearly identical to developing inside of Microstation’sVBA environment. All of the code we have created and worked with inside of Microstation VBA can be ‘ported’to VB6 with very little difficulty.
848
I Chapter 38: Writing VB6 Applications I There is one thing we need to be careful about when ‘porting’ a VBA program to VB6. When we are working in VBA, several Objects are naturally exposed for our use, the Application object, for example. In Microstation’s VBA environment, we can type Application.ActiveDesignFile
. . . . .
and the code works. When we are working in VB6, every Microstation Object must be implicitly set. If we declare a variable to represent the Microstation Application as Public in a Code Module, however, we only need to set it once and we can use it thereafter without the need to ‘set’ it again.
NOTE: Microsoft Visual Studio 6 has been replaced with Visual Studio .NET2003 and Visual Studio .NET 2005. The .NETfamily of development products can produce standalone .EXEfiles and can create DLLfiles that are accessible through COM. However, .NET cannot be used to create ActiveX Controls. So, f a new Control is needed in MicroStationS V B A environment, it should be created in VB6.
39
Using VB.NET On the surface, it appears as though VBA, VB6, and VB.NET are identical. They each bear, in part, the title “Visual Basic” and make use of the same “Visual Basic” basics. And yes, they are very similar. But there are also differences between them. VBA and VB6 have much more in common than VB.NET. The differences between them (VBA and VB6) and VB.NET can be quite significant, depending on which ‘area’ we are considering. When Microsoft introduced the VB.NET environment in 2003, one of the aims was to make Visual Basic much more Object-Oriented. This is one of the primary differences between VB.NET and VBA or VB6.
This chapter deals with not only controlling Microstation in VB.NET applications but also concentrates on the differences between these environments so we can become more proficient in the VB.NET environment and language. We will be using Visual Studio 2005 for our discussion. In this Chapter:
An Introduction to the VB.NET Environment El You can do this in VB.NET!
VBA / VB.NET Cross Reference Distributing VB.NET Applications 849
850
I Chapter 39: Using VB.NET I The Future of VB.NET with Microstation
VB.NET INTRODUCTION Let's take a look at the VB.NET environment. The first thing we should get out into the open is the fact that whereas VB6 is an Application and VC++ is its own Application, the .NET environment is used for developing Applications in VB, C# (pronounced C Sharp), J#, etc.
"..
I d ~~ i ~ ~lto Y m r ~ Ippr wiih~~ ~~ and Visual i, Rarir~ ~ FI, 27 Ian 2006 05:56:02 GMT - My.Blog5 15 a collection of sample code that shows how to ea4y provide programmatic access to bbgs in your
appLcation5. Chris Mayo shows how easy it 15 to read and publish blog entrier within Visual Basic 2005 using My.Blogr. Visual B a s i ~ ~ : * ~The, .NFT~ ~ t ~Ind Yom PvOiectS Tue, 24 Ian 2006 1 8 5 8 4 8 GMT - One of the biggest irruer that programmersrun into 15the sheer breadth of .NET. which can make finding the best clarr for a particular taskquite daunting. Ouncan Mackenzie shows how the My namespace in Visual Basic 2005 will make C ~ , , ~ ~ ~aLDi~" i~~ - R ~ ~ ~I ~ " i" =l id ~ = tfrom ~ ~ "Y b U d Basic 6 Lo,.. Tue, 24 Ian 2006 1824:04 GMT - Ken Get2 and Paul Sheriff show you how to migrate your data-oriented application from Visual Basic 6.0 to Visual B a r t 2005 in Part 3 of this rerier. C h ~ " ~ [3: = iMIX with Bill Gales iii Yesas Mon, 23 Ian 2006 23:i 1:59 GMT - Watch the MIX content team in action ar they talk about their plans for this new conference happening at the Venetian hotel in Vegas. User Interlace ~ ~ in Yisud " B ~ S~ Kb and~VISUIII Saiic ~ ZUU5 1 Fn, 20 Ian 2006 20:08:28GMT - When upgrading Visual Basic 6 applications, little cause5 more concern than controls that lust wont convertin-project controlr. curtom Pictivex controls. built-in controls. etc. Bill Sernpf rhowr you how to ease this procerr. ~~~~~~~~~
~ l
~
~
~
__
~
Press R ~ l ~ X= ~ "~ : ~ l ~~ ~~ ii.W ~ ~Help ~ ~ ~ ~~ ~~ i ~dTad ~ ih ~~~ Wed, I8 Ian 2006 19:08:34 GMT - The Vliual Studio 2005 50K rhipr with
~
i
"
~
a free tool for authoring Help content and integrating it with Visual Studio 2005. HelpStudioLLe, built by Innomy5, Ltd., is a lightweight vermn OF HelpStudlo, optimized for V15ual Studio Industry Partners. R ~hiow fur~;he MIXi "n6C ~ " ~ i~ in ~ tar ~ ~Vegas ~ " ~~ ~ Fri, 13 Ian 2006 17:59:25 GMT - Attend the MIX conference. a LIVE1 converration between web developerr. designerr and burinerr leaderr. being held in tar Vegaz from March 20 - 22 at the Venetian hotel with Bill Gates and Tim ORellv.
The Start Page of the Visual Studio 2005 environment
From the Start Page of Visual Studio 2005, we can create or open new Projects. In addition to the ability to create new projects and open existing ones, we can see the RSS feed from Microsoft's MSDN Visual Basic homepage. (We discussed RSS technology in an earlier chapter.)
I VB.NET Introduction I
851
Creating a new Project
Visual Studio installed templates
My Templates
We will create a new Project named “Microstation Control A”. It will be a Windows Application. As with VB6, a new project is created with a single Form added. We know we want to control Microstation. What is the next step? Adding a Reference, of course.
1 From the Project menu, select Add Reference.
2
Select the COM tab and scroll down to “Bentley Microstation DGN #.# Object Library”.Clicking the OK button adds the Reference and we are ready to continue.
852
I Chapter 39: Using VB.NET I 3 Double-click on our Project Name in the “Solution Explorer” and then select the References tab to display all the references of the current project.
From this window, we can see a reference named System, one named System.Data, another named System.Drawing and others. They have a Trpe of “.NET’: We can see that the Bentley Microstation reference has been added. Its Trpe is “COM’: Let’s write some code now.
4
Switch over to the Forml.vb tab and pin the Toolbox by clicking on the pushpin icon at the top of the toolbox.
5 Now, drag and drop a Button from the Toolbox to the Form. ForrnLvb [Design
6 Next, double-click on the Button to enter the Click event of the Button.
7 Now that we are in the Click event of the Button, we are going to enter some very simple code that ‘attaches’to Microstation and displays the Application’s Caption in a MessageBox. Here is the entire listing of code that includes this basic functionality.
I VB.NET Introduction I
853
Public Class Form1 Private Sub Buttonl-Click( ByVal sender As System.Dbject, ByVal e As System.EventArgs1 Handles Buttonl.Click Dim myMSAppCon As MicroStationDGN.Application0bjectConnector Dim myMSApp As MicroStationDGN.Application myMSAppCon = Getobject(, Mi c r o St a t i on D G N .Ap p 1 i cat i on 0 b je c t C o n n e c t o r 1 myMSApp = myMSAppCon.Application MsgBox(myMSApp.Caption) ~
”
”
myMSApp = Nothing myMSAppCon = Nothing End Sub End Class
The code should look very familiar. It resembles the code we created in VB6 as well as code created for Microsoft Excel. One of the main differences between what we see here and what we used before is the absence of the “Set” statement when we are working with Objects. Why is this? Because all variables are Objects in VB.NET. Strings are Objects. Integers are Objects. So, we don’t need to use “Set”when we are assigning variables their values or objects. If we look at the MessageBox statement, we see that we are using parenthesis in VB.NET where we do not do this in VB6 except when we are getting a return value. Any time we use a Function or Procedure in VB.NET that uses Parameters, we surround the Parameters with parenthesis.
8 If we try running our code, we will find that the code seems to run fine. But before doing anything else, we should save our project. Selecting File > Save All displays the Save Project dialog box.
With the settings as they are shown, a new directory is created named “Microstation Control A”.
854
I Chapter 39: Using VB.NET I Compiling our application
1 We begin compiling our application by going to the menu Build > Build MicroStationControl A.
Publish MicroStation Control A
When we “Build” our application, an .exe file is generated. Let’s take a look at this next graphic and then we will discuss it.
1,208 KB 28 KB 60 KB 1 KB
DLL File Application Program Debug Database XML Document
When we compile applications in VB.NET, by default, they are compiled into one of two folders: Debug or Release. “Debug and “Release” are compiler configurations we can use as we develop and compile our applications. These configurations have different settings pertaining to debugging, performance options, and processor preferences. We specify which configuration we want to use in the “Configuration Manager” (Build > Configuration Manager).
NOTE: Ifwe are attempting to debug an application in VB.NET and we seem to be unable to step through our code using
I VB.NET Introduction I 2
If the Configuration Manager menu item does not appear in the menu, you can turn it on by setting the option in Tools > Options.
3 Select “Show advanced build configurations” to turn on Configuration Manager in the Build menu group. Now, let’s take a look at the Configuration Manager.
When we Build our applications, they are placed in the folder specified for the active configuration. By default, “Release” compilations are compiled in the Release folder and “Debug” compilations are compiled in the Debug folder. We use the Configuration Manager to specify the Active Configuration. When we Build our applications, the compilation is based on the active configuration. This concludes our brief introduction to the VB.NET environment in Microsoft Visual Studio 2005.
855
856
I Chapter 39: Using VB.NET I YOU CAN DO THIS IN VB.NET! We just created a very simple, very small application in VB.NET. There are some differences between the IDE (Integrated Development Environment) of VB.NET and that of VBA and VB6. Once we learn how to use the IDE in VB.NET, we can begin looking around a little at what else we can do in VB.NET. If we explore a little bit, we will find a few ‘tools’ that are not readily available in VBA. Let’s create a few applications that make use of VB.NET-specific functionality. The next project we will create is a “Windows Application” project named “MicroStation Control B’: The source files are on the CD accompanying this book. Let’s take a look at the application when it is being run. We will discuss the functionality and then look at the code.
HEIGHT
NORTH
The concept for this application is fairly simple. We see a list of cells that can be inserted into MicroStation with a thumbnail image of the Cell accompanying the Cell name. We also see information about the selected cell in a text box on the right. This additional ‘information’ is stored in an .info file with the same file name as the preview image. If a line beginning with the text “Website Address:” is found in the .info file, a hyperlink ‘To Website’ is shown which, when clicked, opens a new web browser window and opens the website address in the.info file. If a website address is not in the .info file or if an .info file is not available,
I You can do this in VB.NET! I
857
the "To Website" link is not displayed. When the user double-clicks on an item in the ListView control, the 'double-clicked' cell is inserted into Microstation. To keep things simple, we will insert the Cell at the center of the current view. Rather than hard-code cell names and force the user to create thumbnail images of a particular size, we base the contents of the entire list on the availability of bitmap (.bmp) files in the Application's folder. The name of each bitmap file corresponds with a cell name. Information Files (.info) match the file name of the bitmap files and contain reference information about the cell. A thumbnail is automatically created in memory for use in the ListView based on each bitmap file. Each dynamically created thumbnail is 64 pixels wide and 64 pixels high. The source bitmap files can be any size but will ideally be square in shape since the thumbnail image that is created does not compensate for differences in aspect ratios. Here is the code for this project. Keep in mind that we already have a reference added to the Bentley Microstation DGN #.# Object Library. P u b l i c C l a s s Form1 P u b l i c E x e P a t h As S t r i n g P u b l i c F i x e d H e i g h t As L o n g P r i v a t e Sub F o r m l L L o a d ( B y V a 1 s e n d e r As S y s t e m . O b j e c t , B y V a l e As S y s t e m . E v e n t A r g s ) H a n d l e s MyBase.Load D i m m y F i l e As S y s t e m . I O . F i l e I n f o D i m m y F o l d e r As New
System.IO.DirectoryInfo(Application.Executab1ePath) D i m m y L s t V I As L i s t V i e w I t e m D i m myImg As Image D i m myThumb As Image E x e P a t h = myFolder.Parent.Ful1Name F o r Each m y F i 1 e I n m y F o l d e r . P a r e n t . G e t F i l e s ( " * . b m p " ) myImg = Image.FromFile(myFile.FullName) myThumb = -
myImg.GetThumbnailImage( ImageListl.ImageSize.Width, ImageListl.ImageSize.Height,
-
Nothing, Nothing) myT humb . T a g = my F i 1 e . Name. ToUppe r . Rep1 a c e ( " . BM P " , ImageListl.Images.Add( " " ) , my F i 1 e . Name . Rep 1 a c e ( " . bmp " , myT h umb m y L s t V I = lstvCells.Items.Add(myThumb.Tag, 1 m a g e L i s t l . I m a g e s . C o u n t - 1) Next End Sub
" "
I Chapter 39: Using VB.NET I Private Sub lstvCellspItemSelectionChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.ListViewItemSe1ectionChangedEventArgs) Handles 1stvCells.ItemSelectionChanged Dim myFileInfo As 1O.FileInfo Dim myFileReader As 1O.StreamReader TextBoxl.Text = LinkLabell .Visible = Fa1 s e myFileInfo = New IO.FileInfo(ExePath & " \ " & e . I t e m .Text & . i n f o ) If myFileInfo.Exists = True Then my F i 1 e Re ad e r = my F i 1 e I n f o .Open T e x t TextBoxl.Text = myFileReader.ReadToEnd myFileReader.Close0 If InStr(TextBoxl.Text, "Website Address:") LinkLabell .Visible = True End If End If End Sub "I'
"
"
>
0 Then
Private Sub lstvCellspDoubleClick(ByVal sender As Object, ByVal e As System.EventArgs1 Handles 1stvCells.DoubleClick Dim myMSAppCon As MicroStationDGN.ApplicationObjectConnector Dim myMSApp As MicroStationDGN.App1ication Dim myCellElem As MicroStationDGN.CellElement Dim Cellorigin As MicroStationDGN.Point3d ProgressBarl.Visib1e = True ProgressBarl.Value = 10 myMSAppCon = Getobject(, "MicroStationDGN.App1 i c a t i o n O b j e c t C o n n e c t o r " ) ProgressBarl.Value = 20 myMSApp = myMSAppCon.Application ProgressBarl.Value = 40 Cellorigin = myMSApp.CommandState.LastView.Center ProgressBarl.Value = 60 myCellElem = myMSApp.CreateCellElement3( lstvCells.SelectedItems(O).Text, Cellorigin, True) ProgressBarl.Value = 80 myMSApp.ActiveMode1 Reference.AddElement(myCel1 El em) ProgressBarl.Value = 100 ProgressBarl .Visibl e = Fa1 s e End Sub Private Sub LinkLabellLLinkClicked(ByVa1 sender As System.Object, ByVal e As System.Windows.Forms. LinkLabel LinkClickedEventArgs) Handles LinkLabell.LinkClicked Dim xSplit As S t r i n g 0 Dim strwebsite As String
-
I You can do this in VB.NET! I
859
x S p l i t = Split(TextBoxl.Text, vbCrLf) F o r Each s t r w e b s i t e I n x S p l i t I f strWebsite.StartsWith("Website A d d r e s s : " ) = T r u e Then D i m a As P r o c e s s S t a r t I n f o = New P r o c e s s S t a r t I n f o ( Mid(strWebsite, InStr(strWebsite, " 1 + 2)) Process.Start(a) End I f 'I:
Next End Sub P r i v a t e Sub F o r m l L R e s i z e ( B y V a 1 s e n d e r As O b j e c t , B y V a l e As S y s t e m . E v e n t A r g s ) H a n d l e s M e . R e s i z e I f F i x e d H e i g h t = 0 Then F i x e d H e i g h t = Me. H e i g h t Else Me.Height = FixedHeight End I f End Sub End C l a s s
Code is placed into five events which are triggered by either the application starting or by user interaction. Let's discuss each of these events and what they are accomplishing.
Private Sub Forml-Load This application makes use of a ListView control. This control allows us to display images and descriptions in a list. ListView controls use ImageList controls to hold the images that are to be displayed inside the ListView control. We look in the folder in which the Application resides (the .exe file) for any Windows Bitmap (.bmp) files. Each Bitmap file represents a Cell in Microstation. These Bitmap files can be 'CAD drawn' or can be actual photographs or illustrations of the object the Cell represents. We create a thumbnail in memory for each bitmap by using the GetThumbNai 1 Image method of the Image object. Although this same functionality could be duplicated in VBA, VB.NET makes this step very easy. After adding each thumbnail to the ImageList, we add an item to the ListView using the thumbnail image just 'created' and the description of the Cell.
Private Sub IstvCells-ItemSelectionChanged Each time the user changes the selection in the Listview, we look to see if an .info file is available for the selected bitmap. If we find an .info file, we populate the TextBox with the contents of the .info file. If we find a
860
I Chapter 39: Using VB.NET I line in the .info file that contains “Website Address:”, we make the LinkLabel control visible so it can be clicked.
Private Sub IstvCells-DoubleClick When the user double-clicks on the Listview, we insert the doubleclicked cell into Microstation. There are numerous ways we can determine the cell’s origin. In our example, we place the cell in the center of the current view in Microstation. A ProgressBar displays the progress of the code as it runs.
Private Sub LinkLabell -Lin kclicked The only way to click on the LinkLabel is for it to be visible. It is only visible if “Website Address:” is in the .info file of the selected cell. When the LinkLabel is clicked, we get the “Website Address” in the .info file and start a new process using the website address. P r o c e s s . St a r t begins a new process (starts an application) and opens the provided file in the application. P r o c e s s . S t a r t is equivalent to the She1 1 E x e c u t e Windows API call we discussed in a previous chapter.
Private Sub Forml-Resize User Forms in VB6 and VB.NET can be resized. In our example we only want the form to be able to be resized in its width. So, we use a Variable named FixedHeight when the form is initially created (the Resize event is triggered as well as the Load event when a Form is displayed) and continue to use this variable for the Form’s Height any time the Form is resized. As we review the code shown above or if this project is opened in Visual Studio, we will find that something is missing. What is it? We do not have any error handling. As a standalone.EXE file, it is possible someone could open the program without Microstation running first. As we look through the code we can see that the only place where this may be a problem is in the IstvCells-Doubleclick event. Let’s add a little error handling the VB.NET way. It is a little different than what we have used in VB6 and VBA. Private Sub IstvCells-DoubleClick(ByVa1 sender As Object, ByVal e As System.EventArgs) Handles 1stvCells.DoubleClick Dim myMSAppCon As MicroStationDGN.Application0bjectConnector Dim myMSApp As MicroStationDGN.App1ication
I You can do this in VB.NET! I
861
D i m m y C e l l E l e m As MicroStationDGN.CellE1ement D i m C e l l o r i g i n As M i c r o S t a t i o n D G N . P o i n t 3 d ProgressBarl.Visib1e = True ProgressBarl.Value = 10 Try myMSAppCon = G e t o b j e c t ( , " M i c r o St a t ion D G N . Ap p 1 ic a t io n 0 b j e c t C o n n e c t o r " 1 P r o g r e s s B a r l . V a l u e = 20 myMSApp = m y M S A p p C o n . A p p l i c a t i o n P r o g r e s s B a r l . V a l u e = 40 C e l l o r i g i n = myMSApp.CommandState.LastView.Center P r o g r e s s B a r l . V a l u e = 60 m y C e l 1 E l em = m y M S A p p . C r e a t e C e l 1 E l e m e n t 3 ( lstvCells.SelectedItems(O).Text, C e l l o r i g i n , T r u e ) P r o g r e s s B a r l . V a l u e = 80 ~
myMSApp.ActiveModelReference.AddElement~myCellE1em~ ProgressBarl.Value = 100 C a t c h e x As E x c e p t i o n S e l e c t Case E r r . N u m b e r Case 4 2 9 ' M i c r o S t a t i o n n o t s t a r t e d M s g B o x ( " M i c r o S t a t i o n i s n o t s t a r t e d . " & vbCr & "Please s t a r t M i c r o S t a t i o n and t r y a g a i n . " ) ProgressBarl.Visible = False E x i t Sub Case E l s e MsgBox(ex.Message & vbCr & e x . S t a c k T r a c e , , "ERROR") ProgressBarl.Visib1e = False E x i t Sub End S e l e c t End T r y ~
ProgressBarl .Visi b l e End Sub
=
Fa1 s e
When we perform error checking in VB.NET, we anticipate that a line of code may cause an error. We Try the line or lines of code after which we Catch the error or errors that may arise. We will see a few additional examples of this as we continue.
862
I Chapter 39: Using VB.NET I
A DGN BROWSERAPPLICATION Our next application is also a Windows Application. It is titled DGN Browser. It makes use of a TreeView control, a CommandButton, a TextBox, a Folder Browser Dialog, a NotifyIcon, an Image List, and a ToolTip Control. Let’s take a look at the GUI when it is being run.
Levels A-F6-G-BldgExtl A-FB-G-BldgMisc A-G251-G-WallExtl A-2000-0-Dim A-2000-G-Anno A-2001 -G-lden A-2011-G-Tit1 A-201 3-G-Legn Default Frame Links Oblect Models Composite Cut Ground Floor Plan Ground Floor Plan BS130ME201 -Elevations dgn Levels A-F44-G-SubsNich
Users browse to a folder they want to view. Each .dgn file is loaded into the TreeView control and each.dgn file is opened in Microstation “For Program”. Levels and Models are extracted and displayed under the “Levels” and “Models” icons under each design file. The 3D and 2D models are distinguished by their icons. Once again, Microstation must be running for this program to work correctly. If it is not running, we don’t want our program to blow up, so we simply show the .dgn files in the TreeView but Levels and Models are not shown.
Here’s what it looks like when Microstation is not running:
BS1300AElOl-Plan.dgn BS1300AE201-Elevations.dgn BS1300AE301-Sections.dgn BS1300AE501-Details.dgn BS1300AE701-RCPlan.dgn BS1300AE9.Atriurn.dgn BS1300AE9-Core.dgn BS1300AE9-Shell.dgn BS1300C-9-Site.dgn BS13OOG1001-Cover.dgn BS1300G19-3DMaster.dgn BS13001-9-lnterior.dgn BS1300S-9.Atriurn.dgn BS1300S-9-Structural.dgn BS130W-9-Sign.dgn
I A DGN Browser Application I Let’s imagine that we have built this application and we are so pleased with it that we are sure users will want it available with the quick click of a mouse. How can we make our program this accessible? One way is to place it in the Windows Status Notification Area of the Task Bar.
863
DGN Explorer
How difficult is this to accomplish in VB.NET? Simply add a “NotifyIcon” Control to the Form and it displays in the TaskBar. Of course, we could not let this wonderful program show up with a standard VB.NET icon so we use the Microstation V8 XM icon. So, to review:
1 The program is launched by double-clicking on the icon in the Windows Status Notification area.
2 We select a folder by clicking the Browse button. The program then finds all .dgn files in the selected folder and displays the file name in a TreeView.
3 If Microstation is open, we use Getobject to attach to Microstation and open each file in the list “For Program” and add the Levels and Models to the Tree View control. Simple enough? It truly is when working in VB.NET. Here’s the code: Public Class Form1 Private LastPath As String Private myMSApp As MicroStationDGN.App1ication Private myMSAppCon As MicroStationDGN.ApplicationObjectConnector Private MicroStationError As Boolean Private MicroStationOpen As Boolean Private Sub NotifyIconl-MouseDoubleCl ick(ByVa1 sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles NotifyIconl.MouseDoubleC1ick Me.WindowState End Sub
=
FormWindowState.Norma1
Private Sub ButtonlLClick(ByVa1 sender As System.Object, ByVal e As System.EventArgs) Handles Buttonl.Click FBD1.SelectedPath
=
LastPath
I Chapter 39: Using VB.NET I FBD1.Description = "DGN E x p l o r e r Path:" FBDl.ShowDialog0 TextBoxl.Text = FBD1.SelectedPath
DisplayDGNs(FBD1.SelectedPath) End Sub P r i v a t e Sub D i s p l a y D G N s ( B y V a 1 P a t h I n As S t r i n g )
D i m myDI As 1 O . D i r e c t o r y I n f o D i m myFI A s 1 O . F i l e I n f o D i m myNode As T r e e N o d e D i m F i l e c o u n t e r As L o n g tvl.Nodes.Clear0 myDI = New IO.DirectoryInfo(PathIn) I f N o t m y D I . E x i s t s Then MsgBox("The p a t h " & P a t h I n & " does n o t e x i s t . " , MsgBoxStyle.Cri t i c a l TextBoxl .Text = " " E x i t Sub End I f LastPath = PathIn ToolTipl.SetToolTip(TextBox1, P a t h I n ) Filecounter = 0 ProgressBarl.Visib1e = True F o r Each myFI I n r n y D I . G e t F i l e s Filecounter = Filecounter + 1 ProgressBarl.Value = Filecounter * 10 ProgressBarl.Refresh0 S e l e c t Case r n y F I . E x t e n s i o n . T o U p p e r Case " . D G N " myNode = tvl.Nodes.Add(myFI.Name) rnyNode.Irnage1ndex = 0 rnyNode.SelectedImageIndex = 0 I f M i c r o S t a t i o n E r r o r = F a l s e Then GetFileComps(myFI.FullNarne, rnyNode) End I f End S e l e c t I f F i l e c o u n t e r = 1 0 Then F i l e c o u n t e r = 0 Next P r o g r e s s B a r l . V i s i b l e = Fa1 s e tvl.Sort0 End Sub P r i v a t e Sub F o r m l L L o a d ( B y V a 1 s e n d e r A s S y s t e m . O b j e c t , B y V a l e A s S y s t e r n . E v e n t A r g s ) H a n d l e s MyBase.Load LastPath End Sub
=
"C:\MicroStation
VBA"
P r i v a t e Sub G e t F i l e C o m p s ( B y V a 1 F i l e I n A s S t r i n g , B y V a l F i l e N o d e As T r e e N o d e ) D i m myLevel As MicroStationDGN.Leve1 D i m myModel A s M i c r o S t a t i o n D G N . M o d e l R e f e r e n c e
-
I A DGN Browser Application I
865
Dim LevelNode As TreeNode Dim ModelNode As TreeNode Dim tmpNode As TreeNode If MicroStationOpen = False Then Try myMSAppCon = Getobject(, Mi c r o S t a t i on D G N .Ap p 1 i cat i on 0b j e c t C o n n e c t o r 1 myMSApp = myMSAppCon.Application Catch MicroStationError = True Exit Sub End Try Dim DGNFile A s M i c r o S t a t i o n D G N . D e s i g n F i 1 e Try DGNFile = myMSApp.OpenDesignFileForProgram( FileIn, True) Catch Fi 1 eNode. ForeCol or = Color. Red Exit Sub End Try LevelNode = FileNode.Nodes.Add("Levels") LevelNode.ImageIndex = 1 LevelNode.SelectedImageIndex = 1 ModelNode = FileNode.Nodes.Add("Models") ModelNode.ImageIndex = 3 ModelNode.SelectedImageIndex = 3 ~
"
"
~
For Each myLevel In DGNFile.Levels tmpNode = LevelNode.Nodes.Add(myLeve1.Name) tmpNode.ImageIndex = 2 tmpNode.SelectedImageIndex = 2 Next For Each myModel In DGNFile.Models tmpNode = ModelNode.Nodes.Add(myMode1.Name) If myModel . Is3D Then tmpNode.ImageIndex = 4 tmpNode.SelectedImageIndex = 4 Else tmpNode.ImageIndex = 5 tmpNode.SelectedImageIndex = 5 End If Next DGNFile = Nothing End If End Sub End Class
The ImageList Icons are located on the CD that accompanies this book. Are there things we could do to make this program even more powerful? Of course. We could automatically open a file when it is double-clicked in the Tree. We could allow the user to drag and drop
866
I Chapter 39: Using VB.NET I folders from Windows Explorer into our DGN Explorer. We could store the most recently selected folders in a ComboBox instead of using a TextBox to display the selected path. We could have the option of including subfolders when a folder is selected. There are many things we could do to this project but we are not going to do them here. We will leave embellishments up to the reader.
VBA TO VB.NET REFERENCE One question that is often asked is, “What programming language should I be learning? VBA? VB6? VB.NET?” That is a good question. And if you ask this question to 100 different experts, you will hear a 100 different answers. We discussed some of the reasons why you would want to develop in VBA versus VB6 or VB.NET previously. If there are clear distinct benefits to developing in VBA, then of course, specializing in VBA makes sense. And if VBA is the primary development environment, then VB6 is a much more natural transition than VB.NET. However, as they say, “The writing is on the wall”.VB6 is going away and VB.NET is the future, so, it would be a good idea for us to get familiar with the .NET environment and how things are done in VB.NET. And as for VBA, Microsoft has developed a .NET-like VBA environment for applications, so it will likely be adopted in the coming years. This section of this chapter is not meant to be a comprehensive listing of VBA to VB.NET calls. Rather it aims to provide information on how some of the common things we have explained in this book are being done in VB.NET. See the Other References section in this book for more information on the VB.NET environment.
Everything is an Object When developing in VBA or VB6, we can declare a variable as a String. In VBA and VB6, a String is a data type, not an Object. In VB.NET, a String is an Object with its own properties and methods. For example,
I VBA to VB.NET Reference I
867
when we type the name of a variable declared as a String and press the period key, we see: Substring Tacharkray Totower ToLowerInvariant ToString
As we can see here, we can use the ToUpper method of a String Object instead of using U C a s e (see below). Ucase(myName1 ‘VB6 a n d VBA m y S t r i n g . T o U p p e r ‘i/B.NET
ToUpper is a method of the String Object. And how about the String Object’s properties? Yes, there are Properties as well. In VB.NET, we use the Length property of a String Object instead of using the Len function (as we do in VB6 and VBA).
Overloaded That word brings vivid pictures to mind. In VB.NET it means that a single procedure, method, function, etc., can have more than one implementation, each with its own set of unique Parameters. Here’s an example using the FileInfo Object’s Open method: IS
When we attempt to use the Open method of the FileInfo object, we initially see the Intellisense hint as being “1 of 3’: We can step through each of the overloaded methods by using the arrow keys or by clicking on the Up and Down arrows shown in the tooltip. These methods make
868
I Chapter 39: Using VB.NET I use of varying numbers and types of parameters but all accomplish the same thing - they open a file as a Filestream. Not only are standard VB.NET functions, procedures, etc., overloaded, but we can create our own overloaded functions as well. This comes in very handy because if we need the same function to be used with different parameters, we can overload it instead of adding a constantly growing number of optional parameters to the function.
Procedures and Functions In VB.NET, parenthesis are placed around Procedure calls as well as Function calls. One example of this is the MessageBox. In VBA and VB6: MsgBox " T h i s i s a t e s t . "
In VB.NET: MsgBox("This
i s a test.")
Accessing ASCII Files Reading and Writing ASCII files is easy to do in VBA and VB6. It is also easy to do in VB.NET.
Reading Files in VBA Sub ReadFi 1 eVBA( ) D i m F F i l e As Long D i m s t r L i n e As S t r i n g FFile = FreeFile Open " C : \ M i c r o S t a t i o n V B A \ p o i n t s . t x t " W h i l e EOF(FFi1e) = F a l s e Line Input #FFile, strLine Debug. P r i n t s t r L i n e Wend C1 o s e B F F i 1 e End Sub
Reading Files in VB.NET Sub ReadFi 1 eA( ) D i m myFS As New I O . S t r e a m R e a d e r ( "C:\MicroStation VBA\points.txt") W h i l e myFS.EndOfStream = F a l s e Oe b u g . P r in t ( my FS . Read L i n e 1 End W h i l e
-
F o r I n p u t As I l F F i l e
I VBA to VB.NET Reference I
869
my F S . C1 o s e ( ) End S u b
S u b ReadFi 1 eB( 1 D i m myFS As New I D . S t r e a m R e a d e r ( "C:\MicroStation VBA\points.txt") ~
Debug.Print(myFS.ReadToEnd1
my F S . C1 o s e ( )
End S u b
In VB.NET we can read a file one line at a time just as we do in VBA. We also have the option to ReadToEnd. When we use the ReadToEnd method, we are returned the entire file at once in one String.
Writing Files in VBA S u b Wri t e F i 1 eVBA( ) D i m F F i l e As L o n g D i m s t r L i n e As S t r i n g FFile = FreeFile Open " C : \ M i c r o S t a t i o n V B A \ p o i n t s P . t x t " F o r O u t p u t As B F F i l e Pr in t # F F i 1 e , "1 ,1,0,1" P r in t # F F i 1 e , " 2 ,2, 0 ,2" P r in t # F F i1 e , " 3 . 3 . 0 . 3 " P r in t # F F i1 e , "4.4.0.4 " C1 o s e # F F i 1 e End S u b
Writing Files in VB.NET S u b Wri t e F i 1 eA( ) D i m myFW As New I D . S t r e a m W r i t e r ( "C:\MicroStation VBA\pointsZ.txt") my FW. W r it e L i n e ( "1.1.0.1" 1 my FW . W r it e L i n e ( "2,2,0,2 " 1 my FW. W r it e L i n e ( " 3 , 3 , 0 , 3 " 1 my FW. W r it e L i n e ( "4.4.0.4" 1 myFW.Close0 End S u b ~
Writing files in VB.NET is very similar to writing files in VBA. We write one line at a time.
E-mailing in VB.NET For an example of e-mailing using VBA, refer to previous chapters where this was discussed. As for VB.NET, here are a couple of examples: S u b SendMai 1A ( 1
870
I Chapter 39: Using VB.NET I Dim mySMTP As New System.Net.Mail.SmtpC1ient
mySMTP.Host = "yoursmtpserver.com" my SM T P . Sen d ( m e@ ben t 1 ey . com , y o u@ ben t 1 ey . co m , "MicroStation V B A " , "Emailing using .NET is easy.") End Sub "
"
"
"
Sub SendMai 1 B ( ) Dim mySMTP As New System.Net.Mail.SmtpClient Dim myMessage As New System.Net.Mail.MailMessage Dim myAttachment As New System.Net.Mail.Attachment( "C : \Mi cr oSta t i o n VBA\poi n ts . txt "
mySMTP.Host = "yoursmtpserver.com" myMessage.To.Add( "you@bentl ey. com" ) myMessage.From = New System.Net.Mail.MailAddress("[email protected]"~ myMessage.Subject = "Emailing a n Attachment" myMessage.Body = "HTML Email is also easy." myMessage.IsBodyHtm1 = True myMessage.Attachments.Add(myAttachment) mySMTP.Send(myMessage) End Sub
The first example, SendMa i1A shows us that with only three lines of code, we can send an e-mail. SendMai 1 B does things a little differently. S e n d M a i l B adds an Attachment to the e-mail that is being sent. Of course, the SMTP.Host, From, and To fields in each of these examples need to be modified to reflect legitimate e-mail server and mailbox settings.
Traversing a Folder and its Subfolders We have seen already how this can be done using the File System Object in VBA. Let's take a look at how this is accomplished in VB.NET. The first example gets all of the subdirectories of a specified directory: Sub TraverseFol ders( ) Dim myDI As New IO.DirectoryInfo("C:\MicroStation VBA") Dim mySubDI As 1O.DirectoryInfo For Each mySubDI I n myDI.GetDirectories(""."", 1O.SearchOption.AllDirectories) Debug.Print(mySubDI.FullName) Next End Sub
G e t D ir e c t o r ie s has three different implementations. The one shown
here allows us to specify a pattern to look for as well as the ability to specify "AllDirectories" or "TopDirectoryOnly':
I VBA to VB.NET Reference I
871
In VBA, we use a recursive procedure to get to the subfolders. When we use VB.NET, we can get all folders with a single line of code. C:\NicroStation C:\NicroStation C: \NicroStation C:\NicroStation C:\NicroStation C: \NicroStation C:\NicroStation C:\NicroStation C:\NicroStation C:\NicroStation C:\NicroStation C:\NicroStation C:\NicroStation C:\NicroStation C:\NicroStation C:\NicroStation C:\NicroStation C:\NicroStation C:\NicroStation
VBA\BatchProcessing VBA\cd material VBA\docs VBA\Documents VBA\Fonts VBA\ from mark VBA\FTP in VB Dot Net VBA\pics VBA\Source Code VBA\USCS VBA\VB6
VBA\FTP VBA\FTP VBA\FTP VBA\FTP VBA\FTP VBA\FTP VBA\FTP VBA\FTP
in in in in in in in in
VB VB VB VB VB VB VB VB
Dot Dot Dot Dot Dot Dot Dot Dot
Net\CS Net\VB Net\VB\FtpSample Net\VB\FtpSample\bin Net\VB\FtpSample\obj Net\VB\FtpSample\obj\Debug Net\VB\FtpSample\obj\Debug\TempPB Net\CS\FTPSample
Getting All Files in a path Just as VB.NET makes it easy to traverse the folders under a specified folder, traversing files under a specified folder (and its subfolders) is also done with only a few lines of code: S u b F i n d F i 1 esA( ) Dim myDI A s New ID.DirectoryInfo("C:\MicroStation V B A " ) Dim myFI A s 1O.FileInfo For E a c h myFI In rnyDI.GetFiles("*.DGN", 1O.SearchOption.AllDirectories) Debug.Print(rnyFI.Ful1Name) Next End S u b
872
I Chapter 39: Using VB.NET I In this example, we get all .dgn files beginning in the C:\MicroStution VBA path and its subdirectories. C:\BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation C: \BicroStation
VB1\BatchPr~cessing\Fil=B.dgn VB1\BatchPr~cessing\BatchD\Pile W. dgn VB1\BatchPr~cessing\BatchD\Pile X.dgn VB1\BatchPr~cessing\BatchD\Pile Y.dgn VB1\BatchPr~cessing\BatchD\Pile 2. dgn VBA\BatchProcessing\BatchC\File U.dgn VBA\BatchProcessing\BatchC\File V.dgn VB1\BatchPr~cessing\BatchB\Pile R. dgn VB1\BatchPr~cessing\BatchB\Pile S. dgn VB1\BatchPr~cessing\BatchB\Pile T. dgn VB1\BatchPr~cessing\Batchli\Pile F. dgn VB1\BatchPr~cessing\Batchli\Pile C. dgn VB1\BatchPr~cessing\Batchli\Pile H.dgn VB1\BatchPr~cessing\Batchli\Pile 2’. dgn VB1\BatchPr~cessing\Batchli\Batchli-3\Fil=P. dgn VB1\BatchPr~cessing\Batchli\Batchli-3\Fil=Q. dgn VB1\BatchPr~cessing\Batchli\Batchli-e\Pil= N.dgn VB1\BatchPr~cessing\Batchli\Batchli-e\Pil= N.dgn VB1\BatchPr~cessing\Batchli\Batchli-1\Pil= K.dgn VB1\BatchPr~cessing\Batchli\Batchli-1\Pil= 1. dgn
Returning Function Values VB.NET requires us to use the ‘Return’statement when a function needs to return a value. This is different than VBA and VB6 when we use the name of the function, then the equal sign, then the value or object that is to be returned.
Windows API Calls The.NET Framework exposes many objects with methods and properties that previously had been the exclusive realm of Windows API calls. For example, accessing the entire Windows Registry was a somewhat tedious process. Of course, we could create our own classes that wrapped up the code for accessing the Registry. In VB.NET, accessing the Registry is much easier, though. But not all Windows API calls are placed at our fingertips as we will see in our next example. When we look at Windows Explorer, files are shown with their names and then with an icon. The icon normally reflects the application that is used to open or edit the file. There are several places in the Registry where the “DefaultIcon”property of a specific file type is found. In our next example, we are going to look at each file extension in a folder, attempt to find the default icon that is used for each file extension in the Registry, and then save the icon out as a Bitmap (.bmp) file. To
I VBA to VB.NET Reference I
873
accomplish this we will use two Windows API calls as well as the built-in Registry access objects in .NET. First, here are the Windows API calls declared just below the "Public Class" statement in VB.NET: Declare Function ExtractIcon L i b "she1132.dll" A l i a s " E x t r a c t I c o n A " ( B y V a l h I n s t As I n t e g e r , B y V a l 1 p s z E x e F i l e N a m e As S t r i n g , B y V a l n I c o n I n d e x As I n t e g e r ) As I n t e g e r
-
~
Public Declare Function FindExecutable L i b "she1132.dll" A l i a s " F i n d E x e c u t a b l e A " ( B y V a l l p F i l e As S t r i n g , B y V a l 1 p D i r e c t o r y As S t r i n g , B y V a l l p R e s u l t As S t r i n g ) As L o n g ~
The first call, E x t r a c t I c o n extracts a specific icon from an .exe or.dll file. F i n d E x e c u t a b l e allows us to specify a file name and it returns the path to the program registered to open the file. We will see these used in a little while. Let's look at the main Procedure that kicks things off. We begin by getting a DirectoryInfo Object that is pointing to the path C:\MicroStation VBA. When we look at each file in this folder, we check to see if we have already created a Bitmap file for the file extension of the file we are looking at. If we have not already created a Bitmap file for the file extension, we begin the process of identifying the icon file to use to create the Bitmap. Our first attempt at identifying the icon is looking in the Windows Registry based on the File Extension. If this fails, we get the Icon from the registered application of the file. If this fails, we get the default Windows icon (which comes from the she1132.dll file). When we successfully obtain an Icon, we save the file as a Bitmap. Then, later in the code, we place the Icon in a PictureBox control and then save it out to a file. The reason we use the PictureBox control is to maintain some of the transparency information found in the icon file. Sub S a v e F i 1 eIcons( 1 D i m myDI As New ID.DirectoryInfo("C:\MicroStation D i m myFI As 1 O . F i l e I n f o D i m I c o n F i l e As 1 O . F i l e I n f o D i m m y I c o n As B i t m a p
VBA")
874
I Chapter 39: Using VB.NET I Dim tmpImage As Image Dim myPictureBox As New PictureBox For Each myFI In myDI.GetFiles IconFile = New IO.FileInfo(myDI.FullName & " \ " & my F I . Ex t e n s i on . Rep 1 a c e ( . , ) & .bmp ) If 1conFile.Exists = False Then 'First look at extension myIcon = IconFromExtension(myF1.Extension) If myIcon Is Nothing = False Then my I con. Save ( c : \Mi c r oS t a t i on VBA\ & my F I . Exten s i on. Repl ace ( . , & . bmp , System.Drawing.Imaging.1mageFormat.Bmp) Else 'Now look at file myIcon = IconFromFile(myFI.Ful1Name) If myIcon Is Nothing = False Then my I con. Save ( c : \Mi c r oS t a t i on VBA\ & myFI.Extension.Replace(".", & ".bmp", System.Drawing.Imaging.1mageFormat.Bmp) Else 'Use Default Windows Icon myIcon = G e t D e f W i n I c o n O my I con. Save ( c : \Mi c r oS t a t i on VBA\ & & . bmp my F I . Exten s i on. Repl ace ( . , System.Drawing.Imaging.1mageFormat.Bmp) End If End If If myIcon Is Nothing = False Then myPictureBox.1mage = myIcon tmpImage = myPictureBox.Image tmp I ma g e . Sa v e ( C : \Mi c ro St a t i on V BA \ & & . a bmp ) my F I . Exten s i on. Repl ace ( . , End If End If Next End Sub "
"
" "
"
"
"
"
" "
"
"
"
"
"
"
~
" ' I )
"
"
~
" "
"
"
"
"
"
"
"
" "
"
"
9
-
~
"
Now, for the Functions IconFromExtension, IconFromFi 1 e, and GetDefWi nIcon. Each of these functions use another function, I conFromVa1 ue , which parses the results of the Defaul t I con value when it is retrieved from the Registry. F u n c t i o n IconFromExtension(ByVa1
FileExtension As String)
As Bitmap Dim MyKey As M i c r o s o f t . W i n 3 2 . R e g i s t r y K e y Di m My Defaul t Key As Mi crosoft .W i n32. Regi s t ry Key Dim myCR As M i c r o s o f t . W i n 3 2 . R e g i s t r y K e y Dim myDefaultIcon As M i c r o s o f t . W i n 3 2 . R e g i s t r y K e y Dim DefValue As String myCR = M i c r o s o f t . W i n 3 2 . R e g i s t r y . C l a s s e s R o o t MyKey = m y C R . O p e n S u b K e y ( F i l e E x t e n s i o n )
-
I VBA to VB.NET Reference I
875
I f MyKey I s N o t h i n g Then Return Nothing E x i t Function End I f ' F i r s t l o o k f o r Value DefValue = MyKey.GetValue("Defau1 t I c o n " ) I f D e f V a l u e I s N o t h i n g = F a l s e Then Return IconFromValue(DefVa1ue) E x i t Function End I f ' N e x t l o o k f o r Key D e f Va 1 u e = My Key. G e t Va 1 u e ( " " I f D e f V a l u e I s N o t h i n g = F a l s e Then M y D e f a u l t K e y = myCR.OpenSubKey(DefValue) I f M y D e f a u l t K e y I s N o t h i n g = F a l s e Then ' L o o k f o r Key my De f a u 1 t Ic o n = My D e f a u 1 t Key. Open S u b Key ( " D e f a u 1 t Ic o n " 1 I f m y D e f a u l t I c o n I s N o t h i n g = F a l s e Then Return I c o n F r o m V a l u e ( m y D e f a u l t Ic o n . GetVa 1 u e ( " " ) ) E x i t Function Else 'Look f o r Value Return Ic o n F r omV a 1 u e ( My De f a u 1 t Key. G e t V a 1 u e ( " De f a u 1 t Ic o n " ) ) E x i t Function End I f End I f End I f Return Nothing End F u n c t i o n ~
~
F u n c t i o n I c o n F r o m F i l e ( B y V a 1 F i l e I n A s S t r i n g ) As B i t m a p D i m myDI A s 1 O . D i r e c t o r y I n f o D i m myFI A s New I O . F i l e I n f o ( F i l e I n ) D i m myExe A s S t r i n g D i m n I c o n A s Long D i m t m p B i t m a p As B i t m a p myDI = New IO.DirectoryInfo(myF1.DirectoryName) myExe = S p a c e ( 2 5 5 ) FindExecutable(myFI.Name, m y D I . F u l l N a m e , myExe) nIcon = ExtractIcon(0, myExe.Substring(0, m y E x e . I n d e x O f ( C h r ( O ) ) ) , 0) I f n I c o n > 0 Then tmpBitmap = Bitmap.FromHicon(nIcon) Return tmpBitmap Else Return Nothing End I f End F u n c t i o n ~
F u n c t i o n G e t D e f W i n I c o n O As B i t m a p
876
I Chapter 39: Using VB.NET I Dim nIcon As Long Dim tmpBitmap A s Bitmap nIcon = ExtractIcon(0, "C:\Windows\System32\shell32.dll", 0) tmpBitmap = Bitmap.FromHicon(nIcon) Return tmpBi tmap End Function Function IconFromValue(ByVa1 ValueIn As String) As Bitmap Dim x S p l i t 0 As String Dim tmpBitmap A s Bitmap Dim nIcon As Long tmpBitmap = Nothing xSpl i t = Spl i t(Va1 ueIn, " , " ) If xSplit(0) = Then Return Nothing Exit Function End If Select Case xSpl it. Length Case 1 '.ico file If xSpl i t ( 0). Conta i n s ( " % " ) = Fa 1 se Then Then If xSplit(0) <> tmpBitmap = Bitmap.FromFile(xSplit(0)) End If End If Case 2 '.exe or .dll file nIcon = ExtractIcon(0, xSplit(O), xSplit(1)) If nIcon > 0 Then tmpBitmap = Bitmap.FromHicon(nIcon) End If Case Else Return Nothing End Select Return tmpBi tmap End Function " "
"I'
dgn.brnp
dgna.brnp
doc.brnp
doca.brnp
gz.brnp
gza.brnp
htrn.brnp
htrna.brnp
When this program is run, Bitmap files are created for each unique file extension found in the specified folder.
I Distributing VB.NET Applications I
877
DISTRIBUTING VB.NET APPLICATIONS Distributing a VB.NET Application is a matter of a few mouse clicks because it is built into VB.NET.
ConfigurationManager..?
1 Use Build > Publish XXXXXXX to begin the 'publishing' process.
Where do you want to publish the application?
878
I Chapter 39: Using VB.NET I We are asked where to publish our application. In addition to publishing to the hard drive, we can publish to an FTP server or Web server.
f
2
In our example, we w ill create a Setup that is to be run from CDROM or DVD-ROM.
.
I Distributing VB.NET Applications I
879
To simplify things, we will not use the Update feature. Ready to Publish! The wizard will now publish the application bared on your choices,
3 Clicking Finish causes the Publish Wizard to create the setup file. It takes a few moments to create the Setup file. “Publish building is shown in the status bar of the VB.NET IDE with an animated icon.
Here are the files generated by the Publishing Wizard.
880
I Chapter 39: Using VB.NET I
REVIEW The specific API calls used to control Microstation using VB.NET are not different in any way when compared with Microsoft Excel or VB6. We still add a Reference to the “Bentley Microstation DGN #.# Library”. We still use Getobject. The are other differences between VB.NET and VB6/VBA. These differences often result in greatly simplifying file and folder access as well as other areas of programming that had previously been difficult and tedious. There is little question that VB.NET is the future of VB programming. Although we can continue to develop powerful applications in Microstation’s VBA environment, it is a good idea to become familiar with the .NET world.
Additional Sources GENERAL VBA RESOURCES http://msdn.microsoft.com/vba VBA Overview, Whitepapers, etc.
http://bentleyinstitute.bentley.com/catalo-.aspx?discipline= 10 Programming Classes offered by Bentley Mastering Microsoft VBA; ISBN: 0782144365 VBA Developer‘s Handbook; ISBN: 0782 129781 Google, Yahoo, or other Internet Search for “VBA” or “Visual Basic for Applications” http://discussion.bentley.com Look for the bentley.microstation.v8.vba discussion group. VB and VBA in a Nutshell; ISBN: 1565923588
SQL STATEMENTS http://msdn.microsoft.com/library/en-us/tsqlref/ts sases 9sfo.asp?frame=true SQL Statement Explanations and Examples from Microsoft
881
882
I Additional Sources I
VB.NET http://msdn.microsoft.com/vbasic/
Visual Basic 2005 Programmer's Programmer); ISBN: 0764571982
Reference
MATHCAD www.mathcad.com
XML XML in a Nutshell, Third Edition, ISBN: 0596007647 XML Programming Bible, ISBN: 0764538292
(Programmer
to
INDEX
Index Terms
Links
Symbols & (ampersand) ' (apostrophe)
100 6
* (asterisk symbol)
105–106
\ backslash symbol
106
^ (carat symbol)
106
: (colon symbol)
291
/ (forward slash)
106
- minus symbol
105
| (pipe symbol)
729
+ (plus symbol)
104
.// (selectNodes)
592
736
A About Microsoft Visual Basic
21
Abs function
110–111
Absolute Value
110–111
Accept event Access, Microsoft
399 554–557
accessors
193
Action parameter
548
active design file
95
ActiveModelReference ActiveSheet
417 692–694
This page has been reformatted by Knovel to provide easier navigation.
Index Terms ActiveX Automation
Links 769
784
787
714–716
740
808 ActiveX Controls
816
compiling
830–832
creating, in VB6
822–823
debugging
827–830
registering
669
using
667–670
ActiveX Data Objects
556
ActiveX DLLs
816
ActualSpacing function
779
Add Note method Add Reference dialog Add-In file Add-In Manager
736–737 785 803–805 20
adding files to Setup package
843
item pairs
678
add-ins menu
20–21
Addltem method addition
161 104–105
Additional Controls
20
AddNew method
729–730
addresses, cell and range
697–698
ADO constants
746–749
ADOX reference
555
After Detach event
524
AfterActivate event
527–529
AfterAttach event
520–524
Alias, the
168–169
650 This page has been reformatted by Knovel to provide easier navigation.
Index Terms alignment
Links 362
buttons
367–368
creating
735
horizontal
365–368
vertical
368–370
AlignSelected method alphabetizing ampersand (&)
366–368 87 100
API Calls
649–651
API Types
650–651
apostrophe (') Append procedure Application, MicroStation Application Object
6 131 73 42–44
188–189
193
784
list of properties and methods in
194–222
in Mathcad
769–770
ApplicationObjectConnector
483
222
applications building in VB.NET
854–855
communicating with other
784
compiling in VB6
838
compiling Microsoft Visual Studio 2005
854
compiling VB6
837–838
distributing VB6
839
distributing VBA
501–503
distributing VB.NET ArcElement
877 75–76
arcs, creating
301
ArcTangent
110
This page has been reformatted by Knovel to provide easier navigation.
191
Index Terms arrays
Links 60
changing the size of dynamic returning, in a function code storing file extensions to ASC function ASCII Text files accessing, in VB.NET
416 371–372 446 96–98 19
exporting tags using
580–584
reading, using File System Object
676–677
reading and writing to
130–131
tab-delimited
481–483
asterisk symbol (*)
105–106 736
Atn (ArcTangent) function
110
AttachmentLog.txt
523
AttachmentModified event
525
detaching of
96
868 599–603
attachments
447
60
batch processing in
use in SQL
77
519–526 524
displaying
381–382
386
e-mail
620–621
680–681
list of object properties and methods for
521–522
list of properties and methods in
222–227
Auto List Members
190
autocomplete
154
Auto-Load Auto-Run
11
494
494–495
This page has been reformatted by Knovel to provide easier navigation.
647
Index Terms
Links
B backslash symbol (\) .bas (Module) files
106
190–191
16
BaseElement
573–574
BasePoint click
363–364
batch processing
599–621
Beep function
128
650
Before Detach event
524
526
BeforeActivate event Before Attach event BeginUndoRedo Event
527–529 525 537–538
“Bentley MicroStation DGN #.# Object Library
788
795
851 Bentley website help files
41–42
44
690–691
785–787
Bitmap files
859
873–876
Bookmarks
18
Boolean data type
72
Binding
Boolean value
113–114
BorderStyle property, of Image Control Break
167 20
Break point
357
Browse button
384
bubble sorting
372–373
bubble sorting function “Building” project
465 377
88–90 571
854–855
This page has been reformatted by Knovel to provide easier navigation.
817
Index Terms
Links
buttons alignment
366–368
Browse
384
Cancel
171
Change Current Selection
357–358
Close
358–359
Command
164
creating
798–802
Draw In MicroStation
737–738
Pick
172
Run Macro seed file selection Select
443
173
4 442 356–357
Spin
167
Step Into
12
Toggle
163
unassigned cursor
337
ByRef (By Reference)
64
68
ByVal (By Value)
64
65
C .CAB files
847
Cad Input Queue CadlnputMessage list of properties and methods in calendar
334–340
364
334 227–228 669
Call Stack window calling
18
27
57–58
class modules
394–395
PointString element
417
variables
421
This page has been reformatted by Knovel to provide easier navigation.
68
Index Terms
Links
Cancel button
171
“CantBeUndone” parameter
540
capitalization of text elements capturing events of external applications
80 400–402 287 106
Carriage Return constant
100
case sensitivity
80
cataloging files
679
Catch function
861
CDbl function
111–113
cell addresses
493
793
carat symbol (^)
CDO (Collaborative Data Objects)
443
620
680
697–698
cells adding to library
306–307
changing column designation in Excel
695
changing formula in
701
changing values in
701
creating declarations
304–307 305
finding specific Excel
694–695
selecting
346–347
Cells Collection
694–695
CGPlace Line Constrained command Change Current Selection Button
349 357–358
Change event
166
ChangeInX property
465
ChangeInY property
465
183
This page has been reformatted by Knovel to provide easier navigation.
Index Terms ChangeinZ property
Links 465
ChangeTag procedure
578–580
ChangeTrackEvents Interface
549–550
ChangeType parameters
531–532
channel levels of RSS files characters, counting
741 90–91
CheckBoxes
162
CheckLevel function
740
choosing an application
793–794
Chr function
96
CInt function
111–114
Circle element
738–739
circles
75
drawing
410–412
selecting center point procedure for
298–299
selecting two points procedure for
299–300
test procedure code for
298–299
297–300
class files
16
Class Module windows
19
30
class modules
54
394
adding
52–54
calling up
394–395
clsTimeTrack
491–493
interfaces and
392
lifecycle
395–396
MDL Applications and
387–388
Class_Initialize event
493
Class_Terminate event
493
ClassComplete property
424–425
This page has been reformatted by Knovel to provide easier navigation.
445
Index Terms classes
Links 52
67
784
161
732
18
52–54
516
26
135
see also objects adding
524
default file name of
394
implementing terminating declared Cleanup event
52–53 396 399
Clear
17
click events
29
CommandButton CLng function
836–837 111–114
Close and return to MicroStation function
17
Close Button
358–359
CloseMode parameter
360–361
.cls files
16
clsImageInsertion
389–390
clsLineElem
474–477
clsModelEvents code
527–529
clsStandCheckA code
626–627
adding to Standards Checker clsTimeTrack cmdCancel_Click code controlling execution of repeating use of, within VBA Project Manager viewing Code Modules
627–628 491–493 171
137–138 4
6
29–30 30
45
516 This page has been reformatted by Knovel to provide easier navigation.
51
Index Terms
Links
collection methods
316–324
Collections
470–477
colon symbol (:)
291
color changing active changing line
333 293–295
Color Table
294
ColumnCount property
160
318–319
ColumnCount property, for ListBox control
161
columns
555
COM interfaces
784
ComboBoxes
29
695 32
817–818 command area
330
Command Buttons
164
CommandButton
29
commands
22
capturing
352
input initiated as
337
348
CommandState.CommandName property comments compatibility, setting
541
548
6
47
834
compiling ActiveX Controls
830–832
applications in VB6
838
applications in VB.NET
854
Complete Word complexity, degrees of
18 368
This page has been reformatted by Knovel to provide easier navigation.
160–161
Index Terms Compressed (zipped) Folders
Links 507
Computer name function
613–615
Configuration Manager
854–855
Connection Object
715
719–722
Connection Strings
563
716
constants
78
100
ADO
746–749
cursor type List
722–723
725 17
lock type
725–726
message boxes
118–120
QueryClose
360–361
vbProperCase contents tab
85 35
control events
154–155
control names
377
control placement
363
control properties
377
controls
20
29
353–355 ActiveX adding
669–670 168
creating initializing properties for ComboBoxes
822–826 829 160–161
properties for ListBox
161
properties for TextBox
159
registering standard in Toolbox
830–831 27 152
668
This page has been reformatted by Knovel to provide easier navigation.
151
Index Terms
Links
controls (Cont.) TreeView
822–826
use of frame with
164
ControlTipText property
158
360
converting strings to numbers
111
supplied parameters to doubles
112
coordinates
351
CopyFileToZipFile procedure
508–509
Cos (cosine) function
107–108
counting characters elements Create button
90–91 374 13
Create3dLines
291–294
CreateArcElement
301–303
CreateCellElement
305–307
CreateCircle
298–300
CreateDatabaseLink
564–565
CreateDesignFile
307–309
CreateDialog
459
CreateEllipseElement
297–298
CreateLineElement
289–295
CreateObject
691
CreatePolygon
296–297
CreateShapeElement
295–296
CreateTextElement
303–304
cursor buttons, unassigned
337
cursor movement
426
cursor type constants
725
300–301 789
This page has been reformatted by Knovel to provide easier navigation.
790
Index Terms
Links
Custom Class Modules
474–478
Custom file Properties
686–687
Custom tab Custom types Cut
685 282–283 17
D data, security issues with creating
309
DataBaseLink object
552
databases connecting to connecting to records in
557–561 724
creating
563–564
creating a user interface
566–567
creating in-memory
740–741
creating new
554
creating records with SQL
565–566
examining schema of
744–749
linking elements to
552–554
DataPoint event
407–408
date and time functions
122–124
date data type DateAdd function
719
287
72 122–123
DateDif function
123
DDE (Dynamic Date Exchange)
784
Debug folder
854
debug menu
19
debug mode
12
debug toolbar
22
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
debug window, see immediate window De-bug.print declaration of API calls
85 189 649–650
area
66
default setting
67
DIM
66
form functions
66
public
66–67
of variables of variables in Mathcad
70
76
650–651 66 762
default file name
436
DefaultFileName property
453
“define” region
773
Definition
355
431
private of types
282–283
18
DeleteSetting function description area
129 12–13
design files attachments
381–382
creating new
307–309
exporting to
377
saving changes to
333
Design Mode Design Time, setting properties at DesignFile Object
20 152 74
DesignFile property
522–523
DGN Browser application
862–865
228–230
This page has been reformatted by Knovel to provide easier navigation.
Index Terms DGN files
Links 74
compressing into Zip file creating levels in numbering within zip files opening multiple searching for dialog boxes
507 796–802 510 484–485 382 12
Information
443–444
MDL Applications and
387–388
OpenAlert
446
353
432
dialog functions
432–444
DialogOpenfile command
352–353
Dictionary Object
676–677
Digital Signature
20
DIM statements
6
66
Dir function
125–127
603
directories
124–125
displaying
23
attachments Excel files models and levels in Active Design File
381–382 437 822–826
more than one file type
438
one file type at a time
437
RSS files text as code executes distribution division
743–744 26 374–375 106
division points
422–424
DivPts property
422–424
This page has been reformatted by Knovel to provide easier navigation.
443
Index Terms DLLs
Links 20
creating Active X
832
Do ... Loop statement
138–140
DoCellInsertion docking tab
431
670
364
173 27–28
documents, creating
307–309
DoFilesinFolder procedure
484–485
double precision floating point number doubles
71 48
Draw In MicroStation button DrawCircle
58
737–738 420
467–468
drawing a 2D plate circles
771–772 75
in Mathcad
775–781
rectangles
409–410
DrawLine method
466
DrawLinePerp method
466–467
DrawPart procedure
776–778
DriveLetters variable
410–412
652
drives determining type of getting all network
652–653 676
using File System Object to get information
675–676
DSO OLE Document Properties Reader 2.0 DSOFile Reference DTR (Degrees to Radians)
681–682 681–682 59
This page has been reformatted by Knovel to provide easier navigation.
71–72
Index Terms Dynamic Data Exchange (DDE) Dynamics event optimizing
Links 784 399–404
407–408
426–427
E early binding
690
edit button
12
edit menu
17
edit toolbar
22
Element objects
230–236
ElementChanged event
539–540
ElementEnumerator elements
691
236 369–370
deleting
547–548
displaying DatabaseLink in message box
565
finding specific
404–406
ID property of
577–578
linking to database records modifying saving changes to design file selecting with two point method user selection of ElementScanCriteria object
564 539–542 333 341–342 332 316–318
list of methods
325–326
list of properties and methods in
236–237
EllipseElement
379
75
ellipses, creating
300–301
410–412
e-mail, sending
619–620
680–681
in VB.NET
870
This page has been reformatted by Knovel to provide easier navigation.
785–787
Index Terms
Links
Empty String
435
Enabled property
157
EndPoint properties
463–465
Entity property
565
EntityNumber property
552
enumerations
243–245
ErrorType
639
for horizontal alignment of text
366
list of
245–277
MCRegionType
765–766
MsdACSType
245
MsdAddAttachmentFlags
245
MsdAngleAccuracy
245
MsdAngleFormat
245
MsdAngleMode
246
MsdAttachMode
246
MsdBsplineCurveOffsetCuspType
246
MsdBspIineCurveType
246
MsdBsplineParametrizationType
246
MsdBsplineSurfaceDirection
246
MsdBsplineSurfaceType
246–247
MsdCadlnputType
247
MsdCellType
247
MsdChangePropagation
247
MsdChangeTrackAction
538–539
MsdCommanResult
248
MsdConversionMode
248
MsdCoordinateAccuracy
248–249
MsdCoordinateFormat
249
MsdCopyContextLevelOption
249
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
enumerations (Cont.) MsdCopyViewPort
249
MsdDatabaseLinkage
249
MsdDataEntryRegionJustification
249
MsdDesignFile Format
250
MsdDevelopableElementOutputType
250
MsdDialogBoxResult
250
MsdDimAccuracy
250–251
MsdDimAlignment
251
MsdDimAlternateThresholdCompanion
251
MsdDimAngleMeasure
251
MsdDimBallAndChainAlignment
251
MsdDimBallAndChainChainType
252
MsdDimCustomSymbol
252
MsdDimDMSPrecisionMode
252
MsdDimLabelLineFormat
252
MsdDimMLNoteFrameType
252
MsdDimMLNoteJustification
252
MsdDimNoteHorizontalAttachment
252–253
MsdDimNoteLeaderType
253
MsdDimNoteTextRotation
253
MsdDimNoteVerticalAttachment
253
MsdDimNoteVerticalJustification
253
MsdDimPlacementTextPosition
253
MsdDimRadialMode
253
MsdDimStackedFractionalAlignment
254
MsdDimStackedFractionType
254
MsdDimStyleProp
254–260
MsdDimSuperscriptMode
260
MsdDimSymbolType
260
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
enumerations (Cont.) MsdDimTerminatorArrowhead
260
MsdDimTerminatorMode
260
MsdDimTerminatorType
260–261
MsdDimTextField
261
MsdDimTextFormat
261
MsdDimTextFrameType
261
MsdDimTextJustification
261
MsdDimTextLocation
261
MsdDimTextOrientation
262
MsdDimThousandOpts
262
MsdDimToleranceType
262
MsdDimType
262–263
MsdDimValueAngleFormat
263
MsdDimValueAnglePrecision
263
MsdDimVerticalTextOptions
263
MsdDrawingMode
263
MsdElementCachePurpose
263
MsdElementClass MsdElementSubtype
263–264 264
MsdElementType
264–265
msdElementType
312–313
MsdError
265–271
MsdFileAccessMode
271
MsdFillMode
271
MsdFontType
271
MsdGeoReferenceSisterFileType
271
MsdGlobalLineStyleScale
271
MsdLevelChangeType MsdLevelElementAccess
271–272 272
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
enumerations (Cont.) MsdLimits
272
MsdMeasurementBase
272
MsdMeasurementSystem
272
MsdMemberTraverseType
272
MsdMessageCenterPriority
273
MsdModelChangeType
273
MsdModelType
273
MsdNestOverrides
273
MsdNewLevelDisplay
274
MsdRasterBlockType
274
MsdRasterDisplayOrderCommand
274
MsdRasterDisplayPriorityPlane
274
MsdRasterModificationType
274
MsdRasterWorldFile
274
MsdReferenceSystem
274–275
MsdRenderingMode
275
MsdStandardsCheckerReplaceChoice
275
MsdStandardsCheckerReplaceOptions
275
MsdStatusBarArea
275
MsdTagType
275
MsdTangentElementOutputType
276
MsdTangentInterpolationType
276
MsdTextDirection
276
MsdTextJustification
276
MsdTextNodeLineSpacingType
276
MsdV7Action
276
MsdViews
277
MsdXDatumType
277
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
enumerators and scan criteria objects Err object
321–322 147
error handling default path validation of error in VB.NET
142–148 52
“eval” region
773 549–550 6
29
468–470
519
Accept
399
accept
399
After Detach
524
AfterActivate
527–529
AfterAttach
520–524
application object
189
AttachmentModified
525
BeforeActivate BeforeAttach BeginUndoRedo
285
527–529 525 537–538
capturing time
493
Change
166
Class_Initialize
493
Class_Terminate
493
Cleanup
399
click
452
860–861 639
events
791
451–452
“ErrorType” enumeration Event Handler
639
29
CommandButton click events
836–837
control
154–155
183
161–164
This page has been reformatted by Knovel to provide easier navigation.
836
Index Terms
Links
events (Cont.) DataPoint Before Detach
407–408 524
526
Dynamics
399–404
407–408
426–427
ElementChanged
539–540
406–407
420
96
155
160
169–170
364–365
Form Load
817
IAttachmentEvents Interface
519
ILevelChangeEvents Interface
531–532
ILocateCommandEvents Interface
396–397
IModelActivate
527–528
IModelChange
527–530
Initialize
183–184
IPrimitiveCommandEvents
392–393
ISaveAsEvents Interface ISaveAsEvents_BeforeRemap
510 510–511
IViewUpdateEvents_AfterRedraw
493
KeyDown
160
Keyin
416
KeyPress KeyUp
160
LineAdded
468–469
LocateFailed
399
LocateFilter
398
LocateReset
399
ModelChange
400
527–530
MouseDown
155
MouseMove
359
OnDesignFileClosed
286
493
509–510 This page has been reformatted by Knovel to provide easier navigation.
507
Index Terms
Links
events (Cont.) OnDesignFileOpened
286
QueryClose
360–361
RoomLabelCheck
635–638
RunCheck
634
Scroll
166
Show
287
Start
399
UserFormInitialize examples folder Excel
168–169
360
39 437
Add-In files
803–805
connecting to
689–690
customizing
798
as databases
749–754
MicroStation
793–795
opening
753
starting or creating new instance of
691
XML file exported from
493
793–794
593–594
Excel Function RAND
704
Excel.Application Object
692
.exe files
815
EXE files
839
Execute method
719
execution of code breaking immediate
91 26–27
Exit Do statement
139
Exit function statement
139
Exit Sub statement
139
364
This page has been reformatted by Knovel to provide easier navigation.
506
Index Terms
Links
exponents
106
export file
16
exporting field names
735
tag information
580–584
user forms to a design file
377–379
ExtCount
447–448
“Extended Property”
749–754
Extensible MarkupLanguage. see XML ExtractIcon
873
F favorites tab feedback
37–38 329–331
capturing user
359
field names, exporting
735
fields
375–376
746–749
Fields Collection of the Recordset Object File
735 19
file extensions
445–453
458
460
576
675–678
508 file headers
508
file menu
16
file names
510
File Open dialog box
436
File System Object
484
FileCreate function
458–459
FileDateTime function
124
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
FileLen function
124
FileOpen function
432
files adding to Setup package ASCII Text batch processing of all folder cataloging, using Dictionary Object filtering finding
843 19 603–604 679 317–318 3
identifying loading
665–666 2
processing, in folders and subfolders
607
reading in VBA
868
reading in VB.NET registering
868–869 837
saving as Bitmap
873–874
scanning
311–314
searching
311
sending, to CD writer types of user interface for
512–513 604 608–613
writing in VBA
869
writing in VB.NET
869
zip
841
507–508
FilesInFoldersmethod
385–386
FileSystemObject
385
FillMode parameter
296
filtering
317–318
Find
404–406
17
Find method
727–728
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
FindExecutable
656–657
Fix function
112
floating point
71
fmStyleDropDownList
169
FolderIn parameter
607
folders
873
23
processing
606–608
root
382
selecting
382
traversing folder-sharing, handling font size
675
870–871 495–496 20
footers
179
For ... Next statement
135–137
For Append
131
483
For Each ... Next statement
141
472
form files
16
Form Load event
817
Form1 (VB6)
810
Format function
100
format menu
19
FormatCurrency function
98
FormatDateTime function
99
FormatNumber function Forms
98–99 49
creating with VB6 displaying as modeless public
353
809–816 361 67
window forward slash (/)
28–29 106
This page has been reformatted by Knovel to provide easier navigation.
Index Terms frames
Links 164
FreeFile
168
131–132
.frm files
16
frmAlign.Text.frm
362
frmDFAV.frm
381
frmExportElements.frm
377
frmMatchProperties.frm
353
FSOC
675–676
Function (keyword) functionality encapsulating similar functions
55
58–63
457–458
517
445 6
DLLs and VBA string functions
55
650
431 84
G geometry, creating MicroStation
771
GetAllRegions function
773–774
GetAllSettings function
129–130
GetComputerName
613–614
GetDiskFreeSpace
657
GetDriveType
652–653
GetEvals
774–775
GetExts
447
GetInput method
335
GetLogical Drives
652
GetObject
653
689–690
GetSelectedCount
374
GetSetting function
128
GetString method
728
450
788–789
This page has been reformatted by Knovel to provide easier navigation.
818–819
Index Terms GetSystemMetrics GetTags
Links 658–659 573
GetThreeVals
65
GetTickCount
659–660
Getting All Files
871–872
GetType functionality
544–547
GetUserName
660
GetValue
759–760
GetVersionEx
653–655
GetWindows Directory
660–661
graphical elements Graphical User Interfaces (GUIs) grid
772–773
289 7
49
96
20
group collection method
324–325
grouping
474–478
groups, creating named
323
H hardcoding
47
headers
178
height property
157
351
help files Bentley website
41–42
Object Browser
190–191
Visual Basic help menus
44
21
34
21
30
High Security Mode
502–503
.htm files, creating
582–584
Hungarian notation
79
This page has been reformatted by Knovel to provide easier navigation.
34
Index Terms
Links
I IAttachmentEvents Interface ID property
519 577–578
IDE (Integrated Development Environment)
856
If. . .Else If statements
178–182
If. . .Then statements
141–142
ignored problems
645–647
ILevelChangeEvents Interface
531–532
ILocateCommandEvents Interface
396–397
Image control images, inserting
167 387–389
IMathcadApplication2
770
IMathcadRegion2Object
767
Immediate Window
18
IModelActivate events
527–528
IModelChange events
527–530
Implements (keyword) import file
26–27
392 16
Import Image MDL application IncludeOnly Within Range method Indent
387–388 326 17
Index attribute index tab
595–596 36
information retrieving from a web site
617–618
storing
615–616
Initialize event
183–184
initializing, UserForm
741–742
360
This page has been reformatted by Knovel to provide easier navigation.
742
Index Terms InputBox
Links 120–122
Input Queue
174
input types
335
inputs, capturing user “Insert Into” SQL statement Insert menu
338–341 737 19
InStr function
92–94
InStrRev function
94–95
Integer
605
70
integers, converting
111–113
Intellisense
786–787
Internet, logging activities over
616–617
Internet Explorer
785–787
Internet help IPrimitiveCommandEvents ISaveAsEvents Interface IsNumeric function IsOleFile
40–41 392–393
406–407
510 113–114 686
IStandardsChecker
624–625
Item child in RSS files
741
item index
474
item levels in RSS files
741
IViewUpdateEvents_AfterRedraw
493
J Join function
95
K KeyAscii parameter
170
365
This page has been reformatted by Knovel to provide easier navigation.
420
Index Terms
Links
KeyDown event
160
Key-In dialog box
352
Keyin event
416
Keyin window
337
key-ins
417
KeyPress Event Keys, using KeyUp event keywords
96
155
169–170
364–365
160
473–474 160 78
Kill function
127
L Label control
158
late binding
690
Lcase function
84–85
LCE_DistanceText
402–404
LCE_Text
397–398
Left function
90
Left property
156
Len (length) function Level Mapping file Levels
785–787
90 488–489 74–75
adding to a collection
473
batch processing
600
CheckLevel function for
704–706
creating in .dgn file
796–802
equalizing
738–740
list of properties and methods in
237–238
modifying
548–549
377
This page has been reformatted by Knovel to provide easier navigation.
531
Index Terms
Links
Levels Collections
140–141
LevelSpecA procedure
485–487
libraries
24
LineAdded event
468–469
LineAngleDegs
465–466
LineAngleRads
465
LineElement lines
75 178–179
3d
291–294
creating
289–293
drawing with cursor
407–409
linking elements LinkLabel
552–554 860
List Constants
17
List Properties/Methods
17
ListBoxes
161
ListView controls
859
IngDivisions property
424
load behavior load project button Locals Window LocateCriteria object
21 3 18
26
404–406
LocateFailed event
399
LocateFilter event
398
LocateReset event
399
Lock project property
500
lock type constants
725–726
log files
613–614
LogonUser
661
LogToWeb
617
400 797
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
Long number
70–71
lower case function
84–85
111–112
M macros
3–5
recording
348–351
running
793–794
running a MVBA
5
running Excel
797–805
using Mathcad
757–759
Macros dialog box
12
Make Project dialog box (V6)
811
Make Project Group
837–838
mapping standards
485–488
Match Properties Form
20
362
Mathcad advantages of using
772
communicating with
763–766
overview
755–756
Mathcad Object Model
769–770
MathInterface property
767
MathRegion
766
mcMathRegion MDL Applications mdlDialog_fileCreate Function
765–766 387 439–440
mdlDialog_fileCreateFromSeed Function mdlDialog_fileOpen function mdlDialog_openAlert Function mdlDialog_openInfoBox Function
441–442 432–434
453
443 443–444
This page has been reformatted by Knovel to provide easier navigation.
Index Terms members list memory, opening files in menus
Links 24
189
496–497 15–21
message boxes
117–120
MessageBeep function
662–663
methods Add Note
736–737
AddItem
161
AddNew
729–730
AlignSelected
366–368
application object collection
189 316–324
creating new in Class module
459
DrawLine
466
DrawLinePerp Execute
466–467 719
FilesInFolders
385–386
Find
727–728
GetInput
335
GetString
728
group collection
324–325
IncludeOnlyWithin Range
326
interfaces and
392
MoveFirst
727
multi-criteria collection
168–169
321–324
New
692
OpenDialog
446
OpenSchema
746
overloaded
867–868
Pages.Add
165
751
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
methods (Cont.) Pages.Count
165
ScanCriteria collection
316–322
Show
183–184
ShowError
332
ToUpper
867
Update Microsoft Access
729–730 556–557
Microsoft ADO Ext. 2.8 for DDL and Security Reference
554
Microsoft CDO for Windows 2000 Library Microsoft Excel #.# Object Library
680–681 805
Microsoft Internet Controls Reference Microsoft OLE DB Simple Provider
617
787
740
Microsoft Package and Deployment Wizard Microsoft Scripting Runtime
841–847 385
484
606
674
605
606
21
34
576
Microsoft Shell Controls and Automation Microsoft Visual Basic Help MicrosoftSpeechObjectLibrary MicroStation
679 18
MicroStation attaching to project in Visual Studio 2005 controlling with VB6
852–853 816
creating new instances of
790–791
Excel versus
793–795
This page has been reformatted by Knovel to provide easier navigation.
671
Index Terms
Links
MicroStation DGN file
74
MicroStation VBA Help file
39
MicroStationDGN.ApplicationObjectConnector
818–819
MicroStation-Specific variable types
73
Mid function
91–92
Midpoint property
463–466
minimum and maximum points of selected text minus symbol MkDir function Mod function ModelChange event
369–370 104 124–125 114 527–530
modeless dialog boxes
353
modeless form display
173
ModelReference Object
74
list of properties and methods in module files
238–241 16
modules
45–46
insert
19
opening
30
MonthName function
67
86
MouseDown event
155
MouseMove event
359
MoveFirst method
727
MS_STANDARDSCHECKERAPPS
647
MS_VBA_OPEN_IN_MEMORY
361
495–497
MSCatalog table
552
MsdDesignFile Format
243
MsLink property
552
561
565
565
588–589
This page has been reformatted by Knovel to provide easier navigation.
Index Terms multi-criteria collection method Multipage control
Links 321–324 165
multiple users, log files for
613–615
multiplication
105–106
.mvba extension myExcel
3
613–614
690–691
N Name property
189
named group search
323
naming controls conventions variables .NET Framework New (keyword)
155 79
355
287
660
42–44
78–79 872 789–790
New method
692
“NotifyIcon” Control
863
Now function
122
numeric functions
103
NumericValue
760
NumericValueObject
156
760–761
O Object, view menu
18
Object Browser
18
24
149
757
description of
188–190
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
Object Browser (Cont.) displaying Members of the Object opening Microsoft VBA Help in object data type
671–672 40 72–73
Object Model
42
Object Names
784
objects
29
accessing, in a collection application type of creating
469
506 462–469 552
removing, from a collection
474
setting
786
471–473
DataBaseLink returning
784
63 76–77
in VB.NET OLE Documents
866–867 681–682
OLEDB connection
554
OnDesignFileClosed event
286
493
507 506
509–510 OnDesignFileOpened event
286
493
OnProjectLoad procedure
495
647
OpenAlertDialog Box
432
OpenDesignFileforProgram
386–387
OpenDesignFileOpened
506
OpenDialog method
446
opening files
386
438
OpenSchema method
746
751
operating system, determining
653–655
OptionButtons
162–163
option explicit
67
80–81
This page has been reformatted by Knovel to provide easier navigation.
Index Terms Options
Links 20
Order By statement (SQL) Order of Operations
732–733 116
Outdent
17
Output
483
overloaded methods
867–868
P Pages.Add method
165
Pages.Count method
165
Pan operations
183
ParamArray Parameter Info parameters
57–58 17 47–49
57
65
68 “parcel” table
553
parenthesis, using
853
in VB.NET
868
passwords protecting
498–500
requesting from user
661–662
Paste
17
Path property Paths
189 451–452
Pause Recording Macro
656–657
11
PCE_CircTest
410–412
PCE_LineTest
407–408
PCE_PointStringTest
414–416
PCE_PolyTest
412–414
PCE_RecTest
409
417–420
This page has been reformatted by Knovel to provide easier navigation.
424–426
Index Terms PCE_TestLine personalizing help files pFileExts
Links 420–424 38 447–450
Pi function
59
Pick button
172
PictureBox control
873
pipe symbol (|)
729
Place Block
341–343
Place Line command
341–343
PlaySound function
662–663
plus symbol (+) Point List Reader
173
350
104 174–177
Point3d type
279
PointElems array
292
283
points capturing
337
user selection of
363–364
PointsByLine function
341–344
PointsByRectangle function
345–346
PointString PolarPoint function polygons, creating
416
414–416
417
62–63 296
Poly Test
412–414
PopulateFileList
384–385
412–414
populating a collection with levels ComboBoxes porting Print
474 817–818 847–848
Preserve (keyword)
363
416 17
This page has been reformatted by Knovel to provide easier navigation.
463
Index Terms Private (keyword)
Links 66
Private Sub Form1_Load
859
Private Sub Form1_Resize
860
Private Sub Linklabel_LinkClicked
860
Private Sub lstvCells_DoubleClick
860
Private Sub 1stvCells_ItemSelectionChanged problem report
859–860 645–647
Procedure
19
procedures
6
55
66 processing
606–608
processor time, maximizing
426–429
project, creating a VBA
46–47
Project Explorer
18
Project Explorer
23
Project Groups in VB6 project manager
827–828
837–838
3
projects creating in Microsoft Visual Studio
851
distributing
500
protecting
498–499
un-signed
502–503
prompt area properties Custom file implementing, for classes viewing, of variables writing
330 20
application object
811
152
189 686–687 52 192 682–683
This page has been reformatted by Knovel to provide easier navigation.
57–58
Index Terms Properties Window
Links 18
“Property Get” statement
449
“Property Let” statement
449
provider component of UDL file
563
Public (keyword) publishing process
25
152–153
66–67 877–878
Q Query Builder QueryClose event Quick Info
557
562
360–361 17
R R1C1 reference style
696–697
radians
107–108
“Radius” parameter RaiseEvent statement RAND function
56 469 704–705
random number, function to generate
115
range addresses
697–698
Range Collection
695
reading ASCII Text files in VBA in VB.NET from the Windows Registry XML files Read-Only property
130–131
132–135
868 868–869 616 587–594 449
496–498
This page has been reformatted by Knovel to provide easier navigation.
676–677
Index Terms Record Macro
Links 11
RecordCount property
726
records, adding new
737
records, database Recordsets
565–566 724
731
rectangles, drawing
410–412
523
recursive execution
606–608
675
Redo
17
References
20
500–501
785
795
817
840 adding
484
adding, to DLLs
670
adding, to Microsoft Excel Object Library
690
“Bentley MicroStation DGN #.# Object Library
788 851
DSOFile
681–682
Mathcad
756
Microsoft ADO Ext. 2.8 for DDL and Security Microsoft Internet Controls
554 617
787
Region Objects
766–767
773
Regions, in Mathcad
765–767
registry editor
382
RegSvr32.exe
830–831
relative coordinates
351
Release folder
854
remainder value
114
Remove
17 This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
removing objects from a collection
474
removing projects from VB6
812
Replace
17
Replace function
92
ReportError procedure Reset
639–640
644–645
20
Reset Input resetting Level names procedure
338 487–488
Return statement
872
return value array
373
return values
434
setting
373
of user forms
441
rewriting element to model right function
314 90–91
RmDir function
125
Rnd function
115
RoomLabelChecks event
635–638
Root Folders
382
Round function
114
rows and columns in Excel
695
RSS files
741
RTD (Radians to Degrees) Run Macro run menu
325
605
743–744
59 4–5
11
20
RunCheck event
672–673
634
running a project automatically
494–495
run-time, setting properties at
152–154
This page has been reformatted by Knovel to provide easier navigation.
20
Index Terms
Links
S Save button
22
SaveAs functionality
510–517
SaveSetting function
128
saving class modules into VBA Project projects with CreateDialog method ScanCriteria collection method scope
461–462 459 316–322 66–68
Scroll Bars
166
Scroll event
166
search and replace function
92–94
search tab
37
searching
311
folders for .dgn files
382
sub folders for .dgn files
382
security issues Seed file
309
502–503
441–442
Select All
17
select button
356
Select case statement
118–119
error handling
142–147
Select statement (SQL)
731–732
selecting folders
382–384
selection sets, working with selectNodes
332 591–592
SendCommand
348
SendDragPoints
341
sending files to CD writer
314
512–513
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
settings retrieving
382
saving to Registry
383
storing
382
Settings dialog box SetUp program
630 841–846
SetValue call
764
Sgn function
115
shapes, creating
295
sharing, handling folder
495–496
Shell32 BrowseForFolder
672–674
ShellExecute
664–665
SHGetFilelnfo
665
shortcuts
844
Show method
183–184
ShowCommand
339–340
ShowError method
332
ShowEvents procedure
287
ShowPrompt ShowTempMessage Sin (sine) function Sleep function SND_ASYNC flag
383
339–340
359–360
331 107–108 656 663–664
sorting bubble
88–90
in SQL
732–733
text
87–90
sounds
663–664
Space function spaces, removing
372–373
435 86
This page has been reformatted by Knovel to provide easier navigation.
377
Index Terms
Links
spacing, text
368
SpacingXIn formulas
779
SpacingYIn formulas
779
speech capabilities
679
Spin Buttons
166–167
Split function
95
375–376
SQL Query Builder
561
565–566
SQL Select Statements
562
730–731
Sqr (square root) function
107
squares function
106
Standard EXE
810
Standard toolbar
22
Standard VBA variable types
70
839
standards checking for cross-company maintaining
634 485–487 479
Standards Checker Interface
480
Start event
399
Start Up project
828
StartPoint properties
813
State property
722
status area
330
Stop
830
463–465
Startup Objects
Step Into buttons
624–625
731–732
12 465
StopRecording Macro
11
storing confidential information information
449 615–616
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
storing (Cont.) point selections
366
settings
387
632
StrComp function
87–88
StrConv function
85
string data type
72
83
32–33
70
buffered
652
653
concatenate strings
100
counting character
90–91
string type variables strings
in VB.NET String Value in Mathcad “Structured Storage” Documents Style property SUB (keyword)
866–867 761 681–682 169 55
subfolders
870–871
subtraction
105
Summary Properties
682–684
T Tab Order
18
Tab strips
164
tab width
20
tab-delimited files
483
TabIndex property
157
tablename field
552
tables, creating
555–563
TabStop property
157
Tag property
158
This page has been reformatted by Knovel to provide easier navigation.
Index Terms tags
Links 571–578
extracting information TagSetName Tagsets
707–711 573 575–576
TakeFocusOn Click property Tan (tangent) function
164 107–110
temporary message
331
temporary storage location
513
terminology “Test Connection” message box TestCadInputN
32 560–561 352
testing ActiveX DLL
836
clsTimeTrack
493
default path DrawLinePerp method file extensions startpoint and endpoint
451–452 467 447–450 464
TestMatchProperties
361
text alignment
362
text boxes
37–38
364
397–398
400–401
text elements capitalizing comparing creating determining number of text files
87 303 369–370 95–96
TextBox control TextElement Object “TheAttachment” parameter
158–159 76 521
This page has been reformatted by Knovel to provide easier navigation.
Index Terms Thumb
Links 166
time, tracking
490–493
Timer function
124
Title parameter
120
Toggle button
163
ToMiles function
107–108
Tool Tip
32
Toolbars
18
toolbars
22–23
798
Toolbox
18
840
27
152
controls in Tools Menu
20
Top property
156
ToUpper method
867
tracking
615
transaction logs
619–620
translating standards
485–487
traversing a folder and subfolder
870–871
Trim function
86
Try function
861
.txt files
445
Type Libraries types
20
785
279–283
API
650–651
available in MicroStation VBA
281–282
MicroStation-specific variable
73
returning
62–63
standard VBA variable
70–73
This page has been reformatted by Knovel to provide easier navigation.
668
Index Terms
Links
U UCase function UDL files
84 557–560
connecting to Excel file
750–751
opening a connection using
722–723
RSS and UnassignedCB input
741 337
underscore character (_)
60
Undo
17
Unload Me un-signed projects
563
171 502–503
unsupported levels procedures, finding Update method Upper case function
480–482 729–730 84
user feedback
375–376
providing
379–380
User forms exporting to a new design file VB6
49
860
377–379 812
user input, getting
334–337
user interface exercises
167–174
user interfaces
566–567
UserForm
608–613
19
code
286
Initialize event toolbar
168–169
360
23
users, capturing current
287
This page has been reformatted by Knovel to provide easier navigation.
715–719
Index Terms
Links
V Val function validating path, default Value property values
113 451–452 162
163
166
167
63
assigning constant retrieving cell, in Excel
76–77 78 694–695
retrieving in Mathcad
773
returning function
872
tag
572
variables
6
adding a watch to
192
calling
421
configuration defined as dynamic arrays MicroStation-Specific naming conventions for as new class type
66
68
469
496–497 70 371–372 73 78–79 395
as private
66
as Public
450
setting, to an object
786
as a string
435
types of
70
using
81
variants
165
61
67
This page has been reformatted by Knovel to provide easier navigation.
73
Index Terms
Links
VB6 differences between VBA and
807–808
MicroStation
816
structure of projects
809
VB6 Virtual Machine file
839
VBA differences between VB6 and overview string functions in variable types in windows VBA editor VBA Files From Levels form VBA IDE
807–808 1–5 84 70–73 23 12 380–381 15–30
VBA Project Manager elements of
3–5 10
VBAPM
3–5
vbCr constant
100
VB.NET
850
vbProperCase
85
vbTab constant
101
vertical order
7
369–371
View Code
29–30
View menu
18
visible property
157
164
Visual SQL Query Builder
557
562
Visual Studio 2005
850
voice capabilities
679
This page has been reformatted by Knovel to provide easier navigation.
9–13
Index Terms
Links
W Watch Window
18
watch window
25
watches, adding
192
WeekDayName function
85–86
WeekDayNumber function
85–86
Where statement (SQL) While ... Wend statement
732 137–138
Width property
157
window menu
21
Windows API Calls Windows API functions Windows Registry
872 45
382–384
649–650
615–616
632–633
872
506
792–793
Windows Status Notification Area
863
WithEvents (keyword)
286
WithEvents form
469
Wordwrap property
159
workbooks
692–693
worksheets
692–694
wrappers
130
701–702
832
Write Out File Write-Only property
177–178 449
writing files ASCII
130–132
using File System Object
676–677
from the Windows Registry
615–616
X x and y text boxes
364
This page has been reformatted by Knovel to provide easier navigation.
Index Terms
Links
.xla files
803–805
.xls files
445
XML button
741
overview
585–587
XML files reading structure of tracking row and column of cells Xpath statement
587–594 586 595–597 591
Z zip files
507
Zoom operations
508
182–184
This page has been reformatted by Knovel to provide easier navigation.
Sign In