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()

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()

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