I have two different browse procedures that depends on a user’s categories. (Each of these procedures are set to only run one copy.) I used a button “Selected” embed point to select the appropriate browse:
IF UserType1 = TRUE
START(Browse1, 25000)
ELSE
START(Browse2, 25000)
END
This works as expected EXCEPT when that procedure is closed and I re-select the log button, the Select embed doesn’t run a second time. (I have to select some other button/procedure then when I reselect the log button, it seems to have reset itself?)
I moved my code to the Accept embed point, and that works without having to select a different button/procedure. When I close the log procedure and re-press the log button, it opens the appropriate procedure that was “de-activated” using the Select embed.
HOWEVER, using the Accept embed point, bypasses the Browse “run only one” code. Pressing it three times opens 3 copies of the procedure.
My work around is to leave the button embed points empty and have the button call a “Source Procedure” with the same code I had in the embed points.
Any info/ideas on why the Accept embed bypasses the run only one code in the Browse Procedure?
Well, you learn something new every day! I had no idea this option existed…
From “What’s new in Clarion 9” in the help…
“- ABC: New option to allow a procedure to run only one instance. NOTE: This work when the ‘Call a Procedure’ is used from Menu and Buttons and the START a new thread is used.”
Unfortunately, I suspect Bob Foreman had already left Soft Velocity so the help and examples were never updated to explain how to use it, as far as I can tell.
I had a quick play around with it. The procedure, that you add the “only one instance” property to, creates a variable and sets the value to 0 after ThisWindow.Run()…
CODE
GlobalResponse = ThisWindow.Run() ! Opens the window and starts an Accept Loop
GLO:oneInstance_UpdateStudents_thread = 0
So, in the procedure you STARTED, in the embed for Event:OpenWindow, set that value to the THREAD() value.
! Generated Code
OF EVENT:OpenWindow
! Start of "Window Event Handling"
! [Priority 2500]
GLO:oneInstance_UpdateStudents_thread = THREAD()
! Generated Code
And in the button that STARTs this procedure, check the value before you call it…
OF ?BUTTON1
ThisWindow.Update()
IF GLO:oneInstance_UpdateStudents_thread <> 0
CYCLE
END
START(UpdateStudents, 25000)
! [Priority 5000]
! End of "Control Event Handling"
How does that generated code get added to the Started Proc?
Does a Start Once Extension template get added to UpdateStudents?
Or does adding the Start Once in the Starting proc all that is needed?
It seems like this would not work with the procedures on different DLLs because the Global would need to be in the DCT and exported from the data App.
Jim DiFabia’s Run Once template from CMag was only added to the Started proc. Also when it found it was already running it have focus to the running thread. So this does not appear to be as good.
First the correct Embed is Accepted event After Generated Code. Selected event will occur when the User Tabs to the Button, so even though not pressed it will run your code. But…
Your embed code is not going to work well with the Run One Copy generated code at all. So don’t have embed code on the button…
What will work is to make 2 Buttons call them Btn1 and Btn2. And they are simply set to Start Browse1 or Browse2, plus have the Run One Copy on.
Then after the Open Window add embed code to Hide the Button that cannot be used. A Developer could have both.
IF DevBackDoor = TRUE
!Leave both visible for developer
ElsIF UserType1 = TRUE
HIDE(?Btn2) !uses Browse1
ELSE
HIDE(?Btn1) !uses Browse2
GetPosition(?Btn1,X1,Y1)
SetPosition(?Btn2,X1,Y1) !move 2 where 1 is
END
If your window is tight on space, or it may look better to position button 2 on top of 1. It’s nice to Not have the 2 buttons on top of each other at design time.
In the procedure that you want to start once, you go to Actions, then Window Behaviour button and there’s a checkbox for Start Once. That creates the global variable and adds the code shown above to the STARTed procedure after the RUN statement, but it doesn’t set that value while the procedure is running, so the developer has to add the code I showed.
So the global variable is set by the developer in the STARTed procedure and can be checked in the calling procedure and the developer can conditionally prevent calling it again if the global variable indicates the procedure is already running, as shown above.
It’s a half arsed solution and I agree it seems unlikely to be useful in a multi-DLL environment.
Jim’s solution is probably better but I know nothing about that either!
I think the Run Once option will work with the existing code in an embed. You just have to check the global variable and cycle before calling the code if the STARTed procedure is already running.
It could work…
If the code is in the right embed after the 'If Once" generated code
If an embed exists between “If Once” and Start()
If it’s the right code
If it does a CYCLE so the generated Start() does not run and start 2
My way there is no tricky embed code for Run Once. Also the App Tree will show both Browse1 and 2.
On on my phone so don’t have code or templates so see how C9 Run Once works. The current way with only 1 button calling Browse1 there will only be GLO:oneInstance_Browse1_thread.
Won’t the Browse2 procedure use the “2” variable GLO:oneInstance_Browse2_thread?
That probably why the OP reports the Run One Copy not working.
If the thread is already running the code currently does nothing when the user clicks the button, possibly leaving him confused especially if it is hidden behind another window.
What you can do is bring that running Browse 1/2 Thread to the top and active:
IF NOT Glo:ThreadNo
IF UserType1 = TRUE
Glo:ThreadNo = START(Browse1, 25000)
ELSE
Glo:ThreadNo = START(Browse2, 25000)
END
ELSE !Make running Browse1/2 window Top Most and Active
SETTARGET( , Glo:ThreadNo )
IF 0{Prop:Iconize} THEN !is it minimized?
0{Prop:Iconize} = False
END
0{Prop:Active} = True
SETTARGET()
END
Jane one thing Jim’s template did was if the procedure was called with Request = SelectRecord then it did not execute the Run Once logic at all.
The thinking being that when a Browse is START from the menu, then only allow one.
But often that same Browse is called to Select Record from a lookup button and that must stay in the same thread or it will not work.
So maybe these 2 lines?
IF Self.Request = SelectRecord Then !Select Browse runs always and cannot be LOC:ThreadNumber
! Leave IsRunning = 0 and ThreadNum unchanged
ElsIF NOT LOC:ThreadNumber
LOC:ThreadNumber= THREAD()
LOC:IsRunning=TRUE
I would also Not POST(EVENT:GainFocus,,LOC:ThreadNumber)
Instead right there I would put that Gain Focus code inside a SetTarget. That also lets you get rid of the LOC:Restore variable. E.g.
ELSE
#EMBED(%jfsi2,'Add more if-not-running here.')
!Carl no: LOC:Restore=true
!Carl no: POST(EVENT:GainFocus,,LOC:ThreadNumber)
SetTarget( , LOC:ThreadNumber ) !Carl add
TARGET{PROP:Active} = true
IF TARGET{PROP:Iconize} = TRUE !Carl no: AND LOC:Restore=true
TARGET{PROP:Iconize} = ''
!Carl no: LOC:Restore=false
END ! IF
SetTarget( ) !Carl add
RETURN(LEVEL:FATAL)
END
The code I use was directly stolen from the old SV FAQ.
I never ran into a problem.
But then, if I have a lookup browse it usually is small and has only one or perhaps two fields, so it’s not the same browse that I would use as an MDI data window.
Yep, Richard’s code does the same thing as the checkbox you used, but does it in a way that is easier to implement and understand than the mysterious checkbox code. [g]
It only works if Browse 1 and 2 are only STARTed, or called, from that one procedure.
If they are started or called elsewhere, like the frame, when the Browse closes it will always set 'Glo:ThreadNo = 0` possibly letting it start twice from the window trying to prevent that.
The right way to do it is to have all the Run-Once logic in callee procedure (e.g. Browse 1 / 2). That way no matter who starts it you get one. It also works Multi-DLL. Jane’s code is good example of how to do it. You can do it without a template, it’s not that much code to add.
Yeah, my comments were only related to the built-in technique, which I hadn’t been aware of and Richard’s variation.
Jane’s code looks useful and since it is a template that actually works, is probably the easiest to implement.
@jack_MDB seems to have a solution that works for him, I think he probably has a better idea of how it actually works, which is the most important thing here, IMO.
As to best solution, that depends on circumstance. For instance, I can think of a circumstance where you might want to open a window once, for one customer and have other instances open, simultaneously, for other customers. Perhaps to compare information about multiple customers at once. In this case, none of the offered solutions are adequate out of the box, but all can be adapted to solve the problem, once we know what is going on.