0

I am making an invoicing application. I have a label (lblCost) inside of a UserControl (InvoiceEntry) inside of a FlowLayoutPanel (pnlEntries). InvoiceEntry represents a line item on the invoice, and pnlEntries is the "body" of the invoice. pnlEntries can hold several InvoiceEntry controls.

I am attempting to sum all of the lblCost values from each InvoiceEntry control to make a subtotal, and I want that subtotal to change automatically if costs are changed (e.g., a change in quantities being ordered). Is there a way to handle when the lblCost.Text property of any of the InvoiceEntry controls contained within pnlEntries changes?

InvoiceAdd.vb:

' Instantiate objects of the various database interaction classes.
Private mCustomer As New Customers
Private mItem As New Items
Private mInvoices As New Invoices
Private mInvoiceItem As New InvoiceItems

' An array of InvoiceEntries for DB processing
Private Entries() As InvoiceEntry
Private numEntries As Integer = 0

Private Sub InvoiceAdd_Load(sender As System.Object, e As System.EventArgs) _
            Handles MyBase.Load

    Me.CustomersTableAdapter.Fill(Me.Bauer_BusinessDataSet.Customers)

    ' Set the DataSource properties of the invEntry control.
    ' NOTE: For some reason, if this is done inside the
    ' control code, the program attempts to look in the
    ' wrong directory for the database. I'm not entirely
    ' sure why this is. Setting these properties in the form code,
    ' rather than in the control code, is a successful workaround.
    With invEntry.cboItem
        .DataSource = mItem.Items
        .DisplayMember = "ItemName"
        .ValueMember = "Id"
    End With
End Sub

Private Sub UpdateTotal() Handles nudTaxRate.TextChanged, _
                                  pnlEntries.GotFocus

    Dim total As Decimal = 0

    If Entries IsNot Nothing Then

        For Each x In Entries
            total += CDec(x.lblTotal.Text)
        Next
        lblSubtotal.Text = total.ToString("C")
        lblTax.Text = (lblSubtotal.Text * (nudTaxRate.Text / 100)).ToString("C")

        Dim subtotal As Decimal = 0
        Dim tax As Decimal = 0
        If Not lblSubtotal.Text = Nothing And Not lblTax.Text = Nothing Then
            Decimal.TryParse(lblSubtotal.Text.Substring(1), subtotal)
            Decimal.TryParse(lblTax.Text.Substring(1), tax)
        End If

        lblGrandTotal.Text = (subtotal + tax).ToString("C")
    End If
End Sub

Public Sub invEntry_ItemSelected() Handles invEntry.ItemSelected

    ' Increment the number of entries to reflect the addition of a new entry.
    numEntries += 1

    ' ReDim the Entries array to compensate for a new item being added.
    ReDim Preserve Entries(numEntries - 1)

    ' Store the line item that was selected in the Entries array.
    Entries(numEntries - 1) = invEntry

    ' Set the selected line item to a new blank line item and
    ' add it to the pnlEntries' Controls Collection.
    invEntry = New InvoiceEntry
    With invEntry
        .Name = "Textbox" & numEntries - 1
        .Location = New Point(10, (numEntries - 1) * (.Height + 30))
        With .cboItem
            .DataSource = mItem.Items
            .DisplayMember = "ItemName"
            .ValueMember = "Id"
        End With
    End With
    pnlEntries.Controls.Add(invEntry)

    ' Enable the remove button on the previous list item, as
    ' it is no longer an empty entry.
    Entries(numEntries - 1).btnRemove.Enabled = True

End Sub

Public Sub pnlEntries_ControlRemoved() Handles pnlEntries.ControlRemoved
    numEntries -= 1
    ReDim Preserve Entries(numEntries - 1)

    ' As the Entries array does not know which control was removed,
    ' repopulate it with the controls from the panel.
    ' NOTE: This intentionally leaves the blank line item (invEntry)
    '       out of the array, as this array will be used to add
    '       the invoice line items to the database.
    For x As Integer = 0 To Entries.Count - 1
        Entries(x) = pnlEntries.Controls(x)
    Next
End Sub

InvoiceEntry.vb:

' Instantiate an object to interact with the Item table.
Private mItem As New Items

Public Event ItemSelected As EventHandler

Private Sub cboItem_SelectionChangeCommitted(sender As System.Object, _
                    e As System.EventArgs) _
                    Handles cboItem.SelectionChangeCommitted
    RaiseEvent ItemSelected(Me, EventArgs.Empty)
End Sub

Private Sub InvoiceEntry_Load(sender As System.Object, _
                              e As System.EventArgs) _
                              Handles MyBase.Load

    ' Set the various properties of the Item combobox.
    With cboItem
        .SelectedIndex = -1
        .DropDownStyle = ComboBoxStyle.DropDownList
    End With

    ' Set the Remove button to be disabled by default.
    btnRemove.Enabled = False
End Sub

Private Sub cboItem_SelectedIndexChanged(sender As System.Object, _
                    e As System.EventArgs) _
                    Handles cboItem.SelectedIndexChanged

    ' If nothing is selected, clear and disable all relevant controls.
    If cboItem.SelectedIndex = -1 Or cboItem.Text = "" Then
        lblDescription.Text = ""
        lblTotal.Text = ""
        nudQuantity.Enabled = False
        lblPrice.Text = ""
    Else

        ' Else, set the control texts to their respective values.
        lblDescription.Text = _
            cboItem.DataSource.Rows(cboItem.SelectedIndex)("Description")
        lblPrice.Text = cboItem.DataSource.Rows(cboItem.SelectedIndex)("Price")
        lblTotal.Text = (lblPrice.Text * nudQuantity.Value).ToString("C")
        nudQuantity.Enabled = True
    End If
End Sub

Private Sub lblTotal_TextChanged(sender As System.Object, _
                                 e As System.EventArgs) _
                                 Handles lblTotal.TextChanged

    ' This is part of my workaround that I describe in the comments section
    ' of this StackOverflow question.
    Parent.Focus()
End Sub

Private Sub nudQuantity_ValueChanged(sender As System.Object, _
                                     e As System.EventArgs) _
                                     Handles nudQuantity.TextChanged

    ' If the quantity changes, set the total price to reflect this.
    lblTotal.Text = (lblPrice.Text * nudQuantity.Value).ToString("C")
End Sub

Private Sub btnRemove_Click(sender As System.Object, _
                            e As System.EventArgs) _
                            Handles btnRemove.Click

    ' Remove the control from the parent design.
    Me.Parent.Controls.Remove(Me)
End Sub
Todd Bauer
  • 219
  • 1
  • 7
  • 15

1 Answers1

2

Since you have already got the TextChanged event handler within InvoiceEntry, you can easily call the UpdateTotal function in InvoiceAdd Form

InvoiceEntry.vb

Private Sub lblTotal_TextChanged(sender As System.Object, _
                                 e As System.EventArgs) _
                                 Handles lblTotal.TextChanged

    ' This is part of my workaround that I describe in the comments section
    ' of this StackOverflow question.

    'Parent.Focus()
    'Me.Parent could give you the parent control which is pnlEntry not InvocieAdd form
    'you need to use FindForm here
    Dim MyParentForm As InvoiceAdd = CType(Me.FindForm(), InvoiceAdd)
    MyParentForm.UpdateTotal()
End Sub

InvoiceAdd.vb

Public Sub UpdateTotal() Handles nudTaxRate.TextChanged, _
                                  pnlEntries.GotFocus

    'Change the function to Public

    Dim total As Decimal = 0

    If Entries IsNot Nothing Then

        For Each x In Entries
            total += CDec(x.lblTotal.Text)
        Next
        lblSubtotal.Text = total.ToString("C")
        lblTax.Text = (lblSubtotal.Text * (nudTaxRate.Text / 100)).ToString("C")

        Dim subtotal As Decimal = 0
        Dim tax As Decimal = 0
        If Not lblSubtotal.Text = Nothing And Not lblTax.Text = Nothing Then
            Decimal.TryParse(lblSubtotal.Text.Substring(1), subtotal)
            Decimal.TryParse(lblTax.Text.Substring(1), tax)
        End If

        lblGrandTotal.Text = (subtotal + tax).ToString("C")
    End If
End Sub
Nick
  • 1,128
  • 7
  • 12
  • Unfortunately, no. The label is generated within the UserControl, and it is assigned a value by multiplying the item price found in a database with the quantity. I made a work-around by having the UserControl execute Parent.Focus(), then adding a handler in the form for pnlResults.GotFocus(), but I'm pretty sure that's more of a hack than legitimate code. – Todd Bauer May 01 '12 at 00:42
  • So you are not able to touch the UserControl source code and only able to work on the Parent object? – Nick May 01 '12 at 00:59
  • If this is the case, I would recommend you add an KeysUp event handler for the parent object and check if the text changed for lblCost – Nick May 01 '12 at 01:16
  • I can touch the UserControl source code. The problem is that, if I have 40 line items, I don't want to have to write code for each one's TextChanged event on the main form - and the UserControl doesn't have access to the subtotal label on the main form, so I can't put the code there either. And, again, KeyUp will not work because I would have to write a KeyUp for every control, and, since they're dynamically added at runtime, I don't think that's possible, considering I have no idea how many UserControls will be added. – Todd Bauer May 01 '12 at 17:16
  • If you have 40 line items, you don't need to write 40 different TextChanged events but only one, so you can assign that single event to all your dynamic controls. You also can create a property in the main form that can be updated the total value from your single TextChanged event. – Nick May 01 '12 at 23:44
  • I apologize profusely, I do not think I understand what you are saying. Does AddHandler go in InvoiceEntry or the main form? I would assume the form, but that means I have to bind the handler to a specific InvoiceEntry object, which defeats the purpose as I only have the most recently created InvoiceEntry control stored as an object. – Todd Bauer May 02 '12 at 00:29
  • I have updated my answer so you can see the TextChanged event is added for every single InvoiceEntry object. When the lblCost changes the text, the TextChanged events will be fired. – Nick May 02 '12 at 00:42
  • I have no idea at compile time how many controls the user may add, so I don't think that For loop will work. Also, pnlEntries.Controls.Add() only accepts one argument... – Todd Bauer May 02 '12 at 03:04
  • The For loop is just for your reference, you should add the AddHandler wherever you create the InvoiceEntry Object. Would you mind showing me your code, so I could point out where you can add the event handler. Yes, pnlEntries.Controls.Add only accepts one argument, I mixed up with the TableLayoutPanel – Nick May 02 '12 at 03:20
  • I have added any code that might concern this question (and removed all code that was irrelevant) to the original post. Thank you for being so patient with me. – Todd Bauer May 02 '12 at 05:00
  • I have updated my answer, I have just put a simple code in the `lblTotal_TextChanged` event so it can get the InvoiceAdd form and call the UpdateTotal function – Nick May 02 '12 at 05:33
  • Hallelujah, it works! I had no idea about the FindForm() function. Thank you so, so much for your help, and sorry again for being a dolt. Question is marked as answered. – Todd Bauer May 02 '12 at 17:47
  • Oh, and I thought you might want to know that I was fooling around, and had an epiphany about the first method you tried to show me as well. It's so simple and straight-forward, I couldn't feel sillier. I learned something pretty essential though! Couldn't have done it without you, buddy. – Todd Bauer May 02 '12 at 18:08