Friday, December 21, 2007

Code Signing Your VFP EXEs

John Robbins has a great post on why and how to digitally sign your EXEs. As he mentions, this is very important, especially on Windows Vista, if you want to have a professional looking application.

I've been using code signing for most of this year. We actually not only sign our application's EXE but also the installer so the user doesn't get the "unknown publisher" dialog when they install it.

Thursday, December 13, 2007

Sorting in Natural Order

Jeff Atwood discussed "natural sorting" yesterday in his Coding Horror blog (a blog I highly recommend, by the way). This is something I've needed to do every now and then, so I decided it was time to write a function to do that. The code is shown below. Note that this isn't necessarily the most elegant solution; I used the "brute force" method, so I appreciate any comments about tightening it up.
* Function: NaturalSort
* Purpose: Sorts an array in natural rather than ASCII order
* Author: Doug Hennig
* Last Revision: 12/13/2007
* Parameters: taArray - the array to sort (passed by reference)
* tnColumn - the column to sort on (optional: if it isn't
* specified, column 1 is used)
* tlDescending - .T. to sort in descending order; if .F. or
* not specified, ascending order is used
* Returns: .T. if it succeeded or .F. (and an error is raised) if
* invalid parameters are passed or the specified column
* doesn't contain a homogeneous data type
* Environment in: none
* Environment out: none

lparameters taArray, ;
tnColumn, ;
local lnColumn, ;
lnRows, ;
lnCols, ;
lnOrder, ;
laArray[1], ;
lnI, ;
lcKey, ;
llInNumeric, ;
lcString, ;
lnJ, ;
lcChar, ;
llNumeric, ;
lcNumeric, ;
laClone[1], ;

* Define some constants.

#define cnLENGTH 20
&& the length to pad numeric sections to
&& Function argument value, type, or count is invalid
&& Data type mismatch

* Ensure taArray is an array and tlDescending is logical if it's specified.

if type('taArray', 1) <> 'A' or ;
(pcount() = 3 and vartype(tlDescending) <> 'L')
return .F.
endif type('taArray', 1) <> 'A' ...

* If the column to sort on wasn't specified, assume 1.

lnColumn = iif(pcount() = 2, tnColumn, 1)

* Figure out the size of the source array.

lnRows = alen(taArray, 1)
lnCols = alen(taArray, 2)

* Ensure the column to sort on is valid.

if vartype(lnColumn) <> 'N' or not between(lnColumn, 1, max(lnCols, 1))
return .F.
endif vartype(lnColumn) <> 'N' ...

* Figure out the order flag for ASORT().

lnOrder = iif(tlDescending, 1, 0)

* Get the data type of the first key value. If it isn't character, we don't
* have to do anything fancy; ASORT() will take care of it for us.

if vartype(taArray[1, lnColumn]) = 'C'

* Create an array we'll sort on.

dimension laArray[lnRows, 2]

* Go through each element we're sorting on.

for lnI = 1 to lnRows
lcKey = taArray[lnI, lnColumn]

* Bug out if the data type is different.

if vartype(lcKey) <> 'C'
return .F.
endif vartype(lcKey) <> 'C'

* Create a key that will sort properly by looking for numeric sections and
* left-padding them with zeros.

llInNumeric = .F.
lcString = ''
for lnJ = 1 to len(lcKey)
lcChar = substr(lcKey, lnJ, 1)
llNumeric = isdigit(lcChar) or ;
(lcChar = '.' and isdigit(substr(lcKey, lnJ + 1, 1)))
do case
case llNumeric and llInNumeric
&& if we have a digit and we're already in a numeric
&& section, add to the numeric part
lcNumeric = lcNumeric + lcChar
case llNumeric
&& if we have a digit and we're not in a numeric
&& section, flag that we are in such a section and add to
&& the numeric part
llInNumeric = .T.
lcNumeric = lcChar
case llInNumeric
&& we don't have a digit and we were in a numeric section
&& so pad the section and add it to our string
llInNumeric = .F.
lcString = lcString + padl(lcNumeric, cnLENGTH, '0') + ;
lcString = lcString + lcChar
next lnJ

* Finish the string if we were still processing a numeric section.

if llInNumeric
lcString = lcString + padl(lcNumeric, cnLENGTH, '0')
endif llInNumeric

* Store the new key and the original index in our sort array.

laArray[lnI, 1] = lcString
laArray[lnI, 2] = lnI
next lnI

* Now sort the array and reorder the values in the source array by cloning it
* and copying the values from the each row in the clone to the new row in the
* source array.

asort(laArray, 1, -1, lnOrder, 1)
acopy(taArray, laClone)
for lnI = 1 to lnRows
lnIndex = laArray[lnI, 2]
if lnCols > 0
for lnJ = 1 to lnCols
taArray[lnI, lnJ] = laClone[lnIndex, lnJ]
next lnJ
taArray[lnI] = laClone[lnIndex]
endif lnCols > 0
next lnI

* Just use ASORT to do the job.

asort(taArray, lnColumn, -1, lnOrder, 0)
endif vartype(taArray[1, lnColumn]) = 'C'
return .T.

Wednesday, December 05, 2007

Where Should Data go in Vista?

In part 1 of my two-part Advisor article on Windows Vista (, I discussed some of the recommended places writable files should go in Vista. (To refresh your memory, your program files folder is most likely read-only even for administrators, so you can't store anything that needs to be updated there, such as your application's tables, INI files, etc.) However, I've recently changed where I store some writable data to a Data subdirectory of the program folder. The reason: I need a consistent location to look for global (that is, not user-specific) data. If the user installs the app on a server, I can't use a location like {CommonAppData} because that's on the user's hard drive. I need a location I can always count on regardless of whether the app's on a local or network drive.

"Wait a minute," you're probably thinking. "That's (1) bad practice and (2) not allowed." Although I agree it's not an ideal location, it's not a completely bad practice. The reason for the program folder being read-only is to prevent malware from attacking your EXE and DLL files. Putting only data files into a Data subdirectory doesn't open any security holes because no executables go there.

As for not being allowed, that's true by default. However, you can easily change permissions on the folder when your installer creates it. In Inno Setup, simply create your directory like this:
Name: "{app}\Data"; Permissions: everyone-modify
This gives all users the ability to write to the folder.

I don't store much in Data, certainly not the data files my application uses. The main thing stored there is an INI file that contains a setting specifying the location of the data files. When the user starts the app for the first time, a dialog prompts them for the data file location and writes the response to the INI file (which it can do because the INI file is in the writable Data folder). Our app looks in a consistent writable location for the INI file, making the code easy.

Monday, November 26, 2007

Edit Property/Method Dialog Replacement Available

On Friday, I posted an update for the VFPX New Property/Method Dialog Replacement project that adds a new feature: a replacement for the Edit Property/Method dialog, created by Marcia Akins.

In case you're not familiar with this project, its purpose is to replace the native VFP New and Edit Property/Method dialogs with dialogs that have a lot more functionality. The New dialog has the following features:
  • It automatically updates the _MemberData property (adding that property if necessary) so the case entered for the new member is used (even for access and assign methods if they're created as well) and the member is displayed on the Favorites tab if that option is turned on in the dialog.
  • It's non-modal. That means you can keep it open, add some properties or methods, switch to other things, come back, and add some more members.
  • How many times have you accidentally selected the wrong dialog, entered the name and description, only to realize your mistake and have to close the dialog and start all over? With the replacement dialog, you can switch between Method and Property without closing the dialog.
  • It's dockable: try tab-docking it with Properties window.
  • It's resizable and persists its size and position to your resource (FOXUSER) file.
  • It has an Add & Close button to perform both tasks with one click.
  • The default value for a property is automatically set to a value based on the data type of the property if you use Hungarian names. For example, lTest would be logical, so the default value is .F. For nTest, the default is 0.
  • For logical properties (those with a default value of .T. or .F.), turning on the Use Toggle Editor option installs a property editor that allows you to double-click the property in the Properties window to toggle its value. This property editor is included inside NewPropertyDialog.APP.
  • It hides rather than disables non-applicable controls. Since this one dialog is used for both New Property and New Method for both the Class and Form Designers, some options may not be applicable for a given instance.
  • It disallows invalid names when you enter them rather than when you click on Add or Add & Close.
  • The Add buttons are only enabled if a name is entered.
The Edit dialog has the following features:
  • It automatically selects the chosen member when you right-click a member in the Properties window and choose Edit Property/Method from the shortcut menu.
  • While it's easy to add a member to the Favorites tab of the Properties window (right-click and choose Add to Favorites), there isn't an easy way to remove it from Favorites. The replacement dialog has a Favorites button, making it easy to add or remove a member from Favorites.
  • If you delete a member that has associated member data, the member data is automatically removed for that member.
  • It automatically updates the member data when you rename a member so it respects the case of the member's name.
  • Like the New Property/Method dialog, it's non-modal, dockable, resizable and persistent, and allows changing a property to a method and vice versa.
  • Selecting the description of a member and pressing Ctrl+C to copy it causes the Apply button to become enabled in the native dialog, requiring you to either choose Apply or Cancel to close the dialog (pressing Esc brings up a "save changes" dialog). The replacement dialog does not have this behavior.
  • The New button launches the replacement New Property/Method dialog.
  • The Zoom window (right-click on the Properties window for a property and choose Zoom) is resizable.
Documentation is provided in a CHM file, but like every downloaded CHM has an issue with security. To fix that, right-click the CHM, choose Properties, and click Unblock.

To install the dialog, download the code and run both of the APP files. This will install the necessary MENUHIT and MENUCONTEXT records into your IntelliSense table to enable the new dialogs. Full source code is provided so feel free to check out how these dialogs work. Please post any suggestions for improvements to the VFPX site.

Thursday, November 22, 2007

Fixing the "Reinstall Your App" Problem

Have you ever installed a new application only to find that running an old application now brings up a dialog asking to install it or a completely different application? That happens because when updated ActiveX controls are installed, their InprocServer settings in the Windows Registry, which points to the physical location of the OCX file, are overwritten with Windows Installer encrypted keys. When you run an older application that uses those ActiveX controls, Windows Installer thinks there’s something wrong with the ActiveX control and so prompts you to reinstall the application. This is a common conflict between VFP applications using ActiveX controls like the TreeView and Microsoft Office, for example.

This problem doesn't occur if you use Inno Setup instead of Windows Installer-based tools such as InstallShield. In the mean time, the workaround is to find the Registry entry for the ActiveX control and fix its InprocServer32 entry. This is easier said than done, because you don't necessarily know which control to fix; you need to find entries for all the controls your app uses. Here's how you do this:
  • Launch RegEdit.
  • Find "treeview" or whatever control you're looking for. The entry you want is in HKEY_CLASSES_ROOT\CLSID and it'll be a GUID. For example, for the TreeView control version 6, the entry is HKEY_CLASSES_ROOT\CLSID\{C74190B6-8589-11D1-B16A-00C0F0283628}.
  • Look in InprocServer32 for the InprocServer32 key. It's supposed to contain the name and path for the OCX, but notice it contains gibberish; that's the Windows Installer encrypted key that's causing the problem.
  • Edit the value and replace it with the name and path for the OCX; for the TreeView control, it should be C:\Windows\System32\MSComCtl32.OCX (the "(default)" value contains this, so you can copy it from there and paste it into InprocServer32).
  • Repeat for each of the controls your app uses.

Monday, November 19, 2007

New Southwest Fox Blog

A new blog dedicated to Southwest Fox is now available.

Update for Sedna Upsizing Wizard

Someone recently emailed me about a couple of changes they wanted in the new Upsizing Wizard in Sedna. One was the ability to select Varchar(MAX) as the data type to use when upsizing a memo field. The other was the ability to use a different date in the place of blank VFP dates; the default in the Upsizing Wizard is 01/01/1900 but they wanted to use 12/31/1899 instead.

The first change simply involved adding a new record to TypeMap.DBF, the table containing the type mappings. Set LocalType to "M" (for Memo), FullLocal to "memo", RemoteType to "varchar(max)", and Server to "SQL Server". Leave the logical fields as .F., especially VarLength, which determines whether the user is prompted for a field length.

The second was a little more work. First, BulkXMLLoad.PRG needs a change to support this because it used a hard-coded value. Change the statement setting ltBlank to:

ltBlank = iif(vartype(ttBlank) $ 'TD', ttBlank, SQL_SERVER_EMPTY_DATE_Y2K)
and add the following statement after the LOCAL statement:

#include Include\AllDefs.H
Second, JimExport (interesting method name!) in WizUsz.PRG also used a hard-coded value, so change the assignment to lcEmptyDateValue in the first CASE statement to:

lcEmptyDateValue = "IIF(EMPTY(" + ALLT(lcCursorName)+'.'+ALLT(aTblFields[ii,1])+ "), " + ;
transform(This.BlankDateValue) + ","
Finally, to change the actual date, change these two constants in AllDefs.H to the desired values:
Rebuild UpsizingWizard.APP and you're ready to go.

Tuesday, November 06, 2007

Cathy Blogs!

In the "it's about time" category, Cathy Pountney has started blogging. Looks like the arm twisting we did at Southwest Fox did the trick. I'm really looking forward to her posts. Her sessions at Southwest Fox were very well attended and rated (we're compiling the evals right now), so being able to get snippets of her wisdom on a regular basis is most welcome.

Friday, November 02, 2007

My Newest Time Waster

Rick Schummer blogged yesterday about a new time waster for him: 3-D Pong. My new time waster is, "a webcomic of romance, sarcasm, math, and language". And programming: check out Exploits of a Mom. I actually laughed out loud at that one. Then I started going through some of the several hundred other comics in the collection and poof, an hour had gone by. Fortunately, it has an RSS feed so I can get my daily dose.

Heading for Germany and Holland

On Tuesday, I'm heading for Frankfurt for my third German DevCon (and the 14th edition of this great conference). About 1/3 of the sessions are in English, and there are English sessions in almost every timeslot, so if you're unilingual as I am (and not proud of it, either), there are plenty of great sessions by speakers such as Andy Kramek, Marcia Akins, Rick Schummer, Steve Black, Craig Berntson, and Alan Griver to attend. I'm even planning on attending a couple of German sessions, in particular Christof Wollenhaupt's session on Guineu. He showed a few minutes of it during the Southwest Fox keynote, enough to whet my appetite for more.

One of the interesting things about German is that it seems to be relatively easy to pick up at least a few words. There are lots of similarities between words in German, English, French (which I took in high school 75 years ago), and Dutch (which my wife speaks fluently so I've picked up a very small amount), enough so that sometimes I can at least get the gist of a conversation if not the details. Plus, in technical sessions, there seem to be actual English words in almost every sentence, and of course, code is code.

After DevCon, Rick Schummer and I head for Holland to speak at a one-day VFP conference hosted by Software Developer Network. The next day, we're going to do some sightseeing in Amsterdam, which I visited five years ago and have been wanting to go back to. Rick's going to be in shock when he sees the Red Light District, which we pretty much can't avoid since it's right by the central train station.

Thursday, November 01, 2007

Duct Tape Marketing

I rarely read business books, but as my business partner Mickey's urging (much closer to nagging, actually), I read Duct Tape Marketing by John Jantsch on my flights to and from Phoenix for the Southwest Fox conference. Wow! This is one of those seminal, "this will change your life" kind of books (well, not your life; if this book changed your life, you really needed to get a new life!). The book is an easy read: there's no marketing theory or page-filling fluff; it's just jammed full of ideas and "to-do's" about how to market your business. One of the key points is that you are in marketing even if you don't think you are. Everyone is in marketing to some extent or another.

I was so excited about the ideas I read in this book that I took several pages of notes. Mickey has been ordering copies of this book for everyone in our company, and we're even considering giving it to clients. It's now on my "requirted reading" list.

Friday, October 26, 2007

Southwest Fox 2007 Day 4

Last day. In a way, I'm relieved; this has been a ton of work and an exhausting several days. On the other hand, people I like and respect and only see a couple of times a year or less will be heading home today or tomorrow, so I'm kind of bummed too.

Once again, I missed a session. Bo Durban and I inadvertently did some pair programming. He started showing me some really cool things his product, Moxie Report Objects, does, then mentioned another cool feature he was thinking of adding: a find function in the Report Designer that would find any text in a report expression, group expression, print when expression, etc. He started working on it, I made some suggestions, he tweaked something, and before you knew it, we were pair programming. It was a lot of fun and Bo ended up creating a working version of the code in no time. Once again, it's the networking that's the best part of conferences.

After presenting the second instance of my vertical app development session, I squeezed myself into what little space was left in Cathy Pountney's Outfox the VFP Report Writer session. I'd heard great comments from other attendees and Cathy didn't disappoint. Her session gets my vote for best in show. She showed how to take control of the printing process, giving you the ability to create books (with their out-of-order pagination), offset the top and left edges of a print job, printing N-up pages per sheet, accessing the properties dialog for a printer, and many other things. The best part is that all of the code is on the conference CD, so I can start implementing her ideas right away.

After a quick break to rearrange the four break-out rooms into one large one, we started the closing session. We reminded everyone to turn in their evaluation forms; those that did were entered into a drawing for a free registration to next year's conference. That's how important evals are to us and the speakers. Rick thanked speakers, sponsors and exhibitors, Arizona Golf Resort staff, Rick Borup (for creating the RSS feed for; we somehow missed thanking him in the keynote), and attendees. We then gave out thousands of dollars in software, books, and other goodies donated by sponsors. We announced the dates for Southwest Fox 2008: October 16 - 19, at a location to be announced later (although it's fairly likely we'll be back at the Arizona Golf Resort). Dave Aring created a cool animated logo just for this (for some reason, it's not animated here):

We listing some resources, such as the Universal Thread, Foxite, Foxforum, user groups, the Fox Wiki, blogs, VFPX, and FoxCentral. We finished up asking for photos and testimonials and wishing everyone a safe trip home.

Rick, Tamar, and I had a lot of people come up to us during and after the conference thanking us for taking up the reins of Southwest Fox and for putting on such a great conference. The buzz at the conference was amazing. Everyone was so jazzed about VFP that they were almost bouncing off the walls. I personally felt totally energized and full of new ideas for my applications, and I'm sure almost everyone else felt the same way.

After cleaning up the conference area and packing up the few things we had to take home, about thirty of us went to the hotel restaurant for lunch. My last meal at the hotel didn't disappoint either; the burger was one of the best I've ever had.

Tamar, Rick, Marshal, Therese, and I then met with the hotel staff for a post-conference debriefing. We told them about the few complaints we had and those we heard from attendees, but they were very picky things; nothing was even close to being a major issue. The hotel staff, especially those in the conference and catering areas, were incredible. When we asked for something, it was cheerfully done in minutes. Mark, Brett, Mari, Sharon, and their staff bent over backwards for us and were always pleasant and fun to work with. We let them know how pleased we were with the facilities and service.

We then had a post-conference meeting of our own, going over things we need to ensure we do next year (such as signage for the registration table and providing coffee and beverages during breaks). We even started planning next year's conference, so save the date!

Rick and Therese headed to Sedona for a few days of much-deserved R & R. About twenty of those still around went to My Big Fat Greek Restaurant for dinner and had some really good Greek food. wOOdy and Christof ordered a Meat Lover's Platter for two that looked like it was really for four; I wanted to see those guys eat that much food, but they were done after less than half the plate.

Thanks again to everyone who helped make this a great conference. We'll see you again next year!

Southwest Fox 2007 Day 3

I went to Alan Steven's Integrating Windows Forms UI Elements session. It was nice to see so many people (the room was almost full) at an 8:00 session, especially given the intensity of the day before, and the pre-con day for those who attended, and the West Wind Web Connection training the two days before that. Alan stressed several times that .NET is not a competitor to VFP; it should be part of every developer's toolkit. He showed how the Windows Forms Interop Toolkit from Microsoft can be used to easily create a .NET Windows form that's exposed as a COM object that can be called from VFP applications. However, not many developers will do that because it's a completely stand alone form. He then showed the new Interop User Control that can be used to easily create ActiveX controls you can drop on VFP forms. He showed how easy it is to raise events in the control so you can put code into the event in your VFP form. Now we can easily take advantage of the really cool UI objects .NET has to offer in our VFP forms. He didn't present this as a means of migrating your apps to .NET but as a way of making your VFP forms look better and have more functionality.

I skipped the next session to catch up on email and forum and blog reading (gotta love free wireless access in the conference area!). I then did the second instance of my VFP Vista session. Once again the room was full, so that means that more than half of the attendees saw this session.

Lunch was again served outside in the courtyard. It was Italian day, so we had antipasto and canneloni. Once again, the food was great. I sat outside and had a lively discussion of health care in Canada vs. the US. The attendee who asked about Canadian health care wished he hadn't; it's one of my hot buttons, and I guess I got a little zealous -- sorry about that! It was pretty warm and I started sweating, so I headed back into the air conditioned comfort of the conference center.

I missed the first post-lunch session because I was chatting with some other attendees. As many people will tell you, networking is one of the best things about going to a conference. I also chatted with several of the vendors. Chick Bornheim from Micromega told me that he had a better response at Southwest Fox 2007 than all of the previous three years combined. Although I really wanted to see Toni Feltman's DBI controls session, I ended up missing that too while talking to folks.

The last session of the day was our vendor session for Stonefield Query and we once again had a lot of people show up, this time without any free beer. I love showing this product and watching the smiles on the faces of developers when they realize how much it'll help their users and themselves in providing a customized reporting solution for their application.

The speakers headed to Rustler's Rooste for a dinner to show our appreciation for all the effort they put into creating sessions and white papers, practicing their sessions, and presenting them to the attendees. The restaurant is massive and has a great view from its location on the side of one of the many mountains in Phoenix. I was a little disappointed in the food, but the company was great. A magician showed off some pretty cool tricks after dinner; we talked about one of them for quite a while afterward.

Those interested in indoor kart racing went to the F1 Race Factory to reprise the races we had at last year's conference. Cathy Pountney narrowed edged out Rick Schummer last year (I was a close third), so there was some serious trash talking in the days preceding Saturday night. Cathy even stooped so low as to bring last year's score card to show off. Unfortunately, we hadn't made a reservation, so when we got there, we were told it would be 11:30 p.m. at the earliest before we could race, so we headed back to the hotel. I hung out in the lobby for a while, but a week of getting up at 5:00 a.m. was taking its toll, so I headed to bed.

Here's a picture of attendees (Steve Sawyer, Sue Cunningham, and Brenda Erickson in the foreground) chatting in one of the courtyards between sessions:

Southwest Fox 2007 Day 2

Friday ended up being an even longer day than Thursday. We once again opened registration before 7:00 a.m., expecting a few people who hadn't made it to the keynote. Surprisingly, three people showed up who hadn't previously registered, so that boosted our counts nicely. I wanted to go to Craig Boyd's ClickOnce Deployment session but it ended up being busier than expected at registration plus I chatted with some folks. We also decided that it was a mistake not having coffee for a morning refreshment break (none of the organizers drink coffee so we somehow missed that), so we asked the hotel staff to put some out and they quickly complied. I joked with one of the new registrants that his conference fee paid for the coffee for everyone else, so they all owed him thanks.
Here's a picture of Rick and Tamar at the registration table:

I then did my Best Practices for Vertical Application Development session. The room was jammed and I got lots of great feedback from attendees. This is a different kind of session for me; it's a business track session so I discuss facets of vertical development such as activation and licensing, support policies, update mechanisms, and so on rather than the usually code-intensive sessions I do. In fact, I didn't show any code at all because for some reason, although my laptop worked on the projector the night before in the keynote, for some reason it couldn't see the projector at all in this session. Fortunately, Scott Malinowski had downloaded the conference materials before the conference started and kindly offered to let me use his system. Restarting my computer fixed the problem for my next session, so it wasn't a problem again. By the way, this was "the other" Scott Malinowski. It's really interesting that there are two Scott Malinowskis (the other one is from Phoenix but was out of town during the conference) in a community as small as ours.

I attended Craig's Power of Regular Expressions session. Power is right. Although I've played with regular expressions a bit, Craig did a great job of going over the various symbols and their meanings (he claimed the language was invented by aliens, given the arcane syntax). He had tons of examples, and even gave pop quizzes to the audience, showing an expression and some text and asking what the matches would be. I saw a lot of light bulbs come on for people in this great session.

Lunch was served in the courtyard. Like almost every day we were in Phoenix, the weather was gorgeous, so many people ate at the tables outside. Lunch was build-your-own sandwiches and very tasty.

After lunch, I attended Steve Sawyer's Marketing for Custom Software Services session. It was great seeing Steve again, who sort of dropped out of the community for a few years. He talked about why he felt his former business didn't succeed and what he'd do different if he could do it over again. Although the first half of the session seemed a little theoretical, Steve made some great practical suggestions about meeting new clients. He finished early, so a general discussion on marketing ideas broke out, with me, Steve Bodnar, Jeff Zinnert, and Russ Swall all contributing our viewpoints.

I then went to Christof Wollenhaupt's On the Dark Side of FoxPro session. Christof once again amazed with his incredibly deep knowledge of VFP internals, discussing how variables and objects are stored, how cache memory is used, and how Rushmore does its magic. Although this isn't a session where you walk out with ideas to implement, it does give a glimpse under the hood and lets you understand why some limitations in VFP are the way they are.

I next presented my Developing VFP Applications for Windows Vista session. This is a fun session to do, because I show all of the potential gotchas Vista has for VFP developers and workarounds for all of them. I also show how to take advantage of some of the new features in Vista, including Windows Desktop Search (which is available as an OLE DB provider, making it easy to use a CursorAdapter to query your system), new dialogs (using the Vista Toolkit in Sedna, created by Craig Boyd -- there, I mentioned your name one last time for good measure, Craig), XPS documents, and more. It was obvious from some of the questions that several people had struggled with some of these issues, so it was timely. Tracy Pearson also contributed with a couple of tips.

The last session ended at 5:30 and vendor sessions started at 7:00, so there wasn't a lot of time for dinner. We had told the conference staff on Wednesday that since there was only an hour and a half, most people would likely eat at the hotel restaurant so they'd better prepare for 180 people. They came up with a great plan: a prime rib buffet for only $16.50. It was laid out very nicely and they had enough staff available that everyone had a great meal and were back at the conference center on time.

I was pleasantly surprised at how many people showed up for vendor sessions. Although some rooms were a little light, most of the sessions had about 20 people, meaning almost half of the attendees stayed late into the night seeing what features the vendor's products had. Stonefield had a Stonefield Query developer meeting rather than a vendor session (our vendor session was scheduled for Saturday afternoon). I'm not sure if it was the free beer or our product, but we had about 30 people in attendance, which was great. We went over the new features we added in our latest release, wowing the audience with things such as drilldown reports and charting. We then went over our proposed new features for our next release and asked for feedback about which they really wanted. We also got some great suggestions for things we hadn't thought of. It was also nice that I could introduce Jeff Zinnert, Trevor Mansuy, and Chris Wolf from our company so they and customers could finally meet in person.

Since there was some wine left from our meeting, we took the extra bottles and headed to the hotel bar. We were hassled a couple of times about bringing in outside liquor until we told them that we'd purchased it from them. After a couple of hours blabbing, I was ready to collapse so I called it a night.

Here's a picture of the trade show area in the hallway outside the session rooms:

Southwest Fox 2007 Day 1

Tamar Granor and her husband Marshal, Rick Schummer and his wife Therese, and I arrived in Phoenix on Tuesday, October 16, to finish the final preparations for Southwest Fox 2007.

The hotel and conference buildings are beautiful. Since it's laid out in a resort style, every hotel room has an outside entrance, meaning we get to spend more time outdoors instead of never leaving the hotel building. The hotel intertwines with the golf course, so some of the room buildings are a bit of a hike from the main building and the conference center (mine was about a five minute walk) but a little exercise, especially after sitting all day in sessions, is a good thing. For those who didn't feel like walking, the hotel staff would provide "limo" service using golf carts. The conference buildings are nicely sized for a conference like Southwest Fox, with a couple of courtyards right outside, the perfect place to congregate between sessions for networking. Meals and snacks were served outside, although you could also eat in one of the buildings if it was too hot or windy.

Wednesday was the big day: meeting with Arizona Golf Resort staff, assembling conference binders and bags, helping staff set up the session rooms, setting up the wireless routers, moving everything to the conference center, and so on.

Here are Marshal (left) and Rick after we assembled the binders:

Here are Therese (left) and Tamar surrounded by binders:

Thursday was registration and pre-con day. We manned the registration table starting just before 7 a.m. because attendees taking Andy Kramek's pre-conference session needed to register before his session started at 8. Since we're anal-retentive geeks, we pretty quickly worked out a mechanism to optimize registration (and even documented the process!) so lineups were a minimum. The conference bag was jammed full of goodies: binder, conference T-shirt, drink koozie (courtesy of Servoy, who also provided enough bags for most of the attendees), demo CDs, brochures, wireless Internet information, note pad, pen (courtesy of Tech Smith), CodePlex T-shirt (while they lasted, courtesy Alan Griver), and squishy fun ball. Although we had created a schedule of who would be at the table when, we pretty much all hung around there all day because it was the best place to greet attendees, meet old friends, and be introduced to new people.

Based on the comments I heard from attendees, the pre-cons went very well. We provided lunch to those who registered for two pre-cons. Alan Stevens, one of our talented new speakers, pointed out that we didn't have any coffee put out for the pre-cons, and in fact told his attendees to remind us, which they did several times. I personally delivered a cup of coffee to Alan in the middle of his session, but he took it as a kind gesture rather than the prank that was intended.

Late afternoon, vendors started setting up their tables and booths. We had nine exhibitors this year, three times as many as last year. Right after the last pre-con session ended, we held a short meeting to go over logistics with all the speakers.

Just before the keynote presentation started, Ken Levy surprised us by popping in. He was in town visiting family and decided to stay for the keynote.

The keynote started at 7:00. Rick thanked all the attendees for coming and mentioned these statistics:
  • Attendees came from 7 countries and 35 U.S. states (when he pointed out that he'd forgotten to count the number of Canadian provinces, I said there were attendees from at least 15 provinces).
  • There were 148 attendees, not counting speakers and vendors. (We actually got three more registrations the next day, for a total of 151, more than double last year's attendance.)
  • With 16 speakers, 12 exhibitors, and 2 staff (Marshal and Therese), there were 178 people at the conference in total (181 by the next morning).

Rick went on to thank other people who helped us, including Bob Kocher (who previously ran the conference and graciously helped us with ideas and suppliers), Dave Aring (who created the logos and Kokopelli application), Mike and Toni Feltman (for their VFE offer to all attendees), other conference organizers (Whil Hentzen, Rainer Becker, Kevin Cully, Igor Vit, and others), DBI (for creating the conference CDs), Craig Boyd (who hosts, bloggers and podcasters (who helped get the word out), speakers (who were all introduced by name), Andy Kramek and Marcia Akins (for creating a "how to speak good" video), Southwest Fox staff (Marshal and Therese), and our sponsors.

Tamar went over conference logistics: what's in the binder, where the session rooms are, using Kokopelli, where meals are, and so on. I then discussed the trade show, going over the hours and urging attendees to visit the booths and talk to the vendors about their wares, and the two developer meeting at the conference, once for Visual MaxFrame Professional and one for Stonefield Query.

Rick spent a few minutes discussing VFP 9 SP2, including what's in it and the fact that there are two releases of it (which he called SP2 and SP2A for fun). He then talked about Sedna: what it is, what components are in it, and when it may be released.

I then had the honor of announcing the recipient of the 2007 FoxPro Community Lifetime Achievement Award. During all of the conference planning, Rick and I told Tamar that the award was for someone else. I did up PowerPoint slides with that person's biography, Rick (who designed the physical award) created a design with that person's name, and we even practiced the keynote earlier that day with the other person as the recipient. So, when I announced that Tamar was the recipient, she jumped liked she'd touched a 20,000 volt power line. I went over the things Tamar has done in her career and why she so rightly deserved the award. I was especially pleased that her husband Marshal could be in the room to watch the announcement. Rick presented her with the award and then, to my surprise, also presented awards to the previous recipients--Whil Hentzen, Rick Strahl, and I--who hadn't actually received anything physical before. It was great that all of the recipients were actually at the conference.

While Christof Wollenhaupt was setting up for the next part of the keynote, we did some drawings for raffle prizes, including several Hentzenwerke books. Christof then amazed the audience with his demo of Guineu, a .NET runtime for VFP applications. He started by telling is that FoxPro was dead and it was really a shame that you can't run VFP applications on Windows Mobile devices while he proceded to do exactly that. He then showed the same VFP app running as a .NET WinForm application and a browser application, all the while saying it was too bad that it wasn't possible to do that. It was a great presentation that really fired up the crowd!

We gave our some more prizes while Toni Feltman set up for her presentation on .NET Extender for VFP and VFP Compiler for .NET from Although it has a similar purpose as Guineu (running VFP applications in .NET), it has a completely different technology. Toni demoed some of the current capabilities of these products and mentioned that there are new releases every month with additional features added. She also suggested that at just $120, it was worth buying just to see where it's going to go.

We gave away a few more prizes while Craig Boyd and Alan Stevens prepared to show their "super secret project". They're using extensibility in Visual Studio to create something they call VFP Studio, which gives Visual Studio the ability to compile and generate VFP DLLs when you build a .NET solution. This saves jumping back and forth between different IDEs.

I then wound up the keynote with a call to action: download SP2 and Sedna, join VFPX, start your own blog, join (or start, as Cathy Pountney pointed out) a user group, and tell the world about VFP.

We then adjourned to the courtyard and trade show area for a welcome reception. It was supposed to only last until 9:30 but attendees were still talking to vendors as late as 10:30, and it appeared that everyone had a great time.

Whew! Thursday was a great kickoff for Southwest Fox!

Thursday, October 25, 2007

New Stonefield Query Blog

We've created a new blog specifically for Stonefield Query: That's not to say that I won't talk about Stonefield Query in this blog, but more business- and product-related things will go there, while this blog is more development focused.

Monday, October 15, 2007

New Blog Series by Lisa

If ever there was a reason to read Lisa Slater Nicholls' blog (and there are plenty), she just added a new one: writing "the missing manual" (oops, not supposed to use that term {g}) for the reporting system enhancements in VFP 9 SP2.

Saturday, October 13, 2007

DEMOgala 2007

My business partner Mickey Kupchyk, sales guys Jeff Zinnert and Chris Hodgins, and I were in Denver this week for a trade mission organized by Industry Canada, Saskatchewan Trade and Export, and the Canadian Consulate in Denver. I did a brief presentation on Stonefield Query to approximately 100 guests at a reception at the Canadian Consulate Tuesday evening. On Wednesday, we met for several hours with Bob Ogdon and his staff at SwiftPage to discuss how to integrate our products and were very excited about the possibilities for both integration and cross-marketing.

Thursday was DEMOgala 2007, Colorado's premier IT event. We had a booth and showed Stonefield Query to a lot of very interested people. At lunch, I was honored to be part of a roundtable of C-level executives moderated by Jon Nordmark, CEO of Jon let me talk about Stonefield Query in front of the audience of about 600 CEOs and CIOs, plus we had a great discussion about problems caused by the falling US dollar/rising Canadian dollar and the challenges of recruiting staff in the red-hot Canadian economy.

What a great trip! Not much in the way of sleep but we made a lot of very important contacts, got tons of leads, and a new business partner with some cool products.

Leaving for Phoenix

Early (6:00 a.m.) Tuesday, I leave for Phoenix and Southwest Fox 2007. It doesn't start until Thursday, but organizers Rick Schummer, Tamar Granor, and I, plus our "staff" of Rick's wife Therese and Tamar's husband Marshal, have several days of last-minute details to finish, such as assembling the conference binders and bags, coordinating meals and rooms with the hotel, and so on.

I'm really excited about Southwest Fox: the lineup of speakers and sessions is fantastic, there are some cool products to see in the trade show, and there are about double the number of attendees we were expecting, including a great mix of new folks, people I haven't see at a conference in a while, and the usual but always appreciated gang. I'm also glad the conference is almost here; it was a lot of work! Some days, emails from Rick and Tamar were 75% of my total inbox (and no, that doesn't mean I got three from them ; my normal load is about 50 - 60 legitimate messages a day). I have a new appreciation for the work Whil Hentzen (Great Lakes Great Database Workshop), Igor Vit (DevCon Praha), Rainer Becker (German DevCon), Craig Bailey (OzFox), Kevin Cully (Fox Forward), Bob Kocher (Southwest Fox until this year), and others have done over the years.

See you next week!

VFP 9 SP 2

As you've likely heard by now, Visual FoxPro 9 Service Pack 2 was released this week. Like Rick Schummer, I never install updates for anything just before a conference, so with Southwest Fox 2007 starting next week, I'm not going to install it on my laptop. However, I did install it on my home system. I followed the advice Colin Nicholls posted on Rick's blog and the Universal Thread and completely uninstalled VFP 9 first, then installed VFP 9 RTM (i.e. not SP1), then installed SP2; this ensures that all FFC classes are properly patched.

One other thing I did was to delete the Microsoft Visual FoxPro 9 folder from my Windows Vista Virtual Store (C:\Users\\AppData\Local\VirtualStore\Program Files\Microsoft Visual FoxPro 9). Thanks to virtualization, anytime you write to a file in a protected folder, Windows writes it to the Virtual Store instead. The next time you open that file, you get the virtualized copy rather than the original. So, if you opened a class in the FFC folder of the VFP home directory (assuming you installed it under the default Program Files), it's likely the VCX file was virtualized. That means any references to it will see the older, pre-SP2, virtualized copy rather than the new SP2 version. Nuking that folder takes care of that problem.

Wednesday, October 03, 2007

Stonefield Query Developer Meeting

We're holding a Stonefield Query Developer Meeting at the Southwest Fox 2007 conference. It's scheduled for Friday, October 19, from 8:30 to 9:45 p.m. in the Fairway 4 conference room. We plan on going over new features added in Stonefield Query 3.1, including charting and drilldowns, and go over planned features for the next version. Bring your ideas for new features you'd like to see. This meeting is open to everyone, not just those currently using Stonefield Query.

If you'd like to learn more about Stonefield Query and will be at Southwest Fox, we also have a vendor session planned for Saturday, October 20, from 4:15 to 5:30 p.m. in the Fairway 1 room. We'll show how easy yet powerful Stonefield Query is and how you can create a customized version of Stonefield Query for your application so you can stop writing every single custom report every one of your users would ever want.

Tuesday, October 02, 2007

Southwest Fox 2007 Keynote

A couple of very cool demos will be shown at the Southwest Fox 2007 Keynote presentation, Thursday, October 18, at 7:00 p.m. Toni Feltman will show .NET Extender for VFP and VFP Compiler for .NET from and Christof Wollenhaupt will demonstrate Guineu. Both of these are gaining a lot of attention in the VFP community. Although they're similar in concept--they allow you to run VFP code in .NET--they are very different in implementation. Be sure to attend this exciting session to see how these products may affect your future development efforts.

MVP Award

I am honored to be given the Microsoft Most Valuable Professional (MVP) award for 2007-2008. This is the thirteenth consecutive time I've been awarded, and it never gets old! Congratulations to other award recipients; see for a complete list of VFP MVPs.

Thursday, September 20, 2007

Southwest Fox News Feed

In the "better late than never" category, thanks to Rick Borup, an RSS feed is now available for the Southwest Fox News page. Subscribe to to get updates in your RSS reader.

Southwest Fox 2007 Hotel is Nearly Full

The beautiful Arizona Golf Resort and Conference Center, the location of this year's Southwest Fox conference, is nearly sold out; there are only two rooms left for Friday night. If you haven't already booked your hotel, do so immediately! Other hotels in the area are available if necessary, but it's always more fun to stay at the conference hotel.

Wednesday, September 19, 2007

Southwest Fox 2007 Transportation

Southwest Fox 2007 is less than 30 days away and registrations continue to pour in: there'll be over 150 people at the conference this year.

To help coordinate transportation between the airport and the Southwest Fox conference hotel, please add your travel information to so you can find someone to share a cab or limo to the hotel.

Also, be sure to check the Southwest Fox News page regularly (yeah, I know we should've done an RSS feed for it; it's the "shoemaker's kids" syndrome) because announcements are coming fast and furious (or perhaps just slightly angry) these days.

Monday, September 10, 2007

I do my Little Turn on the Catwalk

Although I'm definitely not too sexy for my shirt, on Friday night I modelled in a fashion show. It was a fund raiser for Sofia House, a second stage shelter for women and children escaping domestic violence. The theme was Pirates of the Caribbean, which explains the large skull and crossbones logo at the back of the stage and the Johnny Depp makeup around my eyes (which fortunately you can't see in this photo).

I was in four relatively simple sets (some of others were pretty complicated) showing casual, business casual, and business outfits from local shops. Many of the models were fairly experienced, but fortunately there were several newbies like me, and we had several practices in the weeks before the show. We also had copious amounts of alcohol back stage to settle any nerves. It was actually a lot of fun; I'd definitely do it again. Although I'd like to think the 450 women in attendance were hooting for me, it was more likely for the firefighter calendar boys who had their own set.

Although the final figures aren't in (hmm, I'm sure I could come up with a pun on that), the fashion show likely raised about $15,000 for Sophia House. Since the organization gets no government funding, everything they make comes from private donations, organizations like the United Way, and fund-raising efforts from the dedicated board, making this a very worthy cause.

Southwest Fox 2007 Attendance Update

There are now over 140 people registered for Southwest Fox 2007, which is the most ever registered for this conference. For details about who's coming, see

After reading Tod's, Dave's, and Kevin's blogs about FoxForward and how much fun it was, I can hardly wait for Southwest Fox to start. Well, except for the fact that we still have a ton of things to do before it starts, that is.

Wednesday, September 05, 2007

Updated My Namespace

VFP developer Laurie Alvey pointed out to me that there were a couple of bugs in the Settings class of the My Namespace included in Sedna. One of them (the use of "System.Int32" in one place and "System.Integer" in another) was already fixed but the other (supporting "System.Double" in Save but not Load) wasn't. He also recommended adding support for System.Decimal for VFP Currency data types. Laurie not only pointed these out but also sent me the code for the fixes! You gotta love that!

I went over the FoxUnit tests I had created for the Settings class and found that I had only tested one data type, System.String/Character. So, I created additional tests so all data types were covered, and I'm really glad I did: I found a couple of bugs I'd inserted in the new code. Although I don't use it as much as I should, I love FoxUnit. I blogged about it at

I've posted an updated version on the Technical Papers page of one of my Web sites,

Friday, August 24, 2007

Vista Article Online

The first of a two-part article on developing VFP applications for Windows Vista in Advisor Guide is now available online at It's actually more current than the printed version; for some reason, they printed an earlier version of the article, while the online copy is the latest one I submitted.

Update: Since Advisor is closing online access to their articles, I have put a copy of my “Developing VFP Applications for Windows Vista” white paper, the source material for this article, on the Technical Papers page of Stonefield’s web site.

Monday, August 20, 2007

Lisa Blogs!

In case you missed it (I did until her husband Colin mentioned it on the Universal Thread), Lisa Slater Nicholls started (or perhaps more accurately re-started) blogging last month. She's already had several interesting articles about reporting in VFP SP2.

Friday, August 17, 2007

Southwest Fox 2007 Attendance

The 108th person registered for Southwest Fox 2007 today, making it 123 including speakers. That means conference attendance is already up from last year (a trend we sadly haven't seen in a lot of years) and we still have ten weeks or so to go. We also have double the number of exhibitors at the trade show and a whole pile of sponsors. This conference is going to ROCK! See you in Phoenix in October!

Saturday, July 07, 2007

John McClane for President

I think Senator John McClane from Arizona would make a great U.S. President. Not only is he a decorated war veteran, he's also a savvy politician who knows the ins and outs of Washington politics. However, his most admirable trait is his tough stance on crime.

Fifteen years ago, while visiting his estranged wife in Los Angeles, he accidentally became embroiled in a plot at Nakatomi Plaza to rob hundreds of millions of dollars in bearer bonds. Hans Gruber and his band of criminals murdered several hostages before Senator McClane, through sheer guts and determination, killed several bad guys and stole the detonators they were planning on using to blow up the entire building. He was shot at, beaten, and even had to walk over broken glass in his bare feet but still managed to kill all the criminals and rescue the hostages.

A few years later, he again had to become a reluctant hero when several commandos decided to take over an airport in Chicago in order to spring a foreign general about to be tried for drug trafficking. Senator McClane once again outthought the bad guys and beat them at their own game. He even came up with a catch-phrase, "yippie-kie-yay-expletive", that's frequently used by other heros as they dispatch the evildoers.

Now, in 2007, even while in the middle of a presidential campaign, he once again stepped up to fight cyber-terrorists trying to destroy American infrastructure. Of course, since he's in his 70s now (amazingly, he looks like a man in his early 50s), he needed some help, so he got that "I'm a Mac" guy to handle the computer skills. And talk about courage: he had to shoot himself to take out the bad guy who held his daughter hostage. (He must've remarried a much younger woman because his daughter's still in high school.)

Given his experience fighting crime, I can't think of anyone more qualified to guide America through some very dangerous times. And he'd clean up corrupt politics in Washington. I can picture him telling "Scooter" Libby: "Here's your pardon. No jail time for you. BLAM!" Dick Cheney: "Hey dude, let's go hunting. Bring a vest."

What's that? It's Senator John McCain? John McClane is a fictional character played by actor Bruce Willis? Well, that's very different then. Never mind.

(My homage to Gilda Radner's Emily Litella.)

Friday, June 29, 2007

Southwest Fox 2007 Early-Bird Deadline in 2 Days

There are only two more days to take advantage of the Southwest Fox 2007 early-bird registration. Be sure to register now and save $75 plus receive a free pre-conference session (a $99 value) plus be eligible for the White Light Computing scholarship.

Monday, June 25, 2007

A Day at the Races

On Saturday, I was in my first ever bike race. Well, it wasn't exactly a bike race; it was a team pentathalon called Echo Challenge, a fundraiser for our local YMCA, located at Echo Lake, a 45 minute drive from Regina in the beautiful Qu'Appelle Valley. However, it was a bike race to me because my event was the 10 KM cycling stage.

My team, Thunder, didn't have a good start. I'm not sure how experienced our swimmer was, because he came out of the water dead last, and by a significant margin. In his defence, it was tough swim: 750 M in cold water that was extremely choppy because of strong winds (which would also play a factor in my event). I'm really glad it wasn't me in that stage. Most of our team members were first-timers (like me) so I wasn't expecting us to be competitive, but I didn't think we'd be 25 minutes behind the leaders after the first stage.

The next stage was a hill run, which is exactly what it sounds like: you run up a hill and then back down. Our runner was very fast; it seemed like he disappeared into the tree line and reappeared just a moment later. He tagged me and I took off.

I knew it was windy but I wasn't expecting it to be quite that bad: 40 KPH head-winds the entire distance. Also, it was very hilly -- mostly uphill the entire way with a long descent at the very end. The wind was much more challenging than the hills, though. Fortunately, I was feeling pretty strong so I just put my head down and went as fast as I could. I managed to pass four cyclists (not bad considering the lead they had) and almost passed a fifth. Unfortunately, while shifting into my highest gear so I could pass him, the chain went past the last gear and got lodged between it and the bike frame. Rather than stopping to fix it, I coasted down the long descent until it started to flatten out, then got off my bike and sprinted with it the last 100 M.

My wife Peggy, son Nicholas, and friend Joanne and Molly Wade cheered me on throughout the stage. They drove about 1 KM ahead, cheered me as I went past, then moved another 1 or 2 KM ahead. They also cheered me at the finish line. Here's a picture of Nick and I a few minutes after finishing:

The next stage was a 6 KM run. Our runner didn't have any water with her, expecting that there'd be frequent water stations, but unfortunately there wasn't. As a result, she had a tough run on a pretty warm day.

The last stage of the event swamped us, literally. Our team captain, one of two members in the 1600 M canoe race, had never canoed before, and the other member didn't have much more experience. Unfortunately, they overturned the canoe 100 M from the start. As a result, we finished in last place.

The event was a lot of fun and other than the wind (which died down shortly after the cycling stage was over; it figures) and bit of rain before it started, it was a gorgeous day in a beautiful setting, and we raised $22,000 for programming for the YMCA.

The other benefit, from a personal perspective, is that it fired me up for cycling, so I'm planning on riding a lot more this summer.

Friday, June 22, 2007

Fix for Sedna Beta Installation Issue

Like many of you, I downloaded and installed the new VFP 9 Service Pack 2 and Sedna betas (available from here). I was careful to first uninstall the previous SP2 and Sedna CTPs, and while SP2 installed just fine, I got error 2869 at the end of the Sedna installation. Because I was busy, I didn't have time to dig in and figure out what caused the error.

Fortunately, I didn't need to. The next day, Rick Bean posted a message on a forum with the same problem, then later posted the solution. Like Rick, I'm using Windows Vista, and it looks like the installer needs admin rights to do its job but it isn't set up to request elevation automatically. So, Rick's solution is to choose Start, All Programs, Accessories, Command Prompt (Rick, if you're reading this, here's a shortcut: click Start and type "cmd"; it'll appear at the top of the search list), then right-click and choose Run as Administrator. Then, using good old-fashioned DOS commands, CD to the directory where the Sedna installer is located, type the installer name, and press Enter. Since it's launched from an elevated process, it also runs as administrator and successfully installs.

I hope everyone gets some time to work with SP2 and Sedna. I haven't gone through all of the Sedna tools yet, but have spent a fair bit of time with the My namespace and Upsizing Wizard (duh {g}), and love the work Craig Boyd has done with the Vista Toolkit. If you come to Southwest Fox or the German DevCon, attend my Developing Visual FoxPro Application for Windows Vista session and I'll show you how cool this toolkit is.

Monday, June 18, 2007

Two Weeks to the Southwest Fox Deadline

There are only two weeks left to get in on the early-bird registration for Southwest Fox 2007; the deadline is July 1. There are several benefits to registering now:

As Kevin Cully notes, the deadline for registration for FoxForward 2007 is also approaching.

You owe it to yourself and your career as a development professional to attend one of these conferences, or the German DevCon, another great conference now in its 13th year.

Tuesday, May 29, 2007

Has a File Been Virtualized?

If you've been working with or reading about Windows Vista, you know there's a new feature called virtualization that causes writes to files in "protected" folders (such as C:\Program Files\Some Folder) to be redirected to a user-specific folder (C:\Users\user name\AppData\Local\VirtualStore\Program Files\Some Folder). While this is sort of a good thing--it means pre-Vista applications won't give an error when they update data stored in their own directory--it can be a problem for a variety of reasons:
  • Since the files are redirected to a user-specific location, other users on the same system won't see each others' changes.
  • Backing up the files in the application folder won't back up the user-specific files if the backup program is Vista-aware.
  • If the user turns off User Access Control (UAC) or virtualization, the application not only breaks because it still can't write to the protected folders, it also doesn't see the virtualized files any more so data entered by the user appears to be gone.

So, I've been reworking those parts of Stonefield Query that expected to write to the current or application folder. It was actually fairly easy to do because most file paths were referenced with an application object property (e.g. USE (oApp.cAppDataDir) + "SomeTable") so simply changing the value of the property redirected the files to a location the user has write access to. I then ran the application and looked for any files that appeared in my virtualization folder, and turned off virtualization and tested to see if anything broke. I found a few stragglers where I hadn't put a path on the filename (typically writing to some temporary files) and fixed them.

However, one thing puzzled me: a table that wasn't being written to was virtualized. In tracking it down, I found that the virtualized table was created immediately after the USE command. To make matters even odder, when I turn off virtualization, the table wasn't virtualized and the application didn't give an error. Also, while the DBF and FPT files were virtualized, the CDX was not.

I guess this makes sense. Since I was opening the table in read-write mode (ie. no NOUPDATE clause), I suspect VFP was doing some minor update to the table behind the scenes (e.g. writing to the header of DBF and FPT files for locking purposes), resulting in it being virtualized. You can see this effect if you open one of the classes in the FFC subdirectory of the VFP home directory; simply opening it immediately causes its virtualization. With virtualization turned off, the table was automatically read-only since it's in a protected folder, so there's no need to virtualize it. Adding a NOUPDATE clause to the table (since it's a meta data table, it's used for reference purposes only) took care of that.

However, the SDK for Stonefield Query includes an editor program for that table, so that program does open the table for read-write purposes and will likely write to the table (otherwise it wouldn't be an editor). The problem is that the user may be unaware that the table is virtualized and will likely distribute the wrong instance of the table (the one in the application folder rather than the virtualized one) after making changes to it. So, I figured I should let them know which table they're writing to so they know which one to distribute.

However, how can you tell if a file has been virtualized? If you open a file in C:\Program Files\Some Folder and that file has been virtualized, the operating system actually opens the virtualized copy instead of the one you specified, and the application can't tell the difference.

I settled on the obvious solution: check whether a virtualized copy of the file in question exists. If so, we know that's the one Vista will open. Here's some code to do that. First, we get the directory the application is running in (not necessarily the current directory) by calling GetAppDirectory (a routine I wrote years ago; I'll show you the code later). Then we call SpecialFolders, a routine I discussed in my Finding the Paths for Special Folders blog entry, to get the current user's local app data path (C:\Users\user name\AppData\Local\). Notice that this code uses SUBSTR(lcDirectory, 3) to strip the drive letter, colon, and backslash from the application folder name. We then check if the desired file exists in the virtualization folder.

lcDirectory   = GetAppDirectory()
lcVirtualPath = SpecialFolders('LocalAppData') + ;
'VirtualStore\' + substr(lcDirectory, 3)
lcFile = lcVirtualPath + 'SomeTable.DBF'
if file(lcFile)
* the table has been virtualized

Here's the code for GetAppDirectory. It handles runtime and development time conditions differently.

local lcPath
if version(2) = 2
lcPath = justpath(sys(16, 0))
if atc('PROCEDURE', lcPath) > 0
lcPath = substr(lcPath, rat(':', lcPath) - 1)
endif atc('PROCEDURE', lcPath) > 0
lcPath = addbs(justpath(_vfp.ServerName))
endif version(2) = 2
return lcPath

Thursday, May 03, 2007

Why I Signed

I signed the petition today.

All the negative comments from some people in the community had done more to convince me not to sign than anything else. I'm a pretty laid back guy, but I get really pissed off when people question my integrity.

Few of the comments on my "Why I Haven't Signed" blog entry or the Wiki topic actually tried to make an argument. Most of them amounted to "you're wrong" (that's your idea of a convincing argument?) and "VFP is a great tool" (thanks, know that already).

However, Mike Asherman's comment made me think about this some more. He said, "Adding your name means that you support the cause, not that you believe it is likely to succeed." Good point. I've voted for people I've believed in before knowing they didn't have a chance in winning because it was the right thing to do.

So, I signed as my way of showing solidarity with the community. I still believe this petition is futile and will have absolutely no impact on Microsoft's decision, but I've cast my ballot anyway.

For those of you questioning the reasons why others haven't signed, I suggest spending your time in more productive manners, such as writing code and helping others on community forums, rather than driving the wedge into our community even further. Alienating people by questioning their motives is a sure way to get the opposite of what you desire.

Wednesday, May 02, 2007

Taking out the Slow Parts

Sage Timberline Office, like many accounting systems, allows you to customize the captions that appear on-screen for most fields. To make it easier for the user, it makes sense for Stonefield Query for Sage Timberline Office, the newest member in the Stonefield Query family of products, to display the user's own captions. However, one slight complication is that they aren't stored in a manner that's easily readable. Instead, we have to call a COM object to retrieve the captions. This object returns XML specifying the name of each field and its caption. Given that there are tens of thousands of fields in Timberline, this XML can be over 1 MB in size. Here's what the XML looks like:

<fieldcaption>Caption for Field</fieldcaption>
We want to use this XML to update the Stonefield Query data dictionary. Our first attempt used XMLTOCURSOR() to put the XML into a cursor, which we then process. It took somewhere around 30 seconds, which seems like forever because it happens every time the user starts Stonefield Query. XMLTOCURSOR() uses the XML DOM object, which gets exponentially slower as the XML gets larger.

The next approach was to manually parse the XML using STREXTRACT(). STREXTRACT() is the perfect function for parsing XML because it was designed to find text between delimiters. If you know the XML structure, it's really easy to use. Here's the code we used:

create cursor __CAPTIONS (FIELDNAME C(119), CAPTION C(60))
for lnI = 1 to occurs('<tablename>', lcXML)
lcTable = strextract(lcXML, '<tablename>', '</tablename>', lnI)
lcField = strextract(lcXML, '<fieldname>', '</fieldname>', lnI)
lcCaption = strextract(lcXML, '<fieldcaption>', '</fieldcaption>', lnI)
insert into __CAPTIONS values (lcTable + '.' + lcField, lcCaption)
next lnI
Interestingly, this turned out to be way slower than XMLTOCURSOR() (in fact, I stopped it after a couple of minutes, so I don't know how long it would've taken).

It occurred to me while looking at the XML that if we could convert it into comma-delimited text, we could use APPEND FROM to suck it into the cursor. Here's the code for that:

#define ccCRLF chr(13) + chr(10)
lcXML = strtran(lcXML, '<tablename>')
lcXML = strtran(lcXML, '</tablename>' + ccCRLF + '<fieldname>', '.')
lcXML = strtran(lcXML, '</fieldname>' + ccCRLF + '<fieldcaption>', ',"')
lcXML = strtran(lcXML, '</fieldcaption>' + ccCRLF + '</newrecord>' + ;
ccCRLF + '<newrecord>', '"')
lcXML = strtran(lcXML, '</fieldcaption>' + ccCRLF + '</newrecord>' + ;
ccCRLF + '</vfpdata>', '"')
lcTemp = forcepath(sys(2015) + '.txt', sys(2023))
strtofile(substr(lcXML, 91), lcTemp)
create cursor __CAPTIONS (FIELDNAME C(119), CAPTION C(60))
append from (lcTemp) delimited
erase (lcTemp)
This took 0.7 seconds, 42 times faster than using XMLTOCURSOR().

But still, I wondered why the STREXTRACT() approach was so slow; my experience is that the VFP text handling functions are blindingly fast. I wondered if it had to do the with fourth parameter, the one that specified the occurrence. I rewrote the code to this:
create cursor __CAPTIONS (FIELDNAME C(119), CAPTION C(60))
lnLines = alines(laLines, lcXML)
for lnI = 1 to lnLines
lcLine = laLines[lnI]
do case
case '<TableName>' $ lcLine
lcTable = strextract(lcLine, '<TableName>', '</TableName>')
case '<FieldName>' $ lcLine
lcField = strextract(lcLine, '<FieldName>', '</FieldName>')
case '<FieldCaption>' $ lcLine
lcCaption = strextract(lcLine, '<FieldCaption>', '</FieldCaption>')
insert into __CAPTIONS values (lcTable + '.' + lcField, lcCaption)
next lnI
This took 0.4 seconds. Wow! Nearly two orders of magnitude improvement over the initial attempt. It really does pay to take out the slow parts!

Off to DevCon

Saturday morning, I'm heading for DevCon in Anaheim, California. Coincidentally, Rick Schummer and I are on the same flight from Minneapolis to Orange County, which may be bad news for him, given my luck with flights leaving on time.

This will be my 17th DevCon as an attendee (I only missed the first one in Toledo in 1989) and my 11th as a speaker (1997 was the first). I'm the only person who will have attended 17 consecutive DevCons, although this will also be number 17 for Alan Griver (just not consecutive; he was at the first one but missed one in the middle).

However, my string of consecutive DevCons as an exhibitor (1993 was the first) is broken: For the first time in fifteen years, I won't have a booth there. Unfortunately, it just wasn't cost effective anymore, plus our booth (and staff) are going to be at the 2007 Timberline User Group National Conference in Dallas at roughly the same time, showing our new Stonefield Query for Timberline.

For posterity, here are the ones I've attended:

1990 Toledo
1991 Toledo
1992 Phoenix
1993 Orlando
1995 San Diego
1996 Scottsdale
1997 San Diego
1998 Orlando
1999 Palm Springs
2000 Miami
2001 San Diego
2002 Ft. Lauderdale
2003 Palm Springs
2004 Las Vegas
2005 Las Vegas
2006 Phoenix
2007 Anaheim

I'm presenting four sessions at DevCon:

  • Best Practices for Vertical Application Development: this is an updated version of the session I presented at Great Lakes Great Database Workshop in 2006 and earlier this year at OzFox. I'll also be presenting this session at Southwest Fox 2007.

  • Integrating RSS and VFP: this is an updated version of the session I gave last year at DevCon. It now includes material on accessing the common RSS store installed with Vista and IE 7.

  • Deploying VFP Applications with InnoSetup: this is an updated version of the session I gave at last year's Southwest Fox and earlier this year at OzFox. It has a new section on deploying on Vista.

  • Developing VFP Applications for Windows Vista: this is a new session and the one I'm most excited about. Vista presents some cool opportunities but also some challenges to all developers, regardless of the language they use. I think it'll be an eye-opener for those who haven't deployed an application on Vista yet or are struggling with the issues right now. I'm also presenting this session at Southwest Fox 2007.

Tuesday, May 01, 2007

Southwest Fox 2007 Speakers and Sessions Announced

The crew at Geek Gatherings are pleased to announce that speakers and sessions have been selected for Southwest Fox 2007. I am really excited about both the lineup of speakers and the sessions they're going to be presenting. This is shaping up to be the must-attend conference of the year.

OK, I'm biased for multiple reasons--both as a speaker and being part of Geek Gatherings--but I really do think this is going to be a killer conference. Some of the sessions, like Alan Stevens' Integrating Windows Forms UI Elements with Existing Applications, Kevin Goff's Professional Business Reporting with Crystal Reports, Christof Wollenhaupt's On the Dark Side of FoxPro, and Toni Feltman's Introduction to the DBI Controls Included in Sedna, are very high on my personal list.

Registration is now open. Be sure to take advantage of the early-bird discount: register by July 1 to save $75 and get a free pre-conference session (a $99 value).

Friday, April 27, 2007

Elevating Tasks in Vista

One of the Stonefield Query dialogs has a command button that allows the user to launch the ODBC Administrator, likely because they need to set up or modify a DSN. The button has "ODBC Admin" as its Caption and the following code in Click:
lcSystem = Thisform.oUtility.GetSystemDirectory()
run /n1 &lcSystem..odbcad32.exe
(GetSystemDirectory is a utility method that returns the location of the Windows System folder; I'll show the code for that later.)

This worked great until I tried it on Vista; it fails there because the ODBC Administrator requires admin privileges and the RUN command doesn't support requesting elevation. Fortunately, there's an easy solution to that: use ShellExecute instead. I changed the code to:
lcSystem = oUtility.GetSystemDirectory()
oUtility.ShellExecute(lcSystem + 'odbcad32.exe', 'RunAs')
(Like GetSystemDirectory, ShellExecute is another utility function. It runs the specified command. The cool thing about ShellExecute is that it'll launch the appropriate program. In the case of an EXE, it runs it. In the case of an HTML file, it launches the default browser. In the case of a DOC file, it launches Word.)

Now when the user clicks the button, they're requested to elevate to administrator before the ODBC Administrator appears. To make it obvious that this will happen, I've followed the Vista convention of adding a shield image to the button to alert the user that elevation will be requested. To do that, I set PicturePosition to 1-Left of Caption, Picture to VistaShield.BMP (an image I created using SnagIt to grab the shield icon displayed in the User Accounts control panel applet), and shortened Caption to just "ODBC" so it all fits. Here's what the button looks like:

Here's the code for GetSystemDirectory:
#define cnMAX_PATH 260
#define ccNULL chr(0)
local lcBuffer
lcBuffer = space(cnMAX_PATH)
declare integer GetSystemDirectory in Win32API ;
string @szBuffer, integer nLength
GetSystemDirectory(@lcBuffer, cnMAX_PATH)
return addbs(alltrim(left(lcBuffer, at(ccNULL, lcBuffer) - 1)))
Here's the code for ShellExecute:
lparameters tcFileName, ;
tcOperation, ;
tcWorkDir, ;
local lcFileName, ;
lcWorkDir, ;
lcOperation, ;
lcParameters, ;
if empty(tcFileName)
return -1
endif empty(tcFileName)
lcFileName = alltrim(tcFileName)
lcWorkDir = iif(vartype(tcWorkDir) = 'C', alltrim(tcWorkDir), '')
lcOperation = iif(vartype(tcOperation) = 'C' and not empty(tcOperation), ;
alltrim(tcOperation), 'Open')
lcParameters = iif(vartype(tcParameters) = 'C', alltrim(tcParameters), '')
lnShow = iif(upper(lcOperation) = 'Print', 0, 1)
declare integer ShellExecute in SHELL32.DLL ;
integer nWinHandle, ; && handle of parent window
string cOperation, ; && operation to perform
string cFileName, ; && filename
string cParameters, ; && parameters for the executable
string cDirectory, ; && default directory
integer nShowWindow && window state
return ShellExecute(0, lcOperation, lcFilename, lcParameters, lcWorkDir, ;

Why I Haven't Signed

As many of you are aware, there's a petition VFP developers are being urged to sign to try to convince Microsoft to reverse their decision to not continue development of VFP. I think every VFP developer should sign this if they feel it will have an impact. However, to date, I, some other MVPs, and some prominent VFP people have not signed it.

Apparently, this is a problem for some people. A debate has been going on over on the FoxWiki (as well as other forums) about this. Some people have suggested that those who don't sign the petition have an agenda, that somehow we want VFP to die. Nothing could be further from the truth. This decision impacts my revenue as much as it does everyone else's. Others have suggested that we won't speak out against Microsoft, that somehow we're their cheerleaders or that we won't bite the hand that feeds (or as someone suggested, pays) us. Again, not true. I wish Microsoft hadn't made this decision. I've bitched about them plenty over things they've done in the past and likely will do so again in the future.

I can't speak for anyone else, but my reason for not signing the petition is simple: I believe it's a futile effort and I don't think someone should put their name on something they don't believe in.

However, I consider myself to be an open-minded person. If someone can give me a convincing argument about why I should sign the petition, I'll do so.

"It can't hurt" isn't a convincing argument. In fact, I'd argue that this petition has done more harm than good for our community, since some people now have an us (the signers) against them (non-signers) attitude and are looking for conspiracy reasons why someone won't sign.

"It only takes 30 seconds" isn't a convincing argument. This isn't about how long it takes; it's a matter of principle.

"It'll show you support the community" is also not a convincing argument. I think writing more than 100 articles over 10 years, presenting more than 200 sessions at conferences and user groups, writing blog entries describing how to do some complex things in VFP, being a co-administrator and a project leader for VFPX, and spending thousands of hours online helping other developers shows that I support our community as much as anyone else. The same is true of other MVPs. The very definition of an MVP is someone who spends a lot of personal time supporting the community. This whole argument that not signing the petition means you don't support is community is almost identical to another ludicrous argument: that if you don't believe in the war in Iraq, you don't support the troops. Like the petition, those two things have nothing to do with each other.

Please post your arguments on this blog. I have been convinced to change my position on issues in the past. All it takes it a well-thought out, logical (not emotional) argument.

Thursday, April 26, 2007

Scheduling Tasks

Stonefield Query has had a reports scheduler for several years. We simply use the Windows Task Scheduler to run Stonefield Query, with the name of the report to run and where to send the output as parameters, at the desired schedule. To communicate with the Task Scheduler, we use the freeware TaskScheduler.DLL, written by Mark Pryor. It doesn't look like the site I downloaded it from is available anymore, so it's in the download mentioned later.

However, after upgrading to Windows Vista, I discovered the scheduler didn't work. In researching this issue, I discovered that Vista includes Task Scheduler 2.0, while the DLL was written to support Task Scheduler 1.0, which has a completely different interface. So, back to the drawing board.

Fortunately, the MSDN documentation on Task Scheduler 2.0 has tons of detail and several examples in VBScript, which are easily converted to VFP. Because I still need to use Task Scheduler 1.0 with XP and earlier systems, I decided to create a TaskScheduler base class and XP and Vista subclasses of it.

I won't show the code for these classes here because it's fairly lengthy (you can download it from the Technical Papers page of my Web site), but here are some examples of how easy they are to use to schedule tasks. These examples run on Vista; use XPTaskScheduler instead for Windows XP or earlier.
* Create a task that runs at 3:00 AM every day.

loSchedule = createobject('VistaTaskScheduler')
with loSchedule
.TaskName = 'My Task Name'
.UserName = 'Your Windows UserName'
.Password = 'Your Windows Password'
.StartTime = {^2007-04-26 03:00:00}
.EXEName = 'Full path to EXE'
.EXEParameters = 'Any parameters to pass'
.ScheduleType = 1 && daily
if not .CreateTask()
endif not .CreateTask()

* Create a weekly task that runs at 3:00 AM Tues, Thurs, and Sat
* of every second week.

loSchedule = createobject('VistaTaskScheduler')
with loSchedule
.TaskName = 'My Task Name'
.UserName = 'Your Windows UserName'
.Password = 'Your Windows Password'
.StartTime = {^2007-04-26 03:00:00}
.EXEName = 'Full path to EXE'
.EXEParameters = 'Any parameters to pass'
.ScheduleType = 2 && weekly
.Interval = 2
store .T. to .DaysOfWeek[3], .DaysOfWeek[5], .DaysOfWeek[7]
if not .CreateTask()
endif not .CreateTask()

* Create a monthly task that runs at 3:00 AM on the 1st and 15th
* of every month.

loSchedule = createobject('VistaTaskScheduler')
with loSchedule
.TaskName = 'My Task Name'
.UserName = 'Your Windows UserName'
.Password = 'Your Windows Password'
.StartTime = {^2007-04-26 03:00:00}
.EXEName = 'Full path to EXE'
.EXEParameters = 'Any parameters to pass'
.ScheduleType = 3
store .T. to .DaysOfMonth[1], .DaysOfMonth[15]
.MonthsOfYear = .T. && initialize all 12 elements of array to .T.
if not .CreateTask()
endif not .CreateTask()

Wednesday, April 18, 2007

Handling Multiple Monitors

One of the things I did when I got my new laptop is to set up multiple monitors. Developers I know and respect have raved about this for years, so I figured it was time to give it a try. Needless to say, I love it. I typically have browser and explorer windows open on the second monitor and keep the primary monitor for those things I live in all day long (VFP and Outlook, mostly). I'm more productive now that I'm not digging through stacks of windows and constantly moving or resizing one window or another.

However, one of the things I discovered today is that some of my applications don't respect the second monitor. For example, I have a class called SFPersistentForm that I drop on most of my forms. It saves the form size and position when the form is closed and restores it when the form is reopened, giving the user the experience they expect when working with that form. However, I discovered that if I opened a form and moved it to the second monitor then closed it, when I reopened it, the form displayed on the primary monitor instead (this was a form with Desktop set to .T. so it can exist outside the application's window).

I quickly found out why: the persistence code was trying to prevent the situation where the form may open outside the screen boundaries, making it invisible. The following code handled that:
Thisform.Width  = min(max(Thisform.Width,  0, Thisform.MinWidth), ;
Thisform.Height = min(max(Thisform.Height, 0, Thisform.MinHeight), ;
Thisform.Left = min(max(Thisform.Left, 0), _screen.Width - 50)
Thisform.Top = min(max(Thisform.Top, 0), _screen.Height - 50)
("- 50" is used to ensure the form doesn't start at exactly the right or bottom boundaries of the monitor, making it essentially invisible.)

There are actually two problems with this code. First, the reliance on _SCREEN assumed the form exists within _SCREEN; with a top-level form or one with Desktop set to .T., that's not necessarily the case. Second, even if _SCREEN is maximized, that only fits it in the current monitor. If the form is on the other monitor, _SCREEN's dimension are irrelevant.

I initially changed the code to:
if Thisform.Desktop or Thisform.ShowWindow = 2
lnWidth = sysmetric(1)
lnHeight = sysmetric(2)
lnWidth = _screen.Width
lnHeight = _screen.Height
endif Thisform.Desktop ...
Thisform.Width = min(max(Thisform.Width, 0, Thisform.MinWidth), ;
Thisform.Height = min(max(Thisform.Height, 0, Thisform.MinHeight), ;
Thisform.Left = min(max(Thisform.Left, 0), lnWidth - 50)
Thisform.Top = min(max(Thisform.Top, 0), lnHeight - 50)
However, it turns out that SYSMETRIC() only returns values for the primary monitor. So, I changed those two statements to:
declare integer GetSystemMetrics in Win32API integer
#define SM_CXVIRTUALSCREEN 78 && Virtual Width
#define SM_CYVIRTUALSCREEN 79 && Virtual Height
lnWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN)
lnHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN)
Now the form reopens in the exact same size and position, including the monitor it was on.

Update: As I posted in a comment, this code correctly handle the situation where the form was originally placed on a second monitor but now that monitor doesn't exist. However, it didn't handle the case where the secondary monitor is on the left, so the starting X position is negative. See the code in the comment to deal with that.