A day in the life (of a developer) RSS 2.0
 Thursday, September 25, 2008

One of the things missing from the GridSort control I posted about while back was a sample of how it's used. I was thinking of just including a sample form, but I decided to just walk through the steps of using it instead. Let's start off by creating a new form. Next, we'll drop a grid control onto the form and name it "grdSample". Now we need some data to fill in the form - let's use one of the sample tables included in VFP - Customer. Right-click on the form and edit the data environment. Click on Other and navigate to C:\Program Files\Microsoft Visual FoxPro 9\Samples\Data\ and select "Customer.dbf". Now close the data environment. Run the form.

I ended up stretching the grid out a bit to show more of the information and anchoring it so that if I stretched the form the grid would resize. Now we're going to add the rcsGridSort control to the form - I like to just use the class browser to open the class up, click on "gridsort" and then drag and drop the "shape" icon in the upper left hand side of the window onto the form. In the property sheet we're going to need to fill in the cGridEval property of the gridsort control. Enter: ThisForm.grdSample. Now run the form again.

Double-click on the various column headers: an arrow should appear and the column should be sorted. Double-click on the same column and the sort order will flip (if it was ascending it will change to descending or vice-versa). If the images are missing it's because VFP isn't finding them; either add the images to your path or include them in current directory. Or, you can set the pathing in the cSortAscendingGraphic/cSortDescendingGraphic properties of the control.

gs

Like I mentioned in my original post, this control uses the BINDEVENT command which I think was introduced in VFP 8. Therefore, the control requires VFP 8 or later.

Links:

http://www.rcs-solutions.com/blog/2008/07/31/SortingTheVFPGrid.aspx
http://www.rcs-solutions.com/downloads.aspx

Thursday, September 25, 2008 5:51:32 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] -

VFP
 Friday, August 15, 2008

I've been using a "common" style for my reports for quite a while. I include a report title, then below that I usually have one or more lines which show which filter and sorting criteria were used to generate the report. I include a page X/Y line, usually in the bottom right hand side of the report. At some point I started adding the print date/time, who it was printed by, and the name of the file of the report. All of these fields together have been valuable when making changes and in determining why something isn't working.

The other day I had someone bring a report that was generated last year for the first 6 months of 2007 by an employee who is no longer with the company. They were attempting to tie the summarized numbers back to the detail which generated them and weren't sure how to find the detail. We had recently changed a fundamental aspect of how this particular report and supporting (detail) reports generated some of their numbers so I decided to just run a query against the database to get the information they were looking for. I looked at the timeframe the report was run for and wrote the query - there were no filters so it was really straightforward, or so I thought. When I totaled up the numbers they didn't match the report. Uh oh. I started worrying about what kinds of bad things might have happened that would have changed our historical tables.

Ugh...Then I noticed that the report was printed on the ending date of the report (in this case, it was run for 1/1/2007 - 6/28/2007) and it was printed on 6/28/2007. The light bulb went on. This particular report showed numbers that weren't available until a few months after the month they were applicable to. Essentially, we don't get numbers for January until March (even though they are posted back into January). So when this report was run, the numbers for April forward weren't available yet and weren't included in the report (even though the report range said it was through 6/28/2007). In the meantime those numbers had been posted and since I was pulling these numbers a year later they appeared in my version. I adjusted my query to exclude numbers posted after the report run date/time and suddenly everything balanced. Without knowing when the report was run it would have taken a LOT of work to resolve this (I'm not even sure I would have been able to).

Are there any types of things you're including on reports which have saved you?

In addition to the fields mentioned above, I've recently started including a "key" at the bottom of a number of reports which explain how numbers are generated and a more detailed explanation of their meaning. As questions come up about them I adjust my descriptions so they answer the questions asked. I'm using a really tiny font to keep the amount of space lost to them to a minimum but I'm finding they have also been really valuable since I'm not having to refer to the code as much to explain how some numbers were derived. It also addresses the issue where you use common names for column headers on various reports but the underlying numbers in those columns are actually inclusive or exclude different things.

Friday, August 15, 2008 8:48:03 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] -

VFP
 Thursday, July 31, 2008

I'm a big fan of "drop in" components which can add functionality to my applications as opposed to what is more commonly thought of as framework-level code which bakes in functionality. What do I mean by that? The common way of building in functionality into an application (usually via a framework) is to develop an class hierarchy, then inherit from those classes in your application as you build up functionality. A form class may have code which remember it's location and size when you close it, an edit box may add some spell checking ability, etc. If you inherit all your forms from these classes they suddenly have all this extra functionality and life is good. Then, as the framework is built you tend to end up with a number of interdependencies between components, which suddenly makes it more difficult to be able to use these components in other applications which don't include most of the framework libraries and inherit from the proper classes. On one level that's fine, since it allows for a fairly high level of functionlity and consistency. However, it requires a high level of "buy in" in order to use even the most basic aspects of the framework. In many cases you just can't use some cool combobox class from the framework in another application without requiring the full framework (and inheritance chain) to come along for the ride, which is a bit of a bummer. There are a huge number of applications written that can't easily be integrated with a framework (as most VFP developers think of them).

What if, instead, components had been built to be highly modular - drop in a small class library, add the control to your form and you're good to go. Until VFP 9 this was actually a bit hard to do - what if your control needed to respond to various form events? Everytime you dropped the control on a new form you suddenly have to do a lot of wiring up by adding code to the various events your control was interested in. Which leads back to the first style of development that just assumes you'll be using these components as a whole. However, in VFP 8 they introduced a set of commands to allow for event binding: BINDEVENT(), UNBINDEVENTS(), RAISEEVENT() and AEVENTS(). So what do these commands do and what do they give you? They give you a mechanism of listening for specific events that fire on an object and handling those events in your own code. You can do this without the "source" object even being aware you are listening to it's events.

A real example might help:

Let's suppose we want to add the ability to sort a column in a grid. The common way of doing that would be to create a subclass of a grid, add some code which does the sorting as methods on the grid, then require the developer to "hook up" the functionality during development so that this new functionality runs when a user double clicks on the row header. In effect, add code to every column's header and adding code to the DblClick() event. You'll even notice that even if we require you to use our new subclassed grid the functionality still isn't really just drop-in seamless. So how can event binding help here? Can it help us achieve both goals of not requiring any glue code and not requiring us to inherit from a specific grid control?

What I'd love to end up with is a control I can drop on a form, point it to some grid control and have the grid "magically" let you sort without requiring a lot of changes to existing code. So let's start from that premise and create a custom control. I'll add cGridEval property which can be filled in with a string which is EVAL'd at runtime to resolve a live object reference to our grid, ex. you can fill in something like "This.grdSample". That's the easy part, now how do we use event binding to let us do everything else?

I'm going to create a BindControl() method in my custom control and we'll first get our object reference to the grid:

loGrid = EVALULATE(This.cGridEval)

Every grid has a Columns collection that we can iterate through and each column has a Controls collection. If we take a look at the BaseClass property of the controls here we can determine which one is the Header column. At that point we can use BINDEVENT() to hook up the DblClick() event to us - when a user double clicks on this grid column the event will fire on the grid and we'll also get a notification that the event has fired.

FOR EACH loColumn IN loGrid.Columns
    FOR EACH loControl IN loColumn.Controls
        IF loControl.BaseClass = "Header"
           BINDEVENT(loControl, "DblClick", This, "Sort")
           EXIT
        ENDIF
    ENDFOR
ENDFOR

The BINDEVENT() function is where the real work happens. We pass the control we want to listen to as the first parameter, in this case the header. Then we pass (as a string) the name of the method we want to listen to, "DblClick". The third parameter is an object reference to the subscribing object (us), and the fourth is the method VFP should call on our object. You must make sure that the method you create on your subscriber accepts all of the same parameters as the event being fired. For example, if we hooked into the KeyPress event accepts two parameters, nKeyCode and nShiftAltCtrl - you have to accept the same parameters in your subscribing method. There is a fifth parameter which can be passed that we're not using which allows you to specify when your code is called - before or after the original control's method fires. DblClick doesn't pass in any parameters, so we're good. All we need to do is create a "Sort" method on our custom object.

At this point, when a user double clicks on a header in a hooked up grid, our Sort event fires. Great - now how do we figure out which header was clicked on? Here's where the AEVENTS() function comes into play - it fills an array with information about the object that triggered the event. We can use this information to get a reference to the actual header the user double clicked on. From there, we can determine which column in the grid to sort.

AEVENTS(laEvent, 0)

This gives us a 3 column array, laEvent:

laEvent[1] - An object reference to the header that was clicked
laEvent[2] - The event that was fired
laEvent[3] - The event type (how the event was raised).

In our case we're really only interested in laEvent[1]. Once we have our header reference we can get the column's control source like this:

loHeader = laEvent[1]
loColumn = loHeader.Parent
lcControlSource = ALLTRIM(loColumn.ControlSource)

All that's left is for us to build up a way of creating temporarily indexes and maintaining a list of which indexes have been created (and whether we're current ascending or descending). One thing that is kind of nice is that since we now have a reference to the column header, we can also do things like add some graphical image to the sorted column to make it easy for the user to see which column has been sorted and in which direction.

You'll notice that I didn't cover the UNBINDEVENTS() and RAISEEVENT() functions. UNBINDEVENTS does just what you'd expect - it unhooks an event handler so that it no longer receives the bound event. RAISEVENT() lets you "fire" an event (both things like custom events and events on native VFP objects). I don't have a nice example of this so I'll leave that for some other time.

You can download the finished control below.

Links:

http://www.rcs-solutions.com/Download.ashx?File=rcsGridSort.zip

Thursday, July 31, 2008 6:00:22 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] -

VFP
 Saturday, July 12, 2008

I mentioned Dependency Injection / Inversion of Control (DI/IoC) recently and I really didn't explain what it is, why you might want to use this particular pattern, and why on earth you'd need a framework for it. It's a fancy name for a fairly simple concept. Instead of creating objects inside of your classes, you let the calling code "inject" the necessary instances into your code. It's probably easiest to see this in some code. I'm going to show both C# and VFP code, since I don't want you to get the idea that this is a .NET-only type thing.
 

   1 public class SampleDependency

   2 {

   3    public string SayHello()

   4    {

   5       return "Hello";

   6    }

   7 }

   8 

   9 public class Sample

   10 {

   11    protected SampleDependency m_depend;

   12 

   13    public SampleDependency Depend

   14    {

   15       set { this.m_depend = value; }

   16    }

   17 

   18    public Sample(SampleDependency depend)

   19    {

   20       m_depend = depend;

   21    }

   22 }

 

      Sample sample = new Sample(new SampleDependency());

 

VFP Version
DEFINE CLASS SampleDependency AS Session 
   FUNCTION SayHello() 
      RETURN "Hello" 
   ENDFUNC 
ENDDEFINE 

DEFINE CLASS Sample AS Session 
   oDepend = NULL 
   FUNCTION Init(toDepend) 
      This.oDepend = toDepend 
   ENDFUNC 
ENDDEFINE 

loSample = CREATEOBJECT("Sample", CREATEOBJECT("SampleDependency"))
 

Notice that in both cases, we are passing in the instance we want the class to use instead of letting the class create the instance itself. That's all DI/IoC is. Honest, that's it. This is DI via a constructor (you can also do it via a property setting instead; notice in the sample C# code I created a write-only property which could hold the reference).

So the next obvious question is, why? What's wrong with just creating the object inside of the class?

One thing DI gives you is the ability to easily swap in different objects. In the C# example, we probably would change the parameter from a specific type to an interface. Now any class which implements that interface can be injected into this class. In VFP, since it's not strongly typed, you can just pass in whatever instance you'd like (it's up to you to make sure it doesn't blow up at runtime by accessing some method or property which isn't on the passed in object). My initial thought after seeing this was, well, can't I just use an abstract factory pattern instead? In an abstract factory you delegate object creation to a "object factory" - usually passing in a name or calling a method which returns the actual instance you'd like to use. This sounds like almost the same thing.

An abstract factory does let you do that, but it doesn't let you easily do something the DI/IoC pattern does: test your objects. Let's suppose you want to write a test for a class which uses another class to send out an e-mail. You aren't really trying to test sending an e-mail - that's just one of the things the class you're testing happens to do during some process. In fact, you really don't want to send out an e-mail; we don't want to spam our users. If you happen to use the abstract factory pattern, you would need to modify it to create your dummy/stub/mock object for sending an e-mail (in VFP that's most likely by editing a record in a table, but the idea is the same), then test the object in question. If you used the DI/IoC pattern the only thing you need to do is pass in your dummy/stub/mock object. No other modifications are necessary.

OK, so this all looks simple enough. Why would you need a framework for the above?! One of the biggest reasons - less typing. You'll notice that in order to use any of these objects you may have to pass in a bunch of other dependency objects (which themselves may have other dependencies). For a complex set of objects that could really suck. A DI framework does that for you along with the benefits of an abstract factory, all rolled up into one. In your code you call the DI framework and tell it to get you an instance of a class - it figures out what objects need to be passed in for you so you don't need to do it. In your tests, you can instanciate the objects directly and pass in your stub/dummy/mock objects instead.

Links:

http://weblogs.asp.net/rosherove/archive/2007/09/16/mocks-and-stubs-the-difference-is-in-the-flow-of-information.aspx
http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

Saturday, July 12, 2008 9:34:13 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] -

.NET | Software | VFP
 Sunday, March 09, 2008

Wow, local DAFUGer Frank Perez has actually posted new content on his blog. He's worse than I am with keeping it updated regularly (and I'm pretty sure only 3 or 4 people even knew he HAD a blog; that probably keeps the pressure to update it regularly to a minimum). Anyway, he's showing off a neat plug-in tool for Beyond Compare that let's you run comparisons against various VFP-specific filetypes (DBF/MNU/SCX/VCX among others). Check it out.

Links:

http://www.dafug.org
http://www.pfsolutions-mi.com/blog/2008/03/08/BeyondCompare.aspx

Sunday, March 09, 2008 10:48:44 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] -

VFP
 Saturday, March 08, 2008

We ran into an interesting performance issue a few weeks back. We had migrated our primary fileserver over to a new machine (it was previously running on the same machine as our website). We expected some of the pages on our website to be a bit slower since the files were no longer on the same box (we have both data in VFP and in SQL Server 2005), but some of them were suddenly painfully slow. They went from 1-2 seconds to more than 7+ seconds. We talked about installing a dedicated network card to link two servers to help, but it still didn't seem like the performance should take that much of a hit.

My boss spent some time tracing the problem and found it was really slow when we opened the company file from our accounting system (Sage Pro). I've talked about the performance of USE in the past, but it was still amazing to see how slow it was. He tried using a mapped drive and UNC mapping but the performance of each was about the same. This particular table is part of a database, so he tried prefixing the USE statement with the database name, ex. USE F:\PathTo\Data\MyDatabase!MyTable IN 0 - the difference was amazing. That 7+ seconds dropped back down to 1-2 seconds: a bit slower than it was when it was local, but still much faster than it had been. Resolving the database is apparently very slow.

Saturday, March 08, 2008 2:16:34 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2] -

VFP
 Sunday, February 10, 2008

I just uploaded a new version of the VFP calendar. I've fixed the problem where the drop-down version of the calendar wouldn't collapse if you clicked away from it. I initially thought it would be a simple matter of just adding some code to the calendar form's LostFocus event. That appeared to work, but had the side effect of breaking the drop-down button. If the calendar was being displayed and you then tried to click on the drop-down button to hide the calendar, the LostFocus would fire (hiding the calendar), then the Click event of the button would fire which would redisplay the calendar. Not quite what I was expecting.

I experimented with a few different ways of attempting to address this, but I just couldn't get the events to fire the way I wanted. Ultimately, I ended up adding code in the Click event to save off the last time since the calendar was hidden. If it is under the configured minimum interval, we don't do anything (the assumption is the the LostFocus just fired and hide the calendar). If the interval has been exceeded, the calendar is displayed as normal. A little weird, but relatively simple to code.

I've also added another sample to show how the rcsDateTimePicker controls can be hooked together for Start/End date scenarios. The controls keep the start date from being later than the ending date. The parent (Start) / child (End) relationship is setup via two properties on the control: uParentPicker and uChildPicker. These properties are evaluated to resolve to an object reference to the proper control.

 

Links

http://www.rcs-solutions.com/downloads.aspx

Sunday, February 10, 2008 1:17:16 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] -

VFP
 Sunday, January 13, 2008

I'm happy to say my winter cleaning give away was a success. Almost all of the books that were available have new owners (although they may still be waiting for them, since they were shipped at the book rate which can take forever), and my huge pile of computer equipment is also gone. I had a taker for the computer equipment within a day of posting it on my local Freecycle site. I thought he'd pick through the rubble, but he ended up taking the whole pile (including my Windows NT 3.51, NT 4.0, and Windows 2000 Resource Kits - those alone took up a full bookshelf).

There are a few books still available, if anyone is interested.

Img_4417

 

Links:

http://www.rcs-solutions.com/blog/2007/12/13/WinterCleaning.aspx
http://www.freecycle.org

Sunday, January 13, 2008 5:33:56 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] -

Other | VFP

I was just reading some of the threads over on the UT and ran across one where someone was looking for a drop-down calendar control. Someone suggested they look at the one that I've got in the downloads section on my site. Unfortunately, the version I've had up forever doesn't actually have a "drop down" version of the control. I've had an updated version for a long time now, but I've never gotten around to posting it (until now). This thread kicked me in the butt a little to update this on the site.

Here's a screenshot of the updated control:

DropdownCalendar

Hopefully you find it useful.

 

Links:

http://www.universalthread.com
http://www.rcs-solutions.com/Downloads.aspx

Sunday, January 13, 2008 5:17:45 PM (Eastern Standard Time, UTC-05:00)  #    Comments [3] -

VFP
 Wednesday, December 12, 2007

I've been spending a lot of time lately doing some winter cleaning. We're trying to free up some space in the basement for a play area for Brendan. It's amazing how much stuff you can collect. We've thrown away a LOT of stuff - I'm pretty sure the garbage guys hate us by now. Jenn mentioned that one of them didn't look too pleased when he tried to lift one of the bags we put out. We've been donating anything with think might still be useful, and we have people who drive through our subdivision on garbage days looking for interesting finds. More power to them, I say; I'd rather someone finds some use for this stuff instead of throwing it out. Besides, who's got the patience for a garage sale? And who really wants to deal with people trying to get half price for an item marked $1 that originally cost $50.

Since I've been involved with computers for quite some time (and not all of it as a developer), I've managed to collect quite a collection of old computers. Old Pentiums, 486's, a few 386's, motherboards, cases, power supplies, an unbelievable amount of cables, network cards, video cards, etc. I'm planning on posting that stuff on our local freecycle site to see if anyone might be interested in it before tossing it. One of my regrets with a lot of this is that I didn't give it away sooner, while it still may have been of more use to someone. I guess that may have been why I kept it.

A big part of this collection is a ton of books and magazines. I've whittled the magazines down to something manageable, but I still have way too many books. I'm sure I'll add more to the list as soon as I can convince myself that I really don't need them anymore, and once I have time to go through the ones still hiding in the basement (and hopefully before some of them aren't useful anymore). Here's a list of what's on the chopping block (you might be surprised; there are some good books here):

  • Apple II Plus/IIe Troubleshooting & Repair Guide, Robert C. Brenner. Sams. ISBN: 0-672-22353-8
  • DNS and BIND 3rd Edition, Paul Albitz & Cricket Liu. O'Reilly. ISBN: 1-56592-512-2
  • XML Extensible Markup Language (w/CD), Elliotte Rusty Harold. IDG Books. ISBN: 0-7645-3199-9
  • The Unified Modeling Language User Guide, Booch, Rumbaugh, Jacobson. Addison-Wesley. ISBN: 0-201-57168-4
  • The Visual FoxPro 3 Codebook (CD is missing), Yair Aan Griver. Sybex. ISBN: 0-7821-1648-5
  • Object Orientation in Visual Foxpro, Savannah Brentnall. Addison-Wesley. ISBN: 0-201-47943-5
  • Object Models: Strategies, Patterns, & Applications (Second Edition), Coad, North, Mayfield. Yourdon Press. ISBN: 0-13-840117-9
  • Visual Basic 6 Business Objects, Rockford Lhotka. Wrox. ISBN: 1-861001-07-X
  • ASP.NET 2.0 Unleashed, Stephen Walther. Sams. ISBN: 0-672-32823-2
  • Hacker's Guide to Visual FoxPro 6.0, Granor, Roche. Hentzenwerke Publishing. ISBN: 0-96550-936-2
  • The Inmates Are Running The Asylum, Alan Cooper. Sams. ISBN: 0-672-31649-8
  • About Face: The Essentials of User Interface Design, Alan Cooper. IDG Books. ISBN: 1-56884-322-4
  • The Improvement Guide, Langley, Nolan, Nolan, Normal, Provost. Jossey-Bass. ISBN: 0-7879-0257-8
  • HTML: The Complete Reference (Second Edition), Thomas A. Powell. Osborne. ISBN: 0-07-211977-2
  • Effective Techniques for Application Development w/VFP 6.0, Booth, Sawyer. Hentzenwerke. ISBN: 0-96550-937-0
  • What's New in Visual FoxPro 8.0, Granor, Hennig. Hentzenwerke. ISBN: 1-930919-40-9
  • CrysDev: A Developer's Guide to Integrating Crystal Reports, Craig Berntson. Hentzenwerke. ISBN: 1-930919-38-7
  • Advanced Object Oriented Programming w/VFP 6, Egger.  Hentzenwerke. ISBN: 0-96550-938-9
  • Client/Server Applications w/VFP & SQL Server, Urwiler, DeWitt, Ley, Koorhan. Hentzenwerke. ISBN: 1-930919-01-8
  • C# Unleashed, Joseph Mayo. Sams. ISBN: 0-672-321-22-X
  • Measuring and Managing Performance in Organizations, Robert D. Austin. Dorset House. ISBN: 0-932633-36-6
  • Windows 2000 Server Resource Kit (No CD), 8 books total
     

If anyone might be interested in this stuff (books or computer techno-rubble), drop me a line (I can take a pic. of the computer stuff). All of it free as long as you pick up the shipping cost. I hope I don't regret giving away some of this, these books have served me well <g>

 

Links:

http://www.freecycle.org

Wednesday, December 12, 2007 10:23:03 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] -

.NET | Other | VFP
 Thursday, November 15, 2007

It looks like XSource for VFP 9 SP2 is now up on the Microsoft website:

http://www.microsoft.com/downloads/details.aspx?familyid=320b6438-be76-48c7-a68e-1792e2aa3bf2&displaylang=en

The interesting thing about this is that it's now licensed under the Microsoft Permission License so you can now distribute it or your own custom modifications of any of the tools included in it. This is basically all the source code for things like the DataExplorer, the Report Builder/Output/Preview controls, the Task Pane, etc.

Reporting bug in VFP 9 SP2

Cathy Pountney has an article regarding a new bug introduced into the reporting engine in SP2 using grouping and group headers.

http://cathypountney.blogspot.com/2007/11/gotcha-serious-report-bug-with-data.html

Thursday, November 15, 2007 9:32:01 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] -

VFP
 Friday, November 02, 2007

One thing I've learned while using SQL Server is (whenever possible), perform as much as you can on the server. That means either stored procedures or SQL commands passed up to the server. It also means thinking about problems as sets, as opposed to a more procedural approach. Visual FoxPro makes it easy to blur the lines between SQL commands and normal procedural code (mixing and matching as you'd like), so you tend to start getting locked into thinking about data on those terms. While some of this translates directly to SQL Server (obviously, the SQL language itself), other things don't.

I recently decided to add a new foreign key to a table; the structural specifics aren't particularly important so I won't go into too much detail about them. Essentially, I had a parent (A) and child table (B). There is a posting process which takes a snapshot (C) of some or all of the records in the child table for a specific parent. This snapshot includes a foreign key back to the original child record but didn't include a foreign key back to the parent (since it could be derived by joining to this child table). I began to realize that, because of the number of records involved (it could be as many as 250,000 records in the child (B) for a single parent record (A), which meant up to 250,000 records in our snapshot table) that most of the queries were really slow because of the extra hop involved in resolving this parent key. So, I decided to go ahead and add a foreign key into the snapshot table (C) to the parent (A). That also meant I needed to go back and fill in the new foreign key in the Snapshot (C) table.

 

Table

 

In VFP, you might just open the tables, SCAN through the Snapshot (C) table, do a SEEK into the Child (B) table for the foreign key back to the Parent (A) table and save it into the Snapshot (C) table. That works OK if the tables are local, what about with SQL Server? I guess you could take the same approach; pull a copy of the records from the Snapshot (C) and Child (B) tables locally, index them, then run through the same SCAN loop (or if you're really old school, set a relation and just do a REPLACE). But how long will that take - remember we're talking about pulling down up to 250,000 records in the Child (B) table, plus another 250,000 records from the Snapshot (C) table, then updating all of the records in Snapshot (C) table. Multiply that by the number of master records (in my case, only 20-30 of the master records had anywhere near 250,000 records but we're still talking millions of records).

Suddenly the above approach doesn't look like such a good idea. Thankfully, SQL includes provisions in the UPDATE command to do exactly what I wanted to. It would still take a while to run (I clocked it around 25 minutes), but it was a significantly better solution than a SCAN loop. Here's what the query ends up looking like:

UPDATE Snapshot
   SET Snapshot.fk_Parent = Child.fk_Parent
  FROM Child
INNER JOIN Snapshot
    ON Snapshot.fk_Child = Child.PrimaryKey

 

So what does this actually mean? We're basically saying, inside of our Snapshot (C) table set our new foreign key equal to the Parent (A) foreign key in the Child (B) table. The FROM/INNER JOIN defines how to match the record in the Snapshot (C) to the one in Child (B). Simple and fast.

Friday, November 02, 2007 8:31:41 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2] -

SQL | VFP
 Monday, October 29, 2007

There have been a lot of great sessions done over the years about Intellisense, builders, etc. (usually by Rick Schummer or Doug Hennig). Every time I've been able to sit in on these sessions, I end up leaving all geeked up to write some cool routine or code that will make my life easier. A few days go by and reality sets in - I don't really have the time to actually write any of that stuff. 6 months go by, someone shows some other slick utility they wrote and I get all excited about it again.

With that in mind, here are a few easy changes you can make (that won't take more than a few minutes) that can help save you some time. Just think how many times a day you type some of these things:

Tools > Intellisense Manager > Custom

IntellisenseManager

CreateObject

In the Replace field, enter CO in the With field, enter CREATEOBJECT. Change the Type combo to Function. Then click Add.

MessageBox

Now type MB in the Replace field and MESSAGEBOX in the With field, again make sure the type combo is set to Function.

Now close the Intellisense Manager. At the command window enter:

USE (_foxcode) IN 0 AGAIN SHARED
SELECT FoxCode
APPEND BLANK
BROWSE

IF ENDIF

In the new record enter:

Type: U
Abbrev: IF
Cmd: {stmthandler}
Data: Double click on it and enter:
IF ~
<<lcSpace>>ENDIF


Save: T
The timestamp & UniqueID are optional.

SCAN ENDSCAN

APPEND BLANK

Type: U
Abbrev: SCAN
Cmd: {stmthandler}
Data: Double click on it and enter:
SCAN ~
<<lcSpace>>ENDSCAN


FOR ENDFOR

APPEND BLANK

Type: U
Abbrev: FOR
Cmd: {stmthandler}
Data: Double click on it and enter:
FOR ~
<<lcSpace>>ENDFOR


WITH

(can you guess?)

APPEND BLANK

Type: U
Abbrev: WITH
Cmd: {stmthandler}
Data: Double click on it and enter:
WITH ~
<<lcSpace>>ENDWITH

TRY CATCH

APPEND BLANK

Type: U
Abbrev: TC
Cmd: {stmthandler}
Data: Double click on it and enter:
TRY
   ~
CATCH TO loEx
ENDTRY

TRY CATCH FINALLY

APPEND BLANK

Type: U
Abbrev: TCF
Cmd: {stmthandler}
Data: Double click on it and enter:
TRY
   ~
CATCH TO loEx
FINALLY
ENDTRY

Close the browse window and the foxcode table.

Hotkeys:

This one will reset the dev. environment for you.

Select Tools > Macros. Click on New. Hit F4 as the key, enter "ClearIt" as the macro name. Paste this into the macro contents:

_genscrn=_foxcode{ENTER}
_foxcode{SPACEBAR}={SPACEBAR}""{ENTER}
set{SPACEBAR}development{SPACEBAR}on{ENTER}
execscript("do{SPACEBAR}while{SPACEBAR}txnlevel(0{BACKSPACE})>0"+chr(13)+"rollback"+chr(13)+"enddo"){ENTER}
clear{SPACEBAR}all{ENTER}
release{SPACEBAR}all{ENTER}
clear{SPACEBAR}program{ENTER}
set{SPACEBAR}procedure{SPACEBAR}to{ENTER}
set{SPACEBAR}classlib{SPACEBAR}to{ENTER}
clear{ENTER}
_foxcode=_genscrn{ENTER}

 

Hit OK.


Now try everything out - open a program window:

CO(
MB(


Hit the spacebar after these:

IF
SCAN
FOR
WITH
TC
TCF

 

Links

http://rickschummer.com/blog
http://doughennig.blogspot.com

Monday, October 29, 2007 8:40:50 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] -

VFP
 Monday, October 22, 2007

I've had these notes about optimizing VFP code hanging around a long time (and added a few a little more recently). According to the date on it, most of this is from 8/1/2002. Wow!

Most of this came out of a data conversion project that would take 3-4 hours each time it was run (and during development we needed to run this a LOT so 3-4 hours would just kill productivity).

  • () evals are much faster than macro substitution:

    REPLACE (lcField) WITH 1

    instead of

    REPLACE &lcField WITH 1

    2.5 - 3X faster

    Directly accessing a field:

    REPLACE field WITH 1

    is 8-10X faster than the REPLACE (lcField) WITH 1 code.

  • Do multiple field updates in a single REPLACE.

    For example:

    REPLACE field WITH 1, field2 WITH 2, field3 WITH 3 IN TableName

    Instead of:

    REPLACE field1 WITH 1 IN TableName
    REPLACE field2 WITH 2 IN TableName
    REPLACE field3 WITH 3 IN TableName


  • If you are doing COM interop, use WITH/ENDWITH instead of directly accessing object references - this keeps VFP from having to traverse the object hierarchy on each line. This can be significantly faster.

  • SCATTER MEMVAR is up to 10X faster than SCATTER NAME, although SCATTER NAME provides better encapsulation. If you are doing this for a large number of records, SCATTER MEMVAR may be a much better choice.

  • If you are doing a SELECT ... WHERE IN (SELECT ...) style query, check to see if it's functionally equivalent to an INNER JOIN. This can make a HUGE difference.

  • CREATEOBJECT() is much faster than NEWOBJECT().

  • Opening and closing tables is one of the slowest thing you can do. In the data conversion app. we had some code that opened a lookup table at the beginning of the function, then closed it at the end. The data conversion process was spending approx. 45 minutes of time in this routine alone. By opening the tables only once (and leaving them open), the processing time dropped to under 3 minutes (It was so much faster that I had to test this a few times to make sure I hadn't broken it).

 

OK, nothing really earth shattering here. But hopefully you'll find one or two of the above helpful at some point.

Monday, October 22, 2007 9:23:45 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] -

VFP
 Friday, October 12, 2007

We had our monthly FoxPro user group meeting last night, and, like always, there were some cool new things shown and talked about that I’ve now got to play with. Cathy Pountney was kind enough to drive over from the Grand Rapids area to do a session on extending VFP 9’s report writer through extensions and hooking into the generation process with listeners. She demo’d some code which added a new print dialog box which allowed the user to pick/configure the printer before printing, being able to print a document range (ex. 1-10, 11), “Print to Fit” which resizes a report to fit on 1 page, duplex printing, booklet printing, and being able to print multiple pages per sheet of paper. Really cool stuff. She’ll be giving this same presentation at Southwest Fox next week, so if you’re planning on going, don’t miss this session.

One of the “hey, I didn’t know about that command” that came up was SYS(2600). This command lets you pass in a integer (pointer) and then returns the memory pointed to by the pointer as a string. It can also write data into the memory point. This is pretty slick since it allows you to easily interface with win32 API calls that require you to pass in pointers to structures, or use API calls that return pointers to structures. By setting/getting this memory as a string, you can then easily parse apart the structure to get the data back.

To be able to do anything useful with this, you’re also going to need the ability to allocate/de-allocate some memory (and get a pointer to it). The easiest way to do this is through a few API calls:

#DEFINE GMEM_ZEROINIT     0x40    && Init memory contents to zero

DECLARE INTEGER GlobalAlloc IN kernel32.dll ;
        INTEGER wFlags, ;
        INTEGER dwBytes       

DECLARE INTEGER GlobalFree IN kernel32.dll ;
        INTEGER hMem 

lnPointer = GlobalAlloc(GMEM_ZEROINIT, 110)

?lnPointer

IF lnPointer > 0

   * You should see a a bunch of squares (nulls)

   ?SYS(2600, lnPointer, 110)
   SYS(2600, lnPointer, 100, REPLICATE("X", 100))

   * Now you should see 100 X's followed by 10 nulls

   ?SYS(2600, lnPointer, 110) 

   GlobalFree(lnPointer)

ENDIF          

A side note: You may see some apps use LocalAlloc/LocalFree instead of GlobalAlloc/GlobalFree. They are essentially the same thing at this point (that is, there isn’t any difference between the two of them). This is one of those “backwards compatibility” things. If you want to know more: http://msdn2.microsoft.com/en-us/library/ms810603.aspx

There were a couple of other cool utilities/products that came up as well that I’ll be trying out/writing about.

Friday, October 12, 2007 6:56:57 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2] -

VFP


Navigation
Archive
<October 2008>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2008
Paul Mrozowski / RCS Solutions, Inc.
Sign In
Statistics
Total Posts: 57
This Year: 32
This Month: 0
This Week: 0
Comments: 21
All Content © 2008, Paul Mrozowski / RCS Solutions, Inc.
DasBlog theme 'Business' created by Christoph De Baene (delarou)