How to show dialog at the beginning of the process and handle errors inside UI.start_timer?

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.

2 Likes

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?

    def self.show_progress_dialog
      dialog_width = 200
      dialog_height = 150

      viewport = Sketchup.active_model.active_view.vpwidth, Sketchup.active_model.active_view.vpheight
      left = (viewport[0] - dialog_width) / 2
      top = (viewport[1] - dialog_height) / 2

      @progress_dialog = UI::HtmlDialog.new(
        {
          :dialog_title => "ElipseGrade",
          :preferences_key => "com.sample.plusdsadagin",
          :scrollable => false,
          :resizable => false,
          :width => dialog_width,
          :height => dialog_height,
          :left => left,
          :top => top,
          :style => UI::HtmlDialog::STYLE_DIALOG
        })

      @progress_dialog.set_html('
        <!DOCTYPE html>
        <html>
        <head>
          <title>Processing</title>
          <style>
            body {
              background-color: #F3F3F3;
              font-family: Arial, sans-serif;
            }
            p {
              text-align: center;
              padding-top: 10px;
              font-size: 0.9em;
            }
          </style>
        </head>
        <body>
          <p>Processing...<br><br>Please wait</p>
        </body>
        <script>
        function dialog_ready() {
          document.removeEventListener("DOMContentLoaded", dialog_ready);
          sketchup.progress_dialog_ready();
        }
        document.addEventListener("DOMContentLoaded", dialog_ready);
        </script>
        </html>
      ')

      @progress_dialog.add_action_callback("progress_dialog_ready") do |action_context|
        puts "Dialog ready"
        Constructor.run_constructor
      end
      @progress_dialog.show
    end

image

The callback code needs a timer for your task.

See attached, if you uncomment line 38 and comment out line 39, it repros your issue.

Wrote the code for Ruby 2.2 (and later) compatibility.

Greg

00-dialog.rb (2.0 KB)

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.

UPDATE: No change using window onload event

REF: Window: load event - Web APIs | MDN (mozilla.org)

Ex:

        <script>
        function dialog_ready() {
          window.removeEventListener("load", dialog_ready);
          sketchup.progress_dialog_ready();
        }
        window.addEventListener("load", dialog_ready);
        </script>

SOLVED it without a timer !

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.

  const dialog_ready = () => sketchup.progress_dialog_ready("",
    {
      onCompleted: function() {
        console.log('Ruby callback "progress_dialog_ready" has returned.');
      }
    }
  );
  window.addEventListener("load", dialog_ready);

I used Greg’s example to test, and added in a percent_done() method to draw a graphic progress indicator.

image


ProgressBar_dialog_fixed.rb (3.7 KB)

2 Likes