Question Abstracting/overriding tab pages

jwcoleman87

Well-known member
Joined
Oct 4, 2014
Messages
124
Programming Experience
Beginner
I will try to make this post as coherent as possible while the intention of this post is to gather my thoughts and get myself going in the right direction, thus possibly leading to some incoherence. That's where you guys come in! :distant:

What I'm trying to do is add and remove tab pages on the fly. These tab pages will contain controls which have handlers for them. I'm expecting to have to predetermine some of these things before run time, such as the handlers and parameters, some things can be done on the fly, such as the organization of the controls (using flow layout panels). This all looks something like this:

Unit Actions.JPG

The code to create these tab pages looks like this:

        'This creates the AddComments Tab        Dim CommentsTab As New TabPage With {.Name = "Comments", .Text = "Add Comments"}
        TabControl1.TabPages.Add(CommentsTab)
        Dim flpcomments As New FlowLayoutPanel With {.Parent = TabControl1.TabPages("Comments"),
                                                     .Dock = DockStyle.Fill,
                                                     .BorderStyle = BorderStyle.Fixed3D,
                                                     .FlowDirection = FlowDirection.TopDown}
        Dim txtComments As New TextBox With {.Parent = flpcomments, .Name = "CommentBox", .Width = 250, .Height = 100, .Multiline = True}
        Dim btnSaveComment As New Button With {.Parent = flpcomments, .Text = "Save"}
        AddHandler btnSaveComment.Click, AddressOf Me.SaveComment
        Dim rc As New RepairControl


And:

        'This creates the enter Failure/Fix Tab
        Dim FailureFixTab As New TabPage With {.Name = "FailureFix", .Text = "Enter Failure/Fix"}
        TabControl1.TabPages.Add(FailureFixTab)
        Dim flpFailureFix As New FlowLayoutPanel With {.Parent = TabControl1.TabPages("FailureFix"),
                                                       .Dock = DockStyle.Fill,
                                                       .BorderStyle = BorderStyle.Fixed3D,
                                                       .FlowDirection = FlowDirection.TopDown}
        Dim cbSelectedFailure As New ComboBox With {.Parent = flpFailureFix, .DataSource = rc.GetFailureFix(1), .SelectedItem = Nothing}
        Dim cbSelectedFix As New ComboBox With {.Parent = flpFailureFix, .DataSource = rc.GetFailureFix(2), .SelectedItem = Nothing}
        Dim btnSaveFailureFix As New Button With {.Parent = flpFailureFix, .Text = "Save"}
        AddHandler btnSaveFailureFix.Click, AddressOf Me.SaveFailureFix


As you can see, they have some things in common, the difference lies in the input methods and controls used to gather that input. I am presuming that I will have to hard code most of this. What I'm wondering about is a way to abstract these. I was thinking I could make a base class that had an overridable constructor in them. If I did this then when I went to design a new action tab I could simply derive it from the base class and carry over the common things. I'm currently trying to determine what the common things really are.

A going list I currently have running around my head:

  • Tab Control (parent of the tab page)

  • Tab name
  • Tab text
  • FlowlayoutPanel name
  • Flowlayoutpanel Parent (The tab page we created)
  • FlowLayoutPanel Dock
  • FlowLayoutPanel Flowdirection

These I believe cover the standardized things, the basic layout of each tab page. What is seemingly more complex to me is the abstraction of the stuff that lies within. There are lots of definitive indefinite variables, such as: (please focus your expertise and responses in this area)

  • An indefinite number of controls (perhaps this could be abstracted as a "control collection"?)
  • An indefinite number of handlers for these controls (perhaps a param array of some sort?)
  • An indefinite number of parameters for the methods called by the events (this is based on the control collection I think?)

Other things????

Please help! I expect this to be a pretty long and drawn out thread.
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,439
Location
Norway
Programming Experience
10+
.Parent = TabControl1.TabPages("Comments")
Why do this when you already have a reference to the TabPage?

You could refactor the part creating TabPage/FlowLayoutPanel and adding the controls with a method like this:
Private Function CreateTab(name As String, text As String, controls As Control()) As TabPage

You don't need the TabControl here, add the page to that after. Adding the controls (in method):
panel.Controls.AddRange(controls)

Creating the controls (and adding relevant event handlers) and gather them as array needs to be done outside that method.

'controls' parameter could be declared ParamArray, but it is just a easy having it as regular array.
 

jwcoleman87

Well-known member
Joined
Oct 4, 2014
Messages
124
Programming Experience
Beginner
Thanks for pointing out the unnecessary code there, I didn't think to call the tabpage explicitly rather than going to the tabcontrol and finding the tab page. I'm working out a solution for the individual controls and event handlers. Gathering them as an array is a great idea!

Public MustInherit Class ActionTab : Inherits TabPage


    Private ChildContainer As ContainerControl

    Public Property ChildContainerControl() As ContainerControl
        Get
            Return ChildContainer
        End Get
        Set(ByVal value As ContainerControl)
            ChildContainer = value
        End Set
    End Property


    Public Sub New(Name As String, Text As String, Container As ContainerControl)
        ChildContainer = Container
        Container.Parent = Me
        Container.Dock = DockStyle.Fill
        Container.Name = Name
        Container.Text = Text
    End Sub


End Class


This is what I'm thinking about doing, deriving a class from tabpage as an abstract class. I want to add a MustOverride sub to this in order to determine the controls present within the container control, as well as arrange them. I also want to add a MustOverride sub which will hook necessary handlers up to those controls.

Is this a totally bad idea? I suppose the main goal I'm trying to accomplish is to keep this code separated and independent of an individual form, and abstracting a subclass of tabpage seems to be where I need to go to accomplish this. What do you think?
 

jwcoleman87

Well-known member
Joined
Oct 4, 2014
Messages
124
Programming Experience
Beginner
Expanding on this idea a bit, I have written this basic framework:

Public MustInherit Class ActionTab : Inherits TabPage


    Private ChildContainer As ContainerControl
    Private EventHandlerArray As Array






    Public Property ChildContainerControl() As ContainerControl
        Get
            Return ChildContainer
        End Get
        Set(ByVal value As ContainerControl)
            ChildContainer = value
        End Set
    End Property


    Public Property ControlEventHandlersArray() As Array
        Get
            Return EventHandlerArray
        End Get
        Set(ByVal value As Array)
            EventHandlerArray = value
        End Set
    End Property


    Public Sub New(Name As String, Text As String, Container As ContainerControl)
        ChildContainer = Container
        Container.Parent = Me
        Container.Dock = DockStyle.Fill
        Container.Name = Name
        Container.Text = Text
    End Sub


    Protected MustOverride Sub InitializeControls()
    Protected MustOverride Sub InitializeEventHandlers()


End Class




I'm sure that this isn't complete, it's very basic. I've provided subs requiring an overload when deriving classes from action tab. In the overloaded subs I will create the controls needed as parameters for the handling code, the controls will be grouped by the controls property in ChildContainer. I will define the handlers and place them in the eventhandlerarray to be hooked up to the events.

From this information I should be able to figure out how I need to modify the constructor in order for this code to work every time, as long as there are controls and events.

I will be using a table in a database to determine what handlers to use in each tab (I'm still trying to figure out how to work this in)
 
Last edited:

jwcoleman87

Well-known member
Joined
Oct 4, 2014
Messages
124
Programming Experience
Beginner
So here is what I have so far:
Public MustInherit Class ActionTab : Inherits TabPage


    Private ChildContainer As Panel
    Private EventHandlerArray As Array






    Public Property ChildContainerControl() As Panel
        Get
            Return ChildContainer
        End Get
        Set(ByVal value As Panel)
            ChildContainer = value
        End Set
    End Property


    Public Property ControlEventHandlersArray() As Array
        Get
            Return EventHandlerArray
        End Get
        Set(ByVal value As Array)
            EventHandlerArray = value
        End Set
    End Property


    Public Sub New(Name As String)
        Dim flp As New FlowLayoutPanel
        ChildContainerControl = flp
        ChildContainerControl.Parent = Me
        ChildContainerControl.Dock = DockStyle.Fill
        ChildContainerControl.Name = Name
        InitializeControls()
        InitializeEventHandlers()


    End Sub


    Protected MustOverride Sub InitializeControls()
    Protected MustOverride Sub InitializeEventHandlers()


End Class


And here is an example of the non-abstract derived class from ActionTab

Public Class CommentTab : Inherits ActionTab
    Sub New()
        MyBase.New("Comments")
    End Sub
    Protected Overrides Sub InitializeControls()


    End Sub
    Protected Overrides Sub InitializeEventHandlers()


    End Sub
End Class




After some modification ans realization that a containercontrol is not the base class of any panel I switched childcontainer to a panel.

This now leads me to my next road block, how to set up event handlers and place them in an array of event handlers??? I'm talking about this method:
    Protected Overrides Sub InitializeEventHandlers()

    End Sub


I would like this to create event handlers and place them in the array. I know how to programmatically add and remove event handlers from controls, but I've never actually initialized handlers, stored them, and then call all during run-time.

Any words of advice here?
 

jwcoleman87

Well-known member
Joined
Oct 4, 2014
Messages
124
Programming Experience
Beginner
Okay, turns out I realized that I essentially already had the functionality defined for the events that I need elsewhere (in my repaircontrol class). Now I just need to figure out how to pass parameters from controls on any form when instantiating the CommentTab class, this is starting to flesh out like this:

Public Class CommentTab : Inherits ActionTab
    Sub New()
        MyBase.New("Comments")
    End Sub
    Protected Overrides Sub InitializeControls()
        Dim txtComments As New TextBox With {.Parent = Me.ChildContainerControl, .Name = "CommentBox", .Width = 250, .Height = 100, .Multiline = True}
        Dim btnSaveComment As New Button With {.Parent = Me.ChildContainerControl, .Name = "btnSaveComment", .Text = "Save"}
    End Sub
    Protected Overrides Sub InitializeEventHandlers()
        Dim rc As New RepairControl
        AddHandler ChildContainerControl.Controls.Find("btnSaveComment", True), rc.SetStatusAndAssignment()
    End Sub
End Class




Obviously I need parameters for this function, but I feel like I may need to change some things to make this automatic in the ActionTab abstract class. I think I may need to use a paramarray in the parameters of the abstract class action tab.

Time to google paramarrays lol!
 

jwcoleman87

Well-known member
Joined
Oct 4, 2014
Messages
124
Programming Experience
Beginner
AHAH! :excitement:

I found out what I need! I need a reference to the parent of the tabcontrol (the form!) !!!!! (it was right in front of me the whole time, I talked to a friend and he's like, sounds like the only problem you're really having is scope!

And I'm like scope???? Scope..... Scope!!!!!

That way I can define a handler in the abstract derivation of TabControl (ActionTab) which has a normal event handler signature, which will call the rc.SetStatusAndAssignment function, which will pull it's parameters from a reference of the grand daddy form (which has the specific record information I'm working on)

I think this will work, I just need to test it!

Public MustInherit Class ActionTab : Inherits TabPage


    Private ChildContainer As Panel
    Private EventHandlerArray As Array
    Public ParentFormReference As Object




    Public Property ChildContainerControl() As Panel
        Get
            Return ChildContainer
        End Get
        Set(ByVal value As Panel)
            ChildContainer = value
        End Set
    End Property


    Public Property ControlEventHandlersArray() As Array
        Get
            Return EventHandlerArray
        End Get
        Set(ByVal value As Array)
            EventHandlerArray = value
        End Set
    End Property




    Public Sub New(Name As String, ByRef ParentForm As Object)
        ParentFormReference = ParentForm
        Dim flp As New FlowLayoutPanel
        ChildContainerControl = flp
        ChildContainerControl.Parent = Me
        ChildContainerControl.Dock = DockStyle.Fill
        ChildContainerControl.Name = Name
        InitializeControls()
        InitializeEventHandlers()


    End Sub


    Protected MustOverride Sub InitializeControls()
    Protected MustOverride Sub InitializeEventHandlers()
    Protected MustOverride Sub ActionEvent(sender As Object, e As EventArgs)


End Class




And the implementation of this abstract class here:
Public Class CommentTab : Inherits ActionTab
    Sub New(ByRef ParentForm As Form)
        MyBase.New("Comments", ParentForm)
    End Sub
    Protected Overrides Sub InitializeControls()
        Dim txtComments As New TextBox With {.Parent = Me.ChildContainerControl, .Name = "CommentBox", .Width = 250, .Height = 100, .Multiline = True}
        Dim btnSaveComment As New Button With {.Parent = Me.ChildContainerControl, .Name = "btnSaveComment", .Text = "Save"}
    End Sub
    Protected Overrides Sub InitializeEventHandlers()
        Dim rc As New RepairControl
        AddHandler ChildContainerControl.Controls.Find("btnSaveComment", True)(0).Click, AddressOf ActionEvent
    End Sub
    Protected Overrides Sub ActionEvent(sender As Object, e As EventArgs)
        Dim rc As New RepairControl
        rc.SetStatusAndAssignment(ParentFormReference.lblSelectedSN.Text, ParentFormReference.User,
                                  ParentFormReference.lblStatus.Text, ChildContainerControl.Controls.Find("CommentBox", True)(0).Text)
    End Sub
End Class


With this I should be able to create a "CommentTab" tab page on the fly and it will work in a very automated way.

Edit: Realized I need to use a property in the form rather than the labels in the form:

Public Class CommentTab : Inherits ActionTab
    Sub New(ByRef ParentForm As Form)
        MyBase.New("Comments", ParentForm)
    End Sub
    Protected Overrides Sub InitializeControls()
        Dim txtComments As New TextBox With {.Parent = Me.ChildContainerControl, .Name = "CommentBox", .Width = 250, .Height = 100, .Multiline = True}
        Dim btnSaveComment As New Button With {.Parent = Me.ChildContainerControl, .Name = "btnSaveComment", .Text = "Save"}
    End Sub
    Protected Overrides Sub InitializeEventHandlers()
        Dim rc As New RepairControl
        AddHandler ChildContainerControl.Controls.Find("btnSaveComment", True)(0).Click, AddressOf Me.ActionEvent
    End Sub
    Protected Overrides Sub ActionEvent(sender As Object, e As EventArgs)
        Dim rc As New RepairControl
        If IsNothing(ParentFormReference.RepairInformation) Then
            MsgBox("RepairInformation is empty!")
        Else
            rc.SetStatusAndAssignment(ParentFormReference.RepairInformation.SerialNumber, ParentFormReference.User,
                                      ParentFormReference.RepairInformation.Status, ChildContainerControl.Controls.Find("CommentBox", True)(0).Text)
        End If


    End Sub
End Class


 
Last edited:
Top Bottom