Saturday, December 17, 2022

Introducing Clarion FileManager (Part 2)

SoftVelocity Clarion

This is a continuation from Part 1.

Exercise 5: Try Some Error Checking

So far we have simply assumed everything is working, but we haven't tested for any error conditions. You shouldn't do that in real programs. In particular, we should use TryOpen, TryNext, TryNext, TryInsert and TryUpdate.

Work with pairs

One way to make this easier is to work with method pairs while you are writing the code. The first is the TryOpen and Close pair:
if Access:Table.TryOpen() = Level:Benign then ! Open Table
    !// Table has been opened
    
else ! Open Table
    ud.Debug(' Open Table failed')
end ! Open Table
Access:Table.Close()
Next, add the ClearKey and SET pairs.
if Access:Table.TryOpen() = Level:Benign then ! Open Table
    !// Table has been opened
    Access:Table.ClearKey(TBL:KeyName) ! Top of the primary key
    SET(TBL:KeyName) ! Set the processing order
else ! Open Table
    ud.Debug(' Open Table failed')
end ! Open Table
Access:Table.Close()
After that, you will probably want a loop to read several records. So add in the LOOP UNTIL TryNext and END pair.
if Access:Table.TryOpen() = Level:Benign then ! Open Table
    !// Table has been opened
    Access:Table.ClearKey(TBL:KeyName) ! Top of the primary key
    SET(TBL:KeyName) ! Set the processing order
    LOOP UNTIL Access:Table.TryNext() <> Level:Benign ! Loop Table
    
    END ! Loop Table
else ! Open Table
    ud.Debug(' Open Table failed')
end ! Open Table
Access:Table.Close()
Notice how we comment the "if ... then", "else" and "end" statements to keep track of which "end" statement belongs to which "if" statement. Also, the same applies to the "LOOP" and "END" statements. Don't only rely on Ctrl-i to do the indenting for you. Use it to check your own comments and see if they line up properly. If not, check your code.
Also, you can avoid unforeseen bugs by ensuring that you put an "else" whenever you have an "if". Get into the habit of writing the "else" and the "end" whenever you write the "if", and put the comments at the end of each line, before you worry about what happens immediately after the "if".
With all of this in mind, open "MyTestCode" in the Embeditor and fix up the code. Note I have also copied the CUS:Address code into the insert block. It should end up something like this:
 Access:Customer.UseFile()
 if Access:Customer.TryOpen() = Level:Benign then ! Customer Open
     Access:Customer.Clearkey(CUS:KEYCUSTNUMBER) ! Top of the primary key
     SET(CUS:KEYCUSTNUMBER) ! sets the processing order
     i = 0 ! Record counter
     LOOP UNTIL Access:Customer.TryNext() <> Level:Benign ! CUS loop
         !// Process each customer record
         i += 1 ! Count the records
         ud.Debug(' Customer: ' & CUS:CustNumber |
             & ' Company: ' & clip(CUS:Company))
         0{PROP:StatusText,1} = 'Customer record ' & i
     END ! CUS loop
     ud.Debug(' Loop ends. There are ' & i & ' records')
     0{PROP:StatusText,1} = 'There are ' & i & ' records'
     !// List of US state codes, including Guam and Puerto Rico
     strStates = '..AK.AL.AR.AZ.CA.CO.CT.DE.FL.GA.GM.HI.IA.ID.IL.' |
       & 'IN.KS.KY.LA.MA.MD.ME.MI.MN.MO.MS.MT.NC.ND.NE.NH.NJ.NM.' |
       & 'NV.NY.OH.OK.OR.PA.PR.RI.SC.SD.TN.TX.UT.VA.VT.WA.WI.WV.WY.'
     !// State table lookups    
     Access:State.UseFile()
     if Access:State.TryOpen() = Level:Benign then ! State Open
         Access:State.Clearkey(STA:KEYSTATE) ! Top of the primary key
         SET(STA:KEYSTATE) ! sets the search order 
         !   
         !// Add in and update some more records
         LOOP i = 9 TO RANDOM(150,250) ! i
             CUS:CustNumber = i
             IF Access:Customer.TryFetch(CUS:KEYCUSTNUMBER) |
                = Level:Benign THEN ! Fetch
                 ud.Debug(' Fetched Customer: ' & CUS:CustNumber |
                     & ' State ' & clip(CUS:State))
                 STA:State = CUS:State ! This is the state code we want
                 IF Access:State.TryFetch(STA:KEYSTATE)|
                    = Level:Benign THEN ! STA
                     CUS:Address = 'Here in ' & clip(STA:StateName)
                 ELSE ! STA
                     CUS:Address = 'Not Supplied'
                 END ! STA 
                 if Access:Customer.TryUpdate() = Level:Benign then
                     ! Update changes to the customer record
                     ud.Debug(' Updated Customer: ' & CUS:CustNumber)
                     0{PROP:StatusText,1} = 'Updated customer ' |
                         & CUS:CustNumber
                 else ! Update
                     ud.Debug(' Failed to update Customer: ' |
                         & CUS:CustNumber)
                     0{PROP:StatusText,1} = 'Failed to update customer ' |
                         & CUS:CustNumber                         
                 end ! Update
             ELSE ! Fetch
                 !// Record not found, so add it in
                 Access:Customer.PrimeRecord() ! Cust number is autonumber
                 CUS:Company = 'Company ' & i
                 CUS:FirstName = 'First ' & i
                 CUS:LastName = 'Last ' & i
                 n = RANDOM(1,52) ! Get a random state number
                 CUS:State = SUB(strStates,n*3,2) ! State Code
                 CUS:City = SUB(strStates,n*3,2) & ' City'
                 CUS:ZIPcode = n * 1000 + i ! Made up "Zip" code
                 STA:State = CUS:State ! This is the state code we want
                 IF Access:State.TryFetch(STA:KEYSTATE)|
                    = Level:Benign THEN ! STA
                     CUS:Address = 'Here in ' & clip(STA:StateName)
                 ELSE ! STA
                     CUS:Address = 'Not Supplied'
                 END ! STA                  
                 if Access:Customer.TryInsert() = Level:Benign then
                     ! Add in the customer record
                     ud.Debug(' Added Customer: ' & CUS:CustNumber |
                         & ' Company: ' & clip(CUS:Company))
                     0{PROP:StatusText,1} = 'Added customer ' |
                         & CUS:CustNumber
                 else ! Insert
                     ud.Debug(' Failed to add Customer: ' |
                         & CUS:CustNumber & ' Company: ' |
                         & clip(CUS:Company))
                     0{PROP:StatusText,1} = 'Failed to add customer ' |
                         & CUS:CustNumber
                 end ! Insert
             END ! Fetch        
         END ! i
     else ! State Open
         ud.Debug(' Unable to open the State table')            
     end ! State Open
 else ! Customer Open
     ud.Debug(' Unable to open the Customer table')
 end ! Customer Open
 0{PROP:StatusText,1} = 'Test code completed.'
 Access:Customer.Close()
 Access:State.Close()
Now run the application and see if the debug messages match what you expected to see.
You may decide that it is important to notify the user of some error conditions. I have only used debug messages in this example for consistency, not for any other reason.
Warning: If you write
if Access:Customer.TryUpdate() then
and omit the
= level:Benign
part you will tear your hair out for days trying to figure out why the update "isn't working" when in fact it is working correctly but the IF statement is the wrong way around. I speak from personal experience.

Exercise 6: Making Changes Across Multiple Records

In SQL it takes a single UPDATE command with some WHERE clauses to update a bunch of records. With FileManager we don't always have that luxury. Say, for example, we want to delete all customers in the state of California. One way to do it is simply to loop through all the records and every time we find a California state code, delete the record and move on. It's not particularly efficient, but it will get the job done.
Open LCLesson, select the "BrowseCustomers" form and open it with the Window designer.
Drag a button control from the Toolbox and place it on the form as shown.
Change the Text to "Delete CA", the Use property to "?btnDeleteCA", the TextColour to Red, and the Message property to "Delete CA Customers". Then select the button, and double-click it.
This should open the Embed points for the button. Click on "Accepted", press the "Insert" button, choose "Source" and "Select".
Type in "ud.Debug(' Delete CA')" and press the green "Save and Close" button three times. Save your work. Now click on the "Embeditor" button.
Click on the "Next Filled Embed" button until you find the "ud.Debug" statement. Now we can add some suitable code:
   ud.Debug(' >Delete CA')
   Access:Customer.UseFile()
   if Access:Customer.TryOpen() = Level:Benign then ! Customer Open
       Access:Customer.ClearKey(CUS:KEYCUSTNUMBER)
       SET(CUS:KEYCUSTNUMBER)
       LOOP UNTIL Access:Customer.TryNext() <> Level:Benign ! Cust Loop
           ?btnDeleteCA{PROP:Text} = clip(CUS:CustNumber) ! Display Cust No
           DISPLAY() ! Update the screen display
           !// Delete stuff goes here
       END ! Customer Loop
   end ! Customer Open
   Access:Customer.Close()
   ud.Debug(' <Delete CA')
Use the green "Save and Close" button to exit the Embeditor. Save your work. Start the application and test the button.
We haven't actually deleted anything, but we have made sure that the looping works. Use the "Fast Forward" button to go to the last record and check that the record number matches the number on the button text.
So let's return to the code and make it a bit faster, and to count how many records were deleted. The variable i# is an "implicit LONG" variable used here.
   ud.Debug(' >Delete CA')
   Access:Customer.UseFile()
   if Access:Customer.TryOpen() = Level:Benign then ! Customer Open
       Access:Customer.ClearKey(CUS:KEYCUSTNUMBER)
       SET(CUS:KEYCUSTNUMBER)
       i# = 0
       LOOP UNTIL Access:Customer.TryNext() <> Level:Benign ! Cust Loop
           if clip(CUS:State) <> 'CA' then CYCLE . ! Try next record
           ?btnDeleteCA{PROP:Text} = clip(CUS:CustNumber) ! Display Cust #
           DISPLAY() ! Update the screen display
           !// Delete without asking
           if Access:Customer.DeleteRecord(False) |
               = Level:Benign then ! Delete
               ud.Debug(' Record deleted ' & CUS:CustNumber)
               i# += 1 ! Count the deleted record
           else ! Delete
               ud.Debug(' Record NOT deleted: ' & CUS:CustNumber)
           end ! Delete
       END ! Cust Loop
   end ! Customer Open
   Access:Customer.Close()
   ?btnDeleteCA{PROP:Text} = 'Deleted ' & i# ! No of records deleted      
   ThisWindow.Reset(1) ! Update display to remove the deleted records
   ud.Debug(' <Delete CA')
Some lines need further explanation: the purple line is a one-line if statement because it ends with a dot. The CYCLE command forces execution to go to the top of the LOOP, so everything below the purple line is ignored, down to the END of the loop. It prevents non-'CA' records from being deleted.
This is another way to write the test: look for the qualifying record and process it inside the "if" block. It's a matter of preference and legibility. You choose.
Click on the green "Save and Close" button, save your work and start the application. Let's see if the new code actually works as intended.
How to check? Click on the "by ZIP Code" tab and see if you can see any California addresses.
Did you notice these lines?
   if Access:Customer.DeleteRecord(False) = Level:Benign then ! Delete
       ud.Debug(' Record deleted ' & CUS:CustNumber)
       i# += 1 ! Count the deleted record
   else ! Delete
       ud.Debug(' Record NOT deleted: ' & CUS:CustNumber)
   end ! Delete
How could we display the Customer number from a deleted record? (Line 2) Because the record buffer did not disappear when the physical record was deleted. It only changes once we move to the next record.

Exercise 7: Indexes Make Record Access Easier

Close the LCLesson application and open the LCLesson dictionary instead. Consider the Customer table.
Notice that the "State" code is not part of an index. So let's add an index for it. This will enable us to jump to any record based on its state code.
Right-click on the "Keys" folder and choose "Add Key"
Give the key the label "KEYSTATECODE" and a description, and then add the field "State" as the only field in the key. Click "OK"
Move the new key to the bottom of the list, and then click on the "Save All" button to save these changes. Now we need to get the data file itself to change.
Right-click on the Customer table and select "Browse Table".
Choose the "Convert and backup" option, and click "OK".
Click the top dropdown box to check that the new index/key is present in the data file. Close the window, and close the dictionary.
From the Start Menu, open the LCLesson Application. We need to introduce some changes to the Customer browse form. Select it, and click on the "Window" button to open the Window Designer.
Use the "List Box Format" command to change the width property of "First Name" and "Last Name" to "40" instead of "80". Then adjust the width of the screen to 640, and adjust the SHEET and the LIST controls as shown.
Now click on the SHEET to select it, and then right-click to add a new Tab.
In the properties on the right is a "Tabs" property, with the word "(Collection) ..." after it. Click on the ellipsis button and choose "tab4" in the Control Collection Editor". Change its text to "by State Code" and click "OK".
Change to the new "by State Code" tab, and then drag and drop the "State" field from the Data/Tables window and place it below the State column as shown. You can delete the label. Then mark the State control as "Flat" and "Read Only" as shown. This will display the state code for whatever record is selected in the browse form. Click on the green "Save and Close" button and save your work.
Keep the "BrowseCustomers" form selected, and click on the "Extensions" button. This will display the extensions that apply to the form.
First, we are going to delete the top extension, the one that allows the form to be resized. This will prevent the buttons moving around on the form. Then double-click on the line that reads "Browse on Customer ()".
Change to the "Conditional Behavior" tab, and click on the "Properties" button. This shows how the sort behaviour based on which tab is selected, works. Highlight and copy the "Condition" expression and click "Cancel".
Now click "Insert", and paste the clipboard to the "Condition" field. Change it to end with a "4" instead of a "2". Click on the ellipsis on the "Key to use" field and select the "KEYSTATECODE" key. Click "OK".
Make sure the choices are in numerical order, for sanity's sake. Click "OK". Click on the green "Save and Close" button and save your work. Start the application and try out the changes in the Customer Browse form.
Adjust the screen to fit the data. Notice how the scroll bar is using up some of the data space, so let's make a note to reduce the size of the "City" column just a bit (change the width from "80" to "70"). Notice how the data is sorted by the field on which the key is based. Close the app and return to Clarion.
Open the Window Designer and return to the "by State Code" tab. Add a button labeled "?btnDeleteThis" with text "Delete this State:" and Red TextColor. Change the adjacent "State" control to have red text too, and line up their middles for neatness.
Double-click on the button and click on the "Accepted" event. Thenc click on the "Insert" button and choose "Source". Type in "ud.Debug(' btnDeleteThis ' & CUS:State)" and click on the green "Save and Close" buttons (three times) and save your work.
Note: always use the "Insert" button shown. Do not be tempted to use the "Source" button to get into the code and add in stuff. It will cause the Clarion IDE to crash and you will lose some of your work. Never enter the Embeditor directly from the Window Designer.
Now that we have left the Window Designer, we can go to the code using the Embeditor. Click on the "Next Filled Embed" button a few times until you get to the "btnDeleteThis" section. The code is not going to be the same as the previous button, but we will borrow pieces from it.
First, move up to the top of the "ThisWindow.TakeAccepted" procedure, and define two variables as shown. Then move down to the "btnDeleteCA" code block and change the three "i#" entries to "i" because we have explicitly defined the i variable as LONG. Now let's write the new code:
  strStateCode = clip(CUS:State) ! Remember which state to delete
  ud.Debug(' ?btnDeleteThis ' & strStateCode)
  Access:Customer.UseFile()
  if Access:Customer.TryOpen() = Level:Benign then ! Customer Open
      Access:Customer.ClearKey(CUS:KEYSTATECODE) ! Top of the index
      CUS:State = strStateCode ! Jump to this state
      ! SET using this index, starting at this index value
      SET(CUS:KEYSTATECODE,CUS:KEYSTATECODE)            
      i = 0
      LOOP UNTIL Access:Customer.TryNext() <> Level:Benign ! Cust Loop
          if clip(CUS:State) = strStateCode then ! State
              ! Display Customer Number
              ?btnDeleteThis{PROP:Text} = clip(CUS:CustNumber) 
              DISPLAY() ! Update the screen display
              !// Delete without asking
              if Access:Customer.DeleteRecord(False) |
                  = Level:Benign then ! Delete
                  ud.Debug(' Record deleted ' & CUS:CustNumber)
                  i += 1 ! Count the deleted record
              else ! Delete
                  ud.Debug(' Record NOT deleted: ' & CUS:CustNumber)
                  MESSAGE('Unable to delete ' & CUS:Company)
              end ! Delete
          else ! State
              !// We have moved on to the next state code
              ud.Debug(' Break at ' & CUS:State)
              BREAK ! All done
          end ! State
      END ! Cust Loop
  end ! Customer Open
  Access:Customer.Close()
  ?btnDeleteThis{PROP:Text} = 'Deleted ' & i ! No of records deleted       
  ThisWindow.Reset(1) ! Update display to remove the deleted records
  ud.Debug(' <btnDeleteThis ' & strStateCode)  
Note the SET command has the same index listed twice, to specify the search and sort order, and to allow you to specify the starting point of the loop. From the help file: "SET (key,key) specifies keyed sequence processing and positions to the first ... record which contains values matching the values in the component fields of the key. Both key parameters must be the same."
I always think of this technique as the "hop, skip and jump" method. We start with the index being reset. Then we use the assignment statement to tell the index which record to "hop" to. While we loop through the records, we are skipping from one valid record to the next. When we run out of valid records, we use the BREAK command to "jump" out of the loop because there are no more valid records. The index is doing most of the work, instead of having to rummage through all the records in the entire table.
Close the Embeditor, save your work, and Start the application. Go to a state near the end of the alphabet (I chose Vermont) and click the "Delete this State:" button. Watch in the debugger how the program skips through the records.

Exercise 8: Be Careful Not To Delete Parent Records

I assumed that since there was a relationship between Customers and Orders (and it has a "restrict" setting for deleting records) that "Customer" records with existing child records in the "Orders" table, would not be deleted. I was wrong. In any case, it is always better to check for these things explicity than to expect them to happen implicitly.
Open the Embeditor for the "BrowseCustomers" form, and go to the "Data/Tables" panel. Click on the "Other Files" folder, and the "Add" button, and select the "Order" table. This makes it easy to see the Index and Field names, and to drag and drop them into the code. So let's modify the code for the "delete this" button:
  strStateCode = clip(CUS:State) ! Remember which state to delete
  ud.Debug(' ?btnDeleteThis ' & strStateCode)
  Access:Customer.UseFile()
  if Access:Order.TryOpen() = Level:Benign then ! Order Open
    if Access:Customer.TryOpen() = Level:Benign then ! Customer Open
      Access:Order.ClearKey(ORD:KEYCUSTNUMBER) ! Top of the index
      Access:Customer.ClearKey(CUS:KEYSTATECODE) ! Top of the index
      CUS:State = strStateCode ! Jump to this state
      SET(CUS:KEYSTATECODE,CUS:KEYSTATECODE)  
      SET(ORD:KEYCUSTNUMBER) ! Use this index to find stuff 
      i = 0
      LOOP UNTIL Access:Customer.TryNext() <> Level:Benign ! Cust Loop
        if clip(CUS:State) = strStateCode then ! State
            ?btnDeleteThis{PROP:Text} = clip(CUS:CustNumber) ! Display Cust #
            DISPLAY() ! Update the screen display
            !// Delete without asking
            ! Look for this Customer in the Orders Table
            ORD:CustNumber = CUS:CustNumber 
            IF Access:Order.TryFetch(ORD:KEYCUSTNUMBER) |
              <> Level:Benign THEN ! Orders
                !// No orders found. Safe to delete empty customer
                if Access:Customer.DeleteRecord(False) |
                  = Level:Benign then ! Delete
                    ud.Debug(' Record deleted ' & CUS:CustNumber)
                    i += 1 ! Count the deleted record
                else ! Delete
                    ud.Debug(' Record NOT deleted: ' & CUS:CustNumber)
                    MESSAGE('Unable to delete ' & CUS:Company)
                end ! Delete
            ELSE ! Orders
                ud.Debug(' Orders exist. Record NOT deleted: ' & CUS:CustNumber)
                MESSAGE('Unable to delete ' & clip(CUS:Company) | 
                    & ' because of existing orders')    
            END ! Orders
        else ! State
            !// We have moved on to the next state code
            ud.Debug(' Break at ' & CUS:State)
            BREAK ! All done
        end ! State
      END ! Cust Loop
    end ! Customer Open
  end ! Order Open
  Access:Customer.Close()
  Access:Order.Close()
  ?btnDeleteThis{PROP:Text} = 'Deleted ' & i ! No of records deleted       
  ThisWindow.Reset(1) ! Update display to remove the deleted records
  ud.Debug(' <btnDeleteThis ' & strStateCode)        
Now that we have the required checks in place, "Save and Close" the Embeditor, save your work, and start the application.
Change to the "by State Code" tab, and choose South Carolina. Click on the "Delete this State" button. The first record in the Customer table has an invoice, so we get the correct notification. Same with the third customer.
In the end, we deleted the empty customers, and two SC customers remain. We would have to delete their orders before we should delete these customers.

Essential Reading

I learnt most of what is covered in these articles from "Section 2: Accessing Data Tables" from Bruce Johnson's book, "Programming in Clarion's ABC". You really need to invest $49 in this book. I was also assisted by some very helpful people at ClarionHub, who kindly guided me through my mistakes and misunderstandings.




Friday, October 28, 2022

Clarion Resources and Products

SoftVelocity Clarion

This is an attempt to bring together the various useful Clarion resources, particularly those in Africa, since we have an Africa Clarion WhatsApp Group. Please contact me on WhatsApp if you want your Clarion-related website to be included in this list.

Clarion Accessories

  • Capesoft has produced a number of indispensable accessories for Clarion, including FM3, NetTalk, StringTheory and others. Link: CapeSoft Accessories for Clarion
  • ProperData has two accessories: Relational Browse, and Edit-on-the-spot. Link: ProperData
  • ClarionShop is the marketplace for all the accessories mentioned here, plus many others from around the world. All prices are in US$. Link: ClarionShop


Built with Clarion

  • CapeSoft develops Time & Attendance, Access Control, Job Costing, Panel Beating & Workshop Management, and Depreciation applications. Link: capesoft.com
  • Crommix provides software and services to Pharmacies, Medical Centres, Clinics and businesses in Burkina Faso. Link: crommix.com
  • Dr Smash Software provides packages focused on the Motor Industry and the requirements around Insurance and Manufacturer Approvals. With packages that range from Basic Quoting to Full Financial Business management, Dr Smash is your one stop shop for all your Motor Industry software requirements. Link: Dr Smash
  • SofTrends Solutions supplies Payroll, FAS Accounting, POS Inventory, Property Manager, HR Manager, Fleet Manager, Tours Manager, VetMed Manager, Insurance Brokers Manager, Hotel-Club Manager, and Bulk SMS. Custom software is the cornerstone of our business, based in Kenya. Link: SofTrends Solutions
  • Trident Software focuses on Managing and Rental Agent Software (townhouse complexes, homeowner associations, retirement homes, business parks etc.) Contact Ken Ward 083 235 5495 Link: Trident Software
  • WatchManager is used in the security industry to handle numerous incoming burglar alarm messages, and assist the control centre operators to follow the correct instructions for dealing with different types of emergencies. Link: WatchManager
  • WinFin develops accounting software for farmers and business in English and Afrikaans. Developed by Helgard Scholtz at ProperData. Link: WinFin


Worldwide Clarion Community: helping one another

  • Clarion Live has been running support and information webinars for many years. They have also published some free utilities for the community to use. They also host the Clarion International Developers Conference every few years. Links: ClarionLive, CIDC 2019, CIDC 2020/2, CIDC 2023 (Sept 11-15 2023, Orlando, Florida).
  • ClarionHub is the place to go to ask questions or share programming tips. Links: ClarionHub
  • NetTalk Central is the place to go to ask questions or share programming tips relating to the NetTalk ecosystem. Link: NetTalk Central
  • Clarion Mag archive has articles from 1999 to 2011. It's a gold mine of useful stuff. Link: clarionmag.jira.com
  • Clarion Blog is the official announcement page for SoftVelocity. Link: Clarion Blog
  • Principled Programming is a set of principles published by Daniel Read in 2001. Copy and paste this file in your main procedure to remind you of the principles. Article: developerdotstar.com
  • Skype Chats are available for a number of topics. This list comes from the ClarionHub forum. Link: Skype Chat Groups listing


Worldwide Clarion Accessories

  • SoftVelocity is the source for Clarion, plus several add-ons: In-Memory Driver, IP Driver, AnyScreen, Business Maths Library, Report Writer, etc. Link: store.softvelocity.com
  • Noyantis Software is a leading provider of Clarion Wrapper Templates. Each of our wrapper templates enables Clarion developers to quickly add powerful enhancements to their applications in a matter of minutes. Link: noyantis.com
  • LinderSoft is the home of SetupBuilder, a best-of-breed product for developers who want complete control over their software installations. Link: lindersoft.com
  • ohnOsoft products include Classify.It!, Analyze.It! and Update.It! . Link: ohnosoft.com
  • BoxSoft has created a number of Super Templates for use by Clarion developers. They have several vertical market products written in Clarion. Link: boxsoft.net
  • IceTips has Templates, Tools and Utilities for Clarion Developers. Link: icetips.com
  • CHT is short for Clarion Handy Tools, a wide variety of templates, classes, utilities, apps and projects. Link: cwhandy.ca
  • Mitten Software: Clarion Tools, Templates and Programming. Link: Mitten Software
  • Developer Team has numerous useful templates, plus a facility for sending WhatsApp messages. Link: developerteam.com.ar
  • Templates Clarion has some useful templates and products from Clarioneros in Argentina. Link: templatesclarion.com
  • Laro Group is also based in Argentina, and have developed numerous templates. clariontemplates.com
  • Upper Park Solutions is best known for their Version Control systems, but they also provide other Clarion services. Link: upperparksolutions.com
  • Ingasoft Plus aims to make your life easy, with templates and tools. Link: ingasoftplus.com
  • KlariSoft develops for Clarion programmers, ranging from simple utility templates to class/template wrappers of various third-party software. Link: klarisoft.com
  • Clarion ProSeries are serious tools for Clarion developers. Link: clarionproseries.com
  • Sterling Data supplies free and paid Clarion templates, a Clarion blog with articles of interest to Clarion developers and also provides Clarion programming services to assist Clarion developers. Link: sterlingdata.com


Worldwide Open Source Resources

  • GitHub topics listed on Github. Clarion.Studio has foked and collected 109 repositories as well. Links: github.com and Clarion.Studio
  • Clarion Community Help is a wiki community project to keep the help for the Clarion up to date with additional notes and helpful pointers. Link: clarion.help
  • Clarion Standards is a document from Mitten Software that identifies good coding practice. It's a ZIP download.
  • Carl Barnes has posted a number of useful code, examples, tools, templates and utilities. Link: github.com. His "following list" has a lot of good stuff too.
  • Mike Duglas has contributed 54 repositories. Many are free, some are commercial. Link: github.com
  • Steve Parker collected a large number of useful tips, FAQs, articles and files. These have been archived on the IceTips website. Link for articles: icetips.com. Link for Downloads: icetips.com
  • ClarionLife.net is a (Russian language) portal for Clarion developers. Google Translate will help. Link: ClarionLife.net
  • The History of Clarion is the (very unofficial) History of JPI, Clarion and SoftVelocity, by Paul Attryde. Link: pisoft.ru
  • Programming Objects in Clarion is an invaluable book by the late Russ Eggen. Link: github.com


My Clarion Lessons


These lists are a work in progress. Please send me any links you may have.

Saturday, October 15, 2022

How to keep your useful CapeSoft accessories up to date

SoftVelocity Clarion

The accessories I use from from CapeSoft are updated regularly, as new features and fixes are released. I haven't signed up for the Ohnosoft Update.It! service yet, so I have my own (manual) update method. First you need to download my list files. Extract them to the "C:\Clarion11\accessory\Documents\Capesoft" folder.
First, let's use the full list. Delete the current CapesoftAccessories.htm file, and then make a copy of "CapesoftAccessoriesFull.txt". Rename the copy to "CapesoftAccessories.htm" and open it in your browser.
It's a tweaked copy of the CapeSoft Accessories page. The second column shows the version numbers of their products that you may have installed. The right-hand column shows the current version number of the product. Close this page.
Delete the CapesoftAccessories.htm file, and then make a copy of "CapesoftAccessoriesMine.txt". Rename the copy to "CapesoftAccessories.htm" and open it in your browser. This is the list of the accessories that I have bought, but it's easy enough to edit the HTML to add or remove products from the list.
Notice how some of my accessories are out of date. I have highlighted them in yellow for this article. Click on the "Downloads" link at the top of the page.
Click on the "Last Updated" column heading to get all the new stuff to display first. Then click on the "Download" button for each new version you need. When the downloads are done, use the "Show in Folder" option in your browser to go to your Downloads folder.
My downloads folder has a copy of my "SafeReader Passwords.txt" file, and a shortcut link to the folder I use to store all my CapeSoft installed files. Open the passwords file in notepad or your favourite text editor.
Extract each .saf file, and rename the installer to include its version number, as shown here. Once you have done all the files, put today's date in the bottom of the passwords file, and save it. This will move it to the top of the downloads folder if you keep it sorted by "Date Modified", like I do.
Copy the updated passwords file to the "CapeSoft" folder. Then delete the .saf files and move the installer files to the "CapeSoft" folder. Then click on the "CapeSoft" shortcut link to go there.
In my "CapeSoft" folder, I have a subfolder called "old". After I install each new accessory, I move the previous version of the accessory installer to the "old" file, just in case. The nice thing about these installers is they register the extension templates and stuff for you. I also have a shortcut to my "C:\Clarion11\accessory\Documents\Capesoft" folder. Click on it to return to this folder.
The installers have replaced your previous "CapesoftAccessories.htm" file with their own one. Rename it to "CapesoftAccessories.org.htm" and then copy "CapesoftAccessoriesMine.txt". Rename the copy to "CapesoftAccessories.htm" and open it. Voila! The version numbers should all match up, and all your useful CapeSoft accessories are up to date.



Update Tuesday 2 October 2023: New version of the text files, to open links in a new window, and listing additional CapeSoft products, such as xFiles 4, NetTalk 14, etc.

Thursday, October 06, 2022

Adding Extensions to Clarion

SoftVelocity Clarion

Clarion includes some useful templates and extensions, and there are many others, both free and commercial, that you can add. Let's consider some of these. First, go to clarionlive.com, and from the "other" menu, select "Utilities" to download the ClarionLive Utility Pack. Next, visit CapeSoft.com and buy the CapeSoft ABC Defaults accessory. You will receive a decryption keyword for the download. You also need to download their free safe reader utility to decrypt the ABC Defaults download. Once you have the ABCDefaultsInstall.exe and ClarionLiveUtilities.exe files, close your Clarion IDE, and run the installers using all the default options.
Open the Clarion IDE and then open your project, e.g. LCLesson. Click on the "Global Extensions" tab.
Click on "Insert" and scroll down until you find the "cwVersion - Version Resource" template. Select it.
Fill in the fields as required, and go to the "Product Version" tab.
Again, fill in the fields as required, and go to the next tab.
These are my suggested options. Click "OK".
Click on the "Generate and Make Current Application" button. Assuming it compiled without incident, open the folder that contains the LCLesson.exe file and right-click to view the file properties.
As you can see, the file has professional version and product information, which was not present before.

ABC Defaults

Let's return to the Clarion IDE and search for another template to use. Click on "Insert" and type "defaults" to the search bar.
Select the "ActivateABCDefaults" template.
Click "OK" The documentation explains what this template does. Basically, it stops certain misbehaviour by your application when some error conditions arise.

Ultimate Debug

Before we use this template, please download DebugView++ and install it. It is an extremely useful debugging tool for Windows. There are others, so if you are happier with a different one, that's also OK.
Click "Insert" and search for "debug". Click on "UltimateDebugGlobal" and click "Select".
The default settings in the template are fine for now. You may want to tweak them later, but I haven't found a reason to do so yet. Click "OK".
Build the solution as normal, and wait for the app to run. Then fire up your debugger program (DebugView++)
The template has inserted debug trace information into your program. As you open and close screens, the debugging information will display in the debug window. This is helpful in figuring out what your program is doing. In the next lesson we will add in some of our own debugging code.


One More Thing ...

I discovered a bug in the Ultimate Version Resource template while writing this lesson. So how do we disable or remove a template or extension? First, close the application you are busy with, and return to the IDE Start Page. Next, go to the "Tools" menu and select "Edit Template Registry".
Wait for the list to load. Then click on the "Contract All Nodes" button.
Scroll down to find "Class UltimateVersion", select it and click the "Disable" button. Click on the green "Save and Exit" button. The "Ultimate Version Resource" is no longer shown when you try to insert an extension.