ClarionMag TheClarionIDE
ClarionMag TheClarionIDE
ArchiveUserQ.UserID = ausers:userid
ArchiveUserQ.UserName = acm_users.username
ArchiveUserQ.LowerUserName = lower(acm_users.username)
ArchiveUserQ.FirstName = acm_users.firstname
ArchiveUserQ.LastName = acm_users.lastName
ArchiveUserQ.DaysExpired = acm_users.daysexpired
ArchiveUserQ.DaysLeft = acm_users.daysleft
ArchiveUserQ.Password = acm_users.PASSWORD
add(ArchiveUserQ,ArchiveUserQ.Lowerusername)
On the other hand, if I can select some text, press Ctrl-Shift-I, and I get this:
ArchiveUserQ.UserID = ausers:userid
ArchiveUserQ.UserName = acm_users.username
ArchiveUserQ.LowerUserName = lower(acm_users.username)
ArchiveUserQ.FirstName = acm_users.firstname
ArchiveUserQ.LastName = acm_users.lastName
ArchiveUserQ.DaysExpired = acm_users.daysexpired
ArchiveUserQ.DaysLeft = acm_users.daysleft
ArchiveUserQ.Password = acm_users.PASSWORD
I have to admit that I find the second example cleaner and easier to read. For me it's not so much that I can see the variable names any better, it's
simply that I can tell right away that all of these are assignment statements, and that's helpful. Also I work on a team with other developers, some
of whom do format assignment statements this way, so if I can easily make my code conform to their standards life is easier.
But I noticed one problem early on. If I had a non-assignment statement somewhere in the middle of my code block, the indent wasn't preserved:
ArchiveUserQ.UserID = ausers:userid
ArchiveUserQ.UserName = acm_users.username
ArchiveUserQ.LowerUserName = lower(acm_users.username)
ArchiveUserQ.FirstName = acm_users.firstname
! A comment here
ArchiveUserQ.LastName = acm_users.lastName
ArchiveUserQ.DaysExpired = acm_users.daysexpired
ArchiveUserQ.DaysLeft = acm_users.daysleft
ArchiveUserQ.Password = acm_users.PASSWORD
Notice that the alignment after the comment has been reset to the shortest required spacing.
I asked Rick about this, but I needn't have wasted his time. He just pointed me to the settings, which are available on the Options menu:
I unchecked Process Contiguous Blocks Separately, and the addin maintained a consistent assignment operator column despite the insertion of a
non-assignment statement. Slick!
ArchiveUserQ.UserID = ausers:userid
ArchiveUserQ.UserName = acm_users.username
ArchiveUserQ.LowerUserName = lower(acm_users.username)
ArchiveUserQ.FirstName = acm_users.firstname
! A comment here
ArchiveUserQ.LastName = acm_users.lastName
ArchiveUserQ.DaysExpired = acm_users.daysexpired
ArchiveUserQ.DaysLeft = acm_users.daysleft
ArchiveUserQ.Password = acm_users.PASSWORD
I left the Invoke Smart Formatting First option checked; I use Shift-I on a regular basis to reformat code, so I'm happy to have Rick's addin do that
for me if I forget.
Now that I have this addin I use it constantly, both on new code and whenever I revisit old code.
The Format Assignment Statements Addin by Upper Park Solutions is available from ClarionShop. The price is a very reasonable $29.95. Highly
recommended.
As I was processing records in primary key order I decided I could do everything more simply with a window procedure. And it turned out to be
even easier than I expected, thanks to some useful progress bar property statements. I'll get to those a little later.
In this article I'll show how to add a similar procedure to the venerable People.app.
I start by adding a button to the browse from which I want to call the export process. (I won't actually do any exporting in this example; instead I'll
just display data from the records I process.) I set the Action of that button to call a new procedure, which I create using the Window template.
Feel free to use the Window wizard if you're more comfortable with it - the only difference is that the wizard lets you set styles and will create the
window for you; with the template you'll create the window the first time you go to the window editor.
I need the following elements on the form:
a pause button
Simply change the ENTRY to a STRING - everything else on the line can stay the same.
Once you get comfortable with Clarion Window structures you can make all kinds of changes. One I make regularly is change entry
fields to spin boxes. It's less work than populating a new control and changing the use variable.
Using a timer
Looping through records in a tight loop with no possibility of interrupting the loop is almost never a good idea unless you are only dealing with a
handful of records. Clarion reports and processes avoid this problem by using a window timer and looping through a smaller number of records on
each timer event. In between these processing loops the UI can respond to user input.
I'm going to use a similar approach, which means I need to be able to put code into the Event:Timer embed. And that embed only appears when
the window has a Timer attribute. Any non-zero value will do.
When the window opens up, any timer event code will execute because of that required Timer attribute. So I need to disable the timer. Right after
Self.Open(WINDOW) I add this code:
0{prop:timer} = 0
The value of 0 before the property assignment means I am assigning to the currently active window. I could also use
Window{prop:timer} = 0
but "0" takes less typing. And if you're ever writing code that needs to apply generically to a window using 0 means you don't need to know the
window label.
I have a little more setup code that executes around the same time (actually just before opening the window):
assumes the first record I want to process is in memory, and saves the ID of that record in FirstPeopleID
primes LastPeopleID with the hightest ID in the file
clears the record so nothing displays when the window first opens
Here's the Event:Timer code (I'll explain the use of PROP:Progress in a moment):
next(People)
if errorcode() or PEO:Id > LastPeopleID
0{prop:timer} = 0
post(event:closewindow)
else
?Progress{PROP:Progress} = PEO:Id
end
First I set a local variable which is the value that will be applied to Prop:Timer. There are two places this happens (as you'll see); I use a local
variable so that if I want to change the value I only need to do so in one place. A value of 50 means the timer will execute every half second,
which is probably a lot less frequent than I'd really want it to happen but it allows the process to execute at a good demonstration speed. I'll also
only be processing one record per timer event.
This is the bit I really like about this whole procedure. By default, progress bars have a range of values from 1 to 100, and if you want to use that
range and you don't have exactly 100 records you need to do some math.
?Progress{PROP:RangeLow} = FirstPeopleID
?Progress{PROP:RangeHigh} = LastPeopleID
The RangeLow and RangeHigh properties let you set the lower and upper boundaries to whatever value you like. Because I'm setting the low
value to the first primary key value, and the high value to the last primary key value, all I need to do to display the progress bar is set
PROP:Progress to the current primary key value (as shown earlier):
?Progress{PROP:Progress} = PEO:Id
I'm not sure just how big RangeHigh can get, but I've tried it with the maximum LONG values and it still works, so if you're using LONGs for
primary key values you needn't worry.
There are two more events I need to respond to: Pause and Cancel.
The Cancel button code is simple - just disable the timer and post a CloseWindow event:
0{prop:timer} = 0
post(EVENT:CloseWindow)
The Pause button code is more elaborate, as I want to be able to toggle the button text between 'Pause' and 'Resume'. This is also the second
place where I use the TimerInterval variable.
if ?PauseButton{prop:text} = 'Pause'
if 0{prop:timer} > 0
0{prop:timer} = 0
?PauseButton{prop:text} = 'Resume'
end
else
?PauseButton{prop:text} = 'Pause'
0{prop:timer} = TimerInterval
end
Summary
Although I could have used a Process template in this situation, with relatively little code I have achieved the following:
This technique works best on sequentially numbered single component keys, such as primary keys, although with a bit of work you might be able
to adapt it to other situations.
And of course this was a one-off job; if I come across other places where I need something similar I'll look into creating a class and/or a template.
But sometimes a little hand code goes a long way.
Source code
There's also information about Devstart.io itself, including the product roadmap:
Many of the menu items are currently under construction, but what is there seems to work very well. Brahn continues to do great work extending
the IDE for a better developer experience.
That is, it's no great hardship except for having to deal with the annoying UAC prompt every single time I run Clarion. Finally I got annoyed
enough to start looking for a way to disable UAC for just the Clarion IDE.
I found the answer at http://www.wintuts.com/Disable-UAC-for-certain-applications. That page has a nice, detailed explanation of the process.
Here's a summary.
First, download and install the Microsoft Application Compatibility Toolkit, then run the Compatibility Administrator. Under the Custom Databases
node select the default custom database and right-click. Choose Create New|Application Fix. Fill in the blanks on that form and click Next. On the
Compatibility Modes window select RunAsInvoker. Click Test Run if you want to be sure this works.
You can accept the defaults on the following Compatibility Fixes window.
You may however want to consider some of the options for the Matching Information window. If you accept the defaults, then any time you
upgrade Clarion you'll also have to recreate or update the database information. If you remove all of the matching options then you can upgrade
without issue, but UAC will not be able to detect any malicious changes to the executable.
Click Finish, then use File|Save and save the database to disk. Run a command prompt elevated and execute
sdbinst name_of_the_database_file
And that's it - if you did everything right you'll be able to run Clarion without getting a UAC prompt.
I tried to add two entries to my database, one for Clarion 8 and one for Clarion 9 but I got a warning that as the entries were similar I could get
unpredictable results. I'm guessing that since the settings are the same that wouldn't be a problem, but to be safe I created two databases, one
for each Clarion version.
Expanding and moving the listbox formatter and data property windows
Mike Hanson reminded me of a nifty Windows trick the other day, one which I'd never thought to use on the Clarion IDE's list box formatter.
The list box formatter window is by default quite small, but you probably already know that it's resizable, Just grab the handle at the lower right
corner and drag the window to the desired size.
You can also maximize the window if you like, and the IDE does remember the last position of the window.
As of Windows 7, you can also maximize windows vertically. Just put your cursor at the top or bottom of the window until you see the resize arrow
and then double-click (as Mike reminded me). Presto - the window maximizes vertically! This is especially useful because at the default size many
of the properties are not visible - vertically maximizing the window shows as many properties as possible and greatly simplifies setting those
properties.
You can also do this via the keyboard. Hold down Shift+Windows and the up or down arrow key to vertically maximize/restore the window.
If you have multiple monitors, try Shift+Windows and the left and right arrow keys - these will move the list box formatter window to the next
monitor. However you locate/resize the window the IDE will remember the location the next time you open the window.
Another window that behaves this way in the IDE (aside from the IDE itself) is the data properties window ( as when you're in the data pad and
you want to edit local, module or global data. Because the data properties window has a control properties pad similar to the one in the list box
formatter, vertically maximizing is helpful her too although mainly just on the Controls tab.
Most IDE windows, however, do not follow these rules of behavior. Dockable windows such as the properties pad, task list etc. are unaffected by
the double-click and Windows key shortcuts. These windows can still be relocated and resized using the mouse.
But maybe there are other windows in the IDE that can be do these Windows 7 tricks. If you know of any please post a comment!
Managing multiple Clarion versions with ClaSwitch2
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.
<?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.
<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>
<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>
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).
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
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 ?.
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
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
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.
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.
The one most often used by Clarion devs is Flywheel, which you can download from any number of web sites including PC World. Another is Free
wheel.
Flywheel is no longer supported but it does require registration. All users can register under the name "I Am Free" with the registration number
13601409.
The stack trace suggested a problem with LoadSolutionProjectsInternal() - could there be some bad project data? But no, I couldn't find any
reason in the project data for the problem.
I'd also been making some modifications to the Clarion template chain (I'm working with a lot of legacy apps these days) and at first I thought this
had something to do with a template mod gone bad. But oddly most of the apps I tried to generate worked fine; only a few crashed. And they were
apps I'd worked on recently, and I'd selected some global options in those apps. I thought maybe that was the reason.
But I couldn't find a pattern; I tried copying in saved versions of the apps; the same ones crashed as before.
You can also clear out preferences via the ResetIDE program that ships with Clarion.
ClarionCL command line parameters
The Help file lists a number of parameters for the ClarionCL command line utility. Here's one that appears to be undocumented, and was passed
along to me by Ilka Ferrett.
The /p parameter lets you pass compiler settings, which in this example will set debug info (vid) to min and turn on line numbers.
/p:vid=min;line_numbers=True
If you have any other ClarionCL tips, please post them here!
The SharpDevelop team began work on ILSpy after Red Gate announced that it would stop releasing thee free version of .NET Reflector after
Feb 2011, and only paid versions would be available.
You can also view the IDE's XLog files using Jeff Slarve's utility, available from the downloads page.
Dictionary Editor
Show the data type
Turn off the partial save warning
Fortunately there's a way to fix this. Go to Tools | Options | Clarion | Dictionary Editor Options. Click on the Column Options tab.
There are a bunch of things you can add, but the one I really cared about was Column Type.
That let me see the data type along with the column name, which made it a whole lot easier to see what I'd done and what still needed to be
done.
Now, if the display were columnar instead of just concatenated text that would be really cool....
The downside, of course, is that you don't get a chance to reject your changes to a field. But any information in the columns list isn't updated until
you accept your changes (say, by clicking on another field) so if you need to back out you can always use the column display as a reference.
Turn off the partial save warning
You're editing a dictionary, and every time you make a field change you get the annoying warning "The item has changed. Do you want to save
the changes?"
The fix is easy. Go to Tools | Options and locate the Dictionary Editor Options settings for Clarion. The Ask Before Partial Save option controls the
display of the message.
Uncheck the option and the warning message goes away.
Easy OMITs
Mark Goldberg had a nice tip in the March 16 ClarionLive webinar. Highlight some text you want to omit and click on the drop button as shown:
You'll get another window where you can type in the terminator and an optional expression:
OMIT('**END**')
CASE STATUS(PassedLabel) !Check parameter passed
OF WINDOW:OK !if window is good, and opened
CYCLE !continue
OF WINDOW:NotOpened !if window is good, but not opened
OPEN(PassedLabel) !open it
OF WINDOW:BadWindow !bad window?
MESSAGE('Invalid Window','Note:') !exit procedure
RETURN
END
!end of OMIT('**END**')
Type in an expression or value (you'll need to define an equate somewhere to define the value) and you get a conditional omit. With an
expression of MyFlag you get this:
OMIT('**END**', MyFlag)
CASE STATUS(PassedLabel) !Check parameter passed
OF WINDOW:OK !if window is good, and opened
CYCLE !continue
OF WINDOW:NotOpened !if window is good, but not opened
OPEN(PassedLabel) !open it
OF WINDOW:BadWindow !bad window?
MESSAGE('Invalid Window','Note:') !exit procedure
RETURN
END
!end of OMIT('**END**', MyFlag)
Naturally, I want to know the status of those builds. I use TeamCity's Window Tray Notifier, a little app that sits in the system tray and gives me a
visual indicator of the status of the four or five projects (individual releases of that massive app) that may be building on any given night.
Only lately I can't seem to get get the Tray Notifier to report on all those apps - it just seems to want to report on two of them. I should find out
why.
But in the meantime I had another thought. TeamCity uses a web interface, so when I want to see all of the currently active projects I'm actually
just launching a browser.
And doesn't the IDE use an embedded browser for its start page?
So I got to wondering if there was some way to configure the IDE to show a specific web page instead of its own start page. I didn't find a way to
do that in the IDE's configuration files (if you know how, please tell me) but I did find out that if you pass a URL as a parameter to Clarion.EXE the
IDE will display that URL. Problem solved.
The only minor hassle is that to display the regular start page I have to choose View | Start Page (or press Alt-V,S).
So now I have two Clarion8 shortcuts. I've named one Clarion 8 (recents):
and it has the usual Target value:
The other shortcut is called Clarion 8 (TeamCity) and it has this target:
Since I'm mainly concerned with the status of the nightly builds, I'll usually run the TeamCity version once in the morning; after that I'll stick with
the regular shortcut.
The more embeds I'm using, the more jumping around I tend to do. If the embeds are close together it's no big problem. If they're all over the
procedure it's more of a hassle to quickly get to where I need to be.
The Clarion IDE does have a nifty bookmarking capability - just press Ctrl-F2 on any line and a bookmark appears in the bookmarks list. (Ctrl-F2
again to remove the bookmark, or click on the ilttle button icon that appears to the left of the line number.) Double click on the bookmark to go to
that line in the source; if the file isn't open the IDE will open it for you and take you to that line. Bookmarks are persistent for source files, so they'll
be there the next time you load that project.
But what about APPs? It turns out that bookmarks work within the embeditor:
But as soon as you close the embeditor any bookmarks you've created in that procedure disappear from the bookmarks view.
There is however an easy way to create persistent bookmarks for embed points. Go to Tools | Options | Task List and add a token called
BOOKMARK (or any other text you like):
Anywhere you want a bookmark in your code, put the text
!BOOKMARK
followed by whatever text you want to appear in the bookmark. You might add something descriptive such as a routine name:
!BOOKMARK BRW1::EIPManager.SaveOnChangeAction
With the embeditor open, view the task list and look for lines that begin with BOOKMARK. Double-click on any item to jump to that location in the
code.
The downside
For one, the bookmarks only show up when you're in the embeditor.
For another, once the code is generated you may find that you have duplicated line entries:
This is because the IDE is showing you both the embeds in the embeditor and in the generated source. To avoid confusion click on the File
header to sort the list in file order - that way you'll at least have all of the embeditor bookmarks in the same location.
There's another issue, which is that unlike the bookmarks list, which a tree view, this is a straight list of tasks and you can't hide tasks from other
files by collapsing a tree node.
And finally this list contains all tasks - if you have !TODO entries in your code they'll show up along with your bookmkarks.
That said, this is still a useful trick and can be a real timesaver if you're spending a lot of time in a procedure and you need to regularly move
between distant embed points.
WSLDial errors
WSLDial errors were more common in the early days of Clarion for Windows, but they still crop up every now and then.
WSLDial 01
This error
Note to programmers
WSLDIAL 03 error
- A procedure is calling itself.
WSLDIAL 04 error
This error is generated by incorrect data definitions.
Declarations must be the same throughout apps and dlls
eg: SampleQ Queue,THREAD,external,dll
SampleQ Queue,THREAD,dll (Missing the external attribute)