Show single instance of form in other project - vb.net

I have multiproject solution where one project is "main", startup project.
From this project I starting forms in other projects which all are referenced in startup project.
Problem is that I can start those forms as instances like this:
Dim ka As New otherproject.frm_thatform
With ka
.BringToFront()
.Show(Me)
End With
That opens new "thatform" every time but I would like to to start "thatform" like single instance every time and that "thatform" comes to front.
How to do that?
I try like this:
Dim ka As otherproject.frm_thatform
With ka
.BringToFront()
.Show(Me)
End With
... but that don't work (Object reference not set to an instance of an object).

The trick is to not Dim the form and use it as a Shared object.
Otherproject.frm_thatform.Show()
Otherproject.frm_thatform.BringToFront()
As long as you dont close this window you can call it the same way as a Dimmed object.
Instead of ka you just type Otherproject.frm_thatform
As soon as you close the window it will loose everything in it.
EDIT: Apparently this only works with forms inside the project. My bad :(
What you need to do instead is keep a list of Forms inside your main project.
Make sure you give the form a name, and when you click the button to open the form simply loop through the list to find the set name.
Something like this:
Private FormList As New List(Of Form)
Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
Dim theForm As Form = Nothing
For Each f As Form In FormList
If f.Name = "Selected form name" Then
theForm = f
End If
Next
If theForm Is Nothing Then
theForm = New Otherproject.frm_thatform()
theForm.Name = "Selected form name"
FormList.Add(theForm)
End If
End Sub

A couple of options -
create a static/ shared form variable
Shared form As SingletonformEx.Form1
If form IsNot Nothing Then
form.BringToFront()
Else
form = New SingletonformEx.Form1()
form.Show()
End If
Extend the form class and implement singleton pattern on it.
Public Class SingletonForm Inherits Form
Private Shared m_instance As SingletonForm
Private Sub New()
'InitializeComponent();
End Sub
Public Shared ReadOnly Property Instance() As SingletonForm
Get
If m_instance Is Nothing Then
m_instance = New SingletonForm()
End If
m_instance.BringToFront()
Return m_instance
End Get
End Property
End Class
note: this is C# code converted to vb.net so i am not sure if this is perfect

In your original code, move the form declaration out to class (form) level:
Private ka As otherproject.frm_thatform = Nothing
Then check to see if it is Nothing or has been disposed, and recreate it as necessary:
If (ka Is Nothing) OrElse ka.IsDisposed Then
ka = New otherproject.frm_thatform
ka.Show(Me)
End If
If ka.WindowState = FormWindowState.Minimized Then
ka.WindowState = FormWindowState.Normal
End If
ka.BringToFront()

Related

Parent form sometimes does not close (Only in Windows 10)

My main form is frmInvoice. This sub is located inside frmInvoice.
This is one of the Subs that sometimes causes frmDark to not close. frmLookup does not display when this happens. frmDark just stays there covering frmInvoice. It's like it doesn't reach the call to frm.ShowDialog(frmDark), cause when I press the lookup key, it displays the frmLookup, but upon closing frmLookup, frmDark is still there.
No exception is being raised.
Note that this only happens in Windows 10. In Windows 8/7, this never happened. What am I missing?
This happens at different times. Sometimes I could press the lookup key for 20 times and it will display fine. Sometimes, after 1 press of the lookup key and this happens.
Private Sub ItemLookup()
Try
Using frmDark As New Form
With frmDark
.ShowInTaskbar = False
.Icon = Me.Icon
.FormBorderStyle = Windows.Forms.FormBorderStyle.None
.BackColor = Color.Black
.Opacity = 0.95
.WindowState = FormWindowState.Maximized
.Show(Me)
Using frm As New frmLookup
With frm
.Icon = Me.Icon
.ShowDialog(frmDark)
frmDark.Close()
If .DialogResult = Windows.Forms.DialogResult.OK Then
' Do stuff here
End If
End With
End Using
End With
End Using
Catch ex As Exception
ErrMsg(ex)
End Try
End Sub
UPDATE: I'm using .Net Framework 4.8
Thanks
I would suggest rearranging the code like so:
Dim lookupResult As DialogResult
Using frmDark As New Form With {.ShowInTaskbar = False,
.Icon = Me.Icon,
...}
frmDark.Show(Me)
Using frm As New frmLookup With {.Icon = Me.Icon}
lookupResult = frm.ShowDialog(frmDark)
End Using
End Using
If lookupResult = DialogResult.OK Then
'...
End If
Because that code exits the Using block that created frmDark, there should be no way that it can't close.
Also, instead of using a vanilla Form and configuring it on demand, I would suggest that you create a dedicated form type to use as the overlay in that scenario. You can then get rid of all the property assignments.
Having a dedicated overlay form would also allow you to reconfigure things significantly and, in my opinion, better. The overlay form could have a property of type Form. You main form could then create a frmLookup instance and assign it to that property, than call ShowDialog on the overlay form. In the Shown event handler of the overlay form, it could then call ShowDialog on the form in that property. When that call returns, it could assign the result to its own DialogResult property and close itself. The main form would then just get the result from calling ShowDialog on the overlay. That might look like this:
Public Class OverlayForm
Public Property DialogueForm As Form
Private Sub OverlayForm_Shown(sender As Object, e As EventArgs) Handles Me.Shown
DialogResult = DialogueForm.ShowDialog()
End Sub
End Class
and this:
Public Class MainForm
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using dialogue As New DialogueForm,
overlay As New OverlayForm With {.DialogueForm = dialogue}
If overlay.ShowDialog() = DialogResult.OK Then
MessageBox.Show("OK")
End If
End Using
End Sub
End Class

Open Form By Variable Value

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

How to pass a form, object or data to a second form

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

Accessing label on Passed Form

I'm trying to add an activity logger into my application. I'm hoping to keep my code clean and so only want to state the current activity once in my code and then it displays it in lblStatus and updates the log file. I'm trying to do this like this:
I'm passing it like this on my normal form:
LogActivity.LogActivity(Me, "Checking program directories...")
And this is the sub that's doing the work.
Public Sub LogActivity(FormID As Form, ByVal ActivityDescription As String)
'Log Activity code is here
'Update Status Label on form
FormID.lblStatus = ActivityDescription
end sub
But Visual Studio doesn't understand the syntax, I can understand why but I'm not sure how to do this correctly.
'lblStatus' is not a member of 'Form'
However, all my forms will call this sub, so I really need my code to understand which form called the sub and to update the lbl on that form specifically.
I could just check the form's name like this:
If Form.Name = "Main_Loader" Then
Main_Loader.lblStatus = ActivityDescription
elseif Form.Name = "..." then
end if
But again that's not very clean and doesn't seem like the proper way... Could anyone advise?
Assuming the Label is called "lblStatus" on ALL of the Forms, you could simply use Controls.Find() like this:
Public Sub LogActivity(FormID As Form, ByVal ActivityDescription As String)
Dim matches() As Control = FormID.Controls.Find("lblStatus", True)
If matches.Length > 0 AndAlso TypeOf matches(0) Is Label Then
Dim lbl As Label = DirectCast(matches(0), Label)
lbl.Text = ActivityDescription
End If
End Sub
You could use a custom event. The form that needs the info reported back subscribes to the event that is on the form that has the LogActivity.
Public Class frmActivity 'name of class is an example
Private log As New LogClass
Public Event LogActivity(ActivityDescription As String, log As LogClass)
'somewhere in code raise this event and send to info to main form
Private Sub SomeEventSub()
RaiseEvent LogActivity("some status", log)
End Sub
'...
End Class
Public Class frmMain 'name of class is an example
Private Sub btnGetActivity() Handles btnGetActivity.Click
Dim frm As New frmActivity
Addhandler frm.LogActivity, AddressOf LogActivity
frm.Show()
End SUb
Private Sub LogActivity(ActivityDescription As String, log As LogClass)
lblStatus = ActivityDescription
'then use the log variable to store the log data
End Sub
'...
End Class

dynamic database driven menus in VB.Net

I'm trying to construct a database driven VB.Net app that pulls a list of registered accounts from a database and displays the usernames of throes accounts in menu, so the user can select one and a new form open (where they work with it).
what I have so far is the constructor for the MDI parent window
Public Sub New()
InitializeComponent()
Dim tsmi As New ToolStripMenuItem("Users", Nothing, AddressOf users_mousedown)
MenuStrip1.Items.Add(tsmi)
End Sub
The handler for the user menu (where SQLite_db is a class that looks after the database and user_class is a class with two items (username and password) as strings.
Sub users_mousedown()
Dim submenu As New ContextMenuStrip
Dim database As New SQLite_db
Dim user_list As New List(Of user_class)
user_list = database.List_Users
For Each user As user_class In user_list
submenu.Items.Add(user.username, Nothing, AddressOf Open_new_window(user))
Next
submenu.Items.Add("Add new user", Nothing, AddressOf AddNew)
submenu.Show(Control.MousePosition)
End Sub
What I want to happen is when a user clicks on the context menu a new MDI child form is created and the data in user is passed, however because AddressOf doesn't like passing data this doesn't work...
I've looked at delegates and landa expressions but don't think either of them do what I need, the other option is to make my own subclass of the ContextMenuStrip class, which 1) handles the clicks the way I want to and 2) sounds like a nightmare.
Before I launch into what I think will be a hell of a lot of work, am I missing something? is their a simple way to do what I want to do? or if not will sub-classing ContextMenuStrip work and if not any ideas as to what will (and if it will, any ideas as to how to start learning how to do that)
A simple way to encapsulate user info is with a Helper Class, where you store the context info.
Public Class Question1739163
Class HelperUserCall
Public userId As String
Sub New(ByVal id As String)
userId = id
End Sub
Public Sub OnClick()
MsgBox(Me.userId)
End Sub
End Class
Private Sub Question1739163_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim t As New ToolStripMenuItem("Users", Nothing, AddressOf user_mousedown)
MenuStrip1.Items.Add(t)
End Sub
Public Sub user_mousedown()
Dim s As New ContextMenuStrip
Dim a As HelperUserCall
a = New HelperUserCall("u1")
s.Items.Add(a.userId, Nothing, AddressOf a.OnClick)
a = New HelperUserCall("u2")
s.Items.Add(a.userId, Nothing, AddressOf a.OnClick)
s.Items.Add("New User", Nothing, AddressOf add_new)
s.Show(Control.MousePosition)
End Sub
Sub add_new()
MsgBox("add new")
End Sub
End Class
You could improve the helper class adding the reference to database in the constructor, and retrieving the user info when the user click into the option.