FormName.ButtonName.Visible = True - not working (vb.net) - vb.net

I am going to start by giving a simplified example of what I am dealing with
I have a windows form - lets call it 'formA' and on formA I have a button that in the properties for the button i set visible = false.
I have a different class called MainLoop
Public class MainLoop
sub new()
end sub
public sub run()
If someCondition then
formA.ButtonName.Visible = True
End if
end sub
end class
I have more than one thread running in this application and one thread just keeps running through this 'public sub run' looping through it until certain conditions are met. By debugging and stepping through the application I am sure that it is running the line that sets the visiblity = true. But the buttons are just not showing up on my form. I have confirmed that its not a simple spelling mistake or anything - it seems I am missing a fundamental piece of logic here.
I have tried doing
dim TempForm as new formA
then in the IF statement I tried
TempForm.ButtonName.Visible = true
but that is creating a new instance of the form - and its not actually setting the current form that I'm using's button.
Any help is appreciated.

If you are setting the visibility on a thread other than the UI thread, you will have to invoke it in order to make it visible. You can't touch anything on the UI thread from another thread.

To anyone who is wondering the answer ended up being,
Control.Invoke Method (Delegate, Object())
Tutorial can be found here

Related

MultiThreaded solution to avoid DialogBox pausing execution

I am currently automating a series of calls to a library in VB.NET consoleApplication. The functioncalls usually require a series of user selected inputs. My problem with this is that a set of these functions create a programmatically inaccessible DialogBox instance and pauses the execution of the program until they have been interacted with.
Right now I have tried to solve this problem by using multiple threads according to the code below.
Public Sub StartFormFunction(ByVal inputValue As String)
frameWork.showHiddenDialogBox(inputValue)
End Sub
Public Sub threadFunction(ByValue inputValue As String)
Dim nrOfOpenForms As Integer = Application.OpenForms.Count()
Try
Dim t As New Thread(New ParameterizedThreadStart(AddressOf StartFormFunction))
t.Priority = Threading.ThreadPriority.Highest
t.Start(inputValue)
'Wait until the prompt has been created.
While (Application.OpenForms.Count() = nrOfOpenForms) And (t.IsAlive)
End While
if Not t.IsAlive Then
log.Error("Thread did not open dialogBox")
Return
End If
'Select preffered button on dialogBox
Dim isFinished As Boolean = False
For Each curForm As Form In Application.OpenForms
For Each btn As Button In curForm.Controls.OfType(Of Button)
If btn.Name = "Button3" Then
btn.PerformClick()
isFinished = True
Exit For
End If
Next
if isFinished Then
Exit For
End If
Next
'Wait until thread completed Function
While t.IsAlive
End While
Catch ex As Exception
log.Error("Thread Error")
End Try
End Sub
I have not found a way to use Control.Invoke() in a console application yet and is because of this the reason it is not used.
The way I can get my code to be able to execute completely is to disable CheckForIllegalCrossThreadCalls which I am trying to avoid.
Is it possible to solve the problem of accessing a DialogBox without using multiple threads? If not, is the problem solvable by invoking the subcall?
EDIT
Some of my description might have been lacking in detailed information.
My problem is that my application run a method showHiddenDialogBox(), that run a set of instructions in a class that is kept out of scope from my code. This inaccessible class displays a form when all functionality have been executed. When this form is shown the application pause all execution of code until a user is promoting a input.
This makes it necessary to use multiple threads to get around. However this new thread will own this form while it is displayed an all of the content. This included in the buttons that I would need the other thread to access.
Dont use "CheckForIllegalCrossThreadCalls", just invoke a control with this code:
'Insert this in a module
<Runtime.CompilerServices.Extension()>
Public Sub InvokeCustom(ByVal Control As Control, ByVal Action As Action)
If Control.InvokeRequired Then Control.Invoke(New MethodInvoker(Sub() Action()), Nothing) Else Action.Invoke()
End Sub
The call this sub for every control in a thread
textbox1.InvokeCustom(sub() textbox1.text = "abc")

Cross-Threading issues [duplicate]

I am using vb.net, and in my program I get this 'crossthread operation not valid' error when I run my backgroundworker that will make this textbox enabled true. My main sub will first turn the enabled to false, and when the backgroundworker runs it will turn it back true then exit. Why does it give me an error? FYI: There is more code to this but I don't want to make it any more confusing...
Here is the stack trace:
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.OnEnabledChanged(EventArgs e)
at System.Windows.Forms.Control.set_Enabled(Boolean value)
at Helium.Form1.BackgroundWorker1_DoWork(Object sender, DoWorkEventArgs e) in C:\Users\Kevin\documents\visual studio 2010\Projects\Helium\Helium\Form1.vb:line 167
at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)
and here is the exact error message:
{"Cross-thread operation not valid: Control 'mainText' accessed from a thread other than the thread it was created on."}
Can someone please help me out!
Thanks,
KEvin
The purpose of the BackgroundWorker class is to perform work on a non-GUI thread while the GUI remains responsive. Unless you set Control.CheckForIllegalCrossThreadCalls to false (which you shouldn't do), or use Invoke as suggested in the other answers (which I also wouldn't recommend), you're going to get an illegal cross-thread operation exception.
If you want GUI-related "stuff" to happen while your BackgroundWorker is running, I'd generally recommend using the BackgroundWorker.ReportProgress method and attaching an appropriate handler to the BackgroundWorker.ProgressChanged event. If you want something on the GUI to happen once the BackgroundWorker is finished, then simply attach your handler to update the GUI to the BackgroundWorker.RunWorkerCompleted event.
Type the following code in the Form1_Load (or whatever your form is) sub:
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
It fixes all problems with blocked cross-thread operations.
Better way to this in VB.NET is to use a Extension it makes very nice looking code for cross-threading GUI Control Calls.
Just add this line of code to any Module you have.
<System.Runtime.CompilerServices.Extension()> _
Public Sub Invoke(ByVal control As Control, ByVal action As Action)
If control.InvokeRequired Then
control.Invoke(New MethodInvoker(Sub() action()), Nothing)
Else
action.Invoke()
End If
End Sub
Now you can write Cross-Thread Control code that's only 1 line long for any control call.
Like this, lets say you want to clear a ComboBox and it's called from threads or without threads you can just use do this now
cboServerList.Invoke(Sub() cboServerList.Items.Clear())
Want to add something after you clear it?
cboServerList.Invoke(Sub() cboServerList.Items.Add("Hello World"))
Where exactly do you set the Enabled property? If you do it within the DoWork event handler, this code is running on a different thread than the button was created on, which should give the exception that you experience. To get around this, you should use BeginInvoke. For convenience it could be wrapped into a method, like so:
Private Sub SetControlEnabled(ByVal ctl As Control, ByVal enabled As Boolean)
If ctl.InvokeRequired Then
ctl.BeginInvoke(New Action(Of Control, Boolean)(AddressOf SetControlEnabled), ctl, enabled)
Else
ctl.Enabled = enabled
End If
End Sub
Now you can safely call that method to enable or disable any control from any thread:
SetControlEnabled(someButton, False)
You cannot directly set a control's property that is on the UI thread from another thread. It can be done though, here is an example from msdn.
Private Sub SetText(ByVal [text] As String)
' InvokeRequired required compares the thread ID of the'
' calling thread to the thread ID of the creating thread.'
' If these threads are different, it returns true.'
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
Your Form_Load () pls write below code part. All your problems will be solved.
'## crossed-thread parts will not be controlled by this option...
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
Suggest to use AutomationPeer.
For example below code calls Execute button when I press F5 key. Similarly if you want a thread or Background worker to call an event or function you can use Automation Peer. In your case instead of button (Which I used here) you can use text box with its appropriate property to invoke.
'Button name=btnExecute
'Imports System.Windows.Automation.Peers
'Imports System.Windows.Automation.Provider
If e.Key = Key.F5 Then
Dim peer As New ButtonAutomationPeer(btnExecute)
Dim invokeProv As IInvokeProvider = TryCast(peer.GetPattern(PatternInterface.Invoke), IInvokeProvider)
invokeProv.Invoke()
End If
Regards
RV
CheckForIllegalCrossThreadCalls = False

How to make a loader in a separate thread?

I have a main form wich is expected to perfom some long operations. In parallel, I'm trying to display the percentage of the executed actions.
So I created a second form like this:
Private Delegate Sub DoubleFunction(ByVal D as Double)
Private Delegate Sub EmptyFunction()
Public Class LoaderClass
Inherits Form
'Some properties here
Public Sub DisplayPercentage(Value as Double)
If Me.InvokeRequired then
dim TempFunction as New DoubleFunction(addressof DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End if
End sub
Public Sub CloseForm()
If Me.InvokeRequired Then
Dim CloseFunction As New EmptyFunction(AddressOf CloseForm)
Me.Invoke(CloseFunction)
Else
Me.Close()
End If
FormClosed = True
End Sub
End class
My main sub, the one which is expected to perform the long operations is in another form as follows:
Private Sub InitApplication
Dim Loader as new LoaderClass
Dim LoaderThread as new thread(Sub()
Loader.ShowDialog()
End sub)
LoaderThread.start()
Loader.DisplayPercentage(1/10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2/10)
ConnectToDataBase()
Loader.DisplayPercentage(3/10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4/10)
LoadClients()
...
Loader.CloseForm()
End sub
The code works almost 95% of the time but sometimes I'm getting a thread exception somewhere in the sub DisplayPercentage. I change absolutely nothing, I just hit the start button again and the debugger continues the execution without any problem.
The exception I get is: Cross-thread operation not valid: Control 'LoaderClass' accessed from a thread other than the thread it was created on event though I'm using : if InvokeRequired
Does anyone know what is wrong with that code please ?
Thank you.
This is a standard threading bug, called a "race condition". The fundamental problem with your code is that the InvokeRequired property can only be accurate after the native window for the dialog is created. The problem is that you don't wait for that. The thread you started needs time to create the dialog. It blows up when InvokeRequired still returns false but a fraction of a second later the window is created and Invoke() now objects loudly against being called on a worker thread.
This requires interlocking, you must use an AutoResetEvent. Call its Set() method in the Load event handler for the dialog. Call its WaitOne() method in InitApplication().
This is not the only problem with this code. Your dialog also doesn't have a Z-order relationship with the rest of the windows in your app. Non-zero odds that it will show behind another window.
And an especially nasty kind of problem caused by the SystemEvents class. Which needs to fire events on the UI thread. It doesn't know what thread is the UI thread, it guesses that the first one that subscribes an event is that UI thread. That turns out very poorly if that's your dialog when it uses, say, a ProgressBar. Which uses SystemEvents to know when to repaint itself. Your program will crash and burn long after the dialog is closed when one of the SystemEvents now is raised on the wrong thread.
Scared you enough? Don't do it. Only display UI on the UI thread, only execute slow non-UI code on worker threads.
Thank you for your proposal. How to do that please ? Where should I
add Invoke ?
Assuming you've opted to leave the "loading" code of the main form in the main UI thread (probably called from the Load() event), AND you've set LoaderClass() as the "Splash screen" in Project --> Properties...
Here is what LoaderClass() would look like:
Public Class LoaderClass
Private Delegate Sub DoubleFunction(ByVal D As Double)
Public Sub DisplayPercentage(Value As Double)
If Me.InvokeRequired Then
Dim TempFunction As New DoubleFunction(AddressOf DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End If
End Sub
End Class
*This is the same as what you had but I moved the delegate into the class.
*Note that you do NOT need the CloseForm() method as the framework will automatically close your splash screen once the main form is completely loaded.
Now, over in the main form, you can grab the displayed instance of the splash screen with My.Application.SplashScreen and cast it back to LoaderClass(). Then simply call your DisplayPercentage() method at the appropriate times with appropriate values:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitApplication()
End Sub
Private Sub InitApplication()
Dim Loader As LoaderClass = DirectCast(My.Application.SplashScreen, LoaderClass)
Loader.DisplayPercentage(1 / 10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2 / 10)
ConnectToDataBase()
Loader.DisplayPercentage(3 / 10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4 / 10)
LoadClients()
' Loader.CloseForm() <-- This is no longer needed..."Loader" will be closed automatically!
End Sub
Private Sub LoadLocalConfiguration()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub ConnectToDataBase()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadInterfaceObjects()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadClients()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
End Class
If all goes well, your splash screen should automatically display, update with progress, then automatically close when your main form has finished loading and displayed itself.
Me.Invoke(TempFunction, Value)
Should be:
Me.Invoke(TempFunction, new Object(){Value})
because the overload with parameters takes an array of parameters.
Value is on the stack of the function in the current thread. You need to allocate memory on the GC heap and copy the value to that memory so that it is available to the other thread even after the local stack has been destroyed.

Why doesn't this multithreaded VB.NET (2010 Express Edition) program work properly?

I'm trying to make a program of mine into a multithreaded application, but I've hit a pair of snags that I documented in the following code. Any help that I can get with this to make it behave properly would be greatly appreciated so I can expand this stub into a more efficient version of my existing application.
Thank you for any advice you have on this matter.
- Aaron
Imports System.Threading
Public Class frmMain
''' <summary>Initializes the multithreaded form</summary>
Private Sub Initialize() Handles MyBase.Load
AddThread(AddressOf Update_UI)
running = True
For Each Thread In ThreadPool
Thread.IsBackground = True
Thread.Start()
Next
End Sub
''' <summary>Terminates the multithreaded form</summary>
Protected Overrides Sub Finalize() Handles MyBase.FormClosing
running = False
For Each Thread In ThreadPool
Thread.Join()
Thread = Nothing
Next
End Sub
''' <summary>Adds a worker thread to the ThreadPool</summary>
''' <param name="pointer">The AddressOf the function to run on a new thread.</param>
Private Sub AddThread(ByRef pointer As System.Threading.ParameterizedThreadStart)
Dim newthread As Integer
If ThreadPool Is Nothing Then newthread = 0 Else newthread = ThreadPool.GetUpperBound(0) + 1
ReDim Preserve ThreadPool(newthread)
ThreadPool(newthread) = New Thread(pointer)
End Sub
''' <summary>Updates the User Interface</summary>
Private Sub Update_UI()
'HELP: The commented out lines in this subroutine make the program work incorrectly when uncommented.
'HELP: It should echo 'output' to the titlebar of frmMain, but it also makes the form unresponsive.
'HELP: When I force the form to quit, the 'termination alert' does not trigger, instead the application hangs completely on Thread.Join (see above).
'HELP: If I remove DoEvents(), the form is unable to be closed...it simply goes unresponsive. Shouldn't the multithreading keep us from needing DoEvents()?
'If Me.InvokeRequired Then
' Me.Invoke(New MethodInvoker(AddressOf Update_UI))
'Else
While running
Dim output As String = System.DateTime.Now + " :: Update_UI() is active!"
Debug.Print(output)
'Application.DoEvents()
'Me.Text = output
End While
Debug.Print(System.DateTime.Now + " :: Termination signal recieved...")
'End If
End Sub
Delegate Sub dlgUpdate_UI()
Private ThreadPool() As Thread
Private running As Boolean
End Class
Yes, none of what you tried can work properly. You correctly identified the need to use Control.Invoke() to run the Me.Text assignment on the main thread. This is what is going wrong:
Your Invoke() call makes the entire method run on the main thread. It will start executing the loop and never exit. Your form goes catatonic since it can't do anything else anymore, like repaint the caption bar to show the changed text or respond to user input
The DoEvents call makes the form come back alive but now you've got a new problem: the user can close the window and your code keeps running. The running flag will never be set to false so the program won't stop. The user interface is gone though. Code would normally bomb on an ObjectDisposedException but not in your specific case, the Text property is stored in a private variable
You could alter the code so that only the Me.Text assignment runs on the main thread. But now you've got a new problem: the main thread will get pummeled by invoke request and doesn't get around to doing its regular (low priority) duties anymore. It goes catatonic. The essential problem is that you are trying to update the caption bar way too fast. There's no point, the user cannot read that fast. Update 20 times per second is plenty and looks smooth to the human eye
Do not use the Finalize() method for tasks like this, the code can easily trigger the 2 second finalizer thread time-out, bombing your program.
Do consider using the BackgroundWorker class, it takes care of some of these details.
It's the do while loop that is burning up all your cycles so you are loosing the battle and keeping the procesor busy no matter how many threads you use. Something like the following will be better suited for what you are trying to achieve.
Imports System.Threading
Public Class Form1
Private t As New Timer(AddressOf DoTimer, Nothing, 1000, 1000)
Private Sub DoTimer(ByVal state As Object)
UpdateUi()
End Sub
''' <summary>Updates the User Interface</summary>
Private Sub UpdateUi()
If InvokeRequired Then
Invoke(New DlgUpdateUi(AddressOf UpdateUi))
Else
Dim output As String = DateTime.Now & " :: Update_UI() is active!"
Debug.Print(output)
Text = output
End If
End Sub
Delegate Sub DlgUpdateUi()
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
t.Dispose()
End Sub
End Class
If I have said once I have said it a million times. Using Invoke, while useful in many situations, is abused and way overused. If all you want to do is have the progress of the worker threads displayed to a user then using Invoke is not always the best option. And it does not look like the best option here either.
Instead, publish the status text you are assigning to output into a variable that can be accessed via the UI thread. Then use a System.Windows.Forms.Timer to periodically poll its value at a more reasonable rate...maybe every 1 second or so. The Tick event already runs on the UI thread so you can immediately begin using this value to display to the end user by manipulating the various UI controls.
Strings are really easy to pass around from thread to thread because they are immutable which means they are inherently thread-safe. The only thing you really have to worry about is making sure the UI thread sees the most recent reference published to the shared variable. In C# you would use the volatile keyword for this. In VB you can use Thread.VolatileRead from the UI thread and Thread.VolatileWrite from the worker thread. Of course, if you are more comfortable wrapping the reads and writes in a SyncLock that is perfectly acceptable as well.

Crossthread operation not valid... - VB.NET

I am using vb.net, and in my program I get this 'crossthread operation not valid' error when I run my backgroundworker that will make this textbox enabled true. My main sub will first turn the enabled to false, and when the backgroundworker runs it will turn it back true then exit. Why does it give me an error? FYI: There is more code to this but I don't want to make it any more confusing...
Here is the stack trace:
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.OnEnabledChanged(EventArgs e)
at System.Windows.Forms.Control.set_Enabled(Boolean value)
at Helium.Form1.BackgroundWorker1_DoWork(Object sender, DoWorkEventArgs e) in C:\Users\Kevin\documents\visual studio 2010\Projects\Helium\Helium\Form1.vb:line 167
at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)
and here is the exact error message:
{"Cross-thread operation not valid: Control 'mainText' accessed from a thread other than the thread it was created on."}
Can someone please help me out!
Thanks,
KEvin
The purpose of the BackgroundWorker class is to perform work on a non-GUI thread while the GUI remains responsive. Unless you set Control.CheckForIllegalCrossThreadCalls to false (which you shouldn't do), or use Invoke as suggested in the other answers (which I also wouldn't recommend), you're going to get an illegal cross-thread operation exception.
If you want GUI-related "stuff" to happen while your BackgroundWorker is running, I'd generally recommend using the BackgroundWorker.ReportProgress method and attaching an appropriate handler to the BackgroundWorker.ProgressChanged event. If you want something on the GUI to happen once the BackgroundWorker is finished, then simply attach your handler to update the GUI to the BackgroundWorker.RunWorkerCompleted event.
Type the following code in the Form1_Load (or whatever your form is) sub:
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
It fixes all problems with blocked cross-thread operations.
Better way to this in VB.NET is to use a Extension it makes very nice looking code for cross-threading GUI Control Calls.
Just add this line of code to any Module you have.
<System.Runtime.CompilerServices.Extension()> _
Public Sub Invoke(ByVal control As Control, ByVal action As Action)
If control.InvokeRequired Then
control.Invoke(New MethodInvoker(Sub() action()), Nothing)
Else
action.Invoke()
End If
End Sub
Now you can write Cross-Thread Control code that's only 1 line long for any control call.
Like this, lets say you want to clear a ComboBox and it's called from threads or without threads you can just use do this now
cboServerList.Invoke(Sub() cboServerList.Items.Clear())
Want to add something after you clear it?
cboServerList.Invoke(Sub() cboServerList.Items.Add("Hello World"))
Where exactly do you set the Enabled property? If you do it within the DoWork event handler, this code is running on a different thread than the button was created on, which should give the exception that you experience. To get around this, you should use BeginInvoke. For convenience it could be wrapped into a method, like so:
Private Sub SetControlEnabled(ByVal ctl As Control, ByVal enabled As Boolean)
If ctl.InvokeRequired Then
ctl.BeginInvoke(New Action(Of Control, Boolean)(AddressOf SetControlEnabled), ctl, enabled)
Else
ctl.Enabled = enabled
End If
End Sub
Now you can safely call that method to enable or disable any control from any thread:
SetControlEnabled(someButton, False)
You cannot directly set a control's property that is on the UI thread from another thread. It can be done though, here is an example from msdn.
Private Sub SetText(ByVal [text] As String)
' InvokeRequired required compares the thread ID of the'
' calling thread to the thread ID of the creating thread.'
' If these threads are different, it returns true.'
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
Your Form_Load () pls write below code part. All your problems will be solved.
'## crossed-thread parts will not be controlled by this option...
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
Suggest to use AutomationPeer.
For example below code calls Execute button when I press F5 key. Similarly if you want a thread or Background worker to call an event or function you can use Automation Peer. In your case instead of button (Which I used here) you can use text box with its appropriate property to invoke.
'Button name=btnExecute
'Imports System.Windows.Automation.Peers
'Imports System.Windows.Automation.Provider
If e.Key = Key.F5 Then
Dim peer As New ButtonAutomationPeer(btnExecute)
Dim invokeProv As IInvokeProvider = TryCast(peer.GetPattern(PatternInterface.Invoke), IInvokeProvider)
invokeProv.Invoke()
End If
Regards
RV
CheckForIllegalCrossThreadCalls = False