Add an event handler to each control on a form at runtime VB6
Asked Answered
V

7

5

I have a VB6 application where I'd like to have a consistent behavior among its controls application-wide. One of the behaviors, for example, would be highlighting a text box when it gains focus and removing the highlight when it loses focus. I'd like this to happen on every form.

What I'm trying to do is have one sub procedure that can be called by all forms when they load that will make this behavior happen. That way, I don't have to manually code for each individual text box to make it highlight.

I've tried getting VB6 to attach an event handler to a control at runtime but it just barks at me. I come from a .Net background so maybe I'm approaching it wrong for VB6. But how can I get this desired behavior without having to manually code it for every control?

Vitebsk answered 25/3, 2011 at 16:32 Comment(0)
A
3

You could also "Subclass" Your TextBox Controls Using WithEvents. The advantage here is that you can code the highlighting and de-highlighting in one place without having to go through and replace all of your existing controls (as Scott suggests).

The downside is that you have to add code to the Form_Load event of all your forms to "register" the controls on that form. However, even this should not be too bad if you want to apply the technique to every control; in that case, you just need to write a function that loops through the .Controls collection of a form and registers each control. Then just call this function in each form's Form_Load event.

Amorist answered 25/3, 2011 at 18:9 Comment(4)
+1 although I dislike calling this subclassing. It isn't OO subclassing (inheritance) nor Windows API subclassing (intercepting Windows messages).Openhearth
True. I added quotes around the term in my answer. I'm not sure of a precise technical term for what is actually going on with this approach.Amorist
I don't know of a specific term either. But subclassing already has 2 two meanings. I think its overloaded enough :) BTW If you like this, you might be interested in some of Karl Peterson's event magicOpenhearth
I like this. It adds minimal code to the project and lets me write this special code only once and without modifying already existing code and controls. This is just what I was looking for.Vitebsk
F
2

Check this out:

Control Arrays for Visual Basic 6.0 Users

Fulbert answered 25/3, 2011 at 16:44 Comment(6)
This is not what the OP is asking. He wants to change the behavior of all TextBoxes in the entire application, kind of like aspect-oriented programming.Holotype
Some of this can be done using control arrays though. It is just a less sophisticated and less general approach.Fading
I think this is exactly what the OP is asking for. His .NET background means he knows how to attach event handlers at runtime, which you can't do in VB6, and also (probably) that he doesn't know the term "Control Array," because it's VB6-specific. How many times has the question been asked the other way? "How can I simulate controls arrays in VB.NET?" The answer is usually a code example to attach event handler to all of the textboxes on a form...Collectivity
That would work, except these are controls defined at design time. They are not dynamically added. Is there a way, at runtime to add all of the controls of a certain type to a control array and give that control array a handler to do the highlighting? One other thing too. If the control has a handler already (an example might be a combo box whose selection drives something else) will the originally handler work along with the control arrays handler so it keeps its original behavior but also gains the desired look? I also don't want to rename the controls.Vitebsk
At design time, you create one textbox, and set its Index property to 0 in the properties panel. That creates a a control array. Then, double-click on the textbox, and VB6 will create an event handler with an additional Index property. That's how you can tell which control in the array was clicked, changed, or whatever else raised the event. At runtime, you use the Load statement, passing the name of the textbox and an index value. You then have to programatically reposition the new control and set its properties. The events will all be attached to every control in the control array. HTH.Collectivity
@john you are describing this answer by mwolfe02, except that it doesn't use control arrays and the event handler is written in a separate object.Openhearth
L
2

Unfortunately VB6 does not support implementation inheritance and you can't inherit TextBox and just modify or add functionality. It does not support COM aggregation too, though I doubt ActiveX controls specification supports it too.

What you are left with is reimplementing a control from scratch or implementing a custom UserControl that contains the original one and forwards every method, property or event. The problem with the latter approach is not that it's lots of pointless code but the performance of VB6's custom user controls. Built-in controls are really fast and you can place hundreds of labels or textboxes before noticing degradation.

What I'm doing in cases like yours is to implement an extender class that holds a reference to the textbox control, subclasses it and/or listens and responds to raised events from the control. The extender class implements the desired/modified behavior on GetFocus event or WM_GETFOCUS, whatever. Next, for each textbox on the form an instance of the extender is initialized with a reference to the control. All the extenders are held in a collection which can be part of a class that extends the form itself. The form extender can wrap the instantiation and initialization of the control extenders (the For Each In Controls part).

I'm doing this constantly, having very rich extenders for every possible control I'm placing on forms that wrap every property/method I'm accessing. I'm listening for events only on the extenders too. The nice part is that when I find a bug in a 3rd party control I can mitigate it very easily in the control extender.

Liegeman answered 25/3, 2011 at 19:35 Comment(2)
When you say subclassing do you mean Windows subclassing (intercepting Windows messages) or do you mean using a WithEvents variable to sink the COM events?Openhearth
Usually both. The behavior he wants to implement, I'm doing by setting a flag on WM_MOUSEACTIVATE and selecting contents on focus only if not flagged.Liegeman
O
2

Another way to achieve the behaviour you want is not to handle the textbox events at all. Instead, set up a Timer control with a small tick interval, say 50 milliseconds. In the Tick event, check Me.ActiveControl to see whether the focus has moved, and highlight/dehighlight accordingly. You will need a static variable to remember which control has the focus.

This is a nice easy way to get a universal GotFocus / LostFocus event handler in VB6.

Openhearth answered 25/3, 2011 at 21:1 Comment(2)
while that would work, I don't think using timers in that way is the correct solution.Vitebsk
mwolfe02 has the best answer - just wanted to remind everyone about this trick.Openhearth
O
2

I am with the Extender idea myself thanks to the tip from this site I have come up with my own solution:

Class clsTextBoxExtender Defintion:

Public WithEvents Control As TextBox

Private Sub Control_GotFocus()
    Control.SelStart = 0
    Control.SelLength = Len(Control.Text)
End Sub

Private Sub Control_LostFocus()
    Control.SelLength = 0
End Sub

Module Module1 Defintion:

Public Sub InitialiseTextBoxExtenders(ByRef myForm As Form, ByRef extenderCollection As Collection)
    Dim formControl As Control
    Dim oTBXExtender As clsTextBoxExtender
    For Each formControl In myForm.Controls
        If TypeOf formControl Is TextBox Then
            Set oTBXExtender = New clsTextBoxExtender
            Set oTBXExtender.Control = formControl
            extenderCollection.Add oTBXExtender
        End If
     Next
End Sub

Form Form1 Definition:

Private textBoxExtenderCollection As New Collection

Private Sub Form1_Load()
    Module1.InitialiseTextBoxExtenders Me, textBoxExtenderCollection
End Sub

'No longer required
'Private Sub TextBox1_GotFocus()
'    TextBox1.SelStart = 0
'    TextBox1.SelLength = Len(TextBox1.Text)
'End Sub

So in effect for every new form all you have to do is declare a collection and call the initialiser code in the form load event. Simple!

Furthermore if you have further requirements that you need to refer back to you extender class rather than looping thru your collection you may choose to create a Key of say the control's name when adding to the collection however keep in mind if you are using control arrays on your form your form may need to include the Index in the key.

Also note if you declare the same event in your form for your control both your event and the extender event will fire one after the other. I do not know of any documentation on this however, from my experimentation the extender event goes last.

Oslo answered 6/12, 2013 at 0:32 Comment(1)
This is almost exactly what I was about to suggest. I was surprised I did not see it further up the post. I would much rather have a behavior object I can attach to any TextBox than a custom TextBox or forced conversion to control array. Keeps everything neat and named, keeps the TextBox compatible with ordinary TextBox code, and it allows you to mix and match behaviors as necessary. Reminiscent of the way I handle attached property behaviors in WPF, which is what led me to think of it.Payne
H
1

The appropriate way to do what you're asking is to define a new UserControl (MyAdvancedTextBox) and code your intended behavior in there. Then replace all of your text boxes with that user control. It's a lot of work, but it's less work than the alternative:

Manually define an event handler in the code-behind for each text box (or text box control array) and have the event handler pass itself to some common module subroutine that executes your common handling logic.

VB6 events are a lot more primitive than .NET.

Holotype answered 25/3, 2011 at 17:8 Comment(1)
There are several easier alternatives. See the other answers.Openhearth
D
0

The tips are good. However, the example shared is very limited. I have an issue with the events for dynamic controls. i have to create check box , text box , radio buttons and Combo box on click of a button. I am able to successfully create the dynamic controls. BUT i am not able to capture the actions of each of this control, such change status of check box or radio options or changes in Dropdown text...

Adding the code for reference: Expectation:
1. I should be able to capture delete Row change in the check box
2. I should be able to capture changes in Combo box

Static Controls:
1. Form: frmcharacteristics
2. Button: cmdAddCharacteristics
3. SSTab: tabDisplay

Code in Module1:

Public SR_NO As Long
Public Top_Position As Long

code in frmCharacterisitcs

Option Explicit
Dim WithEvents Ch_Delete_Row As CheckBox
Dim WithEvents Ch_SR_NO As Label
Dim WithEvents Ch_Name As TextBox
Dim WithEvents Ch_Type As ComboBox

Dim WithEvents Extended_Control As VBControlExtender


Private Sub cmdAddCharacteristics_Click()

    Module1.SR_NO = Module1.SR_NO + 1
    Set Ch_Delete_Row = frmCharacteristics.Controls.Add("VB.CheckBox", "Ch_Delete_Row" & (Module1.SR_NO), tabDisplay)
    Ch_Delete_Row.Visible = True
    Ch_Delete_Row.Top = Module1.Top_Position + 100
    Ch_Delete_Row.Width = 1000
    Ch_Delete_Row.Left = 500
    Ch_Delete_Row.Caption = ""
    Ch_Delete_Row.Height = 315
    'MsgBox Ch_Delete_Row.Name

    Set Ch_SR_NO = frmCharacteristics.Controls.Add("VB.Label", "Ch_SR_NO" & (Module1.SR_NO), tabDisplay)
    Ch_SR_NO.Visible = True
    Ch_SR_NO.Top = Module1.Top_Position + 200
    Ch_SR_NO.Width = 750
    Ch_SR_NO.Left = Ch_Delete_Row.Left + Ch_Delete_Row.Width + 400
    Ch_SR_NO.Caption = Module1.SR_NO
    Ch_SR_NO.Height = 315

    Set Ch_Name = frmCharacteristics.Controls.Add("VB.TextBox", "Ch_Name" & (Module1.SR_NO), tabDisplay)
    Ch_Name.Visible = True
    Ch_Name.Top = Module1.Top_Position + 100
    Ch_Name.Width = 2000
    Ch_Name.Left = Ch_SR_NO.Left + Ch_SR_NO.Width + 200
    Ch_Name.Text = ""
    Ch_Name.Height = 315

    Set Ch_Type = frmCharacteristics.Controls.Add("VB.ComboBox", "Ch_Type" & (Module1.SR_NO), tabDisplay)
    Ch_Type.Visible = True
    Ch_Type.Top = Module1.Top_Position + 100
    Ch_Type.Width = 1500
    Ch_Type.Left = Ch_Name.Left + Ch_Name.Width + 50
    Ch_Type.Text = ""
    'Ch_Type.Height = 315
    Ch_Type.AddItem "Service"
    Ch_Type.AddItem "Special"
    Ch_Type.AddItem "Option"

    Module1.Top_Position = Module1.Top_Position + 400
End Sub

Private Sub Form_Load()
    Module1.SR_NO = 0
    Dim Test_Line As Control
    Set Test_Line = frmCharacteristics.Controls.Add("VB.Line", "LINE", frmCharacteristics)
    Test_Line.Visible = True
    Test_Line.X1 = 100
    Test_Line.Y1 = 600
    Test_Line.X2 = frmCharacteristics.Width
    Test_Line.Y2 = 600
    Top_Position = Test_Line.Y1
    frmCharacteristics.Show
    tabDisplay.Width = frmCharacteristics.Width - 1000
    tabDisplay.Height = frmCharacteristics.Height - 1500
    tabDisplay.Left = frmCharacteristics.Left + 200
    Call set_labels
End Sub


Sub set_labels()

    Dim Label_SR_NO As Control
    Dim Label_Name As Control
    Dim Label_Delete_Row As Control
    Dim Label_Type As Control

    Set Label_Delete_Row = frmCharacteristics.Controls.Add("VB.Label", "Label_Delete_Row" & (Module1.SR_NO), tabDisplay)
    Label_Delete_Row.Visible = True
    Label_Delete_Row.Top = Module1.Top_Position + 100
    Label_Delete_Row.Width = 1000
    Label_Delete_Row.Left = 300
    Label_Delete_Row.Caption = "Delete(Y/N)"
    Label_Delete_Row.Height = 315

    Set Label_SR_NO = frmCharacteristics.Controls.Add("VB.Label", "Label_SR_NO" & (Module1.SR_NO), tabDisplay)
    Label_SR_NO.Visible = True
    Label_SR_NO.Top = Module1.Top_Position + 100
    Label_SR_NO.Width = 750
    Label_SR_NO.Left = Label_Delete_Row.Left + Label_Delete_Row.Width + 400
    Label_SR_NO.Caption = "SR_NO"
    Label_SR_NO.Height = 315

    Set Label_Name = frmCharacteristics.Controls.Add("VB.Label", "Label_Name" & (Module1.SR_NO), tabDisplay)
    Label_Name.Visible = True
    Label_Name.Top = Module1.Top_Position + 100
    Label_Name.Width = 2000
    Label_Name.Left = Label_SR_NO.Left + Label_SR_NO.Width + 400
    Label_Name.Caption = "Characteristics Name"
    Label_Name.Height = 315

    Set Label_Type = frmCharacteristics.Controls.Add("VB.Label", "Label_Type" & (Module1.SR_NO), tabDisplay)
    Label_Type.Visible = True
    Label_Type.Top = Module1.Top_Position + 100
    Label_Type.Width = 1500
    Label_Type.Left = Label_Name.Left + Label_Name.Width + 50
    Label_Type.Caption = "Charac. Type"
    Label_Type.Height = 315

    Module1.Top_Position = Module1.Top_Position + 400
End Sub
Dragon answered 21/7, 2016 at 4:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.