VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用

在一个项目中不可避免会有多个窗体的控件布局类似,里面的代码也有好多相同的,可以新建窗体,然后复制粘贴来实现窗体的重复使用,这种方式固然好,可麻烦也随之而来,随着项目的深入,不可避免的要修改窗体布局或代码等,这时就要对多个这样的窗体全部进行修改。正是如此,所以笔者决定采用继承窗体的方式来实现窗体的复用。

窗体继承的官方帮助:Windows 窗体可视化继承

一、 继承窗体的操作方法

窗体继承有两种方式:编程方式或使用视觉继承选取器。编程方式简单易上手,本文仅介绍以编程方式继承窗体,操作步骤如下:

1、创建项目

新一个Windows窗体应用程序,包含被继承的窗体(在这里称基窗体)BaseFrm和继承自基窗体的子窗体Form2。

2、创建基窗体

在基窗体设计界面拉入一个DataGridView控件、一个Button控件和一个TextBox控件,当然啦,你还可以拖入其他控件,只要你需要。如图所示:
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第1张图片

3、创建子窗体

打开子窗体的代码界面,我们将会看到自动生成的代码,如下图:
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第2张图片
要使Form2窗体继承自基窗体BaseFrm,很简单,只需要添加代码:Inherits BaseFrm即可。如下图:
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第3张图片
是不是很简单?

二、窗体继承问题多多

测试过程中发生很多意想不到的错误和问题,具体如下:

1、为类“Form2”指定的基类“BaseFrm”不能与它的其他分部类型之窗体继承一的基类“Form”不同。

当键入Inherits BaseFrm后就会报这个错误,如下图所示:
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第4张图片
解决办法:
在“解决方案资源管理器”中依次点开“Form2.vb”左侧的三角形。
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第5张图片
双击打开“Form2.Designer.vb”代码窗口,将代码Inherits System.Windows.Forms.Form修改为Inherits BaseFrm,如下图:
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第6张图片

2、文件中的类都不能进行设计,因此未能为该文件显示设计器。设计器检查出文件中有以下类: Form2 — 未能加载基类“窗体继承.BaseFrm”。请确保已引用该程序集并已生成所有项目。

打开子窗体Form2的设计界面,会出现上述错误,如图:
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第7张图片
在官方帮助中有这么一段话:为了从窗体进行继承,包含该窗体的文件或命名空间必须生成为可执行文件或 DLL。 若要生成项目,请从“生成”菜单选择“生成”。 此外,对命名空间的引用必须添加到继承窗体的类。 显示的对话框和菜单命令可能会与“帮助”中的描述不同,具体取决于你现用的设置或版本。 若要更改设置,请在 “工具” 菜单上选择 “导入和导出设置” 。
解决办法
根据提示,点击“生成”菜单选择“生成 窗体继承”。
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第8张图片
关闭Form2窗体的设计界面再重新打开即可看到设计视图,如图:
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第9张图片

3、子窗体只能添加新控件,不能修改从基窗体继承来的控件。

如上图,继承来的控件会有一把锁,我们无法移动,也无法在控件属性窗口里修改其属性。
解决办法
在基窗体BaseFrm中把需要在子窗体中修改属性的控件(本文中仅修改DataGridView控件和Button控件)Modifires属性设置为protected或public,如下图所示,注意,只能在属性窗口设置,不能通过代码设置Modifires属性。回到子窗体设计界面,还是不能修改控件属性。
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第10张图片
在官方帮助里的看到这样的说明:在应用程序开发过程中,可能经常需要更改基窗体的外观,在设计时,如果生成了包含基窗体的项目,则对基窗体外观所做更改(属性的设置或控件的增减)将反映在继承的窗体上。 仅将更改保存到基窗体是不够的。 若要生成项目,请从“生成”菜单选择“生成”。在运行时对基窗体所做修改不影响已实例化的继承窗体。
根据官方提示,重新生成项目,关闭子窗体设计界面再打开,出现了下图错误提示:值不在预期的范围内。
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第11张图片
万般无助,尝试点击“开始调式”,调试过后再打开子窗体界面,居然能正常显示设计界面了,Button控件可以修改属性,但DataGridView控件还是处于锁定状态而无法修改其属性。
从官方帮助中发现这段说明:对于WebBrowser、ToolStrip、ToolStripPanel、TableLayoutPanel、FlowLayoutPanel、DataGridView控件,无论使用何种修饰符(private、protected 或 public),继承的窗体中的这些控件始终为只读。
原来DataGridView控件只能为只读。

4、在基窗体和子窗体里添加的事件处理程序会同时调用

分别在基窗体和子窗体里为按钮Button1添加Click事件处理程序:
基窗体:

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        MessageBox.Show("这是基窗体Button1控件Click事件")
    End Sub

子窗体:

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        MessageBox.Show("这是子窗体Button1控件Click事件")
    End Sub

测试时,这两段代码均被执行,如下图:
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第12张图片
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第13张图片
可否控制这两段代码的执行呢?比如仅执行子窗体里的Button1按钮Click事件。
解决办法
最简单的办法是删除不需要执行的代码,如果仅执行子窗体里的Button1按钮Click事件,则把基窗体里的Button1按钮Click事件代码删除即可。
当然不删除也是可以的,需把以上代码修改为:
基窗体:

    Protected Overridable Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        MessageBox.Show("这是基窗体Button1控件Click事件")
    End Sub

子窗体:

    Protected Overrides Sub Button1_Click(sender As Object, e As EventArgs)
        MessageBox.Show("这是子窗体Button1控件Click事件")
    End Sub

可以看出,把访问级别Private修改为Protected或public,基窗体里的代码增加了Overridable修饰符,表示该方法可以被重写,而子窗体里增加Overrides修饰符,表示重写基窗体里的Button1按钮Click事件处理函数。特别注意子窗体里不再需要关联事件,即需要删除这段代码:Handles Button1.Click。测试结果与预想一样,仅执行子窗体里的Button1按钮Click事件。
如果我想同时执行基窗体里的Button1按钮Click事件,可以实现吗?只需在子窗体里的Button1按钮Click事件处理函数里增加代码:MyBase.Button1_Click(sender, e)
完整代码如下:

    Protected Overrides Sub Button1_Click(sender As Object, e As EventArgs)
        MessageBox.Show("这是子窗体Button1控件Click事件")
        MyBase.Button1_Click(sender, e)
    End Sub

    Protected Overrides Sub Button1_Click(sender As Object, e As EventArgs)
        MyBase.Button1_Click(sender, e)
        MessageBox.Show("这是子窗体Button1控件Click事件")
    End Sub

代码顺序不同执行的顺序也不同,具体你可以亲自测试一下。
经过如此处理,我们就可以随心所欲的控制基窗体与子窗体里的事件的发生。
如果遇到基窗体里的某个方法不能被重写,可以自定义一个方法,称为虚方法。还是以Button1按钮Click事件为例来演示。
基窗体:自定义了一个虚方法BtnClick,然后在Button1按钮Click事件处理函数里调用该方法。

    Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        BtnClick()
    End Sub

    Protected Overridable Sub BtnClick()
        MessageBox.Show("这是基窗体Button1控件Click事件")
    End Sub

子窗体:重写基窗体里的 BtnClick方法即可,不需要添加Button1按钮Click事件。

Protected Overrides Sub BtnClick()
        MessageBox.Show("这是子窗体Button1控件Click事件")
    End Sub

如果需要同时调用基窗体和子窗体里的Button1按钮Click事件,子窗体里的代码增加MyBase.BtnClick(),即:

Protected Overrides Sub BtnClick()
        MessageBox.Show("这是子窗体Button1控件Click事件")
        MyBase.BtnClick()
    End Sub

5、打开子窗体设计界面时会调用基窗体的无参构造函数和Load事件

在基窗体代码窗口里添加New函数和Load事件代码:

Public Sub New()

        ' 此调用是设计器所必需的。
        InitializeComponent()

        ' 在 InitializeComponent() 调用之后添加任何初始化。

        MessageBox.Show("这是基窗体New函数")
    End Sub

    Private Sub BaseFrm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        MessageBox.Show("这是基窗体Load事件")
    End Sub

打开子窗体设计界面时,你会发现这两段代码均被执行。虽然并无大碍,但总感觉不舒服,在其他博文说可以通过Component.DesignMode 属性来阻止以上代码在打开设计界面时执行,即把不需要运行的代码包含在以下IF块中:

        If Not DesignMode Then
          
        End If

关于Component.DesignMode 属性,摘抄于其他博文,本人并不是很理解:
“一个控件只有在它自己被拖拽到设计器的时候,其 DesignMode 才是真,如果它被包含在其他控件中被加入到设计器,那么那个控件才是在设计模式,而它不是!换句话说,DesignMode 并不能反映当前环境是否是运行时,它只能告诉你,这个控件当前是不是直接被设计器操作(嵌套的已经不算了) 。”
修改过后的代码如下:

    Public Sub New()

        ' 此调用是设计器所必需的。
        InitializeComponent()

        ' 在 InitializeComponent() 调用之后添加任何初始化。

        If Not DesignMode Then
            MessageBox.Show("这是基窗体New函数")
        End If
    End Sub

    Private Sub BaseFrm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        If Not DesignMode Then
            MessageBox.Show("这是基窗体Load事件")
        End If
    End Sub

测试发现仅能屏弊Load事件,而New函数里的if块并没有被屏弊。
如果在基窗体里添加的是含参New函数又会如何呢?
我们把原来的无参New函数修改为:

    Public Sub New(mystring As String)

        ' 此调用是设计器所必需的。
        InitializeComponent()

        ' 在 InitializeComponent() 调用之后添加任何初始化。

        If Not DesignMode Then
            MessageBox.Show("这是基窗体New函数,传入的参数为:" & mystring)
        End If
    End Sub

会报错:类“Form2”必须声明一个“Sub New”,原因是它的基类“BaseFrm”没有不使用参数就可以调用的可访问“Sub New”。
根据提示为“Form2”必须添加“Sub New”:

    Public Sub New()
        ' 此调用是设计器所必需的。
        InitializeComponent()

        ' 在 InitializeComponent() 调用之后添加任何初始化。
    End Sub

又报错:“Form2”的基类“BaseFrm”没有不使用参数就可以调用的可访问“Sub New”,因此该“Sub New”的第一个语句必须是对“MyBase.New”或“MyClass.New”的调用。
再根据提示增加MyBase.New:

    Public Sub New()
        MyBase.New("ok")
        ' 此调用是设计器所必需的。
        InitializeComponent()

        ' 在 InitializeComponent() 调用之后添加任何初始化。
    End Sub

终于不再报错,调试后再次打开子窗体设计界面,又报错了:未找到类型“窗体继承.BaseFrm”上的构造函数。如图:
VB.NET学习笔记:一步一步跟我学习Windows 窗体可视化继承实现窗体复用_第14张图片
估计在设计期间只能调用无参New函数,所以尝试在基窗体里再次添加无参New函数,问题解决,不再报错并正确显示设计界面。

6、窗体与类

面对对象编程中,窗体也是类,所以我们也可以通过类来继承窗体,具体测试就不再演示了。但我还是建议按本文方式继承窗体,这样会自动生成一个窗体可视设计器对应的Designer类,使窗体初始化代码与逻辑代码分离为两个文件。而如果通过新建一个类的方式,会导致窗体可视化设计器生成的代码在本身的类中。

你可能感兴趣的:(VB.NET学习笔记)