点击下载本文配套的演示程序代码http://files.cnblogs.com/xdesigner/VB-CS-WinformControl.zip。

一.前言:

  虽然IT开发技术日新月异,不过业界仍然运行着大量的VB系统,这些系统凝聚了不少客户的投资,应当要一定程度的保护和利用。因此也就产生了一种需求,也就是使用旧的开发技术仍然可以使用新技术的产出。本文就讨论如何在VB6.0开发中使用上WinForm.NET控件。[袁永福版权所有]

二.软件原理:

  运行VB IDE,打开或创建一个EXE工程,打开窗体设计器,如下图所示:

  为了能在窗体上添加控件,需要往窗体左边的工具箱上添加项目,需要点击菜单项目“Project-Components”,此时会弹出如下图所示的对话框:

  点击“Browse”按钮,弹出文件选择对话框,这个对话框中优先选择OCX文件,而C#编译结果绝不可能是OCX文件的,此时即使选择一个.NET程序集DLL文件,无论如何必然会报错“This file not registerable as an ActiveX Component”。[袁永福版权所有]

  因此也就是说,使用C#开发的WinForm.NET控件是不可能直接通过传统的模式放置在VB窗体上。

  不过VB仍然可以通过COM方式调用.NET程序集中的对COM公开的类型。此时就可以想出一种曲线实现方式,那就是VB创建C#组件,该组件是一个WinForm.NET控件,然后调用Win32API SetParent函数,将WinForm.NET控件硬塞入VB窗体中。这样在用户界面上,用户能看到和使用WinForm.NET控件;在后台,VB代码能访问.NET组件提供的公开的属性、方法和事件,实现了VB全方位的调用WinForm.NET控件。

三.C#开发

C#控件开发

  根据上述的软件原理,笔者开发一个WinForm.NET控件并成功的应用于VB6.0的开发中,现对软件进行说明。

  这个WinForm.NET控件名为MyWinFormControl,派生自System.Windows.Forms.UserControl类型,它包含在一个名为DCWinFormControlLib的C#项目中,项目输出类型为类库,目标框架为.NET2.0,添加了对System.Windows.Forms.dll的引用。

界面设计:

MyWinFormControl控件的用户界面设计如下:

  在界面上放置一个名为“btnAction”的按钮,一个名为“myTextBox”的文本框。

定义公开属性和方法:

  打开该控件的C#代码文件,可以看到声明该类型的C#代码如下:

[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.Guid("60550064-C97F-4306-A8B2-6908F50780E3")]
[System.Runtime.InteropServices.ComSourceInterfaces(typeof(IComMyEvent))]
public partial class MyWinFormControl : UserControl
{
}

  这段代码中,第一行代码的ComVisible标记类型为COM公开的;第二行代码Guid标记了类型在COM中的唯一编号;第三行代码的ComSourceInterfaces指明该类型实现了名为IComMyEvent的事件接口。[袁永福版权所有]

  VB中无法直接绑定编译阶段未知的控件事件,同时也无法直接感应C#中的事件,为此需要编写一个接口通知VB存在若干事件,使得VB能绑定事件。因此在此定义了IComMyEvent接口,声明了C#控件中的事件,IComMyEvent接口定义如下

using System.Runtime.InteropServices;[Guid("096EF9A6-24CB-4091-A18F-34DA38C9A6F1")]
[ComVisible( true )]
[InterfaceType( ComInterfaceType.InterfaceIsIDispatch )]
public interface IComMyEvent
{/// <summary>/// 按钮按下事件/// </summary>[DispId(12340)]void ComButtonClick();/// <summary>/// 文本内容修改事件t/// </summary>[DispId(12350)]void ComTextChanged();
}/// <summary>
/// 无参数无返回值委托类型
/// </summary>
public delegate void VoidEventHandler();

而后在控件的C#代码中添加以下代码:

#region 实现 IComMyEvent 中的成员
/// <summary>
/// 按钮按下事件
/// </summary>
public event VoidEventHandler ComButtonClick = null;
/// <summary>
/// 文本内容修改事件
/// </summary>
public event VoidEventHandler ComTextChanged = null;#endregion

  这样C#中定义的事件在VB中就能绑定了,以下代码就是触发这些事件的:

private void btnAction_Click(object sender, EventArgs e)
{if (ComButtonClick != null){// 触发ComButtonClick事件
        ComButtonClick();}
}private void myTextBox_TextChanged(object sender, EventArgs e)
{if (ComTextChanged != null){// 触发ComTextChanged事件
        ComTextChanged();}
}

  对控件实现了COM公开的事件后,就可以编写COM公开的属性和方法,其代码如下:

/// <summary>
/// 公开的属性
/// </summary>
public string UserText
{get{return myTextBox.Text;}set{myTextBox.Text = value;}
}/// <summary>
/// 公开的方法
/// </summary>
public double Calcute(double p)
{return Math.Sin(p);
}

  这个用户控件虽然能在VB代码中创建和访问,但还不能直接拖放到VB窗体上,此时还需要使用代码将C#控件添加到VB窗体上:

/// <summary>
/// 将控件添加到指定句柄的窗体中
/// </summary>
/// <param name="containerHandle">指定的窗体句柄对象</param>
/// <returns>操作是否成功</returns>
public bool AppendToContainerControl(int containerHandle)
{CrossPlatformControlHostManager man = new CrossPlatformControlHostManager();man.ContainerHandle = new IntPtr(containerHandle);man.ControlHandle = this.Handle;man.Dock = this.Dock;return man.UpdateLayout();
}

  在这个函数中,参数为VB窗体中某个控件的句柄,该控件用于承载C#控件。这段代码使用了笔者编写的一个CrossPlatformControlHostManager类型,该类型专业用于执行跨应用程序的控件承载,实现“乾坤大挪移”,该类型首先定义了几个属性:[袁永福版权所有]

private IntPtr _ControlHandle = IntPtr.Zero;
/// <summary>
/// 操作的控件句柄对象
/// </summary>
public IntPtr ControlHandle
{get{ return _ControlHandle; }set{ _ControlHandle = value; }
}private IntPtr _ContainerHandle = IntPtr.Zero;
/// <summary>
/// 容器元素对象
/// </summary>
public IntPtr ContainerHandle
{get{ return _ContainerHandle; }set{ _ContainerHandle = value; }
}private DockStyle _Dock = DockStyle.Fill;
/// <summary>
/// 停靠样式
/// </summary>
public DockStyle Dock
{get{ return _Dock; }set{ _Dock = value; }
}

此外还定义了一个方法,其代码如下:

/// <summary>
/// 更新排版
/// </summary>
/// <returns>操作是否成功</returns>
public bool UpdateLayout()
{WindowInformation info = new WindowInformation(this.ControlHandle);WindowInformation container = new WindowInformation(this.ContainerHandle);if (info.CheckHandle() == false|| container.CheckHandle() == false){return false;}if (info.ParentHandle != container.Handle){if (info.SetParent(container.Handle) == false){return false;}}Rectangle clientRect = container.ClientBounds;Rectangle bounds = info.Bounds;Rectangle descBounds = bounds;switch (this.Dock){case DockStyle.Fill:descBounds = clientRect;break;case DockStyle.Bottom:descBounds = new Rectangle(0,clientRect.Height - bounds.Height, clientRect.Width,bounds.Height);break;case DockStyle.Left:descBounds = new Rectangle(0,0,bounds.Width, clientRect.Height);break;case DockStyle.Right:descBounds = new Rectangle(clientRect.Width - bounds.Width,0,bounds.Width,clientRect.Height);break;case DockStyle.Top:descBounds = new Rectangle(0,0,clientRect.Width,bounds.Height);break;}if (descBounds != bounds){info.Bounds = descBounds;}return true;
}

  这段代码中,用到了一个WindowInformation的类型,这个笔者编写的另外一个类型,实现了一些窗体相关的Win32API的封装。

  在UpdateLayout中,首先判断双方的窗体句柄是否有效,然后判断两个窗体控件的父子关系,若不是则设置控件的父子关系,底层调用的是Win32API函数SetParent。[袁永福版权所有]

  然后根据对象的Dock属性值和父控件的客户区大小来计算出子控件应有的位置和大小,然后设置子控件的位置和大小。

  这样这个控件本身开发完毕了。

设置程序集

另外还需要对C#项目进行一些设置来完成公开COM开发接口的工作。

打开工程文件中的AssemblyInfo.cs文件,该文件默认在Properties目录下,设置其代码内容为:

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;// 有关程序集的常规信息通过以下
// 特性集控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("袁永福的COM测试")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("DCWinFormControlLib")]
[assembly: AssemblyCopyright("Copyright ©  2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]// 将 ComVisible 设置为 false 使此程序集中的类型
// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型,
// 则将该类型上的 ComVisible 特性设置为 true。
[assembly: ComVisible(true)]// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("c241537f-dfe8-4502-b43a-967f9437ee7c")]
[assembly: AssemblyVersion("1.0.0.*")]

  这段代码中,重点之一是“ComVisible”,这是程序集允许向COM公开;还有一个“Guid”,这是为类型库提供一个编号;还有“AssemblyVersion”设置程序集的版本号,这里有一个*表示这个版本号是累计的,[袁永福版权所有]每次编译生成的程序集的版本号都会增加的。

强名称

要提供COM开发接口,.NET程序集必须是强名称的。

要对.NET程序集强名称,首先是要有一个SNK签名文件,若没有,则可以使用.NET SDK中的SN.EXE工具生成一个SNK文件。

打开VS.NET自带的.NET SDK命令行界面,执行命令行“SN.EXE –k d:\dctest.snk”,即可生成一个强名称使用的SNK文件。则命令行输出文本为:

Microsoft(R) .NET Framework 强名称实用工具 版本 4.0.30319.1
版权所有(C) Microsoft Corporation。保留所有权利。

密钥对写入到 d:\dctest.snk

在VS.NET中打开项目的属性页面,切换到“签名”页面,如下图所示:

勾选“为程序集签名”,然后在强名称秘钥文件下列列表中点击“浏览”项目,选择SNK文件。然后重新编译,即可生成强名称的.NET程序集DLL文件了。

注册和生成类型库文件

当成功的编译生成.NET程序集DLL文件后,需要对该程序集进行注册和生成COM开发使用的类型库文件。[袁永福版权所有]

在VS.NET命令行环境,执行命令“regasm  编译输出目录\DCWinFormControlLib.dll /tlb /codebase”,则该命令行输出的内容如下:

Microsoft(R) .NET Framework 程序集注册实用工具 4.0.30319.1
版权所有(C) Microsoft Corporation 1998-2004。保留所有权利。

成功注册了类型
成功注册了导出到“编译输出目录\dcwinformcontrollib.tlb”的程序集和类型库

特别要注意,对于Win7或更高系统中,必须以管理员的模式启动VS.NET命令行环境,否则程序会爆无权限的错误。

四.VB程序开发

  当成功的执行完上述操作后,我们就已经生成了可用于VB开发的.NET程序组件了。

  笔者使用VB6.0开发了一个程序能应用MyWinFormControl控件,程序已经写好,现对其进行说明。[袁永福版权所有]

  首先该VB工程名为DCVBExe,它是 一个普通的EXE的VB工程。在VB6.0 IDE中点击菜单“工程-引用”,显示如下图所示的添加引用对话框:

在该对话框中就可以看到电脑系统中已经有了DCWinFormControlLib的引用了,VB工程就包含了该引用。

本VB工程的主窗体设计如下:

这个VB窗体中放置了一个名为“cmdSetText”的按钮控件,还有一个名为“picContainer”的PictureBox控件。

该窗体的VB代码如下:

Option Explicit' 定义控件变量
Private WithEvents myWinFormControl As DCWinFormControlLib.myWinFormControl' VB按钮点击事件处理
Private Sub cmdSetText_Click()myWinFormControl.UserText = "袁永福到此一游"
End Sub' 窗体加载事件
Private Sub Form_Load()Set myWinFormControl = New DCWinFormControlLib.myWinFormControlmyWinFormControl.Dock = 4myWinFormControl.AppendToContainerControl Me.picContainer.hWnd
End Sub' 窗体大小改变事件
Private Sub Form_Resize()If Me.ScaleWidth > 3000 And Me.ScaleHeight > 3000 ThenMe.picContainer.Width = Me.ScaleWidth - Me.picContainer.Left - 50Me.picContainer.Height = Me.ScaleHeight - Me.picContainer.Top - 50If Not myWinFormControl Is Nothing ThenmyWinFormControl.AppendToContainerControl Me.picContainer.hWndEnd IfEnd If
End Sub' 窗体卸载事件
Private Sub Form_Unload(Cancel As Integer)Set myWinFormControl = Nothing
End Sub' 响应控件ComButtonClick事件
Private Sub myWinFormControl_ComButtonClick()MsgBox "用户点击了控件中的按钮"
End Sub' 响应控件ComTextChanged事件
Private Sub myWinFormControl_ComTextChanged()Me.Caption = "用户修改了控件中的文字"
End Sub

  在这段代码中“Private WithEvents myWinFormControl As DCWinFormControlLib.myWinFormControl”定义了C#控件的变量,此处使用了WithEvents关键字声明变量可以绑定事件,此时就可以添加myWinFormControl_ComButtonClick方法和myWinFormControl_ComTextChanged方法,这两个方法就是控件的事件处理。[袁永福版权所有]

  在窗体加载代码中调用了“myWinFormControl.Dock = 4”,实际上就是设置了C#中的System.Windows.Forms.Control类型的Dock属性,由于VB中还不能识别System.Windows.Forms.DockStyle枚举类型,因此只能设置整数,这里的整数4等价于DockStyle.Right值。

  由于MyWinFormControl类型只声明了IComMyEvnet接口,没实现其他接口,因此在开发阶段,VB无法识别控件的所有的属性和方法,因此在编写代码的时候无法弹出类型成员列表,因此只能利用VB的动态语言特性先硬编码,在运行时VB会后期的识别和调用组件的公开的类型成员。

  在窗体加载时还运行了“myWinFormControl.AppendToContainerControl Me.picContainer.hWnd”,用于将控件作为图片框的子控件硬塞入到VB窗体中。

  在窗体大小改变事件中,这行代码还重新执行了一遍,用于根据作为容器的图片框控件的大小来更新WinForm.NET控件的大小。

  这样VB程序开发完毕,编译执行EXE文件,该VB程序的运行界面如下图所示:

  本程序有一个已知的BUG,那就是输入焦点分配有点混乱。由于很多代码绕过了VB运行库,而是在.NET运行库中运行,这是一种不多见的软件运行架构。因此用户界面上尽量少放置或不放置可以获得焦点的VB控件。

五.常见问题和调试

  VB调用.NET控件是一种异构软件架构,很容易出现各种问题,最常见的是在VB中试图创建控件对象实例时爆出“ActiveX component can't create object”的错误。

解决方法如下

1.       首先确定系统安装了.NET框架。
2.       执行命令行“regasm 程序集目录\DCWinFormControlLib.dll   /tlb   /codebase”。
3.       若还不行需要清理下系统注册表,对于本控件,设置的GUID值为“60550064-C97F-4306-A8B2-6908F50780E3”,因此使用注册表编辑器打开注册项目路径“HKEY_CLASSES_ROOT\CLSID\{60550064-C97F-4306-A8B2-6908F50780E3}\InprocServer32”,可以看到该组件历次版本的注册信息,删掉这些注册表项目,然后重新运行一遍regasm命令行。
4.       可以使用gacutil工具将DCWinFormControlLib.dll放置在全局程序集缓存中。
5.       还可以将VB编译生成的EXE文件放在DCWinFormControlLib.dll和DCWinFormControl.tlb的文件目录中运行。

  另外一个有可能遇到的情况就是VB程序不能运行,运行VB程序需要系统中有VB虚拟机,要检查Windows系统目录下的System32子目录中是否存在MSVBVM60.DLL,若没有则需要下载安装VB6运行时,推荐下载地址为“http://download.microsoft.com/download/5/a/d/5ad868a0-8ecd-4bb0-a882-fe53eb7ef348/VB6.0-KB290887-X86.exe”。[袁永福版权所有]

六。小结

  本文虽然讨论的仅仅是VB中调用C#写的WinForm控件,实际上可以扩展开来使用,比如可以以此为基础,实现在VB\PB\DELPHI\VC窗体中嵌入WinForm.NET控件,由于WinForm.NET控件能承载WPF元素,因此也就能在VB中嵌入WPF程序了。

VB调用C#写的WinForm.NET控件相关推荐

  1. 如何在多线程中调用winform窗体控件2——实例篇

    如何在多线程中调用winform窗体控件2--实例篇 针对之前文章<如何在多线程中调用winform窗体控件>,下面举个我项目中的实际案例,这是一个我自定义控件在异步设置焦点时的代码.在新 ...

  2. Github 开源:使用控制器操作 WinForm/WPF 控件( Sheng.Winform.Controls.Controller)

    利用午休时间继续把过去写的一些代码翻出来说一说,文章可能写的比较简略,但是我会努力把核心意思表达清楚,具体代码可直接访问 Github 获取. Github 地址:https://github.com ...

  3. DevExpress Winform 常用控件

    前言 DevExpress 控件的功能比较强大,是全球知名控件开发公司,对于开发 B/S 或 C/S 都非常出色,可以实现很炫且功能强大的效果. DevExpress Winform 常用控件是本人在 ...

  4. winformbutton边框怎么改_C# WinForm窗体控件Panel修改边框颜色以及边框宽度方法

    C# WinForm窗体控件Panel修改边框颜色以及边框宽度方法 1.新建组件这里可以自定义一个Panel控件起名为PanelEx 2.增加一个BoderColor属性和BoderSize属性 pr ...

  5. winform常用控件介绍

    winform常用控件介绍 1.窗体 1 2.Label 控件 3 3.TextBox 控件 4 4.RichTextBox控件 5 5.NumericUpDown 控件 7 6.Button 控件 ...

  6. winform 判断控件有没有被遮挡_编程入门基础之 winform(2)

    在编程入门1中,我们通过WINFORM等控件画出了登录界面,我们今天写登录界面的登录按钮事件. 装修预算小程序登录按钮事件 在上图中,我们看到了,有几个判断 ,在C#中,if ,else是作为条件判断 ...

  7. WinForm皮肤控件(SkinEngine)

    WinForm皮肤控件(SkinEngine) 利用 IrisSkin2.dll 所提供的控件 SkinEngine 来为窗体添加皮肤. IrisSkin2.dll 及 皮肤素材,下载地址:http: ...

  8. VB生成二维码图形的控件,CSDN利用盗版卖卖会员44积分赚钱

    VB生成二维码图形的控件+纯VB6生成二维码-无需控件无需DLL-CSDN下载 https://download.csdn.net/download/xiaoyao961/11226174 上面这个是 ...

  9. WinForm的控件

    WinForm的控件: -------------------- WinForm的控件: ---------------------      不同的控件类型:           --------- ...

最新文章

  1. 阅读Book:MultiObjective using Evolutionary Algorithms(7)---Weighted Sum Methods ε-Constraint Methods
  2. python执行系统命令的方法
  3. Optiver Career Fair
  4. Codeforces - 466C - Number of Ways - 组合数学
  5. Java使用JDBC连接随意类型数据库(mysql oracle。。)
  6. delete与delete[]的区别
  7. centos系统时间不准
  8. 如何应对Spark-Redis行海量数据插入、查询作业时碰到的问题
  9. python错误异常处理try except Error
  10. 使用ViewSwitcher模拟手机屏幕应用分屏和切换
  11. msbuild.exe编译c#项目
  12. Android 保持屏幕常亮
  13. 亮风台AR眼镜震撼发布 HiAR 产品全面升级
  14. 黑金AX7Z100 FPGA开发板移植LWIP库(一)PS端
  15. 计算机网络 之 BitTorrent技术对网络的潜在危害
  16. 高质量WordPress下载站模板5play主题源码
  17. HDU 6078 Wavel Sequence【动态规划】
  18. .net 5+ 知新:【2】 .Net Framework 、.Net 、 .NET Standard的概念与区别
  19. java比较两个对象_java判断两个对象是否相等的方法
  20. windows10 该值受安全引导策略保护,无法进行修改或删除。禁用驱动程序强制签名

热门文章

  1. jQuery原理系列-css选择器实现
  2. 【EXLIBRIS】随笔记 001
  3. 洛谷P1036选数(素数+组合数)
  4. iis下 ActiveSync插件无法访问(下)
  5. 20160531-20160607springmvc入门
  6. 《Lua游戏开发实践指南》学习笔记3
  7. OpenXava 4.6.1 发布,Web 快速开发套件
  8. 浅析枚举类型(Enumerated types)
  9. 一个ant的简单实例
  10. 接口文档-swagger-bootstrap