Why dialog.visible? works, but dlg.visible? does not?

Hi,

This Works…
Here is API example Link

if dialog.visible?
  dialog.bring_to_front
else
  dialog = UI::HtmlDialog.new
  dialog.set_url("https://www.sketchup.com")
  dialog.show
end

But, this Code does not work for me

if dlg.visible?
  dlg.bring_to_front
else
  dlg = UI::HtmlDialog.new
  dlg.set_url("https://www.sketchup.com")
  dlg.show
end

I get the following error…

"undefined method `visible?' for nil:NilClass (Line 1)"

Even if I use @dialog.visible? it does not work or if I wrap the code with modules.

Any help will be appreciated!

Maybe you created dialog = UI::HtmlDialog.new variable before.

It should be if dlg && dlg.visible?

1 Like

You must have all of your code within a module.

The constructor for UI::HtmlDialog warns you to use persistent references …

Note: If there is no reference kept to the HtmlDialog object, the window will close once the garbage collection runs. This behavior can be confusing in trivial test code but is usually not a concern in real life scenarios. Typically a persistent reference, e.g. an instance variable, should be kept to bring the dialog to front, rather than creating a duplicate, if the user should request it a second time.

So this means an @dlg or @@dlg kind of variable.

Either way, both in recent Ruby versions need to be initialized before they are evaluated in a statement. If not, the @@var will cause a SyntaxError, and the uninitialized @dlg will cause a warning output to STDOUT. The latter might be upgraded to an exception for Ruby 3.0.

So the simplest thing to do is declare them at the top of your module …

  @dlg = nil unless defined?(@dlg)

… or init them in the “run once” block where you setup menus and toolbars.

1 Like

Perhaps his playing at the console, or someone else’s code is not running within a module and has corrupted the objectspace. Normally, Kernel#require tries to prevent local variables from doing this and removes them after the file is done being evaluated.

But SketchUp embedded Ruby might be more susceptible to global corruption. So the main “rule of thumb” is run your code within a module and when weird stuff happens, restart SketchUp.

I’ve been reading some issues in the core Ruby tracker where they are talking about not allowing certain variable kinds to be declared in the top level objectspace. Ie, elevating warnings to exceptions. So it is best to follow best practices, despite any published poor examples.

1 Like

Hi voquochai, thank you very much for the help.

The example code I originally posted was indeed solved with ‘if dlg && dlg.visible?’.

However, I was originally creating the HTML dialog inside an initialize method inside a class named Main. And, when the user pressed the toolbar button to load the Main class, the HTML dialog was being created again with a new id. So when I tried using the following if condition ‘if dlg.visible?’ it was always returning false.

Below is a simplified file structure for my extensions where I managed to solve my problem by storing the HTML dialog inside a constant in the load.rb file. After that, I can run if condition inside the main.rb file.

1st I register the extension and call the load.rb file…

# Plugins/plugin_name.rb
require 'sketchup'
require 'extensions'

module ModuleName
  DIR = File.dirname(__FILE__) unless defined?(self::DIR)

  unless file_loaded?(__FILE__)
    ex = SketchupExtension.new('plugin_name', 'folder_name/load') 
    ex.description = 'description'
    ex.version = 'version'
    ex.copyright = 'copyright'
    ex.creator = 'creator'
    Sketchup.register_extension(ex, true)
      file_loaded(__FILE__)
    end
  end
end

2nd I create a constant for Html dialog…

# Plugins/plugin_folder/load.rb
module ModuleName
  Sketchup.require 'plugin_folder/main'

  unless file_loaded?(__FILE__)
    # -------------------------------------------------------
    # Using Constant to store HTML Dialog
    # -------------------------------------------------------
    DLG = UI::HtmlDialog.new # <-- HTML Dialog!

    tb = UI::Toolbar.new 'Tool Name'
    cmd = UI::Command.new('Command Name') do
      Sketchup.active_model.select_tool Main.new
    end

    cmd.small_icon = "#{icon_path}#{icon_name}#{icon_format}"
    cmd.large_icon = "#{icon_path}#{icon_name}#{icon_format}"
    cmd.tooltip = 'tooltip'
    cmd.status_bar_text = 'status_bar_text'
    cmd.menu_text = 'menu_text'
    tb = tb.add_item cmd
    tb.get_last_state == TB_NEVER_SHOWN ? tb.show : tb.restore
    # ----------------------------------------
    file_loaded(__FILE__)
end

Now with the HTML dialog being a constant, I can use ‘if DLG.visible?’ effectively.

# Plugins/plugin_folder/main.rb
module ModuleName
  class Main
    def initialize
      if DLG.visible?
        DLG.bring_to_front
      else
        DLG.set_url('https://www.sketchup.com')
        DLG.show
      end
    end
  end
end

Not 100% sure if I could explain my problem & solution well but if there is confusion please let me know and I’ll give it a second try.

Again thanks voquochai!

Yes. I personally wrap my code with two modules for making sure my code do not affect other extensions.

Some years ago I was having the persistence problem when not using a class variable to store the dialog.

This I think is the solution to my problem. Do you think using a constant in the load.rb was a good decision?

Or should I use a class variable and pass it as an argument when calling Main.new(@dlg)

Thanks in advance!!

Yes, I was running the test code from the console. And, I think you are right that maybe some extension was not using modules and that is why the local variable named ‘dialog’ was working.

1 Like

Well it is a persistent reference. But since you are using a class, you might just as well use an instance variable and create the dialog in the class’ initialize method.

1 Like

The problem I was having by creating the dialog in the initialize method with an instance variable was that every time the toolbar button was activated the instance variable will change the existing id of the dialog to a new one.

And when using ‘if @dialog.visible?’ it would always return false.

In general I’d avoid creating the dialog or most thing when your extension load. We’re seeing that extensions have startup-time impact for the user. Ideally the extension load its resources on demand.

If you look at this example:

We use ||= to create the dialog on-demand. self.create_dialog is called once, the first time the method is invoked.

Somewhat off topic…

Glad you mentioned that, in particular “Ideally the extension load its resources on demand.”…

I have tried using the ||= but each time I press the toolbar button this is what I get in the Ruby console when I call puts @dlg

Each HtmlDialog has a unique id and that is why @dialog.visible? always returns false no matter how many HTML dialogs are open.

Maybe this is happening because I am calling a class named Main.new each time I activate the toolbar button?

Bellow is my initialize method…

module ModuleName
class Main
  def initialize
    @dlg ||= UI::HtmlDialog.new
    puts @dlg

    # Because each time the tool activates the value of @dlg changes 
    # and the if condition always returns false. 
    if @dlg.visible?
      @dlg.bring_to_front
    else
      @dlg.set_url('https://www.sketchup.com')
      @dlg.show
    end
  end
end
end

And, below is a sniped of code where I create the toolbar and command to activate the Main.new

# ----------------------------------------
# UI Add Toolbar
# ----------------------------------------
tb = UI::Toolbar.new NAME
cmd = UI::Command.new(NAME) do
  Sketchup.active_model.select_tool Main.new
end
# ...
tb = tb.add_item cmd
# ...

In your case you are using an instance variable within a class. So every instance you make of Main will create it’s own UI::HtmlDialog.new. Notice that in the example I posted I use ||= in the context of a module.

Your code shows that your tool holds the reference to your dialog. You button logic creates a new instance of the tool every time you click it. But it since you only want one instance of the dialog you need some other factory method to ensure you have only a single instance of the dialog.

module ModuleName

  def self.get_dialog
    # The instance variable belongs to the ModuleName module.
    @dlg ||= UI::HtmlDialog.new
    # TODO: add your action callbacks here.
    @dlg
  end

class Main
  def initialize
    # Now, because the dialog isn't owned by the tool instance
    # it will no longer create a new dialog every time.
    dlg = ModuleName.get_dialog
    puts dlg

    if dlg.visible?
      dlg.bring_to_front
    else
      dlg.set_url('https://www.sketchup.com')
      dlg.show
    end
  end
end
end
2 Likes

The Html dialog is working as expected now and is not creating a new dialog every time.

1 Like

fistbump

1 Like