More Than Four

Areas of Interest, as counted by my cat

Page 10 of 12

FONTMETRIC(), TXTWIDTH(), and screen DPI in Windows

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.

Tie a UI Ribbon ’round that old menu tree

Office 12: Now with Bumps TM, thanks to superior Dalek technology.

Before you all go crazy adding new funky Ribbon UI technology to your contact manager application, please think about this: Do you really need it? Do your users really need it? Is your application’s command tree really that complicated and deep?

I doubt it very much.

Still, if this gets developers excited about smart/thick/local/non-web clients again, I’m all for it.

[Update] Well, that didn’t take long.

Structured error handling with NEWOBJECT errors

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 Execute
      TRY
         xxxxxx
      CATCH
         MESSAGEBOX(“Exception 2!”)
      ENDTRY
   ENDFUNC

   FUNCTION Error(nError, cMethod, nLine)
      MESSAGEBOX(MESSAGE())
   ENDFUNC   
ENDDEFINE

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?

Extending the Report Builder’s Multiple Selection dialog

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.

Link: Extending the Report Builder’s Multiple Selection dialog

[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.]

Why do report layouts in VFP9 need wider field/expression controls than in VFP8 and earlier?

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.

Pessimism is Positive

It’s really better to be a pessimist than an optimist. I think pessimists get a bad rap. Sure, everyone likes the idea of being upbeat and happy. But it’s not a good survival perspective. Problems only get solved because people worry about them.

David Brin answered the critics of the pre-millennium Y2K disaster theorists by saying that it was a classic case of a self-defeating prophesy. I agree with him. If everyone had just said, “She’ll be right, mate” and done nothing at all about the problem, then almost certainly IT departments everywhere would have been extremely busy and stressed come Jan 1, 2000. (You just know that’s an understatement.)

Good programmers are pessimists. They have to be – they learn to be very early in their career.

“That will never work, you know.”

“Why not? I’ve debugged it, it runs fine. I’ve tested it. I’m a good programmer…”

“What about more than one user running that screen at a time? What about a user who stays in that screen overnight while your midnight triggers are running? What about machines with no audio card? No installed printer? You’re doomed.”

“Why, I’ll show you, you… you… big fat pessimist, you.”

Techniques for alternate Report Previews in VFP 9.0

Quite a few of the discussions on the Universal Thread Visual FoxPro forum recently have been on the subject of the new Report Preview screen in Visual FoxPro 9.0, and how to control it. A particular concern is the behavior of the preview toolbar when a REPORT FORM.. PREVIEW is issued in a topform application. In my sessions I have striven to show that the default preview application is just that – a default – and that alternative forms of a preview solution are not only possible, but easy to implement. In this article I show an alternative user interface for the report preview window that you can take away and customize to your heart’s content.

Link: Techniques for alternate Report Previews in VFP 9.0

Spanish translation available here:
 http://www.portalfox.com/article.php?sid=1944 

COM Interop

This week I wanted to get my ASP.NET web application to create a text file and return it to the browser. The catch was, the text files are created by some pretty gnarly FoxPro code (sourcing Fox data). The solution seems to be to use COM interop. It’s a lot easier to rework that vintage FoxPro procedural code into a VFP COM server than it is to re-implement it using DataSets in C# (or VB for that matter).

Rick Strahl has a good article discussing this, and there is quite a lot of “high level” information on COM interop out there on the web also. Briefly, here is what I did:

I re-packaged the pertinent FoxPro code into a class method of a COM server. I tested this out in the VFP Command window:

x = CREATEOBJECT("MyServer.MyClass")
_cliptext = x.GetTextDoc("123")

Now, in the web application I wanted a hyperlink that would return the text document to the browser. So I was looking to implement a URL like this:

<a href="CreateTextDoc.aspx?param=1234">Create Text Document</a>

Now, CreateTextDoc.aspx is basically all code-behind – there is no HTML code, just the directives:

<%@ Page language="c#" 
         Codebehind="CreateTextDoc.aspx.cs" 
         AutoEventWireup="false" 
         Inherits="MyCompany.MyProject.MyBasePage" 
         ASPCOMPAT=true
%>

That last page directive is specifically for pages that instantiate COM objects. Some people say it’s optional, only necessary when you are embedding the object into the page directly with <object> tags. Others say that you should use it even if you are using the early binding wrapper classes. I figured, better safe than sorry.

Now, the code-behind:

  using System;
  using System.Web;
  using MyServer;
  :
  private void Page_Load(object sender, System.EventArgs e)
  {

      // get the parameter:
      //
      string cParam = "";
      cParam = Request["param"] ;

      // We're returning a text file:
      //
      Response.ContentType = "text/plain";

      // This forces nicer behavior in IE, prompting to download or open as text.
      // also the filename is now "1234.txt" instead of "CreateTextDoc.aspx"
      //
      Response.AddHeader("content-disposition","attachment; filename="+cParam+".txt" ) ;
		
      // Create the COM server wrapper:
      //
      MyServer.MyClass x = new MyServer.MyClassClass() ;

      // Generate and return the text:
      //
      Response.Write( x.GetTextDoc( cParam )) ;

      return ;
  }

Nothing tricky there, once you understand the odd invocation of the COM server through the .NET wrapper class.

This worked a treat: In FireFox, the text file appears in the browser quite naturally. IE screwed up, however, because it appeared to be trying to recognise the plain text response as some other kind of format. Rather than do the registry hack, I added the content-disposition header to the response. Now the behavior in IE is actually preferable: I get a dialog prompting me to Open or Save As. If I Open, the text file opens in Notepad. This is better for my users – although I would have been content with the Firefox behavior (pardon the pun).

Now we get to the problem: Every time I hit that URL to generate a text file, another instance of my COM server executable was created. Ouch! If I waited long enough – about 20 minutes – they would terminate and disappear from Task Manager. But as it was, this didn’t scale well at all.

I mentioned this behavior to Rick, and he couldn’t explain why the MyServer.exe instances were hanging around. But he did have a good suggestion: “If the object is not releasing you can call Marshal.ReleaseComObject() to make sure it gets cleaned up and doesn’t wait for the GC to clean it up for you.

This change was easy to make. First, I added the reference to the top of the file:

using System.Runtime.InteropServices;

Then, after the call to GetTextDoc(..):

      // Create the COM server wrapper:
      //
      sisserver.ArtForm x = new sisserver.ArtFormClass() ;

      // Call the Artform listing generator:
      //
      Response.Write( x.GetArtFormDoc( cArtform ) ) ;
      
      // Dispose of the COM server manually:
      // 
     System.Runtime.InteropServices.Marshal.ReleaseComObject( x );

This worked beautifully. No more hanging executables in Task Manager! My application scales again. Thanks for the pointer, Rick.

« Older posts Newer posts »

© 2025 More Than Four

Theme by Anders NorenUp ↑