How to notice any change on a form - vb.net

I have a form with many different radiobuttons, checkboxes and textboxes. Depending on their values I start my calculations. The results are shown on the same form and panel. If any of my controls (checkboxes, ...) changes, I want to immediatly update the results without a need to press any update-button.
I could define a statsChanged-sub for every single control on the form but there are so many. Isn't there a way/event of the form starting whenever a control is changed? It should be something like controlOnFormChanged. How can I get a sub that starts whenever a any control on the form changed?
Thank you in advance!

You could wire up the events that correspond to the desired change manually to a specific event handler:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Iterate through all controls and handle them according to their type
For Each c As Control In Me.Controls
If TypeOf (c) Is CheckBox Then
AddHandler CType(c, CheckBox).CheckedChanged, AddressOf SomethingChanged
ElseIf TypeOf (c) Is RadioButton Then
AddHandler CType(c, RadioButton).CheckedChanged, AddressOf SomethingChanged
ElseIf TypeOf (c) Is TextBox Then
AddHandler CType(c, TextBox).TextChanged, AddressOf SomethingChanged
ElseIf ......
......
End If
Next
End Sub
Private Sub SomethingChanged(sender As Object, e As EventArgs)
'Whatever it is you do
End Sub
End Class
Whenever one of the events on a control fires the sub SomethingChanged is called, allowing you to update your results.
Please be aware: If you have controls in subcontainers like Panels you need to modify this method and iteratively get all controls in all containers.
Here is, for example, a solution to this:
http://kon-phum.com/tutors/pascal/programming_cs_getcontrolsonform.html
Public Shared Function GetAllControls(ctrls As IList) As List(Of Control)
Dim RetCtrls As New List(Of Control)()
For Each ctl As Control In ctrls
RetCtrls.Add(ctl)
Dim SubCtrls As List(Of Control) = GetAllControls(ctl.Controls)
RetCtrls.AddRange(SubCtrls)
Next
Return RetCtrls
End Function

Related

How to set an event for MDI child forms without adding code to each form?

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.

VB .Net Can i manage all events from inside a groupbox in an efficient way?

I have a groupbox containing a lot of Checkboxes - only Checkboxes.
Is there a simple/fast way to handle the same event coming from different controls?
I know I can write a single sub and let it handle all the Events, but it's really time-consuming to write.
Using Visual Studio 2012
If your time concern is about writing and managing a large Handles clause, you can simply loop through the GroupBox's Controls collection upon construction of your UserControl/Form and wire up each event on each CheckBox, like so:
Imports System.Linq
Public Sub New()
InitializeComponent()
For Each chkBox As CheckBox In yourGroupBoxVariable.Controls.OfType(Of CheckBox)()
AddHandler chkBox.CheckStateChanged, AddressOf YourCheckStateChangedHandlerMethod
Next
End Sub
Private Sub YourCheckStateChangedHandlerMethod(ByVal sender As Object, ByVal e As EventArgs)
' Your handler code for the checkboxes
End Sub
This leverages LINQ's OfType Enumerable extension to filter down all child controls of the GroupBox to those of type CheckBox.
Another possibility is to create a custom GroupBox, i.e. deriving from GroupBox and exposing the event yourself:
Public Class CheckboxGroup
Inherits GroupBox
Public Event CheckboxChanged(source As CheckBox, e As EventArgs)
Protected Overrides Sub OnControlAdded(e As ControlEventArgs)
' this method is called everytime a checkbox is added
If TypeOf e.Control Is CheckBox Then
Dim chk As CheckBox = DirectCast(e.Control, CheckBox)
AddHandler chk.CheckedChanged, AddressOf AllCheckedChange
End If
End Sub
Private Sub AllCheckedChange(source As Object, e As EventArgs)
If TypeOf source Is CheckBox Then
Dim chk As CheckBox = DirectCast(source, CheckBox)
RaiseEvent CheckboxChanged(chk, e)
End If
End Sub
End Class
You can then attach to the event in the Form like:
Private Sub CheckboxChanged(source As CheckBox, e As EventArgs) Handles gb.CheckboxChanged
MsgBox(source.Text & " to " & source.Checked)
End Sub
Advantage: you can never miss to add an event handler to a CheckBox, even if it is created dynamically.

Raising events from a List(Of T) in VB.NET

I've ported a large VB6 to VB.NET project and while it will compile correctly, I've had to comment out most of the event handlers as to get around there being no array collection for winform objects and so putting the various objects that were in at the collection array into a List object.
For example, in VB6 you could have an array of Buttons. In my code I've got
Dim WithEvents cmdButtons As New List(Of Button)
(and in the Load event, the List is propagated)
Obviously, you can't fire an event on a container. Is there though a way to fire the events from the contents of the container (which will have different names)?
In the Button creation code, the event name is there, but from what I understand the handler won't intercept as the Handles part of the code is not there (commented out).
I'm not exactly sure what you're after, but if you want to be able to add event handlers to some buttons in a container and also have those buttons referenced in a List, you can do something like
Public Class Form1
Dim myButtons As List(Of Button)
Private Sub AddButtonsToList(targetContainer As Control)
myButtons = New List(Of Button)
For Each c In targetContainer.Controls
If TypeOf c Is Button Then
Dim bn = DirectCast(c, Button)
AddHandler bn.Click, AddressOf SomeButton_Click
myButtons.Add(bn)
End If
Next
End Sub
Private Sub SomeButton_Click(sender As Object, e As EventArgs)
Dim bn = DirectCast(sender, Button)
MsgBox("You clicked " & bn.Name)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' GroupBox1 has some Buttons in it
AddButtonsToList(GroupBox1)
End Sub
End Class

Handling all textbox event in one Handler

I do know how to handle event of textboxes in my form. But want to make this code shorter because I will 30 textboxes. It's inefficient to use this:
Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged, TextBox2.TextChanged, TextBox3.TextChanged, TextBox4.TextChanged, TextBox5.TextChanged, TextBox6.TextChanged, TextBox7.TextChanged, TextBox8.TextChanged, TextBox9.TextChanged, TextBox10.TextChanged
Dim tb As TextBox = CType(sender, TextBox)
Select Case tb.Name
Case "TextBox1"
MsgBox(tb.Text)
Case "TextBox2"
MsgBox(tb.Text)
End Select
End Sub
Is there a way to shorten the handler?
You can use Controls.OfType + AddHandler programmatically. For example:
Dim textBoxes = Me.Controls.OfType(Of TextBox)()
For Each txt In textBoxes
AddHandler txt.TextChanged, AddressOf txtTextChanged
Next
one handler for all:
Private Sub txtTextChanged(sender As Object, e As EventArgs)
Dim txt = DirectCast(sender, TextBox)
Select Case txt.Name
Case "TextBox1"
MsgBox(txt.Text)
Case "TextBox2"
MsgBox(txt.Text)
End Select
End Sub
If you have created very Textbox with the Designer, I don't think there is a better method.
But, if you have created the Textboxes dynamically, you should AddHandler in this way:
For i = 0 to 30
Dim TB as New Texbox
AddHandler TB.TextChanged, TextBox1_TextChanged
'Set every Property that you need
Me.Controls.Add(TB)
Next
Say If you are having that 30 textboxes inside a panel(PnlTextBoxes), Now you can create handler for your textboxes dynamically like this below
For each ctrl in PnlTextBoxes.controls
If TypeOf ctrl is TextBox then
AddHandler ctrl.TextChanged, AddressOf CommonClickHandler
end if
Next
Private Sub CommonHandler(ByVal sender As System.Object, _
ByVal e As System.EventArgs)
MsgBox(ctype(sender,TextBox).Text)
End Sub
The best way would be to inherit from TextBox, override its OnTextChanged method to add your custom handling code, and then use that on your form(s) instead of the built-in TextBox control.
That way, all of the event handling code is in one single place and you increase abstraction. The behavior follows and is defined within the control class itself, not the form that contains the control. And of course, it frees you from having a bunch of ugly, hard-to-maintain Handles statements, or worse, slow and even uglier For loops.
For example, add this code defining a new custom text box control to a new file in your project:
Public Class CustomTextBox : Inherits TextBox
Protected Overridable Sub OnTextChanged(e As EventArgs)
' Do whatever you want to do here...
MsgBox(Me.Text)
' Call the base class implementation for default behavior.
' (If you don't call this, the TextChanged event will never be raised!)
MyBase.OnTextChanged(e)
End Sub
End Class
Then, after you recompile, you should be able to replace your existing TextBox controls with the newly-defined CustomTextBox control that has all of your behavior built in.

EditingControlShowing events firing multiple times

I have a DGV in VB.Net 2008 connected to an Access DB table. The DGV is not Read Only, but is full of read-only columns except for one, which contains a combo box. The combo box allows the user to select an outcome for that particular row, and then the program copies in a pre calculated value into the "Profit" column depending upon the item selected in the combobox. Then the user hits the Save button and the DB updates (currently via SQL methods in the XSD).
Easy enough so far.
Here is the code.
Private Sub DGUserBets_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles DGUserBets.EditingControlShowing
Dim combo As ComboBox = CType(e.Control, ComboBox)
If (combo IsNot Nothing) Then
// Remove an existing event-handler, if present, to avoid
// adding multiple handlers when the editing control is reused.
RemoveHandler combo.SelectedIndexChanged, _
New EventHandler(AddressOf DGUBStake_SelectedIndexChanged)
// Add the event handler.
AddHandler combo.SelectedIndexChanged, _
New EventHandler(AddressOf DGUBStake_SelectedIndexChanged)
End If
End Sub
Private Sub DGUBStake_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs)
Dim myStatus As ComboBox = CType(sender, ComboBox)
Dim row = DGUserBets.CurrentRow
Select Case myStatus.SelectedIndex
Case 0
row.Cells("DGUBProfit").Value = 0
// pending. no action
Case 1
row.Cells("DGUBProfit").Value = row.Cells("DGUBIfWin").Value
// win
Case 2
// loses
row.Cells("DGUBProfit").Value = row.Cells("DGUBIfLose").Value
Case 3
// void
row.Cells("DGUBProfit").Value = 0
End Select
End Sub
The problem I have is that it would seem that if a user selects the desired outcome from the combobox but does NOT hit Enter, and simply mouses on to a different combobox to again select the outcome for a different row, the first eventhandler is not disconnected and thus the events fire multiple times. This then causes various default MsgBox errors and brings up problems when the user tries to commit all changes to the DB/exit program etc etc.
What do I need to do? Do I need to .EndEdit somewhere appropriate to force the row to save the changes? And where should I call this?
Thank you.
A quick glance at the code brings up this question:
If you create a new EventHandler when removing the existing one is it the same one?
I have had a similar issue, add a handler for CellLeave if the cell being exited is the cell you are looking for (IE e.ColumnIndex = myEditableColumn.Index) then call gv.EndEdit()
Also I would recommend making the handlers member variables for assignment and removal because it seems nicer then always saying Remove New and Add New.
CKRet/Quintin , thank you for the fast responses.
A quick try with this code seems better and breakpointing and stepping through the code seems to be firing the events correctly. I'm fairly new to .NET as the last real VB programming I did was VB6 so I'm not sure if this is the most elegant way to solve the problem.
Also a note that when LastEventHandler = Nothing, calling the RemoveHandler does not throw an exception, which is quite nice.
Maybe I should suggest to MS they should update that article.
Private Sub DGUserBets_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles DGUserBets.EditingControlShowing
Dim combo As ComboBox = CType(e.Control, ComboBox)
Static LastEventHandler As EventHandler
If (combo IsNot Nothing) Then
// Remove an existing event-handler, if present, to avoid
// adding multiple handlers when the editing control is reused.
RemoveHandler combo.SelectedIndexChanged, _
LastEventHandler
LastEventHandler = New EventHandler(AddressOf DGUBStake_SelectedIndexChanged)
// Add the event handler.
AddHandler combo.SelectedIndexChanged, _
LastEventHandler
End If
End Sub
Simpler code which also appears to work well, as suggested by CKRet:
Dim combo As ComboBox = CType(e.Control, ComboBox)
If (combo IsNot Nothing) Then
RemoveHandler combo.SelectedIndexChanged, AddressOf DGUBStake_SelectedIndexChanged
AddHandler combo.SelectedIndexChanged, AddressOf DGUBStake_SelectedIndexChanged
End If
I know this is an archaic post, but after buggering around with this same problem for half a day I've found a way to solve this another way, so I thought it would be worth sharing.
Adding a second handler to handle the leave event of the combobox which then removes the handler of selectedvalue changed. Appears to work quite slick, and unlike another option i found gives the desired resulting action (unlike removing the value changed handler on the actual handling event which then won't fire if you re-select from the same combobox)
Private LastEventHandler As EventHandler = AddressOf Me.ComboBoxValueChanged
Private Sub dgvThisDatagrid_EditingControlShowing(sender As Object, e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles dgvOutstandingReminders.EditingControlShowing
If TypeOf (e.Control) Is ComboBox Then
Dim cboThisComboBox = DirectCast(e.Control, ComboBox)
AddHandler cboThisComboBox.SelectedValueChanged, LastEventHandler
AddHandler cboThisComboBox.Leave, AddressOf RemoveValueChangedHandler
End If
End Sub
Private Sub ComboBoxValueChanged(ByVal sender As Object, ByVal e As System.EventArgs)
If TypeOf (sender) Is ComboBox Then
Dim cboThisComboBox = DirectCast(sender, ComboBox)
MessageBox.Show("Value = " & cboThisComboBox.SelectedValue.ToString() & Environment.NewLine & "Text = " & cboThisComboBox.Text) ' Display index
End If
End Sub
Private Sub RemoveValueChangedHandler(ByVal sender As Object, ByVal e As System.EventArgs)
If TypeOf (sender) Is ComboBox Then
Dim cboThisCombobox = DirectCast(sender, ComboBox)
RemoveHandler cboThisCombobox.SelectedValueChanged, LastEventHandler
End If
End Sub