Ask Dr. DePhobia - Questions and Answers about Clarion
Posted March 1 1998
In November 1996, I wrote an article for the Clarion for Windows Journal (Vol. 1, No. 4) which explained one technique of limiting a procedure to a single instance. Now that Clarion 4 is out and in widespread use, there have been many requests for a version of that template that works with the ABC templates. Well, the wait is over.
How do I limit an MDI Child procedure so that it only STARTs once in a session? The behavior I want is: If the user opens a window using the toolbar button or menu command, it opens the window if it is not already open, or brings the running window to the front if it is already open.
This is basic thread management and there are a few utilities available. But in your case, you really only need a few lines of embedded code.
Most thread management schemes approach managing threads from the caller. In other words, code is added to each command button or menu item to handle the thread management. In your scenario, it is easier to add the code to the procedure you want to control. This way ensures that the thread management is enforced no matter how the procedure is STARTed.
Let's look at the code:
First, we need two variables; a global flag and a local flag. These flags will store the thread number of the procedure returned by the THREAD() function. This function returns a LONG data type, but we also learn by reading the Language Reference, that the return value is always less than 64 (the maximum number of threads in Windows). This tells us that we could make our variables BYTEs instead of LONGs and save a small bit of overhead.
Let's name the variables GLO:Running:MyProcedure (where MyProcedure is the procedure name) and LOC:Running. We will use the local variable to flag whether or not to reset the global variable when exiting the procedure. Notice that we add the procedure name to the global variable to ensure it is unique. For the local variable, we'll never have more than one, so we don't have to add anything to make it unique. For this example, we will use BrowseProduct for the name of the procedure.
Add the following code to the WindowManager Method Executable Code Section --Init--()--BYTE Priority:FIRST embed point.
IF NOT GLO:Running:BrowseProduct GLO:Running:BrowseProduct=THREAD() LOC:Running = GLO:Running:BrowseProduct ELSE POST(Event:GainFocus,,GLO:Running:BrowseProduct) Return(Level:Fatal) END
This code does one of two things. When the procedure is STARTed by any means, it checks the global variable to see if a copy of the procedure is already running. If it is, it POSTs a GainFocus event to that window and exits the second instance of the procedure. If it is not running, it stores the thread number in both variables (local and global), and continues.
Next, add the following code to the Window Event Handling--GainFocus Priority:FIRST embed point.
window{PROP:Active}=TRUE
IF window{PROP:Iconize}=TRUE
window{PROP:Iconize}=''
END
This brings the window to the front and restores it if it is iconized.
Finally, add the following code to the WindowManager Method Executable Code Section--Kill-- ()--,BYTE Priority:LAST embed point.
IF LOC:Running:BrowseProduct GLO:Running:BrowseProduct=0 END
This resets the global flag when a running procedure exits. This allows the procedure to run again. It does not set the global flag when exiting a procedure that hasn't run.
The combination of these three embeds accomplish the following.
When the procedure is STARTed by any means, it checks the global variable to see if a copy of the procedure is already running. If it is, it POSTs a GainFocus event to the already running window and exits the second instance of the procedure. If it is not running, it stores the thread number in both variables (local and global), and continues.
The code in the GainFocus event makes it active when focus returns.
Finally, the code in the WindowManager Method Executable Code Section--Kill-- ()--,BYTE embed point resets the global variable when the already running copy exits. This allows the procedure to run again.
Ok, now let's turn this concept into a template so we'll never have to write the code again.
To start the Template, begin with an appropriate header:
#TEMPLATE(ThreadLimiter,' Thread Limiter '),FAMILY('ABC')
#!----------------------------------------------------------------
#! Thread Limiter
#! Written for Clarion Online Subscribers Only
#! Copyright (c) Jim DeFabia 1998 (EMAIL: jim-d@topspeed.com)
#! All Rights Reserved
#! Do Not redistribute without permission from the author
#!---------------------------------------------------------------
#EXTENSION(LimitStarts,'Limit Procedure to One Start'),PROCEDURE
Next, the template must create the global and local variables needed:
#AT(%GlobalData) GLO:RUNNING:%Procedure BYTE #ENDAT #LOCALDATA LOC:RUNNING BYTE #ENDLOCALDATA
Next it writes the code we previously embedded in the WindowManager's Init method:
#AT(%WindowManagerMethodCodeSection,'Init','(),BYTE'),FIRST
IF GlobalRequest <> SelectRecord
IF NOT GLO:RUNNING:%Procedure
GLO:RUNNING:%Procedure=THREAD()
LOC:RUNNING= GLO:RUNNING:%Procedure
ELSE
POST(EVENT:GainFocus,,GLO:RUNNING:%Procedure)
RETURN(Level:Fatal)
END
END
#ENDAT
Next, it writes the code we previously embedded in the Window Event Handling--Gain Focus embed point
#AT(%WindowEventHandling,'GainFocus'),FIRST
%window{PROP:Active}=TRUE
IF %window{PROP:Iconize}=TRUE
%window{PROP:Iconize}=''
END
#ENDAT
Finally, it writes the code we previously embedded in the in the WindowManager's Kill method:
#AT(%WindowManagerMethodCodeSection,'Kill','(),BYTE'),LAST IF LOC:RUNNING GLO:RUNNING:%Procedure=0 END #ENDAT
That's it. Now you can add this extension to any MDI Child procedure and limit it to one start.
Here is the complete template:
#TEMPLATE(ThreadLimiter,' Thread Limiter '),FAMILY('ABC')
#!----------------------------------------------------------------
#! Thread Limiter
#! Written for Clarion Online Subscribers Only
#! Copyright (c) Jim DeFabia 1998 (EMAIL: jim-d@topspeed.com)
#! All Rights Reserved
#! Do Not redistribute without permission from the author
#!---------------------------------------------------------------
#EXTENSION(LimitStarts,'Limit Procedure to One Start'),PROCEDURE
#AT(%GlobalData)
GLO:RUNNING:%Procedure BYTE
#ENDAT
#LOCALDATA
LOC:RUNNING BYTE
#ENDLOCALDATA
#AT(%WindowManagerMethodCodeSection,'Init','(),BYTE'),FIRST
IF GlobalRequest <> SelectRecord
IF NOT GLO:RUNNING:%Procedure
GLO:RUNNING:%Procedure=THREAD()
LOC:RUNNING= GLO:RUNNING:%Procedure
ELSE
POST(EVENT:GainFocus,,GLO:RUNNING:%Procedure)
RETURN(Level:Fatal)
END
END
#ENDAT
#AT(%WindowEventHandling,'GainFocus'),FIRST
%window{PROP:Active}=TRUE
IF %window{PROP:Iconize}=TRUE
%window{PROP:Iconize}=''
END
#ENDAT
#AT(%WindowManagerMethodCodeSection,'Kill','(),BYTE'),LAST
IF LOC:RUNNING
GLO:RUNNING:%Procedure=0
END
#ENDAT
To use this template, copy the above code into a text file and save the file as a .TPL in your ..\Clarion4\Template directory. Register the template and add it to any MDI procedure you want to limit to a single instance.
Article comments
Post a comment
You must be logged on to post comments.
Talk To Us!
Search ClarionMag
From the archives
The Five Minute Developer: Sorting QUEUEs
4/28/2006 12:00:00 AM
Clarion's QUEUE structure is useful not just for storing data, but for sophisticated sorting. But by default, QUEUEs are case sensitive. Dave Harms explores several options for case insensitive sorts, including custom sort procedures.
