Button embed issues to Run One Copy of Two Browses

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?

Presumably because the run once code is on the event:selected not the event:accepted

Glo:ThreadNo Long ! Not Threaded

Accept Embed

IF NOT Glo:ThreadNo
  IF UserType1 = TRUE
  Glo:ThreadNo = START(Browse1, 25000)
  ELSE
  Glo:ThreadNo = START(Browse2, 25000)
  END
END

Browse1 & Browse2 Procedure
Window Events Close Window Embed

Glo:ThreadNo = 0

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!

Yes, event:Accepted is the correct embed.

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.

Bruce,

The code is on the Window Behavior within the called procedure…

I (so far) have ended up using the code as RchdR suggested. I placed it in the button’s Accept embed after the Generated Code and it runs “correctly.”

Thanks for the help on this.

In the Jurassic Period, SV had a FAQ on their website.

One of the items was on how to do a single-instance restriction for a procedure.

The FAQ is long gone, but I copied the code into a little template for my own use. (My fix for mouse wheel scroll is also in this part of it).

#! Dec 26, 2009
#!
#Extension(Single_instance_browse,'Limit MDI window to single instance'),Procedure
#BOXED ('')
#DISPLAY('Limit threaded MDI window to one instance')
#INSERT(%GeneralLogoHeader)  
#ENDBOXED
#AT(%DataSection),PRIORITY(3000)
! placed by Jane's run-once template
LOC:IsRunning        BYTE
LOC:ThreadNumber     LONG,STATIC
LOC:Restore          BYTE,static
#EndAT
#AT(%WindowManagerMethodCodeSection,'Init'),PRIORITY(50)
!   placed by Jane's run-once template
  IF NOT LOC:ThreadNumber
    LOC:ThreadNumber= THREAD()
    LOC:IsRunning=TRUE
    LOC:Restore=false
    #EMBED(%jfsi1,'Add more if-running here.')    
  ELSE
    #EMBED(%jfsi2,'Add more if-not-running here.')  
    LOC:Restore=true
    POST(EVENT:GainFocus,,LOC:ThreadNumber)        ! IF an instance is already running,
                  ! switch to it, maximize it, and bail
    RETURN(LEVEL:FATAL)
  END
#EndAT
#AT(%WindowManagerMethodCodeSection,'Kill'),PRIORITY(4900)
!   placed by Jane's run-once template
    IF LOC:ISRunning
      LOC:ThreadNumber=0
      LOC:Restore = false
      #EMBED(%jfsi3,'Add more if-running here.')      
    END ! IF
#EndAT
#AT(%WindowEventHandling,'GainFocus'),PRIORITY(6050)
!placed by Jane's run-once template
    TARGET{PROP:Active} = true
    IF TARGET{PROP:Iconize} = TRUE  AND LOC:Restore=true
      TARGET{PROP:Iconize} = ''
      LOC:Restore=false
    END ! IF
#EndAT
#Extension(VCR_and_scroll,'Set browse VCR and mouse wheel scroll'),MULTI,Procedure
#!SHEET
#!TAB('')
  #BOXED('Add VCR buttons to browse and set mouse wheel scroll')
  #DISPLAY(' ')
  #PROMPT('Select equate for browse list',Control),%ListEquate,REQ
  #DISPLAY('')
  #INSERT(%GeneralLogoHeader)   
  #ENDBOXED
#!ENDTAB
#!ENDSHEET
#ATSTART
  #IF(NOT %ListEquate)
    #ERROR('Procedure ' & %Procedure & 'error:  list equate not specified')
  #ENDIF
#ENDAT
#AT(%WindowManagerMethodCodeSection,'Init'),PRIORITY(9300)
! placed by Jane's VCR/wheel-scroll template
    %ListEquate{prop:vcr}=1
    %ListEquate{prop:wheelscroll}=120
#EndAT

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

Good thought, Carl.

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.

But I agree, it’s a potential weakness.

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.

Indeed.
For several years, I just manually pasted the code from the SV FAQ.

Eventually I made a little extension template so I wouldn’t need to keep looking up the code.

IIRC @jslarve had a Thread Limit solution that used a Class in an interesting or unique way… Jeff ?