Sie sind auf Seite 1von 15

Adding Settings to the AutoCAD Options Dialog with VB.

NET
Mike Tuersley Ohio Gratings, Inc.

CP118-1

Almost every program has default or user settings such as folder locations, printing devices, color, or font settings. The question is how and where to let the user change them when needed. The solution is simple: place them with settings in AutoCAD by adding a tab for your program to the Options dialog box. Now the information is in a central location that is easy to modify and familiar to the CAD user. This session will show the .NET programmer how to add a tab to the Options dialog and where to store the information, including the pros and cons for each possibility. Storage options covered are the registry, AutoCAD or application configuration file, as well as a custom XML configuration file. Two of the subtopics shown will be how to serialize and deserialize XML data.

About the Speaker:


Mike works for Ohio Gratings Inc. as the Technical Lead and Senior Developer focusing on enterprise level automation using the latest Microsoft technologies. Before this, he was the founding member and senior lead application developer for RAND IMAGINiTs National Services Team, which focuses on providing customization services to meet customer visions. Mike has been customizing AutoCAD since release 2.5 and he is a past columnist for Cadalyst magazine. For 6 years, he wrote the AutoCAD "Help Clinic" and the "CAD Clinic"; the latter focused on teaching AutoCAD programming topics. Mike can be contacted at mike.tuersley@mtuersley.com

Adding Settings to the AutoCAD Options Dialog with VB.NET

Introduction
One of the key design goals of a developer, or hobbyist programmer, is to have their application addin appear as if it is part of the original program; to seamlessly integrate it into the host environment. To accomplish this lofty goal, one must use the same user interface (UI) constructs as the host application wherever possible. Taking this into consideration, what is the best method for allowing the AutoCAD user to adjust whatever settings your program requires? There are many different approaches. Some have added special commands accessible from the command line, included commands within their unique interface, required users to modify external files such as INI files. The easiest and most logical solution is to allow access somewhere that is already intuitive to the user and easily accessible to them the AutoCAD Options dialog. In addition to looking at how to add a tab to the Options dialog, I will also look at how to store and recall the data that will be placed there using methods such as XML Serialization and Isolated Storage.

Accessing AutoCAD's Option Dialog box and Adding a Tab


The code required to access the Options dialog box is relatively simple and does not require a lot of code to implement. So lets get started! 1. First, start a new Windows Forms Control Library project.

Figure 1. Visual Studio New Project dialog

2. Add references to the AutoCAD managed libraries: acmgd.dll and acdbmgd.dll 3. Go to the code view of the user control [UserControl1 unless its been renamed] 4. Add the Imports statements for Windows Forms, AutoCADs Application and ApplicationServices. I like to add them like so to eliminate typing later: 2

Adding Settings to the AutoCAD Options Dialog with VB.NET

Imports System.Windows.Forms Imports cadAppSrvs = Autodesk.AutoCAD.ApplicationServices Imports cadApp = Autodesk.AutoCAD.ApplicationServices.Application

5. Then add the following code:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Public Sub OnOk() MessageBox.Show("OnOk") End Sub Public Sub OnCancel() MessageBox.Show("OnCancel") End Sub Public Sub OnHelp() MessageBox.Show("OnHelp") End Sub Private Shared Sub Application_TabbedDialog( _ ByVal sender As Object, _ ByVal e As cadAppSrvs.TabbedDialogEventArgs) Dim ctrl As UserControl1 = New UserControl1() e.AddTab(".NET Demo1", _ New cadAppSrvs.TabbedDialogExtension( _ ctrl, _ New cadAppSrvs.TabbedDialogAction(AddressOf ctrl.OnOk), _ New cadAppSrvs.TabbedDialogAction(AddressOf ctrl.OnCancel), _ New cadAppSrvs.TabbedDialogAction(AddressOf ctrl.OnHelp) _ ) _ ) End Sub <Autodesk.AutoCAD.Runtime.CommandMethod("tabdemo")> _ Public Shared Sub DoIt() AddHandler cadApp.DisplayingOptionDialog, _ AddressOf Application_TabbedDialog End Sub

To explain the code above: Lines 1-12: stubs for the three events available for the Options Tab Line 13: starts the hook that will add the UserControl as a tab in the Options tab collection whenever the Options command is called within AutoCAD Line 16: instantiates a new instance of the user control Line 17-24: adds the user control to AutoCADs Option dialog box as another tab including the event handlers for the Option dialogs OK, CANCEL and HELP buttons 6. Save the project 7. Start the debug command 8. Whenever AutoCAD opens, type in NETLOAD and browse to the location of the dll generated by this project 9. After netloading, type tabdemo into the command line to activate the dll

Adding Settings to the AutoCAD Options Dialog with VB.NET

10. Go into AutoCADs Options by either menu selection [Tools>Options] or typing in OP at the command line. If all worked well, a new tab called .AU2009 should be included in the Options dialog:

Figure 2. New Tab in Options

That is as hard as it gets to add a tab to the Options dialog box. Now code will need added to the OnOk, OnCancel and OnHelp subroutines to handle those specific functions and we will do it further on within this document. In addition to adding the usercontrol to the Options dialog, the same control could be added to the Drafting Settings and Customization dialogs by adding these lines to the DoIt subroutine to accommodate them:
AddHandler cadApp.DisplayingDraftingSettingsDialog, _ AddressOf Application_TabbedDialog AddHandler cadApp.DisplayingCustomizeDialog, _ AddressOf Application_TabbedDialog

Storage considerations
Now that we have a place for the CAD user to adjust any settings our program requires, we need to consider how data should be stored. Typically, programmers seem to choose one of two options: 1. Registry the programmer stores all data within the registry. Some think this makes it more secure and easier to hide than using a file on the hard drive. This is a bad idea for three reasons a. With the free registry tracking software available, the idea that this is hidden or safer than a physical file isnt valid. Some of the registry cleaning software can even remove the programs data if the programmer does not store it correctly. b. The end user may not have the rights necessary to change the data. c. Even under the most stringent precautions, incorrectly writing to the registry can crash a computer.

Adding Settings to the AutoCAD Options Dialog with VB.NET

2. Physical file while better than the registry, there are more hurdles here than appear at first glance. Some of the questions involved are: a. Location where will the file be located? Some place hard coded but what if the file moves? Specified at install but how to know where? b. Format what is the best format to store the data in? Simply write each value to a line and track which line is which when reading back? Using Comma-Separated Values (CSV) then worrying about tracking locations and hoping the user doesnt add data that includes the delimiter? Or having to re-dimension arrays/collections if the amount of fields changes with either of these? Use an INI file but having to write a custom parser? These are just some of the things to consider when writing a custom application that will be delivered to a client whether the client is someone who paid for this, or someone within your own organization. Unfortunately there is no standard for where to store data; therefore, I offer a compilation of what has worked and what hasnt over the past twenty years of creating custom applications for Autodesk products. Programming information that needs to be stored and/or retrieved generally falls into three categories: Admin the data that the program requires in order to function that will rarely be changed and, usually, only by an administrator-level person. Standard the data that is specific to a group of users; office or project standards; the data is changed less frequently than the User data and typically by a central person User the data that is specific to the user using the program at that particular time; each user has their own unique settings that they change to suit their tastes

Figure 3. Simple Data Storage Overview

Adding Settings to the AutoCAD Options Dialog with VB.NET

To accommodate these three types of data, well use a combination of registry and physical files with a few twists added to make it easy. Figure 3 depicts the overall structure of the end solution. We will be using the registry for Admin data and local file(s) for User data. Standard data would be placed in a networked location if this application had such a function. In some cases such as dealing with remote users, the Standard type data could be stored local to the user but in a different location than the users personal data (since the sample application detailed within this document is selfcontained, there will be no networked requirement). So now that the general location of the data has been determined, what is the format that will be used? To solve this, lets consider the end result. Once the data has been read into the application, it would be nice to have the data easily accessible throughout the program. This would dictate creating a custom object to hold the data since it is easier to use than passing around arrays, collections or hashtables and trying to remember which data is in which location. In order to read/write the custom object to a physical file, we will need to first create a parser to read in the values and distribute them to the correct properties of the custom object. This parser will also be required to write out the data to the physical file. Finally, the parser should be generic enough to handle additions/subtractions of properties that could occur during the course of the project as well as future modifications to it. By now, you must be thinking there is an awful lot of work involved here. It would be nice if there was a way to have this functionality without writing the parser, right? Well there is a way to handle the parser in 4 to 5 lines of code use XML. XML, the eXtensible Markup Language, was created for just this scenario. First, the language creates meaningful, self-describing files so its easier to read than using something like CSV. Second, XML includes its own internal functionality for saving the data to a file called Serialization. And, third, it can be done without creating XML Schema Definition (XSD) or Document Structure Definition (DSD) files. If you have never used XML files before, or were frustrated when attempting to use them, you will not believe how simple this is!

Serializing & Deserializing XML Data


As I said, this is very easy to do but dont take this out of context. There is a lot of information and learning that goes into using XML data. This is just a quick example of how to take advantage of this technology. To begin, create a structure called UserDefaults and include String properties for PrinterName and PrinterPaper. These represent two of the three options that will be specific to the user. Now to make this structure serializable, add the following attribute at the beginning of the structure:
<Serializable()> _ Public Structure UserDefaults

This attribute tells the system that the structure can be serialized. It is important to note that only the basic data types defined within the .NET Framework can be serialized; if using custom objects as properties, more work is required than just adding the attribute. Now, as promised, 4 lines of code to save out our structure object to disk:

Adding Settings to the AutoCAD Options Dialog with VB.NET

1 2 3 4

Dim writer As New XMLSerializer(GetType($MyStructureName$)) Dim file As New StreamWriter($FileNameToSaveAs$) writer.Serialize(file, $MyStructureObject$) file.Close()

Line 1 declares and instantiates an instance of an XMLSerializer object using the object type of our structure. Line 2 declares and instantiates an instance of a standard StreamWriter object and we pass the name of the output file Line 3 does the entire writing process Line 4 cleans up the StreamWriter object The end result is a file that looks similar to this:
<UserDefaults> <PrinterName>Whatever the value is</PrinterName> <PrinterPaper>Whatever the value is</PrinterPaper> </UserDefaults>

As shown, the resulting file is a lot easier to read than a CSV file. To read the file back into our object, this code is required:
1 1 1 4 Dim reader As New XmlSerializer(GetType($MyStructureName$)) Dim file As New StreamReader($FileName$) Dim fileData = CType(reader.Deserialize(file), $MyStructureObject$) file.Close()

Line 1 declares and instantiates an instance of an XMLSerializer object using the object type of our structure. Line 2 declares and instantiates an instance of a standard StreamReader object and we pass the name of the saved file Line 3 does the entire reading and populating our structure process Line 4 cleans up the StreamReader object Notice that there is no need to read each line and populate each property of our new structure object which would be required when importing data from a CSV or similar file.

Adding Settings to the AutoCAD Options Dialog with VB.NET

Isolated Files and Storage


Now that we have a file type to use to store our settings, where do we store the file? And, if multiple users share the computer, how do you keep separate files for each users preferences? This is a major consideration and one that could lead to endless headaches if you are not sure of your end users environment. Why? Well your code might lack the permission to write to the file since it inherits from the end users permission level. For example, the user might be running your code in Windows in a locked-down environment or under a roaming user profile. In these cases, the network administrator may have placed severe limitations on the user's rights to write files. So what is the simplest solution? It is called isolated storage. Isolated Storage assigns each user of the system an abstract location called a data compartment. Within the data compartment, there can be one or more stores. Each store can be isolated from one another by both the user and/or the assembly so if two users run the same assembly, and that assembly uses isolated storage, then the two stores are created and both distinct for each user. Isolated storage is a special folder on the hard drive which allows your application to store its application specific data. Isolated stores are located in each users AppData folder under either Local\ or Remote\IsolatedStorage directory. Windows Explorer folder settings must be set to show hidden files and folders in order to see isolated storage. To start programming with Isolated Storage, a reference is needed to System.IO.IsolatedStorage Then its as simple as:
Dim isolatedStore As IsolatedStorageFile = _ IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _ IsolatedStorageScope.Assembly, Nothing, Nothing) Dim isoStream As New IsolatedStorageFileStream(FileName, _ FileMode.Append, FileAccess.Write, isolatedStore)

From here, you use normal System.IO file handling methods such as XML serialization that was discussed in the previous section. As seen in the code above, there is no need to specify the path as the isolated storage location is handled internally by Windows.

Storing program settings


Ok, now well add some functionality to the program that we have so far:

Adding Settings to the AutoCAD Options Dialog with VB.NET

Figure 4. Final Solution

To the UserControl, add: 2 Group boxes 4 Labels 2 Textboxes 2 Comboboxes 1 Button

Arrange and set them up as seen above in Figure 4. The data is separated into the three groupboxes to simulate the three types of data. When this is complete, lets focus on the specific functions needed to save the data into the required locations. As demonstrated in the previous section, we will implement a serialization function to our structure:

1 2 3 4 5 6 7 8 9 10 11 12

Public Sub SerializeMe() Try Dim isolatedStore As IsolatedStorageFile = _ IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _ IsolatedStorageScope.Assembly, Nothing, Nothing) Dim isoStream As New IsolatedStorageFileStream(Options.dat, _ FileMode.Append, FileAccess.Write, isolatedStore) Dim writer As New XmlSerializer(GetType(UserDefaults)) Using file As New StreamWriter(isoStream) writer.Serialize(file, Me) End Using Catch ex As Exception nothing for now End Try End Sub

Adding Settings to the AutoCAD Options Dialog with VB.NET

This code does two things. First it finds the users isolated storage location and then serializes the structure out to a file. We are using Isolated Storage so we do not need to worry about where the file is located or if it will ever move there is no need to worry or code for any of that any more if you use the Isolated Storage mechanism. Lines 5-8 do the actual serialization and include the USING construct so we do not need to worry about closing the objects or disposing of them.

Retrieving program settings


After reading and digesting all of the Saving procedures, the Retrieval process is rather straightforward. As demonstrated in the previous section, we will implement a serialization function to our structure:
1 2 3 4 5 6 7 8 9 10 11 12 Public Sub DeserializeMe() Try Dim isolatedStore As IsolatedStorageFile = _ IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _ IsolatedStorageScope.Assembly, Nothing, Nothing) Dim isoStream As New IsolatedStorageFileStream(Options.dat, _ FileMode.Append, FileAccess.Write, isolatedStore) Dim reader = New XmlSerializer(GetType(UserDefaults)) Using file As New StreamReader(isolatedStream) Dim fileData = CType(reader.Deserialize(file), UserDefaults) End Using Catch ex As Exception nothing for now End Try End Sub

Applying
Lastly, we need to look at how to turn on the APPLY button functionality of the Options dialog if you havent noticed, it has been disabled so far. This is really simple every place where a value is changed such as a Combobox SelectedValue event add the following line of code:
TabbedDialogExtension.SetDirty(Me, True)

This tells AutoCADs internal Options dialog handler that a value has changed that needs saved and, thus, the Apply button will be enabled. When the Apply button is selected, the button will automatically become disabled once the OnApply code has run. To manually disable the Apply button, use the same line of code and change True to False.

Closing
This completes the topic on Adding Settings to the AutoCAD Options Dialog with VB.NET. Hopefully, the discussion of how and where to store your future programming data as well as some of the specific tips and tricks on XML and VB.NET in general, will prompt you to continue exploring in these areas. The complete source code for this session will be available on the Autodesk University website and at www.mtuersley.com/downloads. If you have any questions pertaining to this topic, please email me at mike.tuersley@mtuersley.com. Make sure to include a reference to this session within the email subject so it will not get lost amongst the other email I receive. Thank you for your time and I hope you enjoy your time here at AU2009! 10

Adding Settings to the AutoCAD Options Dialog with VB.NET

Complete Source Code for Final Solution


Imports Imports Imports Imports Imports Imports Imports Imports Imports Imports Autodesk.AutoCAD.Runtime Autodesk.AutoCAD.ApplicationServices Autodesk.AutoCAD.DatabaseServices Autodesk.AutoCAD.EditorInput Autodesk.AutoCAD.Geometry Autodesk.AutoCAD.PlottingServices Autodesk.AutoCAD.Windows System.Collections.Specialized cadApp = Autodesk.AutoCAD.ApplicationServices.Application cadAppSrvs = Autodesk.AutoCAD.ApplicationServices

Public Class OptionsTab2 Public Const ConfigFileName As String = "OptionsTab.dat" Private Shared _mySettings As New UserDefaults Private _DefaultFolder As String = String.Empty #Region "Event Handling" Public Sub OnApply() 'save settings SaveSettings() End Sub Public Sub OnOk() 'save settings SaveSettings() End Sub Public Sub OnCancel() 'do nothing End Sub Public Sub OnHelp() 'do nothing End Sub #End Region <Autodesk.AutoCAD.Runtime.CommandMethod("tabdemo2")> _ Public Shared Sub DoIt() AddHandler cadApp.DisplayingOptionDialog, AddressOf Application_TabbedDialog End Sub

11

Adding Settings to the AutoCAD Options Dialog with VB.NET

Private Shared Sub Application_TabbedDialog(ByVal sender As Object, _ ByVal e As cadAppSrvs.TabbedDialogEventArgs) Dim ctrl2 As OptionsTab2 = New OptionsTab2() e.AddTab(".NET Demo2", _ New cadAppSrvs.TabbedDialogExtension( _ ctrl2, _ New cadAppSrvs.TabbedDialogAction(AddressOf ctrl2.OnOk), _ New cadAppSrvs.TabbedDialogAction(AddressOf ctrl2.OnApply), _ New cadAppSrvs.TabbedDialogAction(AddressOf ctrl2.OnCancel), _ New cadAppSrvs.TabbedDialogAction(AddressOf ctrl2.OnHelp) _ ) _ ) End Sub #Region "Control Code" Private Sub btnBlockFolder_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBlockFolder.Click 'set dialog description dlgFolderBrowse.Description = "Select the folder..." dlgFolderBrowse.SelectedPath = Me.txtBlockFolder.Text dlgFolderBrowse.ShowNewFolderButton = True 'display it and test for return value If dlgFolderBrowse.ShowDialog(Me).Equals(DialogResult.OK) Then 'set the textbox txtBlockFolder.Text = dlgFolderBrowse.SelectedPath End If 'set dirty flag cadAppSrvs.TabbedDialogExtension.SetDirty(Me, True) End Sub Private Sub cboPlotter_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboPlotter.SelectedIndexChanged 'printing device changed so clear then 'grab media specific to selected device cboPaper.Items.Clear() cboPaper.Text = String.Empty GetDeviceMedia(cboPlotter.Text, cboPaper) 'store the value _mySettings.PrinterName = cboPlotter.Text 'set dirty flag cadAppSrvs.TabbedDialogExtension.SetDirty(Me, True) End Sub Private Sub cboPaper_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles cboPaper.SelectedIndexChanged 'store the value _mySettings.PrinterName = cboPaper.Text 'set dirty flag cadAppSrvs.TabbedDialogExtension.SetDirty(Me, True) End Sub

12

Adding Settings to the AutoCAD Options Dialog with VB.NET

Private Sub OptionsTab2_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load _mySettings = New UserDefaults _mySettings.DeserializeMe() 'populate printing devices LoadPrintingDevices2Combobox(Me.cboPlotter) _DefaultFolder = My.Settings.DefaultFolder txtBlockFolder.Text = _DefaultFolder cboPlotter.SelectedText = _mySettings.PrinterName If _mySettings.PrinterName IsNot Nothing Then If Not _mySettings.PrinterName.Equals(String.Empty) Then GetDeviceMedia(_mySettings.PrinterName, Me.cboPaper) cboPaper.SelectedText = _mySettings.PrinterPaper End If End If ' PictureBox1.Image = GetEmbeddedIcon("OptionsTab.au09-badge1.jpg") 'set dirty flag cadAppSrvs.TabbedDialogExtension.SetDirty(Me, False) End Sub #End Region #Region "Functions" ''' <summary> ''' Gets the media names from the AutoCAD printing device ''' </summary> ''' <param name="DevName">String - Name of printing device</param> ''' <param name="cb">ComboBox - combo to populate</param> ''' <remarks></remarks> Public Sub GetDeviceMedia(ByVal DevName As String, ByRef cb As ComboBox) ' Retrieve printing media names Dim medname As String = String.Empty Dim psv As PlotSettingsValidator = PlotSettingsValidator.Current Dim ps As New PlotSettings(True) Using ps ' refresh the list psv.SetPlotConfigurationName(ps, DevName, Nothing) psv.RefreshLists(ps) Dim medlist As StringCollection = psv.GetCanonicalMediaNameList(ps) For i As Integer = 0 To medlist.Count - 1 'ed.WriteMessage(vbLf & "{0} {1}", i + 1, medlist(i)) cb.Items.Add(medlist(i)) Next cb.Sorted = True End Using End Sub Public Shared Function GetEmbeddedIcon(ByVal strName As String) As Bitmap 'pulls embedded resource Return New System.Drawing.Bitmap _ (GetExecutingAssembly.GetManifestResourceStream(strName)) End Function

13

Adding Settings to the AutoCAD Options Dialog with VB.NET

''' <summary> ''' Populates supplied combobox with AutoCAD printing device names ''' </summary> ''' <param name="cb">ComboBox - combo to populate</param> ''' <remarks></remarks> Public Shared Sub LoadPrintingDevices2Combobox(ByRef cb As ComboBox) ' Load all AutoCAD printer names to a combobox Dim devname As String = String.Empty Dim psv As PlotSettingsValidator = PlotSettingsValidator.Current Dim devlist As StringCollection = psv.GetPlotDeviceList() For i As Integer = 0 To devlist.Count - 1 'ed.WriteMessage(vbLf & "{0} {1}", i + 1, devlist(i)) cb.Items.Add(devlist(i)) Next End Sub Private Sub SaveSettings() 'test for value If Not txtBlockFolder.Text.Equals(String.Empty) Then 'set value and save _DefaultFolder = txtBlockFolder.Text My.Settings.DefaultFolder = _DefaultFolder End If _mySettings.SerializeMe() 'reset dirty flag cadAppSrvs.TabbedDialogExtension.SetDirty(Me, False) End Sub #End Region End Class Imports System.IO Imports System.IO.IsolatedStorage Imports System.Xml.Serialization <Serializable()> _ Public Structure UserDefaults Public Const ConfigFileName As String = "OptionsTab.dat" Private printerNameField As String Private printerPaperField As String Public Property PrinterName() As String Get Return Me.printerNameField End Get Set(ByVal value As String) Me.printerNameField = value End Set End Property

14

Adding Settings to the AutoCAD Options Dialog with VB.NET

Public Property PrinterPaper() As String Get Return Me.printerPaperField End Get Set(ByVal value As String) Me.printerPaperField = value End Set End Property #Region "Methods" Public Sub DeserializeMe() Try Dim isolatedStore As IsolatedStorageFile = _ IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _ IsolatedStorageScope.Assembly, Nothing, Nothing) Dim isolatedStream As New IsolatedStorageFileStream(ConfigFileName, _ FileMode.Open, isolatedStore) Dim reader = New XmlSerializer(GetType(UserDefaults)) Using file As New StreamReader(isolatedStream) Dim fileData = CType(reader.Deserialize(file), UserDefaults) PrinterName = fileData.PrinterName PrinterPaper = fileData.PrinterPaper End Using Catch ex As Exception End Try End Sub Public Sub SerializeMe() Try Dim isolatedStore As IsolatedStorageFile = _ IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _ IsolatedStorageScope.Assembly, Nothing, Nothing) Dim isoStream As New IsolatedStorageFileStream(ConfigFileName, _ FileMode.Append, FileAccess.Write, isolatedStore) Dim writer As New XmlSerializer(GetType(UserDefaults)) Using file As New StreamWriter(isoStream) writer.Serialize(file, Me) End Using Catch ex As Exception End Try End Sub #End Region End Structure

15

Das könnte Ihnen auch gefallen