Company logo

FOXSTUFF

Advice, tips, techniques and downloads for Visual Foxpro developers.

Home | Contact

Copying and spell-checking RTF data via the clipboard

Rich Text Format can liven up your applications. But copying it to other programs isn't as easy as you might think.

By Phil Hawkins

In the Samples\Solutions application that comes with Visual FoxPro, there is a good example of the use of the Microsoft Rich Text Format (RTF) ActiveX control. This control allows your users to apply bold, italic and colour to their texts, as well as changing the fonts and font size. It's an attractive feature that appeals to many end users, as it helps make their VFP applications more consistent with other programs such as Word and Excel.

How does RTF work?

RTF is stored in your database tables as ordinary text, but it contains special control characters that are interpreted when the data is displayed. Here's an example of how the text might look in a table:

{\rtf1\ansi\ansicpg1252\deff0{\fonttbl{\f0\fnil\
fcharset0Arial Narrow;}{\f1\fnil\fcharset0 Arial;}}
{\colortbl ;\red0\green0\blue255;} \viewkind4\uc1
\pard\lang2057\f0\fs28This text is set in Arial
 Narrow 14pt. This is\i italic, \i0 this is \b bold,
 \cf1\b0 and this phrase is blue.\cf0\f1\fs18 \par }

Figure 1 shows how this same text would appear in the Rich Text control.

Figure 1: Rich text in a VFP form

The RTF example in the Solutions application uses the Rich Text ActiveX control and a handy toolbar. To make it work, you need the library file RICHTX32.OCX to be present in the System folder. The control will allow your users to enter, edit and store RTF data. You can also give them the option of storing plain text as well as the RTF text, distinguishing between these by means of the Text and TextRTF properties. Plain text will be much more convenient if the users want to include it in a VFP report, for example.

Give 'em an inch ...

You know how it is. Users always want something more. What about spell checking? What about printing? Here's where the problems start.

At present, it isn't possible to put RTF data into a VFP report (if you know different, I'd love to hear about it). To print RTF data, you have to copy it elsewhere, such as to a Word document and print it from there. For spell checking, it's probably most convenient for your users to use the Word spell checker since they know that already, but how do you initiate this from your VFP application and store the results there afterwards, without converting your RTF data to plain text in the process? Again, not easy.

Using the clipboard

One option would be to save the RTF data as a temporary RTF file and then import this into a Word document, using the SaveFile method of the Rich Text control. But this is a cumbersome procedure. A better approach would be to use the clipboard.

There are two possible ways to copy and paste data from the Rich Text control to a Word document via the clipboard. One is by program code using the VFP system variable _CLIPTEXT. The other is by using the popup menu that appears when you right-click in the control (for this to work, you need to set the control's AutoVerb property to .T.; more on this later). Both ways have their advantages and disadvantages. The right-click menu supports RTF but has limited scope for automation in your applications, whilst _CLIPTEXT does not support RTF but would be considerably more flexible if it did.

At this point, I need to explain a little about how data is stored on the clipboard. When you copy data from the Rich Text control to the clipboard by selecting Copy from the right-click menu, the same data will be posted on the clipboard many times in various formats, depending on its complexity. For a simple text sample such as the one shown above, you typically get nine formats: Text, Unicode Text, Locale, OEMText, DataObject, Rich Text Format, Rich Text Format Without Objects, RTF As Text and Ole Private Data.

You can see these format names listed under the View menu in the Windows 2000 Clipbook Viewer (to open the viewer, click Start, Run and enter CLIPBRD). Only a few of these formats - basically the character-related ones - are enabled in the viewer. The viewer doesn't support RTF or other fancy formats, so you can't see the RTF attributes like bold and italic, but you can tell by looking at the list of posted formats that everything is there.

When you paste this text into another RTF-aware application such as Word, it selects the most sophisticated of the available formats on the clipboard and discards the rest. As a result, you get fully formatted text appearing in your Word document. No problem.

However, when you use _CLIPTEXT, you only get the basic character-related formats on the clipboard, even if the data you are posting is RTF. When you paste this into Word, only these character-related formats are available, so Word does its best and what you get is the version of the text that has control characters scattered through it.

Windows API to the rescue

One way to improve this situation is to use Windows API functions to supplement what _CLIPTEXT does, so that RTF data is posted in RTF format on the clipboard. Let's see an example of this technique in action. Suppose you want to provide the users with a command button that copies the contents of the Rich Text control to a Word document, so that it can be printed.

First, you need to declare certain API functions. You only need to do this once, so you could do it at the start of your main program. The following functions will cover all the sample code discussed in this article:

DECLARE INTEGER CloseClipboard IN win32api
DECLARE INTEGER EmptyClipboard IN win32api
DECLARE INTEGER GetActiveWindow IN win32api
DECLARE INTEGER GetClipboardData IN win32ap iINTEGER
DECLARE INTEGER IsClipboardFormatAvailable IN win32apiINTEGER
DECLARE INTEGER OpenClipboard IN win32apiINTEGER
DECLARE INTEGER RegisterClipboardFormat IN win32apiSTRING ;
  STRING @, INTEGER, INTEGER
DECLARE INTEGER SetClipboardData IN win32api INTEGER, INTEGER

As always with Windows API functions, remember that the function names are case-sensitive.

Having declared the functions, we can run the following code (which could be in the Click event of a command button) to copy RTF data to a Word document (in the following code examples, assume that oleRTF is the name of the Rich Text control on your form):

* First we post the RTF text to the  clipboard in
* the ordinary text formats that are supported
* by _CLIPTEXT. There are about 4 or 5 of them.
_CLIPTEXT = thisform.oleRTF.TextRTF

* Obtain a handle for the active window.
LOCAL hActiveWindow
hActiveWindow = GetActiveWindow()

* Open the clipboard with an association to the 
* active window.
= OpenClipboard(hActiveWindow)

* Register RTF as a valid clipboard format. If
* already registered this will return the
* existing handle, otherwise generate a new one.
LOCAL hRTF
hRTF = RegisterClipboardFormat("Rich Text Format")

* Obtain handle for data posted on the clipboard
* in text format.
LOCAL hData
#DEFINE CF_TEXT 1
hData = GetClipboardData(CF_TEXT)

* Re-post the same memory block to the clipboard
* as RTF, then close it.
= SetClipboardData(hRTF, hData)
= CloseClipboard()

* Create a Word application object and store
* its reference.
LOCAL loWord
loWord = CREATEOBJECT("word.application")

* Create a document and store its reference.
LOCAL loDoc
loDoc = loWord.Documents.Add()

* Paste the text into the word document and make
* sure it's visible.
loWord.Selection.Paste()
loWord.Visible = .T.

* Tidy up.
= OpenClipboard(hActiveWindow)
= EmptyClipboard()
= CloseClipboard()

Why bother to clear the clipboard? It's probably safest because of the non-standard nature of this posting. If the user were to paste it manually into an application that doesn't support RTF, such as Windows Notebook, it would come out with all the RTF rubbish instead of as just plain text. Let them use the right-click menu if they want to do that. To enable the right-click menu in the Rich Text control, set the AutoVerbMenu property to true, or tick the appropriate box in the control's Property window (which you reach by right-clicking in the control at design time).

If you have problems with your application staying in front while the document is created behind it, you could minimise your application by using _SCREEN.WindowState=1, but users might then close down the document, not notice the minimised application and load it again unnecessarily. A better way is to reduce the size of the application to a 'normal' state window, neither maximised nor minimised:

_screen.WindowState = 0
_screen.Height = 300
_screen.Width = 400

In the above example, object references to the Word application and the document are both stored locally. This is the slowest option, because every time the user clicks the button, they have to wait for Word to load, which is by far the most time-consuming part of the process. However, if you want to store the Word object reference globally, beware of the user closing down Word while you are still holding the reference within your application. You can allow for this by trapping OLE error 1426 in your error routine and re-creating the Word application object if necessary.

Copying multiple texts

I'll just mention one other issue that will be important if you are attempting to copy two or more RTF texts into the same document. You cannot concatenate multiple texts from the control's TextRTF property and then copy the whole lot to the clipboard in one go. That won't work because each RTF segment is a complete entity. If you stick two of them together, the results are unpredictable. The second segment may inherit unwanted attributes from the first segment, or you may end up only posting the first segment. The only way I have found to concatenate text in this way is with multiple copy-and-paste operations into the same document. This works because the insertion point in the Word document remains at the end of the previously pasted segment. It sounds cumbersome, but once Word is loaded up, it is very quick.

You could also automate printing in the above example by executing the document's Printout or PrintPreview methods. I'll leave these niceties to your ingenuity.

Check your spelling

Spell checking RTF data is much more problematical. First, let's look at how you might do it for plain text:

* Create a Word application object and store
* its reference.
LOCAL loWord
loWord = CREATEOBJECT("word.application")

* Create a document and store its reference.
LOCAL loDoc
loDoc = loWord.Documents.Add()

* Copy plain text to the clipboard.
_CLIPTEXT = thisform.oleRTF.Text

* Paste the text into the word document and
* make sure it's visible.
loWord.Selection.Paste()
loWord.Visible = .T.

* Call the spell checker.
loDoc.CheckSpelling()

* Copy the text back to the VFP control.
thisform.oleRTF.Text = loDoc.content.text
  
* Close the Word doc without prompting to
* save it
loDoc.close(0)

As you can see, in this example the clipboard is used to copy the text to the spell checker, but not back again. I haven't explored other combinations, but the above certainly works. After this, you could either make the Word application invisible (loWord.Visible = .F.) or, if you were holding its object reference globally, you could just minimise it (loWord.WindowState = 2). Note that the settings of the WindowState property in the Word application object are not consistent with those of VFP form objects.

If, in the above example, you used the control's TextRTF property to feed RTF text to the spell checker via _CLIPTEXT, naturally you would see all the RTF control characters in the spell check window. Most of these are ignored by the spell checker, but some aren't. You really don't want to give sticky-fingered users the opportunity to change RTF controls interactively, otherwise the data may become corrupted beyond repair.

To display RTF data properly in the spell checker, we therefore need to combine the above techniques, as follows:

_CLIPTEXT = thisform.oleRTF.TextRTF
LOCAL hActiveWindow
hActiveWindow = GetActiveWindow()
= OpenClipboard(hActiveWindow)
LOCAL hRTF
hRTF = RegisterClipboardFormat("Rich Text Format")
LOCAL hData
#DEFINE CF_TEXT 1
hData = GetClipboardData(CF_TEXT)
= SetClipboardData(hRTF, hData)
= CloseClipboard()
LOCAL loWord
loWord = CREATEOBJECT("word.application")
LOCAL loDoc
loDoc = loWord.Documents.Add()
loWord.Selection.Paste()
loWord.Visible = .T.
loDoc.CheckSpelling()

What next? We can't copy the text straight back to the TextRTF property. No matter how you juggle this around, the RTF attributes are lost and you are left with plain text. So let's use the clipboard as a halfway house again. First, it's a relatively simple matter to re-select the text in the Word document and copy it back to the clipboard:

* Select the document text for copying
* back to the clipboard.
LOCAL lnStart, lnEnd
lnStart = loDoc.Content.Start
lnEnd = loDoc.Content.End
= loDoc.Range(lnStart, lnEnd).Select

* Having effectively done a "Select All"
* we can now copy back to the clipboard.
loWord.Selection.Copy()

This works because the Edit/Copy facility in Word posts data to the clipboard in many different formats, including RTF. The difficult part is pasting this data back into the Rich Text control in your VFP application.

* Obtain RTF handle from the clipboard as before
LOCAL hRTF
hRTF = RegisterClipboardFormat("Rich Text Format")
  
* Open the clipboard and check that RTF data
* is available.
LOCAL hData
IF OpenClipboard(0) <> 0
  IF IsClipboardFormatAvailable(hRTF) <> 0
  
    * Assuming the clipboard was opened successfully,
    * and there is RTF data available on it, obtain
    * a handle for the RTF memory block.
    hData = GetClipboardData(hRTF)
  
    * Retrieve the RTF character string. The two zero
    * characters are used as a text terminator in the
    * clipboard memory block.
    thisform.oleRTF.TextRTF = ;
    Mem2String(hData, CHR(0)+CHR(0))
  
  ENDIF
  
  * Remember to close the clipboard. Nasty things
  * happen otherwise.
  = CloseClipboard()
ENDIF
  
* Close the Word document without prompting the
* user to save it, then make Word application
* object minimised.
loDoc.close(0)
loWord.WindowState = 2
MESSAGEBOX("Spell check complete")

The Mem2String() function used in this code can be coded in various ways, for example as a method on the form, or as a global code function. The following code was developed with the help of some related examples at http://www.news2news.com , which I can heartily recommend as worthwhile reading if you are contemplating using API functions in Visual FoxPro.

* Mem2String
* This method retrieves a character string from
* the specified memory block address, assuming
* that the end of the memory block is indicated
* by a terminator string supplied as the second
* parameter.
  
FUNCTION Mem2String
LPARAMETERS lnMemBlock, lcEndSequence
#DEFINE BUFFER_SIZE 16
#DEFINE EMPTY_BUFFER
REPLICATE(Chr(0), BUFFER_SIZE)
LOCAL lnPtr, lcResult, lcBuffer, lnPos
lnPtr = lnMemBlock
lcResult = ""
DO WHILE .T.
  lcBuffer = EMPTY_BUFFER
  = RtlMoveMemory(@lcBuffer, lnPtr, BUFFER_SIZE)
  lnPos = AT(lcEndSequence, lcBuffer)
  IF lnPos > 0
    lcResult = ;
    lcResult + SUBSTR(lcBuffer, 1, lnPos-1)
    RETURN lcResult
  ELSE
    lcResult = lcResult + lcBuffer
    lnPtr = lnPtr + BUFFER_SIZE
  ENDIF
ENDDO
ENDFUNC

Troubleshooting

Sometimes during spell checking, the inactive VFP window stays on top, preventing the user from seeing the active spell check window behind. If you have this problem, the PC can become completely locked. To work around it, you can minimise the application window whilst spell checking is going on. Notice that identical values of the WindowState property in the Word application object and in VFP form objects have opposite meanings - this isn't a mistake.

loWord.WindowState = 1 && Maximised
_screen.WindowState = 1 && Minimised

What do you do with the Word application, after you have finished with it? It probably isn't a good idea to make it invisible, and you might not want to close it in case the user has other documents open. It's probably best to minimise it. Maximise your application first if you don't want the user to get a glimpse of the PC desktop in between:

_screen.WindowState = 2 && Maximised
loWord.WindowState = 2 && Minimised

And finally, if a user gets an OLE error message with error code 0x80040154, this is probably because the RICHTX32.OCX file isn't registered on the user's system. You can easily fix this by running the REGSVR32 utility from the Windows Run dialogue:

REGSVR32 C:\Winnt\System32\RICHTX32.OCX

(Substitute the path to the System folder appropriate for the user's machine.)

Over to you

Good luck with your RTF endeavours. It's a whizzy feature and the users love it. If you have any comments on this article, please contact me at phil@granacot.co.uk

Phil Hawkins, Granacot Computing Ltd., Oxfordshire, UK (Phil Hawkins is a specialist in client/server applications for the publishing industry.)

Mike Lewis Consultants Ltd. May 2004.
Article and program code Phil Hawkins, 2004

More Visual FoxPro articles | Crystal Reports articles | Recommended books | Visual FoxPro consultancy | Contact us

FoxStuff is maintained by Mike Lewis Consultants Ltd. as a service to the VFP community. Feel free to download and use any code or components, and to pass around copies of the articles (but please do not remove our copyright notices or disclaimers).

The information given on this site has been carefully checked and is believed to be correct, but no legal liability can be accepted for its use. Do not use code, components or techniques unless you are satisfied that they will work correctly in your applications.

© Copyright Mike Lewis Consultants Ltd.