How to make a Pyside UI stay on top of Maya only?

Hi,

I’ve been trying to find this online but can’t get the behavior I am looking for. I’d like to get my PySide UI to stay on top of Maya, but stay behind any other programs.

By default, on Linux, the UIs behave property (parented under Maya only, and stay on top of it only).

When working on Windows, my UI loads up, and once Maya is my active window, the UI goes behind it.

I’ve tried with WindowStaysOnTopHint, but the UI stays on top of any window. If I open a web browser or IDE, that UI is still on top. I’ve tried with both QtWidgets.QMainWindow and QtWidgets.QDialog to no avail.

QtCore.Qt.WindowStaysOnTopHint

What am I missing? Any suggestions?

Thank you.

I remember having that problem, and I remember it was one of those “change one stupid thing that doesn’t seem to matter and it works” solutions. However, I can’t for the life of me remember what exactly it was.

So, in lieu of that, here’s some code that works the way you want, as well as I understand it.
And I know I’m getting the parent window in a strange way, it’s because we use the same code to find the root window in all dcc’s, and I can’t be bothered to find the more canonical way. I don’t think that’s your issue though

from PySide2.QtWidgets import QApplication, QSplashScreen, QDialog, QMainWindow

class TestWindow(QMainWindow):
	def __init__(self, parent):
		super(TestWindow, self).__init__(parent)

def rootWindow():
	'''Returns the currently active QT main window
		Works for any Qt UI, like Maya, or now Max.
	Parameters
	----------
	Returns
	-------
	'''
	# for MFC apps there should be no root window
	window = None
	if QApplication.instance():
		inst = QApplication.instance()
		window = inst.activeWindow()
		# Ignore QSplashScreen's, they should never be considered the root window.
		if isinstance(window, QSplashScreen):
			return None
		# If the application does not have focus try to find A top level widget
		# that doesn't have a parent and is a QMainWindow or QDialog
		if window == None:
			windows = []
			dialogs = []
			for w in QApplication.instance().topLevelWidgets():
				if w.parent() == None:
					if isinstance(w, QMainWindow):
						windows.append(w)
					elif isinstance(w, QDialog):
						dialogs.append(w)
			if windows:
				window = windows[0]
			elif dialogs:
				window = dialogs[0]

		# grab the root window
		if window:
			while True:
				parent = window.parent()
				if not parent:
					break
				if isinstance(parent, QSplashScreen):
					break
				window = parent

	return window


if __name__ == "__main__":
	root = rootWindow()
	win = TestWindow(root)
	win.show()

All you need to do is this.

from maya import OpenMayaUI
from PySide2 import QtWidgets
from shiboken2 import wrapInstance


def maya_main_window():
	main_window_ptr=OpenMayaUI.MQtUtil.mainWindow()
	return wrapInstance(long(main_window_ptr), QtWidgets.QWidget)


class Window(QtWidgets.QDialog):
	def __init__(self, parent==maya_main_window()):
		super(Window, self).__init__(parent)

gui = Window()
gui.show()
1 Like

Thank you for this answer.
This seems like a lot of work to make the parenting work. I’ll look over it in details and test it out.

Thank you for this solution. that seems to do the trick. The problem with this now, is that my UI isn’t a window or dialog anymore, it’s a locked ui on the top left of the screen, as opposed to a gui i could move around before.
I thought adding this would fix it but it doesn’t:

self.setWindowFlags(QtCore.Qt.Tool)

Is it just me or is it not very straight forward make PySide UI’s behaves as we wish?
Thank you

Max and my root getters are functionally equivalent here, he just used that ‘canonical’ way that I didn’t look up because I was tired. Use his.

Onto your second problem: That’s possibly because your window has a position that’s off the screen. Try doing myWindow.move(10, 10)

Thank you! move(100,100) worked.

On linux, I dont have to set the coordinates of where the UI opens, is that because this is on Windows that it’s necessary? Just trying to understand how to handle the tools on both os.

Thank you!

Ya know, I don’t really know for sure. Maybe someone else can chime in here. My best guess is it somehow has to do with how Windows stores and opens new dialogs and windows (ie. the Windows Registry)

I can’t reproduce it on my machine. If you restart Maya and run the code without the window moving code does it happen again? I’m going to guess not, but I’m honestly not sure.

If you want the gui to start in the center of the screen.

from maya import OpenMayaUI
from PySide2 import QtWidgets
from shiboken2 import wrapInstance


def maya_main_window():
    main_window_ptr = OpenMayaUI.MQtUtil.mainWindow()
    return wrapInstance(long(main_window_ptr), QtWidgets.QWidget)


def center_ui(cls):
    frame_geo = cls.frameGeometry()
    centerpoint= QtWidgets.QDesktopWidget().availableGeometry().center()
    frame_geo.moveCenter(centerpoint)
    cls.move(frame_geo.topLeft())


class Window(QtWidgets.QDialog):
    def __init__(self, parent=maya_main_window()):
        super(Window, self).__init__(parent)
        center_ui(self)


gui = Window()
gui.show()
1 Like

I can’t speak for Linux behavior, but to get Maya tools set up correctly on Windows I have to do 2 things:

  1. Parent the tool dialog to the Maya main window
  2. Set the dialog’s window flags to either QtCore.Qt.Window or QtCore.Qt.Tool, depending on the look you want. I always prefer the former for anything other than small confirmation dialogs.

My code usually looks something like this

from PySide2 import QtCore, QtGui, QtWidgets
import maya.OpenMayaUI as omui
from shiboken2 import wrapInstance

def getMayaMainWindow():
    main_window_ptr = omui.MQtUtil.mainWindow()
    return wrapInstance(long(main_window_ptr), QtWidgets.QMainWindow)

class MyDialog(QtWidgets.QDialog):
    def __init__(self, parent=getMayaMainWindow()):
        super(MyDialog, self).__init__(parent)
        self.setWindowFlags(QtCore.Qt.Window)

if __name__ == '__main__':
    try:
        dialog.deleteLater()
    except:
        pass
    dialog = MyDialog()
    dialog.show()

I’ve not had any issues with custom dialogs since I started setting them up this way.

If you have issues with the dialog not appearing at the center of the screen, you can center it when showing the UI. I prefer to override QDialog’s show method for this, since it will have to happen after any widget creation that can change the dimensions of the dialog, and that include’s QtDialog’s show() method. Here’s an example:

# you can use this method or max's method
def centerWindowOn(childWindow, parentWindow):
    childWindow.move(parentWindow.window().frameGeometry().topLeft() + 
                                    parentWindow.window().rect().center() - 
                                    childWindow.rect().center())

class MyDialog(QtWidgets.QDialog):
    
    # def __init__() ...
    
    def show(self):
        super(MyDialog, self).show()
        # if you don't do this last, you won't get the result you expect!
        centerWindowOn(self, self.parent())

I find it very useful to create a library for functions like getMayaMainWindow and centerWindowOn since they tend to be used very often. It also cuts down on the number of import statements you have to write to get something simple working.

That actually worked! Weird.
I set self.move(100,100) so the UI wasn’t on the top left, then i could move it around.
Removed the move line, reloaded and the UI now comes in the center each time. I restart Maya and it’s in the center again.

I don’t know what happens in the back end, but it’s not something I would have thought of doing at all (adding, reloading, then removing).

Thank you for this.

Parenting the tool dialog worked perfectly, all is behaving now.

Thank you all for your help. @tfox_TD @JoDaRober @max_wiklund

2 Likes

Sorry to rehash an older topic, but I am having issues trying to get this to work. And I am wondering if it is because I’m not using a Dialog.

def get_maya_main_window():
    main_window_ptr = omui.MQtUtil.mainWindow()
    return wrapInstance(long(main_window_ptr), QtWidgets.QMainWindow)

class BCTools(QMainWindow):
    def __init__(self, parent=get_maya_main_window()):
        super(BCTools, self).__init__(parent)
        self.setup_ui()

This doesn’t do a thing. If I throw in a setWindowFlags(Qt.WindowStaysOnTopHint) it will basically become part of the Maya Window, overlap the actual viewport as if it looks like a graphicaly glitch and then can’t be touched/resized/moved and has no window.

If I remove the “parent” from the super() call and add the setWindowFlags(Qt.WindowStaysOnTopHint), it will stay on top of everything, including windows outside of Maya. What am I missing here? Do I need to convert my tool to utilize a Dialog?

What’s QMainWindow that you’re inheriting from for BCTools class - since you’re using QtWidgets.QMainWindow at line above, and then just QMainWindow?

Have you created you own QMainWindow class, and inherit from it?

And what do yo do in setup_ui method?

Basically, this works as it should, no matter if you use QDialog, or QMainWindow:

def get_maya_main_window():
    main_window_ptr = omui.MQtUtil.mainWindow()
    return wrapInstance(long(main_window_ptr), QtWidgets.QMainWindow)

class BCTools(QtWidgets.QMainWindow):
    def __init__(self, parent=get_maya_main_window()):
        super(BCTools, self).__init__(parent)

        self.show()

It’s parented to Maya’s main window, and on top of it, but not on top of other applications’ windows.

setup_ui simply has all of the UI elements (line edits, buttons, etc).

I’m not sure I understand your first question. This is setup the same exact way above as a MyDialog inheriting from QDialog, only that its a window. No?

It doesn’t stay on top unless I throw in the flag for specifically that, and then when I do that… it literally becomes part of the Maya UI, unmoveable and stuck. I prob can post a pic to show.

Have you tried copy/paste the code from my reply, replacing your lines?

Difference is (maybe you don’t notice it right away) in line where class definition begins.
You have:

class BCTools(QMainWindow):

And I wrote:

class BCTools(QtWidgets.QMainWindow):

QMainWindow doesn’t exist on it’s own, unless:

  1. You have specifically imported it with:
from PySide2.QtWidgets import QMainWindow

which I doubt, since above it, in wrapInstance method, you access it with ‘QtWidgets.QMainWindow’

  1. Or created some class with that name somewhere (like this, or similar), and then inherit from it in BCTools class:
class QMainWindow(QtWidgets.QMainWindow):
 # class code...

That’s why I’ve asked first question, to figure out what QMainWindow actually is.

Firstly, you don’t want to call get_maya_main_window() in the method signature, because that will call it at definition time rather than instantiation. Don’t worry if you don’t know what this means, the main thing is that it’s bad practice because it can cause bugs.

Second, the parenting behavior is inherited from QtWidgets.QWidget, so that means your dialog is being parented as a QWidget. The Qt docs confirm this…

### void QWidget::setParent(QWidget *parent)

Sets the parent of the widget to *parent*, and resets the window flags.
The widget is moved to position (0, 0) in its new parent.

Your window flags will be cleared when the new parent is assigned. This is nice sometimes because it means you can embed whole windows as widgets into another tool, but in the case of trying to parent to the Maya main window it means you’re gonna get floating widgets somewhere without a window border. You don’t want that.

So you have to remind the window that it’s a window, since the QWidget clears window flags when you assign a parent.

So your class would look like this

class BCTools(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        if not parent:
            parent = get_maya_main_window()
        super(BCTools, self).__init__(parent=parent)
        
        # Remind the window that it's supposed to be a window
        self.setWindowFlags(QtCore.Qt.QWindow)