Modeless is mandatory - but it becomes the revolution - i will not make many words but a bit of code here.

One problem that i can not solve without workaround should be no problem for the one who made the callback-variables to get filled with theirs content

And something else could be implemented in one go. I start peacefully with a little TBMain()-function

As first its to reply a callback instantly and not to wait until your code has finally performed what were to do.
If windows waits too long for a reply it switches to "the prgram is not responding" and once its there you have hardly a chance to catch up - in repeated cases it just turns your program off the screen without any information
SO RETURN TRUE ON THE CALLBACK ASAP grab the required info and get out of there. do not process any code within
the callback that needs time - like loading data or huge calculatiions. Owner draw? Prepare it in advance, flip the buffer but do not start drawing within the callback
' the number of 2048s a bit high.... if everything that were to perform
'gets a combination of 2 identifiers like a pointer Dword where to obtain 
' data and an Index Long of the job to perform it makes a quad (DWordLong)
' and 256 undone jobs could line up in this "messagequeue" 
Global UndoneTasks as Dword = Heap_Alloc(2048) 

Global CurrentData As Dword At UndoneTasks 
Global CurrentJob  As Long At UndoneTasks +4

Global CleanEnd As Quad At Heap_end(UndoneTasks)-7

'CurrentData and CurrentJob are the values that have to be processed  

' these determine where new data or job is to add. 
Global NewData As Dword at UndoneTasks
Global NewJob As Long As UndoneTasks+4

' the positions must be moved +  8 if a task was added 

' the 8 bytes at start of the queue give all information that we need
' the layover of 8 bytes at the end is to wipe after moving the queue

 the NewJob and NewData- elements are movable and allow easy
'and safe to poke new information onto the "stack-sled" 

Global continueExecution as Quad ' if user wants to stop let him
           ' set this to a negative number as 0xF000000000000000
Function TBMain()

'use this to control performance:

Quad TimeIsUp

Boolean beDone 

'to keep it simple:
' assume this calls the creation of MainWindow outside of tbMain and returrns thereafter   

Dword hMain = CreateNewWindow(%hWnd_Desktop,"Mainwindow" )
Local keepRunning As DWord At Varptr(continueExecution)+4

Dialog Show Modeless hMain 

   ' give us 10 ms per frame / hirestimer counts Ás - thats microseconds
    ' - independend of a window and internal tick count
               ' now we have time to do a few things until TimeIsUp
              if CurrentData then 
                   Call_IfExist CurrentJob(CurrentData) to beDone 
                     if beDone then
                           ' push the memory left
                          Memory_Move(UndoneTask, UndoneTasks+8, 2040)
                             ' move also the positions where the values are to fill in
                           SetAt(NewJob, GetAt(NewData)
                          SetAt(NewData, GetAt(NewData)-8)
                         CleanEnd = 0
                 end if
                ' process as much as possible an keep an eye on the timer
       until HHirestimer_Get >= TimeIsUp
              Dialog DoEvents to keepRunning
loop While ContinueExecution > 0 

end function
this part 1 - just to give an idea 10 ms is already a time where many mouse-movements and stuff bring up new messages,

The next were to enumerate the jobs that were to do: but thinbasic makes it so very easy for you:

create a sub that handles the event.
use function_getPtr(yourSubsName) and store it as the job to do. For the data required a parameters in your sub you can use a global string array, dimensioned to 255 slots for this example above you could use an array-and put the data there, plce the index with the function pointer of the handler , put all your parameters tokether using MKL$, MKDWD$(CBHNDL,CBCTL,CBMSG,..) etc if its loose values or grab the whole notification-header with memory_Get.
Avoid repeatedly allocating, redimensioning and create fixed buffers wherever you can.

Then part 2 :

in some OOP-UI-TEMPLATE Eros mentioned it were not easy to create ME within the callback - and it's better this way.

But everyx udt has the abiity to get extended - vice versa of the way its intended to be. So all udts can use the same extends-pattern to have a few equal functions and properties.
So very easy. Asume you had an udt for your textbox tTextbx and another one for a button tButton
actually different types

as you know are udt-statics available to any member of the udt. no matter if the member was just now created or long ago. Also the statics are available if no scalar variable of the type is present if you create the udt virtually upon any memory-position - if valid or not - better is to place it to a valid location that provides sufficient space maybe it gets changed in a future version of tb that the access of static udt-elemets will cause an error or will not be able to return that information because its protected or private and it could not determine if access is granted from a member at 0.

Now lets say i give them someting in common for a certain situation in that a control will come like we have
a CBHNDL and a CBCTL - how do we get that control into the callback?

We dont.

We bring virually the callback to visit the control.

Therfor we need an udt that is designed for a very short lifetime only. Such udts must not get created local in tbMain since local variables in TBMain persist until execution ends. Thats why i also decided to mve the dialogs creation to some other function. When the dialog gets the controls applied it could do this as well, since it has all required information about the controls .
When virtually created, an udt will not fire the _Create-event but it can feed the structure with information about the controls so there were the parent handle (as hDlg) and the controlID and the parent itself that might not have a controlID but the direct memory with first hand information and it's the one that is meant by the CBHNDL's value.
As the parent of the controls that is present when controls are created and when these are receiving messages it should have the information also where its childrens data is located and what type the data is. (Not all buttons require same udts - so these are dynamic parameters.

It's own type of attached data-structure it will also be able to tell. Since the hWnd can change if windows likes to, dialogs within a process should have a DialogID / even their number in creation order would serve .

Now we take this short-time udt-reverse+extension during creation of the dialog and controls - virtually only - or within
the Callback <dialogname>OnInit
      Static pDialogs As Dword ' point a global list of all dialogs  data-pointers
      Static pDataStructure As Dword ' point another list  that tells what
                                                      ' data-structures dialogs are using. 
          ' the two arays above must be in same order and if the first created
            ' window knows that it is window number one then it knows the 
              ' index within the both array it has to use. 

     Reply as Long ' the only thing not static is relevant to reply  the callback 
                             'also  an udt without any subelement that requires memory will 
                             ' cause  an error   
function _Create( ByVal CB_HNDL As DWORD, byval CB_Msg as Long,
                     byval cb_WParam as Dword, byval cb_LParam As Long)
' determine the dialogs own data that it created in setup and pre-select alredy if 
 ' the message is important and if its for a control or for the dialog. 

gather variables , copy notification headers or mouse-position, pressed keys

 ' store required data, set pointer and the job to call 
NewData = pWhereYouPutIt
NewJob = Function_GetPtr(whatSubCanHandleThis)
setat(NewData, getat(NewData)+8)
setAt(NewJb, GetAt(NewJob)+8)
' set 
          me.Reply =True  
   End Function 
End Type
this will automatic fire if you write in the callback function (can be one for all)

callback function cbMain() as Long     
Local dlgUnknown As tDialogInCallback(CBHNDL, CBMSG, CBWParam, CBLParam)
 function = dlgUnknown.Reply
End function
Another Function you will need if you want to call the controls to process their events because you can not do it from within the loop in TBMain. As mentioned before - local variales in TBMain will never go out of scope and have lifetime
unlimited relatiive to variables from other functions.

So there is this parameter "NewJob". Can we agree that Function_GetPtr(scriptfunction) will never ever return
a value in the range -1 to 0 ?
Zero means no Job already. If -1 were the value of the job to call, the timer within TBMain might open the package
and call another sub or function that can delegate the event directly to be handled by the udt of the button that was pressed

If all events are sent to the recipient directly from the callback it were of no use since thhe callback can not return the value while the created udt does not leave its scope and so its not destroyed in the callback. Therfore the unknownDialog-
an udt that virtually only handles any callback instantly because it gets created automatic on every incoming callback -message
Through being connected to all dialogs information it has also the possibility to send a message into the mainloop:
what type of data is the control or real dialog using and where is its position in memory.

Obvious is, it will not work using variables or arrays that will change the position when redimensioned

Udts upon allocated heap - but not stored to a dynamic variables cache where is no safety to find the same variable at the same address - will not move to any position at another difference to the pointer of the heap. If reAllocating is neccessary all re-allocated data maintains the same distance to the pointer. And when controls are created its done mostly in one go to one dialog. If the dialog is destroyed also all controls on it are gone. Without a doubt you get the message if the dialog is about to be destroyed.

Now when the unknownDialogis invoked and creates the package with information it can do that reduced to a minimum of required information when the invoker of the actual event-handler has access to the same information and it were good to have such infornation updated synchroneously thats why the statics of the tDialogOnCallback are Dwords containing one pointer only and not dynamic strings that could hold all pointers.

Anyway, the timer-loop within TBMain can call typeless subs or functions directly . To forward the callback to an udt, tbmain requires another function that dynamically will create udts upon the data provided by the controls or dialogs that have a short lifetime only using something as :

function InvokeJob4UDT(byval pUDTElementData as dword,
                                     byval sUdtName As String,
                                    byval  pParameters As Dword ) as Long 

Local UnknownObject Like "" & sUDTName at  pUDTElementData 

function = UnknownObject.ThereIsAJobToDo(pParameters)

End function
That requires objects to have a function named thereIsAJobToDo
and depending on the job the called udt can just do a portion of it and return a value that signals the job is not done yet.
In that case the value can mean to put the order back onto the queue or to call again if still time left for it. Even a row of actions is possible as telling the dialog to create another tabpage within a tabcontrol the dialog returns the handle of the tabpage so the Main function might wait for the cllback message the dialog inside of the tabpage is ready to load and disiplay some content. And all that allows much more control of performance and avoids the all-of-a-sudden-disappearing dialog that al of you might know since thinair has the lexer/parser-problem that is caused probably by some greedy point in a regEx that runs itself to death because it wants to stay in race as long as possible in combination with the codetips that are not possible to disable and do something what ghosts never must do. Instead of being silent helpers only they react to input and cause the helpfile to be searched and open help on something what is displayed on them already.

Not helpful at all/ While typing the mousepointer-position must not be the cause to open a huge popup that covers
more than the codewindow when split view is used. Also the codetips maintain visible very often and it does not help to drag thinAir somewhere else -it just stays there as glued to the window.

It were of much more use when thinair receives keyboard input to check for typed function-names and to determine if its a function from a module, declaration or user-defined of the current script. Detect if the function-header is in view of the user and if it uses more than one line / so there were more than just one parameter to show and it is not difficult to count how many commata are present - but do not count such inbetween anothe pair of parenthesis and let the codetip or a line in the errorr-window show the position within the declartion through highlighting the current parameter.

that can be also for dl/api-functions that are declared in a declare statement but to be honest, the core-functions parameters i know better than the function-parameters of a function that i created just a few day ago. Even api-functions that i repeatedly use i know sometimes better than my own functions and since thes are mostly not in view when i use the call within another function it would save a lot of time to search it to check the parameters because that moment when i grab the mouse to scroll up or down one of these coetips decides to appear and to cover the scrollbar or the code i want to check

when i run it then and some error occurs or even it the script runs without any error seems thinAir is not replying the callbacks of the os. At least it results in the same situation : thinar is gone and mostly did not save the list of currently opened scripts so when i restart it then it loads something else. At least the most recent script is to find quickly within the list of most recent files.

And by mentioning fiiles thinBasic.Include.Script has a mssing dot in the registry. (classes root) It is not using the symbol and also thinair seems not to detect these in a folder within the thinair-filebrowser. that does not even show them nor opens these if clicking the line #INCLUDE "gkgf.tBasicI" in codewindow. If bug within include its cum,brsome search for it since thinair does not show it.

Did i say in the begining i dont want to make many words?

OK, call me a liar