While working on The Problem with Embeds, Revisited I had to step through the process of using the Clarion IDE to create .INC and .CLW files to hold, respectively, class headers and class methods. I don't do this much any more; normally I use John Hickey's excellent ClarionLive Class Creator.
It's easy enough to create the .CLW file using just the IDE - simply use the Stand Alone Member file quick start as that produces a file with an empty Member() as well as a Map statement.
You can take the same approach when creating the .INC file, although you have to be careful to change the file extension. And of course you'll need to gut the quick-started file because it doesn't have anything you need. If you haven't written a lot of classes you may find it difficult to remember just what you do need.
While I think anyone who writes classes can benefit from the ClarionLive Class Creator, I also think that Clarion could use a Quick Start for classes. So I started digging.
Here are the shipping Quick Starts:
I don't know where the Window Previewer comes from, but the other items can all be found in bin\Addins\BackendBindings\ClarionBinding\ClarionWin\Templates
under the Clarion root directory. Files that end in .xft are text templates, files that end in xpt are project templates:
Here's what Member.xft looks like:
<?xml version="1.0"?> <Template author="SoftVelocity" version="1.0"> <Config name = "${res:Templates.File.Member.Name}" icon = "CW.File.EmptyFile" category = "Clarion" defaultname = "Member${Number}.clw" language = "Clarion"/> <Description>${res:Templates.File.Member.Description}</Description> <Files> <File name="${FullName}" language="Clarion"> <![CDATA[ MEMBER('${ProjectName}') ${StandardHeader.Clarion} MAP END ]]> </File> </Files> <AdditionalOptions/> </Template>
As you can see it's an XML file that contains some configuration information as well as a CDATA section containing the text to be placed in the file itself.
I created two XFT files, one for the class header and one for the class source. The CDATA section was easy to handle. But how to change the name of the quick start and it's description? I poked around a bit trying to find out where res:Templates.File.Member.Name
was stored, but assuming I did find it that would probably mean at least one existing file that would need to be updated and/or replaced.
It was easier to replace these resource identifiers with literal strings. I also changed the defaultname attribute to a literal string.
Here's the source for ClassHeader.xft:
<?xml version="1.0"?> <Template author="SoftVelocity" version="1.0"> <Config name = "Class Header" icon = "CW.File.EmptyFile" category = "Clarion" defaultname = "ClassName?.inc" language = "Clarion"/> <Description>.INC file containing the class definition</Description> <Files> <File name="${FullName}" language="Clarion"> <![CDATA[ ${StandardHeader.Clarion} !------------------------------------------------------------------------------------ ! Change ClassName? to the name of your class !------------------------------------------------------------------------------------ !------------------------------------------------------------------------------------ ! Use of the ,Type attribute is recommended, although it means you'll ! have to instantiate the class before you can use it !------------------------------------------------------------------------------------ ClassName? class,module('ClassName?.clw'),link('ClassName?',1),Thread!,type !ExampleMethod procedure end ]]> </File> </Files> <AdditionalOptions/> </Template>
And here's ClassMethods.xft:
<?xml version="1.0"?> <Template author="SoftVelocity" version="1.0"> <Config name = "Class Methods" icon = "CW.File.EmptyFile" category = "Clarion" defaultname = "ClassName?.clw" language = "Clarion"/> <Description>.CLW file containing the class's methods</Description> <Files> <File name="${FullName}" language="Clarion"> <![CDATA[ ${StandardHeader.Clarion} !------------------------------------------------------------------------------------ ! Change ClassName? to the name of your class !------------------------------------------------------------------------------------ Member() Map End include('ClassName?.inc'),once !------------------------------------------------------------------------------------ ! Implement your class methods here !------------------------------------------------------------------------------------ !ClassName?.ExampleMethod procedure ! code ! ! do whatever ]]> </File> </Files> <AdditionalOptions/> </Template>
Inside project or standalone?
If you create a new file and you have a project open, you'll see this window:
If you create the file inside the project it will be added to the project's list of files to compile. This is not necessary because the class declarations has a LINK attribute which will cause the class file to be compiled and linked.
In the case of INC files you don't want them in the project anyway. By default the compiler will complain because it doesn't know how to handle INC files, so you'll have to change its properties to Compile: Never. That's a hassle.
I recommend you create all class files as standalone files. Then (after you've saved the files) add them to your solution by right-clicking on the Solution in the Solution explorer and choosing Add | Add Item. To keep the files better organized you may want to create a new Solution folder first and then add the classes to that folder.
Here's the Class Header template in action, assuming you're creating the files as standalone. If you create them inside the project you'll also be able to override the file name here (although you'll still have to explicitly save the file later).
And here's the resulting header:
MEMBER() OMIT('***') * User: Dave * Date: 27/06/2013 * Time: 9:24 AM *** !------------------------------------------------------------------------------------ ! Change ClassName? to the name of your class !------------------------------------------------------------------------------------ !------------------------------------------------------------------------------------ ! Use of the ,Type attribute is recommended, although it means you'll ! have to instantiate the class before you can use it !------------------------------------------------------------------------------------ ClassName? class,module('ClassName?.clw'),link('ClassName?',1),Thread!,type !ExampleMethod procedure end
Similarly, the methods file:
And the resulting source:
OMIT('***') * User: Dave * Date: 27/06/2013 * Time: 9:25 AM *** !------------------------------------------------------------------------------------ ! Change ClassName? to the name of your class !------------------------------------------------------------------------------------ Member() Map End include('ClassName?.inc'),once !------------------------------------------------------------------------------------ ! Implement your class methods here !------------------------------------------------------------------------------------ !ClassName?.ExampleMethod procedure ! code ! ! do whatever
Now replace ClassName? with your own class name, and do it for both files at the same time by setting Look in to all open documents.
Save the files, specifying a file name that matches the class name. This is really important - I strongly recommend one and only one class per file, with a descriptive unambiguous name. I also use pseudo namespaces; for an example see the source for The DevRoadmaps Clarion Library (DCL).
You will have to choose a file name when you save, as the default is ClassName?.<extension> and ? is not a valid character for file names. If you're unable to save the file you probably haven't removed the ?.
The class files
I've called my class "MyClass". Here's MyClass.inc:
OMIT('***') * User: Dave * Date: 27/06/2013 * Time: 10:25 AM *** !------------------------------------------------------------------------------------ ! Change MyClass to the name of your class !------------------------------------------------------------------------------------ !------------------------------------------------------------------------------------ ! Use of the ,Type attribute is recommended, although it means you'll ! have to instantiate the class before you can use it !------------------------------------------------------------------------------------ MyClass class,module('MyClass.clw'),link('MyClass',1),Thread!,type !ExampleMethod procedure end
And MyClass.clw
OMIT('***') * User: Dave * Date: 27/06/2013 * Time: 10:25 AM *** !------------------------------------------------------------------------------------ ! Change MyClass to the name of your class !------------------------------------------------------------------------------------ Member() Map End include('MyClass.inc'),once !------------------------------------------------------------------------------------ ! Implement your class methods here !------------------------------------------------------------------------------------ !MyClass.ExampleMethod procedure ! code ! ! do whatever
Using the class
To use the class copy the Include statement from the .CLW file and paste it in an appropriate data embed in the program (global, module, or procedural):
include('MyClass.inc'),once
You can then call the class's methods.
To Type or not to Type? That is the question
If you look at the class header you'll see that there's a commented out Type attribute at the end of the Class line. If Type is not present then the class is automatically instantiated and ready to use. Reluctantly, I've made this the default for the quick start so that no additional steps are needed before the class can be used.
As noted in the source, however, I strongly recommend the use of ,Type. This requires you to create an instance of the class, either in an additional declaration or at runtime, but it offers much more flexibility and helps ensure you're not reusing an existing instance when you may not want to do that.
Getting the QuickStarts
The Quick Start files are included in The DevRoadmaps Clarion Library (DCL). Just grab the latest update, or download the Quck Start files directly via GitHub.