theodox/mGui

Single instance Windows

dshlai opened this issue · 7 comments

HI!:

It looks like the main window is auto-named (window8, window9, etc.) when instantiated . How can I implement a single instance window? The usual way is to use window name together with deleteUI command to ensure that there is only a single instance window.

There are couple idea I am thinking about:

  1. use Maya's global variable as registry for window's name
  2. use Python module level variable to register window'name

Maybe I miss something here, can you guys help out?

Thanks.

The underlying window widget created in mGui will be stored in the widget field of the mGui Window you create. You can use Window.delete on the any instance of Window , or regular old deleteUI to delete an existing window using its widget if it does not have an mGui wrapper.

If you already have an mGui Window and you want to know if it still exists, just truth-test it:

 Window1 = Window()
 if Window1:
      print "Window1 exists, it's underlying Maya window is", Window1.widget
 cmds.deleteUI(Window1.widget)
 # Window.delete(Window1) would do the same thing...
 if Window1:
      print "I never get printed, because 'if Window1' will return False

You can also get an mGui wrapper for an existing window with Window.from_existing('your_window_string'). That will let you query and edit properties on that window but (unlike a mGui window you make the usual way) it won't have access to the window's children.

@dshlai -- did that fix your issue?

Happy new year!

Hi!
Following the suggestion this following code snippet works.

I had to use MEL variable to store the UI name because Python variable gets garbage collected after the script is run.

Each time the script is run it will delete the old Window UI instance and create a new one.

win = None
win_name = ""

# get window UI name if is was stored
try:
    win_name = mel.eval("$temp=$myWindow")
except RuntimeError as e:
    pass

# sometimes the window is closed by user
if win_name:
    try:
        pm.deleteUI(win_name)
    except RuntimeError as e:
        pass

try:
    win = BoundCollectionWindow([])
    selected = [item.fullPath() for item in pm.ls(selection=True, long=True)]
    win.collection.add(*selected)
    win.show()

    # store window name as mel variable
    mel.eval('$myWindow="{}"'.format(win.window.widget))

except RuntimeError as e:
    pass

The usual pattern would be to use a class variable as a keepalive, and rely on the fact that the mGui objects will test as false if they have been deleted:

from mGui.gui import *
from mGui.forms import FillForm
import pymel.core as pm

class ExampleWindow(Window):
    INSTANCE = None 
    def __init__(self, *args, **kwargs):
        super(ExampleWindow, self).__init__(*args, **kwargs)
        selected = [item.fullPath() for item in pm.ls(selection=True, long=True)]

        with FillForm() as root:
            self.main_list = TextScrollList() 
            
        self.main_list.items = [item.fullPath() for item in pm.ls(selection=True, long=True)]

    @classmethod
    def get(cls):
        if cls.INSTANCE:
            cls.delete(cls.INSTANCE)
        cls.INSTANCE = cls()
        return cls.INSTANCE

b = ExampleWindow.get()
b.show()

Hi!:

I found another singleton pattern that works as well.

I think my issue is mainly due to that the calling code currently does reload (the module) each time before the window is initialized, this action seems to destroy the class along with Singleton variable it is holding. (Which make sense since the newly load code should replace the old one)

remove the reload(module) everything works fine.

Thanks

from mGui.gui import *
from mGui.forms import FillForm
import pymel.core as pm

class ExampleWindow(Window):
    INSTANCE = None 
    
    def __new__(cls):
        # check against type to ensure subclass can be used as Singleton well
        if cls == type(cls.INSTANCE):
            cls.delete(cls.INSTANCE)
            cls.INSTANCE = object.__new__(cls)
            return cls.INSTANCE
        else:
            cls.INSTANCE = object.__new__(cls)
            return cls.INSTANCE
        
    def __init__(self, *args, **kwargs):
        super(ExampleWindow, self).__init__(*args, **kwargs)
        selected = [item.fullPath() for item in pm.ls(selection=True, long=True)]

        with FillForm() as root:
            self.main_list = TextScrollList() 
            
        self.main_list.items = [item.fullPath() for item in pm.ls(selection=True, long=True)]

b = ExampleWindow()
b.show()