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.
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()
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
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.
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.
I can’t speak for Linux behavior, but to get Maya tools set up correctly on Windows I have to do 2 things:
Parent the tool dialog to the Maya main window
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).
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?
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.
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)