In the last post we looked at the code behind an ObjectARX module exposing AutoCAD's Properties Palette for use from managed .NET languages. Thanks again to Cyrille Fauvel for providing this implementation. In this post we're going to move right onto using this implementation from C#.
First things first: if you didn't understand much of what was said in the previous post in this series, Don't Panic! (Yes, that's a quick reference to The Hitchhiker's Guide to the Galaxy.) The actual implementation details aren't particularly important - you only really need to understand them if you want to expose additional interfaces to .NET in the same way (such as IFilterableProperty, the interface discussed in this previous post and something I expect to extend the application to handle, at some point).
Here is the project that contains both the ObjectARX module implementing exposing these interfaces and the sample we're showing today. You simply have to make sure the .NET module can find our asdkOPMNetExt.dll module (ideally both this and your .NET module should be located under AutoCAD's program folder).
Then you can simply implement code, as in the below sample, which loads and makes use of interfaces exposed by this assembly.
Here’s the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows.OPM;
using System;
using System.Reflection;
using System.Runtime.InteropServices;
namespace OPMNetSample
{
#region Our Custom Property
[
Guid("F60AE3DA-0373-4d24-82D2-B2646517ABCB"),
ProgId("OPMNetSample.CustomProperty.1"),
// No class interface is generated for this class and
// no interface is marked as the default.
// Users are expected to expose functionality through
// interfaces that will be explicitly exposed by the object
// This means the object can only expose interfaces we define
ClassInterface(ClassInterfaceType.None),
// Set the default COM interface that will be used for
// Automation. Languages like: C#, C++ and VB allow to
//query for interface's we're interested in but Automation
// only aware languages like javascript do not allow to
// query interface(s) and create only the default one
ComDefaultInterface(typeof(IDynamicProperty2)),
ComVisible(true)
]
public class CustomProp : IDynamicProperty2
{
private IDynamicPropertyNotify2 m_pSink = null;
// Unique property ID
public void GetGUID(out Guid propGUID)
{
propGUID =
new Guid("F60AE3DA-0373-4d24-82D2-B2646517ABCB");
}
// Property display name
public void GetDisplayName(out string szName)
{
szName = "My integer property";
}
// Show/Hide property in the OPM, for this object instance
public void IsPropertyEnabled(object pUnk, out int bEnabled)
{
bEnabled = 1;
}
// Is property showing but disabled
public void IsPropertyReadOnly(out int bReadonly)
{
bReadonly = 0;
}
// Get the property description string
public void GetDescription(out string szName)
{
szName =
"This property is an integer";
}
// OPM will typically display these in an edit field
// optional: meta data representing property type name,
// ex. ACAD_ANGLE
public void GetCurrentValueName(out string szName)
{
throw new System.NotImplementedException();
}
// What is the property type, ex. VT_R8
public void GetCurrentValueType(out ushort varType)
{
// The Property Inspector supports the following data
// types for dynamic properties:
// VT_I2, VT_I4, VT_R4, VT_R8,VT_BSTR, VT_BOOL
// and VT_USERDEFINED.
varType = 3; // VT_I4
}
// Get the property value, passes the specific object
// we need the property value for.
public void GetCurrentValueData(object pUnk, ref object pVarData)
{
// TODO: Get the value and return it to AutoCAD
// Because we said the value type was a 32b int (VT_I4)
pVarData = (int)4;
}
// Set the property value, passes the specific object we
// want to set the property value for
public void SetCurrentValueData(object pUnk, object varData)
{
// TODO: Save the value returned to you
// Because we said the value type was a 32b int (VT_I4)
int myVal = (int)varData;
}
// OPM passes its implementation of IDynamicPropertyNotify, you
// cache it and call it to inform OPM your property has changed
public void Connect(object pSink)
{
m_pSink = (IDynamicPropertyNotify2)pSink;
}
public void Disconnect() {
m_pSink = null;
}
}
#endregion
#region Application Entry Point
public class MyEntryPoint : IExtensionApplication
{
protected internal CustomProp custProp = null;
public void Initialize()
{
Assembly.LoadFrom("asdkOPMNetExt.dll");
// Add the Dynamic Property
Dictionary classDict = SystemObjects.ClassDictionary;
RXClass lineDesc = (RXClass)classDict.At("AcDbLine");
IPropertyManager2 pPropMan =
(IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(lineDesc);
custProp = new CustomProp();
pPropMan.AddProperty((object)custProp);
}
public void Terminate()
{
// Remove the Dynamic Property
Dictionary classDict = SystemObjects.ClassDictionary;
RXClass lineDesc = (RXClass)classDict.At("AcDbLine");
IPropertyManager2 pPropMan =
(IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(lineDesc);
pPropMan.RemoveProperty((object)custProp);
custProp = null;
}
}
#endregion
}
A few comments on what this code does…
It defines a class for our custom dynamic property (CustomProp), for which we need a unique GUID (and yes, by definition GUIDs are unique, but if you use the same one twice it ceases to be :-), and implement various callbacks to indicate the name, type, description and writeability of the property, as well as methods to get and set the property value. For this example our property is called “My integer property”, and – guess what? – it’s an integer, and has been hardcoded to have the value 4. We’re not actually storing the data being exposed via this property, but in a real-world application you would probably store it either as XData attached to the object, inside an XRecord in the object’s extension dictionary or in an external database of some kind.
In the rest of the code we’re defining functions that are called when the module is loaded and when AutoCAD terminates (see this previous post for more information on this mechanism). We use the Initialize() callback to load our mixed-mode module for which we presented the code in the last post (asdkOPMNetExt.dll) and then go ahead and instantiate our property and attach it to line objects.
When we build and load the sample, making sure the Properties Palette is visible (using the PROPS command or by double-clicking on the line) and selecting a line we’ve just drawn, we should see our dynamic property appear, as shown in this image.
If you don’t see the property appear, the application is probably having trouble loading asdkOPMNetExt.dll: as mentioned earlier I have placed both this and the sample application in the root folder of my AutoCAD installation. If you’re not sure whether the module is loaded properly, you can step through the Initialize() function in the debugger or add a simple command to your code which will obviously only work if your application has been loaded (the Assembly.LoadFrom() call will throw an exception if it doesn’t find the module, and if an exception is thrown from Initialize() the application will not be loaded, as described in this previous post).
For the sake of simplicity I’m going to leave this basic sample as having just one, hardwired property: hopefully it’s obvious how the application could be extended to handle more properties and to store these properties with their objects (if not, let me know by posting a comment and I’ll put together a more extensive example when I get the chance).