VBcoders Guest



Don't have an account yet? Register
 


Forgot Password?



Get Row and Column positions Fast in a Textbox or Rich Textbox (clean-up)

by David Ross Goben (15 Submissions)
Category: Windows API Call/Explanation
Difficulty: Intermediate
Date Added: Wed 3rd February 2021
Rating: (10 Votes)

This short article will show you how you can EASILY obtain an accurate logical text row count, plus the logical row and column number the cursor is located at in a Textbox or Rich Textbox. Line wraps that will force several additional displayed lines are no problem at all, and the returned indexes will account for them. Plus, afterward, I will show you how you can quickly dress up a selection in a textbox so that it is not always at the bottom of the textbox. Because these textbox controls have to manage these row and column counts and indexes anyway, why not let THEM do ALL the work for you.

Rate Get Row and Column positions Fast in a Textbox or Rich Textbox (clean-up)



 Get Row and Column Positions Fast in a Textbox or Rich Textbox

 

By David Ross Goben

 

 You are free to use this information, plus the included snippets in your own 
 code without even acknowledging my work or me.



A number of people have asked me over the years how to grab the line count, 
 line number, and column position the cursor is located at in a Textbox or Rich 
 Textbox. Other queries regarded positioning the cursor for display.

 

 I have seen a number of samples published here, but they involved VB-only solutions, 
 usually using the Split() function. This is fine for short lines, 
 but if you are writing a text editor (every programmer should write at least 
 one text editor during their career - I have written 8 descent ones, 2 multi-document 
 editors, and VERY profitable professional word-processing and spell-checker packages that used 
 to sell for the Tandy Model I/III/4 TRS-80s).

 

 Most examples for obtaining the row count, row number, and column usually employ 
 a lot of math, because it is a rather complex thing to do. This is due to the reason 
 that multi-line text boxes more often than not have lots of line wraps and such, 
 which forces most VB-only methods to fail miserably.

 

 However, by utilizing just a single API function and 4 constants, we can obtain 
 everything we need in just a very few lines of code by letting the Textbox or 
Rich Textbox do all the work for us.

 

 We will require the SendMessage() API, but we want to use it without 
the As Any frivolity that the VB6 API Viewer provides (the API 
Viewer is activated under the Add-Ins menu via the Add-In Manager
select the VB 6 API Viewer and ensure that the Loaded/Unloaded 
and Load on Startup options are checked). As such, we will rename it
SendMessageByNum(), and change its lParam from As Any 
to As Long. We will also grab 4 constants from the API Viewer: EM_LINEFROMCHAR, EM_LINEINDEX, EM_GETLINECOUNT, 
and EM_LINESCROLL.

 

 So far, we have the following code in our module or form or class (or wherever 
 you need to use these things in):

 

 

 Private Declare Function SendMessageByNum 
 Lib "user32
 Alias "SendMessageA
 _

        (ByVal  hwnd 
 As Long
 _

         ByVal  wMsg 
 As Long
 _

         ByVal  wParam 
 As Long
 _

         ByVal  lParam 
 As Long
 As Long

 

 Private Const EM_LINEFROMCHAR 
 As Long 
 = &HC9

 Private Const EM_LINEINDEX 
 As Long 
 = &HBB

 Private Const EM_GETLINECOUNT 
 As Long 
 = &HBA

 Private Const EM_LINESCROLL 
 As Long 
 = &HB6

 


 We have just set up for using the API. The actual Code is little longer than this,
 though we will make it a bit longer for reasons of clarity.

 

 Note that I declared the API and all the constants Private. Some people prefer 
 to declare them as Public and use them willy-nilly throughout their application, 
 but this is messy. Keep them local and private to one file, and simply write 
 public server subroutines and functions to be invoked from the outside. By wrapping 
 these system services inside controllable environments (wrapping is just a fancy 
 way of saying we are placing a stronger layer of control between the user and 
 the system services), we are less likely to make a goof, and hence our code 
 is more robust (a fancy term we developers like to use to say something 
 is bullet-proof or goof-proof), and it is also easier to debug if we 
 do discover an error.

 

 The following code is designed to work with either a standard Textbox or Rich Textbox 
 (the RTB is enabled via the Project/Components menu and checking the 
 Microsoft Rich Textbox Control 6.0 item).

 

 All you need to do is add the following 3 functions:

 

 
 
 '*******************

 ' GetLineCount

 ' Get the line count

 '*******************

 Public Function  
 GetLineCount
 (tBox 
 As Object
 As Long

   GetLineCount 
 = SendMessageByNum(
 tBox.hwnd, 
 EM_GETLINECOUNT, 0&, 0&)

 End Function

 

 
 '*******************

 ' GetLineNum

 ' Get current line number

 '*******************

 Public Function  
 GetLineNum(
 tBox 
 As  Object
 As Long

   GetLineNum = 
 SendMessageByNum(
 tBox.hwnd, EM_LINEFROMCHAR
 tBox.SelStart, 
 0&)

 End Function

 

 
 '*******************

 ' GetColPos

 ' Get current Column

 '*******************

 Public Function  
 GetColPos(
 tBox 
 As Object
 As Long

   GetColPos = 
 tBox.SelStart - 
 SendMessageByNum(
 tBox.hwnd, EM_LINEINDEX
 
 -1&, 
 0&)

 End Function

 

 

 By declaring tBox as Object 
 instead of a specific TextBox or RichTextbox, we can now use these 
 functions for either, though you can surely define them specifically for your 
 object, which will result in slightly faster-running code.

 

 Some people prefer line numbers and column positions to start with 1, not 0. 
 In this case, all you need to do is add 1 to the results of GetLineNum() 
 and GetColPos().

 

How easy are they to invoke? Almost too easy. Suppose we have 3 labels on a form 
named lblLines, which will report the line count in the textbox, a label 
named lblRow to report the current row, and a label named lblCol 
to report the column position in the row. These are in place to service a Rich 
Textbox control named rtbText (although I am using labels in this 
example, you can just as easily designate panels in a Statusbar control as the 
targets). Suppose further that we want to report 
line and column numbers from 1, not zero. We could write a subroutine to gather 
all three pieces of information, or just the items we require. Suppose we wanted 
all three. We would need only the following 3 lines of code:



Me.lblLines.Caption 
= "Lines: " & CStr(GetLineCount(Me.rtbText))

Me.lblRow.Caption = "Row: " & CStr(GetLineNum(Me.rtbText) + 1)

Me.lblCol.Caption = "Col: " & CStr(GetColPos(Me.rtbText) + 1)



The final thing we may want to do is pretty up 
the display. Suppose you have written a routine that searches through the text for 
a character sequence, and then you want to display it on the screen. Normally when you 
use the SelStart properties of the textbox, it tends to display at the bottom of 
the textbox and looks kind of goofy. It looks a whole lot better if it is displayed 
near the top of the Textbox, except when the selection is forced down due to 
being near the bottom of the text anyway.

 

The fast trick around this is so simple that a lot of people never think of 
it. Simply first set the SelStart property of the text box to the very bottom of the 
text (tBox.SelStart = Len(tBox.Text)
and then point SelStart to the text you actually want pointed to. But it would be even nicer 
if it were possible to display the target text a couple of line down from the top, so the user could 
see what preceded it. All this can be accomplished with the following subroutine:

 

 
 
 '*******************

 ' AdjustTextDisplay

 ' place current position

 ' at top of display, and

 ' scroll display up 2 lines

 '*******************

 Public Sub  
 AdjustTextDisplay(
 tBox 
 As Object)

   Dim cPos 
 As Long

   Dim  
 cLen 
 As Long

 

   With  
 tBox

     cPos 
 = .SelStart       'Save selection

     cLen 
 = .SelLength       

 'Save anything highlighted

     .SelStart = Len(.Text)  'bottom 
 of text

     
 .SelStart = cPos  
       'force top of display

     
 .SelLength = cLen     
'reselecting any selection

     
 Call SendMessageByNum(
 tBox.hwnd, 
 EM_LINESCROLL
 0&, 
 -2&)

   End With

 End Sub

 

 

 
This subroutine will even work if the text box contains only one line of text, 
because if the API sub-system finds that the text box cannot actually scroll, it 
will not try to force it.



If you know how many lines will typically be displayed in the text box, you could actually display 
the selected text in the middle of the text box by replacing the -2& parameter in the 
SendMessageByNum() invocation, which tells the control to scroll up 2 lines without 
changing the selection point, to the number of lines displayable in the textbox divided by two.



And that is all there is to it. It is very simple, very fast, and very efficient.



- David


Download this snippet    Add to My Saved Code

Get Row and Column positions Fast in a Textbox or Rich Textbox (clean-up) Comments

No comments have been posted about Get Row and Column positions Fast in a Textbox or Rich Textbox (clean-up). Why not be the first to post a comment about Get Row and Column positions Fast in a Textbox or Rich Textbox (clean-up).

Post your comment

Subject:
Message:
0/1000 characters