vb.net event handle not working - vb.net

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

Related

Adding a User Control dynamically to a FlowLayoutPanel

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.

How to call a Sub without knowing which form is loaded into panel?

On every DataGridView1_SelectionChanged event I need to run a Private Sub OnSelectionChanged() of the form that is loaded into Panel1 (see the image http://tinypic.com/r/2nu2wx/8).
Every form that can be loaded into Panel1 has the same Private Sub OnSelectionChanged() that initiates all the necessary calculations. For instance, I can load a form that calculates temperatures or I can load a form that calculates voltages. If different element is selected in the main form’s DataGridView1, either temperatures or voltages should be recalculated.
The problem is - there are many forms that can be loaded into Panel1, and I’m struggling to raise an event that would fire only once and would run the necessary Sub only in the loaded form.
Currently I’m using Shared Event:
'Main form (Form1).
Shared Event event_UpdateLoadedForm(ByVal frm_name As String)
'This is how I load forms into a panel (in this case frm_SCT).
Private Sub mnu_SCT_Click(sender As Object, e As EventArgs) Handles mnu_SCT.Click
frm_SCT.TopLevel = False
frm_SCT.Dock = DockStyle.Fill
Panel1.Controls.Add(frm_SCT)
frm_SCT.Show()
Var._loadedForm = frm_SCT.Name
RaiseEvent event_UpdateLoadedForm(Var._loadedForm)
End Sub
‘Form that is loaded into panel (Form2 or Form3 or Form4...).
Private WithEvents myEvent As New Form1
Private Sub OnEvent(ByVal frm_name As String) Handles myEvent.event_UpdateLoadedForm
‘Avoid executing code for the form that is not loaded.
If frm_name <> Me.Name Then Exit Sub
End Sub
This approach is working but I’m sure it can be done way better (I'd be thankful for any suggestions). I have tried to raise an event in the main form like this:
Public Event MyEvent As EventHandler
Protected Overridable Sub OnChange(e As EventArgs)
RaiseEvent MyEvent(Me, e)
End Sub
Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) _
Handles DataGridView1.SelectionChanged
OnChange(EventArgs.Empty)
End Sub
but I don't know to subscribe to it in the loaded form.
Thank you.
Taking into account Hans Passant’s comments as well as code he posted in related thread I achieved what I wanted (see the code below).
Public Interface IOnEvent
Sub OnSelectionChange()
End Interface
Public Class Form1
' ???
Private myInterface As IOnEvent = Nothing
' Create and load form.
Private Sub DisplayForm(frm_Name As String)
' Exit if the form is already displayed.
If Panel1.Controls.Count > 0 AndAlso _
Panel1.Controls(0).GetType().Name = frm_Name Then Exit Sub
' Dispose previous form.
Do While Panel1.Controls.Count > 0
Panel1.Controls(0).Dispose()
Loop
' Create form by its full name.
Dim T As Type = Type.GetType("Namespace." & frm_Name)
Dim frm As Form = CType(Activator.CreateInstance(T), Form)
' Load form into the panel.
frm.TopLevel = False
frm.Visible = True
frm.Dock = DockStyle.Fill
Panel1.Controls.Add(frm)
' ???
myInterface = DirectCast(frm, IOnEvent)
End Sub
Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) _
Handles DataGridView1.SelectionChanged
' Avoid error if the panel is empty.
If myInterface Is Nothing Then Return
' Run subroutine in the loaded form.
myInterface.OnSelectionChange()
End Sub
End Class
One last thing – it would be great if someone could take a quick look at the code (it works) and confirm that it is ok, especially the lines marked with “???” (I don’t understand them yet).

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

SerialPort and Control Updating in MDI form

As my title implies i have the following problem, i am receiving data from serial port and i update a richtextbox in a MDI Form with the control.invoke method
(Code in SerialPort.DataReceived Event)
If myTerminal.Visible Then
myTerminal.MyRichTextBox1.Invoke(New MethodInvoker(Sub()
myTerminal.MyRichTextBox1.AppendText(dataLine & vbCrLf)
End Sub))
End If
But as a mdi form it has the ability to close and reopen. So when the serialport is sending data to richtextbox and the user click the close button and the form gets disposed. Then the error "Invoke or BeginInvoke cannot be called on a control until the window handle has been created."... Any Idea????
My regards,
Ribben
That code is not in the SerialPort.DataReceived event it is in the event handler. (Yes, I'm nitpicking, but it points to a solution.) The best thing to do is have the form that owns myTerminal add the handler when it is created and remove the handler when it closes.
Thank you for your answer but unfortunately that's not the solution. First of all my SerialPort Class must inform 2 Forms (Form with richtextbox, Form with Listview) and another class which is responsible for drawing (Unmanaged Directx 9.0c about 4 Forms), so to implement right the serialport class i have made my own events. Again to the problme, it caused because the Serialport.DataReceived everytime it occurs creates a thread in the threadpool and when i dispose the form simply it's too slow to catch up with all the threads and so there is at least one thread which invokes the control which is already disposed!
As a temp solution i came up with (The Below code is in the TerminalForm Class which inherits Form):
Private VisibleBoolean As Boolean = False
Private Index As Integer = 0
Private Sub DataToAppend(ByVal _text As String)
If VisibleBoolean Then
Me.MyRichTextBox1.Invoke(New MethodInvoker(Sub()
Me.MyRichTextBox1.AppendText(_text & vbCrLf)
End Sub))
ElseIf Index = 1 Then
Index = 0
myDispose()
RemoveHandler myserialport.DataToSend2, AddressOf DataToAppend
End If
End Sub
Private Sub Me_Activated(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Activated
VisibleBoolean = True
AddHandler myserialport.DataToSend2, AddressOf DataToAppend
End Sub
Private Sub myDispose()
If Index = 0 And Not Me.IsDisposed Then
Me.Invoke(New MethodInvoker(Sub()
MyBase.Dispose(True)
End Sub))
End If
End Sub
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
End Sub
Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs)
Index = 1
VisibleBoolean = False
End Sub
I know i don't like either but at least it's working!
Anyother improvement or suggestion is more

dynamic database driven menus in VB.Net

I'm trying to construct a database driven VB.Net app that pulls a list of registered accounts from a database and displays the usernames of throes accounts in menu, so the user can select one and a new form open (where they work with it).
what I have so far is the constructor for the MDI parent window
Public Sub New()
InitializeComponent()
Dim tsmi As New ToolStripMenuItem("Users", Nothing, AddressOf users_mousedown)
MenuStrip1.Items.Add(tsmi)
End Sub
The handler for the user menu (where SQLite_db is a class that looks after the database and user_class is a class with two items (username and password) as strings.
Sub users_mousedown()
Dim submenu As New ContextMenuStrip
Dim database As New SQLite_db
Dim user_list As New List(Of user_class)
user_list = database.List_Users
For Each user As user_class In user_list
submenu.Items.Add(user.username, Nothing, AddressOf Open_new_window(user))
Next
submenu.Items.Add("Add new user", Nothing, AddressOf AddNew)
submenu.Show(Control.MousePosition)
End Sub
What I want to happen is when a user clicks on the context menu a new MDI child form is created and the data in user is passed, however because AddressOf doesn't like passing data this doesn't work...
I've looked at delegates and landa expressions but don't think either of them do what I need, the other option is to make my own subclass of the ContextMenuStrip class, which 1) handles the clicks the way I want to and 2) sounds like a nightmare.
Before I launch into what I think will be a hell of a lot of work, am I missing something? is their a simple way to do what I want to do? or if not will sub-classing ContextMenuStrip work and if not any ideas as to what will (and if it will, any ideas as to how to start learning how to do that)
A simple way to encapsulate user info is with a Helper Class, where you store the context info.
Public Class Question1739163
Class HelperUserCall
Public userId As String
Sub New(ByVal id As String)
userId = id
End Sub
Public Sub OnClick()
MsgBox(Me.userId)
End Sub
End Class
Private Sub Question1739163_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim t As New ToolStripMenuItem("Users", Nothing, AddressOf user_mousedown)
MenuStrip1.Items.Add(t)
End Sub
Public Sub user_mousedown()
Dim s As New ContextMenuStrip
Dim a As HelperUserCall
a = New HelperUserCall("u1")
s.Items.Add(a.userId, Nothing, AddressOf a.OnClick)
a = New HelperUserCall("u2")
s.Items.Add(a.userId, Nothing, AddressOf a.OnClick)
s.Items.Add("New User", Nothing, AddressOf add_new)
s.Show(Control.MousePosition)
End Sub
Sub add_new()
MsgBox("add new")
End Sub
End Class
You could improve the helper class adding the reference to database in the constructor, and retrieving the user info when the user click into the option.