Not wanting L to get all the fun, I’ve posted a new article on the Spacefold article libary:
This used to be a series of blog posts but now it is a shared Google Doc.
Link: I hate knobs
I’ve never really been happy with how checkbox controls look when contained in Grid columns, displaying logical values. Lately I’ve been using the method described below.
Given a table with a logical column such as:
create cursor FILMLIST values ( VIEWED L, TITLE C(50) )
I use an expression for the grid column instead of referencing the table column name directly:
Alignment=2-(Middle Center) ControlSource = iif(FILMLIST.VIEWED,"ü"," ") FontName=Wingdings FontSize=11 ReadOnly=TRUE
The default textbox control in the column will display the logical value as a tick mark (the WingDings character for ü):
This is all you need to do for a read-only column. For my users, I make the column editable by adding some code to the textbox’s .DblClick() event:
replace FILMLIST.VIEWED with !nvl(FILMLIST.VIEWED,.F.) this.Refresh()
This toggles the logical flag when the user double-clicks the mouse in the column – and handles possible NULL values as well.
As an exercise for the student, try adding support for toggling the flag using the keyboard SPACE bar.
In my session sample code for the Advisor Summit on Visual FoxPro, I showed using the gpColor class in the FFC\_gdiplus.vcx class library to convert a color from foxpro RGB to the GDI+ style ARGB, including the alpha channel byte:
: this.colorHelper.Set( mod(m.iRGB,256), ; mod(int(m.iRGB/256),256), ; mod(int(m.iRGB/(256*256)),256), ; this.alpha ) this.brush.BrushColor = this.colorHelper.ARGB :
I see that Walter Nicholls mentions this subject back in May and shows an alternative method that does not require the gpColor object:
this.brush.BrushColor = ; bitor( bitlshift(bitand(iif(vartype(this.alpha)=='N', this.alpha, 0xFF), 0xFF ),24) ; , bitlshift(bitand(m.iRGB,0xFF),16) ; , bitand(m.iRGB,0x0000FF00) ; , bitrshift(bitand(m.iRGB,0x00FF0000),16) )
Although I’m tempted to describe this as an example of “write-only code”, it does show good use of those binary manipulation functions.
In hindsight, it should be obvious. But it turns out that in Visual FoxPro 9.0 (and, I suspect, every previous version as well) the SYS(16) function returns different results depending on the setting of the Debug Info checkbox in the Project Information dialog. So if you are relying on these results, you’d better make sure you understand the difference.
To recap, here are some extracts from the Help file:
SYS( 16 ) returns the file name of the program being executed.
If n is 0 or 1, SYS( 16, n ) returns the name of the main program (the program first executed). The name of the currently executing program is returned if n is omitted. The empty string is returned if n is greater than the program nesting depth.
When the executing program is part of an application (.app), SYS(16) returns only the name of the program, that is, without the path. SYS(16) returns the name of the executable file if called from an executable (.exe) file. If a procedure or function is being executed, SYS(16) returns the name of the file containing the procedure or function after the procedure or function name.
I’m not sure I completely understood that, so when in doubt, I do some empirical tests. Using the source code to ReportBuilder.App (conveniently included in the XSOURCE.ZIP file in the TOOLS directory in the Visual FoxPro home directory) I added a breakpoint on a particular method and displayed the Watch Window and took a look around in the call stack using SYS(16,n):
SYS(16,8) "PROCEDURE FRXSETUPUTILS.GETAPPLICATIONPATH FRXSETUP.FXP" SYS(16,7) "PROCEDURE FRXSETUPUTILS.GETBUILDERREGISTRYTABLENAME FRXSETUP.FXP" SYS(16,6) "PROCEDURE FRXEVENT.GETREGISTRYTABLENAME C:\TEMP\REPORTBUILDER\FRXBUILDER.VCT" SYS(16,5) "PROCEDURE FRXOPTIONS.EXECUTE C:\TEMP\REPORTBUILDER\FRXBUILDER.VCT" SYS(16,4) "PROCEDURE FRXEVENT.SHOWOPTIONS C:\TEMP\REPORTBUILDER\FRXBUILDER.VCT" SYS(16,3) "PROCEDURE FRXEVENT.EXECUTE C:\TEMP\REPORTBUILDER\FRXBUILDER.VCT" SYS(16,2) "PROCEDURE REPORTBUILDER.PROCESSEVENT C:\TEMP\REPORTBUILDER\REPORTBUILDER.APP" SYS(16,1) "C:\TEMP\REPORTBUILDER\REPORTBUILDER.APP"
This is all fine and good. It may even match the documentation, it’s a little hard to be sure. The interesting thing that does not appear to be documented is that these results are affected by the “debug info” checkbox in the Project Information dialog box. Here’s a picture of it in case you don’t remember seeing it before. Open a project, and select Project->Project Info from the menu (or press Ctrl-J):
Normally, it’s checked. So I cleared the checkbox (indicating that I did not want additional debug information compiled into the application), recompiled, ran, and suspended at the same point:
SYS(16,8) "PROCEDURE FRXSETUPUTILS.GETAPPLICATIONPATH FRXSETUP.FXP" SYS(16,7) "PROCEDURE FRXSETUPUTILS.GETBUILDERREGISTRYTABLENAME FRXSETUP.FXP" SYS(16,6) "PROCEDURE FRXEVENT.GETREGISTRYTABLENAME " SYS(16,5) "PROCEDURE FRXOPTIONS.EXECUTE " SYS(16,4) "PROCEDURE FRXEVENT.SHOWOPTIONS " SYS(16,3) "PROCEDURE FRXEVENT.EXECUTE " SYS(16,2) "PROCEDURE REPORTBUILDER.PROCESSEVENT C:\TEMP\REPORTBUILDER\REPORTBUILDER.APP" SYS(16,1) "C:\TEMP\REPORTBUILDER\REPORTBUILDER.APP"
Interesting. We can still read the names of the procedure (.FXP) files, but not the class library (.VCT) files. I wonder why that is? Your guess is as good as mine.
Alex Sosa asked a question on the Universal Thread recently about right-aligned text in Visual Foxpro 9.0. After some back-and-forth email messages, I decided to summarise my findings in a small article.
If you are trying to size objects on a VFP form to match a specific font, you have to take into account the current screen DPI setting as well. Consider sizing a Rectangle object to surround a given piece of text. You know the fontname, fontsize, and the actual text string, but you don’t know the dimensions. No problem, you say, I’ll just use FONTMETRIC() and TXTWIDTH() to figure out the size of the text in pixels:
iAvgCharWidth = FONTMETRIC(6, cFontName, cFontSize, "" ) iTextWidth = TXTWIDTH( cString, cFontName, cFontSize, "" ) * iAvgCharWidth
…and now I can size my Rectangle appropriately, you say.
Not so fast. What if your Windows display is set to 120 DPI? The text will be sized differently. You have to take this into account:
*--------------------------------- * Determine the screen DPI: *--------------------------------- #define LOGPIXELSX 88 declare integer GetDeviceCaps in WIN32API integer HDC, integer item declare integer GetDC in WIN32API integer hWnd *declare integer DeleteDC in WIN32API integer HDC declare integer ReleaseDC in WIN32API integer hWnd, integer HDC local hdc, screenDPI hdc = GetDC(0) THIS.screenDPI = GetDeviceCaps( m.hdc, LOGPIXELSX ) ReleaseDC( 0, m.hdc ) IF iScreenDPI <> 96 iTextWidth = INT( (iTextWidth * iScreenDPI)/96 ) ENDIF
I was grappling with issue recently, dealing with a bug reported by someone who said that Pageframe text captions were getting truncated when they set their environment to 120DPI + “Large Fonts”.
Well, this morning I have read a post by the remarkable Charles Petzold about Device Independent Units (DIU) that goes some way (and more) to explaining why we have to do it, because I believe that Visual FoxPro uses actual pixels rather than DIU. His post his worth a read.
Marcus wrote a very good article about the TRY-CATCH structured error handling in VFP8 and later versions. One section in particular caught my eye because it directly relates to an issue I’ve hit recently:
[..] the result is opposite from the previous example. The Error()event takes precedence over the Try/Catch and handles the error inside the called object.
So what would happen if we added some structured error handling to the TestClass object?
DEFINE CLASS TestClass AS Custom
FUNCTION Error(nError, cMethod, nLine)
In this example, the new Try/Catch will handle the error since it has been defined at a higher level of granularity.
Alas, this is not strictly true for every case. Consider the following code:
q = newobject("myTest") q.Execute() define class myTest as custom procedure execute try *----------------------------------------- * This is handled nicely by the * surrounding TRY_CATCH: *----------------------------------------- y = newobject("invalidClass") *----------------------------------------- * This one ignores the TRY-CATCH and * triggers the .Error() event: *----------------------------------------- THIS.NewObject( sys(2015), "invalidClass", "invalidLib.vcx" ) catch =messagebox( "We successfully caught the error in the catch.",64 ) endtry endproc procedure error( p1, p2, p3 ) =messagebox("The object's error() event was triggered instead!",16) endproc enddefine
The structured error handling works as expected for the NEWOBJECT() function call, successfully trapping the “class not found” error. However, the method call THIS.NewObject() will force the objects .Error() event to fire, requiring a completely different set of handling code to suppress the error. This is not a bug, it’s the way objects work. Objects always defer to their own .Error() events to handle errors. Annoying, isn’t it?
David Stevenson has just informed me that my article in the September issue of FoxTalk is available online: In this article, Colin Nicholls shows you how to extend the VFP 9 Report Builder’s Multiple Selection dialog with additional functionality you design. In doing so, he also reveals the secrets of the Report Builder’s lookup table, or registry, and demonstrates how you can substitute your own version to customize the Report Builder’s native behavior.
[Update: This article no longer appears to be available online. Sorry about that! For what it is worth, this feature has been revised for VFP 9.0 SP2 and although the technique is still valid for earlier versions of the product, you will probably want to pay attention to the changes when SP2 is released.]
A common question about running reports created in Visual FoxPro 8.0 (and earlier) in VFP9 is “Why do I have to make my field/expression controls slightly bigger to avoid having my data truncated in the report output?”.
The glib answer is that, when SET REPORTBEHAVIOR 90, vfp9 uses GDI+ to render text instead of regular GDI. GDI+ requires a little more space to render the same text string than GDI.
If you’re happy with that answer (which is essentially “Just because!”) then great. No need to read further.
If you’re interested in exactly why GDI+ needs to allow more space when rendering text, then check out this: http://support.microsoft.com/?id=307208. It’s pretty interesting.
Update 16-June-2006: This related blog post Text Mess in .NET by Wesner Moise is also interesting.