Sheet with Tabs - how to react to when a tab gets opened?

I want to run a piece of code when a specific tab gets selected - I tried to use CHOICE inside the TabChanging event, sadly that only gives me the current tab before changing - but how can I check if the tab being changed to, is the tab I want to run the code for?

Try the NewSelection event on the SHEET (not a tab). Then you can check for PROP:ChoiceFEQ.

  YourTabFEQ = ?Sheet{PROP:ChoiceFEQ}

With CHOICE(), you just get the position of the tab within the SHEET, which can change as your code gets modified over the years. So ChoiceFEQ is safer.

4 Likes

And I guess I can compare it against the tabs PROP:FEQ?

The field equate itself, such as ?MyTab.

Against the Tab’s USE(?x_PickMe_PickMe_x)

E.g. if Tab 1 is active then ?Sheet1{PROP:ChoiceFEQ} = ?Tab1 which is probably the value 2 (FEQs are assigned a sequence integer by the compiler).

For Tab 1 the CHOICE(?Sheet1) = 1 … currently but CHOICE() could change if the Tabs are reordered, or a new first tab is inserted, and it breaks your code. Using PROP:ChoiceFEQ is more solid code.

        SHEET,AT(7,7,101,67),USE(?Sheet1)
            TAB('Tab1'),USE(?TAB1)
            END
            TAB('Tab2'),USE(?TAB2)  
            END
        END
2 Likes

Greetings -
For those who want to see a debug line showing the name of the TAB that is being accessed, the WHICH TAB= line will show you the tab name.

  ReturnValue = PARENT.TakeNewSelection()
    CASE FIELD()
    OF ?SHEET_Lists
      UD.Debug('%ControlEvenHandling/?SHEET_Lists/NewSelection/5000')
      UD.Debug('WHICH TAB="' & CLIP((?SHEET_Lists{PROP:ChoiceFEQ}){PROP:Value}) & '"')
      
      CASE (?SHEET_Lists{PROP:ChoiceFEQ})
      OF (?TAB_PartPrice)
      OF (?TAB_Documents)
      END
    END

Note: Prop:Value leaves out the “&” Ampersand that prefixes the Accelerator Key, while PROP:Text includes it e.g. TAB('By &Vendor')

3 Likes

IIRC you’re right, you can’t determine the new tab when you receive EVENT:TabChanging
But you can tell after another trip through the accept loop.

CASE EVENT()
  OF EVENT:TabChanging ; POST(EVENT:User:TabChanged)
  OF EVENT:TabChanged  ; NewTabFEQ = ?Sheet{PROP:ChoiceFEQ}
END 

FWIW, I use

Event:APP        EQUATE(08000h)
Event            ITEMIZE(EVENT:APP),PRE(Event)
!...
User:TabChanged     EQUATE(EVENT:App + 100)
!...
                 END 

After Event:TabChanging the Sheet is going to get an Event:NewSelection so I’m not sure I see the need for a POST(EVENT:User:TabChanged). Think that event would come after the NewSelection unless there was a (,,1) to make it first.

I have done the below where in Tab Changing I save what Tab I came from so in NewSelection I can know e.g. to go back to it in some cases.

CASE FIELD()
  OF ?Sheet1
     CASE EVENT()
       OF EVENT:TabChanging   ; TabChangedFrom_FEQ = ?Sheet{PROP:ChoiceFEQ}
       OF EVENT:NewSelection  
           TabSelectedTo_FEQ = ?Sheet{PROP:ChoiceFEQ}
           !Code to decide things based on From and To Tab ...
           ...
           TabChangedFrom_FEQ = 0   !Maybe clear so cannot do it since NewSelection could get posted
END 

A typical use for EVENT:TabChanging is to validate the tab and prevent changing. Seems like a CYCLE would prevent the change, but I do not see it in the help. I would think a SELECT(?ControlWithProblem) would.


I don’t know of a way in Clarion. Maybe look at MSDN the Windows API on Tab Controls.

I see the TCN_SELCHANGING notification code that looks like EVENT:TabChanging. If you search for TCN_SELCHANGING you’ll often find the question “How to know the New Tab”.

Excerpt from one answer that says what I’m saying … Save the Previous Tab in Tab Changing.

But… why don’t you use OnSelChanging to save the previous Tab selected (in a member variable) and OnSelChanged to retrieve the New tab selected, and don’t you do in OnSelChanged what you do in OnSelChanging?
Isn’t it easier?

1 Like

My ancient notes on the subject indicate that it matters if the tab change was user initiated or programattically initiated.

And you won’t always get an event:newselection after you get the event:TabChanging

1 Like

Correct a SELECT(?Tab) or SELECT(?Sheet, Number) does not generate either events. That makes sense to me that if you Change Tabs in Code … then in Code you handle what else you want to happen.

I sometimes have a “Next Tab” button that will use SELECT(?Tab). I have a function for that which has code to Post New Selection:

SelectNextTab   PROCEDURE (LONG SheetFEQ,BOOL NoWrapToFirst=False, BOOL NoEventNewSelect=False)
SheetChoice     LONG,AUTO
SheetTabs       LONG,AUTO
TabFEQ          LONG,AUTO
  CODE
    SheetChoice = CHOICE(SheetFEQ)
    SheetTabs   = SheetFEQ{PROP:NumTabs}
    IF SheetTabs < 2 THEN RETURN.           !If just 1 tab, or would be zero if not a sheet then why bother
    LOOP SheetTabs + 1 TIMES                !Sanity check to prevent infinite loop
        SheetChoice += 1
        IF SheetChoice > SheetTabs THEN     !Are we beyond the last tab
           IF NoWrapToFirst THEN BREAK.     !The current tab must be the last visible tab so no change.
           SheetChoice = 1
        END
        TabFEQ = SheetFEQ{PROP:Child, SheetChoice}
        IF ~TabFEQ OR TabFEQ{PROP:Hide} THEN CYCLE.   !Tab Not Exist (=0) or Tab Hidden then move to Next    

        SELECT(TabFEQ)
        IF ~NoEventNewSelect THEN 
           POST(EVENT:NewSelection,SheetFEQ)    !SELECT(Tab) does NOT cause this event and many windows expect it
           !Don't get event POST(EVENT:TabChanging, SheetFEQ) and CANNOT Post it because SELECT() above has already changed tab
        END
        BREAK
    END
    RETURN

After messing with this for a couple of wizard procedures, I derived a wizard window class adding TakeNext, TakeBack, TakeTabChanging, TakeTabSelection methods. This worked well and didn’t confuse an old brain that struggles to keep a charge.

After I found myself wanting those TakeTab methods in non-wizard procedures, I simply moved them to a derived general windows class and dropped the wizard window class. I am sure it is marginally less efficient, but it gets around the EVENT stuff that is impossible for me to always keep in mind.

I had some success by declaring a string variable - e.g.: SHEETVariable
then… For the SHEET declaration: USE(SHEETVariable)
In CODE:

 If SHEETVariable = 'MyTAB' (Text in TAB properties)
    Do Something
 End

Yes a SHEET(v) + TAB('&Blarg') can work
like an OPTION(v) + RADIO,VALUE('Blarg')
and set the a Variable to the selected thing’s value.

Note the Sheet variable gets the Tab PROP:Value without the (&) Ampersand Hotkey Prefix.

I would not code it that way because if someone decided to change the Tab ‘Text’ it will silently break the code without any compiler error. Same if deleting the Tab. If you spell ‘the value’ wrong it fails silently.

If coded with PROP:ChoiceFEQ it handles most changes, others will cause errors like changing the Tab ?FEQ or spelling it wrong. There is no silent failure.

3 Likes