24
Advanced Programming Concepts The Thought Behind The Code Gary Stark Advanced Programming Concepts Advanced Programming Concepts: what am I talking about today? I consider that advanced programming concepts are more than just the code that we write: they include the methods that we use to assist us in our work of designing and building applications for our clients. We should be improving our productivity. We need to be making our applications more usable by our clients. They need to be sound and robust; our applications should be reliable. In doing our jobs we need to ensure that we can work in the most effective way possible, and using the techniques that I shall describe today will, I hope, assist you in achieving this goal. Please note that nothing that we're going to discuss here will be rocket science, all of it is fairly simple stuff, but some of it may not be so obvious. Some of it you may have already seen, some not. What I shall be discussing relates quite a bit to theoretical concepts rather than language specifics. As such, the concepts are not platform specific. If you’re using VB, VO, Delphi, or maybe even Clipper, than these concepts will still work for you. The ideas that I'm going to present to you are those that I've seen and usually stolen from other people. They work for me, and they may work for you. On the other hand, they may not, and I promise that I won't get upset if you choose to ignore me. Please feel free to interrupt at any time if you agree, disagree, or just want to pass a general comment. _____________________________________________________________ ____________ Advanced Programming Concepts The Thought Behind The Code 1

Advanced Programming Concepts

Embed Size (px)

Citation preview

Page 1: Advanced Programming Concepts

Advanced Programming ConceptsThe Thought Behind The Code

Gary Stark

Advanced Programming Concepts

Advanced Programming Concepts: what am I talking about today? I consider that advanced programming concepts are more than just the code that we write: they include the methods that we use to assist us in our work of designing and building applications for our clients.

We should be improving our productivity.

We need to be making our applications more usable by our clients.

They need to be sound and robust; our applications should be reliable.

In doing our jobs we need to ensure that we can work in the most effective way possible, and using the techniques that I shall describe today will, I hope, assist you in achieving this goal.

Please note that nothing that we're going to discuss here will be rocket science, all of it is fairly simple stuff, but some of it may not be so obvious. Some of it you may have already seen, some not. What I shall be discussing relates quite a bit to theoretical concepts rather than language specifics. As such, the concepts are not platform specific. If you’re using VB, VO, Delphi, or maybe even Clipper, than these concepts will still work for you.

The ideas that I'm going to present to you are those that I've seen and usually stolen from other people. They work for me, and they may work for you. On the other hand, they may not, and I promise that I won't get upset if you choose to ignore me. Please feel free to interrupt at any time if you agree, disagree, or just want to pass a general comment.

Always an easier way.

First, we need to define how we can make ourselves more effective in our tasks. Many of us might say that we need to reduce our development time, so that we can get on with the job and complete the project as quickly as possible, but this is only partly correct. While we certainly need to do this, our tasks are not self-fulfilling. We also need to bear in mind that our clients – those people who are ultimately paying for our services – need to have usable applications.

_________________________________________________________________________

Advanced Programming Concepts

The Thought Behind The Code 1

Page 2: Advanced Programming Concepts

So, although it is good to use the latest tools and techniques, we do need to be constantly aware of the final target. The greatest, most wonderful development environment is completely worthless to us if it doesn’t help us to produce applications that our end users want to use, or can use.

Let me illustrate with a couple of examples.

Several years ago I was living in California; I was working for one organization, who insisted that I use one particular tool within Clipper to develop my screens. While the theory behind the tool was somewhat sound, the code it generated was ugly, repetitive, and slow. While the tool aided somewhat in screen design, the clean-up work one had to do to make the application work properly went far beyond what I would call reasonable. It caused more work than it saved; it was clearly counter productive.

In a similar vein, there are probably a few Clipper programmers here. As good as the Clipper 5.3 IDE is, how many of us, especially those of us who have been using Clipper for any substantial amount of time, actually used it? My personal copy of 5.3 is still in its shrink wrap, in fact.

The simple fact is that perhaps these tools fall far short of their real goals, and development of a complex application usually cannot be simplified to a few simple screens and accommodated within the confines of an application template.

Many of the older style code generators provided insertion points for including your own code; this addressed the problem of including custom code. At least according to the developers of these products it did. With many of these products though you then needed to always re-generate the complete application, which then might have required you to completely re-compile the whole application.

With today’s Windows based IDE products, such as VB, Delphi, VC, and VO, many of these issues seem to have been addressed. The tools are far better, but even today they still don’t really permit generation of even 60% of the total code of an even moderately complex application.

Sure, we can take advantage of OOP techniques, and establish a real means of utilizing and reusing existing code. Too, we can also take advantage of a vast array of third party tools, be they OCX, DLLs, native source, or whatever. While reinventing the wheel is less of an issue today, there is no 100% solution that I have seen.

It's really a cost-benefit situation that each of us needs to judge for ourselves; perhaps we prefer to do it this way, perhaps not, but it's important for us to be aware of the complete situation – and all of the ramifications - before we go in and start working in a particular way, so that we don't spend a lot of time doing something the wrong way.

What I guess I'm saying here is not that you shouldn't use any particular tool, but that you should really consider at which point, when you use it, you decide that the benefits of its use have been exhausted from a practical point of view..

Work efficiently and effectively; take control.

OK, so we're trying to make our work a little easier for ourselves, but is that all there is? I think not.

I also think that much of what we need to do is to make our applications work in the most efficient way possible.

_________________________________________________________________________

2 Gary Stark

Page 3: Advanced Programming Concepts

Performance is important! If our applications don't perform, the our users will form a poor impression of us and the work that we do, and excuses for poor performance are not satisfactory.

We need to take full control of the situation and actively seek to make both our work habits, and our applications, the best that we possibly can.

Know your tools

Let's start with the language. We need to have a good understanding of the language and tools that we're using. This is essential.

If you needed heart surgery you'd feel a whole lot better knowing that your doctor had performed the procedure successfully many times before, wouldn't you. You should consider yourselves as the "doctors" for the data processing needs of your clients' businesses, hopefully coming in to cure any diseases that might be encountered.

Similarly we must be comfortable with our language and our tools. I'm certainly not saying that we need to be able to recite the manuals. In fact, I think I’d be terrified of someone who could.

But I'd like to think that we all knew and understood how to create a treeview or listview by hand coding, for example. For those marginal functions that we only need once in a while, I'd expect you know if, for example, you could do it within your chosen language discipline, or if not which third party tool might support it.

And how it would need to be implemented.

So, you need to know where to look, and for what, if you don't have the answer in your head.

Case study

Several years ago I encountered a problem with a user of an application of mine, who had engaged a so-called consultant to install a LAN on their site. From the nature of the work being performed it quickly became obvious that this person perhaps didn't quite have what I considered to be the requisite knowledge to do the job.

She was installing a peer-to-peer LAN onto the two machines at this site; one was a 486, the other a 286. As I said, this was a few years ago. My application was resident on the 286, which was acting as a server to provide printing services to the system.

The problem was that with a 286 we couldn't load anything high, but we needed the LAN server and terminal software present to provide the support to the LAN. Of course loading the LAN software in conventional memory meant that we reduced that resource, and things being what they are this was reduced to a level below that which was specified in order for my application to load.

I explained the problem patiently to the consultant, and when she realized that the problem was simply that we needed to provide more memory for the app to run in the resolution of the problem became obvious to her - replace the 286 with a 486 with 4Mb of RAM!

_________________________________________________________________________

Advanced Programming Concepts

The Thought Behind The Code 3

Page 4: Advanced Programming Concepts

Unfortunately, I was thinking along different lines, and countered that if the only services being provided to the LAN by the 286 were printing services, perhaps we could install a second printer card in the 486 and remove the server software from the 286, thus providing our user with the head room needed for the app to load and run.

We went down that route, and I had no further support calls from that client. For the record, I asked the consultant how many LAN installations she'd performed; this was her first one.

The real point here is that we all need to be well versed in all aspects of the environment that we work within. Whether it's Windows 98, NT or 2000, OS/2, UNIX, Fox, Visual Basic, Visual Objects, Delphi, or whatever, we need to fully understand everything that's happening and that can happen.

And that we might not expect to happen.

Importantly, we need to explore not just the obvious solution, but to step back a little and look over our shoulders; perhaps there's a better solution.

Think about what you're doing.

We always say that we need to define the problem before we can start our coding, and this is just so very true. The importance of a well defined system specification cannot be over-emphasized, but it doesn't just stop there.

We should carefully consider each and every character of source code that we write before we commit it to the keyboard.

I sometimes need to take time away from my keyboard to just explore the logic paths of what I’m doing. Right now I live fairly close to a beach. I will go for a walk there when I have some difficult or unusual task that needs completion. If it’s an urgent or very complex problem, then I'll probably take a longer walk!

I like to consider different ways of doing things, because some are inherently better than others, and I do like to try to use the best of the alternatives available. One of the nice things about what we do is that we can actually experiment with things, try out some new techniques, do something a different way, and it usually won't cause too many problems.

If you're a heart surgeon a new method that fails might be fatal for the poor soul that you're experimenting on, but in our work the worst that might happen is that you'll freeze up your machine or perhaps lose some test data. No big deal so long as your backups are secure.

Look for the best way; experiment.

So the first couple of things you need to do are to take your time, and think seriously about the problem, then explore and test the alternatives. Look for some alternative solutions; don't just simply say that because I did it this way last time, this way is the best way.

I've lost count of the number of times I've seen messages on the various forums asking if you can do this, or that. The answer is obvious to me, but sometimes needs to be spelled out.

_________________________________________________________________________

4 Gary Stark

Page 5: Advanced Programming Concepts

Try it for yourself and see. Even if I tell you it can be done, and even if I tell you how to do it, you won't believe me and you'll still need to try it for yourself.

When was the last time you copied a file from one place to another? What was the first thing you did after that? "Dir", right? See? When was the last time DOS didn't copy the file? If you don't trust DOS, you sure as hell won't trust me, so you may as well start doing things for yourself today.

Something simple

Let's look at a little code. How many times have you seen this sort of code :

If lTest lTest := FALSE Else lTest := TRUEEnd

There's nothing really wrong with this code, and it works just fine. It's clear, simple and concise. It's really quite obvious what it does.

So is this:

lTest := Iif( lTest, FALSE, TRUE )

And to me it's equally clear, equally concise, perhaps to me slightly simpler. I prefer it, and being inherently lazy there's less code for me to write, so it must be better. Perhaps it may even run a little faster, in this example it wouldn't really make much difference anyway.

But both of these examples of code are simply switching the value of the flag from one value to its opposite value, like this:

lTest := !lTest

All three of these do exactly the same thing. All three of these are perfectly acceptable ways of achieving the desired goal. Although I personally prefer the last of these, I would not dissuade anyone from using any of these examples, but I would encourage you to explore them.

If there's at least 3 different ways to do something as simple as this, can you imagine how many different ways there are to perform complex tasks? And none of them are wrong!

I accept that there's very little between these examples, but I think that we can see that there's probably many different ways we can perform different tasks. In each case perhaps the best method is for us to decide, and there’s no really easy way that we can make that determination. We need to look beyond what involves not just the least code, but also perhaps the best performance.

In this example what I've gained is that I've simplified the work that I need to do, and at the same time I've improved performance for my end-user. While these will not make much difference to an app on their own,

_________________________________________________________________________

Advanced Programming Concepts

The Thought Behind The Code 5

Page 6: Advanced Programming Concepts

consider the ripple effect of extending this over the whole project. Perhaps there may be significant improvements to be achieved!

Inheritance

Sorry, but you VBers out there will have to wait until the next version before you can start to gain the benefits of true object oriented technology, but there are real benefits to be had here.

Consider this situation: I was recently conducting a code review of an application that a colleague had purchased the source to. Within this source, the application generated a number of windows.

A very large number of windows.

Many of these windows were sub-classed, but unfortunately, not all that efficiently. For instance, there was a window that was used for creation of products within an inventory file. This window was sub-classed and a derived class was used as the actual runtime window for adding a product.

For editing the product file items, there was another primary window, with a similar derivative window that was used at runtime. An issue that I observed was that both of these parent class windows were identical.

Clearly, one had been cloned from the other. It was a very simple matter for me to delete one of the parent classes, and switch the inheritance chain to point towards the remaining parent class.

While that solution partially addressed this particular issue, I could have gone a fair bit further. Many of the methods used in the creation functionality will be similar, if not identical, to the those needed during editing. A more effective solution might have been to consolidate the windows completely, sharing functionality but diverging when necessary based upon the current mode of the window.

But for now, I’m actually digressing.

As I examined this application and its component windows, I saw many window methods that contained identical code. Identical method names; identical code: identical functionality.

Clearly, there was a case here for creating a custom window class that contained the basic common methods that were needed, and to build the runtime windows by inheriting from that custom window class. Had the original author of this application gone down that path, he would surely have made his job a whole lot easier than was ultimately the case.

But Wait: There’s More!

The author’s poor use of OOP techniques extended beyond just this simple area. Within many of these windows this person had placed some treeviews. Not a problem in and of itself.

For each of these treeviews within each of these windows he had the same piece of code – duplicated for each window – that would force the treeview to drill down and open every item.

OK, I might query the real practicality of that functionality, but that’s a different discussion entirely.

_________________________________________________________________________

6 Gary Stark

Page 7: Advanced Programming Concepts

But although, as I mentioned before, that sort of common functionality might have been somewhat better placed within a base custom window class, I felt that an even better way to deal with that functionality was to place it back within the confines of the treeview itself.

To me, that is much more in keeping with the concepts of what OOP techniques are all about.

What we can do is create our own custom treeview class, with its own custom methods as desired. Any treeviews that we then create that need that additional functionality can inherit from our custom class, and will immediately have access to this expanded functionality set.

The properties and methods in this case belong therefore at the object level, not at the window or application level.

And again, this – the concept of creating both the custom treeview and the custom window – comes back to a concept I described a little earlier: that of thinking about the work that you’re doing, and making sure that you find and implement an appropriate solution for the problem at hand.

Exploit it.

Let me expand this a little further.

Our first consideration needs to contend with where to place the validation code. Does this code belong at the point of data entry? First indications might suggest that this in fact is the case, but I would suggest that this may not in fact be true.

Data validation is sometimes an issue of data, and others an issue of business rules. Often it’s both.

So we need to consider the most appropriate placement of the validation routines. While it may appear more expedient to make it a part of the data entry functionality, perhaps it’s more correct to do this within the data server.

So, what’s my point?

Again, this comes back to the same concept that I described earlier: that of thinking about the work that you’re doing, and making sure that you find and implement an appropriate solution for the problem at hand.

I hope that you’re beginning to see some sort of pattern emerging here.

_________________________________________________________________________

Advanced Programming Concepts

The Thought Behind The Code 7

Page 8: Advanced Programming Concepts

Can this be used in another way?

So now we've seen two ways that, by simply stepping back and thinking about the correct – or perhaps most appropriate processes or methodologies to use for some particular tasks, perhaps we need to now step back and ask ourselves the question, “How far should you go?”

How big is your imagination?

Let's consider for a moment some form of transaction processing, where we might need to update 3 tables upon data capture in a multi-user environment. If we can't get a lock on each of the records in the three tables, we simply cannot proceed.

In pseudo code, this might look something like this:

METHOD SaveData() Class winDataEntry

If SELF:Server1:RecLok

If SELF:Server2:RecLok

If SELF:Server3:RecLok

// Write Data SELF:Server1:WriteStuff()SELF:Server2:WriteStuff()SELF:Server3:WriteStuff()

SELF:Server1:Commit()SELF:Server2:Commit()SELF:Server3:Commit()

SELF:Server1:Unlock()SELF:Server2:Unlock()SELF:Server3:Unlock()

Else

// Handle the error

End

Else

// Handle the error

End

Else

// Handle the error

_________________________________________________________________________

8 Gary Stark

Page 9: Advanced Programming Concepts

End

While this is not too bad, it lives at the data entry point. As long as the data comes in through the standard data entry point (i.e, the data entry window), then it will be executed.

But what if we choose to accept data through other means, such as related applications sending us data, or through automated email or FTP interfaces, this methodology may not work. Coming back to an example I described earlier, if we move this transaction processing logic from the data entry point back into the data server, then it becomes a property of the data itself.

In this way, it is then available at every data receipt point, and needs to no longer be re-implemented.

I leave it you to judge which is a better or easier way for you to code, and which you may consider more readable or more reliable in operation in a production system.

It's all to easy to forget one line when you need to write everything out every time; if you can encapsulate it all into a few small, neat reusable packages, then why not? And if you can embody this all within the most appropriate location for it, then so much the better.

Send yourself a nicely wrapped package.

How about those times when you might like to use some obtuse Windows SDK function. You know the type: the ones with 2,500 parameters. And of course, the Windows documentation leaves you more than a little confused about the correct usage of it.

The deal here is to first of all understand what the function can do for you, and to get it working the way that you want it to.

Once you have done that, what you can do is place the actual call to the Windows function within your own wrapper function, with a simplified subset of parameters that you will have a better chance of understanding or remembering.

Of course, this might not be just restricted to just the one function . There may be several that need to be called in order to get something done, and a wrapper function is an ideal way of addressing this problem.

If this is something that you might wish to have happen under a particular set of circumstances, or perhaps always, then your wrapper might even form a part of some base class. Let me illustrate again with an example and some code.

The situation is a standard data entry window. The standard data window class that VO gives us is one that is resizable. That is all well and good, but when you spend hours slaving over a hot keyboard to get the design of a particular window just right, the last thing I want is for some end user to go and mess that up by not just resizing that window, but in so doing hiding some of the controls that I’ve given them, thereby potentially compromising the data entry process.

_________________________________________________________________________

Advanced Programming Concepts

The Thought Behind The Code 9

Page 10: Advanced Programming Concepts

For some strange reason, I really don’t like the thought of that, so I have created my own base data entry window class that inherits from the standard one. In order to change the window’s style I need to call a couple of Windows functions.

While I’m at it, I might not like where Windows might arbitrarily decide it wants to place my window on the user’s screen, so I can deal with this at the same time.

CLASS GSWindow INHERIT DataWindow

// Nothing unusual here

As we can see from this class declaration, we’re really not adding anything dramatic to this window. Not yet, anyway. What we’re doing here, after all, is just providing some simple wrapper functionality.

METHOD INit( oWindow,iCtlID,oServer,uExtra ) CLASS GSWindow

LOCAL oOrigin AS OBJECT

// let the base class handle the basicsSUPER:INit( oWindow,iCtlID,oServer,uExtra )

// Now let’s put the window in a known location.// relative to its owner is good.oOrigin := oWindow:OriginSELF:Origin := point{ oOrigin:x + 8, oOrigin:y + 8 }

// Now we can deal with the sizing bordersSELF:EnableBorder( WINDOWNONSIZINGBORDER )SELF:EnableMaxBox( FALSE )

// This makes it all happen.SetWindowPos( SELF:Handle(), NULL_PTR, 0, 0, 0, 0, ;

_or( SWP_FRAMECHANGED, SWP_NOZORDER, SWP_NOSIZE, SWP_NOMOVE ) )

The initialization function here is our wrapper for some Windows API calls, plus it deals with my window positioning issue. First of all, we need to create the window, which we do by passing the call back up the inheritance chain.

In order to place the window in a consistent position on our user’s screen, I’ve decided to make this placement relative to the window’s owner. This is pretty easy stuff. I get the owner window’s origin location, and set my window’s origin location relative to the owner’s.

To address the real problem that I’m trying to solve, I then call some Windows API functions. I first of all change the border, then disable the maximize box in the window’s caption bar. These two calls have the effect of allowing my end users two options when viewing this window: it can be seen at the size I’ve created it at, or minimized.

_________________________________________________________________________

10 Gary Stark

Page 11: Advanced Programming Concepts

Or closed.

Finally, I need to redraw the window with it’s new settings. SetWindowPos() is the API call that does that for me. Note the number of parameters it uses. Trying to remember and understand what they all mean is beyond me. I rarely use this call directly. But I do use it, and I use it in this exact way frequently.

Clearly, placing it in a wrapper is an ideal way to use it. And considering that I use it in conjunction with a couple of other API calls helps, even more, to illustrate the need for this wrapper call.

Now, in VO we can see how I’ve used the inheritance tree to incorporate this into my own window class; every time I create a window for which I want to have this behavior, I just make my window a derivative class of this one.

In VB, at this time, we cannot do this in this manner. While that will change, there are other ways we can address this problem within that environment.

We could create a similar method of the Window object there, but of course because VB has no inheritance we would need to that in each and every case that we want this behavior. Alternatively, we could – and probably should – still place these specific calls within a wrapper function that our window can call. That way we’re dealing with the complexities of the API calls without having to explicitly worry about them.

Or, in this particular case, we could just change the specific properties for that form within the VB IDE. This is the probably the easiest and most appropriate means for dealing with this problem within VB, but is it easier to do than inheritance? That becomes a question for you to answer.

I've suggested to you that having found a good technique you should exploit it, and the creation of wrapper functions is one such technique. It will work just about anywhere, and within just about any language that you might be using.

It will save you heaps of code, and make your code more reliable, more readable.

And more usable.

Be your own worst critic.

Always look critically at your code and see if what you have done well in one way can be applied elsewhere.

In dealing with the example that we just discussed, we observed that I was making my window placement in a consistent and predictable manner. We could extend this practice to other aspects of our application too, couldn’t we?

Take, in VO parlance, a Dialog window. It too sometimes suffers the same problem of random and unpredictable window placement. While it’s all very well to say “That’s how Windows does it”, is that really a good enough answer? How do you think that makes your users feel about you and your work? Is that a truly professional approach?

Personally, I would rather try and deal with this in a proactive manner, so that the problem simply doesn’t arise.

_________________________________________________________________________

Advanced Programming Concepts

The Thought Behind The Code 11

Page 12: Advanced Programming Concepts

So, I can create now my own Dialog Window base class, and make sure of the most appropriate – for my needs – window placement in whatever means I feel is appropriate.

Again, see how I'm thinking, looking for somewhere else to apply a particular technique.

Arrays and Variants

Let's now talk about flexible data types In VO we might be talking about arrays; and in VB, variants.

If you're not exploiting these powerful language features, then you're really missing out on some of the most powerful features of these languages.

I'm going to show you a different way of preparing reports. We'll take advantage of VO’s array handling capabilities to build our reports in memory, before we commit them to any form of output device.

What we're about to discuss is a couple different ways of getting our output to the screen, to the printer, to a file, whatever. Often we'll have heaps of data, frequently from more than one table, and frequently we may have to traverse our tables more than once to get all of the data that we need.

Ever had a problem where you need to process your data and prepare your report, but have needed to maybe get some element of the data that only becomes evident when you've finally completed processing ALL of the data, and print that element on the FIRST page of the report?

How do you deal with this issue?

Most people just accept the fact that they have to process the data table twice, once to calculate the results, and a second time to actually produce the report.

With a small data table, this isn't a major issue, but what about a large table, lots of records, maybe lots of ancillary lookups to process on the way through. Maybe you don't have to process the lookups on one of the passes through the data; that can save some processing time, but the fundamental problem remains

Let's try a different question: your output specifications call for two entirely different reports, each based on the same data, sequenced in the same order too. Maybe it's three, or four, different reports. All based on the same data set.

Do we process the same data 2, 3 or 4 times?

Let's think about the problem, are there any alternatives available to us?

Set Alternate doesn't let us write to more than one alternate file at a time. We cannot switch from one to another; every time that we say Set Alternate To <FileName> we create a brand new file, erasing anything that was in that filename beforehand. That's really not much good to us.

One method that might be acceptable is to use the low-level file functions that Visual Objects provides us with. We can open virtually as many files as we need, writing our respective outputs to each of these files in turn. This works.

But we still have one problem, we still need to place some data on the first page of the report, but we only have access to that data after we have completed processing of all of the data within our tables.

_________________________________________________________________________

12 Gary Stark

Page 13: Advanced Programming Concepts

We could fSeek() the file position and re-write the data, but to my simple mind this seems fraught with danger - we might write to the wrong position, it could be fairly difficult to calculate the correct position to write to.

There's perhaps a better way to address this issue, and that is before you actually commit any of your output to an output device, commit it to an array that represents your printed output first.

So, instead of

Fwrite( nHandle, SomeTable:Field1 )

you might use code that looks like

aAdd( aOutputArray, SomeTable:Field1 )

One trick here might be that as you're adding your output data to your output array, pre-format it.

aAdd( aOutputArray, SomeTable:Field1 + " " + SomeTable:Field2 )

Of course, this technique does take a little time to write; you may need to experiment with the program a few times till you get the layout just right.

When you get to the point where you need to provide for that unknown data, maybe you just provide an empty line for it:

aAdd( aOutputArray, "" )nPlace := Len( aOutputArray )

and record the index into the array pointer, so that when you finally know what you want to write there, it becomes a simple assignment:

aOutputArray[ nPlace ] := nCalculatedValue

When you're finally ready to print your report, its simply a matter of traversing the array and sending the individual elements to your selected output device

For i := 1 to Len(aOutputArray) Fwrite( nHandle, aOutputArray[ i ] )Next

Of course, I also suggested that we may need to prepare different output layouts from the same data .... here's how we might do this from one pass of the data table:

LOCAL nHandle1 := 0 // First reportLOCAL nHandle2 := 0 // Second reportLOCAL nHandle3 := 0 // Third reportLOCAL aOutput1 := {} // First reportLOCAL aOutput2 := {} // Second reportLOCAL aOutput3 := {} // Third report

_________________________________________________________________________

Advanced Programming Concepts

The Thought Behind The Code 13

Page 14: Advanced Programming Concepts

LOCAL cCommon := "" // Data that is common among the reports

LOCAL nTotal := 0 // For the total

LOCAL oServer // the data source

oServer := CreateInstance( #SomeTable ) // Or however you wish to open itoServer:GoTop() // First record

// This report needs a total// at the topaAdd( aOutput2, "" )

// Process the dataWhile !oServer:Eof

cCommon := oServer:Fld1 + " " + oServer:Fld2 + " "

aAdd( aOutput1, cCommon + oServer:Fld3 )

aAdd( aOutput2, cCommon + oServer:Fld4 )

aAdd( aOutput3, cCommon + oServer:Fld5 + " " + oServer:Fld4 )

// accumulate for the total nTotal += oServer:Fld4

oServer:Skip( 1 )

End

// Assign the total to the ;// first element

aOutput2[ 1 ] := "Total value " + Str( nTotal, 6 )

// and write the output to three different files

nHandle1 := fCreate("File1.txt" )nHandle2 := fCreate("File2.txt" )nHandle3 := fCreate("File3.txt" )

aEval( aOutput1, {|e| fWrite( nHandle, e ) } )Eject

aEval( aOutput2, {|e| fWrite( nHandle2, e ) } )Eject

aEval( aOutput3, {|e| fWrite( nHandle2, e ) } )Eject

In Visual Objects we can take this approach to the next level. Firstly, we need to understand that each element of a Visual Objects array may be another array. Secondly, we need to consider that a report formatted as we have just done doesn't really look all that good, we haven't divided it into pages, it's just a long list of lines.

_________________________________________________________________________

14 Gary Stark

Page 15: Advanced Programming Concepts

Let's take a slightly different approach. Each line of our report will still be an array, but we'll now determine, in advance, how many lines we are going to print on each page. This means that we'll have an array of say, 60 lines, which will represent one page of our report.

For each additional page of our report we'll add another 60 line array, and our report then becomes an array, each element of which is a sub-array, being a page of the report. We reduce our overhead in our calls to aAdd, calling it once only for each page, rather than for each line, and we improve the appearance of our report, as we now have page and line controls built right in to the basic structure of the output.

And we can still address multiple reports with one pass of the data table, and we can still place totals or other dependent data anywhere we wish.

Here's our basic code:

// Report arraysLOCAL aOutput1 := {} LOCAL aOutput2 := {} LOCAL aOutput3 := {}

// Current page number counters LOCAL nPage1 := 0 LOCAL nPage2 := 0 LOCAL nPage3 := 0

// Current line number countersLOCAL nLine1 := 99 LOCAL nLine2 := 99 LOCAL nLine3 := 99

// Common dataLOCAL cCommon := "" // For a totalLOCAL nTotal := 0

// And a ServerLOCAL oServer

oServer := CreateInstance( #SomeTable )oServer:GoTop()

// Process the dataWhile ! oServer:Eof

If nLine1 >=60 // initialize a new page aAdd( aOutput1, ArrayNew(60) ) nPage1 := Len( aOutput1 ) nLine1 := 2

End

If nLine2 >=45 // initialize a new page aAdd( aOutput2, ArrayNew(45) )

_________________________________________________________________________

Advanced Programming Concepts

The Thought Behind The Code 15

Page 16: Advanced Programming Concepts

// this page is only 45 // lines long nPage2 := Len( aOutput2 ) nLine2 := 2

End

If nLine3 >=60 // initialize a new page aAdd( aOutput3, ArrayNew(60) ) nPage3 := Len( aOutput3 ) nLine3 := 2

End

// Assign the common data cCommon := oServer:Fld1 + " " + oServer:Fld2 + " "

// Assign the report // specific data aOutput1[nPage1, nLine1] := cCommon + oServer:Fld3

aOutput2[nPage2, nLine2] := cCommon + oServer:Fld4

aOutput3[nPage3, nLine3] := cCommon + oServer:Fld5 + " " + oServer:Fld4

// increment line pointers nLine1++ nLine2++ nLine3++

// accumulate for the total nTotal += oServer:Fld4

// Next record oServer:Skip( 1 )

End

// Assign the total to the // first line of page 1 on// output 2aOutput2[ 1, 1 ] := "Total value " + Str( nTotal, 6 )

We'll now write all of this output to the hard disk, from where we can the print it to the printer, screen, or copy the report file elsewhere at our leisure.

WrtRpt( "Report1", aOutput1 )WrtRpt( "Report2", aOutput2 )WrtRpt( "Report3", aOutput3 )

RETURN NIL

#Define CRLF := _Chr( 13 ) + _Chr( 10 )

_________________________________________________________________________

16 Gary Stark

Page 17: Advanced Programming Concepts

FUNCTION WrtRpt( cFlName, aReport )LOCAL nHandle := 0LOCAL i := 0LOCAL j := 0

nHandle := fCreate( cFlName )For i := 1 to Len( aReport ) For j := 1 to aLen( aReport[ i ] )

fWrite( nHandle, aReport[i, j] + CRLF ) NextNext

fClose( nHandle )

RETURN NIL

Again, we're carrying some overhead in our arrays, but we've scope here for some very flexible, and very powerful, reporting. We could initialize our page arrays to include page headers and footers, with the date and page numbers. And a form-feed character.

Maybe we only want to print selected pages from the report. Maybe only the last page. This is now fairly easy to achieve.

Conclusion

What I've shown you today is definitely not rocket science. They're simply a number of methods of completing some particular tasks. These are certainly not the only way that these tasks can be done, but I offer them to you as suggestions, with one, very much more important suggestion:

When you're writing your code, always think carefully about the task that's at hand. Consider that just because you've always done something in a particular way doesn't necessarily mean that the old way will be the correct way to do it now.

Consider the alternatives. Always.

Gary Stark is a qualified accountant from Australia who moved into the world of Data Processing in the early 1980s. He has been using PC's for most of that time, xBase technology since 1984, and Clipper since 1985. As a consultant in the PC world since 1986 he has projects completed for major Australian insurance companies and banks as well as for government and small business, He has also conducted training courses in Australia and the USA for a number of xBase related products.

In the US he has worked as a senior CA-Clipper analyst/programmer for the Gallo Winery, and for Dallas, Texas based Rent Roll Inc, a manufacturer of vertical market software for the real estate industry. As a co-author of the SAMS publication CA-Visual Objects Developer's Guide, he has spoken at Technicons and DevCons in the USA, U.K. and Germany, as well as to users groups in the USA, Europe and Australia. He

_________________________________________________________________________

Advanced Programming Concepts

The Thought Behind The Code 17

Page 18: Advanced Programming Concepts

has had articles published in Clipper Advisor and VO Developer Magazines, as well as numerous user group publications.

He is currently based in Sydney, Australia acting as an independent software developer and consultant. He can be contacted on the Internet at [email protected]. His home page is located at http://RedbacksWeb.com.

_________________________________________________________________________

18 Gary Stark