A WinDev Approach to Clarion Queues

In Clarion, I use queues quite a bit as a great way to deal with multiple instances of structured data in memory instead of storing in a table or file.  WinDev has several advanced data types that can be used, but none of them are the exact same as a Clarion Queue. This includes: Simple Arrays, Associative Arrays, Dynamic Arrays, Fixed Arrays, Composite Variables, Structures, Dynamic Structures, Queue, List, Stack, and then there are memory tables which are not technically a variable type.

This article will show one approach to replicating a Clarion Queue within the WinDev environment. It is not an exact replacement and might not even be the best approach, but for my current project it got the job done.

Defining the Variables

Of course the first thing we have to do is define our variables. I will be showing the example clarion code but I won’t be explaining it in this article, since the assumption is that you wouldn’t be reading this article if you are not familiar with Clarion.

Clarion Code

CustomerQue  QUEUE
Name   STRING(20)
Zip    DECIMAL(5,0)
Age    LONG
END

WinDev Code

STCustomer is structure

Name is string
Zip is numeric
Age is int
END

gaaCustomer is associative array (ccIgnoreCase + ccIgnorePonctuationAndSpace)  of STCustomer

So as you can see from the code, my approach is using the combination of a structure and an associative array. Arrays can only contain one data type, but luckily that data type does not have to be a simple data type, therefore we can define an array of structures, which is an advanced data type. A structure is a definition of a variable that is the combination of several other variables, so that you can reference them as a complete unit. An Associative Array is similar to a normal Array except it also has a key so we can access the individual records via the key instead of just by its subscript, the numeric position within the array. And finally ccIgnoreCase and ccIgnorePonctuationAndSpace tell WinDev that we want the key to be case insensitive and that punctuation and spaces will not affect the key.

Adding Records

Clarion Code

CustomerQue.Name = 'Jones'
CustomerQue.Zip = 12345
CustomerQue.Age =  45
ADD(CustomerQue,+CustomerQue.Name)
CustomerQue.Name = 'Smith'
CustomerQue.Zip = 54321
CustomerQue.Age =  32
ADD(CustomerQue,+CustomerQue.Name)

WinDev Code

LocalCustomerStructure is STCustomer
LocalCustomerStructure:Name = "Jones"
LocalCustomerStructure:Zip = 12345
LocalCustomerStructure:Age =  45
gaaCustomer[LocalCustomerStructure:Name] = LocalCustomerStructure
LocalCustomerStructure:Name = "Smith"
LocalCustomerStructure:Zip = 54321
LocalCustomerStructure:Age =  32
gaaCustomer[LocalCustomerStructure:Name] = LocalCustomerStructure

So what’s with the first line? We are creating a local variable based on our structure definition, remember that our structure itself is just a definition. Those of you that have worked in C or other structured languages should be accustom to this, but not a concept used as much in Clarion. We have to create it as a local variable because some of the associative array functions insist on having a local variable. The : syntax lets us reference elements of the structured variable. The magic happens in the fifth line, we are moving the structure, that we just populated with values, into the array, remember the array is an array of structures. The part inside the [] is defining our key. An important concept to note here is that the key is just a value, it doesn’t have to be a variable of the structure, in fact it could have been a fixed string such as “Red” or “Blue”. Again the key doesn’t have to be a value stored in the structure!

Reading All records

Clarion Code

LOOP x# = 1 TO Records(CustomerQue)
    GET(CustomerQue,x#)
    MESSAGE('Name = ' & CustomerQue.Name)
    MESSAGE('Zip = ' & CustomerQue.Zip)
    MESSAGE('Age = ' & CustomerQue.Age)
END

WinDev Code

LocalCustomerStructure is STCustomer

FOR EACH LocalCustomerStructure OF gaaCustomer
    Trace("Name = " + LocalCustomerStructure:Name)
    Trace("Zip = " + LocalCustomerStructure:Zip)
    Trace("Age = " + LocalCustomerStructure:Age)
END

So just as with the code adding the records we defined a local structure to hold the values from the Array. The FOR EACH statement just steps through each record of the Array and places it into the structure. There are several syntaxes for the FOR EACH statement so you can step through records where the key matches a defined value, etc. And of course the Trace statements are simply to output the values of the individual elements of the structure so we can see them.

Reading One Record

Clarion Code

CustomerQue.Name = 'Smith'
IF GET(CustomerQue,CustomerQue.Name)
    MESSAGE('Name = ' & CustomerQue.Name)
    MESSAGE('Zip = ' & CustomerQue.Zip)
    MESSAGE('Age = ' & CustomerQue.Age)
ELSE
    MESSAGE('Record Not Found')
END

WinDev Code

LocalCustomerStructure is STCustomer
IF gaaCustomer["Jones"]..Occurrence = 0 THEN
    Info("Jones does not exists")
ELSE
    LocalCustomerStructure = gaaCustomer["Jones"]
    Trace("Name = " + LocalCustomerStructure:Name)
    Trace("Zip = " + LocalCustomerStructure:Zip)
    Trace("Age = " + LocalCustomerStructure:Age)
END

At this point you should be used to the local definition of the structure. We use the ..Occurrence property to find out if the key exists in the Array, otherwise WinDev gives us a nasty error when we attempt to get the record. The actual fetch of the array record is in the gaaCustomer[“Jones”], the key is what is inside the [] this can be a fixed value as in the example, a local variable, etc. It will fetch the record that was added with the same key.

Updating A Record

Clarion Code

CustomerQue.Name = 'Smith'
IF GET(CustomerQue,CustomerQue.Name)
    CustomerQue.Age = 12
    PUT(CustomerQue,CustomerQue.Name)
ELSE
    MESSAGE('Record Not Found')
END

winDev Code

LocalCustomerStructure is STCustomer
IF gaaCustomer["Smith"]..Occurrence = 0 THEN
    Info("Smith does not exists")
ELSE
    LocalCustomerStructure = gaaCustomer["Smith"]   LocalCustomerStructure:Age =  12 gaaCustomer[LocalCustomerStructure:Name] = LocalCustomerStructure
END

Once again we have to use the ..Occurrence property to make sure the key exists to avoid an error during the assignment that follows. This is another opportunity to understand the difference between the keys and the values stored in the structure, we could have just as easily changed the value of Name. However had we done that in this code it would have ended up adding a new record because it is a different key. But we could use the below code and end up with a value in Name that is different from the key. Just a concept to keep in the back of your mind that might come in handy someday.

LocalCustomerStructure = gaaCustomer["Smith"]   LocalCustomerStructure:Name =  "Adams"
gaaCustomer["Smith"] = LocalCustomerStructure

Delete a Record

Clarion Code

CustomerQue.Name = 'Smith'
IF GET(CustomerQue,CustomerQue.Name)
    DELETE(CustomerQue)
ELSE
    MESSAGE('Record Not Found')
END

WinDev Code

IF gaaCustomer["Smith"]..Occurrence = 0 THEN
    Info("Smith does not exists")
ELSE
    ArrayDelete(gaaCustomer,"Smith")
END

We have seen the ..Occurrence a couple of times now, again we just need to make sure the key exists so we don’t generate a runtime error. The ArrayDelete statement deletes the record with the key matching the second parameter.

Miscellaneous Functions

There are many Wlanguage functions related to Arrays some of them are allowed with Associative Arrays and some are not. Below are a few that I used on this particular project.

..Empty

Technically not a function but a property. ArrayName..Empty will return True if there are no records in the Array.

..Occurrence

We saw this used in the example code above. It provides the ability to check for the existence of a key value, prior to trying to retrieve it. This becomes important to avoid a runtime error.

ArrayDeleteAll

Clears all records from an array.

ArrayCount

Returns the number of records in the Array

ArrayDelete

Deletes a record from the Array.  We saw this used in the examples above.

Summary

Again this is just one approach to replicating Clarion Queues and may not work for your situation. One of the downfalls to this approach is there doesn’t seem to be a way to sort the Array, either by its key or any other column of the Array. So the FOR EACH statement loops through the records in the order they were added. The online help page located at  http://doc.windev.com/en-US/?1514058&name=associative-array-type-variable indicates that sorting is possible.  However my attempts to use ArraySort have all resulted in an error and the Wlanguage manual says that sorting is not possible with associative arrays. I have submitted the question to technical support at PCSoft to see what their response is. I believe a memory table may be another approach to Clarion Queues that might have some advantages to this approach, but would require a table to be hidden on the screen to use. Perhaps the next time I need to replicate a Clarion Queue I will work on that approach and create a similar article for it.

[suffusion-the-author display=’author’]

[suffusion-the-author display=’description’]

You can download a WinDev Application that demonstrates the code in this article at www.thenextage.com/downloads/Backup_ClarionQueues.ZIP. There is also a short webinar that explains the technique and app at http://nicetouch.adobeconnect.com/p2zfzk2n9i1

Photo by Saxson

7 thoughts on “A WinDev Approach to Clarion Queues

  1. Another approach would be to create a class in WinDev. Such an approach has several benefits: nicely encapsulated, sorting, filtering etc. And more importantly: this setup is very extensible: you can create new classes that inherit from QueueRecordClass and still use your QueueClass. That enables you to have different ‘record definitions’ in the same queue! E.g.: a queue with persons where employees have different properties (soc.sec.nr) than clients (customercode, lastSalesDate), while they both share common properties such as name and address.

    (simplified)

    KeyStruc is STRUCTURE
    sKey is string
    nIndex is int
    END //structure

    QueueClass is Class
    PROTECTED
    aRecords is array of QueueRecordClass dynamic
    aNameKey is array of KeyStruc //you could create as many as you need of these 'indexes'
    end//class

    Method AddRecord(pRecord is QueueRecordClass dynamic)
    nIndex = ArrayAdd(:aRecords,pRecord)
    TmpStruc is KeyStruc
    TmpStruc:sKey = pRecord:GetName() //or whatever property you want
    TmpStruc:nIndex = nIndex
    ArrayAdd(:aNameKey,TmpStruc) //you can sort this array and you can use it for efficient searching
    //sort this array before you loop through the 'queue', e.g. create a SET method that sorts the array.
    //create a NEXT method that returns the next record (class reference or null if EOF).
    //you 'll end up with a class that works like the ABC Filemanager on a 'Queue'.
    -------
    QueueRecordClass is Class
    PROTECTED
    //fields go here e.g.
    sName is string
    sAddress is string

    //define get methods to access the properties
    method GetName()
    e.g. RESULT :sName

    Mind you, the QueueRecordClass instances have to be dynamic:

    Person is QueueRecordClass dynamic = new QueueRecordClass(sName,sAddress,sCity)
    MyQueue:AddRecord(Person)

    Like

    1. In WinDev the File Actions are accomplished with Hxxxxxx Functions. Such as:

      HReadSeek(CUSTOMER,ID,75)

      This would fetch the customer whose ID equals 75. There is no need to clear the record first with WD. However if you want to clear a file and reset the fields to their default values you would use HReset(FileName)

      Like

      1. Hi
        thanks, looks great.
        How about using ALIAS-es?

        or LOOP-ing through file?

        for example:
        FIL:Date = today()
        SET(FIL:FileDate,FIL:FileDate)
        LOOP UNTIL Access:File.Next() Level:Benign
        IF FIL:Date TODAY() THEN BREAK END
        DO SomeStuff
        END

        I have try to post some questions in the WinDev forum, but can not register.

        Thanks again.

        Like

      2. Alias are very easy, and can be created at run time, instead of just at design time.

        HAlias(Orders,Orders2000)

        Makes an Alias of the Orders table called Orders2000

        And here’s some code, that applies a filter, and loops through the matching records

        SearchKey is string
        // Filters the invoices found between 1/1/2005 and 12/31/2005

        SearchKey = HFilter(Invoice, InvDate, “20050101”,”20051231″)

        IF SearchKey “” THEN
        HReadFirst(Invoice, SearchKey)
        WHILE NOT HOut()
        Send_Letter()
        HReadNext(Invoice, SearchKey)
        END
        END

        // Cancels the filter
        HDeactivateFilter(Invoice)

        We have a Skype group of exClarion developers, we are always happy to help folks making the switch to WinDev, if you would like me to add you just send a Skype message to petehalsted

        Like

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s