I've got a program which calls a second form. This second form has a combo box populated from the contents of an external file, and the user needs to select an option in the combo box from the presented options. This selection is then passed back to the main form where a lot of work is done.
This all works nicely the first time it is done. However, the second time this second form is called, the drop-down is blank. I've confirmed via some debugging that the correct code is being run and that entries are being added via "SecondForm.ComboBox1.Items.Add" (I can clear the combobox, check it's zero, read the data and then check the items in the list again, it increases correctly) but they're just not being displayed on the form. I can't figure out why or how to fix it.
And so the pertinent parts of the code....
At the form level I have this line to set up the second form, I believe I need the WithEvents to pass the selected data back as far as I can tell:
Public Class Form1 Friend WithEvents SecondForm As New Form2
Public Sub OpenStripformatstxtToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles OpenStripformatstxtToolStripMenuItem.Click
Dim fd As OpenFileDialog = New OpenFileDialog()
Dim pos1 As Integer
Dim pos2 As Integer
' Select the file to open
fd.Title = "Open File Dialog"
fd.InitialDirectory = "C:\BEST\Data"
fd.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
fd.FilterIndex = 1
fd.RestoreDirectory = True
' Put the filename selected in strfilename2
If fd.ShowDialog() = DialogResult.OK Then
strFileName2 = fd.FileName
Else : Return
End If
If SecondForm.IsDisposed Then
Dim secondform As New Form2
I suspect this line above is the problem, I'm creating the form a second time but WITHOUT the WithEvents paramater. However I can't use that from within this part of the code, I get an error "'WithEvents' is not a valid local variable declaration". I've read that closing and reopening forms is not good coding and that I should be hiding / showing them
secondform.Show()
InitializeComponent()
Else
SecondForm.Show()
End If
' Copy the file contents to a string called sfcontents (Strip Format Contents)
sfcontents = My.Computer.FileSystem.ReadAllText(fd.FileName)
' Define some points in the string, starting at the beginning
pos1 = 1
pos2 = 1
' Loop from the start to the end of the string
For pos1 = 1 To Len(sfcontents)
' Look for FO, the strip name header, do the following if you find it
If Mid(sfcontents, pos1, 3) = "FO " Then
pos1 = pos1 + 3
pos2 = pos1 + 1
'Find the space after "FO " so we've captured the whole next word, that's the strip name
Do Until Mid(sfcontents, pos2, 1) = " "
pos2 = pos2 + 1
Loop
' Add that strip name to the combobox for selecting by user
SecondForm.ComboBox1.Items.Add(Mid(sfcontents, pos1, pos2 - pos1))
It is this line above which is populating the ComboBox, but that data is NOT being displayed on the form which is shown to the user after the first instance of the form is shown
End If
' Next step in the string
Next pos1
End Sub
Private Sub secondform_formclosing(sender As Object, e As FormClosingEventArgs) Handles SecondForm.FormClosing
There's a few hundred lines of code in here which then work with the data passed from the form closing, ie the selected value of the ComboBox. This all works fine for the first running of the code, but as the ComboBox is empty on subsequent runs, it doesn't work after that. Happy to post that code if anyone thinks it'll help, but I think it'll just muddy the issue at this stage as that code seems fine. However, see the bit below about event handlers...
End Sub
The code on Form2.vb is as follows:
Public Class Form2
Public selectedstrip As String '= ComboBox1.SelectedItem
Public stripfunction As Integer
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If RadioButton1.Checked Then stripfunction = 1
If RadioButton2.Checked Then stripfunction = 2
If RadioButton3.Checked Then stripfunction = 3
selectedstrip = ComboBox1.SelectedItem
Me.Close()
End Sub
End Class
I've read a bit online that says closing and reopening forms is not, excuse the pun, good form. However I'd then need event handlers for form.hide and I can't seem to work out how to use them or even what they are. If hiding the form is a better alternative solution, if someone could point me in the right direction for how to do that and what handlers to use instead, then I'd be grateful.
I'm probably doing something incredibly stupid, because everything I'm doing is self-taught from googling and I probably lack a greater understanding of WHY I need to do certain things, so apologies for any ignorance on my part. With that in mind, if I'm doing anything a completely silly way I'm open to rewriting it in a way that helps, but I may need some hand-holding to do that!
Thanks in advance for any help anyone can give.
The main problem appears to he here:
If SecondForm.IsDisposed Then
Dim secondform As New Form2
You are declaring a new local variable there and assigning the new Form2 object to that rather than the member variable, so when you later refer to the member variable to populate the ComboBox, you're not referring to the Form2 instance you just created.
Your code is rather weird anyway. Here's my advice.
Firstly, get rid of the code form Form1 that populates the ComboBox in Form2. Forms should populate their own controls. Put the code to populate the ComboBox in the Load event handler of Form2. You're then guaranteed that any time you call Show on a new instance of Form2, the code to populate the ComboBox will be executed. That's how a form should work.
As an alternative, given that you're reading from a file and that data probably won't change over the course of a session, read the data and put it into an array in Load event handler of Form1 and then pass that array to the constructor of Form2. You would have to write that constructor yourself and, in it, you would populate the ComboBox with the array data. That way, you're not reading and processing the same data file over and over but you're still populating Form2's controls in Form2.
Secondly, change this code:
If SecondForm.IsDisposed Then
Dim secondform As New Form2
secondform.Show()
InitializeComponent()
Else
SecondForm.Show()
End If
to this:
If SecondForm.IsDisposed Then
'Create and display a new instance.
Secondform = New Form2
Secondform.Show()
Else
'Focus the existing instance.
SecondForm.Activate()
End If
Note that there is no local variable, so the new instance is assigned to the member variable.
There is also no call to InitializeComponent. That method is what creates and configures the controls on a form based on the actions in the designer. The ONLY place that gets called is in a constructor.
Finally, if an instance of Form2 is already displayed, its Activate method is called to make sure that it has focus.
Related
Form Name comes from a variable. I would like to open Form from variable value.
In VBA load("UserFormName") will show the form. But I don't know how to do it in VB.Net.
Ok, of course one would want to be able to open a form by string name.
When you create a vb.net winforms project, then all forms are available as a "base" static class.
You often see a LOT of code thus simply use the base form class.
If I need to display say form2, then I really don't need to create a instance of that form (unless you want to have multiple instances of that form. So a truckload of code will simply launch + use the "base static" class of that form.
eg:
Form2.Show()
I don't consider this all that bad of a practice, since what do you think the project settings to "set" the startup form in the project settings does?
It simply sets the built in instance of "mainForm" = to your startup form and it does NOT create new instance.
So, now that we all can agree for 15+ years anyone who has set the startup form in their project is NOT creating a NEW instance of that form, but in fact using the base class instance. This is really a programming choice.
So, code to display (show) the base static instance of a form by string name will look like this:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim strForm As String = "Form1"
ShowFormByName(strForm)
End Sub
Public Sub ShowFormByName(strFormName As String)
System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(ProductName & "." & strFormName).show()
End Sub
Private Function FormByName(strFormName As String) As Form
Return System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(ProductName & "." & strFormName)
End Function
However, above includes a helper sub that will simply "show" that built in instance of the forms.
And above also includes a function to return class type of the form, since for sure a good many developers prefer to first create a instance of the form, and then "show()" it.
So, often we do want multiple instances, or we just perfer the codeing approach of creating a new instance of the form object.
So, we use the 2nd helper function to return a form object of the type we passed by string.
So, to display 3 instances of form1, but the FIRST instance being the base class, then two more but brand new instances of that form, we have this code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim strForm As String = "Form1"
ShowFormByName(strForm)
Dim f1 As Form = FormByName(strForm)
Dim f2 As Form = FormByName(strForm)
f1.Show()
f2.Show()
End Sub
So the above code snip shows how to display the built in base class form without having to create a instance of that form.
However, the next two forms we load are "new" instances of that form as "string".
So the helper sub, and helper function will give you both choices as to which preference floats your boat.
Dim form = System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(Application.ProductName & "." & MySubForm)
Dim frm As New Form
frm = form
frm.MdiParent = AFrmMainScreen
frm.WindowState = FormWindowState.Maximized
frm.Show()
I prefer to use Reflection.Assembly.GetEntryAssembly because I use several different projects in one solution. This allows me to put this code in a different project(dll) that has a usercontrol that I can then reuse across multiple solutions. You also don't need to know the "Namespace" for the form as long as it is in the startup project.
The code below gets the form type from the exported types from the entry assembly and then uses Activator.CreateInstance to create a new instance of the form. Then I return that form in the function.
Public Function GetForm(ByVal objectName As String) As Form
Try
Dim frmType = Reflection.Assembly.GetEntryAssembly.GetExportedTypes.FirstOrDefault(Function(x) x.Name = objectName)
Dim returnForm = TryCast(Activator.CreateInstance(frmType), Form)
Return TryCast(returnForm, Form)
Catch ex As Exception
Return Nothing
End Try
End Function
To use the above function:
Dim MyForm = GetForm(FormLocation)
If MyForm IsNot Nothing Then
MyForm.ShowDialog()
'You can do any form manipulation from here.
Else
MessageBox.Show($"{FormLocation} was not found.")
End If
I have created 2 forms.
The first one is the button that you want to back up.
In the second there are paths that can be modified.
How to make a reference that after pressing the "backup" button will get a path of 2 forms.
The path is saved when I closed form2
I know how to do it in one form but unfortunately I can not refer to another form.
Source of Form 2:
Private Sub Browser_from1_Click(sender As Object, e As EventArgs) Handles Browser_from1.Click
Dim FolderBrowserDialog1 As New FolderBrowserDialog
FolderBrowserDialog1.ShowDialog()
TextBox1from.Text = FolderBrowserDialog1.SelectedPath
If Browser_from1.Text <> "" And TextBox1from.Text <> "" Then
Backup.StartCopy.Enabled = True
End If
End Sub
Private Sub Browser_to1_Click(sender As Object, e As EventArgs) Handles Browser_to1.Click
Dim FolderBrowserDialog1 As New FolderBrowserDialog
FolderBrowserDialog1.ShowDialog()
TextBox2to.Text = FolderBrowserDialog1.SelectedPath
If Browser_to1.Text <> "" And TextBox2to.Text <> "" Then
Backup.StartCopy.Enabled = True
End If
End Sub
Private Sub TextBox1from_TextChanged(sender As Object, e As EventArgs) Handles TextBox1from.TextChanged
End Sub
Private Sub save_settings_Click(sender As Object, e As EventArgs) Handles save_settings.Click
My.Settings.pathmem = TextBox2to.Text
My.Settings.pathmem1 = TextBox1from.Text
My.Settings.Save()
End Sub
Private Sub setting_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TextBox1from.Text = My.Settings.pathmem1
TextBox2to.Text = My.Settings.pathmem
End Sub
End Class
You dont want to create a reference to a form - that would (or could) create a whole new form. You want to hold onto the form reference.
This is done by passing a reference to the forms, but the talk of one form fiddling with the controls on another form is a bad idea because it breaks encapsulation. But forms are classes (it says so at the top of each one), so you can add Properties and Methods (Sub and/or Functions) to facilitate passing information back and forth.
Method One - Passing a Form Reference
The simplest way is to pass whatever the other form needs in the constructor:
' form 1 / "main" form / form to return to
Dim frm As New Form6(Me)
frm.Show()
Me.Hide()
In order for this to work, you need to modify the constructor (Sub New) on the destination form:
Private frmReturnTo As Form
Public Sub New(f As Form)
' This call is required by the designer.
InitializeComponent()
frmReturnTo = f
End Sub
It is best not to create your own constructor until you are familiar with them. Use the drop downs at the top of the code window: from the left pick the form name; from the right, select New. The designer adds required code to them which must not be changed.
Do not add any code before the InitializeComponent() call at least until you are familiar with the life cycle of a form. The form and its controls do not exist until that runs.
To return to the "main" form:
If frmReturnTo IsNot Nothing Then
frmReturnTo.Show()
End If
You may want to remove some of the title bar buttons or add code to the form Closing event to handle when the user closes via the system menu or buttons.
Using the constructor is ideal for cases where there is some bit of data which the form must have in order to do its job.
Method Two - Passing Data
Thats all well and good, but what about passing data to another form? You can use the constructor for that too. In order to pass say, a string, integer and a Point:
' destination / second form:
Public Sub New(a As String, b As Int32, c As Point)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Label1.Text = a
Label2.Text = b.ToString
Label3.Text = c.ToString
End Sub
Call it like this:
' method two: pass data you want to share in the ctor
Dim frm As New frmData("hello", 6, New Point(150, 550))
frm.Show()
Result:
Method Three: Properties
Thats fine, but if there is a lots of data that way can get cumbersome. Plus, you may want to update some of the data from the calling/main form. For this you can create Properties on the form to handle the data:
Public Property Label1Text As String
Get
Return Me.Label1.Text
End Get
Set(value As String)
Me.Label1.Text = value
End Set
End Property
Rather than a private variable to act as the backing field, one of the controls is used. The name leaves a bit to be desired as it exposes implementation details. So, use names which describe what the data represents rather than where it displays.
Public Property SpecialValue As Integer
Get
Return Integer.Parse(Me.Label2.Text)
End Get
Set(value As Integer)
Me.Label2.Text = value.ToString
End Set
End Property
Public Property SomePoint As Point
Get
Dim data = Me.Label3.Text.Split(","c)
Return New Point(Convert.ToInt32(data(0)),
Convert.ToInt32(data(1))
)
End Get
Set(value As Point)
Me.Label3.Text = value.X.ToString & "," & value.Y.ToString
End Set
End Property
A point was used just to show that other data types can be used. Setting those values from the calling/original/source form:
Using frm As New Form6
frm.Label1Text = "Ziggy"
frm.SpecialValue = 42
frm.SomePoint = New Point(111, 222)
frm.ShowDialog()
' do stuff here with any changes
Dim theint = frm.SpecialValue
End Using ' dispose of dialog
The destination controls would well have been TextBoxes for the user to edit. The Property "wrappers" allow you to fetch those values back, so in this case, a Dialog was used.
Method Four: Methods
You can also use methods as a way to pass data to the second/helper form. Here a List(of T) collection will be passed. In the child/display form a method is added to receive the data which it then displays. The task represented is proofing or viewing a filtered list:
Public Sub UpdateDisplay(lst As List(Of SimpleItem), filter As String)
DataGridView1.DataSource = lst
Label1.Text = String.Format("{0} Total {1} Items", lst.Count, filter)
End Sub
In the main/calling form:
' form level variable
Private frmDV As frmDataView
elsewhere...perhaps in a Click event:
' myList is a simple list of items
' Users pick which color to filter on via a combo box
Dim filter As String
If cboListFilter.SelectedItem IsNot Nothing Then
'Dim frmDV As New frmDataView
If frmDV Is Nothing OrElse frmDV.IsDisposed Then
frmDV = New frmDataView
End If
filter = cboListFilter.SelectedItem.ToString()
' apply the filter
Dim tmpList = myList.Where(Function(w) w.Color = filter).ToList()
frmDV.UpdateDisplay(tmpList, filter)
frmDV.Show()
Else
Return
End If
Result:
With DataBased apps a modified version of this can allow for the case where you display DataGridView data in detail form on another form. You need not have the second form rung SQL to add or update the record, and then the main form running another query to "refresh" the display. If the DataSource is a DataTable backed up by a fully configured DataAdapter, pass the DataTable and have the child form add, change or delete using that. The data will automagically be in the DataTable and DataGridView`.
There are other ways to do this, but they generally all boil down to passing something from A to B. Which way is "best" depends on what the app does, the use-case and the nature of the data. There is no one right way or best way.
For instance, Properties and in many cases Functions allow the B Form to close the feedback loop. With DB items, a DataChanged property might tell the calling form that data was added or changed so that form knows to use the DataAdapter to update the db.
'SECOND FORM
Public class secondForm (blah blah)
Public overloads property owner as myMainForm
'Must be only the form you prepared for that
Private sub secondForm_load(blah blah) handles blah blah
Texbox1.text=Owner.customcontrol.text
End sub
End class
'MAIN FORM
public class myMainForm(blah blah)
Private sub button1_click(blah blah) handles blah blah
Dim NewSecondForm as secondForm = New secondForm
NewSecondForm.owner(me)
NewSecondForm.show(me)
NewSecondForm.dispose()
' so you can have bidirectional communication between the two forms and access all the controls and properties from each other
End sub
End Class
Good Morning all.
I have been working on this forever. but I am down to the very end. I need to get this submitted for may class Sunday.
I have a button that sends my data to the usb drive and I need to send it to a list on a second form. How do I pass this over.
Here is what I have:
Private Sub btnTestResults_Click(sender As Object, e As EventArgs) Handles btnTestResults.Click
' Sends data to the frmresults form three, and creates the "consult.txt file on a
' USB drive for the Nurse can contact the patients.
Dim objWriter As New IO.StreamWriter("e:\consult.txt")
Dim frmThird As New frmElevatedResults
Dim intHighCholesterol As Integer = 200I
' Write the file line by line until the file is completed.
If IO.File.Exists("e:\consult.txt") Then
If _intCholesterolLevel(intCount) < intHighCholesterol Then
objWriter.WriteLine(_strNames(intCount))
objWriter.WriteLine(_intCholesterolLevel(intCount))
Else
MsgBox("The file is not available.
Please restart the program when the file is available", , "Error")
objWriter.Close()
End If
End If
Hide()
frmThird.ShowDialog()
End Sub
Thank you very much
One option would be to make the listbox public on frmElevatedResults:
Add the listbox control via Toolbox to your frmElevatedResults (if not already done).
In the Project-Explorer click on Show all Files.
Open frmElevatedResults.Designer.vb
Find the listbox controle e.g. listBox1 as ListBox
Make it public: public listBox1 as ListBox
(I would have included screens but my VS is not in English).
On your first form you can now use:
Dim frmThird As New frmElevatedResults
...
frmThird.listBox1.Items.Add(string.concat(_strNames(intCount),
" - ",
_intCholesterolLevel(intCount))
...
Hide()
frmThird.Show()
or whatever you want to put in there.
Currently in my windows form, I have a few WinForms to work with. One WinForm acts as a main menu and is supposed to call another form as a secondary window on its own.
Private Sub btnMainGame_Click(sender As Object, e As EventArgs) Handles btnMainGame.Click
' This is the button to call up the main game controller. So simply hide this form aned then open the new form.
Dim frmController As New frmControllerScreen
frmController.Show()
Me.Hide() ' Happens on .Close as well
End Sub
The above code invokes another WinForm which is used to handle more options. When the user clicks on a particular button, a sub form is created again.
Dim OpenNewGameWindow As New frmGameConfig
OpenNewGameWindow.ShowDialog(Me)
Me.DialogResult = DialogResult.None ' Used to prevent the subform from closing the main form when it catches a dialog result.
Now in the frmGameConfig, the program is supposed to take data and pass it back to the form that called it.
Private Sub btnNewGameStartGame_Click(sender As Object, e As EventArgs) Handles btnNewGameStartGame.Click
' ... Skipped code...
frmControllerScreen.MasterQuestionList = QuestionList
frmControllerScreen.blnBankedTime = cbBankedTime.Checked
' ... Skipped code...
End Sub
However, when the frmController tries to reference MasterQuestionList... it returns a nullreference error as if it was not set.
Here's where things get funny...
When I made this code, frmControllerScreen was actually the startup form. Now when I change this form back to frmMainMenu, I get NullReference errors constantly.
My question: How am I supposed to pass information from one form to the next form if it was instantiated from a parent form. (Note I even moved the declartion to Public as a "module-wide" variable... and nothing happens but the same result.) The same error happens even if I go ahead and declare frmController.MasterQuestionList as well.
Instead of trying to pass data back from the called form to the caller, you can reference the called form's controls from the calling code after .ShowDialog.
Dim OpenNewGameWindow As New frmGameConfig
If OpenNewGameWindow.ShowDialog() Then
MasterQuestionList = OpenNewGameWindow.QuestionList
blnBankedTime = OpenNewGameWindow.cbBankedTime.Checked
End If
In OpenGameWindow button click:
Private Sub btnNewGameStartGame_Click(sender As Object, e As EventArgs) Handles btnNewGameStartGame.Click
Me.DialogResult = True
End Sub
I know this has probably been asked 1000 times but I can't get my head around it
I have a text box on a form called 'Settings' that stores a file path and I need to reference that file path in a form called 'Main
I know this should be simple but just cannot get it to work!
Any simple advice
Thanks
As below i need the Dim zMailbox to refer to a textbox value on a separate form (Settings)
Public Class Main
Dim zMailbox As String = "C:\Dropbox\User\Lynx\In\"
Private Sub Main_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim lynxin As New IO.DirectoryInfo(zMailbox)
lstPlanned.Items.Clear()
For Each txtfi In lynxin.GetFiles("*.txt")
lstPlanned.Items.Add(IO.Path.GetFileNameWithoutExtension(txtfi.Name))
Next
End Sub
You should be using something like My.Settings
To do so, you right-click on your project and then click Properties. On the left side, you have a tab called "Settings". You can create a setting there and give it a default value. Ex : MyPath.
Then on your Settings form, you set your value into My.Settings.MyPath.
My.Settings.MyPath = TextboxPath.Text.Trim()
So when you want to access it anywhere in your application after, you can just use :
My.Settings.MyPath