Working with a Windows.Forms.Form from the VS designer I can use the KeyDown event when it is fired on a form or on child control (I am interested in RichTextBox). But I want to use the event on a variable type RichTextBox, say:
Dim rtbx as new RichTextBox
Sub rtbx_OnKeyDown(sender as object, e as KeyEventArgs) Handles rtbx.KeyDown
VS does not understand this. There is a method OnKeyDown listed in the ObjectBrowser, which is associated with the grandparent Control class of the RichTextBox, but I have not found any way to call this method. Examples on Microsoft VB documentation show only code looking like one copied from Form.vb class.
There are two ways to attach event handlers in VB. One is to use the WithEvents keyword on a field and the Handles keyword on a method, e.g.
Private WithEvents myRichTextBox As RichTextBox
and:
Private Sub myRichTextBox_KeyDown(sender as object, e as KeyEventArgs) Handles myRichTextBox.KeyDown
That's how it works when you add controls in the designer and then have the IDE generate the event handler. If you look in the designer code file, you'll see a field declared for each control and component you added and each declared WithEvents. If you do that then the event handler remains connected to the field, even if you change the value of the field.
The other option is to use the AddHandler keyword. This is what you must do when attaching an event handler via a local variable, because they obviously cannot be declared WithEvents, e.g.
Private Sub myRichTextBox_KeyDown(sender as object, e as KeyEventArgs)
Note there's no Handles clause on the method. Then:
Dim myRichTextBox As New RichTextBox
'...
AddHandler myRichTextBox.KeyDown, AddressOf myRichTextBox_KeyDown
That event handler will remain attached to the object and, while it is not strictly necessary in some cases, it is good practice to always detach the event handler when you're done with the it:
RemoveHandler myRichTextBox.KeyDown, AddressOf myRichTextBox_KeyDown
You need a reference to the object in order to do that, so you might use a field in order to store that. If there will only be one object, you may as well declare the field WithEvents and use Handles. If there may be multiple objects then you can use a list and then loop through that list to detach the event handlers.
Related
Consider a simple .NET form with a couple of radio buttons and a checkbox.
Each of the radio buttons has a CheckedChanged handler setup that performs some action based on the state of the checkbox.
My problem is, when I initialize on the default radiobutton to be checked (from the designer properties window) the CheckedChanged event is fired for that radio button, but the Checkbox hasn't been initialized yet so I either get a null pointer exception or the wrong value is used in the handler. Either way, I don't want that handler code to be run unless the user picks a radio button after the form has been loaded.
I currently get around this by not initializing the radio button, but I need to set that default eventually and the best place is from the designer. I also can add a boolean field that's not set to true until the form is fully loaded and not process the events if that is false, but it's a dirty hack.
What can I do to prevent that handler from running its code?
To make it feel slightly less dirty, if you initialize the controls in the constructor of the form you might be able to use the forms IsHandleCreated property rather than your own bool to check if it should actually validate or not.
I would think that normally you wouldn't want to validate anything before it's been shown for the first time and handle isn't created until it is.
Code Example:
Private Sub myRadioButton_CheckedChanged(sender As Object, e As EventArgs) Handles myRadioButton.CheckedChanged
If myRadioButton.Checked AndAlso myRadioButton.IsHandleCreated Then
'Do Work
End If
End Sub
"I also can put a boolean field that's not set to true until the form is fully loaded and not process the events if that is false, but it's a dirty hack."
It's also the easist and best way to do it!
Lets say .NET provides a neat way to turn an and off all the event handlers until the form is loaded. Even just the ones YOU are handling. It would still not be sufficiently flexible to disable what you wanted to enable but disable what you didn't. Often form setups happen and you want the events to fire. Also the form won't build right if no events fire.
The easy solution is to declare an initializing variable:
Private Initializing as boolean = True
Private Sub rb_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles rbNuevos.CheckedChanged, RbDesaparecidos.CheckedChanged, RbModificados.CheckedChanged, RbNoDesap.CheckedChanged, RbDesHoy.CheckedChanged, RbChT.CheckedChanged
if Initializing then return
'Your Code
End Sub
Public Sub New()
' Llamada necesaria para el Diseñador de Windows Forms.
InitializeComponent()
' Agregue cualquier inicialización después de la llamada a InitializeComponent().
initializing = false
end sub
Most sophisticated: Remove the "handles" from the method, and use AddHandler on the new method.
Public Sub New()
' Llamada necesaria para el Diseñador de Windows Forms.
InitializeComponent()
' Agregue cualquier inicialización después de la llamada a InitializeComponent().
AddHandler RbChT.CheckedChanged, AddressOf rb_CheckedChanged
end sub
For radiobutton see Hans Olsson answer
For numeric up down, do it like this
Private Sub myNumeric_ValueChanged(sender As Object, e As EventArgs) Handles myNumeric.ValueChanged
If myNumeric.Value >= 0 AndAlso myNumeric.IsHandleCreated Then
'Do the work
End If
End Sub
The keyword is myNumeric.Value and IsHandleCreated
Yet another way:
Private Sub dgvGroups_CellValueChanged(sender As System.Object, e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvGroups.CellValueChanged
If Me.Visible = False Then Exit Sub ' Sub gets called on form load which causes problems
wksGroups.Cells(e.RowIndex + 1, 1) = dgvGroups.Item(e.ColumnIndex, e.RowIndex).Value
wksGroups.Cells(1, 5) = dgvGroups.RowCount
One thing I've found that works is adding the events manually after you load the form.
To do this you can simply go into the generated form code found in the designer file of that form, and pull out the lines that add the event. It would look something like this:
this.controlName.CheckedChanged += new System.EventHandler(this.controlName_CheckedChanged);
Then put all of these calls into a method that you call after the InitializeComponent call in your form's constructor.
Just in case anyone is still searching for this the event is fired upon initializing the form BUT the form is not yet visible, Also Say that you have a foreign key relationship upon which you have a default value needed issue that gets fired every row update too. So the following code worked for me....
if (Visible && !(e.ColumnIndex == 0))
{
phoneEdited = true;
MessageBox.Show("A Phone entry has been entered");
}
Don't set checked on a control that really does much in designer.
The global flag and conditional exits where needed.
Try..Catch the sore spots to ignore a meaningless exception.
(Using VS 2017) It appears to me that it is an annoyance but not a bug. It is consistent with the model in use. The event is fired by normal operation of code, but code I did not write (but can access where fools fear to tread) and where there appears to be no (decent) place earlier in the normal flow to anticipate it.
The cleanest answer seems to be not to check radio button or checkbox controls in the designer at all if they trigger any significant code. Instead these controls should be changed by code (e.g. checked = true) in the Load event (for example) AFTER all the initialization is done.
There is no loss of flexibility here since both are fixed before the build, only in different places. The event handlers will handle it exactly as if a user had clicked the control in the natural flow of a well designed GUI application. (This reminds me of the ancient RPG proverb "Don't buck the cycle". (Anyone here remember RPG? I, not part of IBM-oriented team, never used it but had interesting discussions with some who did. ) Pre-checking controls hits the wrong part of the VS cycle.)
If for any reason that will not work, the next best thing is the kludge suggested elsewhere of a single status boolean initialized false and set true at the appropriate time with conditional exits in the necessary places to prevent them from crashing until then. It will get the job done, but it's ugly. Better than failure.
Another thing I tried before I decided that designer level pre-set checks were the problem and there was a very acceptable alternative was to put the danger spots in a Try..Catch to be able to ignore the exception. Also a kludge.
For the cleanest code, reverse the True/False approach used in some other examples. Focus on 'ready' rather than 'busy'. Here's an example for a Windows Form:
At the Class level, add Private app_ready As Boolean (it will be False by default).
At the end of the Form.Shown event handler, add app_ready = True.
In each control event handler where it's needed, add:
If app_ready Then
' code
End If
Starting a routine with something like If initialising Then Exit Sub just doesn't feel right!
Maybe for some functionality you can use the click event instead of the check changed event.
I put a public variable in the Module1 file
Dim Public bolForm_LoadingTF as Boolean = True
In each formLoad event I put
bolForm_LoadingTF = True
In each control with an OnSelectedIndexChanged
event I put if bolForm_LoadingTF = True then Exit Sub
At the end of the form load event I put
bolForm_LoadingTF = False
I am probably breaking a bunch of rules but this works
for me.
I'm working on a POS program where I have a POS keyboard COM control within the application. When I double click the icon, an Event is created:
Private Sub PosKeyboard_DataEvent(sender As Object, e As AxOposPOSKeyboard_CCO._IOPOSPOSKeyboardEvents_DataEventEvent) Handles PosKeyboard.DataEvent
If PosKeyboard.POSKeyData = 1 Then Exit_Button.PerformClick()
End Sub**
How do I create an event on a different form within the app for the same device?
Based on the comments so far, I will provide an answer that I think applies.
When it comes to handling events, you have a variable to which you assign an object and then you handle an event of that variable. When the assigned object raises its event, the handling method is executed. So, in your case, you need to start by declaring a variable in the form in which you want to handle the event, e.g.
Private WithEvents posKeyboard As SomeType
The WithEvents keyword is required for that variable to be able to be used in a Handles clause. I use SomeType because I don't know the actual type of your object, although I suspect that it's something like _IOPOSPOSKeyboard.
You can then write a method to handle the desired event, copying the signature from your other form:
Private Sub posKeyboard _DataEvent(sender As Object, e As AxOposPOSKeyboard_CCO._IOPOSPOSKeyboardEvents_DataEventEvent) Handles posKeyboard .DataEvent
'...
End Sub
You then need to pass in the object that will be raising the event from the other form, which you can do via a parameter in a constructor or some other method or a property setter.
I have a piece of code where I add a handler everytime the form is open, I'm working in visual basic, but the first time I enter the form everything works fine, but for the second time I have 2 handlers, if I enter a third I have 3 handlers and so on. I don't know why is this happenning.
Here is what I tried so far.
I have stored all my machines in another class but I'm sending to myForm to show them, but to add them I use this code:
Private Sub add_machine(ByRef machine As Machine)
RemoveHandler machine.imgBox.Click, AddressOf Me.imgBox_Click
AddHandler machine.imgBox.Click, AddressOf Me.imgBox_Click
Me.Controls.Add(machine.get_imgMachine)
Private Sub imgBox_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
'Some code
End Sub
Everytime I open the form I call the sub add_machine to add dinamically my machines, as you can see I send them ByRef to simplify my code I tried to put that RemoveHandler since I'm sending ByRef to avoid having more than one handler, but it's not working please help
Thanks in advance.
Two things, DONT pass machine ByRef. You are not returning a new object. Please read this link for more information on when you should use what Byval vs ByRef
Second, add your handlers during form load (or initialization) and remove them in form closing. This will help ensure you are working with references to the same object.
I'm looking to call a pre-existing event handler subroutine from the form_Load event handler.
But the below doesn't work because control doesn't come back and I want to do more.
UPDATE:
I'm new to this so I don't know what the proper protocol is but...
The reason for the non-return was that a statement like the below ended the subroutines execution.
If aLabel.Tag = 1...
the fix was adding New to the declaration to create an instance of it, ie..
changing....
Dim aLabel As Label
... to ...
Dim aLabel As New Label
I'm surprised I didn't get a warning but instead they just abruptly stopped execution of the sub. That wasn't very helpful :)
Thanks again for your time guys...
(Maybe this question should be deleted now that it has served its purpose)
#konrad #karl
END OF UPDATE
What doesn't work is....
Private Sub Form1_Load...
button1_Click(sender, e) 'But Control doesn't come back.
end sub
Do I change the sender to something?
Thanks in advance
Dave
Invoking event handlers like this is a bad idea, because you are trying to simulate the event context by making sender and/or EventArgs be something else.
Instead, put the logic that you want to invoke into a Subroutine or Function and have your Form1_Load method call that; likewise if you really do have a real click event handler, then that handler code can call the method too, like this:
Private Sub Form1_Load()
DoSomeWork()
End Sub
Protected Sub button1_Click(ByVal sender As Object, ByVal e As EventArgs)
DoSomeWork()
End Sub
Private Sub DoSomeWork()
' Put logic here that you want to do from form load and a button click
End Sub
This has the benefit of making the code cleaner, clearer and easier to maintain as you only need to change the logic in one place should you need to change the logic.
Note: Obviously, you can pass parameters to the DoSomeWork method, if need be, and change it to a Function if you need it to return something.
I need to pass an event as a parameter to a function. Is there a way of doing this?
The reason is that I have a sequence of two lines of code that is littered all over my program, where I dynamically remove the handler to an event, and then set the handler again. I'm doing this for several different events and event handlers, so I've decided to write a function that does this.
As an example, let's say I have a combobox in my code called combobox1, and I have the handler called indexChangedHandler. In several places of my code, I have the following two lines:
RemoveHandler combobox1.SelectedIndexChanged, AddressOf indexChangedHandler
AddHandler combobox1.SelectedIndexChanged, AddressOf indexChangedHandler
Now, I don't want to keep on repeating the above two lines of code (or similar) all over my program, so I'm looking for a way to do this:
Private Sub setHandler(evt As Event, hndler As eventhandler)
RemoveHandler evt, hndler
AddHandler evt, hndler
End Sub
so that everywhere where those two lines of code(or similar) occur in my program, I can just replace them with:
setHandler(combobox1.SelectedIndexChanged, AddressOf indexChangedHandler)
So far, the "evt as Event" part of the argument of the setHandler function is giving an error.
P.S: I've asked this question on a couple of other forums and keep getting asked why I would want to set the handler immediately after removing it. The reason is because dynamically adding an event handler n-times causes the handler to be executed n-times when the event occurs. To avoid this, that is, to ensure that the handler is executed just once when the event occurs, I first remove the handler each time I want to add the handler dynamically.
You might be asking why the handler would be added several times in the first place... The reason is because I add the handler only after a particular event, say E1, in my form has occurred (I add the handler within the handler of event E1). And event E1 can occur several times within my form. If I do not remove the handler each time before adding it again, the handler gets added and thus executed several times.
Whatever the case, the processing occurring within the function is not of ultimate importance to me at this time, but rather just the means of passing an event as a parameter.
Of course you can pass events around... Well you can pass Action(Of EventHandler) which can do what you want.
If you have a class that defines an event like so:
Public Class ComboBox
Public Event SelectedIndexChanged As EventHandler
End Class
Given an instance of ComboBox you can then create add & remove handler actions like so:
Dim combobox1 = New ComboBox()
Dim ah As Action(Of EventHandler)
= Sub (h) AddHandler combobox1.SelectedIndexChanged, h
Dim rh As Action(Of EventHandler)
= Sub (h) RemoveHandler combobox1.SelectedIndexChanged, h
(Now, this is VB.NET 4.0 code, but you can do this in 3.5 using AddressOf and a little more mucking about.)
So if I have a handler Foo:
Public Sub Foo(ByVal sender as Object, ByVal e As EventArgs)
Console.WriteLine("Over here with Foo!")
End Sub
And a Raise method on ComboBox I can now do this:
ah(AddressOf Foo)
combobox1.Raise()
rh(AddressOf Foo)
This writes the message "Over here with Foo!" as expected.
I can also create this method:
Public Sub PassActionOfEventHandler(ByVal h As Action(Of EventHandler))
h(AddressOf Foo)
End Sub
I can pass around the event handler actions like so:
PassActionOfEventHandler(ah)
combobox1.Raise()
PassActionOfEventHandler(rh)
Which again writes the message "Over here with Foo!".
Now, one issue that might be a problem is that you can accidentally swap the add and remove event handler delegates in code - after all they are the same type. So it is easy to just define strongly-typed delegates for the add and remove actions like so:
Public Delegate Sub AddHandlerDelegate(Of T)(ByVal eh as T)
Public Delegate Sub RemoveHandlerDelegate(Of T)(ByVal eh as T)
The code to define the delegate instances doesn't change except for the delegate types:
Dim ah As AddHandlerDelegate(Of EventHandler)
= Sub (h) AddHandler combobox1.SelectedIndexChanged, h
Dim rh As RemoveHandlerDelegate(Of EventHandler)
= Sub (h) RemoveHandler combobox1.SelectedIndexChanged, h
So this now lets you be very creative. You can define a function that will take the add handler delegate, the remove handler delegate and a event handler, which will wire-up an event handler, and then return to you an IDisposable that you can use later to remove the handler without needing to retain a reference to the event handler. This is handy for using Using statements.
Here's the signature:
Function SubscribeToEventHandler(
ByVal h as EventHandler,
ByVal ah As AddHandlerDelegate(Of EventHandler),
ByVal rh As RemoveHandlerDelegate(Of EventHandler)) As IDisposable
So given this function I can now do this:
combobox1.Raise()
Using subscription = SubscribeToEventHandler(AddressOf Foo, ah, rh)
combobox1.Raise()
combobox1.Raise()
End Using
combobox1.Raise()
And this writes the message "Over here with Foo!" only twice. The first and last Raise calls are outside of the subscription to the event handler.
Enjoy!
You cannot pass events around. Event isn't a type, it's a keyword that defines a pair of methods, add and remove, used to change the state of the event member of the class. They are very much like properties in this respect (which have get and set methods).
Like properties, the add and remove methods can do whatever you want. Typically, these will do nothing more than maintain a delegate instance, which itself is a MulticastDelegate or in other words, a list of delegates which are getting called one by one if the event is raised.
You can clearly see this structure in C#, where it is not masked with AddHandler/RemoveHandler, but you directly edit the list of associated handlers: myObject.Event += new Delegate(...);.
And, again like properties, being a member of a class that actually abstracts two different methods, it's not possible to literally pass an event as an object.
There are a number of different meanings for the term "event", depending upon context. In the context you are seeking, an Event is a matched pair of methods that will effectively add or remove a delegate from a subscription list. Unfortunately, there's no class in VB.NET to represent a method address without an attached object. One could probably use reflection to fetch the appropriate methods, and then pass those methods to a routine that would call them both, but in your particular situation that would probably be silly.
The only situation where I can see that it might be useful to pass an event in the sense you describe would be for something like an IDisposable object which subscribes to events from longer-lived objects, and which needs to unsubscribe all of its events when it is disposed. In that case, it may be helpful to use reflection to fetch the remove-delegate method for each event, and then call all of the remove-delegate methods when an object is disposed. Unfortunately, I know of no way to represent the events to be retrieved except as string literals, whose validity could not be checked until run-time.