VB - Find child form from parent - vb.net

I am in a project with multiple form.
I create a TicTacToe form here :
Private Sub MenuTicTacToe(ByVal sender As Object, ByVal e As System.EventArgs)
Dim page As Form = New TicTacToe
page.Show(Me)
End Sub
Here is a TicTacToe form:
Public Class TicTacToe
Public opponent as String
'Some code where user set opponent
Public Function Receive(S As String)
if string = opponent
'Some code
End Function
End Class
I would like to call my function Receive in my main form
If i do:
TicTactoe.Receive(S)
It call a instance of Receive where opponent does not exist.
I would like to find the oppened form of TicTacToe and call Receive
Thanks

Comments in line
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
page.Receive("Joe")
End Sub
'A form level variable to hold a reference to the instance of TicTacToe
'Although vb.net can use default instances, you have created an explicit
'instance of TicTacToe so you need to keep a reference if you want to
'refer to this instance.
Private page As TicTacToe
Private Sub MenuTicTacToe(ByVal sender As Object, ByVal e As System.EventArgs)
page = New TicTacToe()
page.Show(Me)
End Sub
Partial Public Class TicTacToe
Inherits Form
Public opponent As String
'Functions must be declared as a Type
'If you do not need a return value use a Sub
Public Function Receive(S As String) As String
Dim someString As String = ""
If S = opponent Then
'Do something
End If
'There must be a return Value
Return someString
End Function
End Class

Use this to show the form
Dim page As TicTacToe
Private Sub MenuTicTacToe(ByVal sender As Object, ByVal e As System.EventArgs)
page = New TicTacToe
page.Show(Me)
End Sub
Then you can use
page.Receive(S)
Edit
To use multiple forms
For Each f As TicTacToe in Application.OpenForms().OfType(Of TicTacToe)
f.Receive (S)
Next

In C#, you'd need a new instance, but as you are in VB, the compiler already does that for you.
What you are currently doing, is creating a new instance of the TicTacToe form and showing it:
Private Sub MenuTicTacToe(ByVal sender As Object, ByVal e As System.EventArgs)
Dim page As Form = New TicTacToe
page.Show(Me)
End Sub
But you don't save that instance anywhere. Then, in your next piece of code, you are using a different instance, which is the static one created by the compiler:
TicTacToe.Receive(S) // TicTacToe is the static instance
Therefore, you end up calling two different instances, which explains why there is no opponent set.
To get around this problem, do not create a new instance. In your Private Sub MenuTicTacToe, just use the instance created by the compiler, and you won't have this problem, just like this:
Private Sub MenuTicTacToe(ByVal sender As Object, ByVal e As System.EventArgs)
TicTacToe.Show(Me)
End Sub
Hope this helps.

Related

Can't Remove Listview Item From Second Form

I am having an issue when trying to delete ListView Items from a second form.
For example, if I use the following command on Form1 it works:
Listview1.SelectedItems(0).Remove
However, if I attempt to remove from Form2 like so:
Form1.Listview1.SelectedItems(0).Remove
I get the following error:
"Invalid argument=value of '0' is not valid for 'index'. Parameter name: index"
I then tried to get a count of items from the listview on Form2 and it gives me a return of 0
Form1.Listview1.Items.Count
I'm not sure what my problem is.
Update
I have posted a brief example of my code (using your suggestion as I can understand it):
frmShowMessages
Private Sub ViewMessage()
Dim frm As New frmViewMailMessage
frm.Show()
End Sub
Public Sub DeleteItem(ByVal index As Integer)
lsvReceivedMessages.Items(index).Remove()
End Sub
frmViewMessage
Private instanceForm as frmShowMessages
Private Sub frmViewMailMessage_Load(sender As Object, e As EventArgs) Handles MyBase.Load
instanceForm = New frmShowMessages()
End Sub
Private Sub cmdDelete_Click(sender As Object, e As EventArgs) Handles cmdDelete.Click
instanceForm.DeleteItem(_index)
End Sub
Hopefully my code can help identify where my issue is.
In VB.net usually you get a default Form instance for each of your Form. Probably you are creating an instance of Form1 and then you are trying to access ListView1 of default instance.
E.g.
Sub ButtonClick()
Dim f As New Form1()
f.Show()
' at this point if you access f's ListView you will get correct count
f.ListView1.Items.Count
' however if you try to access default instance it will NOT have any item
Form1.ListView.Items.Count
End Sub
It means your instance f is NOT equal to default Form1 instance.
Solution can be, make the f variable as class level variable and use it everywhere. Or if Form1 will have only 1 instance, then you can use the default instance everywhere.
Personally I would NOT go with direct control accessing over forms. I would create a Public method which should return the data as list to the caller, in this case your Form2.
UPDATED-2:
As per your given scenario, I am simplifying things for you, and doing implementation using Event.
Public Class frmShowMessages
Private Sub btnOpenMessage_Click(sender As System.Object, e As System.EventArgs) Handles btnOpenMessage.Click
Dim frmView As New frmViewMessage(Me.ListView1.SelectedItems(0).Index)
AddHandler frmView.MessageDeleted, AddressOf DeleteMessageHandler
frmView.Show()
End Sub
Private Sub DeleteMessageHandler(sender As Object, e As frmViewMessage.MessageDeletedEventArgs)
Me.ListView1.Items.RemoveAt(e.MessageIndex)
End Sub
End Class
Public Class frmViewMessage
' a class which will be used for Event communication
Public Class MessageDeletedEventArgs
Inherits EventArgs
Public Property MessageIndex As Integer
Public Sub New(ByVal iIndex As Integer)
MyBase.New()
Me.MessageIndex = iIndex
End Sub
End Class
' main event which will alert the parent that a message deletion should be done
Public Event MessageDeleted As EventHandler(Of MessageDeletedEventArgs)
' private variable that will hold the MessageIndex
Private Property MessageIndex As Integer
' method that is responsible to raise event
Protected Overridable Sub OnMessageDeleted()
RaiseEvent MessageDeleted(Me, New MessageDeletedEventArgs(Me.MessageIndex))
End Sub
' we want to create this Form using the MessageIndex of ListView
Public Sub New(ByVal iMessageIndex As Integer)
Me.InitializeComponent()
Me.MessageIndex = iMessageIndex
End Sub
' the delete button will raise the event to indicate parent that
' a deletion of message should be done
Private Sub btnDelete_Click(sender As System.Object, e As System.EventArgs) Handles btnDelete.Click
Me.OnMessageDeleted()
End Sub
End Class

Form cannot refer to itself through its default instance on shared call

Consider the following code, in a brand new WinForms .NET 4.0 application, with default settings:
Public Class Form1
Private Sub AAA()
Form1.AAA(Nothing) 'cannot refer to itself through its default instance; use 'Me' instead.
End Sub
Private Shared Sub AAA(str As String)
End Sub
End Class
I am getting this error:
{FORM_CLASS_NAME} cannot refer to itself through its default instance; use 'Me' instead.
I also get this warning at the same line:
Access of shared member, constant member, enum member or nested type through an instance; qualifying expression will not be evaluated.
Assuming default instance is meant here, it ends up in an infinite loop - VS suggests to change Me.AAA() to Form1.AAA(), and then back. AAA() works in both.
Converting Private Sub AAA() to Shared solves the error. It seems like from Microsoft's point of view, all overloads must be shared, if at least one is. Or you get this default instance confusion. Why?
To clarify, I do not want to use default instance here, just do a shared call.
If anyone encountered the same situation, please advise.
Creating a variable alias that has the same name as the type of the Form class is without a doubt the single most disastrous VB.NET problem. But it was necessary to give VB6 developers a fighting chance to move to VB.NET.
The workaround is to stop trying to be explicit about what method you want to call. This compiles fine and is unambiguous, at least in your snippet:
Private Sub AAA()
AAA(Nothing) '' fine
End Sub
If that really, really hurts then simply swapping the two methods removes the ambiguity:
Private Shared Sub AAA(str As String)
End Sub
Private Sub AAA()
Form1.AAA(Nothing) '' fine
End Sub
Can you get away with this? Your usage will be very similar Form1.AAA() vs. code.AAA().
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
code.AAA()
End Sub
Private Class code
Public Shared Sub AAA()
End Sub
End Class
End Class
EDIT
Given the new information in the OP - another solution to your issue may be to use optional parameters -- ie :
Private Shared Sub AAA(Optional ByVal str As String = Nothing)
Also - the resolution works out in the "right" way if you simply change the ordering of the declarations -- this avoids the compiler error:
Private Shared Sub AAA(ByVal str As String)
End Sub
Private Sub AAA()
Form1.AAA(Nothing)
End Sub
--
Keeping this below because it can be helpful in other circumstances
Perhaps your larger application did something like this - VB is full of messes like this you can get yourself into. This will compile but it will crash :
Public Class Form1
Private Shared Sub AAA()
Form1.Text = "this"
End Sub
Private Sub Label1_TextChanged(sender As System.Object, _
e As System.EventArgs) _
Handles Label1.TextChanged
Form1.AAA()
End Sub
End Class
Just the same, this actually is "fine" (I use the term loosely)...
Public Class Form1
Private Shared dont As Boolean = True
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) _
Handles MyBase.Load
dont = False
End Sub
Private Shared Sub AAA()
If Not dont Then Form1.Text = "this"
End Sub
Private Sub Label1_TextChanged(sender As System.Object, _
e As System.EventArgs) _
Handles Label1.TextChanged
Form1.AAA()
End Sub
End Class
This is because the text changed handler will fire before Form1 completes loading (ie : during InitializeComponent()!) and will refer to the default instance which is not yet finished being created - so VB tries to create a new one for you so that you can call the shared method which spins you down the infinite loop.
Oddly, the Load handler is "fine" (again, loosely) to call Form1.AAA() in - as in your opening code - because the default instance (Form1 the instance of Form1 the Class) is finished creation at that point and another won't be created to satisfy the call. Any other code path, however, that starts in the shared call and ultimately ends up touching any instance data, no matter how torturous the path, will loop around and crash.
See also : Why is there a default instance of every form in VB.Net but not in C#?
Unclear what you are trying to accomplish overall. In the OP Form1.AAA should be just AAA.
Private Sub AAA()
AAA(Nothing)
End Sub
Private Sub AAA(str As String)
If str IsNot Nothing Then MsgBox(str) ' else ???
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
AAA()
AAA("hello")
End Sub

Form data erased after Windows form is closed

I have a subsidiary form where I can enter data and then save it before closing the form and going back to using the main form.
When I re-open the subsidiary form, I cannot see the changes in the data that I had entered earlier.
Can anyone tell me where I'm wrong ?
MainForm.vb
Public Class Maincls
oTestObj as New Testcls
oTestObj.XYZ = "XYZ"
Private Sub SoftwareSettingsToolStripMenuItem_Click(sender As System.Object, e As System.EventArgs) Handles SoftwareSettingsToolStripMenuItem.Click
Testcls.tbXYZ.Text = oTestObj.m_XYZ
Testcls.Show()
End Sub
End Class
Form_Testcls.vb
Public Class Testcls
Structure Params
Dim m_XYZ as String
End Structure
Dim oParams as Params
Public Sub New ()
InitializeComponent()
End Sub
Private Sub btnOK_Click(sender As System.Object, e As System.EventArgs) Handles btnOK.Click
XYZ = tbXYZ.Text
Me.Hide()
End Sub
Public Property XYZ() As String
Get
Return Me.oparams.m_XYZ
End Get
Set(ByVal value As String)
Me.oparams.m_XYZ = value
End Set
End Property
End Class
I think in windows forms the work around for this is to create a static class and add properties according to your requirement. Then populate these static properties on closing of your form. Now you can use the value set in the static data members, unless otherwise you change them on any other event.
Edit: In vb.net the Static is actually NonInheritable

inside class calling form1 sub

Hey all i am trying to call a public sub within a class that resides within my form1 code:
Public Class Form1
Public Shared objItem As ListViewItem
Class Server
Private Shared Sub StringMessageReceived(ByVal sender As Object, ByVal e As StringMessageEventArgs)
MsgBox("Received message: " & Convert.ToString(e.Message))
'Form1.ListView1.Items.Add(Convert.ToString(e.Message))
Call Form1.writeToLV(Convert.ToString(e.Message))
End Sub
End Class
Public Sub writeToLV(ByRef theStuff As String)
MsgBox(theStuff)
objItem = ListView1.Items.Add(theStuff)
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
ListView1.View = View.Details
ListView1.Columns.Add("Response", CInt(500))
End Sub
End Class
It sends the value over just fine but when it gets to putting it into the listview it never does?
Any pointers?
David
The most likely explanation is that the form that has been opened on the screen is not the default instance referenced by Form1 in the Server class.
I think that you need to restructure your code somewhat: if you are only going to have one instance of Form1, explicitly create the form and keep a reference to it in a global variable (i.e. g_Form1) rather than relying on the VB-provided default instance (assuming you are ever only going to have 1 instance of the form.
If you can have more than 1 instance of Form1, I would convert your internal Server static class to an Interface and when a new form is created, have it register itself with whatever mechanism is calling Server.StringMessageReceived.

Using private function

Hi
I am using following code to run a private function.
I have two values in my combo box, One and Two and two private functions with the same names, Private Sub One() and Private Sub Two()
I want my application to call the function whatever value user choses in the combo box.
If One is chosen in the combo box, Private function one should be called.
Thanks
Code is below, that does not work
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim vrValue = ComboBox1.Items(1)
Call vrValue()' In this case vrValue is Two, so Two() should be called.
End Sub
Private Sub two()
MsgBox("Function called")
End Sub
Make your subs functions (the only difference is the returning of a value) and put them in their own class:
Public Class RunFunctions
Dim oMessageBox As MessageBox
Public Function One() As String
'oMessageBox = MessageBox
Return "Message One"
End Function
Public Function Two() As String
Return "Message Two"
End Function
End Class
Add Each function from the class as an item in your combo box:
Public Class Combo_Functions
Dim oRunFunction As RunFunctions
Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As Object _
, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
MessageBox.Show(ComboBox1.Items(ComboBox1.SelectedIndex()))
End Sub
Private Sub Combo_Functions_Load(ByVal sender As Object _
, ByVal e As System.EventArgs) Handles Me.Load
oRunFunction = New RunFunctions
ComboBox1.Items.Add(oRunFunction.One())
ComboBox1.Items.Add(oRunFunction.Two())
End Sub
End Class
When the combo box is changed (or use the code for the button click) the messagebox for the correct function is executed.
Dim vrValue = ComboBox1.SelectedItem.ToString()
Select vrValue
Case "One"
One()
Else
Two()
End Select
It looks like what you're trying to do is to dynamically call a particular method using a string variable that contains its name. For example, the combo box would contain items "One" and "Two", and you would call the sub named "One" if the first item in the combo box is selected, or the sub named "Two" if the second item is selected. To that end, you may find this article interesting:
http://www.codeproject.com/KB/cs/CallMethodNameInString.aspx
The code in the article is in C#, which shouldn't be too difficult to convert to VB. But here's the translated version of the code for simply invoking a method without passing or returning any parameters (note: I have not tested this code). It simply uses reflection to find the appropriate method:
Public Shared Sub InvokeStringMethod(ByVal typeName As String, ByVal methodName As String)
'Get the type of the class
Dim calledType As Type = Type.[GetType](typeName)
'Invoke the method itself
calledType.InvokeMember(methodName, BindingFlags.InvokeMethod Or BindingFlags.[Public] Or BindingFlags.[Static], Nothing, Nothing, Nothing)
End Sub
You simply pass the name of the class that contains the method(s) you want to call as the typeName and the name of the method itself that you want to call as the methodName:
InvokeStringMethod("MyClass", "Two")