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.
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.
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.
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.
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.
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.
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