如何用ATL创建ActiveX控件
实现了一个ActiveX控件,它在一个圆内部有个正多边形,当用户在多变形内部单击将会使多边形的边数在当前的基础上+1,在多变形外部单击将会使多边形的边数在当前的基础上-1,并能改变多边形的颜色.最后举了两个例子说明了如何使用这个刚刚生产得控件.一个是把该控件应用到网页中,一个则是用于一个基于对话框的程序中.详细代码请下载压缩包.
(一) 创建工程
(1) 打开VC6集成开发环境,按新建按钮,选择PROJECT标签。 (2) 选择ATL COM AppWizard。 (3) 在右侧Project Name下面的空白处输入"Polygon"。 如下图所示: 图1
按下OK按钮,出现如下对话框: 图2
按Finish按钮,接受默认设置,出现如下对话框: 图3
按下OK按钮,ATL COM AppWizard将生成一系列的文件,现在描述如下:
Polygon.cpp:
包含了DllMain,DllCanUnloadNow, DllGetClassObject,DllRegisterServer,DllUnregisterServer的实现, 同时它也包含object map: BEGIN_OBJECT_MAP(ObjectMap) //这里将列出你的工程中将会用到的ATL对象,这里最初为空, //因为我们目前还没有创建新的ATL对象 END_OBJECT_MAP()
Polygon.def DLL便准模块定义文件 Polygon.dsw 项目工作区文档 Polygon.dsp 项目设置文档 Polygon.idl 接口定义语言文件, 它详细的描述了您的工程中所有的接口 Polygon.rc 资源文件, 它包含了版本信息和工程名称字符串 Resource.h 资源文件的头文件 Polygonps.mk 这个就是make file,它能被用来创建代理存根DLL Polygonps.def 代理存根DLL的模块定义文件 StdAfx.cpp 此文件包含ATL的执行档 StdAfx.h 此文件包含ATL的头文件
为了使它(Polygon DLL)变得有用,我们需要用ATL Object Wizard给它添加一个控件(control)。
(二)添加一个控件
(1) 打开INSERT菜单,选择New ATL Object项,出现如下对话框: 图4
(2) 我们在左边选择"Controls",右边选择Full Control,按下NEXT按钮,出现如下所示对话框: 图5
(3) 我们在Names标签页,"Short Name"后面的空白中输入"PolyCtl",这时你将注意到其他的空白将会自动完成。 Class域显示控件将会使用的类名称。 CoClass是控件的组件类ID Interface是接口名称,我们将会在此接口中实现一些方法和属性 Type是控件描述 ProgID是易记的类ID名称,用它可以得到控件的CLSID
(4) 为了激活错误提示信息和connection points支持,我们选择Attributes标签页,选择Support ISupportErrorInfo和Support Connection Points,结果如下图所示: 图6
(5) 由于我们将会在多变形内部染色,所以我们需要增加一个Fill Color属性支持。我们选择stock property标签页,在左边的列表框中双击Fill Color,结果如下图所示: 图7
(6) 按下“确定”按钮,结束创建控件。 VC6将会生成如下新的文件: PolyCtl.h/cpp:包含了C++类CPolyCtl的实现 PolyCtl.rgs:一个包含了注册控件所需要的注册信息的文本文件 PolyCtl.htm:一个HTML文件,其中有关于这个控件的引用的代码。例如在我这个例子中有:
同时Wizard也改变了以下几处: a)在StdAfx.h和StdAfx.cpp文件中增加了一条include语句,它把控件必需的ATL文件包含进来了 b)注册脚本文件PolyCtl.rgs被增加到工程资源中。 c)Polygon.idl被修改以便包括新的控件细节信息。
文件PolyCtl.h是最有趣的,因为它包含实现你的控件主要的代码。 现在,你已经准备好了建立你的控件: 1.在Build菜单点击Build Polygon.dll。 2.一旦你的控件已经完成Build,你就可以点击在Tools菜单上的ActiveX Control Test Container,控件测试容器工具将启动。 3.在ActiveX Control Test Container中,选择Edit菜单的Insert New Control,Insert Control会话框出现。如下所示: 图8
4.从Insert Control会话框的列表框中选择 PolyCtl class,按下OK,你将看到ActiveX Control Test Container客户区出现一个长方形,在其*显示了本文" ATL 3.0: PolyCtl",如下所示: 图9
5.关闭ActiveX Control Test Container。
然后,你将会在控件中加入定制属性。
(三)为控件添加一个属性
(1) IPolyCtl是包含你定制的方法和属性的接口。 要把属性加入这一个接口的最容易的方法是在ClassView中右击它,而且选择Add Property。如下所示: 图10
(2) Add Property to Interface会话框出现,允许你加入你的属性细节: 1.在属性类型的下拉列表框中选择short。 2.输入"Sides"作为我们的属性名称。当你编辑属性名字域的时候,Implementation下面的编辑框将会出现一些信息,这些信息将被增加到你的IDL文件。如下所示: 图11
3.按下OK按钮。
MIDL(编译idl文件的程序)定义了一个Get和一个Put方法,他们将分别取得和设定属性。 当MIDL编译文件的时候,它对属性名字加前缀put_ 和get_, 在接口中自动地定义那二个方法。
连同把必需的信息加入.idl文件, Add Property to Interface对话框也在类定义文件PolyCtl.h中加入Get 和Put函数原型,并在类实现文件PolyCtl.cpp中加入相应的空的实现函数。
(3) 为了能设定并且取回属性值,我们需要一个地方来储存它。从FileView, 打开 PolyCtl.h,在类定义结尾即在m_clrFillColor定义之后加入如下一行代码: short m_nSides;
(4) 现在你能实现Get和Put方法。get_Sides和put_Sides函数定义已经被增加到 PolyCtl.h 。你把代码加入 PolyCtl.cpp如下列各项: STDMETHODIMP CPolyCtl::get_Sides(short *pVal) { *pVal = m_nSides; return S_OK; }
STDMETHODIMP CPolyCtl::put_Sides(short newVal) { if (newVal > 2 && newVal < 101) { m_nSides = newVal; return S_OK; } else return Error(_T("Shape must have between 3 and 100 sides")); }
get_Sides函数只是通过pVal指针返回属性Sides的当前值。在put_Sides方法中,你确定使用者正在对Sides属性设定可接受的值。你需要超过2条边, 而且由于你以后将会为每个边储存点的阵列,100是一个合理的最大值界限。如果有非法的值传递进来,你可以通过使用ATL IErrorInfo接口的Error函数来设定详细的错误信息。 如果你的客户(container)需要比HRESULT更多的关于错误的资讯,这是有用的。
(5) 你为属性做的最后一件事是设定m_nSides初值。藉由把一行代码加入 PolyCtl.h 的构造函数中使一个三角形成为默认形状: CPolyCtl() { m_nSides = 3; }
你现在拥有了一个叫做Sides的属性。 除非你对它做一些事情,否则它并没有什么用处,下一步我们将改变画图代码并使用该属性。
(四)变更画图代码
(1) 在画图编码中你将会使用sin和cos动作计算多边形顶点, 因此在 PolyCtl.h 的顶端包含 math.h: #include <math.h> #include "resource.h" // main symbols
在Release builds时需要注意:当ATL COM AppWizard产生内定工程的时候,它定义了 _ATL_MIN_CRT宏。这个宏的作用是,在你不需要C Run-Time Library支持的时候, C Run-Time Library不被带到你的代码之内。多角形控件需要C Run-Time Library start-up code设定浮点函数初值。 因此, 如果你建立一个释放版本,你需要除去_ATL_MIN_CRT宏。 为了要除去该宏,点击Project 菜单上的Settings。 在Settings For:下拉框中选择Multiple Configurations。在跳出来的Select project configuration(s) to modify对话框中,为所有的四个释放版本按复选框, 如图所示: 图12
然后点击OK。在C/C++标签页,选择General, 除去Preprocessor definitions定义结尾的 ",_ATL_MIN_CRT" 图13
(2) 一旦多边形顶点计算出来了,你就可以通过增加一个POINT类型的数组来保存所有的点,在PolyCtl.h中: OLE_COLOR m_clrFillColor; short m_nSides; POINT m_arrPoint[100];
(3) 现在改变 PolyCtl.h 的OnDraw函数。注意你需要除去对Rectangle和DrawText函数的调用。你需要明确地得到而且选择黑色的笔和白色的刷子。 这么做是,以防你的控件正在运行在无窗口环境中。 如果你没有你自己的窗口, 你不能假定具备绘制所需要的设备环境。
完成的OnDraw函数如下所示: HRESULT CPolyCtl::OnDraw(ATL_DRAWINFO& di) { RECT& rc = *(RECT*)di.prcBounds; HDC hdc = di.hdcDraw;
COLORREF colFore; HBRUSH hOldBrush, hBrush; HPEN hOldPen, hPen;
// Translate m_colFore into a COLORREF type OleTranslateColor(m_clrFillColor, NULL, &colFore);
// Create and select the colors to draw the circle hPen = (HPEN)GetStockObject(BLACK_PEN); hOldPen = (HPEN)SelectObject(hdc, hPen); hBrush = (HBRUSH)GetStockObject(WHITE_BRUSH); hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
Ellipse(hdc, rc.left, rc.top, rc.right, rc.bottom);
// Create and select the brush that will be used to fill the polygon hBrush = CreateSolidBrush(colFore); SelectObject(hdc, hBrush);
CalcPoints(rc); Polygon(hdc, &m_arrPoint[0], m_nSides);
// Select back the old pen and brush and delete the brush we created SelectObject(hdc, hOldPen); SelectObject(hdc, hOldBrush); DeleteObject(hBrush);
return S_OK; }
你现在需要一个函数,叫做了CalcPoints, 它将会计算多边形顶点的坐标。这些计算将会以被获准进入函数的RECT变量为基础。首先你应该把CalcPoints的定义加入到PolyCtl.h中的IPolyCtl类的公众区段: void CalcPoints(const RECT& rc);
公共区段看起来应该如下: // IPolyCtl public: STDMETHOD(get_Sides)(/*[out, retval]*/ short *newVal); STDMETHOD(put_Sides)(/*[in]*/ short newVal); void CalcPoints(const RECT& rc);
接着在PolyCtl.cpp尾部添加函数CalcPoints的具体实现: void CPolyCtl::CalcPoints(const RECT& rc) { const double pi = 3.14159265358979; POINT ptCenter; double dblRadiusx = (rc.right - rc.left) / 2; double dblRadiusy = (rc.bottom - rc.top) / 2; double dblAngle = 3 * pi / 2; // Start at the top double dblDiff = 2 * pi / m_nSides; // Angle each side will make ptCenter.x = (rc.left + rc.right) / 2; ptCenter.y = (rc.top + rc.bottom) / 2;
// Calculate the points for each side for (int i = 0; i < m_nSides; i++) { m_arrPoint[i].x = (long)(dblRadiusx * cos(dblAngle) + ptCenter.x + 0.5); m_arrPoint[i].y = (long)(dblRadiusy * sin(dblAngle) + ptCenter.y + 0.5); dblAngle += dblDiff; } }
现在初始化变量m_clrFillColor,选择绿色作为默认颜色并加如下语句到CPolyCtl类构造函数中: m_clrFillColor = RGB(0, 0xFF, 0); 类CPolyCtl构造函数现在看起来如下: CPolyCtl() { m_nSides = 3; m_clrFillColor = RGB(0, 0xFF, 0); }
现在重新编译控件,如果发现如下重载函数模糊调用错误(一剑:"这可能是VC6的一个BUG,亦或是由于我的VC6没有打SP6包的缘故吧:)"): f:\myprogram2\polygon1\polyctl.h(106) : error C2668: 'InlineIsEqualGUID' : ambiguous call to overloaded function 我们可以在polyctl.h中出错位置修改如下: if (::InlineIsEqualGUID(*arr[i], riid)) return S_OK;
再次编译发现已经通过,再次测试它.打开ActiveX Control Test Container并插入它,你将会看到一个圆内部有着一个绿色的三角形,如下所示: 图14
按照如下所述方法我们试着改变多边形的变数.
为了在Test Container内改变控件的边数属性,我们使用Invoke Methods:
1. 在Test Container内,点击Control菜单中的Invoke Methods,出现Invoke Method对话框 图15
2.选择Method Name下拉框中的Sides (PropPut)
3.在Parameter Value编辑框中输入5,点击Set Value并按Invoke.
这时你会发现我们的控件的多边形变数已经有了变化,如图: 图16
(五)增加事件响应
现在我将会为我们的ATL控件加入一个ClickIn和一个ClickOut事件,当用户点击多边形内(外)部的时候将会执行ClickIn(ClickOut)
还记得第(二)步中我在创建控件的时候选择了Support Connection Points复选框吗?这样做会在我们的.idl文件中产生_IPolyCtlEvents接口.注意该接口名称前面有个下划线.这提示了用户:改接口是一个内部接口.因此,一些COM对象浏览程序允许用户选择是否显示内部接口对象.我们也应该注意到.idl文件中加入了一行: [default, source] dispinterface _IPolyCtlEvents; 这暗示了_IPolyCtlEvents是默认的消息来源接口.source属性暗示该控件是notifications消息的来源.
现在我们来为_IPolyCtlEvents接口增加ClickIn和ClickOut方法: 1. 在ClassView中右击_IPolyCtlEvents选择Add Method…出现Add Method to Interface对话框. 2. Return Type选择void 3. 在Method Name下面的空白中输入ClickIn 4. 在Parameters下面的空白中输入[in] long x, [in] long y 5. 按下OK
检查.idl文件发现有新的代码生成.同样的道理,我们为控件增加ClickOut方法,parameters和return type与ClickIn方法一样.现在我们的.idl文件看起来如下: dispinterface _IPolyCtlEvents { properties: methods: [id(1), helpstring("method ClickIn")] void ClickIn([in]long x, [in] long y); [id(2), helpstring("method ClickOut")] void ClickOut([in] long x, [in] long y); };
上面这辆个方法中的参数x和y是鼠标坐标信息
现在是生成我们的类型库(type library)的时候了.我们可以通过rebuild的方法来生成类型库,也可以在FileView中右击.idl文件选择Compile Polygon.idl,这将会产生Polygon.tlb文件.这就是我们的类型库文件了.
接着,为我们的ATL控件实现一个连接点接口IConnectionPoint和一个连接点容器接口IConnectionPointContainer.为了实现IConnectionPoint,请按如下步骤去做:
1. 打开ClassView标签 2. 右击控件实现类,这里也就是CPolyCtl 3. 选择Implement Connection Point….出现Implement Connection Point对话框. 图17
4. 选择列表框中的_IPolyEvents按下OK就会产生一个关于connection point的代理类,这里就是CProxy_IPolyCtlEvents
打开文件PolygonCP.h看下,你会发现CProxy_PolyCtlEvents是继承自IConnectionPointImpl,PolygonCP.h还定义了Fire_ClickIn 和Fire_ClickOut两个方法,同样拥有两个关于鼠标信息的参数,这些就是当想要从您的控件响应(FIRE射击)一次事件的时候您所呼叫的方法了.
向导还为我们把CProxy_PolyEvents和IConnectionPointContainerImpl添加到多重继承的列表中.
现在我们要加入响应事件的代码了.为了找出用户何时单击左键,首先要为WM_LBUTTONDOWN消息增加一个处理.在ClassView中右击CPolyCtl选择Add Windows Message Handler...出现New Windows Message and Event Handlers for class CPolyCtl对话框,选择左边列表框中的WM_LBUTTONDOWN单击Add Handler,然后按OK 图18
接着,在OnLButtonDown中加入一些新的代码,删除向导生成的一些代码:最后该函数看起来应该如下: LRESULT CPolyCtl::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { HRGN hRgn; WORD xPos = LOWORD(lParam); // horizontal position of cursor WORD yPos = HIWORD(lParam); // vertical position of cursor
CalcPoints(m_rcPos);
// Create a region from our list of points hRgn = CreatePolygonRgn(&m_arrPoint[0], m_nSides, WINDING);
// If the clicked point is in our polygon then fire the ClickIn // event otherwise we fire the ClickOut event if (PtInRegion(hRgn, xPos, yPos)) Fire_ClickIn(xPos, yPos); else Fire_ClickOut(xPos, yPos);
// Delete the region that we created DeleteObject(hRgn); return 0; }
再次编译后加入ActiveX Control Test Container进行测试,发现已经可以调用ClickOut和ClickIn方法了.
下一步将为我们的ATL控件增加属性页.
(六)增加一个属性页
为了给控件增加一个属性页,我们可以使用ATL Object Wizard(戏称:ATL对象巫术师)。
选择菜单Insert中的New ATL Object...打开ATL Object Wizard并在左边的列表框中选择Controls,然后选择右边的Property Page,接着按NEXT 图19
在Short Name右边的空白输入PolyProp 图20
我们注意到Interface右边的空白是灰色的,这是因为一个属性页不需要定制一个接口.
点击Strings标签在Title下面的空白中输入属性页的标题.这里输入&Polygon
DOC String是属性框架状态提示或工具提示的描述.这里输入Polygon Properties清空Helpfile下面的内容.按下OK 图21
我们发现向导创建了如下文件: PolyProp.h 包含C++类CPolyProp, 它实现了属性页 PolyProp.cpp 它包含了PolyProp.h文件 PolyProp.rgs 注册属性页对象的脚本文件
除此向导还改变如下一些位置的代码:
新的属性页被加入到Polygon.cpp中的对象入口映射宏中 PolyProp类被加入到Polygon.idl文件 新的注册脚本文件PolyProp.rgs被加入到工程的资源中 一个新的对话框模板被加入到工程资源中(如果您的VC没有自动加入这个模板,那你就手动加入好了) 我们前面所指定的属性字符串被加入到字符串资源表中
现在在对话框模板中加入如下图所示控件(编辑框ID设为IDC_SIDES): 图22
在PolyProp.h文件头部加入 #include "Polygon.h" // definition of IPolyCtl
现在需要在用户按下应用按钮的时候激活我们的CPolyProp类来设置多变形的边数,改变PolyProp.h中的应用按钮函数如下: STDMETHOD(Apply)(void) { USES_CONVERSION; ATLTRACE(_T("CPolyProp::Apply\n")); for (UINT i = 0; i < m_nObjects; i++) { CComQIPtr pPoly(m_ppUnk[i]); short nSides = (short)GetDlgItemInt(IDC_SIDES); if FAILED(pPoly->put_Sides(nSides)) { CComPtr pError; CComBSTR strError; GetErrorInfo(0, &pError); pError->GetDescription(&strError); MessageBox(OLE2T(strError), _T("Error"), MB_ICONEXCLAMATION); return E_FAIL; } } m_bDirty = FALSE; return S_OK; }
接着同样在ClassView中右击CPolyProp选择Add Windows Message Handler...在右下列表框中选择IDC_SIDES,再在左边列表框中选择EN_CHANGE并双击它,按下OK按钮.编码OnChangeSides函数如下: LRESULT OnChangeSides(WORD wNotify, WORD wID, HWND hWnd, BOOL& bHandled) { SetDirty(TRUE); return 0; }
在PolyCtl.h中的属性映射宏中加入 PROP_ENTRY("Sides", 1, CLSID_PolyProp) 现在控件的属性映射宏看起来应该如下: BEGIN_PROP_MAP(CPolyCtl) PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4) PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4) PROP_ENTRY("FillColor", DISPID_FILLCOLOR, CLSID_StockColorPage) PROP_ENTRY("Sides", 1, CLSID_PolyProp) // Example entries // PROP_ENTRY("Property Description", dispid, clsid) // PROP_PAGE(CLSID_StockColorPage) END_PROP_MAP()
现在再次编译后加入ActiveX Control Test Container测试,我们在控件上右击鼠标选择Properties... 改变变数测试一下效果如何?应该成功了,效果如下图所示! 图23
下一步我们将把我们生产的控件放到一张网页上:)
(七) 把控件放到一张网页上
为了在实际应用我们生产的控件,我们这里选择了将它放到一张网页上来展示用法,当ATL Object Wizard创建该控件的时候它已经为我们生成了一个网页文件:PolyCtl.htm,我们现在用IE打开这个文件效果如下: 图24
但是它不能做任何事情,连我们做好的事件都不能响应.我们需要在网页脚本中加入新的脚本,完后网页脚本应该如下: <HTML> <HEAD> <TITLE>ATL 3.0 test page for object PolyCtl</TITLE> </HEAD> <BODY> <OBJECT ID="PolyCtl" CLASSID="CLSID:86A079D2-EF8A-4531-AE37-1EDFA0002E58"></OBJECT>
<SCRIPT LANGUAGE="VBScript">
<!--
Sub PolyCtl_ClickIn(x, y)
PolyCtl.Sides = PolyCtl.Sides + 1
End Sub
Sub PolyCtl_ClickOut(x, y)
PolyCtl.Sides = PolyCtl.Sides - 1
End Sub
-->
</SCRIPT>
</BODY>
</HTML>
保存文件,打开IE,为了确保安全设置是在中等级别上,打开工具菜单中的Internet选项,选择安全标签 按自定义级别按钮,在重置自定义设置中的下拉框中选择"安全级 - 中",确定.
刷新网页,点击网页中的控件出现IE的提示消息框: 图25
我们生产的控件,我们自己知道它是安全的,选择是. 现在在控件多变形内部单击将会使多边形的边数在当前的基础上+1,在控件多变形外部单击将会使多边形的边数在当前的基础上-1
为了使IE不提示控件安全消息框,我们可以通过IObjectSafety接口来实现. 打开PolyCtl.h在class ATL_NO_VTABLE CPolyCtl : ... public CProxy_IPolyCtlEvents< CPolyCtl >的下一行加入: public IObjectSafetyImpl 并在public CProxy_IPolyCtlEvents< CPolyCtl >后面加上一个逗号"," 然后再在END_COM_MAP()上一行加入: COM_INTERFACE_ENTRY(IObjectSafety)
再次编译,成功!!打开IE点击控件,没有出现提示控件安全消息框,万事OK了.
同样的道理,我们也可以在VC程序中来使用它,步骤如下:
1) 为简单起见,创建一个默认的基于对话框的程序:UsePolygonCtrl 2) 打开Project菜单下的Add to project.../Components and Controls Gallery,打开Registered ActiveX Controls资料夹,找到已经注册的控件,这里名字是PolyCtl Class,按Insert,然后关闭对话框. 3) 打开资源编辑器,可以看到工具箱中多了一个名为PolyCtl Class的工具,选定它在对话框上加一个PolyCtl Class控件. 4) 打开ClassWizard,选择Message Maps选项卡,在左边Object IDs列表框中选定IDC_POLYCTL1,可以看到在右边Messages列表框中出现了我们所希望响应的消息: ClickIn ClickOut
分别双击他们,加入我们自己的实现如下: void CUsePolygonCtrlDlg::OnClickInPolyctl1(long x, long y) { int nSides=((CPolyCtl *)GetDlgItem(IDC_POLYCTL1))->GetSides(); nSides++; if(nSides>100) nSides=100; ((CPolyCtl *)GetDlgItem(IDC_POLYCTL1))->SetSides(nSides);//就如同一般控件一样使用它 SetDlgItemInt(IDC_EDIT_SIDES,nSides); CString str; str.Format("(%ld, %ld)",x,y); SetDlgItemText(IDC_EDIT_MOUSE,str); m_i=0; //RADIO控件的变量 UpdateData(FALSE); }
void CUsePolygonCtrlDlg::OnClickOutPolyctl1(long x, long y) { int nSides=((CPolyCtl *)GetDlgItem(IDC_POLYCTL1))->GetSides(); nSides--; if(nSides<3) nSides=3; ((CPolyCtl *)GetDlgItem(IDC_POLYCTL1))->SetSides(nSides);//就如同一般控件一样使用它 SetDlgItemInt(IDC_EDIT_SIDES,nSides); CString str; str.Format("(%ld, %ld)",x,y); SetDlgItemText(IDC_EDIT_MOUSE,str); m_i=1; UpdateData(FALSE); }
下面这个函数是设置多变形内部颜色的按钮响应函数. void CUsePolygonCtrlDlg::OnButtonSet() { int ulValue=GetDlgItemInt(IDC_SET_COLOR); ((CPolyCtl *)GetDlgItem(IDC_POLYCTL1))->SetFillColor(ulValue); ulValue=((CPolyCtl *)GetDlgItem(IDC_POLYCTL1))->GetFillColor(); SetDlgItemInt(IDC_EDIT_SHOW,ulValue); }
终于说完了,例子也举得够多得了,呵呵,Windows控件就是这么生产与应用的,是不是很简单啊:)一剑希望本文能够给您带来帮助与启迪.有什么问题清联系loomman@hotmail.com,最后这个应用例子得程序运行效果如下: 图26
|