VB.NET WinForm中如何从其他类控制非启动窗体
问题场景
现有VB.NET测试程序包含2个窗体、1个公共类:
- Form1:内置控件
TextBox1、Button1 - Form2:内置控件
TextBox1 - Class1:提供修改两个窗体文本框内容的公共方法
各模块原有实现代码如下:
Class1代码:
Public Class Class1 Public Sub ChangeTextOnForm1() Form1.TextBox1.Text = "Success" End Sub Public Sub ChangeTextOnForm2() Form2.TextBox1.Text = "Success" End Sub End Class
Form1代码:
Public Class Form1 Dim Class1 As New Class1 Public Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Class1.ChangeTextOnForm1() End Sub Public Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim Form2 As New Form2 Form2.Show() End Sub End Class
Form2代码:
Public Class Form2 Dim Class1 As New Class1 Public Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load Class1.ChangeTextOnForm2() End Sub End Class
运行现象
- Form1设为启动窗体时,加载阶段调用
Class1.ChangeTextOnForm1()可正常将Form1的TextBox1文本修改为"Success" - Form2设为启动窗体时,加载阶段调用
Class1.ChangeTextOnForm2()可正常将Form2的TextBox1文本修改为"Success" - Form1设为启动窗体时,点击Button1弹出的Form2中,TextBox1文本未发生任何修改
问题根因
VB.NET WinForm项目内置窗体默认实例机制:项目启动时,框架会自动为启动窗体在My.Forms全局集合中生成一个共享的默认实例,代码中直接通过Form1.XXX、Form2.XXX的形式访问的,本质就是这个全局默认实例。
第三种场景失效的核心原因是操作的窗体实例和实际显示的窗体实例不是同一个对象:
Form1的Button点击事件中通过Dim Form2 As New Form2手动创建了一个独立的Form2实例并展示,但Class1的ChangeTextOnForm2方法修改的是从未被加载显示的My.Forms.Form2默认实例,弹出的窗体实例从未被Class1操作过,自然不会出现文本变化。
前两个场景能正常运行,是因为启动窗体会由框架自动加载默认实例,代码操作的实例和实际显示的实例完全一致,所以效果符合预期。
解决方案
方案1:使用默认实例打开窗体(快速修复,适合小型项目)
无需修改Class1和Form2的代码,仅调整Form1中Button1的点击事件逻辑,不要手动实例化Form2,直接调用框架提供的全局默认实例即可,保证Class1操作的实例和实际显示的实例为同一个对象:
Public Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' 直接访问全局默认实例 Form2.Show() End Sub
该方案改动量极小,但由于依赖全局共享实例,耦合度较高,不适合中大型项目使用。
方案2:传递目标窗体实例作为参数(规范实现,推荐)
移除Class1对全局默认实例的硬编码依赖,将需要操作的窗体实例作为参数传入方法,从根源上规避实例不匹配的问题,符合面向对象设计的低耦合要求。
首先修改Class1的方法定义,增加目标窗体参数:
Public Class Class1 ' 接收要操作的Form1实例 Public Sub ChangeTextOnForm1(targetForm As Form1) targetForm.TextBox1.Text = "Success" End Sub ' 接收要操作的Form2实例 Public Sub ChangeTextOnForm2(targetForm As Form2) targetForm.TextBox1.Text = "Success" End Sub End Class
修改Form1的加载事件,传入当前窗体实例:
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' Me代表当前加载的Form1实例 Class1.ChangeTextOnForm1(Me) End Sub
修改Form2的加载事件,传入当前窗体实例:
Public Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' Me代表当前加载的Form2实例,无论实例是如何创建的都能准确操作 Class1.ChangeTextOnForm2(Me) End Sub
Form1中Button1点击事件手动New Form2的逻辑无需改动,运行后点击按钮弹出的Form2可正常修改文本框内容。该方案不依赖任何全局状态,不管窗体是否为启动窗体、创建多少个并行的窗体实例,只要传入需要操作的实例即可精准控制,适配所有场景。
补充说明:VB.NET中窗体控件默认的访问级别为
Friend,同项目内的类可以直接访问控件成员;如果需要跨程序集访问窗体控件,需要将对应控件的Modifiers属性修改为Public。
内容的提问来源于stack exchange,提问作者codestalker

