Wednesday, December 17, 2008

Microsoft Security Update

Microsoft recently released a security update that lists Visual FoxPro 8 and 9 amongst the affected applications. In case you're wondering exactly what this is about, here are the details. This update provides patched versions of the following ActiveX controls that ship with VFP:

  • MSWinSck.ocx (Microsoft WinSock Control 6.0)
  • MSMask.ocx (Microsoft Masked Edit Control 6.0)
  • MSFlxGrd.ocx (Microsoft FlexGrid Control 6.0)
  • MSHFlxGd.ocx (Microsoft Hierarchical FlexGrid Control 6.0)
  • MSChrt20.ocx (Microsoft Chart Control 6.0)
  • ComCt232.ocx (Microsoft Animation Control 5.0, Microsoft UpDown Control 5.0)
  • MSComCt2.ocx (Microsoft Animation Control 6.0, Microsoft Date and Time Picker 6.0, Microsoft Flat Scrollbar Control 6.0, Microsoft MonthView Control 6.0, Microsoft UpDown Control 6.0)

In addition to updating your own system, remember to distribute the updated versions of any OCXs your applications use to your clients.

Tuesday, December 09, 2008

ATC(), Only Better

Because our flagship product, Stonefield Query, is a reporting tool, we do a LOT of parsing here, especially expression parsing. One parsing task we frequently do is look for the existence of a field in an expression. It's not as simple as it sounds. For example, suppose you have a SQL statement like this stored in a variable named lcSelect:

select Employees.FirstName,Employees.LastName,Employees.CountryOfBirth from ...

and you want to know if the Country field is involved in the query. Unfortunately, ATC('Country', lcSelect) will return a false positive because "Country" is contained within "CountryOfBirth".

One of Stonefield's genius developers, Trevor Mansuy, wrote a replacement for ATC() called ATCWord that searches for words rather than substrings. He used the VBScript regular expression parser to do the hard work. So, using the example above, ATCWord('Country', lcSelect) returns 0.

There are a lot of comments in ATCWord that explains how it works. Thanks, Trevor.

Update: Mike Potjer tweaked the code so it returns the correct position when the string you're searching for comes after a substring match, such as looking for "Country" in "Employees.CountryOfBirth,Employees.Country". Thanks, Mike.

* Function: ATCWord
* Purpose: Performs an ATC() on whole words, not substrings
* Author: Trevor Mansuy, with a tweak by Mike Potjer
* Last revision: 12/09/2008
* Parameters: tcSearch - the string to search for
* tcString - the string to search for a match in
* tnOccurrence - which occurrence to search for
* Returns: the index of the first character of the match
* Environment in: VBScript.RegExp can be instantiated
* Environment out: none

lparameters tcSearch, ;
tcString, ;
local lnOccurrence, ;
loRegExp, ;
lcLeftBoundary, ;
lcRightBoundary, ;
lcSearch, ;
loMatches, ;
lnReturn, ;
loMatch, ;

* Ensure the necessary parameters were passed.

assert vartype(tcSearch) = 'C' and not empty(tcSearch) ;
message 'ATCWord: invalid search string passed'
assert vartype(tcString) = 'C' ;
message 'ATCWord: invalid string passed'

* Bug out if the string is empty.

if empty(tcString)
return 0
endif empty(tcString)

* Set an occurrence if one wasn't passed.

if not vartype(tnOccurrence) = 'N'
lnOccurrence = 1
lnOccurrence = tnOccurrence
endif not vartype(tnOccurrence) = 'N'

* Create a VBScript.RegExp object.

loRegExp = createobject('VBScript.RegExp')
loRegExp.IgnoreCase = .T.
loRegExp.Global = .T.

* This is here for future proofing. Right now, the function expects
* whatever we search for to begin and end with non-word characters,
* but just in case, save the boundary characters in variables so we
* can change them easily. Right now, we are using word-boundaries
* (the space between a word and a non-word character. I originally
* wanted to use non-word characters (\W) for this, but the match
* consumes the character, so side-by-side matches won't be found. The
* drawback of \b is if we do an ATC() for a string with a non-word
* character at the beginning or end, there is no guarantee that there
* is a word boundary on the other side of that character.

lcLeftBoundary = '\b'
lcRightBoundary = '\b'

* Certain RegExp special characters prevent matches, so escape them.

lcSearch = AddRegExEscape(tcSearch)

* In the pattern below, the three sets of parentheses represent three
* match groups. The first match group, (\W|^), means we first match
* either a non-word character or the beginning of a string. Similarly,
* (\W|$) means at the end we match either a non-word character or the
* end of the string. This means we make a match in every situation
* except where the string in lcSearch is a substring of one of the
* strings in tcString.

loRegExp.Pattern = '(' + lcLeftBoundary + '|^)(' + lcSearch + ')(' + ;
lcRightBoundary + '|$)'

* Test the string. If a match collection is returned, check how many
* matches were made. If the number of matches is less than the
* occurrence passed, return 0, otherwise, do the ATC.

loMatches = loRegExp.Execute(tcString)
if loMatches.Count < lnOccurrence
lnReturn = 0

* Retrieve the specified occurrence from the Item collection. If the
* match is the one we want, return its start location in the string.

loMatch = loMatches.Item(lnOccurrence - 1)
lcMatch = loMatch.Value
lnReturn = iif(upper(tcSearch) = upper(lcMatch), ;
loMatch.FirstIndex + 1, 0)
endif loMatches.Count < lnOccurrence
return lnReturn

* Function: AddRegExEscape
* Purpose: Escape certain characters in regular expressions
* Author: Trevor Mansuy
* Last revision: 09/03/2008
* Parameters: tcString - the string to escape
* Returns: the string with certain characters escaped
* Environment in: none
* Environment out: none

function AddRegExEscape
lparameters tcString
local lcString
lcString = strtran(tcString, '\', '\\')
lcString = strtran(lcString, '?', '\?')
lcString = strtran(lcString, '*', '\*')
lcString = strtran(lcString, '+', '\+')
lcString = strtran(lcString, '.', '\.')
lcString = strtran(lcString, '|', '\|')
lcString = strtran(lcString, '{', '\{')
lcString = strtran(lcString, '}', '\}')
lcString = strtran(lcString, '[', '\[')
lcString = strtran(lcString, ']', '\]')
lcString = strtran(lcString, '(', '\(')
lcString = strtran(lcString, ')', '\)')
lcString = strtran(lcString, '$', '\$')
return lcString

Friday, December 05, 2008

Updated Blog Page

I added a couple of new gadgets on my blog page today:

  • Links allowing you to subscribe to posts and comments using a variety of means.
  • Members, using the new Google Friend Connect gadget. Feel free to join my site.
  • Ratings, which allows you to rate blog posts.

Tuesday, December 02, 2008

Dynamically Moving Controls

I have a class that provides a file selection control. It consists of a label, a textbox, and a command button. The user can either type the name of the file or click the button to bring up a file dialog.


Obviously, a more descriptive label than "File" would be useful, but it would be painful to manually change the caption and then adjust the textbox so the two don't overlap. To make this dynamic, the class has a cLabelCaption property you can set. That property has an Assign method that calls a custom AdjustControls method. AdjustControls set the caption of the label to the value in cLabelCaption and then changes the Left and Width properties of the textbox to make room for the caption. The Init method also calls AdjustControls so entering a value for cLabelCaption in the Properties window adjusts the controls as well.

The problem is that this didn't always work. Sometimes the label and textbox were sized properly and sometimes the label overlapped the textbox. In tracking this down, I discovered that the problem occurred when cLabelCaption was changed before the form the control is on was visible. Changes to the size of controls isn't applied until the form actually appears. So, my calculations which relied on the Width of the label, which I assumed changed automatically when Caption is changed because AutoSize is .T., didn't work.

The solution is to manually determine the width of the label if the form isn't visible yet. You could use TXTWIDTH(), but as I blogged about a couple of years ago, the GDI+ GdipMeasureString function is more accurate. My SFGDIMeasureString class is a wrapper for that function.

Here's the code I now use in AdjustControls. Note the handling of txtFile.Anchor; any time you manually change the position of a control that has anchoring turned on, you need to do this or anchoring won't work properly for that control.

local loSFGDI

with This
if not empty(.cLabelCaption) and ;
not .lblFile.Caption == .cLabelCaption
.lblFile.Caption = .cLabelCaption
    if not Thisform.Visible
      loSFGDI = newobject('SFGDIMeasureString', ;
      .lblFile.Width = loSFGDI.GetWidth(.lblFile.Caption, ;
        .lblFile.FontName, .lblFile.FontSize, ;
        iif(.lblFile.FontBold, 'B', ''))
endif not Thisform.Visible
   .txtFile.Anchor = 0
  .txtFile.Left   = .lblFile.Width + 5
   .txtFile.Width  = .Width - .txtFile.Left - .cmdGetFile.Width
   .txtFile.Anchor = 10
endif not empty(.cLabelCaption) ...

Determining the First Date in a Week

Here's a simple algorithm to determine the first date in the week a certain date falls in. This is handy for reporting on aggregate values by week, displayed as "Week of SomeDate". The gotcha is that you need to know what day the user thinks of as the first day of a week. Some people consider Sunday to be the first day, others Monday, and so on. The DOW() function can accept a value indicating what day is the first one, from 0 for Sunday to 7 for Saturday. You'll need to ask the user which day they want a week to start from.

In this expression, ldDate is the date from which to determine the first date in the week (for example, a transaction date) and lnFirstDay is the day number for the first day in a week.

ldDate - dow(ldDate, lnFirstDay) + 1

Here's an example that shows the total freight charged by week, with Sunday being the first day of the week.

select OrderDate - dow(OrderDate, 0) + 1 as Week, sum(Freight) from Orders group by 1

Sunday, November 16, 2008

Updated Property/Method Replacement Dialogs Project

Jim Nelson has added some very cool new features to the Edit Property/Method replacement dialog project on VFPX. Among the new features are:


  • New columns in the grid showing the member hierarchy (native, inherited, or custom) and favorites.
  • Ability to sort the list by clicking on a column header.
  • Filtering so you can see only properties or methods and only native, non-native, inherited, or custom members.
  • Color-coding: in the image above, I specified red for custom members and blue for inherited.
  • Apply and Revert buttons that are enabled when you change an attribute; the grid is also disabled while you're editing a member to ensure you're finished before selecting another member.
  • Editing the value of native properties.

He also fixed a few bugs and updated the user interface to be cleaner. He made some similar UI changes to the New Property/Method dialog.

If you aren't using these dialog, you don't know what you're missing. They are so much better than the dialogs built into VFP that you'd be crazy not to use them. As great as they were, though, the changes Jim made take these dialogs up to a whole new level of productivity. Great job, Jim!

German DevCon

Rainer Becker and his staff once again put on a great conference. This was the 15th annual German DevCon, and my fourth time speaking at it. It was nice renewing acquaintances with people I only see once a year, such as Igor Vit, Hans and Gaby Lochmann, and of course Rainer, his wife, and their new baby.

I presented the two sessions I did at Southwest Fox--Creating Explorer Interfaces in Visual FoxPro and Advantage Database Server for Visual FoxPro Developers--plus a new one, Practical Uses for XML. Since I'm not an organizer for this conference like I am at Southwest Fox, I had a lot more time to attend sessions. I saw Rick Schummer's Extending the Sedna Data Explorer and was inspired to start playing with the Data Explorer again. I also saw his two-part Using VFPX Components in Production Applications. Rick is a terrific speaker, but he outdid himself on these sessions. His enthusiasm for VFPX is infectious and he did a great job showing how some of the VFPX projects can breath new life into the user interface of your applications.

I also saw Tamar Granor present We Used to Do it That Way, But …, a great refresher session on when and why to use some of the newer functions in VFP. I learned several new SQL tricks I wasn't aware of in her Solving Common Problems with VFP’s SQL session. However, the session I liked best was her Making the Most of the Toolbox session. I'd played with the Toolbox a bit before but never got into the habit of using it. However, she showed some ways the Toolbox can make you more productive, such as automatically executing script code when you drop a control on a form. For example, I'm going to create a script for my OK and Cancel button subclasses to automatically name them cmdOK and cmdCancel when I drop them on a form, saving me the time to do that every time I use those controls.

Two sessions I looked forward to seeing were Bo Durban's Creating Custom Report Controls with Sedna and VFP9 and SP2 Reporting Component Basics sessions. Bo has done some really cool things with the new features added to the Report Designer in VFP 9 SP2. I got some great ideas for features to add to Stonefield Query from his sessions plus learned a few things I didn't know, such as how to get a custom page in a report designer dialog to see the changes made to properties on other pages (set Thisform.Frame.AutoRefreshOnPageChange to .T.).

I also watched Kevin McNeish present Building Rich Internet Applications in Silverlight 2.0. He discussed how Silverlight can be used to create rich interfaces for Web applications and showed some cool demos, including zooming in and out of a library of images, that really couldn't be done any other way.

Ken Levy discussed the status of VFP in the keynote session. He reminded everyone that the future of VFP is in the community's hands now, primarily through projects on VFPX. He also did a bonus session one night showing some of the utilities in Sedna (My and .NET4COM) and a new code searching tool he's created called AppScanX. He plans on submitting AppScanX as a VFPX project, but if you want to find out more about it now, listen to his interview with Andrew MacNeill in The FoxShow #60.

As is normal for this conference, there's a ton of delicious food. You definitely have to pace yourself at this conference, because Rainer says his goal is for attendees to gain 5 kg (more than 10 lbs). I tried to be good by not going for seconds, skipping dessert on all but the last meal (the speaker dinner Saturday night), and working out a couple of times in the hotel's exercise room (the best equipped I've ever seen in a hotel) but fear I'll have to work a little harder on the bike for a few weeks once I get home.

Thanks again to Rainer and his right-hand Tina for putting on a terrific conference. You can read more about it on the Universal Thread's coverage by Jan Vit and Jan Kral.

Setting the Background Color of a TreeView Control

Although disabling a TreeView control is easy (you have to set oTree.Object.Enabled rather than oTree.Enabled), making it appear disabled is a different matter. One way to do it is to set the background color of the control to a disabled-looking color. However, when you try to do that, you'll discover the TreeView doesn't have a BackColor property.

The trick to setting the background color of the TreeView is to use some API calls. Also, you need to set the BackColor of each node. Here's the code to do that. Pass it a reference to the TreeView and the RGB value to use for the color.

lparameters toTree, ;
local lnhWnd, ;
loNode, ;

* Declare some Windows API functions and constants we need.

declare long GetWindowLong in Win32API long hWnd, long nIndex
declare long SetWindowLong in Win32API long hWnd, long nIndex, ;
long dwNewLong
declare SendMessage in Win32API long hWnd, long Msg, long wParam, ;
long lParam
#define GWL_STYLE -16
#define TVM_SETBKCOLOR 4381
#define TVS_HASLINES 2

* Get the TreeView's window handle.

lnhWnd = toTree.hWnd

* Set the BackColor for every node.

for each loNode in toTree.Nodes
loNode.BackColor = tnColor
next loNode

* Set the BackColor for the TreeView's window.

SendMessage(lnhWnd, TVM_SETBKCOLOR, 0, tnColor)

* Get the current style, then temporarily hide lines and redisplay them so
* they'll redraw in the new color.

lnStyle = GetWindowLong(lnhWnd, GWL_STYLE)
SetWindowLong(lnhWnd, GWL_STYLE, bitxor(lnStyle, TVS_HASLINES))
SetWindowLong(lnhWnd, GWL_STYLE, lnStyle)

Monday, November 10, 2008

Off to Germany

Tomorrow morning, I'm heading to Frankfurt for the 15th annual German DevCon. This is such a great conference: the sessions are interesting, the food is incredible and in copious amounts, the beer flows freely, even during evening sessions, and it's fun meeting people from other countries sharing a common interest.


Friday, November 07, 2008

Southwest Fox and VFPX

One of the goals of Southwest Fox was to increase the exposure of VFPX to the VFP community. At several conferences last year, Rick Schummer asked attendees how many were familiar with VFPX and was astounded at how few hands went up. So, we purposely had several presentations on VFPX at Southwest Fox this year, including showing some of the projects in the keynote and having a bonus session on VFPX, both of which are available on streaming video. The idea was not just to get more people downloading the various cool VFPX projects but also to fire up the imagination of the VFP community and get more people participating.

It looks like the plan worked. Joel Leach started a new project for the FoxTabs utility (not a new utility but it was languishing and wasn't a community project); he also started blogging. Jim Nelson and Matt Slay started working on the New and Edit Property/Method Replacement Dialog project and have added several cool new features, including filtering by member type. A new person is helping Andrew MacNeill with the Code Analyst project. The VFPX administrators have received submissions for other projects that hopefully will come online soon.

So, what's your idea for a VFPX project? What tools have you created that make your development life a little easier? Or perhaps you have ideas that will take an existing project to the next level. It's time to share them with the community.

Sunday, October 26, 2008

Southwest Fox, Post-Conference

On Monday, Rick and Therese Schummer headed for Sedona for their annual "corporate retreat". I also planned to go to Sedona, but my wife Peggy and son Nick didn't arrive until Tuesday, so I spent Monday taking it easy: sleeping in, working on emails, and getting some exercise.

Before they left for home, Tamar, Marshal, and I went to the Desert Botanical Garden near the airport, where we had a great time seeing a lot of varieties of cactus and other desert plants. I picked up a car at the airport, went back to the resort, worked for a couple of hours, then drove Christof to the airport. When I returned, I hung out with Craig Boyd and Bo Durban for a while, who are here for a couple more days working on a project together, then worked for another hour or so before the three of us went out for Mexican.

Tuesday, I picked up Peggy and Nick and we drove to Sedona. Although I've been there several times on day trips, I've never stayed there, and Peggy and Nick have never been to Arizona at all, so we were all really looking forward to a few days there (it was my 50th birthday celebration). We stayed at A Sunset Chateau, a very nice bed and breakfast with a great view of the mountains. We met Rick and Therese and they took us sightseeing to some of their favorite spots around Sedona, then went to a Japanese restaurant for a great meal. Here are some photos of the area around Sedona:

Phoenix 2008 004

Phoenix 2008 005

Phoenix 2008 006

On Wednesday, we headed to the Grand Canyon. On the way out of Sedona, an engine light came on in the rental car, but it was for traction control, which I've had on my car from time-to-time, so I didn't think it was too serious. However, as soon as we got on the highway, I noticed that the engine was definitely lugging; I couldn't get the car to go over about 65 MPH even with the gas pedal floored. Since it was only about 40 miles to Flagstaff, I figured we'd get a replacement car there. Unfortunately, we didn't even make it that far: about 10 miles later, going up a steep hill and passing a slow-moving truck, the engine died and a message said that starting was disabled. It was a little scary trying to get the car off the side of the road with other cars zooming around us, and in fact I couldn't even get the car way all the way out of the right lane. I immediately told Peggy and Nick to get out of the car to be safe and flagged a couple of other cars over to help push it the rest of the way off the road. A nice couple on their way to Flagstaff gave us a ride to the Budget booth at the Flagstaff airport, where the folks there quickly got us going with another car. The rest of the way to the Grand Canyon was thankfully uneventful.

We had a great time at the Grand Canyon. It's one of those places that no matter how many times you go (this was my second visit), it's still awe-inspiring. We stopped at several viewpoints and spent several hours at the Grand Canyon Village. After a long drive back to Sedona, we finished with a very nice dinner at a gourmet pizza restaurant not far from our hotel. Here are some photos of the Grand Canyon. You can't really get a sense of the scale, especially in a photo, but the far side of the canyon is 11 miles away.

Phoenix 2008 010

Phoenix 2008 019

Phoenix 2008 024

We did some sightseeing Thursday, starting with Oak Creek Canyon and Slide Rock State Park. Slide Rock is a very cool place: a natural water slide you can have a lot of fun in. Unfortunately, it was a little too cool to go in the water, so we just enjoyed the vistas while Nick splashed around a bit. We then went to Tlaquepaque, a gorgeous Mexican-inspired village in Sedona full of art galleries and stores. After lunch, we did a bit of shopping on the main street in Sedona, then went on a Pink Jeep tour of Broken Arrow Canyon. Wow, that was amazing. The jeeps went places I didn't think any vehicle could go, including what seemed like nearly vertical inclines and declines. Highly recommended!

Phoenix 2008 037

Phoenix 2008 108

Phoenix 2008 112

We headed back to Phoenix on Friday and mostly took it easy: a couple of hours at Amazing Jake's riding go-karts and rock wall climbing (yes, even me), some pool time, a movie (Lakeview Terrace, with one of my favorite actors, Samuel Jackson), and then a very nice late dinner at Joe's Crab Shack.

Saturday's return home was uneventful, which is my favorite way to travel. All in all, a great family mini-vacation. Now it's time to catch up: it's been two weeks since I've been in the office and couldn't send emails since Tuesday (I had an Internet connection but my mail server would only let me receive, not send, emails).

Southwest Fox Ambassador Fund

At the keynote presentation for Southwest Fox 2008, we announced the Southwest Fox Ambassador Fund. (We actually called it the Worthy Developer Fund, but that name was just a placeholder until we came up with a better one. Thanks to Christof Wollenhaupt for the inspiration for the new name.)

Many VFP developers around the world are providing incredible contributions to the VFP community, such as working on VFPX projects, blogging about techniques and code to do cool things in VFP, or providing exemplary support in various VFP forums. However, for most of them, attending a conference such as Southwest Fox isn't feasible.

The Southwest Fox Ambassador Fund is intended to raise money from the VFP community to allow one (or more, depending on how much is raised) developer to attend next year's conference as an ambassador for the developers in their country. This will give them the opportunity to meet and share experiences with developers attending Southwest Fox and give us the opportunity to learn about VFP development going on in their country.

Geek Gatherings, the organizers of Southwest Fox, are kicking off the fund by contributing the conference registration fee. Also, we held a silent auction at Southwest Fox to start the ball rolling. Between the silent auction and contributions from generous people at the conference, we've already raised over $1,100!

We urge you to consider donating any amount you see fit, small or large, to this fund. Contributions can be sent by check to:

Geek Gatherings, LLC
Ambassador Fund
42759 Flis Drive
Sterling Heights, MI 48314

100% of donated money will go to the travel costs for the ambassador. Not a single penny will be used to cover administrative costs. All time managing the fund and working through the selection process will be donated. No one will be paid for their time. So if you want to donate to the fund, please send a check or we can arrange non-credit card PayPal transaction so we don't have unnecessary discount fees.

Also, let us know if you want your contribution to be public or not. If so, we will include your name in the list of contributors announced at next year's conference.

Southwest Fox, Day 3

I started the last day of the conference by presenting the second instance of my Creating Explorer Interfaces in Visual FoxPro session.I really enjoy doing this session; I get a lot of positive feedback and a ton of appreciative comments afterward. I think this session really hit a sweet spot.

I then attended Rick Borup's Automating QuickBooks with QODBC session. Although I haven't done any work with QuickBooks (either as a user or a developer), there's a possibility I may in the future, so I wanted to see how the QODBC driver can be used to retrieve and update information using normal SQL statements. Once again, a great, thorough presentation by Rick.

I finished the regular sessions of the conference by watching Cathy Pountney present her Customizing Your Vertical Market Application session. Rick Schummer told me it was a don't-miss session for several reasons, and he was right. Cathy started by relaying the bad news (for her) of last night's racing results. She then discussed a strategy for customizing vertical market applications for specific customers that allows you to leave the core code alone (creating a custom EXE for each client would definitely be bad) yet satisfy almost every request for customization. The secret lies in creating hook points throughout your application and using a special customization manager class to handle customized (or new) forms, reports, menus, and other components. However, rather than showing how to customize the vertical market app she works on, she showed a simpler version: a core application with a simple menu, a few forms, and a couple of reports. She then showed customized versions of that application for Tamar, Rick, and I. Of course, part of the core application involved racing results. Since Tamar didn't race, those components were removed from the menu in her version but had customized forms in Rick's and my versions. Throughout the presentation, Cathy threw out little digs, making it clear she's already gearing up for next year's race.

After giving the conference center staff a few minutes to convert the smaller breakout rooms into one larger one, we started the closing session. We thanked the speakers, attendees, and conference center staff, announced the dates for Southwest Fox 2009 (October 15-18, 2009, once again at the Arizona Golf Resort), and gave away the rest of the raffle prizes. We then drew the names of three people who turned in their evaluation forms and awarded them MSDN Premium Subscriptions, each worth almost $11,000.

The last day of a conference is always bittersweet. I'm exhausted from all the work of putting on the conference (and averaging 4-5 hours sleep a night for the past week), energized from the cool sessions I saw and ideas they generated, happy to have spent time hanging out with people I like and respect but only see a couple of times a year, and glad to have met and chatted with some new folks.

After spending an hour or so tidying up the conference center, Rick, Tamar, Marshal, Therese, and I met with conference center staff for a debriefing. Things went very smoothly again this year thanks to the hard work and "get it done" attitude of every staff member. There were even fewer complaints than last year (one being that lunch was a little slow on Friday) and they immediately come up with ideas on how to take care of those for next year. Kudos once again to the Arizona Golf Resort for making us look good.

We then had a planning session of our own to discuss what things worked and what we need to tweak for next year. This meeting was a lot shorter than last year's because we have another year of experience under our belts. Most of what we discussed was fine-tuning little details. We were then going to hang out at the pool for an hour or so before dinner, but I found Craig Boyd, Bo Durban, and Jody Tooley hanging out on the patio for Toni and Mike Feltman's room, so I decided to hang out with them instead. Others showed up as well, including Christof Wollenhaupt and Alan Stevens.

A bunch of us went to the Cheesecake Factory for dinner (thanks, Joel) while others had a bratwurst cookout (there are BBQs set up between groups of rooms). When we returned, we went back to Toni and Mike's room/patio. By this time, a lot more people showed up. Toni told us later that was actually the highlight of the conference for her and suggested we consider doing something similar on a larger scale next year.

Thanks to everyone who made Southwest Fox 2008 one of the best conferences I've ever attended (and there have been a LOT of them over the past nearly twenty years). See you all next year!

Southwest Fox, Day 2

After breakfast Saturday morning, I presented my Creating Explorer Interfaces in Visual FoxPro session. This was a fun session to do. I started by discussing the basic components of an explorer interface--a list of items at the left (typically implemented with a TreeView control), properties of the selected item at the right, a splitter control between the two, and a status bar. I then discussed the various problems with the Microsoft TreeView control and presented a class, SFTreeViewContainer, that works around all of those issues and adds additional functionality, such as "go back" behavior. I spent some time showing a data-driven subclass of SFTreeViewContainer called SFTreeViewCursor. The nice thing about that class is you only have to put code in three methods to display any hierarchy of data: FillTreeViewCursor, which fills a cursor with records representing the nodes to load, LoadImages, which loads the images used in the TreeView, and DisplayRecord, which tells the class what to do when a node is selected. From there, I showed additional classes, including SFExplorerFormTreeView, which encapsulates all the common behavior of explorer forms with just a TreeView at the left, and SFExplorerFormOutlook, which includes the Outlook bar control, part of the VFPX Themed Controls project. You can watch a video of how SFExplorerFormOutlook allows you to create a great looking UI for your applications.

I attended Christof Wollenhaupt's Optimizing and Debugging session. Christof explained that it was two sessions for the price of one because optimizing and debugging are often related. He started by discussing the importance of having the right attitude toward debugging. He suggested that unlike fiction novels, code should be boring, with no surprises or tricks. He then said the best line I heard at the whole conference: "If it's cool, fix it." He also presented what I thought was the best explanation for the process of debugging I've ever heard. Christof is definitely a "speaker's speaker"; there were more speakers in his session than any other session I attended. In fact, he showed a form that by simply moving SET EXCLUSIVE ON from the Init method to the Load method prevented the MESSAGEBOX() function from working. Not one of the speakers in the room could figure out how he did it (hint: it involves code in the DataEnvironment and the BINDEVENT() function).

My vendor session for Stonefield Query was in the next time slot. I had a pretty good turnout considering the quality of sessions I was up against (including Toni Feltman's Leveraging .NET using .NET Extender, which I really wanted to see) and had lots of great questions.

After lunch (which was a delicious Italian meal), I presented my Advantage Database Server for VFP Developers session again. Thanks to J.D. Mullin, R & D Manager for Advantage, who sat in on my first session, I had correct answers to a few obscure questions I missed the first time.

I spend the rest of the day catching up on email, blog reading, and chatting with other developers. As we pointed out in the keynote, skipping sessions at a conference is OK, especially if it's because you're talking with other attendees, getting energized with ideas that flow when you have those kinds of discussions.

Here's are photos of the mid-afternoon break:

Southwest Fox 2008 014

Southwest Fox 2008 015

Rick, Tamar, and I took the speakers to Omaha Steakhouse for a dinner to show our appreciation for all of their hard work. The food was much better than last year's dinner and we had a room all to ourselves so we didn't have the noisy environment like we did last year.

Then, the event we waited two years for: showdown at the F1 Race Factory. At the 2006 conference, Cathy narrowly beat Rick and I at indoor kart racing and has been rubbing our noses in it ever since. We tried to go last year but neglected to make a reservation so were turned away. This year, I made a reservation several weeks in advance to make sure we weren't disappointed again. Trash tasking was at an all-time high. Even the sign-up list for those wanting to race was a victim of tampering. Finally, it was time to race. Cathy's car was directly ahead of mine in the pit, so while we waited to start, I gave her a friendly little bump just so she knew I was there. When the smoke cleared 15 minutes later (tire smoke that is; you really squeal tires around the corners), I was the victor, with Rick closely behind me and Cathy a distant fifth, measured by fastest lap time. Measured by average lap time, Rick was first, so he and I took turns on the "first place" podium while Cathy sat hanging her head on the "third place" podium (which she really didn't deserve, but there was no podium for a finish as low as hers). We raced a second time, and Rick Strahl, Bo Durban, and Dave Hanna beat all of us. For overall races, Rick Strahl was first, Bo second, me third, and Rick Schummer fourth.

After we returned to the hotel, we hung out on the patio outside the bar, gloating and regaling the others with our racing stories. I called it a night about 1:00 a.m. because I had to present an 8:00 a.m. session.

Tuesday, October 21, 2008

Southwest Fox, Day 1

Friday got an early start because there were still people arriving and several new folks registered. I planned to attend Bo Durban's VFP 9 and SP2 Reporting Component Basics session, but was busy helping with registration and chatting with people. I figured that since he's doing that session again at the German DevCon next month, I can check it out there. In fact, for that reason, I skipped all of his, Tamar's, and Rick Schummer's sessions here so I could take in more of the other sessions. Rob Eisler and Chris Wolf, who both work for me, attended Bo's session and it gave them lots of good ideas for new features to implement in Stonefield Query.

I went to Bo's Moxie Report Objects session in the next time slot. Bo has a very cool tool that extends the VFP reporting engine, providing capabilities such as outputting RTF and HTML content on reports, rendering ActiveX controls on reports, and allowing you to gradient fill columns. I'm planning on implementing Moxie Report Objects in the next release of Stonefield Query.

I then presented my Advantage Database Server for VFP Developers session. Advantage Database Server (ADS) is a high-performance client-server database engine from Sybase iAnywhere. There are several things that make ADS an excellent back-end for VFP applications. One is that ADS concepts are very similar to those in VFP, such as "Advantage optimized filters", which in my opinion are identical to Rushmore, so there isn't as much of a learning curve for VFP developers as there is for other client-server engines. Another reason for using ADS are very cool features such as table encryption, online backup, and lightning-fast full text search (if you missed this session, you can watch my video of full text search). However, the best reason is that ADS can use VFP DBF files as a data store. That's right: your existing tables can be accessed by ADS, giving you the best of both worlds: direct access in VFP or client-server access (and all of the benefits that brings) through ADS. I discussed how an application migration strategy might work: if you want to move your application to a client-server back-end, you can migrate one module at a time (updated modules access the table via ADS while existing modules continue to use the DBF files directly) rather than having to rewrite the entire application before deploying it.

After a delicious lunch, I went to Alan Steven's Ignorance is Bliss session. Alan presented a strategy for storing application data that provides great flexibility to your application. It consists of several layers and uses XML as the transport mechanism between layers. Like many in the audience, I thought MSXML was slow for large XML documents, but Alan pointed out that in fact it was very fast but the VFP XMLTOCURSOR() function is slow. So, he showed how to use XMLAdapter to break the XML into 500-node chunks to great increase performance.

Next up was Christof Wollenhaupt's Creating Owner Drawn Controls in VFP. He discussed the pitfalls of creating your own controls by combining various controls and how the VFPX GDIPlusX project provides the same kind of functionality Microsoft uses internally to draw Windows controls. Although this is a complex subject, Christof did a good job of covering all of the things you need to be aware of when creating attractive, powerful controls.

By this point, I was getting a little tired (I'd been up since 4:00 a.m.) so I skipped the next session and instead hung out outside with Craig Boyd, Andrew Macneill, and Toni and Mike Feltman.

We added a new event at this year's conference: a dinner party Friday night. The food was excellent: steak, salmon, and grilled veggies. After everyone finished eating, we handed out some more raffle prizes, including a couple of MSDN Premium Subscriptions, each worth almost $11,000. Ken Levy also had some prizes to give away: copies of previous versions of VFP, including VFP for the PowerMac, that had been in his office at Microsoft. One of the winners, Boudewijn Lutgerink of the Netherlands, announced that he couldn't take his package home because it was too heavy (that was from the days when software used to come with manuals) and did a not-so-silent auction to raise money for our Worthy Developer Fund (which I haven't blogged about yet because we're going to rename it).

After dinner, we had five simultaneous bonus sessions and developer meetings. I really wanted to attend both the VFPX session (which you can watch on streaming video at and the "Show Us Your Apps" session, but I had a Stonefield Query Developer Meeting to preside. According to all reports, the "Show Us Your Apps" session was a big hit, so we're thinking about expanding it next year (and certainly having it in a larger room).

We wound up the evening in the resort bar, which is the usual meeting place for attendees once the sessions are over. I was a good boy and turned in about midnight because I had an 8:00 a.m. session to present the next day,

Southwest Fox: Pre-Conference

On Tuesday, Rick Schummer and his wife Therese, Tamar Granor and her husband Marshal, and Rob Eisler, Chris Wolf, and I (Rob and Chris work for me) arrived in beautiful Phoenix to get ready for Southwest Fox. I love Phoenix in October: it's warm and sunny and makes me forget the 6 inches of snow that fell at home two days before we left.

The resort was as beautiful and nicely laid out as last year.

Here are some shots of us putting together conference binders and bags on Wednesday:

Southwest Fox 2008
Therese and Tamar start organizing binders

Southwest Fox 2008 
Rob and Chris assembling binders

We ran into several attendees who arrived early, either because they were from far away (such as Tore Bleken from Norway) or because they were attending Rick Strahl's West Wind training (such as Peter Cortiel). It's always nice seeing familiar faces again.

Thursday things started cooking (and not just because it was 92F). There were four pre-conference sessions, presented by Menachem Bazian (returning as a VFP speaker after being away more than 10 years), Andy Kramek, and Cathy Pountney. Registration opened at 7:15 so we could get pre-con attendees registered before the sessions started. From the comments I heard, the sessions were well-received.

Attendees continued to arrive throughout the day. It's a lot of fun hanging out at the registration booth because you get to see old friends, meet new people, and finally put faces to names from the Universal Thread, Foxite, and other forums.

After a quick dinner, the keynote started at 7:00. I won't go over it here; you can watch it yourself at Having streaming video available for the keynote was very cool, especially for those interested in seeing the demo of VFP Studio that Craig Boyd and Bo Durban presented (or should I say "performed", given the magic show they put on). Several people, including Steve Black and Cesar Chalom, watched it live but you can also watch the archived version. We'll definitely do it again next year. Here are some photos taken just before the keynote started:

Southwest Fox 2008

Southwest Fox 2008

After the keynote, we held a reception in the trade show area. From an exhibitor perspective, it was great: I did a lot of demos of Stonefield Query and was well on my way to getting my usual raspy conference voice by the time everyone cleared out.

Once again, a great start to the best (and only) VFP conference in North America.

Monday, October 20, 2008

VFPX Administrator’s Outstanding Service Award

At the keynote presentation for Southwest Fox 2008, the administrators of VFPX (Craig Boyd, Rick Schummer, and I) presented Bo Durban with the first VFPX Administrator’s Outstanding Service Award. Bo has been one of the driving forces in GDIPlusX, a key project in VFPX that not only brings amazing graphics functionality to VFP but is also the foundation for several other very cool projects such as the Themed Controls project. Congratulations, Bo; very well deserved!

Sunday, October 12, 2008

Another Fund Raiser

Last month, we were at another fund raiser for Sofia House, a second stage shelter for women and children escaping domestic violence. This was a little more sedate than last year's fashion show, but my son Nick and I did have to get dressed up so we could be ushers. He was considerably more popular with the ladies than I.


This year's show raised over $10,000 for Sophia House. Since the organization gets no government funding (which is a crime considering that our provincial government is wallowing in cash right now due to high commodity and oil prices), everything they make comes from private donations, organizations like the United Way, and fund-raising efforts.

Friday, October 10, 2008

Heading to Phoenix

Tuesday morning I take off for Phoenix for Southwest Fox. Tuesday and Wednesday are setup days: picking up shipments of door prizes and printed materials, assembling binders and bags, setting up the registration table, hooking up wireless routers, ensuring the resort staff have everything in place, and so on. Registration starts at 7:15 Thursday morning for those attending pre-conference sessions and the full conference gets started Thursday at 7:00 p.m. with the keynote.

I'll blog about the conference, although not likely during given how hectic it'll be (four presentations, a vendor session, and a Stonefield Query developer meeting, plus being an organizer). If you're not attending, be sure to watch the streaming video of the keynote and VFPX bonus sessions and check The Fox Show for interviews right at the conference. Once you see how great the conference is for yourself, you'll definitely want to attend next year.

Friday, October 03, 2008

Fix for Installation of My in Sedna

If you've installed Sedna and tried to use My and found that it didn't work, here's the fix: open My.PJX, open InstallMy.PRG and change this line of code in the GetScriptCode function:

lcDirectory = sys(16)


lcDirectory = sys(16, 1)

Save and build My.APP, then DO My.APP. My should now work correctly for you.

I'll posted an updated version of My on VFPX when I get a chance (not likely until after Southwest Fox).

In case you're interested, the cause of the failure is that the cProxyClasslib property of the FoxCodeLoader class, which is defined in the DATA memo of the MyScript record in your IntelliSense table (FOXCODE.DBF), specifies the path for My.VCX so it can be found on your system. Due to the bug, the property's value is just "My.VCX", without a path, so VFP can't find the VCX. That's because SYS(16) doesn't return a path when called from within an APP while SYS(16, 1) does.

Streaming Video of Keynote and VFPX Session

Thanks to Steve Bodnar, we're providing streaming video of the Southwest Fox keynote (Thursday, October 16, 7:00 p.m. MST) and VFPX bonus session (Friday, October 17, 8:00 p.m. MST). You can watch both of these videos at

Wednesday, October 01, 2008

MVP Award

I am honored to be given the Microsoft Most Valuable Professional (MVP) award for 2008-2009. This is the fourteenth consecutive time I've been awarded, and I'm thankful each time! Congratulations to other award recipients; see for a complete list of VFP MVPs.

(I removed my previous post about not being re-awarded. I was sent the "sorry" email by mistake.)

Tuesday, September 30, 2008

Email Back Up

Our email server had problems yesterday; it bounced back everything sent to it. Fortunately, it's working again, so please resend any email you tried to send.

Friday, September 26, 2008

The Big 5-0

Today I, to use Tamar's words, "reached the top of the hill" (she's just a couple of days behind herself). Although I may look my age (I was considered to be the poster boy for the "this is what 40 looks like" birthday party at DevCon in Orlando ten years ago), I certainly don't feel it. I think I'm in better shape than I've been in 20 years.

Well, except I notice that injuries take a lot longer to heal. I actually had physiotherapy today for the first time in my life to help with my injured shoulder. And although I love snowboarding with my son, I definitely feel it the next day. And those darned kids with their loud music. I have to go chase some of them off my lawn.

Monday, September 22, 2008

Getting Time Zone Information

Here's some code that retrieves local time zone information:
local lcTimeZone, ;
lnID, ;
lnStandardOffset, ;

* Declare the time zone information API function and get the time zone
* information.

#define TIME_ZONE_SIZE 172
declare integer GetTimeZoneInformation in kernel32 ;
string @lpTimeZoneInformation
lcTimeZone = replicate(chr(0), TIME_ZONE_SIZE)
lnID = GetTimeZoneInformation(@lcTimeZone)

* Determine the standard and daylight time offset.

lnStandardOffset = ctobin(substr(lcTimeZone, 1, 4), '4RS')
lnDaylightOffset = ctobin(substr(lcTimeZone, 169, 4), '4RS')

* Determine the total offset based on whether the computer is on daylight
* time or not. Get the description for the time zone.

if lnID = 2 && daylight time
lcTimeZoneDesc = strtran(strconv(substr(lcTimeZone, 89, 64), ;
6), chr(0), '')
lnTimeZoneOffset = (lnStandardOffset + lnDaylightOffset) * 60
else && standard time
lcTimeZoneDesc = strtran(strconv(substr(lcTimeZone, 5, 64), ;
6), chr(0), '')
lnTimeZoneOffset = lnStandardOffset * 60
endif lnID = 2

You can then add lnTimeZoneOffset to a local time value to get a time in Greenwich Mean Time (GMT) or subtract lnTimeZoneOffset from a time value in GMT to obtain a local time value.

Thursday, September 18, 2008

Southwest Fox News

As we get closer to the conference (it starts four weeks from today), the news gets as hot as the Phoenix weather!

Visionpace is offering a free six-month subscription (or a six-month extension for existing subscribers) for Visual MaxFrame Professional (VMP) to all Southwest Fox attendees. Between all the offers (VMP, xSQL, and CoDe magazine) and prizes to be won (over $100,000), this conference will pay for itself before you attend your first session!

Five bonus sessions are planned for Friday night at 8:00 pm after the dinner party:

  • Stonefield Software is hosting a Stonefield Query Developers Meeting. This meeting is open to everyone.
  • Visionpace is hosting a VMP Developers Meeting. This meeting is open to all developers using VMP (developers not using VMP are also welcome).
  • Bo Durban is leading a VFPX meeting. Everyone interested in VFPX is welcome to attend.
  • Mike Yeager of EPS Software is presenting a bonus session titled "VFP to .NET Migration Strategies." This session provides a detailed overview, based on best practices and real world experience, of the optimal way to transition a mission critical application from any version of FoxPro to .NET and SQL Server.
  • "Show Us Your Apps" is an opportunity to show the crowd what cool things you've done in VFP. Because there's a limited amount of time available, presenters are limited to 10-15 minutes each. Please email to let us know what you'd like to show.

Monday, September 08, 2008

Southwest Fox News

The Southwest Fox 2008 session schedule is now available. One thing you might note is that we've planned a dinner party for Friday night this year. Also, vendor sessions are held concurrently with other sessions rather than in the evenings as they were last year.

I posted a new video demoing one of the cool features I'm showing in my session on Advantage Database Server: full text search capabilities, even against VFP tables.

Former VFP Product Manager Ken Levy will be at Southwest Fox, hanging out at the VFPConversion booth and showing a new utility he's created and releasing into the public domain.

One of the bonus sessions this year is "Show Us Your Apps", an opportunity to show the crowd what cool things you've done in VFP. Because there's a limited amount of time available, presenters are limited to 10-15 minutes each. Please email to let us know what you'd like to show.

Friday, August 29, 2008

VFP 9 SP1 Download Link Goes AWOL

After Bob Pierce pointed it out to her, Tamar Granor pointed out to me that the VFP 9 SP1 download link no longer appears on either the VFP home page or the Product Updates page. Fortunately, Microsoft hasn't removed it altogether; you can download it from here (thanks to Rick Schummer for the URL).

Added Google Analytics

I'm not sure what took me so long, but I finally added Google Analytics to my blog so I can determine how many people actually read it. It was really easy to do, using instructions posted at

Thursday, August 28, 2008

Free CoDe Magazine Subscription for Attendees

EPS Software is offering a free CoDe Magazine subscription to each Southwest Fox attendee. The value for this conference just keeps going up and up!

Tuesday, August 26, 2008

Southwest Fox Exhibitors Area Sold Out

For the second straight year, the exhibitors area for Southwest Fox has sold out. Look for these exhibitors at this year's conference:

  • White Light Computing
  • Stonefield Software
  • Sybase iAnywhere
  • Servoy
  • Micromega Systems
  • Moxie Data
  • VFPConversion
  • West Wind Technologies
  • DBI Technologies
  • GeneXus

All but one (VFPConversion) were at last year's Southwest Fox.

See the Exhibitors page of the conference Web site for details.

Friday, July 18, 2008

Video for VFPX New Property/Method Dialog Replacement

The native New Property and New Method dialogs in VFP have many shortcomings, including being modal, non-resizable, and not supporting MemberData so the case you type for the member isn't preserved. Fortunately, VFP 9 makes it possible to replace native dialogs with our own. VFPX has a project, New and Edit PropertyMethod Replacement Dialogs, that replaces these dialogs with versions that work much better.

I created a 4-minute video showing the features and benefits of these replacement dialogs. Once you start using them, you won't go back.

.Me Domain Available

GoDaddy is offering .Me domains for $19.99 a year. Unfortunately, all the good ones, like Bite.Me, Eat.Me, and several other unmentionables are already taken.

Thursday, July 17, 2008

Friday, July 11, 2008

Creating Explorer Interfaces in Visual FoxPro

One of the sessions I'm doing at Southwest Fox 2008 is titled "Creating Explorer Interfaces in Visual FoxPro". Explorer interfaces appear everywhere these days: Windows Explorer, Outlook, iTunes, Stonefield Query, and so on. These interfaces have one thing in common: a list of items at the left, usually shown in a TreeView control, and information about the selected item at the right.

I'm really jazzed about this session. I'm having so much fun creating the samples for it because I get to play with some very cool user interface controls available from VFPX. In addition, I've created some classes that make it very easy to create your own explorer interfaces with minimal code. No more worrying about how TreeViews work, how to get drag and drop working, how to link a TreeView node with a properties pane, and so on.

To whet your appetite for this session and hopefully entice you to attend, I've created a short video (about 9.5 minutes) showing a sample form using an explorer interface and how easy it is to add a new panel to the form. Anyone attending Southwest Fox will get full source code for everything and a white paper describing how it works and how to use it.

(And in case you're wondering, yes, you can expect some of this eye candy to show up in Stonefield Query in a future release.)

Thursday, July 10, 2008

Free Advantage Database Server Training

J.D. Mullin blogged about the free two-day Advantage Technical Summit being held September 10-11, 2008. Sounds like a great deal: the hotel is inexpensive, the content sounds very interesting (they even have a VFP-specific sessions), and they provide meals.

Wednesday, July 02, 2008

Southwest Fox 2008 Early Bird Deadline Extended

The deadline for the "early bird" discount for Southwest Fox 2008, to be held October 16-19, 2008 in Mesa, AZ, has been extended to July 7th. See the conference blog to learn why. Visit the conference site to learn about the conference, speakers and topics, and to register.

Wednesday, June 25, 2008

Southwest Fox 2008 Early-Bird Deadline Next Week

Hear that sound? Tick, tick, tick, tick...

That's the countdown clock getting closer to the early-bird deadline for Southwest Fox! July 1 is less than a week away and we thought we would pass along a reminder just in case you forgot to type it into your task list, or stick it on your monitor on a yellow sticky note.

Southwest Fox takes place October 16-19, 2008 in Mesa, Arizona and we really hope you can be there. We would hate to see you miss out on the $75 discount, the free pre-conference session, and a chance at the $300 scholarship from White Light Computing.

Already registered? Thanks. Can you help us remind your fellow VFP developers who have been procrastinating that the deadline is looming?

Now if the great sessions, networking opportunities, Arizona weather, fun and merriment, discount, and freebie pre-con are not enough to entice you to come, how about a chance to win an Microsoft's Visual Studio Team System 2008 Team Suite and MSDN Premium Subscription (1-year), valued at $11,000. We've got three of them to give away!

Okay, so maybe that is not enough. What if we told you the moment we process your registration, we'll send you a code for a free license for xSQL Software's xSQL Data Compare Pro (a $399 value). This product allows you to compare and synchronize data in two SQL Server databases. That's right, you get back almost US $400 of the registration cost even before you reach Mesa!

Where are those ginsu knives?

We have lots of raffle prizes from our sponsors and vendors to give away during the conference.

Special thanks to all our sponsors: White Light Computing, Stonefield Query, Tomorrow's Solutions LLC, Sybase iAnywhere, Servoy, xSQL Software, FoxRockX, ApexSQL Software, Sweet Potato Software, Redgate, Information Technology Associates, and Visual Extend.

If you're a user group leader, you probably want to contact us if you have not done so already and let us know you want in on the $25 per registration for your group. Free money to help your organization fund your activities.

You cannot go wrong with this, but you have to let us know by July 1st that you are interested. Send contact information to

Got suggestions?

Got questions?

Got registrations?

Or you can call the Geek Gatherings' World Headquarters at 586.254.2530.

Read about the registration process and get the registration form here:

Check out our list of amazing speakers:

Dig into our five session tracks:

Follow the news about the conference on our blog:

Use our brochure to convince your boss (or spouse or SO) to let you go:

So please beat the rush so we don't have to test the scalability of the registration tracking application next week!

113 days until we gather in Mesa.

Tuesday, June 24, 2008

Another Day at the Races

On Saturday, I competed in my second Echo Challenge, a fund raiser for our local YMCA to help disadvantaged kids go to summer camp and many other important programs run by the Y. Our team finished much better this year, placing 11th of 19 teams. That's not bad, considering we finished last in 2007 and many of the teams are comprised of 20 year old athletes.

Conditions were ideal this year: warm (about 24 C or 75 F) and little wind. The lake was like glass, which was much easier on the swimmers (last year, swimmers had a hard time catching a breath in the meter-high waves). Our swimmer finished ahead of last year's pace, in 13th. Our hill runner did great, finishing in the fastest time, which was tough considering both the competition and the fact that the leg was lengthened by about a kilometer this year.

I was a little nervous about this year's bike ride. On Wednesday, I was doing some hills to train for the event and a combination of new shoes and pedals (the click-in kind, which I've only used once before) and someone in a truck cutting me off on a gravel road up a hill resulted in a nasty crash. I had a lot of gravel-embedded road rash on my right forearm, leg, and back, but the worst part was my shoulder; I landed on my right shoulder and although it didn't hurt much then, by the time I got home it was agony. I got x-rays Thursday morning and fortunately nothing was broken or separated, so it was just soft tissue damage. It was a little better by Saturday morning but still very sore and tight. Fortunately, cycling is all lower body, so I was hoping it wouldn't be too much of a factor. I also have a dark purple bruise the size of a football on my right hip, but there's no pain, so I wasn't worried about it.

This year, I decided to use a road bike rather than a mountain bike and it made a huge difference. It wasn't a very fancy bike -- a $149 no-name brand -- but the lighter frame and much smaller tires really helped. (I did not, however, use the new shoes and pedals.) The other big difference was no head-wind this year. As a result, I felt like I was flying through the race. I passed three other cyclists like they were standing still and felt so strong I didn't even shift down on most of the hills. As they did last year, Peggy, Nick, and friends cheered me on at the start of my leg, drove ahead and cheered me on again, but couldn't beat me to the finish line because of traffic on the narrow, hilly road.

Here's a shot of me just starting off, with my son Nick chasing me:


I finished the 9.45 km race in 16:29, an average of over 34 kph, which I figured was pretty good for an old banged-up guy. It turns out that was the second fastest time of the day, which I was extremely happy about, considering the number of 20 year olds in the race. The only humbling thing was the fastest cyclist finished over a minute ahead of me, and he was in his 60s! He's a serious cyclist, though, with an $8,000 carbon fibre bike and a body that looks like he's made of steel, but he's in my sights for next year.

Here's Nick and I after the race:


Our swimmer from last year was our runner in the 6 km run this year, and he fared much better in this event, finishing 14th. Our canoeists also did much better this year (considering they swamped the canoe last year, they couldn't have done worse), also finishing 14th for their leg.

Echo Challenge raised over $25,000 for the YMCA this year, up significantly from last year. It felt great to have fun, engage my competitive side, and do something worthwhile for the community. I can't wait for next year!

Friday, June 13, 2008

Southwest Fox Just Got Better

Southwest Fox 2008 Gold sponsor xSQL Software is giving away a free license of xSQL Data Compare Pro (a $399 value -- allows you to compare and synchronize data in two SQL Server databases) to every conference participant. What a great offset of the conference price – you pay $695 for the conference and get back more than half of it ($399) with this offer alone! Even better, you'll receive your free license as soon as you register for Southwest Fox 2008 so you don't even have to wait until October!

Wednesday, June 11, 2008

Reset Your VPC Password

Ever forget the password to a Virtual PC? Virtual NT Password Reset Disk is a bootable floppy image you can mount in a VPC to reset the password.

Using the Microsoft Date and Time Picker Control with Date and Time Values

I've used the Microsoft Date and Time Picker (DTPicker) ActiveX control for years. Yesterday, I ran into an interesting issue. First, some background.

The control has four different data entry modes, set via the Format property: short date, long date, time, and custom. The nice thing about the first three is that they automatically use the date/time settings you set in the Regional Settings applet in the Control Panel, so you don't have to worry about localization issues. Custom is used for custom formats.

Time format is fine if you just want the time, but if you want both date and time displayed, you have to set Format to 3 for custom, then set the CustomFormat property to the desired format. For example, "MM/dd/yyy HH:mm:ss" uses two digits for most values and four digits for year (yes, four even though the format string uses three). However, here's the issue: how do you know what format to use? The user could be using MM/DD/YYYY, DD/MM/YYYY, or any of a variety of formats.

Fortunately, VFP has several functions that return the formats for dates. SET("DATE") returns values like MDY or American for MM/DD/YYYY format, BRITISH or DMY for DD/MM/YYYY format, and so on. SET("MARK") returns the character separating parts of the date (such as "/" or "-").

I have a subclass of the DTPicker control called SFDatePicker (actually, it's a container that contains a DTPicker) that provides additional functionality, including empty date support, data binding, and format control. Format control is handled via a custom lDateTime property; the default of .F. means only the date appears while .T. means date and time. Drop one on a form, set cControlSource to the control source if desired, set lDateTime to .T. if you want date and time, and you're done.

Except someone from Australia, which uses DD/MM/YYYY as its date format, reported that if they specify that Stonefield Query should display a datetime field as both the date and time rather than the default date only, when they filter on that field, the filter dialog displays the date as MM/DD/YYYY.

The following code in the SetCustomFormat method of SFDatePicker, which is called from Init and the Assign method of lDateTime (in case you change this property programmatically), sets CustomFormat as necessary:

with This 
  lcFormat = set('DATE') 
  if lcFormat <> 'SHORT' or .lDateTime 
    .oleDTPicker.Object.Format = 3 
    lcMark = set('MARK') 
    do case 
      case inlist(lcFormat, 'AMERICAN', 'MDY', 'USA', 'SHORT') 
        lcCustomFormat = 'MM' + lcMark + 'dd' + lcMark + 'yyy' 
      case inlist(lcFormat, 'BRITISH', 'DMY', 'FRENCH', ;
        lcCustomFormat = 'dd' + lcMark + 'MM' + lcMark + 'yyy' 
      case inlist(lcFormat, 'JAPAN', 'YMD', 'TAIWAN', 'ANSI') 
        lcCustomFormat = 'yyy' + lcMark + 'MM' + lcMark + 'dd' 
    if .lDateTime 
      lcCustomFormat = lcCustomFormat + ' HH:mm:ss' 
    endif .lDateTime 
    .oleDTPicker.CustomFormat = lcCustomFormat 
  endif lcFormat <> 'SHORT' ...

It looks like this code handles different date formats properly, including the date separator, so what's the problem? It's a subtle one: if you use SET SYSFORMATS ON, which you should so the user's regional settings are respected, SET("DATE") returns "SHORT". This code assumes that short dates are treated as MDY.

Now the problem is how to determine what the user's actual date format is. I figured a Windows API function would take care of it, and did find some possibilities on MSDN, but it looks like these functions can't be called directly from VFP because they require callback functions. So, I took a brute force approach:

if lcFormat = 'SHORT'
  ldDate = date(2008, 1, 3)
  lcDate = dtoc(ldDate)
  lnPos1 = at('8', lcDate)
  lnPos2 = at('1', lcDate)
  lnPos3 = at('3', lcDate)
  do case
    case lnPos1 < lnPos2 and lnPos2 < lnPos3
      lcFormat = 'YMD'
    case lnPos1 > lnPos2 and lnPos2 > lnPos3
      lcFormat = 'DMY'
    case lnPos1 > lnPos3 and lnPos3 > lnPos2
      lcFormat = 'MDY'
endif lcFormat = 'SHORT'

This code uses 01/03/2008 (in MDY format) as a date, converts it to a string (which respects the user's regional settings), then figures out what order the month, day, and year parts are in and sets lcFormat accordingly.

Ugly, yes, but it works, so I'll stick with it until a more elegant solution is available.

Update: I figured there was a better way to do this. While I was looking at the code for Carlos Alloatti's cool ctl32_datepicker control, I came across his use of SET('DATE', 1). Looking that up in the VFP help file, I discovered this was exactly the function I needed. I can't believe I either never knew about that or (more likely) forgot about it. So, now the code is a much simpler and cleaner:

if lcFormat = 'SHORT'
  lnDate = set('DATE', 1)
  do case
    case lnDate = 0
      lcFormat = 'MDY'
    case lnDate = 1
      lcFormat = 'DMY'
      lcFormat = 'YMD'
endif lcFormat = 'SHORT'

If you're curious about how the other features work, here are the details.

Control source support: as I mentioned above, cControlSource is a custom property containing the name of the control source if desired. There's also a Value property so the control can look like other data-bound controls. Refresh updates Value from the control source:

if not empty(This.cControlSource)
  This.Value = evaluate(This.cControlSource)
endif not empty(This.cControlSource)

The Change event of the DTPicker, which fires when the user changes the date and/or time, raises a custom DateChanged event of the container. DateChanged updates the control source, which could either be a field in a cursor or something else, such as a property of an object:

with This
  lcAlias = juststem(.cControlSource)
  lcField = justext(.cControlSource)
  do case
      case empty(.cControlSource)
      case used(lcAlias)
        replace &lcField with .Value in (lcAlias)
        store .Value to (.cControlSource)

Value_Access gets the value from the DTPicker, changing it to a date if a datetime isn't needed:

luValue = This.oleDTPicker.Object.Value
if not This.lDateTime
  luValue = ttod(luValue)
endif not This.lDateTime
return luValue

Blank date support: if you've worked with the DTPicker control, you know it doesn't like blank dates. So, the code in Value_Assign uses the current datetime in that case. Also, since DTPicker expects to have its Value property set to a datetime value, we have to handle being passed a date:

lparameters tuValue
local luValue
with This
  do case
    case empty(tuValue)
      luValue = datetime()
    case vartype(tuValue) = 'D'
      luValue = dtot(tuValue)
    case vartype(tuValue) = 'T'
      luValue = tuValue
      luValue = .NULL.
  if not isnull(luValue)
      .oleDTPicker.Object.Value = luValue
      if not .CalledFromThisClass()
        raiseevent(This, 'DateChanged')
      endif not .CalledFromThisClass()
  endif not isnull(luValue)

Wednesday, June 04, 2008

Open a Command Window in Vista

LifeHacker today mentioned a posting by HowToGeek that's a nice time-saver if you need to open a Command window in Vista: find the directory you want the Command window to start in using Windows Explorer, then Shift-right-click and choose Open Command Window Here from the shortcut menu. I don't use a Command window often, but when I do, I usually get annoyed at having to type long paths, complete with quotes, in CD commands, so this definitely goes on the "Vista Tips" list (along with another favorite: Ctrl-Shift-Esc to quickly launch the Task Manager).

Friday, May 30, 2008

Free PDF Writer for Microsoft Office 2007

Steve Wiseman of IntelliAdmin blogged about this a while ago, but I finally got around to downloading and installing this little add-in for Microsoft Office 2007. It adds "PDF or XPS" to the Save As menu, providing a very easy (and FREE!) way to save your Office documents as PDF files.

Stacking Stuff

A stack is a data structure that's been around the computing world since the beginning, and is part of every CPU. Recently I ran into a problem where a stack was the perfect solution.

The issue was saving and restoring the state of global settings, in this case ON('ESCAPE') and SET('ESCAPE'). At the beginning of a routine where I change them (the RunReport method), I stored the current values into properties of the object (I used properties rather than local variables because another method does the actual restore), changed these settings as needed, and at the end of the routine called a method that restores the settings from the saved values. (I'm sure you've written code like this a hundred times.)

However, here's the problem I ran into:

  • RunReport calls a method of another object (PrintReport) that also saves, changes, and restores these settings. This wouldn't normally be a problem because PrintReport cleans up after itself before it exits. However ...
  • We added drilldown capabilities in Stonefield Query. While a report is being displayed, execution is still in PrintReport. If you click a link in the report, RunReport is called again to run the linked report.

See the problem? In the second call to RunReport, the settings it saves are those set by PrintReport, which overwrite the original settings it saved. So, by the time you close the reports, the code restoring the settings doesn't set them to the original values.

Since you can have a virtually unlimited number of levels of drilldowns, it made sense to implement a stack to store the settings. At the start of a routine, the code pushes the current settings onto the stack and at the end, pops them off.

As a refresher for stacks, think about a stack of plates in one of those spring-loaded plate holders you see in some restaurants. When a new plate is added to the stack, it's added at the top and the rest of the plates are pushed down. When a plate is removed, it's the one at the top of the stack (the one added most recently) and the rest of the plates move up. That's exactly what we want to happen in this case. Adding an item to a stack is called "pushing" and removing an item is called "popping".

I briefly considered using an array to hold the settings, but decided instead to use one of my favorite classes, Collection. The implementation was unbelievably simple. I subclassed Collection and added two methods: Push and Pop. Here's the code for Push:

lparameters tuValue

Here's the Pop method:

local luValue
with This
    if .Count > 0
        luValue = .Item(.Count)
        luValue = .NULL.
    endif This.Count > 0
return luValue

Using Count as the index for the item to pop and remove ensures the last pushed item is the one that's popped. Note that I decided to return NULL if the stack is popped more often than pushed (that is, Count is 0). You might decide to throw an error instead.

Here's how this class is used:

loStack = newobject('SFStack', 'SFStack.vcx')
* change the settings and do whatever
lcCurrEscape   = loStack.Pop()
lcCurrOnEscape = loStack.Pop()
on escape &lcCurrOnEscape
if lcCurrEscape = 'OFF'
    set escape off
endif This.cCurrEscape = 'OFF'

Note that items are removed from the stack in the opposite order they're added, so this code handles that when it retrieves the saved settings.

Services as Products

John Jantsch of Duct Tape Marketing blogged today about something that was essentially the reason my partner and I started Stonefield: we wanted to stop trading hours for dollars. Given that we have a limited inventory of hours, doing consulting work at an hourly rate automatically caps your revenue. Also, hours you don't spend billing (sick, vacation, etc.) are lost from inventory. Instead, we wanted to focus on products.

Of course, when we got started, we didn't have any products, so we had to pay for the development of products by doing fee-for-service consulting and development. We did that for several years while we developed product after product: Pharmacy Partner, a retail pharmacy program; Stonefield Data Dictionary, Stonefield AppMaker, Stonefield Database Toolkit, Stonefield Query, and Stonefield Reports, all tools for Visual FoxPro developers; and currently Stonefield Query, our flagship award-winning query and report writing software, which is actually a whole family of products. We haven't done any custom development projects for about eight years and couldn't be happier.

That's not to say that we do no fee-for-service work. We do a lot of consulting for Stonefield Query end-users (creating very complex reports, for example) and Stonefield Query SDK developers (helping them polish their data dictionary, develop complex scripts, or even do the entire work of creating a customized version for them). However, that really is more of a sideline than our core work.

Of course, when you have actual products for sale, that's the market you're in. One thing that's more difficult to do is what John discusses: turning a service into a product. On our Accpac consulting side (Stonefield Systems Group Inc.), we've struggled to convert services like Accpac installation, report creation, and disaster recovery into fixed-fee projects. Interestingly, the struggle hasn't been from the client side. Most clients prefer you to quote a fixed fee for work, and in fact expect it in one form or another (even if you quote an hourly rate, they still want an estimate of the total bill). No, the struggle has been our own mindset: convincing our staff this is the way to go. We've gone a long way to achieving that, but still are doing lots of work on a fee-for-service basis.

But it's worth the struggle. As I pointed out to our staff a couple of years ago, we have a strange paradox: the more experienced we become, the faster we can do a particular job, and therefore the LESS money we make if we're billing by the hour. We'd be better off giving jobs to junior consultants because although their billing rate is less, it takes them WAY longer to do a job. (Just for the record, we have no junior consultants. They've all been working with Accpac for years and are considered among the best in the business). But if we get out of the mindset of billing by the hour and instead look at the value we deliver to the client, that means our experience becomes of value. Say we charge $500 to do a job. A junior person might take five hours to do it (the equivalent of $100 per hour) but a senior consultant may be able to do it in one hour ($500 per hour). It'd be pretty hard to convince the client to pay us $500 per hour but to charge $500 to do something that will save them (or better, make them) thousands of dollars per year seems like a good deal to most people.

As I said, we're still working on this, but we are getting better and hopefully one day, all but the simplest technical support will be packaged and priced as a product.