From Team Developer SqlWindows Wiki
Jump to: navigation, search

MultiLine field & Rich Text


Pointer2.png How to place the cursor at a specific location in the text, text selection and insertion Pointer.png

This applies for datafields and multilines.

Using Windows API messages you can do the following:

  • Set the cursor/caret at a specific location within the text
  • Select a portion or all of the text
  • Insert text at a specific location within the text
  • Replace a preselected portion of text with another text
  • Delete a preselected portion of text
  • Get the current location/selection of text

First define these WinApi constants

   Number: EM_GETSEL         = 0x00B0
   Number: EM_SETSEL         = 0x00B1
   Number: EM_SCROLLCARET    = 0x00B7
   Number: EM_REPLACESEL     = 0x00C2

Place the cursor/caret at a specific character location

The first character position from the left is 0 (zero).
To position the cursor/caret to a specific location, send EM_SETSEL message to the field having wParam and lParam the same value.

   ! Place the cursor at position 200 in the text. Set left and right position at the same value
   Set nLeftPos = 200
   Set nRightPos = 200
   Call SalSendMsg( hWndField, EM_SETSEL, nLeftPos, nRightPos )
   ! When the cursor is out of view, the field does not scroll automatically, so you should scroll it by using :
   Call SalSendMsg( hWndField, EM_SCROLLCARET, 0, 0 )

To set the cursor at the end of the text, use -1 in wParam and lParam.

Select portion of the text

For this you have to define the starting and ending character position.
The wParam holds the start and lParam the end position.

   Set nLeftPos = 200
   Set nRightPos = 210
   Call SalSendMsg( hWndField, EM_SETSEL, nLeftPos, nRightPos )

To select all the text in the field, use wParam = 0 and lParam = -1

Deselect text

To deselect the text, use -1 as value for wParam and lParam.

   Call SalSendMsg( hWndField, EM_SETSEL, -1, -1 )

Insert text at a specific location

First place the cursor at the location you want to insert new text (see above).
Then send the EM_REPLACESEL message to the field using VisSendMsgString.

   ! Place the cursor at position 200 in the text. Set left and right position at the same value
   Set nLeftPos = 200
   Set nRightPos = 200
   Call SalSendMsg( hWndField, EM_SETSEL, nLeftPos, nRightPos )
   ! Now insert some text at the current caret position
   Call VisSendMsgString( hWndField, EM_REPLACESEL, TRUE, "This will be inserted" )

Replace text which is selected

Like above except use the appropriate nLeftPos and nRightPos values.
So first select the part of the text and then send the EM_REPLACESEL message.

Get the current position/selection boundaries

Send the EM_GETSEL message to the field.
It will return the start and end position of the cursor/caret.
The return value contains the start position in Low number and end position in the High number.

   Set nReturn = SalSendMsg( hWndField, EM_GETSEL, 0, 0 )
   Set nLeftPos = SalNumberLow( nReturn ) 
   Set nRightPos = SalNumberHigh( nReturn ) 

If no selections are performed above, you should first set the focus to the field

   Call SalSetFocus( hWndField )

Here you can download a sample:

Pointer2.png How to make a scrollable but disabled multiline Pointer.png

When you disable the field, the scrollbars are also disabled.

Use this instead to have scrolling

   Call VisWinSetFlags ( hWndField, WF_DisplayOnly, TRUE )

Pointer2.png Fast and smooth autoscroll multiline text appending Pointer.png

A common way to append text to a multiline field is to concatenate the text to the multiline field.
Like this:

   Set mlOutput = mlOutput || "Some text to append"

In some cases you could need to show the appended text, to put it into view.
For instance when displaying large amounts of text like for logging/tracing, a realtime and automatic scroll into view is preferable.

To do this, you need to programatically move the vertical thumb of the scrollbar to the bottom after the text is populated.

This is done by sending a message to the multiline field like this:

! Used WinApi constants
Number: WM_VSCROLL     = 0x0115
Number: SB_BOTTOM      = 7

! Scroll the vertical thumb scrollbar to the bottom
Call SalSendMsg( mlOutput, WM_VSCROLL, SB_BOTTOM, NUMBER_Null )

This works, the last text line in the multiline will be scrolled into view.

But when looking closely while populating, the multiline does this not very smoothly. For example the scroll thumb seems to
jump from top to bottom and the text has a flicker. This gives a somewhat unprofessional look & feel.
But another issue: the population is also not as fast as it could be.

To solve this we need another way to append the text. What actually happens when you append text to the multiline like above is that
the field is internally cleared. It is repopulated with the complete new text on every append. This results in the scrollbar jump
to the top and bottom when the new text is placed into the field and causes the text flickering.

Using WinApi, we can actually insert a new text at a specific position in the multiline field.
Position the caret (cursor) at the end of the text in the multiline and then insert the text.

First we need to position the caret at the end, doing this:

! Used WinApi constant
Number: EM_SETSEL    = 0x00B1

! Set the caret at the end of the current text in the multiline
Call SalSendMsg( mlOutput, EM_SETSEL, -1, -1 )

Now we need to insert the text at caret position:

! Used WinApi constant
Number: EM_REPLACESEL = 0x00C2

! Insert the text at the caret position. This will keep the text in the field and only insert the text at the end
! Include vtmisc.apl for the Vis function
Call VisSendMsgString( mlOutput, EM_REPLACESEL, TRUE, "Text to append" )

After this, the text is appended in the field. Only next action is to scroll it into view by sending WM_VSCROLL to the field
as described above.

This in fact will not only make the population smoother and less flicker, but also much faster.

Here you can download a sample:

Pointer2.png RichText control (RTF) as borderless display field Pointer.png

Starting with TD 5.2 we have a native RichText control available.
Using this control we can display and edit complex documents containing text formatting and images.

Though normally used as edit control, it can also be used to display-only complex visuals on your screens.
For instance, you can combine images and text having different fonts and enhancements in one place.


You might think using the RichText control just to display RTF text is straightforward.
But unfortunately, to let the control behave as a borderless field does not work out of the box.

Even when all properties to remove elements from the control are set, the control still has a border:

RTFControlDisplay Attributes.png

Have a look of the technical architecture of the control using WinSpy:

RTFControl WinSpy.png

The actual RTF control is a child of the Gupta Rich Text control object, having classname "RICHEDIT50W".
This object has the WS_BORDER style and the extended style WS_EX_CLIENTEDGE, which is in fact the border we see.

So, to have the control be blending into the form without border we must remove these styles.
We do this by finding the window handle of the object having the classname "RICHEDIT50W" and then remove the style
from that window. Here how to do this:

   Set hWndRTFControl = FindWindowExW( rtfControl, hWndNULL, "RICHEDIT50W", STRING_Null )
   If hWndRTFControl
      ! remove border and clientedge styles
      Call SetWindowLongA( hWndRTFControl, GWL_STYLE, GetWindowLongA( hWndRTFControl, GWL_STYLE ) - WS_BORDER  )
      Call SetWindowLongA( hWndRTFControl, GWL_EXSTYLE, GetWindowLongA( hWndRTFControl, GWL_EXSTYLE ) - WS_EX_CLIENTEDGE )

After the styles are removed, there still seems to be a border displayed.
This is caused by the fact that after removing the styles, the object is not property refreshed.
Unfortunately, it does not help to invalidate and update the window.

The only working trick is to resize the object, even by one pixel to force the complete object to be correctly repainted.
When this is done on the creating of the object, this will be no problem:

   Call SalGetWindowSize( rtfControl, nW, nH )
   Call SalSetWindowSize( rtfControl, nW, nH + 0.01 )

   ! Set the object back to the original size
   Call SalSetWindowSize( rtfControl, nW, nH )

Now we have a borderless control which can be populated with RTF text.

An sample implementation (TD 5.2 and higher) of this can be downloaded here:

The sample also shows a way to dynamically change the RTF text to be displayed in the control.