Você está na página 1de 700
“Using ObjectARX™ EY Autodesk, Press 2% DELMAR US CENGAGE Learning: ‘Africa Australia Canada « Denitiark + Japa » México “New Zealand - Philippines Puerto Rico + Singapore Spain « United Kingdom» United States % % DELMAR oS CENGAGE Learning Programming AutoCAD® 2000 Using ObjctaRx Charles McAuley rectve Director Alar ken rective Editor sandy Clark Aequistons ator: Michael Kopf Developmental itor: John Fisher Foal assistant: Allyson Powell Executive Markting Manager: ‘Maura Theriault ecutive Production Manager ‘Mary Elen Black Prodetion Cordinstor Jennifer ines ‘Art and Design Coordinator Mary Beth Vought Maseting Coudinaton Paula Coline Technology Project tor: Tom Smith Cover iustations: Bruce Rosch Printed inthe United Stats of America 456711 100908 {© 2000 Delmar Cengage tearing ALL RIGHTS RESERVED. No part of his work covered bythe copyright erin maybe ‘reproduce transmittal, stored or ued in any form or by any means graphic, ‘ectroni, of mecha including but ot lite to photocopying, recording. "canning izing apne Web distribution information networks, oinfoemation storage ad retrieval stems except a permitted under Section 07 or 108 ofthe 576 ‘United States Copy Act without the prior writen emission ofthe publisher. or red infomation teil sistas, contacts ‘Cengage Learning Customer Sales Suppor, 18003549706 For emis tous mate mths texto produ, subitaleauess nine tcengaecom/permsions Ture parsons queston eb ald permisinrequestocenggecom ‘AwtoCAD® and the ALtoCAD logo ae registred ademarks of Autodesk Inc. Delmar Learning ses Autodesk Press with permission from Autodesk, nc. for certain purposes Windows ea trademark ofthe Microsoft Corporation. All other product ames are acknowledged a5 rademars ofthe respective ones brary of Congress Control Number 9.048585 ISBN: 978-07668-05436 IsBN10:0.7658-.0688% Delmar Cengage Leming S Manvel Dive ‘lffon Park, NY 120652919 Usa Cengage Learning products are represented in Canada by Nelson Education, td, For your ifelong leaning solutions vist delmarcengage.com Visit ou corporate west at ww.cengagecom Notice tothe Reader Publisher does not warantor guarantee any ofthe products described herein perform any independent analysis in conneton with any ofthe product information Contained herein Publser doesnt assure, and expres iain, any obligation toobtan andincudenformation other than that provided tot bythe manufacturer. ‘The readers expresth wamedt consider and adopt al safety precaution that might be indcated bythe actives described herein nd to avoid al potenti ara. By folowing the instructions contained herein the reader wlingy sume al risksin con nection wth soch nstuctons. The publisher makes no representations or warranties of any kind ncuding but ot ied to, the warrants of nes fr particular purpose or ‘merchantablty or vary such representations implied with respect tothe materi Set forth erin and the publisher takes no responsibilty with respect to such material. ‘The publisher shall not beable for any special, consequential or exemplary damages resulting, n whole rp, fom the readers use of, or rlnce upon, ths material, FOREWORD AWORD FROM THOMSON LEARNING rw AWORD FROM AUTODESK, INC. ‘The Intl dea “Topics For The Programmer Series ‘Who Reads Programming Books? ‘Thirst, Theme, and Variation AWORD FROM BILL KRAMER, PREFACE WHO SHOULD READ THIS BOOK? CONVENTIONS USED IN THE BOOK .. WHAT'S NOT COVERED AND WHY WHAT YOU NEED FOR THIS BOOK. xix WHAT'S ON THE CD ABOUT THE AUTHOR ACKNOWLEDGMENTS CHAPTER | GETTING STARTED INTRODUCTION .. PROGRAMMING AUT ‘OBJECTARX 2000, WHICH ONE? ‘AutoLiSP)Visual USF ‘Visual Basic for Applications (VBA) ADS . ‘Object ARX WHICH VERSION ; WHY SHOULD YOU LEARN MFC? DYNAMIC LINK LIBRARIES (DLL) ‘ARX ENTRY POINT—ACRXENTRYPOINT() CREATING YOUR FIRST OBJECTARX APPLICATION .. (Creating the DLL Inserting files into the Visual C++ project (Changing Project Settings RUNNING THE HELLO! ARX APPLICATION ELEMENTS OF THE ARX HELLO! APPLICATION CREATING AN ARX APPLICATION WITH A CUST - ‘Creating an application with a class derived from AcRxObject ... ned Running the Hello? ObjeccARX application... Elements of the ARX Hello2 application ‘OBJECTARX 2000 WIZARD ADDITIONAL RESOURCES .. CHAPTER 2 OBJECTARX ENVIRONMENT AND BOOK OVERVIEW ‘OBJECTARX LIBRARIES 37 ACRX LIBRARY 38 ‘ACED LIBRARY 38 ‘ACDB LIBRARY 139 ‘ACGI LIBRARY ACGE LIBRARY ADSRX LIBRARY (FORMERLY ADS) ‘OVERVIEW OF THE UPCOMING CHAPTERS .. ‘Chapter 3: Essential ADSRX (ADS) Chapter 4: Understanding AutoCAD's Database and Enty Structure = (Chapter $: ObjectARX's Geometry Classes . (Chapter 6: DCL (Dialog Control Language) Dialogs (Chapter 7: MFC Dialogs and ObjecARXS UI Excension ‘Chapter 8: Custom Clases, Entities, ane ObjectDBX (Chapter 9:Transuctions, Reactors, and Notifications =" READ THIS > CHAPTER 3 ESSENTIAL ADSRX (ADS) HISTORY OF ADS VARIABLES, TYPES, Real Numbers nn Pints Transformation Matrices Type Useful Values enn : Resuit Buffers and Type Codes ADSRX Function Return Types AUTOCAD COMMANDS IN ADSRX SENDING INFORMATION BACK TO SAMPLE APPLICATION CH3_I ELEMENTS OF SAMPLE APPLICATION CH3_1 Contents GETTING INFORMATION FROM THE USER ... SELECTING ENTITIES IN AUTOCAD ELEMENTS OF SAMPLE APPLICATION CH3_I REVISITED . SELECTION SETS Selection Set Filtering. Relational Selection Set Fitering ‘Conditional Selection Set Fitering.. ‘Extended Entiyy Data Selection Set Fikering ‘Transformation Matrices and Selection Sets Manipulating Selection Sets DATA TYPE CONVERSION FUNCTIONS . SAMPLE APPLICATION CH3_2 ELEMENTS OF SAMPLE APPLICATION CH3_2 ... SAMPLE APPLICATION CH3_3 ELEMENTS OF SAMPLE APPLICATION CH3_3 CHAPTER 4 UNDERSTANDING AUTOCAD’S. DATABASE AND ENTITY STRUCTURE SYMBOL TABLES NAMED OBJECT DICTIONARY INITIAL DATABASE OBJECTS wen ENTITY HANDLES, OBJECT IDS, AND MULTIPLE DATABASES .. CREATING OBJECTS ... CREATING OBJECTS WITH OBJECTARX OPENING AND CLOSING OBJECTS .. ADDING A GROUP TO A GROUP DICTIONARY AUTOCAD'S DATABASE STRUCTURE DATABASE RESIDENT OBJECTS neon COMMON RETURN CODES ‘SYMBOL TABLE FUNCTIONS Symbol able Query and Edit Functions . SAMPLE APPLICATION CH4_1 ELEMENTS OF SAMPLE APPLICATION CH4_1 AUTOCAD'S ENTITY CLASSES ‘AcDbéntity Query Functions . ‘AcDbEnigy Edt Functions .. ‘AcDbEntity Miscellaneous Functions AcDbCurve Classes AcDbLine AcDbCirele ‘SAMPLE APPLICATION CH4_2 ELEMENTS OF SAMPLE APPLICATION CH4_2 SAMPLE APPLICATION CH4_3 wi vit ELEMENTS OF SAMPLE APPLICATION CH4_3 OBJECTARX COLLECTION CLASSES .. SAMPLE APPLICATION CH4_4 AUTOCAD’S COMPLEX ENTITIES .. ‘SAMPLE APPLICATION CH4_5 (ACDBPOLYLINE) SAMPLE APPLICATION CH4_6 ACDBBLOCKREFEK ‘Sarmpe Application Ch_7 AcDbBlockRefrence, AcDbAibute) SAMPLE APPLICATION CH4_8 (ACDBBLOCKREFERENCE, ACDBATTRIBUTE) CHAPTER 5 OBJECTARX’S GEOMETRY CLASSES AcGePoint2 .. Matrix Operations .. ‘ACGE 2D ENTITY CLASSES ‘AcGeEntty2d AcGeCurvedd .. ‘AcGeLinearEnt2d ‘AcGeCircAre2d ‘AcGe 3D Entity Class Hierarchy . SAMPLE APPLICATION CHS_! ‘A FINAL WORD BEFORE WE MOVE ON... CHAPTER 6 DCL (DIALOG CONTROL LANGUAGE) DIALOGS. DIALOG BOX COMPONENTS Predefined Active Tes. ‘Acrributes of Predefined Tiles Layout and Stang Attributes .. Functional Ateributes ‘The ‘key’ Attribute DCL SYNTAX ‘SAMPLE APPLIC, x COMMONLY USED DIALOG FUNCTIONS Callback Functions SDI AND PORTING CONSIDERATIONS ... SAMPLE APPLICATION CH6_2 HIDING DIALOG BOXES . SAMPLE APPLICATION CH6_3 NESTING DIALOG BOXES SAMPLE APPLICATION CH6_4 CHAPTER 7 MFC DIALOGS AND OBJECTARX’S UI EXTENSIONS PROJECT SETTINGS RESOURCES ..... MODAL DIALOGS AND SAMPLE APPLICATION CH7_1 Contents MODAL DIALOGS AND SAMPLE APPLICATION CH7_2 MODELESS DIALOGS AND SAMPLE APPLICATION CH7_3 .. ‘TABBED-STYLE DIALOGS AND SAMPLE APPLICATION CH7_4 WIZARD-STYLE DIALOGS AND SAMPLE APPLICATION CH7_5 COMMON CONTROLS AND SAMPLE APPLICATION CH7_6 RESOURCE-ONLY DLLS AND SAMPLE APPLICATION CH7_7 ‘ACKNOWLEDGEMENT ADDITIONAL SAMPLE MATERIAL .. CHAPTER 8 CUSTOM CLASSES, ENTITIES, AND OBJECTDBX EXTENDED ENTITY DATA AND SAMPLE APPLICATION CH8_I .. EXTENSION DICTIONARY AND SAMPLE APPLICATION CH8_2 NAMED OBJECTS DICTIONARY AND SAMPLE APPLICATION CHE_3 . CUSTOM CLASS DERIVED FROM ACDBOBJECT AND SAMPLE APPLICATION CH8_4 OBJECTDBX CUSTOM ENTITIES DERIVED FROM ACDBENTITY AND SAMPLE APPLICATION CHE. .. ADDITIONAL SAMPLE MATERIAL CHAPTER 9 TRANSACTIONS, REACTORS, AND NOTIFICATIONS TRANSACTIONS .. TRANSACTIONS AND SAMPLE APPLICATION CH9_IUI .. REACTORS AND NOTIFICATIONS. TRANSIENT REACTORS/NOTIFICATIONS AND. SAMPLE APPLICATION CH9_2UI ... PERSISTENT REACTORS/NOTIFICATIONS AND. SAMPLE APPLICATION CH9_3UI ADDITIONAL SAMPLE MATERIAL .. A FINAL FAREWELL... INDEX ‘A WORD FROM THOMSON LEARNING Autodesk Press was formed in 1995 as a global strategic alliance between Thomson. Learning and Autodesk, Inc. We are pleased to bring you the premier publishing list of Autodesk student software and learning and training materials to support the Autodesk family of products. AutoCAD® is such a powerful product that everyone who uses it would benefit from a mentor to help them unlock its fall potential. This is the premise upon which the Programmer Series was conceived. The titles in this series cover the most advanced topics that will help you maximize AutoCAD. Our Programmer Series titles also bring you the best and the brightest authors in the AutoCAD community. Maybe you've read their columns in a CAD journal, maybe you've heard them speak at an Autodesk event, or maybe you're new to these authors—whatever the case may be, we know you'll enjoy and apply what you'll lean. from them. We thank you for selecting this title and wish you well on your pro- gramming journey. Sandy Clark Executive Editor Autodesk Press A WORD FROM AUTODESK, INC. From the birth of AutoCAD onward, there has been a large library of source mate- tial on how to use the software; however, relatively little material on customization of AutoCAD has been made available. There have been a few texts written about AutoLISP®, and many general AutoCAD books include a chapter or two on cus- tomization. On the whole, however, the number of texts on programming AutoCAD haas been inadequate given the amount of open technology, the number of application programming interfaces (APIs), and the sheer volume of opportunity to program the ‘AutoCAD design system. Four years ago, when I started working with developers as the product manager for AutoCAD APIs, immediately found an enormous demand for supporting texts about ObjectARX™, AutoLISP, Visual Basic for Applications®, and AutoCAD® OEM. ‘The demand for a “programming series” of books stems from AutoCAD's history of being the most programmable, customizable, and extensible design system on the mar- ket. By one measure, over 70 percent of the AutoCAD customer base “programs” AutoCAD using Visual LISP, VBA, or menu customization, all in an effort to increase productivity. The demand for increasing the designer's productivity extends deeply, creating a demand for new source texts to increase productivity of the pro- gramming and customization process itself. ‘The standard Autodesk documentation for AutoCAD provides the original source of technical material on the AutoCAD API technology. However, it tends toward the clin- ical, which is a natural result of describing the software while itis being created in the software development lab. Documentatior in fully applied depth and breadth is only completed through the collective experiences of hundreds of thousands of developers, customers, and users as they interpret and apply the sytem in ways specific to their needs. ‘Asa result, demand is high for other interpretations of how to use AutoCAD API. This kind of instruction develops the techaique required to innovate. It develops pro- ‘grammer instinct by instructing when to vse one interface over another and provides direction for interpretive nuances that can only be developed through experience. ‘AutoCAD customers and developers look for shortcuts to learning and for alterna- tive reference material. Customers and developers alike want to accelerate their pro- ‘gramming learning experience, thereby shortening the time needed to become expert and enabling them to focus sooner and better on their own specific customization or development projects. Completing a customization or development project sooner, faster, and better means greater productivity during the development project and more rapid deployment of the result. For the CAD manager, increasing productivity through accelerated learning means increasing his or her CAD department's productivity. For the professional develop- er, this means bringing applications to market faster and remaining competitive. THE INITIAL IDEA ‘An incident that occurred at Autodesk University in Los Angeles in the fall of 1997 illustrated dramatically a dynamic demand for AutoCAD API technology informa- tion. Bill Kramer was presenting an overview session on AutoCAD's ObjectARX. The Autodesk University officials had planned on having 20 to 30 registrants for this ses- sion and had assigned an appropriately sized room to the session. About three weeks before it took place, I received a telephone call indicating that the registrations for the session had reached the capacity of the room, and we would be moving the session. ‘We moved the session two more times due to increasing registration from cus~ tomers, CAD Managers, designers, corporate design managers, and even developers attending the Autodesk University customer event. What they ll had in common was an interest in seeing how ObjectARX, an AutoCAD API, was going to increase their ‘own or their department's design productivity. When the presentation started, I walked into the back of the room to see what appeared to me to be over 250 people in a room that was now standing room only. That may have been the moment when T decided to act, or it may have been just when the actual intensity of this demand became apparent to me; I'm not sure which. ‘The audience was eager to hear what Bill Kramer was going to say about the power of Object ARK. Nearly an hour of unexpected follow-up questions and answers fol- owed. Bill had successfully evangelized a technical subject to an audience spanning non-technical to technical individuals. This was a revelation for me, and the begin- ning of my interest in developing new ways to communicate to more people the tech nical aspects, power, and benefits of AutoCAD technology and APIs. As a result, I asked Bill if he would write abook on the very topic he just presented. T'm happy to say that Bill's book has become one of the first books written in the new ‘AutoCAD Programmer Series with Autodesk Press. TOPICS FOR THE PROGRAMMER SERIES The extent of AutoCAD's open programmable design system made the need for a series of books apparent early on. My team, under the leadership of Cynde Hargrave, Senior AutoCAD Marketing Manager, began working with Autodesk Press to devel- op this series. Tewas an exciting project, with no shortage of interested authors covering a range of topics from AutoCAD’s open kernel in ObjectARX to the Windows standard for application programming in VBA. The result is a complete library of references in Autodesk's Programmer Series covering ObjectARX, Visual LISP, AutoLISP, cus- tomizing AutoCAD through ActiveX Automation® and Microsoft Visual Basic for Applications, AutoCAD database connectivity, and general customization of ‘AutoCAD. WHO READS PROGRAMMING BOOKS? Every AutoCAD user will find books ir this series to fit their AutoCAD cus- tomization or development interests. The collective goal we had with our team and Autodesk Press for developing this series, identifying titles, and matching them ‘with authors was to provide a broad spectrum of coverage across a wide variety of cus- tomization content and a wide range of reader interest and experience. Collaborating as a team, Autodesk Press and Autodesk developed a programmer series covering all the important APIs and customization topics. In addition, the series pro- vides information that spans use and experience levels from the novice just starting to customize AutoCAD to the professional programmer or developer looking for anoth- er interpretive reference to increase his or her experience in developing powerful appli- cations for AutoCAD. oy ‘THIRST, THEME, AND VARIATION I compare this thirst for knowledge with the interest musicians have in listening to music performed in different ways. For me, it is to hear Vivaldi’s The Four Seasons time and time again. Musicians play from the samme notes written on the page, with the iden- tical crescendos and decrescendos and other instructions describing the “technical” aspects of the music. All ofthe information to play the piece is there. However, the true creative design and beauty only manifests through the collection of individual musicians, each applying a unique experience and interpretation based on all that he or she has learned before from other mentors in addition to his or her own practice in playing the written notes. By learning from other interpretations of technically identical music, musicians ben efit the most from a new, and unique, interpretation and individual perception. This ‘makes it possible for musicians to amplify their own experience with the technical con- tent in the music. The result is another unique understanding and personalized interpretation of the music. Similar to musical interpretation is the learning, mentoring, creative processes, and resources required in developing great software, programming AutoCAD applications, and customizing the AutoCAD design system. This process results in books such as Autodesk's Programmer Series, written by industry and AutoCAD experts who ‘truly love working with AutoCAD and personalizing their work through development and customization experience. These authors, through this programmer series, evan- gelize others, enabling them to gain from their own experiences. For us, the readers, wwe gain the benefit from their interpretation, and obtain the value through different presentation of the technical information, by this wide spectrum of authors. ‘Andrew Stein Senior Manager Autodesk Business Research, Analysis and Planning A WORD FROM BILL KRAMER It takes time for new technology to evolve. And it takes very special people to make it really useful to others. Charles is one of those gifted individuals who has both the technical and the communication skills nzeded to bring advanced technology down toa readable level. Thad the unique opportunity to review this work before it was completed, My role was to suggest additional items for inclusion as well as help in spotting differences between the AutoCAD Release 14 and AutoCAD 2000 versions in the text and exam- ples. As I read the preliminary work, I keot thinking to myself how useful this book ‘would have been to me when I was first hacking away at ObjectARK. ‘That was the first sign that this was a good book for any serious programmer's library, including my own. The examples and text reflected many things from the real ‘world of applications development and not from the theoretical side that most com- puter scientists tend to adopt when not sure of the audience's interests. Object ARX is a complex subject and requires a solid background in programming and computer-aided design to fully implement. More specifically, the programmer must know a lot about AutoCAD and how operators use that product, ObjectARX gives the programmer control at a deep level inside AutoCAD and as a result, it appears difficult to work with at a first glance. But that is not the case and Charles has done an excellent job in showing just how one goes about using ObjectARX to solve real application problems in AutoCAD. ‘The examples in this book are great. For me, that is an important part of any advanced treatise on a programming language or environment—Iots of good exam- ples. This book has many good examples and a couple of really great examples that will aid the beginning ObjectARX programmer in digging even deeper into the sys- tem. ‘What makes this book special is that Charles has written it with his own experiences as a teacher. And he listened to that teacher and remembered the lessons well. His explanations of complex concepts show this as they are brought down to an under- standable level. Topics such as using MFC, the debugger, AutoCAD entity objects, and reactors are explained and demonstrated in a manner clearly understood by C++ programmers. ‘Do note that you should already be comfortable with programming, specifically in the Cor C++ language. Although a solid background in AutoLISP or Visual BASIC is a good start, the concepts underlying C/C-+ should be understood before you read this book. You do not have to be a master at C/C++ and the examples will be useful to all levels of programmers interested enough to study them. To get the most from this book, what you need is a background in developing applications as well as knowing how to use AutoCAD. When reading this book, take some time to understand the examples. They are great teachers of how to write a working program. But don't just skip over the prose and scan the examples! There are alot of very useful insights contained in these pages. And as always, keep on programmin’ Bill Kramer President ‘Auto-Code Mechanical ‘Welcome to ObjectARX for AutoCAD 2000. You are about to embark on a journey through the most powerful application development environment available for AutoCAD 2000. The intent of this book is to give you, the reader, a thorough understanding of the fundamentals of ObjeeARX. We will not cover the entire con- tents of ObjectARX—I would not have the time or space for such an endeavor. What want to convey is how ObjectARX works. That way, by the end of the book you can. figure out the more esoteric aspects of ObjectARK. This book is not intended to be a replacement for the AutoCAD 2000 ObjectARX Developer's Guide or the ‘AutoCAD 2000 Migration Guide for Applications that comes with ObjectARK 2000. ‘The books intended to be a supplement. We hope to get you over the “hump” of the learning curve and on your way to developing applications for AutoCAD 2000. WHO SHOULD READ THIS BOOK? From the outset, I want to let you know that this book is not a book for beginners. expect you to have development experience and have achieved a certain skill level. First, you need to understand C++: you need to know what classes are, what virtual and overloaded functions are, and how to derive a class from a base class. This book is not a tutorial on C++ if those concepts are foreign to you, you may need to take a refresher course. You need to be familiar with Visual C++ 6.0 and understand how to use AppWizard/ClassWizard. You will ned to know how use a debugger. You donit have to know Microsoft Foundation Classes (MFC), but if you want to take advan- tage of MFC dialogs and AutoCAD/ObjectARX 2000 UI Extensions, you will need to understand MFC and message mapping. You don't have to know everything. about MFC—if you understand dialogs, controls, and message mapping, you are well (on your way. On the subject of AutoCAD, you obviously need to have an understanding of how to use AutoCAD. You don't have to be an ‘expert, but as a bare minimum, you need to know how to create AutoCAD entities end know about layers, linetypes, block, dimensions, text, text styles etc. The more you know about AutoCAD, the better off you are. If you have previous programming experience with AutoCAD, that is help- fal also, but not necessary. oe (ObjectARX is a comprehensive and extensive API. Examine your requirements and needs with respect to application development for AutoCAD 2000; if you never intend to create custom objects or entities, perhaps Visual LISP or VBA may suit your requirements. The learning curve for thos: environments is less steep but they are also not as powerful as ObjectARX. Evaluate your needs first and choose when and ‘what APT is appropriate for your needs. CONVENTIONS USED IN THE BOOK Instructions: ‘When the user is asked to select an item from a menu or a dialog box, the option will be shown as follows: From the File menu, select the Save option. ‘On the Draw Part dialog box, select the Part Name edit box and enter the text “Part #1”. Sclect the Metal Gauge drop-down list box; select the 304 SS option, and then select the OK button to continue. AutoCAD, C/C++/ObjectARX keywords, code and user-added code: All AutoCAD commands and system variables will be shown as in the following example: Make sure you turn off the system varisble FILEDIA. This is what appears at the AutoCAD command prompt as a result of the aeutPrintfQ) function. Command: This is a test of the ObjectaRX code. All code, C/C++/ObjectARX, will also eppear as the following example shows: acedCommand(RTSTR, “circle”, // AutoCAD command RTSTR, “5,5,0", // certer point RTSTR, "2.24086", //. racius RTNONE // end of list 0B Referenced files and path names: Atany time the user is referred toa fle or path name, the file or path name will appear as the following example shows: Foreword For a listing of the possible return values please refer to adscodes.b file in the \ObjeceARX 2000\Unc folder. WHAT’S NOT COVERED AND WHY ‘As mentioned earlier, it would not be possible to cover every aspect of ObjectARX 2000, so there are certain aspects that are not covered: Because this book is about C++ and ObjectARX, I do not cover COM or ATL. Therefore, the Object Properties Manager is not covered, There is a document on the ADN (Autodesk Developer Network) Web site that covers this subject in detail. + The Design Center isnot covered inthis book; there are examples ofthe Design Center inthe following folders: <\ObjectARX 2000\docsamps \ObjectARX 2000\somples + There are some aspects of the ObjectARX 2000 UI Extension that are not covered by the book. Examples can be found at the locations listed previously and as follows: \ObjectARX 2000\samples\nfesamps + Ownership issues and “Deep Cloning” are covered by the AutoCAD 2000 ObjectARX Developer's Guide, and documents such as “Cloning in AutoCAD 2000" can be found on the ADN Web site: hetpulwwwautodesk.com/adn * You will not see a chapter devoted to the Multiple Document Environment (MDE). Refer to Chapter 16,"Multple Document Interface” in the AutoCAD 2000 ObjectARX Developers Guide. | do cover the practical aspects of MDEMDI throughout Chapters 6,7,8,and 9. See “***Read This™” in (Chapter 2 for more information. WHAT YOU NEED FOR THIS BOOK First, you need Microsoft's Visual C++ 6.0, SP2 edition. You need to have Object ARK 2000 installed on your computer. You will also need to have Windows NT 4.0 installed on your computer in order to debug your applications. Your ObjectARX appli- cations will work on Windows 95 and Windows 98 but you cannot debug those appli- cations on those environments. At the time of writing of this book we are waiting for the pending release of Windows 2000. Currently Autodesk is testing ObjectARX for that environment, WHAT’S ON THE CD On the CD you will find the code for AutoCAD/ObjectARX 2000 in a folder called Code. The following is the layout for the code that makes up the chapters for this book. See “Overview of the Upcoming Chapters” in Chapter 2 for an overview of the sample applications that make up the book. Chapter 1 Hellot acrxEntryPoint() Hello2 derive a class from AcRxObject Chapter 3 Ch3_1 data types, user input, commands Ch3_2 selection sets Ch3_3 selection sets Chapter 4 Chat layer table records and iterator Ch42 create entities Chas intersection points and offsets Ch44 ‘change layer for selection set Chas create LWPOLYLINE Ch4_6 insert a block Ch4_7 insert block with attributes Ch4.8 create a block with attributes Chapter 5 Cho geometry classes ChS_ta geometry classes Chapter 6 Ch6_1 load DCL dialog Ch6_2 DCL dialog Ch6_3 hiding DCL dialog Cho_4 nested DCL dialog Chapter 7 chr modal dialog ch72 hiding modal dialog ch7_3 modeless dialog ch7_4 (Ch7_4MFC ch7_5 ch7_6 ch7_7 Ch7_7Res Chapter 8 Ch8_t ch8_2 ch8_3 ch8_4 chs_s (Ch8_SUI Chapter 9 Cot Ch9_1UI DebugDBX ReleaseDBX ch9_2 (Ch9_2U1 DebugDBX ReleaseDBX Ch9_3. Ch9_3UI tabbed style dialog ‘MFC tabbed style dialog wizard style dialog ‘MFC toolbars ‘modal dialog resource only DLL resource only DLL extended entity data extension dictionary named objects dictionary custom object custom entity - ObjectDBX application custom entity User Interface for Ch8_S transactions ObjectDBX file for Debug Ch9_1UI ObjectDBX file for Release Ch9_1UI transient reactors ObjectDBX file for Debug Ch9_2UI ObjectDBX file for Release Ch9_2UI persistent reactor ObjectDBX application persistent reactor User Interface for Ch9_3 For those of you who are using AutoCAD R14 and ObjectARX 2.02, there is an equivalent (where possible) set of applications for AutoCAD R14 and ObjectARX 2.02 in a folder called R14Code. The format and structure are similar to that shown for AutoCAD/ObjectARX 2000. The AutcCAD R14 code was created with Visual Cr 5.0, ABOUT THE AUTHOR Charles McAuley works for Autodesk as « member of the Developer Consulting Group (DCG) and is based in the Dublin, Ireland, Autodesk office, Charles grad- uated from the Dublin Institute of Technology, Bolton Street, Dublin, Ireland as a ‘Mechanical Engineer and worked as a Mechanical Engineer for a number of years. His introduction to Autodesk products came in 1987 with the release of AutoCAD R9. That marked the beginning of the end of his Mechanical Engineering career. Charles became the CAD Manager/Developer for an engineering company in Phoenix, Arizona, and wrote applications in AutoLISP, C, and ADS. He then learned C++ and MFC and continued to develop applications based on AutoCAD. In addition to developing applications, Charles taught AutoCAD, Mechanical Desktop, AutoLISP, C++, and MFC as < part-time instructor at a university in the Phoenix area for a number of years. Charles joined the Developer Consulting Group at Autodesk in July 1998 and specializes in AutoCAD APIs. He and other members of DCG assist third-party developers with their AutoCAD-based applications. In addi- tion, Charles gives ObjectARK training classes at various Autodesk offices attended by the third-party AutoCAD developers and onsite ObjectARK training at various company locations. He also participates in the annual Autodesk Developer Network (ADN) Conference as well as various Autodesk University events. Charles wrote this book with the intent of helping the beginning ObjectARX developer manage the steep learning curve. For more information on the Autodesk Developer Network (ADN) and the Developer Consulting Group (DCG) refer to the following Web site: httpLAwanvautodeskcom/adn Foreword il ACKNOWLEDGMENTS ‘Writing a book is time consuming and a lot of hard work. There are a number of peo- ple I would like to thank for helping with this endeavor. My thanks go out to those directly involved with the book and also to those people who have helped me carve out a career for myself with AutoCAD. Family members: I may as well start out with my family, who provided the backup support for this book. I would like to thank my wife Helen, who has become a computer widow over the last year. As I mentioned earlier, it takes alot of time and work to write a book. During this process my wife provided constant support, backup and encouragement when needed. It pays to be understanding when someone is writing a book and my wife has an abundance of understanding. I would also like to thank her for the years of sup~ port in my journey through AutoCAD and programming. I am a very fortunate per- son indeed. ‘To my parents, Joe and May, thank you for placing a special emphasis on the value of a good education. “The pursuit of education is its own reward.” Also for the fun my Dad and I have playing with computers. My Dad—the senior citizen who embraces technology (they are toys, and he loves toys), Autodesk people: (Developer Consulting Group) ‘There are many Autodesk people all around the world I would like to thank. As you know, I work for the Developer Consulting Group and this group is spread through- out the world with numerous sites in the United States, Europe, India, China, and Japan. The Developer Consulting Group assists third-party developers who write appli- cations that work on top of Autodesk products. So let’s start out in Guildford, England, with special thanks to Phil Holcombe. ‘Thanks for hiring me, Phil (on such short notice, I might add)—this is by far the most fan job I ever had. While we are in Guildford, thanks to the rest of the crew, Harry Denholm and Gabriel Ajyai. ‘Over in Heusenstamm, Germany (near Frankfurt), special thanks to Christian Ney, who wrote some custom entity applications that I stole code from and used in Chapter 8, Thanks to the rest of the crew in Heusenstamm, Stefan Kraus, and Peter Sylvester. Special thanks to Stefan Kraus fer having the insight to be the originator of the ObjectARX Wizard. On to Neuchatel, Switzerland: thanks to Cyrille Fauvel and Kean Walmsley. Thanks, Cyrille, for all your help with the MFC bits of ObjectARX (the subject of Chapter 7) and Kean for answering all my questions over the telephone (I knew you were busy). ‘While still in Switzerland, thanks to Nathalie Odiet for keeping the European crew ‘Across the pond to the United States and spread out across numerous sites: special thanks go out to Ken Mathis and Richard Whitley. Ken and Richard get to work early and because of the time difference between Europe and the United States, I pestered them with questions before they got a chance to get coffee. Thanks to Greg Wenneson for keeping the American crew sane. Thanks to Uvi Narayana for keeping the APAC (Asia/Pacific) crew sane. Thanks to the rest of the American crew, Henry Lee, Shawn Bolan, Albert Szilvasy, Jacques Sedille, Peter Funk, Peter Heald, Brian Ekins, Richard Henley, and Wayne Brill. You are right, Jacques; croissants taste better in Europe. Thanks to Guido Haarmans for keeping the worldwide crew sane. In India, thanks to Thilaknath Rao, Ishwar Nagwani and Chandar Oddiraju. In China, thanks to Bill Zhang. Finally, in Japan, thanks to Toshiaki Isezaki. Also I would like to extend a thanks to the folks who take care of our ADN Web site; thanks to Claudio Ombrella and Pascal Tomasi. If there is anybody I left out in DCG, you must have joined DCG after the writing of this book or we do not work on the same APIs. Other Autodesk people: ‘At the San Rafael office home of Autodesk, Inc. headquarters, I would like to thank Chet Fryjoff of the MCAD group for letting me work on earlier versions of the Mechanical Desktop. It was a lot of fun. Also, in the GIS group, a very special thanks to Ed Connor. Ed is truly a code wizard and it was like having my own per- sonal consultant any time I had an ObjectARX question. Also thanks for telling Phil Holcombe that I was a good candidate for the DCG (the check isin the mail). [would like to thank Bill Jordan, also with the GIS group. If you ever want to know how a user interface and an application should look and work, then Bill is the man you need to talk to, Bill has a truly wonderful sense of design. Other people who work with and use Autedesk products: ‘Wall first and foremost I would like to thank Steve and Peter Kinkel of United Metal Products in Tempe, Arizona. Thank you very much for letting me write all kinds of custom applications for AutoCAD over the years and thanks to Dennis Gaither (CAD Manager), also of United Metal Products, for coming up with the various kinds of applications to write. Okay Dennis, I want you to draw a pface’ entity (private joke). “Thanks also to'Tom Nelson of United Metal Products (CAD Applications Developer). ‘Okay Tom, want to learn ObjectARX yet? I can recommend a good book. To Brad Beckwith of Intel in Chandler, Arizona, it’s been great working with you doing AutoCAD over the years. I can't telieve its almost ten years now. ‘Thanks to Gerry O’Brien of DIT (Dublin Institute of Technology) in Dublin, Ireland. It’s been great working with you on AutoCAD over the years; it’s changed quite a bit hasn't it? Foreword ‘Thanks to Peter Cunningham of Archimedia Studios in Dublin, Ireland. It’s been great working with you too, over the years. Apart from being really good with AutoCAD, Peter is also a wizard with 3D Studio MAX. ‘Thanks to Ray Buff of Precision Surveys in Dublin, Ireland. Ray should work for Autodesk's QA department. Ifthe application works after Ray gets his hands on it, then it’s good. Great working with you, Ray. Autodesk folks in Dublin, Ireland: ‘While not directly involved with this book, the folks in Dublin are responsible for over seeing that shipping packages are done for Europe, the Middle East, and Africa for all the Autodesk products, the manuals, and CDs. So with that said, thanks to Rory ‘Mechan, Brian Stanley, Dean Brandon, Helen Buxton, Barbara Corbett, Martin Gurren, Karl Hutchinson, Geraldine O'Connos, Geraldine Mountaine, Barry Moore, Patrick O'Riordan, Rhona O'Sullivan, Ronan Phipps, Joe Ryan, Peter Seacy, Sharon ‘Towson, and Leslie Wilson. Special thanks t» Linda Barry and Lena Sherlock for tak- ing care of the Autodesk Developer Network (ADN) CDs and related material. ‘Thanks to the Dublin Autodesk folks for leing me have a desk in your office to work from and also for the great coffee machine. ‘The folks at Autodesk Press, Thomson Learning ‘A very special thanks to Sandy Clark, Execative Editor, whom I've known and con versed with for a very long time. Sandy was the first person I spoke to at Autodesk Press regarding the kind of book I could de for them related to Autodesk products. We looked at and explored quite a few options before deciding on a book for ObjectARX. We have been discussing all things AutoCAD since R13 (believe it or not, Sandy!) when I was invited to write some guest chapters for another Autodesk Press publication that dealt with AutoCAD R13 and Windows. I can't believe it has been that long and it seems like only yesterday since we decided on an ObjectARX book, and here we are with the finished product. Well, Sandy, I can't thank you ‘enough—it’s been wonderful working with you all this time. To John Fisher, Developmental Editor, CADD & Drafting, who has been with me every step of the way in the development of this book, a very special thanks. John set up the technical reviews, making sure that the content was correct and the coverage vwas both broad and of quality. John provided lots of feedback from other ‘experts’ in the field and also helped greatly in the transition from AutoCAD R14 to AutoCAD 2000. Throughout this period I couldnit help but get a sense of his love for AutoCAD and the excitement and enthusiasm he had for the product, both in terms of an end user and of AutoCAD's programming capabilities. It was a pleasure to work with John during the production of this book. In addition to having numerous conversations regarding ObjectARX, we had lots of discussions about non~AutoCAD-related items as well. Many thanks, John, for all tae time, advice, and friendship that you have poured into this book. And thanks to Gail Taylor, copy editor, who crossed my tees and dotted my eyes. Gail ‘went through this book one last time to make sure everything was trim, proper, and shipshape. She asked more questions thar I care to remember and came up with loads of great suggestions. Gail was an absolute delight to work with. Thanks Gail, I hope you get to go over my next book. Finally, special thanks are due to the following instructors and professionals who reviewed the proposal and chapters in detail: Bill Kramer, Auto-Code Mechanical, Dublin, Ohio; Brian Matthews, Wake Technical Community College, Raleigh, ‘North Carolina, and Andrew Stein, Autodesk Inc., San Rafael, California. Getting Started INTRODUCTION In this chapter we will take a broad look at all of the technologies available to the developer in designing and implementing AutoCAD 2000-based applications. In terms of computing, Autodesk (the developers of AutoCAD) is an old company and 1 great deal has happened since the introduction of AutoCAD Release 1.0 in the early 1980s. AutoCAD 2000 is clearly the best reease to date, in terms of usability and API functionality. Gone (forever) are the days of DOS, and AutoCAD is now only available on one platform—Win32® (Windows 95/98 and Windows NT). AutoCAD is now an object-oriented CAD application with an incredible amount of power and application development choices available o the user and developer. We will look at the various ways of customizing and developing applications for AutoCAD: we will Jook at Visual C++, Microsoft Foundation Classes (MFC), Dynamic Link Libraries (DLLs), and AutoCAD's version of the DLL known as ARX. We will create a few applications and discuss their elements. Finally, we will show you how to use a debugger for AutoCAD applications. Enough said about what we are going to do— let’s dive right in. PROGRAMMING AUTOCAD USING VISUAL LISP, VBA OR OBJECTARX 2000, WHICH ONE? Since its earliest days, AutoCAD has been an open architecture application. AutoCAD is a general-purpose CAD application; users/developers have been able, by various ‘means, to customize AutoCAD to meet their specific requirements. Changing how AutoCAD works falls into two broad categories, namely customization and pro- ‘gramming. A number of AutoCAD users customize AutoCAD's menus by creating new macros, ceating new toolbars, and perhaps using DIESEL (Direct Interpretively Evaluated String Expression Language) to write to the status bar and create intelli- ‘gent menus and menu groups. They may also have created various “prototype draw- ings” that have preset layers, text styles, dimstyles, linetypes etc. to streamline their process. All of the preceding are examples of customization. A number of you may ‘move beyond customization and learn how to program AutoCAD (that’s what hap- pened to me). So why program AutoCAD? Perhaps the main reasons to program AutoCAD are flexibility and power. Programming AutoCAD provides flexibility ‘way above and beyond that of customization. One of the reasons I eared how to pro- gram was that I wanted AutoCAD to do my drawings for me (I know I'm lazy!). You can tell AutoCAD what to do and how you want it done, and it will faithfully reproduce your wishes time and time again (once you've fixed all the bugs—there had to be a catch somewhere). So how do you program AutoCAD? Here you have a num- ber of choices. AUTOLISPIVISUAL LISP Ifyou are like me and most people, you started out with AutoLISP®, introduced in the early versions of AutoCAD, When I stated AutoCAD Release 9, it was there and thriving. What is AutoLISP? It is a subset of the LISP programming language. LISP stands for List Instruction Set Processing and one of the things you quickly find out is that because its very important that for every bracket like ‘’ you need a matching opposite one of these 9, it became affectionately known as “Lost In Stupid Parentheses.” AutoLISP is a procedural programming language with many of the ele~ ‘ments of its bigger cousins C and C++ but without the headaches. It’s an interpret- ced programming language, so you dontt have to compile it. All the routines are written with a text editor and all the files have the extension LSP. (Anybody remem- ber EDLIN? Ugh!) Because it is an interpreted programming language, itis slower than C++ (ObjectARX) but, as I have told students on many occasions, it can draw 1 whole lot faster than you can manually. Orie of the advantages of AutoLISP is that because itis an interpreted programming language, which does not have to be com- piled, it will rn on any platform. However, now that AutoCAD is written for one plat- form only, that is no longer the considerable advantage it used to be. One of the further advantages of AutoLISP is that it has been around for so long and so many rottines have been written in AutoLISP that there is a good chance that, whatever it is ‘youre looking for, somebody has already written it. Well, how do you find it? You can jump on the Web to Autodesk's Web site, wwwautodesk.com, The magazines Cadalyst and Cadence also have a collection of LISP routines at their Web sites. If you have never programmed before, AutoLISP is a kind and gentle environment to work in and familiarize yourself with programming constructs. VISUAL BASIC FOR APPLICATIONS (VBA) AutoCAD 2000 includes Visual Basic® in the form of VBA (Visual Basic for Applications). I have to admit that I don't know much about Visual Basic but, from what I have seen implemented in AutoCAD 2000, if you already know Visual Basic or VBA, this approach is worth pursuing, especially if you do not know C++. About Getting Started the only limitation that I can think of with respect to VBA is that you cannot create custom AutoCAD objects. ADs AutoCAD Development System® (ADS) was introduced in AutoCAD R10 for 08/2. It consisted of programming in C and provided C libraries for use by the devel- opers creating their applications. ADS was introduced to a larger audience with the release of AutoCAD R11 for DOS. ADS has now gone the way of the dinosaur and hhas been discontinued. That's not entirely true—it has been integrated into ARK in the form of ADSRX. If you are going to write applications for AutoCAD, I recom- mend that you use ObjectARX. However, there are elements of ADS still in ObjectARX. In our journey through ARX, dorit be shocked if you see some ads_XXX names; they are now fully integrated into ObjectARK. On my journey to ObjectARX, C++ and MFC, I did learn C and ADS. I can tell you from experience that C++ is a much better C, ObjectARX is a much better ADS, and MFC/ObjectARX’s UI Extensions are the icing on the cake with a cherry on top. OBJECTARX ‘The ARX part of ObjectARX stands for AutoCAD Runtime eXtension and was called ARX in AutoCAD R13. When AutoCAD R14 was introduced, Autodesk released ObjectARX 2.0, which is much improved and enhanced for AutoCAD R14. ObjectARX 2000 works only for AutoCAD 2000, ObjectARX 2000 is a comprehensive API (application programming interface) that contains some 220 Classes and over 3000 unique member functions. That’ a lot of stuff, but remember, you don't have to know it all to create quality applications. ‘Many of the Classes and functions are grouped by category and there isa certain sub- set that you will use over and over again, so let's move on and talk about Visual C++, ‘MFC, and AutoCAD. WHICH VER: IN OF VISUAL C++? For AutoCAD 2000 / ObjectARX 2000, you need Visual C++ 6.0 with SP2 (Software Patch 2). You will also need to have Windows NT 4.0 installed. Please note that all the applications you create will work on all versions of Windows; however, to debug your applications you need Windows NT 40 installed. Ifyou already have Windows 95/98 installed, you can always install Windows NT 4.0 on the same machine as a dual boot machine. At the time of writing this book I have Windows NT 4.0 SPS installed. We are eagerly (then again maybe rot) awaiting the launch of Windows 2000. WHY SHOULD YOU LEARN MFC? Why should you learn MFC? (Perhaps this is a religious question!) What is MFC? MEC stands for Microsoft Foundation Clases and is a wrapper for the Windows SDK and API. In the bad old days of Windows programming, applications were developed with a combination of C and the Windows SDK. In 1992, Microsoft released Microsoft C/C++ version 7.0, which included version 1.0 of MFC. After the release of Microsoft C/C++ version 7.0, Microsoft released Visual C++ 1.0 series, which has now grown to the Visual C++ 6.0 we know and love today, and MFC has grown along right with it. Developing Windows applications using C and the SDK was torture at best. When I embarked upon my C++ career I started out with Visual C+ 1.0, so I did lean MFC almost from the beginning. I take the “If you don't have to invent the wheel, why should you?" approach. MFC provides Classes, functions (methods), and data members that are wrappers for the Windows API. When you use MFC, it calls down into the APT to do all the wonderful things that Windows does. However, it does not completely encapsulate the API. I have a minimal understanding of the API and I sometimes use it to do perhaps alittle special trick o two that MFC does not provide but, believe me, that is rare. So why, as an AutoCAD developer, should you use MFC? For me, perhaps the best reason to use MFC is that it contains a Class, CDialog, used to create dialog boxes. ‘Without question, the dialog boxes that Windows provides are vastly (in great orders ‘of magnitude) superior to that of DCL (Dialog Control Language). DCL allows you to create a dialog that will work on any platform but, now that we only have one plat- form to deal with (Win32), I believe that DCL will be eventually dropped. We will talk about DCL in more detail later. The CDialog class not only provides a variety of dialog types but also allows you to use any type of Windows control on your dialog, including ActiveX controls. MFC also provides DDX (Dialog Data eXchange) and DDV (Dialog Data Validation) to control the various elements of your dialog. DDX transfers data cither to of from your dialog to your member variables and DDV checks for valid data, including valid data ranges. DCL doesn't even come close. Ifyou understand MFC, you can also take advantage of ObjectARX 2000’ UI Extension classes. There are many other compelling MFC attributes as well, which we will look at in this book. Thave talked to many ObjectARK programmers and it surprises me’how many of them don't know MFC. They all intend to lear it some day but we all know, some day never comes. Apart from writing AutoCAD ObjectARX applications, I also develop ‘Windows applications, so perhaps that is the best reason to learn MFC. DYNAMIC LINK LIBRARIES (DLL) If you use Explorer and navigate through the various Windows directories, you will find many files that have the extension .DLL; these are dynamic link library files. ‘Commonly used functions and indeed dialog boxes (especially Windows Common Controls) can be stored in a type of library called a DLL. DLLs are loaded at run time when your application calls them and they are independent of the code of the calling Getting Started application. So if you add functionality andor change the code in your DLL, you can recompile it without having to recompile the code in the calling application. Replace the existing DLL with the new one, and your application can call it as before. So in a sense, DLLs are global repositories of commonly used functions and/or dialog boxes that can be called by multiple applications. For example, if ewo Windows applications call the file save dialog and the dialog looks identical in both applications, chances are they are both calling the same dialog from a common DLL. As far as the application is concerned, when you call a function from a DLL, it appears to the application to bbe part of the application. However, when you create or alter the DLL, this would not require alteration of the application's source code in order for it to use the DLL. The DLL is independent of the calling application. This is the approach AutoCAD takes with respect to applications that you create in ObjectARX: you don't change AutoCAD’ internal code to use a DLL. AutoCAD does not use the .DLL extension; it uses the ARK extension instead, but they are DLLs none the less and, as far as, AutoCAD is concerned, they behave as if they are part and parcel of AutoCAD itself. ‘There are three kinds of DLLs used in Visual C++: Regular DLLs, Regular DLLs dynamically linked to MFC, and Extension DLLs. For most of the applications in this book, we use Extension DLLs, courtesy of the Object ARX 2000 Wizard (more ‘on the wizard later). Extension DLLs are used by MFC when you create MFC appli- cations. Note that you do not have to use Extension DLLs, but why do they exist and what is the point? In C++ you create Classes, which have methods (functions), properties and data, and you then add the Class to your project and rebuild your appli- cation, which can now use the new class. Using an Extension DLL, you can make the whole Class an Extension DLL, add it to your project, and use it as ifit’s part of your application. It’s very nice—I have used the technique a few times. The other DLLs, ‘Regular and Regular dynamically linked to MFC, are self-explanatory. I mentioned that ARX applications are DDLs and ARX (DLL) applications can also call the ser- vices of other DLLs. One reason to use the services of an exteraal DLL from an ARX application is in international software development, or localization, as they call it. You can create a resource-only DLL and place all the bitmaps, dialogs, strings, et. in the DLL. It’s easy to create DLLs if you need a foreign language version of your application: place the appropriate DLL in the DLL directory and voila, you're done. We will discuss local- ization later in the book. One of the things that you will notice about DLLs is that they do not have a main, fanction as ADS applications did. A DLL is not an application in the sense that it vill run on its own; rather isa service and an application (in our case AutoCAD) will call on the services provided by the DLL, which means that AutoCAD must know the entry point into the service. The entry point for an ARX application, discussed next, is acrxEntryPoint, ARX ENTRY POINT—ACRXENTRYPOINT() Look at aerx€ntryPoint in the listing for the Hellol.cpp file (we will add more details to the file later): finclude finclude void initAapp(): void unloadApp(); void helloWorld(); void initapp() ( // register a command with the AutoCADe command mechanism acedRegCmds->addConmand( “HELLOWORLD_COMMANDS”, Hello”, Bonjour”, ‘ACRX_CMD_TRANSPARENT , helloWorld) ; } void unloadkpp() { ) acedRegCnds->removeGroup( “HELLOWORLO_COMMANDS™) void helloWorld() t ) acutPrintf(“\nHel1o World! extern “C” AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt) ( switch (msg) ( case AcRx::kInitAppisg: acrxDynamicLinker->unlockAppl ication(pkt); acrxRegisterAopMDIAware(pkt) : initappQ): break: Getting Stated case AcRx::kUn] oadAppisg: unloadAppQs break: default: break: } return AcRx::kRetOk: ) ‘The extern “C” tells AutoCAD that aerxEntryPoint is an external function. acrxEntryPoint is the name ofthe function whose return type is AeRic:AppRetCode and this function takes two parameters: an AcRx:AppMsgCode msg and a vold* pointer. We will discuss AcRic:AppMsgCode in a moment, but first le’ take a look at the definition of acrxEntryPoint. The two functions initAppQ and unloadAppQ) shown above are user-defined helper functions, which we will ook at when we cre~ ate an application, Let’s take a closer look at the first paramerer AcRx::AppMsgCode msg. This para~ meter is of the type AcRx:AppMsgCode. ‘When you load an ARX application with AutoCAD’s ARX command or if the application is automatically loaded through the acad file, the first message your appli- cation will receive is AcPox:kinitAppMsg. Remember that the first point of entry into ‘your application is through acrxEntryPoint(). If you begin a new drawing, remem- ber that ARX applications remain loaded and the next message your ARX applica~ tion will receive is AcRoctkLoadDwgMsg. Actually, when you first launch your application in AutoCAD, it will receive AeRx:kinitAppMsg followed by ‘AcRxklLoadDwgMsg. Whether you respond to AeRox:kLoadDwgMsg or not is up to you. In the code above, upon receipt of AeRox:kinitAppMsg, we cal the function initAppQ. This would be an example of a user-defined function where any applica tion initialization would be carried out. Remember, when you close down your cur- rent drawing session and begin a new drawing session, you will receive ‘AcRic:kUnloadDwgMsg and AcRox:kLoadDwgMsg respectively. In this case, because a AcRixzkinitAppMsg was not sent, the user-defined function initAppQ) would not get called—-something to keep in mind. There are more messages than I have shown above; we will get to these when the time comes. Now let's look at the official defi- nitions of these messages. AcRxx:AppMsgCode is an enumerated type as shown: AcRx::AppMsgCode return code definition. enum AppMsgCode — { kNulIMsg = 0, kIni tAppMsg KUnloadAppMsg = = 2, kLoadDwaMsg a KUnloadDugMsg = 4, kInvkSubrlsg = 5 kCfoMsg = 6 kEndMsg -7 kQuittss = 8 kSavelsg = 9 kDependencyNsg = 10, kNoDependencyMsg = 11, kOleUnloadAppmsg = 12, kPreQuittsg = 13, kInitDialogMsg = 14, KEndDialogMsg = = 18 J; ‘As you can see, there are many more messages possible than those I have shown ear- lier. Some of these messages are a carryover from the ADS days, specifically kKinvkSubrMsg, kEndMsg, kQuitMsg, and kSaveMsg, and because we are now using ‘ObjectARX, I wontt cover these messages. So when are the messages that I have shown in the acrxEntryPoint code received? Refer to Table 1.1. Message Description ‘dnitAppMsg ‘Sent when the application is loaded to open communications between AutoCAD snd the application. KUnloadAppMsg | Sent when the ARX application is unloaded (cither when the user unloads the application or when AutoCAD itselfis terminated). Closes files and performs cleanup operations. KLoadDwgMsg | Sent when the drawing is opened. Makes the ADSRX function library available to the application functions that need it. ‘Message sent only if the application has registered an AutoLISP function with acedDefun(). KUnloadDwgMsg_ | Sent when the user quits a drawing session and unloads the ADS function library. Table 1.1 AutoCAD Messages ‘When we start debugging our application, we will use breakpoints to show what mes- sages are received as well as application flow. Now lets look at the aerxEntryPoint Getting Started $$ return type AcRx:AppRetCode. This enumerated type provides the return values for the acrxEntryPoint function, enum AppRetCode { kRetoK = 0, kRetError = 3): Tfall went well in the aerxEntryPoint function, it will return AcRxc:kRetOK. CREATING YOUR FIRST OBJECTARX APPLICATION ‘Wellin keeping with traditional programming, le’ start out with the AutoCAD com- ‘mand prompt equivalent of “Hello World.” As I have mentioned before, AutoCAD ‘ObjectARX applications are DLLs with a ARK file extension. We are about to out- Tine a procedure to create ARX applications that you will use again and again so, before we proceed and assuming you have Visual C++ up and running, close any workspace you are currently in by selecting Close Workspace fom the File menu, CREATING THE DLL All of the following steps are carried out in Visual C++, |. From the File meru select New....The New dialog box will appear as shown below. Make sure that the Projects tab is selected, From the Projects lst select Win32 Dynamic-Link Library. See Figure |. Figure 1.1 New Project from Visual C++ 2. In the Project name edit box type “Hello|:” Note that as you type “Hello!” it will also appear in the Location edit box. You can change the location for your project by selecting the button to the right of the Location edit box. 3. Ifthe Projects type is correct, the Project name is correct, and the project Location is correct, you can select the OK button. 4. In the new dialog box (Step | of 1), choose An empty DLL project. Click Finish and then OK. After you select the OK button, Visual C++ will create a Hello workspace for you. INSERTING FILES INTO THE VISUAL C++ PROJECT 1. Select New... from the File menu in Visual C++. In the New dialog box, rmake sure the Files tab is the active tab. Select the C++ Source File option, name the file“Hellol.cpp” and make sure the Add to project: check box is selected. 2. Ifyou forgot to make sure the Add to project: check box was selected in the previous section, you can add the file to the project in Visual C++. In Visual ‘C++, from the Project menu select the cascading menu Add To Project and then select Files....The Insert Files into Project dialog box will appear. Select hellol.epp and then select the OK button. See Figure 1.2. fhetot.cpp Lezepp:cxsti:h:.th: tc) i Figure 1.2 Insert Files into Project Getting Started WN 3. Add code to the Hello/.cpp file as follows: finclude #include void initapp(); void unloadApp(); void helloWorld(); void initapp( ( // register a command with the AutoCADé command mechanism acedRegCmds- >addCowmand( “HELLOWORLD_COMMANDS”, “Hello”, “Bonjour”, ‘ACRX_CMD_TRANSPARENT, hel loWortd) ; ) void unloadApp() ( ) acedRegCmds - >removeGroup( “HELLOWORLD_COMMANDS”) void helloWorld() { ) acutPrintf(“\nHello World extern “C” AcRx::AppRetCode acrxEntryPoint(AcRx: :AppMsgCode msg, void* pkt) ( switch (msg) t case AcRx::kInitAppMsg: acrxDynamicLinker->unlockAppl ication(pkt);, acrxRegi sterAppMDIAWare(pkt initapp): break: case AcRx::kUn]oadappMsg: unToadApp): break: default: break: ) return AcRx::kRetOk: ‘We now need to create a definition file for our project. Add a new file to our project and save it as Hellol.def, and edit the contents of the file to appear as follows: LIBRARY “Hellol” EXPORTS. acrxEntryPoint PRIVATE acrxGetApiVersion PRIVATE Dont forget to add this ile to our project, ‘Were almost there, but before we can compile and link our application to Hello1.arx, wwe need to set up the project settings. We are going to make a number (a lot!) of changes to project settings, so stay tuned and hang in there. CHANGING PROJECT SETTINGS |. From the Project menu in Visual C++ select Settings.... This will Invoke the Project Settings dialog box as shown. Getting Started 2. In the Settings For drop-down list, make sure All Configuratons is select- ed. Select the General tab on the right side of the dialog box. Verity that we are Not Using MFC. 3. Now select the Debug tab. In the Category drop-down list box make sure General is selected. In the Executable for debug session box, you can select the location of the AutoCAD 2000 executable fle ACAD.EXE by using the button to the right of this edit box. If you do not select the location now, ‘when you debug your application, Visual C++ will prompt you for the location of the ACAD.EXE file. In the Settings For: drop-down lis, select Win32 Debug. In the Working directory: edit box enter the following." \Debug” (without the double quotes, of course).When you debug your application and load an ARX file, ou will be taken right to the directory containing the ARX fle. We are now going to do the same for the Release build. In the Settings For: drop-down list select Win32 Release. In the Working directory: edit box enter the following:"\Release” (without the double quotes, of wourse). 4. Now select the C/C#++ tab and mke sure the Settings For: is set for All Configurations. In the Category: drop-down list box select Code Generation. Now select the Use run-time library drop-down list and select Multithreaded DLL.Yes, know there is a Debug Multithreaded DLL option there, but select the Multithreaded DLL anyway.Trust me! 5, Select the Link tab, From the Settings For: drop-down lst select Win32 Debug; from the Category: drop-down lis select the General category. In the Output file name: edit box, change Debugihellol dll to Debughhelloll.arx. Switch the Settings For: drop-down list back to ‘Win32 Release and change the edit box from Releasefhellol.dll to Release/hellolarx. 6, With the Link tab stil selected ard the Settings For: drop-down list set at All Configurations, in the Objectilibrary module edit box, add the fol- lowing library file names at the beginning of the ist: rxaphlib, aerx8.lib, acutill Sib, and acedapi.tib. 7. Select the OK button and you're done. Phew! Just one more item left. We need to tell Visual C++ the locations for our include direc- tories and also our libraries. From Visual C++, select the Tools menu and then select Options.... When the Options dialog box appears, make sure the Directories tab is selected, Add the location for your ObjectARX 2000\Inc directory and also add your ObjectARX 2000\Lib location. You only have to do this once and Visual C++ will be set. Please note that for all of the applications in this book, this is how I set up my include and library files. You will need to do the same. Refer to Figure 1.4 to see how this is done. G\Pogan Fle\DevStafoWWOL G:PogamFes\DevStafoWVOWFCS JNDeVSudAVELB ENDeSuckAVOWFOR Figure 1.4 Setting Incuide and Library Files Congratulations on your patience! Now we can build, lad, and run the application. ‘From the Build menu in Visual C++, select cither Build hello! .arx or Rebuild all. The debug view window should appear and, if all went well, you should see the following in the debug view window. Compiling... hellol.cpp Linking... Creating library Debug/hellol.lib and object® Debug/hellol.exp hellol.arx - 0 error(s), 0 warning(s) IE you got this far, give yourself a large pat on the back. For those of you who hate typ- ing, you will find the code in the \Cade\Chapter 1\bellol folder. You can also double-click the file Bello1.dsw, which will Iaunch Visual C++ for you and open the Hellol workspace—you may have to make some minor changes to the project settings. Now let’s run the application through the debugger. Remember that if you launch an application using a debugger, it will launch AutoCAD for you. When you stop debugging, it will shut down AutoCAD for you. So without any further ado, from the Build menu in Visual C++, select Start Debug and then select Go from the cascad- ing menu. (Be patient; it may take a little while for AutoCAD to get up and running under a debugger’s control.) Getting Started RUNNING THE HELLO! ARX APPLICATION. Ifyou forgot to enter a location for the AutoCAD executable ACAD.EXE, as outlined previously in step 3 of “Changing Project Settings,” you will see the Executable For Debug Session dialog box shown below. Click the button to the right of the edit box and select the location of your ACAD.EXE. Once you select the location for AutoCAD’s executable file, you will not be prompted by this dialog box again, eres: Figure 1.5 Executable for Debug Session ‘The fist thing you will se is this dialog box generated by Microsoft Developer Studio informing you that AutoCAD has no debugging information. (See Figure 1.6.) Don't be alarmed—naturally enough, we dont have the debug version of AutoCAD. Afterall we're not the ones debugging AutoCAD (hopefilly it’s already done), we're debugging our ARX apps. You can either select the OK button or select the box Do not prompt in the future and then the OK button. Figure 1.6 No Debugging Information Fall went well, Visual C++ will launch AutoCAD for you and you should now be sit- ting at the command prompt in AutoCAD. We now need to load and run our ARK app. At the command prompt in AutoCAD enter “ARX” to execute the ARK com- mand. You will see the following prompt: Command: arx Enter an option [?/Load/Unload/Commands/Options]: 1 This will be followed by AutoCAD’s Select ARX File dialog box as shown below, assuming the system variable ‘FileDia’ is set to 1. Otherwise, you will be prompted at the command prompt for manual input. Select ARX Fie faces Figure 1.7 Select ARX Fle Select the bellof.arx file and then select the Open button. If all went well, you should see the following at the command prompt (you may have to press F2 in AutoCAD to switch to the Text Seren): Inside InitApp - Registering commands with acedRegCmds Enter “HELLO” at the conmand prompt to execute. Received AcRx: :kLoadDwoMsg Command: Getting Started (7 i iT ‘The command prompts above indicate thet AutoCAD sent an AcRaxikinitAppMsg. message, which caused our initApp() function to be called. It also sent an ‘AcRoc:kLoadDwgMsg because the drawing editor is available. At the command prompt, type “hello” and then press ENTER. You should see the following: Command: hello Hello World!! Now, at the command prompt, type “ARX” and press ENTER and then select the ‘Commands option of the ARX commané. You should see the following: Command: arx Enter an option [?/Load/Unload/Commands/Options]: c Commands Registered by Extension Programs: Command Group ‘HELLO_COMMANDS* HELLO, HELLO I will explain what this is later. Now begin a new drawing in AutoCAD. You should see the following: Commany Regenerating drawing. Received Acrx: :kLoadDwgMsg Command: AutoCAD menu utilities loaded. Now notice that we did not receive an AcRox:klnitAAppMsg this time because the appli- cation remains loaded in memory from drawing invocation to drawing invocation. The only message that we received this time was an Acra:kLoadDwgMsg, because we are in a new drawing session in AutoCAD. Now again at the command prompt, type “ARX” and then press ENTER. Select the Unload option from the ARX command. Now AutoCAD needs the name of the ARK file, which is “hello1.” You should see the following: Command: arx Enter an option [?/Load/Unload/Commands/Options]: u Unload ARX file: hellol Goodbye Removing command group “HELLO_COMMANDS” hellol successfully unloaded. ‘The command prompts above indicate that AutoCAD sent an AcRx::kUnloadAppMsg message, which caused our unloadApp() function to be called. Now at the command prompt, type “hello” and press ENTER. You should see the following: Command: he11o Unknown command “HELLO”. Press Fl for help. ‘After unloading the application, AutoCAD does not know what the HELLO com- mand is, naturally enough. Let's end our debugging session: from the task bar switch back to Visual C++ and from the Build menu, select Stop Debugging. This will end your AutoCAD session. You could also have selected Exit from AutoCAD’ File menu, which would also end your debugging session and transfer you back to Visual C++. ELEMENTS OF THE ARX HELL Al CATION Let's review what we have done so far and discuss some of the elements in the code. Let’s start out with the aerxEntryPoint function. The acrxEntryPoint function contains a switch statement that responds to messages sent by AutoCAD to our ARX application. As we have seen, the first message that our application will receive is initAppMsg. In the case segment that handles AcRx:kdnitAppMsg, the first function call is acrxUnlockApplication(ptr);. This is the function that allows us to unload our application by means of the ARX command in AutoCAD, as shown ear~ lier. This statement also requires the inclusion of #include “rxregsve.h” We will dis- ‘cuss the header files a litle later. When ARX applications are initially loaded, they are locked so that they can't be unloaded until AutoCAD shuts down. ARX applications ‘that communicate with each other or entities that require the existence of the ARX app should not be unloadable. To make the ARX app unloadable, remove the call to acrxUnlockApplication(ptr};. If you are not going to use this feature and also if you are not going to register any services (discussed later), you can omit the #inelude “rxregsve:h” header file. ‘The next call is to our user-defined initAppQ function: void initApp() ( // register a command with the AutoCADe Getting Started command mechanism acedRegCmds->addCommand( “HELLOWORLD_COMMANDS” , “Hello”, “Bonjour”, ‘ACRX_CMD_MODAL, hel loWorld) ; acutPrintf(“Bs%s", “Inside Initapp - “,o “Registering commands with acedRegCmds\n"); acutPrintf(“Enter \"HELLO\” at the commande Prompt to execute. \n"); } Inside the initppQ function we call acutPrintf(), which is identical to the C language printf). Output from the acutPrintf() function is directed to AutoCAD's com- mand prompt or text screen. The maximum size of the string that can be displayed with acutPrintf( is 132 characters. The acutPrint{() function requires the #inelude “adslib.h”. The actual definition of aeutPrint{Q is located in the ads. fle in the \Object ARX\nc folder, but including adsli&.balso inchudes the ad. internally. The most important function in initApp( is acedRegCmds->addCommand (actually, it’s a macro). AutoCAD maintains list of commands in its internal stack and you can add your own defined commands to the AutoCAD stack using the acedRegCmds- >addCommand macro. The acedRegCmds macro provides a pointer to the ARK sys- tem AcEdCommandStack object. If you need to define more than one command, you can call the macro repeatedly. The use of acedRegCmds->addCommand requires the inclusion of the header file #include “aced.h”. Let’s look at the syntax of acedRegCmds->addCommand: acedReg(mds->addCommand(“CMD_GROUP™, “GLOBAL_CMD_NAME™, — “LOCAL_CMD_NAME”, ACRX_CMD_MODAL | ACRX_CMD_USEPICKSET, funcl); ‘The first parameter "CMD_GROUP” is the group set to which your command belongs. For example, we used "HELLO_COMMANDS” as our group name. When ‘we executed the ARX command at the AutoCAD command prompt earlier, we selected the Commands option of the command and received the following: Command: arx Enter an option [?/Load/Unload/Commands/Options]: c Commands Registered by Extension Programs: 6 Command Group *HELLOWORLD_COMMANDS” HELLO, BONJOUR The HELLO, BONJOUR ‘refers to the "GLOBAL_CMD_NAME”, “LOCAL_CMD_NAME” respectively. Global command name and Local command name refer to international versions of AutoCAD where the global command name, in our case HELLO, can be used in any language. The Local command name is a trans- lated version of the command name to be used in foreign language versions of AutoCAD. In our case, the Local command name is BONJOUR. A Global command name and a Local command name are required. The fourth parameter is the command flags: possible values for these flags are as follows: ACRX_CMD_MODAL ACRX_CMD_TRANSPARENT ACRX_CMD_REDRAW ‘ACRX_CMD_USEPICKSET I discovered that there are more ACRX_CMD_XXX command flags. However, a num- ber of them are undocumented; if you look in the ObjectARXnc\acemd.b, you will see a list of them. In AutoCAD commands can either be transparent or mod: our case we are using a modal command. If our command were modal and also made use of a selec- tion set, we could use the following combination as the fourth parameter using the bit- wise OR operator: ACRX_CMD_MODAL | ACRX_CMD_USEPICKSET ‘The final parameter is the function address, which in our case is the function hello. Entering “Hello” at the command prompt will cause the function hello to be executed. Remember, function names are the addresses of the function just as an array name is the address of the array. Note that the command name and the function name do not have to be the same. Also, you can add as many commands to the same command ‘group name as you like in separate calls to the acedRegCmds->addCommand CREATING AN ARX APPLICATION WITH A CUSTOM CLASS Now that we know the mechanism for creating an ObjectARX application, let's tun our attention to creating an ObjectARX application with a custom Class. In the previous application, we created custom commands and added those commands to the Getting Started 21 ae AutoCAD command stack. In this application, we will again create custom commands ‘but we will also create a Class derived from AcRxObject. What is AcRxObject, you may well ask? AcRxObject is the base Class for numerous Classes in AutoCAD and wwe can best demonstrate it by executing the ARX command at the command prompt in AutoCAD. So, Enter “ARX” at the AutoCAD command prompt. Then select the ‘O'option, and select ‘CL, after which you will see a tree of the Class Hierarchy in AutoCAD, some of which I reproduce below. Command: arx Enter an option [?/Loac/Unload/Conmands/Options]: 0 Enter an option [Group/Classes/Services]: cl Class Hierarchy for: ‘AcRxObject’ AcRxObject. ACRxC1ass AcRxImpClass AcRxDictionary AcRxImpDictionary AcRxGenTypedDictionary ‘AcRxDLinkerReactor ‘AcRxDynamicLinker ‘AcRxImpDynamicLinker AcRxGenLinkedList AcRxGenLinkedListElement AcRxGenTableentry ‘AcRxGenVoid AckxIterator AcRxDictionarylterator ‘AcRxImpDictionarylterator AcRxGenLinkedListIterator AcRxImpSet Iterator AcRxTablelterator AcRxImpTablelterator AcRxService Acbbobject AcDbDictionary AcDbEntity ‘AcDb3dSolid AcDbBIockBegin AcDbBlockEnd AcDbBlockReference AcDbMInsert Block AcDbBody AcDbCurve AcDb2dPolyline AcDb3dPolyline AcDbArc. AcDbCircle AcDbEI1ipse AcDbLeader AcDbLine AcDbPolyline ‘AcDbRay AcDbSpl ine AcDbX1 ine ‘AcDbDimension ‘AcDb2.ineAngularDimension AcRFXPlane ACRFXPoint ACRFXPolyl ine AcRFXSphere ACRFXSp1ineCry AcREXSp1ineSurf AcRFXSurface AcRFXTorus, ACRFXUtiT End of List AutoCAD maintains a Class hierarchy tree, and in the next application we will add ‘our Class to that hierarchy tree. As you can see, AcRxObject is at the base of the tree. One of the Classes derived from AcRxObject is AcDbObject, which contains AutoCAD Database Resident Objects; items that fall under this category include Dictionaries, Groups, Mline styles, Symbol tables, Symbol table records and Entities. Alll entities in AutoCAD are derived from AeDbEntity, such as AeDbLine. Note that you can derive your own entities from AutoCAD entities and add custom behavior to those entities, but I'm getting way ahead of myself. Getting Started 23 ‘I mentioned before that AutoCAD is an Object Oriented CAD application. Well, from the discussion above, I think that you can see that I wasnt kidding. So let’s move ‘on and create our second application, which will include our own Class, derived from AcRxObject. CREATING AN APPLICATION WITHA CLASS DERIVED FROM ACRXOBJECT ‘The files for this completed application are located in the \Code\Chapter 1\Hello2 folder. |. Repeat the exact steps as outlined earlier except that this project will be called Hello2. Here is the listing for the Hello2.cpp file;add this to the Hello? project: /1 hello2.cpp TTT LLL LTT uit #include #include #include “HloClass.h” // entry point for this application extern “C” AcRx::AppRetCode acrxEntryPoint( ACRx::AppMsgCode msg, void* ); // helper functions void initApp (void): void unloadApp( void): // user defined functicns void hello(void); TTL LALLA WATT // acrxentryPoint( internal) 1/ This function is the entry point for youre application. TTL LLL TTL AcRx: :AppRetCode acrxEntryPoint (AcRx: :AppMsgCoded! msg, void* ptr) t switch (msg) { case AcRx::kInitApplsg = acrxUnlockApplication(ptr); acrxRegisterAppMDIAware(ptr) initapp0): break: case AcRx::kUnloadAppMsg : unToadApp(); break; case AcRx::kLoadDwoMsg : acutPrintf (“Received Acrx: break; kLoadDwgMsg\n™); case AcRx::kUnloadOwoMsg : break; default: break; Y 17 switeh return AcRx::kRetOK: ) void initApp(void) i // TODO: init your application // register a command with the AutoCADe command mechanism W/ Step 1: W JJ Add classes to the runtime-identifiabled class hierarchy. W //_ This call was defined by use of thee ‘ACRX_CONS_DEFINE_MEMBERS() 77 macro on CHelloClass. CHeT1oClass::rxInit(): //_ Tf there are any other classes that need tod be initialized J/ they would go here in the following format. J/ Classname>: :rxInitQs 11 Step 2: u" //_ Build class relationships before proceeding. Use after 77 making a set of ::rxInitOe calls which add a 17 contiguous growth onto the inheritance tree. uy acrxBuildClassHierarchy(); IT Step 3: uy // Do any other initialization you need.@ The point is that it is // best to do the classes first, since thee rest of the initialization // is usually concerned with them. u" // This example application registers ad service name, needed for // symbolic funczion export, and a handyd object for verifying the 71 presence of tris application. uw acrxRegi sterService( SERVICE) ; /1 register a command with the AutoCAde command mechanism WW acedRegCmds->addConmand( “HELLO_COMMANDS”, & “HELLO2", “HELLO2", ACRX_CMD_MODAL, hello); acutPrintf (“Type \"HELLO2\” to execute. \n"); ) void unloadApp( void) ( // TODO: clean up your application JJ Remove the service that was registered viag acrxRegisterService() Wy delete acrxServiceDictionary->remove( SERVICE); // Remove the command group added via acedRegCnds->addCommand "W acedRegCmds->removeGroup( “HELLO_COMMANDS”) ; // remove CHelloClass from the AcRx runtime? tree au" deleteAcRxClass(CHel loClass: :desc())s } J/ T0D0: add your other functions here void hello(void) { CHelloClass *pHloClass = new CHelloClass; acutPrintf(“Calling the CHelloClass hellod function: \n”): if({pHloclass) ( acutPrintf(“OOPS!! we got a null pointere eee \n")s return; ) pH oClass->hel10(): delete pHloClass; acutPrintf (“Deleted the CHelloClass Object pointer. \n ) Here is the listing for the Hello2.def fie: add this to the Hello2 project. DESCRIPTION ‘ObjectARX application” LIBRARY “hel102” EXPORTS ——acrxEntryPcint PRIVATE acrxGetApiVersion PRIVATE Here is the listing forthe HloClassh file; add this to the Hello? project. 11 Woclass.h Getting Started 27 ifndef HELLOCLASS_H f#define HELLOCLASS_H #include include fdefine SERVICE “CHelloClass_Services” 1 HelloClass class CHelloClass : public AcRxObject ( public: ACRX_DECLARE_MEMBERS(CHe110Class); void hel10(); Mh endif 4. Here is the listing for the implementation of the CHloClass in the HloClass.cpp file;add this to the Hello2 project. Hinclude “HToClass.h” 17 HelloClass implementation: ACRX_CONS_DEFINE_MEMBERS(CHelloClass, AcRxObject, 0 void CHelloClass: :hel1o() { acutPrintf(“Hello vorld from thee CHelloClass::hello function. \n’ ) All of the linker and project settings from the last application also apply to this application. RUNNING THE HELLO2 OBJECTARX APPLICATION |. From the Visual C++ Debug mens select Start Debug and then select Go to launch AutoCAD under the control of the debugger. 2. At the AutoCAD command prompt, execute the ARX command and load the hello?.arx application. 3. When the ObjectARX application is loaded, enter “HELLO2” at the command prompt to execute our application. Output from the AutoCAD command prompt is shown below: Command: he110z Calling the CHelloClass hello functio Hello world from the CkelloClass::hello function. Deleted the CHelloClass Object pointer. 1. Execute the ARX command again at the AutoCAD prompt and select the ‘Options option followed by the CLasses option. The output to the text screen shows the ARX Class hierarchy. However, the very last Class is our (CHelloClass. The partial output o the AutoCAD text screen is shown below: AcRFXTorus ACRFXUtIT CHel1oClass End of List (Our Class CHelloClass has been added to the runtime tree. Now load in the first ObjectARX application that we developed earlier— hello Lore. The AutoCAD text screen output is shown below. Command: arx 2/Load/Un1oad/Commands/Options: 1 Inside InitApp - Registering commands withe acedRegtmds Enter “HELLO” at the command prompt to execute. Received Acrx::kLoadDwsMsg Now, at the AutoCAD command prompt, execute the ARX command, again selecting the Commands option ofthe command.The AutoCAD text screen ‘output is shown below. Command: arx Enter an option [2/Load/Unload/Commands/Options]: Commands Registered by Extension Programs: Command Group *HELLOWORLD_COMMANDS* HELLO, HELLO HELLOZ, HELLOZ Getting Started 29 $$$ Note that there are now two commands registered under the HELLOWORLD_COM- MANDS’ group. Each application adds a separate command to the ‘HEL- LOWORLD_COMMANDS! group using the acedRegCmds->addCommand macro discussed earlier. Also note that both of these applications make a call to acedRegCmds->removeGroup(“HELLO_COMMANDS"”); in the unloadApp() fanction call. Unloading either application will remove the entire “HELLO_COM- MANDS"” group. 7. At the AutoCAD command promot, execute the ARX command again. Select the Options option and then select the Services option. Output from the ‘AutoCAD command prompt is shown below. Command: arx Enter an option [?/Load/Unload/Commands/Options]: 0 Enter an option [Group/Classes/Services]: s Names of Services entered intod acrxServiceDictionary: KernelServices AckdServices AcDbServices AcGi Services AdeskAutomation AcMeServices CiTServices AcmStdManagerService AutoCadMServices CHelloClass_Services End of List. ‘The output that you receive may be different from mine; however, what we are interested in the last entry—ChelloClass, Services. These are the se vices registered by our application You don't have to register a service. 8. At the AutoCAD command prompt, execute the ARX command and selet the Unload option of the command as shown below. Command: arx Enter an option [2/Load/Unload/Commands/Options]: u Unload ARX file: hell02 hello2 successfully unloaded. 9. Now,at the AutoCAD command prompt, execute thé ARX command once again and select Commands option of the command, Note thatthe entire *HELLO_COMMANDS! group has been unloaded. if you want, you can stop debugging the application now by selecting Stop Debugging from the Debug menu inVisual CH*, ELEMENTS OF THE ARX HELLO2 APPLICATION ‘Wal, this application is a litle more complicated than the previous application, but what I wanted to demonstrate was the mechanism for adding your own Classes to the AutoCAD runtime tree. We added two new files to this application, namely HloClass.b and HloClas.cpp. These files represent the CHelloClass Class, which we derived from ‘AcRxObject, AutoCAD’ base Class. Lets examine these files in more detail, start- ing with the HloClas.b header file shown below. // hloclass.h ifndef HELLOCLASS_H fidefine HELLOCLASS_H Hinclude #include fidefine SERVICE “CHel10Class_Services” 17 HelloClass Class CHelloClass : putlic AcRxObject ( public: ‘ACRX_DECLARE_MEMBERS (CHe110Class): void hello(): hs fendi f ‘The #inelude contains the definition of the Class AeRxObject; it also has within it a number of include statements, specifically #inelude “rxbollerch”, which is required by the macro ACRX_DEFINE_MEMBERS. Our Class “CHelloClass” is publicly derived from AutoCAD's base Class AcRxObject. We have 1 public: access specifier and a single hello() function (or method if you prefer the Object Oriented programming parlance). This Class does not define any data mem- Gatting Started 31 sr bers but it could have if we had wanted. The odd-looking statement /ACRX_DECLARE_MEMBERS(CHelloCiass); is a macro and has a companion in the implementation file ACRX_CONS_DEFINE_MEMBERS(CHelloClass,AcRxObject, 0);, which we will look at in a moment. /ACRX_DECLARE_MEMBERS(Classname); must have #include “rxboilerth” but as we stated earlier, that is included inside the #include header file. ACRX_DECLARE_MEMBERS(Classname) defines a number of very useful functions that are used to determine the kind of object that we are dealing with when it comes to the runtime tree. The macro defines AcRxClass* CLASS_NAME: :desc() AcRxClass* CLASS_NAME::sA() const ACRxClass* CLASS_NAME::gpDesc = NULL. ‘When used, this macro must be terminated by a. ACRX_DEFINE_MEMBERS (MyClass); ‘We will discuss these functions in detail when we create examples employing them. ‘Now let’s look at the implementation file. finclude “HloClass.h” // HelloClass implementation: ‘ACRX_CONS_DEFINE_MEMBERS(CHeTloClass, AcRxObject, 0); void CHelloClass::hel1o() i acutPrintf(“Hello world from thee CHelloClass::hello function. \n"); ) This fanction simply prompts the string to the command prompt, so let’s look at ACRX_CONS_DEFINE_MEMBERS(CHe110Class. ACRxObject, 0); ‘ACRX_CONS_DEFINE_MEMBERS(ClassName, ParentClassName, VerNo} ClassName Input the name of the class being declared ParentClassName Input the name of the ARX tree resident base class VerNo Input the version number for this class (not currently used by ARX) There are a number of ACRX macros used for custom Classes in AutoCAD. ACRX_CONS_DEFINE_MEMBERS defines the CLASS_NAME:rxinit() function. ‘We will look at these functions in more detail when we start writing applications that, use custom Classes, Now let’s turn our attention to the initAppQ) function in Hello2.cpp file, as shown below. void initapp(void) i // TODO: init your application // register a command with the AutoCADe command mechanism JI Step 1: uW // Add classes to the runtime-identifiabled class hierarchy. W J/ This call was defined by use of thed ‘ACRX_CONS_DEFINE_MEMBERS() 7/ macro on CHelloClass. CHe11oClass:srxInit(): J) If there are any other classes that needd to be initialized 7/ they would go here in the following format. 77 CClassname>: :rxinit(); JI Step 2: uw JJ Build class relationships before? proceeding. Use after // making a set of calls which add a // contiguous growth onto the inheritance tree. uw acrxBuildClassHierarchy(); erxInitOe /1 Step 3: uw Getting Started 33 a Do any other initialization you need.& The point is that it is VW best to do the classes first, since the? rest of the initialization a is usually concerned with them. ue dt This example application registers a& service name, needed for Vt symbolic function export, and a handy? object for verifying tre 7] presence of this application. “te acrxRegisterService( SERVICE) ; J register a command with the AutoCAD commandé? mechanism “ acedRegCnds - “HELLO2", “HELLO: \ddConmand( “HELLO_COMMANDS” ACRX_CND_MODAL, hello); acutPrintf(“Type \"HELLO2\" to execute. \n"); ) ‘The steps outlined in the initApp) function (which is called when we receive an ‘AcRx:ckinitAppMsg message from AutoCAD) are the steps required to add a Class to the AutoCAD runtime tree. When you are building applications that have custom (Classes, these are the steps that will be repeated time and time again. In step 1 we ini~ tialize our Class as follows: CHelloClass::rxdnit();. The rxinit() function performs the following tasks. + Registers the custom Class + Creates the Class descriptor object + Places the Class descriptor object in the Class dictionary Every C++ Class that is registered in the ARX runtime class tree must have this fune- tion defined. Afterall our Classes have been registered, we need to build a Class hier- archy using acrxBuildClassHierarchy() as cutfined in step 2. In step 3, we register our service with a call to aerxRegisterService(SERVICE);. Remember, in our header file we had #define SERVICE “CHelloClass Services”. Finally we add our user-defined command(s) to the AutoCAD command stack. Anyway, the most important thing to note is that these represent the steps required to add a custom Class to the ARX Class tree. Keep in mind that your application can define Classes that are never added to the ObjectARX runtime tree; for those Classes that are not added to the runtime tree, you do not have to go through the steps above. If our Class is allowed to be unloaded as a result of receiving an ‘AcRic:kUnloadAppMsg, we call the unloadApp() function shown below. void unloadApp( void) ( // TODO: clean up your application // Remove the service that was registered? via acrxRegisterService() WW delete acrxServiceDictionary->remove( SERVICE); // Remove the commend group added via acedRegCmds->addCommand uw acedRegCmds->removeGroup(“HELLOWORLD_COMMANDS™) ; // remove CHelloClass from the AcRx runtimed tree uv deleteAcRxClass(CHel 1oClas. desc())s ) Unloading our application is pretty much the reverse of initializing our application. We delete the service dictionary, remove the command group, and finally delete the ARX runtime Class. When we call deleteAcRxClass(CHelloClass:idesc(), the parameter CHelloClass:idese() returns a pointer to the AcRxClass object repre~ senting the CHelloClass Class. ‘When we entered the HELLO2 command at the AutoCAD command prompt, we invoked the global ‘hello function in the Hello2.hel10(); delete pHloClass; acutPrintf(“Deleted the CHelloClass Objecté pointer. \n™); ) Inside the global ‘hello’ function, we allocate memory for a new CHelloClass (Class. If this was successful, we then call the hello( function of the CHelloCtass Class. Please note that the global function hello() and the hello() function that is part of the CHelloCtass Class have nothing to do with each other I should have used different names here, but I didn't, so I hope I haverit caused any confusion, After calling the ‘CHelloCiass hello() function, we deallocate the memory by calling the delete func tion. Well, [hope you made it through that okay. ‘Wal, that almost brings us to the end of Chapter 1. We covered a lot of material. In the next chapter we will take an overall look at the libraries that ObjectARX provides and try to get a broad overview of what OtjectARK is all about. We will also discuss ‘what is coming up in the other chapters of the book. But before we move on, I want to say more about the ObjectARX 2000 Wizard and other resources available to you. OBJECTARX 2000 WIZARD As you can sec, it's quite a bit of work to manually set up applications for AutoCAD/ObjectARX 2000. If you are like me, you are more interested in getting ‘on with your work than in worrying about all the project settings, linker, and compiler ‘options available. You will be happy to know that there is an application that will come to your rescue. After you install ObjectARX 2000 on your computer, look in the \ Object ARX 2000\Util\ ObjARXWiz folder, where you will find the Object ARX 2000 Wizard. Read the documentation on how to install and operate this application. do suggest, however, that you download it from the Autodesk Developer Network (ADN) Web site (htp://vwvautodesk.com/adn) because it is constantly updated. The version that is on the Web site is much improved over the version that comes with the ObjectARX 2000 CD, or if you download ObjectARX from the utp://vww.autodesk.com/abjectarx Web site. I make this suggestion at the time of writing this book; it may have been updated by the time you read this book. ‘The ObjectARX 2000 Wizard is a tool that works inside Visual C++ 6,0 and is much like a combination of the AppWizard/ClassWizard that Visual C++ 6.0 provides. It will set up your ObjectARX application files for you and can also be used after your files are set up to add commands and Classes and other items of interest in ObjectARX. It also works in conjunction with the Visual C++ 6.0 Class Wizard. You will see that I have also used the ObjectARK 2000 Wizard to create a number of the sample applications that you will find throughout this book. I was recently giving an ‘ObjectARX 2000 training class where one of the attending developers declared that he liked to set up everything manually. After a few demonstrations of what the ‘ObjectARK 2000 Wizard was capable of achieving, the developer in question retract- ed his statement and is now an avid ObjectARX 2000 Wizard user. I wish I could say that I wrote the ObjectARX 2000 Wizard, but I didn't. Compliments go to the following membess of the Developer Consulting Group at Autodesk: Stefan Kraus, Cyrille Fauvel, Albert Szilvasy, and Kean Walmsley. ADDITIONAL RESOURCES ‘After you install ObjectARK 2000, refer to the following folders for additional sam- ples of ObjectARX 2000 applications: \ObjectARX 2000\arxlabs \ObjectARX 2000\docsamps \ObjectARX 2000\samples ‘The doctamps folder contains completed samples of the sample code fragments that are used throughout the ObjectARX 2000 Developer Guide—lots of useful code here. also suggest you read ObjectARK 2000 Migration Guide if you are porting appli- cations to AutoCAD 2000. Log on to the ADN Web site and download the latest ver- sions of the samples, because these are updated from time to time. ‘The Autodesk Developer Network (ADN) is located at: http/www.autodesk.com/adn Here you will find the updated ObjectARX 2000 Wizard, updated samples, and API documents. In addition, there are also searchable Technical Solutions, documents that answer questions that may be useful to a number of developers. These documents con tain sample code fragments and they usually have a zip file attached to them that con- tains ready-to-build code. At the time of writing this book there were over 1000 ‘Technical Solutions available on the ADN Web site. Autodesk also maintains a number of newsgroups that deal with all aspects of Autodesk products; some of these are for ObjectARX, VBA, and Visual LISP. ObjectARX Environment and Book Overview In the previous chapter we looked at how you write an ARX application. In partic- ular, we looked at the entry point to all ARX applications, namely acrxEntryPoint. ‘We created a simple application that prompted to AutoCAD’s command line. We also created an application in which we derived our own custom class derived from ‘AcRxObject and looked at the runtime tree. In this chapter I want to step back and take a broad look at what ObjectARX has to offer. We will take a look the libraries first and then outline the various chapters that make up the book. OBJECTARX LIBRARIES In very general terms, ARX provides the following libraries, which we will discuss in more detail later. ‘AcRx | Classes used for binding an application and for runtime class registration and identification. ‘AcEd | Classes for registering native commands and for system event notification, AcDb AutoCAD database classes (where all the entities are and more!). ‘AcGi | Graphics interface for rendering AutoCAD entities. ‘AcGe | Usilty ibrary for common liner algebra and geometric objects (I wish they had this years ago!) ‘ADSRX | A C library used to create AutoCAD applications. ObjectARX applications typically se this library for operations such as entity selection, selection set manipulation, and data acquisition. Table 2.1 ObjectARX Libraries ” ACRX LIBRARY ‘We have already used some of the facilities of the AcRx Library It provides system- level clases for dynamic link ibrary (DLL) initialization and linking and for runtime class registration and identification. The base class of this library is AcRxObject. Functions in this library that you will use often are AcRxObject JJ Class identification AcRxObject 1/ Class identificationg and derivation AcRxObject: :desc() // Returns the class name AcRxObject: :cast() // Cast an object from ae base class to a mored specific class ‘The isAQ function is for class identification; the class being identified must be of the type being tested. The isKindOf0) function is also used for identification, but here the class can be of the type tested or a derivative of the tested class. The dese() function returns the name of the class—we will see examples throughout the text. The east() function allows you to try to change the type of the base class to a class of a higher level. This will succeed or fail; ifit succeeds, you know that the base object pointer is a pointer of the type you cast to. This library also allows you to lock and unlock your application. Using ‘acrxUnlockApplication() allows your application to be unloaded. If you don't want your application to be unloadable, do not use acrxUnloadApplication(). For exam- ple, the existence of your application may tie your entities to an external database, and unloading your application may cause you problems. For those of you who program using ActiveX, AeRxObject has a number of COM functions. Another useful class in the library is AcRxService. Using this class, you can write utility functions and register the services that these functions provide. Your appli- cation can then check for the existence of the service and, if it exists, you may use the utility functions provided by the application that registered the service ACED LIBRARY ‘There are a number of important classes in the AcEd library. When you define your commands in AutoCAD, they are defined as native commands and are added to AutoCAD’s command stack. The function for adding commands to the command stack is addCommand(. This library also provides an editor reactor, you can add and remove a reactor to the AutoCAD editor. An editor reactor allows you to watch events in AutoCAD such as when commands are started and finished. Using the services of ObjectARX Environment and Book Overview 39 $n the reactor, you can respond appropriately. This ibrary also provides a transaction man- ager, which allows your application to respond to Undo events and ESC events, The transaction manager and reactor and editor reactor are covered in Chapter 9, “Notifications, Transactions, and Reactors.” ACDB [ARY ‘There isa lot to the AcDb library. It contains all the symbol tables, such as inetypes, layers, text styles, dimension styles, and others. Every AutoCAD database has a Named Objects dictionary, AeDbDictionary, which allows you to store custom data ‘within the drawing file; the Named Objects dictionary is part of this library, as is every AutoCAD entity. As we have seen earlier, we can react to AutoCAD editor, and we can also react to events that happen to AutoCAD database objects on an object by object basis. The major class players in the library are Root database: AcDbObject AcDbDictionary Symbol Tables: AcDbViewTable AcDbViewportTable AcDbLinetypeTable AcDbLayerTable AcDbTextStyleTable AcDbDimStyleTable AcDbBlockTable and other tables Entities: AcDbEntity ‘The AcDbObject class is responsible for opening and closing objects as well as appending objects to the AutoCAD database. The AcDbDictionary class allows you to add custom data to the database file, and important entries in the dictionary are AutoCAD's ‘groups’ and ‘mline’ styles. The symbol tables are pretty self-explanatory with regard to what they contain. Perhaps the most interesting class is AeDbBlockTable. This is where all of AutoCAD entities are stored. Two of the most important records in AeDbBlockTable are the *MODEL_SPACE record and the *PAPER_SPACE record. As you will see, all enti- ties in the AutoCAD database belong to one of these records. Block definitions are also stored in the AcDbBlockTable. We will begin our tour of the AutoCAD data- base in Chapter 4, “Understanding AutoCAD’s Database and Entity Structure.” Finally, every entity in AutoCAD is derived from AdDbEntity; you can also derive your own entities from AeDbEntity, as we will see later in Chapter 8, “Custom ‘ObjectARX Classes and Entities.” CGI LIBRARY AcGi stands for AutoCAD graphics interface. The services provided by this library really come into play when you define custom entities. In a 3D solid or mesh, for exam- ple, we have face data and edge data; there are classes in the AcGi library you can use to manipulate these subentities. Two of the most important classes that this library pro vides are AeGIWorldDraw and AcGiWorldGeometry. We will see examples of how this library is used in Chapter 8, “Custom Object ARX Classes and Entities.” ACGE LIBRARY The AcGe ibrar is a utility library. It provides mathematica calculation and geom- try calculation functions such as vectors, points and matrices. For every entity in ‘AutoCAD there is also a corresponding geometry equivalent, which can be used in math calculations that aid in the creation of the real entities counterparts. This library provides geometry for 2D and well as 3D calculations. For example, if line passes close to a circle and you want to find the closest point on the line to the cir- cle, there is a geometry function that will achieve this for you. Chapter 5,"ObjectARX’s Geometry Classes” is devoted entirely to a discussion of the geometry classes. ADSRX LIBRARY (FORMERLY ADS) ‘Those of you who have been around AutoCAD for a long time will recognize ADS (AutoCAD Development System). ADS wis a C programming environment, and ele~ ments of this environment have made their way into ObjectARX. In the earlier ADS programming environment, communication between AutoCAD and ADS ‘was by means of AutoLISP through an IPC (interprocess communication). Somebody once told me it was like being underwater in a swimming pool and breathing through a straw (it was slow). You will be happy to know that the ADS environment has been completely incorporated within ObjectARX and is now called ADSRX. There is no more AutoLISP communication or slow IPC mechanism enabling you to use ADS. ‘You will find that you will only use a small portion of ADS compared to what was pre viously used in a pure ADS environment. ADS is primarily used for selecting enti- ties, getting input from the user, and selection sets. ADS functions are easy to recognize—they all begin with ads_. However, most of them have been renamed in ObjectARX 2000. The remaining ADS environment as it pertains to ObjectARX (now called ADSRX) is covered in Chapter 3, “Essential ADSRX (ADS).” ObjectARX Environment and Book Overview OVERVIEW OF THE UPCOMING CHAPTERS Let’s move on and take a look at an overview of the upcoming chapters in the book. CHAPTER 3: ESSENTIAL ADSRX (ADS) ADS, now known as ADSRX, is stil part of ObjectARX and in this chapter we cover ADSRX data types, getting input from the user, selecting entities in AutoCAD, ‘manipulation of selection sets, and data conversion functions. Ifyou are a seasoned ADS programmer, you can take a quick giance at this chapter. Even though we are dealing with ADS here, we are dealing with ADS in terms of ObjectARX applica- tions and not the standalone executables that were created in the past. ‘There are three sample applications in this chapter. Application Ch3_1.arx covers data types, user input, executing AutoCAD commands and the all-important resbuf structure. Applications Ch3_2.arx and Ch3_3.arx cover selection sets. CHAPTER 4: UNDERSTANDING AUTOCAD'S DATABASE AND ENTITY STRUCTURE As you will see later, this very large chapter covers the heart and soul of ObjectARK. Here we cover symbol tables, the Named Object dictionary, AutoCAD object IDs and entity handles, the process of creating objects and appending them to the AutoCAD database, protocols required for opening and closing objects, and AutoCAD's data- base structure. ‘To help cement the concepts provided in this chapter, there are eight sample appli- cations: + Application Ch4_I.arx demonstrates how to add new lyer table records to the layer table. I also demonstrates how to use a yer table iterator + Application Ch4_2.arx demonstrates how to create entities that are added to. ‘the AutoCAD database and also provides a preview of how to use the AcGe geometry classes that aid in the creation of AutoCAD entities. + Application Ch_3.arx demonstrates how to use the ently classes to return intersection points and how to use existing entities to create offset ences. + Application Ch4_4.arc demonstrates how to change the layer for'a group of entices contained ina selection set + Application Ch4_5.arx demonstrates how to create a complex entity using ‘AutoCAD’s new LWPOLYLINE entity. + Application Ch_6.0rx demonstrates how to insert a block in the AutoCAD drawing. 4 ‘+ Application Ch4_7.arx demonstrates how to insert a block that contains attributes in the AutoCAD drawing. + Application Ch4_8.arx demonstrates how to create a block definition that contains attribute definitions. CHAPTER 5: OBJECTARX’S GEOMETRY CLASSES Chapter 5 is devoted entirely to geometry classes and usage of the built-in calculation functionality that the geometry classes offerThe application associated with this chap- tex, Ch5_L.ars, draws a window. There are three possible kinds of window this appli- cation draws: @ rectangular window, an arched window, and an apex type window. This application makes good use of the functiorality provided by the geometry classes. It also sets the stage for most of the applications that follow this chapter. CHAPTER 6: DCL (DIALOG CONTROL LANGUAGE) DIALOGS For those of you who are not up to speed on MFC (Microsoft Foundation Classes), DCL (Dialog Control Language) dialogs ae the dialogs, still with us, that were intro- duced in AutoCAD R12. ‘There are four sample applications associated with this chapter: + Application Ché_I.arx demonstrates the mechanism of loading a DCL dialog. + Application Ch6_2.arx puts a DCL interface on the window-drawing applica- tion of Chapter 5. ‘+ Application Ch6_3.arx builds on the previous application and demonstrates the ‘mechanism of hiding DCL dialogs. + Application Ch6_4.0rx builds on the previous two applications and demon- strates how to nest DCL dialogs. CHAPTER 7: MFC DIALOGS AND OBJECTARX’S UI EXTENSION In this chapter we once again create the window demonstrated in the previous appli- cation. However, we use MFC to create the dialogs and user interface elements. We start out with a discussion of the project setings that are required when you use MFC and ObjectARX. This is followed by a discussion of resources. We look at modal and modeless dialogs. We explore tabbed style dialogs and wizard style dialogs. We also take a look at Window's common controls. A demonstration of creating a resource only DLL with a discussion of localization issues finishes off the chapter. ‘There are seven applications associated with this chapter: ‘+ Application Ch7_I.arx demonstrates how to use resources in ObjectARX as well as how to use a modal dialog Object ARX Environment and Book Overview 43 ‘+ Application Ch7_2.anx demonstrates once again how to use a modal dialog, how to hide a modal dialog and alow the user to select a point from the ‘AutoCAD graphics screen, and also how to nest MFC dialogs. + Application Ch7_3.anx demonstrates how to use modeless dialog in ObjectARX. ‘+ Application Ch7_4.anx demonstrates how to use a tabbed style dialog in ObjectARX. ‘+ Application Ch7_S.anx demonstrates how to use a wizard style dialog in Object RX. + Application Ch7_6.anx demonstrates how to use MFC toolbars and tree con- trols in ObjectARX. ‘+ Application Ch7_7.anx demonstrates how to use a resource-only DLL, which is ‘consideration in localization issues. CHAPTER 8: CUSTOM CLASSES, ENTITIES, AND OBJECTDBX In this chapter we explore storing data in entities using a combination of Extended Entity Data, Extension dictionaries, and the Named Objects dictionary. Then we look at custom objects derived from AeDbObject and storing the data associated with the custom object in the drawing file. We finish this chapter with a discussion of custom entities derived from AeDbEntity. We also discuss UI/DB (User Interface/DataBase) separation issues and ObjectDBX. There are five applications associated with this chapter: + Application Ch8..ane demonstrates storing Extended Entity Data in an entity. + Application Ch8_2.anx demonstrates Extension dictionaries. + Application Ch8_3.an demonstrates storing data in the Named Objects dic- tdonary. + Application Ch8._.anx demonstrates deriving custom objects from ‘AcDbObject and the requirements for saving your custom objects in the AutoCAD drawing file, *+ Application Ch8_5.arx demonstrates creating custom entities derived from ‘AcDbEntity; here we also demonstrate how to define custom behavior for ‘these entities. In this example application, we will create an ObjectDBX appli- cation to define the custom entity and its behavior. The ObjectARX< portion of this application will demonstrate the UI portion of the application. CHAPTER 9: TRANSACTIONS, REACTORS, AND NOTIFICATIONS As the title of this chapter suggests, we discuss transactions, reactors, and notifica- tions. It starts out with a discussion of transactions and the transaction manager. Using these mechanisms, our applications can respond to the user selecting Undo and pressing Esc. This is followed by a discussion of reactors, which respond to events with- in the AutoCAD environment. Reactors come in two types: transient and persistent. ‘Transient reactors are not saved with the AutoCAD database, whereas persistent reac- tors are. Notification is a technique where one reactor can call another reactor in response to change. ‘There are three applications associated ch this chapter: *+ Application Ch9_I.arx demonstrates the use of a transaction manager, which allows this application to respond co the user selecting the Undo option as well as the user pressing ESC. + Application Ch9_2.arx demonstrates the use of reactors and notifications. However, the reactors are transient in nature and will not be saved with the drawing file. *+ Application Ch9_3.ar« i similar to application Ch9_2.arx. However, the reac- tors in this application are persistent and will be saved with the drawing file. ‘Well, that is an overview of what is to come. **% READ THIS *** In AutoCAD/ObjectARX 2000, there is the issue of the Multiple Document Environment (MDE). In the ObjectARX 2000 Developers Guide, this is referred to as the Multiple Document Interface (MDI), which isa term very familiar to Windows and MFC developers. In AutoCAD 2000, we have the ability to open and edit multiple documents (drawings). This has an impact on applications developed for ‘AutoCAD 2000 and applications that are in the process of being ported to AutoCAD 2000. The specific issue is the handling of global data. There is no chapter in this book that is devoted specifically to the Multiple Document Environment. Instead, the issue of global data and MDE/MDI is spread throughout multiple chapters. ‘The sample applications of Chapter 6 represent the start of this process. See the “SDI and Porting Considerations” section in Chapter 6 and subsequent sample applications. ‘These sample applications were originally AutoCAD R14 / ObjectARX 2.02 appli- cations and were ported to AutoCAD/ObjectARX 2000. They are representative of issues faced by developers in porting their applications. The applications in the remaining chapters (Chapters 7, 8, and 9) are all MDI aware and they discuss the issues and the handling of global data in great detail. I strongly suggest that you read Chapter 16, “The Multiple Document Interface” in the ObjectARX Developers Guide. The last section of the chapter discusses an ‘MDI-aware application; you can read through this if you want. Chapters 7, 8 and 9 of this book use a different (and in my opinion vastly superior) mechanism to achieve ObjectARX Environment and Book Overview 45 $$$ this MDI-aware capability The applications for these and some other applications were all created through the ObjectARX 2000 Wizard. The wizard sets up this vastly supe~ sor mechanism for dealing with global data and MDI-aware capability for you. The ObjectARX 2000 Wizard is supplied courtesy of a number of developers from the Developer Consulting Group (DCG) at Autodesk. Visit the ADN (Autodesk Developer Network) Web site to download the latest version of the ObjectARX 2000 ‘Wizard, because itis constantly being updated. Read the supplied documentation that, informs you how to install and use this wizard. Here is where you will find information about ADN and DCG and extensive amounts of useful information: ‘http://www.autodesk.com/adn Essential ADSRX (ADS) HISTORY OF ADS When Autodesk released AutoCAD Release 10 for the OS/2 operating system, it had 4 new programming environment called AutoCAD Development System (ADS). ADS was a C programming environment with libraries and header files provided by ‘Autodesk. ADS became available to DOS AutoCAD ("the rest of us”) users with the release of AutoCAD R11. Prior to that release, most applications were developed with ‘AutoLISP and when you sold your third-party applications, you could not prevent access to your source code. There were applications available that allowed you to encrypt your LISP code—Kelvinator comes to mind. However, within the ever- resourceful community of developers out there, there were also applications that ‘would unprotect your code. I personally never used the Kelvinator program; I decid- ed that if you could figure out my code, you were good enough to write AutoLISP applications anyway. So now that we had ADS, we had a means of compiling our applications, AutoCAD ADS applications came in two varieties at the time: Real mode and Protected mode. Real mode applications were the ones that worked within the DOS 640K-memory limitation and Protected mode applications were the ones that could break the 640K DOS barrier by means of DOS extenders. There were a number of compilers that you could use for ADS development. In the Real mode environment we had Borland’s Turbo C and Microsoft C 5.1, and in the Protected mode environment we had MetaWare High C, Zortech C++, and Watcom C. At the time the Protected ‘mode compilers were expensive. By the time I got through the learning curve, I set- ted on using the Watcom product. a So why do we need to know ADSRX now that AutoCAD has ObjectARX? Even though AutoCAD has ObjectARX, AutoCAD has rolled ADS functionality into ObjectARX and called it ADSRX. You will be happy to know that we dontt need to know everything about ADSRX. Mainly, it is used in three areas: + Getting input from the user. + AutoCAD’ selection sets, + DCL ype Dialog Boxes. See Chapter 6, Dialog Boxes and DCL (Dialog Control Language).” tis a relatively easy process to convert existing ADS applications to Object ARK appli- cations using ADSRX. If you are new to AutoCAD programming, learn the ‘ObjectARX way of doing things. ADS will eventually go away as Object ARX pro- ‘esses in future releases of AutoCAD. In AutoCAD/ObjectARX 2000, the ads XXX names have mostly been replaced with newer names. Where this occurs, I will chow the new and old names as follows: NewObjectARX2000name [OldAds_name] know that many of you have amassed giant amounts of legacy code, and you will be happy to know that ObjectARX 2000 provides a migrtion.b file. Ifyou include this file in your projects, the conversion will be made automatically so you don't have to ‘worry about it. Please note that in the next version of AutoCAD, this migrtion.h file ‘may not be provided. Its intended only as a temporary measure to assist in migrat~ ing code to ObjectARK 2000. So now that we know we have to learn some ADSRX, let’s move on. VARIABLES, TYPES, AND VALUES DEFINED IN ADS ‘There are four header files used by ADS, namely adslikh, ads.b, adscodes.h, and ads- dlg.b, The adsdig-h file is used for DCL (Dialog Control Language) type dialog boxes and is the subject of Chapter 6. ObjectARK 2000 provides a large number of data types (this applies to ObjectARX 2.02 for AutoCAD R14 development also). However, there are some old-style ADS (ADSRX) data types still in use. In appli- cations, when you use #finelude it automatically includes the adt.4 and the adscodes.b files for you. In the ads.b file, there are a number of type definitions as fol- lows: REAL NUMBERS. Real numbers in AutoCAD are double-precision floating point values and are defined in the ads. file as follows: typedef double ads_real; Exsential ADSRX (ADS) ‘When you are using doubles in AutoCAD, use ads_real. POINTS In AutoCAD, points are X,Y,Z locations and are defined to be an array type in the ads. file as follows: typedef ads_real ads_point(3]; ‘An ads_point is an array of three ads_real values. In dealing with point values and cal- culations, you will find the following definitions handy in the ads. file. define X 0 fidefine Y 1 fidefine Z 2 In the following code fragment we have two point variables, pel and pe2. We will show ‘you how to initialize a point value and make one point equal to another point. ads_point ptl, pt2; ptl(X] = ptiLY] = ptl{Z] = 0.0; 1 This is wrong!!! pt2 = ptl; // Remember @ point is an array. The name of and array is the beginning address of the array // so what we have done here is assign the arraye pt2 the same address as ptl // Here is correct method to assign one pointe equal to another point. pt2ex] = ptl(x]: pt2lY] = ptiLy] pt2ez] = ptltz]: For your convenience, AutoCAD provides a copy macro in the ads file, namely acdbPointSet(). In order to use the acdbPointSet() macro in your application, you have to include string. in your application, #include ‘The following code fragment sets the point to equal to the point from as shown: ads_point to, from; ” from[X] = from[Y] = from{Z] = 0.0; acdbPointSet(from, to); // Now ads_point to is equal to ads_point from. ‘TRANSFORMATION MATRICES TYPE Here I just want to talk about the definition ofa transformation matrix type—we will discuss transformation matrices in more detail when we discuss selection sets. A transformation matrix is a 4 x 4 array of real values ads_real, defined in the ads. as follows: typedef ads_real ads_matrix(4][4]; USEFUL VALUES In the adilib file, you will find the following useful definitions provided for your con fidefine TRUE 1 fidefine FALSE 0 dtdefine EOS *\0’ /* String termination character */ Another useful definition is the PAUSE symbol, which is a string that contains a sin- se backslash and is used with the functions acedCommand( and acedCmdQ)(dis- ‘cussed later). In menu macro customization, the backslash symbol represents a pause point, which allows the user to enter information in a command sequence. fdefine PAUSE “\\" /* Pause in command argument Vist */ RESULT BUFFERS AND TYPE CODES While the result buffer (resbutf) is still used in ObjectARX, its use has changed from its original intention. I should explain first what a result buffer is, then how it ‘was used in ADS, and finally how it is used in ObjectARX. The result buffer struc- ture, resbuf, is defined in ads. in conjunction with a union, ads_u_val, that allows for various types used in AutoCAD and ADS. Here is the union: union ads_u_val_( ads_real rreal: ads_real rpoint[3]: short rint; /* Must be declared short, not int */ char *rstring; Tong riname(2]; Jong rlong: struct ads_binary rbinary: hi Here is the resbuf structure: struct resbuf ( struct resbuf *rbnext; /* Linked list pointer */ short restype: union ads_u_val resval; hs Result buffers can be chained together to form a singularly linked lst using the rbnext field of the structure. When rbnext equals NULL, we have come to the end of the linked list. Usually you define two resbut pointers, one pointing to the beginning of the linked list and the other used to traverse the list. The restype field is used to indi- cate what kind of data is stored in the resval field of the union. The restype field con- tains one of a number of predefined “Result type codes” defined in the adscades.b file, as shown in Table 3.1. Code Description RTNONE | No result value RTREAL Real floating-point) value RTPOINT | 2D point (K and ¥; Z ==0.0) RTSHORT _| Short (16-bit) integer RTANG Angle RTSTR String RTENAME | Entityname RTPICKS _| Selection set name RTORINT —_| Orientation RT3DPOINT | 3D point (X,Y, and Z) RTLONG | Long (32-bit) integer RTVOID Void (blank) symbol Table 3.1, Result Type Codes Code Description RTLB List begin (or nested lis) RTLE List end (for nested list) RTDOTE —_| Dot (for dotted pit) RIT “AuroLISP ¢ (tue) RTNIL, ‘AutoLISP nil RTDXFO ‘Group code zero for DXF lists (used only with acutBuildList()) ‘Table 3.1. Result Type Codes (continued) Let’ talk about how result buffer lists were used in ADS. In ADS during the enti- ty access process, we used acedEntSel() to select an entity, and then acdbEntGet() to get at the entity's underlying data. We will discuss acedEntSel() later. The ADS function acdbEntGet() returns a result buffer linked list. We would then traverse the result buffer list to ascertain what kind of entity we were dealing with and what kind of properties it had. result > ~ > “ ° ® ename “circle” sor 10 40 Figure 3.1. Resutt Buffer Lnked List Eventi ADSRX (ADS) 53 $$$ One of the requirements for entity access in ADS (in AutoLISP too for that matter) is that you know and understand DXF codes. As Figure 3.1 shows, we have a result buffer list that is a “Circle” entity type on layer “O” with a center point of 5,5,0 and radius of 2.24086 and flat as a pancake in the World Coordinate System. Let's assume we have two result buffers pointers, rbl and tbl, as shown: resbuf *rbl, *tbl; JI Note that we do not need the struct keyword ing cH // as would be required in C, you can use it ife you want. // struct resbuf *rbl, *tb1; After the acedEntSel() and acdbEntGet() operations, the result buffer *rb! would now contain a result buffer. tbl = rbl; // Traverse buffer *tbl now points to thee beginning of the list ‘We traverse the list in the following manner, moving the *tb1 pointer down the list tbl = tbl->rbnext; ‘Assuming we wanted to assign the ads_real variable rad the radius of the circle as shown in Figure 3.1, we would check the restype field of the result buffer to see if it has a value of 40 (DXF code for a circle radius is 40). ads_real rad; if(tbi->restype == 40) ( J] Me have the radius field of the entity rad = tbl->resval reals ) ‘We will write an application later to draw ard modify this circle, so you will get a feel for result buffers as they were used in ADS. ObjectARX uses result buffers, but to nowhere near the same extent that ADS did, In ADS, result buffers are used for enti- ty access, symbol table access, iteration, and also with acedCommand(, acedCmdQ, and acutBuildList(), all of which will be discussed soon. I do not want to delve too deeply into areas of the result buffer that no longer apply in ObjectARX because ‘ObjectARK has much better mechanisms for dealing with entity access, modification and creation, and symbol table access. Just be aware that you will still be dealing with the result buffers in Object ARK. In ObjectARX, all entities are Classes and each Class thas query and edit functions (methods) that allow us to manipulate entities. With respect to symbol table access, ObjectARX has symbol table iterator classes that allow us to traverse the symbol tables all without using result buffers, which are some of the reasons why I don't want to dig too deeply into the ADS way of doing things. ADSRX FUNCTION RETURN TYPES Before we get into a discussion of acedCommand(), acedCmd(, and acutBuildList), we need to talk about ADSRX library function return types. ADS uses the following return type codes, found in the adscades.b file, to indicate the success failure, or spe cial conditions (such as user cancellation) of the ADS Library functions. Code Description RTNORM User entered a valid value. RTERROR ‘The function call failed. RTCAN User pressed BSC. RTREJ ‘AutoCAD rejected the request as invalid. RTFAIL ‘AutoLISP communication failed. RTKWORD ‘User entered a keyboard or arbitrary text. ‘Table 3.2 Library Function Return Type Codes Not all ADSRX library functions return these status codes; some return values directly. The ones that we are most interested in are RTNORM, RTERROR, RTCAN and RTKWORD. AUTOCAD COMMANDS IN ADSRX acedCommand() [ad:_command()] Let us now turn our attention to acedCommand(). Here is its definition: int acedCommand(int rtype, ...): Eventi ADSRX (ADS) 55 $$ ‘The acedCommand( function has a variable-length argument list. Arguments to acedCommand() are treated as pairs. The fist of each pair identifies the type of the argument that follows, and the second contains the actual data. The final argument in the list must be a single argument whose value is either 0 or RTNONE. Each type identifier argument must equal one of the result type codes defined in adscodes. (such as RTPOINT) listed earlier. The acedCommand() function will execute any ‘AutoCAD command as ifit had been typed in at the keyboard. To draw the circle illustrated in the result buffer list of Figure 3.1 we would execute acedCommand() as follows: acedCommand(RTSTR, “circle”, // AutoCAD command RTSTR, “5,5,0", // center point RTSTR, 2.24086", // radius RTNONE // end of Vist de could have used a lone 0 to end the list instead of the code RTNONE, but I prefer to use RTNONE. If the center of the circle is a point value and the radius a real value, why did I use RTSTR types? If you look at the values entered, they are in double quotes, which are string types exactly as ifT had entered the command at the command prompt. I could also have entered the command as follows: ads_point cp; ads_real rad; cp{X] = cpy] = 5.0; eplZ] = 0.0; // Set to zero Z for 20 point rad = 2.24086; if(acedCommand(RTSTR, “circle”, // AutoCAD command RTPOINT, cp, // center point RTREAL, rad, // radius RTNONE 11 end of Vist ) == RTNORM) ( // acedConmand worked a ) else { // acedCommand failed ) In this code fragment, note how we compare the success or failure of the ADSRX library function acedCommand() against the return code symbol RTNORM. So where do aced@md() and acutBuildList() come in? acedCmd() [ads_emdi)] ‘Well, here is the definition of aced¢maQ: int acedCnd(struct resbuf *rbp); [Notice that this function takes a resbuf pcinter as its argument. Where does that res buf pointer come from? It comes from aeutBuildList(). With the acedCommandQ function, we are essentially building a resbuf structure for the command to execute, except that we know what the arguments are at compile time. With acedCmdQ) we can build a resbuf structure at run time and pass it in to acedCmd0, so let's take a clos~ er look at acutBuildList(). acutBuildList() [ads buildlist()] Here is the definition of acutBulldListQ: struct resbuf *acutBuildlist(int rtype. . ‘The acutBulldListQ) function creates a linked list of result buffers. Notice that acutBuildList() returns a pointer to a resbuf structure or NULL ifit fils. mentioned earlier that not all ADSRX functions return the codes listed previously, and this is one of them. It also takes a variable-length argument list like acedCommand( does. So let's take a look at creating a line entity using acedCmdQ). resbuf *rb; // struct resbuf *rb; // if you prefer if((rb = acutBuildList(RTSTR, “line”, RTSTR, “1,1 RTSTR, 2,2", RTSTR, “*, // Equivalent of pressing thee ENTER key RTNONE)) != NULL) ( if(aced¢md(rb) == RTNORM) ( ) else { } 17 aced¢md worked J/ acedCnd failed Essential ADSRX (ADS) 57 ) else ( ) // acutBuildlist filed if(rb != NULL) i acutRel Rb( rb); acutRelRb() [ads_rlré()] Look at the last part of the code fragment—we have a new function, namely acutReIRb(). This function is release result buffer and has the following definition: ‘int acutRelRb(struct resbuf *rb); ‘The acutReIRbQ function releases the memory allocated to a result buffer or linked list of result buffers. The ADS function acutBuildList() will allocate memory for the list of result buffers; i is up to us to release chat memory. If the function acutReIRb() is successful, it returns RTNORM even though I did not test for that here. There are cccasions when you need justa single result buffer and not a linked list of result buffers. ‘The ADS function acutNewRb() comes in handy for such occasions and we will see an example of its use later acutNewRb() [ads_newrd()] Let's look at the definition of acutNewRb). struct resbuf *acutNewRs(int v); ‘The acutNewRb() function allocates a new result buffer and sets its restype field to v. The acutNewRb() function returns a pointer to the newly allocated result buffer. ‘The v argument should equal one of the result type codes defined in adscodes.h (for example, RTPOINT). Don't forget to call acutRelRb() to release the memory that you allocated with acutNewRb(. Wel, that’s all I'm going to say about result buffers; if you want to know more about them, consult the ADSRX documentation for more information. SENDING INFORMATION BACK TO THE USER ‘Sending information back to the user is accomplished by the following three ADS fanctions: acutPrompt), acutPrint{0, and acedAlert). acutPrompt() [ads prompt] ‘The acutPrompt() function displays a message on the command line and has the fol- lowing definition: int acutPrompt(const char *str); ‘The acutPrompt() function returns RTNORM if successful; otherwise, it returns an ‘error code.’The argument to the function isa constant string pointer, the function can- not change the contents of the string. acutPrompt(“This is a message “): ‘The following would appear at the command prompt: Command: This is a message acutPrintft) fads_pringf)] Here is the definition of acutPrintf(Q): int acutPrintf(const char *format, ...): ‘The acutPrint{( function is identical to the standard C library function printfQ) except that acutPrintf() displays output on the text screen. The acutPrint{Q) function saves its output string in an internal buffer before it sends it to AutoCAD. This buffer con- tains 133 bytes, so messages that are displayed when you call acutPrintf() must not exceed 132 characters. The acutPrintf() functions uses the exact same scan codes as its C counterpart printfQ, as in the following example: ads_real val = 136.35; acutPrintf(“The length of the line is %1f inches® “) val): ‘The result is the following output on the text screen or command prompt: Command: The length of the line if 136.35 inches If acutPrintf() succeeds, it returns RTNORM; otherwise, it returns an error code. acedAlert() [ads_alert()] The acedAlert() function displays a box with an error or warning message; here is its definition: int acedAlert(const char *str); Esential ADSRX (ADS) 59 $$ his function displays an alert box with an error or warning message passed in the str argument. An alert box is a dialog box with a single OK button. If you want the mes- sage to have multiple lines, you can include newline characters (specified by "wn in the string. The string can contain up to 132 characters (the 133rd character is reserved as the end-of-string character). IfacedAlert() succeeds, it returns RTNORM; otherwise, it returns RTERROR. All of the sample applications for this chapter were created through the ObjectARX 2000 Wizard. The ObjectARX 2000 Wizard places all user-defined commands in a Commands.pp file. In this sample application we illustrate various ADSRX data types and show how to use the following ADS functions: acedCommand() acedCmd() acutBui IdList() acutPrintf() ‘We also illustrate how to traverse a result buffer linked lst. There are a number of func- tions in this sample application that we have not talked about yet, but we will talk about these functions later in this chapter. The finctions are listed as follows: acedInitGet() acedGetKword() acedGetDist() acedGetPoint() First we will ist the application and then we will discuss the various elements of the application later. Here is the isting of the CA3_1.cp file: /1 Ch3_1.cpp : Initialization functions 11 CH3_1.cpp // by Charles Mc Auley // “Programming AutoCAD 2000 with ObjectARX” // This application defined a command ‘RBCIRC’. which // stands for Result Buffer CIRCle. Here we create a // a circle using result buffer technology andé then using // the same result buffer, we traverse thee result buffer J/ and extract the newly created entitys information from a 11 resutl buffer. // This application represents the old stylee methods for // creating AutoCAD entities and this method hase been // superceded by ObjectARK. u TLL TLL LLL Wil include “Stdafx. iFinclude “StdArx.h" #include “resource.h” HINSTANCE _hd11Instance =NULL + // This command registers an ARK command. void AddCommand(const char* cmdGroup, const char*# cmdInt, const char* cndLoc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idlocal = -1); // NOTE: DO NOT edit the following lines. 7 {APX_ARX_MSG void InitApplication(); void UnloadApplication(); 11) )AFX_ARX_MSG JJ NOTE: DO NOT edit the following lines. 7 ((AFX_ARX_ADDIN_FUNCS 11) )AFX_ARX_ADDIN_FUNCS HATTA LLL MALT // DLL Entry Point extern “C” BOOL WINAPI DI1Main(HENSTANCE hinstance, DWORDS dwReason, LPVOID /*1pReserved*/) ( if (dwReason = DLL_PROCESS_ATTACH) { _hd1 Instance = hinstance; } else if (dwReason — DLL_PROCESS_DETACH) { Euentil ADSRX (ADS) 61 ) return TRUE; —// ok ) MATT LLL MATL 11 ObjectARX EntryPoint extern “C” AcRx: :AppRetCode acrxEntryPoint(ACRx: :AppMsgCode msg, void* pkt) ( switch (msg) { case AcRx::kInitAppsg: 7/ Comment out the following line if your 71 application should be locked into memory acrxDynamicLinker->unlockAppl ication(pkt);, acrxDynamicLinker->registerAppMDIAware(pkt) ; Initapplication(); break: case AcRx::kUn1oadAppiisg: UnloadApplication(): break: ) return ACRx::kRetOK; // Init this application. Register your 7/ commands, reactors... void InitApplication() ( 17 NOTE: DO NOT edit the following lines. TI CCAFX_ARX_INIT AddCommand(*CH3_APPS’ ACRX_CMD_MODAL, rbcirc): T/V)AFX_ARX_INIT “RBCIRC™, “RBCIRC",& // TODO: add your initialization functions acutPrintf(“Enter \"RBCIRC\” at the commandd prompt to execute. \n"); ) J/ Unload this application. Unregister all objects JJ registered in Initkpplication. void UnloadApplication() fl // NOTE: D0 NOT edit the following lines. 7 (APX_ARK_EXIT. acedRegCnds->removeGroup(“CH3_APPS”) ; TD) YAPX_ARX_EXIT // TODO: clean up your application acutPrintf(“ks&s", “Goodbye\n”, “Removingd! command group \"RBCIRC\"\n"): ) // This functions registers an ARX command. // It can be used to read the localized command name // from a string table stored in the resources. void AddCommand(const char* cmdGroup, const char*& cmdint, const char* cndLoc, const int cmdflags, const AcRxFunctionPtre cmdProc, const int idlocal) ( char cmdLocRes(65]; // If idlocal is no -1, it’s treated as an ID for // a string stored in the resources. if Cidlocal I= -1) [ // Load strings from the string table and& register the command. ::LoadString(_hdilInstance, idLocal, cmdLocRes,& 64); acedRegCmds->addCommand(cmdGroup, cmdInt,& cmdLocRes, cmdFlags, emdProc): ) else // idlocal is -1, so the ‘hard coded’ // Vocalized function name is used. acedRegCmds->addCommand(cmdGroup, cmdint,& cmdloc, cmdFlags, cmdProc); ) // User defined function called from Ewential ADSRX (ADS) 63 J rbcirc() see Ch_3Commands.cpp ‘int printEntInfo(ads_nane ent) { struct resbuf *rbént; struct resbuf *rbTrav; rbEnt = acdbEntGet(ent); if (irbent) { acutPrintf(“\nFailed to get entities resulte buffer linked list. “); return 0; ) rbTrav = rbEnt; while(rbTrav) fi switch(rbTrav->restype) ( case -1 : // Ename acutPrintf(“The entity name is “, rbTrav->resval .rstring): break; case 0: // Entity type acutPrintf(“\nThe entity is a %s on “.o rbTrav->resval .rstring); break; case 8: // Layer acutPrintf(“lay2r \"Xs' rbTrav->resval.rstring); break; case 10 : // Center point acutPrintf("\nThe center point is (%.21f.¢ B.21f, B.21f). rbTrav->resval .rpointlX], rbTrav->resval .rpoint LY], rbTrav->resval .rpointLZ]); break: case 40 : // Radius acutPrintf("\nThe radius = %.51f “,& rbTrav->resval .rreal); break; case 210 : // Plane orientation acutPrintf("\nThe planar orientation is? (B21, B.21F, B.21f). rbTrav->resval .rpointLX]., rbTrav->resval .rpointLY]., rbTrav->resval .rpoint[Z])s break; default + break: 1/1 switch rbTrav = rbTrav->rbnext; )// white if(rbent) ( acutReTRb(rbEnt) ; ) return 1; ) ‘Here is the listing for the user-defined command RBCIRG in C3_1Commands. TATA TLL 17 ObjectARX defined commands finclude “StdAfx.h” #include “StdArx.h” // This is command ‘RBCIRC* void rbcirc() ( ads_point cp = (5.0, 5.0, 0.0}; ads_real rad = 2.24086; Esential ADSRX (ADS) 65 ———— ads_name circEnt; int rc = RTNORM; char kw(4]; struct resbuf *rbcirc: acedCommand(RTSTR, “CIRCLE”, RTPOINT, cp, RTREAL, rad, RTNONE) : re = acdbEntLast(circent); if(re == RTNORM) fi if(!printEntInfo(ci-cEnt)) ( acutPrintf(“\nFailed to print entity info. return; ) ) else { acutPrintf(“\nFailed to retrieve last entity.@ : return; ) while(rc == RTNORM) { acedInitGet(NULL, “Yes No”); rc = acedGetKword(“\nDraw another circled LYes/Nol: “, kw); switch(re) ( case RTERROR: acutPrintf("\nA1 input error occured -# aborting. “); return; break: case RICAN: acutPrintf(“\nUser pressed the [ESC] key. “)s return; break; case RTNONE: strepy(kw, “Yes"); rc = RTNORM: brea ) // switch if(stremp(kw, “No") = 0) "he = ernones tise ‘ acedInitGet(RSG_NONULL, NULL); re = acedGetPoirt(NULL, “\nPick circle centers point: “, cp): if(rc 1= RTNORM) t acutPrintf("\nError picking circle centere point: “); break; ) acedInitGet(RSG_NONULL + RSG_NOZERO +9 RSG_NONEG, NULL); rc = acedGetDist(cp, “\nCircle radius: Brad); if(re = RTNORM: ( acutPrintf("\rError entering circle radius:¢ break; ) rbcirc = acutBuildList(RTSTR, “CIRCLE”, RTPOINT, cp, RTREAL, rad, RTNONE) ; if(irbeire) i acutPrintf(“\nError with acutBuildlist():¢ Essential ADSRX (ADS) 67 break: ) re = acedCmd(rbcirc); if(re I= RTNORM) { acutPrintf("\nError creating circle with acedtmd(): “); acutReIRb(rbcirc); break; } re = acdbEntLast(circEnt); if(re [= RTNORM) { acutPrintf(“\nFailed to retrieve laste D3 acutRelRb(rbcire); break; ) entity. if(!printEntInfo(circent)) ( acutPrintf(“\nFailed to print laste circle’s data. “); ) acutRelRb(rbcirc); ) // else Y// while } ELEMENTS OF SAMPLE APPLICATION CH3_1! In this application we have the required entry point function aerxEntryPoint(). ‘The aerxEntryPoint() function calls the user-defined function initpplication() during the AutoCAD application startup process if the application name is part of the acad.rx file) or when the user loads the application using the ARX command. In cither case the application will receive AcRoezklnitAppMsg. The initApplication() finc- tion calls the user-defined AddCommand(), which is responsible for calling ‘acedRegCmds->addCommand(). This adds our command RBCIRC (Result Buffer ‘CIRCic) to the command stack. When the AutoCAD session is terminated or the user ‘manually unloads the application using the ARX command, the application receives an Aenc:kUnloadAppMsg, which in turn calls the unloadApplication( function. The unloadApplication() function removes our command group from the command stack using the call to acedRegCmds->removeGroup() function. If you are a little hazy as to what is going on here, please refer to Chapter 1, “Getting Started,” where wwe discuss in detail the mechanism of starting and running ARX applications. We've added two user-defined functions, rbeire() and printEntInfo(). The rbeire() function is responsible for drawing the circle that was outlined in the result buffer of Figure 3.1, shown carlier. The printEntInfo() function is responsible for traversing the result buffer and printing information about the circle drawn. In the function rbcire(), the circle is created with a call to acedCommand() as shown: acedCommand(RTSTR, “CIRCLE”, RTPOINT, cp, RTREAL, rad! RTNONE) ; [Notice that we declare and initialize the ads_point variable ep in a single call as shown: ads_point cp = (5.0, 5.0, 0.0); ‘We could have declared and initialized ep like this as shown: ads_point cp; cpLX] = cplY] = 5.0; cpLZ] = 0.0; ‘The next function, aedbEntLast(), which we will discuss in more detail later, grabs the last entity drawn by AutoCAD and stores its ads_name in circEnt. We use circEint in the call to printEntinfo(), which traverses the result buffer list. In the while loop of the rbeire() function, we are asking the user if he would like to draw anoth- cer circle entity. There are a number of user input functions that we have not talked about yet; that will be remedied in the next session. Consider this a prelude to what's coming. What I want to draw your attention to is where we call acutBuildList() to build our result buffer list, which in turn we pass to the acedCmdQ) function, which is responsible for actually drawing the circle entity. ‘The printEntinfo( is where we traverse the result buffer linked list. This function expects an ads_name argument. The aedbEntGet( function will populate the result buffer rbEne with the properties ofthe last entity, which is passed in as an argument. Result buffers have a field rbnext, which points to a result buffer. This isthe field that allows us to build up a linked list of result buffers. We also have a rbTrav result buffer, which will be used to traverse the linked list; rb Trav is set to point to the beginning of the linked list in the call to rbTrav = rbEnt. Notice that at the end of the while loop Essential ADSRX (ADS) 69 $$$. ‘we have a call to rbTrav = rbTrav->rbnext This moves us from one result buffer to the next—as long as there is a result buffer o point to, the while loop will be true. In the switch statement we examine the restype field. Here is where you need to know your DXF codes. During the debugging of the application, place a breakpoint before the end of the while statement at the call to rbTrav = rbTrav->rbnext; and look at the value of the rbEnt result buffer in the variables window. Make sure the variables ‘window is on the Locals tab, as shown in Figure 3.2: ° rbivay © rhTrav-orbnext, > white it (2neat) ade_zelzb(xbEnt): Figure 3.2 Result Buffer in Debug Mode GETTING INFORMATION FROM THE USER ‘Most applications require input from the user to communicate with ADS, and ADS provides a rich assortment of user input functions and techniques. If you have used the AutoLISP input functions, you will find the ADS equivalents very similar. All ADS user input functions take the form acedGetXXX(), where XOX is the kind of data being input. Some of the input types are obvious; we will list all the acedGetXXX() first and go through them one by one. Here is the list of acedGetXXX( functions, in the order in which we will discus them. acedGetInt() acedGetReal() acedGetString() acedGetDist() acedGetPoint() acedGetCorner() acedGetAngle() acedGetOrient() acedInitGet() acedGetKword() acedGet Input () acedGetFileD() ‘What’ this acedinieGet() doing in the middle of the acedGet0OX( functions? The acedinitGet() function is not a typo on my part—it controls the user input for the acedGetXXX( functions and will be explained later. So let's start with an easy one, acedGetint(). acedGetint() fads_getint()] ‘The acedGetint() function pauses for user input of an integer; here is its definition: int acedGetInt(const char *prompt, int *result); The acedGetint( function pauses for user input of an integer and sets result to the selected value. The prompt argument specifies a string that acedGetint() displays before it pauses. The prompt is optional; if you dont use it, pass a NULL pointer instead ofa string value. The AutoCAD user can enter any valid (short) integer in the range of -32,768 to +32,767. ‘The acedGetint() function returns one of the following: RTNORM if it succeeds, RTERROR if't fils, or RTCAN if the user cancels the request (by pressing ESC). This is true of most of the acedGetXXX0 functions. if(acedGetInt(“\nHow many parts in the assembly? “,¢ fagty) == RTNORM) { // do something with qty ) else ( Euential ADSRX (ADS) 7 sr // acedGetInt did not work!! } I want to call your attention to the fact that we pass in the address of aty in acedGetint() because it expects a pointer to (or the address of) an integer and NOT the integer itself This is a common mistake made by beginning ADS pro- ‘grammers (not that I ever made that mistake!) and this technique is common to a number of the acedGetXX0 functions, so be aware—read the fine print! acedGetReal() [ads_getreal()] The acedGetReal() function is almost identical to the acedGetInt() function, except that che result argument is an ads_real type; here is its definition: int acedGetReal(const char *prompt, ads_real@ *result); Note that the result argument is a pointer to an ads_real type. acedGetString() fads_getString()] ‘The acedGetStringQ function pauses for wer input of a string; here is its definition: int acedGetString(int cronly, char *prompt, chard *result)s This function sets result to the string that the user enters. The cronly argument spec- ifes whether the string can contain spaces. The prompt argument specifies a string that acedGetString() displays before it pauses. The prompt is optional; if you don't use it, pass a null pointer instead of a string value. The result argument must point to 4 memory area large enough to contain the string. The acedGetString() function returns up to 132 characters, so the result string does not need to be longer than 133 characters. Ifthe cronly argument is true (nonzero), the string can contain blank spaces and the user must terminate it by pressing ENTER. If eronly is false (0) either enter ing a blank or pressing ENTER terminates the string. The acedGetString() function returns one of the following: RTNORM if it succeeds, RTERROR if it fails, or RTCAN if the user cancels the request (by pressing ESC). Here is a sample code fragment without spaces: char PartNum[g0]; int rc = acedGetString(0, “Enter part number: “,¢ PartNum) ; 1 No spaces allowed if(re t= RTNORM || *ParsName —= *\0") i acutPrintf (“Invalid part number ‘&s*.\n",¢ PartNum) ; return 0; ) Here is a code fragment with spaces allowed. char PartDes{80]: int re = acedGetString(1, “Enter part description:% *, partDes); // spaces allowed if(re t= RTNORM || *PertDes — ‘\0’) { acutPrintf(“Invalid Part Description ‘%s*.\n", PartDes); return 0; ) acedGetDist() (ads_getdist()] ‘The acedGetDist() function pauses for user input of a linear distance; here is its def- inition: int acedGetDist(const ads_point pt, const chare ‘prompt, ads_real *result); ‘This function pauses for user input of a linear distance and sets result to the value of the selected distance. The pt argument specifies a base point in the current UCS. The prompt argument specifies a string that acedGetDist() displays before it pauses. Both pt and prompt are optional; if you donit use them, passa null pointer instead of a point or string value. The user can specify the distance by entering a value at the keyboard. The user can also set the distance by specifying two locations on the graphics screen. ‘AutoCAD draws a rubber-band line from the first point to the current crosshairs posi~ tion to help the user visualize the distance. If the pt argument is not null, AutoCAD uses this value asthe frst of the two points. By default, acedGetDist() treats pt and result as three-dimensional points. A prior call to acedinitGet() (discussed later) can force pt to be two-dimensional, ensuring that acedGetDist() returns result as a 2D planar distance. The acedGetDist() function returns one of the following: RTNORM if it succeeds, RTERROR ifit fils, or RTCAN if the user cancels the request (by pressing ESC). Here is a sample code fragment: ads_point cen; ceniX] = cenl¥] = 5. cen(Z] 0.0; Ewential ADSRX (ADS) 73 eee double rad; ro = acedGetDist(cen, “Radius: “, &rad); // Uses cen as the first point if (re t= RTNORM) { } return; acedGetPoint() ads getpoint()] ‘The acedGetPoint() function pauses for user input of a point; her i its definition: int acedGetPoint(const ads_point pt, const charg *prompt, ads_point result); ‘This function pauses for user input ofa point and sets result to the value of the select- ed point. The pt argument specifies a relasve base point in the current UCS. The Prompt argument specifies a string that acedGetPoint() displays before it pauses. Both and prompt are optional; if you dont use chem, passa null pointer instead of a point or string value. The AutoCAD user can specify the point by entering a coordinate value at the keyboard. The acedGetPoint() treats pt and result as three-dimensional points. The user can specify the point also by specifying a location on the graphics screen. If the pt argument is not NULL, AutoCAD draws a rubber-band line from pt to the current crosshairs position. The coordinates of the point stored in result are ‘expressed in terms of the current UCS. The acedGetPoint() function returns one of the following: RTNORM if it succeeds, RTERROR if it fails, or RTCAN if the user can- cels the request (by pressing the E5C). Here is a sample code fragment: int res ads_point pt; if((rc = acedGetPoint(NULL, “Pick start point: “,¢ pt)) —= RTNORM) t ) // Do something with pt acedGetCorner() [ads_geteorner()] ‘The acedGetCorner() pauses for user input of the opposite comer of a rectangular box drawn on the graphics screen. int acedGetCorner(const ads_point pt, const chare *prompt, ads_point result); ” ‘This function pauses for user input of the opposite corner of a rectangle, and sets result to the value of the selected point. The pe argument specifies the base point of the rec~ tangle in the current UCS; this argument is NOT OPTIONAL for acedGetCorner(). The prompt argument specifies a string that acedGetCorner() displays before it pauses. The prompt is optional; if you don't use it, pass @ null point- er instead of a string value. The AutoCAD user can specify the comer by entering a point from the keyboard; acedGetCorner() treats pt as a three-dimensional point. The user can specify the comer also by specifying a location on the graphics screen. ‘AutoCAD draws a dynamically sized rectangle from pt to the current crosshairs posi tion to help the user visualize the location ofthe second comer. The rectangle is drawn in the XY plane of the current UCS. The acedGetCorner() function returns one of the following: RTNORM ifit succeeds, RTERROR ifit fils, or RTCAN if the user can- cels the request (by pressing ESC). Here is one difficult aspect of the function acedGetCorner(): if the user is viewing an object from a 3D point of view, the acedGetCorner() function always draws its dynamic rectangle parallel to the screen face. acedGetAngle() [ads_getangle()] acedGetOrient() [ads getorient()] “The acedGetAngle() prompts for user input of an angle, taking into account the cur- rent value of the ANGBASE system setting. int acedGetAngle(const ads_point pt, const char *prompt, ads_real *result); int acedGetOrient(const ads_point pt, const chard *prompt, ads_real *result); ‘The acedGetAngle() function pauses for user input of an angle and sets result to the value of the selected angle. The pt argument specifies an angle base point in the cur- rent (two-dimensional) UCS. The prompt argument specifies a string that acedGetAngle() displays before it pauses. Both pt and prompt are optional; if you don't use them, pass a null pointer instead of a point or string value. “The AutoCAD user can specify the angle by entering a number at the keyboard. The user can also set the angle by specifying two 2D locations on the graphics screen. AutoCAD draws a rubber-band line from the first point to the current crosshairs posi- tion to help the user visualize the angle. Ifthe pt argument is not null, AutoCAD uses this value as the first of the two points. The angle is measured in the XY plane of the current UCS (acedGetAngle() ignores the Z field of pt). The acedGetAngle() function always sets result to a value expressed in radians. ‘The acedGetAngle() function always takes into account the current value of the ANGBASE system variable. For acedGetOrient(, the 0 degree angle is always to the Essential ADSRX (ADS) right: “east” or “3 o'clock.” Both acedGetAngle() and acedGetOrient() return a (real) angle value in radians measured counterclockwise from a base (0) angle. If you always need 0 degrees to point to the right (“east”), use acedGetOrient(). The acedGetAngle(), acedGetOrient() functions return one of the following: RTNORM ifit succeeds, RTERROR if it fils, or RTCAN if the user cancels the request (by press- ing Esc). acedInitGet() [ads_initget()] ‘The acedinitGet( function controls user input for the next acedGetXXX( function. For example, in the acedGetint() function, the user could input a negative number, O or press ENTER and not have any input at all, as illustrated in our code sample frag ments. Now suppose that in our application we require input and negative numbers and 0 are not allowed. How do we control this? A call to acedInitGet() with the appropriate settings prior to a call to the acedGetXXX( function would allow us to set up these requirements, Here is the definition of acedinitGet(): int acedInitGet(int val, const char *kw1): The val argument enables or disables certain input values and input styles. We accomplish this by setting control bits on or off (as specified in the val argument). The combination of the control bits set controls the input to the next and only the next acedGetXXX() function and is discarded immediately afterward. The application doesn't have to call acedinitGet() a second time to clear any special conditions. The optional lew! (keyword list) argument specifies the keywords that the user input fanction accepts. We will discuss keywords alter later. f you dont pass any keywords, lew should be a null or empty string. If acedinieGet() sucoceds, it returns RTNORM, otherwise, it returns RTERROR. Let's take a look at the codes specified by the val argu- ‘ment that control input to the acedGetXXX() functions. Input options as set by the acedinitGet() function are shown in Table 3.3. Tewas stated earlier that we wanted to use the acedGetint() function with the fol- lowing conditions: we must have an input value, and O and negative numbers are not acceptable. This is achieved as shown in the following example code fragment: int val, qty: acedInitGet(RSG_NONULL + RSG_NOZERO + RSG_NONEG, NULL): acedGetInt(“How many items to draw: “, &val) acedGetInt(“How many parts in each item: “, aqty); To get a combination of the codes to work, add them together, as previously shown in the code fragment. Ifthe user tries to press ENTER of input 0 or a negative num= % % Bit Bit don Code Description Bit 0 1 RSG_NONULL | Disallow null input Bit I 2 RSG_NOZERO | Disallow zero values Bitz | 4 RSG_NONEG _ | Disallow negative values Bit 3 8 RSG_NOLIM | Donotcheck drawing limits, even if LIMCHECK ison Bit 4 16 Not used Bits | 32 RSG_DASH —_| Use dashed lines when drawing subber-band line or box. Bité | 64 RSG_2D ‘Ignore Z coordinate of 3D points (acedGetDist() only) Bit7 128 | RSG_OTHER | Allow arbitrary input, whatever the user types ‘Table 3.3 input Options for acedInitGet() ber, AutoCAD will complain and keep on prompting until val gets a positive integer. In the next call to acedGetint() the user can simply press ENTER and AutoCAD will continue on its merry way; the variable qty will not have a value. Remember, as shown earlier in the code fragment, the acedInitGetQ) function works only for the first call to acedGetint() and not the second acedGetint() call. If you wanted to apply user input control to the second acedGetint(), you would have to call acedinitGet() again with the appropriate codes just prior to calling the second acedGetint() func tion. All of the possible codes listed in the table do not apply to all of the acedGetXXXQ_ fanctions, as shown in the following table. For example, it makes no sense to apply an IRSG_NONEG value to acedGetPoint(). However, it does make perfect sense to apply RSG_NONULL to acedGetPoint(). Table 3.4 shows which codes apply to which acedGetXXX( functions. sential ADSRX (ADS) 77 $$$ 7 Sela Ble Gly SE 3 al, oll f ae 209 [od fe8 508 aad/dseres $i golf gif glo gig gia else! 3 gy g) ge 25 2)" “eg acedGetint() x x x x acedGetReal() x x x acedGetDist() x x | x x x | x acedGetAngleQ | X x x x acedGetOrient() x x x x acedGetPoint() x x x x acedGetCorner() x x x x acedGetKword() x x ‘Table 3.4 User Input Functions and Applicable Control Bits acedInitGet() [ads_initget()] acedGetKword() [ads_getword()] ‘The second argument to acedInitGet( is a keyword lst. Sole’ talk about what a key word list is and how this ties into acedGetKword(). A keyword list is set up through acedinitGet(). Let's assume that we are drawing one of three objects: a ball, a pyra~ mid, and a cube. We can use acedinieGet() to set up a keyword list as follows: int re: char kwl20]; acedInitGet(RSG_NONULL, “Ball Pyramid Cube”) rc = acedGetKword(“Draw a [Ball/Pyramid/Cube]: “,% kw); In this case, “Ball Pyramid Cube” is the keyword ist that is set up prior to the call to acedGetKword(). Notice the square brackets around the prompt that appears on the command line. If you move the mouse into the graphics area of the screen of the active drawing and right-click, the items that appear inside the square brackets also appear as options on a context shortcut menu. So there! Now you know how to add options for right-click menus. Note that the command prompt must be active at the same time. Here is the definition of the acedGetKword() function: int acedGetkword(const char *prompt, char *result); Let's assume that the user wants to draw a ball. At the command prompt in AutoCAD the user selects the Ball option by pressing the letter ‘B’. This could be entered as upper- case of lowercase, and you could also enter the whole word if you wanted to. The result of this action is that the kw string buffer now contains the string “Ball\0”, one of the keyword lst strings. Its the uppercase letter of each word in the keyword string list that defines how much the user needs to type in order for acedGetKword() to accept the input and place the keyword ir the buffer. What if the user can choose to : “, en, pt); switch (re) { case RICAN: case RTNONE: acutPrintf (“Nothing selected. \ return 1; case RTERROR: acutPrintf(“Error in acedEntSel.\n”); return 0; case RTKWORD: if (acedGet Input (kw) != RTNORM) { acutPrintf(“Error in acedGetInput.\n”); return 0; } if (stremp(kw, “Model”) == 0) fi ) else if (strcmp(kw, “Paper”) == 0) { } else ( /1 Do something with Model Space // Do something with Paper Space rc = acedGetString(0, “Enter block® name: “, bName); if (reteRTNORM || *bName = *\0") ( acutFrintf(“Invalid block nameg “ks".\n", bName); return 0; ) ) break; case RTNORM: // Do something with the picked blocks enitity here u" ) /1 switch ‘We have not talked about the data type ads_name or the function acedEntSel() yet; ‘we will later. Let's look at the call to acedinitGetQ first. acedinitGet(0, “Model Paper Name”); ‘The keyword list is “Model Paper Name” so the user, at a minimum, can type in at the keyboard an ‘mi’, ‘p” ot ‘ni’ to match one of the keywords in the keyword list. Notice also that we have 0 defined for the bit code. This means that the user could press ENTER with no input; this is perfectly acceptable. The acedEntSel() function allows the user to select an entity. As you can see, there are a number of input choices here. If the user presses ENTER, the return code re has a value of RTNONE, or if the "user presses ESC, re will have a value of RTCAN. Either case, RTNONE or RTCAN, is captured in the first part of the switeh statement. The case RTERROR will be returned if there was an error in the acedEntSel() function. Before we look at the RTK- WORD case, let's look at the RTNORM case. The return code re will have a value of RTNORM if the user selected an entity, and whatever processing that is required will be carried out on the entity as a result in that part of the switch statement. The return code re will have a value of RTKWORD if the user has selected one of the keywords in the keyword list that was established by acedInitGet(.. This is where the acedGetinput() function comes into play: which keyword was selected? Note that the return value of acedGetinput() is compared against RTNORM; if it’s not equal to RTNORM, an exror occurred. Assuming that no error occurred with acedGetinput(), wwe check the value of the keyword in the buffer kw and take the appropriate action. So, a situation where a user can make use of a keyword list or other input option is where acedGetInput() comes into play. If acedGetinput() succeeds, it returns RTNORM; otherwise, it returns RTERROR. Essential ADSRX (ADS) acedGetFileD( fads_getfled()] The acedGetFiteDQ function is not part of the acedGetXXX0 set of functions and is not controlled by the acedinitGet() function, but because of ts acedGet prefix, we will talk about it anyway. The acedGetFileDQ function prompts the user fora file name with the standard AutoCAD file dialog box. Here is the definition of acedGetFileDQ: int acedGetFileD(const char *title, const chare ‘default, const char *ext, int flags. struct resbuf *result): The title argument specifies the label of the entire dialog box, default specifies the default file name (which can be NULL), and ext is the default file name extension (if passed as NULL, ext defaults to *). Beginning with AutoCAD Release 13, the ext argument accepts multiple file name extensions separated by semicolons. If acedGetFileDQ) gets a file name successfully, it returns RTNORM and sets result to contain the file name and path name if other than the current directory; otherwise, it returns RTERROR. Here is a sample code fragment: const char* title = “Select an ARK file”; const char* default = “/r2000/support/”: struct resbuf *fname; fname = acutNewRb(RTSTR); if(acedGetFileD(title. default, “ARK, 0, frames == RTNORM) t // Do something with the file name ing fname~>resval.rstring } // Don't forget acutRelRb( fname) ; The sample code fragment would produce the dialog box shown in Figure 3.3. Figure 3.3. Select an ARX File Dialog Box: acedGetFileD() ‘The acedGetFileDQ function allocates memory for the pathname string. Your pro- gram is responsible for calling acutRelRb( in the result buffer. The documentation for ADSRX mentions the Type it button. Please note that in AutoCAD R14 and ‘AutoCAD 2000 there is no Type it buttcn in the dialog box; the dialog box behaves exactly like an “Explorer” style dialog box. The Object ARX documentation is also in error when it discusses the Type it button with regard to AutoCAD R14 and ‘AutoCAD 2000. The AutoLISP documentation, however, is correct for AutoCAD 2000. Also in AutoCAD R14/2000, the system variable FILEDIA has no effect on acedGetFileD(. If FILEDIA is set to 0 you will still get a dialog as a result of a call to acedGetFileDQ. Table 3.5 shows the meaning of the flags bits (see the ADSRX documentation for more information on their meaning). If you want to set more than one bit flag, simply add their values together. Exsntial ADSRX (ADS) $$$ __~__ Value Description Indicates a request for a new fileto be created Disables the Type it button (not in AutoCAD 14/2000) Enables the user to enter an arbitrary file-name extension Performs a library search for the file name entered elale 16 Interprets the second argument to acedGetFileD() as a path or directory name, not a file name 32 Inhibits display ofthe alert box, which wars that a file exists when a new file of the same name is opened ‘Table 3.5 Volues for the System Variable FILEDIA SELECTING ENTITIES IN AUTOCAD ads_name [Still the same in AutoCAD 2000} acdbNameSet() fads_name_set()] acdbNameEqual() /ads_name_egual()] acdbNameClear() [ads_name_clear()] acdbNameNil() (uils_narme_nil()] In ADS, ADSRX, or ObjectARX, the method of selecting entities has not changed, but some of the names of the functions have changed in ObjectARX 2000 (other ‘names have not changed; this is largely true of data types and DCL dialogs discussed in Chapter 6). We will introduce a new data type: ads_name. This data type is filled in as a result of the successful selection of an entity. Usually entities are select- ced so that the user can get or modify the entity property. This is where old-style ADS and modern API ObjectARX are worlds apart. Let’s look at the definition of ads_name before we discuss the difference between ADS and ObjectARX in terms of getting at an entity's underlying data. typedef long ads_name(2]; ‘The ads_name data type is an array of two longs, and therefore we cannot use the assignment operator to make one ads_name object equal to another ads_name object. This situation is similar to the ads_point data type we saw earlier. As with the ads_point data type, AutoCAD provides 1 macro for making one ads_name equal to another ads_name object, namely acdbNameSet(). Well, you will be happy to know that ADS/ADSRX provides a number of mactos for dealing with ads_name objects. To make one ads_name object equal to another ads_name object, use the acdbNameSet() macro. To see if two ads_name objects are equal to each other, use the acdbNameEqual() macro. To assign a null value to an ads_name object, use the acdbNameClear() macro. To test if an ads_name object has a valid value, use the acdbNameNil() macro. Here is a sample code fragment: ads_name old, new: // assume that old has a valid value // to make new and old equal to each other if (acdbNameEqual (old, new)) // clear the old ads_name object acdbNameClear(old); // check to see if old has a value if (acdbNameNi1 (old) ) acedEntSel() (ads_entsel()] acdbEntGet() [ads_entget()] acdbEntMod() [ads_entmod()] acdbEntUpd() fads_entupdl)] Keep in mind that a selection set is also an ads_name object (I know it can be more than a little confusing). We will talk about selection sets soon. I mentioned earlier that there is a world of difference between the ways ADS and ObjectARX get and mod- ify entity data. We will talk about the ObjectARX method in later chapters. In ADSRX, you use the following function to get an entity’s data, acdbEntGet(), and if you want to modify and update the entity, you use acdbEntMod() and acdbEntUpd(). Here are the definitions for these functions: int acedEntSel(const char *str, ads_name entres.& ads_point ptres); struct resbuf *acdbEntGet(const ads_name ent int acdbEntMod(const struct resbuf *ent); Esential ADSRX (ADS) 0S $$$. int acdbEntUpd(const ads_name ent); With acedEntSel(), the selected entity will be associated with the ads_name object. I want to talk about acdbEntGet0, acdbEntMod(, and acdbEntUpd() before I talkin detail about the acedEntSel0 function. The aedbEntGet() function returns a single linked list of result buffers. You can traverse and examine the result buffer linked list using the rbnext field of the resbuf object. Ifrb is a resbuf result buffer, the syntax is as follows: rb = rb->rbnext; You can also change the values of the particular result buffer as you go along, When you are finally finished, you call acdbEntMod() to modify the entity’ internal data. ‘As you can see, this function expects a resbuf object. Ifyou are dealing with a com- plex object such as a block or polyline, you can use aedbEntUpd() to see the effect of the changes made by the various calls to the aedbEntModQ function. Essentially, when using ADSRX, you deal with linked lists of result buffers. The ObjectARX method is radically different and orders of great magnitude better. For those of you with a great deal of legacy data, these functions still work in ObjectARX thanks to ADSRX (the folks at Autodesk put ADS inside Object ARX). Because this book deals with ObjectARX, that’s all I'm going to say about acdbEntGet(), acdbEntMod(), and acdbEntUpd(. ‘Now back to acedEntSel() because that function is still widely used in ObjectARX. Once again, here is the definition of acedEntSel(): int acedEntSel(const char *str, ads_name entres, ads_point ptres); ‘The acedEntSel() function pauses for user input and returns both an entity name in centres and the point that is used to select the entity in ptres. The str argument spec- ifie a string that acedEntSel0 displays before it pauses. The str argument is option al; if t is a null pointer, AutoCAD displays the "Select objects:|"default prompt. ‘When the user responds to acedEntSel() by specifying a complex entity, it returns the polyline or block header. The acedEntSel( function returns RTNORM ifit succeeds, RTERROR ifit fails, or RTCAN if the user cancels the request (by pressing ESC). The acedEntSel() function can be used with the acedInitGet() function, as shown earli- er in a sample code fragment. How is acedEntSel0) used in ObjectARX® Allright, we are about to get ahead of our- selves here, so let's go! AutoCAD treats the drawing just like a database. Every enti- ty in each open AutoCAD drawing has an AutoCAD database unique object ID associated with it. We can open an AutoCAD entity by its object ID and determine ‘what kind of entity we are dealing with. Then we can use the entity’ get and set meth- ‘ds (functions) to manipulate the entity. Once we have an ads_name object, we can get its associated AutoCAD database object ID (data type = AcDbObjectid). Here is the function definition for getting an AutoCAD database object ID: acdbGetObjectId(AcDbOtjectId& objld, ads_named objName) ; // returns an object ID // for a given ads_name The ads_name object is as a result of successfully selecting an entity using acedEntSel(). Here is an ObjectARK code fragment using the acedEntSel() function: AcDbEntity* selectEntity(AcDbObjectId& eld, 2 ‘AcDb: :OpenMode openMoce) t ads_name en; ads_point acedEntSel(“\nSelect an entity: en, pt); // exchange the ads_name for an object Id uW acdbGetObjectId(eld, en); AcDbEntity * pent; acdbOpendbject (pent, eId, openMode); return pent; ) ‘This function retums an AcDbEntity pointer in pEnt; notice how we use acedEntSel() to select the AutoCAD entity. acdbEntLast() [ads_entlast()] acedNEntSel() fads_nentsei()] acedNEntSelP() [ads_nentselp()] ‘There are other entity selection functions in ADS: acdbEntLast(), acedNEntSel(), and acedNEntSelP(). Let's take a look at aedbEntLast(). AutoCAD always knows the last entity that was created, which can be found through the acdbEntLast() function. Here is the definition of aedbEntLast(): int acdbEntLast(ads_neme result); sential ADSRX (ADS) 87 $$$ The acdbEntLast() function finds the last entity in the drawing and sets result to the name of the last (nondeleted) main entity in the drawing database. The last entity is selected even if it is not on screen or is on a frozen layer. The last entity is the most recently created entity, so acdbEntLast() can be used to obtain the name of an enti- ty that has just been added to the AutoCAD database. If acdbEntLast() succeeds, it ‘returns RTNORM; otherwise, it returns RTERROR. For complex entities, blocks, and polylines the ADS functions acedNEntSel() and acedNEntSelP() (which stand for nested entity select) will select attributes if a block entity is selected (assuming that the block attributes are defined) and vertex infor- ‘mation if polyline is selected. Autodesk recommends using the acedNEntSelP( func- tion as opposed to acedNEntSel( function. The acedNEntSelPQ is a newer addition than the acedNEntSel() function, so I will only discuss the acedNEntSelP(Q func tion, Here is the definition of acedNEntSelP() function: int acedNEntSelP(const char *str, ads_name entres, ads_point ptres, int pickflag, ads_matrixe xformres, struct resbuf **refstkres): ‘This is a complex function with many arguments. The acedNEntSelP() pauses for user input and passes back both an entity name in entres and the point used to select the entity in ptres. The pickflag argument is either FALSE or TRUE and specifies ‘whether acedNEntSelP( is being used interactively (that is, allowing the user to select the point as opposed to a supplied point). If pickflag is FALSE, acedNEntSelP() prompts the user to specify an entity; the initial value of the ptres argument is ignored. If plekflag is TRUE, the initial value of ptres is used to select the entity. I rarely ever use pickflag set to TRUE. If you want a description of the other arguments, you can refer to the ADSRX documentation. Ifstr is NULL, AutoCAD will prompt ‘with the standard "Select objects:|” prompt. The argument that I'm most interest- cd in is entres, which will be either a vertex or an attribute. ELEMENTS OF SAMPLE APPLICATION CH3_1 REVISITED In the sample application CH3_1.ARX, we wed the acedGetXXX() functionality right after we created the first circle. Let’ take another look at those functions now. In the while loop, we are asking users if they want to draw another circle. As long as a user does not answer “No” we will keep asking the user to draw circles. Let's look at the first two lines of the while loop: acedInitGet(NULL, “Yes No”); re = acedGetkword(“\nDraw another circle [Yes/Nol: ", kw); In the acedinieGet() call the keyword lists “Yes No”. The first parameter is NULL 80 we can accept no input. We could have used RSG_NONULL and forced the user to type a“Y” or “N”. Typical in AutoCAD, the default option is indicated in angle brackets, in this case . Ifthe user types “Y”, the character string kw will hold “Yes”. If the user types “N”, kw will hold “No”. However, if the user just presses ENTER, kw will be an empty string. This case is caught in the switch statement under the RTNONE case, which copies “Yes” to the kw buffer. Remember that the acedinitGet() function applies to the next acedGetXXX0 function only. When you run this application again, try typing some letters other than “Y” or “N” and see ‘what happens. You will be reprompted until you press Y, N, or ENTER. If the user wants to draw another circle, that will take us to the else branch of the if statement, where we ask the user to select the center of the circle, asthe following code fragment indicates: acedInitGet(RSG_NONULL, NULL): re = acedGetPoint(NULL, “\nPick circle center? point: “, cp); ‘Here we use RSG_NONULL for this acedinitGet() call because we want the user to select a center point. In the following call to acedGetPoint(), note that the first argu- ment is NULL. Remember that in acedGetPoint(, the first argument allows us to select a point using a reference point. This is our first point, so we use NULL. Try to press ENTER when asked to select a center point and you will be reprompted. ‘When asked for the circle radius we use acedGetDist() instead of acedGetPoint(, because the response to acedGetDist() allows us to select a point or manually enter a distance at the keyboard. Here is the code fragment using the acedGetDist() function: acedInitGet(RSG_NONULL + RSG_NOZERO + RSG_NONEG, & NULL): rc = acedGetDist(cp, “\nCircle radius: “, &rad): Notice how we call acedinitGet() with a combination of RSG_NONULL, RSG_NOZERO, RSG_NONEG. This is because we are going to use acedGetDist() following the setup call to acediInitGet(. In the acedGetDist() function the first para ‘meter is ep, which isthe point we selected earlier. This will cause a rubber-band line to be drawn from the point ep. When the user selects a second point, the distance from the point ep to the second selected point is passed into the variable rad, which is an ads_real data type. However, the user also has the option of typing input at the key- board. We will not allow zero and negative numbers, which is why we also include RSG_NONULL and RSG_NONEG in the call to the acedinitGet() function. Essential ADSRK (ADS) In the user-defined printEntinfo() function, which takes an ads_name argument, we call the acdbEntGet( function to retrieve linked list of result buffers. Ifthe call is successful, the result buffer linked list contzins the entities data structure, as the fol- lowing code fragment shows: struct resbuf *rbEnt; rbEnt = acdbEntGet (ent); ‘We traverse the linked list using the rbEntrbnext field and examine the value in the ‘bEnt->restype field, which indicates the type of data stored in the rbEnt->resval field. SELECTION SETS AA selection set is a group of AutoCAD entities in the current drawing session that are referred to by name, in this case an ads_name object. Selection sets are very similar to “groups” of AutoCAD entities. Once we have a selection set, we can determine the quantity of entities that make up the selection set (also referred to as the length of the selection set). Now that we know the length of the selection set, we can use a loop ing mechanism to step through each entity inthe selection set and read or edit the enti- ty as appropriate, A single entity can be represented only once in a selection set; however, an entity can belong to more than one selection set. If we have a selection set ssl and we wish to add a line entity to the selection set, we can select the line enti- ty manually. We can physically select the line entity more than once, but only a sin- gle representation of the line entity will be present in the selection set. If we now create ‘second selection set ss2 and select the same line entity, that entity will be added to the second selection set. The line entity now appears in two selection sets, ss and ss2, but in cach selection set the line entity is represented only once. selection set is a named set of entities, selected by the AutoCAD user manually or added to a selection set based on the properties of the entities, Selecting entities based on property is like selecting all the circles on the “parts” layer that have a radius less that 0.25, which would allow us to change their radius to 0.375. A selection set will select entities even if they are on a frozen kiyer. A selection set can also be empty— it is just a container that holds AutoCAD entities, ike a paper bag of candies, empty if you havent put any candy in itor you ate them all. Table 3.6 lists the ADS func- tions that deal with selection sets. 8 Selection Set Functions. Description equivalent ‘acedSSGet() | Select entities to adi toa selection set ssget ‘acedSSLength() | Return the length ofthe specified selection set | sslength acedSSAdd() | Add entities to an existing selection set or | ssadd create an empty selection set ‘acedSSDel(Q) | Delete entities from an casting selection st | ssdel acedSSName() | Retrieve the ads_name ofan entity inthe | ssname selection set acedSSMemb() | Is anentityamemter of the selection set? ssmemb acedSSFree() _| Freea selection set N/A ‘Table 3.6 ADS Selection Set Functions acedSSGet() [ads_ssget()] ‘A selection set is an ads_name object; you have to select entities using acedSSGet() to add entities to the specified selection set or acedSSAddQ if you know the name of the entity beforehand. Let’ look at acedSSGet(); here is its definition: int acedSSGet (const char *str, const void *ptl.? const void *pt2, const struct resbuf *filter, ads_name ss); ‘The acedSSGet() function returns a selection set obtained when one of the AutoCAD selection modes is specified, ether through a prompt to the AutoCAD user ot by fil tering the drawing database. There are vasious ways to use the acedSSGet ftnction. First we will explain what the arguments are and then we will show the various ways in which the acedSSGet() function can be used. ‘The str argument is an optional string that specifies the entity selection modes. ‘The pt! and pt2 arguments specify optional points relevant to some of the selection modes. The pt! argument can also be a result buffer linked list that contains multi- ple points for the polygon or fence selection options. The filter argument is an optional result buffer linked list that enables acedSSGet() to filter the drawing to select entities that are of certain types and/or that have certain properties. Whichever mode you use to obtain the selection set, use ss to identify the selection set's name. ‘The str argument specifies which selection mode to use. It can be one of the strings listed in Table 3.7. Ewential ADSRX (ADS) 91 ———— Value (mode) Description NULL | Single-point selection (if ptl isspecified) or user selection (ifptl is also NULL) “ ‘The PICKFIRST set “o Crossing selection ‘Crossing polygon selection Fence (open polygon) selection Last created entity ‘Previous selection set ‘Window selection ‘Window polygon selection Filter selection only (the one I use most often) Groups ‘Prompts supplied “Other” callbacks Duplicates allowed Everything in aperture Keyword callbacks Nested Force single object selection only User selection ‘Non geometric (all las, previous) “AP All BOX oa Multiple ‘Table 3.7 Selection Options for acedSSGet(), Value of str Argument ‘As you can see from the previous table, there are multiple selection set modes; you will bbe happy to know that I rarely ever use them, except for NULL and ‘If you want to explore all the options, they are listed in the documentation. Normally I only do one of two things: have the user select entities (the NULL mode option) or select entities based on properties (the *X" mode option). ‘The following code shows some representative calls to the acedSSGet() function. As the acutBuildList() call illustrates, for the polygon options CP and WP (but not for F), acedSSGet() automatically closes the list of points. You don't need to build a list that specifies a final point identical to the first. 11 variables ads_point pti, pt2, pt3. pta: struct resbuf *pointlist: ads_name ssname; pti{X] = pti(v] pt2[x] = pt2[v] pt2[Z] = 0.0; pti(Z] = 0.0; 5.0; // Ask the user for a general entity selection // this is the method I use most often acedSSGet(NULL, NULL, NULL, NULL, ssname); // Get the current PICKFIRST set, if there is one acedSSGet(“I", NULL, NULL, NULL, ssname); // Selects the most recently selected objects acedssGet(“P”, NULL, NULL, NULL, ssname); J/ Selects the last entity added to the database acedSSGet(“L”, NULL, MULL, NULL, ssname); // Selects entity passing through point (5,5) acedSSGet (NULL, pt2, NULL, NULL, ssname); // Selects entities inside the window from (0,0)¢ to (5,5) acedsSGet(“W", ptl, pt2, NULL, ssname); // Selects entities enclosed by the specifiedd polygon pt3[X] = 10.0; pt3Y] = 5.0; pt3(Z] = 0.0; Esential ADSRX (ADS) 93 ns aa ptaCxX] = 5.0; ptaly] = ptatz] = 0.0; pointlist = acutsuildList(RTPOINT, pti, RTPOINT.& pt2, RTPOINT, pt3, RTPOINT, ptd, 0); acedSSGet(“WP”, pointlist, NULL, NULL, ssname); JI Selects entities crossing the box from (0,0) to (5,5) acedSsGet(“C*, ptl, pt2, NULL, ssname); // Selects entities crossing the specified polygon acedSSGet(“CP”, pointlist, NULL, NULL, ssname); acutRelRb(pointlist); // Selects the entities crossed by the specifiede fence ptaCy] = 15.0; ptaz] = 0.0; pointlist = acutBuildList(RTPOINT, pti, RTPOINT,& pt2, RTPOINT, pt3, RTPOINT, pt4, 0); acedSSGet(“F”, pointlist, NULL, NULL, ssname); acutRelRb(point list); SELECTION SET FILTERING ‘To.use selection set filtering, you must specify “X” in the str argument. Selection set Gilverinyg allows you to select entities based on properties. The filter argument is a result buffer list and it is here that you specify to acedSSGet() what kinds of entities and what kinds of properties you want to use. Ifthe filter argument is NULL and the str (mode) argument is “X”, the selection set ss will contain every entity in the current AutoCAD drawing, regardless of whether the entities reside on frozen layers or not. Here is the call: // selection set ads_name ss; acedSSGet ("X' + NULL, NULL, NULL, ss); ‘The selection set ss will now contain every entity in the current AutoCAD drawing session. So how do we select all the circle entities in the database? To use selection sets effectively you need to know your DXF codes. First we have to build up a result buffer, but because we are only looking for one kind of entity, we can use acutNewRb() to create the result buffer. Here isa sample code fragment: // Circle result buffer struct resbuf *pcb; char sbuf{10]; ads_name ssl; pcb = acutNewRb(RTDXFO); // Creates a new result buffer and sets if restypee field to 0 // Remember 0 is the CXF code that indicates? entity type strepy(sbuf, “CIRCLE”); pcb->resval.rstring = sbuf: pcb->rbnext = NULL; // No other properties JJ Now here is the call to acedssGet // Select all the circles acedSSGet(“X", NULL, NULL, pcb, ssl); acutRelRb(peb); // Don’t forget Here is an example of the selection of all entities on a certain layer. The DXF code for a layer is 8. // Layer result buffer struct resbuf *plb; char sbuf(32]; ads_name ssl: plb = acutNewRb(8); // Creates a new result buffer and sets ifé restype field to 8 // Remember 8 is the DXF code for layers strepy(sbuf, “PARTS"): plb->resval.rstring = sbu plb->rbnext = NULL: // No other properties J/ Now here is the cail to acedSsGet // Select all the entities on layer = “PARTS” acedSsGet(“X", NULL, NULL, pIb, ssl); Euential ADSRX (ADS) 95, ————— acutReIRb(plb); // Don't forget ‘Wal, le’s make the next example a little more complicated; suppose we wanted to select all the circles on the “parts” layer. This is an example of where I would use ‘acutBuildList() to build the result buffer list before passing it to acedSSGet(). ads_name ssl; struct resbuf *rbl; // Result buffer list rb1 = acutBuildList(RTDXFO, “CIRCLE”, 8, “PARTS",& RTNONE) ; // Remember that acutBuildList must end with a O¢ or the equivalent RTNONE // 0 is used to end acut8uildList but 0 is alsog used for DXF entity types // so when you want to use a OXF 0 use RTDXFOe instead acedSsGet(“X", NULL, NULL, rbl, ssl); // The selection set ssi now contains all thed circles J on Vayer “PARTS” acutReTRb(rb1); RELATIONAL SELECTION SET FILTERING ‘We can also use relational operators in our selection sets. For example, we could select all the circles on the “parts” layer that have a radius greater than or equal to 2.0. By default, acedSSGet( selects entities that match all the criteria in the filter list. The implied relation between each item in the filter is “equals.” For numeric groups (integers, reals, points, and vectors) you can specify other relationships by including a special “4” result buffer that specifies a relational operator. The operator applies to the result buffer that immediately follows it. Relational operators are specified by strings. Table 3.8 lists all the relational opecators. Relational Opeaine Description “" ‘Anything goes (always true) “a” | Equals “ta” | Not equal to(C/C++ style) =” | Not equal to (AutoLISP style) or | Not equal to “e Tess than wea” | Less than orequal to Greater than (Greater than or qual to “ae | Bitwise AND (integer groups only) “a” | Bitwise masked equals (integer groups only) ‘Table 3.8 Relational Operators for Selection Set Fier Lists Relational operators are best illustrated by example so, continuing with our example, selecting all the circles on layer “parts” that have a radius greater than or equal to 2.0 could be coded as follows: ads_name ssl; struct resbuf *rbl; // Result buffer list rb] = acutBuildlist(RTOXFO, “CIRCLE”, -4, ">=", // Greater than or equal 40, 2.0, // 40 = DXF code for radius,& 2.0 in our example 8, “PARTS”, // Layer “PARTS” RTNONE) acedSsGet(“x", NULL, HULL, rb1, ssl): // The selection set ssi now contains all thee circles J/ on layer “PARTS” whose radius is greater thang or equal to 2.0 Essential ADSRX (ADS) 97 $$ acutRelRb(rb1); CONDITIONAL SELECTION SET FILTERING In additional to relational tests, we have conditional operators. Table 3.9 lists all the conditional selection set operators. oes Encloses poets “” “OR” | Oncormoreoperinds | “ORD” “KOR” —_| Two operands “XOR>” “” ‘Table 3.9 Conditional Operators for Selection Set Fier Lists Conditional selection set operators allow us to carry out selection set operations such as selecting all the circles in the drawing with a radius of 1.0 and all the lines on the “parts"layer. This could be coded as follows. ads_name ssl: struct resbuf *rbl; // Result buffer list rbl = acutBuildList(-4, “”, // Ending operator and “4, “", // Ending operator and -4, “0R>” " // Ending operator or RTNONE); acedssGet("x", NULL, NULL, rb1, ssl); // The selection set ssi now contains all thee circles // whose radius “parts” 1.00 and all lines on layere acutRelRb(rb1); Te takes practice to get good at relational and conditional selection set filtering. EXTENDED ENTITY DATA SELECTION SET FILTERING Since Release 11, AutoCAD has had a mechanism for adding data to entities called Extended Entity Data (xdata). I don't want to get into a long discussion about Extended Entity Data because starting with AutoCAD R13 c4a and AutoCAD R14 and forward a new mechanism has been introduced, namely XRecords, which I will talk about in later chapters. Extended data (xdata) are text strings, numeric values, 3D points, distances, ayer names, or other data attached to an object, typically by an external application. The size of extended data is 16K bytes per entity. Note that Records are not attached to any entity and therefore do not require the existence of an entity for their existence. You can retrieve extended data for a particular applica tion by specifjing its name in a filter list, using the -3 group code sentinel. The acedSSGet() function returns entities with extended data registered to the specified name; acedSSGet() does not retrieve individual extended data items (with group codes in the range 1000-2000). The following sample code fragment selests all entities that have extended data reg istered to the application whose ID is "APPNAME”: struct resbuf *xrb; ads_name ssxd: xrb = acutBuildList(-3, 1001, “APPNAME”, RTNONE); acedSsGet("x", NULL, NULL, xrb, ssxd); acutRel Rb(xrb); ‘There is more information regarding Extended Entity Data in the ADSRX docu- ‘mentation, but because AutoCAD R14 and beyond uses XRecords, we will discuss XRecords in later chapters in more detai. TRANSFORMATION MATRICES AND SELECTION SETS acedXformSS() [ads_xforms()] Selection sets can make use of transformation matrices with the function ads_xformSS(. Using a transformation matrix on a selection set, we can scale, ‘move, rotate, or mirror the entities that male up the selection set. This is achieved by Evential ADSRX (ADS) 99 ‘call to acedXformSSQ with the appropriate matrix members set, instead of loop- ing thorough the selection set and carrying out acedCommand(/acedCmd() that scale, move, rotate, or mirror on each entity. I do not intend to get into a complete dis- cussion of matrix algebra. ObjectARX has already defined numerous functions that deal with matrix algebra (see AcGeMatrixid and AcGeMatrix3d matrix manipula~ tion classes). Ifyou recall, a transformation matrix is a 4x 4 array of ads_real values. ‘The frst three columns of the matrix specify scaling and rotation. The fourth column of the matrix is a translation vector. The final row of the matrix has the nominal value of (000 1}; itis ignored by the functions that pass ads_matrix arguments. ADSRX defines the symbol T for translation, as follows: define T 3 ‘Here is the definition of acedXformSSQ): int acedXformSS(const ads_name ssname, ads_matrix? genmat); ‘The acedXformSSQ) function applies a transformation matrix, genmat (general ‘matrix), to the selection set specified by ssname. The genmat argument is a 4x 4 matrix. If genmat does not do uniform scaling, acedXformSS() returns RTERROR. Applying a transformation to a selection set is a means of scaling, rotating, or mov- ing the entities in the selection set without using acedCommand(), acedCmd(), or acdbEntMod(. If it succeeds, acedXformSS() returns RTNORM; otherwise, it returns RTERROR. Initializing @ matrix Here is the code fragment that initializes a matrix: void ident_init(ads matrix id) ( int i, ds for (inO; i<=3; i++) for (j=0; §6-3; j++) idL1]EJ] = 0.05 for (i=0; 4 i+) idLiCi] = 1.0; Tin the function we initialize an ads_matrix with 0 values using a for loop inside a for Joop. We then use a for loop again and initialize elements 0,0 1,1 2,2 and 3,3 to a value of I. Now the matrix is initialized as an identity matrix. Let's look at the translation of the matrix as shown by Tx, Ty, and Tz. 100 1x 010Ty oo1Tz ooo01 Transformation matrix ‘You change the first three rows of the fourth column and apply acedXformSS(, and all the entities in the selection set will move the appropriate distance in the X, Y, and Z directions. Note that if you only wanted to move in the X direction you would change only the Tx value of the matrix. sx0 0 0 0 syo o 0 0 szo ooo) Scaling matrix ‘When you deal with selection sets and acedXtormSSQ, all the scaling factors have tobe equal, that is, Sz = Sy = Sz. There are also other operations such as rotation matri- ces, both 2D and 3D (see the ADSRX and Object ARK documentation). Let's look at an example of matrix manipulation using selection sets. 0.5 0.0 0.0 20.0 0.0 0.5 0.0 5.0 0.0 0.0 0.5 0.0 0.0 0.0 0.0 1.0 Here is the matrix that we want to achieve on the selection set. We want to scale the XYZ by 0.5 and move the selection set by 20.0, 5.0 Here is the code fragment: int re. 4, ds ads_point ptl, pt ads_matrix matrix ads_name ssname; J* Initialize ptl and pt2 here */ Euential ADSRX (ADS) 101 re = acedSSGet(“C”, pti, pt2, NULL, ssname); if (re = RTNORM) { /* Initialize to identity */ ident_init(matrix); /! Shown earlier /*Initialize scale factors */ matrix(0}{0] = matrix(1][1] = matrix(2][2] = 0.5; /* Initialize translation vector */ matrixCOJ(T] = 20.0; // rember T is #defined T 3¢ in ADSRX matrixC1J(T] = 5.0; re = acedXformSS(ssname, matrix); ) Fun stuff MANIPULATING SELECTION SETS Now that we have looked at various methods for creating selection sets, let's look at the functions that will allow us to manipulate those selection sets, Perhaps the first thing that we would want to know about a selection set is how many entities make up that selection set. acedSSLength() fads_slengtb()] ‘The acedSSLength( function will return the number of entities that make up a selec- tion set. Here is the definition of acedSSLength(): int acedSSLength(const ads_name sname, long *len); ‘The acedSSLength() function returns a long integer, len, that contains the number ‘of entities in the selection set sname. The result is the number of distinct entities; selec- tion sets never contain duplicate entities, regardless of how a set was chosen. If acedSSLength() succeeds, it returns RTNORM; otherwise, it returns an error code, The acedSSLength( function is usually used in conjunction with a for loop, as shown in the following code fragment: ads_name sset; if (acedSSGet (NULL, NULL, NULL, NULL, sset) ! RTNORM) { acutPrintf(“\nNothing selected”); return; } Jong len; acedSSLength(sset, len); for (int i = ( i < Ten; i++) ads_name ent; acedSSName(sset, i, ent); 17 Okay I’m sneaking in a little ObjectARX here! AcDbObjectId objld; acdbGetObjectId(objId, ent): 1] Now that we have an objld // we can open up the AutoCAD J/ database object AcDbObject *pObj; acdbOpenObject(p0bj, objld, AcDb: // Get some info here ForRead) : pObj->close(): ) acedSSFree(sset); acedSSName() [ads_ssname()] In the previous code fragment, if the acedSSLengthQ function retums to the len vari- able a value greater than 0, we then can get a particular entity in the selection set at the location represented by the long variable i To get an entity at a particular location in a selection set, we use the function acedSSName(). Selection sets start counting at 0; the first element is at the 0 location. Here is the definition of acedSSName(): int acedSSWame(const ads_name ss, long i, ads_named entres); ‘The acedSSName() function selects an entity at index position | in the selection set ss and returns the entity’s name in entres. Entities are numbered from 0, so 1 must be nonnegative and no greater than the index of the last entity in the selection set (acedSSLength(ss) - 1). Entity names in selection sets obtained with acedSSGet() are always the names of main entities. The acedSSName() function cannot obtain the names of subentities (such as block attributes and polyline vertices). The acedSSName() function returns RTNORM if successful; otherwise, it returns an error code. In the past, with ADS and selection sets, the function acedSSLength() would return the length of the selection set. Once we had the length of the selection set, we would use a for loop to navigate through the selection set and get entity names using the acedSSName() function. Once we had an entity, we could get its data using Esential ADSRX (ADS) 103 acdbEntGet(), which would return its dita in a linked list of result buffers. We could then modify the result buffer list and call aedbEntMod(), and then change the entities database and acdbEntUpd( to update is representation if necessary. As I have ‘mentioned earlier, ObjectARX has better mechanisms for dealing with entity data- bases, some of which I alluded to in previous code fragments. We tend not to use acdbEntGet(), acdbEntMod(, or acdbEntUpd() anymore. However, we still use selec- tion sets. acedSSFree() [ads_sfree()] In the last line of the code fragment we make a call to acedSSFree(). When you are dealing with selection sets, i's important to fice the selection set when you are fin ished with it because AutoCAD can have open only a limited number of selection sets at one time (128 maximum). Here is the definition of acedSSFree(): int acedSSFree(const ads_name sname); ‘The acedSSFree() function frees the selection set specified by sname. The set must have previously been obtained from a call to acedSSGet() or acedSSAdd(), We'll talk about acedSSAdd() and acedSSDel() a little later. An ADSRX application cannot have more than 128 selection sets open at once. If the limit is reached, AutoCAD refuses to create more selection sets. Simultaneously ‘managing a large number of selection sets isnot recommended. Instead, keep a rea sonable number of sets open at any given time, and call acedSSFree() to free unused selection sets as soon as possible. Ifit succeeds, acedSSFree() returns RTNORM; oth- erwise, it returns an error code. acedSSAdd() fads_ssadd{)] ‘The acedSSAdd() function creates a new selection set or adds an entity to an exist- ing selection set. Here is the definition of acedSSAdd(): int acedSSAdd(const ads_name ename, const ads_named sname, ads_name result); ‘The ename argument specifies an entity, and sname specifies a selection set. If both ename and sname are null pointers, acedSSAdd() creates a new selection set with no members (empty selection set to which you can add members later) whose rname is set to result. If ename is a valid entity but sname is NULL, acedSSAdd() creates a new selection set whose (single) member is ename and the name of the selec- tion set is result. If ename specifies a valid entity and sname specifies an existing selec- tion set, acedSSAdd() adds the ename entity to the set specified by sname. In all cases, acedSSAdd() sets result to the name of the new or updated selection set. If the entity specified by ename is already in the set sname, acedSSAdd() ignores the request and does not report an error. The sname and result arguments can specify the same selection set. This is the most intuitive way to add the ename entity to an existing selection set. Every selection set that is created by a call to acedSSAdd() with a null sname must later be released by a call to acedSSFree(). This applies even to empty selection sets (when ename is also NULL). If acedSSAdd() returns an error status code, no selection set is created. If acedSSAdd() succeeds, it returns RTNORM; otherwise, it returns an error code. acedSSDel() fads_ssdel()] To delete an entity from an existing selection set use the acedSSDel() function. Here is the definition of acedSSDel(): int acedSSDel(const ads_name ename, const ads_named ss): ‘The acedSSDel() function deletes the emtty specified by ename from the selection set ss. Both the entity and selection set names must be valid for the current drawing. acedSSMemb() [ads_ssmemb()] Finally, you can check to see ifan entity is a member of an existing selection set using ‘the acedSSMemb() function. Here is the definition of acedSSMemb( int acedSSMemb(const ads_name ename, const ads_named ss); ‘The acedSSMemb() function tests whether the entity ename is a member of the selec~ tion set ss. The entity and selection set names must be valid for the current drawing. TfacedSSMemb() finds ename, it returas RTNORM; ifit doesnt, it returns RTER- ROR. DATA TY! CONVERSION FUNC’ INS ‘On occasion you will be required to convert data types, especially when you use the acutPrint{Q function to prompt back to the user at the command prompt. So let’ look at the ADS conversion functions listed in Table 3.10. Exsental ADSRX (ADS) 05 —————_ Function Description ‘acdbRToS() Converts an ads_real value to a string acdbAngToS()_| Formats an angle 1s a string acutToUpper() | Converts the chancter to uppercase acutToLower() | Converts the charctr to lowercase acedTrans() | Translates a pointor a displacement from one coordinate system to another ‘Table 3.10 The More Useful ADS Conversion Functions acdbRToS() [ads_rtos()] Here is the definition of the aedbRToSQ) function: int acdbRToS(ads_real val, int unit, int prec.& char *str); ‘The acdbRToS() function formats val into a string, according to the settings of unit and prec (precision), The result is placed in str, which must point to a buffer large ‘enough to hold the formatted string, The unit argument selects the units into which the string is formatted. The value should correspond to the values allowed for the AutoCAD LUNITS system variable (for 1 ~5, see ‘system variables in the AutoCAD online help). If unit is set to -1, acdbRT0SO uses the current value of LUNITS. The prec argument selects the number of decimal places of precision to include in the string. If prec is set to -1, acdbRT0SQ) uses the current value of the AutoCAD LUPREC system variable. The current value of the AutoCAD DIMZIN dimension ing variable controls how acdbRToS() writes leading or trailing zeros to str. If acdbRTOSQ succeeds, it returns RTNORM; otherwise, it returns an error code. acdbAngToS() fads_angtos()] Here is the definition of the acdbAngTS() function: int acdbAngToS(ads_real v, int unit, int prec.¢ char *str); ‘The acdbAngToSQ function formats the angle v into a string, according to the set- tings of unit and prec (precision). The value of vis in radians. The result is placed in str, which must point to a buffer large enouzh to hold the formatted string, The si of the string depends on the requested mode and precision; 15 bytes is usually ade~ quate. The unit argument selects the units into which the angle is formatted. The value should correspond to values allowed for the AutoCAD system variable AUNITS. Also, if unit is set to -1, aedbAngToSQ bases the angle’ units on the current value of AUNITS. The prec argument selects the number of decimal places of precision to include in the string. If prec is set to -1, aedbAngToS() uses the current value of the ‘AutoCAD system variable AUPREC. The current value of the AutoCAD dimen- sioning variable DIMZIN controls how aedbAngToSQ) writes leading or trailing zeros to str. Possible values of DIMZIN are described in the AutoCAD User’s Guide. If acdbAngToS() succeeds, it returns RTNORM; otherwise, it returns an error code. acutToUpper) [ads toupper()] acutToLower() [ad:_tolower()] ‘Here are the definitions of the acutToUpper() and acutToLower() functions. int acutToUpper(int c): int acutToLower(int c); ‘These functions convert the character argument (ASCII int value) to uppercase or low- excase. acedTrans() [ad:_trans()] Here is the definition of the acedTrans() function: int acedTrans(const ads_point pt, const structd resbuf *from, const struct resbuf *to,? int disp, ads_point result); ‘The acedTrans() function translates a point or a displacement from one coordinate system to another. It interprets the pt argument as either a three-dimensional point or a three-dimensional displacement vector. The from argument specifies the coor- dinate system in which pt is expressed, and the to argument specifies the coordinate system of this function's result. Ifthe disp argument is nonzero, pt is treated as a dis- placement vector; otherwise, itis treated as a point. The best way to illustrate the acedTrans() is through a sample. There are coordinate systems used by AutoCAD where 0 = World (WCS) and 1 = User (current UCS). They also define 3 and 4, which I rarely use; you can look them up in the documentation. Here is a sample code fragment that converts from UCS to WCS: 11 User to World funcition int u2w(ads_point pl, ads_point p2) t struct resbuf wes, ucs; Esential ADSRX (ADS) $$$ wes.restype = RTSHORT; wes.resval.rint = 0; ucs.restype RTSHORT; ucs.resval.rint = 1; return acedTrans(pl, aucs, &wcs, 0, p2);, ) ‘This is the first code fragment where we did not use pointers to result buffers. They are declared locally in the function and are automatically destroyed on exit of the func tion. No need to call acutReIRbQ) here. Note, however, that we have to place the & operator in front of the resbuf variables because acedTrans() requires a pointer (address of) to resbuf structures. SAMPLE APPLICATION CH3_2 In this application we demonstrate once again the use of the acedGetXXX() func- tions. We also demonstrate the selection set functions. As we loop through the selection set, we also demonstrate how to change the properties of the entities that ‘make up the selection set using a result buffer linked list. In a pure Object ARX appl cation, changing the properties of the entities is done through the ‘get’ and ‘set’ functions of the appropriate entity classes. Let’s look at the code first and discuss its elements later. Here is the listing of CH3_2.CPP file. J Ch3_2.cpp : Initialization functions J CH3_2.cpp // by Charles Mc Auley /1 “Programming AutoCAD 2000 with ObjectARX” uW if This application demonstrates the use of@ selection // sets. The user is asked to select a set of@ entities // and then asked to se‘ect a target entity whosee layer // we extract, after which we move the entities ing the selection // set to the target layer. We use result buffers to J modify the entites in the selection set. J/ This application represents the old styled methods for // mainpulating AutoCAD entities and these method has been 11 superceded by ObjectARX. uv LLL LLL Wit finclude “StdAfx.h” finclude “StdArx.h” finclude “resource. HINSTANCE _hd11Instance =NULL J/ This command registers an ARK command. void AddCommand(const char* cmdGroup, const char*# cmdInt, const char* cndLoc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idlocal = -1); // NOTE: DO NOT edit the following lines. 1 LAPX_ARX_MSG void InitApplication(); void UnloadApplication(); 1) VAFX_ARK_MSG // NOTE: DO NOT edit the following lines. 77 ((AFX_ARX_ADDIN_FUNCS, 7) )AFX_ARX_ADDIN_FUNCS MALT LLL LLL TALL 77 DLL Entry Point extern “C” BOOL WINAPI D11Main(HINSTANCE hInstance, DWORD® dwReason, LPVOID /*1pReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { -hd11Instance = hinstance; } else if (dwReason == DLL_PROCESS_DETACH) ( ) return TRUE; = // ok ) Essential ADSRX (ADS) 109 ————_ HATTA MATT /1 ObjectARX EntryPoint extern “C” AcRx: :AppRetCode acrxEntryPoint(AcRx: :AppMsgCode msg, void* pkt) (i switch (msg) { case AcRx: :kInitAppMsc: 1/ Comment out the following line if your 1/ application should be locked into memory acrxDynamicLinker->unlockApplication(pkt) ; acrxDynamicLinker->registerAppMDIAware(pkt) ; Initapplication(); break: case ACRK::kKUnToadApp¥sg: UnloadApplication(); break: ) return ACRx::kRetOK: ) // Init this application, Register your 17 conmands, reactors... Void InitApplication() ( J NOTE: DO NOT edit the following lines I UCAFX_ARX_INIT. AddCommand(*CH3_APPS”, “CEL”, “CEL”, ACRX_CMD_MODAL, cel): T/Y)AFXARXINIT // TODO: add your initialization functions // CEL = Change Entities Layer acutPrintf(“Enter \"CEL\" at the command prompté to execute. \n"); ) // Unload this application. Unregister all objects // registered in InitApplication. void UnloadApplication() { JJ NOTE: DO NOT edit the following lines. TAAPX_ARX_EXIT. acedRegCmds- >removeGroup(“CH3_APPS") ; 17) \APX_ARX_EXIT // TODO: clean up your application acutPrintf(“Ss%s”, “Goodbye\n”, “Removing commande group \"CH3_APPS\"\n"): } // This function registers an ARX command. 7/ Tt can be used to read the localized command name // from a string table stored in the resources. void AddCommand(const char* cmdGroup, const char*o cmdint, const char* cmiLoc, const int cmdflags, const AcRxFunctionPtré cmdProc, const int idlocal) { char cmdLocRes(65]: // If idlocal is not -1, it’s treated as an ID for // a string stored in the resources. if Cidlocal != -1) ( // Load strings from the string table andg register the command. r:LoadString(_hdllInstance, idlocal, cmdLocRes,# 64); acedRegCmds->addConmand(cmdGroup, cmdint,& cmdLocRes, cmdFlags, cndProc); ) else // idlocal is -1, so the ‘hard coded’ // localized function name is used. acedRegCmds->addConmand(cmdGroup, cmdint.& cmdloc, cmdFlags, cmdProc); ) // User defined function called by // cel() see Ch3_2Commands . cpp void chgEntslyr(ads_name ent, ads_name ss) i long id: set marker long lenSS; // Length of selection set // Index of the selectiong Eventi ADSRX (ADS) NN $$ char lyrName[32]; // Target layer name - maxe Jength of layer name = 31 ads_name ssEntName; // Entity name in thee selection set struct resbuf *rbTargtnt; // resbuf to hold enté data struct resbuf *rbSSEnt; // resbuf of selectiond set entity struct resbuf *rbTrav; // resbuf to traversed linked list int res WJ Return code // Get the layer name of the target entity rbTargént = acdbentGet(ent); if(irbTargént) { acutPrintf(“\nFailed to get target entities? data. “): return; ) rbTrav = rbTargEnt; // Make rbTrav point toe rbTargént while(rbTrav) { switch(rbTrav->restype) { case 8 : // DXF code for layer name strepy(lyrName, rbTrav->resval .rstring): break; 1//switch // 1 could have used an if statement // here as I'm only serching for one // item, but when I'm dealing with // vesbufs I prefer to use a switch // statement. rbTrav = rbTrav->rbnext; V/white // We don’t need the rbTargEnt resbuf anymore // so release it - don’t forget 12 if (rbTargént) i acutRelRb(rbTargEnt) ; ) // Now we are going to traverse the selection set // changing each entity to the target layer. Check // to see if it’s not an empty selection set re = acedSSLength(ss, &lenSS); // Note: we passé the address of lenSS if(re = RTNORM) ( acutPrintf("\nInvalid or empty selection set.& return; ) for(idx = 0; idx < TenSS: idx++) { // Get the entity rame at the specified index re = acedSSName(ss, idx, ssEntName); if(re I= RTNORM) { break; ) // Get ssEntName entity data rbSSEnt = acdbEntGet(ssEntName); if (irbSSEnt) { break; ) rbTrav = rbSSEnt; // Make rbTrav point toe rbSsent. // Now that we have a linked list resbuf // change the OXF 3 field to the target // Vayer name. while(rbTrav) ( Essential ADSRX (ADS) 13 switch(rbTrav->restype) ( case 8 : // DXF code for layer name strepy(rbTrav->resval.rstring, lyrName) ; break; HW /switch rbTrav = rbTrav->rbnext; V//white /1 Now we need to modify the entity // with the changed resbuf Fe = acdbEntMod(rbSsent) ; if(re != RTNORM) ( acutPrintf(“\nFailed to modify the entity “): ) // Release the resbuf if (rbSSEnt) i acutRel Rb(rbSSEnt); ) V/for ) Here is the listing for the user-defined command CEL in CA_2Commands.cpp: WATTLE J/ ObjectARK defined conmands finclude “Stdafx.n" finclude “Stdarx. 11 This is command ‘CEL void ce1() cl ads_name srcSS;_// Source selection set ads_name targént; // Target entity int re; JJ Result code ads_point pickPt; // Lsed in the call tod 14 acedentSel() acedPrompt(“\nSelect entities for layer change “): re = acedSSGet(NULL, NULL, NULL, NULL, sreSS); if(re I= RTNORM) ( acutPrintf(“\nNo entities selected! return: } re = acedEntSel(“\nSelect target layer entity. “.¢ targEnt, pickPt); switch(re) { case RTERROR : acutPrintf(“\nNothing selected! “ break: case RTCAN acutPrintf( break; nUser canceled. case RTNORM : chgEntsLyr(targent, srcSS): break; ) // Don’t forget to free the selection set acedSSFree(srcSS); ) ELEMENTS OF SAMPLE APPLICATION CH3_2 ‘This application changes the layer ofa selected group of entities to the layer of a select- ced target entity. While a command like this already exists in AutoCAD R14/2000, ‘what I want to demonstrate is the use of a selection set. In this application, the user is asked to select entities, which populate the selection set sreSS as follows: acutPrompt(“\nSelect entities for layer change “); ro = acedSSGet(NULL, NULL, NULL, NULL, srcSS); ‘We then follow this with a call to acedEntSel() to select the target entity as follows: ro = acedEntSel(“\nSelect target layer entity. “,¢ sential ADSRX (ADS) us targEnt, pickPt); fall went wel, we finaly call the user-defined function ehgEntsLyr(), which is where all the action occurs. This function expects two arguments: an entity and a selection set, Note that they are both ads_name data types. We need to extract the target enti- ty’s data. This is achieved with a call to acdbEntGet(), which returns a result buffer linked list in rbTargEnt. We use the result buffer rbTrav to traverse the linked list, where we extract the layer name and copy it to lyrName. Dont use the rbTargEnt result buffer to traverse the list, because by the time you get to the end you would not be able release the memory allocated by acdbEntGet(), a classic memory leak, more common than you can imagine, The next task is to get the length of the selection set using the acedSSLength() function, after which we enter the for loop. In the for loop ‘we get the entity name using the acedSSName() function and return it in ssEntName. Once again, we use rbTrav result buffer to traverse the linked list. When we get to the layer result buffer we change the value of the rbTrav->resval.rstring to the lyrName. Notice how we modify the entity using acdbEntMod(rbSSEnt). We use the result buffer rbSSEnt to modify the entity because rbSSEnt isthe result buffer linked list and rbTrav is only a result buffer pointer that is used to traverse the linked list. We release the rbSSEnt buffer before the for loop starts again. Once we are back in the calling function, we call acedSSFree() before we exit the application. Dont forget to free the selection set. SAMPLE APPLICATION CH3_3 ‘This application demonstrates how to use matrices on a selection set using the data type ads_matrix and the selection set function that operates on the ads_matrix type, namely acedXformSS(). With matrices you can scale rotate, mirror, translate, and rotate about the X, Y, or Z axis. In this application we show only scaling. Here is a listing of CH3_3.CPP: // Ch3_3.cpp : Initialization functions 11 CH3_3.cpp J by Charles Me Auley J “Programming AutoCAD 2000 with ObjectARX” /1 This application demonstrates how to transform // selection sets using acedXform$S() in this application //-we ask to select a group of entities and then // what the scale factor is. We then transform thee selection 11 set. 116 WW TTT LLL TTL Wy Hinclude “Stdafx. Hinclude “Stdarx. iFinclude “resource.h” HINSTANCE _hd11Instance =NULL ; // This command registers an ARX command. void AddCommand(const char* cmdGroup, const char* cmdInt, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtr# cmdProc, const int idlocal = -1); // NOTE: DO NOT edit the following lines. TH((APX_ARK_MSG. void InitApplication(); void UnloadApplication(); 11) YAPX_ARK_MSG /1 NOTE: D0 NOT edit the following lines. 7 (APX_ARX_ADDIN_FUNCS 71) )AFX_ARX_ADDIN_FUNCS MLL LLL LLL MUTT 7 DLL Entry Point extern “C" BOOL WINAPI DI1Main(HINSTANCE hInstance, DWORDS dwReason, LPVOID /*1pReserved*/) { if (dwReason —= DLL_PROCESS_ATTACH) t _hd11Instance = hinstance; ) else if (dwReason —= DLL_PROCESS_DETACH) { ) return TRUE; — // ok ) TATTLE Exsental ADSRX (ADS) (7 ae a TILL 71 ObjectaRX EntryPoint extern “C” AcRx: :AppRetCode acrxEntryPoint(AcRx: :AppMsgCode msg, void* pkt) ( switch (msg) { case AcRx::kInitAppMsg: // Comment out the following line if your /1 application should be locked into memory ‘acrxDynamicLinker->unlockApplication(pkt) : acrxDynamicLinker->registerAppMDIAware(pkt);, InitApplication(); break: case ACRx: :kUnloadAppisg: Unloadappl ication(); break: ) return ACRx::kRetOK: // Init this application. Register your // commands, reactors. void Initapplication() { /1 NOTE: 00 NOT edit the following lines. TI APX_ARY_INTT ‘AddCommand(“CH3_APPS' ACRX_CMD_MODAL, tss); J) VAFX_ARX_INIT “TSS", “TSS”. // T0D0: add your initialization functions acutPrintf(“Enter \"TSS\" at the command prompté to execute.\n"); ) // Unload this application. Unregister all objects // registered in InitAppl ication. void UnloadApplication() (i // NOTE: DO NOT edit the following lines. 11 UOAFX_ARY_EXIT. acedRegCmds - >removeGroup(“CH3_APPS") ; 11) YAFX_ARY_EXIT // T0D0: clean up ycur application acutPrintf(“Ests”, “Goodbye\n”, “Removing commandé group \"CH3_APPS\"\n"); } // This functions registers an ARX command. // It can be used to read the localized commande name // from a string table stored in the resources. void AddCommand(const char* cmdGroup, const char*é cmdInt, const char* cndloc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idlocal) fi char cmdLocRes(65]; JJ If idlocal is not -1, it’s treated as an ID for // a string stored in the resources. if Cidlocal != -1) { // Load strings from the string table andé register the command. LoadString(_hdilinstance, idlocal, cmdLockes.@ 64); acedRegCmds->addConmand(cmdGroup, cmdInt,¢ cmdLocRes, cmdFlags, cndProc); } else // idlocal is -1, so the ‘hard coded’ // Vocalized function name is used. acedRegCmds->addConmand(cmdGroup, cmdInt, cmdLoc, cmdFlags, cmdProc); ) // User defined function called by 17 tssQ), see Ch3_3Commands .cpp void ident_init(ads_matrix id) for(inO; 1-3; i++) t Essential ADSRX (ADS) 119 for (jn0: j<=3; j++) i idCi]€j] = 0.0; ) ) for(inO; 1-3; i++) { id C404] = 1.0; } } Here is the listing for the user-defined command TSS in Ch3_3CommandsworkingDatabase(); Te returns a pointer to the current AutoCAD database. CREATING OBJECTS From a programming point of view, creating objects in Object ARX is vastly differ~ ent from creating objects in ADS or AutcLISP. In ADS we could use, in combina- tion with result buffers, the function acedCommand(), acedCmd(), or acdbEntMake( to create AutoCAD entities. In AutoLISP we used the (command) ‘or (entmake) function to create AutoCAD entities. The building of the result buffer list was critical, as was the syntax of the AutoCAD command. In ObjectARX, we are now dealing with clases and class hierarchies. Before we talk about these classes, I want to talk about what happens when the user creates entities, layers, and groups in ‘AutoCAD manually. We will borrow the examples from the ObjectARX Developers Guide and put our own twist on what's happening. The AutoCAD user has entered the following LINE command at the command prompt. Line 3,2 10,7 “The first thing that AutoCAD does is create an instance of the AcDbLine class. As mentioned earlier, al entities in AutoCAD belong to the block table. An entity is cre~ ated either in Model Space or one of the many Paper Spaces (layouts). The block table contains two special records: *MODEL_SPACE and *PAPER_SPACE. Entities are added either to the Model Space record or to the Paper Space record and not to the block table directly. Let’s assume that we are in Model Space. The first thing that AutoCAD does is open the block table to find the *MODEL_SPACE record. Having found the *MODEL_SPACE record, it then appends the line entity to the *MODEL_ SPACE record as shown in Figure 4.2, Paper space. Figure 4.2 Adding a Line to the Model Space Record Understanding AutoCAD's Database and Entity Structure 129 ——_ Ifthe user executes the AutoCAD CIRCLE command at the command prompt as fol- lows, circle 9,3 2 AutoCAD creates an instance of the AcDbCircle class. It looks for the block table’s *MODEL. SPACE record and appends the entity to the database as shown in Figure 43. Paper space Block Table a NN Model space | Tine [——[__ irate Layer Tabie Figure 4.3 Adding a Cirle to the Model Space Record Now suppose the AutoCAD user creates a new layer by typing the following command at the command prompt: layer make arx This time AutoCAD goes to the symbol tables (remember, there are nine) and finds the layer table. It opens up the layer table and adds a new layer table record named “ax” to the layer table, as shown in Figure 4.4: Paper space 1 I — ie |———s[__eireie Block Table Layer Tabte Layer - 0 Layer ~ ARK Figure 44 Adding Layer Table Record tothe Layer Table CREATING OBJECTS WITH OBJECTARX In dealing with Object ARX, you will discover that we execute the same kinds of processes, over and over again. In this section T'm going to throw a lot of sample code at you without much explanation of what is going on in the background, First, I want ‘you to observe that, regardless of what we do in ObjectARX, we are always carrying ‘out similar processes. When we are finished discussing these processes we will step back and take a high-level look at AutoCAD’: database structure, which isa topic of dis- cussion later in this chapter. Getting back to creating objects with ObjectARX, what we have to do in our code is imitate what AutoCAD automates when it creates new objects. First we will look at creating the line that we created earlier. The following code (without error checking) illustrates che process of adding AutoCAD entities to the database. AcDbObjectId createLire() t AcDbDatabase *pCurDb; AcGePoint3d start?t(3.0, 2.0, 0.0); AcGePoint3d endPt(10.0, 7.0, 0.0); [Understanding AutoCAD’ Databare and Entity Sructure 34 —————— a oe AcDbLine *pLine = new AcDbLine(startPt, endPt): pCurDb = acdbHostApplicationServices()& >workingDatabase(); AcDbBlockTable *pBlockTable; pCurDb->getBlockTable(pBlockTable, & AcDb: :kForRead) ; AcDbBlockTableRecord *pBlockTableRecord; pBlockTable->getAt(ACDB_MODEL_SPACE, & pBlockTableRecord, AcDb: :kForWrite): pBlockTable->close( AcDbObjectId linelc pBlockTableRecord->appendAcDbEntity(1ineld,& pLine); pBlockTableRecord->close(); pLine->close(); return linel ) ‘The user-defined function createLine(), 2: shown, adds a line entity to AutoCAD's database and returns the object ID of the entity. Remember, all entities appended to the database get an objéct ID. The variables startPt and endPt are of the type AcGePoint3d. Any object that begins with AeGe- is part of the AutoCAD geome- try classes. The AcGePoint3d class contains a number of different overloaded con- structors. (A constructor is automatically caled when you declare objects of that type.) This is C++, folks—every AutoCAD object now is some kind of class. So startPt and endPe are initialized by virtue of the fact that we declared objects of the type ‘AcGePoint3d. This is much easier than initializing an ads_point data type. Now we need to create one of AutoCAD's entities. As you know, there are many different kinds of entities in AutoCAD, all of which form part of a hierarchical tree. The line enti- ty AeDbLine is derived from AcDbCurve (a line is a straight curve), which in turn is derived from AcDbObject, which in turn is derived from AcRxObject. The AcRxObject class is the base class of most ObjectARX classes. The AcGe- classes, of which AcGePoint3d is a member, are not part of this hierarchy. They are pure math- ematical geometry classes with tons of useful functions. We have now created an ‘AcDbLine pointer in pLine and allocated memory for a line object with a call to new as shown: AcDbLine *pLine = new AcDbLine(startPt, endPt); Is the line in AutoCAD’s database yet? The answer is no. We need a place to put it, ‘Where do all AutoCAD entities reside? They are part of the block table, either the "MODEL SPACE, “PAPER SPACE, or laycut records. We now need a pointer to the block table, after which we will open the block table for a read operation. i // Assume pCurDb points to a valid open database. AcDbBlockTable *pBlockTable; pCurDb->getBlockTable(pBlockTable, AcDb::kForRead); ‘The getBlockTable() function is part of the AeDbDatabase class access functions and expects as parameters an AcDbBlockTable pointer and a mode of operation. Why is the mode of operation a read as opposed to a write? Remember that we are not adding any records to the block table—we just want to locate the *MODEL. SPACE record. ‘We will open the *MODEL, SPACE record for a write operation because it's the *MODEL_SPACE record that we are adding our line entity to. So what do we need? ‘We need a pointer to the AcDbBlockTableRecord and then we need to open the block table record for a write operation, as shown in the following call. AcDbBlockTableRecord *p8lockTableRecord: pBlockTable->getAt(ACDB_MODEL_SPACE. pBlockTableRecord, Actb: :kForrite) s pBlockTable->close(): Notice how we open the block table record for a write operation using one of the ‘AcDbBlockTable query overloaded functions, getAtQ. In this function, we are look- ing for a particular entry, as indicated by ACDB_MODEL, SPACE (Wwe could also have used "MODEL SPACE instead). If found, the block table record pointer pBlockTableRecord will point to the AcDbBlockTableRecord. This is where we are going to execute a write operation, as indicated by AeDb::kForWrite. At this point, we dontt need the block table anymore, so we can close the block table pointer pBlockTable. It's very important to close all objects when we no longer need them. ‘We then create a local variable lineld of type AcDbObjectid to hold the object ID returned by the call to appendAcDbEntity(). “The function appendAeDbEntity() expects an AcDbEntity* pointer as its second argu ‘ment, yet we passed in a pointer to an AcDbLine*. Why does the function allow us do this without complaining? This is feasible because of inheritance. The AcDbLine class is derived from the AeDbEntity class, and therefore the class AeDbLine is an ‘AcDbEntity. We then close the block table record and the line entity pointed to by pLine. Finally, if all went well, we return the object ID for the line. Why do we not call delete on pLine? After all, we allocated memory with a call to new. After the call to the appendAcDbEntity() function, AutoCAD owns the line entity and is respon- sible for memory management from that point onwards. Prior to the call, we owned the entity: As soon as AutoCAD owns the entity, it becomes visible on the screen. Now let’ step back from the code and look at the steps required in placing an enti- ty in the AutoCAD database. This is a process that will be executed repeatedly for adding entities to AutoCAD. Undertending AutoCAD’: Database and Entity Structure 133, a |. Create a new entity. 2. Open the block table for a read operation. 3. Open the block table record for a write operation and find either ACDB_MODEL_SPACE or ACDB_PAPER SPACE or one of the lay- outs. 4. Close the block table. 5. Append the entity to the block tale record. 6. Close the block table record. 7. Close the entity object. Creating the circle entity is a process identical to the line creation function that we talked about previously. Here is the code for creating the circle: AcDbObjectId createCircie() ( AcDbDatabase *pCurdb; AcGePoint3d center(9.0, 3.0, 0.0); AcGeVector3d normal(0.0, 0.0, 1.0); AcDbCircle *pCire = new AcDbCircle(center,¢ normal, 2.0 pCurDb = acdbHostAppl icationServices()@ ~>workingDatabase(); AcDbBlockTable *pBlockTable; acdbCurDwg()->getBlockTable( pBlockTable, AcDb: :kForRead); AcDDBlockTablekecord *pilockTableRecord; pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, ‘AcDb: : KForWrite) ; pBlockTable->close(); AcDbObjectId circleld: pBlockTabl eRecord->appendAcDbentity(circleld,& pCire): pBlockTableRecord->close(); PCirc->close(): return circleld; } ‘The only additional code here is the AeGeVector3d variable normal. This is required ‘because the circle needs to know what plane it is oriented in. ‘Now let's look at the code for creating a new layer named “arx”: Void createNewLayer() t AcDbDatabase *pCurDb: AcDbLayerTable *playerTable: pCurDb = acdhHostApplicationServices()a —>workingDatabase(); pCurDb->getLayerTeble(pLayerTable, & AcDb: :kForWrite); AcDbLayerTableRecord *playerTableRecord = newd AcDbLayerTableRecord: pLayerTableRecord->setName(“ARX” // The linetype objectId default is 0 which ts // not a valid Id. So, it must be set to a // valid Id...say for the CONTINUOUS Tinetype. // Other data members have valid defaults so // they can be left alone. u AcDbLinetypeTable *pLinetypeTbl ; pCurDb->getLinetypeTable(pLinetypeTbl.& AcDb: :kForRead) : AcDbObjectId ItypeObjld: pLinetypeTbl->getAt(*CONTINUOUS”, ItypeObjId); playerTableRecord- >setLinetypedbjectId(ItypedbjId) ; playerTable->add(pLayerTableRecord) ; pLineTable-close(?; playerTable->close(): playerTableRecord->close(): } The code for creating a new layer is not that far removed from the code for creating the entities that we saw earlier. Here we open the layer table for a write operation: AcDbLayerTable *playerTable; pCurDb->getLayertable(pLayerTable, AcDb::kForWrite); ‘After setting its layer name and linetype, we add the new layer table record to the data~ base. Think of layer table records as the ‘entities’ of the layer table. Note the warning about the layer linetype. We open the linetype table for a ead oper~ ation and get at the CONTINUOUS linetype. Remember, the CONTINUOUS. linetype always exists. Finally, we add the new layer table record to the layer table and follow by closing the layer table and the kyer table record. This is not too terribly dif- ferent from creating entities. I hope you are beginning to see processes here. Understanding AutoCAD’ Databae and Entity Structure i ENING AND CLOSING OBJECTS 1 cannot overstate the importance of opening and closing objects in AutoCAD. New instances of an object are considered to be open. An object cant be closed until it has been added to the database. In addition, you own the object and can freely delete it at any time before the object is added to the database. After that, the data~ base owns it, and deleting the object will cuse AutoCAD to terminate. ADDING A GROUP TO A GROUP DICTIONARY Groups in AutoCAD are very similar to selection sets except that groups are saved with the drawing, To create a group, the AutoCAD user can use the GROUP command and name the group. During the selection process, at the Select Objects: command prompt, the user can type “group”, at which time the user will be asked to enter the name of the group to search for, that is, a text search key string. If the group exists, the entities will be highlighted. A group is a named collection of database entities. A ‘g70up can also be created through Object ARK and added to the ACAD_GROUP dic- tionary. Then the name of the group is a text searchable key string. A single entity can belong to different groups but it can be represented only once in a particular group. So let’s add the entities that we created earlier toa group and then add the group to the GROUP dictionary, as shown in the following code fragment: void createGroup(AcDbObjectIdArray& objIds, char*& PGroupName, AcDbDatabase *pDb) ( AcDbGroup *pGroup = new AcDbGroup( pGroupNane) ; for (int i = 0: 4 < objIds.length(): i++) ( PGroup->append(objldsl1]); // Put the group in the group dictionary.é which resides // in the named object dictionary. us AcDbDictionary *pGroupDict; pDb->getGroupDictionary(pGroupDict, AcDb: :kForWrite) ; ‘AcDbObjectId groupld; PGroupDict->setAt(pGroupName, pGroup, groupld); PGroupDict->close() pGroup->close(): 136 This function takes three parameters: an AeDbObjectldArray reference in objids (which are the object IDs of the entities that make up the group stored in an array), a character string in pGroupName (which is the name of the group in AutoCAD), and finally a pointer to an open AcDbDatabase in pDb (the current database or some other open database for that matter). Every object in AutoCAD has an object ID, namely AeDbObjectld. The string passed into the function is the name of the group, which will become its key search string. We create a new group with a call to the new operator and the kind of object that we want to create, in this case an AcDbGroup object, a follows: AcDbGroup *pGroup = new AcDbGroup(pGroupName) ; ‘The AeDbObjectidArray passed into the function contains the IDs of the line and circle objects, which we append to the group pointed to by pGroup as follows: for (int 1 = 0; i < objids.length(): 144) ( } Now we need to access the already existing GROUP dictionary, which we open for ‘awrite operation as we are adding entities to it, shown as follows: // Assume pDb is a valid database AcDbDictionary *pGroupDict; pDb->getGroupDictionary(pGroupDict, AcDb::kForWrite); Now we need to add the group object to the GROUP dictionary with the following call: pGroup->append(objIdsCil); AcDbObjectId pGroupId; PGroupDict->setAt(pGroupName, pGroup, groupld); Don't forget to close your objects. Everything that we have done so far has consisted of AutoCAD database operations. ‘As you can see, they are not far removed from ordinary database operations. We cre~ ated a line and a circle entity and added those entity records to the Model Space record of the block table database. We created a new layer record and added it to the layer table database. Finally, we created a group from the line and circle and created a group record, which was appended to the group dictionary. I've thrown a lot of code at you without much explanation about what was going on. What I wanted to demonstrate was that regardless of what we did in the code samples, we always carried out a sim- ilar type of process. It is the process that I want you to understand. Now let’ take a step away and discuss in detail AutoCAD's database and operations from an ObjectARX point of view. Understonding AutoCAD’ Database and Entity Structure 37 —————____. AUTOCAD’S DATABASE STRUCTURE All databases have tables and records and AutoCAD treats the drawing structure as, a database. A drawing has the following structure: + The layer table and its records—AcDbLayerTable, ‘AcDbLayerTableRecord. AcDbBlockTableRecord. All entities in AutoCAD (visible objects) belong to the block table records. The block table contains two special records, namely “MODEL, SPACE” and““*PAPER_SPACE”, and itis to these records that all AutoCAD entities belong. + The symbol tables and their record types, which were listed earlier + The Named Object dictionary, which contains the GROUP dictionary and the MLINE style dictionary. From an ObjectARX point of view, the layer table and the block table are considered to be part of the symbol tables, but in illustrations in the ObjectARX documentation they are shown as separate (not that this is anything to be concerned about). Every ‘object that we have talked about so far is a Database Resident Object (DRO) with the following exceptions: the AcDbObjectldArray, which is a collection class, and AcGePoint3d, which is part of ObjectARX’s geometry library. The reason AcDbObjectidArray is prefixed with AeDb- is that it is a tool that interacts with Database Resident Objects. You will see the acronym “DRO” used often in the ObjectARX documentation—now you know what it is. Every object in AutoCAD is considered a class in ObjectARX. Each object (clase) contains constructors, destruc tors, methods (functions) and properties and it forms part of a class tree hierarchy, of which AcRxObject is the root class of most of the classes that make up ObjectARX. DATABASE RESIDENT OBJEC So just what are the Database Resident Objects? Every single object that makes up the Database Resident Objects is prefixed with AeDb. These objects are grouped in the following broad categories: symbol tables, symbol table records, entities, basis, and raster classes. All of the Database Resident Objects are derived from the AcDbObject class, which in turn is derived from the AeRxObject class, which is the root class. Figure 4.5 shows the symbol tables and the symbol table records. [aappangect RebkSyaborTabie [acohSyaboiTanienecora [RcphabetractViewTable DunbatractViewTahleRecord [acDaviewt [acbviewfanleRecord L_[repwviemportrane DaviewportTablenecora [AcDbLinetypeTabie [acdbLinetrpeTableRecord [acbLayertanie DhLayerFableRecord [acbotentStyleTanle DhteatStylefableRecord [ncpbuestabie [aconuesTamleRecora [AcohRegkppTabie [AcDhneshppYabieRecora [AcouDindtylevabie [acpeDsnstyieTanienecord [AcDbBLockTanle {ncbenockTanieRece Figure 4.5. ObjectARX Symbol Table and Symbol Table Records Hierarchy ‘As you can see, the base class for the symbol tables is AcDbSymbolTable, and for the symbol table records it is AeDbSymbolTableRecord. The AcDbViewTable and AcDbViewportTable classes are derived from the AcDbAbstractViewTable and the AcDbViewTableRecord and AcDbViewportTableRecord are derived from AcDbAbstractViewTableRecord. Normally, you use only the derived classes from these classes and not the base classes. Every single one of the classes under the sym- ‘bol tables has creation, query, and edit functions (methods). The creation functions are usually constructors, which are automatically called when variables of the appropri- ate type are created. ‘To use any of the symbol tables and symbol table records, you must include dbsymth.b in your application: #include ‘The query functions contain the following: + AcDbtitfTables:getAe() Open the appropriate table for read, write or notify operations. + AcDbit#fTable:has() Does the appropriate table have the item specified in the string parameter? Understanding AutoCAD’ Database and Entity Structure 139 ————___. + AcDbitt##Tablenewlterator() Create an iterator to step through the tables (more on iterators ater). Substitute the appropriate symbol name for the ### characters. ‘The edit functions contains the following: + AcDbitti#fTable:addQ, ‘Add a symbol table record to a ‘able, Substitute the appropriate symbol name forthe ###f characters, The symbol table records are where you change the properties of such items as layer color, linetype, and text style in addition to creating new records to be added to the appropriate symbol table. Al of the symbol table records have creation, query, and edit functions. The creation functions are typically constructors. Here are the query functions for the AcDbLayerTableRecord. + AcDbLayerTableRecord::color() What color is the layer? + AcDbLayerTableRecord:isFrozen() Is the layer frozen? + AcDbLayerTableRecordsisLocked() Is the layer locked? + AcDbLayerTableRecord:isOf) Is the layer off + AcDbLayerTableRecord:linetypeObjectid() What kind of linetype object is the layer associated with? + AcDbLayerTableRecord:VPDFLTO, ‘What is the Viewport Visibility defauit? Here are the edit functions for the AeDbLayerTableRecord. + AcDbLayerTableRecord:setColor() ‘Change the lyer’s color + AcDbLayerTableRecord:ssetlsFrozen() ThawiFreeze + AcDbLayerTableRecord:setisLocked() Lock/Unlock + AcDbLayerTableRecord:setisOf ono + AcDbLayerTableRecordisetLinetypeObjectld() Change the layers asso- ated linetype object + AcDbLayerTableRecord::setVPDFLT() ‘Change the layers visibil- ity default In essence, the symbol table records are where you change the properties of the record objects in the database. For example, suppose you wanted to change the line~ type of a layer. First you would check, using the has() method, the AcDbLinetypeTable to see if it has the appropriate linetype. Then if tt has the linetype you are looking for, you would use the getAt() method to retrieve the linetype object ID. You then pass the linetype object ID to the setLinetypeObjectld() method of the ‘AcDbLayerTableRecord and change the linetype for that particular layer. In the basics category of the Database Resident Objects, we have the following classes: Basics Description Include file ‘AcDbDictionary | Database Resident Object dictionary, | dbdice which maintains a map between text strings, and danbase objects. ‘AcDbGroup ‘This class represents a collection of | dbgroup.5 contitics referred to by a single name. All AcDbGroup objects belong to a dictionary object. ‘AcDbMlineStyle | AcDbMlineStyle class objects are used | dbmstyl.d to store the information about the number, linetypes, and offsets of mline Tine patterns to be used by AcDbMline entities. ‘AcDbXrecord “The AcDbXrecord class is a data second storage class, which was implemented primarily to allow ADS and AutoLISP programs a means to store data in pieces larger than the 16 kb per object limit of xdata, ‘AcDbProxyObject | This abstract chss provides an interface | diproxy to the description data for the objects stored within ProxyObjects in drawings. Table 4.2 Classes in the Basics Category for Database Resident Objects All the objects listed within the basics category are derived from AcDbObject and all have creation, query, and edit functions. The AeDbProxyObject class contains enti- ties that were created with external applications that are not available with the cur rent user of AutoCAD, so for the object to represent them on the screen they become AcDbProxyObjects. A typical scenario would be a user who opens a [Understanding AutoCAD’ Database and Entity Structure AV [AcbbObject Basics ‘AcbhDictionary [acDbGroup ‘AcDEMLineStyle [acDbxrecord [AcDbProxy0bject Raster [AcbbDimStyleTableIterator [AcbbDimStyleTableIterator [AcDbBLockTableiterator Figure 4.6 ObjectARX Basics and Raster Classes ‘Mechanical Desktop drawing with standard AutoCAD. The Mechanical Desktop object would appear as an AcDbProxyObject; however, if the user purchased Mechanical Desktop and opened up the drawing, the Mechanical Desktop object ‘would appear as a normal object. ‘The raster objects are for placing different kinds of graphic format files within the AutoCAD drawing, and the raster classes are represented by the following classes: + AcDbRasterVariables + AcDbRasterlmageDef + AcDbRasterlmageDefReactor Within the Entities area, all of which are derived from AcDbEntity, are a number of sub-categories. I will list the sub-categories here and talk about them when we dis- cuss Entities in detail later. + Basics + Basics Complex + Curves + Curves Complex. + Dimensions + Subentices + Bracket Entities + ACIS Based Entities (Solid Modetng and Mechanical Desktop). Before we look at the definition of the various functions outlined above, we will look at some of the common return codes you will encounter. COMMON RETURN CODES All ofthe functions we have talked about have return codes, so let's look at these return codes first and where they are defined. The following are the most common return codes. They all reside in the header files of the \OdjectARX\nc folder. DO NOT mod- ify the contents of these header files. Acad::ErrorStatus defined in acdhb Adesk::Boolean defined in adesk.b ‘The key to finding these definitions is :o look at what comes before the double colons; in our case, we have Acad and Adesk. These are structures. What comes after the double colons is usually an enumerated type or a typedef, which is the member of the structure, Here is the Acad:ErrorStatus partial listing from acdb. A: struct Acad ( enum ErrorStatus (e0k ¢ = 0, ellot ImplementedYet - 1 ellotApplicable 2 3 elnval idInput - Lots more Understanding AutoCADs Database and Entity Structure (43 $$ Cunes Dimensions ‘Complex ‘Complex [aeobPoiygeatech [acsesapoipiane [acsboogece Subenitios ACIS Rasa eevee paciaboar comes Figure 4.7 ObjectARX Entity Classes eNul extents = 312, e€xplodedgain = 313 A ‘Tjust want to make you aware that there isa header file acdhabbr. that contains abbre~ viations for the definitions that are found in the acdé.h file. Justin case you run into them, here is a partial list of what you will find in the acdbabbr.h file. Note that there are many of them. typedef Acad: :ErrorStatus ErrorStatu: penMode OpenMode: ‘leVersion FileVersior typedef Achb::OsnapMace —OsnapMade: Ifyou include the acadabbr.b file in your application, it will include the acd&b fle for you. This allows you to drop the AeDbi: prefix. ‘Here is the Adesle:Boolean partial listing from adesk.b, Note the enumerated types kFalse and kTrue. struct Adesk ( // The types Int, Inti6 and Int32 will bee conditionally compiled // to guarantee that each one represents ang integer type of exactly 7/ 8, 16 and 32 tits respectively. These ared to be used only when // the EXACT size of the integer is critical. uv typedef char [nt 8; typedef short [nt 16: typedef Tong [nt 32: uw 1] The unsigned versions of the above types. uw typedef unsigned char UInt8;: typedef unsigned short UInt16; typedef unsigned long UInt32; /1 Convenient abbreviations (use optionally). uw typedef unsigned char uchar; Understanding AutoCADs Database and Entity Structure 4S $$$. typedef unsigned short ushort; typedef unsigned int uint typedef unsigned long ulong; // Logical type (Note: never use int wheng Boolean is intended!) uv typedef int Boolean; enum { kFalse = 0, kTrue = 1}; h ‘There are many other return type codes, which we will cover as we get to them. SYMBOL TABLE FUNCTIONS ‘We will now return to the symbol table furctions and take a closer look at their def- initions and how they were used in the code samples we saw eatlie, For all of the sym- bol tables, we can open the table for a read, write or notify operation with a _getit##fTable() function. Substitute the appropriate table name for the #### to reference the table you are interested in. Let’s look at opening the layer table again, as follows: // Assume pOb is a valid database AcDbLayerTable *pLayerT31; pOb->getLayerTable(pLayerTbl, AcDb. ForRead); Here is the definition of getLayerTableQ: Acad: :ErrorStatus getLayerTahle(AcNbLayerTable+& pTable, AcDb::OpenMode mode); ‘The getLayerTable() function opens the database’s layer table in the mode specified by mode. The pTable pointer is filled in with the address of the layer table. It returns Acad:eOk if open is successful, which is one of the Acad:ErrorStatus items that we discussed earlier. The open mode can be one of three modes: AcDb::kForRead, AcDb::kForWrite, and AcDb::kForNotify. These modes can be found in the aed file, which is partially reproduced here. struct AcDb enum OpenMode { kForRead e kForWrite kForNoti fy more here In the kForRead mode, an object can be opened for ead as long as the object is not already open for write or for notify. Here you do not intend to modify the object, you are essentially doing a query on the object. In the kForWrite mode, an object can be opened for write if itis not already open—here you intend to change the properties ofan object. In the kForNotify mode, ar. object can be opened for notification as a result of a change in another object that affects the current object, and any required action can be taken as a result. Notifications are the subject of a later chapter. So when we want to open one of the symbol tables, we have getLayerTable(), getLinetypeTable(,, getTextStyleTable() :tc. Prior to calling the get function, we cre- ate the appropriate pointer object, which is initialized during the get function. We also specify in what mode the symbol table isto be opened. Before I move on, I want to revisit the getBlockTable() function that we saw earli- erin one of the code samples. Here isa code fragment from the code that we saw ear- lier: // Assume pDb is a valid database AcDbBlockTable *pBlockTable; pDb->getBlockTable(pBlockTable, AcDb::kForRead); AcDbBlockTableRecord *pBlockTableRecord; pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite) ; pBlockTable->close() Here we open the block table for a read operation because we are going to add enti- ties to the block table record, which we open for a write operation, Remember that all entities belong to the block table Model Space record, the block table Paper Space record, or one of the many layouts. That is why we open the block table to find the Model Space record or the Paper Space record. There are a number of string con- stants defined in the acdhh file, of which ACDB_MODEL_ SPACE and ACDB_PAPER SPACE are just two. Here is a partial listing of the acd file: // String Constants uw fidefine ACDB_SERVICES /*MSGO*/"AcDbServices” fidefine ACDB_MODEL_SPACE 7*MSGO*/"*MODEL_SPACE” Understanding AutoCADs Database and Entity Structure \47 $$$ define ACDB_PAPER_SPACE /*MSGO*/"*PAPER_SPACE” define ACDB_NULL_HANOLE 7*MSGO*/*\0\0\0\0\0\0\0" fidefine ACDB_BROKEN_HANDLE /*MSGO*/" FEFFFEFE” fidefine ACDB_OPEN_8RACE_STR raMsgo*s"(* iidefine ACDB_OPEN_BRACE_CHAR PaMSGO*/"(* iidefine ACDB_CLOSE_BRACE_STR sausegoxs")" iidefine ACDB_CLOSE_BRACE_CHAR />MSGO*/")* define ACDB_GROUP_DICTIONARY /*MSGO*/"ACAD_GROUP” ‘#define ACDB_MLINESTYLE_DICTIONARY 7*MSGO*/"ACAD_MLINESTYLE” ‘You could just as easily get at the block table record with the following call: AcDbBlockTableRecord *pBlockTableRecord: pBlockTable->getAt(“*MODEL_SPAC pBlockTableRecord, AcDb::kForWrite); pBlockTable->close(); ‘Whichever way you want to make the call, it’s up to you. Just in case you see both methods, now you know why. SYMBOL TABLE QUERY AND EDIT FUNCTIONS For the symbol tables we have the query furctions has, getAt(), and newlterator() and the edit function add(). I will discuss these functions in terms of the layer table, but please note that they are almost identical for all the symbol tables. Here is the def inition of the query has() function: Adesk::Boolean has(const char* name) const ‘The has() function returns Adesk:kTrue if the table contains a record with a name that matches name; otherwise, it returns Adeske:kFalse, The name argument is not ‘case-sensitive; here is an example of its use Tf (pLayerTbI->has(“MYLAYER")) fi 7/ Do something ‘Ifhas() returned Adesk:kTrue, we would use the getAt() function to open a sym- bol table record for a read or write operation or to return the object ID of the record. ‘The getAt() function is an overloaded function, as the following definitions show: Acad::ErrorStatus getAr(const char* entryName,& AcDbObjectId& recordid Adesk: :Boolean getErasedRecord) const ‘The getAt() function returns the AcDbObjectld of the record specified by entryName in the value recordld if a matching record is found. If getErasedRecord is true, then matches against erased records are possible. Possible return Acad::ErrorStatus codes are Acad::eOk, Acads:eKeyNotFound, and Acad:sePermanentiyErased. Acad::ErrorStatus getAt(const char* entryName, # AcDbLayerTableRecord*& phecord, AcDb: :OpenYode openMode, Adesk: :Booleang openErasedRecord) const his second overloaded version of the getAt() function opens the record specified by ‘entryName and returns a pointer to the opened record in pRecord if a matching record is found and the open operation (using openMode) succeeds. If openErasedRecord is true, then erased records will be opened. Possible return Acad:ErrorStatus codes are Acad:eOk, Acad:eKeyNotFound, ‘AcadiiePermanentlyErased, Acad:ieAtMaxReaders, Acad::eWasOpenForNotify, ‘Acad::eWasNotifying, Acad:eWasOpenForUndo, Acad::eWasOpenForWrite, and Acad:eWasOpenForRead. As you can see, there are many possible variations to the return code. Here is an example of the usage of getAt() AcDbLayerTableRecord *plyrTbIRecs If(playerTable->getAt("MYLAYER", _//_ Layer string plyrTbiRec, // Layer Table Record AcDb::kFordrite, // Write mode Adesk::kFalse // Skip purged layers ) == Acad: :e0k) ( ) plyrTbIRec->setIsCff(Adesk: :kTrue); The next query function is newlterator(). We have not talked about iterators yet, and [guess we might as well talk about them aow. All of the symbol table classes have an associated iterator object. The iterator objects are not Database Resident Objects; they are just helper classes that allow you to traverse the associate table and query the object of that class. Each symbol table has a corresponding iterator that you can create with [Understending AutoCAD’ Databae and Entity Sructure 49 $$$» the AcDbitifTable::newlterator() function. Iterators belong to the associated tables, Figure 4.8 shows alist of the symbol table iterators. ‘Step through thg Bloc Table Record [AcbeBLockTableRecorditerator | oiicown Ma Seck Tone Record Step through en “insert Object bBleckReferenceiterator | AlsCADDrowngteck end mes ern ss aeseee ‘reads block information and sttributes: [AcDhsyabo1Tablerterator AcDhAbstractViewTahleIterator [AcDMViewTableIterator [AcDhViewportTablerterator ‘Symbol Teble testers derived ftom AcDbSyabol Tableieerator [AcDhLinetypeTableTterator [AcDbLayerTableTterator [AcDbTextStyleTablerterator | Nae: Alotthe tert shownhere ‘xe heber classes ond are NOT ‘Served romAcbbob3 ect. [AcDbUCSTabieTterator [AcDbReyAppTableiterator [AcDuDinStyleTablerterator [acDbBlockTableIterator Figure 4.8 ObjectARX Symbol Table and Block Herators ‘There are also other iterators apart from the symbol table iterators. Common func- tions for iterators are listed here. Iterator Function Description ‘AcDbSymbolTablelterator::done() Returns Adesk::kTrue if the iterator has been positioned past either end of the symbol table; otherwise it returns Adesk:kFalse. AcDbSymbolTablelterator::getRecord() | Opens (in openMode) the record at which the iterator is positioned, setting pRecord 10 point to the opened record. ‘This function is also defined in and usually used by the class derived from ‘AcDbSymbo!Table. ‘AcDbSymbolTablelterator::getRecordld() | Reruns the AcDbObjectld of the object at which the iterator is positioned. AcDbSymbo!Tablelterator::seek() ‘Used to position the iterator at the record identified by the record's AcDbObjectld. ‘AcDbSymbolTablelterator::start() ‘Used to initialize the position of an iterator to either the beginning or the end of the table, ‘AcDbSymbolTablelterator::stepQ) ‘Moves the iterator to the next (or previous) record inthe table, ‘Table 4.3 Common Functions for Iterators Perhaps the best way to explain iterators is by example, as shown in the following code, which creates an iterator that walks through the symbol table records in the line- type table. Ie obtains each record, opers it for read, obtains the linetype name, closes the record, and then prints the linetype name. At the end, the program deletes the iterator. Remember, you are responsible for deleting the iterator when you are finished with it. Understanding AutoCAD’ Database and Entity Structure $$ void iterateLinetypes() ( AcDbDatabase *pCurd AcDbLinetypeTable *pLinetypeTbl: pCurdb = acdbHostApplicationServices()¢ >workingDatabase(): pCurDb->getLinetypeTable(pLinetypeTb! .& AcDb: :kForRead) : // Create a new iterator that starts at table // beginning and skips deleted. " AcDbLinetypeTablelterator *pLtIterator: pLinetypeTbl ->newlterator(pLtIterator): 71 Walk the table getting every table recorde and JJ printing the linetype name. us AcDbLinetypeTableRecord *pLtTableRcd; char *pLtNam for (; !pLtIterator->done(); pltiteratore >stepQ) t pLtIterator->getRecord(pLtTablercd, & AcDb: :kForRead): pLtTableRcd->getName(pLtName) : pLtTableRcd->c'ose(): acutPrintf(“\nLinetype name is: %s".8) pLtName): free(pLtName); ) delete pltIterator: pLinetypeTbI->close(); In this code example we open the linetype table for a read operation, after which we create a new iterator. (Remember, the linetype table owns the iterator.) We then cre ate a linetype table record pointer and use the iterator functions in the for loop to step through the linetype table records and get the linetype name of each of the records. ‘Note that each time we get a record, we close it when we are finished with it. After we exit the for loop, we delete the iterator and close the linetype table. We can use an iterator on any of the symbol tables. ‘The add() function is used to add a symbol table record to the symbol table. Here is the add@) function for the layer table. This function is overloaded. 152 Acad::ErrorStatus add(AcDbLayerTableRecord* pRecord) his function adds the record pointed to ky pReeord to the layer table. Possible return Acad:ErrorStatus codes are AcadeOk, Acad:eOutOfMemory, ‘Acad::eDuplicateRecordName, and Acad::eNoDatabase. Acad: :ErrorStatus add(AcDbObjectId& recordid, AcDbLayerTableRecord* pRecord) ‘The add() fanction adds the record pointed to by pRecord to the layer table. If suc- cessful, the recordld is set to the AcDbObjectld of the record in the database. Possible return Acad:ErrorStatus codes are Acad:eOk, Acad:ieOutOfMemory, ‘Acad:ieDuplicateRecordName, and Acad:seNoDatabase. Here is a code fragment outlining the ase of the addQ function from an earlier example: // Assume pDb is 3 valid database AcDbLayerTableRecord *pLayerTableRecord = news AcDbLayerTableRecord; playerTableRecord->setName(“ARX”): AcDbLinetypeTable *pLinetypeTbl ; pDb->getLinetypeTable(pLinetypeTbl,& AcDb: :kForRead) : AcbbObjectId ItypeObjId: pLinetypeTb1->getat( “CONTINUOUS”, 1typeObjId): playerTableRecords >setLinetypedbjectId(ItypedbjId); playerTable->add(pLayerTableRecord) ; playerTable->close(); playerTableRecord->close(); ‘We have covered a great deal of ground here. We have looked at the Database Resident Objects of ObjectARX. We covered all the symbol tables, symbol table records, and the process of opening and closing objects. We looked at a number of the header files, dictionaries and the GROUP dictionary. We discussed common return’ types and looked at the symbol table iterators (helper classes, not DRO). Before we ‘move on to ObjectARX’s “Entities” Database Resident Objects, le’ create a sample application that uses the symbol table, symbol table records and symbol table iterators. In this application we will demonstrate how to create a new layer and set its color and linetype, in addition to demonstrating how to use an iterator. Take note that if you know how to use one symbol table, symbol table record, and symbol table record iter Understonding AutoCAD’ Database and Entity Structure 53 ——————— ator, the same techniques apply to all symbol tables, records, and iterators So let’ lst the code and discuss its elements after. Here is the listing for Ch4_4.opp: J Chd_1.cpp : Initialization functions 7 CH4_1.cpp // by Charles Mc Auley // “Programming AutoCAD 2000 with ObjectARX” // This application demonstrates how to add // new layer table records to the layer table. J/ It also demonstrates how to use a layer table // iterator iHinclude “ #include “ iHinclude “resource.h” HINSTANCE _hd11Instance =NULL : // This command registers an ARX command. void AddCommand(const char* cmdGroup, const chared cmdint, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idlocal = -1); J/ NOTE: DO NOT edit the following lines. TH((AFX_ARK_MSG void InitApplication(); void UnloadApplication(); ZT) APX_ARK_MSG J NOTE: DO NOT edit the following lines. 77 AAPX_ARX_ADDIN_FUNCS 7) )AFX_ARX_ADDIN_FUNCS TTT LTT LLL MUTT // DLL Entry Point extern “C” BOOL WINAPI D11Main(HINSTANCE hinstance, DWORDS dwReason, LPVOID /*1pReserved*/) { if (dwReason = DLL_PROCESS_ATTACH) { -hdllInstance = hInstance: } else if (dwReason == DLL_PROCESS_DETACH) { ) return TRUE; // ok ) MTT LLL IAT // ObjectARX EntryPoint extern “C" AcRx: :AppRetCode acrxEntryPoint(AcRx: :AppMsgCode msg, void* pkt) { switch (msg) { case AcRx::kIni tAppMsg: 7/ Comment out the following line if your 1/ application showld be locked into memory acrxDynamicLinker->unlockApplication(pkt); acrxDynamicLinker->registerAppMDIAware(pkt) ; Initapplication(); break: case AcRx: :kUn] oadAppisg: UnloadApplication(): break: ) return AcRx:: } // Init this application. Register your // commands, reactors... void Initapplication() ( J/ NOTE: DO NOT edit the following lines. ZCCAPX_ARX_INIT. AddCommand(™CH4_APPS", “CNL™, “CNL”, @ ACRX_CMD_MODAL, cn) 11) YAFX_ARX_INIT 1/ T0D0: add your initialization functions acutPrintf(“Enter \"CNL\” to create a newe Jayer.\n"); } // Unload this application. Unregister all objects 71 registered in InitApplication. void UnloadAppl ication: ) ( J NOTE: 00 NOT edit the following lines. TCOAFX_ARX_EXIT. acedRegcmds->removeGroup(“CH4_APPS”) 5 71 YAFX_ARX_EXTT. acutPrintf(“%sts”, “3oodbye\n”, “Removing commandé group \"CH4_APPS\"\n" ) // This functions registers an ARX command. // Tt can be used to read the localized command name RetOK: Understanding AutoCAD's Databae and Entity Structure 185 $$ // from a string table stored in the resources. void AddCommand(const char* cmdGroup, const chart cmdInt, const char* cmdloc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idLocal) { char cmdLocRes(651; /1 If idlocal is not -1, it’s treated as an ID for // a string stored in the resources. if Cidlocal != -1) ( J Load strings from the string table andé register the command. :LoadString(_hdllInstance, idLocal, cmdLockes,& 64); acedRegCmds->addComnand(cmdGroup, cmdInt.& emdlocRes, cndFlags, cmdProc): ) else 1] idlocal is -1, so the ‘hard coded’ // localized function name is used. acedRegCnds->addComnand(cmdGroup, cmdInt, ¢ cmdLoc, cmdFlags, cmdProc); } // This function called by ‘cnl()’ which is found on 71 Ch4_Conmands .cpp Adesk::Boolean getNewLyrLtId(AcDbObjectId& Itypeld) fi J/ We know that the linetype ‘CONTINUOUS’ exists // so give the user a choice of pressing [ENTER] // for ‘CONTINUOUS’ linetype or a listing option /1 to show all the linetype available or input a // Vinetype name. char kwE201: char linType(50): char *pLtName; AcDbDatabase *pCurDb = NULL AcObLinetypeTable *pltTabt AcDbLinetypeTableRecord *pLtTableRcd; AcDbLinetypeTablelterator *pLtIterator; int r PCurDb = acdbHostAppl icationServices()o —>workingDatabase(); acedInitGet(NULL, “Name List Continuous rc = acedGetkword(“\nLinetype - [Name/Li st /Continuous}: kw) switeh(re) { case RTCAN: acutPrintf(“\nUser canceled return Adesk: :kFalse: case RTERROR’ acutPrintf("\nFailed in getNewlyrLtId(e function. “); return Adesk: :kFalse: break: case RTNONI pCurDb->getLinetypeTable(pItTable,& AcDb: :kForRead); pltTable->getAt("CONTINUOUS”, Itypeld): pltTable->close(); return Adesk: :kTrue: if(stremp(kw, “Name ( re = acedGetString(0, “\nEnter linetype? string name: “, linType)s switeh(re) ( case RTCAN: case RTERROR: acutPrintf(*\nError in retrievinge! linetype name.”); return Ades| breaks case RTNORM: if(linTypel0] = *\0") { acutPrintf(“\nNo layer name given.”); return Adesk: :kFalses ) 1] Check to see if the linetype II exists pCurDb->getLinetypeTable(pltTable. & AcDb: :kForRead) if(p1tTable->has(1inType)) fi I/ Retrieve the AcDbObjecld of thee layer table record = 9) iikFalse; Understending AutoCAD’ Database and Entity Structure |S] $< pitTable->getat(linType, Itypeld): pitTable->close(); return Adesk: :kTrue: ) acutPrintf(“\nLinetype ‘Bs’ does note exist using CONTINUOUS. “, lintype); pltTable->getAt(“CONTINUOUS”, Itypeld); pltTable->close(); return Ades«: :kTrue: break: V1 switch WI if else if(stremp(kw, “List") == 0) i 11 Here we will use a Linetype Tablee Iterator // to ist out all the available linetype // and then ask the user to enter a // valid name for one of the layers acutPrintf(“\nThe following linetypes ared available. “); pCurDb->getLinetypeTable(pltTable, kForRead); pltTable->newIterator(pLt Iterator); for(; !pLtIterasor->done(); pLtIterator- >step0) { AcDb 1/ get the current linetype record frome the iterator, 11 opened for read pltIterator->getRecord(pltTableRcd, AcDb: :kForRead): 11 get the linetype name from thee linetype record // and store it in pLtName pLtTableRcd->getName(pLtName) ; // close the linetype record pLtTableRcd->close(); acutPrintf(“\nLinetype name: %s",¢ pLtName) ; delete [] plttame; VI for /1 Don’t forget to delete the iterator // that’s your responsibility delete pLtiterator: rc = acedGetString(0, “\n\nEnter linetype? string name: “, linType); switeh(re) case RTCAN: case RTERROR: acutPrintf("\nError in retrievings! linetype name.”): return Adesk::kFalses break: case RTNORM: 4#(1inTypel0] = ‘\0") { acutPrintf(*\nNo layer name given.”); return Adesk::kFalse: ) 1] Check to see if the linetype 11 exists pCurDb->gesLinetypeTable(pltTable.@ AcDb: :kForRead): if(p1tTable->has(1inType)) ( 1/ Retriave the AcDbObJecId of thee layer table record pitTable->getat(1inType, Itypeld): pitTable->close(): return Adesk: :kTrues ) acutPrintf(“\nLinetype ‘Bs’ does note exist using CONTINUOUS. linType) pltTable->getAt( “CONTINUOUS”, Itypeld); pitTable->:lose(); return Adesk: :kTrues break; V1 switch ) else if(stremp(kw, “Continuous") == 0) ( // Remember the CONTINUOUS linetype always Understanding AutoCAD Database and Entity Structure 159 // exists in a drawing file. pCurDb->getLinetypeTable(pltTable, & AcDb: :kForRead) ; pltTable->getAt( “CONTINUOUS”, Itypeld): pltTable->close(): return Adesk: :kTrue: } else fi return Adesk: :kFalse: } break; VIL switch return Adesk::kFalse: } 11 This function called by ‘cnl()’ which is found on 17 Ch4_ACommands..cpp void createNewLayer(char* lyrname, Adesk: :UIntl6y clr, AcDbObjectId Itypeld, Adesk::Boolean current) ( 11 We need to check if the layer name exists // Vf the layer name exists, apply the color 71 Vinetype id and wether to make it current // or not. In order to be current it cannot be 11 frozen, so we neec to check for this also. 11 1f the layer name does not exist we juste create // a new layer with the properties contained ing the arguments AcDbLayerTable *plyrTable: AcDbLayerTableRecord *plyrTb1 Record; AcbbObjectId recld: AcCmColor color: color.setColorIndex(cir): // set color toe parameter cir AcDbDatabase *pCurDb = NULL: pCurDb = acdbHostApplicationservices()o —>workingDatabase(); pCurDb->getLayerTable(pLyrTable, AcDb: :kForRead) ; /1 Check to see if the ayer name exists if(plyrTable->has(1yrname) ) ( plyrTable->getat(lyrname, plyrTbIRecord, & AcDb} Forlirite, Adesk::kFalse); // plyrTbiRecord now points at the layerd table record // which was opened for write plyrTb1Record->setIsFrozen(Adesk: :kFalse): plyrTbIRecord->setColor(color): plyrTblRecord->setLinetypeObjectId(1typeld); ) else { // Note how we can change the open mode /1 of the layer table from AcDb::kForRead 11 to AcDb: :kForWr’ te plyrTable->upgradeOpen(): plyrTb1Record = nev AcDbLayerTableRecord; plyrTb1Record->setName(1yrname) : plyrTbIRecord->setColor(color): plyrTblRecord->setLinetypeObjectId(1typeld); plyrTable->add(pLyrTb1 Record) ; ) // Get the layer Table Objectid recld = plyrTb1Record->objectId(): plyrTb1Record->closet BlyrTable-delose(): Set the layer current if current iis equal to Adesk: :kTrue 7/ pCurDb is point to the current /] drawing database // The database AcDbDatabase has a number of // query and edit functions for the header? variables if(current) ( pCurDb->setClayer(recId): } } Here is the listing for the user-defined command ENL in Ch4_1Commands.cpp: MALT LLL LLL // ObjectARX defined commands Hinclude “StdAfx.. #include “StdArx.. // This is command ‘CNL’ void cni() Understanding AutoCAD’: Database and Entity Structure \61 —————— char lyrName(256]; char kwl20]; int re; /7 return code int col; // Color value AcDbObjectId Itypeld; // We need the object id of the Tinetype 11 for layer creation re = acedGetString(0, “\nNew layer name: “.3 lyrName) : switch(re) ci case RTCAN: acutPrintf(“\nUser canceled”); return: case RTERROR: acutPrintf("\nError with input of new layerd name“): return: break: } ifClyrNamel0] == *\0") i acutPrintf(“\nEmpty string for new layer named > invalid * return: ) // Get the layer coler acedInitGet(RSG_NONULL + RSG_NONEG + RSG_NOZERO, ¢ NULL): acedGetInt(“\nLayer color (1 - 7 only) “, &col); if(col > 7) ( acutPrintf(“\nToo high a color value. “): return: ) // Get the layer line type default = ‘CONTINUOUS’ if(getNewLyrLtId(1typetd) ) ( acutPrintf("\nUnabl2 to retrieve the linetyped AcDbObjecId *); return: ) // Now that we have @ layer name, layer colord 162 and line type // Ask the user if he wants to make the news layer the current layer acedInitGet(NULL, “Yes No” rc = acedGetKword("\nMake new layer currenté - LYes/Nol: “, kw); switch(re) ( case RTCAN: acutPrintf("\nUser canceled”); break; case RTERROR: acutPrintf(“\nError in acedGetkwordg function” break: case RTNONE: // If the user presses the CENTER] key 1/1 we take this as @ “Yes” createNewLayer(lyrName, col, Itypeld.@ Adesk: :kTrue); case RTNORM: if(stremp(kw, “Yes") —= 0) ( createNewLayerilyrName, col, Itypeld. Adesk: :kTrue); ) else if(stremp(kw, “No”) == 0) ( createNewLayer‘lyrName, col, Itypeld,@ Adesk: :kFalse): ) break; ) ) ELEMENTS OF SAMPLE APPLICATION CH4. Well, I hope by now that aerxEntryPoint(), initApplication(), and unloadApplication() are old hat! In this application we have three user-defined fanc~ tions: cnlQ, createNewLayer(), and getNewLyrLtid(. Let’s look at enlQ) first, ‘which isthe fanction that gets called asa result of the invoking of the registered com- mand NL on the command stack. In enl() we ask the user for a layer name, which is stored in the string lyrName[256].I believe that, part from alphanumeric char- Understanding AutoCAD's Database and Entity Structure 163 $$$ acters, only a few special characters can be used. In this application I do not test for invalid characters but, in real world applications, you should—I'm leaving that up to you. I will show you in a later chapter, using an MFC-derived edit control, how you can automatically enforce that kind of checking. So, ifthe return is successful, we ask the user fora layer name, color and a linetype in getNewLyrL.tld() (which I will explain later). Finally, we ask if the user wants to make the new layer current. Having gotten this fir, we call the user-defined finction ereateNewLayer() (which I will explain latex) with four parameters, namely layer name, color, linetype ID, and whether we want the newly created layer to be current or not. Without our delving into the user-defined functions getNewLyrLt() and createNewLayer(), the code is pretty straightfor- ward. Now let’s assume that we have successfully retrieved our linetype ID using the func tion getNewLyrL.t(). We will move on to a discussion of ereateNewLayer() first. The getNewLyrLt( function involves the use cfa linetype iterator and I want to discuss that function in detail after I discuss ereateNewLayer() in detail. ‘The createNewLayer() function takes four parameters, as mentioned earlier. The sec- ond parameter is Adesle:Uintl6 elr, which is an unsigned 2-byte integer. This para~ eter is referenced by the AeCmColor cliss as follows: AcCmColor color; color.setColorIndex(clr); // set color to® parameter clr ‘The AcCmColor class is Object ARX’s color model class and about the only function _you will use of this clas is setColorindaxd), which expects an Adcale:UineI6 data type. Valid values range from 0 to 256. I remember 1 to 7 off the top of my head because I spent years using AutoCAD as an engineer, and I remember 256, which is color ByLayer. Next, we open the layer table fora read operation, which returns a point- ex to the current drawing database (AeDbDatabase). Look at how we check to see if the layer name already exists with a call :0 the has() function, If the layer table record already exists, we then open the layer table record with a call to getAt() for a write operation. This gives us a pointer to the layer table record in pLyrTbIRecord. ‘The fourth parameter, Adestc:kFalse, tells us to ignore erased (purged) layer. We use the layer table record pointer to set the properties of the layer: setlsFrozen(), setColor(, and setLinetypeObjectld(). Note that the current layer cannot be frozen, ‘As an afterthought, pethaps I should have checked the status of the fourth parame ter to createNewLayer() to see if it was AdeskatkFalse. If the layer was already frozen, just leave it the way it was. Oh well, the code is written and I guess its too late now. Now here is the interesting part: what if the layer does not exist? Look at how we change the status of the layer table from a read operation to a write operation using upgradeOpen() as shown: plyrTable->upgradeopen(); plyrTbIRecord = new AcDbLayerTableRecord: plyrTb1Record->setNameilyrname) ; plyrTb1Record->setColor(color) plyrTbIRecord->setLinetypeObjectId(1typeld) : plyrTable->add(pLyrTb1 Record) : ‘The upgradeOpen() function applies to any object derived from AcDbObject; this ‘works for entities as well. There is also the opposite version, downgradeOpen(), which changes the status from write to read. So here we create a new layer table record, set some of its properties, and add itto the layer table. Before we close the layer table and, ‘more specifically, the layer table record, we get its object ID. We need this forthe call to setClayer() as shown: if(current) i pCurDb->setClayer(recId); ) ‘This is an example of how you manipulate the system settings for AutoCAD. [Remember that the database also contains header variables, which are located in the ‘AcDbDatabase class and consist of query and edit operations on the variables. These variables are not Database Resident Objects. ‘Now let's go back to getNewLyrLtld0, which gets the linetype of the new (existing) layer and returns Adeste:Boolean if successful. This function also demonstrates how to use a linetype table Iterator. The code here is pretty straightforward—we give the user an option of selecting Linetype - [NamelList/Continuous]}. I want to discuss the List option because that is where the linetype table iterator is. Here is the code for the iterator part: pCurDb->getLinetypeTable(pItTable, AcD pltTable->newlterator(pLtIterator); for(; !pLtIterator->done(); pLtIterator->step()) i kForRead); // get the current linetype record from thee iterator, // opened for read pLtIterator->getRecord(pltTableRcd, & AcDb::kForRead); // get the linetype name from the linetype record 7/ and store it in pLtName Understanding AutoCAD Database and Entity Structure — pLtTableRcd->getName(oLtName) ; /1 close the linetype record pLtTableRcd->close(); acutPrintf(“\nLinetype name: %s”, pLtName); delete [(] pLtName; VW for /1 Don’t forget to delete the iterator // that's your responsibility delete pltiterator; ‘The first operation is to create a new iterator with a call to newlterator(). The iter- ator belongs to the linetype table. The iterator allows us to step through the linetype table and open the records for a read operation, where we get the name of the line type. On each pass through the for loop we close the current linetype table record. One last thing: dont forget to delete the iterator when you are finished with it—that’s your responsibility. Iterators can also be used to step through polylines and blocks (read- ing attributes). AUTOCAD’S ENTITY CLASSES AAs you can see from Figure 4.7, there are many AutoCAD entity classes, all of which are derived from AeDbEntity. In AutoCAD, all entities are classes and as such, each class has member function and properties. Each class contains creation, query, and edit functions as well as specialized functions dependant on the particular class wwe are dealing with at the time. All entities in AutoCAD are handled in terms of the World Coordinate System, with just a few minor exceptions. The AeDbEntity class contains a number of functions that are common to all entities, so let's take a look at these functions first. ‘ACDBENTITY QUERY FUNCTIONS Here is alist of some of the query functions for AcDbEntity: AcDbEnt ity: :blockId AcDbEnti ty: :color AcDbEntity: :colorIndex AcDbEntity: :getécs AcDbEntity: :getGeomExtents AcDbEnti ty: :getGripPoints AcDbEnti ty: :getGsMarkersAtSubentPath AcDbEntity::getOsnapPoints AcDbEntity::getStretchPoints AcDbEntity::getSubentPathsAtGsMarker AcDbEntity: :getTransformedCopy ‘AcDbEntity AcDbEntity AcDbEnti ty AcDbEnti ty AcObEntity AcDbEntity AcDbEnti ty AcDbEntity layer layerid Vinetype linetypeld linetypeScale list subentPtr visibility Of the query functions, I will explain the most commonly used functions. AcDbObjectId AcDbEntity::blockId() const ‘This function returns the AeDbObjectld of the entity's owner, which must be an ‘AcDbBlockTableRecord. Note that all entities belong either to the *MODEL_SPACE or *PAPER_SPACE block table record of the block table or a layout, or to another ‘AcDbBlockTableRecord, which contains the definition of an AcDbBlockReference. ‘You can use this function to get the AeDbObjectid of the appropriate record. Ifthe entity does not have an owner yet, then AcDbObjectd::kNull will be returned. Assuming that we have an AeDbLine *pLine and that the entity isin Model Space, the following code fragment would return the AeDbObjectid of the *MODEL, SPACE (ACDB_MODEL_ SPACE if you prefer) block table record: AcDbObjectId btrid; btrId = pLine->blockId(); AcCmColor AcDbEntity::color() const Adesk::UIntl16 AcDbEntity::colorIndex() const ‘The AcDbEntity:color( function returns the AutoCAD color number of the enti- ty within an instance of AeCmColor. The index value will be in the range 0 to 256, 0 and 256 being special values. The value 0 indicates that the entity uses the color of the Block Reference that's displaying it.I the entity is not displayed through a Block Reference (for example, it's direcly owned by the Model Space block table record) and its color is 0, then it will display as though its color were 7. The value 256 indicates that the entity uses the color specified in the layer table record it references. ‘AcCmColor is an AutoCAD Color Model Color class, which is a wrapper for the AcCmComplexColor class. It is unlikely that you will use the AeCmColor class directly with entities. It has a member function, AcCmColor::colorindex(), that returns an Adeska:Uinelé data type. You will most likely use the AcDbEntitiycolorindex() function, which also retums an Adesk:Uint!6 data type to get the color directly. Here isa code fiagment showing both methods: AcGePoint3d startPt(4.0, 2.0, 0.0); Understanding AutoCAD's Database and Entity Structure $$ AcGePoint3d endPt(10.0, 7.0, 0.0); AcDbLine *pLine = new AcDbLine(startPt, endPt): AcDbBlockTable *pBlockTable; PCurDb->getBlockTable(p3lockTable, AcDb: :kForRead); AcDbBlockTableRecord *p3lockTableRecord pBlockTable->getAt (ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite) ; pBlockTable->close(); AcDbObjectId 1inel pBlockTableRecord->appendAcDbEntity(lineld, pLine); Adesk: :UInt16 colVal; ‘AcCmColor col; col = pLine->color(); colVal = col.colorIndex(): // Here is a more direct method of getting the color colval = pLine->colorIndex(); pBlockTableRecord->close(); pLine->close(); ‘The AcDbEntity:layerQ function retums a copy of the name string in the ‘AcDbLayer TableRecord object referenced by the entity. The calling application is responsible for deallocating the memory used by the returned string. Either the C++ delete [] operator or the C free() function may be used. Here is its definition: char* AcDbEntit WARNING: Calling this function before the entity has had its referenced layer object ID set (that is, it's still st to AcDbObjectld:kNull) will crash AutoCAD. If the ref cerenced layer object ID is AcDbObjectidi:kNull, when the entity is added to a data base it will be set to the object ID of the database's current default layer. ‘The following code fragment shows how to use the AeDbEntityslayer() function using a local character array. Remember that AutoCAD layer names can be 255 characters (only 31 characters in AutoCAD R14). acdbOpenAcDbEntity((AcDbEntity*&)pLine, lineld,& AcDb::kForRead); char lyrStr[256); strepy(lyrStr, pLine->layer() acutPrintf(\nLayer name = %s", lyrStr); pline->close(); layeri) const Look at how we cast pLine, which is an AcDbLine class entity, back to the base class ‘AcDbEntity, which is the base class for all entities. Here is another example of the _usage of AeDbEntity:layer() in which we dynamically allocate the character array using the new operator and deallocate the memory using the delete operator: acdbOpenAcDbentity((AcDbEntity*&)pLine, lineld,& AcDb: :kForRead) char* lyrStr = new char[sizeof(pLine->layer() +3 bi: strepy(1yrStr, pLine->Tayer()); acutPrintf(“\nLayer name = %s”, lyrStr): delete [] lyrStrs pline->close(); ‘The AeDbEntity::layerld() function returns the AcDbObjectld of the ‘AcDbLayerTableRecord referenced by the entity. Ifthe layerld has not been set yet, then AcDbObjectld::kNull is returned. Here is an example of how it is used: acdbOpenAcDbEntity((AcDbEntity*&)pLine, lineld,& AcDb: :kForRead) ; AcDbObjectId lyrId: lyrid = pline->layerId(); pLine->close(); ‘After selecting an entity in AutoCAD, we could obtain its layerld as shown, and then ‘we could open the layer table record for a write operation and change the color of the referenced layer. The functions AcDbEntity=linetype() and AcDbEntity:linetypeld() are very similar to AeDbEntity:layer() and AcDbEntity:layerld0, except that they refer to the linetype table record. ‘The AcDbEntityslinetypeScale() function returns the linetype scale factor for the enti- ty. Here is its defini double AcDbEntity:: inetypeScale() const ‘This is the value ofthe system setting CELTSCALE (current entity linetype scale) that ‘was in effect at the time the entity was created. This is not to be confused with the global system setting LTSCALE (linetype scale), which is a global scaling multiplier for all entities in the drawing similar in nature to DIMSCALE (dimension scale), which isa global multiplier for all dimension entities. ‘Wel, that concludes most of the commonly used AeDbEntity query functions; let's move on to the AeDbEntity edit functions. ACDBENTITY EDIT FUNCTIONS Here is a list some of the edit functions for AeDbEntity. In this group I will discuss the most commonly used functions. Understanding AutoCAD Database and Entity Structure 169 AcDbEnti ty AcDbEntity rexplode :moveGripPointsAt AcDbEnt ity: :moveStretchPointsAt AcDbEntity::setColor AcDbEnt ity: :setColorIndex AcDbEntity: :setDatabaseDefaults AcDbEntity::setLayer AcDbEntity::setLinetype AcDbEnt ity: :setLinetypescale AcDbEnt ty: :setPropertiesFrom AcDbEntity::setVisibi lity AcbbEntity::subSetDatabaseDefaults AcDbEntity::transformBy ‘The AcDbEntity:setColorindex() function sets the entity’ color to the AutoCAD color index color. Ifthe entity owns subentities and doSubents == Adeslc:\True then the color index change will be applied to the subentities as well. The color value will be in the range 0 to 256, 0 and 256 being special values. The value 0 indicates the enti- ty uses the color of the BlockReference that: displaying it. Ifthe entity is not displayed through a BlockReference (for example, it’s directly owned by the Model Space block table record) and its color is 0, then it will display as though its color were 7. The value 256 indicates that the entity uses the color specified in the layer table record it references. This function returns Acad:zeOk if successful. If the color value is out of the acceptable range, then Acad:ielnvalidindex is returned. Here is the definition of AcDbEntity:setColorindex(: Virtual Acad: :ErrorStatus AcDbEntity::setColorIndex(Adesk::UInt16 color, Adesk::Boolean doSubents = Adesk: :kTrue) Here is an example ofits usage: acdbOpenAcDbEntity((AcDbEntity*&)pLine, lineld.& AcDb::kForWrite) ; pLine->setColorIndex(3); // Green specific color pLine->close(); ‘The AcDbEntity:setDatabaseDefaults() function sets the entity's color, layer, line- type linetype scale, and visibility to the default values of the database in which the ent ty currently resides or, if the entity is no: part of a database yet, of the current database in which the AutoCAD editor is used. Here is the definition of AeDbEntity::setDatabaseDefaults(): void AcbbEntity::setDatabaseDefaults() An example of its usage would be to open an entity for a write operation and apply the function AeDbEntity:setDatabaseDefaults() on the entity as shown: acdbOpenAcDbEntity((AcDbEntity*&)pLine, lineld.& AcDb: :kForWrite); pline->setDatabaseDefaults(); pline->close(): ‘The AcDbEntity:setLayer() function is an overloaded function that allows you to change the layer an entity is on. Here is its definition: virtual Acad: :ErrorStazus AcDbEntity::setLayer(AcDbObjectId newVal. Adesk::Boolean doSubents = Adesk: :kTrue) virtual Acad: :ErrorStatus AcDbEntity::setLayer(const? char* newVal Ades! Boolean doSubents ~ Adesk True) ‘The first function expects an AeDbLayerTableRecord AcDbObjectld. This function sets the entity to reference the AcDbLayerTableRecord that has the AcDbObjectid newVal. If the entity owns subentities and deSubents == Adesle:kTrue, then the layer change will be applied to the subentitics as well. Ifthe function is successful, then Acad:zeOk is returned. If the object with AcDbObjectid newVal is not an ‘AcDbLayerTableRecord, then Acad::eWrongObjectType wil be returned, ‘The second function expects a name of a valid layer. This function sets the entity to reference the AeDbLayerTableRecord identified by the name specified in newVal. If the entity owns subentities and doSubents == Adeslc:kTrue, then the layer change will be applied to the subentities as well. The database containing the entity is searched for the AeDbLayerTableRecord object with the name pointed to by newVal. Ifa matching layer table record is found, then the entity will be set to reference it and ‘Acad:eOk will be returned. If no layer table record object is found with the name pointed to by newVal, then Acad::eKeyNotFound will be returned. If the layer table record object found has been erased, then Acad:eDeletedEntry will be retumed. Here is an example of using AcDbEntity:setLayer() function: AcDbObjectId targid, entId: acdbOpenAcDbEntity(pTarget, entId, AcD! targld = pTarget->layerld(); pTarget->close(): // targld now has the layer objectid of pTarget kForRead); Understanding AutoCAD's Database and Entity Structure $$ acdbOpenAcDbEntity(pEnt, entId, AcDb::kForWrite): pEnt->setLayer(targld); pEnt->close(); 1/ pint is now on the same layer as pTarget ‘The AcDbEntity:setLinetype( function is very similar to the AeDbEntity:setLayer() function. ‘The AcDbEntity::setPropertiesFrom() function is a very useful function in that it copies the color, layer, linetype, linetype sce, and visibility from a target entity and sets the calling entity with matching properties. Here is the definition of the AeDbEntity:setPropertiesFrom() function: Acad: :ErrorStatus AcDbEntity::setPropertiesFrom? (const AcDbEntity* pEntity, Adesk::Boolean doSubents = Adesk::kTrue) If the entity owns subentities and doSubents == Adeski:kTrue then the property changes will be applied to the subentities as well. It returns AcadseOk if successful. Here is an example ofits usage: AcDbEntity *pEnt; AcDbAttributeDefinition *paAttdef; for (plterator->start(); !pIterator->done(): plterator->step()) ( 1] Get the next entity a plterator->getEntity(pEnt, AcDb::kForRead); // Make sure the entity is an attribute definition // and not a constant “ pAttdef = AcDbAttributeDefinition::cast(pEnt); if (pAttdef != NULL && !pAttdef->isConstant()) ( // We have a non-constant attribute definition // so build an attribute entity Mr AcDbAttribute *pAtt = new AcDbAttribute(); pAtt->setPropertiesFrom(pAttdef); pAtt->setInvisible(pAttdef->isInvisible()); // More code here pBIkRef->appendAttribute(attId, pat); pAtt->close(): ) pEnt->close(): // us? pent... pAttdef might bee NULL ) delete plterator; ‘The AcDbEntity::transformBy() function allows us to apply a transformation matrix to an entity. In Chapter 3 we saw how to apply an ads_matrix to a selection set using the acedXformSS() function. With the AcDbEntity:transfromBy( function we can apply a transformation matrix to an entity. Here is its definition: virtual Acad: :ErrorStatus AcbbEnt ity: :transformBy(const AcGeMatrix3d& xform) ‘This function takes an AcGeMatrix3D reference; the AcGe classes have a number of extremely useful geometry calculation functions. It returns Acad::eOk if successful. Return values for errors are implementation-dependent. ACDBENTITY MISCELLANEOUS FUNCTIONS ‘The AeDbEntitysintersectWithQ function is an extremely useful function and an explanation of how it is used is part of an upcoming sample application. ACDBCURVE CLASSES ‘The AcDbCurve class is the base class for all the entity classes that are variations of ‘acurve, such as AeDbArc, AcDbCircle, AcDbEllipse, and AcDbSpline. This base class provides the common functionality such as finding parameters for a point on the curve, finding offset curves, and finding projections of the curve onto a plane. If you refer back to Figure 4.7, you will see, under the general category Curves, that AcDbCurve is the base class. Figure 4.9 shows the complete class hierarchy for the Curves classes. Yes it’s true: a line is considered a straight curve, as you can see in AeDbLine. In our discussion of all the various functions tha: the base class AeDbCurve class has, keep in mind that a great deal ofthis functionality is used in conjunction with the geom- etry classes, which are prefixed with AcGe-. The geometry classes are the subject of the next chapter. Here is a listing ofall the AeDbCurve query functions: AcbbCurv AcbbCury AcDbCurve: :getDistatParam Understanding AutoCAD’ Database and Entity Structure 73 AcDbonject [AcDbEntity [acpecurve [AcDbLine [AcDbRay [AcDbxLine [AcDharo |—{acpncircie |—{acpnratipse |—{acpnspiine [AcDbleader [acbbPolyline [AcDb2aPolyline [AcDb3aPolyline Figure 4.9 ObjectARX Curve Entities 4 AcDbCurve: :getDistatPoint AcDbCurve: :getEndParam AcDbCurve: :getEndPoint AcDbCurve: :getFirstDertv AcbbCurve: :getOffsetCurves AcDbCurve: :getOrthoProjectedCurve AcDbCurve: :getParamAtb" st AcDbCurve: :getParamAtPoint AcDbCurve: :getPlane AcDbCurve: :getPointAtD* st AcDbCurve: :getPointAtParam AcDbCurve: :getProjectedCurve AcDbCurve: :getSecondDeriv AcDbCurve: :getSpline AcbbCurve: :getSplitCurves AcbbCurve: :getStartParam AcDbCurve: :getStartPoint AcDbCurve: : isClosed AcDbCurve AcDbCurve isPeriodic isPlanar ‘As before, I will only go through a few of the most commonly used functions. ‘The AcDbCurve::getArea() function, as its name suggests, returns the inside area enclosed by the curve. For the AutoCAD built-in classes, the curve must le on a plane. Ifthe curve is not closed, its start and end points are considered as connected by a line segment that closes it. This function returns AcadseOk if successful. For the ‘AutoCAD built-in classes, Acadzelnvalidinput is returned ifthe curve is not planar. ‘Other return values are possible for custom entity classes, depending on how they were implemented. Here is the definition of AcDbCurvegetArea(): virtual Acad::ErrorStatus AcDbCurve::getArea(double& area) const ‘The AcDbCurvengetClosestPointTo() furction finds the closest point on a curve from a point. There are two versions of this function. The first version takes into account that the point selected (supplied) is not on the same plane as the curve. Here is the definition of the first versio virtual Acad::ErrorStatus AcDbCurve: :getClosestPointTo(const AcGePoint3d& givenPnt, const AcGeVector3d& normal, AcGePoint3d& pointOnCurve, Adesk: :Boolean extend = Adesk::kFalse) const Understanding AutoCAD's Database and Entity Structure (75 In this case, the curve is projected onto the plane defined by glvenPt and normal. Then givenPt is calculated from the nearest poirt on the projected curve, after which is it projected back to the original curve and retuned in pointOnCurve. In the second ver~ sion of this function, givenPt and pointOnCurve are on the same plane; its defini- tion is shown below: virtual Acad: :ErrorStatus AcDbCurve: :getClosestPointTo(const AcGePoint3d& givenPnt, AcGePoint3d& pointoncurve, Adesk::Boolean extend = Adesk::kFalse) const If the operation is successful, this function should return AcadseOk. Return values for errors are dependent upon the error and the implementor. Look in the header fle acdb.b for a list of possible ErrorStatus values. The default implementation returns ‘Acad:zeNotimplemented. Remember that if you define your own custom entities, you can define the error values to return in the case of a failure; otherwise you will get the default implementation. In both versions of this function the extend argument is Adesk:kFalse, which indicates that the curve is not extended if the givenPt is beyond the length of the curve. If the extend argument AdeskkTrue is used, the curve will be extended to a distance where the end/start point of the curve is closest to the given point. ‘The AcDbCurvezxgetDistAtPoint() function calculates the length of the curve from the starting point to the point referenced on the curve. The definition of AcDbCurve::getDistAtPoint() is as follows: Virtual Acad: :ErrorStatus AcDbCurve::getDistAtPoint(const AcGePoint3d& point, double& distance) const Te returns Acad:zeOk if successful, or Acads:elnvalidinput if the point is not on the curve. For other errors, the implementor must decide what return value to use (see the aacdb.h header file for possible ErrorStatus values). The default implementation returns Acad:seNotimplemented. Here is an interesting and useful function: AcDbCurve::getOffsetCurves(). Here is its definition: virtual Acad: :ErrorStatus AcbbCurve: :getOffsetCurves(double of fsetDist, AcDbVoidPtrArray& offsetCurves) const ‘The first argument is the offset distance, which can be either a positive or a negative value. The second argument is an array of void pointers to AeDbEntity types. The enti 176 ties contained in the AeDbVoidPtrArray depend on what itis you are offsetting. The result may be one or more entities, not necessarily of the same class as the entity that was offset. We have not talked about ObjectARX collection classes yet, of which ‘AcDbPtrArray is one. When you are dealing with the AeDbVoidPtrArray, you can test for specific entities by using the AcRxObject:cast() operator as shown in this example: AcDbEntity pnt; AcDbArc *pArc: if (parc = AcDbArc::cast(pEnt)) != NULL) i I/... 00 whatever ) If the entity pointed to by pAre is an arc, pAre will not be NULL. We can also use the AcRxObjectzisA() function for custom entities or AcRxObjectidese() to test the entities. What is the meaning of offsetDist for different kinds of entities you are deal- ing with? For an arc entity, a negative value means a smaller arc. The entities that are retumed in the offsetCurves array are dynamically allocated but have not been added to an AeDbDatabase yet. So the application that calls this function is respon- sible for their memory. If they are subsequently appended to a database, the database takes over responsibility for their memory, otherwise the application is responsible for deleting them when they are no longer needed. The AeDbCurve function returns AcadueOk if offvetting is successfully completed. Ifthe offset distance is invalid (for ‘example, if you are offsetting an arc such that the offset result would be a negative radius), then Acad:elnvalidinput is returned. This is a complex function of which we will show an example later on. Here is a sample code fragment: AcDbEllipse *pEllipse: acdbOpenObject(pEllipse, ellipseld, AcDb::kForRead); 1/ Now generate an ellipse offset by 0.5 drawing 3 units “ue AcDbVoidPtrarray curves; pEllipse->getOffsetCurves(0.5, curves); pEllipse->close(); Now curves would contain an array of AeDbEntity types. It is the user’s responsibilty to add entities to the AutoCAD database, at which point AutoCAD will take over the memory management. If you do not add the entities to the AutoCAD database, it's ‘your responsibility to delete the entities. Lets move on and talk about some of the enti- Understanding AutoCAD's Databae and Entity Structure 77 ———___~__ ties that are derived from AcDbCurve. I'm not going to talk about every single entity derived from the AeDbEntity class. What I want you to be aware of is that each class has creation, query, and edit functions, most of which are similarly implement- ed. ACDBLINE ‘Well, perhaps one of the easiest entities to create is the line entity, and there are two ‘ways to create a line, as shown in the following example: AcDbLine *pLine = new AcDbLine; AcGePoint3d sp(5.0, 5.0, 0.0); AcGePoint3d ep(10.0. 10.0, 0.0 pline->setStartPoint (sp); pLine->setEndPoint (ep): (Or we could create the line in the following manner: AcGePoint3d sp(5.0, 5.0, 0.0); AcGePoint3d ep(10.0, 10.0, 0.0); AcDbLine *pLine = new AcDbLine(sp, ep); // Don't forget to add this line to the database ‘These are the two forms of the AeDbLine constructor. Here are the AcDbLine query functions: AcDbLine: :endPoint AcDbLine: :normal AcDbLine::startPoint AcDbLine: : thickness ‘The AcDbLinezendPoint() function return the line’s endpoint in WCS coordinates. ‘The end point value is used for DXF group code 11. Here is its definition: AcGePoint3d AcDbLine::endPoint() const: ‘The AcDbLiner:startPoint() is identical to the AcDbLinezendPoint() function. ‘The return value is the DXF group code 10. Here is its definition: AcGePoint3d AcDbLine::startPoint() const: ‘The AcDbLineznormal( function returns the lines unit normal vector in WCS coor- dinates. The normal value is used for DXF group code 210. This is how AutoCAD orients an entity in 3D: AcGeVector3d AcDbLine::normal() const; ‘The AcDbLine::thickness() function retums the line’s thickness value. The thickness is the line's dimension along its normal vector direction (sometimes called the extru- sion direction). The thickness value is used for DXF group code 39. Here are the AcDbLine edit functions: :isetEndPoint setNormal setStartPoin: AcDbLine: :setThickness ‘We have already seen an example of the usage of AeDbLine:setStartPoint() and AcDbLine:ssetEndPoint(} here are their definitions: Acad: :ErrorStatus AcDbLine::setStartPoint(const# AcGePoint3d& startPt) Acad: :ErrorStatus AcDbLine AcGePoint3d& endPt) etEndPoint(const# ‘They both return Acad::eOk if successful or Acad in is not acceptable. Here are the definitions of AcDbLine:ssetNormal() and AcDbLine::setThickness(): Acad: :ErrorStatus AcDbLine::setNormal(consté AcGeVector3d& normal) Acad: :ErrorStatus AcDbLine::setThickness(doubled thickness) invalidinput if the data passed ‘They both return Aead:eOk if successful or Meadselnvalidinput if the data passed in is not acceptable. ‘You will find a number of the same kind of functions in all of the entity classes and a number of the functions are pretty self-explanatory. Let's take a look at the AcDbCircle class. ACDBCIRCLE Again, the circle entity class AcDbCirele contains creation, query, and edit functions, just like the AeDbLine class does. The AeDbCirele creation function expects three arguments as shown: AcDbCircle::AcDbCircle(const AcGePoint3d& center, const AcGeVector3d& normal, double radius) The center and radius arguments are pretty obvious, but what is the normal argu- ment, which is of the type AcGeVector3d? This argument tells the circle what plane to align itself in, as shown in the fellowing code fragment. Understanding AutoCAD's Database and Entity Structure $a AcGePoint3d cp(5.0, 5.0, 0.0); AcGeVector norm(0, 0, 1): double rad = 3.0; AcDbCircle *pCirc = new AcDbCircle(cp, norm, rad); // Don't forget to add it to the database Here are the AeDbCirele query functions, which are almost identical to the AeDbLine query functions: AcDbCircle::center AcDbCircle: :normal AcDbCircl AcDbCircl Here are the AcDbCircle edit functions, which are almost identical to the AeDbLine edit functions: AcDbCircle::setCenter AcDbCircle::setNormal AcObCircle: :setRadius AcDbCircle: :setThickness Thope you are beginning to see patterns in all of the entities. We have not talked about ‘complex entities yet, namely block definitions and attributes, block insertions, and poly- Iines, which we will get to, but first I would like to illustrate what we have done so far by way of a sample application. SAMPLE APPLICATION CH4_2 Tn this application we have functions that draw lines, arcs, and circles and add them to the ACDB_MODEL_ SPACE block table record. This application also demonstrates how to use some of the AcGe geometry classes, which are the subject of the next chap- ter. Here is the listing for the Ch4_2.cpp: 11 Ch4_2.cpp : Initialization functions 11 CHA_2.cpp 11 by Charles Mc Auley 11 “Programming AutoCAD 2000 with ObjectARX” au“ // This application demonstrates how to created entities // that are added to the AutoCAD database it alsoe gives 7] us a preview of how to use AcGe geometrys classes that // aid in the creation of AutoCAD enities. uW TMA AAA LLL wn iFinclude “StdAfx.h” finclude “Stdarx.h” finclude “resource.h” HINSTANCE hdl Instance =NULL 11 This command registers an ARX command. void AddConmand(const char* cmdGroup, const chart cndInt, const char* cmcloc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idLocal = -1): // NOTE: 00 NOT edit the following lines. 7 UUKEX_ARX_MSG void InitApplication(): void UnloadApplication(): 7) YAPX_ARX_MSG /J NOTE: DO NOT edit the following lines. 71 CCAFX_ARX_ADDIN_FUNCS 71) YAFX_ARX_ADDIN_FUNCS LTT TATTLE HALT 71 DLL Entry Point extern “C” BOOL WINAPI D11Main(HINSTANCE hInstance, DWORD? dwReason, LPVOID /*1pReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { _hd11Instanes ) else if (dwReason ) return TRUE; // 0x ) TLL TLL LTT UMMM hinstance; DLL_PROCESS_DETACH) { 11 ObjectARX EntryPoint extern ‘AcRx: :AppRetCode acrxEntryPoint (ACRx: :AppMsgCode msg, void* pkt) ( switch (msg) case AcRx: :kIni tAppMsg: 7/ Conment out the following line if your Understending AutoCAD’ Databare and Entity Sacre 4 $$ // application should be locked into memory ‘acrxDynamicLinker->unlockApplication(pkt); acrxDynamicLinker->registerAppMOIAware( pkt) ; InitApplication(); break; case AcRx: :kUn]oadAppis: UnloadApplication(); break; } return AcRx RetOK; } // Init this application. Register your // commands, reactors... void InitApplication() fl // NOTE: DO NOT edit the following lines. J7(APXARX_INIT AddCommand(*CH4_APPS”, “CIR”, “CIR".@ ACRX_CMD_MODAL, cir); AddCommand(“CH4_APPS", “LIN”, “LIN”, ACRX_CMD_MODAL, Tin); AddCommand(“CH4_APPS", “ARK, “ARK”, ACRX_CMD_MODAL, ark); 71) AFX_ARX_INIT // T0D0: add your initialization functions acutPrintf(“Zs%s%s%s", “Commands defined bye \"CH4_2.ARX\" ari “\m\"CIR\" creazes circles, “, “\"LIN\" creates lines and “, “\"ARK\" creates 3 point arcs.”); } // Unload this application. Unregister all objects /1 registered in Initapplication. void Unloadapplication() i // NOTE: DO NOT edit the following lines. TA(APX_ARX_EXIT acedRegCnds->removeGroup(“CH4_APPS”) ; J) YAPX_ARX_EXIT /1 T0D0: clean up your application } // This functions registers an ARX command. // Tt can be used to read the localized command name // from a string table stored in the resources. void AddCommand(const char* cmdGroup, const chars cmdint, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idlocal) { char cmdLocRes(65]; // If idlocal is not -1, it's treated as an ID for // a string stored in the resources. if Cidlocal != -1) { // Load strings from the string table andé register the command. r:LoadString(_hdilinstance, idlocal, cmdLocRes,& 64); acedRegCmds->addConmand(cmdGroup, cmdInt,& cmdLocRes, cmdFlags, cndProc); } else // idlocal is -1, so the ‘hard coded" // localized function name is used. acedRegCmds->addConmand(cmdGroup, cmdint,& cmdLoc, cmdFlags, cmdProc); ) Here is the listing for the user-defined commands CIR, LIN, and ARK in Ch4_2Commands.cpp TATTLE // ObjectARX defined commands Hinclude “StdAfx.h” #include “StdArx.h” finclude “GeAssign.h” 1/1 This is command ‘CIR’ void cir) { AcDbDatabase *pCurDb; AcDbBlockTable *pB1kTable: AcDbBlockTableRecord *pB1kTableRec; AcDbObjectId circld; AcDbCircle *pCirc; AcGePoint3d cen; AcGeVector3d normal (0.0, 0.0, 1.0); // Planee orientation ads_point cp; double rad; acedInitGet(RSG_NONULL, NULL): acedGetPoint(NULL, “\nPick circle center point: “, Understanding AutoCAD's Database and Entity Structure 183 $$ cp): cen = asPnt3d(cp); // convert the ads_point tod AcGePoint3d acedini tGet(RSG_NONULL + RSG_NOZERO + RSG_NONEG, & NULL: acedGetDist(cp, “\nCircle radius: // Create the circle entity PCire = new AcDbCircle(cen, normal. rad); /1 We are going to acd the entity to the JI Model Space Block Table Record of the JI Block Table pCurDb = acdbHostApplicationServices()e —>workingDatabase(); // Open the Block Table for a read operation pCurDb->getBlockTable(pBlkTable, AcDb: :kForRead); // Get the Model Space Block Table record of thee Block Table pBlkTable->getAt(ACDB_MODEL_SPACE, pBIkTableRec, & ‘AcDb: :kForWrite); /1 Add the newly created circle entity to the 11 Block Table Record PBIkTableRec->appendacbbEntity(circld, pCirc); // Close the Block Table, the Block Table Record /1 and the Entity pB1kTable->close(): pB1kTableRec->closet); pCirc->close( } 7 This is command ‘LIN’ void lin() { AcDbDatabase *pCurDb; AcDbBlockTable *pBlkTable: AcDbBlockTableRecord *pB1kTableRec; AcbbObjectId lineld: AcDbLine *pLn; AcGePoint3d sp, ep: acedInitGet(RSG_NONULL, NULL): JJ Note how we convert an ads_point to a@ AcGePoint3d // using the mechanisn (double*)(&point) acedGetPoint(NULL, “\nPick line start point: “.o (double*)(&sp)); acedInitGet(RSG_NONULL, NULL): + brad); acedGetPoint ((double*)(&sp), “\nPick line ende point: “, (double*)(&ep)): // Create the line entity pln = new AcDbLine(sp, ep): // We are going to add the entity to the // Model Space Block Table Record of the /1 Block Table pCurDb = acdbHostApplicationServices() —>workingDatabase(); // Open the Block Tazle for a read operation pCurDb->getBlockTable(pB1kTable, AcDb::kForRead); 7/ Get the Model Spaze Block Table record of theo Block Table pB1kTable->getAt(ACDB_MODEL_SPACE, pBIkTableRec, # AcDb: :kForWrite); 1/ hdd the newly created circle entity to the 1/ Block Table Record pB1kTableRec->appendacDbntity(lineld, pin): // Close the Block Table, the Block Table record 11 and the enity pB1kTable->close(); pB1kTableRec->close(); pln->close(): ) 11 This is command “ARK” void ark() ( AcDbDatabase *pCurDb; AcDbBlockTable *pBIkTable: AcDbBlockTableRecord *pB1kTableRec: AcDbObjectId arkI AcDbArc *pArc: 7/ The AcDbArc constructors deal in terms of // a center point, start angle, end angle and // radius. There are no constructors for 3 point /1 arcs. The AcGe geometry classes allows us to // create a mathematical arc using 3 points from // which we can get the center, radius and // calculate the start and end angles. AcGePoint2d pl, p2, p3, cen; // location points AcGePoint3d_cenari // center location AcGeLine2d lineEnt: // 2d infinite line AcGeTol tol: // tolerance used by isOn()e function Understending AutoCAD’ Database and Entity Structure 185 $$$ double rad, eang, sang: ads_point sp. ap. ep: acedInitGet(RSG_NONULL, NULL); acedGetPoint(NULL, “\nPick arc start point: “.@ sp): acedInitGet(RSG_NONULL, NULL); acedGetPoint(sp, “\nPick second point of arc: + ap): acedGrOraw(sp, ap, 1, 0); // acedGrOraw draws temporary vectors from J] start point to end point in color,é with/without high] ight acedInitGet(RSG_NONULL, NULL); acedGetPoint(ap, “\nPick arc end point: “, ep): acedGrOraw(ap, ep, 1, 0); pLX] = splX]: pILY] = spl]; p2[X] = apLxd: p2ly] = apl¥]: p3LX] = eplx]; p3LY] = epl¥]: lineEnt.set(pl, p3); // create the infinited mathematical line // Create the mathematical arc AcGeCircArc2d geark(pl, p2, p3): rad = geark.radius(); cen = geark.center(): cenark.set(cen[X], cen{Y], 0.0); tol.setEqualPoint(0.001); // set tolerance value 1 default is 1.e-10 /1 Look at how we test to see if geometry point // p2 in on the infinite line defined by pl and p3 if(lineent.isOn(p2, tol)) { acutPrintf(“\nPoints picked are colinearg - invalid arc. “ return; ) // Calculate the start and end angles // Notice how we convert the AcGePoint2d to // an ads_point using the cast mechanism // (double*) (&point) sang = acutAngle((double*) (&cen), sp): eang = acutAngle((double*) (&icen), ep); // Dependant on the arc direction switch // the start and end angles if(geark.isClockWise()) ( pArc = new AcDbArc(cenark, rad, eang, sang); ) else ( parc = new AcDbArc(cenark, rad, sang, eang); } pCurDb = acdbHostApplicationServices(e —>workingDatabase(); pCurDb->getBlockTable(pB1kTable, AcDb: :kForRead); pB1kTable->getAt(ACDB_MODEL_SPACE, pBIkTableRec, # AcDb: :kForirite): pB1kTableRec->appendAcDbEntity(arkId, pArc): pB1kTableRec->close(): pB1kTable->close(): parc->close(); } ELEMENTS OF SAMPLE APPLICATION CH4_2 ‘We then have three function prototypes that draw a line, circle, and arc respectively: lind, cirO, and arkQ). The lind) and cir( functions are very similar, so let's discuss the lin function. In dealing with entities, you always have the following elements: a point- ex to the AeDbBlockTable, AcDbBlockTableRecord, and a pointer to the entity, in this case AeDbLine. We also need an object ID because, when we add the entity to the database, AutoCAD assigns each entity an object ID. In our case the ‘AcDbObjectid is lineld. Entities are not added to the block table; they are added to either the ACDB_MODEL, SPACE or ACDB_PAPER_SPACE block table record or some layout. So we open the block table for a read operation to find either of the block table records mentioned earlier. Once we have a pointer to the block table record, we ‘open the record for a write operation and call appendAcDbEntity() to add the enti- ty to the record, which gives us an Objectid in return. As always, we close the block table, the block table record, and the entity object when we are finished with them. ‘The method of adding the entity to the database is the same for all of the functions listed earlier. What is different about the ark() function is how we go about creating the AeDbAre object. If you look at the constructors for the AeDbAre class, they deal in terms of a center point, radius, start angle, and end angle—there is no three-point method of creating the arc. Well, what if you calculated three points that the ‘AcDbAre is to pass through? How do you create the arc entity? That is where the ‘geometry classes come in. I do not want to get into a major discussion of the geom- Understanding AutoCAD's Database and Entity Structure etry classes now because that is the subject of the next chapter. Basically, we pick three points and create a mathematical representation of an arc in memory. We also create 2D mathematical infinite line, as shown by the following code fragment: AcGeLine2d lineEnt; // 2d infinite line lineEnt.set(pl, p3): // create the infinite mathematical line 1/ Create the mathematical arc AcGeCircArc2d geark(pl. p2, p3); rad = geark.radius(); cen = geark.center(); ‘We then query the geometry arc for the center point and the radius, from which we can calculate the start angle and end angle. We can also test to see if the three points are colinear; in that case we would have an invalid arc. In the geometry classes we have tolerance functions that allow us to test ifa point is on a line to within a tolerance that ‘we control, as shown by the following code fragment: tol.setEqualPoint(0.001); // set tolerance value W/ default is 1.e-10 JJ Look at how we test to see if geometry point 11 p2 is on the infinite line defined by pl and p3 if(lineEnt.isOn(p2, tol)) ( acutPrintf("\nPoints sicked are colinear -¢ invalid arc. *); return: ) ObjectARX will let you create an arc that isthe result of points selected that are lin- car. The results will be unpredictable—what you may get is a dot on the screen that when listed states that it is an ARC entity with a zero starting and ending angle. The point here is that it is up to you to do the testing before you create the arc object. Look at how we check to see if the arc is clockwise or counterclockwise in the following code fragment: // Dependant on the arc direction switch 11 the start and end angles if(geark.isClockWise()) ( pArc = new AcDbArc(cenark, rad, eang, sang); } else ( pArc = new AcDbArc(cenark, rad, sang, eang); ) The rest of the code is as before for the other functions. So, as you can see from the ‘ark() function, using the geometry classes we can get the desired arc type and still cre- ate our AcDbAre by passing in the calculated parameters. The geometry classes provided by ObjectARX area ral benefit to developers and make our lives so much easier. I was really pleased to see this included in ObjectARX—well done, Autodesk! So once again, we move on to another application that will demonstrate how to check for intersections and how to offset entities using the query and edit functions provided by the entity classes. SAMPLE APPLICATION CH4_3 In this application we examine how to use some of the functionality provided by the entity classes, specifically the AeDbEntityzintersectWithQ function, which tests if cone entity intersects with another and, if so, returns an array of the intersecting points, We also take a look at how to us: AcDbCurver:getOffsetCurves(), which returns an array of void pointers to the offiet entities. We have mentioned the word “array” so we may as well discuss ObjectARX’s collection classes. Again, if you know one, you pretty much know them all In this application we also delve into some of the geometry classes and show you how to use matrix classes as well as translate points. So as before, let’ list the code here and discuss its elements later. Here is the code listing for Ch4_3.cpp: JI Ch4_3.cpp : Initialization functions V1 CH4_3.cpp // by Charles Mc Auley // “Programming AutoCAD 2000 with ObjectARX” /I This application denonstrates how to use 71 AcObEntity::intersectWith() function, we also /I use the AcGe Geometry classes to draw temporary // vectors around the intersection points of theé selected // entities. We also demonstrate how to use // AcDbCurve: :getOffsetCurves() iT MUTT wit Hinclude “Stdafx. finclude “Stdarx. #include “resource. h” HINSTANCE _hd1 Instance =NULL ; // This conmand registers an ARX command. Understanding AutoCAD’ Databate and Entity Structure 189 a void AddCommand(const char* cmdGroup, const char*d cmdInt, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idlocal = -1); 11 NOTE: D0 NOT edit the following lines. TI AFX_ARK_MSG void InitApplication(); void UnloadApplication(); 7D) VAFX_ARK_MSG // NOTE: DO NOT edit the following lines. 7/((AFX_ARX_ADDIN_FUNCS 7) VAFX_ARX_ADDIN_FUNCS TATTLE TLL LL TIT TL TLL TTL 77 DLL Entry Point extern “C” BOOL WINAPI D11Main(HINSTANCE hinstance, DWORDS dwReason, LPVOID /*IpReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { -hdllInstance = hInstance: } else if (dwReason — DLL_PROCESS_DETACH) ( ) return TRUE: // ok } MATL TTL LATA TLE 11 ObjectaRX EntryPoint extern “C” AcRx::AppRetCode acrxEntryPoint(AcRx: :AppMsgCode msg, void* pkt) ( switch (msg) { case AcRx::kInitAppMsg: // Conment out the following line if your 11 application should be locked into memory acrxDynami cLinker->unlockAppl ication(pkt);, acrxDynamicLinker->registerAppMDIAware( pkt): Initapplication( break: case AcRx: :kUn] oadAppisg: UnloadAppl ication(): break: ) return ACRx::kRetOK; } // Init this application, Register your /1 commands, reactors... void InitApplication() (i // NOTE: DO NOT edit the following Jines. TICCAFX_ARX_INIT. ‘AddCommand(*CH4_APPS” ACRX_CMD_MODAL, chkints): ‘AddCommand(“CH4_APPS”, “OFCRVS", “OFCRVS",@ ACRX_CMD_MODAL, ofcrvsi: LIV YAFX_ARX_INIT. 71 TODO: add your initialization functions acutPrintf(“%ststs”,"\nCommands defined by? \"CH4_3.ARX\" are “, \n\"CHKINTS\” for checking intersections ‘CHKINTS", “CHKINTS" ,& and “, "OFCRVS\" for offsetting curves.”); ) // Unload this application. Unregister all objects // registered in InitApplication. void Unloadapplication() ( // NOTE: 00 NOT edit the following lines. HU (AFX_ARX_EXIT acedRegCmds- >removeGroup(“CH4_APPS"); 71) YAPX_ARX_EXIT. J TODO: clean up your application acutPrintf(“Rs%s", “Goodbye\n”, “Removing commande group \"CH4_APPS\"\n"); } // This function registers an ARK command. // Tt can be used to read the localized command name // from a string table stored in the resources. void AddCommand(const char* cmdGroup, const chare cmdInt, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idlocal) ( char cmdLocRes( 653; // If idlocal is not -1, it’s treated as an ID for // a string stored in the resources. if Cidlocal I= -1) ( Understending AutoCAD’ Databare and Entity Structure 191 $$$ J Load strings fron the string table andé register the command. ::loadString(_hd1lInstance, idLocal, cmdLocRes,¢ 64); acedRegCnds->addCommand(cmdGroup, cndInt, cmdLocRes, cmdFlags, cmdProc): ) else II idlocal is -1, so the ‘hard coded’ 11 \ocalized function name is used. acedReg(mds->addCommand(cmdGroup, cmdint,& emdLoc, cmdFlags, cmdProc): } Here is the listing for the user-defined commands CHKINTS and OFCRVS in Cht_3.cpp: TTL LLL ELL 11 ObjectARX defined conmands #include “Stdafx. nh” #include *Stdarx. nh" 17 This is command *CHKINTS* void chkints() t ads_point pickPt; ads_name ent; AcDbObjectid entId1, entid2; AcDbEntity *pEntl, *pént2; AcGePoint3darray intPoints: // This array holdsé the intersecting points AcGePoint3d intPt: AcGePoint3d rl, r2, r3. AcGeVector3d transVec(-0.1, 0.1, 0.0); // Pointe translation vector AcGeMatrix3d transMat; /1 Translation’ matrix char kwl10]; int extVal = 0: // Extend value, 0 = no, 1 =& first entity, /1 2 = second entity, 3 = both entities // see intersectWith() function below int res rc = acedEntSel(“\nSevect first entity to teste for intersectio ent, pickPt); switeh(re) { case RTCAN: acutPrintf("\nUser canceled.”) return; break: case RTERROR: acutPrintf(“\nNothing selected.”): return; break: DIT switch acdbGetObjectId(entIdl, ent); // Is the entity a line, arc or circle? // Here we limit ourselves to lines, arcs // and circles, because that is what we created // in the last’ application. acdbOpenAcDbEntity(pEntl, entId1, AcObs :kForRead) ; iC (pEntl->iskindOf(AcDbLine::descQ)) || pEnt1->iskindOf(AcDbCircle::desc()) || pEnt1->isKindOF(AcDbArc: :desc()) ) ) ( acutPrintf(“\nNot @ line, arc or circled entity.”); pEnt1->close(); return: } 71 Now get the second entity re = acedEntSel(“\nSelect second entity to teste for intersection: “, ent, pickPt); switeh(re) ( case RTCAl acutPrintf(“\nUser canceled.” return: breaks case RTERROR: acutPrintf(“\nNothing selected.” return: break; V1 switch acdbGetObjectId(entId2, ent); acdbOpenAcDbEntity(pEnt2, entid2, AcDb ForRead) Understanding AutoCADs Database and Entity Structure 193, a // 1s the second entity the same as the first? if(entIdl — entid2) ( acutPrintf(“\nSame entity selected!"); pEntl->close(): pEnt2->close(): return; ) J] Is the second entity a line, arc or circle? pEnt2->iskindOf(AcDbLine::desc()) || pent2->isKindOf (AcDbCircle pEnt2->isKindOf(AcobArc: : ) ) ( acutPrintf("\nNot @ line, arc or circled entity."); pEntl->close(); pEnt2->close(); return; ) // If the first entity is not a circle // ask if the user wants to extend the entity J/ for apparent intersection if (1pEnt1->iskindOf(AcDbCi rc} { pEnt1->highl ight acedInitGet(NULL, “ves No"); re = acedGetkword(“\nExtend the highlighteds entity - [Yes/No]: “, kw): pEnt1->unhi gh] ight switeh(re) ( ::desc())) case RTCAN: case RTERROR acutPrintf("\nError with acedGetkword(). pEnt1->close(); pEnt2->close(); return; break; case RTNONE: break; case RTNORM: if(stremp(kw, “Yes") == 0) ( extVal = 1 } break: } } J/ If the second entity is not a circle 7/ ask if the user wants to extend the entity 7/ for apparent intersection 4 F(1pEnt2->4 skindOf (A2DBCi rcle::desc())) ( pent2->highlight(): acedInitGet(NULL, “Yes No"); re = acedGetkword("\nExtend the highlighted? entity - [Yes/No]: “. kw); pEnt2->unhighl ight): switch(re) { case RTCAN: case RTERROR: acutPrintf(“\nError with acedGetkword(). “): pEnt1->close(): pEnt2->close(): retur breaks case RTNONE: breaks case RTNORM: if(stremp(kw, “Yes”) == 0) t iftextval == 1) c extVal = 3; // Both } else ( extVal = 2; // Second only ) ) break: ) ) // Does the first selected entity // intersect with the second entity? Undertending AutoCAD's Database and Entity Structure 195 a switch(extVal) ( case 0: pEnt1->intersectWith(pent2, AcDb::kOnBothOperands, intPoints); 11 No extension on either entity break: case 1: pEnt1->intersectWith(pEnt2,¢ AcDb: :kExtendThis, intPoints); // Extend the first entity break: case 2: pEnt1->intersectWi th(pEnt2. AcDb::kExtendArg, intPoints); 1/ Extend the second entity break: case 3: pEnt1->intersectWith(pEnt2, AcDb: :kExtendBoth, intPoints): // Extend both entities break: } pEnt1->close(): pEnt2->close(): // Were is an example of the AcGePoint3darray class // as well as a demonstration of how to used translation 11 matrices AcGeMatrix3d if(1 intPoints.isempty()) ( for(int 4 ( intPt = intPoints.at(i): transMat = AcGeMatrix3d(transVec); rl = intPts rl. trans formBy(transMat): transVec.set(0.0, -0.2, 0.0); transMat = AcGeMatrix3d(transVec); r2= rl: r2.transformBy(transMat) ; acedGrDraw((doubl2*)(&r1), (double*)(&r2), 3, i < intPoints.tength(); i++) 0: transVec.set(0.2, 0.0, 0.0); transMat = AcGeMatrix3d(transVec); r3 = res 3. transformBy(transVec) ; acedGrDraw((double*)(&r2), (double*)(&r3),¢ 3, 0s transVec.set(0.0, 0.2, 0.0); transMat = AcGeMatrix3d(transVec); r4 = 135 4. trans formBy(transVec) 5 acedGrDraw((double*)(&r3), (double*)(&r4) 2 3, 0; acedGrOraw((double*)(&r4), (double*)(&rl). 3. 0); ‘transVec.set(-0.1, 0.1, 0.0); acutPrintf(“\nIntersection point ~ (%.21f¢ .21f £218)", intPt.x, intPt.y, intPt.2); } } } // This is command ‘OFCRVS’ void ofervs() ( AcDbDatabase *pCurDb: AcDbEntity *pEnt; AcbbCurve *pCurv AcDbObjectId entid: AcDbVoidPtrarray offCurvs: AcDbBlockTable *p81kTables AcDbBlockTableRecord *pB1kTableRecords double offVal; ads_name en: ads_point pickPt; rErrorStatus es; // Select the entity to offset re = acedEntSel(“\nSelect entity to offset: “.# en, pickPt): switch(re) { case RICAN: case RTERROR: acutPrintf(“\nError or nothing selected”); Undersending AutoCAD’ Database and Entity Structure (97 —————$_____ return: break: J // Now get the offset distance either a positive // or negative number is fine. but not zero acedInitGet(RSG_NONULL + RSG_NOZERO, NULL); re = acedGetReal(“\nEnter offset distance - nog zero allowed: “, &offVal): // Open the entity select by acedEntSel for // a read operation, don’t forget to get the // objectId first, before attempting to open // the object, otherwise you will crash AutoCAD. // Not that Ihave ever done this of course! ! acdbGetObjectId(entId, en); acdbOpendbject (pent, entId, AcDb::kForRead); // Check if the entity selectd is derived // from AcbbCurve if((pCurv = AcDbCurve::cast(pEnt)) == NULL) { acutPrintf(“\nEntity select is note offsetable.”): pEnt->close(): return; ) // Apply the offset curve, here is one area where // always test the es return value. You couldé try to 7/ offset the curve with an invalid value in which // case you get Acad::elnvalidinput. Another? return code // is kcad::eNotImplenentedYet which is fore cases when // you have to override the getOffsetCurves,& especially for // custom entities. es = pCurv->getOffsetCurves(offVal, of fCurvs): iffes I= Acad::e0k) t acutPrintf("\nError offsetting curve"): pEnt->close(): return: ) PEnt->close(); if (loffCurvs. isempty()) ( pCurDb = acdbHostApplicationservices()e ->workingDatabase(); // Open the Block Table and find thee ACDB_MODEL_SPACE 71 record. pCurDb->getBlockTable(pBIkTable, AcD! pBlkTable->getAt(ACDB_NODEL_SPACE. & pBIkTableRecord, AcDb::kForWrite) : pBIkTable->close(): 7/ Step through the offCurvs void pointer array for(int 1 = 0; 4 < offCurvs.length(); i++) { // cast the void pointer to an AcDbEntity? pointer 71 add the entity close to the Block Tablee record // close the entity pent = (AcDbEntity*)offCurvs.at(i); pB1kTableRecord->appendAcDbEntity(entId, pEnt): pEnt->close(); ) // Close the Block Table Record pB1kTableRecord->close(): ) ) kForRead); ELEMENTS OF SAMPLE APPLICATION CH4_3 Jn this application we define two user-defined functions, chkints() and ofervs(. In ‘order to be able to use the chkints() function, you will need to draw some entities on the AutoCAD screen. [limit you to arc, ciccle, or line entities only because that is what ‘we drew in the CA3_2.arx application. I also show you how to check to see if an enti- ty is a line, arc, or circle. So let’s look at the ehkints() function in more detail. In chk- ints() we ask the user to select two entities. We then check to see if the entities have an actual intersection point, an apparent intersection point, or an extended intersec~ tion point. When we select our first entity, we place that entity in ads_name ent. We then convert the entity name to an object ID using acdbGetObjecttdQ. The ‘AcDbObjectid is stored in ential. The ertity is then opened for a read operation with acdbOpenAcDbEntity() I could equally have used acdbOpenObject() to open the entity for a read operation, but here I know I'm dealing with AeDbEntity object. ‘The question is, which kind of entity am I dealing with? In the following lines of code, look at how we test for a line, circle, or an arc entity using the isKindOf0 function: Understending AutoCAD’ Database and Entity Structure $$ iC (pEntL->iskindOf(AcDbLine: :desc()) || pEnt1->iskindOf(AcDbCircle::desc()) || pEnt1->isKindOf(AcDbArc: :desc()) ) ) ‘The AcRxObject class has a number of useful functions for testing entities, namely AcRxObject::desc(), AcRxObjectzisAQ, and AcRxObject:isKindOf(). ‘AcRxObject:dese() returns a pointer to an AcRxClass object, which we then pass to the AcRxObjectiisKindOf0) function, which returns Adesle:Boolean, The AcRxObjectisKindOf0 in this case tests to see ifthe entity is a line, ac, circle, or 1 custom entity that is derived from any of the classes, whereas the AcRxObjectzisA() tests only to see if the entity is an AeDbLine (or whatever we are testing for). If my custom entity were derived from AcDbLine (and it was part of the ObjectARX hierarchy) and I used the AcRxObject::isAQ) to test a line entity against my custom entity, the AcRxObject:isAQ would return false as shown: // This would fail pMyEnt->isA(AcDbLine::desc()) // This would work pMyEnt ->isKindOf(AcDbLine::desc()) Just a quick note: most of the R12 entities were objectified in AutoCAD 2000. So AcRxObjeczisAQ expects the entity to be of the classtype tested, no more, no less, whereas the AcRxObjectzisKindOf0 function expects the entity to be the entity test- ed or an entity that is derived from the tested class. We select the second entity in a similar fashion as we did the first entity. ‘When testing for intersections there are four possibilities: + We want an actual intersection. Ifthe entities actually intersect, we get the returned point if not, we get nothing. In that case we passin the argument ‘AcDb::kOnBothOperands. + We want to see if the first entiy selected if extended, would intersect with the second entity selected. In this case we use the AcDb:skExtendThis argument. + We want to see ifthe second entiy selected, if extended, would intersect with the first entity selected. In this case we ues the AcDbr:kEntendArg argument. + Lastly, we want to see if both entities, f they were extended, would intersect. In this case we use the AeDb::ExtendBoth argument. ‘When you run this application, draw a number of lines, arcs, and circles that actual Jy intersect and others that do not intersect, and test the various combinations. In the application, when the user has selected both entities, we then ask the user if the enti- ty is to be extended, as long as its not a circle entity (extension has no meaning for a circle). Look at how we highlight and unhighlight the entities. Eventually we step into the switch statement and call the appropriate intersectWith( function. The third argument to the interseetWith( function is an array of AcGePoint3dArray which contains the intersection points if the entities intersected. Remember, entit can intersect more than once; you can test the array length. I will talk about ObjectARX arrays in a litte while. Here is the code fragment where we walk through the AcGePoint3dArray: if(1 intPoints. isEmpty()) { for(int i = 0; 1 < intPoints.length(); i++) i intPt = intPoints.at(i); transMat = AcGeMatrix3d(transVec); rl = intPt; rl. transformBy(transMat); acutPrintf(“\nIntessection point = (%.21f %.21f¢ a.21f)", intPt.x, intPt.y, intPt.z): ) ) First we test to see if the array is empty; ifit is not, we then jump into the for loop and process the loop according to the length (numberof the points in the aray) of the array. For each point, we retrieve its coordinates and then we set up the transforma tion matrix using the translation vector ransVec. We then transform the points using the transformation matrix. This is éone for four points around the array point, to which we apply the acedGrDraw() function to draw a green rectangle. ‘The ofervs() function allows the user to select an entity and apply an offset distance to that entity provided the entity is derived from AeDbCurve. When you run this application, draw a number of lines, arc, circle, polylines, and splines and see how the entities respond to the application. Type in some negative and positive values and see how the entities behave. We ask the user to select an entity and test to see ifit is derived from AcDbCurve, as shown in the following code fragment: Understending AutoCAD’ Database and Entity Siracture 201 /1 Check if the entity selectd is derived J from AcDbCurve ifC(pCurv = AcDbCurv i acutPrintf("\nEntity select is not offsetable.”); pEnt->close(); return; } cast(pEnt)) == NULL) Then we apply getOfsetCurves() to the entity, as the following code fragment shows: JJ Apply the offset curve, here is one area where // 1 always test the es return value. You couldg try to // offset the curve with an invalid value in which // case you get Acad::eInvalidInput. Anotherd’ return code JT 4s Acad: : when /] you have to override the get0ffsetCurves, & especially for // custom entities. es = pCurv->getOffsetCurves(offval, of fCurvs): if(es I= Acad::e0k) ( acutPrintf(*\nError o*fsetting curve”); pEnt->close(); return: ) pEnt->elose(): If the Acad:ErrorStatus( is successful, the AcDbVoidPtrArray will contain the enti- ties that ae the result of the getOffsetCurves() function. Note that the offset curve ‘may not be the same type is the original curve and you may also have more that one entity in the offset. Try drawing a spline entity and offset ita few times; you will even~ tually get more than one offset curve. What happened when you offset a line? It dida't work? Surprise! For a line entity, you are responsible for defining the meaning of a negative or a positive offet. In the line case, you will get the AcadzeNotimplementedYet error. Debug the application and place a breakpoint right at the beginning of the if statement and look at the value of the es after you have tried to offset a line entity. Next, we check to see if the AcDbVoidPerArray is empty. The AcDbVoidPtrArray contains an array of AeDbEntity objects. At this point we don't know the kind of entities we are dealing with. What we do know is that they have not NotImplementedYet which is for caseso been added to the database yet. If we do not add them to the database, we are responsible for deleting them. Look at how we process the for loop in the following code fragment: for(int 4 cl J] cast the void pointer to an AcObEntity pointer 11 add the entity close to the Block Table record 11 close the entity pEnt_= (AcDbEntity*)offCurvs at (i): pB1kTableRecord->appendAcbbentity(entId, pent); pEnt->close(); ) 3 1 < offCurvs.length(); i++) This array contains void pointers; we need to cast each element to an AeDbEntity base clase type and append it to the database. Now that we have raised the subject of arays, let's turn our attention to ObjectARX’s collection classes. OBJECTARX COLLECTION CLASSES ObjectARK has a number of collection classes that are implemented as dynamic arrays. In regular C/C++, arrays are fixed in length. In ObjectARX and in MFC there are a number of collection classes (they work similarly in both environments) that start out with a fixed size (physical length). As elements are added (logical length) to the array to the point where logical length grows beyond the physical length, the array will grow (Gncrease its physical size) by a certain step size, and the cycle continues. If you understand one collection class in ObjectARX, they all behave in a similar fashion. Here is alist of some of the ObjectARX collection classes: AcDbVotdPtrArray AcDbIntArray AcDbObject IdArray // Geometry class arrays AcGePoint2dArray AcGePoint3dArray AcGeVector2dArray AcGeVector3dArray AcGeDoubleArray AcGelIntArray AcGeVoidPointerArray ‘We will talk about the AcDbIntArray cliss when discussing these classes. There are ‘two constructors for the AeDbIntArray array, as shown: Understanding AutoCAD's Databae and Entity Structure 203 $$ AcDbIntArray: :AcDbIntArray(int_initialPhysicalLengthe = 0, int initialGrowLength = 8) AcDbIntArray::AcObIntArray(const AcDbIntArray& otherArray) ‘The second constructor is simply a copy constructor. The first version, as we can see, sets the grow length at 8 and the physical length at 0. As soon as you add an element to the array, its physical length grows to 8 elements, its logical length is (at this point) 1 clement. As we add more elements, the logical length grows until it equals the physical length. When we add another element, the logical length will be 9 and the physical length will be 16 (the aray grew by 8 elements) and this process is repeat- ed. If you want, you can change the grow length, as we will see later. Here are some of the most useful element access functions: int AcDbIntArray::at (int index) const AcDbIntArray& AcDbIntArray::setAt (int index, inte value) AcDbIntArray& AcDbIntArray::setAll (int value) ‘The atQ fanction returns the value of the integer at a specified location and is typ- ically used in for loops. The setAtQ function will allow you to specify a value at a cer- ‘tain index. If you want to set all the values in the array to the same value, use the ‘setAIQ function. Another useful function in the AeDbIntArray class is the append() function, which allows you to add elements to the end of the array. It also allows you to append the contents of one AeDbintArray to the end of another AeDbIntArray, as shown by the following definitions: ‘int AcDbInt::append(int value) AcDbIntArray& AcDbIntArray: :append(const AcDbIntArray& array) AcDbIntArray& AcDbIntArra: int value) nsertat(int index, The insertAt() function allows you to insert a value at a specified location. Some of the more useful array length functions are shown as follows: int AcDbIntArray::Tength() const Adesk: :Boolean::isEmpty() const int AcDbIntArray::logicalLength() const ‘The length and logicalLength() functions are both the same and return the num~ ber of elements in the array; this is not the same as the physical length of the array, ‘which may be larger than the logical length ofthe array. You can also test to see if the array is empty using the function IsEmpty(). Although there are other functions, the cones mentioned above are some of the most useful, and all the collection classes imple~ ment a similar kind of scheme. SAMPLE APPLICATION CH4_4 Before we move on and discuss ObjectARX’s complex entities (polylines and blocks), let’ revisit the CA3_2.arx application, which allowed us to select a group of entities and then select a target entity whose layer we extracted. With the extracted layer, we then changed the group of previously selected entities to match the extracted layer. This ‘was accomplished with result buffers. In this next application, Ch4_4.arx, we achieve the same result in an ObjectARX manner without using result buffers. Here isthe list- ing of sample application Ch4_4.cpp: // Ch4_4.cpp : Initialization functions 1 CHA_4.cpp 11 by Charles Mc Auley /1 “Programming AutoCAD 2000 with ObjectARX” uw 11 This is a redo of sample application CH3_2.ARX // in which we used result buffers and selection set // to change the layer of selected entities tod that of // a target layer. Now that we understand entities? and // the database, you can see that changing ang entities // Vayer is much simpler than that of CH3_2.ARK uw UUM ML wit #include “Stdafx.h” #include “Stdarx.h #include “resource.h HINSTANCE _hd1lInstance =NULL // This conmand registers an ARX command. void AddConmand(const char* cmdGroup, const char* cmdInt, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtr? cndProc, const int idlocal = -1); // NOTE: DO NOT edit the following ines T/{(EX_ARK_MSG void InitAapplication(); void UnloadApplication(); 71) )AFXARKMSG Understanding AutoCAD's Database and Entity Structure 205 $< J1 NOTE: DO NOT edit the following lines. 71 ((AFX_ARX_ADDIN_FUNCS 7) VAFX_ARX_ADDIN_FUNCS TATTLE TLL TALL 77 DLL Entry Point extern “C” BOOL WINAPI D1IMain(HINSTANCE hinstance, DWORDS dwReason, LPVOID /*1pReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { -hd1lInstance = hinstances } else Tf (dwReason — DLL_PROCESS_DETACH) ( } return TRUE: // ok } TTL TTT TTT 11 ObjectaRX EntryPoint extern “C” AcRx::AppRetZode acrxEntryPoint(AcRx: :AppMsgCode msg, void* pkt) { switch (msg) { case AcRx::kInitAppMsg: 7/ Comment out the following line if your J] application should be locked into memory acrxDynamicLinker->un1ockApplication(pkt);, acrxDynamicLinker->registerAppMDIAware(pkt) ; Initapplication(); break: case ACRx::kUnloadAppisg: Unloadapplication(): break: ) return AcRx:: } // Init this application. Register your // commands, reactors... void InitApplication() (i RetOK; // NOTE: DO NOT edit the following lines. 71 (AFX_ARX_INIT AddCommand(*CH4_APPS", “CEL”, “CEL”. & ACRX_CMD_MODAL, cel): 11D )AFX_ARX_INIT // T0D0: add your initialization functions acutPrintf("\nType \"CEL\" to execute. “ } // Unload this application. Unregister a1] objects /1 registered in InitAppl ication. void UnloadApplication() { 11 NOTE: DO NOT edit the following lines. TT CUORFX_ARX_EXIT acedReg(mds->removeGroup(“CH4_APPS") 71) YAPX_ARX_EXIT 1/ TODO: clean up your application acutPrintf(“Es%s", “Goodbye\n”, “Removing commande group \"CH4_APPS\"\n’ } // This function registers an ARX command. // It can be used to read the localized command name // from a string table stored in the resources. Void AddConmand(const char* cndGroup, const chart cmdInt, const char* cmdloc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idlccal) t char cmdLocRes(651; // If idlocal is not -1, it's treated as an ID for 11 a string stored in the resources. if (idlocal I= -1) ( // Load strings from the string table and& register the command. oadString(_hdilInstance, idlocal, cmdLocRes,@ 64); acedRegCmds->addConmand(cmdGroup, cmdInt.& cmdLocRes, cmdFlags, crdProc); } else // idlocal is -1, so the ‘hard coded’ // localized function name is used. acedRegCmds->addConmand(cmdGroup, cmdInt, cmdLoc, cmdFlags, cmdProc); ) // User defined function // called by cnl(), see // Ch4_ACommands .cpp Understanding AutoCAD's Database and Entity Structure 207 sr void chgEntslyr(ads_nam2 ent, ads_name srcSS) { AcbbEntity *pents AcbbObjectId entId, lyrId: Jong TenSs; ads_name ssEnt: int res // Verify that there are entities in // the selection set re = acedSSLength(sresS, &lenSS); if (re 1= RTNORN) { acutPrintf("\nInvalid or empty selection set”); return; } acdbGetObjectId(entId, ent): acdbOpenAcDbEntity (pent, entId. AcDI lyrld = pEnt->layerId(): pent ->close(); 7/ Now that we have the target entity layer // substitute the layer record for each entity // in the selection set. for(long i = 0; i < lenSs; i++) c ::kForRead); // Get the entity name at the specified index rc = acedSSName(srcsS, i, ssEnt); if(re f= RTNORM) ( break: ) J/ Get the entity objectId from the entity name acdbGetObjectid(ent:d, ssent); acdbOpenAcDbEntity(pEnt, entId, AcDb::kForWrite); pent->setLayer(lyrId); pent->close(); ) ) Here is the listing for the user-defined CEL command in Ch4_4Commands.cpp: TATTLE // ObjectARX defined conmands #include “StdAfx.h” Hinclude “StdArx.h” JJ This is command ‘CEL’ void cel() t ads_name srcSS; // Source selection set ads_name targEnt; // Target entity int re: // Result code ads_point pickPt; // Used in the call too acedEntSel() acedPrompt(“\nSelect entities for layer change “); re = acedSSGet (NULL, NULL, NULL, NULL, srcSS); if(re != RTNORM) { acutPrintf("\nNo entities selected!! return; ) rc = acedEntSel(“\nSelect target layer entity. “,& targEnt, pickPt); switch(re) i case RTERROR : acutPrintf("\nNothing selected! “ break; case RTCAN : acutPrintf("\nUser canceled. “); break; case RTNORM : // See Ch4_4.cpp for definition chgEntslyr(targEat, srcSs); break; ) // Don't forget to free the selection set. acedSSFree(sreSS); ) ‘The only function that has really changed here is the chgEntsLyr() function. There is nothing really complex here; we already know how to manipulate the symbol tables, symbol table records, and entities, so T'm not going to discuss the application in any more detail. AUTOCAD’S COMPLEX ENTITIES ‘When we are dealing with AutoCAD's complex entities, we ae talking about the poly- line entity and the block entity, with associated attribute definitions. The entities that ‘I will talk about here are the AcDbPolyline entity and the AcDbBlockReference enti- ty: AcDbPolyline is AutoCAD’s new LWPOLYLINE entity. You can still create Undertending AutoCAD’ Database and Entity Structure 209 $$$. AutoCAD R13-style polylines by using the AeDb2dPolyline class and appending ‘AcDb2dVertex subentties. When you want to walk through an AeDb2dPolyline, you can attach a vertexiterator() and step through the polyline entity. Now that we have an understanding of how ObjectARX works, perhaps the best way to discuss the AdDbPolyline entity is by way of an example. After we discuss the AeDbPolyline, we will discuss how you insert an AcDbBlockReference in the AutoCAD drawing and also how you can check for attributes as you are inserting them in your drawing. So let's start out with an AeDbPolyline sample application. SAMPLE APPLICATION CH4_5 (ACDBPOLYLINE) In this application the user is asked to select a number of points, which are stored in an AcGePoint2dArray. We then process the array in a for loop, in which we add ver- tices to our polyline entity before we append it to the block table record. Here is the application listing for Cb4_5.cpp (we will discuss its elements afterward): // Ch4_5.cpp : Initialization functions 11 CHA_5.CPP 11 by Charles Mc Auley /1 “Programming AutoCAD 2000 with ObjectAR) // This application shows you how to create a // LWPOLYLINE entity in AutoCAD which is the // AcDbPolyline class. TTT TLL TL Wit Hinclude “StdAfx.h” #include “StdArx.h” include “resource.h” HINSTANCE _hd1lInstance “NULL ; // This command registe-s an ARX command. void AdéConmand(const char* cmdGroup, const charx# cmdInt, const char* cmd.oc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idLocal = -1); J/ NOTE: DO NOT edit the following lines. H((AFX_ARX_MSG. void InitApplication() void UnloadApplication() 17) APX_ARX_MSG 17 NOTE: DO NOT edit the following lines. 7 ((AFX_ARX_ADDIN_FUNCS 17) ) AFX_ARX_ADDIN_FUNCS ITLL TLL TLL 210 MAMTA 77 DLL Entry Point extern “C” BOOL WINAPI D11Main(HINSTANCE hInstance, DWORDS dwReason, LPVOID /*IpReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) t hdl Instance = hInstance: } else if (dwReason == DLL_PROCESS_DETACH) ( } return TRUE; // ok ) TTT TLL LLL VTL 17 ObjectARX EntryPoint extern “C” AcRx: :AppRetCode acrxEntryPoint(ACRx: :AppMsgCode msg, void* pkt) (i switch (msg) ( case AcRx: :kInitApplsg: 7/ Comment out the following line if your 71 application should be locked into memory acrxDynamicLinker->unlockAppl ication(pkt):, acrxDynamicLinker->registerAppMDIAware(pkt) : Initapplication(); break; case AcRx::kUnloadAppMsg: UnToadApplication(); break: ) return ACRx::kRetOk; ) // Init this application. Register your /1 commands, reactors void. InitApplication( i // NOTE: DO NOT edit the following lines. TUCAFX_ARX_INIT. AddCommand(*CH4_APPS", “LWPOLY", “LWPOLY”, ACRX_CMD_MODAL, Iwpoly) ZIV )AFX_ARX_INIT 71 TODO: add your in‘tialization functions acutPrintf(“\nType \"LWPOLY\” to execute. “): Understanding AutoCAD's Database and Entity Structure 241 $$$ ) // Unload this application. Unregister all objects // registered in InitApplication. void UnloadApplication() { /1 NOTE: 00 NOT edit the following lines. TT CURPX_ARX_EXIT acedRegCmds ->removeGroup(“CH4_APPS”) ; TV YAFX_ARY_EXIT 17 TODO: clean uy acutPrint#(“tsks” group \"CH4_APPS\"\n } // This function registers an ARX command. // It can be used to read the localized command name // from a string table stored in the resources. void AddConmand(const char* cmdGroup, const chart? emdInt, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtre emdProc, const int idLocal) i your application “Goodbye\n”, “Removing commande char cmdLocRes(651; // If idlocal is not -1, it’s treated as an ID for // a string stored in the resources. if Cidlocal != -1) { // Load strings fron the string table andé register the command oadString(_hd1lInstance, idlocal, cmdLocRes,@ 64. acedReg(mds->addComnand(cmdGroup, cmdInt.& cmdlocRes, cmdFlags, cméProc); } else JI idlocal is -1, so the ‘hard coded’ // localized function name is used. acedRegCmds->addCommand(cmdGroup, cmdInt. cmdLoc, cmdFlags, cndProc); ) Here is the listing for the user-defined command LWPOLY in Ch4_5CommandsworkingDatabase(); // Get the model space Block Table Record pCurDb->getBlockTable(pBlkTable, AcDb::kForRead); pB1kTable->getAt(ACOB_MODEL_SPACE, pBIkTableRecord, AcDb::kForWrite); pBlkTable->close() PPolyEnt = new AcDbPolyline(numPts) : // Process the AcGePoint2dArray and add vertices# to 11 our polyline. for(int idx = 0; idx < numPts; idx++) i pkPt = arPts.at(idx); pkPt. transformBy(ucsMat2d) ; pPolyEnt->addVertexat(idx, pkPt): } 11 hdd the AcDbPolyline entity to the Block 24 // Table Record pB1kTableRecord->appendAcDbEntity(polyld,& pPolyEnt); pB1kTableRecord->close(): pPolyEnt->close(): } Polyline entity points are stored in terms of their entity coordinates. Ifwe are select ing points and we happen to be in the World Coordinate System, there is a one-to- cone correspondence between the Entity Coordinate System and the points selected. So, if we do not happen to be in the WCS, how do we translate the points selected so we can draw the polyline properly? Lets examine the following code fragment from the application: acdbUcsMatrix(ucsMat); ucsMat2d(0,0) ~ ucsMat0,0); ucsMat2d(0,1) = ucsMat(0,1); ucsMat2d(0,2) = ucsMat(0,3); ucsMat2d(1,0) = ucsMat(1,0) ucsMat2d(1,1) = ucsMat(1.1) ucsMat2d(1,2) = ucsMat(1.3); ‘The function aedbUcsMatrix() takes an AcGeMatrix3d as an argument. If we are not in the WCS, the matrix will give the rotation around the X, Y, or Z axis as well asthe distance from the WCS origin to the UCS origin (if they are different). The next six ines following acdbUesMatrix() set up an AcGeMatrix2d. Because all of the points used in creating an AcDbPolyline are in terms of 2D, from the AcGeMatrix3d we build an AcGeMatrix2d matrix. This mztrix will give us the direction of the X and YY axes and the translation from the WCS origin to the current UCS origin. In the for loop, we apply the appropriate translation to the points that come from the ‘AcGePoint2dArray before we append the AeDbPolyline to the database, as shown in the following code fragment: pPolyEnt = new AcDbPolyline(numPts) ; // Process the AcGePoint2dArray and add vertices to // our polyline. for(int idx = 0; idx < numPts; idx++) { pkPt = arPts.at(idx); pkPt.transformBy(ucsMat2d) ; pPolyEnt->addVertexAt(idx, pkPt); ) // Add the AcDbPolyline entity to the Block // Table Record pBlkTableRecord->appendAcDbEntity(polyId, pPolyEnt); Understanding AutoCAD’ Database and Entity Structure 218 $$ In the for loop shown we extract the point from the arPts AcGePoint2dArray, after which we apply the AcGeMatrix2d matrix to the point in the transformBy() function. If you want to see the effect of the transformBy() function, place a com- ment marker in front of transformBy(Q) and recompile the application. Create a new UCS with origin at 5,5 and rotate around the Z axis by 45 degrees. Run your application and look at where the polyline falls. Polylines have bulges and width infor to manipulate that information (we have already seen the addVertexAt() function): AcDbPoly1 ine: :getBulgeAt() getConstantWidth() getWidthsat() setBulgeat() At this point I will eave it up to you to explore what these and the other AeDbPolyline functions have to offer. SAMPLE APPLICATION CH4_6 ACDBBLOCKREFERENCE In this application we look at how to insert a block in your drawing. In AutoCAD, ‘when you create a block, you give it a name and insertion point and select whatever entities make up the block definition. The entities disappear off the screen and a new block table record is added to the AutoCAD database. This block table record is not the Model Space block table record or the Paper Space block table record; tha’s where AutoCAD‘ entities live. Remember that the block table contains records for block def initions, dimensions, and anonymous blocks (hatch patterns for example). When a block instance is inserted in your drawing, we need to refer to the block definition so AutoCAD can figure out how to draw it. An instance of a block definition is an AcDbBlockReference, so before we add the AcDbBlockReferenee entity to the block tuble record (Model/Paper space), we need to give it the Objectld of the block def inition in the block table. On the Object ARX CD there is a doctamples folder, which shows you how to create a block definition by selecting the entities that make up the block. It also shows you how to add attribute definitions to the block definition. When Tuse AutoCAD, I normally et AutoCAD create the blocks for me using the BLOCK command with attributes if necessary. I do not use ObjectARX to create the blocks for me—some things are just too much work! When you run this application, you will need to create a block definition in AutoCAD first using the BLOCK command. So with that background, here is the listing for Co#_6.cpp: J! Chd_6.cpp : Initialization functions JI CH4_6.CPP 26 I] by Charles Me Auley J/ “Programming AutoCAD 2000 with ObjectARX” // In this application we show you how to J/ insert a block into your drawing. This 11 block does not have any attributes. You will 17 need to create a block in AutoCAD before you 7/ run this application. uy TTL LLL Wy Hinclude “StdAfx.h Hinclude “StdArx.h #include “resource.h” HINSTANCE _hd1lInstance =NULL ; // This command registers an ARX command. void AddCommand(const char* cmdGroup, const charté emdint, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtre emdProc, const int idlccal = -1): /1 NOTE: DO NOT edit the following lines. TI UUAFX_ARX_MSG void InitApplication(); Void UnloadAppl ication(); TLV YARX_ARXMSG J NOTE: DO NOT edit the following lines. 71 ((AFX_ARX_ADDIN_FUNCS 7) YAFX_ARX_ADDIN_FUNCS TTT ATT LLL LTA TTT 71 DLL Entry Point extern “C” BOOL WINAPI D11Main(HINSTANCE hInstance, OWORD® dwReason, LPVOID /*1pReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) ( _hd11Instance = hinstance; } else if (dwReason == DLL_PROCESS_DETACH) ( ) return TRUE: // ok ) MALT TLL LL IAL [Understonding AutoCAD's Database and Entity Structure 217 —————___ 11 ObjectARX EntryPoint extern “C” AcRx: :AppRetCode acrxEntryPoint(ACRx: :Ap2MsgCode msg, void* pkt) ( switch (msg) ( case AcRx::kInitAppMsg: /1 Comment out the following line if your JI application should be locked into memory acrxDynamicLinker->unlockAppl ication(pkt);, acrxDynamicLinker->registerAppMDIAware( pkt) : Initapplication(); break: case AcRx: :kUn] oadAppisg: UnloadApplication(); break: } return AcRx } // Init this application. Register your // commands, reactors... void InitApplication() ( // NOTE: DO NOT edit the following lines. TI UCAFX_ARX_INIT. AddCommand(*CH4_APPS”, “INSRTBLK", “INSRTBLK”. ACRX_CMD_MODAL, insrtB1k): TIT YRFX_ARX_INIT. /1 TODO: add your initialization functions acutPrintf("\nType \"INSRTBLK\” to execute. “); ) 1/ Unload this application. Unregister all objects 1/ registered in Initapplication. void UnloadApplication() t // NOTE: DO NOT edit the following lines. I UAPX_ARX_EXIT acedRegCnds->removeGroup(“CH4_APPS") : 17) )AFX_ARX_EXIT 17 000: clean up your application acutPrintf("Es%s”, “Goodbye\n”, “Removing conmandé group \"CH4_APPS\"\n"): ) :kRetOK; // This functions registers an ARX command. // Tt can be used to read the localized command name // froma string table stored in the resources. 218 void AddCommand(const char* cmdGroup, const charts cmdint, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtre? cmdProc, const int idlocal) { char _cmdLocRes(65]; // 1f idlocal is not -1, it’s treated as an ID for // a string stored in the resources. if (idlocal != -1) { // load strings from the string table and& register the command. oadString(_hdllinstance, idlocal, cmdLocRes,& 64) acedRegCmds->addConmand(cmdGroup, cmdInt,& cmdLocRes, cmdFlags, cndProc); d else /1 idlocal is -1, so the ‘hard coded’ // localized function name is used. acedRegCmds->addConmand(cmdGroup, cndint.& cmdloc, cmdFlags, cmdProc); ) Here is the listing for the user-defined command INSRTBLK in Ch4_6Commandspp: DLL 11 ObjectARX defined commands include “StdAfx.h” #include “StdArx.h” fHinclude “GeAssign.h” // This is command *INSRTBLK* void insrtalk() ( char _bIkName(501; AcDbDatabase *pCurDb; AcDbBlockTable *pBlkTable; AcDbBlockTableRecord *pB1kTableRecord; AcDbBlockReference *pInsrt0bj; AcDbObjectId IKI AcGePoint3d insPt; ‘int retCode; retCode = acedGetString(0, “\nEnter Block Name:& “, blkName); if(retCode != RTNORM || bikName(O] = ‘\0") ( acutPrintf("\nInvalid block name."); Understending AutoCAD’ Databae and Entity Structure return: ) pCurDb = acdbHostApplicationServices()¢ >workingDatabase(); // Check to see if the block table // has bikName pCurDb->getBlockTable(p81kTable, AcDb::kForRead) ; if (!pB1kTable->has(b1kName) ) t acutPrintf("\nBlock definition %s not found. “,@ bikName); pBIkTable->close(); return: } // Get the AcDbObJectId of the block 11 definition. PBIKTable->getAt(blkName, bIkId); pB1kTable->getAt(ACDB_MODEL_SPACE, & pB1kTableRecord, AcDb: :kForWrite): pB1kTable->close(): acedInitGet(RSG_NONULL, NULL); acedGetPoint (NULL, “\nPick insertion point: “.@ asDblArray(insPt)); plnsrt0bj = new AcDbBlockReference(insPt, blkId); // Here is where you can set scale, rotationg and other // properties to the block entity. If you want to // see the AcDbBlockReference class for mored details. pB1kTabl eRecord->apperdAcDbEntity(bIkId,& pinsrt0bj); pBIkTableRecord->close(): pinsrt0bj->close(): ‘Well, as you can see, it's not that difficult to add an AcDbBlockReference to the AutoCAD drawing. What about a block reference with attributes? Yes, that isa lit- tle more difficult, 50 let's talk about it before we list our next application, SAMPLE APPLICATION CH4_7 ACDBBLOCKREFERENCE, ACDBATTRIBUTE) ‘When we deal with blocks and attributes, there are two scenarios: the frst is when we are inserting a new block in the drawing that may or may not have attributes and the second is when the user selects a block entity (already in the database) for editing. We will start out by discussing the first scenario and we will also provide a sample appli- cation. Perhaps the frst question to ask yourself (from a programming point of view) is how do we know that an AeDbBlockReference has any attributes? If you look inside the various functions that AeDbBlockReference has to offer, you wort find any fanc~ tions that will tell you that the block insert has any attributes. So the question is still cout there, how do we know the block has atributes? The answer lies in the block table record that defines the makeup of the block definition. The block table record con- tains all the entities that describe the block as well as the attribute definitions (AcDbAttributeDefinition) that belong to the block. So if we look at the block table record (AeDbBlockTableRecord), we see that it has @ function that returns AdeskikTrue or Adesk:kFalse if it has attribute definitions. The function is AcDbBlockTableRecord::hasAtributeDefinitions(). Well, ifit turns out that there are attributes associated with our block definition and we want to navigate through those attributes, how do we do it? We attach a block table record iterator to our block definition using the AeDbBlockTable:newiterator( function. We then step through the entities looking for attribute definitions. When we find an attribute definition, we create an attribute (AcDbAttribute) using the properties of the attribute defini- tion. We then append the new attribute to the block reference. When you are finished with the block table record iterator, dont forget to delete it—that’s your responsibil- ity, Well, that’s the theory of how all this works, so lets illustrate this by way of an example. Then we will move on to selecting an existing block in the drawing that has attributes. Here is the listing for Ch4_7.arx application Cb4_7.opp. By the way, you will need to create a block with attributes to test the application. /I Ch4_T.cpp : Initialization functions 11 CHA_T.CPP /1 by Charles Mc Auley // “Programming AutoCAD 2000 with ObjectARX” 7/ In this application we show you how to // insert a block into your drawing that has 7/ attributes associated with it. You will // need to create a block with an attribute(s) 7/ in AutoCAD before you run this application. WW TLL LATTA WL finclude “StdAfx.h” finclude “StdArx.h” #include “resource. HINSTANCE _hd1lInstance =NULL ; // This command registers an ARX command. Understending AutoCAD Database and Entity Siracture 22) sr void AddCommand(const char* cmdGroup, const char*d cmdInt, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idlocal = -1); // NOTE: DO NOT edit the following lines. 7 ((AFX_ARX_MSG void InitApplication(): void UnloadApplication(): J ))APX_ARX_MSG. // NOTE: DO NOT edit the following lines. 7 {AFX_ARX_ADDIN_FUNCS 71) )AFX_ARX_ADDIN_FUNCS MATT TLL ALLL HNL /7 DLL Entry Point extern “C” BOOL WINAPI DI1Nain(HINSTANCE hinstance, DWORDE dwReason, LPVOID /*1pReserved*/) { if (dwReason = DLL_PROCESS_ATTACH) { _hd11Instance = hinstance; } else if (dwReason == DLL_PROCESS_DETACH) ( } return TRUE; // ok } LTT TTTLT LATTA TAL TTL TTL, 11 ObjectARX EntryPoint extern “C” AcRx: :AppRetCode acrxEntryPoint(ACRx::AppMsgCode msg, void* pkt) { switch (msg) ( case AcRx::kInitAppMsg: 7/ Comment out the following line if your /1 application should be locked into memory acrxDynamicLinker->unlockAppl ication(pkt):, acrxDynamicLinker->registerAppMDIAware( pkt) ; InitApplication(); break: case ACRx: :kUnloadAppNsg: Unloadapplication(); break: ) return AcRx::kRetOK: } // Init this application. Register your 1/ commands, reactors... void InitApplication() ( // NOTE: D0 NOT edit the following lines. THCCAPX_ARX_INIT ‘AddCommand(*CH4_APPS”. “INSRTBLK”, “INSRTBLK”. ACRX_CMD_MODAL, insrtB1k); TTY YAPX_ARX_INIT 11 T000: add your initialization functions acutPrintf(“\nType \"INSRTBLK\” to execute. “); ) // Unload this application. Unregister all objects // registered in InitAgplication. void UnloadApplication() ( // NOTE: 00 NOT edit the following lines. 7 UUAFX_ARX_EXIT. acedRegCnds->removeGroup(“CH4_APPS”); TLV YARXARX_EXIT 71 TODO: clean up your application acutPrintf(*Bs%s”, “Goodbye\n”, “Removings? command group \"CH4_APFS\"\n"); ) // This function registers an ARX command. // It can be used to read the localized command name // from a string table stored in the resources. void AddConmand(const char* cmdGroup, const charto cmdInt, const char* cméLoc, const int cmdFlags, const AcRxFunctionPtre? cmdProc, const int idlocal) t char cmdLocResl651: // If idlocal is not -1, it's treated as an ID for // a string stored in the resources. if (idLocal != -1) ( // oad strings from the string table andé register the command. r:LoadString(_hdilInstance, idLocal, cmdLocRes.¢ 64): acedRegCmds->addCommand(cmdGroup, cmdInt. cmdLocRes, cndFlags, cndProc); Understanding AutoCAD's Database and Entity Structure 23 ————___~— } else !/ idlocal is -1, so the ‘hard coded” // localized function name is used. acedRegCmds->addComnand(cmdGroup, cmdint,& cmdLoc, cmdFlags, cmdProc); ) Here is the user-defined command INSRTBLK in Ch4_7Commands.cpp: MTL TLL 11 ObjectARX defined conmands #include “Stdafx.h” #include “Stdarx.h" #include “GeAssign // This is command *INSRTBLK* void insrtBik() ( char bikName[50I; AcDbDatabase *pCurDb; AcDbBlockTable *pB1kTable; AcDbBlockTableRecord =pBlkTableRecord; AcDbBlockTableRecord =pB1kDefRecord; AcDbBlockReference *pinsrt0bj; AcDbEntity *pEnt: AcDbBlockTableRecordIterator *pIterator; AcDbAttributeDefinition *pAttDef; AcDbAttribute *patt: AcdbObJectId bIkId AcDbObJectId insrtid: char *pTagPrompt; AcGePoint3d insPt; AcGePoint3d basePt; int retCode: retCode = acedGetString(0, “\nEnter Block Name: % “, bikName) if(retCode != RTNORM || blkName[O] = *\0") ( acutPrintf("\nInvalid block name.”); return: ) PCurDb = acdbHostAppl icationServices() —>workingDatabase(); 11 Check to see if the block table 11 has bikName pCurDb->getBlockTable(pBIkTable, AcDb::kForRead): if(1pB1kTable->has (b1kName)) ( acutPrintf("\nBlock definition %s not found. & bikName); pBIkTable->close(); return; } J/ Get the AcDbObjectld of the block 11 definition. pBIkTable->getAt(bikName, bIkId); pBIkTable->getAt(ACDB_MODEL_SPACE, pB1kTableRecord, AcDb: :kForWrite); pBIkTable->close(); acedInitGet(RSG_NONULL, NULL); acedGetPoint(NULL, “\nPick insertion point:¢ “, asDblArray(insPt)); pInsrtObj = new AcDbElockReference(insPt, bikId): 7/ Here is where you can set scale, rotation andé other // properties to the block entity. If you want to // see the AcDbBlockReference class for mored details. pBIkTableRecord->appendAcDbEntity(insrtId,é pinsrt0bj): acdbOpenObject (pB1kDefRecord, bIkId,& AcDb: :kForRead) ; 17 Now check to see if the Block Definition // has attributes. If it does we will add // a Block Table Record Iterator to step through 17 the entities and find the Attributed Definitions. 1f(pB1kDefRecord->hasAttributeDefinitions()) pB1kDefRecord->newlterator(pIterator) : for(pIterator->start(); !plterator->done():¢ plterator->step()) ( plterator->getEntity(pEnt, AcDb: :kForRead); // Check to see if the entity is an // attribute definition. pAttDef = AcDbAttributeDef inition: :cast (pent); if(pAttDef != NULL && !pAttDef->isConstant()) ( Understending AutoCAD’ Database and Entity Sirucure 225 —————_ // If it is and it’s not constant 1/ create a new attribute PAtt = new AcDbAttribute(); // setPropertiesFrom will copy 11 Color, Layer, Linetype,Linetype scale and 11 Visibivity. pAtt->setPropertiesFrom(pattDef); // setup more properties from the attribute 11 definition pAtt->setInvisible(pAttDef->isInvisible()): basePt = pAttDef->position(); basePt + pInsrt0bj->position().asVector(); pAtt->setPosition(basePt) : pAtt->setHeight(pAttDef->hetght()): pAtt->setRotation(pAttDef->rotation()): 11 Take note how we get the tag. plagPrompt = pattDef->tag(); pAtt->setTag(pTagPrompt); free(pTagPrompt) ; 71 Normally you would prompt the user 11 and ask for input values. pTagPrompt = pattDef->prompt(): acutPrintf(“Zs%s". “\n", pTagPrompt); free(pTagPrompt /1 The setFieldlength is not required J/ even though it is listed in the 7/ documentation. pAtt->setFieldLength(25); 7/ setTextString is the value the 1/ attribute receives which would 7/ normally be a user input value. PAtt->setTextString(“This is a test”): pinsrt0bj->apperdattribute( pat); pAtt->close(); } pEnt->close(); VI for 1// if has attribute definitions delete pIterator; /1 Note that we close the Model Space 11 block table record after we have added 11 our attributes. pB1kTableRecord->closa(); pInsrt0bj->close(); ) ‘After inserting our block reference, we need to check if the block definition has attrib- utes defined. This is accomplished by the following code fragment. if (pB1kDefRecord->hasAttributeDefinitions()) Only if the block definition has attributes defined do we then attach an iterator to the block table record, after which we search for the attributes. The insertion point for an attribute definition is in terms of its location with reference to the block definition. ‘We need to take into account the location of the block reference point to get the cor- rect location of the attribute, as shown by the following code fragment: basePt = pAttDef->position(); basePt += pInsrt0bj->position().asVector(); pAtt->setPosition(basePt); Ifyou look at the functions for an attribute, a number of functions used above are not in the attribute clas, especially setTextStringQ). You will find the function defined in the base class of AeDBAttribute, namely AcDbText. I have mentioned before that, ifyou are looking for a function in a particular class and you cannot find it, perhaps it belongs to a base class. Okay, so let's move on to selecting a block that is already in the drawing. SAMPLE APPLICATION CH4_8 (ACDBBLOCKREFERENCE, ACDBATTRIBUTE) ‘When you select an entity, you have to test the entity to see ifit is a block reference. Ifit is a block reference, does it have attributes? If it does, this is where we attach an attribute iterator. As you will see in the next listing, the attribute iterator is attached to the block reference and steps through only attributes as opposed to the previous application, in which the iterator was attached to the attribute definition and stepped through all the entities in the block definition. Stepping through the attributes is pret- ty straightforward, with the exception of the user-defined function getAttDef(). I have to admit that I was surprised to find out that there was no direct method to get to the attribute definition given an attribute entity. As you will see in this function, we pass in the tag value of the attribute so we can search for the corresponding tag in the attribute definition. If found, we pass back a pointer to an AcDbAttributeDefinition. Imagine all that work we have to go through just to get the prompt value of the attribute, Here is the isting for the Ch4_8.arx application in Ch4_8.opp: J/ Ch4_8.cpp : Initialization functions 11 CH4_8.CPP Understanding AutoCADs Database and Entity Structe 27 $$ // by Charles Mc Auley // “Programming AutoCAD 2000 with ObjectaRX” // This application demonstrates how to iterate // through all the attributes of a selected block // reference and prompts the user to change // the value of the selected Attribute. In order // to use the application, you must have a block // entity already present in the drawing and that 41 block reference entity must contain attributes. UE Wit iinclude “StdAfx.h” fHinclude “StdArx.h” finclude “resource.h” HINSTANCE _hd1l Instance =NULL ; // This command registers an ARK command. void AddCommand(const char* cmdGroup, const charxs cmdInt, const char* cmdloc, const int cmdFlags, const AcRxFunctionPtre) cmdProc, const int idlocal = -1); // NOTE: DO NOT edit the following lines. H(APX_ARK_MSG void InitApplication(); void UnloadApplication(): J) )AFX_ARX_MSG J NOTE: DO NOT edit the following lines. 77 (AFX_ARX_ADDIN_FUNCS 71) AFX_ARX_ADDIN_FUNCS TUTTE TIT TTAATTL // DLL Entry Point extern “C” BOOL WINAPI DI1Main(HINSTANCE hInstance, DWORDS dwReason, LPVOID /*1pReserved*/) ( if (dwReason == DLL_PROCESS_ATTACH) { hdl Instance = hinstance; } else if (dwReason == DLL_PROCESS_DETACH) { ) return TRUE; = // ok } TLL LLL ELT TMT TTT 71 ObjectAaRX EntryPoint extern “C” AcRx: :AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt) q switch (msg) ( case AcRx: :kInitAppMsg: 7/ Comment out the following line 1f your 71 application should be locked into memory acrxDynamicLinker->unlockAppl ication(pkt); acrxDynamicLinker->registerAppMDIAWare( Pkt): Initapplication(): break: case AcRx: :kUnToadAppMsg: UnloadAppl ication(}; break: ) return ACRK::kRetOK; ) // Init this application. Register your /1 commands, reactors void Initapplication( J/ NOTE: DO NOT edit the following lines. TCCAFX_ARX_INIT. ‘AddCommand(“CH4_APPS*, “PKBLK”, “PKBLK”.< ‘ACRX_CMD_MODAL, pkb1k); 17) YAFX_ARX_INIT // T0D0: add your initialization functions ) // Unload this application. Unregister all objects 1/ registered in Initapplication. void UnloadApplicationt) ( J NOTE: DO NOT edit the following lines. 7HCCRPX_ARX_EXIT acedRegimds->removeGroup("CH4_APPS"); TT) YAFK_ARX_EXIT // TODO: clean up your application ) // This function registers an ARX command. // It can be used to read the localized command name // from a string table stored in the resources. Understending AutoCAD’ Databoe and Entity Structure 229 ————— void AddCommand(const char* cndGroup, const charté cmdInt, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idLocal) { char cmdLockes{651; // If idlocal is not -1, it’s treated as an ID for // a string stored in the resources. if (idLocal != -1) { 1/ Load strings from the string table andy register the command. riLoadString(_hdI Instance, idlocal, cmdLocRes,é 64): acedRegCmds->addComnand(cmdGroup, cmdInt,& cmdlocRes, cmdFlags, cmdProc): ) else 11 idlocal is -1, so the ‘hard coded’ IJ Vocalized function name is used. acedRegCmds->addComnand(cmdGroup, cmdInt. cmdLoc, cmdFlags, cmdProc); ) 11 User defined function called by 11 pkblk() see Ch4_8Conmands.. cpp AcDbAttributeDefinition* getattDef(char* tagValue.¢@ AcDbBlockTableRecord* p31kDefRecord) { AcDbAttributeDefinition *pRetattNef = NULL: ‘AcDbBlockTableRecordIserator *pAttDefltr; AcDbEntity *pents char thisTagl255]; char* pThisTag: pB1kDefRecord-»newlterator(pattOefltr) for(pattDefltr->starti); !pAttDefltr->done():e pattDefltr->step()) ( pAttDefltr->getEntizy(pEnt, AcDb::kForRead) : pRetAttOet = AcDbAttributeDefinition::cast(pEnt): if(pRetAttDef I= NULL && !pRetattDery >isConstant()) ( pThisTag — pRetattDef->tag(): strepy(thisTag, pThisTag): free(pThisTag); if(stremp(thisTag, tagValue) = 0) ( delete pattDefitr; return pRetattlef; ) ) pEnt->close(): M1 for delete pAttDefltr: return pRetattOet: ) Here is the listing for the user-defined command PKBLK in Ch4_8Commands.cpp: HALL LTT 71 ObjectARX defined commands Hinclude “Stdafx. Hinclude “Stdarx. 71 This is command ‘PKBLK* void pkbik() ( ads_name ent: ads_point pickPt; AcDbObjectId entId AcDbObjectId bikDef Id: AcDbObjectId attId: AcDbEntity “pent: AcDDBlockReference *pB1kRef: AcDbBlockTableRecord *p81kDefRecord: AcDbAttributeDefinition *pAttDef: AcDbObjectIterator *pattItr: AcDbAttribute *patt; char* pTagPrompt; char* pValue: char tagValue(2551; char promptValue( 255); char attStrValue[255]: char kwl4]; int retCode; retCode = acedEntSel(“\nSelect Block reference. “, ent, pickPt): switch(retCode) t case RTERROR : acutPrintf(“\nNothing selected!! “); Understending AutoCAD’ Database and Entity Structure 231 $$ a“ return; break: case RTCAN : ‘acutPrintf("\nUser canceled. “): return: break: ) // Get the Objectid of the selected entity acdbGetObjectId(entId, ent); acdbOpenAcDbEntity(pEnt, entId, AcDb: :kForRead); PBIKRef = AcDbBlockReference: :cast(pEnt); iF (pBIkRef == NULL) t acutPrintf("\nNot a block reference. “): pEnt->close(): return: ) bikDefId = pB1kRef->blockTableRecord( acdbOpenObject(pB1kDefRecord, bikDefId, ‘AcDb: : KForRead); if (1pBikDefRecord->hasAttributeDefinitions()) { acutPri attributes. pBikDefRecord->close(); pEnt->close(): return: } pAttItr = pBIkRef->attributelterator(): for(pattItr->start(); !pattItr->done( ig? pAttItr->step()) ( attld — pAttItr->robjectId(): pBIkRef->openAttribute(pAtt, attld,¢ AcDb: :kForRead): pTagPrompt = pAtt->tag(); strepy(tagValue, pTagPrompt); PAttDef = getAttDef{tagValue, pB1kDefRecord); free(pTagPrompt) ; if (pAttDef —= NULL) { patt->close(); break: f("\nThe selected block has nod m [Yes/No 1 ) pTagPrompt = pAttOef->prompt(): strepy(promptValue, pTagPrompt); free(pTagPrompt) ; pvalue = patt->textString(): stropy(attStrValue, pValue): free(pValue): acutPrintf("Zs%szsts", “\n", promptValue, - \"", attStrvalue); * changed acedInitGet (NULL, “Yes N retCode = acedGetkword("\. . kW): switch(retCode) ( case RTERROR : acutPrintf("\nError in acedGetkword “ break; case RTCAN acutPrintf(“\nUser canceled. breaks case RTNONE: strepy(kw, “No”): retCode =" RTNORM; break: } if(stremp(kw. “Yes") == 0) t acedGetString(1, “\nNew value: “.@ attStrValue); PAtt->upgradeOpen(); // Don’t forget to# upgrade it first!! } pAtt->setTextString(attStrValue); ) pAtt->close(); } delete pattitr; pB1kDefRecord->close(): pEnt->close(); ‘Wall, believe it or not, we are finally at the end of Chapter 4. Congratulations! Give yourself a pat on the back; this was a huge chapter. You will be happy to know that the rest of the chapters in this book are not this big. In the next chapter we will look at ObjectARX geometry classes. ObjectARX’s Geometry Classes In the previous chapter we covered ObjectARX’s database and entity classes. We looked at how the geometry classes helped us in creating, entities. In this chapter we want to take a look at those geometry classes in much more detail. During my years of involvement with AutoCAD, I wish they had geometry classes before Autodesk introduced ObjectARX—my life would have been easier. The first thing that I ‘want you to know about the geometry classes is that they are purely mathematical. ‘They are not entities nor do they create entities; their sole purpose is to assist us in our calculations so that we can create entities. So let’s start out with the AcGe primitives classes first. Here is a listing of those classes: AcGePoint2d AcGePoint3d AcGeVector2d AcGeVector3d AcGeMatrix2d AcGeMatrix3d AcGeScale2d AcGeScale3d AcGeKnotVector ‘AcGeCurveBoundry AcGeTo! AcGelnterval ACGEPOINT2D As before, I will talk only about the most useful classes and functions. The ‘AcGePoint2d and AcGePoint3d classes represent 2D and 3D locations in space. All geometry class calculations are in terms of the WCS (World Coordinate System). The AcGePoint2d class is an array of two doubles and the AcGePoint3d class is an array of three doubles. With either of the clases, if we create an object of the class 233 type without parameters it creates a location at the origin. There is also a copy con- structor. Here is a code fragment showing the various ways of creating geometry loca~ tions: AcGePoint2d locl; // Vocl at origin (0.0, 0.0) AcGePoint2d loc2(3.0, 4.8); // passing in two parameters AcGePoint2d loc3(loc2); // copy constructor loc3# = (3.0, 4.8); AcGePoint3d ptl; AcGePoint3d pt2(4.5, 2.3, 7.8); AcGePoint3d pt3(pt2); Here are some of the transformation functions for the AcGePoint2d class: AcGePoint2d: :mirror() AcGePoint2d: :rotateBy() AcGePoint2d: :scaleBy() AcGePoint2d: : transformly() ‘The AeGePoint2d::mirror() function expects an AcGeLine2d as an argument. An ‘AcGeLine2d is an infinite 2D line and can be oriented at any angle. Here is a code fragment that shows you how to use AcGePoint2d:mirror(: AcGePoint2d sp(5.0, 5.0): AcGePoint2d spl: AcGePoint2d epl(0.0, 5.0): // Note this line does? not have to be vertical AcGeLine2d lin(spl, epl); sp.mirror(lin): 11 sp should now be (-5.0, 5.0) ‘Why was I able to use spl in creating the line without init it? The default AcGePoint2d constructor creates a point at the origin. Now let’s turn our attention to the AcGePoint2d:rrotateBy() function. This function takes two arguments: the first is a double, which is the angle (in radians), and the second is an AcGePoint2d, which is the center of rotation. We then use the distanceTo() function to see if the distance between the rotated point and the origin is still the same. The function expects an AcGePoint2d argument and returns a double. We continue from the previous code fragment: J spl is still (0, 0) ObjecARXs Geometry Clases 235 // Continuing from before epl.set(1.0, 0.0); // Rotation origin double ang = 0.7853981567229: // 45 degrees ing radians // 1 radian = 57.29578 degrees epl.rotateBy(ang, spl); J/ epl should be approx (0.707, 0.707) double dist; dist = epl.distanceTo(sp1 // should be equal to 1.00 In the next code fragment, we scale an AcGePoint2d by using the AcGePoint2d::scaleBy() function. The first argument to the scaleBy() function is a double, which isthe scale factor. The second argument is an AcGePoint2d point, which is the scale from argument. After we scale the point, we create an AcGeMatrix2d matrix ‘mat2d, which is an identity matrix. We then create a translation vector in vee2d. Using. ‘matrix functions we can scale, translate, rotate, and mirror points. We change the iden tity matrix to a translation matrix using the AcGeMatrix2d:setTranslation() function ‘on the matrix mat2d. That translation is applied to the AeGePoint2d point, which retums us to our original location. Finally, inthis code fragment I show you how to use the scaling overloaded operators ,*, /, and f= and then the translation overloaded oper- ators +, +5, and -5, all of which expect AcGeVector2d arguments. Inthe las state~ ‘ment in this code fragment Im using the + operator to set ep| back to its original value because veetd is a negative vector. // spl is still (0, 0) // AcGePoint2d::scaleBy) epl.set(1.0, 1.0); epl.scaleBy(4.0, spl); // epl will now be (4.0, 4.0) // AcGePoint2d: : transformBy() AcGeMatrix2d mat2d; AcGeVector2d vec2d(-3.0, -3.0); mat2d.setTranslation(vec2d); epl.transformBy(vec2d) ; epl *= 4.0; // ep is now once again = (4.0, 4.0) scaled by 4.0 epl 4 vec2d: /1 epl is now once again = (1.0, 1.0) Here are some useful equivalence operator of the AeGePoint2d class: ==, !=, and isEqualTo(). These functions return Adesk::Boolean. The isEqualTo() function allows us to test if points are equal within a specified tolerance. The default value of an AcGeTol is I.e-10 (very small indeed); you can also supply your own tolerance value if you want. Here is a sample of its usage: AcGeTol tol: tol.setEqualPoint(0.1); AcGePoint2d pl(5.0, 5.0) AcGePoint2d p2(4.95, 4.95); if (pl. isEqualTo(p2)) { acedAlert (“Equal”); ) else { acedAlert(“Not equal”); ) if (pl.isEqualTo(p2, tol) fl acedAlert (“Equal”); ) else ( acedAlert(“Not equal”): ) ‘The AcGePoint3d functions are very similar to those of AcGePoint2d. MATRIX OPERATIONS ‘There are two matrix classes in the geometry classes, namely AcGeMatrix2d and AcGeMatrix3d, These classes are heavily tied into the AcGeVector2d and AcGeVector3d classes. In Chapter 3 we saw how to use a matrix operation on a selec~ tion set using acedXformSS0) In Chapter 3, a number of the sample applications used matrices, So just what is a matrix? A matrix allows us to do one, all, or a combination of rotation, scaling, mirroring, and translation of points. In order to use AcGeMatrix2d, you will need to include gemat2d.. An AcGeMatrix2d is a 3 x 3 array of doubles. We will start out by showing you what state an AcGeMatrix2d is in when it is created. AcGeMatrix2d mat2d: ‘This would produce a matrix that looks like this: 1.0 0.0 0.0 ObjectARX's Geometry Clases, 237 ae oe 2 0.0 1.0 0.0 0.0 0.0 1.0 In all AcGeMatrix2d operations we ignore the bottom row. What happens to the ‘matrix when we apply various operations to the matrix? Here is the scaling matrix operation: AcGePoint2d pnt: AcGeMatrixed mat2ds mat2d = mat2d.scaling(2.0, pnt); This would produce the following matrix: 2.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 1.0 So scaling operations always affect the following elements of a matrix: [0][0] and [II[I]. Here is a mirror operation: AcGePoint2d pnt; AcGePoint2d p1(0.0, 0,0); AcGePoint2d p2(0.0, 5.0): AcGeLine2d lin(pl, p2): AcGeMatrix2d mat2d; mat2d = mat2d.mirroring(1in); ‘This would produce the following matrix: “1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 Here is a rotation operation: AcGePoint2d cp: double ang = 0.7853981587229; // 45 degrees ing radians AcGeMatrix2d mat2d: mat2d = mat2d.rotation(ang, cp); ‘This would produce the following matrix: 0.707 -0.707 0.0 0.707 0.707 0.0 00 8 8=©60.0 1.0 Rotation and mirror operations always affect the following elements of the matrix: £07103, [07£19, [1700], and [1][1]. Now let’s take a look at a translation operation: AcGeMatrix2d mat2d; AcGeVector2d vec(2.0, 2.0); mat2d = mat2d.translation(vec); ‘This would produce the following matrix: 1.0 0.0 2.0 0.0 1.0 2.0 0.0 0.0 1.0 ‘Translations affect the [0][2] and [1][2] elements of a matrix. When we deal with matrices, the elements [0][0], [O0E17, [11(0], and [VIE] are referred to as the linear portion of the matrix and elements [0][2] and [1][2] are referred to as the transla~ tion portion of the matrix. How do we apply the matrix functions to the points specified hy AeGeMatriv2d? We use the transformBy0 function as shown by the fol- lowing example: 11 epl is an AcGePoint2d 11 AcGePoint2d: :transformBy() AcGeMatrix2d mat2d: AcGeVector2d vec2d(-3.0, -3.0); mat2d.setTranslation(vec2d); epl.transformBy(mat2d) ; We also have a number of set functions that make it somewhat easier to deal with matrix data types. AcGeMatrix2d AcGeMatrixad AcGeMatrixad AcGeMatrixad setToMirroring() setToRotation() setToScal ing() setToTranslation() ObjecARX's Geometry Clawes 239 AcGeMatrix2d::setTrans1ation() ‘We could use these functions in the following manner: AcGeMatrixed mat2d; AcGePoint2d pt; double sf = 2.0; mat2d.setToScaling(sf, 9): (Or we could have done the following: mat2d = mat2d.scaling(sf, pt); So what is the difference between AcGeMatrix2d and AcGeMatrix3d? An ‘AcGeMatrix2d is an array of 3 x 3 doubles. An AcGeMatrix3d is an array of 4x 4 doubles. This is what a matrix looks like when we create an AcGeMatrix3d object: AcGeMatrix3d mat3d; Here is the matrix: 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 In this AcGeMatrix3d matrix we ignore the bottom row just as we did with the AcGeMatrixdd. Elements [0][3], [1][3], and [2]3] are the translation elements for X,Y, and Z respectively. The scaling elements are [0}[0J, [1][1J, and [2][2] for the X,Y and Z scale factors. The elements [O}{01, C0111, [0(21, (13001, (13047, £17021, [2100], [2101] and [2112] are the elements that we use for mirror and rotate functions. Just as with the AcGeMatrix2d classes, there are a number of useful and similar set functions in the AeGeMatrix3d classes, which I will let you explore. ACGE 2D ENTITY CLASSES Even though the classes are called 2D Entity classes, the geometry classes are just ‘mathematical representations of entities—they do not create entities. Here isa list- ing of all the AeGe 2D geometry clases: 2D Entities [accerntaty2a [AcGePointEnt2a [acGePointoncurve2d [ncGePositionza lAcsecarveza [AcGeLinearEnt2a ‘GeLineSeg2a GeRay2a [acceLine2a [acGecirearcza [AcGeraiiparcza [AcGeExternalCurve2d [AcGeConpositecurve2d [ncceortsetcurveza [AcGespiinerntza ‘GePolylineza [AcGecuniesplinecurve2d [acGewarhCurve2a [AcGeBounaBiock2a [AcGeCurveCurveint2d Figure 5.1 Geometry Class 2D Entities ObjectARX’s Geometry Clases 241 ACGEENTITY2D ‘The class AcGeEntity2d is the base class for the AcGe 2D Entities. This class and the classes that are derived from this class represent database-resident equivalent enti- ties. These classes are just mathematical representations of their equivalent entity types. ‘When we deal with the AcGe entity types, the base class (AeGeEntity2d) has the fol- lowing type identification functions: Adesk: :Boolean AcGeEnt ity2d: : iskindOf(AcGe: :Entityldg entType) const AcGe::Entityld AcGeEntity2d:: ype() const ‘You would typically use these functions when you offset AcGe entity objects and you want to test what kind of entities the offset entities are. These functions either take asa parameter or return AeGexEntityld, whose definitions are as follows: AcGe ( static const AcGeLibVersion glibVersion; static int gLastError; enum { eGood, eBad }; enum Entityld { kentity2d, kEntity3d, kPointEnt2d, kPointEnt3d, Lots more here kEI1ipCone, KEI1ipCyl inder, kIntervalBoundB1ock, KC1ipBoundary2d a ‘These definitions are located in gegbi.2. There is also an abbreviated version of def- initions in geglbabl:b, which allows you to remove the AeGer: prefix. Other useful finc- tions in the AcGeEntity2d base class are the transformation functions: m AcGeEntity2 AcGeEntity2d: AcGeEntity2d: :transformBy() AcGeEntity2d: :translateBy() AcGeEntity2d::mirror() rotateBy() scaleBy() ‘These functions are identical to their counterparts in the database-resident entities. Another useful function is the AcGeEntity2d::isOn() as follows: Adesk::Boolean AcGeEntity::isOn(const AcGePoint2d& pnt, const AcGeTol& tol) const ‘This function returns Adesk::Boolean and tests whether AcGePoint2d pnt with the AcGeTol tol is on the geometry entity. There are also equality checking functions and copy functions. ACGECURVE2D ‘Now let’s turn our attention to the AcGeCurve2d class. Here is a list of the ‘AcGeCurve2d functions that we will cover in this section: AcGeCurve2d: :paramOf() AcGeCurve2d: : reverseParam( ) AcGeCurve2d: :distanceTol ) AcGeCurve2d: :closestPointTo() AcGeCurve2d::1sOn() AcGeCurve2d: shasStartPoint() AcGeCurveed: :hasCndPoint() AcGeCurve2d::isLinear() AcGeCurvezd: : Tength() AcGeCurve2d: :paramAtLength() AcGeCurve2d: :eval Point () AcGeCurve2d: :getSamplePoints() ‘As you can see from Figure 5.1, the AcGeCurve2d class is also the base class of many curvature classes such as lines, circles, ares, polyline, offsets, and others. Base classes usually contain common functionality for all of their derived classes. The ‘AcGeEntity2d class contains common functionality forall of the 2D AeGe primitives classes. There are a number of functions in the various AcGe classes that require a knowledge of math (yes, that dreaded subject). You will be happy to know that we are not going to be too involved in the math functions (I have to brush up on my math skills too). The AcGeCurve2d class has many functions. Here are the AeGeCurve2d parameterization functions: AcGeCurvegd: :getInterval() ObjectARX's Geometry Clases 243 AcGeCurvegd: :paramOf() AcGeCurve2d: : reverseParam() ‘We will take a look at the paramOfQ function first. Here is the prototype of the paramo function: double AcGeCurve2d::param0f(const AcGePoint2d& pnt, const AcGeTol& tol) const ‘This function returns the parameter value of pnt. It assumes that pn¢ les on the curve and does not verify this. If pnt does not ie on the curve, this function will return ‘unpredictable results. You need to check to see ifthe point pnt lies on the curve firs; this can be achieved with the tsOnQ functioa, which we will discuss later. So just what does it mean when they say that this function will retumn the parameter value of the point? Perhaps the following sample code fragment better explains this: AcGeCircArced geark(pl, p2, p3); rad = geark.radius(); cen = geark.center(): paraml = geark.paramOf(p1, tol): param2 = geark.paramOf(p2, tol): param3 = geark.paramOf(p3, tol); Inth = geark.length(paraml, param3, 0.0); Let's assume the pt has a value of (1,0), p2 has a value of (2,2), and p3 has a value of G0). The arc will have a clockwise directicn starting at pl, passing through p2, and ending at p3. If we examine the values of param, param2, and param3, they will have values of 0.00, 2.21, and 4.42 respectively. What do these values mean exactly? Thave to admit I'm not sure, but I do know that the start point will always have a param value of 0.00 and it increases as you move along the curve. Look at the value of p2: it is 2.21, which is half the value of p3 at 4.42. IfT wanted to only travel one quarter the distance along the curve, I would divide the value of the parameter at p3 ‘by 4 and then call the function evalPoint() (discussed later), which would return the coordinate at the quarter mark. If that was not the coordinate I wanted I could call reverseParam() and reverse the parametric direction of the curve. In the example code fragment we are using an arc as an example, but this could be any kind of complex curve. If wanted to find the length along the curve between two points on the curve, could get the parameters at the points anc then use the length() function to return the length along the curve between the two points. Now that we are on the subject of length, look at how we find the length of the arc: Inth = geark.length(paraml, param3, 0.0); ‘The first two arguments are double types, in this case the parameter values of pl and p3. The third parameter, which is a double also isa tolerance value, which is not the same kind of tolerance as the AeGeTol class. (Strange, you would think they would be consistent!) Using the previous code fragment as an example, let’ look at the distance To) finc- tion. It is an overloaded function that will find the closest distance between two curves or a reference point and a curve. AcGePoint2d ref; AcGeTo! tol: double len: // using our previous curve Jen = geark.distanceTo(ref, tol): In this code fragment, ifr has a value of 0,0) and tol has a default value of 1.e-10, the length from the reference point ref to the nearest point on the ar, ifusing the pre~ vious values of pl, p2, and p3, will be 0.83. Using AutoCAD, manually draw a 3-point arc that passes through (1,0) (2,2), and (3,0), and then draw a circle that has a cen- ter of (0,0) with a radius of 0.83. You will see that the circle is tangent to the arc at the point closest to the reference point. Speaking of closest point, there is also a closestPointTo() function. This is also an overloaded function that finds the two clos- est points between the reference curve and an input curve. The second version also returns the point on the reference curve closest to the input point. We continue with our sample code fragments: AcGePoint2d np; np = geark.closestPointTo(ref, tol); As a result of this code the AeGePoint2d np will have a value of (0.83, 0.31). ‘As mentioned earlier when dealing with parameter values, for example, we need to test first if point is on the curve before we execute the paramOM() function. To test ifa point is on a curve, we use the isOnQ) function; here is its prototype: Adesk: :Boolean AcGeCurve2d::isOn(consté AcGePoint2d& pnt, const AcGeTol& tol) const Adesk: :Boolean AcGeCurve2d: : isOn(doubled Param, const AcGeTol& tol) const Adesk: :Boolean AcGeCurve2d::isOn(const AcGePoint2d&e pnt, double& param, const AcGeTol& tol) const ‘As you can see, this function is overloaded and each version returns Adesk::Boolean. ‘The first version takes a point and a tolerance as parameters. The second version takes 1 param value and a tolerance. Remember, in the second version the param value that ObjectARX’s Geometry Clases 248 ‘you pass in could be greater than the maximum param value of the curves. The final version of this function takes three parameters; if the point is on the line, it will return. in the pnt parameter the param value at that point. if(geark.isOn(pnt, tol) ( acedAlert(“Point is on line”); } ‘The function evalPoint() retums the coordinates of a param value along a curve. There are two versions of this function; here is the version that I use most often: AcGePoint2d AcGeCurve2d: :evalPoint (double parame const Here is an example of its usage using the values we obtained previously: AcGePoint2d testPt; testPt = geark.evalPoint(param2); // testPt will contain the value (2, 2) ‘The paramAtLength() function allows us to find a param value at a specified distance along a curve from a datum reference poin:. Here is the function prototype: double AcGeCurve2d::paramAtLength(double datumParam, double len, Adesk::Boolean posParamDir, double startoint().x,3 plapLine->startPoint().y, plapLine->endPoint().x,0 plapLine->endPoint().y } // Don't forget to check if pLapLine is not NULL // if so delete it when you are finished with it. if(plapLine != NULL) { delete pLapLine; ) Pay particular attention to how we had to cast the pLapLine argument to get this to work. This code fragment will produce the following at the AutoCAD command prompt: Overlapped line segment is from (2.00, 2.00) tod (5.00, 5.00) ‘Moving on, the isColinearTo0, isParallelTo0, and isPerpendicularTo() functions all return Adesk:Boolean and take the same kinds of arguments. Hlere are their proto- types: Adesk: :Boolean AcGeLinearént2d::isColinearTo(conste AcGeLinearEnt2d& line, const AcGeTol& tol) const Adesk: :Boolean AcGeLinearEnt2d::isParallelTo(conste AcGeLinearEnt2d& line, const AcGeTol& tol) const Adesk: :Boolean AcGeLinearnt2d::sPerpendicularTo(const# AcGeLinearént2d& line, const AcGeTol& tol) const The function IsColinearTo() is a special kind of isParalletTo(). Lines that are collinear are definitely parallel; however, lines that are parallel on not necessarily collinear. Again continuing with the previous code example, here is a code frag- ment outlining the use of isColinearTo0, isParallelTo(), and isPerpendicularTo(): 1f(1s1.isColinearTo(1s2, tol)) ( acutPrintf(*\nLines are colinear™); } if(1s1.isParallelTo(1s2, tol) ( acutPrintf("\nLines are parallel"); ) ObjecARX’s Geometry Clases 254 $$. J/ Note here the user of the ! operator // because in this case I know the lines /1 are not perpendicular iF(1151. isPerpendicularTo(1s2, tol) ( acutPrintf("\nLines are NOT perpendicular” ) ‘When we deal with AcGeLineSeg2d or AcGeRay, these entities are bounded either in both directions, in the case of an AcGeLineSeg2d, or bounded in one direction, in the case of AcGeRay. If you want to create an AcGeLine2d entity from cither an ‘AcGeLineSeg2d or an AcGeRay, the getLine() function will return an infinite line that is coincident with the source line. Here's a code fragment outlining its usage using the AcGeLineSeg2d Is! we saw earlier: AcGeLine2d newline; Isl.getLine(newLine); // newline will be an infinite line 11 of type AcGeLine2d ‘The function getPerpLine() will return an AcGeLine2d perpendicular to the given line and passing through the supplied AeGePoint2d point, as shown in the prototype for getPerpLine(): void AcGeLinearént2d: :getPerpLine(conste AcGePoint2d& pnt, AcGeLine2d& perpLine) const Here is an example of its usage: pl.set(0, 0) p2.set(S, 5): AcGeLineSeg2d 1sl(pl, p2): AcGePoint2d testPt(3, 3); AcGeLine2d perpLine: Isl.getPerpLine(testPt, perpLine); // perpline is now an infinite AcGeLinezd // that is perpendicular to Isl and passes // through (3, 3) ACGECIRCARC2D ‘The AcGeCireAre2d class represents both full circles and circular arcs in 2D space. In order to use this class you will have to include the geare2dh header file in your appli- cation. In one of the sample applications we saw how to create AcGeCireArc2d enti- ties. There are a number of overloaded constructors, which allow you to create these kinds of entities, but I will let you explore them for yourself. What I'm interested in iis what kind of properties and geometric constructions I can use for calculation pur- poses. So with that said, here is a list of the functions that we will talk about in this section: angent() AcGeCircArc2d: : isInside( ) AcGeCircArc2d: :radius() enter() AcGeCircArc2d::isClockWise() ‘When dealing with circles and ares, we can use the intersectWith function to check if an AcGeLinearEnt2d or an AcGeCircAre2d entity intersect. Both versions of this function return Adeste:Boolean. Here is the prototype for the first version: Adesk: :Boolean AcGeCircArc2d:: intersectWith(const AcGeLinearEnt2d& line, int& irtn, AcGePoint2d& pl, AcGePoint2d& p2, const AcGeTol& tol) const ‘This version tests to see ifa linear entity line intersects with our arc or circle. If so, the second argument intn will return the number of intersection points. The value of intn will be 0, 1, or 2. Ifthe intn value is 0, then there is no intersection and the value of pl and p2 will be undefined. Ifintn is 1, then there will be a single intersection and pl will be initialized to the value of the intersection point. If the line crosses through the circle, the value of intn will be 2. In that case the arguments pl and p2 will be valid. ‘The last argument is an AcGeTol value. Here is the prototype of the second version: Adesk: :Boolean AcGeCirckrc2d:: intersectWith(const AcGeCircArcad& arc, int& intn, AcGePoint2d& pl, AcGePoint2d& p2, const AcGeTo1& tol) const ‘The only difference between the first ard second versions is the first argument, which in this case is an AcGeCircArc2d entity. Here isa sample code fragment show- ObjectARX's Geometry Clases 253 $$$. ing how to use the intersectWith() function using the values we obtained previously ‘against a linear entity: AcGePoint2d Ipl, 1p2, 1p3; Ipl.set(O, 1); Ip2.set(5, 1); VineEnt.set(1pl, 1p2): AcGePoint2d intPl, intP2; int intPts: geark.intersectWith(1ineEnt, intPts, intPl, intP2,¢ tol); switch(intPts) { case (2): acutPrintf(“\nTwo intersection points found”); acutPrintf(“\nat (%.21f, %.21f) and (%.21f, B21)", intP1.x, intPl.y, intP2.x, intP2.y): break; case (1): acutPrintf(“\nOne intersection point found até (B21F, 8.218)", intPl.x, intPL.y); break: case (0): acutPrintf(“\nNo intersection found”); break; ) ‘This code fragment produced the following output at the AutoCAD command prompt: Two intersection points found at (3.22, 1.00) and (0.78, 1.00) Now look at the following code fragment using the values we obtained previously: AcGeLine2d perpLine: AcGePoint2d intP3(2, 2 Vinegnt.getPerpLine(intP3, perpLine): geark.intersectWith(perpLine, intPts, intPl, intP2,¢ tol); switch(intPts) ( case (2 acutPrintf(“\nTwo intersection points found"); acutPrintf("\nat (2.21f, .21f) and (%.21f, 8.21)", intPL.x, intPl.y, intP2.x, intP2.y)i break: case (1): acutPrintf("\nOne intersection point found ate (B.21f, &.218)", intPl.x, intPl.y); break; case (0 acutPrintf("\nNo intersection found” break; ) This code fragment produced the following output at the AutoCAD command prompt: One intersection point Found at (2.00, 2.00) ‘The tangent() function will return the tangent line to the circle at a given input point only ifthe input point is on the full circle. Here is the function prototype: Adesk: :Boolean AcGeCircArc2d: : tangent (const, AcGePoint2d& pnt, AcGeLinezd& line, const AcGeTol& tol) const This function returns Adeste:Boolean. Ifthe input point pnt is on the fill crcl, this function will return Adeste:kTrue. In addition to returning Adeskc:kTrue, it will also return AcGeLine2d as a line that is tangent to the arc or circle at the AcGePoint2d pnt within the AeGeTol tolerance. Using the isInside() function allows you to query if a point is inside an arc or circle. This function treats the arc asa fall circle even ifit is bounded and determines ifthe input point lies inside the full circle. Here is the function prototype of isinside(): ObjeccARX's Geometry Clases 255 a cr Adesk: :Boolean AcGeCircArc2d: : isInside(consté AcGePoint2d& pnt, const AcGeTol& tol) const AcGePoint2d pnt is the point tested within tolerance AcGeTol tol. In sample application Co4_2.arx in the last chapter, we saw how to use the following. AcGeCircArc?d functions, AcGeCircAre2d:rradius(), AcGeCircArc2d::center() and AcGeCircArc2d:isClockwise(). There are also a number of other useful query functions and set functions associated with this class that are pretty self-explanatory; Iwill leave it as an exercise for you to explore, ACGE 3D ENTITY CLASS HIERARCHY Here I just wanted to show you a listing ofall the AcGe 3D Entity classes (Figure 5.2). will leave it up to you to explore what these classes have to offer. Now that we have covered the geometry classes, le’s build a more sophisticated application using the geometry elements that we have learned. In this application we are going to use more error checking than we have done before. Also, this application will be more object-oriented than previous applications. In previous applications, while wwe have be using C++, the examples have been procedural. If you don't know what classes, virtual functions, inheritance, private, protected, and public specify, you may need to brush up on your standard C++. If you are only alittle rusty in these areas, this application will suffice as a reminder. This application also demonstrates code reuse. So with that introduction, what does this application do? It draws three kinds of win- dows: rectangular, arched, and apex. Users ace asked to choose the kind of window, as mentioned earlier. They are asked to enter the window length and the window height. They are then asked for the number of rows and columns that the rectangu~ lar portion of the window will have. As you will see in Figure 5.3, the number of rows and columns refer to the number of glass panes that lie in between the internal members of the rectangular portion of the windows. If the user specifies one row and ‘one column, there will be no internal members in the rectangular portion of the win- dow. Finally, users are asked to select the lower left point for the window and voila, ‘we have a window! The following illustration demonstrates the three kinds of win- dows that this application will generate. In this example illustration, each window is sixty inches high by forty inches wide and has three rows and three columns. ‘3D Entities ‘3D Entities (contd) [Rccobatatyaa [actesurtace (nccePaintEatea [nctecene [ActetyLinaor [ActePointOncarvead [Rotespnere [ActePointonSurtace [Rcseronitionsa Cad —_ [ActeoreectSurtace [ActeinternalioundedSurface [actetinesevaa [aceenaysa [accocirenreaa SS [accertaiparosa [AcScBownadiockia [ActeCompariteCarve3d ES [AcseCurveCurveintoa [accespiincmntsa [aceePonyiinesa ehugPolyiine3d [accecubicspiinecarvesa [actemurbcarveda Figure 5.2 Geometry Class 3D Entities Figure 5.3. Ch5_I Application Window Types ‘This application is by far the largest application that we have created so far. First I will briefly discuss the classes that make up the three types of window, and then I will dis~ cuss the driver application in detail, after which I will return to a detailed discussion of each of the classes. Here is the code listing for the driver application, which is Ch_S.cpp: J/ CHS_1.cpp // by Charles Mc Auley // “Programming AutoCAD 2000 with ObjectARX” // This application draws three kinds of windows. // The window styles are Arch, Apex and Rectangular. 1/ This application defines 3 window type classes. // The base class is RectWindow and the derived // classes are ArchWindow and ApexWindow. We also // demonstrate Object Oriented techniques and coded’ reuse. /1 This application also does more error checkingd than before. // This application also makes use of ObjectARX'sé geometry classes. an MUMUMUUUUATTA wi #include // acdb definitions #include // ads defs #include /! unlock application #include #include /! Symbol Tables #include /! for asDblArray() #include /! acdbHost Applicat ionServices() 17 TODO: add your own includes finclude “RectWindow.h” // RectWindow class finclude “ArchWindow.h” // ArchWindow class #include “ApexWindow.h” // ApexWindow Class // Function prototypes // entry point for this application extern “C” AcRx::AppRetCode acrxentryPoint( AcRx::AppMsgCode msg, void* ); // helper functions void initApp (void): void unloadApp( void); // user defined functions void windo(: Adesk: :Boolean getWindowInputParameters(RectWindow*d pWindow) : Adesk: :Boolean getModel SpaceRecord(AcDoBlockTableRecord*ad? pB1kTableRecord); TTT TIT ObjecARXs Geometry Clases 259 // acrxEntryPoint( internal) // This function is the entry point for youre application. TAUNTS TAT AcRx: :AppRetCode acrxEntryPoint(AcRx: :AppMsgCoded msg, void* ptr) ( switch (msg) ( case AcRx::kInitApplsg : acrxUnlockApplication( ptr); acrxRegi sterAppMDlAware(ptr) ; initappQ; break; case ACRx::kUnToadAppisg + unToadApp(); break: defaut break; ) // switch return AcRx::kRetOK: ) void initAapp(void) t /1 T0D0: init your application // register a command with the AutoCAD commande mechanism acedRegCmds ->addCommand(*CH5_APPS”, “WINDO”, WINDO", ACRX_CND_MODAL, windo); acutPrintf(“Enter \"WINDO\" to execute thee application. \n"); void unloadApp( void) { // TODO: clean up your application // Remove the command group added viae acedRegCmds- >addCommand acutPrintf(“Ss%s”, “Goodbye\n”, “Removing command? group \"CHS_APPS\"\n"); ‘acedRegCnds->removesroup(CHS_APPS") ; ) // T0D0: add your other functions here void windo() { AcDbBlockTableRecord *pB1kTableRecord; char kw(20]; int re: // Return code RectWindow *pRectWindow; // RectWindow class ArchWindow *pArchWindow; // Derived ArchWindowe class ApexWindow *pApexWindow: // Derived ApexWindowe class acedInitGet(NULL, “Arch apeX Rect”); re = acedGetKword(“\nSelect window typed + TArch/apeX/Rect]: “, kw); switch(re) { case RTCAN: acutPrintf(“\nUser cancelled. return; break: case RTERROR: acutPrintf("\nError with selection type. return; break: case RTNONE: strepy(kw, “Rect” re = RTNORM; pRectWindow = new RectWindow: ObjectARXs Geometry Clases 261 ————— if (1getWindowInputParameters(pRectWindow) ) t acutPrintf("\nError in window inputé parameters. “); if(pRectWindow != NULL) i delete pRectWindow: } return; } if (1 getModel SpaceRecord( pB1kTableRecord)) { return; } pRectWindow->drawdindow(pB1kTableRecord) ; if (pRectWindow != NULL) { delete pRectWindow; ) pBIkTableRecord->close(); break: case RTNORM: if(stremp(kw, “Arch") == 0) (i pArchWindow = rew ArchWindow; if (1 getWindowInputParameters (pArchWindow) ) ( acutPrintf("\nError in window inpute parameters. “); if(pArchWindow != NULL) { delete pArchWindow; } return; } if (!getModel SpaceRecord( pB1kTableRecord) ) ( return; ) pArchWindow->drawWindow( pB1kTableRecord); if(pArchWindow t= NULL) { delete pArchWindow; ) pBIkTableRecord->close(); WL it else if(stremp(kw, “Rect") = 0) ti pRectWindow = new RectWindow: if (1getWindowInputParameters(pRectWindow)) { acutPrintf(“\nError in window inpute parameters. “); if (pRectWindow != NULL) ( delete pRectWindow: ) return } 1 f(1getModel SpaceRecord(pB1kTableRecord)) ( return; ) pRectWindow->drawWindow(pB1kTableRecord) : if(pRectWindow = NULL) " gerete pRectWindow: ) pB1kTableRecord->close(); V/ielse if ObjecsARK's Geometry Claser else if(stremp(kw, “apeX") = 0) ti PApexWindow = new ApexWindow: if (1getWindow Input Parameters (pApexWindow) ) ( acutPrintf(“\nError in window inpute parameters. “); if (pApexWindow != NULL) ( delete pApexWindow: return; ) if (1getModel SpaceRecord( p81kTableRecord) ) { return; ) pApexW/indow- >drawWindow(pB1kTableRecord); if(pApexWindow != NULL) ( delete pApexWindow: ) pBIkTableRecord->close(); ) else fi acutPrintf("\nError in retrieving keyword. & return; W/ else break; VI switch } Adesk: :Boolean getWindewInputParameters (RectWindow*e? pWindow) 283 AcGePoint2d startPt; AcGePoint3d pickPt; int rows = 1, cols = 1; double windLength, windHei ght; ‘int re; // Return code acedInitGet(RSG_NONULL + RSG_NONEG + RSG_NOZERO, & NULL); rc = acedGetReal(“\nWindow Height: “.¢ awindHei ght); if(re —= RTCAN || re == RTERROR) { return Adesk: :kFalse: ) acedInitGet(RSG_NONULL + RSG_NONEG + RSG_NOZERO, & NULL): rc = acedGetReal(“\nWindow Length: “,¢ awindLength); if(rc = RTCAN || rc == RTERROR) { return Adesk: :kFalse; } acedInitGet(RSG_NOZERO + RSG_NONEG, NULL); rc = acedGetint(“\nNumber of window columnse for none: “, &cols); if(re == RTCAN || rc —= RTERROR) (i return Adesk::kFalse: ) acedInitGet (RSG_NOZERO + RSG_NONEG, NULL); re = acedGetInt(“\nNunber of window rows d for none: “, &rows): if(re == RTCAN || rc == RTERROR) { return Adesk::kFalse; ) ObjetARX's Geometry Clases acedInitGet(NULL, NULL); re = acedGetPoint(NULL, “\nPick lower left windowd insertion point : “,¢ asDblArray(pickPt)): switch(re) fi case RTCAN: case RTERROR: return Adesk break; kFalses case RTNONE: startPt.x = 0; startPt.y = break: case RTNORM: startPt.x = pickPt.x; startPt.y = pickPt.y: break; } // Fill out the window class object 7/7 with the input values pWindow->setWindowLength(windLength) : pWindow->setWindowHeight (windHei ght) : pWindow->setWindowCols(cols); pWindow->setWindowRows (rows): pWindow->setWindowStartPoint(startPt); return Adesk::kTrue; Adesk: :Boolean getMode1 SpaceRecord(AcDbBlockTableRecord*ag pBIkTab1eRecord) { AcDbDatabase *pCurDb; AcDbBlockTable *pBIkTable; 265. Acad: :ErrorStatus es; pCurDb = acdbHostApplicationServices() —>workingDatabase(); es = pCurDb->get8lockTable(pBIkTable, # AcDb: :kForRead. if(es I= Aca { acutPrintf("\nFailed to open Block Table for ag read operation. return Adesk ) e0k) es = p81kTable->getAt(ACDB_MODEL_SPACE, pB1kTableRecord, AcDb::kForWrite): if(es != Acad: :e0k) { acutPrintf(“\nFailed to open MODEL SPACE for ad write operation.”): pBIkTable->close( return Adesk: :kFalse: } J/ We don’t need the block table anymore so wed can close it. pBikTable->close(); return Adesk: :kTrue; ) ‘The listing above is the driver application that contains the acrxEntryPoint for the application and defines a command called windo. Notice that there are three user- defined header files that represent the three classes or types of windows we are drawing. The base class is Reet Window, and both ArchWindow and ApexWindow are derived from RectWindow. In the windo() function we have three pointers, ‘one for each class type as follows: RectWindow *pRectWindow: // RectWindow class ArchWindow *pArchWindow: // Derived ArchWindows class ApexWindow *pApexWindow: // Derived ApexWindowe class ObjectARX's Geometry Clases 267 ~~ Which of these pointers gets initialized will be decided by which option the usr selects, Let's assume that the user selected the Rect option; pRectWindow will be initialized by creating a Rect Window object. We then call getWindowinputParameters() with a pointer of the appropriate type. The getWindowinputParameters() returns an ‘Adeste:Boolean true or filse value. The interesting part of this function is how we ini- tialize the class member variables as follows: // Fill out the window class object // with the input values pWindow->setWindowLengta(windLength) ; pWindow->setWindowHet ght (windHei ght) : pWindow->setWindowCols(cols); PWindow->setWindowRows (rows) : pWindow->setWindowStart?oint(startPt): Here is another interesting point about this function. Originally, when I was coding this function, I forgot that acedGetPoint() expects an ads_point data type, which is an array of three ads_real values. I passed in an AcGePoint2d point parameter, which initialized the x and y values okay; however, it set the variable windLength to 0.0, Having realized the error of my ways, I passed in an AcGePoint3d point. The ObjectARX-supplied function asDblArray() converts the ads_point to an array of three double values, which is what an AcGePoint3d is, When asked to select a lower left location for the window start point, the user has a choice of entering a value or pressing ENTER for a default value of (0, 0). When I pressed ENTER, the asDblArray() filled in the point pickPt variable with erroncous values. Every time I ran this I got different values, so this is why I precede the acedGetPoint() function with a call to acedinitGet(NULL, NULL). This will allow acedGetPoint() to return RTNONE, which I can trap in the switch statement. Then I explicitly set the values of the startPt x and y values to zero. If all goes well, this function returns AdeskikTrue. Next we make a call to getModelSpaceRecord(), which returns Adesk::kTrue if the model space block table record was successfully opened for a write operation. Let's look at the function prototype: Adesk: :Boolean getModel SpaceRecord(AcDbBlockTableRecord*&d pBlkTableRecord) ; ‘Why do we pass in the address of the AeDbBlockTableRecord pointer? Inside the getModelSpaceRecord() we initialize the AeDbBlockTableRecord pointer, but not before it’s passed in. Ifyou dont pass in the address of the pointer and try to initial- ize the pointer, you will get an exception error. In this application I decided to open the model space block table record for a write operation once and then pass this point- er to the class responsible for drawing the window. After we are finished, we then close the block table record. You don't have to do this if you don't want to. For every entity created you can open and close the model space block table record for a write ‘operation on an entity by entity basis. Look at how we call the window draw function: pRectWindow- >drawWindow( pB1kTableRecord); ‘This could equally be an ArchWindow poirter or an ApexWindow pointer. The point Tm trying to make is that the classes are responsible forall the drawing and calcula tions required by each window type. So that now takes us to the classes themselves; ‘we will start out with the base class ReetWindow. Hire is the listing for the head- er file for the base class Rect Window, RetWindow.h: 11 RectWindow.h 17 by Charles Mc Auley 17 “Programming AutoCAt 2000 with ObjectARX” ifndef RECTWINDOW_H fidefine RECTWINDOW_H include class RectWindow ‘ public: RectWindow(); // Constructor RectWindow(); // Destructor void drawWindow(AcbBlockTableRecord*# pB1kTableRecord) ; void setWindowRows(int rowQty); void setWindowCols(int coldty); virtual void setWindowHeight (double windHeight); void setWindowLength(double windLength); void setWindowStartPoint(AcGePointzde lowLeftPoint) : protected: double getWindowLength() const; ObjectARX's Geometry Clases 269 double getWindowHeight() const; AcGePoint2d getWindowStartPoint() const: double getIntrFrameThk() const; double getRectframeThk() const; Adesk::Boolean drawIntBar(AcDbB1ockTableRecord*? pBlkTableRecord, AcGePoint2dArray intBarar); private: Adesk: :Booleang drawRectFrame(AcObBlockTableRecord* p81kTableRecord, AcGePoint2dArray &inrRectFrame); int getWindowRows() const; int getWindowCols() const: int rows; int cols: double rectFrameLen; double rectFrameHt; double rectFrameThi double intrFrameThl AcGePoint2d lowLeft?t; is dendif In this class we have public and protected functions along with private functions and private data members. The public functiors are accessible from the calling (driver) application, as we have seen in the calls from the driver application to the set func~ tions. The other public function that is called from the driver application is drawWindow(), which is responsible for drawing the window. Other public functions in this header file ae the constructor and destructor. When you declare a variable of a classtype or use the new operator to allocate memory for a class ofthis type, the con- structor is automaticaly called. For those of you who need a quick refresher, the con- structor has the same name as the class and has no return type. In our application, there is only one version of the constructor, but it can be overloaded. Usually, in a constructor, you do initialization of any variable members the class has. When we made the fol- Jowing callin the driver application, the corstructor was automaticaly called; we will see this later when we discuss the implementation of these functions. pRectWindow = new RectWindow; When you delete the class pointer or when the variable goes out of scope, the destructor is automatically called. The descructor has the same name as the class but hhas a tilde character in front of the function name. The destructor does not return a data type. Also, it cannot be overloaded and does not take any parameters. Usually —// acdb definitions include // ads defs finclude #include finclude —// AcDbPolyline #include #include itinclude “RectWindow.h” RectWindow i // Initialize the rect Frame Thickness // and the internal Window Framing thickness ectWindow(} rectFrameThk = 2.00; intrFrameThk = 1.01 rows cols = 1; rectframeLen = 0.0; rectFrameHt = 0.0; lowLeftPt.set(0.0, 0.00); ) RectWindow { // Does nothing ) RectWindow() void RectWindow: :drawWindow(AcDbB1ockTableRecord*& pB1kTableRecord) cl AcGePoint2dArray inrRectFrame; AcGePoint2dArray inrTopAr, inrBtmAr, inrLeftAr.é inrRtars AcGePoint2dArray barAr; AcGeLineSeg2d inrTop, inrBtm, inrLeft, inrRt; AcGeVector2d vec: AcGePoint2d sp, ep, 11, Ir, ul, ur; double frameThk; int arSize: int rowPos, colPos; int rows, cols; if(!drawRectFrame(pB1kTableRecord, inrRectFrame)) { ) return; rows = getWindowRows( cols = getWindowCols( // Check to see if rows and cols are // both 1, in that case we return. if(rows = 1 && cols = 1) { ) return; arSize = inrRectFrame.length(); if(arSize I= 4) ( acutPrintf(“\nInvalid internal rect framee array. “): return; ) // Get the inner Frame thickness ObjectARX's Geometry Clases frameThk = getIntrFrameThk(); barAr.setLogicalLength(0); J/ Initialize the line segments inrTop.set(inrRectfrane.at(3), inrRectframe.at(2)); inrBtm.set(inrRectframe.at(0), inrRectframe.at(1)); inrLeft.set(inrRectFrame.at (0), inrRectFrame.at(3)): inrRt.set(inrRectFrame.at(1), inrRectFrame.at(2)): 11 Now that we have the line segments, get thee point /1 along the lines trat match the row and cole count 11 Note: The getSamplePoints() function willd include // the point at the begining as well as the point // at the end of the line. We are interested ing the // points in the middle. inrTop.getSamplePoints(cols + 1, inrTopAr); inrBtm.getSamplePoints(cols + 1, inrBtmAr); inrLeft.getSamplePoints(rows + 1, inrLeftAr); inrRt.getSamplePoints(rows + 1, inrRtAr); if(cols = 1 && rows > 1) i // Draw horizontal bars only for (rowPos = 1: rowPos < rows: rowPost+) ( // Get the start point and the end pointe from the array sp = inrLeftar.at(rowPos); ep = inrRtAr.at(rowPos): // from sp and ep calculate 11, Ir ,ul, ur vec.set(0, (framethk / 2)); V1 = sp.- vecs Ir = ep - ve ul = sp + vec: By) oy ur = ep + vec; // Add the point to the barAr array barAr.append( 11); barAr.append( Ir): barAr.append(ur barAr.append(ul); if(1drawintBar(p8lkTableRecord, barAr)) t acutPrintf(“\nE-ror drawing internald horizontal frame member. “); barAr.setLogicalLength(0); Vi for Wi if if(rows == 1 && cols > 1) JJ Draw vertical bars only for (colPos = 1; colPos < cols; colPost+) ( // Get the start point and the end pointe from the array sp = inrBtmAr.at(co1Pos): ep = inrTopAr.at(colPos): 17 from sp and ep calculate 11, Ir ,ul, ur vec.set((frameThk / 2), 0 11 = sp - vecs In = sp + vec: = ep - vec: = ep + vec; J/ Add the point to the barAr array barAr.append(11 barAr.append(1r); barr. append(ur): barar.append(ul): if(1drawintBar(pBlkTableRecord, barAr)) ( acutPrintf(“\nError drawing internal ObjectARK’s Geometry Clases 275 horizontal frame member. ) barAr.setLogicallength(0); Wd for Writ if(rows > 1 && cols > 1) i // Draw both kinds of // internal bars 1/ Draw all the horizontal bar first for(rowPos = 1; rowPos < rows: rowPos++) t for(colPos = 1: colPos <~ cols: colPost!) { sp.x = inrBtmAr.at(colPos - 1).x: sp.y = inrLeftar.at(rowPos).y: ep.x = inrBtmAr.at(colPos).x: ep-y = inrleftar.at(rowPos).y: “1 Calculate 11, Ir, ul, ur if(colPos = 1) { vec.set(0, (frameThk / 2)); 11 = sp ~ vec; ul = sp + vec vec.set( -(frameThk / 2), -(frameThk /o 20s Ir = ep + vec; vec.set( -(frameThk / 2), (frameThk / 2)); ur = ep + vec; // Add the point to the barAr array barAr.append(11); barAr.append(1r); barAr.append(ep); barAr.append(ur); barAr.append(ul); if (IdrawintBar(pB1kTableRecord, barAr)) fl acutPrintf(*\nError drawing internalé horizontal frame member. “); 7% d barAr.setLogicalLength(0); ) else if(colPos = cols) { vec.set((frameThk / 2), (frameThk / 2))s ul = sp + vec: vec.set((frameThk / 2), -(frameThk /2)); 11 = sp + vec: vec.set(0, (frameThk / 2)); Ir = ep - vec ur = ep + vec: barAr.append(sp): barAr.append(11) barAr.append(1r) barAr. append(ur) barAr.append(ul); if(!drawIntBar(pB1kTableRecord, barAr)) { acutPrintf("\nError drawing internald horizontal frame member, “); 2) ) bard. setLogicalLenath(0): else ( vec.set((frameThk / 2), (frameThk / 2))3 ul = sp + vec: vec.set((frameThk / 2), -(frameThk /2)); 11 = sp + vec; vec.set( -(frameThk / 2), (frameThk / 2)); ur = ep + vec; vec.set( -(frameThk / 2), -(frameThk /¢ Ip = ep + vec; barAr.append( sp); barAr.append(11): barAr.append(1r): barAr.append(ep); ObjectARX's Geometry Clases 277 barr. append(ur barAr.append(ut if(!drawIntBar(pB1kTableRecord, barAr)) { acutPrintf(“\nError drawing internald horizontal frame member. “); ) barAr.setLogicalLength(0: ) VI for colPos = 1; VW for // Draw all the vertical bars for(colPos = 1; colPos < cols; colPos++) ( for(rowPos = 1; rowPos <= rows; rowPos++) i sp.x = inrBtmAr.at(colPos).x; sp.y = inrLeftar.at(rowPos - 1).y: ep.x = inrBtmAr.at(colPos).x: ep.y = inrLeftar.at(rowPos).y; if(rowPos == 1) i vec.set((framathk / 2), 0); 11 = sp - vec: Ir = sp + vec; vec.set( -(frameThk / 2), -(frameThk /& 2): ul = ep + vec vec.set( (fra ur = ep + vec: “1 Add the point to the barAr array barAr.append(11); barAr.append(Ir); barAr.append(ur); barAr.append(ep); barAr.append(ul); hk / 2), -(frameThk / 2) if(drawIntBar(pB1kTableRecord, barAr)) { acutPrintf(*\nError drawing internald horizontal frame member. “); 2) ) barAr.setLogicalLength(0); ) else if(rowPos = rows) { vec.set((frameThk / 2), 0); ul = ep - vec; ur = ep + vec: vec.set ( -(frameThk / 2), (frameThk /2 11 = sp + vec; vec.set ( (fremeThk / 2), (frameThk / 2)); Ir = sp + vec; // Add the point to the barAr array barr. append(sp); barAr. append(1r) barAr. append(ur) barAr. append(ul): barAr.append(11); if(!drawIntBar(pB1kTableRecord. barAr)) ( acutPrintf("\nError drawing internald horizontal frame member. “); 200s barAr.setLogicalLength(0); } else ( vec.set((frameThk / 2), (frameThk /2)); Ir = sp + vec: ul = ep - vec: vec.set ( -(frameThk / 2), (frameThk /& 11 = sp + vecs ur = ep - vec: ObjecARX's Geometry Clases 279 $$ J Add the point to the barAr array barAr.append(sp); barAr.append(ir) barAr.append(ur) barAr.append(2p); barArappend(ul); barAr.append(11); if(1drawIntBar(pB1kTableRecord, barAr)) fl acutPrintf(*\nError drawing internale horizontal frame member. “); ) barAr.setLogicalLength(0); ) VW for rowPos = VWI for Whit ) // Get and Set functions ‘int RectWindow: :getWindowRows() const { return rows; ) int RectWindow::getWindowCols() const ( return cols: ) double RectWindow::getWndowHeight() const ( return rectFrameHt; ) double RectWindaw. { jetWindowLength() const return rectframeLen: ) AcGePoint2d RectWindow: :getWindowStartPoint() const { return lowLeftPt: ) double RectWindow: :getRectFrameThk() const i return rectframeThk; } double RectWindow: :getIntrFrameThk() const { return intrFrameThk: ) void RectWindow::setWindowRows(int rowQty) ( rows = rowOty; ) void RectWindow::setWindowCols(int coldty) ( cols = coldty: ) void RectWindow: :setWindowHei ght(double windHei ght) { rectFrameHt = windHeigh ) void RectWindow: :setWindowLength(double windLength) i rectframeLen = windLength; ) void RectWindow: :setWindowStartPoint(AcGePoint2de lowLeftPoint) ( lowLeftPt = lowLeftPoint; ) ObjectARKs Geometry Clases 28) Adesk: :Boolean RectWindow: :drawRectFrame( AcDbB1ockTableRecord*# pBlkTableRecord, AcGePoint2dArray& inrRectFrame) i AcGePoint2dArray outrFrame; AcDbPolyline* pFrame; AcDbObjectId plineld; AcGeVector2d vec; AcGePoint2d 11, Ir, ur, ul; AcGePoint2d vrtxPt; double len, ht, frameThk; Acad: :ErrorStatus es; // Get the window length, height and start point Jen = getWindowLength(); ht = getWindowHeight(); 11 = getWindowStartPoint(): // Calculate the other 3 corner points WW from 11 vec.set(len, 0); Ir = 11 + vec: vec.set(0, ht); ur = Ir + vee: ul = 11 + vec: // Add the four calculated points to the array outrFrame, append(11); outrFrame. append( Ir); outrFrame. append(ur); outrFrame.append(ul); pFrame = new AcDbPolyline(4): for(int count = 0; count < outrFrame.length();¢ count++) ( vetxPt = outrFrame.at(count); pFrame->addVertexAt count, vrtxPt); ) pFrame->setClosed(Adesk: :kTrue) ; es = pBIkTableRecord->appendAcDbEntity(plineld, & pFrame) if(es I= Acad: :e0k) ( acutPrintf(“\nError in drawing window frame. if(pFrame != NULL) ( delete pFrame: ) return Adesk::kFalse; ) pFrame->close(); // Get the frame thickness frameThk = getRectFraneThk(): // Offset the four points 11, Ir, ur and ul // by the frame thicknesss vec.set(frameThk, franeThk); 11 4= vec: ur -= vec: vec.set(-frameThk, frameThk); Ir + ver vec.set(frameThk, -frameThk); ul += vec // Append the newly calculated points 1/ to the AcGePoint2dArray parameter 11 inrRect Frame inrRectFrame.append(11); inrRectFrame.append(17); inrRectFrame.append(ur) inrRectFrame.append(ul) pFrame = new AcDbPolyline(4); for(count = 0; count < inrRectframe.length():% count+) i vrtxPt = inrRectFrame.at(count); ObjetARX's Geometry Classes 783 pFrame->addVertexAt(count, vrtxPt); ) pFrame->setClosed(Adesk: :kTrue); es — p81kTableRecord->appendacDbEntity(plineld.& pFrame); if(es I= Acad: :e0k) ( acutPrintf(“\nError in drawing window frame. “): if(pFrame != NULL) { delete pFrame: ) return Adesk::kFalse; ) pFrame->close(): return Adesk } True: Adesk: :Boolean RectWindow: :drawIntBar(AcDbBlockTableRecord* pBkTableRecord, AcGePoint2dArray intBarAr) { AcDbPolyline* pBars AcGePoint2d vrtxPt; AcDbObjectId plineld; Acad: :ErrorStatus es: pBar = new AcDbPolylineCintBarAr.1ength()); for(int count = 0; count < intBarAr.length():¢ count++) ( vetxPt = intBarAr.at(count); pBar->addVertexAt(count, vrtxPt); ) pBar->setClosed(Adesk: :kTrue); es = pBIkTableRecord->appendAcDbEnt ity (plineld. & pBar if(es != Acad: :e0k) ( acutPrintf("\nError in drawing frame member if(pBar != NULL) { delete pBar; ) return Adesk: :kFalse; ) pBar->close(); return Adesk::kTrue; ) ‘When we create a RectWindow class with the new operator, the constructor is called, which in turn initializes the private data member of our class to default values. In the constructor I initialize the rectFrameThk to a value of 2.00 and the intrFrameThk to a value of 1.00. In the d:iver application T do not ask the user for values of these variables, but if you wanted to, you could. The driver application calls drawWindow() and passes in an AeDbBlockTableRecord pointer pBIkTableRecord. Note that the block table record is already open in write mode; all we are doing is pass- ing in a pointer to the block table location. The drawWindow() function in turn calls other helper functions to draw the rectangular window. You are already familiar with the data types that are declared in this function, so let's move on to the first helper function that drawWindow() calls, namely drawRectFrame(). drawRectFrame(pBIkTableRecord, inrRectFrame) In the drawRectFrame() function we pass in a pointer to the AeDbBlockTableRecord and the address of the AcGePoint2dArray variable inrRectFrame. The reason we send in the address of the AeGePoint2dArray is that when the function is finished draw- ing the outer rectangular frame, we pass back to the calling function the four inner cal- culated corner points in the array inrRectFrame. This array will be used by the other functions that draw the inner members of the rows and columns, if the user has specified a column and a row count. The drawRectFrame() function calls getWindowLengthQ), getWindowHeight(), getWindowStartPointQ, and getRectFrameThic) to retrieve the class's private data and assign those values to the appropriate variables. The rest of the function is pretty straightforward in that we ObjectARK's Geometry Clases 285 $$ uw manipulate vectors to calculate our points and create two lightweight polylines. If all ‘went well we return Adeste:kTrue. We then return to drawWindow(), where we call. getWindowRows() and getWindowCols( to retrieve the number of columns and rows entered by the user. Remember that the number of rows and the number of columns determine the number of internal glass panes. If rows and cols are both equal to 1, we do not draw any internal members—in this condition there is just single pane of glass. If either or both rows and cols are greater than 1, we then initialize the AcGeLineSeg2d lines. The AcGeLineSeg2d are like lines in that they are not infi- nite in length, but they have a beginning and an end point. Next we fill up ‘AcGePoint2dArray arrays with the cal to getSamplePoints() as follows: inrTop.getSamplePoints(cols + 1, inrTopAr); inrBtm.getSamplePoints(cols + 1, inrBtmAr); inrLeft.getSamplePoints(rows +1, inrLeftar); inrRt.getSamplePoints(rows + 1, inrRtAr); ‘The function getSamplePoints() includes both the beginning and the end points of the line in the array, but we are only interested in the middle points. So if the user enters a value of 4 for the cols variable, a value of 5 points (cols + 1) will be added to the array. Ignoring the beginning and the end points, that leaves us with three points. For four columns of glass panes, there will be three sets of vertical internal members. When the internal members are drawn, there are a number of scenarios at ‘work, as follows: if(cols = 1 && rows > 1) { /7 Draw horizontal bar only AE if(rows == 1 && cols > 1) ( // Draw vertical bar only ) if(rows > 1 && cols > 1) { J/ Draw both kinds of // internal bars ) Let’ look atthe frst two cases. In these cases, we draw either horizontal internal bars or vertical internal bars. These elements consist of four vertex polylines. The AcGePoint2dArray arrays are passed into the drawintBar() function, which is responsible for drawing the polyline. In the for loop, atthe row position we get the start point sp and the end point p; these are the middle points of the inrLfeAr and the inrRtAr. We calculate the four points using our vector and build up the array barArQ. The block table record pointer pBikTableRecord and the AcGePoint2dArray barAr are passed into the drawintBar() function. At the end of the for loop, we set the logical length of the array back to 0 using the setLogicalLength() function. The vertical bar scenario is identical to the horizontal bar scenatio. If the rows and the eols are both greater than 1, then we draw horizontal and verti- cal internal members on the window. First we draw the horizontal member, and then ‘we draw the vertical members. Refer to Figure 5.4, which displays a single horizon- tal row of internal members. will be deaum ao 6 elenent vertex polylines ; "— - | ; a ; “T —T Figure 5.4 Ch5_I Application Internal Window Members ‘When drawing the horizontal members, we have a for loop inside a for loop. We start at the first row and then move into the inner for loop, where we start on the left side of the window because colPos will be equal to 1. We then get the spx, sp.y, ep.x, and epy from the arrays at the appropriate row-column position. Because colPos is equal to 1, we calculate a 5-point AcGePoint2d array, which we pass into the drawintBar() function, which creates the polyline entity. If the cols variable is ObjectARX’s Geometry Class 287 ——————— greater than 2, then we are in the situation where we pass in 6-point vertex ‘AcGePoint2dArray to the drawintBar( function. Refer to the illustration above and look at the middle elements. Finally, colPos will equal the cols variable; in this case wwe are on the right side of the window. In this case we pass in a S-vertex AcGePoint2dArray to the drawintBar() function. If there are multiple rows, in order to get back to the left side of the window we need to set colPos = | in the outer for loop. We then move up a row and repeat the process again until all the horizon- tal rows are complete. Following the horizontal rows, we then process another for loop inside a for loop in order to create the vertical members. The mechanisms in this process are identical to the first “loop inside a loop” process. ‘Well, that takes us to the end of how the Rect Window class works. Now let’s move con and take a look at the frst of the derived classes, ArchWindow. Here is the list- ing for the ArchWindow.h file: 11 AechWindow. hy /! by Charles Mc Auley 71 “Programming AutoCAD 2000 with ObjectARX” fifndet ARCHWINDOW_H define ARCHWINDOW_H finclude class ArchWindow : public RectWindow { public: ArchWindow: :ArchWindow(); ArchWindow: :~ArchWindow() ; void drawWindow(AcCbBlockTableRecord*# pB1kTableRecord) ; void setWindowHeigtt (double windHei ght); private: Adesk::Boolean drawArch(AcDbBlockTableRecord*# pB1kTableRecord) ; Adesk: :Boolean drawArc(AcDbBlockTableRecord*# pB1kTableRecord, AcGeCircArc2d geArc); Adesk: :Boolean drawArchInterna1Bars(AcDbB1 ockTableRecord* pBlkTableRecord, AcGePoint2darray arcPtar, AcGePoint2d cp, AcGeCircArc2d inrGearc, AcGeCircArcéd smlGeArc): double rectFrameHt; endif ‘The first thing that we notice about the 4rch Window. file is that itis smaller than RectWindow.b, Als, this clas is derived from the RectWindow class. Why is it small- ex? The ArchWindow class inherits all the functionality and data types that are contained in the base class in addition to defining its own functionality and data types. ‘Also, there are no protected members in this class because there are no classes derived from this class. Let's look at the public functions first. First we have the usual constructor and destructor, followed by drawWindow0, which is responsible for drawing the window. We also have the setWindowHeight() function, whose functionality is different fom that ofthe base class. Inthe private area of the class we have helper drawing, finc~ tions, namely drawArch(), drawArc(), and drawArchinternalBars(). There is also a single private data member, rectFrameHt. Remember that this class inherits all the data members of the base class. Let’s move on and look at the implementation of this class; here is the listing of the ArchWindow class ArchWindow.cpp: 11 ArchWindow. cpp // by Charles Mc Auley // “Programming AutoCAD 2000 with ObjectARX” #include // acdb definitions #include // ads defs #include #include finclude finclude finclude #include #include #include #include #include “RectWindow. h” #include “ArchWindow. h” ArchWindow: :ArchWindow() void ArchWindow: :drawWindow(AcDbB1ockTableRecord*# pBIkTableRecord) fl // Call the base class to draw the // rectangular part of the window RectWindow: :drawhindow(pB1kTableRecord) : if (IdrawArch(pB1kTableRecord) ) ( acutPrintf("\nError drawing arch. “) return: ) ) Adesk: :Boolean ArchWindow: :drawArch(AcDbB1 ockTableRecord*< pBIkTableRecord) c AcGePoint2d sp, tp, ep, cp, 11Pt; AcGeVector2d vec; AcGePoint2dArray arcPtars double windLen, windkt, frameThk, smIRad: windLen = getWindowLength(); windHt = getWindowHeight(); 11Pt_ = getWindowStartPoint(); frameThk = getRectFrameThk(); // Calculate the start point, top point 17 and end point of the arc. vec.set(0, windHt); sp = 11Pt' + vec: vec.set(windLen, 0): ep ~ sp + vec; vec.set((windlen / 2), (windlen / 2)): tp = sp + vect AcGeCircArc2d outrGeArc(sp, tp, ep): if(1drawArc(pB1kTablekecord, outrGeArc)) ( return Adesk: :kFals2; ) J Now draw the inner arc // Move sp, tp and ep to the inner 71 arc point vec.set(frameThk, 0); Pp vec.set(0, frameThk tp “= vec; AcGeCircArc2d inrGeArc(sp, tp, ep); if(IdrawAre(pBIkTableRecord, inrGearc)) ( return Adesk: :kFalse; ) // now draw the small arc and make its radius 7/1/10 th, of the windLen smlRad = windLen / 10: cp = inrGedrc.center(): vec.set(smlRad, 0); sp = cp - vecs ep = cp + veci vec.set(0, smiRad); ObjectARXs Geometry Claes 291 $$$ tp = cp + vec: AcGeCircArc2d smiGeArc(sp, tp, ep): if(IdrawArc(pB1kTableRecord, smlGeArc)) ( return Adesk: :kFalse; } // Get 5 sample point along the inrGeArc inrGeArc.getSamplePoints(5,. arcPtar): if(\drawArchInternaléars(pB1kTableRecord, arcPtar, cp, inrGeArc, smlGeArc)) ( return Adesk::kFalse: ) return Adesk::kTrue; ) Adesk: :Boolean ArchWindow: :drawArc(AcDbBlockTableRecord*s pBlkTableRecord, AcGeC'rcArc2d geArc) fl AcDbAre *pArcs AcDbObjectId arcld: Acad: :ErrorStatus es: AcGePoint2d sp, cp, AcGePoint3d cenPt; double sang, eang, rad: sp = geArc.startPoint(); ep = geArc.endPoint(): cp = geArc.center(): rad = geArc.radius(): sang = geArc.startang( eang = geArc.endAng(); cenPt.set(cp.x, cp.y. 0.0); pArc = new AcDbArc(cenPt, rad, sang, eang); es = pB1kTableRecord->appendAcDbEntity(arcld,& parc); if(es != Acad::e0k) { if (parc != NULL) { delete pArc: } return Adesk: :kFalse; ) pArc->close(): return Adesk::kTrue; ) Adesk: :Boolean Archiindow: :drawArchInternalBarsé (AcDbBlockTableRecord *9B1kTableRecord, AcGePoint2dArray arcPtar, AcGePoint2d cp, AcG2CircArc2d inrGeArc, AcGaCircArced smlGeArc) AcDbLine *pLinEn' AcDbObjectid linEntId; AcGeLine2d cenLine, offLinel, offLine2; AcGePoint2d sp, ep, startPtl, endPtl, startPt2.@ endPt2; AcGePoint3d startPt3d2, endPt3d1; AcGePoint2dArray offCurviAr, offCurv2Ar; AcGeTol tol; double inrFrmthk; int intn; inrFrmThk = getIntrFrameThk( sp = cp: for(int count = 1; count < (arcPtar.length() -# 1); count++) ( ep = arcPtAr.at (count); cenLine.set(sp. ep): AcGeOffsetCurve2d offCurvi(cenLine, (inrFrnThke ObjetARX's Geometry Clases 293 12005 AcGeOffsetCurvezd cffCurv2(cenLine, -(inrFrmThke 12): offCurv1.getSamplePoints(2, offCurviAr); offCurv2.getSamplePoints(2, offCurv2Ar); offLinel.set(offCurvlAr.at(0); of fCurviAr.at(1)); offLine2.set(offCurv2sr.at(0), of fCurv2ar.at(1)); inrGeArc.intersectWith(offLinel, intn, endPtl, startPtl, tol); smlGeArc. intersectWith(offLinel, intn, startPt2, endPt2, tol); startPt3d2.set(startPt2.x, startPt2.y, 0.0); endPt3di.set(endPti.x, endPti.y, 0.00); pLingnt = new AcDbLine(startPt3d2, endPt3dl); pB1kTableRecord->appendAcDbEntity(1inEntId, pLinént): pLinEnt->close(); inrGeArc. intersectWith(offlinez, intn, endPtl,é startPtl, tol); smiGeArc. intersectWith(offLine2, intn, startPt2,¢ endPt2, tol); offlinel.set(offCurvlAr.at(0), offCurviAr.at(1)); offLine2.set(offCurv2Ar.at(0), offCurv2Ar.at(1)); startPt3d2.set(startPt2.x, startPt2.y, 0.0); endPt3d1.set(endPtl.x, endPtl.y, 0.00); pLinEnt = new AcDbLine(startPt3d2, endPt3d1): pB1kTableRecord->appendAcDbentity(1inentId, & pLinEnt): pLinEnt->close(); VI for return Adesk::kTrue; y 294 void ArchiWindow::setWindowHeight (double windHei ght) i double windLen = getWindowLength(); rectFrameHt = windHeight - (windLen / 2); RectWindow: : setWindowHei ght (rectFrameHt) ; ) Before I talk about the drawWindow() function, I want to talk about the setWindowHeightQ) function. Remember that in the base class the setWindowHeight() function is a virtual function. In the driver application, the ‘getWindowinputParameters() function takes a RectWindow pointer. If we do indeed pass in a Rect Window pointer, the base class version of setWindowHelght() is called. The ArchWindow and the ApexWindow classes are both derived from RectWindow. Apart from passing in a Rect Window pointer, you can also pass in an ‘ArchWindow or an ApexWindow pointer, because ArchWindow and ApexWindow each form an ‘s a relationship with RectWindow. Apart from inheriting the functions and data member of the base class, the derived classes also inherit a virtual function pointer table. This table contains the address of the functions to call. So if, for example, we pass in an ArchWindow pointer, the tual function pointer table knows which version of setWindowHeight() to call; in this case, it will the setWindowHeight() function in the ArchWindow class. Remember, the name of a function is the address ofthe Zunction, just the way the name of an array is the address of the array. The height of all the window types is the same. However, the rectangular portion of the ArchWindow and the ApexWindow is smaller than that of the RectWindow. In this function we recalculate the height of the rectangu- lar portion for the ArchWindow and we also call the base class and reset the height of the window in the base class as follows: RectWindow: : setWindowHei ght (rectFrameHt); So now let's get back to the ArchWindow drawWindow() function. Here is where ‘we get code reuse. As you can see, the drawWindow() function is pretty small. The first thing that we do is call the base class drawWindow(), which draws the rectan- ‘gular portion of the window as follows: RectWindow: :drawWindow(9B1kTableRecord) ; Next we call the drawArch() function, which in turn makes calls to the helper pri- vate functions drawArc() and drawArchinternalBars(). The drawArc() and drawArch() functions are pretty straightforward; however, prior to exiting the drawArch() function we get five sample points along the inrGeAre, Again here, I am using a hard-coded number to draw four panes of glass in the arch portion of the win- ObjetARX's Geometry Claes 295 i dow. (If you wanted to, you could ask the user to specify the number of panes of glass in the arch portion of the window.) I am interested in the three middle sample points along the inrGeAre. Also, the radius of the smlGeAre is made to be one-tenth the window length. This was just my preference; you could ask the user for a length if you wanted to. ‘The drawArchinternalBars() function takes five parameters: a block table record pointer, the array of sample points along the inrGeAre, the center point of the arc, and the geometry arcs inrGeAre and smIGeAre. The heart of the function is inside the for loop. Here we create an AcGeLine2d infinite line cenLine from the center point sp to ep (sp was made equal to ep prior to the for loop). Then we create two AcGeOfisetCurve2d entities on cither side of the AeGeLine2d and we get sample points on either end of the AcGeOffsetCurve2d entities. Ifyou offiet an infinite line, should the offset entity also be infinite? Well, surprise, surprise! The getSamplePointsQ) function on the offset entity did indeed produce two points, with which T created an AcGeLine2d offLine! and offLine2. Then it was easy to cal~ culate the intersection point between the AeGeLine2d and the AcGeCircAAre2d entity, after which we created two AeDbLine entities. The same process was repeat- ed over and over again in the for loop. ‘Well, that takes care of the ArchWindow class. Let’s move on to the ApexWindow class. Here is the listing of the Apex Window.b file: 11 kpexWindow.h 11 by Charles Mc Auley // “Programming AutoCAD with ObjectARX” ifndef APEXWINDOWH f#define APEXWINDOW_H class ApexWindow : public RectWindow void drawiindow(AcDs8lockTableRecord*# pB1kTableRecord); void setWindowHeight (double windHei ght); private: Adesk: :Boolean drawApex(AcDbBlockTableRecord*# pB1kTableRecord) ; Adesk: :Booleang drawApexInternalBars(AcDbBlockTableRecord*a pB1kTableRecord) : double rectFramelt; fendif Here the setWindowHeight() function is the same as the setWindowHeight() fanction in the ArchWindow class. The drawWindow() function calls the private functions drawApex() and drawApexinternalBars(). Here is the listing for the implementation of the ApexWindow.cpp file: 11 ApexWindow..cpp 1/ by Charles Nc Auley // “Programming AutoCAD with ObjectARX” ifinclude // acdb definitions #include // ads defs #include #include #include #include #include Hinclude #include 7 include “ApexWindow. h” ApexWindow: :ApexWindow() ( } ApexWindow: ( ~ApexWindow( ) ObjectARK's Geometry Clases 297 ) void ApexWindow: :drawWindow(AcDbBlockTableRecord*& pB1kTableRecord) ( // Call the base class to draw the 7/ rectangular part of the window RectWindow: :drawWindow(p81kTableRecord) ; if (!drawApex(pB1kTableRecord)) ( acutPrintf("\nError drawing Apex. “); return; } } Adesk: :Boolean ApexWindow: :drawApex(AcDbBlockTableRecord* pB1kTableRecord) { AcGePoint2d sp, tp, ep, 11Pt; AcGePoint2d 11, Ir, ur, uls AcGePoint2d leftid, rightMid, btmMid, intPt.& vertTp; AcGePoint2dArray apexAr; AcGePoint2dArray intBarAr; AcGeLineSeg2d leftApexSide, rightApexSide; AcGeLineSeg2d btmApexSide, horMidLine; AcGeLine2d perpLine; AcGeVector2d vec: AcGeTol tol; double windLen, windit, frameThk, intrFrameThk; double deg45cut; WindLen = getWindowLength(); windlit = getWindowHeight(); pt = getWindowStartPoint(); frameThk = getRectFrameThk(); intrFrameThk = getIntrFrameThk(); // Calculate the start point, top point 71 and end point of the arc. vec.set(0, windHt); sp = 11Pt + vec; vec.set(windLen, 0); ep = sp + vec; vec.set((windLen / 2), (windLen / 2)); tp = sp + vec: apexAr.append(sp): apexAr.append( tp); apexAr.append(ep); J] now reset sp. tp, ep 1/ by the hypotenuse of the frame thickness // cut at 45 degrees. sqrt and pow are part // of the math.h header file. degd5cut = sqrt( powiframeThk, 2.0) +0 pow(frameThk, 2.0); vec.set(deg45cut, 0); sp + vec: ep -= vec: vec.set(0, deg45cut); tp -= vec: apexAr. append(ep)s apexAr.append( tp); apexAr.append( sp): vertTp = tp: // Initialize the Tine segments leftApexSide.set(sp. tp): rightApexSide.set(tp, ep): btmApexSide.set(sp, ep): // Get the midpoints of leftapexSide andy rightApexSide leftMid = leftapexSide.midPoint(): rightMid = rightApexSide.midPoint(); // Initialize the horizontal line horMidLine horMidLine.set(leftMid, rightMid); ObjetARXs Geometry Clases // Get the midpoint of the horMidLine intPt = horMidLine.midPoint(); // Call the base class // RectWindow: :drawintBar() to 71 draw the polyline even though J this is not an internal bar it // does accept an AcDbBlockTableRecord pointer // and an AcGePoint2cArray so it will suit our /1 purpose. if(!drawintBar(pB1kTableRecord, apexAr)) { acutPrintf("\nError drawing Apex. “); return Adesk: :kFalse; ’ // Draw the horizontel internal bars first sp = leftMid: ep = int?) vec.set((intrFrameThk / 2), (intrFrameThk / 2)); ul = sp + vec: 11 = sp - vec; Ir = ep - vec: vec.set( -(intrFrameThk / 2), (intrFrameThk / 2); ur = ep + vec; intBarar.append(11)5 intBarar.append(1r); intBarar.append(ep); intBarAr.append(ur); intBarAr.append(ul); if(1drawintBar(pB1kTableRecord, intBarAr)) ( acutPrintf(“\nError drawing internal apex bar.@ ”s return Adesk::kFalse; ) // Don’t forget to empty out the array ‘intBarAr.setLogicalLength(0); sp = intPts ep = rightMid; vec.set((intrFrameThk / 2), -CintrFrameThk / 2)): 11 = sp + vec: Vr = ep + vec: ur-= ep - vec: vec.set((intrFrameThk / 2), (intrFrameThk / 2)); ul = sp + vec: intBarAr.append(11); intBarAr.append(1r): intBarAr.append(ur); intBarAr.append(ul): intBarAr.append(sp): if(1drawintBar(pB1kTableRecord, intBarAr)) i acutPrintf("\nError drawing internal apex bar.g ”s return Adesk. ) False: intBarar.setLogicalLength(0); // Draw the vertical internal bars sp = intPt; ep = vertTp; vec.set((intrFrameThk / 2), (intrFrameThk / 2); Ir = sp + ve ul = ep = vec: vec.set( -(intrFramethk / 2), (intrFrameThk / 2)); 11 = sp + vec: ur = ep - vec; ‘intBarAr.append(sp); intBarAr.append( Ir); intBarAr.append(ur): intBarAr.append(ep); intBarAr.append(ul) ‘intBarAr.append(11); if rawintBar(pB1kTableRecord, intBarAr)) acutPrintf(“\nError drawing internal apex bar.& ObjetARXs Geometry Clases 301 “ys return Adesk ) False: intBarAr.setLogicalLength(0); // Initialize the btnMid point btmMid = btmApexSide.midPoint(); sp = btmMid; ep = intPt; vec.set((intrFrameThk / 2), 0); sp + ve sp - vec: vec.set( -(intrFrameThk / 2), -(intrFrameThk /o 20d: ul = ep + vec; vec.set((intrFrameThk / 2), -(intrFrameThk / 2)); ur = ep + vec; intBarAr.append(1r); intBarAr.append(ur) intBarAr.append(ep); intBarAr.append(ul); intBarAr.append(11); if(drawIntBar(pB1kTableRecord, intBarAr)) ( acutPrintf(“\nError drawing internal apex bar.@ “5 return Adesk: :kFalses ) intBarAr.setLogicalLength(0); // Draw the left Vertical bar btmApexSide.getPerpLine(leftMid, perpLine); btmApexSide. intersectwith(perpLine, Ir, tol); vec.set(0, -(intrFrameThk / 2)); ur = leftMid + vec; vec.set( -(intrFrameThk / 2), 0); ep = ur + vec: vec.set( -(intrFrameThk / 2), -(intrFrameThk /& 2); ul = ep + vec; vec.set( -(intrFrameThk), 0): 1 = Ir + vec; intBarAr.append(1r); intBarAr.append(ur); intBarAr.append(ep); intBarAr.append(ul); intBarAr.append(11); if(1drawintBar(pB1kTableRecord, intBarAr)) i acutPrintf(“\nError drawing internal apex bar.& "): return Adesk::kFalse; ) intBarAr.setLogicalLength(0); // Draw the right Vertical bar btmApexSide.getPerpLine(rightMid, perpLine): btmApexSide. intersectWith(perpLine, 11, tol); vec.set(0, -(intrFrameThk / 2)): ul = rightMid + vec: vec.set((intrFrameThk / 2), 0); ep = ul + vec: vec.set((intrFramethk / 2), -(intrFrameThk / 2)); ur = ep + vec; vec.set((intrFrameThk), 0): p= 11 + veces intBarAr.append(1r); intBarAr.append(ur); intBarAr.append(ep) ; intBarAr.append(ul); intBarAr.append(11); if(idrawIntBar(pB1kTableRecord, intBarAr)) fi acutPrintf("\nError drawing internal apex bar.@ ObjectARX's Geometry Clases 303 return Adesk::kFalse; ) intBarAr.setLogicalLength(0); return Adesk: :kTrue; ) void ApexWindow: :setWindowHei ght (double windHei ght) ( double windLen = getWindowLength(); rectFrameHt = windHeight - (windlen / 2); RectWindow: :setWindowdei ght (rectFrameHt); } ‘There is nothing new in this class except perhaps how we call down into the base class drawintBar() function to draw the internal bars of the apex portion of the window. Earlier we mentioned the use of virtual functions, and the example we saw was the setWindowHeight() function. I could have made the RectWindow base class func tion drawWindow( a virtual function also, as shown in this alternate version of the RectWindow class header file (see application C5_ta for this alternate approach): J} RectWindow.h 11 by Charles Mc Auley 1/ “Programming AutoCAD 2000 with ObjectARK” #ifndef RECTWINDOW_H define RECTWINDOW_H #include class RectWindow ‘ public: RectWindow(); // Constructor RectWindow(); // destructor Virtual void drawWindow(AcDbBlockTableRecord*d pB1kTableRecord) ; void setWindowRows(int rowQty): void setWindowCols(int colaty): virtual void setWindowHeight (double windHeight); void setWindowLength(double windLength) ; void setWindowStartPoint (AcGePoint2d lowLeftPoint) ; protected: double getWindowLength() const; double getWindowHeight() const: AcGePoint2d getWindowStartPoint() const: double getIntrFrameThk() const; double getRectFrameThk() const: Adesk: :Boolean drawintBar(AcDbBlockTableRecord*# pBlkTableRecord, AcGePoint2dArray intBarar);, private: Adesk: :Booleang drawRectFrame(AcDbBlockTableRecord* pB1kTableRecord, AcGePoint2dArray &inrRectFrame); int getWindowRows() const; int getWindowCols() const; int row: int col double rectFrameLen; double rectFrameHt; double rectFrameThk; double intrFrameThk; AcGePoint2d lowleftPt; hs endif, If you were to use this version of the RectWindow class, it would require a change to the driver application, as outlined in this alternate partial listing. void windo() i AcDbBlockTableRecord *p81kTableRecord; ObjectARX's Geometry Clases 308 char kw£20); int re; // Return coce RectWindow *pRectWindow; // RectWindow class ArchWindow *pArchWindow: // Derived ArchWindowe class ApexWindow *pApexWindow: // Derived ApexWindows? class RectWindow *pGenericWind; acedInitGet(NULL, “Arch apeX Rect”); re = acedGetkword("\nSeTect window type -@ Arch/apeX/: “, kw); switch(re) ( case RICAN: acutPrintf("\nUser cancelled. “): return; break; case RTERRO acutPrintf(“\nError with selection type. return; break; case RTNONE: strepy(kw, “Rect”; Fo = RTNORM; pRectWindow = new RectWindow: PGenericWind = pRectWindow: break; case RTNORM: if(stremp(kw, “Arch”) —= 0) i pArchWindow = new ArchWindow; pGenericWind = pArchWindows Wr if else if(stremp(kw, “Rect") —= 0) ( pRectWindow = naw RectWindow: pGenerichind = RectWindow; Vielse if else if(stremp(kw, “apeX") — 0) ( pApexWindow = naw Apexiindow: pGenericWind = pApexWindow: ) else { acutPrintf(“\nError in retrieving keyword.@ return; MI else break; V1 switch if (IgetWindowInputParameters(pGenericWind)) ( acutPrintf(“\nError in window input parameters.¢ “); if(pGenericWind != NULL) i delete pGenericWind: ) return; ) 1 (1 getMode1 SpaceRecond( pB81kTableRecord)) ( return; } pGenericWind->drawWindow(p81kTableRecord) ; if(pGenericWind != NULL) ( delete pGenericWind: } pB1kTableRecord->close(); ObjeccARX’s Geometry Clases 307 ———————$— In this version, we create an extra Rect Window pointer pGenerieWind. When the user is asked to select a window type, we create a new class of whichever type was selected and then make the pGenericWind pointer point to that new class. From that point on, we use the pGenericWind pointer and, because of the virtual function point- et table, the application knows which version of drawWindow() to call. ‘Wall, that takes us to the end of Chapter 5. Now it’s time to add some user interfaces to this application. In Chapter 6 I will discuss the traditional AutoCAD style dialogs, namely DCL (Dialog Control Language), and in Chapter 7, for those of you who are proficient in MFC (Microsoft Foundation Classes), we will discuss MFC-style dialogs and also the new UI Extensions provided by ObjectARX 2000. A FINAL WORD BEFORE WE MOVE ON ‘There is one consideration that I want to bring to the fore before we continue with the rest ofthe chapters in this book: None of the applications for this chapter was cre ated with the ObjectARX 2000 Wizard. They were originally ObjectARX 2.02/AutoCAD R14 applications and as such needed some very minor modification in porting to ObjectARX 2000. The only change made was in the AeRoc:klnitAppMsg ‘message area with the addition of acrxRegi sterAppMDIAware(ptr); Here we are telling our application to work in a Multiple Document Interface envi- ronment. That's easy, you say! Well, that isnot the whole story—there are porting con~ siderations, especially when it comes to glebal data. 'm not going to talk about that here, because this subject will be spread throughout the rest of the chapters in this book Even if you are not interested in DCL, at least look at one of the applications, because it is here that I start talking about porting considerations. The rest of the chap- ters will discuss in more detail the handling of and recommendations for global data. DCL (Dialog Control Language) Dialogs ‘The DCL (Dialog Control Language) dialog is the traditional style of dialog that has been in AutoCAD since Release 12. These dialogs are defined by ASCII text files written in the Dialog Control Language. The contents of these text files describe the look, fee! and behavior of the dialog boxes. The layout design of the dialog is done in a text file with the file extension -DCL, unlike the design for MFC (Microsoft Foundation Classes) counterparts, which is defined graphically. Prior to AutoCAD R14, AutoCAD appeared on many different platforms, Windows, DOS, UNIX®, (Various flavors) and Macintosh®. With the release of AutoCAD R14, AutoCAD now supports only one platform: Windows. Because in the past AutoCAD supported many platforms, the DCL dialog file (text based) was interpreted by the platform system. “Through a single DCL text file, a dialog box on a UNIX workstation took on the appearance of the GUI (Graphical User Interface) that the particular UNIX work- station supported. The same DCL text file could also be used on a DOS computer, taking on the appearance of that supported GUI. Thus a single DCL file support- ed all platforms. ‘Wall, that situation was both good and bad. It was good from the point of view that DCL files ae easy to create. It was bad from the point of view that any special user interface elements supported by a particular operating system could not be used. For example, with DCL files you cannot have list tree views the way you have in ‘Windows Explorer. Now that AutoCAD supports only one platform, Windows, with AutoCAD 2000 you can use MFC-style dialogs in your applications as well as modal and modeless dialogs (more on modal and modeless in the next chapter). DCL dialogs are easy to create and implement in ObjectARX. There is one point that I should mention at the outset: DCL represents the old-style dialogs and as such will not be enhanced in future eleases of AutoCAD, With that said, Ido not intend to do an exhaustive treatment of DCL dialogs. I wil cover only the most common elements of DCL and provide sample applications that use DCL. For additional help with DCL see Appendix B, “Programmable Dialog Boxes” in the ObjectARX Developer Guide. 310 DIALOG BOX COMPONENTS ‘As mentioned earlier, DCL dialogs are based on text files with the extension DCL. Ifyou look in the SUPPORT folder unde: AutoCAD 2000, you will find a number of DCL files, the most important of which are acad.del and base.del. Do not edit the contents of those files, because they are the basis for all DCL-style dialogs. Ifyou want to see examples of DCL-style dialogs, at the AutoCAD command prompt, execute the following AutoCAD command, ddvpoint. If you look in the SUPPORT folder, you will find the corresponding dévpoint.di! file. Again, don't edit the contents of this file. AutoCAD and Windows control the layouts of these dialogs, based on the con- tents of the DCL text files. First I want to show you a sample dialog output on the AutoCAD screen, and then want to show the DCL syntax that produced this dialog. Here is the dialog as it appears on the AutoCAD graphics screen: Figure 6.1, Sample DCL Dialog Here is the DCL syntax that produced the dialog: excel : dialog i label = "Export to EXCEL Selection"; : boxed_col umn i label = "Selection Types"; : toggle { label = "Class 4 Panel Details"; key = "tg_c4pd"; value = "1"; ) : toggle DCL (Dialog Control Language) Dialogs 311 $$ label = “Liner Sheet Metal Cut Lists”; key = “tg_Inrct”; value fl ) 1 toggle ( label = “Sheet Metal Cut Lists”: key = htct” value ) 1 toggle ( label = “Burny Sheet Metal Cut Lists": key = “tg_bshtet”; value = “1”; toggle { label = ural Cut Lists”; key value = } }//boxed_col umn : boxed_radio_row ( label = “Selection Method”; rradio_button ( label = “Automatic”; key = “rb_auto”: value = “I"; } radio_button label = key Manual Select”; rb_man" ; }//boxed_radio_row ok_cancel; } want to discuss DCL syntax in greater detail lates, so I will give you only a brief dis- ‘cussion of what is happening here. First and foremost, a single DCL file can contain more than one dialog, but in our case we just have one. Look at the first line of the DCL file and you will see the following syntax: 312 excel : dialog ‘The first word (in this case, excel) is the name of the dialog and it is case-sensitive. This is followed by a colon and the keyword dialog, which defines a DCL dialog box. Following the dialog keyword is a beginning curly brace and at the bottom of the file there is a corresponding ending curly brace; these denote the beginning and the end of the dialog definition. In between are a number of components, all preceded by a colon and delimited with curly braces. Components can contain other compo- nents. A DCL dialog isa tree-like structure stating at the top and working its way down to the bottom. The label of the dialog is “Export to EXCEL selection”, which appears in the title ofthe dialog. This is followed by a : boxed column, which isthe line border that fits around the toggle boxes (check boxes) with the title of “Selection Types”. Contained within the boxed_column are five toggle boxes, each with its own delimiting curly braces. Note how you place a comment in a DCL file. Comments begin with two forward slashes, as shown in the syntax atthe end of the boxed_column component: //boxed_column Following the boxed_column is a boxed_radio_row, which contains two radio_but- ton components. The boxed_radio_row is the borderline that surrounds the radio but- tons and has the title “Selection Method”, Finally, before the last curly brace we have the following statement: ok_cancel; ‘This produces the OK and Caneel buttons that appear in the dialog; itis an exam- ple of a predefined sub-assembly of components. The ok cancel; definition can be found in the base.de file in the AutoCAD SUPPORT directory. The base.del ile has ‘numerous sub-assemblies, which can be included in your DCL file. Once again, do not edit the base.de! file. Here isa partial list of the base.del file; look at the ok_can- cel definition: uw buttons. Pre-built arrays of dialog bottom-lined ok_only : column { fixed_width = true; alignment = centered; : ok_button { is_cancel = true; DOL (Dialog Control Language) Dialogs 313 Ce Cone aig) Dickey 313 ok_cancel : column { i row ( fixed_width = crue: alignment = centered; ok_button; : Spacer { width = 2; } cancel_button; ) ok_cancel_help : column { 2 row { Tixed_width = trues alignment = centered: ok_buttor : spacer { width = 2: } cancel_button; 2 spacer ( width = 2: } he1p_button; } ok_cancel_help_info : column ( : row { fixed_width = true; alignment = centered ok_button; + Spacer { width = 2: } cancel_button; : spacer ({ width = 2; ) help_button; : spacer ( width = 2; ) info_button; } Up until now we have referred to all the elements that are contained in a dialog box «as components. From now on (with respect to DCL), we will use the DCL name of “tile” to refer to these components. Let's move on and look at some of the most com- monly used tiles. 34 PREDEFINED ACTIVE TILES Here is a list of the DCL tiles that we will discuss in this section: button radio_button Vist_box column boxed_col umn radio_column boxed_radio_column row boxed_row radio_row boxed_radio_row edit_box toggle ‘image image_button text Vist_box Popup_list ‘Well, perhaps the best way to show the kinds of tiles that DCL dialogs can have is to show a real-world example in Figure 6.2. Looking at Figure 6.2, we have a list_box, which can have multiple rows and, ifthe number of rows exceeds the visible portion of the list_box, a scroll bar will appear on. the tight side of the ist_box. Ina list_box you can select either one line or multiple lines (ifthe attribute property allowing you to select multiple lines is set). The top line is index 0. In this example, only single selection is allowed and the text that appears in the list_box has been formatted with tubs. Below the list_bax we have a row. The row contains two edit_box tiles. If we did not ‘use a row to constrain the edit_box tiles, the edit_box tiles would appear one on top of the other. The labels appear to the left of the edit boxes. In an edit_box we can define the maximum number of characters the user can enter if we wish. Below the row we have a boxed_row whose label is Process Codes. Inside the boxed_row we have seven toggle tiles. In order to achieve the alignment as shown, the boxed_row contains four colurnn tiles, each of which contains two toggle tiles, except for the last column, which contains one toggle. The label for each of the tog- ‘fe tiles appears to the right of the toggle. I can tell you from experience that it takes practice to get these DCL dialogs looking right. DCL (Dialog Control Language) Dialogs 315 r “MEW ln gp aed sie vows DEFAME SEW Base Frane Aist_hox {ease nox | woxea_row button Figure 6.2 Basic DCL Dialog Components Following the array of toggles we have another row that contains a toggle tile and a text tile. Then we have two rows of button tiles. The label of the button tile is the text that appears on the button. Here is the DCL file for this dialog: toc : dialog ( label = “Table of Contents”; : concatenation ( : text_part ( Jabel = “Pof”; width = 5; ) : text_part ( label = “VPset”; 316 width = 12; text_part label width “Process”: 9% text_part t Jabel = “Description”; } }//concatenation 2 1ist_box ( key = “vpdes”; value = “"; width = 60 height = 10: tabs = “5 17 26"; 1//ist_box row ( edit_box ( key = “pgno” label = “Pail”; edit_width fixed_width = true; 1/ Jedi t_box : edit_box t key = “des”; label = “Description”; edit_width = 40; 1//ist_box V//row boxed_row ( label = “Process Codes"; : column ( : toggle { key = “prob” DOL (Dialog Control Language) Dialogs 317 label = “BURNY"; ) : toggle ( key = “pros”: Vabel = “SHEAR”: } } : column t + toggle t key = “pron”: Vabel = “NOTCH”; ) : toggle ( key = “prof”: Vabel = “FAB™; ) ) column : toggle ( key = “prow” label = “WELD”; ) 2 toggle { key = “prop” label = “PAINT”: } ) : column ( : toggle ( key = “p Vabel = “INSULATION”; ) ) V//row row 318 toggle ( key = “tog_pgno”; label = “Page No. Sort”; }//Sort by Page No. 2 text { key = “maxpgno”; Jabel =“ } Mirow : row fl button ( key Arline"; label = “Clear LINE"; button key = “valpgro”; label = “Valid Page Nos.. } : button { key = “inspgno”; Jabel = “Insert Page No...” button key = “update”; Tabel = “UPDATE™: button fl key = “btoc”; label = “Build TOC"; ) button ( key = “tochdr”; DGL (Dialog Control Language) Dialogs 319 ———— label = “Header..."; ) : button fl key = “shovp": label = “Show VPset”; ) button { key = “xit"s Vabel = “EXIT’ is_cancel = true: } 1 /row )//dialog Let’s move on and take a look at Figure 6.3 and what it has to offer. image_button ok cancel (predefined tite boxed_column in base.del) oxed_radio_column \— radio_colwm Figure 6.3 Advanced DCL Dialog Components 320 In the top portion of this dialog we have two rows of image buttons. Each row contains two image buttons. An image_button behaves just like a regular button, except that it can contain graphics, The kinds of graphics that an image_button can contain are vector graphics, AutoCAD slide images, and solid color swatches. In the example shown here, the image buttons contain slide images. Ifthe user picks the Next button, the Previous button becomes enabled and four new slides fill up the image_button tiles. The DCL file does not contain the slides or images; it is up to the application to process the image buttons and fill their contents with slides. Below the image buttons we have a row that contains two text tiles, and below the ‘wo text tiles we have a row that contains two popup lists. The popup_lst is simi- lar to the list_box that we saw earlier, with the exception that only one item can be selected from the popup_list. Following the popup_tist tiles we have a row that contains a boxed_radio_colurnn, boxed_column, and another boxed_column. The boxed_radio_column contains radio_button tiles of which only one can be selected. The boxed_radio_column con- tains a line border, in this case with a title of "Process”. The radio_column is sim- ilar to the boxed_radio_column except that it is without a line border. In a radio_column, only one radio_button can be selected. In the middle boxed_column of the illustration, it contains a toggle and a radio_column. The radio_column contains two radio_button tiles of which only one can be selected. The final boxed column is kind of tricky in that it contains a row with two columns. The first ‘column contains two toggle tles and the second column contains two popup_list tiles. As I said before, it takes practice to get the dialogs right. Finally, there is a sow of button tiles, of which the last two buttons are the predefined tile buttons in the Base.dél file. Here is the DCL file li cApnil : dialog ( ting for Figure 6.3: label = “Class 4 - Panels”; key = “header”; column fl :roW t mage_buttor key = “ricl allow_accept = true; color = 0; DCL (Dialog Control Language) Dialogs 321 << width = 225 aspect_ratio 0.75 ) mage_button key = “r1c2"; allow_accept color = 0; width = 22; aspect_ratio = 0.7: true; ) VW srow row { :image_button ( key = “r2cl" allow_accept = trues color = 0; width = 22; aspect_ratio = 0.7; ) mage_button fl key = “r2c2"; allow_accept = true: color = 0; width = 22; aspect_ratio = 0.7; ) V//row 1//column :row ( : text ( } : text { label = “Panel selected: key = “curpni"; label = “ width = 16 } VV /row :row ( children_fixed 2 popup_list ( idth = true; label = “GAUGE key = “c4pniga”; list = “14\n16\n18"; value = “1" edit_width = } + popup_list t Vabel = “MATL: key = “cdpnimati”; Vist = “GALV\n304 SS"; value = “0"; edit_width = 10; ) : spacer width = 8; ) Mfrow row i : boxed_radio_column ( Vabel Proces: : radio_button t label = “BURN key = “cdburny’ ) : radio_button (i DCL (Dialog Control Language) Dialogs 323 $$ label = “SHEA key = “cdshea value = “I”; ) spacer_l: )//boxed_radio_col umn : boxed_col umn. i Vabel = “Insulation: : toggle { label = “Yes” key = “c4instog’ : radio_column : radio_button i label = “1\" key = “c4ins1"; is_enabled = false; ) : radio_button t label = “2\""; key = “c4ins2”; is_enabled = false; ) )// radio_column }//boxed_co} umn : boxed_column ( label = “Inner Liner : row { : column ( : toggle t label = “Yes”; key = “c4linrtog”; is_enabled = false; toggle label = “Perf”: key = “c4perftog”; is_enabled = false: ) spacer_l: }// column : column ( children_alignment = rights : row { text i label = “GAUGI key = “galinrtx is_enabled = false: ) + popup_list ( key = “célinrga”; list = 3 “16\n18\n20\n22\n24" value = "3" is_enabled = false: } VW /row 2 row t + text { Vabel = “MATL”: key = “mat}Tinrtxt” ‘is_enabled = false; + popup_list ( key = “cdlinrmat]"; Vist = “GALV\n304 SS"; value = "0"; is_enabled = false: DCL (Dialg Control Language) Dialogs eee ) W/row M//column M/1 row 1/7 boxed_column 1//row :row i : button { key = “next”; Jabel = “Next”; width = 10; button key = “prev™; label = “Previous”; ‘is_enabled = fals ) ok_cancel; }//row }//dialog Figure 6.4 shows a dialog that appears as a result of the selection of one of the image_button tiles of Figure 6.3. Because an image_button is a button it can fire off an event, which in this case causes 2 nested dialog, or sub-dialog, to appear; this is controlled by the application. Figure 6.4 shows a nested dialog containing a row that contains a boxed_radio_col- ‘umn, column and another boxed_radio_column. The column contains a text tile and an image tle. The image tle is similar to the image_button in that it can display vec~ tors, color swatches and AutoCAD lide files however, it does not fre any events. Here is the DCL listing for Figure 6.4: c4pni2 : dialog ( label =“ *; key = “Tab12"; : row ( : boxed_radio_column text column ok_only (predefined vite in base.del) boxed_redic_column Figure 6.4 Advanced DCL Dialog Components with Nested Dialog Feature ( label = “Left”; spacer, : radio_button key = “In Vabel = “MALE” radio_button key = “If" Tabel = “FEMALE”; spacer }//boxed_radio_column : column DCL (Dialog Control Language) Dios 337 a 2 text label = “Layout”; : image ( key = “mfsid"; color = 0; width = 10, aspect_ratio=0.7; V/image }//column : boxed_radio_column fi label = “Right”; spacers : radio_button ( key = “rn”; Tabet = “MAL ) 2 radio_button ( key = “rf”; label = “FEMALE”; ) spacer; )//boxed_radio_column V//row spacer_l; ok_only; )/1dialog I you look at the label (title) for the dialog box of Figure 6.4, you will see that it is blank. The reason it is blank is that it is filled in at run time. Assuming the user selects the top left image_button tile, the application applies the text “A” ~Type to the label of the nested dialog. Another interesting feature ofthe application that uses these DCL file is that it remembers what the user selected from invocation to invocation of the application. Again, this feature is application-specific. The AutoCAD help facility con- tains a detailed discussion of the DCL ties. 38 ATTRIBUTES OF PREDEFINED TILES ‘Ifyou look through the DCL files presented 30 far, you will see that each tile has some kind of attribute associated with it. An attribute is made up of the properties and behavior that a tile displays. So let’s look at the properties of the tiles outlined in the previous section, ‘The only attribute ofa boxed_column is a label. The same applies to a boxed_row. If the label is blank, the tiles within the boxed_column are enclosed by a line with- out a label. Here is a sample DCL code fragment: : boxed_column ( label = “Insulation // more stuff here }// boxed_column ‘The boxed_radio_row and the boxed_radio_column have a label attribute in addi- tion to a value attribute. The value attribute is a quoted string that contains the key of the radio_button that has a value of 1. have not talked about keys in DCL yet— Iwill later in great detail. Essentially, a key is the way an application communicates with the tiles in the dialog, Every tile ina dialog can have a key, but a key does not affect how the tile looks or behaves. ‘The button tile has three attributes, namely a label, is_default, and is cancel. The is_default attribute has two values: true or false. Ifthe value is set to true and the user presses ENTER, it isthe same as if the user selected the button with the mouse. The is_cancel attribute, if set to true, will respond to ESC as if the user pressed the Cancel button. This applies only if you define your own buttons and do not use the predefined sub-assemblies of the based! file. Here is an example of is default and is cancel usage: : button fl key = “btn_ok"; Vabel = “OK"; is_default = true; } : button { key = “btn_cancel"; label = “Cancel is_cancel = true; ) DCL (Dialog Control Language) Diclogs 329 $$$ $$ The edit_box tile has a number of attributes, namely label, edit_width, edit. limit, value, and allow_accept. The label is the text that appears to the left of the edit box. The edit_width is the width of the edit_box in character units. The edit_limit specifies the number of characters that a user can enter in an edit_box. The value attribute is a quoted string, which will be placed in the edit_box when the dialog becomes visible. The final attribute, allow_accept, has a value of cither true or false. If set to true and the user presses ENTER, it is the equivalent of selecting the cedit_box with the mouse. Here is a sample DCL code fragment: + edit_box { key = “lyr. value = *0' edit_width = 3 edit_limit = 3 ) box” The image tile has two attributes, namely color and aspect_ratio, The color attribute isthe color of the background, which can be a number from 0 to 7 or one of the symbolic names black, red, yellow, green, cyan, blue, magenta, and white. The aspect_ratio isthe relationship of the width to its height and isa floating-point value, The best way to get the aspect_ratio rights to experiment, Here is a sample DCL code fragment: : image { key ifsld" color = 0; aspect_ratio = 0.7; 1/ image The image_button has the following attribute values: color, allow accept, and aspect_ratio. The color and aspect_ratio are the same as for the image tle, and we have already seen the allew_accept attribu:e. ‘The list_box tile has the following attributes: label, multiple_select, list, tabs, value, and allow_select. The multiple_select is set to cither true or false. If true, it allows the user to select multiple lines of text in the ist_box tl, The list attribute spec- ifies the initial set of strings that will appear in the list_box, each string separated by an escape character (\n) The strings can also include the tab character (t) to allow spe cial formatting of the string. The tabs attribute is a string containing numbers that specify where the tabs will fallin the listbox. This is illustrated in the list_box in Figure 6.2. The value attribute is a quoted string that contains integers and specifies which lines of text are initially elected. The lines of text in the lst_box are zer0 based. If muttiple_select is false, the value attribute can only contain a single quoted inte~ ger. Here is a sample DCL code fragment: : list_box ( key = “vpdes value = width = 60; height = 10; tabs = “5 17 26"; }//1ist_box ‘The popup list tle has the following attributes: label, edit_width, value, list, and tabs. ‘The popup_tist does not have the multiple_select attribute. ‘The radio_button has the following attributes: abel and value. The value attribute has a value of quoted integers, either “0” or “I”. If the value is “I”, then the radio_button is in a selected state. Here is a sample DCL code fragment: : radio_button ( Vabel = “oBD key value = “1"; ) ‘The toggle tile is similar to a radio_button tile. The toggle has attributes of label and value, which are identical to those of the radio_button, LAYOUT AND SIZING ATTRIBUTES Here is a listing of the layout and sizing attributes: width height alignment children_alignment fixedwidth fixed_height children_fixed_width children_fixed_height will leave it up to you to read the ObjectARX or AutoCAD documentation for a description of these attributes. Of the attributes listed above, the only two that I used to use on a regular basis were the width end height attributes. Nowadays T use the MFC-style dialogs. DCL (Dialog Control Language) Dialogs 33) —$ FUNCTIONAL ATTRIBUTES ‘The functional attributes are is_enabled, is_tab_stop, and mnemonic. The is enabled attribute has a value of true or false. Whea the dialog first appears on the screen, if the is_enabled attribute is set to false, the tle will appear dimmed. The is_tab_stop attribute has a value of true or false also, and if the attribute is set to true, the user can press AB to step through the tiles on the dialog for each tile that has this attribute set to true. The mnemonic atribute allows you to set up a leter that, when pressed at the keyboard, will se focus to the tile represented by the mnemonic. The ‘mnemonic letter must be capitalized and must be one of the uppercase leters of the tile’s label, Here is a sample DCL code fragment: + edit_box { label = “A - Top Vert Opng”; key = “eb_tvo"; edit_width mnemonic = }/Jedit_box ‘THE ‘KEY’ ATTRIBUTE Finally, the most important attribute of all is the key attribute. Every active tile ‘must have a key value and it must be unique to the dialog box.’The key value is a quot- ed string, The key value is how the application that uses the dialog communicates with the tle to read or set the current tile value. It can also enable or disable the tile. When it comes to giving key attributes names, I came up with a naming scheme so I would know that kind of tle I was dealing with in the application code. Keys are case sensitive. Here is my naming scheme for the prefixes: btn__ button prefix tg_ toggle prefix rb_ radio_button prefix eb_ edit_box prefix im_ image tile prefix ‘imb_ image_button prefix Ib_ list_box prefix pl. popup_list prefix tg_ toggle prefix DCL SYNTAX Perhaps the best way to discuss DCL syntax isto create a DCL file that will be part of an application we will develop later. In the previous chapter, we created an appli- 32 cation that drew three different kinds of windows: rectangular, arch, and apex. That application was driven from the AutoCAD command prompt. What we want to do now is create the DCL file that the application will use. Here is the dialog that the DCL file produces when it is loaded by the application. Coens Figure 6.5 First Window Porometers Dialog Here is the Ch6_2.de file that produces the dialog box shown in Figure 6.5: ch6_2 : dialog { label = “Window Paraneters”; 2 row ( : boxed_radio_column t Jabel = “Window Type’ key = “bre_wintype”; + radio_button t label = “Rect”: key = “rb_rect”; value = “1” mnemonic = “R’ : radio_button label = “Arch” key = “rb_arch’ mnemonic = “A’ DCL (Dialog Control Language) Dialogs 333 ——————— ) : radio_button ( Jabel = “apex” key = “rb_apex’ mnemonic = “X" } } // boxed_radio_column : boxed_column label = “Specifications”; : edit_box ( Jabel = “Height’ key — “eb_hei ght! mnemonic = “H"; edit_width = 8; : edit_box label = “Width”, key = “eb_width’ mnemonic = “W’ edit_width = edit_box Jabel = “Columns”; key = “eb_cols" mnemonic = “C"; edit_width = 8; value = “1"; edit_box Jabel = “Rows”; key = “eb_rows” mnemonic = “R’ edit_width = 8: value = “1"; ) ) 71 boxed_column VT row ok_cancel; } Even though this file is called Ch6_2.del, we haven't yet created the Ch6_2.arx appli- cation, of which it will be a part. The first application we will create, C6_1.arx,will show you how to load and display DCL files. As we have mentioned earlier, DCL files are text files with the extension DCL. This DCL file contains only a single dialog, but DCL files can contain more than one dialog. The syntax for creating a dialog is as follows: dialog_name : dialog (// Starting brace more stuff here 1 7 Closing brace ‘The dialog name is case-sensitive and is followed by a single colon, which is then fol- lowed by the keyword dialog. The dialog is contained within two curly braces, a start- ing brace and an ending brace. In between the starting and ending braces are the tile // acdb definitions #include // ads defs include /! unlock application include iHinclude // Symbol Tables #include // OCL style dialogs // T0D0: add your own includes 11 Function prototypes // entry point for this application extern “C” AcRx::AppRetCode acrxEntryPoint(? AcRx::AppMsgCode msg, void* ); // helper functions void initApp (void); void unloadApp( void): //_user defined functions void Toaddel(); MALLET HAT /1 acrxEntryPoint (internal) J1 This function is the entry point for youre application. TTT TTT LLL TTL ‘AcRx: :AppRetCode acrxEntryPoint(AcRx: : AppMsgCoded? msg, void* ptr) { switch (msg) { case AcRx::kInitApplsg : acrxtinlockAppl ication(ptr) acrxRegisterAppMDIAware(ptr) : initAappQ): break: case AcRx::kUnloadAppMsg : unloadApp(); break; default: break; ) 1 switch return AcRx::kRetOK; ) void initApp(void) { J 1000: init your application /1 register a command with the AutoCaDe command mechanism acedRegCmds ->addConmand(“CH6_APPS", “LOADOCL”,& DCL (Dialog Control Language) Dialogs 339 $$$ > “LOADDCL”, ACRX_CMD_MODAL, loaddcl); acutPrintf(“Enter \"LOADDCL\" to view a DCL& file.\n"); ) void unloadapp(void) t J T000: clean up your application /1 Remove the command group added viae acedRegCmds->addCommand acutPrintf(“Zs%s", “Goodbye\n", “Removing commande group \"CH6_APPS\"\n") ; ‘acedRegCmds->removeGroup(“CH6_APPS") ; ) // T0D0: add your other functions here void loaddel() ( // Use the acedGetFileD() to select your // OCL file. ‘int re; // Return code struct resbuf *rbDCL; char delFilef{TILE_STRLIMIT]; // TILE_STR_LIMITS = 255 in adsdig.h file char dc1Name(801; int delld, dbStat; J/ del 10 and dialoge box status ads_hdig digHd1; // Dialog Box handle PbDCL = acutNewRb(RTSTR) rc = acedGetFileD(“Select DCL file to view", //o Dialog Title NULL, JJ No default name “del”, // del file extension 2, J Bit flag disable “Typed it” button rbOCL); // result buffer // Check the return value of rc if(re != RTNORM) { acutPrintf(“\nError selecting DCL file or usere pressed Cancel button”); if(rbDCL != NULL) ( acutRe1Rb( rbDCL) ; ) return; } // vesbuf rbOCL now contains the del filename and path // in its resval.rstring member strepy(dclFile, rbDCL->resval.rstring); iF(rDDCL != NULL) { acutRel Rb(rbDCL) ; ) // The name of the dialog is the name in the DCL file that comes 11 before the follows: J/ name : dialog { re = acedGetString(0, “\nDCL dialog name (Cased Sensitive) “, dclName); if(re != RTNORM) { acutPrintf(“\nError in retrieving DCL dialoge name”); return; } and the word “dialog” as J Now that we have the OCL filename, load theo dialog box re = ads_load_dialog(dclFile, &dclId); if(re [= RTNORM) { acutPrintf("Zs Zs", “\nError loading DCL file. DCL (Dialog Control Language) Dialogs 341 $$$ “, delFile): return; ) /1 Now that the dialog is loaded, display it rc = ads_new_dialog(dclName, dclid, NULLCB.2 kdl ghd1); if(re l= RTNORM) ( acutPrintf(“\nError displaying dialog. return; ) J] Now that the dialog is visible on the screen // initialize it. re = ads_start_dialog(d1gHdl, adbStat); if(re != RTNORM) { acutPrintf("\nFailed to initialize the dialog? box. “)s return; } // Unload the dialog box ads_unload_dialog(dcl id); ) In this application, the only user-defined function is loaddel(). Look at the call to acedGetFileD(). The acedGetFileD() function, as you will recall from an earlier chap- ter, displays a dialog that allows a user to selet a file based on the user input to the function. In this case, we are looking for files ofthe type ‘idl. If successful, the result buffer rbDCL contains the string thatis the path to the DCL file. We copy this string to the string buffer delFile, after whick we release the result buffer rbDCL. After releasing the result buffer, we ask the user for the name of the dialog using the acedGetString()finction. When you run this application, dont forget to enter a name for the dialog at the command prompt after picking a DCL file from the file selec~ tion dialog box. From here on, we execute the four steps that are required to load, interact with and unload the dialog box. The fist step is to locd the dialog using the ads load_dialog() fanction. Ifall went well, the integer delld wil be initialized, Remember, this only loads the dialog into memory—it does not display the dialog, Following the call to ads load_dialog(, we call ads_new_dialog() with the name of the dialog, the delld, 3 and NULLCB. The third argument to ads_load_dialog() is for callback function; if you dont have any callback functions, use the predefined constant NULLCB. If all went ‘well, the ads_hdlg variable digHdl will be initialized. After the ads_new_dialog() func- tion is normally where you initialize all the dialog tiles and callback functions. Next, we call ads_start_dialog() using the dialog handle previously established in the call to ads_new_dialog(). The ads_start_dialog( fills out the integer dbStat. This can be either a value of DLGOK or DLGCANCEL or a user-defined value. Finally, when we are finished with the dialog, we must unload it from memory using the ads_unload_dialog() function with the delld of the dialog that was established in the call to ads_load_dialog(). ‘You need to have a DCL file created in order to execute this application. Go ahead and build the application and then run it. When you run the application, select the b6_2.del file and then, at the command prompt in AutoCAD, enter the name of the dialog, which in this case is “ch6_2.” Dont forget that the name of the dialog is case sensitive. If there is an error in the syntax ofthe DCL file, the dialog box will not load. ‘COMMONLY USED DIALOG FUNCTIONS ‘We have already discussed the mechanisms of loading and unloading dialog boxes. We hhave covered the ads_load_dialog(), ads_new_dialog(, ads_start_dialog(), and ‘ads_unload_dialog() functions. In this se:tion we will discuss the following dialog functions: ads_action_tileQ) ads_set_tile() ads_got_tile() ads_mode_tile() ads_done_dialog() During the process of loading a dialog and after calling ads_new_dialog(), but prior to calling ads_startdialogO, is where a DCL dialog is initialized. Functions typically used during the initialization process are ads_set_tile(), ads_mode_tile(), and ads_action_tile(). So let's discuss these functions first. You can give a DCL tile an ini- tial value during the design of the DCL file if you want to, using the value attribute DCL key word as follows: : edit_box ( Jabel = “Rows”; key = “eb_rows” mnemonic = edit_width = DGL (Dialog Control Language) Dialogs 343 $$ value = } You don't have to initialize a tile at design time if you don't want to, Alternatively (my preference), you can change the value of a tile at run time using the ads_set.tile()func~ tion. Here is the prototype of the ads_set tile() function: int ads_set_tile (ads_hdig hdig, char *key, charg *value); ‘This function is pretty straightforward in its use. It has three parameters, namely a di log handle, a key, and the value that is to be passed to the tile, as the following ‘example shows: ads_set_tile(dlgHdl, “es_height”, “20.00"); In this case, eb_height is an edit_box that receives at run time the value of "20,00. The complementary function to ads_set_tle( is the ads_get.tile() function, which retrieves a value from a til, The value retrieved from the tile isa string, You are respon- sible for any conversion of the string to a numeric value, if required. Here isthe pro- totype for the ads_get.tile() function: int ads_get_tile(ads_hdlg hdlg, char *key, chard *value, int maxlen); ‘As in the previous function, ads_set_tile), the first two arguments are a dialog handle and a key associated with a tile. The third argument is a string buffer that receives a value from the tile; you are responsible for allocating the memory before- hand, The final argument is the number of characters to read. Here is an example of its usage: ads_get_tile(cpkt->dialog, “eb_cols”, strWinCols,¢ 20); The frst argument, while it looks strange, isa dialog handle. This is part ofa callback fanction and we will discuss callback functons very shortly. Another function that you will use with respect to DCL dialogs at run time is the ads_mode_tile( function, First, here i the prototype of the ads_mode_ile( function: int ads_mode_tile(ads_hdig hdlg, char *key, short mode); Again, the first and second arguments should be familiar by now. The third argument is a mode argument and it affects how the tile in the dialog looks. Here is alist of all the possible mode values: Value | ADSRX symbol Description 0 MODE_ENABLE Enable tile t MODE_DISABLE Disable tile 2 | MODE SETFOCUS | Set focus to tle 3 | MODE_SETSEL Select edit box contents| 4 | MODE_FLIP Flip image highlighting on or off ‘Table 6.1. Possible Values for Mode Parameters use the first three modes most often. In dialog design I usually disable the OK but- ton, only enabling it when all the appropriate ties (fields) have been filled out. In the dialog box that we designed earlier, we used the predefined sub-assembly ok only. In ‘order to disable a button tile in this assembly, how do we access the key value? If you look in the dace.de file in the SUPPORT directory, you will find that the OK button hhas a key value of “accept”. The Cancel button tile has 2 key value of “cancel”, for that matter. So if wanted to disable the OK button in the ok cancel sub-assembly, I would use the following syntax: ads_mode_tile(dgHd, “accept”, MODE_DISABLE); ‘To enable the OK button again, I would use the following syntax: ads_mode_tile(digHdl, “accept”, MODE_ENABLE); Let’s move on and talk about callback functions and ads_action_tile() and ads_done_dialog(). CALLBACK FUNCTIONS Callback functions are defined through the ads_action_tile() function. The ads_action_tile() function associates a function witha tle in the dialog box. Ifa user selects a tle in a dialog box and if the tle is associated with a callback function, that callback function is executed. A dialog car. define as many ads_action_tile() calls as it likes and different tiles can call the same callback function if desired. Hee isthe pro- totype of the ads_action_tile() function: int_ads_action_tile(ads_hdig hdlg, char *key,o CLIENTFUNC tilefunc); DGL (Dialog Control Language) Dialogs 345, $$$ ‘The first two arguments are old hat by now. The third argument is a CLIENT- FUNC data type, a function pointer data type. It contains the address of the function to be called when the appropriate tile is selected. Remember, the name of a function is the address of a function, much in the same way the name of an array is the address of the array. The definition of CLIENTFUNC is in the adsdig.h file of the ObjectARK Inc directory as follows: typedef void (*CLIENTFUNC) _((ads_callback_packet *epkt))s Here is an example of how to use the ads action. tile() function: ads_action_tile(digHd], “accept”, dlgOkBtnCB); The first argument digHal is the dialog ads hig handle. The second argument is the key value of the ok_cancel predefined sub-assembly, as discussed earlier. The third argument isthe name (address of) the function that is executed as a result ofthe selec- tion of the OK button. When I'm dealing with dialog callback functions, I usually end the name of the callback function with CB, which isan indicator that I am using a call- back function, So how do you declare and define a callback function? Here is a sample function pro totype for a callback function that we will see later: /1 Dialog call back function prototypes static void CALLB dlg0kBtnC8(ads_callback_packet? *epkt); Alll callback functions are static—the name of the function is dlgOkBtnCB and it takes a single argument a pointer of type ads_callback_packet. The CALLB is a blank- defined symbol and is used only as a placeholder to make it easier to find dialog call- back functions. The ads_callback packet is a structure, which is defined in the adsdlg.h file as follows: typedef struct { ads_hdlg dialog; ads_htile tile; char* value: void* client_deta; int reason; long at ys } ads_callback_packet; In this structure, the fields that we are usually most interested in are the dialog field, which is the handle of the dialog box, the value field, which returns the value of a specific tile, and the x,y fields, which return the x and y coordinates ofa pick point within an image_tile, A good example of the x and y field usage is the AutoCAD command ddvpoint. Finally, there is the ads_done_dialog( function, which is used within a callback fanc- tion to dismiss the dialog box. Here is the function prototype for ads_done_dialog(): int ads_done_dialog(ads_hdlg hdig, int status); ‘As mentioned earlier, the ads_done_dialog() function is used within a callback fanction and accepts as arguments an ads_hdlg handle and a status, which can be any of the following values: DLGOK, DLGCANCEL, DLGSTATUS, or a user-defined value. We will see how to use ads_done_dialog() in an upcoming sample—just remember that they are used within callback functions. Finally, before we move on to another sample application, all of the functions outlined in this section return RTNORM if all went well or return an error otherwise. SDI AND PORTING CONSIDERATIONS AutoCAD 2000 is an MDI (Multiple Document Interface) application. MDI refers to the ability to open and edit more than one drawing file at a time. MDI is a pro- ‘gramming term and will be familiar to those of you who are involved in MFC. The marketing name for MDI is MDE: Multiple Document Environment. So how does this affect us? In previous versions of AutoCAD, you were able to edit only one drawing ata time. So many of us (myself included) used global variables in our applications. When we ran our applications inside AutoCAD, we would read the previously saved values for these global variables; in effect, our applications would remember previously used values. Now let us consider the AutoCAD 2000 case. I have executed my application in an open drawing and saved some global data for later use. ‘Then I switch over to another open drawing and execute my application again but with different values. Wouldnt it be nice if, when I returned to the original drawing, it would remember the values that I used in that drawing? So as I switched from drawing to drawing, each drawing would have its own set of global data? To put it more correctly, ‘plobal data on a per document basis. I'm sure a number of us find ourselves in this sit- uation. ‘What kind of a structure do we need in plice to handle switching from document to document (in other words, drawing to drawing) to handle this kind of situation? Fortunately for us, the ObjectARX 2000 Wizard comes to the rescue in this regard. However, all the upcoming applications in this chapter were not created with the ObjectARX 2000 Wizard and they are literally sprinkled with global data. Perhaps the easiest step to take is to make our applications not MDI aware but SDI (Single Document Interface) aware. In other words, AutoCAD will only allow us to open one DCL (Dialog Control Language) Dialogs 347 ae drawing at a time. While this is not an elegant or long-term solution, itis going to be the practice that we will adopt for the rest of this chapter. In later chapters we will be completely MDI aware, with global data on a per document basis. So how do I make sure that my application is not MDI aware? Code the AcRoxz:kinitAppMsg as follows: case ACRx::kInitAppMsg : acrxUnlockApplication(ptr); acrxRegi sterAppNotMDIAware(ptr) ; initappQ; break: ‘Wal, that is about all it took to get the following applications to work in AutoCAD 2000. All of these applications were Objec:ARX 2.02/AutoCAD R14 applications. ‘Wo! That was easy, you say. Sorry, but that is ctll not the whole story. When you load this application you may be greeted with the following message on the command ‘Prompt: :-( ARX application : F:\Ch6_2.arx is NOT MDI aware. :-( ‘The reported message at the AutoCAD command prompt may be taken out of ‘AutoCAD by the time you read this book. If so, execute the ARX command at the command prompt and enter a single question mark (2). Her isthe kind of output you will receive: Command: arx Enter an option [?/Load/Unload/Commands/Options]: 7? Loaded Runtime Extension Programs: acadapp.arx - NDI Aware. acdim.arx - MDI Avare. aceplotx.arx - MDI Aware. acetutil.arx - MOI Aware. achapil5.dbx - MDI Aware. achInkui.arx - MDI Aware. adcgcommandsincmdgroup.arx - MOI Aware. ch6_2.arx - SDI Only. oleaprot.arx - MDI Aware. vi.arx - MDI Aware. End of List. ‘There is also a system variable in AutoCAD 2000, SDI, that controls the Single Document Interface; its default value is ©, meaning that AutoCAD 2000 can open and edit multiple drawings. When you load your non-MDI aware application, SDI will have a value of 2. See AutoCAD's online help for more information on SDI. Sorry, there is still more bad news. With your application loaded, you will only be able to work on one drawing at a time, until you unload your application. The next piece ‘of bad news is that if AutoCAD had more than one drawing file open for editing prior to loading your non—-MDI aware application, you will not be able to load your appli- cation until you only have only one drawing file open. Dont forget to change the value of SDI back to 0 after you have unloaded your application. Note you have to do this manually. For the applications in this chapter, I went through and changed all the appropriate ‘ads_s00 names to their ObjectARX 2000 equivalents. Ifyou dont fel like doing this, make sure you include the migrtion. file in your applications—it will automatically convert all the ads 2OOC names for you as well as allow you to use acdbCurDwg(). Please note that for future versions of AutcCAD, this migrtion.b file may not be avail- able. All of the applications for the later chapters in this book will be MDI aware and will behave as good MDI citizens. We will revisit these topics later in the upcoming chapters. SAMPLE APPLICATION CH6_2 In this application we revisit the window d-awing application that we developed in the last chapter. The application prompted the user for input from the AutoCAD com ‘mand prompt. In this version of the application we prompt the user for input from the (Ch6_2.del file dialog that we created earls. Ifyou are building a debug version of the application, place the Cb6_2.di! file in the debug directory or place it in a directory in the AutoCAD search path. In the Preferences dialog box of AutoCAD, you can add a new directory to the Support File Search Path on the Files tab of the dialog box. ‘This application is exactly the same as that of Chapter 5 with respect the RectWindow, ArchWindow, and ApexWindow classes, and therefore the implementation and header files for the Rect Window, ArchWindow, and ApexWindow classes are not reproduced here. Here is the listing for the Ch6_2.opp file: 11 CH6_2.cpp // by Charles Mc Auley // “Programming AutoCA) 2000 with ObjectARX” uw JJ This application is a redo of the Windows DOL (Dialog Control Language) Dialogs 349 application /1 of Chapter 5. The user interface now supports# a ock JI style dialog. uy TTL an Winclude // acdb definitions iHinclude —// ads defs finclude // unlock application #include finclude // Symbol Tables finclude // DCL style dialogs finclude // asDblArray #include // acdbHostAppl icationServices() // TODO: add your own includes include “RectWindow.h” // RectWindow class Hinclude “ArchWindow.h” // ArchWindow class #include “ApexWindow.h” // ApexWindow Class // Function prototypes // entry point for this application extern “C” AcRx::AppRetCode acrxEntryPoint( ACRX::AppMsgCode msg, void® ); /J helper functions void initapp (void); void unloadApp( void) ; //_user defined functions void windo(): Adesk: :Boolean getModel SpaceRecord(AcDb81ockTableRecord*ag? pBIkTableRecord) ; // Dialog call back function prototypes static void CALLB digOk&tnCB(ads_callback_packet# *cpkt): // Global variables, place holders for the values // obtained from the d‘alog box double g_winHt, g_winhidth; int g_Rows, g Cols: char g_strWinTypel10]; Boolean g_bProceed = Adesk: :kFals Boolean g_bWindcParamSet = Ades! False: TTL HAL 11 acrxEntryPoint (internal) // This function is the entry point for youre application. HU MUMMMITAAL UUM ‘AcRx: :AppRetCode acrxEntryPoint (ACRx: : AppMsgCoded? msg, void* ptr) { switch (msg) { case AcRx::kInitApplsg = acrxUnlockapplication(ptr) : acrxRegi sterAppNotMDIAware( ptr): initapp(): break: case AcRx::kUnloadAppMsg = un oadApp(); break: rrkLoadDwgMsg kUnloadDwgMsg : default: break; dT switch return AcRx::kRetOK: ) DOL (Dialog Control Language) Dialogs 351 $$ void initApp(void) { // TODO: init your application // register a command with the AutoCADé command mechanism acedRegCmds ->addConmand(“CH6_APPS", “WINDO”, “WINDO”, ACRX_CMD_MODAL, windo); acutPrintf(“Enter \"WINDO\” at the command prompte to draw a window. \n"): } void unloadApp( void) ( // 7000: clean up your application // Remove the command group added via acedRegCmds->addCommand acutPrintf(“ks%s", “Goodbye\n”, “Removing commande group \"CH6_APPS\"\n”); ‘acedRegCmds ->removeGroup(“CH6_APPS”); } /1 T0D0: add your other functions here void windo() ( int rez // Return code char del FiletTILE_STRLIMITI; // TILESTRLIMITS = 255 in adsdig.h file char de1Name(80]; int delld, dbStat; // del 10 and dialoge box status ads_hdig digHd1: 17 Dialog Box handle AcDbBlockTableRecord *pB1kTableRecord; RectWindow *pRectWindow; // RectWindow class ArchWindow *pArchWindow; // Derived ArchWindowe class 382 ApexWindow *pApexWindow; // Derived ApexWindowe class RectWindow *pGenericWind: AcGePoint2d startPt; AcGePoint3d pickPt; // Lower left point of the window char buffer[80]; // Used in the conversion of@ numbers to string strepy(dclFite, “ch62.de1"): strepy(dcIName, “ch6_2"); re = ads_load_dialog(dclFile, adclId): if(re [= RTNORM) ( acutPrintf("Ss %s", file. *, dclFile); return; ) “\nError loading Ch6_2.0CL& // Now that the dialog is loaded, display it rc = ads_new_dialog(dclName, dclId, NULLCB,& adigld!) ; if(re I= RTNORM) { acutPrintf("\nError displaying dialog. “); return; ) // Now that the dialog is visible on the screen // initialize it. if(q_bWindoParamSet) ( if(stremp(g_strWin-ype, “rb_rect”) == 0) ads_set_tile(dlgidl, “rb_rect™, “17); ) else if(stremp(g_strWinType, “rb_arch”) == 0) i ads_set_tile(digHd1, “rb_arch”, “1"); } DCL (Dialog Control Language) Dialgs 353 ee else if(strcmp(g_strWinType, “rb_apex”) == 0) ‘ ads_set_tile(digHdl, “rb_apex”, “1"); tise ‘ ads_set_tile(dlgldl, “eb_height’ ) ads_set_tile(dlgldl, “eb_widt // Convert the doutles for the height and width —gevt(g_winHt, 10, buffer); ads_set_tile(digidl, “eb_height” —gevt(g_winWidth, 10, buffer); ads_set_tile(digid!, “eb_width”, buffer); buffer); // Convert the integers for the rows and columns ~itoa(g_Cols, buffer, 10); ads_set_tile(digHdi, “eb_col —itoa(g_Rows, buffer, 10 ads_set_tile(digldl, “eb_rows”, buffer); ) else ( buffer); ads_set_tile(digHdl, “eb_height”, 20.00"); ads_set_tile(digldl, “eb_width”, “20.00"); } ads_action_tile(digld", “accept”, d1gOkBtncB); re = ads_start_dialogidigHdl, adbStat); if(re != RTNORM) ( acutPrintf(“\nFailed to initialize the dialoge box. “); return; J // Unload the dialog box ads_unload_dialog(dcl1d); if(g_bProceed) { acedInitGet (NULL, NULL); rc = acedGetPoint(MULL, “\nPick lower lefté window insertion point : “, asDblArray(pickPt)); switch(re) ( case RICAN: case RTERROR: return; case RTNONE: startPt.x = 0; startPt.y break; case RTNORM: startPt.x = pickPt.x: startPt.y = pickPt.y: break; ) J/ What kind of window did the user select? if(stremp(g_strWintype, “rb_rect”) == 0) { pRectWindow = new RectWindows PGenericWind = pRectWindow: ) else if(stremp(g_strWinType, “rb_arch") == 0) ( pArchWindow = new ArchWindows pGenericlind = pArchWindow; } else if(stremp(g_szrWinType, “rb_apex”) == 0) { pApexWindow = new ApexWindow: pGenericWind = pApexWindow; ) else ( retur ) DOL (Dialog Control Language) Dialogs 355 $$ // Fill out the window class object // with the input values pGenericWind->setWindowLength(g_winWidth): pGenericWind->setWindowHei ght (g_winHt); pGenericWind->setWindowCols(g_Cols): pGenericWind->setWindowRows(g_Rows) : pGenericWind->setWindowStartPoint(startPt); if (1getModel SpaceRecord( p81kTableRecord)) ( return; ) pGenericWind->drawWindow( pB1kTableRecord) ; if (pGenerichind { delete pGenerichind; ) = NULL) pBIkTableRecord->close(); // Set the boolean back to false, because // in the next invocation of the application // AutoCAD remembers the values of the global // variables. g_bProceed = Adesk::kFalse; g_bWindoParamSet Adesk: :kTrue; 1// if g_bProceed else { acutPrintf(“\nUser cancelled. } ) 17 Dialog Box callback functions static void CALLB dig0kBtnCB(ads_caT1back_packet® *cpkt) ( char strWint(100]; 356 char strWinWidth[1001; char strWinRows(20]; char strWinCols(20]; char strWinType(10]; int re: double winHt, winWidth; ‘int winRows, winCols; // Get the window hei ght re = ads_get_tile(cpkt->dialog, “eb_height”.& strWint, 80); if(re != RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf(“\nError in Height edit box. “): return; ) winlt = atof(strWinl:): if(winkt <= 0.0) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf(“\nInvalid value for window Height. & return; 17 Get the window width re = ads_get_tile(cpkt->dialog, “eb_width”.& strWinWidth, 80); if(re = RTNORM) ( ads_done_dialog(cpxt->dialog, DLGOK); acutPrintf(“\nError in Width edit box. return; } winWidth = atof(strWinWidth); if(winWidth <= 0.0) t ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf("\nInvalid value for window width.& “)s return; DCL (Dialog Control Language) Dials 357 $$ } // Get the number of window columns re = ads_get_tile(cpkt->dialog, “eb_cols”, strWinCols, 20); if(re $= RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf("\nError in Columns edit box. return; } winCols = atoi(strWinCols); if(winCols < 1) { acedAlert(“Invalid value for windowd columns. \nMinimum value = 1"); ads_mode_tile(cpkt->dialog, “eb_cols”.@ MODE_SETFOCUS) ; } // Get the number of window rows re = ads_get_tile(cpkt->dialog, “eb_rows”, strWinRows, 20); if(re 1= RTNORM) { ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf(“\nError in Rows edit box. return; ) winRows = atoi(strWinRows); if(winRows < 1) { acedAlert(“Invalid value for window rows. \nMinimum value = 1"); ads_mode_tile(cpkt->dialog, “eb_rows”, MODE_SETFOCUS) ; ) 11 The following ads_get_tile() call will placee in strWinType 17 the Key of the radio button that is selected in other words // the radio button that has a value of The key brc_wintype 1.8 J/ is a boxed_radio_column tile that containse radio_button tiles. rc = ads_get_tile(cpkt->dialog, “bre_wintype”, strWinType, 10); if(re = RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK) acutPrintf(“\nError in window type. “): return; ) ads_done_dialog(cpkt->dialog, DLGOK); // Transfer the values obtained to the global // variables. gwinkt = winkt; g_winWidth = winWidsh; gCols = winCols; g_Rows = winRows: strepy(g_strWinType, strWinType); g_bProceed = Adesk::kTrues ) // User defined functions Adesk: :Boolean getMode! SpaceRecord(AcDbBlockTableRecord*&e pB1kTab1eRecord) { AcDbDatabase *pCurDb; AcDbBlockTable *pBlkTable; Acad::ErrorStatus es; pCurDb = acdbHostApplicationServices()% =>workingDatabase(): es = pCurDb->getBlockTable(pB1kTable. g ‘AcDb: :kForRead); DCL (Dialog Control Langue) Dialogs 359 if(es != Acad: :e0k) { acutPrintf("\nFailed to open Block Table for ag read operation.”); return Adesk: :kFalse; } es = pB1kTable->getAt(ACDB_MODEL_SPACE, pBIkTableRecord, AcDb: :kForWrite): if(es != Acad: :e0k) { acutPrintf(“\nFailed to open MODEL SPACE for ag write operation.”); pB1kTable->close() return Adesk: :kFal ) // We don’t need the block table anymore so wee can close it. pBIKTable->close(); return Adesk: :kTrue; ) As I have mentioned previously, the listing does not include the classes for RectWindow, ArchWindow, or Apex Window because they have not changed. In the user-defined function prototypes, the functon getModelSpaceRecord() is the same as it was in Chapter 5. Speaking of function prototypes, here is the function proto- type for the callback function that is called when the user selects the OK button on the dialog: static void CALLB dlgOkBtnCB(ads_callback_packet? *cpkt); Dont forget: you can have as many of these callback functions as you like. Right after the function prototypes I have introduced a number of global variables: these are all prefixed with g_. I do initialize the two Adesle:Boolean global variables. When you first load the ARX application Ch6_2.arx, these global variables will have no value, with the exception of the two Adesle:Boolean variables that I have already initialized. Let’ look at the windoQ) function frst, which is called as a result of registering the command WINDO with the acedRegCmds call. We already know how to load and unload dialogs and we have already seen the pointers to the various window types. I have introduced another pointer to a RectWindow class, namely pGenericWind. ‘When you initialize a dialog, it is initialized after the call to ads_new_dialog() and before the call to ads_start dialog(). Remember that ads_start_dialog() hands over control to the dialog box for processing. ‘When you run the application for the first time, these global variables ae initialized and they retain their value from invocation to invocation of the application within a single AutoCAD session. This application also runs in SDI mode. As you will see later, ‘use this feature to fill in the values of the edit boxes and radio buttons, so if you want to use the same values over and over again, you dontt have to enter them every time. Let’s assume we are invoking the application for the first time. The global variable ‘ WindoParamSet will be false in the if statement so we end up jumping over this section and into the else branch, where we call the ads_set.tile() function to set the edit_box for Height and Width with values of 20.00. Then we call ads_action_tile() to associate the OK button, which has 2 key value of accept (discussed earlier), with the callback function dlgOkBtnCBQ. Once our initialization is finished, we then call ads_start_dialog() to hand over control over to the dialog box. Now that the dialog box has control, we select the kind of window we wish to draw and fil in the parameters for Height, Width, Columns and Rows. When the user selects the OK button, the digOkBtnCBQ) function is called, which takes a single parameter of type ads_callback_packet. The dlgOkBtnCBQ func tion is where you read values from the various tiles in the dialog. The values are returned as strings and you are responsible for any conversion that is required. Look at the following code fragment, where we cetricve a value from the Height edit_box: // Get the window heignt rc = ads_get_tile(cpkt->dialog, “eb_height”,# strWinHt, 80); if(re f= RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf(“\nError in Height edit box. “); return; winHt = atof(strWinHt); if(winHt <= 0.0) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf(“\nInvalid value for window Height. “ return; ) DCL (Dialog Control Language) Dialogs 361 SSS ‘The parameter epkt->dialog is a handle to the dialog of type ads_hdlg in the ds_get_tile() call. Here I am being a little cruel—if the user enters a value that is less than or equal to 0.00, I dismiss the dialog with a call to ads_done_dialog(). The func- tion ads_done_dialog() must be called within a callback function to dismiss the ialog box and hand control back to AutoCAD. Dont forget to convert the strings if you need to. Instead of being cruel, you could use the approach shown in this code fragment: // Get the number of window columns re = ads_get_tile(cpkt->dialog, “eb_cols”.& strWinCols, 20); if(re != RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf(“\nError in Columns edit box. return: ) winCols = atoi(strWinCols); if(winCols < 1) ( acedAlert(“Invalid value for window? columns. \nMinimum value = 1"); ads_mode_tile(cpkt->dialog, “eb_cols”,% MODE_SETFOCUS) ; ) Instead of penalizing the user for invalid input, I pop up an acedAlert() dialog and then set the focus back to the offending tile—a kinder gentler approach. Eventually, we have to call ads_done_dialog0, after which I transfer the values received in the dialog back to the global varibles. Control returns to the windo() func tion right after ads_start_dialog(). I then call ads_unload_dialog() to unload the dia- log from memory. The global variable g_bProceed will be TRUE if the user selected the OK button. As before, I ask the user to select a start point. Then we create the appropriate window type, make the pointer pGenerieWind point to the appropriate ‘window type and eventually make the follewing c pGenericWind->drawWindow(p81kTableRecord) ; ‘This is an example of polymorphic bebavicr, in which the pointer knows what kind of object it's pointing to and draws the correct window type. Before we leave the ‘windo() function, we set the global variable g BWindoParamSet to TRUE. Now that we have run the application once, the global variables are initialized and retain their values in memory. The next time we run the application, the global variable g_bWindoParamSet will be TRUE. In that case, we initialize the dialog box with the values that we had used previously. Why do I not draw the window inside the call- back function digBtnOkCB(Q? There is nothing to stop me from drawing the window from within the callback dlgBtnOkCB( function, but that is not the purpose of the OK button; its purpose is to retrieve the values entered by the user and then transfer the values to global variables. HIDING DIALOG BOXES When a dialog box is active, itis in control of the application. There are times dur- ing the dialog box process that it would be useful to be able to select a point or an enti- ty. In order to allow this you must hide the dialog, select an entity or a point, and then bring the dialog back to life. There are a number of ADSRX functions that are not accessible while the dialog box is active—these are listed in the documentation if you ‘want to investigate them. For example, you cannot execute AutoCAD commands while the dialog is active (acedCmd and acedCommand). So how do you hide a dialog? Earlier we discussed the ads_done_dialog() function. Te takes two arguments: the first is an ads_hdlg and the second is an int status. ‘There are three defined values for the second argument, namely DLGOK, DLGCAN- CEL, and DLGSTATUS. If DLGOK or DLGCANCEL is used, the dialog will be ter- inated. However the user can pass in a user-defined value as an argument, which will cause the dialog to be hidden and suspended. The dialog can then be brought back to life after the user has interacted with the drawing, The defined values of DLGOK, DLGCANCEL, and DLGSTATUS are as follows in the adsdlg. file: /* Return by reference integers for ads_start_dialog “4 define DLGCANCEL 0 /* User pressed Cancel org equivalent */ define DLGOK 1 /* User pressed Ok */ define DLGALLOONE -1 /* Al] dialogs terminatede with term_dialog */ fdefine DLGSTATUS 2 /* start of user returne codes */ ‘Therefore a user-defined value for status must be greater than 2, We will see this in our next application. Hiding a dialog is implemented within a while loop because the user can hide the dialog box more than once. The exit condition for the while loop is when the status value is less than DLGSTATUS. Let’s move on and look at the next sample application, Ch6_3.ars. DGL (Dialog Control Language) Dialogs 363, CATION CH6_3 ‘This application is once again a continuation of the window drawing application. In the previous version of the application, the OK button was sclected after the user had selected the window type, height, width, columns and rows. When the dialog dis- appeared, the user was then asked to select the start point for the window, after which the window was drawn. In this version of the application we are going to allow the user to interactively select the start point for the window by hiding the dialog box. ‘When the user has selected the start point, the dialog will appear again and, ifthe user is happy with all the parameters, the OK button can be selected, after which the appro- priate window type will be drawn. Here is an illustration of the dialog for the (Ch6_3.del file: Figure 6.6 Second Window Parameters Dialog with Added Pick Button As you can see, we added a boxed_row, which contains a pick button and two cedi¢_box tiles forthe X value and the Y value respectively. Pm not going to show the entire Ch6_3.del file here. The Ch6_3.dcl file is almost identical to the Ch6_2.ddl file -with the following addition, added to the end of the file just prior to the ok cancel button sub-assembly: : boxed_row { label = “Start Point”: : button label = * < Pick key = “btn_pick”; edit_box Jabel = “x Valu key = “eb_xval”; mnemonic = “X"; edit_width = 8: value = “0.00; 1 edit_box label = “Y Valu key = “eb_yval mnemonic = “Y" edit_width = value = “0.00’ } ) // boxed_row Here is the listing of the Ch6_3.

// acdb definitions #include // ads defs #include i/ unlock application DCL (Dialog Control Language) Dialogs 365 ee finclude #include // Symbol Tables H#include // DCL style dialogs finclude // asDblArray finclude // acdbHostAppl icationServices() // TODO: add your own includes #include “RectWindow.h” // RectWindow class finclude “ArchWindow.h” // ArchWindow class finclude “ApexWindow.h” // ApexWindow Class // Function prototypes // entry point for this application extern “C” AcRx::AppRetCode acrxEntryPoint( ACRx::AppMsgCode msg, void* ); // helper functions void initapp (void); void unloadapp( void); J/_user defined functions void windo(); Adesk: :Boolean getMode SpaceRecord(AcDbB1ockTableRecord*&e? pB1kTableRecord) : // Dialog call back function prototypes static void CALLB dlgOk&tnCB(ads_callback_packet? static votd CALLB dgPickPtBtnCB(ads_cal1back_packet Static’ votd cALLB di gWinTypeRbCB(ads_callback_packeto esate void CALLB d1gEbHei ghtCB(ads_cal1back_packeto esate void CALLB dl gEbWidthCB(ads_cal1back_packet? ctatig void CALLE d1gEbColsCB(ads_cal!back_packet? Statte vota CALLB di gEbRowsCB(ads_callback_packeto *cpkt) s static void CALLB dIgEbXValCB(ads_callback_packeto *cpkt); static void CALLB d1gEbYValCB(ads_callback_packeto *epkt); J/ Global variables, place holders for the values // obtained from the dialog box double g_winHt = 20.00; double g_winWidth = 20.00; int g_Rows = 1; int g_Cols = 1; char g_strWinType[10] = (“rb_rect™): AcGePoint2d g_startPt(0.0, 0.0); Adesk: :Boolean g_bProcesd = Adesk::kFalse; MTL LLL TTT // acrxentryPoint(interral) 1/ This function is the entry point for youre application. MTL TLL LLL TAD WLLL AcRx: :AppRetCode acrxEntryPoint (ACRx: : msg, void* ptr) i switch (msg) ( case ACRx::kInitAppisg : acrxUnTockApplicazion(ptr); acrxRegisterAppNo:MDIAWare( ptr); initapp): break: case AcRx::kUnToadAapisg : unloadApp(): break: case ACRK: break: case AcRx::kUnloadDvoMsg : break; DGL (Dialog Control Language) Dialogs 367 $$$ default: break; ) 11 switch return AcRx::kRetOK; ) void initApp(void) { // T0D0: init your application // register a command with the AutoCADé command mechanism acedReg0mds->addConmand(“CH6_APPS”, “WINDO”, < “wrnno”, ACRX_CMN_MODAL, windo): acutPrintf (“Enter \"WINDO\" at the commande Prompt to draw a window. \n” } void unloadApp( void) ( // TODO: clean up your application // Remove the command group added via acedRegCmds->addCommand acutPrintf(“Ss%s", “Goodbye\n", “Removingg command group \"CH6_APPS\"\n" acedRegCmds - >removeGroup( “CH6_APPS”) ; ) // TODO: add your other functions here void windo() t int rez // Return code char dclFile{TILE_STRLIMIT]; // TILE_STRLIMITS = 255 in adsdig.h file char dc1Name[801 int delld, dbStat; box status // del 1D and dialog? ads_hdlg digHdl; // Dialog Box handle AcDbBlockTableRecord *pB1kTableRecord; RectWindow *pRectWindow; // RectWindow class ArchWindow *pArchWindow: // Derived ArchWindow? class ApexWindow *pApexWindow: // Derived ApexWindowe class RectWindow *pGenericWind; AcGePoint3d pickPt; // Lower left point ofé the window char buffer(80]; // Used in the conversion of # numbers to string dbstat = 5; // Set an initial value for dbstaté must be greater than 1 strepy(dclFile, “ch6_3.dc1"); strepy(delName, “ch6 re if(re { acutPrintf (“Bs Bs", “\nError loading Ch6_3.0CLe file. ", dclFile); return; } ads_load_dialog(dclFile, &dclId); RTNORM) while(dbStat >= DLGSTATUS) fi // Now that the dialog is loaded, display it re = ads_new_dialog(dclName, delId, NULLCB,& adighd1); if(re t= RTNORM) ( acutPrintf("\nError displaying dialog. “); return; ) DCL (Dialog Control Language) Dialogs 369 $$$ // Now that the dialog is visible on the screen // initialize it. if(stremp(g_strWinType, “rb_rect") = 0) ( ads_set_tile(dighd], “rb_rect”, “1"); ) else if(stremp(g_strWinType, “rb_arch”) == 0) ( ads_set_tile(digHd], “rb_arch", “1"); ) else if(stremp(g_strWinType, “rb_apex”) == 0) { ads_set_tile(dlgHdl, “rb_apex”, “1”); ) else { ads_set_tile(dIgHdl, “eb_height”, “20.00” ads_set_tile(dighd], “eb_width”, “20.00"); } // Convert the doubles for the height and width acdbRTOS(g_winHt, 2, 3, buffer); ads_set_tile(digid!, “eb_height”, buffer); acdbRTOS(g_winWidth, 2, 3, buffer); ads_set_tile(digdl, “eb_width”, buffer); // Convert the integers for the rows and columns -itoa(g Cols, buffer, 10): ads_set_tile(digHdi, “eb. —itoa(g Rows, buffer, 10 ads_set_tile(digHdi, “eb_rows”, buffer); acdbRT0S((ads_real)g_startPt.x, 2, 3, buffer); ads_set_tile(dlgHdl, “eb_xval”, buffer); acdbRT0S((ads_real)g_startPt.y, 2, 3, buffer); re = ads_set_tile(digidl, “eb_yval", buffer); ols”, buffer); ads_action_tile(dighd!, “accept”, digOkBtncB); ads_action_tile(digid!, “btn_pick",& dl gPickPtBtncB); ads_action_tile(digidl, “rb_rect”, dl gWinTypeRbCB) ; 370 ads_action_tile(dighd1, “rb_arch",& d1gWinTypeRbCB) : ads_action_tile(dIgHd1, “rb_apex", d1gWinTypeRbCB) : ads_action_tile(digid!, “eb_height”.& di gEbHeightcB) ads_action_tile(dighd1, “eb_width".& d1gEbWidthcB); ads_action_tile(digd1, “eb_cols”, d1gEbColsCB): ads_action_tile(digHd1, “eb_rows”, d1gEbRowsCB) ads_action_tile(digd1, “eb_xval", dIgEbXValCB); ads_action_tile(digHd1, “eb_yval”, dlgEbYValCB); re = ads_start_dialog(dlgldl, &dbStat); if(re I= RTNORM) { acutPrintf("\nFailed to initialize thee dialog box. “); return ) switch(dbStat) i case 4: acedInitGet(NULL. NULL): re = acedGetPoint(NULL, “\nPick lower left window insertion’ point : “, asDblArray(pickPt)); switch(re) ( case RICAN: case RTERROR: return; case RTNONE: gistartPt.x = 0; g_startPt.y = break; case RTNORM: DCL (Dialog Control Language) Dialogs 371 gistartPt.x = pickPt.x; g_startPt.y = pickPt.y; break; 1 // inner switch break; 1/1 switch ) 1 white J/ Unload the dialog box ads_unload_dialog(dclld); if (g_bPraceed) ( J/ What kind of window did the user select? if(stremp(g_strWinType, “rb_rect") == 0) i pRectWindow = new RectWindows pGenericWind = pRectWindow; ) else if(stremp(g_strWinType, “rb_arch”) == 0) { pArchWindow = new ArchWindow; pGenericWind = pArchWindow; ) else if(stremp(g_st-WinType, “rb_apex”) == 0) ( PApexWindow = new ApexWindows pGenericWind = pAsexWindow; ) else i return; ) /1 Fill out the window class object // with the input values pGenericWind->setWindowLength(g_winWidth): pGenericWind->setWindowHeight(g_winHt); pGenericWind->setWindowCols(g_Cols); pGenericWind->setWindowRows(g_Rows): pGenericWind->setWindowStartPoint(g_startPt); if (1getModel SpaceRecord(pB1kTableRecord)) { return; } pGenericWind- >drawWindow(pB1kTableRecord); if(pGenericWind != NULL) ( delete pGenericWind; ) pBIkTableRecord->close(); JI Set the boolean back to false, because // in the next invocation of the application // AutoCAD remembers the values of the global // variables. g_bProceed = Adesk::kFalses V// if g_bProceed else { acutPrintf("\nUser cancelled. * ) ) J/ Dialog Box callback functions static void CALLB d1g0kBtnCB(ads_callback_packet# *cpkt) { char strWinHt(1001; char strWinWidtht100]; char strWinRows[20]; char strWinCols(20]; char strWinTypeC10]; char strxPt(20: char stryPt(20. int res DOL (Dialog Control Language) Dogs 373 —$— double winkt, winWidth; double xPt, yPt; ‘int winRows, winCols: // Get the window height re = ads_get_tile(cpkt->dialog, “eb_height”,& strWinit, 80); if(re I= RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf(“\nError in Height edit box. return; } winlt = atof(strWinkt); if(wint <= 0.0) t ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf("\nInvalid value for window Height.& return; } // Get the window width re = ads_get_tile(cpkt->dialog, “eb_width”,& strWinWidth, 80); if(re t= RTNORM) ( ads_done_dialog(cpk:->dialog, DLGOK); acutPrintf(“\nError in Width edit box. “); return; ) winWidth = atof(strWiaWidth); if(winWidth <= 0.0) { ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf("\nInvalid value for window width. & “); return; ) // Get the number of window columns re = ads_get_tile(cpkt->dialog, “eb_cols”,¢ strWinCols, 20); a4 if(re != RTNORM) { ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf(“\nError in Columns edit box. “): return; ) winCols = atoi(strWinCols); if(winCols <1) t acedAlert(“Invalid value for windows columns. \nMinimum value = 1"); ads_mode_tile(cpkt->dialog, “eb_cols”,# MODE_SETFOCUS) ; ) // Get the number of window rows re = ads_get_tile(cpkt->dialog, “eb_rows”,¢ strWinRows, 20); if(re 1= RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf("\nError in Rows edit box. “); return; d WinRows = atoi(strWirRows); if(winRows < 1) acedAlert(“Invalid value for window rows. \nMinimum value = 1"); ads_mode_tile(cpkt->dialog, “eb_rows”. MODE_SETFOCUS) ; ) // The following ads_get_tile() call will placed in strWinType // the key of the radio button that is selectede ‘in other words // the radio button that has a value of “1”. Thed key brc_wintype 7/ is @ boxed_radio_column tile that contains® radio_button tiles. re = ads_get_tile(cpkt->dialog, “brc_wintype”, strWinType, 10 DGL (Dialog Control Language) Dialogs 378 $$ if(re I= RTNORM) { ads_done_dialog(cpkt->dialog, DLGOK): acutPrintf("\nerror in window type. return; ) // Get the Window starting x and y values ro = ads_get_tile(cpkt->dialog, “eb_xval”,¢ strxPt, 20); ro = ads_get_tile(cpkt->dialog, “eb_yval”.¢ strYPt, 20); xPt = atof(strxPt); yPt = atof(strvPt); ads_done_dialog(cpkt->dialog, DLGOK): // Transfer the values obtained to the global // variables. Qwinlt = winHt; gwinWidth = winWidth; giols = winCols g_Rows = winRows: strepy(g_strWinType, strWinType); gistartPt.set(xPt, yPt); g_bProceed = Adesk::kTrue: ) static void CALLB digPickPtatnCB(ads_callback_packet? *cpkt) fl ads_done_dialog(cpkt->dialog, 4); ) static void CALLB digWinTypeRbCB(ads_callback_packet? *cpkt) ( 376 char strWinType(10]: int res // The following ads_get_tile() call will place in strWinType // the key of the radio button that is selecteds in other words // the radio button that has a value of “1”. Thee key brc_wintype // is a boxed_radio_column tile that containsé radio_button tiles. rc = ads_get_tile(cpkt->dialog, “brc_wintype".& strWinType, 10); if(re RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK) acutPrintf(“\nError in window type. return; ) strepy(g_strWinType, strWinType); ) static void CALLB dlgEbHei ghtCB(ads_callback_packeto *epkt) ( int re: char strWinHtl100]; double winkt; if(cpkt->reason = CER_LOST_FOCUS) i // Copy the value to the global variable // Get the window height rc = ads_get_tile(cpkt->dialog, “eb_height”.# strWinkt, 80); if(re I= RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf(“\n€rror in Height edit box. “); return: } DGL (Dialog Control Language) Dialogs 37 een Winkt = atof(strWinHt); if(winktt <= 0.0) i acedAlert(“Invalid value for window height"); ads_mode_tile(cpkt->dialog, “eb_height”.¢ MoDE_SETFOCUS); } else { // Copy value ouz to global gwinlit = winkt; ) ) ) static void CALLB d1gEbWidthCB(ads_cal1back_packet# ¥cpkt) ( int rez char strWinwidth[1007; double winkidth; if(cpkt->reason = CBR_LOST_FOCUS) { // Copy the value zo the global variable // Get the window height rc = ads_get_tile(cpkt->dialog, “eb_width",& strWinWidth, 80); if(re 1= RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf("\nError in Width edit box. return; ) winWidth = atof(strWinWidth); if(winWidth <= 0.0) ( acedAlert(“Invalid value for window ads_mode_tile(cpkt->dialog, “eb_widt! MODE_SETFOCUS); y else { dth”); // Copy value out to global gwinWidth = winWidth; } } } static void CALLB d1gEbCo1sCB(ads_cal Iback_packet# *cpkt) t int re: char strWinCols(20); ‘int winCols: if(cpkt->reason == CBR_LOST_FOCUS) ( // Copy the value to the global variable // Get the window height re = ads_get_tile(cpkt->dialog, “eb_cols".¢ strWinCols, 20): if(re l= RTNORM) i ads_done_dialog(cpkt->dialog, DLGOK): acutPrintf(“\nError in Columns edit box. “): return: ) winCols = atoi(strWinCols); if(winCols < 1) { acedAlert(“Invalid value for window? columns. \nMimimum = 1°}; ads_mode_tile(cpkt->dialog. “eb_cols”.@ MODE_SETFOCUS); ? else { IJ Copy value ou: to global gCols = winCols; ) ) ) static void CALLB dIgEbRowsCB(ads_callback_packeto *epkt) DOL. (Dialog Control Language) Dielogs 379 a int res char strWinRows(20]; int winRows; if(cpkt->reason == CBR_LOST_FOCUS) fi // Copy the value to the global variable 1/ Get the window height ro = ads_get_tile(cpkt->dialog, “eb_rows”,& strWinRows, 20); if(re != RTNORM) t ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf("\nError in Rows edit box. “); return: ) winRows = atoi(strWinRows); if(winRows <1) ( acedAlert(“Invalid value for window rows. \nMiminum = 1"); ads_mode_tile(cpkt->dialog, “eb_rows”,@ MODE_SETFOCUS); ) else ( // Copy value out to global g_Rows = winRows: ) ) ) static void CALLB dlgEbXValCB(ads_callback_packet® *epkt). { int res char strXPt(20]; double xPt; if(cpkt->reason == CBR_LOST_FOCUS) i // Copy the value to the global variable // Get the window height re = ads_get_tile(cpkt->dialog, “eb_xval”.& strxPt, 20); if(re I= RTNORM) { ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf("\nError in X Value edit box. * return; ) xPt = atof(strxPt); gistartPt.x = xPt; ) } static void CALLB dlgEbYValCB(ads_cal lback_packeto *cpkt) { int res char_strYPt(201; double yPt: if(cpkt->reason == CBR_LOST_FOCUS) t // Copy the value <0 the global variable // Get the window height re = ads_get_tile(cpkt->dialog, “eb_yval",# strYPt, 20); if(re = RTNORM) ( ads_done_dialog(cpkt->dialog, DLGOK); acutPrintf(“\nError in Y value edit box. return; } yPt = atof(strvPt); gistartPt.y = yPt: ) ) // User defined functions DGL (Dialog Control Language) Dialogs 381 $$ Adesk: :Boolean getMode! SpaceRecord(AcDbB1ockTableRecord*ke? pBIkTableRecord) ( AcDbDatabase *pCurDb; AcDbBlockTable *p81kTable: Acad: :ErrorStatus es; pCurDb = acdbHostApplicationServices()# —>workingDatabase(); es = pCurDb->getBlockTable(pB1kTable, & AcDb: :kForRead) if(es I= Acai acutPrintf(“\nFailed to open Block Table for ag read operation.” return Adesk::kFalse; ) 0k) es _= pBlkTable->getAt(ACDB_NODEL_SPACE, & pBlkTableRecord, AcDb: :kForWrite) : iffes I= Acad::e0k) ( acutPrintf(“\nFailed to open MODEL SPACE for ag write operation."): pB1kTable->close() return Adesk: :kFal } JJ We don’t need the block table anymore so wee can close it. pBlkTable->close(); return Adesk::kTrue; ) ‘Wall, the first thing to notice about this application is that there are more callback firnc~ tions than in the previous version of the application. After the declaration ofthe call- back functions I initialize the global functions. ‘Let’s move on and look at the windo() function: in it | initialize the int variable dbStat to a value of 5, and then J call ads load_dialog(). We then enter the while loop for the first time, in which case dbStat has a value of S, which is greater than DLGSTA- TUS. Inside the while loop we make a cal! to ads_new_dialog(), which displays the dialog on screen, but we do not have control ofthe dialog at this point. We then ini- tialize the tiles of the dialog, after which we have numerous calls to ads_action_tile() function. Remember, the purpose of the ads_action_tile() is to associate a callback function with a tile. Finally, we hand over control to the dialog with a call to ads_start.dialog(. Following the ads_start_dialog() is a switch statement with a single case that has a value of 4. What causes dbStat to have a value of 4? And if t has a value of 4, how can you call acedGetPoint(), as shown in the code? Affe all, isnt the dialog active? Thave to admit that it’s not very obvious fiom the code how the dialog is hidden. At this point, this is our first pass through the while loop and dbStat has a value of 5, so the ease statement would be bypassed in this case anyway. We will revisit how dbStat gets a value of 4 in a moment, so please endure. At this point the dialog is active on the screen, patiently waiting for user input. Assuming the window type, height, width, columns, and rows have been selected, the user now selects the < Pick button. What happens? The ads_action_tile() function hhas associated the < Pick button with the digPickPtBtnCBQ function. Here is the digPickPeBtnCBQ function listing: static void CALLB dlgPickPtBtnCB(ads_callback_packet *cpkt) ( ads_done_dialog(cpkt->dialog. 4): ) The sole purpose of the function is to hide the dialog with a single call to ads_done_dialog). However, here we have a user-defined value of dbStat, which is 4. Now the dialog disappears from the screen, but instead of being terminated, itis in a temporary suspended state. When we now enter the switch statement, dbStat will have a value of 4, in which case the user is now asked to select a point. As a result of selecting a point, the global variable g startPt is assigned the values ofthe point select- ed. We once again loop through the while loop and redisplay the dialog, followed by initialization and a fresh call to ads_startdialog(, which will give dbStat a new value of DLGSTATUS. If the user selects the < Pick button again, the process is repeated. ‘How do we set the dbStat variable to a value of less than DLGSTATUS? Simple: the user can select either the OK or the Caneel button, which will give dbStat a value of DLGOK or DLGCANCEL, causing us to pop out of the while loop. If the user selected the OK button, the digOkBtnCBQ function will be called, which is similar in nature tothe previous application. This will cause the global variable g bProceed to be TRUE and a window of the appropriate type will be drawn as a result. DCL (Dialog Control Language) Dialogs 383 $$ So what are the other callback functions and ads_action_tile() functions? Every ‘time we hide the dialog as a result of the < Piek button being selected, the dialog is redisplayed and initialized. If the user has entered a value in the Hleight edit_box for ‘example, and then the user selects the < Piek button, eventually, when the dialog reap- pears, the edit_box will contain the value that the user entered prior to selecting the ‘< Pick button. Let's look at some of these functions in det. Ifthe user selects one of the radio_but- tons, the callback function dlgWinTypeRbCBQ is executed. The purpose of the fanction is to get the key of the radio_button selected and copy to the global variable &.strWinType the value of the local variable steWinType. Now let’s look at the callback for the Height edit box. Here is the listing for the call- ‘back fanction: static void CALLB dlgEbHei ghtCB(ads_callback_packet# epkt) { int re; char strWinHt(100]; double winkt; if(cpkt->reason == CBR_LOST_FOCUS) ( // Copy the value to the global variable // Get the window height re = ads_get_tile(cpkt->dialog, “eb_height”,& strWinkt, 80); if(re t= RTNORM) ( ads_done_dialog(cokt->dialog, DLGOK); acutPrintf("\nError in Height edit box. “); return; ) winkt = atof(strWinit); if(winlt <= 0.0) { acedAlert(“Invalid value for window height”): ads_mode_tile(cpkt->dialog, “eb_height”, MODE_SETFOCUS) } else fi // Copy value out to global g.winlit = winkt; ) ) ) In this function, we look at a different field for the callback packet, namely epkt->rea- son, and verify that it is identically equal to the predefined symbol CBR LOST_FOCUS. The CBR_LOST_FOCUS will occur as a result of moving the focus away from the edit box. The CBR_ prefix stands for ‘call back reason.” We have not discussed call back reasons yet. The CBR codes are used with edit_box, list_box, slider, and image_button tiles. Table 6.2 shows a listing of all the possible CBR codes: CBR Code Value CBR_SELECT The user selected the tile. CBR LOST_FOCUS | Foredit baxes, the user moved to another tile but didrit make a final selection, CBR_DRAG For sliders, the user changed the value by dragging the indicator (or equivalent) but didn't make a final selection. CBR_DOUBLE_CLICK | For list boxes or image buttons, the user double- clicked to make a final selection. ‘Table 6.2 Possible CBR Values In the dlgEbHeightCB() function, we check the value entered in the Height edit_ box and then transfer the value of the local variable winHt to the global variable g_winHt. All ofthe other callback functions are similar in nature to the dlgEbHeightCBQ func tion, Let’ move on and talk about displaying nested dialogs (sub-dialogs). NESTING DIALOG BOXES Before I talk about the application that implements the nested dialog, I need to discuss the Ch6_4.de file. Here is the listing for the C6_4.del file: ché_4 : dialog t label = “Window Parameters”; : row DGL (Dialog Control Language) Dialogs 385 ———— : boxed_radio_col umn { label = “Window Type"; key = “bre_wintype”: : radio_button { label = “Rect’ key = “rb_rect value = mnemonic : radio_button key = mnemonic = “A”; radio_button label = “apex”; key = “rb_ape) mnemonic = "X’ ) } // boxed_radio_column boxed_column label = “Specifications”: : edit_box { Jabel leight"; key = “eb_height”; mnemonic = “Hi edit_width = 8; edit_box label = “Width” key = “eb_width’ mnemonic = “W"; edit_width = 8; 2 edit_box label = “Columns”; key = “eb_cols”: mnemonic = edit_width = value = “17; edit_box label = “Rows”: key = “eb_rows' mnemonic = “R” edit_width = 8; value = “17; } } // boxed_column VT row : boxed_row label = “Start Point”; “ < Pick “5 ytn_pick”: key = mnemonic = “X"; edit_width = 8 value = “0.00; edit_box label = “Y Value": key = “eb_yval”: mnemonic = “Y"s edit_width = 8 value = "0.00"; ) ) 11 boxed_row DCL. (Dial Control Language) Dialogs 387 $$$ : row : button label = “Stats... key = “btn_stats”; : button label = “Ok”; key = “accept”; is_default = true; button label = “Cancel key = “cancel”; is_default = true; ) ) } stats : dialog i label = “Window Statistics”: : concatenation { text_part label = “Type”: text_part label = “Rows”; text_part label = “Col width = 6; text_part label = “Height”; text_part fi label = “Width”; ) }//concatenation Vist_box key = “Ib_stats”; value width = 40; height = 3; tabs = “6 12 18 27" 1//1ist_box ok_only; ) ‘This file is very similar to the Ch6_3.dd/ file. The first major difference is that addi- tion of a second dialog named stats. Figure 6.7 shows the dialog that the stats part of the Ch6_4.dcl file produces: Figure 6.7 Nested DCL Dialog ‘As you can see, the concatenation tile ties all the text. part stings together. The lay- out of the concatenation depends on the width of the text_part ties. The text_part tiles that do not have the width specified use the default width. Getting the layout cor- rect is purely a matter of trial and error. Following the concatenation tie i alist_box tile. The listbox tile has a width of 40 and a height of 3, which means the list_box will display 40 characters horizontally and 3 lines of text vertically. The text that appears inside the list box is a tab-formatted DL (Dialog Control Language) Dialogs 309 string (\e) that you will see when we look at the codes look at the tabs attribute in the list_box. Tabs values are a quoted string of integers. The integers represent the loca~ tions for the tab stops in the width of the list_box. Again, coming up with the cor- rect values is a matter of trial and error. Finally, there is a single ok_only, which is a predefined tile that is part of the base.de! file. If you look at the bottom of the Cb6_4de file, you will see that I have deleted the ‘ok_cancel predefined sub-assembly and replaced it with a row tile containing three button tiles as follows: : row ( : button label = “Stats. key = “btn_stats. ) : button ( label = “Ok” key = “accept is_default = true: button label = “Cancel”; key = “cancel”; is_cancel = true; ) } ‘There are two buttons that have the keys accept and eancel; both of these tiles have the attribute is_default = true; and is_cancel = true; respectively: These two buttons replace the ok cancel predefined sub-assembly. Therefore, in order for the buttons to react to the selection of the OK or Cancel button, they need to have the attribute {is default = true; defined. Ifyou did not have the attribute is_defaule and/or the is_ean- ccel defined, the DCL file will not load and you will be prompted with a message from AutoCAD. When you define your own buttons, you can define their widths. In this case each button fills up the space with equal widths, as shown in Figure 6.8: Corea Figure 6.8 Using User-Defined Buttons Figure 6.9 shows what the nested dialog will look like when activated from the Stats... button: Figure 6.9 Nested DCL Dialog DOL (Dialog Cntel Langue) Dialogs 391 Displaying a nested dialog is identical to the procedure for displaying a normal dia- log. The ads_toad_dialog(), ads_new_dialog(, ads_start dialog), and ‘ads_unload_dialog( functions are al called from within a callback function, SAMPLE Al ‘ATION CH6_4 The only difference between this application and the previous application is the addition of an extra callback function. Therefore, I'm going to list only the callback function, the function prototype, and the ads_action_tile() call that sets up the association between the Stats... button and the nested dialog. Here is the function prototype: static void CALLB dlgStatsBtnCB(ads_callback_packet# *epkt); Here is the ads_action_tile() call within the user-defined windoQ function just prior to the call to the ads_start_dialog() for the main dialog, // The action tile to launch the nested dialog ads_action_tile(dlgHdl, “btn_stats”, digStatsBtncB); ‘And finally, her isthe listing of the function dlgStatsBenCBQ: // Callback function to load the nested dialog static void CALLB digStatsBtnCB(ads_callback_packet® *epkt) i char sub_dciName[20] = {“stats"}; char sub_dclFile{TIL€_STRLIMIT] = (“ch6_4.dc1"); char str_list(100]; char strWinType(201; int subdclid, subDbStat: ads_hdlg subHd1; int re; rc = ads_load_dialog(sub_dclFile, &subdclId); if(re t= RTNORM) ( acutPrintf(“Ss %s", “\nError load OCL file”.& sub_dcl Fite); return; ) re = ads_new_dialog(sub_dcIName, subdclId.@ NULLCB, &subHdT if(re != RTNORM) ( acutPrintf("\nError displaying dialog”): return; ) if(stromp(g_strWinType, “rb_rect) == 0) " serepy(strintype. Rect"); tise if(stromp(g_strkinType, “rb_arch”) == 0) ‘ strepy(strWinType, “Arch”): tise if(stremp(g_strWinType, “rb_apex”) == 0) ‘ strepy(strWinType, “Apex”); tise { ; strepy(strWinType, “—" I/ Initialize the sub dialog here sprintf(str_list, “Zs\tti\t@i\te.31f\ts.31f". strWinType, g_Rows, g_Cols, gwinlt,@ g_winWidth) ; // Build up the list ads_start_list(subHdi, “Ib_stats”, LISTLNEW, 0); ads_add_list(str_list); ads_end_listQ; re = ads_start_dialog(subHd1, &subDbStat); if(re != RTNORM) { acutPrintf("\nFailed to initialize the dialoge box."); return; ) ads_unload_dialog(subdcl Id); ) DCL (Dialog Control Language) Dialogs 393 $$ ‘Pay attention to how we build up the tabbed string using the sprintf function and how we add the string to the list_box using the combination of ads_start_list(), ‘ads_add_listQ), and ads_end list(). If you need to add more than one string to the listbox, you can make repeated calls to ads_add_list() prior to calling ads_end_list). ‘There are notes in the documentation on how to handle list_box tiles. ‘Wal, that brings us to the end of this chapter. In the next chapter we will look at how to use MFC-style dialogs (Microsoft Foundation Classes) in ObjectARK and some ‘other MFC issues that are associated with ObjectARK. MFC Dialogs and ObjectARX’s UI Extensions In this chapter we will explore how to use MFC (Microsoft Foundation Classes) style dialogs within ObjectARX as well as ObjectARX’s UI Extensions. This chapter is not a lesson on MEC; its primary purpose is to show you how to use MFC within ‘ObjectARX. This chapter assumes you know how to use AppWizard and Class Wizard and that you know how to use ClassWizard to add member variables, DDX, DDV, and message mapping functions to your applications. (Ifall of the pre- viously mentioned terms are foreign to you, perhaps you need to review MFC.) ‘We will look at resources first, and then at modal and modeless dialogs, followed by a look at property pages and wizards. Then we will take a look at some of the com- ‘mon controls and user interfaces offered by MFC and discuss how they are used in ObjectARX. We will also discuss resource-only DLLs. All of the sample applications for this chapter were built with the ObjectARX 2000 AppWizard. I suggest you read the documentation that comes with the ObjectARX 2000 AppWizard to become familiar with this environment. First let's quickly review the project settings that are required for MFC and ObjectARX. PROJECT SETTINGS ‘HE you want to make your life easy with regard to creating ObjectARX 2000 appli- ‘ations that use MFC, I strongly suggest you use the ObjectARX ClassWizard that comes with ObjectARX 2000. The ObjectARX ClassWizard is located in the ObjectARX 2000\uti Ob/ARXWiz folder. However, I suggest you download the lat- est version from the ADN web site. It is located at: ‘Here you will find a link “ObjectARX 2000 Wizards”. 396 lease note that this application is constantly being updated, so be sure to check back at the ADN web site from time to time. Refer to the documentation that comes with the ObjectARX ClassWizard for information on how to use/install the wizard. Believe me, this is by far the easiest way to create Object ARX applications, using MFC or otherwise. When you select the MFC check box on the wizard, leave all the defaults set. The wizard will create a set of starter files for you; it can also be used in. ‘your application just like Class Wizard can be used in MFC applications. In your Visual C++ applications project settings, add the ObjectARX 2000\Inc direc~ tory for the various ObjectARK header files that ObjectARX uses. From the Tools menu in Visual C+4, select the Options... menu item. When presented with the Options dialog, select the Directories tab. Make sure that that Include files is select ed in the Show Directories for: drop-down list. Add the path to your ObjectARX 2000\nc directory as Figure 7.1 shows: G\Program Fies\DevStudio\VCMNCLUDE Program Fles\DevStudo\VC\MFC\include G.\Program Files\DevStucb\VC\ATL\nclude Figure 7.1 Visual C¥+ Settings for ObjecARX Library Files ‘We need to repeat the process outlined above except that this time, make sure the Show Directories for: drop-down lis is selected at the Library files option. Add the path ‘to your OdjettARX 2000\Lib directory as Figure 7.2 shows: ‘MBC Dialogs and Object ARK UI Extensions 397 G:\Progam Fies\DevStudo\VC\UB G.AProgiam Fies\DevStudo\VC\MFCMS INDevStudo\VEALIB INDevStudio\ VEEL Figure 7.2 Visual C+ Settings for ObjectARX Include Files AAlll of the sample applications for this chapter were created with the ObjectARX ClassWizard in combination with MFC ClassWizard. Let's move on and talk about Windows resources, AutoCAD, ObjectARX, and MFC. RESOURCES In Windows application development, you will be hard pressed to find an application that does not use resources. Applications that employ graphical user interface elements, be they dialogs, controls, or menu items, refer to these elements by means of resources, ‘The resource file contains IDs for all the user interface elements. When writing standalone Windows applications, you usually don't have to worry about resources. However, ObjectARX applications are not standalone Window applications—they are “in-process” service DLLs. They do, however, have an ARK file extension as opposed toa DLL file extension. These ARX applications, in conjunction with MFC and graphical user interface elements they employ, use resources. These resources are not the same as the resources found in AutoCAD; they may be of the same type but have different resource IDs. The same holds true for any other type of DLL you may write, be it a resource-only DLL or an Extension DLL. This applies to all kinds of ‘Windows programming, not just AutoCAD and ObjectARK. So by now, you should be getting the picture that resources are important. Even more important is the management of those resources. In ObjectARX 2000, resource management is handled much better than in ObjectARX 2.02 for AutoCAD R14. However, in ObjectARX 2.02, resource management was not that difficult. ‘ObjectARX 2000 provides a class CAcModuleResourceOverride that handles resouice management for you, a8 we will se later in our applications. In ObjectARX 2.02 and MFC, there are three ways to handle resources. The first method is to use a class called CTemporaryResourceOverride (see Object ARK 2.02 documentation). The second method is to allow AutoCAD to search through the resource chain until it finds your resource ID. What happens if it finds a resource ID with the same resource ID as the one located in your application? Well, as the story goes: “first come first served.” Finally, the third method is to explicitly change resource handles to that of your ObjectARX 2.02 application. When you are finished with the user interface elements that are required by your application, you explicitly switch back to AutoCAD’s resources. In ObjectARX 2.02, you are responsible for the switching; ‘AutoCAD will not magically switch you back. The reason I mention resource han- dling in ObjectARX 2.02/AutoCAD Ri4 is that we will see the use of explicit resource handling in some aspects of ObjectARX/AutoCAD 2000 (but not nearly as Our first application demonstrates the use of a modal dialog. The modal dialog that this application uses will be used in later sample applications. It will be that same win- dow drawing application that the last chapter used except that, instead of using DCL dialogs, we are now going to demonstrate MFC dialogs. In ObjectARX 2000 there are a number of UI/MEC Extension classes. In our sample applications we use pure MFC and ObjectARX’s UI/MFC Extension classes, Broadly peaking, for UV/MEC Extensions there are two major classes, namely CAdUIXXX and CACUIXXX. CAcUI classes are derived from CAdUi and tend to be specific to AutoCAD. CAdUi can be used by any application that interacts with any Autodesk product. You can use the CAdUi controls in applications that are AutoCAD-specif- icor in standalone applications that in some manner interact with Autodesk products. Here I will show you how to use the CAcUW/CAdUI controls in your application; you ‘ean refer to the documentation for a complete discussion of the classes and methods offered. In the last application we talked about the Single Document Interface (SDI) and port- ing considerations. Throughout this chapter we will discuss global data in detail and how to handle it All of the applications for this chapter are MDI (Multiple Document Interface) aware. All of the applications are well behaved, law-abiding MDI-aware citizens. (MPC Dialogs and ObjetARX's UL Extensions 399 eee ion MODAL DIALOGS AND SAMPLE APPLICATION CH7_1 Using the resource editor, create a dialog IDD_WINDOTYPE, as shown in Figure 7.3: Figure 7.3. Modal Dialog ‘Table 7.1 shows the resource IDs for the appropriate controls in the dialog. All other controls in the dialog are IDC_STATIC. The OK and Cancel buttons are standard buttons. The reason that the frst radio button has a ‘Group’ style set is to indicate where the grouping mechanism starts. The Group box also has the ‘Group’ bit se; this indicates the end of the group mechanism. Now Class Wizard knows how to manage the three radio buttons as a group. Figure 7.4 shows the tab order for the dialog. and Type Resource ID Resource Settings ‘Dialog “Window IDD_WINDOTYPE | Style « Popup’, Border = Parameters” ‘Dialog Frame’, Title Bar, System menu IDC_RB_TYPERECT _ | Visible, Group, Tab Stop and Auto. Caption = ‘Rect? Radio button ~ “Arch? IDC_RB_TYPEARCH | Visible, Tab Stop and Radio button ~"apeX” ‘IDC_RB_TYPEAPEX Group Box-“Window Type” IDC_STATIC Edit box - “Height” IDC_EDIT HEIGHT | Visible, Tab Stop, Auto HScroll, Border. Edit box— “Width” IDC_EDIT_WIDTH | Visible, Tb Stop, Auto HScroll, Border. Editbox “Columns” IDC_EDIT_COLS Visible, Tab Stop, Auto Scroll, Border. Edit box “Rows” IDC_EDIT_ROWS | Visible, Tab Stop, Auto HScroll, Border. ‘All other elements IDC_STATIC Default properties, see illustration. Table 7.1, Resource IDs forthe Diolog Contos of Fgure 73 MFC Dialogs and ObjetARX'$ UL Extensions 401 Figure 7.4 Modal Dialog Tab Order ‘When you have finished creating the dialog, use the ObjectARX 2000 AppWizard to create « CWindoTypeDlg class based or the CAcUIDialog class. For this you will select the ObjectARK MFC Support toolbar button of the ObjectARX 2000 AppWizard. As I suggested eatlies, you should read the ObjectARK Class Wizard doc- ‘umentation for a description of how this is done. You will also use ClassWizard to hook up the controls to the various member variables. Let's take a look at the implementation of the Cs7_1.CPP file, which was generat- ed by the ObjectARX 2000 AppWizard: // Ch7_l.cpp : Initialization functions #include “StdAfx.h” #include “StdArx.h” #include “resource.h” include HINSTANCE _hd1l Instance =NULL ; // This command registers an ARX command. void AddCommand(const char* cmdGroup, const chart# emdint, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtry cmdProc, const int idlocal = -1); // NOTE: DO NOT edit te following lines. T/({APX_ARX_MSG void InitApplication(); void UnloadApplication( 7) AFX_ARX_MSG // NOTE: DO NOT edit the following lines. 7 ({AFX_ARX_ADDIN_FUNCS 7) AFX_ARX_ADDINFUNCS MALT TLL LLL TLL, MALL u" // Define the sole extension module object. AC_IMPLEMENT_EXTENSION_MODULE(Ch7_1DLL); 11 Now you can use the CAcModuleResourceverrided class in // your application to switch to the correct? resource instance. 1/ Please see the ObjectARX Documentation for mored details TITTLE TTT 77 DLL Entry Point extern “C” BOOL WINAPI D11Main(HINSTANCE hinstance, DWORD® dwReason, LPVOID /*1pReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) ( // Extension DLL one time initialization Ch7_1DLL.AttachInstance(hinstance); InitacUiDLLO: ) else if (dwReason == DLL_PROCESS_DETACH) ( // Terminate the library before destructors® are called Ch7_1DLL.DetachInstance(); ) return TRUE; // 0« ) ITLL TTT IATL TLL MFC Dialogs and Object ARK: UI Extensions 403 —$—____~__ 11 ObjectARX EntryPoint extern “C” AcRx: :AppRetCode acrxEntryPoint(AcRx: :ApaMsgCode msg, void* pkt) ( switch (msg)_{ case AcRx::kIni tAppMsg: // Comment out the following line if your /1 application should be locked into memory acrxDynamicLinker->unlockAppl ication(pkt) s acrxDynamicLinker->-egisterAppMOIAware(pkt) ; chd1lInstance =::GetModuleHandle (“Ch7_1.arx”) Initappl ication( brea case ACRx: :kUn]oadAppitsg: UnloadApplication(); breaks } return AcRx::kRetOK: // Init this application. Register your // commands, reactors... void InitApplication() ( JJ NOTE: DO NOT edit the following lines. 7 AAPX_ARX_INIT AddCommand(*CH7_APPS", “WINDO", “WINDO”, ACRX_CMD_MODAL, windo); T/)YAFX_ARX_INIT // 7000: add your initialization functions acutPrintf(“\nType \*WINDO\” to execute”); 1 // Unload this application. Unregister all objects // registered in InitAppl ication. void UnloadApplication() { // NOTE: DO NOT edit the following lines. J HUCAPX_ARX_EXIT acedRegCmds->removeGroup(“CH7_APPS"); JA) YAPX_ARK_EXIT // TODO: clean up your application ) // This function registers an ARX command. // It can be used to read the localized command name // from a string table stored in the resources. void AddCommand(const char* cmdGroup, const char«o cmdInt, const char* cm¢Loc, const int cmdFlags, const AcRxFunctionPtre cmdProc, const int idiccal) ( char cmdLocRes{65]; // If idlocal is not -1, it’s treated as an 10 for // a string stored in the resources. if CidLocal != -1) { HMODULE hModule = GetModuleHandle(“Ch7_1.arx"); 17 Load strings from the string table and& register the command. riloadString(hModule, idLocal, cmdLocRes, 64); acedRegCmds->addConmand(cmdGroup, cmdInt, & cmdLocRes, cmdFlags, cndProc); ) else 11 idlocal is -1, so the ‘hard coded’ 17 Vocalized function name is used. acedRegCnds->addConmand(cmdGroup, cmdInt.& cmdloc, cmdFlags, cmdProc); ) The first item that I want you to note is the following: AC_IMPLEMENT_EXTENSION_MODULE(Ch7_1DLL); All ObjectARX applications for this chapter are Extension DLLs. Inside DilMain you will see a call to InitaciDLLO: ‘This initializes the AcUi DLL so that we can use those controls in our applications. ‘We have seen how to register commands in earlier applications and this is done in InitApplication. (MPC Dialogs and Objet ARXs UI Extensions — 405 ———__~_ ‘The rest of the items in this file are generated by the ObjectARX 2000 AppWizard, so let’s move on to our user-defined command windoQ. By the way, I should men- tion here that the ObjectARX 2000 AppWizard also has a command CMD toolbar button that will enable you add as many commands as you like to your application, ‘The commands for this application are in the Ch7_1Commands.CPP file as follows: TELLTALE // ObjectARX defined commands itinclude “StdAfx.h” #include “Stdarx.h” #include “WindoTypeDlg.h” // This is command *WINDO* void windot) { 71 TODO: Implement the command // When resource from this ARX app is needed,& just // instantiate a local CAcModuleResourcedverride CAcModuleResourceOverride resOverride; CWindoTypeDl ge dig(Chnd: : FromHand1e(adsw_acadMainwind())); if(d1g.DoModal() == 100k) ( // Do something useful here acutPrintf(“\nWindow type = 45". windInfo.windType) ; acutPrintf(“\nWindow height = %.21f",¢ windInfo.m_dWindHt) acutPrintf(“\nWindow width = %.21f",3 windInfo.m_dWindWt) acutPrintf(“\nlindow cols = %d",o windInfo.m_nCols); acutPrintf(“\nWindow rows = 2d", windInfo.m_nRows); } ) The most important aspect in using MFC elements in your application is resource ‘management, as mentioned earlier. This is atte easier in AutoCAD 2000 than it was for AutoCAD R14 applications. In Object ARX 2000, simply include the following in your application before you display your dialog box: CAcModuleResourceOverride resOverride; Creating an instance of CAcModuleResourceOverride, its constructor will auto- ‘matically swap resources for you. When this function (winde) goes out of scope, the destructor will automatically switch back to AutoCAD's resources—that’ all there is to it. Ifyou leave out the CAcModuleResourceOverride, you may get some funny looking labels in your dialog, because chances are, you are using AutoCAD resource IDs. The call to dig. DoModald will display our dialog, so let’s take a look at the ObjectARK 2000 AppWizard created CWindoTypeDig header fle WindoTypeDig.h: 1/—____________—_.- #if !defined(ARX_WINDOTYPEDLG_H_19990606_105025) fidefine ARX_WINDOTYPEDLG_H_19990606_105025 Le #if _MSC_VER > 1000 pragma once dendif // _MSC_VER > 1000 0@™ #include “resource.h” class CWindoTypeDIg : public CAcUiDialog { DECLARE_DYNAMIC (CWindoTypeD1g) public: CWindoTypeDlg (CWnd* pParent =NULL, HINSTANCE® hinstance =NULL) ; //(AFX_DATA(CHindoTypeD1g) enum ( 10D = IDD_WINDOTYPE }; CAcUiNumericEdit m_ctrlEdHei ght; CAcUiNumericEdit m_ctrlEdWidth; int munWindType; int mnCols: int _monRows: J) )AFX_DATA CString mstrHeight; CString mstrWidth: ‘MFC Dialogs and Objet RX UI Extensions 497 ———_______. // (CAFX_VIRTUAL(CHindoTypeD19) protected: virtual void DoDataéxchange(CDataxchange* pDX):¢ 11 DDX/DDV_ support JI) YAFX_VIRTUAL protected: 11 ((AFX_MSG(CHindoTypeD19) afx_msg LONG OnAcadKeepFocus(UINT, UINT): virtual BOOL OnInitDialog(); afx.msg void Onkil]focusEdi thei ght(); afx_msg void Onkil]focusEdi twidth(); virtual void On0K(): //))AEXMSG DECLARE_MESSAGC_MAP() a 11 ((AFX_INSERT_LOCATION} } 1/ Microsoft Visual CH will insert additionalg declarations immediately before the previous line. fendif //---- Idefined(ARX__WINDOTYPEDLG_H_19990606_105025) Notice that the class that CWindoTypeDI is derived from is CAcUiDialog, which, by the way, is derived from CAdUiDialog and from CDialog. Examining the head er file above, we have some edit controls that are CAeUiNumericEdlt, which are uli~ mately detived from CEdit controls. These controls were added through Visual C++’s MFC ClassWizard Member Variables tab. When associating a control with a ‘member variable, in the Category: drop-down list box you will see the following types: Value, Control, Autodesk Control, AutoCAD Controt—MFC ClassWizard is very smart! All of the Messages Map items are standard MIFC messages with the sin- gle exception of OnAcadKeepFocus, which we will discuss later because it’s more appropriate to modeless dialogs. Let’s take a look now at the implementation file Wind TypeDig.pp: WindoTypeDlg.cpp : implementation file finclude “StdAfx.h” finclude “StdArx.h” finclude “resource.h” finclude “WindoTypeDIg.1” LL dHifdef _DEBUG fidefine new DEBUG_NEW fundef THIS_FILE static char THIS_FILEC] = _FILE_; Hendif UL IMPLEMENT_DYNAMIC (CWindoTypeDIg, CAcUiDialog) BEGIN_MESSAGE_MAP(CWindoTypeD1g, CAcUiDialog) 71 (TAFX_MSG_MAP(CWi ndoTypeD1a) ON_MESSAGE(WM_ACAD_KEEPFOCUS, OnAcadkeepFocus)¢ /7 Needed for modeless dialog. ON_EN_KILLFOCUS( IDC_EDIT_HEIGHT, Onki11 focusEditHei ght) ON_EN_KILLFOCUS( IDC_EDIT_WIDTH, Onki11 FocusEditwidth) 7) YAFX_MSG_MAP END_MESSAGE_MAP() i WindoTypeD1g (Cnd* pParent P*=NULL*/, HINSTANCE hinstance /*=NULL*/) : CAcUiDialog (CWindoTypeDlg::I0D, pParent, hinstance) ( //((AFX_DATA_INIT(CWindoTypeD1g) mnWindType = -1; mnCols = windInfo.mnCols; mnRows = windInfo.mnRows; /7)VAFX_DATA_INIT ) void CindoTypeD1g: :DoDataExchangee (CDataéxchange *pDX) { CAcUi Dialog: :DoDataExchange (pOX) ; 7/{(AFX_DATA_MAP (CHindoType01 9) DDX_Control(pOX, IDC_EDIT_HEIGHT, m_ctrlEdHeight); DDX_Control(pDX, IDC_EDIT_WIDTH, mctrlEdWidth); DDX_Radio(pDX, TDC_RB_TYPERECT, m_nWindType); DDX_Text(pDX, IDC_EDIT_COLS, m/nCols): DDV_MinMaxInt(pOX, mnCols, 1, 10); DDX_Text(pDX, IDC_EDIT_ROWS, mnRows); DDV_MinMaxint (pDX, mnRows, 1, 10); MPC Dialogs and ObjetARX's UI Extensions 409 $1 1) AFX_DATA_MAP ) // Needed for modeless dialogs to keep focus. // Return FALSE to not keep the focus, return TRUE® to keep the focus LONG CWindoTypeD1g::OnAcadKeepFocus(UINT, UINT) { ) us return TRUE: BOOL CWindoTypeDlg: :OnInitDialog() i CAcUIDialog: :OnInitDialog(); /1 TODO: Add extra initialization here CButton *pWindoType; CString strWindType(windInfo.windType) ; if(strWindType == “Rect”) t pWindoType = (CButton *)g GetD1 gl tem( TDC_RB_TYPERECT) pWindoType->SetCheck(1): ) else if(strWindType == “Arch") ( pWindoType = (CButton *) GetD1gItem(T0C_RB_TYPEARCH) ; pWindoType->SetCheck(1): ) else // It must be an “Apex” type of window ( pWindoType = (CButton *)¢ GetD1 gl tem(IDC_RB_TYPEAFEX) ; pWindoType->SetCheck(1); MctrlEdHeight.SetRange(10.00, 120.00); mctrlEdWidth.SetRange(10.00, 120.00); 410 acdbRToS(windinfo.m_dWindHt, 2, 3.8 m_strHei ght. GetBuffer(80)); m_ctrlEdiei ght. SetWindowText(m_strHei ght); acdbRToS(windinfo.m_dWindWt, 2, 3,0 m_strWidth.GetBuffer(80)): mctrlEdWidth. SetWindowText(m_strWidth); return TRUE; // return TRUE unless you set thee focus to a control // EXCEPTION: OCX Property Pages® should return FALSE } void CWindoTypeDlg: :Onki11focusEdi tHei ght() { // T0D0: Add your control notification handlerd code here m_ctrl€dHeight.Convert(); if(1m_ctriEdHeight.Validate()) ( fxMessageBox("Sorry, Range = 10.00 tog 120.00"); m_ctrlEdHeight.SetFocus(); mictriEdHeight.SetSel(0, -1); ) ) void CWindoTypeD1g: :OnKi11focusEditwidth() ( // TODO: Add your control notification handlerg code here m_ctrlEdWidth.Convert(); if(1mctrlEdWidth.Validate()) t rrAfxMessageBox("Sorry, Range = 10.00 to# 120.00"): mctrlEdWidth.SetFocus(); mctrlEdWidth.SetSel(0, -1); ) ) void CWindoTypeD1g: :On0K() (MPC Dialogs and ObjetARX's UI Extentions 41) —_—$ J/ TODO: Add extra validation here if (1UpdateData(TRUE)) i return; ) mctrlEdHei ght. GetWindowText(m_strHei ght): mctrlEdWidth.GetWindowText(m_strWidth); windInfo.m_dWindWt = atof(m_strWidth windInfo.m_dWindHt = atof(m_strHei ght) windInfo.mnCols = mnCols; windInfo.m_nRows = m_nRows; + switch (mnWindType) ( case 0: strepy(windInfo.windType, “Rect”); break; case 1: strepy(windInfo.windType, “Arch"); break: case 2: strepy(windinfo.windType, “Apex”); break; ) CAcUIDiatog: :0n0K(); ) In the constructor there is a reference to windinfo, which suggests that this is a structure of some type. One important item that we have not yet discussed is glob- al data, Tim going to be brief here, because I wll discuss global data in much more detail Jater. Global data was easy to manage in AutoCAD R14 because in that environment you could only have a single drawing file open at a time. However, as you now know, in AutoCAD 2000 you can have multiple drawing files open. What we want to achieve is global data on a per drawing (per document—more later) basis and this structure (for want of a better term for right now) helps us achieve this. The values in this constructor were added by hand and not through the MFC ClassWizard. The DoDataExchange is pretty straightforward in that it was handled by the MFC ‘Class Wizard. In OntnitDialog( look at the following lines of code: an mctrl£dHeight.SetRange(10.00, 120.00); mctrl£dWidth.SetRange(10.00, 120.00); ‘This SetRange() is part of the CAcUiEdit control functionality. CAcUiNumericEdit is a special numeric edit control (as the name suggests). Look at one of the ‘OnKillFocus message map functions. Here we have two more useful Autodesk edit control functions, namely Convert() and Validate(); everything else is MFC. What Twant to encourage you to do is investigate what functionality you get by using the ‘Autodesk controls. The other controls in this application are handled by MIC, so what ‘we end up with is a combination of Autodesk technology and standard MFC usage. I did not go overboard on this application and error check all the other controls. ‘There are two additional classes that I did not talk about, namely AsdkDataManager and CDoeData. Both of these classes were created by the ObjectARX 2000 AppWizard. The purpose of these classes is to help you manage global data on a per document (per drawing) basis. As an expeciment try the following: |. Create two new drawings in AutoCAD 2000. 2. Execute the Windo command from one of the active drawings. Enter some values in the dialog and select the OK button. 3. From the second drawing, execute the Windo command again. This time enter some values different from the ones entered previously for the first drawing. Select the OK button. 4. Now make the first drawing the active drawing and execute the Windo com- ‘mand. Did you notice anything? The frst drawing remembered the values you had used earlier 5. Now make the second drawing the active drawing and execute the Windo ‘command, Surprise, surprise! The second drawing also remembers the values you had put in previously. ‘That is what I mean by global data on a per document (per drawing) basis. How is this achieved? For the answer to this question, let's first look at the class definition for ‘CDocData and then at the constructor for the CDocData class. Here is the listing for the header file DocData.t: J/ docdata.h : include file for document specific data " an instance of this class is automaticallyg created ul and managed by the AsdkDataManager class W see the AsdkOmgr.h DocData.cpp for mored detail we (MPC Dialogs and ObjectARX's UI Extensions 413 —$—$_~___ !defined(AFX_DOCDATA_H_68C98B7F_1BEE_11D3_A7A7_¢ 000000000000__INcLUDED_) define AFX_DOCDATA_H__68C9887F_1BEE_11D3_A7A7_o000000000008 —INCLUDED__ dif MSC_VER > 1000 ‘#pragma once endif // MSC_VER > 1000 MATT TLL uw // Here you can store the document / database // related data. Ww class CDocData { public: CDocData(): ~CDocData(); JJ NOTE: 00 NOT edit the following lines. 1 LAFX_ARX_DATA(CDocJata) 1) APX_ARX_DATA // TODO: here you can add your variables uv which depends on a document / database. char_windTypet5]; double m_dWindt; double m_dWindwt int mnRows: int mnCols: h endif Here you can see thatthe data for our application is contained inside a class. The con- structor for this class initializes the data as follows in the listing for DacDatacpp: finclude “StdAfx.h” finclude “StdArx.h” // The one and only document manager object // You can use the DocVars object to retrieve a4 J] document specific data throughout youre appl ication AsdkDataManager DocVars; uy // Implementation of the document data class. uW CDocData: :CDocDatat) fl // NOTE: DO NOT edit the following lines. //((AFX_ARX_DATA_INIT(CDocData) 7) )AFX_ARX_DATA_INIT // T000: add your own initialization. strepy(windType, “Rect”): mdWindHt = 10-00; m_dWindWt = 10.00; manRows = 1; manCols = 1; ) CDocData: :~CDocData() { J/ NOTE: DO NOT edit the following lines. H/((AFX_ARX_DATA_DEL(CDocData) J) )AFX_ARX_DATA_DEL. 71 T0D0: clean up. ) (Olay, so far so good. But where is the windlnfo variable? Before we take a look at that, notice the following from the previous code, a global instance of AsdkDataManager in DoeVars: AsdkDataManager DocVars; ‘AsdkDataManager manages CDocData classes. Now let's take a look at the Stddrx.) file (not to be confused with the Std4fs.b fle that MFC creates): uN 11 StdArx.h : include file for ObjectARX/DBXe include files // this file is only included once by your stdafx.h (MPC Dialogs and ObjetARXs UI Extensions 15 $$ Hite def ined(AFX_STOARX_H__68C98B78_18£E_1103_A7A7_¢ 000000000000__INCLUDED_) define AFX_STOARX_H__68C98B78_1BEE_1103_A7A7_0000000000008 —INCLUDED_, #if MSC_VER > 1000 pragma once #endif // _MSC_VER > 1000 1 ((AFX_ARX_INC_SELECTED 1/)) AFX_ARX_INC_SELECTED 7 LAFX_ARX_INC_OTHERS 17) )AFX_ARX_INC_OTHERS. include “AdskOMgr.h” // Utility classe for document data #include “docdata.h” “1 Your documents specific data // Declare it as an extern here so that it // becomes available in all modules extern AsdkDataManager DocVars; define windInfo DocVars.docData() T/A APX_ARX_FUNC 1) JAFX_ARX_FUNC void windo(); J/ T0D0: Here you can add your own includes /¢ declarations endif Here is where you will find windInfo, which is a #define. So everywhere you see a windInfo as follows, windInfo.mnCols = mncols; it becomes at compile time: DocVars.docData().m_nCols = m_nCols: 416 ‘This brings up the next question: what is AsdkDataManager? First, I don't want to get into it too deeply, because i's not necessary for us to understand all the implica tions ofthis. Suffice it to say it manages a singe instance of CDoeData for every draw- ing file that is open. When you switch from one drawing file to another, it knows which instance of CDoeData belongs to the active document (drawing). A suggest cd practice is that all global data be placed in these CDocData classes and that ‘AsdkDataManager manage that data for every drawing. We will be visiting global data and the Multiple Document Environment (MDE) in more detail in a later chapter. If you are familiar with MFC, Multiple Document Interface (MDI) is more famil- iar to you. Welcome to the marketing name of MDI, namely MDE, as far as AutoCAD 2000 is concerned. MODAL DIALOGS AND SAMPLE APPLICATION CH7_2 Ifyou followed along with the last chapter, which dealt with DCL style dialogs, the CB7_2.arx application is the MFC equivalent of the Cb6_4.arx application of the last chapter. Ifyou recall, the DCL application demonstrated two techniques. The first technique showed how to hide the dialog to allow the user to select a point with acedGetPoint(). You may recall that in order for a point to be selected on the screen with a DCL dialog you had to hide the dialog, allow the point to be selected, and then bring the dialog back to life. You could not select a point while the dialog was visi- ble. This application also displayed the technique of displaying a nested dialog. The (Ch7_2.ars application is the MFC equivalent. In this application we display a modal dialog box. The modal dialog box has a < Pick button that lets you hide the dialog facil- itating the use of acedGetPoint() after which the dialog comes back to life. There is also a Stats... button, which launches a nested dialog that displays various statistics about the window parameters the user selected. ‘Wait a minute, hold the phone! What do you mean by “hide” a modal dialog? Well, for those of you who asked that question, go straight to the head of the class, and for those of you who still think it’s Monday morning, time to wake up! The very nature of a modal dialog is just that: modal, meaning you cant do anything while it's still alive. ‘You certainly cant select a point on the AutoCAD screen. Well, as it turns out, ‘modal dialogs aren't quite so modal afterall (reminds me of “the truth is out there” and “don't believe everything you hea”). It turns out that some of my colleagues here showed me some tricks with the various bits and bobs of MFC that will allow you to hide a modal dialog. Figure 7.5 shows the main dialog for this application: Well, as you can see, there is nothing very different here. It looks very similar to its DCL counterpart. Figure 7.6 shows what the nested dialog looks like: MPC Dialogs and ObjatARXs UI Extensions 417 $$ Figure 7.5 Hiding Modal Dialogs Figure 7.6 Nested Modal Dialog ‘Now this looks a litte different from the DCL equivalent. This dialog contains one ‘of Windows’ new common controls, namely a CListCtrl shown in a ‘report’ view style. Yes folks, welcome to MFC. In Windows we have buckets of controls that DCL just doesn't have. 418 First, Table 7.2 shows the settings for the class CWindoTypeDlg: Resource Name and Type Rescue ID Resource Settings Dialog-"Window IDD_WINDOTYPE | Style = Popup, Border = Parameters” ‘Dialog Frame’, Tile Ba, System menu Radio button—"Rec®” | IDC_RBTYPERECT | Visible, Group, Tab Stop and Auto. Caption = “Rect? Radio button — “Arch” IDC_RB_TYPEARCH Visible, Tab Stop and Auto. Caption ='8Archt Radio button—“apeX” | IDC_RB_TYPEAPEX | Visible, Tab Stop and Auto, Caption =ape8iX” Group Box—“Window | IDC_STATIC Visible, Group Tet Caption =" Window Type’ Edit box "Height™ IDC EDIT HEIGHT — | Visible, Tab Stop, Auto Scroll, Border Edit box — “Width” IDC_EDIT WIDTH | Visible, Tab Stop, Auto HScroll, Border. Edit box —“Colurns™ 1DC_EDIT_COLS Visible, Tab Stop, Auto HScroll, Border. Edit box— “Rows? IDC_EDIT_ROWS | Visible, Tab Stop, Auto HScroll, Border. Push button=" 1000 pragma once d#endif // _MSC_VER > 1000 «a #include “Resource.h” class CWindoTypeDlg : public CAcUiDialog { DECLARE_DYNAMIC (CWindoTypeD1g) public: void updateGlobalDocbata(); CWindoTypeD1g (CWnd* pParent =NULL, HINSTANCES hinstance =NULL) ; MBC Dialogs and ObjetARX's UI Extensions 424 $< 1/ {AFX_DATA(CHindoTypeD1g) enum { IDD = IDD_WINCOTYPE }; CAcUiPickButton mctrIBtnPickPt; int munCols; int mnRows; CAcUiNumericEdit m_ctriEdHei ght; CAcUiNumericEdit m_ctr1EdWidth; CAcUiNumericEdit m_ctriEdXval; CAcUiNumericEdit m_ctr1EdYVal; int mnWindTypes IV )AFX_DATA CString mstrHeight; CString mstrWidth; CString mstrXxVa CString mstrYVal; 1 (AFX_VIRTUAL(CHindoTypeD1g) protected: virtual void DoDataExchange(CDataéxchange* pOX); — // DDX/DDV support 11) )AFX_VIRTUAL protected: 7 LAFX_MSG(CHindoTypeD19) afxmsg LONG OnAcadKeepFocus(UINT, UINT); virtual BOOL OnInitDialog(); afx_msg void Onkill fccusEdi thei ght( afxmsg void Onkill fecusEdi tWidth(); virtual void On0k(); afx.msg void OnBtnPickPt(); afx_msg void OnBtnStats(); 11) YAFX_MSG DECLARE_MESSAGE_MAP() ng 7 (AFX_INSERT_LOCATION) } // Microsoft Visual C++ will insert additional declarations immediately before the previous line. fendif // !defined(ARX__WINDOTYPEDLG_H__19990606_204349) m All the member variables were created with the help of MFC ClassWizard and ObjectARX 2000 AppWizard. As discussed before, we will see how DoDataExchange() interacts with the variables when we get to the implementation file for this class. The user-defined functions are OnBtnStats), OnBtnPickPt(, and exactly as before, the message map functions OnKillfocusEditHelght(), OnKilfocusEditWidth0), and OnAcadKeepFocus(). Look at the member control m_etriBtnPickPt: its a CAcUIPickButton, 2 specialized AutoCAD owner-draw point-picking button that is used in various locations throughout AutoCAD 2000. CAcUIPickPointButton is derived from CAdUIOwnerDrawButton, which in turn is derived from CButton. Here is the listing for the implementation file for this class, WindoTypeDigcppt WindoTypeD1g.cpp : implementation file #include “StdAfx.h” #include “StdArx.h" #include “resource. h” #include “WindoTypeD!g.h” #include “WindoStatD1g.h” ffinclude “geassign.h” He ifdef DEBUG fdefine new DEBUG_NEW fundef THIS_FILE static char THIS_FILEC] = _FILE_: fendi f a$$ _—_————_- IMPLEMENT_OYNAMIC (CWindoTypeDlg, CAcUiDialog) BEGIN_MESSAGE_MAP(CWindoTypeD1g, CAcUiDialog) T/((AFX_MSG_MAP(CHindoTypeD1g) ON_MESSAGE(WM_ACAD_KEEPFOCUS, OnAcadKeepFocus) // Needed for modeless dialog. ON_EN_KILLFOCUS(1DC_€DIT_HEIGHT, & Onki11focusEdi tei ght) ON_EN_KILLFOCUS(1DC_€DIT_WIDTH, & Onki11 focusedi tWidth) ON_BN_CLICKED(IDC_BTN_PICKPT, OnBtnPickPt) ON_BN_CLICKED(IDC_BTN_STATS, OnBtnStats) J) )AFX_MSG_MAP. END_MESSAGE_MAP() MPC Dialogs and Objet ARX's UI Extensions 423 $< It a CWindoTypeD1g::CWindoTy2eD1g (CWnd* pParent /*=NULL*/, HINSTANCE hInstance /*=NULL*/) :@ CAcUiDialog (CWindoTypeDlg::10D, pParent, Instance) { //((AFX_DATA_INIT(CWindoTypeD1g) mnCols = windInfo.mnCol m_nRows = windInfo.m_nRow: mnWindType = -1; IT ))AFX_DATA_INIT ) void CWindoTypeDlg::DoDataExchange (CDataxchanged *p0X) { CAcUiDialog: :DoDataéxchange (pDX) ; 7/ (AFX_DATA_MAP(CHi ndoTypeD1 a) DDX_Control(pOX. IDC_TN_PICKPT, mctriBtnPickPt); DDX_Text(pDX, IDC_EDIT_COLS, mncois); DDV_MinMaxInt(pOX, maCols, 1, 10); DDX_Text(pDX, TDC_EDIT_ROWS, mnRows); DDV_MinMaxInt(pDX, maoRows, 1, 10); DDX_Control(pOX, 10C_EDIT_HEIGHT, m_ctr1EdHeight); DDX_Control(pOX, IDC_EDIT_WIDTH, mctrlEdWidth); DDX_Control(pOX, IDC_EDIT_XVAL, mctr1EdXVal); DDX_Control(pOx, IDC_EDIT_YVAL, mctrlEdYVal DDX_Radio(pDX, TOC_RB_TYPERECT, m_nWindType) TYYAFX_DATA_MAP } // Needed for modeless dialogs to keep focus. // Return FALSE to not keep the focus, return TRUE® to keep the focus LONG CWindoTypeDlg::OnAcadkeepFocus(UINT, UINT) t ) return TRUE: ur BOOL CWindoTypeDIg: :OnInitDialog() ( CAcUiDialog: :OnInitDialog(); // TODO: Add extra initialization here ms CString str_edboxVal; CButton *pWindoTypes CString strWindType(windInfo.windType); if(strWindType == “Rect”) ci plindoType = (CButton *)& GetD1g1 tem(TDC_RB_TYPERECT) ; pWindoType->SetCheck(1); else if(strWindType == “Arch”) { pWindoType = (CButton *)¢ GetDI gi tem(TDC_RB_TYPEARCH) ; pWindoType->SetCheck(1): } else // It must be an “Apex” type of window ( pWindoType = (CButton *)o GetD1gI tem(1DC_RB_TYPEAPEX) ; pWindoType->SetCheck(1); ) mctrlEdHeight.SetRarge(10.00, 120.00); m_ctrlEdWidth.SetRance(10.00, 120.00); acdbRToS(windinfo.m_cWindHt, 2, 3.0 str_edboxVal .GetBuffer(80)); m_ctrl€dHeight.SetWirdowText(str_edboxVal); acdbRToS(windinfo.mcWindWt, 2, 3.0 str_edboxVal .Get8uffer(80)): mctrl EdWidth.SetWincowText(str_edboxVal); acdbRToS(windInfo.mcXval, 2, 3,0 str_edboxVal .GetBuffer(80)); m_ctrlEdxVal . SetWindowText (str_edboxVal); acdbRToS(windInfo.mdYval, 2, 3.2 str_edboxVal .GetBuffer(80)); m_ctrl€dyVal.SetWindowText(str_edboxVal); m_ctr1BtnPickPt.AutoLoad(); MBC Dialogs and ObjatARX UI Extensions 405 $$ return TRUE; // return TRUE unless you set thee focus to a control Jf EXCEPTION: OCK Property Pagesd should return FALSE ) void CWindoTypeDlg: :OnKil1 focusEditHeight() t /1 T0D0: Add your control notification handlerd code here m_ctrlEdHeight.Convert(); if(1m_ctrlEdieight.Validate()) fi AfxMessageBox(“Sorry, Range = 10.00 toe 120.00"); mctrlEdHeight SetFocus(): mctrl€dieight.Setsel(0, “1): } } void CWindoTypeD1g: :Onk‘11 focusEditwidth() i // T0D0: Add your control notification handlere code here m_ctrlEdWidth.Convert(); if(1mctridWidth, Val idate()) { riAfxMessageBox(“Sorry, Range = 10.00 tod 120.00"); mctrlEdWidth.SetFocus(); mctrlEdWidth.SetSel(0, -1); ) ) void CWindoTypeD1g: :On0k() ( /1 TODO: Add extra validation here if (UpdateData (TRUE)) fl return; ) updateGlobalDocData(): CAcUiDialog: :On0K(); 28 void CWindoTypeD1g: :OnBtnPickPt() { J/ T0D0: Add your control notification handlere code here // Here we hide our nodal dialog // to allow the user to pick a point AcGePoint3d pkPt; int retCode; UpdateData (TRUE); updateGlobalDocData(): BeginEditorCommand(); // This replaces the nexté commented out lines /1CHnd* pacadWnd = Chind: : FromHand1e(adsw_acadMainWind()); /Ipkcadkind->EnableWindow(TRUE); // Enable thee parent window J/ which in our case is the 71 AutoCAD main window. //ShowWindow(SH_HIDE); // Hide our dialog //pAcadWind->SetFocus(); // Give AutoCAD the focus. acedInitGet(NULL, NULL); retCode = acedGetPoint(NULL, “\nPick lower lefte corner of window: *, asDb1Array(pkPt)); switch(retCode) { case RICAN : case RTNONE : pkPt.set(0.0, 0.0, 0.0); break; ‘MFC Dialogs and ObjectARXs UI Extensions 427 $$ —_$_$__~___ case RTNORM : break; case RTERROR : CancelEditorCommand(); // Most likely won’t® get here // 1 just wanted to let you know // Cancel€ditorCommand() exists. break; ) windInfo.m_dXval = pkPt.x windInfo.m_dyVal = pkPt.y CompleteEditorCommand(); // This replaces thee next commented out lines //ShowWindow(SW_SHOW); // Display our dialog again //SetFocus(); // Reset the focus back to ourselves //pAcadiind->EnableWindow(FALSE); // Disable AutoCAD window //EnableWindow( TRUE); // Enable our dialog OnInitDialog’ ) void CWindoTypeD1g: :OnBtnstats() { /1 TODO: Add your control notification handler? code here if (UpdateData (TRUE)) ( return; ) updateGlobalDocData(); CWindostatD1g dig; d1g.DoModa1(); void CWindoTypeDlg: : updateGlobalDocData() m_ctr]EdHei ght .GetWindowText (m_strHei ght); m_ctrlEdWidth.GetWindowText(m_strWidth) ; mctr1EdXVal .GetWindowText(m_strXxVal); m_ctrlEdYVal .GetWindowText(m_str¥Val); windInfo.m_dWindWt windInfo.m_dWindHt windInfo.m_dxVal windInfo.m_dYVal windInfo.mnCols = my windInfo.m_nRows = m_nRows; atof(mstrWidth): tof(m_strHeight) : atof(m_strXval); atof(m_strYVal); switch (mnWindType) ( case 0: strepy(windInfo.windType, “Rect”): break; case 1: . strepy(windInfo.windType, “Arch”): break: case 2: strcpy(windInfo.windType, “Apex”); break: ) } Look at the DoDataExhange() function. Here, with the help of ClassWizard, we can map the controls of the dialog edit boxes end radio buttons to the member variables of the class, complete with DDX (Dialog Data eXchange) and DDV (Dialog Data ‘Validation) mechanisms. This is followed by our “message map,” which responds to the user selecting the Stats... button and the AutoCAD-style pick point button by calling the user-defined functions OnBtnStats() and OnBenPickPt() respectively. ‘Taking a closer look at the OnBtnStats() function, we call the UpdateData() func~ tion with an argument of TRUE. This call will transfer values from the controls to the member variables. We then call the user-defined function updateGlobalDocData(), which transfers values from the member variables to the global (document-specific) variables, after which we call our second nested dialog (discussed later). That's all there is to nesting dialogs in MFC—simply call another dialog. The virtual OnOK( is iden- tical to the OnBtnStats() function in transferring data from the member variable to the global variables. Before we leave the OnOK( function, we call the base class ‘MRC Dials and Objet RX UL Extensions 929 —$___~_— CAcUIDialog:OnOK() method. So finaly, on to the big secret of hiding modal dialogs. Hiding a modal dialog in MFC is simpler than it is in DCL. In hiding a dialog you make the dialog invsible—the dialog is stil alive. Focus is passed back to AutoCAD, the point is selected, and the dialog is made visible again with a return of focus to the dialog. Here are the details of how this is dane in AutoCAD Ri4—the lines of code are in the AutoCAD 2000 sample application but they are commented out. GetParent()->EnableWindow(TRUE); // Enable thee parent window // which in our case is the 17 AutoCAD main window. ShowWindow(SW_HIDE); — // Hide our dialog GetParent()->SetFocus(); // Give AutoCAD the focus. In AutoCAD 2000, you simply call BeginEditorCommand(). This simply replaces the code above. Selecting a point is as before. Just a quick aside: we are using acedinitGet(NULL, NULL) here to force an RTNONE. If you remember previously, the function asDbIArray() returns incorrect values ifthe user just presses ENTER; here we expli itly set the point selected to (0,0). Now back to where we were for AutoCAD RV ShowWindow(SH_SHOW); // Display our dialog again SetFocus(); // Reset the focus back to ourselves GetParent()->EnableWindow( FALSE); // Disabled AutoCAD window EnableWindow(TRUE); // Enable our dialog However, the lines above are commented out in our sample, and in AutoCAD 2000 you simply call CompleteEditorCommand(). After our point is selected, we simply redisplay our dialog again and set focus back to it. Disabling the parent window, which is AutoCAD, follows this. That’s how you hide a modal dialog—pretty simple when ‘you know how, even much easier in AutoCAD 2000. In a sample application later, ‘when we are looking at tabbed dialogs and wizard-style dialogs, there is a slight vari- ation to hiding the dialog. We launch our second nested dialog from ‘CWindoTypeDig::OnBtnStats(), which is simply a matter of calling DoModal( for the second dialog, Here isthe listing for the WindoStatsDig.h file: Hite Idefined(AFX_WINDOSTATDLG_H__EF266281_1CD9_1103_& ATAC_000000000000__INCLUDED_) defines ‘AFX_WINDOSTATOLG_H__£F266281_1CD9_1103_A7AC_¢ 000000000000__INCLUDED_ #if MSC_VER > 1000 pragma once #endif // _MSC_VER > 1000 // WindoStatOlg.h : header file “a TTL TTL TTT // CWindoStatD1g dialog class CWindoStatD1g : public CDialog ( 11 Construction public: CWindoStatDIg(CWnd* pParent = NULL): //@) standard constructor 11 Dialog Data J (AFX_DATA(CHindoStat1g) enum { TDD = IDD_WINDOSTAT }; // NOTE: the ClassWizard will add data members@ here T/SYAFX_DATA 11 Overrides // ClassWizard genereted virtual functiong overrides 7/ (AFX_VIRTUAL (CWI ndoStatD1g) protected: Virtual void DoDataExchange(COataExchange* pDX): // DDX/ODV. support 17) YAFX_VIRTUAL // Implementation protected: // Generated message map functions // (AFX_MSG(CWindoStatD1g) virtual BOOL OnInitDialog(); ‘MBC Dialogs and Objet ARX's UI Extensions 43) JD)VAFXMSG DECLARE_MESSAGE_MAP() } J ((AFX_INSERT_LOCATION) } // Microsoft Visual C++ will insert additionale declarations immediately before the previous line. endif 110 !def ined (AFX_WINDOSTATOLG_H__€F266281_1CD9_1103_¢ A7AC_000000000000__INCLIDEO_) About the only thing special here is that we used MFC ClassWizard to set up the ‘OninitDialog(). Here is the listing for the WindoStatsDlg.cpp file: J/ WindoStatD1g.cpp : implementation file ul finclude “Stdafx.h” #include “StdArx.h finclude “resource.h” #include “WindoStatD1g.h” fifdef DEBUG fdefine new DEBUG_NEW fundef THIS_FILE static char THIS_FILEC] = FILE; ftendif MUTT ITT // CWindoStatOlg dialog CiindoStatD1g: :CWindoStatD1g(CWnid* pParent /*=NULL*/) : CDialog(CWindoStatDig::IDD, pParent) { /I((AFX_DATA_INIT(CWindoStatD19) 1 NOTE: the ClassWizard will add membere initialization here A) YAPX_DATALINIT ) m void CWindoStat01g: :DoDataExchange(CDataExchange* pox) ( (Dialog: :DoDataéxcharge(pOX); J/(AFX_DATA_MAP(CHirdoStatD1g) J/ NOTE: the ClassWizard will add DDX and DDVe calls here 1) AFX_DATA_MAP. ) BEGIN_MESSAGE_MAP(CWincoStatD1g, CDialog) 7 (AFX_MSG_MAP(CWindoStatD1g) 11) \AFX_MSG_MAP END_MESSAGE_MAP() TTL LTT LLL TLL MALTA // CWindoStatDlg message handlers BOOL CWindoStatO1g: :OnInitDialog() fl CDialog nInitDialog(): // TOD0: Add extra initialization here CListCtri* plist; plist = (CListCtrI*) GetDlaltem(IDC LIST & WINDOSTAT) ; char* arCols[5] = ( “Height”, “Width"); ype", “Cols”, “Rows”, LV_COLUMN 1vCol; lvCol.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT: lvCol.fmt = LVCFNT_LEFT; IvCol.cx = 50; for(int nColumn = 0; nColumn < 5; nColumn++) ( 1vCol.pszText = arCols{nColumn]; pList->InsertColumn(nColumn, &1VCol): } MFC Dialogs and Objet ARK UI Extensions 433 $$$ if(stromp(windInfo.windType, “Rect") == 0) ‘ pList->InsertItem(0, “Rect); tise if(stremp(windInfo.windType, “Arch”) == 0) ‘ plist->Insertitem(0, “Arch” tise ; pList->InsertItem(0, “Apex”); char temp[80]; pList->SetItemText(0, 1, itoa(windInfo.mncols.# temp, 10)): pList->SetItemfext(0, 2, itoa(windInfo.mnRows,¢ temp, 10)): acdbRToS(windInfo.m_dWindHt, 2, 3, temp); pList->SetItemText(0, 3, temp); acdbRToS(windInfo.m_dvindWt, 2 pList->SetItemText(0, 4, temp); temp): return TRUE; // return TRUE unless you set the? focus to a control // EXCEPTION: OCX Property Pages® should return FALSE } A\ll the action here happens in OnInitDialog() with the initialization of the CListCtrl. During our design of the dialog we gave the list control a ‘report’ style layout. Here is where we initialize the control, giving the control a header. The header of the con~ trol is tied into an LV_COLUMN structure. You can refer to the MFC documenta tion for more information on the structure LY_ COLUMN. One of the fields of the LV_COLUMN structure is the mask field Ths field i initialized with bit-coded flags and basically tells the control how to behave and what parameters are valid for the con~ ‘tol. The combination of bit-coded flags we are adding here tells the control that the control will allow text formatting (LVCF_FMT), that the width of the column can be set and adjusted with the mouse (LVCF_WIDTH), and also that the columns can accept text (LVCF_TEXT). The fe field tells us that the text in the column head- er will be left justified (LWCFMT_LEFT), ard finally, we make the width of each col- a ‘umn 50 pixels. In the for loop is where we put the text for the columns and insert the columns in the control with the InsertColumn() function. Inserting lines of text in the list control is a simple call to SetitemText(). We could use a for loop to insert multiple lines of text in the control, but here we are only insert- ing one line of text. The SetlternText() also fills out the columns of text fora particular line of text. Again, this is also zero based. In later applications, instead of using C and ADS functions for doing our string formatting, we will use MFC’s CString class. MODELESS DIALOGS AND SAMPLE APPLICATION CH7_3 ‘You will be happy to know that handling modeless dialogs in ObjectARX is not much different from handling modeless dialogs in MFC. The dialog for this application is almost identical to the dialog for the previous application with the exception of removing the Stats... button and renaming the OK button (IDOK) to Draw Window. Figure 7.7 shows the dialog for this application. Pri re ee ee) Figure 7.7 Modeless Dialog ‘When you have finished creating the dialog, use the ObjectARX 2000 AppWizard to create a class called CWindoTypeDlg, derived from CAcUiDialog. Here is the windoQ) command function that launches our modeless dialog from the CB7_3Commands.cpp file: MUTT TTT // ObjectARX defined commands ‘MPC Dialogs and Objet RX UI Extensions 285 aaa finclude “Stdafx.h fHinclude “StdArx.h include “WindoTypeD!g.h” extern CWindoTypeD1g* gpDlg; // This is command ‘WINDO” void windo() { // TODO: Implement tte command if(!gp01g) ( CAcModuleResourceOverride resOverride: gpD1g = new CWindoTypeD1g(acedGetAcadFrame()); gp01g->Create(IDD_WINDOTYPE) ; else i rrAfxMessageBox(“Modeless Dialog is alreadyd active!"); } ) ‘We create the modeless dialog in the windoQ) function. We initialize our WindoTypeDlg pointer, using the new operator to create an instance of the (WindoTypeDig class with the AutoCAD frame window as the parent of the mod- less dialog. Next, we call the create() function with the resource ID of our dialog. ‘That's all there is to creating a modeless dialog. Now remember, when our dialog is active and visible, we can switch over to AutoCAD and execute any AutoCAD ‘command and then switch back to our dia‘og. Where we have to be careful is when we close the dialog; we will see this when we discuss the CWindoTypeDlg class. ‘There are a number of differences between the CWindoTypeDig class of this appli- cation and that of the previous applicatior, mainly due to the fact that we are now behaving as a modeless dialog. Here is the listing of the WindoTypeDik.h file: Mr = #if !defined(ARX_WINDOTYPEDLG_H__19990608_210816) #define ARX__WINDOTYPEDLG_H__19990608_210816 Ip #if MSC_VER > 1000 fpragma once #endif // _MSC_VER > 1000 #include “Resource.h” MS class CWindoTypeDIg : public CAcUiDialog DECLARE_DYNAMIC (CWindoTypeD1g) public: void updateGlobalDocData(): CWindoTypeDlg (Chind* pParent -NULL, HINSTANCES hinstance =NULL) 7 ({AFX_DATA(CHindoTypeD1 9) enum { IDD = IDD_WINDOTYPE }; CAcUiNumericEdit mctrl€dHeight; CAcUiNumericEdit mctriEdWi del CAcUiNumericEdit mctriEdXVal CAcUiNumericEdit mctriEdYVal; int mnCols; int mnRows; int mnWindType: CAcUiPickButton mctriBtnPickPt; TI) YAFX_DATA Cstring mstrHetght; CString m_strWidth; CString mstrXVal; CString mstrYVal; 1 ((AFX_VIRTUAL (CHI ndoTypeD1g) protected: Virtual void DoDataExchange(CDataExchange* pDX); J DDX/DDV support virtual void PostNcDestroy(); J) )AFX_VIRTUAL protected: 7/((AFX_MSG(CWindoTypeD1g) afx_msg LONG OnAcadkeepFocus(UINT, UINT); virtual void On0K(); virtual BOOL OnInitDialog(); (MPC Dialogs and ObjecARX's UI Extensions 437 $$$ afx_msg void Onkil] fccusEditHeight(); afx_msg void Onkill fccusEdi tWidth(); afx_msg void OnBtnPickPt(); virtual void OnCancel(); JD) YAFXMSE DECLARE_MESSAGE_MAP() i 1 ({AFX_INSERT_LOCATION} } // Microsoft Visual C++ will insert additionalg declarations immediately before the previous line. fendif //-- Idef ined(ARX__WINDOTYPEDLG_H__19990608_210816) In this application we used Class Wizard to respond to the PostNeDestroy() and ‘OnCancel() functions, whose implementation we will se later. The OnOKO func tion is called in response to the selection of the Draw Window button, which is still the (IDOK) resource ID. We do not destroy the dialog here; we just draw a window. The dialog still lives until we select the Caneel button. Here is the listing for the WindoTypeDig.cpp file. WindoTypeD1g.cpp Finclude #include #include : Hinclude “WindoTypeD1g.h” #include “geassign.h” implementation file extern CWindoTypeD1g* gp0lg; I fifdef DEBUG fidefine new DEBUG_NEW fundef THIS_FILE static char THIS_FILEL) itendif FILE: up : IMPLENENT_DYNAMIC (CWindoTypeDIg, CAcUiDialog) 88 BEGIN_MESSAGE_MAP(CHindoTypeDIg, CAcUiDia1og) 7/((AFX_MSG_MAP(CWindoTypeD1a) ‘ON_MESSAGE(WH_ACAD_KEEPFOCUS, OnAcadKeepFocus) 11 Needed for modeless dialog. ‘ON_EN_KILLFOCUS( IDC_€DIT_HEIGHT. OnkiT1 focusEditHei ght) ON_EN_KILLFOCUS( IDC_EDIT_WIDTH, Onkil1 focusEditwidth) ON_BN_CLICKED(IDC_BTN_PICKPT, OnBtnPickPt) 7) )AFX_MSG_MAP END_MESSAGE_MAP() VL - CWindoTypeD1g: :CWindoTypeDlg (CWnd* pParente /*=NULL*/, HINSTANCE hinstance /*=NULL*/) 72 CAcUiDialog (CWindoTypeDlg:: 10D, pParent, hInstance) ( // ((AFX_DATA_INIT(CWindoTypeD1g) mnCols = windInfo.mnCols; m_nRows = windInfo.m_nRows ; mnWindType = -1; JT)VAFX_DATA_INIT } void CWindoTypeD1g: :DoDataExchange (CDataéxchanged *p0X) { CAcUiDialog::DoDataFxchange (pOX) : 7 ((AFX_DATA_MAP(CHindoTypeD1g) DDX_Control(pOX, IDC_EDIT_HEIGHT. mctriEdHeight): DDX_Control(pOX, IDC_EDIT_WIDTH, mctrledwidth); DDX_Control(pOX, IDC_EDIT_XVAL, m_ctr1EdXVal): DDX_Control(pOX, IDC_EDIT_YVAL, mctriEdvVal): DDX_Text(pDX, IDC_EDIT_COLS, m_nCols); DDV_MinMaxInt(pOX, mnCols, 1, 10); DDX_Text(pDX, 1DC_EDIT_ROWS, mnRows); DDV_MinMaxInt(pOX, mnRows, 1. 10); DDX_Radio(pDX, IDC_RB_TYPERECT, m_nWindType) : DDX_Control(pOX, “IDC_BTN_PICKPT, mctriBtnPickPt): TI) YAFX_DATA_MAP ) 11 Needed for modeless dialogs to keep focus. // Return FALSE to not keep the focus. return TRUE? to keep the focus (MPC Dialogs and ObjetARX's UI Extentions 439, ——$—___~ LONG CWindoTypeD1g: :OnAcadkeepFocus(UINT, UINT) { } “ue return TRUE; void CWindoTypeD1g: :0n0K() ( // TODO: Add extra validation here CString strMsgl, str¥sg2, strMsg; if (1UpdateData(TRUE)) { return: ) updateGloba1DocData( strisgl = “If you were to draw a window, & then\n\n”; strMsg2.Format(“Type\t = &s\nWidth\t <0 4.21f\nHeight\t = £.21f\nRows\t = &d\nCols\t = HA\MK\t = B.21F\nY\t = 8.218" windInfo.windType, windInfo.mdiindwt, windInfo.m_diindit, windInfo.m_nRows, windInfo.mnCols, windInfo.mdXval, windInfo.m_dYva1): strMsg = strMsgl + strMsg2; Chind: :MessageBox(strMsg, “Selected Windowg Parameters”, MB_ICONINFORMATION) ; ) BOOL CWindoTypeD1g: :OnInitDialog() { CAcUIDialog: :OnInitDialog(); // TODO: Add extra initialization here CString str_edboxVal: CString fName: CString mainTitle("Window Parameters Modelesso ooy ‘AcApDocument *pCurrDoc = acDocManagerd) ->curDocument(); fName = pCurrDoc->docTitle(); mainTitle += fName: this->SetWindowText (mainTitle); CButton *pWindoTypes CString strWindType(windInfo.windType); if(strWindType —= “Rect”) i pindoType = (CButton *) GetD1gItem( IDC_RB_¢ TYPERECT); piindoType->SetCheck(1); ) else if(strWindType == “Arch”) i pWindoType = (CButton *)¢ GetD1 gi tem(10C_R8_TYPEARCH) ; piindoType->SetCheck(1); ) else // It must be an “Apex” type of window { piindoType = (CButton *)¢ GetD1gItem(1DC_RB_TYPEAPEX) ; phindoType->SetCheck(1); ) mctrlEdHeight.SetRange(10.00, 120.00); m_ctrlEdWidth.SetRange(10.00, 120.00); acdbRToS(windinfo.m_dWindHt, 2, 3,0 str_edboxVal .GetBuffer(80)): m_ctrlEdHei ght . SetWindowText(str_edboxVal); acdbRToS(windinfo.mdWindWt, 2, 3,0 str_edboxVal .GetBuffer(80)); (MBC Dialogs and ObjeARX's UI Extensions 444 $$ m_ctrlEdWidth. SetWindowText(str_edboxVal) ; acdbRToS(windInfo.mdXval, 2, 3,0 str_edboxVal .GetBuffer(80)): m_ctrlEdXVal.SetWindowText(str_edboxVal); acdbRToS(windinfo.mdVal, 2, 3,0 str_edboxVal .GetBuffer(30)); m_ctrlEdYVal.SetWindowText(str_edboxVal): m_ctr1BtnPickPt.AutoLoad(); return TRUE; // return TRUE unless you set thee focus to a control // EXCEPTION: OCX Property Pages® should return FALSE ) void CWindoTypeD1g: :Onki11 focusédi tei ght() t // TODO: Add your, control notification handlers code here mctrl€dHeight.Convert(); if(Im_ctrledHeight.Yalidate()) ( riAfxMessageBox(“Sorry, Range = 10.00 tog 120.00"); mctrlEdHeight.SetFocus(); mctrl€dHeight.SetSel(0, -1); ) ) void CWindoTypeD1g: :Onki11 focusEditwidth() { /1 TODO: Add your control notification handlery code here m_ctrlEdWidth.Convert(); if(1mctrl EdWidth.Valsdate()) i AfxMessageBox(“Sorry, Range = 10.00 to 120.00"); mctrlEdWidth.SetFocus(); mctrlEdWidth.SetSei (0, -1); } ) void CWindoTypeD1g: :OnEtnPickPt() { /1 TODO: Add your control notification handlerd code here // Here we hide our nodeless dialog // to allow the user to pick a point AcGePoint3d pkPt; int retCode; CString str_edboxVal; UpdateData (TRUE); updateGlobalDocData(); J{ShowWindow(SW_HIDE); // Hide our dialog BeginEditorCommand( ); acedinitGet(NULL, NULL); retCode = acedGetPoint(NULL, “\nPick lower lefte corner of window asDblArray(pkPt)); switch(retCode) ( case RICAN: case RTNONE : pkPt.set(0.0, 0.0, 0.0): break: case RTNORM = break; case RTERROR : break: ) windInfo.m_dxval windInfo.m_dYVal pkPt. x pKPt.ys MPC Dialogs and ObjocARX'+ UI Extensions 443 —————__~ CompleteEditorCommand(); // This replaces thed next commented out Tines //ShowWindow(SH_SHOW); // Display our dialog again // Update our X and Y Edit Boxes acdbRToS(windInfo.m_dXVal ae str_edboxVal .GetBuffer(80)); m_ctrlEdxVal .SetWindowText (str_edboxVal): acdbRToS(windInfo.m dYVal, str_edboxVal .GetBuffer(80)): m_ctrlEdyVal. SetWindowText(str_edboxVal); 38 ) void CWindoTypeDig: ( updateGobalDocData() m_ctrlEdHei ght. GetWindowText(m_strHei ght); m_ctrlEdWidth.GetWindowText(m_strWidth) : m_ctrlEdXVal.GetWindowText(m_strXVal) mctrlEdYVal.GetWindowText(m_strYVal); windInfo.m_dWindWt = atof(mstrWidth); windInfo.m_dWindHt = atof(m_strHeight); windInfo.m_dXVal_ = atof(m_strXval windInfo.m_dYVal = atof(m_strYVal windInfo.m_nCols = mnCol windInfo.m_nRows = mnRows; switch (mnWindType) ( case 0: strepy(windInfo.windType, “Rect”); break; case 1: strepy(windInfo.windType, break; ‘Arch”) ; case 2: strepy(windInfo.windType, “Apex”): break; void CWindoTypeDlg: :OnCancel() // TODD: Add extra cleanup here 1 Do NOT call CAcUiDialog: :OnCancel(); // Remember we're modeless, // Call DestroyWindow() instead! DestroyWindow(); ) void CWindoTypeD1g: :PostNcDestroy() ( /1 1000: Add your specialized code here and/ore call the base class delete this: if(gp01g != NULL) { gpD1g = NULL; } CAcUi Dialog: :PostNcDestroy(); ) ‘The constructor and the DoDataExchange() are the same as in the previous appli- cation. Look at the message map, in particular ON_MESSAGE (WM_ACAD_KEEP- FOCUS, onAcadKeepFocus). So just whats WM_ACAD_KEEPFOCUS? It certainly is not one of the messages managed by MFC. This particular Windows message is fired off by AutoCAD itself. You will find its definition in two locations: in the sadscodes.b and in the remfeapi. files of the ObjectARX 2000\Inc directory. Now what is it used for? When you use modeless dialog in AutoCAD, AutoCAD always tries to regain focus. The effect is, as soon as yeu move your mouse off the modeless dia log, the modeless dialog loses focus. If you want to see this effect, instead of return ing TRUE from the CWindoTypeDig:onAcadKeepFocus(, return FALSE. Then run the application after you have rebuilt it and you will see the focus switch back and forth between the modeless dialog and the AutcCAD frame window. To avoid this effect, ‘AutoCAD sends out a WM_ACAD_KEEPFOCUS message and if you retumn TRUE, your modeless dialog will keep focus. Your modeless dialog will lose focus only when you click inside the AutoCAD main frame area or graphics screen, (MPC Dialogs and ObjecARXs UI Extentions 445 ——___. ‘The CWindoTypeDigOnOK( function is called when the user selects the Draw ‘Window button, Remember, the Draw Window button is really the button with the resource ID of IDOK. This function pops up a Message Box that shows what values you have selected. It does not dismiss the dialog, as would happen in a modal dialog. Hiding the dialog to select a point on the graphics screen is handled by the ‘WindoTypeDig:OnBinPickPt() function. Hiding a modeless dialog is different from hiding a modal dialog and easier too. We simply make the dialog invisible, select our point, and make the dialog visible once agtin. There is no switching focus from the dialog to the AutoCAD window and back again—we simply call ShowWindow() with cither SW_HIDE or SW_SHOW; this works in AutoCAD R14/2000. Here I use the BeginEditorCommand(/CompleteEditorCommand(), which is AutoCAD 2000 only. Either way works in AutoCAD 2000. Closing a modeless dialog is a two-step process, very similar to closing a modeless dia~ log in MFC. When the user selects the Cancel button (IDCANCEL), the ‘WindoTypeDig-OnCancel() function is called, in which we call DestroyWindow(). (In hindsight, I should have renamed the Cancel button to Close. Oh well, too late now) So this represents the first step; take particular note of the comments where I caution you NOT to call the base class CDialog::OnCancel()—that is just for modal dialogs. Our dialog window in no longer visible at this stage, but the dia- Jog object is still very much alive. We used ClassWizard to add a handler for the PostNcDestroy(). So how does PostNeDestroy() get called and what docs the Ne part stand for anyway? The Ne part stands for non-client. When Windows destroys a win dow, the dialog will receive a WM_NCDESTROY message and the default OnNeDestroy() is called—this is standard Windows behavior. The OnNeDestroy() function calls the PostNeDestroy() function, and this is where we can perform our cleanup because PostNeDestroy() is a virtual function. In this function we delete the pointer to the dialog frame window and call the base class CDialog:PostNeDestroy(). So that’s how a modeless dialog gets destroyed. Before we move on to our next sample applications, let's revisit global data. We have already seen the classes CDocData and AsdkDataManager. We have not discussed the CDocReact user-defined class yet. For this application, these classes are the same as previously described. Now we are about to jump way ahead of ourselves here when discussing the class CDocReact. The class CDocReact is a reactor class and responds to events in AutoCAD. This class is derived from the AcApDocManagerReactor class. We will discuss notifications and reactors toward the end of this book. Basically, the AcApDocManagerReactor is responsible for events that happen at a document (drawing) level. Let’s go through a demonstration first. |. Create two drawing files and tile the drawing windows vertically 2, From one of the drawing windows execute the WINDO command and enter ‘some values in the dialog. Select the Draw Window button; this will write ‘out the data for the active document. 13 Switch the drawing window and enter some new values for the dialog and select the Draw Window button agai 4, Now, with the modeless dialog acive, switch from drawing window to draw- ing window. 5, Notice that the title bar of the modeless dialog reflects the name of the cur~ ‘rent drawing in addition to the data changing when you switch from drawing to drawing. We can achieve this through the use of a reactor, in this case AcApDocManagerReactor. What we are responding to is when the user switches from drawing to drawing: CDocReact::documentActivated(). Here is the listing for the documentedActivated() function, which is located in the rDocManagerReactorpp file. void CDocReact: :documentActivated(AcApDocument*¢ pactivatedDoc) t // TODO: implement this function. if (p01) { CString fName; CString mainTitle(*Window Parameters Modeless -@ AcApDocument *pCurrDoc = acDocManagere —>eurDocument(); fName = pCurrDoc->docTitle(): mainTitle += fName; gp1g->SetWindowText(mainTitle); gpD1g->SetFocus(); CButton *pWindoType: Chind *pEdBox: CString str_edboxVal; CString strWindType(windInfo.windType); if(strWindType — “Rect”) ( pWindoType = (CButton *) gpDlge —>GetD1 gl tem( IDC_RB_TYPERECT) ; (MBC Dialogs and ObjeARX's UI Extensions 447 SE pWindoType->SetCheck(1) pWindoType = (CButton *) gpDlge? —>GetD1 gI tem(IDC_RB_TYPEARCH. pWindoType->SetCheck(0); pWindoType = (CButton *) gpDlge —>Get D1 g1 tem( IDC_RB_TYPEAPEX pWindoType->SetCheck(0 else if(strWindType == “Arch”) cl pWindoType = (CButton *) gpDlge? —>GetD1 gI tem( 1DC_RB_TYPEARCH: pWindoType->SetCheck(1 pWindoType = (CButton *) gpDlge —>Get01 gI tem(IDC_RB_TYPERECT: pWindoType->SetCheck(O pWindoType = (CButton *) gpDlge =>GetD1 gI tem(IDC_RB_TYPEAPEX) ; pWindoType->SetCheck(0) ; else // It must be an “Apex” type of window ( pWindoType = (CButton *) gpDlgg =>GetD1 gl tem(IDC_RB_TYPEAPEX) ; pWindoType->SetCheck(1); pWindoType = (CButton *) gpDlgg =>GetO1 gl tem(IDC_RB_TYPEARCH) : pWindoType->SetCheck(0); pWindoType = (CButton *) gpDlge =>Get01 gl tem(IDC_RB_TYPERECT) ; pWindoType->SetCheck(0); PEd80x = gpD1g->GetD1 gitem(10C_EDIT_COLS); PEdBox->SetWindowText (itoa(windInfo.m_nCols. str_edboxVal .GetBuffer(80), 10)); PEdBox = gpD1g->GetD1 gltem( 1DC_EDIT_ROWS) ; PEdBox->SetWindowText (toa (windInfo.m_nRows str_edboxVal .GetBuffer(80), 10)); acdbRToS(windinfo.n_dWindHt, 2, 3, str_edboxVal .GetBuffer(80)); gpDlge >m_ctrlEdHei ght . SetWindowText(str_edboxVal); acdbRT0S(windInfo.m_dWindWt, str_edboxVal .GetBuf fer(B0)): ‘gpD1g->m_ctrlEdWidth. SetWindowText(str_edboxVa1): 3e acdbRToS(windInfo.mdXval, 2, 3.0 str_edboxVal .GetBuffer(80)); ‘gp01g->m_ctriEdXVal.SetWindowText(str_edboxVal); acdbRToS(windinfo.mdYVal, 2, 3,0 str_edboxVal .GetBuffer(80)); ‘gpD1g->m_ctr1 EdyVal.SetWindowText(str_edboxVal); } ) As you can see from this code, when we switch documents we call upon the services of the global document manager aeDoeManager to retrieve the ttl of the current doc ument, with which we change the title of the modeless dialog. Also notice how we are retrieving the global data that pertains to the active document and updating the fields of the modeless dialog accordingly. TABBED-STYLE DIALOGS AND SAMPLE APPLICATION CH7_4 ‘Tabbed-style dialogs were introduced with Windows 95 and allow the developer to pack a lot of information into limited screen real estate. A good example of a tabbed dialog is the Preferences dialog in AutoCAD. Tabbed dialogs are created through two classes in MFC, namely CPropertySheet and CPropertyPage. These can be used in AutoCAD 2000; however, ObjectARX 2000 provides its own derived equivalents, namely CAcUiTabMainDialog and CAcUITabChildDialog respectively. (CPropertySheet/CAcUiTabMainDialog is the main frame (parent) of all the tabs in a tabbed-style dialog. A CpropertyPage/CAcUiTabChildDialog represents each tab. So let’s go ahead and create one of these tabbed-style dialogs. This application is in terms of AutoCAD 2000 and CAcUiTabMainDialog; however, we have provided 1 pure MFC version of this application, which uses CPropertySheet/CPropertyPage, namely Ch7_4MFC, if you want to investigate further. Figure 7.8 shows the tabbed-style dialog for this application. We will use the resource editor to create three dialogs, the first of which is shown in Figure 7.9; it is the dialog for the CWindTypePg class that will be derived from CPropertyPage. MPC Dialogs and ObjectARX's UI Extensions 449 Career Figure 7.9 CWindTypePe Property Page When inserting a new the Resource... menu ‘our project, from the Insert menu in Visual C++, select. ‘When the Insert Resource dialog appears, expand the Dialog option in the tree view. Select the dialog based on IDD_PROPPAGE_SMALL. ‘Table 7.4 shows the settings for the CWindTypePg dialog. Resource Name and Type Resource ID Resource Settings Dialog—"Type IDD_PAGE_WINDTYPE | Style = Child’, Border = ‘None’, Title Bas, Disabled Radio button="Rect” | IDC_RB_TYPERECT __| Visible, Group, Tab Stop and Auto, Caption = ‘BRect? Radio button=“Arch” | IDC_RB_TYPEARCH _| Visible, Tab Stop and Auto. Caption = 8Arch’ Radio button-“apeX” | IDC_RB-TYPEAPEX _| Visible, Tab Stop and Auto. Caption = ape8X° Group Box=“Window | IDC_STATIC Visible, Group Type” Caption = "Window Type ‘Allother elements IDC_STATIC Default properties, see illustration. ‘Table 7.4 Resource IDs for the Dialog Controls of gure 7.9 ‘When you have finished creating the dialog, use the Object ARK 2000 AppWizard to create a class CWindTypePg based on the CAcUiTabChildDialog class. Make sure you pick CAcUiTabChildDialog as the base class. Figure 7.10 shows the dialog and ‘Table 7.5 shows the settings for the CwindSpeesPg. After you have finished, use (Class Wizard to create a class based on CacUiTabChildDialog. Figure 7.10 CWindSpecsPe Property Page ‘MPC Dialogs and ObjctARX's UI Extensions 451 Resource Name and Type Resource ID Resource Settings Dialog - “Window Type” IDD_WINDOTYPE ‘Style = ‘Child’, Border = ‘None’, Title Bar, Disabled Group Box-"Window Type 1DC_STATIC Visible, Group Caption = “Window Type’ Edit box - “Height” IDC_EDIT_HEIGHT || Visible, Tab Stop, Auto HScroll, Border. Edit box — “Width? IDC_EDITWIDTH _ | Visible, Tab Stop, Auto HScroll, Border. Edit box “Columns” IDC_EDIT_COLS Visible, Tab Stop, Auto| Scroll, Border. Edit box “Rows? IDC_EDIT_ROWS —_| Visible, Tb Stop, Auto Hcroll, Border. All other elements IDC_STATIC Default properties, see illustration Table 7.5 Resource IDs for the Dialog Controls of Figure 7.10 Figure 7.11 shows the dialog and Table 7.6 shows the settings for the CwindStartPePg. ‘After you have finished, use ClassWizard to create a class based on ‘CAcUiTabChildDialog. Start Point Figure 7.11 CWindStartPtPg Property Poge —e, Resource ID Resource Settings Dialog "Stat Point” | IDD_PAGE_WINDSTRIPT | Style - Child’, Border = ‘None’ Tie Bar, Disabled Push button—*< Pick? | 1DC_BTN_PICKPT Visible, Tab Stop uit box "8X Value” | 1DC_EDIT_XVAL Visible, Tab Stop, Auto HScrol, Border Bit box—"®Y Value” | 1DC_EDIT_YVAL Visible, Tab Stop, Auto HScroll, Border. Pash button~"Stas..” | IDC_BIN_STATS Visible, Tab Stop Allotherclements | IDC_STATIC Deefule properties, ee ilusration. ‘Table 7.6 Resource IDs for the Dialog Controls of Figure 7.11 (MPC Dialogs and ObjectARXs UI Extensions 453 $$ Here is the listing for the Cb7_4Commands.cpp file: MTL TLL // ObjectARX defined commands #include “StdAfx.h” #include “StdArx.h” J/#include “Resource.h” J/ This is command ‘WINDO’ void windo() { // T0D0: Implement the command CAcModuleResourceOverride resOverride; CWindoMainSht windosht; if(windoSht .DoModal() == 100K) ( windInfo.m_dWindHt = windoSht.m_specPg.m_dHei ght: windInfo.m_dWindWt = windoSht.m_specPg.m_dWidth; windInfo.m_nCols = windoSht.m_specPg.m_nCols; windInfo.m_nRows = windoSht.m_specPg.m_nRows; windInfo.m_dXVal = windoSht.mstartPtPg.m_dxval; windInfo.m_dyVal = windoSht.m_startptPg.m_dYVal; windInfo.m_nWindType = windoSht .m_windPg.m_nWindType: switch(windInfo.m_nWindType) ( case 0 : // Rect AfxMessageBox(“Here is where we would haved drawn an Rect window."); break; case 1: // Arch AfxMessageBox(“Here is where we would haved drawn an Arch window.”); break; case 2: // Apex AfxMessageBox(“Here is where we would haved drawn an Apex window.”}: break; ) 17 switch ) ) Nothing unusual here; CWindoMainSht is derived from CAcUITabMainDialog and everything else we have seen before. Let's take a look at the header file for the CWindoMainSht class, namely WindoMainSht.b: I —- #if_Idefined(ARX_WINDCMAINSHT_H_19990626_105244) define ARX_WINDOMAINSHT_H_19990626 105244 [a a #if _MSC_VER > 1000 pragma once #endif // _MSC_VER > 1000 finclude “Resource.h” #include “StdArx.h” a0 class CWindoMainSht : public CAcUiTabMainDialog { DECLARE_DYNAMIC (CWindoMainSht) public: CWindoMainSht (CWnd* pParent =NULL, HINSTANCES hInstance =NULL) ; J/{(AFX_DATACHi ndoMeinSht ) enum ( TDD = IDD_WINDOSHT }; CAcUiTab m_tabCtrl; TI) YAFX_DATA CindTypePg mwindPg; CWindSpecsPg m_specPg: (MFC Dialogs and Objet ARX's UI Extensions 455 ———————u§~ CWindStartPtPg m_startPtPg; // (AFX_VIRTUAL(CWindoMainSht) protected: virtual void DoDataExchange(CDataExchange* pDX): // DDX/ODV. support. J) VAFX_VIRTUAL protected: //((AFX_MSG(CHi ndoMaiaSht ) afx_msg LONG OnAcadKeepFocus(UINT, UINT); virtual BOOL OnInitDialog(); HIV AFX_MSG. DECLARE_MESSAGE_MAP() Ve //((AFX_INSERT_LOCATION} } // Microsoft Visual C++ will insert additionalé declarations immediately before the previous line. fendif // !defined(ARX__WINDOMAINSHT_H_19990626_105244) Perhaps the most interesting item here is the embedded control m_tabCtri, which is derived from CAcUITab. This isthe parent for all ofthe tabs, as we will see later. Here is the implementation for WindoMainSht.cp file: Wr - J/--== WindoMainSht.cpp : implementation file #include “Stdafx.h” #include “Stdarx.h” #include “resource. h” include “WindoMainSht..” WI ifdef _DEBUG iidefine new DEBUG_NEW undef THIS_FILE static char THIS_FILEC] = _FILE_; endif uy IMPLEMENT_DYNAMIC (CWindoMainSht, CAcUiTabMainDialog) BEGIN_MESSAGE_MAP(CWindoMainSht, CAcUiTabMainDialog) 77 ((AFX_MSG_MAP (CHindoMainsht) ON_MESSAGE(WM_ACAD_KEEPFOCUS, OnAcadKeepFocus) /1 Needed for modeless dialog. 17) VAFX_MSG_MAP- END_MESSAGE_MAP() ML —_—- CWindoMainsht: :CWindoMainSht (CWnd* pParenté J*=NULL*/, HINSTANCE hinstance /*=NULL*/) :2 CAcUiTabMainDialog (CWindoMainSht::1DD, pParent,& hInstance) ( //{AFX_DATA_INIT(CWindoMainsht) ID) \AFX_DATALINIT ) void CWindoMainSht: :DoDataExchange (CDataExchanged *p0X) { CAcUiTabMainDialog: :DoDataéxchange (pDX) ; //((AFX_DATA_MAP(CWindoMainSht) DDX_Control(pOX, IDC_TAB1, m_tabCtr1); 7/)YAFX_DATA_MAP. ) J/ Needed for modeless dialogs to keep focus. // Return FALSE to not keep the focus, return TRUE? to keep the focus LONG CWindoMainsht: :OnAcadKeepFocus(UINT, UINT) ( return TRUE: ) I BOOL CWindoMainSht: :OntnitDialog() { CAcUiTabMainDialog: :OnInitDialog(); // TODO: Add extra initialization here SetAcadTabPointer(&m_tabCtrl); MPC Dialogs and ObjecARX's UI Extensions 457 ———_____~_ m_tabCtr1.AddTab(O, _T(“Type"), CWindTypePg: :100,% &am_windPg); m_tabCtri.AddTab(1, _T(“Specs”), CWindSpecsPg &m_specPg); m_tabCtrl.AddTab(2, _T(*Start Point"), & CWindStartPteg::10D, &mstartPtPg); 100, return TRUE; // return TRUE unless you set the? focus to a control J EXCEPTION: OCK Property Pages# should return FALSE ) ‘The most interesting function here is perhaps OninitDialog(), which is where we add our tabs to the embedded tab control. In the resource editur lovk ut the IDD_WIN- DOSHT dialog and you will see that we have placed a tab control on the dialog and used ClassWizard to hook up the control to m_tabCtrl. Look at how we get a pointer to the embedded tab control using SetAcadTabPointer(). Once we have the pointer to our tab control, we simply call the AddTab() method of CAcUiTabMainDialog. Now let's look at the CWindStartPePg derived property page. We are only going to look at the CWindStartPtPg class because the other two property page classes are very similar. Here is the listing for the WindStartPPg.h file: Ih ifndef ARX_WINDSTARTPTPG_H__19990626_105823 #define ARX__WINDSTARTPTPG_H__19990626_105823 UL #if _MSC_VER > 1000 pragma once #endif // _MSC_VER > 1000 finclude “Resource.h” aL class CWindStartPtPg : public CAcUiTabChildDialog ( DECLARE_DYNAMIC (CWindStartPtPg) public: CWindStartPtPg (CWind *pParent -NULL, HINSTANCES hDialogResource =NULL) ; virtual void OnTabActivation (BOOL bActivate) ; virtual BOOL OnTabChanging () ; public: // (AFX_DATA(CHindStartPtPg) enum { IDD = IDD_PAGE_WINDSTRTPT }; double mdxVa double mdYVals J) )AFX_DATA //((AFX_NIRTUAL(CHindStartPtPg) protected: virtual void DoDataExchange(CDataExchange* pOX): 11 DDX/DDV support 1) YAFK VIRTUAL protected: 7 LAFX_MSG(CWindStartPtPg) afx_msg void OnBtnPickPt(); LDVYAFX_MSG DECLARE_MESSAGE_MAP() Ve MTT LLL LLL LLL MMA J/((AFX_INSERT_LOCATION) ) 11 Microsoft Developer Studio will insert additional declarations immediately before the previous line. endif //---- ARX_WINDSTARTPTPG_H_19990626_105823 ‘As you can see, there is nothing too unusual here. What I want to look at is the imple~ ‘mentation of OnBtnPickPt() and in particular, how we hide the property page and its parent property sheet. Here is the implementation of WindStartPtPg.cppr =u __§€§_§_| @ ——- J/—- WindStartPtPg.cpp : implementation file #include “StdAfx.h” #include “Stdarx.h” #include “resource.h” #include “WindStartPtPa.h” #include “geassign.h” (MPC Dialogs and ObjetARXs UI Extensions 459 Ws iifdef _DEBUG fidefine new DEBUG_NEW fundef THIS FILE static char THIS_FILEL] = _FILE_; endif It a IMPLEMENT_DYNAMIC (CWindStartPtPg, & CAcUiTabChi1dDialog) BEGIN_MESSAGE_MAP(CWindStartPtPg, & CAcUiTabChi1dDialog) 7 LAFX_MSG_MAP(CWindStartPtPg) ON_BN_CLICKED( IDC_BTN_PICKPT, OnBtnPickPt) 1) )AFX_MSG_MAP END_MESSAGE_MAP() a ___+_§_—___—_—_. CWindStartPtpg: :CWindStartPtPg (CWnd* pParent /*=NULL*/, HINSTANCE hinstance /*=NULL*/) :@ CAcUiTabChildDialog (pParent, hInstance) { //((AFX_DATA_INIT(CWindStartPtPg) mdXVal = windInfo.m_dxVal; mdYVal = windinfo.m_dyVa /7)\AFX_OATA_INIT ) void CWindStartPtPg: :DodataExchange (CDatafxchange *pOX) ( CAcUiTabChi1dDialog: :DoDataExchange (pDX) 7/(CAFX_DATA_MAP(CHindStartPtPg) DDX_Text(pDX, IDC_EDIT_XVAL, mdXVa1); DOX_Text(pDX, IDC_EDIT_YVAL, m_dYVal); JIV)AFX_DATALMAP. ) I void CWindStartPtP: bActivate) { //---~ T0D0: Add your code here ) nTabActivation (BOOL? BOOL CWindStartPtPg::OnTabChanging () { //---~ TODO: Add your code here return (TRUE) ; ) 1 void CWindStartPtPg: :OnBtnPickPt() ( /1 T0D0: Add your control notification handler code here // Were we hide our nodal dialog property sheet 11 to allow the user to pick a point AcGePoint3d pkPt: int retCode: GetParent()->GetParent()->EnableWindow( TRUE); // Enable the parent window J/ which in our case is the JJ AutoCAD main window. GetParent ()->ShowWindow(SW_HIDE); // Hide oure? dialog GetParent()->GetParent()->SetFocus(); // Gived AutoCAD the focus. acedInitGet(NULL, NULL); retCode = acedGetPoint(NULL, “\nPick lower lefte corner of window: “, asDblArray(pkPt)): switch(retCode) "case RICAN: case RTNONE : pkPt.set(0.0, 0.C, 0.0); break; case RTNORM : ‘MPC Dialogs and Objet ARK UI Extensions 464 ~~ break; ) mdXVal = pkPt. x: mdVal = pkPt.y; GetParent()->ShowWindow(S_SHOW); // Display ouré dialog again GetParent()->SetFocus); // Reset the focus back# to ourselves GetParent()->GetParent()->EnableWindow( FALSE): //@ Disable AutoCAD window GetParent()->EnableWindow(TRUE); // Enable oure dialog JI Transfer the data values from the memherd variables 11 to the dialog. UpdateData( FALSE); } Here we don't use BeginEditorCommand(/CompleteEditorCommand() combi~ nation as we did previously. We simply use MFC functionality with a double call to GetParent() to enable the AutoCAD mair. frame window. Then we hide our dialog and hand focus over to the AutoCAD main frame. After picking our point, we go through the process of showing our dialog, setting focus back to our dialog, disabling the AutoCAD main frame window, and finally enabling our dialog once again. Why do we have the double call to GetParent()? Wel, we have to call GetParent() to get back to the AutoCAD main frame. Remember that the parent of each tab is the CAcUiTabMainDialog, not AutoCAD itself The single call o GeeParent() is where ‘we want to get back to the parent of the CAcUiTabChildDialog, which is ‘CAcUiTabMainDialog. As you can see, it is relatively straightforward to hide the CAcUiTabMainDialog dialog. Hiding a property sheet is no different from hiding a modal dialog. The only thing that we have to take into consideration is that the parent of the property page is the property sheet and the parent of the property sheet is AutoCAD. Hence the calls to the GetParent() function and also the double calls to GetParent() to get the ‘AutoCAD frame window. WIZARD-STYLE DIALOGS AND SAMPLE APPLICATION CH7_5 This application is a pure MFC, ObjectARK 2000 application that demonstrates how to use MFC wizard-style dialogs based on CPropertySheet and CPropertyPage. You will be happy to know that there is hardly any difference between a tabbed-style dia~ log and a wizard-style dialog in MFC. In this application, the dialog and resources are identical to that of the last application. The only exceptions are that the caption that appears on each page is different from the previous application and that the border style for each of the property pages is style thin as opposed to style none in the resource editor for this application. Here are the dialogs IDs and associated captions: IDD_PAGE_WINDTYPE ‘Step 1 of 3 - Window Type’ IDD_PAGE_WINDSPECS ‘Step 2 of 3 - Specifications’ IDD_PAGE_WINDSTRTPT ‘Step 3 of 3 - Start Point’ Figure 7.12 shows the wizard-style dialog for this application. ee Figure 7.12 Wizard-Style Dialog ‘The main difference between a tabbed-style dialog and a wizard-style dialog is the method in which they are launched. Here is a listing for the windo() function for this application, which is located in the C7_SCommands.cpp file: MBC Dialogs and ObjetARXs UI Extensions 463 $$ $$ MALT // ObjectARX defined ccmmands fHinclude “StdAfx.h” fHinclude “StdArx.h” finclude “WindSpecsPg.h” finclude “WindStartPtPg. h” #include “WindTypePg.h” // This is command ‘WINDO" void windo() { // TODO: Implement the command CAcModuleResourceOverride resOverride: CPropertySheet propSht(IDS_TITLE, acedGetAcadFrame()); CWindTypePg — windPg; CWindSpecsPg specPg: CWindStartPtPg startPtPg: windPg.m_psp.dwFlags &= ~PSP_HASHELP; specPg.m_psp.dwFlags &= ~PSP_HASHELP; startPtPg.m_psp.dwFlags &= ~PSP_HASHELP; propSht.m_psh.dwFlags & ~PSH_HASHELP: propSht .AddPage(&wind?g) ; propSht .AddPage(&spec?g); propSht .AddPage(&startPtPg) ; propSht. SetWizardMode( ); if(propSht.DoModal() == ID_WIZFINISH ) { windInfo.m_dWindHt = specPg.m_dHei ght: windInfo.m_dWindWt = specPg.m_dWidth; windInfo.m_nCols = specPg.m_nCols; windInfo.m_nRows = specPg.m_nRows; windInfo.m_dXVal = startPtPg.mdXVal; WindInfo.m_dYVal = startPtPg.m_dYVal; windInfo.mnWindType = windPg.m_nWindType: switch(windInfo.m_nWindType) ( case 0: // Rect AfxMessageBox(“Here is where we would haved drawn an Rect window.”); break: case 1: // Arch AfxMessageBox("Here is where we would haved drawn an Arch window."); break; case 2: // Apex AfxMessageBox("Here is where we would haved drawn an Apex window.”): break: } } ) Property pages belong to a property sheet. After setting our resource handle using CacResourceModuleOverride, we declare a CPropertySheet in propSht. IDS_TITLE is the ttle of the property sheet and it is located in the String Table. The property sheet belongs to the AutoCAD frame window. We then create three property pages. I will discuss the property page classes in detail later. Following this is some curious bit ‘manipulation. By default, a property sheet has a Help button; here we change the style of the property sheet by removing the Help button, because we are not going to imple ‘ment a Help facility. Here is the trick to removing the Help button from the prop- erty sheet: You have to remove the “style” bit from the underlying property sheet PROPSHEETHEADER structure. The member variable m_psh points to this PROP- SHEETHEADER structure. Using this member variable, we can remove the style bit PSH_HASHELP as follows: PropSht.m_psh.dwFlags & ~PSH_HASHELP; However, that is not the whole story: if tis is to work, you also have to do the same for each of the property pages. The property page contains a member variable m_psp MPC Dialogs and ObjetARX's UI Extensions 465 $$ that points to a PROPSHEETPAGE structure. Ifyou were to add an extra page to the property sheet, you would have to make another bit removal call on the new page to Temove its PSP_HASHELP style bit. Now if you created the new page but forgot to add the call to remove the PSP_HASHELP style bit, the Help button on the prop- exty sheet would appear disabled. When yeu eventually navigated to your new page, the Help button would then appear enabled. So if you want a Help button enabled only on certain pages, do not remove the PSP_HASHELP style bit for the page you are interested in. See the MFC documentation for more information. Add property sheet pages to the property sheet in the order that you want them to appear when the tabbed dialog appears on your screen. To turn this into a wizard-style dialog as opposed to a tabbed-style dialog, call propSht.SetWizardMode() prior to calling DoModalQ. Look at how we call DoMedal() in this application: if(propSht.DoModal() == ID_WIZFINISH ) C /IMore stuff here ) Here we compare the propSht. DoModal() to ID_WIZFINISH 2s opposed to IDOK. As you navigate your way through the dialog, on the fist page the Back button is dis- abled and the Next button is enabled. On the second page both the Back and Next buttons are enabled. Finally, on the last page we change the caption of the Next but- ton to Finish. We used ClassWizard to add a handler for the virtual function ‘OnSetActive() on each of the pages in our property sheet. This is where we control the enabling/disabling and captions of our buttons. Here are the OnSetActive() functions for each of the classes, where you will see how the buttons are controlled in ‘each of the pages: BOOL CWindTypeP. { CPropertySheet* pParent; pParent = (CPropertySheet*) GetParent(); pParent->SetWizardButtons(PSWIZB_NEXT); OnSetactive() return CPropertyPage: :OnSetActive(); BOOL CWindSpecsPg: :OnSetActive() ( CPropertySheet* pParent; pParent = (CPropertySheet*) GetParent(); pParent->SethizardButtons (PSWIZB_BACK |& PSWIZB_NEXT) ; return CPropertyPage::OnSetActive(); ) BOOL CWindStartPtPg: :OnSetActive() ( CPropertySheet* pParent; pParent = (CPropertySheet*) GetParent(): pParent->SethizardButtons(PSWIZB_BACK |o PSWIZB_FINISH) ; return CPropertyPage::0nSetActive(); ) (On the final page (CWindStarePePg in our case), we used Class Wizard to add a han- dler for OnWizardFinish(), which is called when the user selects the Finish button. Here isthe listing for that function: BOOL CWindStartPtPg: :OnWizardFinish() { UpdateData (TRUE) ; return CPropertyPage::OnWizardFinish(): ) Let's look at the one of the property pages; here is the listing for WindStartPtPg.b: Hite defined (AFX_WINDSTARTPTPG_H__2A92A2A3_37A0_1102_@ A2B9_0080C7D122A2__INCLUDED_) Hdefined AFX_WINDSTARTPTPG_H_2A32A2A3_37AD_11D2_A2B9_¢ 0080¢70122A2_INCLUDED_ #if _MSC_VER >= 1000 pragma once #endif // _MSC_VER >= 1000 // WindStartPtPg.h : header file “ws finclude “Resource.h” ‘MFC Dialogs and ObjctARX'+ UT Extensions 467 $$$ MTT ATLL TULA // CWindStartPtPg dialog class CWindStartPtPg : public CPropertyPage { DECLARE_DYNCREATE(CWindStartPtPg) // Construction public: CWindStartPtpg(); ~CWindStartPtPa(); // Dialog Data J/{(AFX_DATA(CMi ndStartPtPg) enum { 10D = IDD_PAGE_WINOSTRTPT }; double mdXVal; double m_dYVal; J) VAFX_DATA /1 Overrides // ClassWizard generate virtual function overrides 7/{{AFX_VIRTUAL(CWindStartPtPg) public: virtual BOOL OnSetactive(); virtual BOOL OnhizardFinish(); protected: Virtual void DoDataexchange(CDataexchanger? POX); // DDX/DDV support 11) )AFX_VIRTUAL // Implementation protected: // Generated message map functions 7/(AFX_MSG(CHindStartPtPg) afx.msg void OnBtnPickPt(); TVYAFX_MSG DECLARE_MESSAGE_MAP() ds /((AFX_INSERT_LOCATION) ) // Microsoft Developer Studio will insert additional declarations immediately before the previous line. endif // def ined(AFX_WINDSTARTPTPG_H__2A92A2A3_37AD_11D2_A2B9 0080C7D122A2__INCLUDED_) Here is the implementation of the WindStartPt.opp file: J/ WindStartPtPg.cpp : implementation file WW finclude “stdafx.h” #include “StdArx.h” finclude “WindStartPtPc.h” ifinclude “gepnt2d.h ifinclude “gepnt3d.h #include “adslib.h” iHinclude “geassign.h” iHinclude “acedads.h” fifdef _DEBUG fidefine new DEBUG_NEW fundet THIS_FILE static char THIS_FILEL] = _FILE_; Hendif MITTAL LLL IATL // CWindStarteteg property page IMPLEMENT_DYNCREATE(CWindStartPtPg, CPropertyPage) CWindStartPtPg: :CWindStartPtPg() :¢ CPropertyPage(CWindStartPtPg: : 100) ( // (AFX_DATA_INIT(CWindStartPtPg) m_dXVal = windInfo.m_dxval; m_dYVal = windInfo.m_dYVal; [MBC Dialogs and ObjetARX% UL Extentions 469 $$$ JA) )AFX_DATA_INIT ) CWindStartPtpg: :~CWindStartPtPg() ( ) void CWindStartPtPg: :DcDataExchange(CDataExchange*? pox) i CPropertyPage: :DoDataExchange(pDX) ; //{(AFX_DATA_MAP(CWindStartPtPg) DDX_Text(pDX, IDC_EDIT_XVAL, m_dXVal); DDX_Text(pDX, IDC_EDIT_YVAL, m_dYVal); 17) YAFX_DATA_MAP BEGIN_MESSAGE_MAP(CWindStartPtPg, CPropertyPage) //({AFX_MSG_MAP(CWindStartPtPg) ON_BN_CLICKED(IDC_BTN_PICKPT, OnBtnPickPt) 1) )APX_MSG_MAP END_MESSAGE_MAP() TTL TTL IATL /1 CWindStartPtPg messege handlers void CWindStartPtPg: :OrBtnPickPt() ( // Here we hide our modal dialog property sheet // to allow the user to pick a point AcGePoint3d pkPt: int retCode: GetParent()->GetParent ()->EnableWindow( TRUE) ; // Enable the parent window JJ which in our case is the 71 AutoCAD main window. GetParent()->ShowWindow(SW_HIDE); // Hide oure’ dialog GetParent()->GetParent()->SetFocus(); // Gived AutoCAD the focus. acedInitGet(NULL, NULL); retCode = acedGetPoint (NULL, “\nPick lower lefté corner of window: “, asDblArray(pkPt)); switch(retCode) { case RTCAN case RTNONE : pkPt.set(0.0, 0.0, 0.0); break; case RTNORM = break; ) mdXVal = pkPt.x: m_dYVal = pkPt.y: GetParent()->ShowWindow(Sh_SHOW): // Display ourg dialog again GetParent()->SetFocus(); // Reset the focus back? to ourselves GetParent()->GetParent()->EnableWindow( FALSE); //# Disable AutoCAD window GetParent()->EnableWindow(TRUE); // Enable oure dialog // Transfer the data values from the memberd variables // to the dialog. UpdateData( FALSE); ‘MEG Dialogs and ObjetARX's UI Extensions $$$. OnSetActive() BOOL CWindStartPtPs { // TODO: Add your specialized code here and/ore call the base class CPropertySheet* pParent; pParent = (CPropertySheet*) GetParent(): pParent->SetWizardButtons(PSWIZB_BACK |@ PSWIZB_FINISH’ return CPropertyPage::OnSetActive(): ) BOOL CWindStartPtP: { OnWizardFinish() J TODO: Add your specialized code here and/ore call the base class UpdateData( TRUE); return CPropertyPage::OnWizardFinish( ) As you can see, there is not too much difference between the OnBtnPickPt( func- tion of this application and the last application. Before we move on to our next application, le’s look atthe issue of Windows 95 and ObjectARX with reference to MFC-style property pages. There is a problem and here is my limited understanding of what happeas in Windows 95 (please don't quote me ‘on this). In early versions of Windows 95, when property pages from DLLs were used (ObjectARX applications are DLLs), the resources were read only and when a prop- erty page wanted to access a resource. An exception would be thrown, leaving you ting on the desktop or rebooting you computer. This was fixed in later versions of ‘Windows 95 and was never a problem in Windows NT: To get around this problem, you have to copy the resources into memory and access them from there. This applies to every property page that your property sheet uses. Let us illustrate this with some sample code: CWindStartPtPg::CWindStartPtPg() : CPropertyPage () ( //(AFX_DATA_INIT(CCustomi zeUi Page) m_dXVal = windInfo.startPt.x; m_dYVal = windInfo.startPt.y: 77) VAFX_DATALINIT HRSRC_hResInfo = FindResource(_hd11 Instance, & MAKEINTRESOURCE( TDD), RT_DIALOG) : HGLOBAL hRes = LoadResource(_hdl1 Instance, @ hRes Info); LPVOID pResource = LockResource(hRes); WORD size = SizeofResource(_hdIlInstance, hResInfo); LPCDLGTEMPLATE pResourceCopy =< (LPCDLGTEMPLATE)new BYTE [size]: memcpy((void *)pResourceCopy, pResource, size): InitModa1 Indirect (rResourceCopy); mpsp.dwFlags |= PSP_DLGINDIREC m_psp.pResource = pResourceCopy: Ignore what is in the AFX_DATA_INIT comments; that was placed there by the Class Wizard. The code that follows it must be placed in the constructor of each prop- erty page that you plan to use. I did not writ this code—my knowledge of Windows does not go that deep. Essentially, we are copying our resources and loading them into locked global memory. When we create our property page indirectly, we get the resources from memory. So now we dont have to worry about the read only problem. This also works in Windows NT. If this is « concern to you, make sure that this code is added to the constructor of every property page that you intend to use. ‘The ObjectARX documentation discusses this problem in more detail and it shows a class derived from CPropertyPage called IndirectPage. So if you are to use this method, derive your classes from IndirectPage as oppoved to CPropertyPage. However, you will have to include the header and implementation files in your pro- ject if you want to use this method. Both methods work—I prefer using the first method. In the next application we will demonstrate how to use a €TooIBar control in com- bination with a modeless dialog and also a CTreeView control. COMMON CONTROLS AND SAM CH7_6 In this application we demonstrate how to use a toolbar (ToolBar) and a tree view control (CTreeCtrl), The toolbar is launched from a modeless dialog and the tree view control is launched from the toolbar. The tree view control is embedded inside a modal dialog. We will start out by listing the Ch7_6Commands.pp file, which is where we launch our modeless dialog CWindoTypeDlg. Here is the listing: E APPLICATION MPC Dialogs and ObjecARX's UI Extensions 473 $$. TTT TLL TTD // ObjectARX defined commands ifinclude “StdAfx.h” diinclude “StdArx.h” extern CWindoTypeD1g *c_pWindoTypeDlg: extern CAcToolBar *g_pAcToolBar; “1 This is command *WINDO” void windo() ( // TODO: Implement the command CAcModuleResourceOverride resOverride; g_pWindoTypeDlg = new CWindoTypeD1g(acedGetAcadFrame()); 9_pWindoTypeD1g->Create( ID_WINDOTYPE, & acedGetAcadFrame()); g_pWindoTypeD1g->CenterWindow(): g_pWindoTypeD1g->Showilindow(SH_SHOW) : if(g_pAcToolBar != NULL && g_pAcTool Bare —>IsWindowVisible()) ( ((CButton*) g_pWindoTypeDlge —>GetD] gl tem( IDC_CHK_VISTB))->SetCheck(1): ) ‘When we first run the application, our toolbar will not be visible and therefore the check box will not have a check mark. Here we are checking to see if our toolbar point et is not NULL and also ifthe toolbar window visible; this could be the case when we run the application again later. If so, we turn on the check button in the modeless dia- log (we will see this in a while). Here is the listing for the Ch7_6. ™ HINSTANCE _hd11Instance =NULL ; // This command registers an ARX command. void AddCommand(const char* cmdGroup, const char*# cmdint, const char* cmcLoc, const int cmdFlags, const AcRxFunctionPtre) cmdProc, const int idlecal = -1); /1 NOTE: DO NOT edit the following lines. TH ((AFX_ARK_MSG: void InitApplication(); void Unloadapplication(); TT) YAPX_ARX_MSG // NOTE: D0 NOT edit the following lines. 7 (AFX_ARX_ADDIN_FUNCS 7/)) AFX_ARX_ADDIN_FUNCS CWindoTypeD1g* g_pWindoTypeD1g = NULL; CAcToolBar* —g_pAcToolBar = NULL: CTBGenWind* — g_pTBGenWnd = NULL: MALT TLL ITLL WV // Define the sole extension module object. ‘AC_IMPLEMENT_EXTENSION_MODULE(Ch7_6DLL); // Now you can use the CAcModuleResourceOverrided class in // your application to switch to the correcte resource instance. // Please see the ObjectARX Documentation for mored details MALTA TTL TTT 77 DLL Entry Point extern “C” (MBC Dialogs and ObjectARXs UI Extensions 475 $$ BOOL WINAPI D11Main(HINSTANCE hinstance, DWORD® dwReason, LPVOID /*1pReserved*/) fl if (dwReason == DLL_PROCESS_ATTACH) { hdl lInstance = hinstance; // Extension DLL one time initialization Ch7_6DLL.AttachInstance(hInstance); InitacUiDLLO; } else if (dwReason = DLL_PROCESS_DETACH) { // Terminate the library before destructors® are called Ch7_6DLL.DetachInstance(); ) return TRUE; // ok ) UMMM TATTLE // ObjectARK EntryPoint extern “C” AcRx: :AppRetCode acrxEntryPoint(AcRx: :AppMsgCode msg, void* pkt) { switch (msg) { case AcRx: :kInitappMsg: 11 Comment out the following line if your // application shold be locked into memory acrxDynami cLinker->unlockApplication(pkt); acrxDynamicLinker->registerAppMDIAware(pkt) ; Initapplication(): break: case ACRx::kUn1oadAppls UnloadApplication(); break: } return AcRx: :kRetOK: ) // Init this application. Register your // commands, reactors... void InitApplication() 6 ( // NOTE: DO NOT edit the following lines. J/((APX_ARX_INIT AddCommand(“CH7_APPS”, “WINDO”, ACRX_CMD_MODAL, windo); 1 D)\APX_ARX_INIT // T0D0: add your initialization functions acutPrintf(“\nType \"NINDO\” to execute” } JI Unload this application. Unregister a11 objects 11 registered in InitAppl ication. void UnloadApplication() ( // NOTE: DO NOT edit the following lines. I ({APX_ARX_EXIT acedRegCnds->removeGraup( “CH7_APPS”) 71) YAFX_ARX_EXIT // TODO: clean up your application } JJ This function registers an ARX command. // It can be used to read the localized command name // from a string table stored in the resources. void AddCommand(const char* cmdGroup, const char+o cmdInt, const char* cmdLoc, const int cmdFlags, const AcRxFunctionPtr cmdProc, const int idlccal) ( char cmdLocRes(65]; // If idlocal is not -1, it’s treated as an ID for // a string stored in the resources. if Cidlocal I= -1) [ J/ Load strings from the string table ande register the command. oadString(_hdilInstance, idlocal, cmdLocRes,@ acedRegCmds->addConmand(cmdGroup, cmdint, & cmdLocRes, cmdFlags, cndProc) ; (MBC Dialogs and ObjectARX's UI Extensions 477 $$ } else J/ idlocal is -1, so the ‘hard coded’ 1/ Vocalized function name is used. acedRegCmds->addConmand(cmdGroup, cndInt,& cmdLoc, cmdFlags, cmdProc); ) void drawWindo() ( switch(windInfo.mnWindType) ( case 0: AfxMessageBox(“Here I would have drawn a Rect window.”); break: case 1: AfxMessageBox(“Here I would have drawn a Arch window."): break: case 2: AfxMessageBox(“Here I would have drawn @ Apexe window.” break; ) ) ‘The only elements in this listing that I want to draw your attention to are the glob- al pointers to some of the windows that our application implements; we will discuss those later. CWindoTypeDig* g_pWindoTypeDlg = NULL; CAcToolBar* — g_pAcToolBar = = NULL CTBGenwind* 9_pTBGenWind = NULL: Briefly, the CWindoTypeDig is the modeless dialog we have seen before, with just some slight modifications. The CAcTooIBar is a class that is derived from the base class CToolBar and represents our application’s toolbar. The CTBGenWned class is derived from the genetic CWnd class. This window will be invisible in our applica- tion; its sole purpose is to process the messages coming from the toolbar (more on that later). 478 Figure 7.13 shows the modeless dialog for this application. comes Cer Figure 7.13 Modeless Dialog with Spin Controls Look closely at the Columns and Rows edit boxes: they have spin controls (CSpinCtri) attached to them. The spin controls are initialized in the OnInitDialog() function of the CWindoTypeDig clas. Table 7.7 shows the settings and resource IDs for the mod- cless dialog that is represented by the CWindoTypeDlg class. Resource Name and Type Resource ID Resource Settings Dialog -“Window Parameters - Modeless” IDD_WINDOTYPE Style = Popup’, Border = ‘Dialog Frame’, Title Ba, System menu Radio button ~ “Rect” IDC_RB_TYPERECT Visible, Group, Tab Stop and Auto. Caption = ‘BeRect? IDC_RB_TYPEARCH Visible, Tab Stop and Auto. Caption = '8&Arch’ ‘Table 7.7 Resource IDs for the Dialog Controls of Figure 7.13 MRC Dialogs and Objet ARX's UI Extensions 479 $$$ Type” era ece Resource ID Resource Settings Radio button="apeX” | IDC_RB_TYPEAPEX | Visible, Tab Stop and Auto, Caption = ‘apeBex’ Group Bax—"Window | IDC_STATIC Visible, Group Caption ="Window Type’ Bait box “Height” IDC_EDITHEIGHT | Visible, Tab Stop, Auto HScroll, Border Edit box— "Width? IDC_EDIT.WIDTH __ | Visible, Tab Stop, Auto HScroll, Border. Bai box ~ "Columns" IDC_EDIT_COLS Visible, Tab Stop, Auto HScroll, Border. Spin Control IDC_SPIN_COLS. Visible, Auto Buddy, Set buddy integer, Arcow keys, Alignment « Right’. Edit bor ~ “Rows” IDC_EDIT.ROWS | Visible, Tb Stop, Auto HScrol, Border. Spin Control IDC_SPIN ROWS Visible, Auto Buddy, See buddy integer, Arrow keys, Alignment = ‘Right Push button ~“« Pick” IDC_BTN_PICKPT Visible, Tab Stop Editbox-“8&X Value” | IDC_EDIT_XVAL Visible, Tab Stop, Auto HScroll, Border. Edit box-"&Y Value” | IDC_EDIT_YVAL Visible, Tab Stop, Auto HScroll, Border. (Check Box - ‘Show 1DC_CHK_VISTB Visible, Tab Stop, Auto. Toolbar’ All other elements IDC_STATIC Default properties see illustration, ‘Table 7.7 Resource IDs for the Dialog Controls of Figure 7.13 (continued) This modeless dialog is similar to the modeless dialog of the (b7_3.arx application with the exception of the handler for the radio button and the addition of spin con- trols, which are initialized in the OntnitDialog( function. Instead of listing the header and implementation files, I will list only the OntnitDialog() and the OnChkViewToolBar() functions. Here is the listing for the OntnitDialog() function: BOOL CWindoTypeD1g: :OnInitDialog() { CDialog: :OnInitDialoc(); CSpinButtonctr1* pspin; pSpin = (CSpinButtonctri*)o GetD1 gI tem( IDC_SPIN_COLS); pSpin->SetRange(1, 10); pSpin = (CSpinButtonCtri*)o GetDigItem(1DC_SPIN_ROWS); pSpin->SetRange(1, 10); return TRUE: // return TRUE unless you set the focus to a control 71 EXCEPTION: OCX Property Pages? should return FALSE } This is pretty straightforward; we are just initializing the range of our spin controls. ‘When the Columns and Rows edit boxes are active, you can also use the arrow keys on the keyboard to adjust the values of the edit control. Here is the listing for the OnChkViewToolBar( function, which is called asa result of selecting the check box to display the toolbar: void CWindoTypeD1g: :OnChkViewToolBar() { CMDIFrameWnd *pAcadFrame = acedGetAcadFrame(); if(g_pAcToolBar != NULL && g_pAcTool Bare —>IsWindowVisible()) ( ((CButton*) GetD1gitem( 1DC_CHK_VISTB)) =>SetCheck(0); pAcadFrame->ShowControlBar(g_pAcToolBar, FALSE, # FALSE); GetO1gI tem( 1DC_8TN_HIDEDLG)->EnableWindow( FALSE) ; }