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
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)
LikeLike
Thanks for the contribution Dries, creating a class definitely makes it an approach that is reusable. I must admit that I don’t use classes as often as I should, guess I am just to old school.
LikeLike
Hi
thanks.
how about files?
I mean, in Clarion we can use:
CLEAR(FIL:Record)
FIL:ID = 10
GET(File, FIL:IdKey)
How in WD?
Thanks
LikeLike
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)
LikeLike
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.
LikeLike
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
LikeLike
Excelent post.
Thank you.
LikeLike