Showing posts with label VFP. Show all posts
Showing posts with label VFP. Show all posts

2023-04-16

Capture Form portions - FoxyCapture

Recently Tom Knauf from Germany asked for a feature in FoxyPreviewer3 to allow users to pick specific parts of their reports and export them to the clipboard or save as image in an easy way.

Nothing new on this, I even published about 10 years ago a solution for that, but it was not working anymore.

So, I took the opportunity to create it from scratch, but this time thinking of a good way to make it work safely with FoxyPreviewer3.

FOXYCAPTURE is a custom VFP class that you can toss in any VFP form.

To activate it, just call the method "StartCapture", and select the rectangle portion of your form you's like to capture. After dragging, a shortcut menu will allow you to either save as PNG or send the image to the clipboard.

Source code and a sample form included, please let me know if you find any issues or make any improvements.

Below some animated images showing how it works.


Picture1 - Capture to image file



Picture2 - Capture image to the clipboard, and paste it to WhatsApp web:


Files to include in your project:

  • FoxyCapture.vcx
  • FoxyCapture.vct
  • fc_Save.Bmp
  • fc_Clipboard.bmp


Related Methods / Properties

Method:

StartCapture - Starts capturing the mouse events to capture the form surface


Properties:

BorderColor - Numeric, the RGB value of the border color to drawn to determine the rectangle to be captured

ShowBorder - Logical, determines if the dotted shape borders will appear in the captured image or not


Download link:

LATEST UPDATES

- 2023-04-22 v1.04 - Now instead of trying to bind all the form objects it draws an invisible container on the screen, where the dotted rectangle used for selecting the screen contents is drawn.
This brought the advantage of allowing to select any part of the form, Grids, PageFrames, Textboxes, etc, without the need of Disabling objects. Forms with grids are working fine as well.


2022-05-08

XFRX vs FoxyPreviewer

I felt very honored to know that the VFP guru Rick Schummer presented a session - Visual FoxPro Reporting: XFRX vs. FoxyPreviewer in the 2022 version of VFF - Virtual Fox Fest. The organizers were very generous offering a recorded version of all sessions for free for the community in YouTube.



Rick very quickly and impartially shown some of the main features of both tools. 

Regarding FoxyPreviewer, I have some few considerations or remarks. Of course it would be impossible or Rick to know all the possibilities and features available. I just want to make clear that I feel very thankful for the review. And I am totally aware that the documentation needs some updates, and some points could be better explained, so here I take the opportunity to try to clarify some points.


  • Exporting report to an image file - There are 3 available options:
    - OBJECT TYPE 16 - This is the object type related to exporting to image files. Works exactly the same as the others.
    - OBJECT TYPE 20 - This OBJECT TYPE was not discussed in the presentation. It will choose automatically the ReportListener needed to generate the desired output, according to the file extension passed in the TO FILE clause. The available types are: PDF, RTF, HTML, MHTML, GIF, TIFF, EMF, JPG or PNG.
    REPORT FORM YourReport TO FILE MyImageReport.JPG OBJECT TYPE 20
    - Using the FoxyListener.OutputPage method &&Like regular VFP9
  • The truncated numeric fields in reports - He was very precise in his explanation, but I still would like to add some few information from a FAQ related to this:
When I run my reports with FoxyPreviewer sometimes asterisks symbols ********************* appear instead of the field. This was originally working, before using FoxyPreviewer

That happens because FoxyPreviewer uses the SET REPORTBEHAVIOR 90 mode, that uses GDI+ to render the texts. Unfortunately there is a slight difference of the size of the strings between these modes. To fix it, just edit your report and enlarge that field!

You can set the property - lExpandFields to make the report engine show the field numeric value ignoring the field size.
Using "lExpandFields", FoxyPreviewer retrieves the value that overflowed and resends it to the report engine with an enlarged field width.

This is a known issue, and Lisa Slater Nicholls wrote a short blog post regarding it:
Why do report layouts in VFP9 need wider field/expression controls than in VFP8 and earlier?

And here's another interesting text from Lisa, that explains the reason for that:
With REPORTBEHAVIOR=90, the new report engine uses GDI+ to render output, and text string rendering requires more space than plain old GDI.
The Report Designer uses GDI - not GDI+ - to render the report layout components, including all the text strings that you see. So if you visually right-align a label report element, the report designer records the leftmost co-ordinates of the element (the text start position) in the layout.
The length of the string under GDI+ rendering will most likely be greater than what you would think, based on what you see in the Designer.

  • SET COMPATIBLE ON - Oh, this is a known issue, and I had it fixed before. I just don't know why the fix was not included in the last version. I hope to post an update very soon.
  • ReportPreview.App and ReportOutput.App - As shown by Rick, these files are no longer needed if FoxyPreviewer was elected to be your single ReportEngine. I just feel important to add that if you are used to turn it off, you still need those files to restore the Reporting engine to the default VFP9.
  • Excel bug / Enhancements - Please send me your files and I will be very happy and honored to aply your updates to the main distributable version!
  • lOpenViewer property - In one of your samples Rick was using the property ".lOpenViewer = .T." - That means the generated output file would be opened automatically after generated. But the next REPORT FORM command contained the "TO FILE" and "PREVIEW" clauses. The "PREVIEW" clause means exactly the same - to open the file automatically!
    That's a very interesting usage of FoxyPreviewer. If you want your report to be shown in the default PDF viewer instead of a VFP FORM, just use the command:
    REPORT FORM YourReport OBJECT TYPE 20 TO FILE TempFile.pdf PREVIEW
  • The "PrintingPreferences" - Rick told us he feels some kind of frustration with the fact that the report is closed after you change your report settings in that dialog. But that's exactly what the "PRINTER PROMPT" clause does! From that dialog you have the opportunity to change the printer, copies and other settings and call the printer directly.
  • Rick was asked about embedding the toolbar in the Preview window. As he said, not in v2.99, but docking the toolbar is possible.
  • David Acuña asked about determining the SMTP server values previously. Yes, that's possible. Just set the "cSMTPServer", "nSMTPPort" and other related properties previously, just after FoxyPreviewer was initialized.
  • Maybe I misunderstood the real meaning of Rick's statement when he said that "The configurations are Global, not Per User". In fact, when you start FoxyPreviewer.App it will load the settings from the file "FoxyPreviewerSettings.dbf". This file is automatically generated after the 1st run of Foxy in that folder. Each of your users can have their own Settings Dbf file! You can save a copy of FoxyPreviewerSettings.DBF when your user logs off. Next time, after logging in and before calling FoxyPreviewer.App replace this file with the desired one. So, you can save a settins file for each of your users! When FoxyPreviewer does not find that DBF in its folder it will create a new one.

    Yes, everyone using that APP on that same machine will use those settings, as he mentioned.

    Here are some new thoughts regarding this, using a not very well documented possibility... There is an option to start FoxyPreviewer in a separate folder:

    Immediately after the user logs in your App, and the user is stored as "m.lcUser", people can try the following:

    lcFolder = ADDBS(JUSTPATH(SYS(16)))
    lcUserFolder = lcFolder + "Users\" + m.lcUser && Make sure the folder exists
    DO FOXYPREVIEWER.APP WITH (lcUserFolder)

    This way, every user will easily have his own Settings table. What do you think?


  • Distributing FoxyPreviewer - People just need ONE SINGLE FILE - FoxyPreviewer.App. And there is no need to install it. Just a simple"DO FOXYPREVIEWER.APP" at the beginning of your EXE code and it's done! The recommended is to store it at the same folder of your EXE, and make sure that folder will allow Foxy to create a subfolder and save some custom configuration files.

2021-04-25

Getting the correct Windows version - OS()

VFP9's OS() function can't return reliable information to help us to detect wether running WIN10.

Under Win10, the OS() returns to us "Windows 6.02", the same information for Windows 8.

A Simple workaround is to use the RtlGetVersion Api call to get the OSVERSIONINFO structure that brings us the relevant information.

Save the code below as GETWINVERSION.PRG, and call it the same way you call the OS() function.


Parameters

nValue

Specifies the item to return, according to the following table.

nValues

ValueDescription

     1     

Specifies that the name and version number of the operating system is returned.

     3     

Identifies the major version number of the operating system. For example, for Windows 2000, the major number is 5.
For Windows 10, the major number is 10.

     4     

Identifies the minor version number of the operating system. For example, for Windows 2000, the minor version number is 0.

     5     

Identifies the build number of the operating system.





FUNCTION GetWinVersion(tnType)
* Similar to VFP9 OS() function, returning the correct Win version for Windows 10

* OSVERSIONINFOA structure (winnt.h)
* https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa
*!*	typedef struct _OSVERSIONINFOA {
*!*	  DWORD dwOSVersionInfoSize;
*!*	  DWORD dwMajorVersion;
*!*	  DWORD dwMinorVersion;
*!*	  DWORD dwBuildNumber;
*!*	  DWORD dwPlatformId;
*!*	  CHAR  szCSDVersion[128];
*!*	} OSVERSIONINFOA, *POSVERSIONINFOA, *LPOSVERSIONINFOA

*!*	The following table summarizes the values returned by supported versions of Windows. 
*!*	Use the information in the column labeled "Other" to distinguish between operating systems with identical version numbers.

*!*	Operating system	 Version dwMajorVersion	dwMinorVersion	Other
*!*	Windows 10	           10.0*		    10		0	
*!*	Windows Server 2016	   10.0*		    10		0	
*!*	Windows 8.1	            6.3*		     6		3	
*!*	Windows Server 2012 R2	6.3*		     6		3	
*!*	Windows 8	              6.2		       6		2	
*!*	Windows Server 2012	    6.2	    	   6		2	
*!*	Windows 7	              6.1	    	   6		1	
*!*	Windows Server 2008 R2	6.1	    	   6		1	
*!*	Windows Server 2008	    6.0	    	   6		0	
*!*	Windows Vista	          6.0	    	   6		0	
*!*	Windows Server 2003 R2	5.2	    	   5		2	
*!*	Windows Server 2003	    5.2	    	   5		2	
*!*	Windows XP	            5.1	    	   5		1	
*!*	Windows 2000	          5.0	    	   5		0	
*!*	* For applications that have been manifested for Windows 8.1 or Windows 10. Applications not manifested for Windows 8.1 or Windows 10 will return the Windows 8 OS version value (6.2). To manifest your applications for Windows 8.1 or Windows 10, refer to Targeting your application for Windows.

LOCAL lcOS, lcOsVersionInfo, lcReturn, lcVersion, lnBuild, lnMajor, lnMinor, lnPlatformId, lnRet, lnSize

* https://docs.microsoft.com/en-us/windows/win32/devnotes/rtlgetversion
DECLARE INTEGER RtlGetVersion IN NTDLL.DLL STRING @lcOsVersionInfo

m.lcOsVersionInfo = REPLICATE(CHR(0), 148) && Initialize osVersionInfo structure
m.lnRet = RtlGetVersion( @m.lcOsVersionInfo )

m.lnSize = CTOBIN(SUBSTR(m.lcOsVersionInfo, 1, 2), "2RS") && DWORD dwlcOsVersionInfoSize
m.lnMajor = CTOBIN(SUBSTR(m.lcOsVersionInfo, 5, 4), "4RS") && DWORD dwMajorVersion
m.lnMinor = CTOBIN(SUBSTR(m.lcOsVersionInfo, 9, 4), "4RS") && DWORD dwMinorVersion
m.lnBuild = CTOBIN(SUBSTR(m.lcOsVersionInfo, 13, 4), "4RS") && DWORD dwBuildNumber
m.lnPlatformId = CTOBIN(SUBSTR(m.lcOsVersionInfo, 17, 4), "4RS") && DWORD dwPlatformId
m.lcVersion = SUBSTR(m.lcOsVersionInfo, 21) && CHAR  szCSDVersion[128]

DO CASE

CASE EMPTY(m.tnType)
	DO CASE
	CASE m.lnMajor = 10 AND m.lnMinor = 0
		m.lcOS = "Windows 10"
	CASE m.lnMajor = 6 AND m.lnMinor = 3
		m.lcOS = "Windows 8.1 / Server 2012 R2"
	CASE m.lnMajor = 6 AND m.lnMinor = 2
		m.lcOS = "Windows 8 / Server 2012"
	CASE m.lnMajor = 6 AND m.lnMinor = 1
		m.lcOS = "Windows 7 / Server 2008 R2"
	CASE m.lnMajor = 6 AND m.lnMinor = 0
		m.lcOS = "Windows Vista / Server 2008"
	CASE m.lnMajor = 5 AND m.lnMinor = 2
		m.lcOS = "Windows Server 2003"
	CASE m.lnMajor = 5 AND m.lnMinor = 1
		m.lcOS = "Windows XP"
	CASE m.lnMajor = 5 AND m.lnMinor = 0
		m.lcOS = "Windows 2000"
	OTHERWISE
	ENDCASE
	m.lcReturn = m.lcOS + " " + TRANSFORM(m.lnMajor) + "." + TRANSFORM(m.lnMinor) + " build " + TRANSFORM(m.lnBuild)

CASE m.tnType = 1
	m.lcReturn = "Windows " + TRANSFORM(m.lnMajor) + "." + TRANSFORM(m.lnMinor)

CASE INLIST(m.tnType, 2, 6, 7, 8, 9, 10, 11)
	m.lcReturn = ""

CASE m.tnType = 3
	m.lcReturn = TRANSFORM(m.lnMajor)

CASE m.tnType = 4
	m.lcReturn = TRANSFORM(m.lnMinor)

CASE m.tnType = 5
	m.lcReturn = TRANSFORM(m.lnBuild)

OTHERWISE
	m.lcReturn = ""

ENDCASE
RETURN m.lcReturn


2021-03-23

Saving a VFP Report at a higher resolution with FoxyPreviewer

VFP9 brought the possibility to save our reports as images, using the ReportListener class, and the OutputPage method:

loListener.OutputPage(lnPage, "\MyReportImage.PNG", 104) && PNG file type


This brings us some useful, but lousy images regarding quality. For instance, the Sample "COLORS.FRX" brings me a 816 x 1056 pixels image - a really poor quality image, if we are thinking of printing or manipulating it further.


But the "OutputPage" method also allows us to draw the report page at any desired image size, by passing a GDI+ Graphics handle instead of the file name widely used.

Here is the working sample - notice that the report engine works only with 96 DPI, so to have better quality, you need to save in bigger dimensions.

Use the function GETREPORTPAGEEX to get your higher resolution reports, here is the parameter list

  • tcFile - The destination image file name
  • toListener - The ReportListener associated with the current report
  • tnPage - The report page number
  • tnEncoder - 100=EMF, 101=TIFF, 102=JPEG, 103=GIF, 104=PNG, 105=BMP
  • tnScale - the scale factor to be applied to the image. 1=Default (low quality), 10=Super high quality
  • tnWidth - The output image width (optional, if using the "tnScale")
  • tnHeight - The output image height (optional, if using "tnScale")


DO FoxyPreviewer.App

LOCAL loListener AS REPORTLISTENER
LOCAL lcFile, lnPage, lnFileType
m.loListener			  = CREATEOBJECT("FoxyListener")
m.loListener.LISTENERTYPE = 3

REPORT FORM (ADDBS(_Samples) + "Solution\Reports\Colors.FRX") OBJECT m.loListener

m.lnFileType = 104 && PNG
	&& 100 - EMF
	&& 101 - TIFF
	&& 102 - JPEG
	&& 103 - GIF
	&& 104 - PNG
	&& 105 - BMP

FOR m.lnPage = 1 TO m.loListener.PAGETOTAL
	m.lcFile = "c:\temp\Test__" + SYS(2015) + "__" + ALLTRIM(STR(m.lnPage)) + ".png"
	GetReportPageEx(m.lcFile, m.loListener, m.lnPage, m.lnFileType, 5) && 5 times bigger image than default
	* For the default lower quality image, use:
	*   loListener.OutputPage(lnPage, "c:\Test" + ALLTRIM(STR(lnPage)) + ".png", lnFileType)
ENDFOR
m.loListener = NULL
RETURN



PROCEDURE GetReportPageEx(tcFile, toListener AS REPORTLISTENER, tnPage, tnEncoder, tnScale, tnWidth, tnHeight)
	LOCAL lhGfx
	*!*	100 - image type EMF
	*!*	101 - image type TIFF
	*!*	102 - image type JPEG
	*!*	103 - image type GIF
	*!*	104 - image type PNG
	*!*	105 - image type BMP
	m.tnEncoder	= EVL(m.tnEncoder, 104) && Default = 104-PNG
	m.tnScale	= EVL(m.tnScale, 1)
	IF EMPTY(m.tnWidth)
		m.tnWidth  = m.toListener.GETPAGEWIDTH()  / 10 * m.tnScale
		m.tnHeight = m.toListener.GETPAGEHEIGHT() / 10 * m.tnScale
	ENDIF

	#DEFINE Gdiplus_PixelFormat_32BppArgb		0x0026200a
	#DEFINE OUTPUTDEVICETYPE_GDIPLUS 			1

	LOCAL loBMP AS GpBitmap OF ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX"
	m.loBMP = NEWOBJECT("GpBitmap", ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX")
	m.loBMP.CREATE(m.tnWidth, m.tnHeight, Gdiplus_PixelFormat_32BppArgb)

	LOCAL loGfx AS GpGraphics OF ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX"
	m.loGfx = NEWOBJECT('GpGraphics', ADDBS(HOME()) + "/FFC/_GDIPLUS.VCX")
	m.loGfx.CreateFromImage(m.loBMP)
	m.lhGfx = m.loGfx.GetHandle()

	m.toListener.OUTPUTPAGE(m.tnPage, m.lhGfx, OUTPUTDEVICETYPE_GDIPLUS, 0, 0, m.tnWidth, m.tnHeight, 0, 0, m.tnWidth, m.tnHeight)
	m.loBMP.SaveToFile(m.tcFile, "image/png")
ENDPROC

2020-11-23

Unicode Controls in Visual FoxPro - A new faster and efficient aproach

As a continuation of my last post - Unicode Button Icons in Visual FoxPro, I decided to keep trying different solutions to bring Unicodes to our controls - specially as button icons.

The most natural option would be to use real Windows Buttons like the sample we have in VFPX - https://vfpimaging.blogspot.com/2020/11/unicode-button-icons-in-visual-foxpro.html

But this brings the con that we would have to make several changes in our legacy forms in order to adapt all codes related to the Click event, and others.

So, I decided to try a hybrid solution - "Draw" a "Static" Win32 control - similar to our VFP Label control over regular CommandButtons and Labels. These "Static" Win controls allow Unicodes, and would allow us to keep all our legacy codes as they are. 

So here's FOXYOBJECTS - a custom VFP class that can be tossed in our forms, and will "Convert" all buttons and labels that have contents between the <UC> </UC> tags to unicodes, just like the previous post. This time about 4-5 times faster than GradObjects, with the same result.

If you didn't read the previous post, my goal is to bring some cool icons, the same we see all over in WIN10 UI, specially those from SEGOE MDL2 ASSETS, like shown in the CharMap below:



The usage of this new class is really very simple:

  • Open the FoxyObjects project
  • Toss an instance of FoxyObjects to your form
  • Set your command button FontName property to "SEGOE MDL2 ASSETS" or any other that you desire.
  • Set the caption property of the button to accept unicodes, by introducing the unicodes between the <UC> </UC> tags, for instance:
          greek <UC>03b5 03b9 03c1 03ae 03bd 03b7</UC> - This will show the word "Peace" in greek characters, in any regular font, like Arial, Tahoma, Segoe UI, etc
          To get the Printer icon from the SEGOE MDL2 ASSETS, set the commandbutton font to it, and add the following to its caption property: "<UC>E749</UC>"


FoxyObjects brings some few properties, that will be applied to all CommandButton and Label controls that are on the same object level of the FoxyObjects level. For instance, if you want FoxyObjects to apply changes to some selected objects, you can insert them into a container, and add an instance of the class to it. This way, the rest of the objects will not be affected.

By default, it will apply changes only to objects that have the <UC> tag in their captions.



  • BackColor - Numeric, specifies the background color used to display text and graphics in an object.
  • DisabledForeColor - Numeric, specifies the color used to display text when the object is disabled.
  • ForeColor - Numeric, specifies the color used to display text.
  • MouseOverForeColor - Numeric, specifies the color the object text (and icon) will turn into when the mouse will be over the specified object. If you don't want this effect, store the value -1
  • lBindAll - Logical, determines that all objects will be affected, even if they don't have the <UC> in the caption. That means that you can change the caption at runtime, and the unicodes will be respected 
  • lBindLabels - Logical, determines that both CommandButtons and Labels will be affected
  • lBindResize - Logical, determines that whenever any control is resized or moved the label mask will be updated as well.
  • lBindVisible - Logical, determines that whenever any control is hidden or visible label mask will be updated as well.









The Unicodes can be obtained directly by the CharMap.EXE or all over the web. Here's an excellent starting point: https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font


The SEGOE MDL2 ASSETS font comes with Win10, but it is not allowed to be distributed to other OS'. This is not a big deal, because Tahoma also brings several options to us, and we can always work with some of the free fonts available on the web, such as "Material.io". They bring tons of modern and beautiful icons for free. It's really worth a visit - https://material.io/resources/icons/?style=outline


SPECIAL THANKS to Mustapha Bihmuten from Morocco and Leandro Walfrans from Brazil for testing the Pre-Alpha version of the class and for bringing valuable suggestions



 FoxyObjects v0.4 Download 


See Also:
Drawing Unicode Texts with GDI+
Unicode Button Icons in Visual FoxPro
Unicode character search by compart.com
Material icons

2020-11-15

Unicode button icons in Visual FoxPro

A big difficulty foxers have is to update their user interfaces. Since we lost MS support, we need to do almost all UI changes by our own. The Win10 UI today is based in monochrome icons - the ones from the SEGOE UI family - SEGOE UI SYMBOL and SEGOE MDL2 ASSETS. These are true type fonts, that bring tons of icons - the ones that we see all over in Win10.



Unfortunately we can't access those icons directly in VFP, because they use a range higher than the CHR(255) supported by VFP. We still have some options:

1 - Use an ACTIVE-X that supports unicodes

2 - Use "Real Window buttons", that support unicodes - https://github.com/VFPX/Win32API/blob/master/samples/sample_274.md

3 - Get some help from GDI+ - gdiplus.dll and do the drawings for us.


The 3rd option is really nice, but demands a lot, really a lot of work. Fortunately, I did almost all the hard work before, back in 2005, in the GradObjects class, that originally was created to bring gradient backgrounds and buttons to our forms. It recreated every button from the form, and redrawn it to an image file, allowing cool gradient, mouse over and disabled effects.

Having this, I just needed to adapt it, leaving the almost abandoned gradients behind, adding support to unicodes and some adaptations for the mouse over effects.


Here's an updated version of the good and old GRADOBJECTS class, that was intended to generate gradient buttons and backgrounds to our forms back in 2005, in the WinXP times. It's still the same GradObjects, but with some new properties and features. 


The usage is really very simple:

  • Open the GradObjects project
  • Create a form, add some buttons
  • Toss an instance of Gradobjects to your form
  • Set your command button FontName property to "SEGOE MDL2 ASSETS" or any other that you desire.
  • Set the caption property of the button to accept unicodes, by introducing the unicodes between the <UC> </UC> tags, for instance:
          greek <UC>03b5 03b9 03c1 03ae 03bd 03b7</UC> - This will show the word "Peace" in greek characters, in any regular font, like Arial, Tahoma, Segoe UI, etc
          To get the Printer icon from the SEGOE MDL2 ASSETS, set the commandbutton font to it, and add the following to its caption property: "<UC>E749</UC>"


The default "GradObjects" properties will bring you a gradient look, but all you need is to change just 5:

  • BackColor1: Numeric, the RGB of the backcolor
  • BackColor2: Set it to .F. (false), because we don't need gradients here, do we?
  • CaptionForeColor: Numeric, the RGB of the forecolor
  • GradientMode: 0 - We don't need gradients!
  • SelBackColor: Numeric, the RGB of the backcolor when a button is focused or mouse over it
  • SelForeColor: Numeric, the RGB of the forecolor when a button is focused or mouse over it

That's it!

The Unicodes can be obtained directly by the CharMap.EXE or all over the web. Here's an excellent starting point: https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font


For a more detailed information, please refer to the original post for GradObjects: http://vfpimaging.blogspot.com/2006/07/gradient-objects-with-gdi-revisited_48.html



The "GradObjects" object will transform all CommandButtons, Graphical OptionButtons from the same parent object. Use containers, if you need different effects (or none) to some individual controls in your forms.

Start playing with the sample form "TESTUNICODEBTNS.SCX"

 Unicode Buttons Download 


See Also:
Drawing Unicode Texts with GDI+



2020-07-03

FoxyYearCalendar - Adding Win32 controls to VFP forms

Here's a sample that uses the Win32 MonthCalendar control in a VFP form.
This can be used as a base to insert other Windows controls, uch as the trackbar, the date picker and scrollbars.

Allows you to select between a range of dates, and to mark some dates in bold.



Download sample
https://www.foxite.com/uploads/db1656e4-4819-4634-bda7-eb0c3d2460da.zip

2020-06-12

FoxyDialogs v1.0 - going much forward with vista TaskDialog API in VFP

Here comes the evolution of a function I've been working for the last days, FOXYDIALOGS. A Visual FoxPro function that makes it simple for us to deal with several types of dialogs.

The new FoxyDialog function brings an easy way for us to have some more up to date dialogs to inform the users, and also a new way to get some simple information in a much more powerful and beautiful INPUTBOX() function.

- up to 6 buttons allowed
- use the default Windows icons, those stored in "IMAGERES.DLL", used by Windows all over, and also any ICO or bitmap file
- a wider dialog window, that stretches to fit all your text
- apart from the WindowTitle and the Contents, you have the "Main Instruction" parameter
- introduced the timeout property
- allows using Unicodes, for some cool characters and Multilanguages
- Icons in buttons, using some default windows images
- New input data types, such as texts, numbers, dates, date and time, date ranges



The primitive version of this function was derived from the excellent sample published in VFPX , by Anatolyi Mogylevets and Tore Bleken - How to display a Task Dialog (Vista). The original sample displays the dialog, but using only the default Windows dialog buttons, such as "Ok, Cancel, Abort, Retry, Yes, No".

This function is leveraging from some amazing VFPX projects: the VFPX Win32 API from Anatolyi Mogylevets and Tore Bleken and VFP2C32, from Christian Ehlcheid.
I also received a great inspiration from "Fafalone" from http://www.vbforums.com/showthread.php?777021-VB6-TaskDialogIndirect-Complete-class-implementation-of-Vista-Task-Dialogs, who used the biggest brother of the TaskDialog API for other very cool usages. Something that I already did, but hope to bring in another post.


These dialogs are Unicode friendly. That means that we can add almost whatever characters we desire. Just search the Web for "UNICODE + Heart", and you'll get the codes for you to try.
For unicodes, it's important to notice that this works with UTF-16 (16 bit Unicode). For Double-byte characters, like the "Thumbs up", pass the 8 digits inside the tag. All unicodes are accepted, you can pass several unicodes separated by a single SPACE.


Function:
  • NEWDIALOG(tcTitle, tcMainInstruction, tcContent, tcnIcon, tcButtons, tnDefault, tnTimeout)

Parameters:
  • tcTitle - string to be used for the task dialog title.

  • tcMainInstruction - string to be used for the main instruction.

  • tcContent - string used for additional text that appears below the main instruction, in a smaller font. 

  • tcnIcon - Character or Integer that identifies the icon to display in the task dialog. 
This parameter can be an integer or some predefined text values, allowing several options.
- If this parameter is EMPTY() or omitted, no icon will be displayed.
- For numeric, the variety of icons is HUGE, all icons stored in the %systemroot%\system32\imageres.dll file. The imageres.dll file contains many icons, used almost everywhere in Windows 10. It has icons for different types of folders, hardware devices, peripherals, actions, and so on. Below in Appendixes 1 and 2 there is a list of the available strings and enumerated icons. In this parameter you can also determine the background color of the dialog main instruction.
- Send a string comma separated, having the desired main icon first, and after the comma a letter representing the background color: R=Red; G=Green; Y=Yellow; S=Silver; B=Blue; Empty() no background, and finally "-" means no left margin. You can also pass a BMP or ICO file - just make sure to have it available on disk, not embedded in your EXE.
  • tcButtons - This parameter determines some important behaviors of the dialog.
For ordinary dialogs, used for you to pass some information to the users, it specifies the push buttons displayed in the dialog box. A single string containing comma separated captions. If you wish to show a disabled button, add a "\" before the caption. All buttons are Unicode friendly, and you can use some special button captions with special extensions, for the very commonly used buttons - Ok, Cancel, Print, Save, Search. Adding a "#" will add some basic unicode icons. Adding an asterisk - "*" will add some colored icons. 
For INPUTBOXES mode, in the first word of the parameter you can add some special characters as well:

  1. "@I" - turns the dialog into a modern INPUTBOX().
    "@IU" or "@I!" - the textbox will accept only UPPERCASE characters
    "@IL" - the textbox will accept only LOWERCASE characters
    "@IN" or "@ID" - numeric (negative and comma accepted) or DIGITS (only integers)
    "@IP" - Password inputbox, shows asterisks for every character
  2. "@D" - DateBox dialog - showing a cool combobox for date picking
  3. "@T" - DateTimeBox dialog - showing the same combobox above and a time inputbox
  4. "@M" - MonthBox dialog - showing a single calendar for date picking
  5. "@R" - DateRangeBox dialog - showing a double month calendar, allowing users to pick some date ranges

  • tcnDefault -

    - For DialogBox mode - numeric, specifies the button Id that will be focused. Default = 1

    - For special InputBox mode - specifies the default values shown when the input dialog is shown: Character for "@I" or Date for "@D", "@M", "@R" or DateTime for "@T" tcButtons type
  • tnTimeout - Specifies the number of milliseconds the dialog will be displayed without input from the keyboard or the mouse before clearing itself. You can specify any valid timeout value. A value of less than 1 never times out until user enters input and behaves the same as omitting the nTimeout parameter. The Timeout parameter can be a numeric value or a Character, with the time in milisseconds, and the string that will come together with the time shown. The tag " < SECS > " will be replaced by the time in seconds, with the small unicode clock. Don't miss the samples below.
Returns:
  • For Regular DialogBox - nId - the Id of the selected button, or 0 (zero) if Cancelled or -1 for timed out
  • For InputBoxes, according to each type, as follows:

    - "@I", "@I!", "@IP" - returns the character entered, or an empty string if "Cancel"
    - "@IN", "@ID" - returns a numeric value, or an empty string if cancelled. - Notice that cancel returns a Character empty string!
    - "@D", "@M" - returns a Date format value
    - "@T" - returns a DateTime format value
    - "@R" - returns an object with two properties: "StartDate" and "EndDate"

There are numerous ways to use it, but after you play with the samples I hope you'll feel comfortable with it. The samples below will show you almost all the most important features available:



Sample 1:

? FoxyDialog("Vista Task Dialog", "The main instruction text for the TaskDialog goes here", ;
 "The content text for the task dialog is shown here and the text will automatically wrap as needed." + CHR(13) + CHR(13) + ;
 "Any expanded content text for the task dialog is shown here and the text will automatically wrap as needed." + CHR(13) + CHR(13) + ;
    "Do you like it?", "OK2", ;
 "Yes!,Not really,I dunno")

Sample 2:

? NewDialog("Bad Username or Password", ;
      "Access Denied", ;
      "You have entered the wrong credentials for 3 times." + CHR(13) + CHR(13) + ;
             "Please wait for 30 minutes and try again, otherwise your account may be suspended.", ;
      "Lock", ;
      "Return")

Sample 3:

? FoxyDialog("Critical error", ;
     "Corrupted Data", ;
     "An unexpected error has occurred and the system needs to be restarted." + ;
         CHR(13) + CHR(13) + "What do you want to do ?", ;
     "X2", ;
     "Restart Now,Restart later,Keep working", ; && Button captions
     2) && Default button


Sample 4: Dialogs using simple timeout, with unicodes - everywhere, titles, contents and even buttons!

? FoxyDialog("Nice job!  <UC>2661 2665 2764</UC>", ;
    "Success! <UC>d83ddc4d</UC>", ;
    "Your account details have been updated successfully." + ;
    CHR(13) + CHR(13) + "You can proceed with the next step to get your cashback!", ;
    "OK2", ;
    "Ok*,Ok <UC>2714</UC>,Ok <UC>D83DDDF9</UC>", ; && Button captions
    1, ;  && Default button
    9000) && Timeout



Sample 5: Unicodes - Multilanguages

Unicode friendly means endless usages, really very easilly. The dialog below uses several languages from different character sets.
To get the Unicodes, first I used Google translator to translate the word "Welcome". The results were pasted to one among many Unicode translators, like Branah - https://www.branah.com/unicode-converter. Just paste your text and it will provides the unicodes. Make sure to check the "Remove \u", and when pasting, make sure to separate every character with a space. In general, for most languages you'll use 4 characters. For double byte languages, like Chinese, Japanese and Korean, or for some special characters, like the "thumbs up", you'll pass every 8 characters at a time.



? FoxyDialog("Playing with Unicodes  <UC>d83ddc4d</UC>", ;
    "Welcome! <UC>2661 2665 2764</UC>", ;
 "   <UC>27a4</UC>  Turkish <UC>0048 006f 015f 0067 0065 006c 0064 0069 006e 0069 007a</UC>" + CHR(13) + ;
 "   <UC>21f0</UC>  Arabic <UC>0623 0647 0644 0627 2000 0628 0643</UC>" + CHR(13) + ;
 "   <UC>2192</UC>  Greek <UC>03ba 03b1 03bb 03c9 03c2 2000 0397 03a1 0398 0391 03a4 0395</UC>" + CHR(13) + ;
 "   <UC>21e8</UC>  Simplified Chinese <UC>6b228fce</UC>" + CHR(13) + ;
 "   <UC>21fe</UC>  Hebrew <UC>05d1 05e8 05d5 05da 2000 05d4 05d1 05d0</UC>" + CHR(13) + ;
 "   <UC>2794</UC>  Japanese <UC>30883046 3053305d</UC>" + CHR(13) + ;
 "   <UC>27a7</UC>  Russian <UC>0434 043e 0431 0440 043e 2000 043f 043e 0436 0430 043b 043e 0432 0430 0442 044c</UC>", ;
    "OK2", ;
    "No! <UC>d83ddec7</UC>,Cancel*,Ok*,Ok <UC>2714</UC>,Ok <UC>D83DDDF9</UC>") && Button captions





Sample 6: Timeout with special caption

? NewDialog("Covid-19 crazy warning - See the timer -->", ;
    "Please stay home!", ;
    " - Clean your hands often." + CHR(13) + ;
    " - Keep a safe distance from anyone who is coughing or sneezing." + CHR(13) + ;
    " - Don’t touch your eyes, nose or mouth." + CHR(13) + ;
    " - Cover your nose and mouth with your bent elbow or a tissue when you cough or sneeze." + CHR(13) + ;
    " - Avoid close contact with people who are sick." + CHR(13) + ;
    " - Stay at home as much as possible." + CHR(13) + ;
    " - Put distance between yourself and other people." + CHR(13) + ;
    " - If you have a fever, cough and difficulty breathing, seek medical attention." + CHR(13), ;
    "!2", ;
    "More Info,I agree,Leave me!", ; && Button captions
    2, ;  && Default button
    "8000,<SECS> secs.") && Timeout








Sample 7: Predefined unicode buttons:
For the basic captions: "Ok, Cancel, Print, Save and Search", add an "*", and the corresponding unicode icon will be added to the button, as below:


? FoxyDialog("Playing with Unicodes in buttons", ;
    "Predefined buttons", ;
 "There are currently 5 predefined buttons that will add a unicode icon automatically." + CHR(13) + CHR(13) + ;
 "Add an asterisk - '*' after the words below, and the corresponding icons will be added to the buttons:" + CHR(13) + CHR(13) + ;
 "     Ok*        <UC>27f6</UC>     Ok <UC>2713</UC>" + CHR(13) + ;
 "     Cancel*    <UC>27f6</UC>     Cancel <UC>d83dddd9</UC>" + CHR(13) + ;
 "     Print*     <UC>27f6</UC>     Print <UC>2399</UC>" + CHR(13) + ;
 "     Save*      <UC>27f6</UC>     Save <UC>d83dddab</UC>" + CHR(13) + ;
 "     Search*    <UC>27f6</UC>     Search <UC>d83ddd0e</UC>", ;
    "I", ;
    "Ok*,Cancel*,Print*,Save*,Search*")  && Button captions




Sample 8: Custom title with no icon, and with background:
Notice the icon parameter - the comma separated string - ",S" means that there will be no main icon, but with a silver background:

* Sample 8
? FoxyDialog("Covid-19 warning", ;
    "Custom title with no icon and background" + CHR(13) + "PLEASE STAY HOME!" + CHR(13) + "I hope you'll keep your word!", ;
    " - Clean your hands often." + CHR(13) + ;
    " - Avoid close contact with people who are sick." + CHR(13) + ;
    " - Stay at home as much as possible." + CHR(13) + ;
    " - Put distance between yourself and other people." + CHR(13) + ;
    " - If you have a fever, cough and difficulty breathing, seek medical attention." + CHR(13), ;
    ",S", ; && No icon, silver background
    "\More Info,I agree,Leave me!") && Button captions, 1st button disabled




Sample 9: Buttons with custom icons, from Imageres.dll and Shell32.dll
Notice the index numbers in the button captions parameters. These come immediately after every caption.

* Sample 9
? FoxyDialog("Covid-19 crazy warning - See the timer -->", ;
    "Please stay home!", ;
    " - Clean your hands often." + CHR(13) + ;
    " - Avoid close contact with people who are sick." + CHR(13) + ;
    " - Stay at home as much as possible." + CHR(13) + ;
    " - Put distance between yourself and other people." + CHR(13) + ;
    " - If you have a fever, cough and difficulty breathing, seek medical attention." + CHR(13), ;
    "!2", ; && Exlamation default with yellow backgound (default)
    "\More Info_99,I agree_5341,Leave me!_89,Ok_116802", ; && Button captions, 1st button disabled
    2, ;  && Default button
    "8000,<SECS> secs.") && Timeout




Sample 10: Predefined buttons icons:
The icon parameter has the special characters: "I,B", meaning the Information icon, and the blue background
For the basic captions: "Ok, Cancel, Print, Save and Search", add an "#", and the corresponding unicode icon will be added to the button, as below:

* Sample 10
? FoxyDialog("Playing with real icons in buttons", ;
    "Custom title icon and background!" + CHR(13) + "Predefined buttons with colored icons", ;
 "There are currently 5 predefined buttons that will add some colored icons automatically." + CHR(13) + CHR(13) + ;
 "Add an HASHTAG - '#' after the words below, and the corresponding icons will be added to the buttons:" + CHR(13) + CHR(13) + ;
 "  -  Ok#     " + CHR(13) + ;
 "  -  Cancel# " + CHR(13) + ;
 "  -  Print#  " + CHR(13) + ;
 "  -  Save#   " + CHR(13) + ;
 "  -  Search# ", ;
    "I,B", ; && Information icon, blue background
    "Ok#,Cancel#,Print#,Save#,Search#")  && Button captions




Sample 11: Green background with custom icon, and unicodes

* Sample 11
? FoxyDialog("Playing with Unicodes in buttons", ;
    "You did it!!!" + CHR(13) + "Custom title icon and background!" + CHR(13) + "Predefined unicode buttons", ;
 "There are currently 5 predefined buttons that will add a unicode icon automatically." + CHR(13) + CHR(13) + ;
 "Add an asterisk - '*' after the words below, and the corresponding icons will be added to the buttons:" + CHR(13) + CHR(13) + ;
 "     Ok*        <UC>27f6</UC>     Ok <UC>2713</UC>" + CHR(13) + ;
 "     Cancel*    <UC>27f6</UC>     Cancel <UC>d83dddd9</UC>" + CHR(13) + ;
 "     Print*     <UC>27f6</UC>     Print <UC>2399</UC>" + CHR(13) + ;
 "     Save*      <UC>27f6</UC>     Save <UC>d83dddab</UC>" + CHR(13) + ;
 "     Search*    <UC>27f6</UC>     Search <UC>d83ddd0e</UC>", ;
    "Ok3,G", ; && Ok icon, green background
    "Ok*,Cancel*,Print*,Save*,Search*")  && Button captions





Sample 12: Basic INPUTBOX() dialog with custom icon, image buttons, and timeout

* Sample 12 - INPUTBOX
* - The 5th parameter determines the INPUTBOX() type
* - Custom ICO file
* - Active timeout
* - E-mail icon from https://www.creativefreedom.co.uk/icon-designers-blog/email-icon-android-ios-win-app-icon/
? FoxyDialog("New inputbox dialog", ;
    "E-mail registration", ;
    "Please enter your e-mail from a secure server", ;
    "email.ico,S",       ;  && MAIL icon, BLUE background
    "@I,Ok#,Cancel#",    ;  && Buttons, the '@I' at the beginning means INPUTBOX mode
    "johndoe@doeenterprises.com",  ;    && Default text
    "58000,<SECS> secs.")   && Timeout







Sample 13: Password INPUTBOX() dialog with custom icon, image buttons, and timeout

* Sample 13 - PASSWORD INPUTBOX
* The '@IP' parameter does the trick 
? FoxyDialog("New PASSWORD dialog", ;
    "Server Access", ;
    "Please enter the server access password" + CHR(13) + ;
     "Press the CAPS-LOCK key to see the balloontip!", ;
    "LOCK",        ;    && LOCK icon, BLUE background
    "@IP,Ok#,Cancel#", ;    && Buttons, the '@IP' at the beginning means PASSWORD INPUTBOX mode
    "Password1234",  ;    && Default text
    "58000,<SECS> secs.") && Timeout





Sample 14: Uppercase INPUTBOX() dialog


* Sample 14 - UPPERCASE INPUTBOX
? FoxyDialog("New inputbox dialog", ;
    "FORMATTED INPUTBOX", ;
    "All text entered will be converted to UPPERCASE", ;
    "OK3",        ;    && 'Ok' icon, EMPTY background
    "@I!,Ok#,Cancel#", ;    && Buttons, the '@I!' at the beginning means Uppercase INPUTBOX mode
    "This is your default text",  ;    && Default text
    "58000,<SECS> secs.") && Timeout






Sample 14b: Numeric INPUTBOX() dialog


* Sample 14b - NUMERIC INPUTBOX
lnValue = FoxyDialog("New inputbox dialog", ;
    "Enter the amount you need to withdraw from your account", ;
    "This control accepts only numeric keys and one point", ;
    "Money3.ico,B",    ;    && Custom Money icon, BLUE background
    "@IN,Ok#,Cancel#", ;    && Buttons, the '@IN' at the beginning means Numeric INPUTBOX mode
    123456.78)    && Default value
? lnValue, VARTYPE(lnValue)







Sample 15: Month Calendar INPUTBOX() dialog

* Sample 15 - MONTH INPUTBOX
? FoxyDialog("New Month Inputbox dialog", ;
    "Date select", ;
    "Please enter your date of birth", ;
    "Calendar2.ico,B",  ;  && Calendar2 icon, BLUE background
    "@M,Ok#,Cancel#",   ;  && Buttons, the '@M' at the beginning means MONTHBOX mode
    CTOD("01/12/2019"), ;  && Default date
    "58000,<SECS> secs.") && Timeout






Sample 16: Date INPUTBOX() dialog

* Sample 16 - DATE INPUTBOX
? FoxyDialog("New Date Input dialog", ;
    "Date select", ;
    "Please enter your date of birth", ;
    "Calendar4.ico",    ;  && Custom Calendar.ICO icon, no background
    "@D,Ok#,Cancel#",   ;  && Buttons, the '@D' at the beginning means DATEBOX mode
    CTOD("01/12/2019"), ;  && Default date
    "58000,<SECS> secs.") && Timeout





Sample 17: DateTime INPUTBOX() dialog

* Sample 17 - DATETIME INPUTBOX
? FoxyDialog("New DateTime box dialog", ;
    "Date and Time select", ;
    "Please enter the date and time of estimated delivery", ;
    "DateTime1.ico,B",  ;  && DateTime.ico, BLUE background
    "@T,Ok#,Cancel#",   ;  && Buttons, the '@T' at the beginning means DATETIMEBOX mode
 DATETIME(), ; 
    "58000,<SECS> secs.") && Timeout




Sample 18: Month Calendar date range INPUTBOX() dialog

* Sample 18 - MONTH CALENDAR RANGE INPUTBOX
* The date selection is returned in the 'loDatesRange' object as below
loDatesRange = FoxyDialog("New Datebox range dialog", ;
    "Date range select", ;
    "Please enter the date range for your report", ;
    "Calendar1.ico,S",       ;  && LOCK icon, BLUE background
    "@R,Ok#,Cancel#",   ;  && Buttons, the '@M' at the beginning means MONTHBOX mode
    CTOD("01/12/2019"), ;  && Default text
    "120000,<SECS> secs.") && Timeout

? "Initial date:", loDatesRange.StartDate
? "Final date:"  , loDatesRange.EndDate







APPENDIX 1:
The image below shows all the icons stored in ImageRes.Dll. Each icon comes with an ID. Vhoose your button, and pass its ID number to the "tcnIcon" parameter to have it displayed in your dialog.






APPENDIX 2:
Some of the available strings and values for the tnIcon parameter:
ComputerNumericIconBackgroundImage
"!"-1 or 107Warning shield - !
"BEEP"
"!2"-6Warning shield - !Yellow
"!3"84Warning - !
"!4"1403Warning - !
"X"-2 or 105Error shield - X
"BEEP"
"X2"-7Error shield - XRed
"X3"89Error - X
"X4"98Error - X
"X5"1402Error - X
"I"-3Information - i
"BEEP"
"I2"81Information - i
"?"0x7F02 or 99Question - ?
"?2"104Question shield - ?
"OK"106Success shield - Ok
"OK2"-8Success shield - OkGreen
"OK3"1400Success
"OK4"1405Success
"SHIELD"-4Shield
.....
"KEY"82Key
"KEY2"5360Key
"LOCK"59Lock
"LOCK2"1304Lock
"LOCK3"5381Lock
"ZIP"174Zip
"SEARCH"177Search
"SEARCH2"5332Search
"USER"1029User
"USER2"5356User
"CLOUD"1043Cloud
"CLOUD2"1404Cloud
"STAR"1024Star
"FOLDER"1023Folder
"MAIL"20Mail
"CONNECT"25Connect
"CONNECT2"179Connect
"PRINTER"51Printer
"PRINTER2"45Printer
"CAMERA"57Camera
"FILM"46Film
"FAX"76Fax
"DOCUMENT"90Document
"SCAN"95Scan
"COMPUTER"109Computer
"COMPUTER2"149Computer
"DIAGNOSE"150Diagnose
"MUSIC"1026Music
"CANCEL"1027Cancel
"WRITE"5306Write
"PLAY"5341Play
"CLOCK"5368Clock
"MOBILE"6400Mobile






* UPDATE * 2020-05-10
First of all, thanks very much for the positive feedback, bug reports and suggestions from my friends from Foxite
I applied several fixes and tweaks - the most important were:

New property:
- tnDefault, determines the default button that will receive the focus

Fixes:
- When "ESC" or the dialog closed by clicking at the "X" button made the function wrongly return the value "2"
- Sometimes the returned values were negative


They seem to be very simple, but were really very tricky to fix.
The TaskDialog API does not allow almost any external interference, and this obliged me to test several aproaches to try to bypass it. Hopefully things will run fine now.

The most annoying difficulty was with the fact that I could not resize the buttons. This obliged me to use the original "Cancel" button, that had its caption renamed. But whenever the user clicked on the "X" or pressed <ESC> , the Dialog interpreted that the button that originally was named as "Cancel" was pressed. I had to control the WM_KEYUP Windows event, detect if <ESC> was pressed, and change the returned value.
I could not hide the "X" button. I tried several aproaches that usually work in normal forms, but all I could do is to disable it.
If the dialog has 3 or more buttons, pressing <ESC> will close the dialog, returning the value 0 - ZERO.



* UPDATE * 2020-05-17
Some new enhancements and small tweaks, introducing the timeout property, and the possibility to use Unicodes, to get some cool characters to enhance our dialogs.


* UPDATE * 2020-05-23
New features:
- Add a custom icon with 6 predefined background colors: Red, Green, Yellow, Blue, Silver, and white
- Add true icons to your buttons

That means that we don't need to use the shield icons to get the colored backgrounds!
Some custom icons were also predefined.

For the buttons, you may use the icons from both "Imageres.dll" and "Shell32.dll"
Pass the captions with an "underline" - "_" symbol and the index number of the icon. By default, the icons will use ImageRes.dll. To use Shlell32.ddl's, add 100000 to the index number, see the samples below:


Comming next:

There is a much empowered version of the TaskDialog API, the TaskDialogIndirect function, that allows us to have several other components in our dialogs, such as Option buttons, Checkboxes, expandable footer, and even a ProgressBar!
I have this already working, with the help of another great VFPX project, VFP2C, that brings us the possibility to make Callback functions in VFP. I hope to publish it very soon.



See Also: