VB.NET 2015 -- Have placed StatusStrip control with 2 ToolStripStatusLabel on it in a MDI window. when i open a new form, this would be the Child of the MDI window.
How do i read from info ToolStripStatusLabel.
For Each does not loop through all items in StatusStrip, and from what i have learnt, ToolStripStatusLabel is not a Control as such so the For each loop does not work.
Me.Owner.Controls also does not work to access ToolStripStatusLabel for the same reason as above
What to Do ?
The proper way for any child form to get data from its parent is for the child form to raise an event and the parent form to pass the data back to the child via the event args. This way, the child never has to know anything specific about the parent form so, in theory, many different parent forms could display the same child form and pass it data. This is an example of loose coupling.
The child form would look something like this, providing an event that is raised when it needs data and it gets that data back via the custom EventArgs object that it created:
Public Class Form2
Public Event StatusDataNeeded As EventHandler(Of StatusDataNeededEventArgs)
Protected Overridable Sub OnStatusDataNeeded(e As StatusDataNeededEventArgs)
RaiseEvent StatusDataNeeded(Me, e)
End Sub
Private Sub GetStatusData()
Dim e As New StatusDataNeededEventArgs
OnStatusDataNeeded(e)
MessageBox.Show(e.StatusData)
End Sub
'...
End Class
Public Class StatusDataNeededEventArgs
Public Property StatusData As String
End Class
As you can see, there's no reliance on any specific type of parent form there and there's also no reliance on this form being an MDI child. It simply raises its event and anyone listening can provide the status data, no matter the relationship.
In your case, the MDI parent form might look something like this:
Public Class Form1
'Stores the data that will be displayed in the StatusStrip.
Private statusData As String
'Display the status data in the StatusStrip.
Private Sub SetStatusText()
ToolStripStatusLabel1.Text = statusData
End Sub
'Create and display a child form.
Private Sub DisplayChildForm()
Dim childForm As New Form2 With {.MdiParent = Me}
'Handle the event raised when the child requires the status data.
AddHandler childForm.StatusDataNeeded, AddressOf ChildForm_StatusDataNeeded
childForm.Show()
End Sub
'Pass the status data to the child form.
Private Sub ChildForm_StatusDataNeeded(sender As Object, e As StatusDataNeededEventArgs)
e.StatusData = statusData
End Sub
'...
End Class
As you can see, the status data is stored in its own field. The StatusStrip is for display of status data, not storage. The parent form creates and displays a child form and handles the event. In the event handler, it simply passes the status data to the property of the e parameter.
To learn more about creating your own events, see here.
The Event approach by jmcilhinney is definitely a better way however as a quick solution the My Namespace can be used in VB.Net.
My.Forms.<Form Name>.<Control name>.<property>
eg:
My.Forms.AM_MDI.ToolStripStatusLabel1.Text
For those who would love read more on My Namespace -
Namespaces in VB.NET
https://www.thoughtco.com/namespaces-in-vbnet-3424445
Do you use the 'My' namespace in VB.NET?
Do you use the 'My' namespace in VB.NET?
Related
I would like to set the background color for a certain type of control on all child forms that open. I have an MdiParent form that is used to open the other forms within itself. I don't want to add code to each child form as this would be very extensive. This would be used as a theme feature for the application so I would like to have it automatically change the background colors based on logic in the main form. Is there something like a global event that could trigger for all Form.Load events?
So far I have created an event in the Parent form but it doesn't work for nested controls
Private Sub frmMain_MdiChildActivate(sender As Object, e As EventArgs) Handles Me.MdiChildActivate
Dim ParentControl As frmMain = sender
Dim ChildControl = ParentControl.ActiveControl
If ChildControl IsNot Nothing Then
For Each FormControl As Control In ChildControl.Controls
If FormControl.GetType = GetType(GroupBox) Then
RemoveHandler FormControl.Paint, AddressOf PaintBorderlessGroupbox
AddHandler FormControl.Paint, AddressOf PaintBorderlessGroupbox
End If
Next
End If
End Sub
I was able to accomplish this by using Form.MdiChildActivate and adding the event to the appropriate controls based on the Event and EventHandler.
Private Sub frmMain_MdiChildActivate(sender As Object, e As EventArgs) Handles Me.MdiChildActivate
Dim ParentForm As frmMain = sender
Dim ChildForm = ParentForm.ActiveMdiChild
Dim EventName = "Paint"
Dim EventHandlerName = "PaintBorderlessGroupBox"
If ChildForm IsNot Nothing Then
AddEventToControls(ChildForm, GetType(GroupBox), EventName, EventHandlerName)
End If
End Sub
Private Sub AddEventToControls(Control As Control, ControlType As Type, ControlEventName As String, ControlEventMethod As String)
For Each ChildControl In Control.Controls
If ChildControl.GetType = ControlType Then
If ChildControl.Controls.Count > 0 Then
AddEventToControls(ChildControl, ControlType, ControlEventName, ControlEventMethod)
End If
Dim EventMethod = Me.GetType().GetMethod(ControlEventMethod, BindingFlags.NonPublic Or BindingFlags.Instance)
Dim ControlEvent As EventInfo = ControlType.GetEvent(ControlEventName)
Dim del = [Delegate].CreateDelegate(ControlEvent.EventHandlerType, Me, EventMethod)
ControlEvent.RemoveEventHandler(ChildControl, del)
ControlEvent.AddEventHandler(ChildControl, del)
End If
Next
End Sub
The call to AddEventToControls() assigns the handler to the Control and any child controls that it would also apply to. In this case I am setting the Control.Paint event to paint a GroupBox a specific way. This may not be the cleanest method to accomplish this but I was able to create a "Global Event" for all child forms without ever touching the code on each form.
In your parent form, keep a collection of all Child Forms that have been activated. You can then traverse that collection and change the relevant control property for each one.
For Each ChildForm in MyCollection
ChildForm.TextBox.BackColor = Red
Next
Of course:
The ChildForm control has to be accessible by the parent form
The ChildForm has to still exist (i.e. not been closed in the mean
time)
You can't check for ChildForm closure because you are not adding any
code to the ChildForm to signal such an event.
You have to handle the exceptions when you try to change a form that
has been closed.
Much easier to include a method in your ChildForm design for ChangeColour(), whether that method is fired by event or direct call is your design decision. And to include a method to tell the parent form when a ChildForm dies so that it stops looking for it.
Private Sub tsGradovi_Click(sender As Object, e As EventArgs) Handles tsGradovi.Click
For Each f As Form In Application.OpenForms
If TypeOf f Is frmGradovi Then
f.Activate()
Return
End If
Next
Dim f2 As New frmGradovi
f2.MdiParent = Me
f2.Show()
f2.WindowState = FormWindowState.Maximized
resetdgvGradova()
End Sub
On this way i add the Child form to my main Form.
On that frmGradovi form i have the datagridview. Now i added class to my project.
How can i add the datagridview source from my class.
this code is not helping
frmGradovi.DGV.DataSource = SQLDataset.Tables(0)
Probabbly because frmGradovi is mdi child of form1.
Edit:
At class konekcija i need to set the datasource for the frmGradovi form. But that frmGradovi form is an mdi child form of Form1
One way to avoid these types of conundrums is not to write Form-centric code. They are basically a sandbox for collection user input. The other element is to explicitly instance forms: In your code f2 is an instance of frmGradovi. Trying to reference it as frmGradovi elsewhere risks creating a new default instance of it (you'd later have 2 forms of Type frmGradovi in your Forms collection).
I dont know what a Gradovi or a konekcija is, so I will use a Customer example. My app might have a frmCustomer and a Customer class. When it comes time to display a certain customer, rather than the MDI parent form code or button click creating the form, I'd leave that job to the Customer class:
Public Class Customer
' myFrm is an instance of frmCustomer, which is a Type
Private myFrm As frmCustomer
Private myDT As DataTable
Public Sub Display(Id As Int32)
CustId = Id
If myFrm Is Nothing Then
myFrm = New frmCustomer
' MDI boilerplate code
'...
' one time setup code like populate static CBOs:
'...
End If
UpdateDisplay()
myFrm.BringToFront()
End Sub
Public Sub UpdateDisplay()
' display code when something changes such as show new selected Customer
' e.g.:
LoadCustDataToDataTable(CustId)
With myFrm
.tbfirstName.Text = FirstName
.tbLastName.Text = LastName
' ...etc
.dgvPastOrders.DataSource = myDT
End With
End Sub
The "key" is that the Customer class is in charge of the customer form. It creates it and retains a reference to it. When the user clicks Save that task too would be offloaded to the Customer.Save method.
You'll have other gyrations to add to handle when the user closes that form (if they are allowed to close versus just hiding it). In your current approach, your class could fish the reference to its form from the collection as it needs it.
I was wondering how you would grab some text from a textbox in a selected form. I have a MDI form which contains X number of child forms. Each child form has a textbox in it with some text. When I hit the save button how do I know which form is selected and how do I grab the text from that textbox.
Private Sub Forms_Clicked(sender As Object, e As EventArgs) Handles Forms.clicked
globalVar = sender
End Sub
I was thinking to make an event that just detects when ever a form is clicked and save it's form/form ID in a global variable. Any thoughts?
You have to determine the active child from first and from there, you can check which text box you want to read its content.
Like bodjo said, you have the [YourMDIForm].ActiveMDIChild property.
What is unclear, like Plutonix said is where is your Save "Button". Did you meant :
a Save MenuItem from a MenuStrip on your MDI Parent Form ?
or a Save Button inside an MDI Child Form (or either another Form that is not part of the MDI Parent/Child system)
The first case is pretty straightforward : Use the .ActiveMDIChild of your MDIParent Form.
The secund may need some valid way to point to an actual active child form (similar to the one you tried with your globalVar... there are better ways to do it.
By the way, when you get your targeted MDIChild through .ActiveMDIChild, you must access the textbox with a Public, Friend or Protected variable. Usually, controls in forms are private. So you may have to :
Public Class [YourMDIChildClassName]
' ...
' create a Public Property
Public ReadOnly Property ContentToSave() As String
Get
Return [YourTextBox].Text
End Get
End Property
' or make your textbox public in the Form design
Public [YourTextBox] As TextBox
' ... the one or the other, not both.
' ...
End Class
Another way to access the "active" MDIChild, assuming all of your MDIChild are instances of the same class is to create a static (shared) property in your Child class :
Public Class [YourMDIChildClassName]
' ...
Private Shared _CurrentMDIChild As [YourMDIChildClassName] = Nothing
Public Shared ReadOnly Property CurrentMDIChild() As [YourMDIChildClassName]
Get
Return _CurrentMDIChild
End Get
End Property
' ...
End Class
And using the same thing you tried, but using .Activated instead of .Clicked
Public Class [YourMDIChildClassName]
' ...
Private Sub MyChildForm_Activated() Handles Me.Activated
_CurrenMDIChild = Me
End Sub
' And that's ALL this method SHOULD contain.
' If you try to add other activation tricks, like activating another Form,
' or firing up a DialogBox (worst case),
' everything will go WRONG and your application will hang in an endless loop !!!
' Be carefull when using .Activated.
' ...
End Class
Then you could access the currently active MDIChild using the static Property :
[YourMDIChildClassName].CurrentMDIChild
' => Gives you directly a VALID instance of your MDI Child (or Nothing !)
' Then you can use in your MDI Parent :
If [YourMDIChildClassName].CurrentMDIChild IsNot Nothing Then
Dim TextToSave As String = [YourMDIChildClassName].CurrentMDIChild.ContentToSave
' ... save your text...
End If
HOWEVER, because you've created a static (shared) pointer to the last active MDIChild (sort of, I know it's not a pointer like in C) you must update this pointer whenever you close an MDIChild Form !
Public Class [YourMDIChildClassName]
' ...
Private Sub [YourMDIChildClassName]_FormClosing( _
sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
If _CurrentMDIChild Is Me Then ' And "Me" is closing right now...
_CurrentMDIChild = Nothing
' We don't want memory leak or pointer to a Form that has been closed !
End If
End Sub
' ...
End Class
We are not supposed to create custom way of handling Child Form activation. MDIParent.ActiveMDIChild is there for that. However, once we want to access extended/custom/specific Properties of the Child Form that doesn't initially exists in System.Windows.Forms.Form, we need to cast the MDIParent.ActiveMDIChild to the real Form derived Type of our MDIChild. That's one other way to do it, but it's just me : I don't like castings much. Always set IDE to :
Option Explicit On
Option Strict On
Option Infer Off
Forms.Clicked ... does this event (Clicked) really exist ? I'm aware of .Click but not "Clicked". Anyway :
Public Class [YourMDIChildClassName]
' ...
Private Sub MyChildForm_Clicked() Handles Me.Click
' This part is executed when you click INSIDE the Form,
' at a location where there is NO Control.
' If you click on a control, like a Textbox, a Picturebox, a Panel, etc
' this block IS NOT EXECUTED as you've clicked on a control,
' not on the Form !
End Sub
' ...
End Class
I have an MDI application where the MDI form is required to store a bindingsource and dataset. The child forms are "detail forms" in that they should allow the user to alter the properties of each object instance in the parent MDI form dataset.
I have tried to go about it as follows:
Pass the bindingsource object from the MDI parent into the constructor for the new child form. However I get the following error
"An exception of type 'System.ArgumentException' occurred in System.Windows.Forms.dll but was not handled in user code
Additional information: Cannot bind to the property or column Z1 on the DataSource."
Z1 is a public property of my "Pipe" Class which is one object I have created a datasource from in visual studio wizard.
My code for the MDI form 'Main'
'This is the open child form button click handler
Private Sub ToolStripMenuItem2_Click(sender As System.Object, e As System.EventArgs) Handles ToolStripMenuItem2.Click
Dim p As New PipeForm(Me.PipeBindingSource)
Me.Show(p)
End Sub
'This is the overloaded show helper method for the MDI form
Private Overloads Sub Show(f As Form)
For Each testForm As Form In Application.OpenForms
If testForm.GetType().Equals(f.GetType()) Then
f.Activate()
Return
End If
Next
f.MdiParent = Me
f.Show()
End Sub
And my code for the Child form 'PipeForm'
'constructor
Public Sub New(bs As BindingSource)
' This call is required by the designer.
InitializeComponent()
Me.PipeBindingSource = bs
' Add any initialization after the InitializeComponent() call.
End Sub
I can have separate PipeBindingSource objects on each form and work with them through textbox inputs with no issues. The problem arises when i try to set the PipeBindingSource from the parent to the child as equal.
I have two forms, form 1 and form 2 (windows application). How can i access and check a checkbox in form 1 from form 2. Initially i tried calling the form name and then the control like form1.chkCanada.checked = true, it did not work. And then i added a property in form 1
Public Property abc As Boolean
Get
Return chkCanadianStmtInd.Checked
End Get
Set(value As Boolean)
chkCanadianStmtInd.Checked = value
End Set
End Property
and then in form 2
Dim frm As New frm1
frm.abc = True 'Checked
And it still doesnt work. Am i missing anything here?
Alternatively you can pass a handle of form1 to form2 constructor
Form1:
Public Class Form1
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim _form2 As New Form2(Me)
_form2.Show()
End Sub
End Class
Form2:
Public Class Form2
Public Sub New(ByVal _form1 As Form1)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_form1.CheckBox1.Checked = True
End Sub
End Class
Consolidating comments here:
In order to access controls on a form that shows another form to the user you have two options, if no interaction is needed with the first form while the second form is active you can use showdialog and do all of your logic after the second form has closed, if you ned to maintain the ability to interact with the first form while the second is still open then you need to use custom events.
Showdialog:
The simpler of the two options is to switch your form.show() function calls to form.showdialog(). This effectively tells the first form that it should stop processing at the form.showdialog() line and wait for the child form to close before proceeding. Once the second form is closed the first form will pick up where it left off and that would be where any processing that relies on the values of the second form would take place.
Custom Events:
If you want to allow the user to interact with both the first and second forms at the same time then you will need to use custom events. In order to do this you will need three things. The custom event, a raiseevent call and an event handler.
So in your Form2 class you will need to declare the custom event. In this case since you are trying to check(or uncheck I assume) a box your custom event declaration will look like:
public event ChangeCheckedValue(byref state as boolean)
Now on your button click event you will need to raise the event to the handler on Form1:
RaiseEvent ChangeCheckedValue(booleanValue)
Now that those statements are in place you will need to changed your form2 object that is being shown by Form1. What I normally do is make Form2 a form wide variable on Form1 and declare it like:
private withevents frm as Form2
Once you have the frm variable in your Form1 class you can add a handler for the ChangeCheckedValue event:
protected sub HandleCheckChanged(byref bln as boolean) handles frm.ChangeCheckedValue
'Set the checked state of your checkbox.
End sub
Once you have all that set up you should see what you expect.