Back in these previous posts, we introduced an ObjectARX module that exposed AutoCAD’s Property Palette to .NET (thanks again to Cyrille Fauvel for his work on this :-). In this post we’re going to build on this work to demonstrate how to integrate properties with more advanced editing controls into the Property Palette.
[Note: this sample does not deal with persisting the data with an object. If you want to store your data with a particular object, I suggest taking a look at this previous post which demonstrates how to use Xdata to do so.]
This code is being built on top of the sample project provided with the OPM .NET implementation, which I’ve posted previously for AutoCAD 2007-2009 and for AutoCAD 2010. It defines a custom dynamic property – much like the original CustomProp, an integer property – but this one implements a new interface, IAcPiPropertyDisplay. This special interface allows you to – among other things – specify a custom editing control, which gets instantiated by the Property Palette and is then used to allow editing of the property.
The ObjectARX documentation contains this information on the main method we’re interested in implementing, GetCustomPropertyCtrl():
Supplies a custom ActiveX control for property display/edit on a per-property basis.
Before attempting to pick one of the ACPEX stock custom ActiveX controls for property display/edit, the Property Inspector calls this method to see if the object/command wishes to supply its own custom ActiveX control. This method is called on a per-property basis (both static and dynamic) for an object/command whose properties are active for display in the Property Inspector.
The supplied custom control must implement IPropertyEditCtrl to be displayed for property value display/edit. The Property Inspector releases the output BSTR pointer after its use.
We want to enable this interface for use from .NET. In the previous cases we’ve had to do some extra work to expose or enable our Property Palette interfaces for use from .NET, but luckily this work has already been done for AutoCAD’s Tool Palette implementation. We can now simply add an assembly reference to AcTcMgd.dll and include the Autodesk.AutoCAD.Windows.ToolPalette namespace in our code.
[Note: I found this assembly in the inc-win32 folder of the ObjectARX SDK for AutoCAD 2010 (I’m running AutoCAD 2010 on Vista 32-bit), and it’s not clear to me when exactly we delivered this implementation - whether it was done for AutoCAD 2010 or beforehand. If AcTcMgd.dll is not available for your version of AutoCAD, it is still an option to expose this interface for use from .NET yourself, following the examples shown when we originally introduced the OPM .NET implementation.]
When we implement our GetCustomPropertyCtrl() method, we then need to set the string output argument to the ProgId of the editing control we wish to use. To decide an appropriate ProgId, I looked into the Registry, to identify the candidates:
It’s also possible to implement your own truly custom editing control exposing the IPropertyEditCtrl interface, but that’s way beyond the scope of this post. :-)
For our custom distance property, we’re going to use a (standard) custom control that allows the user to select two points in the drawing; the distance between them then gets set as our dynamic property’s value. The ProgId for this control is “AcPEXCtl.AcPePick2PointsCtrl.16”.
Otherwise the methods of IAcPiPropertyDisplay should be fairly straightforward to understand, although perhaps IsFullView() requires a little explanation: the “visible” parameter actually indicates whether the control should take up the whole of the row – obscuring the property name – which is why we set it to false.
One final note: I’ve remapped the GUID for the property, to prevent it from conflicting with the previous CustomProp implementation.
Here’s the C# code for our custom distance property:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows.OPM;
using Autodesk.AutoCAD.Windows.ToolPalette;
using System;
using System.Reflection;
using System.Runtime.InteropServices;
namespace OPMNetSample
{
#region Our Custom Distance Property
[
Guid("78286EBD-7380-4bb5-9EA6-1742677FA2E3"),
ProgId("OPMNetSample.DistanceProperty.1"),
ClassInterface(ClassInterfaceType.None),
ComDefaultInterface(typeof(IDynamicProperty2)),
ComVisible(true)
]
public class DistanceProp : IDynamicProperty2, IAcPiPropertyDisplay
{
private IDynamicPropertyNotify2 _pSink = null;
private double _value = 0.0;
#region IDynamicProperty2 methods
// Unique property ID
public void GetGUID(out Guid propGUID)
{
propGUID =
new Guid("78286EBD-7380-4bb5-9EA6-1742677FA2E3");
}
// Property display name
public void GetDisplayName(out string szName)
{
szName = "Distance";
}
// 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 distance property is from picking two points";
}
// 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 (2), VT_I4 (3), VT_R4 (4), VT_R8 (5),
// VT_BSTR (8), VT_BOOL (11), and VT_USERDEFINED (29).
varType = 4; // VT_R4
}
// Get the property value, passes the specific object
// we need the property value for.
public void GetCurrentValueData(object pUnk, ref object pVarData)
{
pVarData = _value;
}
// Set the property value, passes the specific object we
// want to set the property value for
public void SetCurrentValueData(object pUnk, object varData)
{
_value = (double)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)
{
_pSink = (IDynamicPropertyNotify2)pSink;
}
public void Disconnect() {
_pSink = null;
}
#endregion
#region IAcPiPropertyDisplay methods
public void GetCustomPropertyCtrl(
object id, uint lcid, out string progId
)
{
progId = "AcPEXCtl.AcPePick2PointsCtrl.16";
}
public void GetPropertyWeight(object id, out int propertyWeight)
{
propertyWeight = 0;
}
public void GetPropertyIcon(object id, out object icon)
{
icon = null;
}
public void GetPropTextColor(object id, out uint textColor)
{
textColor = 0;
}
public void IsFullView(
object id, out bool visible, out uint integralHeight
)
{
visible = false;
integralHeight = 1;
}
#endregion
}
#endregion
#region Application Entry Point
public class MyEntryPoint : IExtensionApplication
{
protected internal DistanceProp distProp = 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);
distProp = new DistanceProp();
pPropMan.AddProperty((object)distProp);
}
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)distProp);
distProp = null;
}
}
#endregion
}
Here’s what we see when we NETLOAD the asdkOPMNetExt.dll and our OPMNetSample.dll, and then select a standard line:
When we select the property we see we get a custom editing icon:
When we click that button, we get prompted to select two points in the drawing:
Pick a start point in the drawing:
Pick an end point in the drawing:
Once we’ve done so, the distance between the two points gets assigned to our property:
Bear in mind that we’re currently storing this value with the property, not with the line itself. So you’ll see the same value irrespective of the line you’ve selected. See the note at the beginning of this post if you wish to go further and attach this data per object.
I’m going to spend some time looking further into the capabilities of the custom control mechanism: in the next post in this series we’ll look at hosting a masked string (as in a password) dynamic property inside the Property Palette. If you have suggestions for additional controls to implement, please let me know (and I’ll see what I can figure out :-).