Status Methods and Properties


Whoever heard of an editor that doesn’t display status such as the current line and column? I’m happy to say that the example of such an editor mentioned in the first edition of this book no longer applies, but longtime Visual Basic programmers can probably guess which one I’m talking about.


Edwina displays its line and column status, getting the information from the XEditor control, which, in turn, gets it by sending messages to the internal RichEdit control of the RichTextBox control. RichEdit knows a lot about its current status, but the way to get at the status is far from straightforward.


The XEditor control can supply status information through properties. The Lines, Columns, and Characters properties provide the total number of lines, columns, and characters. Similarly, the Line, Column, and Character properties provide the current line, column, and character. The Percent property supplies the current position as a percentage of the total text length.


The client could check these properties repeatedly to update its display, possibly with a Timer event, or by checking events that might change the position such as KeyUp or MouseDown. The second technique is how I handled it with the old CEditor class. But XEditor can raise events. It knows everything that might cause a position change, and it can give the client one event with all the required information any time something happens. The client doesn’t have to worry about what caused the event.


The StatusEvent procedure takes care of getting everything the client might need to know and passing it on to the client. Here’s the code:

Private Sub StatusEvent()
Dim iLine As Long, cLine As Long, iCol As Long, i As Long
Dim cCol As Long, iChar As Long, cChar As Long

' Count of lines
cLine = SendMessage(txt.hWnd, EM_GETLINECOUNT, ByVal 0&, ByVal 0&)
' Current line (zero adjusted)
iLine = 1 + txt.GetLineFromChar(txt.SelStart)
' Current character
iChar = txt.SelStart + 1
' Length is position of last line plus length of last line
cChar = SendMessage(txt.hWnd, EM_LINEINDEX, ByVal cLine - 1, ByVal 0&)
i = SendMessage(txt.hWnd, EM_LINELENGTH, ByVal cChar - 1, ByVal 0&)
cChar = cChar + i
' Column count is current line length
cCol = SendMessage(txt.hWnd, EM_LINELENGTH, ByVal iChar - 1, ByVal 0&)
' Column is current position minus position of line start
i = SendMessage(txt.hWnd, EM_LINEINDEX, ByVal iLine - 1, ByVal 0&)
iCol = iChar - i
RaiseEvent StatusChange(iLine, cLine, iCol, cCol, iChar, cChar, DirtyBit)
End Sub

What Windows has isn’t exactly what you need, but it is what you need to get what you need. Most of the information comes from SendMessage calls, but the
current line comes from the RichTextBox GetLineFromChar method and the current character position is simply the zero-adjusted SelStart property. Notice that I chose to define Columns as the length of the current line; you could make a case for defining it as the width of the longest line in the text box or as the current screen width of the text box, based on the average character width of the current font.


This code could look cleaner if I had used the existing Line, Lines, Column, Columns, Character, and Characters properties, all of which contain essentially the same code. It would have saved file space in EDITOR.CTL and on this page, but it would have wasted code. That’s because several pieces of the information depend on others. You’d end up sending more Windows messages, as you can confirm by studying the properties. Of course, there might be other cases in which the client needs only part of the position information, which is why XEditor also provides each piece as a separate property.


I started out by calling StatusEvent from every event procedure that might cause a position change—MouseDown, KeyUp, and so on. But after some experimentation, I discovered that these events all triggered the SelChange event. That’s the only movement event where I needed to raise the StatusChange event. I also called StatusEvent from the Property Let of the DirtyBit property. The DirtyBit property is set in several other places such as LoadFile and UserControl_Load because these events indirectly cause the status to change.


Edwina handles the StatusChange event with the following code:

Private Sub edit_StatusChange(LineCur As Long, LineCount As Long, _
ColumnCur As Long, ColumnCount As Long, _
CharacterCur As Long, _
CharacterCount As Long, DirtyBit As Boolean)
With statEdit
.Panels(epLine).Text = "Line: " & FmtInt(LineCur, 4) & _
" / " & FmtInt(LineCount, 4, True)
.Panels(epCol).Text = "Column: " & FmtInt(ColumnCur, 3) & _
" / " & FmtInt(ColumnCount, 3, True)
Dim iPercent As Integer
iPercent = (CharacterCur / (CharacterCount + 1)) * 100
.Panels(epPercent).Text = "Percent: " & FmtInt(iPercent, 3)
.Panels(epSav).Enabled = DirtyBit
.Panels(epIns).Enabled = Not edit.OverWrite
End With
With barEdit
.Buttons(sBold).Value = _
IIf(edit.SelBold, tbrPressed, tbrUnpressed)
.Buttons(sItalic).Value = _
IIf(edit.SelItalic, tbrPressed, tbrUnpressed)
.Buttons(sUnderline).Value = _
IIf(edit.SelUnderline, tbrPressed, tbrUnpressed)
End With
End Sub

The XEditor control also has LineText, LinePosition, and LineLength methods that return the text, the character position, and the length of either the current line or a selected line. Here’s the code for LineLength:

Property Get LineLength(Optional iLine As Long = -1) As Long
If iLine = -1 Then iLine = Line
LineLength = SendMessage(txt.hWnd, EM_LINELENGTH, _
ByVal LinePosition(iLine), ByVal 0&)
End Property

Basic must be the only computer language ever invented whose formatting features don’t allow you to right-justify integers—that is, to generate a simple list of numbers that looks like this:

 345
62
9
1053

Just try doing that with the Format function or the Print statement. A Basic program manager once told me that the ability to right-justify numbers wasn’t worth adding because spaces don’t have the same width as digits in proportional fonts. Well, yes, but…. Fortunately, this quirk is more an annoyance than a limitation. The FmtInt function (UTILITY.BAS) allows you to left-justify or right-justify an integer in a field of a given width. The heart of this function consists of the following expression:

Right$(Space$(iWidth) & iVal, iWidth)

You prepend the maximum number of spaces and trim off the extra. Not difficult, but it shouldn’t be necessary.


Notice that the LineLength property can access the Line property without qualification. For example, you could call it with either of these lines:

c = LineLength        ' Get length of current line
c = LineLength(5) ' Get length of line 5

Many of the other status properties allow indexing. For example, you can call the Line or Column property with an index that is the current character position in the file.