Tekst selecteren in een VBA UserForm TextBox
Inhoud
Introductie
UserForms zijn in VBA een belangrijke manier om interactie met de gebruiker te faciliteren. Helaas is de MSForms-bibliotheek, die de besturingselementen voor UserForms bevat, behoorlijk verouderde code met veel eigenaardigheden. Hier laat ik zo'n rariteit zien en hoe je die oplost.
Het voorbeeldbestand
Download het bestand; het bevat een UserForm die het probleem en de oplossing demonstreert.
Het probleem
Het hierboven genoemde bestand bevat een UserForm met een zoekvak en een tekstvak met voorbeeldtekst. Zodra je in het zoekvak begint te typen, wordt de eerste gevonden overeenkomst in het tekstvak (een TextBox) geselecteerd. Met de knoppen Previous en Next kun je van de ene naar de volgende treffer navigeren. Tenminste, dat is de bedoeling. De schermafbeelding hieronder is eigenlijk een geanimeerde gif (beweeg je muis eroverheen of tik erop om de animatie te zien), maar zelfs het stilstaande beeld laat het probleem zien: de geselecteerde tekst is niet de tekst die we zochten; de selectie is wat verschoven.
Hoe kan dit?
Om tekst in een tekstvak te selecteren, gebruiken we de eigenschappen SelStart en SelLength. Je zou verwachten dat dit werkt:
Dim ctl As MSForms.Control
Dim pos As Long
Dim ct As Long
Set ctl = Me.ActiveControl
If instance > 1 Then
For ct = 1 To instance
If pos = 0 Then
pos = InStr(txtBox, txt2Select)
Else
pos = InStr(pos + Len(txt2Select), txtBox, txt2Select)
End If
Next
Else
pos = InStr(txtBox, txt2Select)
End If
If pos > 0 Then
With Me.tbxText
.SetFocus
.SelStart = pos - 1
.SelLength = Len(txt2Select)
End With
Else
ResetSelection Me.tbxText
End If
ctl.SetFocus
End Sub
De telling van het aantal karakters van een tekstvak heeft echter een eigenaardigheid: elke "Enter" telt als twee tekens.
De oplossing
De oplossing is daarom relatief eenvoudig: kijk terug vanaf het punt waar de selectie moet beginnen, tel het aantal vbLf-tekens en trek dat aantal af van de te selecteren positie:
If position <= 1 Then Exit Function
CountNewLinesBefore = Len(Left$(textValue, position - 1)) - _
Len(Replace$(Left$(textValue, position - 1), vbLf, vbNullString))
End Function
Toegepast in de routine uit het vorige hoofdstuk:
Dim ctl As MSForms.Control
Dim pos As Long
Dim ct As Long
Set ctl = Me.ActiveControl
If instance > 1 Then
For ct = 1 To instance
If pos = 0 Then
pos = InStr(txtBox, txt2Select)
Else
pos = InStr(pos + Len(txt2Select), txtBox, txt2Select)
End If
Next
Else
pos = InStr(txtBox, txt2Select)
End If
If Me.cbxFix.Value Then
pos = pos - CountNewLinesBefore(txtBox, pos)
End If
If pos > 0 Then
With Me.tbxText
.SetFocus
.SelStart = pos - 1
.SelLength = Len(txt2Select)
End With
Else
ResetSelection Me.tbxText
End If
ctl.SetFocus
End Sub
Merk op dat de code de focus tijdelijk op de TextBox zet. Als je dat weglaat, scrollt het tekstvak niet naar de geselecteerde positie.
Nu werkt het:

Veelgestelde vragen
Waarom is de gemarkeerde tekst verschoven in een meerregelige VBA TextBox?
MSForms telt elk regeleinde intern als twee tekens, waardoor een directe SelStart op basis van InStr bij meerregelige inhoud kan afwijken.
Komt dit probleem ook voor in tekstvakken met één regel?
Meestal niet. Het offsetprobleem treedt op wanneer er regeleinden in de tekst staan.
Welke eigenschappen gebruik je om tekst in een TextBox te selecteren?
Gebruik SelStart voor het eerste teken en SelLength voor het aantal tekens dat je wilt selecteren.
Waarom beïnvloedt Enter de selectiepositie?
In dit besturingselement voegt Enter een regeleinde toe dat intern anders wordt geïndexeerd dan je op basis van gewone stringposities verwacht.
Hoe corrigeer ik de startpositie?
Tel de line-feed-tekens vóór de match en trek dat aantal af van de positie die InStr teruggeeft.
Moet ik vbLf, vbCr of vbCrLf tellen?
Voor dit patroon is vbLf tellen in de TextBox-waarde voldoende; dat laat dit artikel ook zien.
Hoe selecteer ik de tweede of derde keer dat een woord voorkomt?
Herhaal InStr-zoekopdrachten vanaf het einde van de vorige match totdat je de gevraagde instantie bereikt.
Waarom zet de code de focus tijdelijk op de TextBox?
Zonder focus op de doel-TextBox scrollt het besturingselement mogelijk niet naar de geselecteerde tekst.
Wat moet er gebeuren als er geen match wordt gevonden?
Laat de huidige selectie staan of wis die, en geef feedback aan de gebruiker, bijvoorbeeld met een statuslabel of bericht.
Kan dit hoofdletterongevoelig worden gemaakt?
Ja. Gebruik een InStr-variant met tekstvergelijkingsmodus en pas daarna dezelfde offsetcorrectie toe.
Geldt dit alleen voor Excel?
Dit gedrag hangt samen met MSForms-besturingselementen, dus vergelijkbare logica kan ook gelden in andere VBA-hosts die dezelfde bibliotheek gebruiken.
Werkt dit ook met lange tekstblokken?
Ja, maar herhaald zoeken kan langzamer worden. Cache posities als je heel vaak vorige/volgende navigatie nodig hebt.
Waar kan ik de werkende voorbeeldwerkmap downloaden?
Gebruik de downloadlink in het gedeelte Het voorbeeldbestand.
Waar lees ik de korte uitleg van het probleem en de oplossing?
Zie Het probleem en De oplossing voor de complete uitleg.

Reacties