I want a simple window to pop up at the beginning of a heavy process. However, the window is not loading properly because the HTML part is delayed by the Ruby script and is only rendered after the job is done. To work around this, I placed the heavy process inside UI.start_timer(0.3, false) {heavy_process} to give the HTML some time to load. It works but, the heavy process contains some raise and rescue bits that when triggered conflict with UI.start_timer. Once an error is raised and rescued, the script doesn’t stop but is instead raised and rescued repeatedly. In this case, a UI.messagebox from the rescue block pops up over and over again.
Do you have any ideas how to make the process wait for the dialog to load, or how to make raise and rescue work with UI.start_timer?
def self.run_constructor
PDialog.show_p_dialog
UI.start_timer(0.3, false) {
begin
some process....
rescue ComponentOutOfBoundsError
UI.messagebox("no luck")
PDialog.close_p_dialog
end
}
end
This is a known problem, See the following code and the explanation in the link below.
def self.run_constructor
# This must be outside of the timer Proc
first_invocation = true
UI.start_timer(0.3, false) {
begin
sleep(1)
x = 1/0 # raise an error
rescue
if first_invocation
first_invocation = false
UI.messagebox("no luck")
end
ensure
p "Close"
end
}
end
run_constructor()
nil
We do this all the time without using timer blocks.
At the bottom of the dialog’s Javascript …
// Tasks to be performed after the document has loaded:
function tasksPostLoad(event) {
// No longer need this event listener:
document.removeEventListener("DOMContentLoaded",tasksPostLoad);
// Tell the SketchUp Ruby-side the document has loaded:
sketchup.dialog_ready();
// other post load task ...
}
// Attach master document DOMContentLoaded event listener:
document.addEventListener("DOMContentLoaded",tasksPostLoad);
The Ruby-side dialog code needs to attach an action callback that reponds to the call from the JS DOMContentLoaded event handler function.
In the following snippet, (taken from an example I am writing to show font size control like in normal browsers,) … I have named it "dialog_ready", but it can be whatever name you desire, as long as the name used in the call upon the sketchup JS object and the name assigned in the Ruby #add_action_callback method call match.
So, this snippet defines three action callbacks:
def add_callbacks
#
unless @dialog
puts 'Dialog object not instantiated.'
return
end
#
# Do initial tasks after dialog has loaded, like setting font size.
# Also, tells the dialog what platform SketchUp is running upon.
@dialog.add_action_callback('dialog_ready') {
puts 'In callback: "dialog_ready"' if @debug
# Restore the font size from the previous use as set by user:
font_size = format('%.3f',@font_size)
@dialog.execute_script(%[fontRestore(#{font_size});])
# Set the dialog's JS var `WINDOWS` as appropriate for platform:
val = (Sketchup.platform == :platform_win rescue RUBY_PLATFORM !~ /darwin/)
puts " Platform WINDOWS is: #{val}" if @debug
@dialog.execute_script(%[WINDOWS = #{val};])
# Let the dialog know the current sort_by key:
@dialog.execute_script(%[sort_by = "#{@sort_by.to_s}";])
}
#
# Callback to save the font size as it is changed by the user:
@dialog.add_action_callback('save_font_size') { |_action_context, fontsize|
save_font_size(fontsize)
}
# Callback to resort the shortcuts table data when the user clicks
# upon one of the column headings in the dialog document:
@dialog.add_action_callback('resort_shortcuts') { |_action_context, sort_by|
puts 'In callback: "resort_shortcuts"' if @debug
# Resort the shortcuts listing and send back to the dialog:
resort_shortcuts(sort_by) if sort_by
}
#
end
The Ruby-side UI::HtmlDialog callbacks should usually be wrapped up in a method so they can be reattached in case the user has closed the window. When the window is closed the callbacks are detached but the Ruby dialog object is still valid.
If your code is going to reshow the dialog window (say if a toolbar button is clicked,) then the callbacks need to be reattached. This is most easily done by calling the method that wraps up the callback definitions.
Thank you very much for the answer. I have made a changes based on your suggestion but the problem persists. That is, the dialog appears but the text inside the <p> tag is not rendered until the run_constructor function is finished. Do you have any idea why that might be?
Hi Greg, Tank you very much for the answer. This is the solution I have tried initially but the problem is that the error handling inside the UI.start_timer is not working (description in the initial post) and this is the functionality that I need.
Nevermind this reply ... I solved it. ... See next post.
I have no idea what is in the run_constructor method.
Are you saying that the text: "Processing... Please wait" … is not rendering until after Constructor.run_constructor method returns ?
If so, then you might change from listening for DOMContentLoaded to the load event on the window object. This event is supposed to not fire until the page is totally loaded.
Further testing shows there still are issues with the HTML dialog approach to displaying progress.
The key / trick is to use a dummy JS callback object and then
the sketchup.progress_dialog_ready callback does not block.
ie … I just used a statement writing a console message:
Notice also that I sent an unused empty string parameter to the Ruby callback. In some older versions the callbacks would not fire unless at least one parameter was sent to Ruby.
I am coming back you your reply after some time. Thanks for your answer, I have run your file and the issue is still there. Do you have any idea what might cause the difference in the behavior?
Since I posted that, I continued testing. I also ran into strange variations where it would fail either with or without the Ruby Console open, but more so in one of the scenarios.
So, sorry, I “jumped the gun” in saying it was solved. It continues to be a problem.
I intended to update what I had previously posted, but then I got distracted off of that onto other projects.
Basically, the HTML Dialogs are still problematic when opened from asynchronous callback blocks.
But … it would still be nice for extension coders to have a nifty way to display progress to users.
I have an idea that might suffice but I need to code it up and test it out.
Okay so I found the test file I was working on. Reran it.
I had a note in the file that it would only run correctly when the Ruby Console was openand the task was sending output text to $stdout (which is SketchUp’s Ruby console object.)
Often the progress bar stops about halfway through the progress (at ~5 secs and sometimes the dialog goes “Not Responding”) until the progress completes then the progress bar fills up in one step.
This most often happens when I hide the Ruby Console whilst the progress dialog is showing.
I have never gotten a blank dialog like you show. I always get the webpage rendered completely in SketchUp 2021.1, 2023.1 (using CEF 88) and 2024.0 (the latter which uses Chromium 112.)
If you are still using SketchUp 2021, then it uses Chromium v64 in SU 21.0 and Chromium v88 for SU 21.1. So yes, there can be differences as the CEF fixes bugs.
Here is another test file that lets you try the different combinations using Boolean flags:
silent : the tasks does not output text to $stdout
hide_console : whether the Ruby Console is open or hides during the operation
timer : whether the operation is wrapped within a timer block or not.
One of the other things I tried to get SketchUp to update the UI, was to force the focus to SketchUp’s application window and then back to the progress dialog. But it was too annoying with the caption bars changing color back and forth throughout the task’s progress.
There is no way to get a progress bar or anything visual during a long task if you do not give back control to the UI of Sketchup explicitely.
So, the best is to cut the long task into small chunks (steps), which also means that you need a re-entrant calculation.
Say you have @nb_steps = 20. Then, the following method will serve to display the status in the UI (here via the status bar):
def process_step(istep)
time_start = Time.now
for i in istep..@nb_steps-1
#Update the status bar
Sketchup.set_status_text '|' * istep
Sketchup.set_status_text "Processing Step", SB_VCB_LABEL
Sketchup.set_status_text istep.to_s, SB_VCB_VALUE
#Do the processing for the step
sleep(0.1)
#Check if the delay for processing has exceeded 1 second
if Time.now - time_start > 1
UI.start_timer(0) { process_step(istep + 1) }
return
end
end
#Processing done
....
end
You launch the processing by calling process_step(0).
For the timer, you can use the Sketchup one, or if you are in the context of an HtmlDialog, the Javascript setTimeout.
PS: It would be great to be able to have a spinner like this :Spinners | Trimble Modus Bootstrap Developer Guide in the first dialog that pops before the big process starts and actually keeps spinning until the end.