VFP's Editor Code RTF2HTML (Part 3)

Version: 2.02.00 - last Update: Thursday, July 24, 2014, 23:45:00 (corrected some faults)

Previous ChapterToolbox Home (TOC)Next Chapter


This thread is all about how to get VFP’s syntax colored code to an HTML-formatted Blog like this is.

Intro

Today let us talk about how to access the Windows clipboard using Windows API calls and let's extend our RTF Evaluator so that we are able to retrieve RTF formatted data from the Windows clipboard directly.

VFP Goes Clipboard

We all know VFP's _CLIPTEXT system variable. One of the simplest ways to access it's content is to echo it to your desktop like so:

? _CLIPTEXT.

Well, most of the time we get some more or less readable text echoed to the screen, but sometimes nothing happens. "Nothing new" you may think, "it depends on what was stuffed into the clipboard before". Yes, you are absolutely right! The Windows clipboard is a pretty complex vehicle enabled to transport an almost unlimited amount of different data formats from one application to another. Even within VFP we are not limited to editor sessions only, but can copy & paste selected controls from one designer session to another one like shown in figure #1 below:

CLP_Demo001 
Figure #1: Copying this selection clears VFP's _CLIPTEXT

After copying (Ctrl+C) these selected controls VFP's _CLIPTEXT system variable is empty (?_CLIPTEXT echoes nothing). The Windows clipboard obviously isn't, because we are able to paste the selected controls into some other form designer session even running in another VFP instance! A real cool thing is: We can emulate that kind of VFP control copy&pasting even programmatically! Just check out the following instructions (I will tell you about all of that later in detail).

A Little Sidekick:
How to Manipulate Fox_Controls Programmatically

1st: Create a new form-designer instance and populate the form with some controls right from VFP's Form Controls toolbar like shown in figure #1 above.

2nd: Download the two code files of download#1 below to your hard-drive and unzip both programs called SaveFrmTemplate.Prg and LoadFrmTemplate.Prg.

Make your current control selections persistent! Reload your persistent control selections!
Download #1: FrmTemplates.Zip

 
Download FrmTemplates.Zip from here

Download #1: FrmTemplates.Zip

3rd: Maybe you want to assign shortcuts to your new programs like
ON KEY LABEL F11 DO SaveFrmTemplate.Prg
ON KEY LABEL F12 DO LoadFrmTemplete.Prg

4th: Select all (or some) controls on your new form and copy them to your clipboard (with Ctrl+C), then run your new SaveFrmTemplate.prg. Please notice that after you ran the first program the clipboard is cleared by the routine (to make the effect a little bit more outstanding ;-)

Finally: Open another form-designer instance. Now run the second program LoadFrmTemplate.Prg, then paste (Ctrl+V) the (programmatically!) added clipboard content into the designer - Cool? Well, maybe you are curious enough to open the generated clipboard-SCX dbf to have a peek :-)

Well, well, well: The bottom line is: VFP's _CLIPTEXT system variable only shows the tip of an iceberg called "The Windows Clipboard" !

My recommendation news2newsBefore we start exploring all aspects of the Windows clipboard that are important for us, let me give you a recommendation on Anatoliy Mogylevets Windows API site Using Win32 Functions in Visual FoxPro ! Anatoliy has piled up an incredible amount of valuable information and examples there. If you are seriously developing sophisticated VFP applications a membership is a must - well, IMHO :-) Therefore, if you need additional information about how to deal with Windows clipboard API calls, go there and read all about it!

The Windows Clipboard

This is the direct link to Microsoft's Developers Network dealing with the Clipboard MDN::Clipboard. To access the Windows clipboard directly using Windows API calls we have to open the Clipboard first. Then, at the end of our routine we have to close it accordingly. Thus, every VFP function or class code that deals with the Windows clipboard directly should be designed like so:

If OpenClipboard(0) <> 0
  
... code that manipulates the Windows clipboard
   = CloseClipboard()
ELSE
   THROW "Error opening Clipboard!"
ENDIF

Opening the clipboard like above can be compared to VFP's record locking mechanism. Thus, the API function name sounds a little bit misleading. IMHO LockClipboard() would have been a better choice naming this function. Anyway, we have to lock the clipboard because it is a Windows-global resource! Passing a <0> to the function associates the lock with the current (our VFP) task, preventing any other task from modifying the current clipboard contents. This is why we have to release our lock ASAP when we're done! Just like unlocking a VFP record after editing.

Clipboard Structure

To keep things simple, Windows clipboard contents could be compared to an array maintained by the operation system somewhere in it's global memory. Table #1 below shows the structure and content of the windows clipboard after copying (Ctrl+C) some selected source from a VFP editor session:

Format Id

Format Name Memory Object Pointer

49298

Rich Text Format 42010096

1

Text format 41270368
16 Locale Identifier handle 41262080
7 Text format with OEM charset 41277032
13 Unicode Text 77695976

Table #1: Windows Clipboard's internal contents

As you can see the copied content isn't stored within the structure but only referenced by a pointer pointing to a Windows memory object containing the actual data.

Clipboard Formats

Every distinct Windows clipboard content has a different structure, which is called a "clipboard format". There are two types of clipboard formats: standard formats and the registered formats. Each format has a unique format name and a corresponding (also unique) format ID. We can register our own user-defined format like so:

DECLARE INTEGER RegisterClipboardFormat IN user32;
    STRING lpszFormat
RegisterClipboardFormat(<new_format_name>)

All registered (user-defined) formats are identifiable by their hex-values which lie between 0xC000 and 0xFFFF. Let's say, we want to register a special data exchange clipboard format called "My Super Exchange Format". The integer return value of the RegisterClipboardFormat() API call represents our new Format ID, like stored in "nFormatID" shown below: nFormatID = RegisterClipboardFormat(; "My Super Exchange") The format ID of a user-defined clipboard format can be considered a system wide unique value representing the format name. Try to register the "Rich Text Format" on your own computer like so:
DECLARE INTEGER RegisterClipboardFormat ;
    IN user32 STRING lcFormat
? RegisterClipboardFormat("Rich Text Format")
What format ID do you get? On my notebook running XP Pro this is 49298 (0xC092 hexadecimal) .

After you retrieved the user defined format id you have to use it in all subsequent steps fetching and/or manipulating the corresponding clipboard data.

Table #2 below lists the standard clipboard formats (with fixed IDs) as well, as some of my user-defined/registered ones (orange text). Maybe you want to include the standard values in your H-files.

Constant ID Name
CF_TEXT 1 Text format
CF_BITMAP 2 Bitmap handle
CF_METAFILEPICT 3 Metafile Picture format
CF_SYLK 4 Microsoft Symbolic Link format
CF_DIF 5 Data Interchange Format
CF_TIFF 6 Tagged-image format
CF_OEMTEXT 7 Text format with OEM charset
CF_DIB 8 BITMAPINFO structure
CF_PALETTE 9 Color Palette handle
CF_PENDATA 10 Pen Computing format
CF_RIFF 11 Audio data format
CF_WAVE 12 Standard Wave format
CF_UNICODETEXT 13 Unicode Text
CF_ENHMETAFILE 14 Enhanced Metafile
CF_HDROP 15 List of files
CF_LOCALE 16 Locale Identifier handle
CF_DIBV5 17 BITMAPV5HEADER structure
CF_OWNERDISPLAY 128 Owner-display format
CF_DSPTEXT 129 Private Text format
CF_DSPBITMAP 130 Private Bitmap format
CF_DSPMETAFILEPICT 131 Private Metafile format
CF_DSPENHMETAFILE 142 Private Enhanced Metafile format
CF_PRIVATEFIRST 512 Private format first
CF_PRIVATELAST 767 Private format last
CF_GDIOBJFIRST 768 GDI object format first
CF_GDIOBJLAST 1023 GDI object format last
CF_USR_RICHTEXT 49298 (variant!) Rich Text Format
CF_FOX_CONTROL 49674 (variant!) FOX_CONTROL

Table #2: Standard Clipboard formats

Finally we still need to know some Windows API calls to access the real clipboard data correctly.
DECLARE INTEGER GetClipboardData ;
   
IN user32 INTEGER
uFormat
hMem = GetClipboardData(nFormatID)
retrieves a pointer called memory handle pointing to the actual data (a contiguous memory block holding the "precious" RTF-formatted text in our case). Gosh! The return value of
GetClipboardData() is an integer value only! Therefore, we need another API function to copy this Windows memory block into a VFP variable:
DECLARE RtlMoveMemory IN kernel32 ;
    STRING @cDestination,;
    INTEGER iSource,;
    INTEGER iLen
does the job for us if we can figure out how many bytes we have to copy: DECLARE INTEGER GlobalSize IN kernel32 INTEGER hMem allows us to query the size of any Windows memory object that is known by it's (integer) handle.
Hint: We will use VFP's SYS(2600) function instead of declaring and calling the Windows RtlMoveMemory() function like this:

cDestination = SYS(2600, m.iSource, m.iLen)

Okay, folks, this is all we have to know at the moment. Let's proceed and start to implement our findings.

Enhancing our RTF Evaluator Form

During part 2 oft this thread we created a tiny VFP form to test our first homegrown RTF sources. Now it is time to enhance this little tool to grab RTF formatted text directly from the Windows clipboard. As you will see, this is no big thing any more :-)

I named my own Evaluator form RtfEval.SCX. Let's stick to this name in this thread - replace it with your own filename wherever necessary.

Anyway, first, let's add a new form method called GetClipboard().

Next place another command button next to the [Refresh] button at the bottom of the form. Give it a caption called "GetClipData" and name it "oBtnGetClipData". Add the following code to the click event method of this command button: THISFORM.GetClipboard(). Figure 2# below shows our new form layout:

The new RTF Evaluator layout 

Figure #2: The new RTF Evaluator layout

Now let's implement our RTF retrieval. BTW: As long as your are testing code like in listing #1 shown below it is a good idea to have the following keyboard macros assigned: ON KEY LABEL F11 CloseClipboard() You might guess why :-)

Okay. Now add listing #1 to your form's new GetClipboard() method:

THISFORM.GetClipboard() Method

#DEFINE CF_USR_RICHTEXT 49298
*\\ You must check the RTF-format id;
    on your computer and replace the;
    value above with your local one!

LOCAL nIndex AS INTEGER
;
   ,  HMEM AS INTEGER
;
   ,  nSize AS INTEGER
;
   ,  cBuffer AS CHARACTER
IF OpenClipboard(0) <> 0
   IF IsClipboardFormatAvailable(CF_USR_RICHTEXT) > 0

      h
Mem = GetClipboardData(CF_USR_RICHTEXT)
      nSize = GlobalSize(m.hMem)
      cBuffer = ""
      IF
m.nSize <> 0
         cBuffer = REPLI(CHR
(0), m.nSize)
         TRY cBuffer
= ;
           SYS(2600, m.hMem, m.nSize)
         CATCH
cBuffer = ;
           "API-ERROR 'RtlMoveMemory()'failed!"

         ENDTRY 
     ENDIF
   ELSE
      cBuffer = "No RTF format on clipboard!" 
  ENDIF
   WITH THIS
      .oedit.VALUE
= m.cBuffer
      .RefreshRTF()
   ENDWITH
   = CloseClipboard()
ENDIF

Listing #1: Our new GetClipboard method

The click event method of your new "GetClipData" button should call this method like so: THISFORM.GetClipboard()

Next add listing #2 to your form's Load() method:

THISFORM.Load() Method

DECLARE INTEGER OpenClipboard IN user32 INTEGER hwnd
DECLARE INTEGER IsClipboardFormatAvailable ; IN user32 INTEGER uFormat
DECLARE INTEGER GetClipboardData ; IN user32 INTEGER uFormat
DECLARE INTEGER GlobalSize IN kernel32 INTEGER hMem DECLARE RtlMoveMemory IN kernel32 ; STRING @Dst, INTEGER Src, INTEGER nLen
DECLARE INTEGER CloseClipboard IN user32

Listing #2: Load event code

Finally it is a good idea to move all functionality out of any control and add it to a discrete form method. Our "Refresh" button still has functionality that I do not like to see there! Therefore, add another form method and move the value refresh of the Rich Text OCX in there like in listing #3.

THISFORM.RefreshRTF() Method

WITH THIS
   TRY
     
.oRTF.OBJECT.TextRTF = .oEdit.VALUE
   CATCH
      .oRTF.OBJECT.TextRTF = ;
           "{\rtf1 OCX-ERROR\par "+;
           ".oRTF.OBJECT.TextRTF "+;
           "= .oEdit.VALUE\par failed!}"
   ENDTRY
ENDWITH

Listing #3: RTF ActiveX refresh method

The click event method of your "Refresh" button now has to call this method like so: THISFORM.Refresh()

This is a more clean an flexible layout - IMHO!

Now test drive your enhanced RTF Evaluator form. Maybe your first results may be disappointing like shown in figures #3 through #5 below:

 What kind of gibberish is that?

Figure #3: ???? What kind of gibberish is that?

TRY/ENDTRY of GetClipboard() caught an error 

Figure #4: TRY/ENDTRY of GetClipboard() caught an error

RTF_Evaluator_Next_004 

Figure #5: TRY/ENDTRY of RefreshRTF() caught an error

This is what I've found out so far

As long as you are running this tool in a separate VFP instance (only reading the clipboard!), it works fine! Yes, I'm serious! In other words: You cannot write VFP source code (Ctrl+C) to the windows clipboard AND run this tool within the same VFP instance.

The difference between the error shown in figure #3 and the next one shown in figure #4 stems from how much source code you tried to copy. One line up to about 50 lines (I didn't count them) of VFP source code "only" trashes the RTF content. But sometimes this trash is to much for the OCX control to parse, causing the exception shown in figure #5. If you want too much (let's say you try to copy about 100 lines of VFP source code) when running the RTF-Evaluator in the the same VFP instance, the Win API call crashes like shown in figure #4 above!

Really, the funny part of the story is, that everything works fine, if we do the "copy job" in another VFP instance! Try this:

  • Start the RTF Evaluator tool in another "clean" VFP instance (Never touch the clipboard there!)
  • Switch back to your first VFP instance
  • Ctrl+C some syntax colored source code into the Windows clipboard
  • Switch back to your RTF-Evaluator and click the "GetClipData" button.

The screen shot below (figure #6) proofs that it works! But WHY?

Yes! It works!
Figure #6: Yes! It works! But is a little bit awkward

SO WHAT'S GOING ON HERE? ANY IDEAS? DOES ANYBODY GIVES A DAMN ABOUT IT?

Any suggestions - or even better: A SOLUTION welcome!
Download RtfEval.Zip

 


Download RTF Evaluator here.

Download #2: RtfEval_09.Zip V0.9 March/06/2008

That's all for now. I hope that someone can shed some light on this "VFP mystery" in the next future - hopefully!

Preview

In part 4 of this thread I will show you a simple but neat trick to add the missing back color to our HTML output. We will extend our RTF Evaluator with a HTML preview, add code to parse and translate RTF content into valid HTML output and finally add some facelift to the GUI.


Previous ChapterToolbox Home (TOC)Next Chapter

7 comments:

  1. Hi folks - today I found a pretty simple workaround/bugfix. You can read about it in part #5 of this thread. Go there, chapter "Working Around VFP's Clipboard Quirk" has the details :-)

    ReplyDelete
  2. I added this code in the load event :
    Declare integer RegisterClipboardFormat ..blah blah
    then fired the recognition for local machine RTF format to put this in a form's property, usable from anywhere later. This avoids to change all the code according to RTF Clip format local variations. How do you think?

    Christian - Bordeaux - France

    ReplyDelete
  3. Hi Christian,

    very good idea! Keep in mind that my old RTF-Evaluator form is miles away from being the perfect solution :-) Don't wait too much time on it!

    I hope that I will be able to publish my first public final version next week (at the end of chapter #6 of this thread). I wolud be glad if you could give it a try then and have a look on that thing :-)
    Best, Burkhard

    ReplyDelete
  4. Can't download FrmTemplates.zip :(

    ReplyDelete
  5. Sorry, Eduard, my fault. I changed sub-domain mappings on my server. Now things should be fixed! Thanks for letting me know! Enjoy!

    ReplyDelete
  6. Updated the zip file content as well. Now, you can download the latest version rtfeval_o9.zip from here, too.

    BTW: Just ran the form on my VISTA system - NO RTF section is found on the clipboard! It seems that I have to alter the part of the code where I'm using a constant for the RTF-ID This is "buggy" - must be dynamically queried, obviously.

    If anyone done this, please let me know!

    ReplyDelete
  7. http://resources.foxquill.de/rtfeval_09.zip
    the download zip link is broken.
    please re make it working !

    ReplyDelete