SlideShare une entreprise Scribd logo
1  sur  17
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.


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.

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.


_________________________________________________________________________

                                                                  Advanced Programming Concepts
                                                                 The Thought Behind The Code                 1
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.

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.



_________________________________________________________________________

2    Gary Stark
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!

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.




_________________________________________________________________________

                                                                 Advanced Programming Concepts
                                                                The Thought Behind The Code                 3
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.

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


_________________________________________________________________________

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


If lTest
    lTest := FALSE
Else
    lTest := TRUE
End

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,
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.



_________________________________________________________________________

                                                                   Advanced Programming Concepts
                                                                  The Thought Behind The Code                  5
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.

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.




_________________________________________________________________________

6    Gary Stark
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
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

        End



_________________________________________________________________________

8    Gary Stark
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.

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.


_________________________________________________________________________

                                                                  Advanced Programming Concepts
                                                                 The Thought Behind The Code                   9
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 basics
         SUPER:INit( oWindow,iCtlID,oServer,uExtra )


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


         // Now we can deal with the sizing borders
         SELF: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.

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.


_________________________________________________________________________

10     Gary Stark
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.

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.


_________________________________________________________________________

                                                                Advanced Programming Concepts
                                                             The Thought Behind The Code                11
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.

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

_________________________________________________________________________

12     Gary Stark
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 report
LOCAL   nHandle2    :=   0      // Second report
LOCAL   nHandle3    :=   0      // Third report
LOCAL   aOutput1    :=   {}      // First report
LOCAL   aOutput2    :=   {}      // Second report
LOCAL   aOutput3    :=   {}      // Third report
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 it
oServer:GoTop()     // First record

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

// Process the data


_________________________________________________________________________

                                                                 Advanced Programming Concepts
                                                              The Thought Behind The Code                  13
While !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.

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 arrays
LOCAL aOutput1 := {}


_________________________________________________________________________

14      Gary Stark
LOCAL aOutput2 := {}
LOCAL aOutput3 := {}

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

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

// Common data
LOCAL cCommon := ""

// For a total
LOCAL nTotal   := 0

// And a Server
LOCAL oServer

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

// Process the data
While ! 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) )
      // 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



_________________________________________________________________________

                                                 Advanced Programming Concepts
                                               The Thought Behind The Code   15
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 2
aOutput2[ 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 )

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

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

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

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.



_________________________________________________________________________

16      Gary Stark
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
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 gstark@RedbacksWeb.com. His home page is located at
http://RedbacksWeb.com.




_________________________________________________________________________

                                                                Advanced Programming Concepts
                                                             The Thought Behind The Code                17

Contenu connexe

Dernier

Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embeddingZilliz
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
The Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfThe Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfSeasiaInfotech2
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Wonjun Hwang
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 

Dernier (20)

Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embedding
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
The Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfThe Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdf
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 

En vedette

Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsPixeldarts
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthThinkNow
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfmarketingartwork
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024Neil Kimberley
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)contently
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024Albert Qian
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsKurio // The Social Media Age(ncy)
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Search Engine Journal
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summarySpeakerHub
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project managementMindGenius
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...RachelPearson36
 
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Applitools
 
12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at WorkGetSmarter
 

En vedette (20)

Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage Engineerings
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental Health
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
 
Skeleton Culture Code
Skeleton Culture CodeSkeleton Culture Code
Skeleton Culture Code
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
 
12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work
 

Advanced Programming Concepts

  • 1. 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. 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. 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. _________________________________________________________________________ Advanced Programming Concepts The Thought Behind The Code 1
  • 2. 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. 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. _________________________________________________________________________ 2 Gary Stark
  • 3. 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! 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. _________________________________________________________________________ Advanced Programming Concepts The Thought Behind The Code 3
  • 4. 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. 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 _________________________________________________________________________ 4 Gary Stark
  • 5. Let's look at a little code. How many times have you seen this sort of code : If lTest lTest := FALSE Else lTest := TRUE End 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, 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. _________________________________________________________________________ Advanced Programming Concepts The Thought Behind The Code 5
  • 6. 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. 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. _________________________________________________________________________ 6 Gary Stark
  • 7. 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
  • 8. 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 End _________________________________________________________________________ 8 Gary Stark
  • 9. 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. 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. _________________________________________________________________________ Advanced Programming Concepts The Thought Behind The Code 9
  • 10. 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 basics SUPER:INit( oWindow,iCtlID,oServer,uExtra ) // Now let’s put the window in a known location. // relative to its owner is good. oOrigin := oWindow:Origin SELF:Origin := point{ oOrigin:x + 8, oOrigin:y + 8 } // Now we can deal with the sizing borders SELF: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. 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. _________________________________________________________________________ 10 Gary Stark
  • 11. 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. 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. _________________________________________________________________________ Advanced Programming Concepts The Thought Behind The Code 11
  • 12. 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. 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 _________________________________________________________________________ 12 Gary Stark
  • 13. 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 report LOCAL nHandle2 := 0 // Second report LOCAL nHandle3 := 0 // Third report LOCAL aOutput1 := {} // First report LOCAL aOutput2 := {} // Second report LOCAL aOutput3 := {} // Third report 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 it oServer:GoTop() // First record // This report needs a total // at the top aAdd( aOutput2, "" ) // Process the data _________________________________________________________________________ Advanced Programming Concepts The Thought Behind The Code 13
  • 14. While !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. 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 arrays LOCAL aOutput1 := {} _________________________________________________________________________ 14 Gary Stark
  • 15. LOCAL aOutput2 := {} LOCAL aOutput3 := {} // Current page number counters LOCAL nPage1 := 0 LOCAL nPage2 := 0 LOCAL nPage3 := 0 // Current line number counters LOCAL nLine1 := 99 LOCAL nLine2 := 99 LOCAL nLine3 := 99 // Common data LOCAL cCommon := "" // For a total LOCAL nTotal := 0 // And a Server LOCAL oServer oServer := CreateInstance( #SomeTable ) oServer:GoTop() // Process the data While ! 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) ) // 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 _________________________________________________________________________ Advanced Programming Concepts The Thought Behind The Code 15
  • 16. 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 2 aOutput2[ 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 ) FUNCTION WrtRpt( cFlName, aReport ) LOCAL nHandle := 0 LOCAL i := 0 LOCAL j := 0 nHandle := fCreate( cFlName ) For i := 1 to Len( aReport ) For j := 1 to aLen( aReport[ i ] ) fWrite( nHandle, aReport[i, j] + CRLF ) Next Next 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. _________________________________________________________________________ 16 Gary Stark
  • 17. 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 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 gstark@RedbacksWeb.com. His home page is located at http://RedbacksWeb.com. _________________________________________________________________________ Advanced Programming Concepts The Thought Behind The Code 17