示例代码http://download.csdn.net/source/1458175
上回我们讲述了一个控件的基本建立过程。控件也显出来了,但是作为一个完整的控件还要有与外界交互的接口和属性以及事件的回馈,没有这些,控件也很难使用。下面我们一一介绍。
一、 属性
在加入属性时一定要注意,我们先要给控件加入持久属性包接口,否则你加上了属性可能也是不能用的,就是下面的几行:
实现接口
public IPersistStreamInitImpl<CPlayerCtrl>, //--手工添加持续性初始接口
public IPersistPropertyBagImpl<CPlayerCtrl>, //--手工添加属性包派生类
加入入口映射
COM_INTERFACE_ENTRY_IMPL(IPersistStreamInit) //--手工添加持续性初始接口表
COM_INTERFACE_ENTRY(IPersistPropertyBag) // 手工添加属性接口表
然后我们添加上一个属性,我们切换到类视图,在接口上进行右键,如下操作,
我们点击“Add Property…”弹出对话框
我们要添加一个字符串类型属性,所以要选 “BSTR”,(这里我们稍提一下这个类型,我们知道COM组件是运行在分布系统中的,所以我们向远程的另一端传字符串时,如果只用一个简单指针那么可能也无法操作,至少我们也要知道 一个字符的长度才能向对方拷贝呀,于是就引用了来自于BASIC中的字符串类型,这个类型的开始是一个字符串的长度后面紧跟的是字符串,这样就很容易操作了)同时给这个属性起一个名字叫“sOpenURL”,下面还有“Get function”“Put function”两个选项,这两个我们也都选上吧,在C++里面这个性属就是两个接口函数来操作一个变量的,对于外面我们就是只是一个属性。好了,点一下“Finish”,OK了,我们会看到控件类中生成了两个函数。这就是属性接口了,有了这个我们再为控件类添加一个性属变量,让这两个属性接口来操作:
ATL::CString m_sURL;
说明一下,只所以加上了一个ATL限制是因为这个CString在WTL中也有,所以有冲突心须加。好,我们把这个变量放到属性接口函数中,
STDMETHODIMP CPlayerCtrl::get_sOpenURL(BSTR* pVal)
{
CComBSTR sBSTR(m_sURL);
*pVal = sBSTR;
return S_OK;
}
STDMETHODIMP CPlayerCtrl::put_sOpenURL(BSTR newVal)
{
CComBSTR sBSTR(newVal);
m_sURL = sBSTR;
//可以进一步触发操作。。
return S_OK;
}
注意BSTR与CString之间的转换,有好多的方法,自己去找去吧,呵呵,一个属性加完我们怎么测一下呢?你还记得我们在写HTML时,OBJECT的下面可以有一堆参数列表吗?说白了那就是OBJECT的属性,我们也来加一个,代码如下:
<OBJECT ID="PlayerCtrl" height="200" width="300" CLASSID="CLSID:3F1D6560-1C97-4C63-8E05-6D8143710528">
<param name="sOpenURL" value="HTTP://www.woNB.com">
</OBJECT>
“sOpenURL”就是我们刚加的变量的名字,而后面的串就是他的值了。我们在put_sOpenURL(BSTR newVal)函数中加一个断点,编译运行一下看看,是不是看到了。。。我哭死了:< 怎么没有反应呀?这东东咋了。。
没有反应就对了,要有那才见鬼了呢! 我说过ATL向导不好用,这也是它在作怪,他没有给我们生成代码,加入一行代码就行了,看下面:
BEGIN_PROP_MAP(CPlayerCtrl)
PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
PROP_ENTRY("sOpenURL", 1, CLSID_NULL)//就是这一行,第一个参数是属性名,1就是一个ID,你可到IDL文件里去找一下对应,如果你加一个属性就这样再添一行就可以了。
END_PROP_MAP()
这里就是属性的对应入口,外部调用时首先从这里找到属性函数,才会再到你刚加断点的位置。这下再运行看看,是不是付值了?那就对了。
二、 接口函数
OK,我们来看接口函数的加入。
我们再切换到类视图,在接口上进行右键,我们点击 “Add” à “Add Method…”弹出对话框如下
在方法名里,加上我们要添加接口的名字,ConnectServer;
我们看到在下有 “in”“out”“retval”三项,这是用来描述接口参数的,“in”表示是输入参数,“out”表示是输出参数,“retval”表示是返回值能数。好我们给接口加一个输入型的参数选 “in”,参数类型中还是选“BSTR”,这个用来传入一个URL地址,参数名字为sURL;点Finish一个接口誔生了。在控件类里生成函数:
STDMETHODIMP CPlayerCtrl::ConnectServer(BSTR sURL)
{
// TODO: Add your implementation code here
CComBSTR sBSTR(sURL);
m_sURL = sBSTR;
//进行服务器连接操作
BOOL bRet = FALSE;
m_lpCommandWnd ? m_lpCommandWnd->OnBnClickedBtnPlay(0,0,0,bRet) : NULL;
MessageBox(m_sURL);
return S_OK;
}
我们让其调用控件窗口的播放函数,然后再在HTML中加入一个这个接口的调用按钮,代码如下:
<INPUT id="Button1" OnClick="Connect();" type="button" value="ConnectServer" name="ConnectServer">
Javascript代码为
<script LANGUAGE="JavaScript">
function Connect() {
PlayerCtrl.ConnectServer("我要连接服务器了?");
}
</script>
OK了,编译运行一下,点一下“ConnectServer”按钮,怎么样,是不是效果相当于点击了播放器播放按钮呀。这就是接口的作用。接口添加至此完成。^_^是不是比属性还要简单许多呀。
三、 事件通知
还剩最后一步就是回馈事件通知函数的添加,两步就可完成。
1. 首先给事件接口类添加一个事件接口函数,如下操作:
如上图添写三个参数后点击Finish.OK事件接口函数已经添入,看一下IDL文件中是不是有了我件的事件函数了,如下:
dispinterface _IPlayerCtrlEvents
{
properties:
methods:
[id(1), helpstring("method FireEvent")] HRESULT FireEvent([in] LONG lParam, [in] LONG wParam, [in] BSTR lpStr);
};
2. 在第一步中加入接口函数并没有实现,我们要通过连接点把这个函数来实现了,切换到类视图,找到类对像CPlayerCtrl,右键如下:
如上图进行,我们只有一个类型库,所以就默认了,至于Source Interface 我们只有一个,也就是我们要实现的把它添加到右边。点击Finish,完成了。我们看一下事件代理类中是不是已经实现了一个和我们当时添加的事件接口函数一样的实体呀,就是他了。没有错;因为我们的控件类中已经继承了这个类,所以在控件中也就有了个这个接口的实现。代码如下:
HRESULT Fire_FireEvent( LONG lParam, LONG wParam, BSTR lpStr)
{
HRESULT hr = S_OK;
T * pThis = static_cast<T *>(this);
int cConnections = m_vec.GetSize();
for (int iConnection = 0; iConnection < cConnections; iConnection++)
{
pThis->Lock();
CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);
pThis->Unlock();
IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);
if (pConnection)
{
CComVariant avarParams[3];
avarParams[2] = lParam; avarParams[2].vt = VT_I4;
avarParams[1] = wParam; avarParams[1].vt = VT_I4;
avarParams[0] = lpStr; avarParams[0].vt = VT_BSTR;
CComVariant varResult;
DISPPARAMS params = { avarParams, NULL, 3, 0 };
hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL);
}
}
return hr;
}
事件回馈函数已经加完了,我们先对ConnectServer()函数进行修改如下:
STDMETHODIMP CPlayerCtrl::ConnectServer(BSTR sURL)
{
CComBSTR sBSTR(sURL);
m_sURL = sBSTR;
//进行服务器连接操作
BOOL bRet = FALSE;
m_lpCommandWnd ? m_lpCommandWnd->OnBnClickedBtnPlay(0,0,0,bRet) : NULL;
CComBSTR sStr("事件通知,我要连接服务器了,哦哦哦,O啦啦哦啦啦。。。");
Fire_FireEvent(0,0,sStr);//向控件外部通知事件
return S_OK;
}
事件已经向外通知了,但外部又如何去接收呢,脚本中调用很简单,就像C++中的重载一样实现一下就可以了,如下javaScritp代码
function PlayerCtrl::FireEvent( wParam, lParam,lpStrInfo)
{
alert(lpStrInfo);
}
你可以运行一下了,我们来看看结果,当你运行起来之后,点击ConnectServer按钮,是不是弹出下面的窗口了。
但这种方法在C++中可就不能用了,我们要想在C++中又应该怎么接收呢,这个就是一个比较麻烦的过程,我们一定为事件的接收建立一个从IDispacth继承的接收类。当然你也可以为这个控件加入一个事件回调函数,这样就可以自己很好的控件了。在C++中的控件使用我们就不进行一一讲述了,如果有什么疑问可以给我留言。