在AutoCAD绘图工作中,经常用到特性面板,它可以方便地查询、修改CAD对象的详细信息,如颜色、线型等,它是一个非常实用且便捷的工具。如果能为自定义对象添加一个特性面板(OPM),这无疑是众多初学者极感兴趣的事情。笔者也是一名ObjectARX的初学者和爱好者,为了给自定义对象添加OPM,笔者查阅了无数的网上和书籍资料,但大多寥寥数语,含糊其辞。功夫不负有心人,在经历了千百次的“测试-失败-改进”后,终于摸索出了自定义对象OPM编程的部分细节,现将之分享。由于笔者水平有限,文中的错误和疏漏还望各位大侠指点。
本文以具有6个顶点的折线及一行格式化文字组成一个自定义对象(MyObject),拟在特性面板中实现折线顶点的显示与编辑、格式化文本的选择以及位置编辑等功能。笔者所用版本为ObjectARX 2020,编程语言为C++。本例功能虽简单,但特性表中的一般项、下拉列表控件、数值调节控件、三维坐标分解等均有涉及,基本可以涵盖特性表的全部常用功能。
- 添加新建项,双击“ArxAtlWizComWrapper”;
- Shot Name填写“MyObjectOPM”,其他默认,点击下一步;
- DBX classname填写自定义对象的类名“MyObject”,并选中“Entity Interfafe support(versus just Object)”、“Use IOPMPropertyExtensionImpl”、“Implement IOPMPropertyExpander”选项,点击完成;
- 打开“MyObjectOPM.h”文件,将#include "***.h"(DBX类的头文件)修改为#include "***_i.h",并添加包含#include "MyObject.h";
- 重新生成一次。
- 为自定义对象类添加函数:virtual Acad::ErrorStatus subGetClassID(CLSID* pClsid) const(protected属性),其实现代码如下:
//获取OPM的ID,以建立COM方式的通讯
Acad::ErrorStatus MyObject::subGetClassID(CLSID* pClsid) const
{assertReadEnabled();*pClsid = CLSID_MyObjectOPM;return Acad::eOk;
}
7. 变量CLSID_MyObjectOPM会报错,添加4中的包含文件:#include "***_i.h"
8. 为自定义对象类添加私有变量:
private:AcGePoint3d mTxPt; //格式化文本插入点AcGePoint3dArray mPts; //折线的顶点坐标集合AcString mTx; //格式化文本
9. 为自定义对象类添加公有函数:
//-----------------------------------------------------------------------------
Acad::ErrorStatus MyObject::GetVertex(int index, AcGePoint3d &pt) const
{assertReadEnabled();pt = mPts[index];return Acad::eOk;
}//-----------------------------------------------------------------------------
Acad::ErrorStatus MyObject::SetVertex(int index, AcGePoint3d newVal)
{assertWriteEnabled();mPts[index] = newVal;return Acad::eOk;
}//-----------------------------------------------------------------------------
AcString MyObject::GetText() const
{assertReadEnabled();return mTx;
}//-----------------------------------------------------------------------------
Acad::ErrorStatus MyObject::SetText(AcString newVal)
{assertWriteEnabled();mTx = newVal;return Acad::eOk;
}//-----------------------------------------------------------------------------
Acad::ErrorStatus MyObject::GetIns(AcGePoint3d &pt) const
{assertReadEnabled();pt = mTxPt;return Acad::eOk;
}//-----------------------------------------------------------------------------
Acad::ErrorStatus MyObject::SetIns(AcGePoint3d newVal)
{assertWriteEnabled();mTxPt = newVal;return Acad::eOk;
}
10. 打开DBX项目的.idl文件,在interface IMyObjectOPM : IAcadEntity { };段中添加如下代码,以初始化特性表:
[propget, id(1), helpstring("折线顶点坐标集合")] HRESULT Vertex([in] SHORT index, [out, retval] VARIANT *pVal);
[propput, id(1), helpstring("折线顶点坐标集合")] HRESULT Vertex([in] SHORT index, [in] VARIANT newVal);
[propget, id(2), helpstring("格式化的文本")] HRESULT Explain([out, retval] BSTR *pVal);
[propput, id(2), helpstring("格式化的文本")] HRESULT Explain([in] BSTR *newVal);
[propget, id(3), helpstring("文本插入点")] HRESULT InsertPt([out, retval] VARIANT *pVal);
[propput, id(3), helpstring("文本插入点")] HRESULT InsertPt([in] VARIANT newVal);
[propget, id(4), helpstring("说明")] HRESULT Note([out, retval] BSTR *pVal);
//注意:id值从1开始自定义编号,既是在“特性”列表中的显示顺序(dispID),但此值在所有代码中须始终一一对应。
11. 打开“MyObjectOPM.h”文件,定义2个常量:#define WjcCategoryID1 86 #define WjcCategoryID2 87//用于特性表的分类号,在BEGIN_OPMPROP_MAP() END_OPMPROP_MAP()段间插入如下语句,以进行属性分类及编辑模式等的声明,这些语句须和10中的语句对应:
//IOPMPropertyExtension
BEGIN_OPMPROP_MAP()OPMPROP_ENTRY(0, 0x001, WjcCategoryID1, 0, 0, 0, _T(""), 0, 1, IID_NULL, IID_NULL, "")OPMPROP_ENTRY(0, 0x002, WjcCategoryID2, 0, 0, 0, _T(""), 0, 1, IID_NULL, IID_NULL, "")OPMPROP_ENTRY(0, 0x003, WjcCategoryID2, 0, 0, 0, _T(""), 0, 1, IID_NULL, IID_NULL, "")OPMPROP_ENTRY(0, 0x004, WjcCategoryID1, 0, 0, 0, _T(""), 0, 0, IID_NULL, IID_NULL, "")
END_OPMPROP_MAP()
/*⑴DescriptionID:默认0
⑵dispID:见10条的注意
⑶catagoryID:分组的ID
⑷catagoryNameID:默认0
⑸elements string list ID (semi-colon separator) :默认0
⑹predefined strings ID (semi-colon separator) :默认0
⑺predefined values:默认_T("")
⑻grouping:默认0
⑼editable:是否可编辑:1-可编辑;0-不能编辑
⑽property:默认 IID_NULL
⑾other:默认 IID_NULL
⑿proppage:默认 ""*/
12. 声明如下函数,这些函数须和10中的语句一一对应,且函数名称、参数都是一一对应的,这些函数是OPM和自定义对象进行数据交换的唯一通道:
public://IMyObjectOPMSTDMETHOD(get_Vertex)(/*[in]*/ SHORT index, /*[out, retval]*/VARIANT *pVal);STDMETHOD(put_Vertex)(/*[in]*/ SHORT index, /*[in]*/ VARIANT newVal);STDMETHOD(get_Explain)(BSTR *pVal);STDMETHOD(put_Explain)(BSTR *newVal);STDMETHOD(get_InsertPt)(/*[out, retval]*/VARIANT *pVal);STDMETHOD(put_InsertPt)(/*[in]*/ VARIANT newVal);STDMETHOD(get_Note)(BSTR *pVal);//在MyObject类中须有与这些函数对应的函数
13. 实现上述函数:
//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::get_Vertex(/*[in]*/ SHORT index, /*[out, retval]*/VARIANT *pVal)
{try{AcDbObjectPointer <MyObject>pC(m_objRef.objectId(), AcDb::kForRead);if (pC.openStatus() != Acad::eOk)return E_ACCESSDENIED;AcAxPoint3d pt;pC->GetVertex(index, pt);pt.setVariant(*pVal);}catch (const HRESULT hf)//{Error("请检查输入的参数!");return hf;}return S_OK;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::put_Vertex(/*[in]*/ SHORT index, /*[in]*/ VARIANT newVal)
{try{AcAxDocLock docLock(m_objRef.objectId(), AcAxDocLock::kNormal);if (docLock.lockStatus() != Acad::eOk && docLock.lockStatus() != Acad::eNoDatabase)return E_ACCESSDENIED;AcDbObjectPointer <MyObject>pC(m_objRef.objectId(), AcDb::kForWrite);if (pC.openStatus() != Acad::eOk){return E_ACCESSDENIED;}AcAxPoint3d pt(newVal);pC->SetVertex(index, pt);}catch (const HRESULT hf){Error("请检查输入的参数!");return hf;}return S_OK;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::get_Explain(BSTR *pVal)
{try{AcDbObjectPointer <MyObject>pC(m_objRef.objectId(), AcDb::kForRead);if (pC.openStatus() != Acad::eOk){return E_ACCESSDENIED;}*pVal = ::SysAllocString(pC->GetText());}catch (const HRESULT hf){Error("请检查输入的参数!");return hf;}return S_OK;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::put_Explain(BSTR *newVal)
{try{AcString ts;ts.format(_T("%s"), *newVal);::SysFreeString(*newVal);AcAxDocLock docLock(m_objRef.objectId(), AcAxDocLock::kNormal);if (docLock.lockStatus() != Acad::eOk && docLock.lockStatus() != Acad::eNoDatabase)return E_ACCESSDENIED;AcDbObjectPointer <MyObject>pC(m_objRef.objectId(), AcDb::kForWrite);if (pC.openStatus() != Acad::eOk){return E_ACCESSDENIED;}pC->SetText(ts);}catch (const HRESULT hf){Error("请检查输入的参数!");return hf;}return S_OK;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::get_InsertPt(/*[out, retval]*/VARIANT *pVal)
{try{AcDbObjectPointer <MyObject>pC(m_objRef.objectId(), AcDb::kForRead);if (pC.openStatus() != Acad::eOk)return E_ACCESSDENIED;AcAxPoint3d pt;pC->GetIns(pt);pt.setVariant(*pVal);}catch (const HRESULT hf)//{Error("请检查输入的参数!");return hf;}return S_OK;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::put_InsertPt(/*[in]*/ VARIANT newVal)
{try{AcAxDocLock docLock(m_objRef.objectId(), AcAxDocLock::kNormal);if (docLock.lockStatus() != Acad::eOk && docLock.lockStatus() != Acad::eNoDatabase)return E_ACCESSDENIED;AcDbObjectPointer <MyObject>pC(m_objRef.objectId(), AcDb::kForWrite);if (pC.openStatus() != Acad::eOk){return E_ACCESSDENIED;}AcAxPoint3d pt(newVal);pC->SetIns(pt);}catch (const HRESULT hf){Error("请检查输入的参数!");return hf;}return S_OK;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::get_Note(BSTR *pVal)
{*pVal = ::SysAllocString(_T("成功展示"));return S_OK;
}
14. 在“MyObjectOPM.h”中声明如下重载函数(public):
//IOPMPropertyExpander//分解复杂的属性(如三维坐标值包含x、y、z属性)STDMETHOD(GetElementStrings)(/*[in]*/DISPID dispID, /*[out]*/OPMLPOLESTR __RPC_FAR *pCaStringsOut, /*[out]*/OPMDWORD __RPC_FAR *pCaCookiesOut);//设置复杂属性的当前值STDMETHOD(GetElementValue)(/*[in]*/DISPID dispID, /*[in]*/DWORD dwCookie, /*[out]*/VARIANT *pVarOut);//读取复杂属性的当前值,并返回给关联的实体STDMETHOD(SetElementValue)(/*[in]*/DISPID dispID, /*[in]*/DWORD dwCookie, /*[in]*/VARIANT VarIn);//设置数值调节钮控件每组数据的数据个数,如三维坐标的每组数据个数为3,即x、y、zSTDMETHOD(GetElementGrouping)(/*[in]*/DISPID dispID, /*[out]*/short *groupingNumber);//设置数值调节钮控件的最大值,其值从1开始,步长为1STDMETHOD(GetGroupCount)(/*[in]*/DISPID dispID, /*[out]*/long *nGroupCnt);//设置下拉列表控件中的预定义字符串(或true/false)STDMETHOD(GetPredefinedStrings)(/*[in]*/DISPID dispID, /*[out]*/CALPOLESTR *pCaStringsOut, /*[out]*/CADWORD *pCaCookiesOut);//设置下拉列表控件的当前显示字符串(或true/false)STDMETHOD(GetPredefinedValue)(/*[in]*/DISPID dispID, /*[in]*/DWORD dwCookie, /*[out]*/VARIANT *pVarOut);//设置列表分类/分组的名称STDMETHOD(GetCategoryName)(/* [in] */ PROPCAT propcat,/* [in] */ LCID lcid,/* [out] */ BSTR* pbstrName);//重置/覆盖列表中简单属性(单行显示)的名称STDMETHOD(GetDisplayName)(/*[in]*/DISPID dispID, /*[out]*/BSTR *pBstr);
15. 实现上述函数:
//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::GetElementValue (DISPID dispID, DWORD dwCookie, VARIANT *pVarOut) {if (pVarOut == NULL) return (E_POINTER);if (dispID == 1){int index;index = int(dwCookie / 3);/*dwCookie是一个计数器,是用于获取和设置每个属性项的值的唯一标识符,编号从0开始,依次递增1.本例中一个三维坐标对应3个连续的dwCookie值*/CComVariant var;get_Vertex(index, &var);AcAxPoint3d pt(var);pVarOut->vt = VT_R8;pVarOut->dblVal = pt[dwCookie % 3];return (S_OK);}else if (dispID == 3){CComVariant var;get_InsertPt(&var);AcAxPoint3d pt(var);pVarOut->vt = VT_R8;pVarOut->dblVal = pt[dwCookie];return (S_OK);}return (E_NOTIMPL) ;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::SetElementValue (DISPID dispID, DWORD dwCookie, VARIANT VarIn) {if (dispID == 1){int index;index = int(dwCookie / 3);CComVariant var;get_Vertex(index, &var);AcAxPoint3d pt(var);pt[dwCookie % 3] = VarIn.dblVal;pt.setVariant(var);put_Vertex(index, var);return (S_OK);}else if (dispID == 3){CComVariant var;get_InsertPt(&var);AcAxPoint3d pt(var);pt[dwCookie] = VarIn.dblVal;pt.setVariant(var);put_InsertPt(var);return (S_OK);}return (E_NOTIMPL) ;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::GetElementStrings (DISPID dispID, OPMLPOLESTR __RPC_FAR *pCaStringsOut, OPMDWORD __RPC_FAR *pCaCookiesOut) {if (pCaStringsOut == NULL || pCaCookiesOut == NULL) return (E_POINTER);if (dispID == 1){pCaStringsOut->cElems = 3;pCaStringsOut->pElems = (LPOLESTR*)CoTaskMemAlloc(sizeof(LPOLESTR) * 3);pCaStringsOut->pElems[0] = SysAllocString(L" 顶点 x 坐标");pCaStringsOut->pElems[1] = SysAllocString(L" 顶点 y 坐标");pCaStringsOut->pElems[2] = SysAllocString(L" 顶点 z 坐标");pCaCookiesOut->cElems = 3;pCaCookiesOut->pElems = (DWORD*)CoTaskMemAlloc(sizeof(DWORD) * 3);pCaCookiesOut->pElems[0] = 0;pCaCookiesOut->pElems[1] = 1;pCaCookiesOut->pElems[2] = 2;return (S_OK);}else if (dispID == 3){pCaStringsOut->cElems = 3;pCaStringsOut->pElems = (LPOLESTR*)CoTaskMemAlloc(sizeof(LPOLESTR) * 3);pCaStringsOut->pElems[0] = SysAllocString(L"文本 x 坐标");pCaStringsOut->pElems[1] = SysAllocString(L"文本 y 坐标");pCaStringsOut->pElems[2] = SysAllocString(L"文本 z 坐标");pCaCookiesOut->cElems = 3;pCaCookiesOut->pElems = (DWORD*)CoTaskMemAlloc(sizeof(DWORD) * 3);pCaCookiesOut->pElems[0] = 0;pCaCookiesOut->pElems[1] = 1;pCaCookiesOut->pElems[2] = 2;return (S_OK);}return (E_NOTIMPL) ;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::GetElementGrouping (DISPID dispID, short *groupingNumber) {if (groupingNumber == NULL) return (E_POINTER);if (dispID == 1){*groupingNumber = 3;//每组三个数据,及x、y、zreturn (S_OK);}return (E_NOTIMPL) ;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::GetGroupCount (DISPID dispID, long *nGroupCnt) {if (nGroupCnt == NULL)return (E_POINTER);if (dispID == 1){*nGroupCnt = 6; // 顶点数return S_OK;}return (E_NOTIMPL) ;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::GetPredefinedStrings(/*[in]*/DISPID dispID, /*[out]*/CALPOLESTR *pCaStringsOut, /*[out]*/CADWORD *pCaCookiesOut)
{if (dispID != 2)return IOPMPropertyExtensionImpl<CMyObjectOPM>::GetPredefinedStrings(dispID, pCaStringsOut, pCaCookiesOut);USES_CONVERSION;if (dispID == 2){long size = 4;pCaStringsOut->pElems = (LPOLESTR *)::CoTaskMemAlloc(sizeof(LPOLESTR) * size);pCaCookiesOut->pElems = (DWORD *)::CoTaskMemAlloc(sizeof(DWORD) * size);pCaStringsOut->cElems = (ULONG)size;pCaCookiesOut->cElems = (ULONG)size;pCaStringsOut->pElems[0] = ::SysAllocString(_T("我们"));pCaCookiesOut->pElems[0] = 0;pCaStringsOut->pElems[1] = ::SysAllocString(_T("一起"));pCaCookiesOut->pElems[1] = 1;pCaStringsOut->pElems[2] = ::SysAllocString(_T("学习")); pCaCookiesOut->pElems[2] = 2;pCaStringsOut->pElems[3] = ::SysAllocString(_T("ObjectARX")); pCaCookiesOut->pElems[3] = 3;return S_OK;}return E_NOTIMPL;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::GetPredefinedValue(/*[in]*/DISPID dispID, /*[in]*/DWORD dwCookie, /*[out]*/VARIANT *pVarOut)
{if (dispID != 2)return IOPMPropertyExtensionImpl<CMyObjectOPM>::GetPredefinedValue(dispID, dwCookie, pVarOut);USES_CONVERSION;const TCHAR* pName = NULL;if (dispID == 2){switch (dwCookie){case 0:pName = _T("我们");break;case 1:pName = _T("一起");break;case 2:pName = _T("学习");break;case 3:pName = _T("ObjectARX");break;default:return E_NOTIMPL;}::VariantCopy(pVarOut, &CComVariant(CT2W(pName)));return S_OK;}return E_NOTIMPL;
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::GetCategoryName(/* [in] */ PROPCAT propcat,/* [in] */ LCID lcid,/* [out] */ BSTR* pbstrName)
{if (propcat == WjcCategoryID1){*pbstrName = ::SysAllocString(_T("分类1 测试"));return S_OK;}else if (propcat == WjcCategoryID2){*pbstrName = ::SysAllocString(_T("分类2 测试")); return S_OK;}else{return S_FALSE;}
}//-----------------------------------------------------------------------------
STDMETHODIMP CMyObjectOPM::GetDisplayName(/*[in]*/DISPID dispID, /*[out]*/BSTR *pBstr)
{if (pBstr == NULL) return (E_POINTER);switch (dispID){case (0x401):*pBstr = ::SysAllocString(_T("对象特性表-乱石作品"));break;case (0x516):*pBstr = ::SysAllocString(_T("颜色"));break;case (0x501):*pBstr = ::SysAllocString(_T("图层"));break;case (0x502):*pBstr = ::SysAllocString(_T("线型"));break;case (0x503):*pBstr = ::SysAllocString(_T("线型比例"));break;case (0x513):*pBstr = ::SysAllocString(_T("打印样式"));break;case (0x514):*pBstr = ::SysAllocString(_T("线宽"));break;case (0x515):*pBstr = ::SysAllocString(_T("超链接"));break;case (0x577):*pBstr = ::SysAllocString(_T("材质"));break;case (0x579):*pBstr = ::SysAllocString(_T("实体透明度"));break;case (0x01):*pBstr = ::SysAllocString(_T("当前顶点"));break;case (0x02):*pBstr = ::SysAllocString(_T("格式文本"));break;case (0x04):*pBstr = ::SysAllocString(_T("说明"));break;default:break;}return (S_OK);
}
16. 重新编译后,在AutoCAD中的测试结果如图,此时选中自定义对象,特性表中已能显示出所需参数,且更改特性表中的参数,自定义对象亦能随之变化。成功!!!