Updating listviews...

Discuss GUI programming with the RapaGUI plugin here
User avatar
airsoftsoftwair
Posts: 5446
Joined: Fri Feb 12, 2010 2:33 pm
Location: Germany
Contact:

Re: Updating listviews...

Post by airsoftsoftwair »

I'm afraid that this is a "feature". GUI applications are expected not to block the main (UI) thread so your application must always make sure to return control to the main loop to give your app a chance to handle refresh and get user input. Instead of timers you could use functions like RunCallback() to push functions on a call stack to make sure your script doesn't block the main loop.

The fact that it works on Windows and on AmigaOS is that the GUI implementation on those OS is more "old-school" than with GTK3 or something. With modern GUI implementations (e.g. GTK3, macOS, Android) is absolutely mandatory to not block the UI thread or your app won't refresh.
User avatar
Allanon
Posts: 732
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: Updating listviews...

Post by Allanon »

airsoftsoftwair wrote: Sun Jan 28, 2024 12:42 pm I'm afraid that this is a "feature". GUI applications are expected not to block the main (UI) thread so your application must always make sure to return control to the main loop to give your app a chance to handle refresh and get user input. Instead of timers you could use functions like RunCallback() to push functions on a call stack to make sure your script doesn't block the main loop.

The fact that it works on Windows and on AmigaOS is that the GUI implementation on those OS is more "old-school" than with GTK3 or something. With modern GUI implementations (e.g. GTK3, macOS, Android) is absolutely mandatory to not block the UI thread or your app won't refresh.
Thank you for the clarification, now the problem is how to implement long callback operations and giving feedback to the user... I was thinking about "application states" to be processed in the main loop.
The idea is that the callback function initialize the long operation and change the app state returning almost immediately, in the main loop, just after the WaitEvent() command I could check the app state and do the processing step by step without blocking the app... I've to think about it...

Code: Select all

--- just an idea ---
Do
  If state = "idle"
    WaitEvent()
    
  Else
    Switch state
      Case "process-file"
        ; When the last file is processed the function sets the state to "idle"
        process_one_file()
      ...
    EndSwitch
    
    CheckEvent()
  
  EndIf
  
Forever
I've to take care that states do not overlap or it became a mess :D
Could this work?
User avatar
Allanon
Posts: 732
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: Updating listviews...

Post by Allanon »

airsoftsoftwair wrote: Sun Jan 28, 2024 12:42 pm I'm afraid that this is a "feature". GUI applications are expected not to block the main (UI) thread so your application must always make sure to return control to the main loop to give your app a chance to handle refresh and get user input. Instead of timers you could use functions like RunCallback() to push functions on a call stack to make sure your script doesn't block the main loop.

The fact that it works on Windows and on AmigaOS is that the GUI implementation on those OS is more "old-school" than with GTK3 or something. With modern GUI implementations (e.g. GTK3, macOS, Android) is absolutely mandatory to not block the UI thread or your app won't refresh.
I was experimenting with my idea and I've noticed that the WaitEvent() never returns, I don't know if this is normal or not with RapaGUI, please have a look at my modified example:

Code: Select all

@REQUIRE "RapaGUI"

Function testFunc()

  For i=1 To 100
    moai.DoMethod("lv", "insert", GetTime(True) .. ": " .. "Hello!\n", "bottom")
    ;moai.DoMethod("lv", "jump", "bottom")
    moai.DoMethod("lv", "redraw")
    Wait(20, #MILLISECONDS)
    DebugPrint("Added item " .. i)
  Next

EndFunction

Function msgHandler(msg)
	Switch msg.action
    ; Parse RapaGUI events
    Case "RapaGUI"
      
      ; Filter actions by attributes
      Switch msg.attribute

        ; Check button presses
        Case "Pressed"
          ; Check buttons
          Switch msg.id
            Case "go"
              testFunc()

          EndSwitch
      EndSwitch
  EndSwitch

EndFunction

moai.CreateApp([[<?xml version="1.0" encoding="iso-8859-1"?>
<application id="app">
	<menubar id="menu">

		<menu title="_File">
			<item id="mn_about">_About...</item>
			<item/>
			<item id="mn_quit">_Quit</item>
		</menu>
	</menubar>

	<window title="TEST LV" id="window" menubar="menu">
		<vgroup frame="true" frametitle="Update Test">
      <texteditor id="lv" readonly="true">
      </texteditor>
      <button id="go">GO</button>            
    </vgroup>
  </window>
</application>
]])

; listen to these events!
InstallEventHandler({
  RapaGUI     = msgHandler
  })
  
; Welcome message
moai.DoMethod("lv", "insert", GetTime(True) .. ": " .. "Hello!\n", "bottom")

; Let's go!
Repeat
  DebugPrint("Before Wait Event")
  WaitEvent
  DebugPrint("After Wait Event")
  
Forever
The string "After Wait Event" is never printed, but RapaGUI still answer to all the events, I think there is something strange in this behaviour, WaitEvent() should wait for an event, trigger the callback(s) and return, isn't it?
I'm still on Linux :)
Flinx
Posts: 192
Joined: Sun Feb 14, 2021 9:54 am
Location: Germany

Re: Updating listviews...

Post by Flinx »

I think that is normal. From the Help About RapaGUI / Compatibility Notes:
WaitEvent() will never return. On most platforms, a call to WaitEvent() will be a one-way ticket that starts your application's main event loop and never returns.
User avatar
Allanon
Posts: 732
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: Updating listviews...

Post by Allanon »

Flinx wrote: Thu Feb 01, 2024 10:42 am I think that is normal. From the Help About RapaGUI / Compatibility Notes:
WaitEvent() will never return. On most platforms, a call to WaitEvent() will be a one-way ticket that starts your application's main event loop and never returns.
Wow, goods to know, thank you for the info. This complicates things at least until I fully understand how to update widget during a callback :)
plouf
Posts: 473
Joined: Sun Feb 04, 2018 11:51 pm
Location: Athens,Greece

Re: Updating listviews...

Post by plouf »

CheckEvents() do not block
While WaitEvent() block
Christos
Flinx
Posts: 192
Joined: Sun Feb 14, 2021 9:54 am
Location: Germany

Re: Updating listviews...

Post by Flinx »

plouf wrote: Thu Feb 01, 2024 11:22 am CheckEvents() do not block
While WaitEvent() block
Hmm. From the same help page:
All RapaGUI projects must use WaitEvent() now. It's no longer acceptable to implement custom event handling using CheckEvent() or CheckEvents().
User avatar
Allanon
Posts: 732
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: Updating listviews...

Post by Allanon »

I'm a bit lost here... below there is the sample test changed to use RunCallback() but it seems that the listview is updated only at the end of the loop... callbacks are set all at once but when the queued callbacks are processed they do not update the listview... OMG this is tricky :D

Code: Select all

@REQUIRE "RapaGUI"

Function testFunc()

  For i=1 To 100
    moai.DoMethod("lv", "insert", GetTime(True) .. ": " .. "Hello!\n", "bottom")
    ;moai.DoMethod("lv", "jump", "bottom")
    ;moai.DoMethod("lv", "redraw")
    Wait(50, #MILLISECONDS)
    DebugPrint("Added item " .. i)
  Next

EndFunction

Function msgHandler(msg)
	Switch msg.action
    ; Parse RapaGUI events
    Case "RapaGUI"
      
      ; Filter actions by attributes
      Switch msg.attribute

        ; Check button presses
        Case "Pressed"
          ; Check buttons
          Switch msg.id
            Case "go"
              ;--testFunc()
              f=Function(msg) DebugPrint("--> ", msg.userdata) Wait(50,#MILLISECONDS) moai.DoMethod("lv", "insert", GetTime(True) .. ": " .. "Hello!\n", "bottom") EndFunction
              fm=Function(msg) moai.DoMethod("lv", "insert", GetTime(True) .. ": " .. "Hello!\n", "bottom") EndFunction
              For n = 0 To 10
                DebugPrint(f,fm)
                RunCallback(fm,n)
                RunCallback(f,n)
              Next
              

          EndSwitch
      EndSwitch
  EndSwitch

EndFunction

moai.CreateApp([[<?xml version="1.0" encoding="iso-8859-1"?>
<application id="app">
	<menubar id="menu">

		<menu title="_File">
			<item id="mn_about">_About...</item>
			<item/>
			<item id="mn_quit">_Quit</item>
		</menu>
	</menubar>

	<window title="TEST LV" id="window" menubar="menu">
		<vgroup frame="true" frametitle="Update Test">
      <texteditor id="lv" readonly="true">
      </texteditor>
      <button id="go">GO</button>            
    </vgroup>
  </window>
</application>
]])

; listen to these events!
InstallEventHandler({
  RapaGUI     = msgHandler
  })
  
; Welcome message
moai.DoMethod("lv", "insert", GetTime(True) .. ": " .. "Hello!\n", "bottom")

; Let's go!
Repeat
  DebugPrint("Before Wait Event")
  WaitEvent
  DebugPrint("After Wait Event")
  
Forever
Flinx
Posts: 192
Joined: Sun Feb 14, 2021 9:54 am
Location: Germany

Re: Updating listviews...

Post by Flinx »

The problem with the last example is that there is also a For loop that does not pass control to the main loop, so all RunCallback calls are executed at once.
I have converted your previous example into a recursive function that only executes one action and then calls itself for the next one via RunCallback.
The start value is passed as simulated userdata on the first call.

Code: Select all

@REQUIRE "RapaGUI"

Function testFunc(msg)

    moai.DoMethod("lv", "insert", ToString(msg.userdata).." " .. GetTime(True) .. ": " .. "Hello!\n", "bottom")
    ;moai.DoMethod("lv", "jump", "bottom")
    moai.DoMethod("lv", "redraw")
    Wait(20, #MILLISECONDS)
    DebugPrint("Added item " .. msg.userdata)
    If msg.userdata<100 Then RunCallback(testFunc,msg.userdata+1)
EndFunction

Function msgHandler(msg)
    Switch msg.action
    ; Parse RapaGUI events
    Case "RapaGUI"
      
      ; Filter actions by attributes
      Switch msg.attribute

        ; Check button presses
        Case "Pressed"
          ; Check buttons
          Switch msg.id
            Case "go"
              testFunc({["userdata"] =1})
          EndSwitch
      EndSwitch
  EndSwitch

EndFunction

moai.CreateApp([[<?xml version="1.0" encoding="iso-8859-1"?>
<application id="app">
	<menubar id="menu">

		<menu title="_File">
			<item id="mn_about">_About...</item>
			<item/>
			<item id="mn_quit">_Quit</item>
		</menu>
	</menubar>

	<window title="TEST LV" id="window" menubar="menu">
		<vgroup frame="true" frametitle="Update Test">
      <texteditor id="lv" readonly="true">
      </texteditor>
      <button id="go">GO</button>            
    </vgroup>
  </window>
</application>
]])

; listen to these events!
InstallEventHandler({
  RapaGUI     = msgHandler
  })
  
; Welcome message
moai.DoMethod("lv", "insert", GetTime(True) .. ": " .. "Hello!\n", "bottom")

; Let's go!
Repeat
  DebugPrint("Before Wait Event")
  WaitEvent
  DebugPrint("After Wait Event")
Forever
User avatar
Allanon
Posts: 732
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: Updating listviews...

Post by Allanon »

Thank you @Flinx to try to resolve this puzzle and taking time to experiment, your example is working but I have a couple of observations about my previous example's loop :)

The For/Next that executes the RunCallback() is not blocking, it just put the callback functions into the event's queue, infact look at this piece of code executed when the button is pressed:

Code: Select all

            Case "go"
              ;--testFunc()
              f=Function(msg) DebugPrint("--> ", msg.userdata) Wait(50,#MILLISECONDS) moai.DoMethod("lv", "insert", GetTime(True) .. ": " .. "Hello!\n", "bottom") EndFunction
              fm=Function(msg) moai.DoMethod("lv", "insert", GetTime(True) .. ": " .. "Hello!\n", "bottom") EndFunction
              For n = 0 To 10
                DebugPrint(f,fm)
                RunCallback(fm,n)
                RunCallback(f,n)
              Next
DebugPrint(f,fm) prints all the stuff in one go because the For/Next is executed in no time and the RunCallback() commands put the function's calls in the event queue, then the control is returned to the WaitEvent().
Now the system should call the functions placed on the events stack, alternatining the f() and the fm() function calls, infact in the debug window the DebugPrint() of the f() function prints some text but the fm() function does not trigger the listview update which is updated only at the end of the event's queue. That's the question: are widgets updated only when all events has been served? Looking at your example seems that the answer is no because you are calling a calback function that never returns calling itself recursively.

I'm not sure why your example works, using recursion like you did is ok but what happens if the recursion goes deep like 30000 times? Stack overflow?

I want to expose my real case for which I want to show some user feedback.
I'm building an utility to calculate checksums & hashes of some files: the user drops the files to process into a list view then, when ready, he push a button and the program starts the calculations. Well, it was an epic fail :P
I'm not able to update the listview while the files are being processed or better: I'm not able to force the listview to show what I've added to it.
About implementing this stuff using recursion.. well, maybe it could work, but I think it's unneeded overcomplication that could lead to stack overflows, I don't know how many files the user wants to calculate, he could ask for the sha1 of his entire disk.

There should be a legal/elegant way to implement such stuff without involving sorceries like transforming a simple loop into a recursive loop :lol:
Any ideas?

Flinx wrote: Fri Feb 02, 2024 4:37 pm The problem with the last example is that there is also a For loop that does not pass control to the main loop, so all RunCallback calls are executed at once.
I have converted your previous example into a recursive function that only executes one action and then calls itself for the next one via RunCallback.
The start value is passed as simulated userdata on the first call.

Code: Select all

@REQUIRE "RapaGUI"

Function testFunc(msg)

    moai.DoMethod("lv", "insert", ToString(msg.userdata).." " .. GetTime(True) .. ": " .. "Hello!\n", "bottom")
    ;moai.DoMethod("lv", "jump", "bottom")
    moai.DoMethod("lv", "redraw")
    Wait(20, #MILLISECONDS)
    DebugPrint("Added item " .. msg.userdata)
    If msg.userdata<100 Then RunCallback(testFunc,msg.userdata+1)
EndFunction

Function msgHandler(msg)
    Switch msg.action
    ; Parse RapaGUI events
    Case "RapaGUI"
      
      ; Filter actions by attributes
      Switch msg.attribute

        ; Check button presses
        Case "Pressed"
          ; Check buttons
          Switch msg.id
            Case "go"
              testFunc({["userdata"] =1})
          EndSwitch
      EndSwitch
  EndSwitch

EndFunction

moai.CreateApp([[<?xml version="1.0" encoding="iso-8859-1"?>
<application id="app">
	<menubar id="menu">

		<menu title="_File">
			<item id="mn_about">_About...</item>
			<item/>
			<item id="mn_quit">_Quit</item>
		</menu>
	</menubar>

	<window title="TEST LV" id="window" menubar="menu">
		<vgroup frame="true" frametitle="Update Test">
      <texteditor id="lv" readonly="true">
      </texteditor>
      <button id="go">GO</button>            
    </vgroup>
  </window>
</application>
]])

; listen to these events!
InstallEventHandler({
  RapaGUI     = msgHandler
  })
  
; Welcome message
moai.DoMethod("lv", "insert", GetTime(True) .. ": " .. "Hello!\n", "bottom")

; Let's go!
Repeat
  DebugPrint("Before Wait Event")
  WaitEvent
  DebugPrint("After Wait Event")
Forever
Post Reply