Version: 2.02.00 - last Update: Thursday, July 24, 2014, 23:45:00 (corrected some faults)
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:
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 |
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 Before 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:
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 |
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 |
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 |
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:
Figure #3: ???? What kind of gibberish is that?
Figure #4: TRY/ENDTRY of GetClipboard() caught an error
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?
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 #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.
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 :-)
ReplyDeleteI added this code in the load event :
ReplyDeleteDeclare 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
Hi Christian,
ReplyDeletevery 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
Can't download FrmTemplates.zip :(
ReplyDeleteSorry, Eduard, my fault. I changed sub-domain mappings on my server. Now things should be fixed! Thanks for letting me know! Enjoy!
ReplyDeleteUpdated the zip file content as well. Now, you can download the latest version rtfeval_o9.zip from here, too.
ReplyDeleteBTW: 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!
http://resources.foxquill.de/rtfeval_09.zip
ReplyDeletethe download zip link is broken.
please re make it working !