Tuesday, September 12, 2006

Prague DevCon, Day 1

I decided to skipped Alan's keynote because I'd seen it at DevCon in Phoenix two weeks ago and wanted to go over my sessions one last time before presenting them (in keeping with Rick's suggestion to practice, practice, practice). I then wanted to go to Craig's session on Windows Communication Foundation and Windows Presentation Foundation, but jet lag got the best of me (although it was 11 am, it was 3 am by my internal clock) so I had an hour nap instead.

Lunch was a submarine sandwich and banana, and then I presented my Extending VFP with VFP session. One thing that's hard for me to get used to is the lack of feedback by European audiences. I mean no disrespect -- it's a cultural thing and I was warned by other speakers last year at the German DevCon -- but it's still hard to tell whether my ideas are sinking in or not. I agree with Dave Crozier's assessment; it was a pretty intense session, so if I do it again, I'll try to lighten up the material a bit.

I attended Craig's Object Thinking session and enjoyed it. As Dave pointed out, some of the concepts are a little controversial, especially the somewhat anti-data ones to a VFP audience. However, it's always good to challenge your viewpoints.

I then presented my Adding IntelliSense to Your Applications session. This is a somewhat esoteric session, in that it's not something every VFP developer would use, but in speaking with several attendees afterward, I know that a least a few will make use of the information.

A group of about 20 of us went to dinner at a restaurant somewhat removed from the hotel (it took about 20 minutes of walking through winding alleys, parks, streets). The food and beer were very good, although the service was very slow -- it was nearly two hours after ordering before our food finally arrived. Igor was kind enough to escort Alan, Craig, and I back to the hotel, as we never would have found it on our own.

I finished up the day catching up on email, talking to my wife, and reading a great book, The Cabinet of Curiosities by Douglas Preston and Lincoln Child (I've read several of their books and am a big fan).

Monday, September 11, 2006

Prague DevCon, Days -1 and 0

Yesterday was a very long day (actually two days). I'm speaking at DevCon in Prague this week, so I got up Saturday at 4 am Mountain Time, which is noon Central European Time (the time zone for Prague), and didn't go to bed until 10 pm Sunday. Except for about a one-hour nap on the flight, I was up for 34 hours.

The flight was uneventful (always a good thing). Igor Vit picked up Craig Berntson and I at the airport because even though we came from different cities on different airlines, we arrived at exactly the same time. After freshening up at the hotel, we had a very traditional Czech lunch of roast pork, bread dumplings, and sauerkraut.

We then met up with several other FoxPro folk (including wOOdy, his wife, Uwe, Venelina, JoKi, and Alan Griver) and took a four-hour walking tour of Prague with a very knowledgeable tour guide, Martina. Prague is beautiful. I loved the architecture of the historical buildings. There were lots of statues and fountains, interesting bridges, and, because it was a gorgeous day, tons of people everywhere. Martina explained the historical significance of the pedestrian-only Charles Bridge and several of the many statues on it. We took the Fundicular (a cross between a cable car and gondola ride) up the hillside and walked through a picturesque park to the Prague Castle. The castle is one of the largest in the world. It isn't a single building but a collection of many, including a Gothic cathedral similar in design to the one in Cologne or the Notre Dame in Paris.

Here are a couple of photos of the areas we visited:

Here's a group photo in front of the fountain in the second courtyard of the castle:

After the tour, we joined up with Hans and Gaby Lochmann and two of the local Microsoft people for dinner at a Brazilian restaurant. If you haven't been to one before, I highly recommend it. We started with a "salad" bar which, although there was no actual salad, had lots of delicious cold foods, including sushi. The waiters then brought us an interesting combination of fried bananas and fries (only Hans and I ate the bananas, so we had all we wanted). Next, the waiters brought an unending series of huge skewers of meat (pork, beef, lamb, sausage) and carved off pieces of any you wanted. You were supposed to flip a card on the table in front of you from green to red when you had enough, but the waiters ignored them and kept bringing tempting morsels anyway. Between all the food, great Czech beer, and pear schnapps in a cool tri-glass, I was completely stuffed. Great conversations with old and new friends, great food, and great beer made for a great evening.

After returning to my hotel room, I called home on my PC using Skype (thanks to Rick Schummer for introducing it to me). I love Skype: it's free to download and you can place calls to another PC on the Internet for free. In my case, I talked to my wife and son on her cell phone using a feature called Skype Out, which isn't free but is very cheap: my 10-minute call from the Czech Republic to Canada cost $0.24.

Time for bed!

Thursday, September 07, 2006

Really Busy

As you can likely tell by the scarcity of my posts lately, I'm busier than usual right now. I had planned to blog about a few things, such as the VFPX GDI+ classes and a new tool called UP2D8, but just haven't had the time. I'll get to them in future posts but first a quick catch-up.

First there was DevCon in Phoenix a couple of weeks ago. I arrived late Saturday night: one of my flights was delayed by three hours so I had a really long layover in Denver, then it took 1/2 hour for my luggage to come down, then SuperShuttle jerked me around for 1/2 hour before taking me to the hotel. The JW Marriott in Phoenix was great. The rooms weren't quite as large or luxurious as the ones in Las Vegas, but the hotel itself was much nicer. It even had a "lazy river" in the pool area.

Sunday Tamar Granor, Rick Schummer, Jeff Zinnert and I went to Sedona, about 1.5 hours north of Phoenix (or about 1 hour with Jeff driving). Rick has been there many times so was a great tour guide for us. The red rock mountains and mesas are so beautiful. My favorite part was the Slide Rock area, where you can slide in a natural water slide (very slippery and pretty cold, but lots of fun) or just jump off a cliff into a somewhat shallow (6 ft. or so) pool.

The conference itself was great as usual: excellent food, good sessions, fun hanging out with friends. The best meal of the trip: the last night at Bahama Breeze in the Desert Ridge Mall, walking distance from the hotel. The only disappointing thing was the attendance; based on a quick head-count at the keynote, I estimated about 120. However, Rick counted attendees in both rooms (yes, only two tracks!) several times and consistently found only 60 - 70 people.

Next, one of our technical support people resigned. Her husband found a job in Saskatoon (about 250 km north of Regina) so she found a new job there as well. So, my partner Mickey and I scrambled to find a replacement. We were already in the process of finding another support person, so we sped things up and did a bunch of interviews the day after I returned from Phoenix. We hired a couple of new guys and they started yesterday. They'll spend the next week with Tonia, getting up to speed on Stonefield Query and how to support it before she leaves us. She'll be greatly missed -- her work ethic, professionalism, and great customer skills were a joy -- so the new guys have big shoes to fill. I'm sure they'll do fine, though.

I had planned to take this week off: my mother-in-law died several weeks ago and my sister-in-law, brother-in-law, and our nephew came from Winnipeg to help my wife Peggy and I clean up her house so we can sell it this fall. What a huge job it's been -- it's not a very large house but she lived there for more than 40 years, so it has 40 years worth of stuff in it. Since they're only here until the end of this week and we're having a garage sale Friday and Saturday, we have some time pressure to get as much done as possible. Unfortunately, with all the things going on at work, I wasn't able to completely take the week off.

Saturday, I leave for Prague to speak at the DevCon there. (Which means I won't be here for the second day of garage sale. Dang. However will I console myself?) Igor Vit has been trying to get me to speak there for four years now, but we could never get our schedules to match up. Fortunately, it worked out this year, so I'm really looking forward to going. I have a few days of sightseeing planned so I'll blog about it when I return.

Thursday, August 17, 2006

What is it With Airlines?

I seem to have ongoing bad luck with Air Canada. It isn't always their fault: I've been stranded overnight at Pearson Airport several times in the past couple of years because of bad weather. That I can live with. It's the attitude of the airline and their employees that pisses me off.

The latest issue: we were on vacation, staying with friends in CT, when my wife Peggy found out her mother was quickly declining (she fought breast cancer for more than six years). I called Air Canada to change her flight to come home on the same flight just one day early, and they wanted $1,000, plus the change fee, of course, to do that.

Change fees I understand (well, sort of; $100 seems like a lot for a simple 2-minute conversation and database change) but the whole "we'll refund your current ticket and then sell you a new ticket at last-minute prices" thing doesn't make sense to me. I paid for a service: flying my family from point A to point B. If there are empty seats on the plane, why can't they simply change the flight from one date to another? I've also run into this when, at the last moment, a staff member got sick and couldn't attend a conference so we wanted to send someone else instead. Again, just a database change, right? Nope, it's the "refund old, buy new" policy again.

Maybe one day airlines will see their customers as people they appreciate doing business with rather than as bags of money.

Monday, August 14, 2006

Gotta Get to Phoenix

In case you missed Andy Kramek's blog on this, let me reiterate his point: Southwest Fox 2006 is a must-see conference. The 2004 and 2005 editions were the best conferences in North America, and I'm sure 2006 will easily exceed everyone's expectations. It's organized around a weekend to minimize missing billable hours and feels more like a reunion of friends than a big formal conference. Come hang out with the speakers and other attendees at meals or over drinks and get all your VFP questions answered.


Friday, July 14, 2006

XZip: Free Zipping Utility

In a message on the Universal Thread, Frank Cazabon pointed out a free zipping utility named XZip. I downloaded and installed it today and already like it. It's a 138K DLL COM object, so it must be registered when you deploy it.

It's really easy to use: simply instantiate XStandard.Zip and call the Pack method to add a file or folder to a ZIP file (the file is created if it doesn't exist) or Unpack to extract a file or folder. There are also methods to delete or move files or folders, and one that returns a collection of items in a ZIP file.

Here's some sample code:

#define tFolder 1
#define tFile 2

loZIP = createobject('XStandard.Zip')
loZIP.Pack('SomeFile.txt', 'MyZip.zip')
loItems = loZIP.Contents('MyZip.zip')
for each loItem in loItems
? 'Name:', loItem.Name
? 'Date:', loItem.Date
? 'Path:', loItem.Path
? 'Size:', loItem.Size
? 'Type: ' + iif(loItem.Type = tFolder, 'Folder', 'File')
next

Vacation Time

Today's my last day at work for three weeks. I haven't taken a three-week vacation in four years; it's always been a week here or ten days there. I'm really looking forward to this; I'm mentally pretty tired right now and need a break, plus we have tons of fun planned.

We (my wife Peggy, son Nick, and I) fly to New York City and stay with friends in Connecticut for a couple of days. We then all drive to a beach house in NC, with an overnight stay in Washington, DC, which we've never visited before. After a week swimming, golfing, and eating and drinking too much, we drive back to CT, visiting with Tamar Granor and her husband on the way. We plan to do some sightseeing in NYC for a few days, then head home and take a few days to decompress from our vacation.

Another interesting aspect of this vacation is it's the first one since the last three-weeker that I'm not taking my laptop. I made a deal with my friend Tracey from Dallas, who will also be at the beach house with her family (three families altogether) that I wouldn't bring mine if she doesn't bring hers. She even more rabidly addicted to email than I am, so it'll be harder on her than it will on me. We'll see if she actually arrives sans computer or not, given the temptation of high-speed Internet access in the house.

What started as three friends (Betsy, Tracey, and I) who met in graduate school in Dallas in 1980 has expanded to three families who vacation together almost every year. The great thing is that you could take any two people out of the group, including kids, and they'd have a great time together. That's pretty rare in my experience. Like the saying goes, old friends are the best friends.

Friday, July 07, 2006

SQLXMLBulkLoad Rocks!

For reasons that will become obvious in September (sly grin), I've been working a lot lately on uploading data to SQL Server 2005. There are a variety of mechanisms you can use to do this, but all of them have their shortcomings under certain conditions. For example, using a series of INSERT statements is slow while bulk insert doesn't work with memo fields. While looking for something else in SQL Server Books Online, I happened across a topic on bulk XML loading. After playing with it for a while, it seemed pretty easy to do, so I did some testing on a relatively large file (365,741 records) containing memo fields (which means I couldn't use bulk insert). Using other mechanisms, it took more than two hours to load the data into a SQL Server table. Using bulk XML load, it took 11 minutes. That's a 90% improvement in speed. I love making things faster!

Using bulk XML load is easy: you instantiate SQLXMLBulkLoad.SQLXMLBulkload.4.0, set its ConnectionString property to an OLE DB connection string, set some other properties appropriately (for example, KeepNulls to .T. to insert null rather than default values for missing column values), and call Execute, passing it the name of a schema file and the name of the XML data file. Execute throws an error if there's something wrong with either file, and import errors are logged to a file whose name is in the ErrorLogFile property.

There's one slight wrinkle in working with VFP data: while the CURSORTOXML() function can create a schema file, it needs to be tweaked slightly to work with bulk XML load. Also, DateTime fields have to be flagged with the sql:datatype="dateTime" attribute (the latter was documented, the former took some trial and error to resolve).

Here's a generic program that will load the data from an open VFP cursor into an existing SQL Server table. You can download it from http://www.stonefield.com/articles/other/bulkxmlload.zip.

*==============================================================================
* Function: BulkXMLLoad
* Purpose: Performs a SQL Server bulk XML load
* Author: Doug Hennig
* Last revision: 07/06/2006
* Parameters: tcAlias - the alias of the cursor to export
* tcTable - the name of the table to import into
* tcDatabase - the database the table belongs to
* tcServer - the SQL Server name
* tcUserName - the user name for the connection (optional:
* if it isn't specified, Windows Integrated Security is
* used)
* tcPassword - the password for the connection (optional:
* if it isn't specified, Windows Integrated Security is
* used)
* Returns: an empty string if the bulk load succeeded or the text of
* an error message if it failed
* Environment in: the alias specified in tcAlias must be open
* the specified table and database must exist
* the specified server must be accessible
* there must be enough disk space for the XML files
* Environment out: if an empty string is returned, the data was imported into
* the specified table
*==============================================================================


lparameters tcAlias, ;
tcTable, ;
tcDatabase, ;
tcServer, ;
tcUserName, ;
tcPassword
local lnSelect, ;
lcSchema, ;
lcData, ;
lcReturn, ;
loException as Exception, ;
lcXSD, ;
loBulkLoad


* Create the XML data and schema files.


lnSelect = select()
select (tcAlias)
lcSchema = forceext(tcTable, 'xsd')
lcData = forceext(tcTable, 'xml')
try
cursortoxml(alias(), lcData, 1, 512 + 8, 0, lcSchema)
lcReturn = ''
catch to loException
lcReturn = loException.Message
endtry


* Convert the XSD into a format acceptable by SQL Server. Add the SQL
* namespace, convert the start and end tags to ,
* use the sql:datatype attribute for DateTime fields, and specify the table
* imported into with the sql:relation attribute.


if empty(lcReturn)
lcXSD = filetostr(lcSchema)
lcXSD = strtran(lcXSD, ':xml-msdata">', ;
':xml-msdata" xmlns:sql="urn:schemas-microsoft-com:mapping-schema">')
lcXSD = strtran(lcXSD, 'IsDataSet="true">', ;
'IsDataSet="true" sql:is-constant="1">')
lcXSD = strtran(lcXSD, '<xsd:choice maxOccurs="unbounded">', ;
'<xsd:sequence>')
lcXSD = strtran(lcXSD, '</xsd:choice>', ;
'</xsd:sequence>')
lcXSD = strtran(lcXSD, 'type="xsd:dateTime"', ;
'type="xsd:dateTime" sql:datatype="dateTime"')
lcXSD = strtran(lcXSD, 'minOccurs="0"', ;
'sql:relation="' + lower(tcTable) + '" minOccurs="0"')
strtofile(lcXSD, lcSchema)


* Instantiate the SQLXMLBulkLoad object, set its ConnectionString and other
* properties, and call Execute to perform the bulk import.


try
loBulkLoad = createobject('SQLXMLBulkLoad.SQLXMLBulkload.4.0')
lcConnString = 'Provider=SQLOLEDB.1;Initial Catalog=' + tcDatabase + ;
';Data Source=' + tcServer + ';Persist Security Info=False;'
if empty(tcUserName)
lcConnString = lcConnString + 'Integrated Security=SSPI'
else
lcConnString = lcConnString + 'User ID=' + tcUserName + ;
';Password=' + tcPassword
endif empty(tcUserName)
loBulkLoad.ConnectionString = lcConnString
*** Can set the ErrorLogFile property to the name of a file to write import
*** errors to
loBulkLoad.KeepNulls = .T.
loBulkLoad.Execute(lcSchema, lcData)
lcReturn = ''
catch to loException
lcReturn = loException.Message
endtry


* Clean up.


erase (lcSchema)
erase (lcData)
endif empty(lcReturn)
select (lnSelect)
return lcReturn

Wednesday, June 14, 2006

Updated SFTreeView

I wrote a two-part article in the July and August issues of FoxTalk called "The Mother of all TreeViews" that presented a class library providing all the features you (or at least I) would ever need in a TreeView, including automatically dealing with the twips coordinate system used by the control, handling drag and drop, only loading parent nodes at startup, saving and restoring expanded and selected nodes, and so forth.

Andrew Nickless emailed me today about a bug: navigating the TreeView using the up and down arrows didn't cause the sample form that accompanied the article to refresh and show the properties for the selected node. Fortunately, it was easy to fix:

1. In TreeNodeClick, change the IF statement as shown:

*** if isnull(.oTree.SelectedItem) or toNode.Key <> .oTree.SelectedItem.Key
if isnull(.oTree.SelectedItem) or not toNode.Key == .cCurrentNodeKey

2. Add a new cCurrentNodeKey property

3. Add this line to SelectNode right after the assignment to cCurrentNodeID:

.cCurrentNodeKey  = loNode.Key

However, while I was looking into this, I decided to delve into another weird behavior that had bugged me for a while: sometimes clicking on the + to expand a parent node showed the placeholder "Loading..." node rather the actual children. The reason is that the Expand method of the TreeView didn't fire, and that method was responsible for removing the placeholder node and adding the child nodes. The weird thing is that it wasn't consistent; I could only make it happen about 25% of the time, if that. And given that I haven't seen that happen in other applications using a TreeView, it was clearly something I'd done in my class.

Long story short, it turned out (by trial and error, sadly) that code I had in the MouseMove method seemed to be the culprit. I say "seemed" because after removing it, I couldn't reproduce the behavior, but given that it didn't always happen, and of course it never happened when tracing the code, it's darned hard to confirm that it's gone for good. MouseMove called TreeMouseMove (thanks to Steve Black for drilling "events call methods" into my brain), which didn't do anything; I put it there in case I wanted to handle that in a subclass. Turns out I never have, so removing that behavior was no big deal.

Monday, June 05, 2006

DevCon Discount

Advisor Media is offering a $100 discount for Microsoft Visual FoxPro DevCon. Simply write "Doug discount" in the comment field of the registration form to qualify.

This will be my 16th DevCon as an attendee (I only missed the first one in Toledo in 1989), 13th as an exhibitor (1993 was the first), and my 10th as a speaker (1997 was the first). As far as I know, I'll be the only person to have attended 16 consecutive DevCons. Anyone know of someone who's attended them all?

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