VB.NET Multithreading. Calling invoke on a UI Control from a class in a separate class file - vb.net

I've been trying to understand this for a few days now and wonder if it's something simple I'm missing or doing completely wrong.
Example:
Two files - TestClass.vb, myForm.vb
TestClass.vb looks as follows:
Imports System.Threading
Public Class TestClass
Private myClassThread As New Thread(AddressOf StartMyClassThread)
Public Sub Start()
myClassThread.Start()
End Sub
Private Sub StartMyClassThread()
myForm.Msg("Testing Message")
End Sub
End Class
myForm.vb is a basic form with a listbox control and a button control named respectively Output and StartButton.
The code behind the form is as follows:
Public Class myForm
Private classEntity As New TestClass
Private Sub StartButton_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles StartButton.Click
Msg("Start Button Pressed")
classEntity.Start()
End Sub
Delegate Sub MsgCallBack(ByVal mesg As String)
Public Sub Msg(ByVal mesg As String)
If Output.InvokeRequired Then
MsgBox("Invoked")
Dim d As New MsgCallBack(AddressOf Msg)
Invoke(d, New Object() {mesg})
Else
MsgBox("Not Invoked")
mesg.Trim()
Output.Items.Add(mesg)
End If
End Sub
End Class
The result:
Application runs, no errors or exceptions.
Displayed is the listbox and the Start button.
I press the start button and a msgbox says "Not Invoked" as expected and upon clicking OK to that msgbox "Start Button Pressed" is added to the Output listbox control.
Immediately following that the msgbox pops up again and says "Not Invoked". I was expecting "Invoked" as a separate thread is trying to use the Output listbox control.
Of course this results in the Output.Items.Add being attempted which results in no visible result as the thread is not allowed to directly update the UI control.
I must've read a small books worth of different pages trying to thoroughly understand pit falls and methods but I feel I may have fallen into a trap alot of people may do.
With my current understanding and knowledge I'm unable to get out of that trap and would appreciate any input or advice.
Kind regards,
Lex

The problem here is that you are calling the function Msg not on an instance of myForm, but as a shared function of the class myForm.
Change your code in TestClass to add
Public FormInstance as myForm
and then replace
myForm.Msg("Testing Message")
with
FormInstance.Msg("Testing Message")
Then in StartButton_Click add the line
classEntity.FormInstance = Me
and you will get the expected result.

Related

vb.net Parameters Startup Form not hiding?

I am creating a application to be used with a touch panel device. The touch panel device comes with a standard windows OSK (On screen Keyboard). Whilst testing its been concluded that the standard OSK is to large and too complex for what we need it in. So I have built my own OSK. some of the feilds though only requier numeric inputs so I though of futher simplifying the process by creating a new form which hosts a numeric pad. so far this is all working. the idea is then to have the app which ask for diffrent inputs then to trigger the OSK application, say that the user wants to enter a phonenumber in one textbox I then want to start the OSK app using a parameter that trigers the OSK to start the NumericForm form first... this too I have working but the thing I can't get right is to hide the AlphabetForm I have tried the following method but am a little stumpt on how to get this right
In short its the Me.hide which isnt working as expected?
Private Sub AlphabetForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
#Region "Recive startup parameters (if any)"
Try
Dim OSKParameters As String = Command()
If OSKParameters = "OSKNUM" Then
NumericForm.Show()
Me.Hide()
Else
ShiftSelect = 0
End If
Catch ex As Exception
'Do nothing
End Try
#End Region
End Sub
Three possible setups, as described in comments:
► Using the Application Framework, override OnStartup and set Application.MainForm to a Form object determined by a command-line argument:
To generate ApplicationEvents.vb, where Partial Friend Class MyApplication is found, open the Project properties, Application pane, click the View Application Events Button: it will add the ApplicationEvents.vb file to the Project if it's not already there.
Imports Microsoft.VisualBasic.ApplicationServices
Partial Friend Class MyApplication
'[...]
Protected Overrides Function OnStartup(e As StartupEventArgs) As Boolean
Application.MainForm = If(e.CommandLine.Any(
Function(cmd) cmd.Equals("OSKNUM")), CType(NumericForm, Form), AlphabetForm)
Return MyBase.OnStartup(e)
End Function
'[...]
End Class
► Disabling Application Framework, to start the Application from Sub Main().
In the Project->Properties->Application pane, deselect Enable application framework and select Sub Main() from the Startup form dropdown.
If Sub Main() doesn't exist yet, it can be added to a Module file. Here, the Module is named Program.vb.
Module Program
Public Sub Main(args As String())
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(True)
Dim startForm as Form = If(args.Any(
Function(arg) arg.Equals("OSKNUM")), CType(New NumericForm(), Form), New AlphabetForm())
Application.Run(startForm)
End Sub
End Module
► If the OSK can be moved to UserControls, similar to what jmcilhinney suggested, run the default container Form and select the UserControl to show using the same logic (inspecting the command-line arguments):
Public Class AlphabetForm
Public Sub New()
InitializeComponent()
Dim args = My.Application.CommandLineArgs
Dim uc = If(args.Any(
Function(arg) arg.Equals("OSKNUM")), CType(New NumericUC(), UserControl), New AlphabetUC())
Me.Controls.Add(uc)
uc.BringToFront()
uc.Dock = DockStyle.Fill
End Sub
End Class

Raise an event on another form

I have two separated form(usercontrol A & B) in VB.Net. When a button clicked on formB, I want to an event raised on formA. How can do that? I searched stackoverflow.com and check several related solutions but they didn't work for me. I don't know what is the problem! Please help me.
For Example I tried this code:
FormA:
Dim MyEditForm As New ucEmployeeTimeEdit
'some code for showing and preparing MyEditForm
Private Sub GetSomeClick() Handles MyEditForm.MyClick
System.Console.WriteLine("test")
End Sub
FormB:
Public Event MyClick()
Private Sub BtnDelet_Click(sender As Object, e As EventArgs) Handles btnDelet.Click
RaiseEvent MyClick()
End Sub
While Idle_Mind answer is great, here's an alternative method with different advantages. (EDIT: I just noticed that he mentions this too, so you can consider this an elaboration on this subject)
If you keep FormB as a modal variable inside FormA, you can tell VB that you want to use it's events. Here's some skeleton code to show you:
Public Class FormA
Private WithEvents myFormB As FormB
'rest of the code for FormA
'you can use FormB's events like this
Private Sub GetSomeClicks() Handles myFormB.MyClick
'code code code
End Sub
End Class
Public Class FormB
Public Event MyClick()
End Class
Have fun!
Somewhere in the some code portion, you need to use AddHandler to "wire up" that event:
Public Sub Foo()
Dim MyEditForm As New ucEmployeeTimeEdit
'some code for showing and preparing MyEditForm
AddHandler MyEditForm.MyClick, AddressOf GetSomeClick
End Sub
Private Sub GetSomeClick()
System.Console.WriteLine("test")
End Sub
Note that GetSomeClick() does not have a "Handles" clause on the end. This is because your instance of ucEmployeeTimeEdit was a local variable.
If you had declared it as WithEvents at class level, then you could use the "Handles" clause (and consequently would no longer need the AddHandler call).

Can't call form_Load event because of "not declared" error, but the name is correct and the event is in the appropriate class

I'm trying to add a load event to a form so that the data on the form (pulled from a DB), will refresh after the user performs an add, delete, or update. Each of these functions is accessed by a click event. The form_Load event is the last line of code in the subroutine calling the click event.
I've made sure the name of the form matches the name in the Load event call, and I've made sure that the form isn't still named "Form1" elsewhere the code.
Here is the code used for each of my Load event calls:
Option Strict On
Public Class frmHome
Private Sub mySub
' do something
'Refresh frmHome to show new customer
frmHome_Load(sender, e)
End Sub
Here is the error message:
Error BC30451 'frmHome_Load' is not declared. It may be inaccessible due to its protection level.
Here's an example of what Jimi is talking about:
Public Class frmHome
Private Sub frmHome_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Foo()
End Sub
Private Sub Foo()
' ... code ...
End Sub
Private Sub mySub()
' do something
Foo()
End Sub
End Class

UserForm won't show

I'm using office developer tools and made a ribbon to access some functions. Thing is, it looks like I can't open a userform from a button, other commands seems to run normally.
Code:
Public Class Empresa
Private Sub Button1_Click(sender As Object, e As RibbonControlEventArgs) Handles btn_DBSol.Click
'Dim wnd As New frm_DBSolventes
'wnd.Show()
MsgBox("Hello World")
End Sub
End Class
This code have this result on excel ribbon:
https://s24.postimg.org/6z16l6g43/Print_1.jpg
Now using this code:
Public Class Empresa
Private Sub Button1_Click(sender As Object, e As RibbonControlEventArgs) Handles btn_DBSol.Click
Dim wnd As New frm_DBSolventes
wnd.Show()
'MsgBox("Hello World")
End Sub
End Class
Results in nothing:
There are no errors on Error List window. frm_DBSolventes is a userform on a userform referenced project, there is nothing on the form right now, just made a new project of userform and trying to show it. Is there something I'm missing? Is there any other way where I can use a userform on ribbon?
As Requested the frm_DBSolventes is
https://s29.postimg.org/6w6ae15qd/Print_3.jpg
Just add a datagridview cause I need to continue working. if it makes difference I can change it. There is no code on the form:
Public Class frm_DBSolventes
End Class
Try to show it as a modal Window.

Update control on main form via Background Worker with method in another class VB.Net

I have been banging my head against the wall all day trying to figure this one out.
I am finishing up a program to simply delete files in specific temp folders. I have read that it is sometimes good practice to create separate classes for methods and variables. So I have created a separate class for a couple methods to delete files and folders in a specified directory. I am using a Background Worker in my Form1 class and am calling my deleteFiles() method from my WebFixProcesses class in the DoWork event in the Form1 class. I am using a Background Worker so that I can easily report progress back to a progress bar on my main form.
The files get deleted without an issue but I just can't get the label on my main form to reflect the current file being deleted. the label doesn't change in any way.
I know the formula is correct as I can get this working if the method is in the Form1 class. and I simply use:
Invoke(Sub()
lblStatus.Text = File.ToString
lblStatus.Refresh()
End Sub)
here is my method that I am calling from the WebFixProcesses class:
Public Shared Sub deleteFiles(ByVal fileLocation As String)
For Each file As String In Directory.GetFiles(fileLocation)
Try
fileDisplay.Add(file)
For i = 1 To fileDisplay.Count
file = fileDisplay(i)
Form1.BackgroundWorker1.ReportProgress(CInt(i / fileDisplay.Count) * 100)
Next
IO.File.Delete(file)
Form1.labelText(file)
Form1.labelRefresh()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
Next
End Sub
labelText() and labelRefresh() are methods from my main form which are using delegates to try to pass information to the control:
Public Sub labelText(ByVal file As String)
If lblStatus.InvokeRequired Then
Dim del As New txtBoxDelegate(AddressOf labelText)
Me.Invoke(del, file)
Else
lblStatus.Text = file.ToString()
End If
End Sub
Public Sub labelRefresh()
If lblStatus.InvokeRequired Then
Dim del As New txtBoxRefDelegate(AddressOf labelRefresh)
Me.Invoke(del)
Else
lblStatus.Refresh()
End If
End Sub
If anyone can help me out to inform me what I may be doing wrong it would be immensely appreciated as my head is in a lot of pain from this. And maybe I am going at it all wrong, and just being stubborn keeping my methods in their own class. But any help would be awesome. Thanks guys!
What Hans wrote on the question comment is true: Form1 is a type, not an instance, but to make things easier to newbye programmes (coming from VB6), M$ did a "mix", allowing you to use the form name as the instance of the form in the main thread.
This however works only if you are on that thread.
If you reference Form1 from another thread, a new instance of Form1 is created.
To solve the issue, add this code to the form:
Private Shared _instance As Form1
Public ReadOnly Property Instance As Form1
Get
Return _instance
End Get
End Property
We will use this property to store the current instance of the form. To do so, add this line to the Load event:
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
_instance = Me
'other code here
End Sub
Now, from every class, in any thread, if you use
Form1.Instance
...you get the actual form. Now you can Invoke, even from the same form:
Me.instance.Invoke(Sub()
Me.lblStatus.Text = "Hello World"
End Sub)