I'm trying to update Label which is in Form1 obviously and it count good proxies, if there is a exception thrown it count bad proxies.
The Sub which invoke this labels are in Form1, the method is called from Class1 so I declared Frm as Form1 but it doesn't invoke and doesn't update labels =+1
I read many similar examples here on forum but I'm not sure what is the solution for me, I want to keep the code for proxies in Class1 if I move it to the Form1 then it works btw.
My Class1:
Imports System.Threading
Public Class Class1
Public Shared Property Frm As Form1
Public Shared _Count As Integer
Public Shared Sub GetProxies()
While True
Try
'MY CODE
Interlocked.Increment(_Count)
Frm.GoodProxyCount()
Catch ex As Exception
Frm.OnBadRequestChanged()
End Try
End While
Properties._runningThreads.Remove(Thread.CurrentThread)
End Sub
End Class
My Form1 Sub methods:
Public Class Form1
Public Sub OnBadRequestChanged()
Try
If Me.lblProxies.Text > 0 Then
Me.Invoke(Sub() Me.lblProxies.Text -= 1)
Else
If Not Me.chkManualProxies.Checked Then
Me.btnScrapeStart()
End If
End If
Catch ex As Exception
End Try
End Sub
Public Sub GoodProxyCount()
Try
Me.Invoke(Sub() Me.lbl_count.Text = Class1._Count.ToString())
If Me.lblProxies.Text > 0 Then
Me.Invoke(Sub() Me.lblProxies.Text -= 1)
Else
If Not Me.chkManualProxies.Checked Then
Me.btnScrapeStart()
End If
End If
Catch ex As Exception
End Try
End Sub
End Class
I have the same problem with anything I try to update like frm.checkbox.checked or other radiobuttons impossible to update or populate listview from outside of the Form1 so I don't know what am I missing here some declaration or property??
thanks for help
Here is the answer for someone who struggle with the same issue so do follow:
Add this declaration line in the top of your Form1 Class:
Public Shared instance As Form1
Declare a Label or CheckBox etc.. what you wish to 'invoke or access' from other class or forms as follows:
Public txt_Title As Label
Note that you have to give other name as the real name what is in your Form1, for example my TITLE LABEL is called: txtTitle but I had to change the name something I remember and similar in this declaration.
Create Sub in Form1 as Follows:
Public Sub Instances()
instance = Me
txt_Title = txtTitle
End Sub
Initialize the Sub in Form1_Load as follows:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
Instances()
Catch ex As Exception
End Try
End Sub
NOW from the Class1 just call any Label or elements you declared in Form1 as follows:
Form1.instance.txt_Title .Text = "me desired text from Class1"
I hope it helps someone as I struggle like 2 days on this since I'm self educated coder and I code for fun I could not known this as this is for someone basics from school btw..
Assuming Class1 still has the Shared variable to reference a Form1:
Public Class Class1
Public Shared Property Frm As Form1
End Class
You'd just do, in Form1:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Class1.Frm = Me
End Sub
Now Class1 has the correct reference to the instance of Form1 that was actually displayed on the screen and it can access all the members of it, such as:
' ... in Class1 ...
Frm.Label1.Text = "Hello from Class1"
You don't need a separate shared variable for Label1 above to be able to access it.
Related
I would like to be able to change an element of the main form inside a thread declared in a separated class (In this case I want to change a label text).
I tried the following code:
Form1:
Imports System.Threading
Public Class Form1
Public counter As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim SecondClassObject As New SecondClass()
End Sub
End Class
SecondClass:
Imports System.Threading
Public Class SecondClass
Public Thread As New Thread(AddressOf Increment)
Public counter As Integer = 0
Sub New()
Thread.Start()
End Sub
Sub Increment()
While True
Form1.Label1.Text = counter
counter += 1
End While
End Sub
End Class
If I do the same thing using a thread but in the form code itself than the label text will change:
Imports System.Threading
Public Class Form1
Public counter As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim thread As New Thread(AddressOf Increment)
thread.Start()
End Sub
Sub Increment()
While True
Label1.Text = counter
counter += 1
End While
End Sub
End Class
How should I do in order to archieve the same result using a thread in a separated class?
First, I do want to point out that you need to use an Invoke/Callback to safely set the label's text from the secondary thread. I don't know if you're doing that in your actual code base, but wanted to specify anyways.
Now, focused on the actual question, I believe that the easiest way to do as requested is to pass a reference to the original instance of Form1 to your SecondClass. Having a reference to the parent, means that you would be able to manipulate the parent's publicly exposed elements as needed.
Consider the below:
Public Class Form1
Public counter As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim secondClass As New SecondClassObject(me)
End Sub
Delegate Sub SetTextCallback (value as String)
Public Sub SetText (value as string)
if me.Label1.InvokeRequired Then
dim d as New SetTextCallback(addressOf SetText)
Me.Invoke(d, New Object() {value})
Else
me.label1.text = value
End If
End Sub
End Class
Public Class SecondClassObject
private _parent as Form1
private myThread As New Thread(AddressOf Increment)
Public Sub New (byref p as Form1)
me._parent = p
myThread.Start()
End Sub
Sub Increment()
While True
Me._parent.SetText(counter)
counter += 1
End While
End Sub
End Class
What is happening is that the a reference to the parent is passed into the second class as a constructor, doing so allows us to interact with the parent from the second class.
Now, that is one way, but other options do exist. Things such as specialized events/handlers or wiring up databinding between the Form1.Label1 and a property exposed from the SecondClassObject. Even a singleton pattern, where the value to be incremented is shared between all instances, so when the SecondClassObject increments it, Form1 would be aware and know to update Label1.
Also, please note that the above code is for example purposes, and is missing things such as a defined declaration for Label1.
On windows it's not possible to change the UI from a non UI thread.
It looks like that you have to use Control.Invoke or better Control.BeginInvoke.
The problem with using Control.Invoke is that it's executed on the UI thread and the calling thread waits for completion. Which would be bad when your background worker continusly does some computations.
The next code works for my, but I d'ont know if it's the best way to do it.
Of this way I need to write: _Button1 = Button1 and _MyVar = MyVar
This way of doing it seems repetitive and long when the parameters
passed to the Class Constructor are many more.
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim myVar As String = "My children"
Dim NewClass1 As New Class1(Button1, myVar)
'... do more ...
End Sub
End Class
Public Class Class1
Private WithEvents _Button1 As Button
Private _MyVar As String
Public Sub New(ByVal Button1 As Button, ByVal MyVar As String)
_Button1 = Button1
_MyVar = MyVar
'... do more ...
End Sub
Private Sub _Button1_Click(sender As Object, e As EventArgs) _
Handles _Button1.Click
MsgBox("Button1 clicked and I love: " & _MyVar)
End Sub
End Class
_Button1 = Button1 and _MyVar = MyVar This way of doing it seems repetitive and long when the parameters passed to the Class Constructor are many more.
This is actually dependency injection and is usually a good thing. If you end up with too many constructor parameters then it should be a clue that your class is doing too many things and is in violation of SOLID principles.
However, your Class1 should not have dependencies on Form1, it should be the other way around. Your code could much more simply be:
Public Class Class1
Friend Sub DoSomething(ByVal MyVar As String)
MsgBox(MyVar)
End Sub
End Class
Public Class Form1
Public Property class1() As Class1
Sub New()
InitializeComponent()
Me.class1 = New Class1()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
class1.DoSomething("blah, blah")
End Sub
End Class
While I am not demonstrating dependency injection here (for simplicity), you can see Class1 only knows that it can do some work, it doesn't know about the Form at all.
You could subscribe to the button click event in Class1 instead of passing the button into it. It would be cleaner, but still bad design.
I have 3 forms, namely Form1, Form2 & Form3.
Both Form1 and Form2 are able to access to Form3. However, I am going to give different function to a button in Form3 depends which form is used to access Form3.
Is anyone free to explain it to me how to code should work? Also, if you guys have link that this question previously answered or better concept, I will highly appreciate it. Thanks in advance.
My rough idea:
Public Class Form3
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If 'user are access from Form1 Then
'Action if user are access from Form1 here
Else
'Action if user are access from Form2 here
End If
End Sub
End Class
The problem with this code
If Me.Owner.Equals(Form1) Then . . .
this way you have tightly coupled objects - Form2 and 1 knows Form3 and Form3 knows 2 and 1. It may be not a problem for you or for now. But in OOP it is a problem, and in the future it may be a problem for your object scalability. Here is OOP approach. Writing from my head, so there could be syntactically incorrect items:
Public Interface IFormCanDoSomething
Sub DoSomething()
ReadOnly Property FormAction As EnumFormActions
End Interface
Public Class Form1
Implements IFormCanDoSomething
ReadOnly Property FormAction As EnumFormActions Implements IFormCanDoSomething.FormAction
Get
Return EnumFormActions.Action1
End Get
End Property
Sub DoSomething() Implements IFormCanDoSomething.DoSomething
Dim f As New Form3(Me)
f.Show()
End Sub
End Class
Public Class Form2
Implements IFormCanDoSomething
ReadOnly Property FormAction As EnumFormActions Implements IFormCanDoSomething.FormAction
Get
Return EnumFormActions.Action2
End Get
End Property
Sub DoSomething() Implements IFormCanDoSomething.DoSomething
Dim f As New Form3(Me)
f.Show()
End Sub
End Class
Public Class Form3
Private _owner As IFormCanDoSomething
Public Sub New(owner As IFormCanDoSomething)
_owner = owner
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If _owner.FormAction = EnumFormActions.Action1 Then
'Action if user needs one thing
ElseIf _owner.FormAction = EnumFormActions.Action2 Then
'Action if user needs another thing here
ElseIf _owner.FormAction = EnumFormActions.Action3 Then
'Action if user needs third thing
End If
End Sub
End Class
So what is the gain here? Look at Button1_Click. Do you see? - now you can have many forms for which Form3 needs to perform Action1, or/and many forms that Form3 needs to perform Action2, etc. This could go farther but for now good enough
Assuming you're using the Show method, pass the Form as the Owner into it:
Form3.Show(Form1)
Then reference the Form3 Owner:
Public Class Form3
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If Me.Owner.Equals(Form1) Then
'Action if user are access from Form1 here
ElseIf Me.Owner.Equals(Form2) Then
'Action if user are access from Form2 here
End If
End Sub
End Class
I want some values in a class to decrease whenever the timer in the main form ticks. I am creating multiple instances of the same class as my program is a simulation application and I am not storing these instances in an array or any list in that matter. I simply declare them and add their picture box to the controls on the main form. However I am hoping to have a sub routine inside the class that triggers whenever the timer in the main form ticks. I thought of something like this:
Public Class Jimmy
Dim _a As Integer = 10
Sub decreseNum(sender As Object, e As EventArgs) Handles mainapp.tmrLog.Tick
_a -= 1
End Sub
End Class
with mainapp being the name of the main form and tmrLog being the timer I want to associate my sub routine with. However the above code doesn't work
You could try defining a local reference to the timer in the Jimmy class:
Public Class Jimmy
Dim _a As Integer = 10
Private WithEvents tmr As Timer
Public Sub New(ByRef MainTmr As Timer)
tmr = MainTmr
End Sub
Sub decreseNum(sender As Object, e As EventArgs) Handles tmr.Tick
_a -= 1
End Sub
End Class
If you want all your classes react to timer.elapsed event, just sign up for it. The program below is fully operational. It is example what you can do to have your children to react to timer events of single parent/timer
Imports System
imports system.timers
Public Module Module1
Public Sub Main()
dim mc as new MainClass()
mc.CreateChildren(5)
System.Threading.Thread.Sleep(60000) ' wait and monitor output of childern
mc.Stop()
Console.WriteLine("All should stop now...")
Console.Read()
End Sub
End Module
public class MainClass 'This class could be your form
private _timer as new Timer(5000)
public sub CreateChildren(count as integer)
For i as integer = 1 to count
dim c as new Child(i)
Addhandler _timer.Elapsed, addressof c.DoWhentimerTicks
next
Console.WriteLine("timer should run now...")
_timer.Start()
end sub
public sub [Stop]()
_timer.Stop()
End Sub
End class
public class Child
private _myNO as integer
public sub new (no as integer)
_myNo = no
end sub
public sub DoWhentimerTicks(sender as object , e as ElapsedEventArgs)
Console.WriteLine(string.format("Child #{0} just ticked. Time = {1}", _myNo, e.signaltime))
end sub
End class
I found my solution, posting here for further reference.
My situation was trying to have my timer in the mainform triggering a sub in a class, and I used the following solution.
Class:
Sub addHandlesToSub
AddHandler Form1.Timer1.Tick, AddressOf subToBeTriggered
End Sub
Sub subToBeTriggered(sender As Object, e As EventArgs)
'My code
End Sub
The parameters in subToBeTriggered are useful when you want to remove the handler with
RemoveHandler Form1.Timer1.Tick, AddressOf subToBeTriggered
Otherwise, there will be an error without the parameters.
Thanks for all the answers though.
I have two forms both with the same buttons on, and I want to have it so that if I click the button both buttons will do the same thing i.e. they are referencing each other on different forms. the way i found was:
Public Class Form2
Dim form1 As New form1
Private Sub Button2_Click
form1.backcolor=black
form2.backcolor=black
end sub
end class
then
Public Class Form1
Dim form2 As New form2
Private Sub Button1_Click
form1.backcolor=black
form2.backcolor=black
end sub
end class
only this doesn't work as there is an error:An unhandled exception of type 'System.StackOverflowException' occurred in System.Windows.Forms.dll as far as i can see there is no infinite loop or stack over flow.
any help would be greatly appreciated.
You have an infinite loop, because each time one of the forms is instantiated, it is instantiating the other. Creating a Form1 will create a Form2, then Form2 immediately creates another Form1 and so on and so on...
Change your code to this:
Public Class Form2
Private Sub Button2_Click
Dim form1 As New Form1
form1.backcolor=black
form2.backcolor=black
End sub
End class
Public Class Form1
Private Sub Button1_Click
form1.backcolor=black
Dim form2 As New Form2
form2.backcolor=black
End sub
End class
Now it will only create the other class instances when you click a button.
Like Karl Anderson said, there is a infinite loop in your code. His solution will create a new form every time you click the button. If you don't want this behavior, I think that the best approach is to use the mediator pattern. And it will be much more easy if you want to add new actions and new forms.
The code will look something like this:
Public Class Mediator
Private forms As New List(Of BaseForm)
Public Sub RegisterForm(form As BaseForm)
forms.Add(form)
End Sub
Public Sub ChangeAllFormsBackColorToBlack()
For Each form In forms
form.ChangeBackColorToBlack()
Next
End Sub
End Class
Public Class BaseForm
Private med As Mediator
Public Sub New(med As Mediator)
Me.med = med
Me.med.RegisterForm(Me)
End Sub
Public Sub ChangeBackColorToBlack()
backcolor = black
End Sub
Public Sub OnButtonClick()
Me.med.ChangeAllFormsBackColorToBlack()
End Sub
End Class
Public Class Form2
Inherits BaseForm
Public Sub New(med As Mediator)
MyBase.New(med)
End Sub
Private Sub Button2_Click()
Me.OnButtonClick()
End Sub
End Class
Public Class Form1
Inherits BaseForm
Public Sub New(med As Mediator)
MyBase.New(med)
End Sub
Private Sub Button1_Click()
Me.OnButtonClick()
End Sub
End Class
Module MediatorDemo
Sub Main()
Dim med As New Mediator
Dim f1 As New Form1(med)
Dim f2 As New Form2(med)
f1.OnButtonClick()
End Sub
End Module