Adding a User Control dynamically to a FlowLayoutPanel - vb.net

I've created a UserControl and added it to a FlowLayoutPanel. The UserControl is being used to allow a user to enter a material, cost and delivery status to the form. When the user has filled it in, I want another UserControl to appear underneath it in the FlowLayoutPanel
The UserControl simply generates a string based on the text entered into two TextBox controls and the status of two Checkbox controls. it also has a property that indicates when the user has filled in enough information. I want to use this property to signal generating a new UserControl.
At the moment I have my first UserControl on the FlowLayoutPanel, it is successfully passing the String and CreateNew property back.
The problems I am encountering are:
How do I monitor to see if CreateNew has changed to True?
How do I add a control to the form and +1 to the controls name for future referencing
Once the new control is added, I need to monitor to see if the new CreateNew state changes to repeat the cycle
Can anyone point me in the right direction here, there's a lot of information out there on this, but I can't seem to find anything useful from other's problems/questions.
UPDATE 1
Thanks to user Zaggler for the comment, I have now managed to get the control to create a new instance of itself on the FlowLayoutPanel. But now I'm faced with a new problem of it only creating one new usercontrol, then it stops.
Here's the code I'm using:
UserControl Code
Public Class Alv_Product_Order_Control
Public OutString As String
Public Event CreateNew()
Dim CreateNewRaised As Boolean
Private Sub OutputString(sender As Object, e As EventArgs) Handles tbMaterial.TextChanged, tbCost.TextChanged,
cbDelivered.CheckedChanged, cbOrderPlaced.CheckedChanged
OutString = "¦¦" & tbMaterial.Text & "¦" & tbCost.Text & "¦"
If cbOrderPlaced.Checked = True Then
OutString = OutString & "Yes¦"
Else
OutString = OutString & "No¦"
End If
If cbDelivered.Checked = True Then
OutString = OutString & "Yes¦"
Else
OutString = OutString & "No¦"
End If
If tbCost.Text = "" Or tbMaterial.Text = "" Then
Else
If CreateNewRaised = False Then
RaiseEvent CreateNew() 'Raise the event that's used to signal adding a new control to the layout
CreateNewRaised = True 'Create A Latched Boolean that cannot change again in the future
End If
End If
End Sub
Public ReadOnly Property Alv_Product_Order_Control As String
Get
Return OutString 'Pass string back to main form
End Get
End Property
Main Form Code
Private Sub CreateSecondPOC() Handles POC1.CreateNew
FlowLayoutPanel1.Controls.Add(New Alveare_VB.Alv_Product_Order_Control)
End Sub
I'm guessing here that the problem is the CreateSecondPOC only handles the event for the the first POC1
How do I create a new Alveare_VB.Alv_Product_Order_Control, name it as POC2 and also add a handler to handle POC2.CreateNew and add another control?
Edit 2
I know I've found the answer, but i'd like to look at this memory leak that has been mentioned. I've changed the code supplied in the answer below to this:
Private Sub CreateSecondPOC(ByVal sender As Object, ByVal e As System.EventArgs) Handles POC1.CreateNew
Try
Dim oldPoc = DirectCast(sender, Alveare_VB.Alv_Product_Order_Control)
RemoveHandler oldPoc.CreateNew, AddressOf CreateSecondPOC
Catch ex As Exception
Debug.Print(ex.Message)
End Try
Dim newPoc As New Alveare_VB.Alv_Product_Order_Control
AddHandler newPoc.CreateNew, AddressOf CreateSecondPOC
FlowLayoutPanel1.Controls.Add(newPoc)
End Sub
And I get the following error on the "RemoveHandler" line:
Unable to cast object of type 'System.Windows.Forms.TextBox' to type 'Alveare_VB.Alv_Product_Order_Control'.
The CreateNew event is raised when a textbox is written in, this is getting passed back as the sender I assume? Not really sure where to go with this now.
Edit 3
The error was in my UserControl, I was sending the incorrect object back (in this case the textbox). I have now changed the RaiseEvent to return the UserControl as an object. Now all is working correctly.

You could change your handler to something like this
Private Sub CreateSecondPOC() Handles POC1.CreateNew
Dim newPoc As New Alveare_VB.Alv_Product_Order_Control
AddHandler newPoc.CreateNew, AddressOf CreateSecondPOC
FlowLayoutPanel1.Controls.Add(newPoc)
End Sub
I'm not sure if you want to keep handling the event, even after it has been populated once, i.e. can it be depopulated, then repopulated, then raise the event again? Maybe you want to lock it once it's populated, but this isn't clear.
You could also keep all your POC controls in a container and only create a new one when they are all populated.
Edit:
According to comments below, you should remove the event handler when you no longer need it in order to avoid memory leaks. Here is one way
Private Sub CreateSecondPOC(sender As Object) Handles POC1.CreateNew
Dim oldPoc = DirectCast(sender, Alveare_VB.Alv_Product_Order_Control)
RemoveHandler oldPoc, AddressOf CreateSecondPOC
Dim newPoc As New Alveare_VB.Alv_Product_Order_Control
AddHandler newPoc.CreateNew, AddressOf CreateSecondPOC
FlowLayoutPanel1.Controls.Add(newPoc)
End Sub
But the last POC control's event handler would never be unsubscribed. So you could also do something like this when closing the form
For Each poc In Me.FlowLayoutPanel1.Controls.OfType(Of Alveare_VB.Alv_Product_Order_Control)()
RemoveHandler poc.CreateNew, AddressOf CreateSecondPOC
Next poc
I mentioned above that You could also keep all your POC controls in a container, which would be a better way to keep track of your controls, instead of using the FlowLayoutPanel as logical container. Just do what works for you and consider removing the handlers.

Related

vb.net event handle not working

I am new in vb.net, I have a data collection application that use scan data in warehouse. it is window system, I couldn't get the event handling work. Please help me find what is wrong with my program below.
Here is the project, In the app I have a scanner class that I use it to scan barcode, and when scan happens raise an event to let the forms know. many forms can use scan data. But only the form visible on top will handle the scanned data.
I have 2 forms order form and ticket form, they both handle the scandata event. First order form handles scan data event and fill in the scanned order text field, then it automatically launches ticket form, and the ticket form suppose to handle the scan data event and post data on ticket form.
The Order form handles the event fine, the problem is after launching the ticket form automatically, it seems cannot associate to the event, the event seems still with the Order forms, So,ticket form not reveiving it and does nothing.
I have to use LaunchTickets() in order form to get to ticket due to business request, How do I implement this to get the event associate to correct form whatever the form visible on top in my application? I don't know if there is a way to get around, if not, what is the best way to implement this? please help me to get this work.
Public Class ClsComPort
Public Shared Event NewScanData(ByVal ScanData As String)
Public Function InitializebcrScanner() As Boolean
AddHandler mySerialPort.DataReceived, AddressOf mySerialPort_DataReceived
End Function
Private Sub mySerialPort_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
'omitted steps to get the scanned data
ScanData = str.Trim()
RaiseEvent NewScanData(scandata) 'raise the scan event
End sub end class
Public Class frmOrder
Friend WithEvents scanner As New ClsComPort
Private Sub GetScanData(ByVal ScanData As String) Handles scanner.NewScanData
txtOrder.Text = ScanData 'this runs ok, I got the scan data in this field
LaunchTickets() 'App automatically go to next form- ticket form, this seems to cause the event handling issue
End Sub
Private Sub LaunchTickets()
Me.Visible = False
frmTickets.WindowState = FormWindowState.Maximized
frmTickets.ShowDialog()
Me.Visible = True
End Sub
End Class
Public Class frmticket
Friend WithEvents scanner As New ClsComPort
Private Sub GetScanData(ByVal ScanData As String) Handles scanner.NewScanData
txtTicket.Text = ScanData
'not working, just nothing happen, and when back to order form, program crashed
End Sub
End Class
When you make the instance of the frmticket class you are subscribing to your custom event, but that event only fires when the DataReceived event fires and that is not wired up until you call InitializebcrScanner(you did not call it) - which should be a Sub not a Function since you do not return a boolean value or need to. Make an instance of the form not the default instance.
Private Sub LaunchTickets()
Me.Visible = False
Dim ticketsForm As New frmTickets
ticketsForm.WindowState = FormWindowState.Maximized
ticketsForm.ShowDialog()
Me.Visible = True
End Sub
This:
Public Function InitializebcrScanner() As Boolean
AddHandler mySerialPort.DataReceived, AddressOf mySerialPort_DataReceived
End Function
Should be in the constructor:
Sub New()
AddHandler mySerialPort.DataReceived, AddressOf mySerialPort_DataReceived
End Sub

How do I use the Tag property with forms and code in VB 2012?

I am writing a program using a database for customers and technicians. The main form (CustomerIncidents) has a toolstripbutton that opens a different form to (SearchByState) where the user inputs a state code and looks for any incidents.
If the user clicks into one of the datagrid cells I want that customers information to be stored in the TAG so that when the form is closed using the OK button that it will show back up in the main form (CustomerIncidents).
Edited 03/11/14 12:21pm
The problem is in the Main Form. When I click the OK button in the Second Form it tries to convert the DialogResult Button to a String. I can't figure out how to fix it.
Customer Form (Main Form) Opens to Secondary Form
Private Sub btnOpenState_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles btnOpenState.Click
Dim frmSearchState As New FindCustomer
----->>Dim selectedButton As DialogResult = frmSearchState.ShowDialog()
If selectedButton = Windows.Forms.DialogResult.OK Then
CustomerIDToolStripTextBox.Text = frmSearchState.Tag.ToString
End If'
Search By State Form (Secondary Form) Or "Child Form"
Private Sub btnOk_Click(message As String, ByVal e As DataGridViewCellEventArgs) Handles btnOk.Click
message = CustomersDataGridView.Rows(e.RowIndex).Cells(e.ColumnIndex).Value.ToString
Me.Tag = message
Me.DialogResult = DialogResult.OK
End Sub
The click event for a button does not have a DataGridViewCellEventArgs parameter, and will throw an exception when you try to use it.
You don't need to use the Tag property since you can just create your own property.
In your child form, create a property called GridValue:
Private Sub btnOk_Click(sender As Object, e As EventArgs) Handles btnOk.Click
If dgv.CurrentCell Is Nothing OrElse dgv.CurrentCell.Value Is Nothing Then
MessageBox.Show("A cell needs to be selected.")
Else
Me.DialogResult = DialogResult.OK
End If
End Sub
Public ReadOnly Property GridValue As String
Get
Return dgv.CurrentCell.Value.ToString
End Get
End Property
In your parent form, you can now access your information:
Using frmSearchState As New FindCustomer
If frmSearchState.ShowDialog(Me) = DialogResult.Ok Then
CustomerIDToolStripTextBox.Text = frmSearchState.GridValue
End If
End Using
My personal approach for doing this kind of stuff is to create a public property in the child form, having the same type as the DATA you want to take back to your main form. So instead of storing DataGridView's reference in Tag property, you should really be storing the actual value that was there in the cell that the user clicked on.
For example, if your DGV cell has a string value in it, you could do something like:
Public Readonly Property StateName As String
Get
If YourDGV.SelectedCell IsNot Nothing Then
Return YourDGV.SelectedCell.Value
Else
Return ""
End If
End Get
End Property
(I have written that code by hand, so there may be some syntax problems, but you should be able to get the idea.)
You can now use ShowDialog() in the main form to bring up this child form and upon OK or Cancel, you could check the value of StateName property of your child form to get this value. The thing to remember here is that closing a form doesn't dispose off all its constituent controls and properties and therefore you can access them even after the form has finished ShowDialog() call.

Handling Event - form/control not updating?

First off, I do not work with winform development full time so don't bash me too bad...
As the title somewhat depicts, I am having an issue refreshing the controls on a form after an event has been raised and captured.
On "Form1" I have a Dockpanel and am creating two new forms as shown below:
Public Sub New()
InitializeComponent()
dpGraph.DockLeftPortion = 225
dpGraph.BringToFront()
Dim frmT As frmGraphTools = New frmGraphTools()
Dim frmG As frmGraph = New frmGraph()
AddHandler frmT.UpdateGraph, AddressOf frmG.RefreshGraph
frmT.ShowHint = DockState.DockLeft
frmT.CloseButtonVisible = False
frmT.Show(dpGraph)
frmG.ShowHint = DockState.Document
frmG.CloseButtonVisible = False
frmG.Show(dpGraph)
End Sub
Within the frmGraphTools class I have the following delegate, event, and button click event defined:
Public Delegate Sub GraphValueChanged(ByVal datum As Date)
Public Event UpdateGraph As GraphValueChanged
Private Sub btnSaveMach_Click(sender As Object, e As EventArgs) Handles btnSaveMach.Click
RaiseEvent UpdateGraph(dtpJobDate.Value.ToString())
End Sub
Within the frmGraph class I have the following Sub defined:
Public Sub RefreshGraph(ByVal datum As Date)
CreateGraph(datum)
frmGraphBack.dpGraph.Refresh()
End Sub
I have a ZedGraph control on the frmGraph form that is supposed to be refreshed/redrawn upon the button click as defined on frmGraphTools. Everything seems to working, the RefreshGraph Sub within frmGraph is being executed and new data is pushed into the ZedGraph control however, the control never updates. What must be done to get the frmGraph form or the ZedGraph control to update/refresh/redraw properly?
Pass the reference to RefreshGroup method from the correct instance of frmGraph
AddHandler frmT.UpdateGraph, AddressOf frmG.RefreshGraph
also this call should be flagged by the compiler because you are passing a string instead of a Date
RaiseEvent UpdateGraph(dtpJobDate.Value.ToString())
probably you have Option Strict Off

How to change programmatically cell values of bound DataGridView without receiving exceptions?

I have problems trying to change programmatically the content of a cell of a bound DataGridView.
I implemented a minimal piece of code to show the problem.
Do the following steps to replicate the problem:
Launch example
Write the title content to create a new row
CTRL+C on inserted title
Move to grid's empty row to force the creation of a new row
CTRL+V on title cell
Click on previous row (new row creation is cancelled)
Click again to the empty row to force the creation of a new row
Exception: Operation is not valid due to the current state of the object.
Here it is the code:
Public Class Form1
Private _dgv As New DataGridView
Private _Movies As New System.ComponentModel.BindingList(Of Movie)
Public Sub New()
InitializeComponent()
Me.Controls.Add(_dgv)
_dgv.Dock = DockStyle.Fill
_dgv.DataSource = _Movies
AddHandler _dgv.KeyDown, AddressOf DataGridView_KeyDown
End Sub
Private Sub DataGridView_KeyDown(sender As Object, e As KeyEventArgs)
If e.Control AndAlso e.KeyCode = Keys.V Then
_dgv.CurrentCell.Value = Clipboard.GetText
End If
End Sub
Public Class Movie
Public Property Title As String
End Class
End Class
For sure there is something wrong in my implementation but I spent many hours searching a workaround without success. Thank you in advance for any help you can give me.
When setting up the form in the constructor, make sure the EditMode of the DataGridView is DataGridViewEditMode.EditOnEnter. This makes the cell we are pasting to enter edit mode as soon as it receives focus and makes the new row stick instead of being cancelled if we move away from it.
Public Sub New()
InitializeComponent()
Me.Controls.Add(_dgv)
_dgv.Dock = DockStyle.Fill
_dgv.DataSource = _Movies
AddHandler _dgv.KeyDown, AddressOf DataGridView_KeyDown
_dgv.EditMode = DataGridViewEditMode.EditOnEnter
End Sub
Then, instead of setting the cell value, set the Title property of the underlying Movie:
Private Sub DataGridView_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
If e.Control AndAlso e.KeyCode = Keys.V Then
Dim Mov As Movie = _Movies.Last
Mov.Title = Clipboard.GetText
_dgv.Refresh()
End If
End Sub
When you add a new row in the DataGridView, the BindingList is automagically adding a new Movie object to the list. _Movies.Last should gets you the newly added Movie.
Why not use the DataGridView event UserAddedRow?
system.windows.forms.datagridview.useraddedrow

EditingControlShowing events firing multiple times

I have a DGV in VB.Net 2008 connected to an Access DB table. The DGV is not Read Only, but is full of read-only columns except for one, which contains a combo box. The combo box allows the user to select an outcome for that particular row, and then the program copies in a pre calculated value into the "Profit" column depending upon the item selected in the combobox. Then the user hits the Save button and the DB updates (currently via SQL methods in the XSD).
Easy enough so far.
Here is the code.
Private Sub DGUserBets_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles DGUserBets.EditingControlShowing
Dim combo As ComboBox = CType(e.Control, ComboBox)
If (combo IsNot Nothing) Then
// Remove an existing event-handler, if present, to avoid
// adding multiple handlers when the editing control is reused.
RemoveHandler combo.SelectedIndexChanged, _
New EventHandler(AddressOf DGUBStake_SelectedIndexChanged)
// Add the event handler.
AddHandler combo.SelectedIndexChanged, _
New EventHandler(AddressOf DGUBStake_SelectedIndexChanged)
End If
End Sub
Private Sub DGUBStake_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs)
Dim myStatus As ComboBox = CType(sender, ComboBox)
Dim row = DGUserBets.CurrentRow
Select Case myStatus.SelectedIndex
Case 0
row.Cells("DGUBProfit").Value = 0
// pending. no action
Case 1
row.Cells("DGUBProfit").Value = row.Cells("DGUBIfWin").Value
// win
Case 2
// loses
row.Cells("DGUBProfit").Value = row.Cells("DGUBIfLose").Value
Case 3
// void
row.Cells("DGUBProfit").Value = 0
End Select
End Sub
The problem I have is that it would seem that if a user selects the desired outcome from the combobox but does NOT hit Enter, and simply mouses on to a different combobox to again select the outcome for a different row, the first eventhandler is not disconnected and thus the events fire multiple times. This then causes various default MsgBox errors and brings up problems when the user tries to commit all changes to the DB/exit program etc etc.
What do I need to do? Do I need to .EndEdit somewhere appropriate to force the row to save the changes? And where should I call this?
Thank you.
A quick glance at the code brings up this question:
If you create a new EventHandler when removing the existing one is it the same one?
I have had a similar issue, add a handler for CellLeave if the cell being exited is the cell you are looking for (IE e.ColumnIndex = myEditableColumn.Index) then call gv.EndEdit()
Also I would recommend making the handlers member variables for assignment and removal because it seems nicer then always saying Remove New and Add New.
CKRet/Quintin , thank you for the fast responses.
A quick try with this code seems better and breakpointing and stepping through the code seems to be firing the events correctly. I'm fairly new to .NET as the last real VB programming I did was VB6 so I'm not sure if this is the most elegant way to solve the problem.
Also a note that when LastEventHandler = Nothing, calling the RemoveHandler does not throw an exception, which is quite nice.
Maybe I should suggest to MS they should update that article.
Private Sub DGUserBets_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles DGUserBets.EditingControlShowing
Dim combo As ComboBox = CType(e.Control, ComboBox)
Static LastEventHandler As EventHandler
If (combo IsNot Nothing) Then
// Remove an existing event-handler, if present, to avoid
// adding multiple handlers when the editing control is reused.
RemoveHandler combo.SelectedIndexChanged, _
LastEventHandler
LastEventHandler = New EventHandler(AddressOf DGUBStake_SelectedIndexChanged)
// Add the event handler.
AddHandler combo.SelectedIndexChanged, _
LastEventHandler
End If
End Sub
Simpler code which also appears to work well, as suggested by CKRet:
Dim combo As ComboBox = CType(e.Control, ComboBox)
If (combo IsNot Nothing) Then
RemoveHandler combo.SelectedIndexChanged, AddressOf DGUBStake_SelectedIndexChanged
AddHandler combo.SelectedIndexChanged, AddressOf DGUBStake_SelectedIndexChanged
End If
I know this is an archaic post, but after buggering around with this same problem for half a day I've found a way to solve this another way, so I thought it would be worth sharing.
Adding a second handler to handle the leave event of the combobox which then removes the handler of selectedvalue changed. Appears to work quite slick, and unlike another option i found gives the desired resulting action (unlike removing the value changed handler on the actual handling event which then won't fire if you re-select from the same combobox)
Private LastEventHandler As EventHandler = AddressOf Me.ComboBoxValueChanged
Private Sub dgvThisDatagrid_EditingControlShowing(sender As Object, e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles dgvOutstandingReminders.EditingControlShowing
If TypeOf (e.Control) Is ComboBox Then
Dim cboThisComboBox = DirectCast(e.Control, ComboBox)
AddHandler cboThisComboBox.SelectedValueChanged, LastEventHandler
AddHandler cboThisComboBox.Leave, AddressOf RemoveValueChangedHandler
End If
End Sub
Private Sub ComboBoxValueChanged(ByVal sender As Object, ByVal e As System.EventArgs)
If TypeOf (sender) Is ComboBox Then
Dim cboThisComboBox = DirectCast(sender, ComboBox)
MessageBox.Show("Value = " & cboThisComboBox.SelectedValue.ToString() & Environment.NewLine & "Text = " & cboThisComboBox.Text) ' Display index
End If
End Sub
Private Sub RemoveValueChangedHandler(ByVal sender As Object, ByVal e As System.EventArgs)
If TypeOf (sender) Is ComboBox Then
Dim cboThisCombobox = DirectCast(sender, ComboBox)
RemoveHandler cboThisCombobox.SelectedValueChanged, LastEventHandler
End If
End Sub