如何从继承它的子窗体访问父窗体上的按钮

时间:2014-10-09 13:04:59

标签: vb.net winforms inheritance

我在VB.NET中创建了一个Windows窗体,其上有一个带有Datagridview的Panel,在它下面有一个带有Save和Close按钮的FlowLayoutPanel,称之为frmParent。然后我创建了frmChild,并在其上继承了frmParent。然后我将我的DataGridView连接到一些数据,它工作正常。

接下来,我尝试添加代码以保存对数据网格的更改,并且按钮被锁定。我检查了修饰符:两个按钮和包含它们的FlowLayoutPanel都是受保护的。我读到访问控件的常见问题,这些控件包含在另一个控件的集合中。但是DataGridView很好,所以我的问题是:

我需要做些什么才能在子表单上使用“保存”和“取消”按钮?我是否需要取消FlowLayoutPanel,还是有另一种方式?在这个简单的例子中,它并不是很重要,但我想定期开始使用视觉继承,我想知道最好的做事方式。

3 个答案:

答案 0 :(得分:2)

这样的东西?

Public Class frmParent

    Private Sub Button_SaveClick(sender As Object, e As EventArgs)
        ButtonSaveClicked(...) ' pass here any argument you want (e, sender, anything...)
    End Sub

    Protected Overridable Sub ButtonSaveClicked(...)
        ' Do here what frmParent should do upon Button_Save Click...
    End Sub

End Class

Public Class frmChild
    Inherits frmParent

    Protected Overrides Sub ButtonSaveClicked(...) ' same arguments
        ' Do here what frmChild should do upon Button_Save Click...
        ' Eventually call MyBase.ButtonSaveClicked(...)
        ' if you want frmParent to act aswell...
    End Sub

End Class

编辑:对于问题的以下部分

  • 分离用户界面和对象数据
  • 是否传递了对象的实例?

我不是编程方面的专家,但我已经阅读了一篇关于该用户界面/对象的有趣内容。

我首先想到的用户界面是一篇博客文章(不要回忆起链接,抱歉),谈论Windows way编程和Linux way。在开始偏离主题之前,我想明确表示我并不关心linux vs windows,而且我对此类辩论并不感兴趣。只想在这篇博文中分享我记得的内容:

在Windows编程中,您通常会想到(并邀请做)的第一件事是创建用户界面:Windows窗体(或WPF中的窗口)然后您将创建对象将通过该界面进行操纵。在Linux编程中,它是反过来的:你首先要创建他们应该能够做的所有事情的对象,并且只有在你想到将会增强&的用户界面之后。 #34;操纵它们的方法。

^^或者类似的..我的Linux知识是零,所以我无法进行比较,但我认为上述情况属于真实情况。而且我认为自从OOP存在以来,这种" windows方式"编程(我曾经习惯)有许多缺点和杂乱的代码:你经常问自己,代码的特定部分应该包含在对象或用户界面中。

所以,我已经了解了我的想法,这可能是愚蠢的,但我很好。我开始尝试" linux方式" ...至少,"我认为Linux程序员做的方式"


让我们采取名为DataGridView的控件。它可以包含文本,保管箱,按钮,图像等。我没有在边界数据集中挖掘以保持简单。基本上,DataGridView是一个对象,拥有操纵其中上述子对象的所有东西。不幸的是,StringImage不是(可视)控件,而dropboxbutton则是。你有一个典型的windows创建对象(DataGridView),它是一个数据和用户界面的熔炉,但是你没有一个只处理DataGridView数据部分的对象,另一个只处理数据部分的对象事物的可视化用户界面(或者至少,我不知道.Net中的此类对象)AFAIK 您被迫处理两个数据和用户界面时使用DGV!

所以我创建了一个类,简单地称它为Table_Class,它可以包含像String,Bitmap,List这样的对象( 不是控件 ) String),XML ...几乎所有包含数据都是二进制编码的。

然后我创建了一个表单,并将其命名为Table_Form,可以显示Table_Class的内容。但几乎所有编辑都是Table_Class成员的函数和方法。如果我想按列对网格进行排序,那么它就是通过其内置函数之一来完成它的类。

当然,我不是在这里表明我浪费时间重新发明轮子,我知道我做错了,但这种方法是我需要的。 ; m处理从文件加载的大量各种数据,并将保存为文件。我很高兴拥有自己的数据类,并乐于在我想要时扩展其功能

此blabla的主要目的是说明我对将整个班级某些成员传递给表单的看法。当然,在这个串联Table_Class / Table_Form中,我将整个Table_Class传递给Table_Form,反之亦然。事实上,它可以同时使用,也可以同时使用两种不同的东西:

' SAMPLE CODE : Entire classes takes several files...
Public Partial Class Table_Class
' here is the object that is invisible to the user
    Private _ContainerForm As Table_Form = Nothing

    Public Sub ShowForm(Optional ByVal OwnerForm As Form = Nothing)
        If _ContainerForm Is Nothing Then
            _ContainerForm = New Table_Form(Me)
        End If
        If OwnerForm Is Nothing Then
            _ContainerForm.Show()
        Else
            _ContainerForm.Show(OwnerForm)
        End If
    End Sub

    Public Sub New()

    End Sub

    Public Sub New(ByRef NewContainerForm As Table_Form)
        _ContainerForm = NewContainerForm
        _ContainerForm.SetTable(Me)
    End Sub
End Class

这是(关联的)Form(构造函数和关联的Object初始化)

Public Partial Class Table_Form
    Private _SelectedTable As Table_Class = Nothing

    Public Sub SetTable(ByRef NewTable As Table_Class)
        RemoveHandlers() ' Table_Class has events.
        ' If _SelectedTable is not Nothing,
        ' remove handlers prior to redefining a new selected Table.
        _SelectedTable = NewSelectedTable
        _SelectedTable.SetContainerForm(Me)
        InitializeHandlers() ' Add all handlers for UI updates upon Table_Class edits.
    End Sub

    Public Sub New()

    End Sub

    Public Sub New(ByRef NewSelectedTable As Table_Class)
        SetTable(NewSelectedTable)
    End Sub
End Class

正如您所看到的,当用户不关心其内容时,或者通过用户界面,我可以在后台操作Table_Class。使用DataGridView,您必须处理所有可视外观,以便为Data / DataSet / Database构建输出。

这里要注意的重要事项是从您希望通过用户界面直观显示对象的那一刻起,您必须将该对象的关键成员传递给显示界面。你不能在某个时刻避免这样做。在我的例子中,我已经通过了ENTIRE Table_Class,因为我正在对该类进行每一次数据修改。

与DataGridView的区别在于:

  • 使用DGV,当您使用KeystrokeOrF2编辑单元格中的一个(字符串)条目时,值存储在该DGV的该单元格中,然后,您的应用程序直接以编程方式将值发送到关联的数据库,或者您有用于更新编辑的保存按钮或保存菜单。
  • 使用我的Table_Class,当然我使用DataGridView来显示其内容,我在工具栏和menuitem中有一个保存按钮。但是,当我单击这些按钮时,Save方法不在我的Table_Form表单中。 Save Method是一个属于Table_Class本身的方法。

这是一个例子,它是可视界面(DataGridView / Form)与实际包含数据的对象之间的分离。

我知道这个Table_Class / Table_Form示例是一个复杂的例子,但是我理解你原来的帖子中的一半secund部分的方式是:"怎么做我将用户界面和数据/对象操作分开了#34;

答案:由您决定!
我认为我们有类似的方法:我的Table_Class将所有内容都作为您的BlackBox类。我的Table_Form显示数据和您的父/子表格。

我假设您问题的剩余部分是:"当它位于FlowLayoutPanel" 时,我无法通过我的按钮的Click方法调用BlackBox.SaveChanges 。这与对象/用户界面限制综合症无关,并且是完全不同的问题。这个长答案之前这个答案的主要目的是:我通过调用方法(ButtonSaveClicked())建议了一种解决方法,而不是在Button_SaveClick()方法下编写代码direclty的保存部分处理点击。我不知道这种方法是否适用于您的锁定"按钮与否。

回到用户界面/对象的事情:上面是这样的:"你必须在某个时刻将对象的关键成员传递给用户界面" ,关键成员必须是一个对象(使用您想要操作的关键数据,在这种情况下,我假设要保存的数据)在我的Table_Class / Table_Form示例中,我已经传递了Table_Form中的整个Table_Class:

Public Partial Class Table_Form
    Private _SelectedTable As Table_Class
    ' ...
End Class

所以当我想要Table_Class(包含数据操作的对象)来保存编辑时,通过表格(作为用户界面的形式)我在表格中有这些方法集:

Public Partial Class Table_Form
    Private Sub Menu_Main_File_SaveClick(ByVal sender As Object, ByVal e As EventArgs)
        ' MenuStrip -> File -> Save
        SaveTable()
    End Sub

    Protected Sub ToolBar_Main_SaveClick(ByVal sender As Object, ByVal e As EventArgs)
        ' A Save Button shortcut on a toolbar.
        SaveTable()
    End Sub

    ' ^^ Both controls calls the same method SaveTable()
    ' This is similar to the ButtonSaveClicked method I initially suggested
    ' What SaveTable does ?

    Protected Overridable Sub SaveTable()
        If _SelectedTable IsNot Nothing Then
            _SelectedTable.Save(True)
        End If
        ' It just calls the Table_Class.Save(... arguments)
    End Sub

    ' ...
End Class

最后,用户界面中不存在处理数据保存方式的代码。为了保存更改,Table_Class对象有一组Save(...)方法来处理扩展,如SaveAs(打开SaveFileDialog),覆盖不带提示,另存为二进制或保存为文本......如果我想要更多扩展比如在Web上,数据库中发送数据,甚至在目录中打包/解压缩表内容,我所要做的就是在Object(Table_Class)中实现它,只有可视部分才会在表单中处理,就像我从一开始就这样做。

所以,当你问我" 我不会以这种方式在父表单中找到特定于孩子的代码吗?"我会说:"它取决于你"。我已经确定Table_Class可以完成所有工作:数据加载/解析,数据更新和数据保存。如果在将来的某个时间我必须实现一种保存数据的新方法,我可以选择添加我的Table_Class的Save()方法的重载,或者使用另一种形式为另一个应用程序构建特定的Save方法我的Table_Class如果我想要的话。这可能是因为Table_Class:

  • 不是用户界面
  • 具有相关的用户界面,以防
  • 虽然它可以通过编程方式读取和写入数据。
  • 是的,我在UI中传递整个Table Class ,无论是使用继承的父表单还是子表单。

我认为"在您的用户界面方面弄乱了父/子代码"高度依赖于(应该)操纵数据的非用户界面对象的功能。

(实际上,表类不是单个表。它是表树的节点,或包含数据的表,以及包含其他每个数据的表子。您可以将其视为DataGridView和TreeView的合并,这就是为什么私有变量和UI窗体被命名为_SelectedTable。)
我提到Table_Class的最后一个复杂性是为了清楚地告知无论对象有多复杂,它都可以很容易地使用你使用的任何用户界面,因为它没有单一的可视代码。对象,并且在用户界面中操作数据的代码非常少(所有UI都是从对象读取数据,显示/编辑或将它们用于其他目的,并将经过验证的修改发送到对象,然后调用Object .Save())


我已经详细解释了我这样做的方式,让您有权决定您的应用程序的行为方式,BlackBox类应该能够做什么实现单独以及用户界面的限制。该分析没有简单的答案,并且在很大程度上取决于谁将您的应用程序用于何种目的;如果您有截止日期,请保持简单,但如果您的目标需要复杂且高度可操作的数据,我认为您不应混用用户界面代码和数据操作代码;如果你以后必须使用新功能升级你的课程,这是一个很难改变的地方。

对不起,很长的帖子。我是法语,我不知道我的英语是否合适。不要介意那些只是我的blabla,不能在没有详细说明的情况下记住这个想法。

尝试使用ButtonSaveClicked()调用解决方法。这对我来说有一个流布局面板:我在父窗体中创建了一个Overridable ButtonSaveClicked(),并在子窗体中显示了一个消息框" Hello"和一个覆盖ButtonSaveClicked() " Coucou",它运作良好。
然后你就可以像在我的Table_Form中那样在父表单中创建一个包含BlackBox对象的变量,以及从子表单访问它的方法(受保护的,或公共属性或一组受保护的方法和函数,我不&# 39;知道......)然后决定你的BlackBox成员(属性,方法,功能,事件......)以及如何/可以从父母和子表单中使用它们。

答案 1 :(得分:1)

要将基类控件上的操作公开为事件(另一个答案显示如何通过可覆盖的方法执行此操作):

基本表格:

Public Event SaveClick As EventHandler

Protected Overridable Sub OnSaveClick(ByVal e As EventArgs)
    RaiseEvent SaveClick(Me, e)
End Sub

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    OnSaveClick(e)
End Sub

儿童表格:

' the event will show up in the event list like any other
' this may be what you want if each child form is supposed to do everything
Private Sub Form1_SaveClick(sender As Object, e As EventArgs) Handles Me.SaveClick
   ' save some stuff
End Sub

' hybrid where the child form does some stuff, base form does some stuff
Protected Overrides Sub OnSaveClick(e As EventArgs)
    MyBase.OnSaveClick(e)              ' base form activities

    'save some stuff

End Sub

因此,基本表单可以引发一些抽象的事件,例如SaveItemNewItemDeleteItem,并且子表单就像界面的工作原理一样。


这种方法的缺陷是控件仍然锁定在BaseForm上,因为它们应该是(控件是朋友)。每个孩子可以将参数传递给基本表单,表示"模式":

Public Sub New()
    MyBase.New("Customer")
    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.

End Sub

但是要使用它意味着基本形式必须具有处理所有各种"模式的逻辑。否定了专门用于处理Foo,Bar,Customer等的继承表单的价值。所以,我不确定表单继承是你想要的:所描述的控件看起来太专业了。

答案 2 :(得分:0)

从功能角度和设计角度来看,最好的选择是以您的子形式创建活动,即OnSaveRequestedOnCancelRequested。在您的父表单中,使用WithEvents声明您的子表单,并为这些事件创建处理程序。如果要从子表单保存,请引发OnSaveRequested事件并在父表单中处理(与取消相同)。

当然,在这种情况下,您的父表单需要知道要保存的内容,因此您可以将数据作为参数传递给事件,或者将公共属性公开给数据中的数据。你的孩子形式。