VB: How to manually call or delay events? - vb.net

Background: I'm new to vb, coming from javascript.
My assignment is to use the txtBox.LostFocus event to do some validation. The problem is I need to cancel the validation step if the user intends to press either two of three buttons.
Illustration:
Code
Dim lblInputBox As Label
lblInputBox.Text = "Name:"
Dim txtInputBox As TextBox
Dim btnClear As Button
btnClear.Text = "Clear"
Dim btnSayName As Button
btnSayName.Text = "Say Name"
Dim btnExit As Button
btnExit.Text = "Exit"
' Some boolean to determine what the next action is
Dim UserIntentDetected = False
' When the user moves focus away from the textbox
Private Sub txtInputBox_LostFocus(sender As Object, e As EventArgs) _
Handles txtIputBox.LostFocus
' I want to be able to detect the next focus, but this is where I'm going wrong
If btnExit.GotFocus Or btnClear.GotFocus Then
UserIntentDetected = True
Else
UserIntentDetected = False
End If
' Only call validate if there is no intent to quit or clear
If Not UserIntentDetected Then
Call validate()
End If
' Reset the intent boolean
UserIntentDetected = False
End Sub
' Validate subroutine
Private Sub validate()
' **Fixed description**
' User moved focus away from txtbox and doesn't intend to clear or exit
Console.WriteLine("You're NOT INTENDING to clear or exit")
End Sub
I've tried to add the two button's GotFocus event to the input box's LostFocus event handler, but there were bugs with the event firing multiple times in a loop.
Eg:
Private Sub txtInputBox_LostFocus(sender As Object, e As EventArgs) _
Handles txtIputBox.LostFocus, btnExit.GotFocus, _
btnClear.GotFocus
... (code follows)
These attempts are entirely wrong, but from a javascript background, although also entirely wrong, I could hack something like this..
... (assume same element structure from vb)
var eventQueue = [];
var userIntentDetected = false;
txtInputBox.addEventListener("blur", function(event){
// Set a timeout to manually trigger the last event's click method in the eventQueue
setTimeout(function(){
if (eventQueue.length > 0 && userIntetDetected)
runIntendedHandler(eventQueue[eventQueue.length -1]);
}, 500);
})
// Both event listeners listen for click and stop default actions,
// set intent boolean to true, and add it's event object to a queue
btnExit.addEventListener("click", function(event){
event.preventDefault();
userIntentDetected = true;
eventQueue.push(event);
});
btn.addEventListener("click", function(event){
event.preventDefault();
userIntentDetected = true;
eventQueue.push(event);
});
// Validation should occur only if the user moves focus to an element
// that IS NOT either the btnExit or btnClear
function runIntendedHandler(event){
if (event.target.id = "btnExit")
// run exit functions
code...
else if (event.target.id = "btnClear")
// run clear functions
code..
userIntentDetected = false;
}
What is the proper way to work with events in vb and how would I go about detecting the next event in the queue before triggering an action? could the RaiseEvent statement help?
UPDATE 3: The answer was a lot easier than I made it seem. Apparently, you can use the btn.Focused property to check the next focus of an element from within the txtInputBox.LostFocus event handler... Go figure!
UPDATE 2: There's been a lot of confusion as to what exactly was needed, and a lot of that was my fault in describing the validation subroutine. I've changed some of the element names and added an image to sum up all of the information that was given to me by my instructor.
UPDATE 1: #TnTinMn has provided the closest working answer that can be used with a minor alteration.
Example follows:
Private LastActiveControl As Control = Me ' initialize on form creation
Protected Overrides Sub UpdateDefaultButton()
' Just added an IsNot condition to stay inline with the requirements
If (LastActiveControl Is txtNumberOfDrinks) AndAlso
((ActiveControl Is btnClear) OrElse (ActiveControl Is btnExit)) Then
Console.WriteLine("Your intent to press either btnClear or btnExit has been heard...")
' Validation happens only if the user didn't intend to click btnClear or btnExit
ElseIf (LastActiveControl Is txtNumberOfDrinks) AndAlso
((ActiveControl IsNot btnClear) OrElse (ActiveControl IsNot btnExit)) Then
Console.WriteLine("You didn't press either btnClear or btnExit.. moving to validation")
validateForm()
End If
LastActiveControl = ActiveControl ' Store this for the next time Focus changes
End Sub
Thank you all!

Winform's event order is confusing at best. For a summary, see Order of Events in Windows Forms.
Assuming I have interpreted your goal correctly, I would not respond to the txtInputBox.LostFocus event but rather override a little known Form method called UpdateDefaultButton. This method is called when the Form.ActiveControl property changes and effectively gives you an ActiveControl changed pseudo-event. If the newly ActiveControl is a Button, this method also executes before the Button.Click event is raised.
Since you want to call your validation code only when the focus changes from txtInputBox to either btnPromptForName or btnExit, you can accomplish that with something like this.
Public Class Form1
Private LastActiveControl As Control = Me ' initialize on form creation
Protected Overrides Sub UpdateDefaultButton()
If (LastActiveControl Is tbInputBox) AndAlso
((ActiveControl Is btnPromptForName) OrElse (ActiveControl Is btnExit)) Then
ValidateInput()
End If
LastActiveControl = ActiveControl ' Store this for the next time Focus changes
End Sub
Private Sub ValidateInput()
Console.WriteLine("You're either intending to prompt for name or exit")
End Sub
Private Sub btnPromptForName_Click(sender As Object, e As EventArgs) Handles btnPromptForName.Click
Console.WriteLine("btnPromptForName_Click clicked")
End Sub
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnExit.Click
Console.WriteLine("btnExit clicked")
End Sub
End Class
Edit: I forgot to mention, that since UpdateDefaultButton runs before the Button.Click event is raised, it is possible wire-up the click event handler only if validation succeeds. You would then remove the event handler as part of the Button.Click handler code.

It looks like the solution was a lot easier than I thought. The intended (instructor's) way to do this is by checking for alternate "btn.Focused" properties within the txtInputBox.LostFocus event handler eg:
' When the user moves focus away from the textbox
Private Sub txtInputBox_LostFocus(sender As Object, e As EventArgs) _
Handles txtIputBox.LostFocus
' Check if the clear or exit button is focused before validating
' Validate only if btnClear and also btnExit IS NOT focused
If Not btnExit.Focused AndAlso Not btnClear.Focused Then
Call validateForm()
End If
End Sub
Thank you #TnTinMn for the UpdateDefaultButton and showing me an alternate way to track events. This was very helpful too.

Related

Radio buttons fire in VB net at form load

I have a simple VB net form with two radio buttons in a group box and button outside the group box that calls another form that sets up other parameters. The radio buttons both send data over the serial port.
Form1_load event has a boolean value form_loading = True. This is checked in the rbtn handler and if true should exit the subroutine. On debug, the check changed even fires one button's event that is checked at design time and at this point the form_loading value is set to false and I have no idea why. There is no form_loading = false statement. If I remove the rbtn handler, the form_loading = True persists when the other form is called and returned. The groupbox with buttons is activated as it sees a rbtn1-CheckChanged when the form loads and form_loading value get set to false. I suspect that the rbtn event is firing as the form begins to load, before the form_loading = True statement is reached, but how do I stop it firing the button event?
As its stands, when debug start, there is an IO exception error:
Serialport is closed, and the code associated with the button is in
the buffer to send to a (closed) com port
Private Sub rbtnDon_CheckedChanged(sender As Object, e As EventArgs) Handles rbtnDon.CheckedChanged
If form_loading Then
Exit Sub
ElseIf rbtnDoff.Checked = True Then 'event fires when other button checkchanged = true, this stops it
Exit Sub
Else
data_out = (SOT + "N" + EOT)
SendtoBoard(data_out)
End If
End Sub`enter code here`
Thanks for your help Everyone. Elsewhere I found this tip: Remove the "handles rbtnDon checkchanged" from the sub:
Private Sub rbtnDon_CheckedChanged(sender As Object, e As EventArgs)handles rbtnDon checkchanged
and add this to the form1_load sub:
AddHandler rbtnDon.CheckedChanged, New EventHandler(AddressOf rbtnDon_CheckedChanged)
That seems to have solved the problem completely.
I could not add the startup code on my original post as for some reason the box will not accept two lots of separate code - or I am not doing it right:
It is here:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For i As Integer = 0 To My.Computer.Ports.SerialPortNames.Count - 1
ccbComPort.Items.Add(My.Computer.Ports.SerialPortNames(i))
Next
If ccbComPort.Items Is Nothing Then
lblMessage.ForeColor = Color.Red
lblMessage.Text = "No Serial Ports found"
Else
ccbComPort.Text = My.Settings.oldPort
End If
ccbBaudrate.Text = My.Settings.oldBaud
pnlComPorts.Visible = True
'form_loading = True
rbtnDon.Enabled = True
AddHandler rbtnDon.CheckedChanged, New EventHandler(AddressOf rbtnDon_CheckedChanged)
End Sub
Originally I had one form with a panel for the Com port setup and everything was fine, the issue only started when I moved the port set up to form2, after which the rbtnDon event fired before form1 started.
I did try the Sub New() approach but then I just get a small blank form on debug. Adding Form1_load to it results in "NotImplemented Exception".
The method described above seems OK and fairly simple to add but maybe it is not good practice?

Add logic inside button click inside a custom control

I am making a custom control that looks like this (there is a label on the right of the button):
I want the user to be able to define what the button does in his code, but still execute some code on the click event on top of the user's code.
What I want to execute :
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.Button1.Enabled = False
Label1.ForeColor = Color.Gray
Label1.Text = "In Progress"
End Sub
then after this is executed the user's on click event would trigger.
How can I achieve this?
With "the user" you mean a developer which uses that user control, right? Well, the most common thing is to raise an event which the next developer can use to implement his own logic. This is exactly the same as you do - you use the Click-Event of the button.
So basically, take your code and add the RaiseEvent below:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.Button1.Enabled = False
Label1.ForeColor = Color.Gray
Label1.Text = "In Progress"
' this does not affect your code but provides a "hook" for
' other developers
RaiseEvent OnButtonClick(Button1)
End Sub
Now you need to define the event itself like this ...
Public Event OnButtonClick(ByVal sender As Control)
... btw, you can pass other stuff (or nothing at all) as arguments. Sending the button as sender is just a habit.
A developer using your user control can attach a so called "Handler" to implement code as soon as the button was clicked, for example:
AddHandler UserControl1.OnButtonClick, AddressOf OnUserControlButtonClick
This code line should only be executed once, so typically it is placed in the Form_Load event.
Now, in this case the button click is routed to a method called OnUserControlButtonClick() which meets the signature of the event: that means it has one argument which is the sender.
Private Sub OnUserControlButtonClick(ByVal sender as Control)
' custom logic here ...
End Sub
There are so many examples on the web, you could start here.

Adding a User Control dynamically to a FlowLayoutPanel

I've created a UserControl and added it to a FlowLayoutPanel. The UserControl is being used to allow a user to enter a material, cost and delivery status to the form. When the user has filled it in, I want another UserControl to appear underneath it in the FlowLayoutPanel
The UserControl simply generates a string based on the text entered into two TextBox controls and the status of two Checkbox controls. it also has a property that indicates when the user has filled in enough information. I want to use this property to signal generating a new UserControl.
At the moment I have my first UserControl on the FlowLayoutPanel, it is successfully passing the String and CreateNew property back.
The problems I am encountering are:
How do I monitor to see if CreateNew has changed to True?
How do I add a control to the form and +1 to the controls name for future referencing
Once the new control is added, I need to monitor to see if the new CreateNew state changes to repeat the cycle
Can anyone point me in the right direction here, there's a lot of information out there on this, but I can't seem to find anything useful from other's problems/questions.
UPDATE 1
Thanks to user Zaggler for the comment, I have now managed to get the control to create a new instance of itself on the FlowLayoutPanel. But now I'm faced with a new problem of it only creating one new usercontrol, then it stops.
Here's the code I'm using:
UserControl Code
Public Class Alv_Product_Order_Control
Public OutString As String
Public Event CreateNew()
Dim CreateNewRaised As Boolean
Private Sub OutputString(sender As Object, e As EventArgs) Handles tbMaterial.TextChanged, tbCost.TextChanged,
cbDelivered.CheckedChanged, cbOrderPlaced.CheckedChanged
OutString = "¦¦" & tbMaterial.Text & "¦" & tbCost.Text & "¦"
If cbOrderPlaced.Checked = True Then
OutString = OutString & "Yes¦"
Else
OutString = OutString & "No¦"
End If
If cbDelivered.Checked = True Then
OutString = OutString & "Yes¦"
Else
OutString = OutString & "No¦"
End If
If tbCost.Text = "" Or tbMaterial.Text = "" Then
Else
If CreateNewRaised = False Then
RaiseEvent CreateNew() 'Raise the event that's used to signal adding a new control to the layout
CreateNewRaised = True 'Create A Latched Boolean that cannot change again in the future
End If
End If
End Sub
Public ReadOnly Property Alv_Product_Order_Control As String
Get
Return OutString 'Pass string back to main form
End Get
End Property
Main Form Code
Private Sub CreateSecondPOC() Handles POC1.CreateNew
FlowLayoutPanel1.Controls.Add(New Alveare_VB.Alv_Product_Order_Control)
End Sub
I'm guessing here that the problem is the CreateSecondPOC only handles the event for the the first POC1
How do I create a new Alveare_VB.Alv_Product_Order_Control, name it as POC2 and also add a handler to handle POC2.CreateNew and add another control?
Edit 2
I know I've found the answer, but i'd like to look at this memory leak that has been mentioned. I've changed the code supplied in the answer below to this:
Private Sub CreateSecondPOC(ByVal sender As Object, ByVal e As System.EventArgs) Handles POC1.CreateNew
Try
Dim oldPoc = DirectCast(sender, Alveare_VB.Alv_Product_Order_Control)
RemoveHandler oldPoc.CreateNew, AddressOf CreateSecondPOC
Catch ex As Exception
Debug.Print(ex.Message)
End Try
Dim newPoc As New Alveare_VB.Alv_Product_Order_Control
AddHandler newPoc.CreateNew, AddressOf CreateSecondPOC
FlowLayoutPanel1.Controls.Add(newPoc)
End Sub
And I get the following error on the "RemoveHandler" line:
Unable to cast object of type 'System.Windows.Forms.TextBox' to type 'Alveare_VB.Alv_Product_Order_Control'.
The CreateNew event is raised when a textbox is written in, this is getting passed back as the sender I assume? Not really sure where to go with this now.
Edit 3
The error was in my UserControl, I was sending the incorrect object back (in this case the textbox). I have now changed the RaiseEvent to return the UserControl as an object. Now all is working correctly.
You could change your handler to something like this
Private Sub CreateSecondPOC() Handles POC1.CreateNew
Dim newPoc As New Alveare_VB.Alv_Product_Order_Control
AddHandler newPoc.CreateNew, AddressOf CreateSecondPOC
FlowLayoutPanel1.Controls.Add(newPoc)
End Sub
I'm not sure if you want to keep handling the event, even after it has been populated once, i.e. can it be depopulated, then repopulated, then raise the event again? Maybe you want to lock it once it's populated, but this isn't clear.
You could also keep all your POC controls in a container and only create a new one when they are all populated.
Edit:
According to comments below, you should remove the event handler when you no longer need it in order to avoid memory leaks. Here is one way
Private Sub CreateSecondPOC(sender As Object) Handles POC1.CreateNew
Dim oldPoc = DirectCast(sender, Alveare_VB.Alv_Product_Order_Control)
RemoveHandler oldPoc, AddressOf CreateSecondPOC
Dim newPoc As New Alveare_VB.Alv_Product_Order_Control
AddHandler newPoc.CreateNew, AddressOf CreateSecondPOC
FlowLayoutPanel1.Controls.Add(newPoc)
End Sub
But the last POC control's event handler would never be unsubscribed. So you could also do something like this when closing the form
For Each poc In Me.FlowLayoutPanel1.Controls.OfType(Of Alveare_VB.Alv_Product_Order_Control)()
RemoveHandler poc.CreateNew, AddressOf CreateSecondPOC
Next poc
I mentioned above that You could also keep all your POC controls in a container, which would be a better way to keep track of your controls, instead of using the FlowLayoutPanel as logical container. Just do what works for you and consider removing the handlers.

RichEditControl event handler, prompting to save upon form close with nothing has changed

Having an issue when closing the form and nothing has changed, it still prompts to save the form. We have a form that has multiple controls. There are a few lookup combo boxes with data. They select their data and then click a button called "view". View then brings up a few textboxes and combo boxes etc and populates with data. There is also a RichEditControl that also gets loaded. After all the data is loaded in the load event. The last line is to call the following method to set event handlers for all controls. If something has changed after that then prompt to save upon form closing.
customFunc.AddDirtyEvent(Me)
The issue is and we have test if there is no richtextbox, it works. If the only control on a form is a RichEditControl, it always prompts to save no matter what, even if nothing has changed upon load. I noticed if you have a form that has a RichEditControl, and it gets populated upon form load. Even if you call the eventhandler after that, it still prompts you to save BUT if you add the eventhandler call in the form shown event, it seems to work as it doesn't set the dirty bit again. Its almost like the events are queue at the end of the form load event. But then it goes to the shown event, the call is made there and the dirty bit doesn't get reset back to true.
Issue is in our case, we can't use a shown event, because we have a button "view" that loads all the data and populates a RichEditControl. So even if we add the event handlers after the data gets loaded in the same method, it always goes back to set the dirty bit to true. We need to somehow keep the dirty bit to false after this, so if there is no changes and they just want to view data don't prompt to save upon form closing. Below is my code.
If customFunc.IsDirty = True Then
Dim dr As DialogResult = MessageBox.Show("Do you want save changes before leaving?", "Closing Mud Report", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2)
If dr = Windows.Forms.DialogResult.Yes Then
SimpleButtonSave.PerformClick()
ElseIf dr = Windows.Forms.DialogResult.Cancel Then
e.Cancel = True
End If
End If
Private Sub SetIsDirty(ByVal sender As System.Object, ByVal e As System.EventArgs)
is_Dirty = True
End Sub
Public Sub AddDirtyEvent(ByVal ctrl As Control)
For Each c As Control In ctrl.Controls
If TypeOf c Is RichEditControl Then
Dim rtb As RichEditControl = CType(c, RichEditControl)
AddHandler rtb.RtfTextChanged, AddressOf SetIsDirty
End If
If c.Controls.Count > 0 Then
AddDirtyEvent(c)
End If
Next
End Sub
In your SetIsDirty method check for RichEditControl.Modified property.
Here is example:
Private Sub SetIsDirty(ByVal sender As System.Object, ByVal e As System.EventArgs)
If TypeOf sender Is RichEditControl Then
Dim rtb As RichEditControl = CType(sender, RichEditControl)
is_Dirty = is_Dirty OrElse rtb.Modified
Else
is_Dirty = True
End If
End Sub

VB.NET Form Hiding Issue

I have a custom form, B. B is created by A, which has a handle to B.VisibleChanged.
B only has an OK and Cancel button on it, and I want to do some logic when OK is hit.
B's OK button is dealt with like this:
Me.Result = Windows.Forms.DialogResult.OK
Me.Hide()
The code in A is properly hit and run, but it never hides B. When I check the values of the properties on B, it shows me Visible = False.
Does anyone have any suggestions as to the possible cause of this issue?
Edit
This form was shown using the Show() command, as I'm making a later call to have the form flash using FlashWindow().
Not exactly sure about your question.
why not use me.Close() instead of me.Hide?
Is it OK to have multiple instances of B at a time? If not, go for ShowDialog.
If you can rephrase the question, someone can probably resolve your problem.
I suppose you want to display a messagebox with an ok & cancel button. Instead of using a form use a mesagebox.
eg:
DialogResult dgResult = MessageBox.Show("Click Yes or No", "Test App", MessageBoxButtons.OKCancel, MessageBoxIcon.Information);
if (DialogResult.OK == dgResult)
{
//Do what you want.
}
else
{
//Do nothing.
}
If you are going to use a form, to do that & wanted to modify the parent's form, it would be advisable to use delegates to prevent form B from modifying form A's variables.
Else: (Not recommended)
Declare form B as a member variable of form A.
when required instantiate form B.
do B.ShowDialog();
internally in OK & cancel do this.dispose();
Again when you need form B just instantiate. re - instantiation will not be too much of an overhead if you dont call it very often.
But if you only need OK Cancel, use message box instead.
The show/hide approach works for me:
Public Class frmViewChild ' your form A
Private WithEvents _edit As frmEdit
'code
Private Sub editCell()
Dim PKVal As String
Dim PKVal2 As String
Dim fieldOrdinalPos As Integer
Dim isLastField As Boolean
If _edit Is Nothing Then
_edit = New frmEdit
_edit.MdiParent = Me.MdiParent
End If
'code
_edit.init(<params>)
If Not _edit.isNothing Then
_edit.Show()
_edit.WindowState = FormWindowState.Maximized
_edit.BringToFront()
End If
End Sub
'code
Private Sub _edit_VisibleChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles _edit.VisibleChanged
If Not _edit.Visible Then
WindowState = FormWindowState.Maximized ' revert after closing edit form
End If
End Sub
Public Class frmEdit ' your form B
Private Sub btnOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOK.Click
Dim ret As Integer
doOK(ret)
If ret > -1 Then ' no error
Me.Hide() ' close form, but didn't cancel
End If
End Sub
HTH