Thoughts on software development in general, projects I'm working on, and anything else I feel like writing about.
Friday, December 21, 2007
Code Signing Your VFP EXEs
I've been using code signing for most of this year. We actually not only sign our application's EXE but also the installer so the user doesn't get the "unknown publisher" dialog when they install it.
Thursday, December 13, 2007
Sorting in Natural Order
*==============================================================================
* Function: NaturalSort
* Purpose: Sorts an array in natural rather than ASCII order
* Author: Doug Hennig
* Last Revision: 12/13/2007
* Parameters: taArray - the array to sort (passed by reference)
* tnColumn - the column to sort on (optional: if it isn't
* specified, column 1 is used)
* tlDescending - .T. to sort in descending order; if .F. or
* not specified, ascending order is used
* Returns: .T. if it succeeded or .F. (and an error is raised) if
* invalid parameters are passed or the specified column
* doesn't contain a homogeneous data type
* Environment in: none
* Environment out: none
*==============================================================================
lparameters taArray, ;
tnColumn, ;
tlDescending
local lnColumn, ;
lnRows, ;
lnCols, ;
lnOrder, ;
laArray[1], ;
lnI, ;
lcKey, ;
llInNumeric, ;
lcString, ;
lnJ, ;
lcChar, ;
llNumeric, ;
lcNumeric, ;
laClone[1], ;
lnIndex
* Define some constants.
#define cnLENGTH 20
&& the length to pad numeric sections to
#define cnERR_ARGUMENT_INVALID 11
&& Function argument value, type, or count is invalid
#define cnERR_DATA_TYPE_MISMATCH 9
&& Data type mismatch
* Ensure taArray is an array and tlDescending is logical if it's specified.
if type('taArray', 1) <> 'A' or ;
(pcount() = 3 and vartype(tlDescending) <> 'L')
error cnERR_ARGUMENT_INVALID
return .F.
endif type('taArray', 1) <> 'A' ...
* If the column to sort on wasn't specified, assume 1.
lnColumn = iif(pcount() = 2, tnColumn, 1)
* Figure out the size of the source array.
lnRows = alen(taArray, 1)
lnCols = alen(taArray, 2)
* Ensure the column to sort on is valid.
if vartype(lnColumn) <> 'N' or not between(lnColumn, 1, max(lnCols, 1))
error cnERR_ARGUMENT_INVALID
return .F.
endif vartype(lnColumn) <> 'N' ...
* Figure out the order flag for ASORT().
lnOrder = iif(tlDescending, 1, 0)
* Get the data type of the first key value. If it isn't character, we don't
* have to do anything fancy; ASORT() will take care of it for us.
if vartype(taArray[1, lnColumn]) = 'C'
* Create an array we'll sort on.
dimension laArray[lnRows, 2]
* Go through each element we're sorting on.
for lnI = 1 to lnRows
lcKey = taArray[lnI, lnColumn]
* Bug out if the data type is different.
if vartype(lcKey) <> 'C'
error cnERR_DATA_TYPE_MISMATCH
return .F.
endif vartype(lcKey) <> 'C'
* Create a key that will sort properly by looking for numeric sections and
* left-padding them with zeros.
llInNumeric = .F.
lcString = ''
for lnJ = 1 to len(lcKey)
lcChar = substr(lcKey, lnJ, 1)
llNumeric = isdigit(lcChar) or ;
(lcChar = '.' and isdigit(substr(lcKey, lnJ + 1, 1)))
do case
case llNumeric and llInNumeric
&& if we have a digit and we're already in a numeric
&& section, add to the numeric part
lcNumeric = lcNumeric + lcChar
case llNumeric
&& if we have a digit and we're not in a numeric
&& section, flag that we are in such a section and add to
&& the numeric part
llInNumeric = .T.
lcNumeric = lcChar
case llInNumeric
&& we don't have a digit and we were in a numeric section
&& so pad the section and add it to our string
llInNumeric = .F.
lcString = lcString + padl(lcNumeric, cnLENGTH, '0') + ;
lcChar
otherwise
lcString = lcString + lcChar
endcase
next lnJ
* Finish the string if we were still processing a numeric section.
if llInNumeric
lcString = lcString + padl(lcNumeric, cnLENGTH, '0')
endif llInNumeric
* Store the new key and the original index in our sort array.
laArray[lnI, 1] = lcString
laArray[lnI, 2] = lnI
next lnI
* Now sort the array and reorder the values in the source array by cloning it
* and copying the values from the each row in the clone to the new row in the
* source array.
asort(laArray, 1, -1, lnOrder, 1)
acopy(taArray, laClone)
for lnI = 1 to lnRows
lnIndex = laArray[lnI, 2]
if lnCols > 0
for lnJ = 1 to lnCols
taArray[lnI, lnJ] = laClone[lnIndex, lnJ]
next lnJ
else
taArray[lnI] = laClone[lnIndex]
endif lnCols > 0
next lnI
* Just use ASORT to do the job.
else
asort(taArray, lnColumn, -1, lnOrder, 0)
endif vartype(taArray[1, lnColumn]) = 'C'
return .T.
Wednesday, December 05, 2007
Where Should Data go in Vista?
"Wait a minute," you're probably thinking. "That's (1) bad practice and (2) not allowed." Although I agree it's not an ideal location, it's not a completely bad practice. The reason for the program folder being read-only is to prevent malware from attacking your EXE and DLL files. Putting only data files into a Data subdirectory doesn't open any security holes because no executables go there.
As for not being allowed, that's true by default. However, you can easily change permissions on the folder when your installer creates it. In Inno Setup, simply create your directory like this:
[Dirs]This gives all users the ability to write to the folder.
Name: "{app}\Data"; Permissions: everyone-modify
I don't store much in Data, certainly not the data files my application uses. The main thing stored there is an INI file that contains a setting specifying the location of the data files. When the user starts the app for the first time, a dialog prompts them for the data file location and writes the response to the INI file (which it can do because the INI file is in the writable Data folder). Our app looks in a consistent writable location for the INI file, making the code easy.
Monday, November 26, 2007
Edit Property/Method Dialog Replacement Available
In case you're not familiar with this project, its purpose is to replace the native VFP New and Edit Property/Method dialogs with dialogs that have a lot more functionality. The New dialog has the following features:
- It automatically updates the _MemberData property (adding that property if necessary) so the case entered for the new member is used (even for access and assign methods if they're created as well) and the member is displayed on the Favorites tab if that option is turned on in the dialog.
- It's non-modal. That means you can keep it open, add some properties or methods, switch to other things, come back, and add some more members.
- How many times have you accidentally selected the wrong dialog, entered the name and description, only to realize your mistake and have to close the dialog and start all over? With the replacement dialog, you can switch between Method and Property without closing the dialog.
- It's dockable: try tab-docking it with Properties window.
- It's resizable and persists its size and position to your resource (FOXUSER) file.
- It has an Add & Close button to perform both tasks with one click.
- The default value for a property is automatically set to a value based on the data type of the property if you use Hungarian names. For example, lTest would be logical, so the default value is .F. For nTest, the default is 0.
- For logical properties (those with a default value of .T. or .F.), turning on the Use Toggle Editor option installs a property editor that allows you to double-click the property in the Properties window to toggle its value. This property editor is included inside NewPropertyDialog.APP.
- It hides rather than disables non-applicable controls. Since this one dialog is used for both New Property and New Method for both the Class and Form Designers, some options may not be applicable for a given instance.
- It disallows invalid names when you enter them rather than when you click on Add or Add & Close.
- The Add buttons are only enabled if a name is entered.
- It automatically selects the chosen member when you right-click a member in the Properties window and choose Edit Property/Method from the shortcut menu.
- While it's easy to add a member to the Favorites tab of the Properties window (right-click and choose Add to Favorites), there isn't an easy way to remove it from Favorites. The replacement dialog has a Favorites button, making it easy to add or remove a member from Favorites.
- If you delete a member that has associated member data, the member data is automatically removed for that member.
- It automatically updates the member data when you rename a member so it respects the case of the member's name.
- Like the New Property/Method dialog, it's non-modal, dockable, resizable and persistent, and allows changing a property to a method and vice versa.
- Selecting the description of a member and pressing Ctrl+C to copy it causes the Apply button to become enabled in the native dialog, requiring you to either choose Apply or Cancel to close the dialog (pressing Esc brings up a "save changes" dialog). The replacement dialog does not have this behavior.
- The New button launches the replacement New Property/Method dialog.
- The Zoom window (right-click on the Properties window for a property and choose Zoom) is resizable.
To install the dialog, download the code and run both of the APP files. This will install the necessary MENUHIT and MENUCONTEXT records into your IntelliSense table to enable the new dialogs. Full source code is provided so feel free to check out how these dialogs work. Please post any suggestions for improvements to the VFPX site.
Thursday, November 22, 2007
Fixing the "Reinstall Your App" Problem
This problem doesn't occur if you use Inno Setup instead of Windows Installer-based tools such as InstallShield. In the mean time, the workaround is to find the Registry entry for the ActiveX control and fix its InprocServer32 entry. This is easier said than done, because you don't necessarily know which control to fix; you need to find entries for all the controls your app uses. Here's how you do this:
- Launch RegEdit.
- Find "treeview" or whatever control you're looking for. The entry you want is in HKEY_CLASSES_ROOT\CLSID and it'll be a GUID. For example, for the TreeView control version 6, the entry is HKEY_CLASSES_ROOT\CLSID\{C74190B6-8589-11D1-B16A-00C0F0283628}.
- Look in InprocServer32 for the InprocServer32 key. It's supposed to contain the name and path for the OCX, but notice it contains gibberish; that's the Windows Installer encrypted key that's causing the problem.
- Edit the value and replace it with the name and path for the OCX; for the TreeView control, it should be C:\Windows\System32\MSComCtl32.OCX (the "(default)" value contains this, so you can copy it from there and paste it into InprocServer32).
- Repeat for each of the controls your app uses.
Monday, November 19, 2007
Update for Sedna Upsizing Wizard
The first change simply involved adding a new record to TypeMap.DBF, the table containing the type mappings. Set LocalType to "M" (for Memo), FullLocal to "memo", RemoteType to "varchar(max)", and Server to "SQL Server". Leave the logical fields as .F., especially VarLength, which determines whether the user is prompted for a field length.
The second was a little more work. First, BulkXMLLoad.PRG needs a change to support this because it used a hard-coded value. Change the statement setting ltBlank to:
ltBlank = iif(vartype(ttBlank) $ 'TD', ttBlank, SQL_SERVER_EMPTY_DATE_Y2K)and add the following statement after the LOCAL statement:
#include Include\AllDefs.HSecond, JimExport (interesting method name!) in WizUsz.PRG also used a hard-coded value, so change the assignment to lcEmptyDateValue in the first CASE statement to:
lcEmptyDateValue = "IIF(EMPTY(" + ALLT(lcCursorName)+'.'+ALLT(aTblFields[ii,1])+ "), " + ;Finally, to change the actual date, change these two constants in AllDefs.H to the desired values:
transform(This.BlankDateValue) + ","
#DEFINE SQL_SERVER_EMPTY_DATE_Y2K {^1900-01-01}Rebuild UpsizingWizard.APP and you're ready to go.
#DEFINE SQL_SERVER_EMPTY_DATE_CHAR "01/01/1900"
Tuesday, November 06, 2007
Cathy Blogs!
Friday, November 02, 2007
My Newest Time Waster
Heading for Germany and Holland
One of the interesting things about German is that it seems to be relatively easy to pick up at least a few words. There are lots of similarities between words in German, English, French (which I took in high school 75 years ago), and Dutch (which my wife speaks fluently so I've picked up a very small amount), enough so that sometimes I can at least get the gist of a conversation if not the details. Plus, in technical sessions, there seem to be actual English words in almost every sentence, and of course, code is code.
After DevCon, Rick Schummer and I head for Holland to speak at a one-day VFP conference hosted by Software Developer Network. The next day, we're going to do some sightseeing in Amsterdam, which I visited five years ago and have been wanting to go back to. Rick's going to be in shock when he sees the Red Light District, which we pretty much can't avoid since it's right by the central train station.
Thursday, November 01, 2007
Duct Tape Marketing
I was so excited about the ideas I read in this book that I took several pages of notes. Mickey has been ordering copies of this book for everyone in our company, and we're even considering giving it to clients. It's now on my "requirted reading" list.
Friday, October 26, 2007
Southwest Fox 2007 Day 4
Once again, I missed a session. Bo Durban and I inadvertently did some pair programming. He started showing me some really cool things his product, Moxie Report Objects, does, then mentioned another cool feature he was thinking of adding: a find function in the Report Designer that would find any text in a report expression, group expression, print when expression, etc. He started working on it, I made some suggestions, he tweaked something, and before you knew it, we were pair programming. It was a lot of fun and Bo ended up creating a working version of the code in no time. Once again, it's the networking that's the best part of conferences.
After presenting the second instance of my vertical app development session, I squeezed myself into what little space was left in Cathy Pountney's Outfox the VFP Report Writer session. I'd heard great comments from other attendees and Cathy didn't disappoint. Her session gets my vote for best in show. She showed how to take control of the printing process, giving you the ability to create books (with their out-of-order pagination), offset the top and left edges of a print job, printing N-up pages per sheet, accessing the properties dialog for a printer, and many other things. The best part is that all of the code is on the conference CD, so I can start implementing her ideas right away.
After a quick break to rearrange the four break-out rooms into one large one, we started the closing session. We reminded everyone to turn in their evaluation forms; those that did were entered into a drawing for a free registration to next year's conference. That's how important evals are to us and the speakers. Rick thanked speakers, sponsors and exhibitors, Arizona Golf Resort staff, Rick Borup (for creating the RSS feed for http://www.swfox.net/; we somehow missed thanking him in the keynote), and attendees. We then gave out thousands of dollars in software, books, and other goodies donated by sponsors. We announced the dates for Southwest Fox 2008: October 16 - 19, at a location to be announced later (although it's fairly likely we'll be back at the Arizona Golf Resort). Dave Aring created a cool animated logo just for this (for some reason, it's not animated here):
We listing some resources, such as the Universal Thread, Foxite, Foxforum, user groups, the Fox Wiki, blogs, VFPX, and FoxCentral. We finished up asking for photos and testimonials and wishing everyone a safe trip home.
Rick, Tamar, and I had a lot of people come up to us during and after the conference thanking us for taking up the reins of Southwest Fox and for putting on such a great conference. The buzz at the conference was amazing. Everyone was so jazzed about VFP that they were almost bouncing off the walls. I personally felt totally energized and full of new ideas for my applications, and I'm sure almost everyone else felt the same way.
After cleaning up the conference area and packing up the few things we had to take home, about thirty of us went to the hotel restaurant for lunch. My last meal at the hotel didn't disappoint either; the burger was one of the best I've ever had.
Tamar, Rick, Marshal, Therese, and I then met with the hotel staff for a post-conference debriefing. We told them about the few complaints we had and those we heard from attendees, but they were very picky things; nothing was even close to being a major issue. The hotel staff, especially those in the conference and catering areas, were incredible. When we asked for something, it was cheerfully done in minutes. Mark, Brett, Mari, Sharon, and their staff bent over backwards for us and were always pleasant and fun to work with. We let them know how pleased we were with the facilities and service.
We then had a post-conference meeting of our own, going over things we need to ensure we do next year (such as signage for the registration table and providing coffee and beverages during breaks). We even started planning next year's conference, so save the date!
Rick and Therese headed to Sedona for a few days of much-deserved R & R. About twenty of those still around went to My Big Fat Greek Restaurant for dinner and had some really good Greek food. wOOdy and Christof ordered a Meat Lover's Platter for two that looked like it was really for four; I wanted to see those guys eat that much food, but they were done after less than half the plate.
Thanks again to everyone who helped make this a great conference. We'll see you again next year!
Southwest Fox 2007 Day 3
I skipped the next session to catch up on email and forum and blog reading (gotta love free wireless access in the conference area!). I then did the second instance of my VFP Vista session. Once again the room was full, so that means that more than half of the attendees saw this session.
Lunch was again served outside in the courtyard. It was Italian day, so we had antipasto and canneloni. Once again, the food was great. I sat outside and had a lively discussion of health care in Canada vs. the US. The attendee who asked about Canadian health care wished he hadn't; it's one of my hot buttons, and I guess I got a little zealous -- sorry about that! It was pretty warm and I started sweating, so I headed back into the air conditioned comfort of the conference center.
I missed the first post-lunch session because I was chatting with some other attendees. As many people will tell you, networking is one of the best things about going to a conference. I also chatted with several of the vendors. Chick Bornheim from Micromega told me that he had a better response at Southwest Fox 2007 than all of the previous three years combined. Although I really wanted to see Toni Feltman's DBI controls session, I ended up missing that too while talking to folks.
The last session of the day was our vendor session for Stonefield Query and we once again had a lot of people show up, this time without any free beer. I love showing this product and watching the smiles on the faces of developers when they realize how much it'll help their users and themselves in providing a customized reporting solution for their application.
The speakers headed to Rustler's Rooste for a dinner to show our appreciation for all the effort they put into creating sessions and white papers, practicing their sessions, and presenting them to the attendees. The restaurant is massive and has a great view from its location on the side of one of the many mountains in Phoenix. I was a little disappointed in the food, but the company was great. A magician showed off some pretty cool tricks after dinner; we talked about one of them for quite a while afterward.
Those interested in indoor kart racing went to the F1 Race Factory to reprise the races we had at last year's conference. Cathy Pountney narrowed edged out Rick Schummer last year (I was a close third), so there was some serious trash talking in the days preceding Saturday night. Cathy even stooped so low as to bring last year's score card to show off. Unfortunately, we hadn't made a reservation, so when we got there, we were told it would be 11:30 p.m. at the earliest before we could race, so we headed back to the hotel. I hung out in the lobby for a while, but a week of getting up at 5:00 a.m. was taking its toll, so I headed to bed.
Here's a picture of attendees (Steve Sawyer, Sue Cunningham, and Brenda Erickson in the foreground) chatting in one of the courtyards between sessions:
Southwest Fox 2007 Day 2
I then did my Best Practices for Vertical Application Development session. The room was jammed and I got lots of great feedback from attendees. This is a different kind of session for me; it's a business track session so I discuss facets of vertical development such as activation and licensing, support policies, update mechanisms, and so on rather than the usually code-intensive sessions I do. In fact, I didn't show any code at all because for some reason, although my laptop worked on the projector the night before in the keynote, for some reason it couldn't see the projector at all in this session. Fortunately, Scott Malinowski had downloaded the conference materials before the conference started and kindly offered to let me use his system. Restarting my computer fixed the problem for my next session, so it wasn't a problem again. By the way, this was "the other" Scott Malinowski. It's really interesting that there are two Scott Malinowskis (the other one is from Phoenix but was out of town during the conference) in a community as small as ours.
I attended Craig's Power of Regular Expressions session. Power is right. Although I've played with regular expressions a bit, Craig did a great job of going over the various symbols and their meanings (he claimed the language was invented by aliens, given the arcane syntax). He had tons of examples, and even gave pop quizzes to the audience, showing an expression and some text and asking what the matches would be. I saw a lot of light bulbs come on for people in this great session.
Lunch was served in the courtyard. Like almost every day we were in Phoenix, the weather was gorgeous, so many people ate at the tables outside. Lunch was build-your-own sandwiches and very tasty.
After lunch, I attended Steve Sawyer's Marketing for Custom Software Services session. It was great seeing Steve again, who sort of dropped out of the community for a few years. He talked about why he felt his former business didn't succeed and what he'd do different if he could do it over again. Although the first half of the session seemed a little theoretical, Steve made some great practical suggestions about meeting new clients. He finished early, so a general discussion on marketing ideas broke out, with me, Steve Bodnar, Jeff Zinnert, and Russ Swall all contributing our viewpoints.
I then went to Christof Wollenhaupt's On the Dark Side of FoxPro session. Christof once again amazed with his incredibly deep knowledge of VFP internals, discussing how variables and objects are stored, how cache memory is used, and how Rushmore does its magic. Although this isn't a session where you walk out with ideas to implement, it does give a glimpse under the hood and lets you understand why some limitations in VFP are the way they are.
I next presented my Developing VFP Applications for Windows Vista session. This is a fun session to do, because I show all of the potential gotchas Vista has for VFP developers and workarounds for all of them. I also show how to take advantage of some of the new features in Vista, including Windows Desktop Search (which is available as an OLE DB provider, making it easy to use a CursorAdapter to query your system), new dialogs (using the Vista Toolkit in Sedna, created by Craig Boyd -- there, I mentioned your name one last time for good measure, Craig), XPS documents, and more. It was obvious from some of the questions that several people had struggled with some of these issues, so it was timely. Tracy Pearson also contributed with a couple of tips.
The last session ended at 5:30 and vendor sessions started at 7:00, so there wasn't a lot of time for dinner. We had told the conference staff on Wednesday that since there was only an hour and a half, most people would likely eat at the hotel restaurant so they'd better prepare for 180 people. They came up with a great plan: a prime rib buffet for only $16.50. It was laid out very nicely and they had enough staff available that everyone had a great meal and were back at the conference center on time.
I was pleasantly surprised at how many people showed up for vendor sessions. Although some rooms were a little light, most of the sessions had about 20 people, meaning almost half of the attendees stayed late into the night seeing what features the vendor's products had. Stonefield had a Stonefield Query developer meeting rather than a vendor session (our vendor session was scheduled for Saturday afternoon). I'm not sure if it was the free beer or our product, but we had about 30 people in attendance, which was great. We went over the new features we added in our latest release, wowing the audience with things such as drilldown reports and charting. We then went over our proposed new features for our next release and asked for feedback about which they really wanted. We also got some great suggestions for things we hadn't thought of. It was also nice that I could introduce Jeff Zinnert, Trevor Mansuy, and Chris Wolf from our company so they and customers could finally meet in person.
Since there was some wine left from our meeting, we took the extra bottles and headed to the hotel bar. We were hassled a couple of times about bringing in outside liquor until we told them that we'd purchased it from them. After a couple of hours blabbing, I was ready to collapse so I called it a night.
Here's a picture of the trade show area in the hallway outside the session rooms:
Southwest Fox 2007 Day 1
The hotel and conference buildings are beautiful. Since it's laid out in a resort style, every hotel room has an outside entrance, meaning we get to spend more time outdoors instead of never leaving the hotel building. The hotel intertwines with the golf course, so some of the room buildings are a bit of a hike from the main building and the conference center (mine was about a five minute walk) but a little exercise, especially after sitting all day in sessions, is a good thing. For those who didn't feel like walking, the hotel staff would provide "limo" service using golf carts. The conference buildings are nicely sized for a conference like Southwest Fox, with a couple of courtyards right outside, the perfect place to congregate between sessions for networking. Meals and snacks were served outside, although you could also eat in one of the buildings if it was too hot or windy.
Wednesday was the big day: meeting with Arizona Golf Resort staff, assembling conference binders and bags, helping staff set up the session rooms, setting up the wireless routers, moving everything to the conference center, and so on.
Here are Marshal (left) and Rick after we assembled the binders:
Here are Therese (left) and Tamar surrounded by binders:
Thursday was registration and pre-con day. We manned the registration table starting just before 7 a.m. because attendees taking Andy Kramek's pre-conference session needed to register before his session started at 8. Since we're anal-retentive geeks, we pretty quickly worked out a mechanism to optimize registration (and even documented the process!) so lineups were a minimum. The conference bag was jammed full of goodies: binder, conference T-shirt, drink koozie (courtesy of Servoy, who also provided enough bags for most of the attendees), demo CDs, brochures, wireless Internet information, note pad, pen (courtesy of Tech Smith), CodePlex T-shirt (while they lasted, courtesy Alan Griver), and squishy fun ball. Although we had created a schedule of who would be at the table when, we pretty much all hung around there all day because it was the best place to greet attendees, meet old friends, and be introduced to new people.
Based on the comments I heard from attendees, the pre-cons went very well. We provided lunch to those who registered for two pre-cons. Alan Stevens, one of our talented new speakers, pointed out that we didn't have any coffee put out for the pre-cons, and in fact told his attendees to remind us, which they did several times. I personally delivered a cup of coffee to Alan in the middle of his session, but he took it as a kind gesture rather than the prank that was intended.
Late afternoon, vendors started setting up their tables and booths. We had nine exhibitors this year, three times as many as last year. Right after the last pre-con session ended, we held a short meeting to go over logistics with all the speakers.
Just before the keynote presentation started, Ken Levy surprised us by popping in. He was in town visiting family and decided to stay for the keynote.
The keynote started at 7:00. Rick thanked all the attendees for coming and mentioned these statistics:
- Attendees came from 7 countries and 35 U.S. states (when he pointed out that he'd forgotten to count the number of Canadian provinces, I said there were attendees from at least 15 provinces).
- There were 148 attendees, not counting speakers and vendors. (We actually got three more registrations the next day, for a total of 151, more than double last year's attendance.)
- With 16 speakers, 12 exhibitors, and 2 staff (Marshal and Therese), there were 178 people at the conference in total (181 by the next morning).
Rick went on to thank other people who helped us, including Bob Kocher (who previously ran the conference and graciously helped us with ideas and suppliers), Dave Aring (who created the logos and Kokopelli application), Mike and Toni Feltman (for their VFE offer to all attendees), other conference organizers (Whil Hentzen, Rainer Becker, Kevin Cully, Igor Vit, and others), DBI (for creating the conference CDs), Craig Boyd (who hosts http://www.swfox.net/), bloggers and podcasters (who helped get the word out), speakers (who were all introduced by name), Andy Kramek and Marcia Akins (for creating a "how to speak good" video), Southwest Fox staff (Marshal and Therese), and our sponsors.
Tamar went over conference logistics: what's in the binder, where the session rooms are, using Kokopelli, where meals are, and so on. I then discussed the trade show, going over the hours and urging attendees to visit the booths and talk to the vendors about their wares, and the two developer meeting at the conference, once for Visual MaxFrame Professional and one for Stonefield Query.
Rick spent a few minutes discussing VFP 9 SP2, including what's in it and the fact that there are two releases of it (which he called SP2 and SP2A for fun). He then talked about Sedna: what it is, what components are in it, and when it may be released.
I then had the honor of announcing the recipient of the 2007 FoxPro Community Lifetime Achievement Award. During all of the conference planning, Rick and I told Tamar that the award was for someone else. I did up PowerPoint slides with that person's biography, Rick (who designed the physical award) created a design with that person's name, and we even practiced the keynote earlier that day with the other person as the recipient. So, when I announced that Tamar was the recipient, she jumped liked she'd touched a 20,000 volt power line. I went over the things Tamar has done in her career and why she so rightly deserved the award. I was especially pleased that her husband Marshal could be in the room to watch the announcement. Rick presented her with the award and then, to my surprise, also presented awards to the previous recipients--Whil Hentzen, Rick Strahl, and I--who hadn't actually received anything physical before. It was great that all of the recipients were actually at the conference.
While Christof Wollenhaupt was setting up for the next part of the keynote, we did some drawings for raffle prizes, including several Hentzenwerke books. Christof then amazed the audience with his demo of Guineu, a .NET runtime for VFP applications. He started by telling is that FoxPro was dead and it was really a shame that you can't run VFP applications on Windows Mobile devices while he proceded to do exactly that. He then showed the same VFP app running as a .NET WinForm application and a browser application, all the while saying it was too bad that it wasn't possible to do that. It was a great presentation that really fired up the crowd!
We gave our some more prizes while Toni Feltman set up for her presentation on .NET Extender for VFP and VFP Compiler for .NET from eTecnologia.net. Although it has a similar purpose as Guineu (running VFP applications in .NET), it has a completely different technology. Toni demoed some of the current capabilities of these products and mentioned that there are new releases every month with additional features added. She also suggested that at just $120, it was worth buying just to see where it's going to go.
We gave away a few more prizes while Craig Boyd and Alan Stevens prepared to show their "super secret project". They're using extensibility in Visual Studio to create something they call VFP Studio, which gives Visual Studio the ability to compile and generate VFP DLLs when you build a .NET solution. This saves jumping back and forth between different IDEs.
I then wound up the keynote with a call to action: download SP2 and Sedna, join VFPX, start your own blog, join (or start, as Cathy Pountney pointed out) a user group, and tell the world about VFP.
We then adjourned to the courtyard and trade show area for a welcome reception. It was supposed to only last until 9:30 but attendees were still talking to vendors as late as 10:30, and it appeared that everyone had a great time.
Whew! Thursday was a great kickoff for Southwest Fox!
Thursday, October 25, 2007
New Stonefield Query Blog
Monday, October 15, 2007
New Blog Series by Lisa
Saturday, October 13, 2007
DEMOgala 2007
Thursday was DEMOgala 2007, Colorado's premier IT event. We had a booth and showed Stonefield Query to a lot of very interested people. At lunch, I was honored to be part of a roundtable of C-level executives moderated by Jon Nordmark, CEO of ebags.com. Jon let me talk about Stonefield Query in front of the audience of about 600 CEOs and CIOs, plus we had a great discussion about problems caused by the falling US dollar/rising Canadian dollar and the challenges of recruiting staff in the red-hot Canadian economy.
What a great trip! Not much in the way of sleep but we made a lot of very important contacts, got tons of leads, and a new business partner with some cool products.
Leaving for Phoenix
I'm really excited about Southwest Fox: the lineup of speakers and sessions is fantastic, there are some cool products to see in the trade show, and there are about double the number of attendees we were expecting, including a great mix of new folks, people I haven't see at a conference in a while, and the usual but always appreciated gang. I'm also glad the conference is almost here; it was a lot of work! Some days, emails from Rick and Tamar were 75% of my total inbox (and no, that doesn't mean I got three from them
See you next week!
VFP 9 SP 2
One other thing I did was to delete the Microsoft Visual FoxPro 9 folder from my Windows Vista Virtual Store (C:\Users\
Wednesday, October 03, 2007
Stonefield Query Developer Meeting
If you'd like to learn more about Stonefield Query and will be at Southwest Fox, we also have a vendor session planned for Saturday, October 20, from 4:15 to 5:30 p.m. in the Fairway 1 room. We'll show how easy yet powerful Stonefield Query is and how you can create a customized version of Stonefield Query for your application so you can stop writing every single custom report every one of your users would ever want.
Tuesday, October 02, 2007
Southwest Fox 2007 Keynote
MVP Award
Thursday, September 20, 2007
Southwest Fox News Feed
Southwest Fox 2007 Hotel is Nearly Full
Wednesday, September 19, 2007
Southwest Fox 2007 Transportation
To help coordinate transportation between the airport and the Southwest Fox conference hotel, please add your travel information to http://fox.wikis.com/wc.dll?Wiki~SouthwestFoxTransportation so you can find someone to share a cab or limo to the hotel.
Also, be sure to check the Southwest Fox News page regularly (yeah, I know we should've done an RSS feed for it; it's the "shoemaker's kids" syndrome) because announcements are coming fast and furious (or perhaps just slightly angry) these days.
Monday, September 10, 2007
I do my Little Turn on the Catwalk
I was in four relatively simple sets (some of others were pretty complicated) showing casual, business casual, and business outfits from local shops. Many of the models were fairly experienced, but fortunately there were several newbies like me, and we had several practices in the weeks before the show. We also had copious amounts of alcohol back stage to settle any nerves. It was actually a lot of fun; I'd definitely do it again. Although I'd like to think the 450 women in attendance were hooting for me, it was more likely for the firefighter calendar boys who had their own set.
Although the final figures aren't in (hmm, I'm sure I could come up with a pun on that), the fashion show likely raised about $15,000 for Sophia House. Since the organization gets no government funding, everything they make comes from private donations, organizations like the United Way, and fund-raising efforts from the dedicated board, making this a very worthy cause.
Southwest Fox 2007 Attendance Update
After reading Tod's, Dave's, and Kevin's blogs about FoxForward and how much fun it was, I can hardly wait for Southwest Fox to start. Well, except for the fact that we still have a ton of things to do before it starts, that is.
Wednesday, September 05, 2007
Updated My Namespace
I went over the FoxUnit tests I had created for the Settings class and found that I had only tested one data type, System.String/Character. So, I created additional tests so all data types were covered, and I'm really glad I did: I found a couple of bugs I'd inserted in the new code. Although I don't use it as much as I should, I love FoxUnit. I blogged about it at http://doughennig.blogspot.com/2006/05/foxunit-is-cool.html.
I've posted an updated version on the Technical Papers page of one of my Web sites, www.stonefield.com.
Friday, August 24, 2007
Vista Article Online
Update: Since Advisor is closing online access to their articles, I have put a copy of my “Developing VFP Applications for Windows Vista” white paper, the source material for this article, on the Technical Papers page of Stonefield’s web site.
Monday, August 20, 2007
Lisa Blogs!
Friday, August 17, 2007
Southwest Fox 2007 Attendance
Saturday, July 07, 2007
John McClane for President
Fifteen years ago, while visiting his estranged wife in Los Angeles, he accidentally became embroiled in a plot at Nakatomi Plaza to rob hundreds of millions of dollars in bearer bonds. Hans Gruber and his band of criminals murdered several hostages before Senator McClane, through sheer guts and determination, killed several bad guys and stole the detonators they were planning on using to blow up the entire building. He was shot at, beaten, and even had to walk over broken glass in his bare feet but still managed to kill all the criminals and rescue the hostages.
A few years later, he again had to become a reluctant hero when several commandos decided to take over an airport in Chicago in order to spring a foreign general about to be tried for drug trafficking. Senator McClane once again outthought the bad guys and beat them at their own game. He even came up with a catch-phrase, "yippie-kie-yay-expletive", that's frequently used by other heros as they dispatch the evildoers.
Now, in 2007, even while in the middle of a presidential campaign, he once again stepped up to fight cyber-terrorists trying to destroy American infrastructure. Of course, since he's in his 70s now (amazingly, he looks like a man in his early 50s), he needed some help, so he got that "I'm a Mac" guy to handle the computer skills. And talk about courage: he had to shoot himself to take out the bad guy who held his daughter hostage. (He must've remarried a much younger woman because his daughter's still in high school.)
Given his experience fighting crime, I can't think of anyone more qualified to guide America through some very dangerous times. And he'd clean up corrupt politics in Washington. I can picture him telling "Scooter" Libby: "Here's your pardon. No jail time for you. BLAM!" Dick Cheney: "Hey dude, let's go hunting. Bring a vest."
What's that? It's Senator John McCain? John McClane is a fictional character played by actor Bruce Willis? Well, that's very different then. Never mind.
(My homage to Gilda Radner's Emily Litella.)
Friday, June 29, 2007
Southwest Fox 2007 Early-Bird Deadline in 2 Days
Monday, June 25, 2007
A Day at the Races
My team, Thunder, didn't have a good start. I'm not sure how experienced our swimmer was, because he came out of the water dead last, and by a significant margin. In his defence, it was tough swim: 750 M in cold water that was extremely choppy because of strong winds (which would also play a factor in my event). I'm really glad it wasn't me in that stage. Most of our team members were first-timers (like me) so I wasn't expecting us to be competitive, but I didn't think we'd be 25 minutes behind the leaders after the first stage.
The next stage was a hill run, which is exactly what it sounds like: you run up a hill and then back down. Our runner was very fast; it seemed like he disappeared into the tree line and reappeared just a moment later. He tagged me and I took off.
I knew it was windy but I wasn't expecting it to be quite that bad: 40 KPH head-winds the entire distance. Also, it was very hilly -- mostly uphill the entire way with a long descent at the very end. The wind was much more challenging than the hills, though. Fortunately, I was feeling pretty strong so I just put my head down and went as fast as I could. I managed to pass four cyclists (not bad considering the lead they had) and almost passed a fifth. Unfortunately, while shifting into my highest gear so I could pass him, the chain went past the last gear and got lodged between it and the bike frame. Rather than stopping to fix it, I coasted down the long descent until it started to flatten out, then got off my bike and sprinted with it the last 100 M.
My wife Peggy, son Nicholas, and friend Joanne and Molly Wade cheered me on throughout the stage. They drove about 1 KM ahead, cheered me as I went past, then moved another 1 or 2 KM ahead. They also cheered me at the finish line. Here's a picture of Nick and I a few minutes after finishing:
The next stage was a 6 KM run. Our runner didn't have any water with her, expecting that there'd be frequent water stations, but unfortunately there wasn't. As a result, she had a tough run on a pretty warm day.
The last stage of the event swamped us, literally. Our team captain, one of two members in the 1600 M canoe race, had never canoed before, and the other member didn't have much more experience. Unfortunately, they overturned the canoe 100 M from the start. As a result, we finished in last place.
The event was a lot of fun and other than the wind (which died down shortly after the cycling stage was over; it figures) and bit of rain before it started, it was a gorgeous day in a beautiful setting, and we raised $22,000 for programming for the YMCA.
The other benefit, from a personal perspective, is that it fired me up for cycling, so I'm planning on riding a lot more this summer.
Friday, June 22, 2007
Fix for Sedna Beta Installation Issue
Fortunately, I didn't need to. The next day, Rick Bean posted a message on a forum with the same problem, then later posted the solution. Like Rick, I'm using Windows Vista, and it looks like the installer needs admin rights to do its job but it isn't set up to request elevation automatically. So, Rick's solution is to choose Start, All Programs, Accessories, Command Prompt (Rick, if you're reading this, here's a shortcut: click Start and type "cmd"; it'll appear at the top of the search list), then right-click and choose Run as Administrator. Then, using good old-fashioned DOS commands, CD to the directory where the Sedna installer is located, type the installer name, and press Enter. Since it's launched from an elevated process, it also runs as administrator and successfully installs.
I hope everyone gets some time to work with SP2 and Sedna. I haven't gone through all of the Sedna tools yet, but have spent a fair bit of time with the My namespace and Upsizing Wizard (duh {g}), and love the work Craig Boyd has done with the Vista Toolkit. If you come to Southwest Fox or the German DevCon, attend my Developing Visual FoxPro Application for Windows Vista session and I'll show you how cool this toolkit is.
Monday, June 18, 2007
Two Weeks to the Southwest Fox Deadline
- Saving $75
- Free pre-conference session ($99 value)
- You're eligible for the White Light Computing Scholarship
As Kevin Cully notes, the deadline for registration for FoxForward 2007 is also approaching.
You owe it to yourself and your career as a development professional to attend one of these conferences, or the German DevCon, another great conference now in its 13th year.
Tuesday, May 29, 2007
Has a File Been Virtualized?
- Since the files are redirected to a user-specific location, other users on the same system won't see each others' changes.
- Backing up the files in the application folder won't back up the user-specific files if the backup program is Vista-aware.
- If the user turns off User Access Control (UAC) or virtualization, the application not only breaks because it still can't write to the protected folders, it also doesn't see the virtualized files any more so data entered by the user appears to be gone.
So, I've been reworking those parts of Stonefield Query that expected to write to the current or application folder. It was actually fairly easy to do because most file paths were referenced with an application object property (e.g. USE (oApp.cAppDataDir) + "SomeTable") so simply changing the value of the property redirected the files to a location the user has write access to. I then ran the application and looked for any files that appeared in my virtualization folder, and turned off virtualization and tested to see if anything broke. I found a few stragglers where I hadn't put a path on the filename (typically writing to some temporary files) and fixed them.
However, one thing puzzled me: a table that wasn't being written to was virtualized. In tracking it down, I found that the virtualized table was created immediately after the USE command. To make matters even odder, when I turn off virtualization, the table wasn't virtualized and the application didn't give an error. Also, while the DBF and FPT files were virtualized, the CDX was not.
I guess this makes sense. Since I was opening the table in read-write mode (ie. no NOUPDATE clause), I suspect VFP was doing some minor update to the table behind the scenes (e.g. writing to the header of DBF and FPT files for locking purposes), resulting in it being virtualized. You can see this effect if you open one of the classes in the FFC subdirectory of the VFP home directory; simply opening it immediately causes its virtualization. With virtualization turned off, the table was automatically read-only since it's in a protected folder, so there's no need to virtualize it. Adding a NOUPDATE clause to the table (since it's a meta data table, it's used for reference purposes only) took care of that.
However, the SDK for Stonefield Query includes an editor program for that table, so that program does open the table for read-write purposes and will likely write to the table (otherwise it wouldn't be an editor). The problem is that the user may be unaware that the table is virtualized and will likely distribute the wrong instance of the table (the one in the application folder rather than the virtualized one) after making changes to it. So, I figured I should let them know which table they're writing to so they know which one to distribute.
However, how can you tell if a file has been virtualized? If you open a file in C:\Program Files\Some Folder and that file has been virtualized, the operating system actually opens the virtualized copy instead of the one you specified, and the application can't tell the difference.
I settled on the obvious solution: check whether a virtualized copy of the file in question exists. If so, we know that's the one Vista will open. Here's some code to do that. First, we get the directory the application is running in (not necessarily the current directory) by calling GetAppDirectory (a routine I wrote years ago; I'll show you the code later). Then we call SpecialFolders, a routine I discussed in my Finding the Paths for Special Folders blog entry, to get the current user's local app data path (C:\Users\user name\AppData\Local\). Notice that this code uses SUBSTR(lcDirectory, 3) to strip the drive letter, colon, and backslash from the application folder name. We then check if the desired file exists in the virtualization folder.
lcDirectory = GetAppDirectory()
lcVirtualPath = SpecialFolders('LocalAppData') + ;
'VirtualStore\' + substr(lcDirectory, 3)
lcFile = lcVirtualPath + 'SomeTable.DBF'
if file(lcFile)
* the table has been virtualized
endif
Here's the code for GetAppDirectory. It handles runtime and development time conditions differently.
local lcPath
if version(2) = 2
lcPath = justpath(sys(16, 0))
if atc('PROCEDURE', lcPath) > 0
lcPath = substr(lcPath, rat(':', lcPath) - 1)
endif atc('PROCEDURE', lcPath) > 0
else
lcPath = addbs(justpath(_vfp.ServerName))
endif version(2) = 2
return lcPath
Thursday, May 03, 2007
Why I Signed
All the negative comments from some people in the community had done more to convince me not to sign than anything else. I'm a pretty laid back guy, but I get really pissed off when people question my integrity.
Few of the comments on my "Why I Haven't Signed" blog entry or the Wiki topic actually tried to make an argument. Most of them amounted to "you're wrong" (that's your idea of a convincing argument?) and "VFP is a great tool" (thanks, know that already).
However, Mike Asherman's comment made me think about this some more. He said, "Adding your name means that you support the cause, not that you believe it is likely to succeed." Good point. I've voted for people I've believed in before knowing they didn't have a chance in winning because it was the right thing to do.
So, I signed as my way of showing solidarity with the community. I still believe this petition is futile and will have absolutely no impact on Microsoft's decision, but I've cast my ballot anyway.
For those of you questioning the reasons why others haven't signed, I suggest spending your time in more productive manners, such as writing code and helping others on community forums, rather than driving the wedge into our community even further. Alienating people by questioning their motives is a sure way to get the opposite of what you desire.
Wednesday, May 02, 2007
Taking out the Slow Parts
<captions>We want to use this XML to update the Stonefield Query data dictionary. Our first attempt used XMLTOCURSOR() to put the XML into a cursor, which we then process. It took somewhere around 30 seconds, which seems like forever because it happens every time the user starts Stonefield Query. XMLTOCURSOR() uses the XML DOM object, which gets exponentially slower as the XML gets larger.
<newrecord>
<tablename>SomeTableName</tablename>
<fieldname>SomeFieldName</fieldname>
<fieldcaption>Caption for Field</fieldcaption>
</newrecord>
...
</captions>
The next approach was to manually parse the XML using STREXTRACT(). STREXTRACT() is the perfect function for parsing XML because it was designed to find text between delimiters. If you know the XML structure, it's really easy to use. Here's the code we used:
create cursor __CAPTIONS (FIELDNAME C(119), CAPTION C(60))Interestingly, this turned out to be way slower than XMLTOCURSOR() (in fact, I stopped it after a couple of minutes, so I don't know how long it would've taken).
for lnI = 1 to occurs('<tablename>', lcXML)
lcTable = strextract(lcXML, '<tablename>', '</tablename>', lnI)
lcField = strextract(lcXML, '<fieldname>', '</fieldname>', lnI)
lcCaption = strextract(lcXML, '<fieldcaption>', '</fieldcaption>', lnI)
insert into __CAPTIONS values (lcTable + '.' + lcField, lcCaption)
next lnI
It occurred to me while looking at the XML that if we could convert it into comma-delimited text, we could use APPEND FROM to suck it into the cursor. Here's the code for that:
#define ccCRLF chr(13) + chr(10)This took 0.7 seconds, 42 times faster than using XMLTOCURSOR().
lcXML = strtran(lcXML, '<tablename>')
lcXML = strtran(lcXML, '</tablename>' + ccCRLF + '<fieldname>', '.')
lcXML = strtran(lcXML, '</fieldname>' + ccCRLF + '<fieldcaption>', ',"')
lcXML = strtran(lcXML, '</fieldcaption>' + ccCRLF + '</newrecord>' + ;
ccCRLF + '<newrecord>', '"')
lcXML = strtran(lcXML, '</fieldcaption>' + ccCRLF + '</newrecord>' + ;
ccCRLF + '</vfpdata>', '"')
lcTemp = forcepath(sys(2015) + '.txt', sys(2023))
strtofile(substr(lcXML, 91), lcTemp)
create cursor __CAPTIONS (FIELDNAME C(119), CAPTION C(60))
append from (lcTemp) delimited
erase (lcTemp)
But still, I wondered why the STREXTRACT() approach was so slow; my experience is that the VFP text handling functions are blindingly fast. I wondered if it had to do the with fourth parameter, the one that specified the occurrence. I rewrote the code to this:
create cursor __CAPTIONS (FIELDNAME C(119), CAPTION C(60))This took 0.4 seconds. Wow! Nearly two orders of magnitude improvement over the initial attempt. It really does pay to take out the slow parts!
lnLines = alines(laLines, lcXML)
for lnI = 1 to lnLines
lcLine = laLines[lnI]
do case
case '<TableName>' $ lcLine
lcTable = strextract(lcLine, '<TableName>', '</TableName>')
case '<FieldName>' $ lcLine
lcField = strextract(lcLine, '<FieldName>', '</FieldName>')
case '<FieldCaption>' $ lcLine
lcCaption = strextract(lcLine, '<FieldCaption>', '</FieldCaption>')
insert into __CAPTIONS values (lcTable + '.' + lcField, lcCaption)
endcase
next lnI
Off to DevCon
This will be my 17th DevCon as an attendee (I only missed the first one in Toledo in 1989) and my 11th as a speaker (1997 was the first). I'm the only person who will have attended 17 consecutive DevCons, although this will also be number 17 for Alan Griver (just not consecutive; he was at the first one but missed one in the middle).
However, my string of consecutive DevCons as an exhibitor (1993 was the first) is broken: For the first time in fifteen years, I won't have a booth there. Unfortunately, it just wasn't cost effective anymore, plus our booth (and staff) are going to be at the 2007 Timberline User Group National Conference in Dallas at roughly the same time, showing our new Stonefield Query for Timberline.
For posterity, here are the ones I've attended:
1990 Toledo
1991 Toledo
1992 Phoenix
1993 Orlando
1995 San Diego
1996 Scottsdale
1997 San Diego
1998 Orlando
1999 Palm Springs
2000 Miami
2001 San Diego
2002 Ft. Lauderdale
2003 Palm Springs
2004 Las Vegas
2005 Las Vegas
2006 Phoenix
2007 Anaheim
I'm presenting four sessions at DevCon:
- Best Practices for Vertical Application Development: this is an updated version of the session I presented at Great Lakes Great Database Workshop in 2006 and earlier this year at OzFox. I'll also be presenting this session at Southwest Fox 2007.
- Integrating RSS and VFP: this is an updated version of the session I gave last year at DevCon. It now includes material on accessing the common RSS store installed with Vista and IE 7.
- Deploying VFP Applications with InnoSetup: this is an updated version of the session I gave at last year's Southwest Fox and earlier this year at OzFox. It has a new section on deploying on Vista.
- Developing VFP Applications for Windows Vista: this is a new session and the one I'm most excited about. Vista presents some cool opportunities but also some challenges to all developers, regardless of the language they use. I think it'll be an eye-opener for those who haven't deployed an application on Vista yet or are struggling with the issues right now. I'm also presenting this session at Southwest Fox 2007.
Tuesday, May 01, 2007
Southwest Fox 2007 Speakers and Sessions Announced
OK, I'm biased for multiple reasons--both as a speaker and being part of Geek Gatherings--but I really do think this is going to be a killer conference. Some of the sessions, like Alan Stevens' Integrating Windows Forms UI Elements with Existing Applications, Kevin Goff's Professional Business Reporting with Crystal Reports, Christof Wollenhaupt's On the Dark Side of FoxPro, and Toni Feltman's Introduction to the DBI Controls Included in Sedna, are very high on my personal list.
Registration is now open. Be sure to take advantage of the early-bird discount: register by July 1 to save $75 and get a free pre-conference session (a $99 value).
Friday, April 27, 2007
Elevating Tasks in Vista
lcSystem = Thisform.oUtility.GetSystemDirectory()(GetSystemDirectory is a utility method that returns the location of the Windows System folder; I'll show the code for that later.)
try
run /n1 &lcSystem..odbcad32.exe
catch
endtry
This worked great until I tried it on Vista; it fails there because the ODBC Administrator requires admin privileges and the RUN command doesn't support requesting elevation. Fortunately, there's an easy solution to that: use ShellExecute instead. I changed the code to:
lcSystem = oUtility.GetSystemDirectory()(Like GetSystemDirectory, ShellExecute is another utility function. It runs the specified command. The cool thing about ShellExecute is that it'll launch the appropriate program. In the case of an EXE, it runs it. In the case of an HTML file, it launches the default browser. In the case of a DOC file, it launches Word.)
try
oUtility.ShellExecute(lcSystem + 'odbcad32.exe', 'RunAs')
catch
endtry
Now when the user clicks the button, they're requested to elevate to administrator before the ODBC Administrator appears. To make it obvious that this will happen, I've followed the Vista convention of adding a shield image to the button to alert the user that elevation will be requested. To do that, I set PicturePosition to 1-Left of Caption, Picture to VistaShield.BMP (an image I created using SnagIt to grab the shield icon displayed in the User Accounts control panel applet), and shortened Caption to just "ODBC" so it all fits. Here's what the button looks like:
Here's the code for GetSystemDirectory:
#define cnMAX_PATH 260Here's the code for ShellExecute:
#define ccNULL chr(0)
local lcBuffer
lcBuffer = space(cnMAX_PATH)
declare integer GetSystemDirectory in Win32API ;
string @szBuffer, integer nLength
GetSystemDirectory(@lcBuffer, cnMAX_PATH)
return addbs(alltrim(left(lcBuffer, at(ccNULL, lcBuffer) - 1)))
lparameters tcFileName, ;
tcOperation, ;
tcWorkDir, ;
tcParameters
local lcFileName, ;
lcWorkDir, ;
lcOperation, ;
lcParameters, ;
lnShow
if empty(tcFileName)
return -1
endif empty(tcFileName)
lcFileName = alltrim(tcFileName)
lcWorkDir = iif(vartype(tcWorkDir) = 'C', alltrim(tcWorkDir), '')
lcOperation = iif(vartype(tcOperation) = 'C' and not empty(tcOperation), ;
alltrim(tcOperation), 'Open')
lcParameters = iif(vartype(tcParameters) = 'C', alltrim(tcParameters), '')
lnShow = iif(upper(lcOperation) = 'Print', 0, 1)
declare integer ShellExecute in SHELL32.DLL ;
integer nWinHandle, ; && handle of parent window
string cOperation, ; && operation to perform
string cFileName, ; && filename
string cParameters, ; && parameters for the executable
string cDirectory, ; && default directory
integer nShowWindow && window state
return ShellExecute(0, lcOperation, lcFilename, lcParameters, lcWorkDir, ;
lnShow)
Why I Haven't Signed
Apparently, this is a problem for some people. A debate has been going on over on the FoxWiki (as well as other forums) about this. Some people have suggested that those who don't sign the petition have an agenda, that somehow we want VFP to die. Nothing could be further from the truth. This decision impacts my revenue as much as it does everyone else's. Others have suggested that we won't speak out against Microsoft, that somehow we're their cheerleaders or that we won't bite the hand that feeds (or as someone suggested, pays) us. Again, not true. I wish Microsoft hadn't made this decision. I've bitched about them plenty over things they've done in the past and likely will do so again in the future.
I can't speak for anyone else, but my reason for not signing the petition is simple: I believe it's a futile effort and I don't think someone should put their name on something they don't believe in.
However, I consider myself to be an open-minded person. If someone can give me a convincing argument about why I should sign the petition, I'll do so.
"It can't hurt" isn't a convincing argument. In fact, I'd argue that this petition has done more harm than good for our community, since some people now have an us (the signers) against them (non-signers) attitude and are looking for conspiracy reasons why someone won't sign.
"It only takes 30 seconds" isn't a convincing argument. This isn't about how long it takes; it's a matter of principle.
"It'll show you support the community" is also not a convincing argument. I think writing more than 100 articles over 10 years, presenting more than 200 sessions at conferences and user groups, writing blog entries describing how to do some complex things in VFP, being a co-administrator and a project leader for VFPX, and spending thousands of hours online helping other developers shows that I support our community as much as anyone else. The same is true of other MVPs. The very definition of an MVP is someone who spends a lot of personal time supporting the community. This whole argument that not signing the petition means you don't support is community is almost identical to another ludicrous argument: that if you don't believe in the war in Iraq, you don't support the troops. Like the petition, those two things have nothing to do with each other.
Please post your arguments on this blog. I have been convinced to change my position on issues in the past. All it takes it a well-thought out, logical (not emotional) argument.
Thursday, April 26, 2007
Scheduling Tasks
However, after upgrading to Windows Vista, I discovered the scheduler didn't work. In researching this issue, I discovered that Vista includes Task Scheduler 2.0, while the DLL was written to support Task Scheduler 1.0, which has a completely different interface. So, back to the drawing board.
Fortunately, the MSDN documentation on Task Scheduler 2.0 has tons of detail and several examples in VBScript, which are easily converted to VFP. Because I still need to use Task Scheduler 1.0 with XP and earlier systems, I decided to create a TaskScheduler base class and XP and Vista subclasses of it.
I won't show the code for these classes here because it's fairly lengthy (you can download it from the Technical Papers page of my Web site), but here are some examples of how easy they are to use to schedule tasks. These examples run on Vista; use XPTaskScheduler instead for Windows XP or earlier.
* Create a task that runs at 3:00 AM every day.
loSchedule = createobject('VistaTaskScheduler')
with loSchedule
.TaskName = 'My Task Name'
.UserName = 'Your Windows UserName'
.Password = 'Your Windows Password'
.StartTime = {^2007-04-26 03:00:00}
.EXEName = 'Full path to EXE'
.EXEParameters = 'Any parameters to pass'
.ScheduleType = 1 && daily
if not .CreateTask()
messagebox(.ErrorMessage)
endif not .CreateTask()
endwith
* Create a weekly task that runs at 3:00 AM Tues, Thurs, and Sat
* of every second week.
loSchedule = createobject('VistaTaskScheduler')
with loSchedule
.TaskName = 'My Task Name'
.UserName = 'Your Windows UserName'
.Password = 'Your Windows Password'
.StartTime = {^2007-04-26 03:00:00}
.EXEName = 'Full path to EXE'
.EXEParameters = 'Any parameters to pass'
.ScheduleType = 2 && weekly
.Interval = 2
store .T. to .DaysOfWeek[3], .DaysOfWeek[5], .DaysOfWeek[7]
if not .CreateTask()
messagebox(.ErrorMessage)
endif not .CreateTask()
endwith
* Create a monthly task that runs at 3:00 AM on the 1st and 15th
* of every month.
loSchedule = createobject('VistaTaskScheduler')
with loSchedule
.TaskName = 'My Task Name'
.UserName = 'Your Windows UserName'
.Password = 'Your Windows Password'
.StartTime = {^2007-04-26 03:00:00}
.EXEName = 'Full path to EXE'
.EXEParameters = 'Any parameters to pass'
.ScheduleType = 3
store .T. to .DaysOfMonth[1], .DaysOfMonth[15]
.MonthsOfYear = .T. && initialize all 12 elements of array to .T.
if not .CreateTask()
messagebox(.ErrorMessage)
endif not .CreateTask()
endwith