vb.net animating a notification form. Problem with threading and sizing - vb.net

I have created a class to use as a notification window (similar to toast notifications, which are disabled on our system).
I use a timer object to timeout the closing the form, and a backgroundworker to handle the animation of it sliding in from the bottom of the screen. For debugging purposes the form just outputs it's own size and the screen bounds.
Imports System.ComponentModel
Public Class ASNotify
Public Sub New(ByVal title As String, ByVal msg As String, ByVal Optional timeout As Integer = 5000)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.Text = title
Me.NotifyMessage.Text = $"{Me.Width}x{Me.Height}{vbCrLf}{My.Computer.Screen.WorkingArea.Size.Width}x{My.Computer.Screen.WorkingArea.Size.Height}"
TimeoutTimer.Interval = timeout
TimeoutTimer.Enabled = True
AnimationWorker.RunWorkerAsync()
End Sub
Private Sub AnimationWorker_DoWork(sender As Object, e As DoWorkEventArgs) Handles AnimationWorker.DoWork
Dim xloc As Integer = My.Computer.Screen.WorkingArea.Size.Width - Me.Width
Dim yloc As Integer = My.Computer.Screen.WorkingArea.Size.Height
For x As Integer = 0 To Me.Height
MoveWindow(xloc, yloc - x)
Threading.Thread.Sleep(2)
Next
End Sub
Private Sub MoveWindow(xloc As Integer, yloc As Integer)
If InvokeRequired Then
Invoke(Sub() MoveWindow(xloc, yloc))
Else
Location = New Drawing.Point(xloc, yloc)
End If
End Sub
Private Sub TimeoutTimer_Tick(sender As Object, e As EventArgs) Handles TimeoutTimer.Tick
Me.Close()
End Sub
End Class
I call this from another form by calling
Private Sub NotifyUser(ByRef a As Alert.Alert)
Dim notify As New ASNotify(a.Location, a.Comment, 5000)
notify.Show()
End Sub
I call that sub by pressing a button on the form, and it works perfectly.....Sometimes.
repeatedly triggering the notify window makes it pops up as one of 2 different sizes on the screen, although the contents showing the size always state 264x81 and screenbounds 1920x1040
and occasionally I get an exception that the line saying 'Location = new Drawing.Point(xloc,yloc) is being called from a thread other than that which it was created on, despite the Invoke call.

Moved the timer-start and animationworker start to the Load method of the form, rather than New.
Private Sub ASNotify_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TimeoutTimer.Enabled = True
AnimationWorker.RunWorkerAsync()
End Sub

Related

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")

Determine Form Size from another Form

VB2012: In order to do some calculations in my main form I need to know the form size of a secondary form. The form size may change from user to user depending on OS and theme. I understand that the client size stays the same. However I think I am not doing something correctly as I get different numbers depending on where I call for the form size.
As an illustration here is my main form where on the load event I attempt to get the size of an Alert form
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'get the default width and height of an Alert form.
Dim frmW As Integer = frmAlert.Width 'in pixels
Dim frmH As Integer = frmAlert.Height 'in pixels
Dim frCsW As Integer = frmAlert.ClientSize.Width 'in pixels
Dim frmCsH As Integer = frmAlert.ClientSize.Height 'in pixels
Debug.Print("frmW={0} frmH={1} frCsW={2} frmCsH={3}", frmW, frmH, frCsW, frmCsH)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'set up a new alert form
Dim frm As New frmAlert
'show the alert form
frm.StartPosition = FormStartPosition.CenterParent
frm.Show() 'with this option the Alert Forms stay on the screen even if the Main form is minimized.
End Sub
Now the Alert form is set with FormBorderStyle=FixedDialog, ControlBox=False, MaximizeBox=False, and MinimizeBox=False and in the Alert form I have this on the load event:
Private Sub frmAlert_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Debug.Print("me.width={0} me.height={1} cs.width={2} cs.height={3}", Me.Width, Me.Height, Me.ClientSize.Width, Me.ClientSize.Height)
End Sub
and here is the debug output
frmW=380 frmH=168 frCsW=374 frmCsH=162
me.width=390 me.height=200 cs.width=374 cs.height=162
As expected the client size is the same but the total form size is different. I am trying to wrap my head around the differences in .Height and .Width. No other code exists to change the form properties. The second debug statement matches the form Size in the IDE designer. Why are the dimensions different? How would I properly query to get the form size from another form?
Before the form is shown it will have a smaller size compared to when it is visible. This is because when you show the form Windows will do all kinds of stuff to it based on the user's screen and theme settings, such as:
Resize it if the user has different DPI settings.
Apply borders to it based on the user's selected window theme.
(etc.)
Your best bet is to show the form first, then get its size.
If you don't want the form to be visible right away you can set its Opacity property to 0 to make it invisible, then change it back to 1.0 once you need the form to be shown to the user.
Ok so based on #Visual Vincent's suggestion I created a constructor when creating a new form. This goes in frmAlert.Designer.vb
Partial Class frmAlert
Private mIsProcessFormCode As Boolean = True
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.mIsProcessFormCode = True
End Sub
Public Sub New(ByVal IsProcessFormCode As Boolean)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.mIsProcessFormCode = IsProcessFormCode
End Sub
End Class
Then on the frmMain I add this code:
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Debug.Print("routine={0}", System.Reflection.MethodBase.GetCurrentMethod.Name)
Dim frm As New frmAlert(False) 'create an instance of the form without any of the Form processes
frm.Opacity = 0 'makes the form totally transparent
frm.Visible = False
frm.Show()
Dim frmWidth As Integer = frm.Width
Dim frHeight As Integer = frm.Height
Dim frCsWidth As Integer = frm.ClientSize.Width
Dim frCsHeight As Integer = frm.ClientSize.Height
frm.Close()
frm.Dispose()
Debug.Print("frmWidth={0} frHeight={1} frCsWidth={2} frCsHeight={3}", frmWidth, frHeight, frCsWidth, frCsHeight)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Debug.Print("routine={0}", System.Reflection.MethodBase.GetCurrentMethod.Name)
'set up the alert form normally
Dim frm As New frmAlert
'show the alert form
frm.StartPosition = FormStartPosition.CenterParent
frm.Show() 'with this option the Alert Forms stay on the screen even if the Main form is minimized.
End Sub
and in the frmAlert I add this code:
Private Sub frmAlert_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
Try
'process the form code ONLY if required
Debug.Print("routine={0} mIsProcessFormCode={1}", System.Reflection.MethodBase.GetCurrentMethod.Name, mIsProcessFormCode)
If mIsProcessFormCode Then
'do Closed stuff
End If
Catch ex As Exception
Debug.Print(ex.ToString)
End Try
End Sub
Private Sub frmAlert_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
Try
'process the form code ONLY if required
Debug.Print("routine={0} mIsProcessFormCode={1}", System.Reflection.MethodBase.GetCurrentMethod.Name, mIsProcessFormCode)
If mIsProcessFormCode Then
'do Closing stuff
End If
Catch ex As Exception
Debug.Print(ex.ToString)
End Try
End Sub
Private Sub frmAlert_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
'process the form code ONLY if required
Debug.Print("routine={0} mIsProcessFormCode={1}", System.Reflection.MethodBase.GetCurrentMethod.Name, mIsProcessFormCode)
Debug.Print("me.width={0} me.height={1} cs.width={2} cs.height={3}", Me.Width, Me.Height, Me.ClientSize.Width, Me.ClientSize.Height)
If mIsProcessFormCode Then
'process text file
End If
Catch ex As Exception
Debug.Print(ex.ToString)
End Try
End Sub
and the debug output:
routine=frmMain_Load
routine=frmAlert_Load mIsProcessFormCode=False
me.width=390 me.height=200 cs.width=374 cs.height=162
routine=frmAlert_FormClosing mIsProcessFormCode=False
routine=frmAlert_FormClosed mIsProcessFormCode=False
frmWidth=390 frHeight=200 frCsWidth=374 frCsHeight=162
routine=Button1_Click
routine=frmAlert_Load mIsProcessFormCode=True
me.width=390 me.height=200 cs.width=374 cs.height=162
I believe this is what I want. All frmAlert sizes match what I have in the IDE designer. If you can think of any modifications please let me know. Many thanks.

Closing form with Gif throws InvalidOperationException

This is clearly a problem of me not understanding how to properly setup a UI thread, but I can't figure out how to fix it.
I have a datagridview where I click a button, get the information from the network, and then display it on the datagridview with the new data. While it is on the network I have a form I show with an updating gif, a form I called "loading". Within that form I have the gif updating using the typical OnFrameChanged and m_isAnimating code that is on the internet.
However, no matter what format I use, I always get this exception caught here:
Public loader As New Loading
Private Sub OnFrameChanged(ByVal o As Object, ByVal e As EventArgs)
Try ' If animation is allowed call the ImageAnimator UpdateFrames method
' to show the next frame in the animation.
Me.Invalidate()
If m_IsAnimating Then
ImageAnimator.UpdateFrames()
Me.Refresh()
'Draw the next frame in the animation.
Dim aGraphics As Graphics = PictureBox1.CreateGraphics
aGraphics.DrawImage(_AnimatedGif, New Point(0, 0))
aGraphics.Dispose()
End If
Catch ex As InvalidOperationException
End Try
End Sub
And it usually says something along the lines of "was accessed from a thread it wasn't created on" or "Cannot access a disposed object. Object name: 'PictureBox'."
But I don't know why that is, since I am creating a new instance here every time. Here's the button's code:
Private Sub btnSlowSearch_Click(sender As Object, e As EventArgs) Handles btnSlowSearch.Click
Me.Cursor = Cursors.WaitCursor
'get datatable
loader.Show()
BWorkerLoadProp.RunWorkerAsync() 'go get data on network
'bworker will update datagridview with new data
'wait for worker to finish
If BWorkerLoadProp.IsBusy Then
Threading.Thread.Sleep(1)
End If
loader.Close()
End Sub
I realize it isn't very good code, but I have tried putting the loader inside the background worker, I have tried whatever. But no matter what the exception is called.
What's the proper way to show another updating form as I do background work?
The behavior documented is difficult to reproduce.
Probably something between the thread switching causes a call to OnFrameChanged after the call to close in the btnSlowSearch_Click.
In any case logic seems to suggest to call the ImageAnimator.StopAnimate in the close event of the form that shows the animation
So looking at your comment above I would add the following to your animator form
// Not needed
// Public loader As New Loading
Private Sub OnFrameChanged(ByVal o As Object, ByVal e As EventArgs)
Try
Me.Invalidate()
If m_IsAnimating Then
ImageAnimator.UpdateFrames()
Me.Refresh()
'Draw the next frame in the animation.
Dim aGraphics As Graphics = PictureBox1.CreateGraphics
aGraphics.DrawImage(_AnimatedGif, New Point(0, 0))
aGraphics.Dispose()
End If
Catch ex As InvalidOperationException
.. do not leave this empty or remove altogether
End Try
End Sub
Private Sub Form_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
... if you need to stop the closing you should do it here without stopping the animation
If m_IsAnimating Then
ImageAnimator.StopAnimate(AnimatedGif, _
New EventHandler(AddressOf Me.OnFrameChanged))
m_isAnimating = False
End If
End Sub
This is certainly not the only way to do this but I will provide you the simplest working example in hopes that it will help you to correct your own application.
1) Create a new vb.net windows forms application and add a button (Button1) onto the form.
2) Change the Form1 code to this:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If fLoading Is Nothing Then ' can only show one loading screen at a time
Dim oLoadingThread As clsLoadingThread = New clsLoadingThread ' creat new thread
oLoadingThread.ShowWaitScreen() ' show the loading screen
'-----------------------------------------
' your real processing would go here
'-----------------------------------------
For i As Int32 = 0 To 999999
Application.DoEvents()
Next
'-----------------------------------------
oLoadingThread.CloseLoadingScreen() ' we are done processing so close the loading form
oLoadingThread = Nothing ' clear thread variable
End If
End Sub
End Class
Public Class clsLoadingThread
Dim oThread As System.Threading.Thread
Private Delegate Sub CloseLoadingScreenDelegate()
Public Sub ShowWaitScreen()
' create new thread that will open the loading form to ensure animation doesn't pause or stop
oThread = New System.Threading.Thread(AddressOf ShowLoadingForm)
oThread.Start()
End Sub
Private Sub ShowLoadingForm()
Dim fLoading As New frmLoading
fLoading.ShowDialog() ' Show loading form
If fLoading IsNot Nothing Then fLoading.Dispose() : fLoading = Nothing ' loading form should be closed by this point but dispose of it just in case
End Sub
Public Sub CloseLoadingScreen()
If fLoading.InvokeRequired Then
' Since the loading form was created on a seperate thread we need to invoke the thread that created it
fLoading.Invoke(New CloseLoadingScreenDelegate(AddressOf CloseLoadingScreen))
Else
' Now we can close the form
fLoading.Close()
End If
End Sub
End Class
Module Module1
Public fLoading As frmLoading
End Module
3) Add a new form and call it frmLoading. Add a picturebox to the form and set the image to your updating gif.
4) Change the frmLoading code to this:
Public Class frmLoading
Private Sub frmLoading_Load(sender As Object, e As EventArgs) Handles Me.Load
fLoading = Me ' ensure that the global loading form variable is set here so we can use it later
End Sub
Private Sub frmLoading_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
fLoading = Nothing ' clear the global loading form since the form is being disposed
End Sub
End Class
Normally I would add the clsLoadingThread Class and Module1 Module to their own files but it's easier to show the code to you this way.

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).

Saving textbox name when clicked in order to input text

Hey all i am in need of some help getting my code working correctly like i am needing it to. Below is my code that when the user click on the textbox, it pops up a keyboard where they can click on any letter and it will type that letter into the textbox. Problem being is i can not seem to get the name of the text box to return so that it knows where to send the letters to.
Order in firing is:
TextBox1_MouseDown
keyboardOrPad.runKeyboardOrPad
kbOrPad.keyboardPadType
ClickLetters
Form1.putIntoTextBox
Form1
Private Sub TextBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseDown
Call keyboardOrPad.runKeyboardOrPad("SHOW") 'Just shows the keyboard
Call kbOrPad.keyboardPadType("PAD", TextBox1)
End Sub
Public Sub putIntoTextBox(ByRef what2Put As String, ByRef whatBox As TextBox)
whatBox.Text = what2Put '<-- has error Object reference not set to an instance of an object. for the whatBox.text
End Sub
kbOrPad class
Dim theBoxName As TextBox = Nothing
Public Sub keyboardPadType(ByRef whatType As String, ByRef boxName As TextBox)
theBoxName = boxName '<-- shows nothing here
Dim intX As Short = 1
If whatType = "PAD" Then
Do Until intX = 30
Dim theButton() As Control = Controls.Find("Button" & intX, True)
theButton(0).Enabled = False
intX += 1
Loop
ElseIf whatType = "KEYB" Then
End If
End Sub
Private Sub ClickLetters(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim btn As Button = CType(sender, Button)
If btn.Text = "Backspace" Then
Else
Call Form1.putIntoTextBox(btn.Text, theBoxName) 'theBoxName taken from keyboardPadType
End If
End Sub
Some visuals for you:
Pastebin code: http://pastebin.com/4ReEnJB0
make sure that theBoxName is a Module scoped variable, then I would populate it like this giving you the flexibility of implementing a shared TextBox MouseDown Handler:
Private Sub TextBox1_MouseDown(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseDown
Dim tb As TextBox = CType(sender, TextBox)
Call keyboardPadType("PAD", tb)
End Sub
Try something like this
Public Class Form1
Dim myKborPad As New kbOrPad
Private Sub TextBox1_MouseDown(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseDown
Dim tb As TextBox = CType(sender, TextBox)
Call myKborPad.keyboardPadType("PAD", tb)
End Sub
Edit Based on your PasteBin code.
I noticed you already have an instance of your keyboardPadType declared in your Module, use that instead of what I said earlier. That code should look like:
remove:
Dim myKborPad As New kbOrPad
and use the theKbOrPad that you created in your module like this:
Private Sub TextBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseDown
Dim tb As TextBox = CType(sender, TextBox)
Call keyboardOrPad.runKeyboardOrPad("SHOW")
Call theKbOrPad.keyboardPadType("PAD", tb)
'Call kbOrPad.keyboardPadType("PAD", tb)
End Sub
Also about the current error your are getting, you are trying to use the default instance of your Form1 , it isn't the actual Form that you are running, you can code around this by making the method you are trying to use as shared. Like this:
Public Shared Sub putIntoTextBox(ByRef what2Put As String, ByRef whatBox As TextBox)
whatBox.Text = what2Put
End Sub
But however I would actually prefer to put it into your Module like this
Public Sub putIntoTextBox(ByRef what2Put As String, ByRef whatBox As TextBox)
whatBox.Text = what2Put
End Sub
and call it like this
Call putIntoTextBox(btn.Text, theBoxName)
after making above changes your code worked.
First, you should replace the ByRef with ByVal (anytime you don't know whether you should use one or the other, use ByVal).
Secondly, I believe you don't need the method, putIntoTextBox, I think you should be able to do that directly (might be threading problems that prevent it, but I don't think that's likely based on your description). You don't show where Form1 is set (or even if it is), and that's another potential problem.
Finally, the better way to call back into the other class is to use a delegate/lambada.
(I know, no code, but you don't provide enough context for a working response, so I'm just giving text).