ObjectARX自定义对象的OPM

        在AutoCAD绘图工作中,经常用到特性面板,它可以方便地查询、修改CAD对象的详细信息,如颜色、线型等,它是一个非常实用且便捷的工具。如果能为自定义对象添加一个特性面板(OPM),这无疑是众多初学者极感兴趣的事情。笔者也是一名ObjectARX的初学者和爱好者,为了给自定义对象添加OPM,笔者查阅了无数的网上和书籍资料,但大多寥寥数语,含糊其辞。功夫不负有心人,在经历了千百次的“测试-失败-改进”后,终于摸索出了自定义对象OPM编程的部分细节,现将之分享。由于笔者水平有限,文中的错误和疏漏还望各位大侠指点。

         本文以具有6个顶点的折线及一行格式化文字组成一个自定义对象(MyObject),拟在特性面板中实现折线顶点的显示与编辑、格式化文本的选择以及位置编辑等功能。笔者所用版本为ObjectARX 2020,编程语言为C++。本例功能虽简单,但特性表中的一般项、下拉列表控件、数值调节控件、三维坐标分解等均有涉及,基本可以涵盖特性表的全部常用功能。

  1. 添加新建项,双击“ArxAtlWizComWrapper”;
  2. Shot Name填写“MyObjectOPM”,其他默认,点击下一步;
  3. DBX classname填写自定义对象的类名“MyObject”,并选中“Entity Interfafe support(versus just Object)”、“Use IOPMPropertyExtensionImpl”、“Implement IOPMPropertyExpander”选项,点击完成;
  4. 打开“MyObjectOPM.h”文件,将#include "***.h"(DBX类的头文件)修改为#include "***_i.h",并添加包含#include "MyObject.h";
  5. 重新生成一次。
  6. 为自定义对象类添加函数: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:
	//IMyObjectOPM
	STDMETHOD(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、z
	STDMETHOD(GetElementGrouping)(/*[in]*/DISPID dispID, /*[out]*/short *groupingNumber);
	//设置数值调节钮控件的最大值,其值从1开始,步长为1
	STDMETHOD(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、z
		return (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中的测试结果如图,此时选中自定义对象,特性表中已能显示出所需参数,且更改特性表中的参数,自定义对象亦能随之变化。成功!!!

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值