How to Convert string to Generic Of T? - vb.net

How I call generic Of T sub choose form string?
How to better way code like this?
Sub ShowAddfrm(Of T As {Form, New})()
dim frm as new T 'New Form
frm.Show()
End Sub
Private Sub btnAddProblemfrm_Click(sender As Object, e As EventArgs)
Dim keys As String = CType(sender, Button).Name.Replace("btnAdd", "")
If keys = "frmShowProblem" Then
ShowAddfrm(Of frmShowProblem)()
End If
If keys = "frmUser" Then
ShowAddfrm(Of frmUser)()
End If
End Sub

Try this overloaded method, allowing both a Form reference and string parameter.
You can pass the default instance of a Form, naming it directly:
ShowAddfrm(Form2)
or the Form's name:
ShowAddfrm("Form2")
or using a Control's Tag property (or any other source) in an event handler:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ShowAddfrm(DirectCast(sender, Control).Tag.ToString())
End Sub
There's a difference:
if you use pass the instance of a Form, only that instance will be created. Meaning, if you use a Button to show the Form and you press the Button multiple times, no new instances will be created. If you close the Form, then a new instance will be shown.
If you use the string version, each time you call this method, a new instance of the Form will be shown, so you can have multiple Forms on screen.
The string version uses Activator.CreateInstance to generate a new instance of a Form using it's name.
Sub ShowAddfrm(Of T As {Form, New})(ByVal form As T)
form.Show()
End Sub
Sub ShowAddfrm(formName As String)
Dim appNameSpace = Assembly.GetExecutingAssembly().GetName().Name
Dim form = CType(Activator.CreateInstance(Type.GetType($"{appNameSpace}.{formName}")), Form)
ShowAddfrm(form)
End Sub

Related

How do I pass an object to a default form instance

I understand its possible to open the "default instance" of a form in vb.net by calling NameOfTheForm.show()
How do I pass an object to the default form instance so i can work on the object and use it to populate the forms Text boxes?
I've tried to add a parameter to the frmNames New method but I'm not sure how then to open the form. The old style instantiate an object works:
Dim DetailsForm As New frmOrder(oOrder)
DetailsForm.Show()
But I'm used to using the :
frmOrder.show
syntax.
Should I use the top method or should I use the bottom method and have a public property on the form to accept the Object?
Have a missed a better method of doing this?
Thanks
Your calling method should use your first option
'Careful about declaring this in a sub, because when that sub ends, the form might get closed.
'It might be best to declare this as an instance var (aka form-level var)
Private DetailsForm As frmOrder
'this could go in an event handler, or anywhere
DetailsForm = New frmOrder(oOrder)
DetailsForm.Show()
You will need to add a constructor to your DetailsForm:
Private _oOrder as OrderType
Public Sub New(oOrder As OrderType)
'Best to save it to a private instance var and process it during Form_Load
_oOrder = oOrder
End Sub
Then when your Form_Load() runs, it can use your private instance var to fill your TextBoxes, like you want.
A second, but less eloquent approach would be to add a public property to the form and after you call .Show(), you can assign a value DetailsForm.OrderObject = oOrder, and then process the object that was passed-in.
The constructor approach is better because it can be "compiler checked"
If you really feel you must use default instance.
In Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Form2.myObject = New Coffee("Breakfast Blend", 7)
Form2.Show()
End Sub
In Form2
Public myObject As Coffee
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TextBox1.Text = myObject.Name
TextBox2.Text = myObject.ID.ToString
End Sub

Update textbox to a different form from backgroundworker

I have two forms, Form1 and Newform. Form1 has two buttons and a textbox and Newform has its own textbox. I am using a settext sub to invoke a delegate sub in the backgroundworker to update the textbox in both forms.
The textbox in Form1 seems to be updating but the textbox in Newform isn't updating.
Is there something that I'm missing if I want to update the textbox on a different form?
Thanks in advance.
Imports System.Threading
Public Class Form1
Dim stopbit As Boolean
Dim TestingComplete As Boolean
Dim ReadValue As Double
Dim FinalValue As Double
Delegate Sub SetTextCallback(ByRef Txtbox As TextBox, ByVal Txt As String)
'Thread Safe textbox update routine
Private Sub SetText(ByRef Txtbox As TextBox, ByVal Txt 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.
Console.WriteLine(Txtbox.InvokeRequired & " textbox invokerequired")
If Txtbox.InvokeRequired Then
Try
'MsgBox("inside settext")
Txtbox.Invoke(New SetTextCallback(AddressOf SetText), Txtbox, Txt)
Catch ex As Exception
MsgBox(ex.Message)
End Try
Else
Txtbox.Text = Txt
Txtbox.Update()
End If
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
newform.Show()
End Sub
Function ReadTemp() As Double
ReadValue = ReadValue / 2
Return ReadValue
End Function
Sub Test()
Dim starttime As Integer
Dim EllapsedTime As Integer
Dim OldValue As Double = 0
Dim NewValue As Double = 0
Dim Difference As Double = 1
Dim Margin As Double = 0.1
stopbit = False
starttime = My.Computer.Clock.TickCount
Do
Thread.Sleep(200)
OldValue = NewValue
NewValue = ReadTemp()
Difference = Math.Abs(NewValue - OldValue)
SetText(Me.TextBox1, Difference.ToString)
SetText(newform.TextBox1, Difference.ToString)
newform.Refresh()
EllapsedTime = My.Computer.Clock.TickCount - starttime
Loop Until EllapsedTime > 5000 Or stopbit = True ' Or Difference < Margin
FinalValue = NewValue
TestingComplete = True
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
stopbit = True
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
For i As Integer = 1 To 10
ReadValue = 100000
TestingComplete = False
ThreadPool.QueueUserWorkItem(AddressOf Test)
Do
Thread.Sleep(200)
Loop Until TestingComplete = True
MsgBox("Final Value " & FinalValue)
Next
End Sub
End Class
Your issue is due to that you're using the default instance of newform. In VB.NET default form instances is a feature that allows you to access a form via its type name without having to manually create an instance of it.
In other words it lets you do this:
newform.Show()
newform.TextBox1.Text = "Something"
...instead of doing it the correct way, which is this:
Dim myNewForm As New newform
myNewForm.Show()
myNewForm.TextBox1.Text = "Something"
Above we create a new instance of newform called myNewForm. This is required to be able to use most objects in the framework (including forms). However, VB.NET simplifies this behaviour by offering to create the instance for you, which is what is going on in my first example.
The problem with these default instances is that they are thread-specific, meaning a new instance is created for every thread that you use this behaviour in.
Thus the form you refer to when you do:
newform.Show()
...is not the same form that you refer to in your thread, because a new instance has been created for it in that thread:
'This is not the same "newform" as above!
SetText(newform.TextBox1, Difference.ToString)
The solution to this is of course to create the instance yourself, allowing you to have full control over what's going on:
Dim newFrm As New newform
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
newFrm.Show()
End Sub
...your code...
Sub Test()
...your code...
SetText(newFrm.TextBox1, Difference.ToString)
...even more of your code...
End Sub
As a side note you can remove your calls to newform.Refresh() and Txtbox.Update(). These just cause unnecessary overhead by forcing the form and text boxes to redraw themselves, which is already done when you change any of their properties that affect their contents/design (so you are essentially making them redraw themselves twice).
Also, if you want to make invoking to the UI thread simpler and you are using Visual Studio/Visual Basic 2010 or newer, you could switch to using lambda expressions instead of regular delegates. They're much easier to use and allows you to create whole methods in-line that can be invoked on the UI thread.
For this purpose I've written an extension method called InvokeIfRequired() which lets you invoke any method/function on the UI thread, checking InvokeRequired for you. It's similar to what you have now, only it works for any control (not just text boxes) and with lambda expressions, allows you to run any code you want on the UI.
You can use it by adding a module to your project (Add New Item... > Module) and naming it Extensions. Then put this code inside it:
Imports System.Runtime.CompilerServices
Public Module Extensions
''' <summary>
''' Invokes the specified method on the calling control's thread (if necessary, otherwise on the current thread).
''' </summary>
''' <param name="Control">The control which's thread to invoke the method at.</param>
''' <param name="Method">The method to invoke.</param>
''' <param name="Parameters">The parameters to pass to the method (optional).</param>
''' <remarks></remarks>
<Extension()> _
Public Function InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object()) As Object
If Parameters IsNot Nothing AndAlso _
Parameters.Length = 0 Then Parameters = Nothing
If Control.InvokeRequired = True Then
Return Control.Invoke(Method, Parameters)
Else
Return Method.DynamicInvoke(Parameters)
End If
End Function
End Module
This allows you to invoke either one line of code by doing:
Me.InvokeIfRequired(Sub() Me.TextBox1.Text = Difference.ToString())
Or to invoke a whole block of code by doing:
Me.InvokeIfRequired(Sub()
Me.TextBox1.Text = Difference.ToString()
newFrm.TextBox1.Text = Difference.ToString()
Me.BackColor = Color.Red 'Just an example of what you can do.
End Sub)
YourSubHere
Me.Invoke(Sub()
Form1.Textbox1.text="some text1"
Form2.Textbox2.text="some text2"
End Sub)
End Sub
Or if it's a one liner.
Me.Invoke(Sub() Form1.Textbox1.text="some text1")
Depending on what you need, you could invoke just some control like:
Textbox1.invoke(Sub() Textbox1.text="some text1")

Get form control using string name VB.NET

I have a control on a form UserNameCtrl and that control has a sub called LoadCtrl
I essentially have loads of these subs for clicks, so I want to put them all into one event handler
Private Sub NewsletterBtn_Click(sender As Object, e As EventArgs) Handles NewsletterBtn.Click, NewsletterImage.Click
If Not MainNewsletterCtrl.Loaded() Then MainNewsletterCtrl.Load()
End Sub
However within each of the subs the control names are hardcoded to call the .loaded and .load functionality.
I've wrote a new version of this
Private Sub GenericNavItem_Click(sender As Object, e As EventArgs)
Dim ctrl As Control = Controls.Find(sender.tag, True).FirstOrDefault
'Want to do the Controlname.Load here
End Sub
Using the tag (which I named as the control name) I got the corresponding control. But it's bringing back it as a control rather than of the type I want it to be.
I know I declare it as Control, but I don't know how I can cast it to be the ControlName.Load rather than the generic control.
If they are all the same class (or base class), then just cast to that class. If they are all different class but have the same method Load and Loaded, then I suggest you create an interface.
Interface ISomeName
Sub Load()
Function Loaded() As Boolean()
End Interface
Make sure all your class implement it and then just cast to that interface.
Private Sub GenericNavItem_Click(sender As Object, e As EventArgs)
Dim ctrl As Control = Controls.Find(sender.tag, True).FirstOrDefault
Dim ctrlInterface As ISomeName = CType(ctrl, ISomeName)
If Not ctrlInterface.Loaded() Then ctrlInterface.Load()
End Sub

How to get values from a dialog form in VB.NET?

I have a "frmOptions" form with a textbox named "txtMyTextValue" and a button named "btnSave" to save and close the form when it's clicked,
then, I'm showing this dialog form "frmOptions" when a button "btnOptions" is clicked on the main form "frmMain", like this
Private Sub btnOptions_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOptions.Click
ShowOptionsForm()
End Sub
Private Sub ShowOptionsForm()
Dim options = New frmOptions
options.ShowDialog()
End Sub
How can I get in the main form "frmMain" the value inserted in the textbox "txtMyTextValue" when the "btnSave" is clicked?
You want to capture the information from the dialog only if the result is OK (user presses Save instead of Cancel or closes the dialog some other way), so do this:
Private Sub ShowOptionsForm()
Dim options = New frmOptions
' Did the user click Save?
If options.ShowDialog() = Windows.Forms.DialogResult.OK Then
' Yes, so grab the values you want from the dialog here
Dim textBoxValue As String = options.txtMyTextValue.Text
End If
End Sub
Now inside of your dialog form, you need to set the result Windows.Forms.DialogResult.OK when the user clicks the button that corresponds to the OK action of the dialog form, like this:
Public Class frmOptions
Private Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click
' Set the result to pass back to the form that called this dialog
Me.DialogResult = Windows.Forms.DialogResult.OK
End Sub
End Class
The simplest method is to add a public property to the frmOptions form that returns an internal string declared at the global level of the frmOptions
Dim strValue As String
Public Property MyStringValue() As String
Get
Return strValue
End Get
End Property
Then, when your user clicks the OK button to confirm its choices you copy the value of the textbox to the internal variable
Private Sub cmdOK_Click(sender As Object, e As System.EventArgs) Handles cmdOK.Click
strValue = txtMyTextValue.Text
End Sub
Finally in the frmMain you use code like this to retrieve the inserted value
Private Sub ShowOptionsForm()
Using options = New frmOptions()
if DialogResult.OK = options.ShowDialog() Then
Dim value = options.MyStringValue
End If
End Using
End Sub
I prefer to avoid direct access to the internal controls of the frmOptions, a property offer a indirection that could be used to better validate the inputs given by your user.
You can use Events to take care of this. With this approach the Settings Form does not have to be Modal and the user can click the Save Button at any time.
In frmOptions:
'You can expand the signature to take more than just a single String.
Friend Event SavedOptions(ByVal strData As String)
Private Sub btnSave_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSave.Click
RaiseEvent SavedOptions(txtMyTextValue.Text)
End Sub
In frmMain:
Private Sub ShowOptionsForm()
Dim options = New frmOptions
AddHandler options.SavedOptions, AddressOf OnOptionsSave
options.ShowDialog()
End Sub
Private Sub OnOptionsSave(ByVal strData As String)
'Or whatever you want to do on frmMain with Options Data.
MsgBox(strData)
End Sub
You can access the value from the frmOptions instance. However, this breaks the law of demeter.
You should expose the value with a property within your class.
Public Class frmOptions
Public ReadOnly Property MyTextValue As String
Get
Return Me.txtMyTextValue.Text
End Get
End Property
End Class
Then you can access the value:
Private Sub ShowOptionsForm()
Dim options = New frmOptions
Dim frmOptionTextValue As String
Dim frmOptionsDiagResult As DialogResult
frmOptionsDiagResult = options.ShowDialog()
If frmOptionsDiagResult = Windows.Forms.DialogResult.OK Then
frmOptionTextValue = options.MyTextValue
Else
'...
End If
End Sub
Finally, if you are using a Dialog then make sure to set the Dialog Result for the button.

How can a called form get the caller ID or tag of the calling control

I have a button on my form FormA which is called search. When this button is clicked the frmSearch loads. I want to make the frmSearch dynamic so if it called by the button 'Search Employee' it searches only employees etc.
Is there a way which I can input the caller's ID or tag into frmSearch instantly?
This is what I have at the moment but it only identifies the caller control. (I could instantiate a global variable and read it from frmSearch but I'm wondering if there's a better way):
Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
frmSearch.Show()
Dim btn As Button = CType(sender, Button)
MsgBox(btn.ToString)
MsgBox("you have clicked button " & CType(CType(sender, _
System.Windows.Forms.Button).Tag, String))
End Sub
In formA your code will look something like this
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim caller As String = DirectCast(sender, Button).Name
Dim f As New frmSearch(caller)
f.Show()
End Sub
Note that I am not using the default instance of the search form, this is important.
In the search form add this code
Dim whoCalled As String '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Public Sub New(caller As String)
InitializeComponent()
whoCalled = caller
End Sub
The variable whoCalled will contain the name of the caller.
If you want to make it impossible to create the form without passing the data, then in the search form also add
Private Sub New()
' This call is required by the designer.
InitializeComponent()
End Sub
This will force you to use the overloaded constructor.