PyQt5 GUI freeze caused by Windows focus-follows-mouse - pyqt

When Windows focus-follows-mouse-without-raising-the-window is enabled by either of the two methods linked to below, I consistently get PyQt5 GUI 'freezes' where you have to type any character in the terminal that you ran python from in order to unfreeze the GUI; complete description and test case (Windows 10, Python 3.6.1, PyQt5) is here: pyqt5 click in terminal causes GUI freeze
To enable the focus-follows-mouse-without-raise behavior, try either of these - they both work in Windows 10:
downloadable program ('X-Mouse' though that name is used by other programs):
https://joelpurra.com/projects/X-Mouse_Controls/
registry hack description:
https://sinewalker.wordpress.com/2010/03/10/ms-windows-focus-follows-mouse-registry-hacks/
So - a few questions:
can anyone reproduce the issue? It seems 100% reproducible for me, but it would be great to hear the same from someone else.
is there a way to change the python code to detect-and-circumvent focus-follows-mouse, or just to be immune to it, i.e. maybe by ensuring the GUI application always takes focus back again when you - for example - click in a dialog or qmessagebox owned by the main GUI window, or by some other means? (Is the object hierarchy set up optimally, and if not, maybe this could all be resolved by correcting the ownership structure?)

The brute-force solution seems to work, though I'd like to leave this question open to see if someone knows of a more optimal solution; it took a fair amount of searching to figure out the right way; mainly by taking a look a the open-source code for X-Mouse. Basically, this method takes effect immediately, whereas the registry hack doesn't take effect until reboot.
New version of pyqt_freeze_testcase.py (the file from the referenced stackoverflow question); the changes are only additions, noted between lines of hash marks:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
####################### added begin:
import win32gui
import win32con
####################### added end
# import the UI file created with pyuic5
from minimal_ui import Ui_Dialog
class MyWindow(QDialog,Ui_Dialog):
def __init__(self,parent):
QDialog.__init__(self)
self.parent=parent
self.ui=Ui_Dialog()
self.ui.setupUi(self)
################################# added begin:
self.initialWindowTracking=False
try:
self.initialWindowTracking=win32gui.SystemParametersInfo(win32con.SPI_GETACTIVEWINDOWTRACKING)
except:
pass
if self.initialWindowTracking:
print("Window Tracking was initially enabled. Disabling it for now; will re-enable on exit.")
win32gui.SystemParametersInfo(win32con.SPI_SETACTIVEWINDOWTRACKING,False)
################################# added end
def showMsg(self):
self.really1=QMessageBox(QMessageBox.Warning,"Really?","Really do stuff?",
QMessageBox.Yes|QMessageBox.No,self,Qt.WindowTitleHint|Qt.WindowCloseButtonHint|Qt.Dialog|Qt.MSWindowsFixedSizeDialogHint|Qt.WindowStaysOnTopHint)
self.really1.show()
self.really1.raise_()
if self.really1.exec_()==QMessageBox.No:
print("nope")
return
print("yep")
################################## added begin:
def closeEvent(self,event):
if self.initialWindowTracking:
print("restoring initial window tracking behavior ("+str(self.initialWindowTracking)+")")
win32gui.SystemParametersInfo(win32con.SPI_SETACTIVEWINDOWTRACKING,self.initialWindowTracking)
################################## added end
def main():
app = QApplication(sys.argv)
w = MyWindow(app)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Related

Reload UI, Rather Than Recreating

import sys
import webbrowser
import hou
from PySide2 import QtCore, QtUiTools, QtWidgets, QtGui
# Calling UI File & Some Modification
class someWidget(QtWidgets.QWidget):
def __init__(self):
super(someWidget,self).__init__()
ui_file = 'C:/Users/XY_Ab/Documents/houdini18.5/Folder_CGI/someUI.ui'
self.ui = QtUiTools.QUiLoader().load(ui_file, parentWidget=self)
self.setParent(hou.qt.mainWindow(), QtCore.Qt.Window)
self.setFixedSize(437, 42)
self.setWindowTitle("Requesting For Help")
window_C = someWidget()
window_C.show()
So, I have created this small script that shows the UI, I have connected this to Houdini Menu Bar. Now The Problem is if I click the menu item multiple times it will create another instance of the same UI & the previous one stays back, What I want is something called "If Window Exist Delete It, Crate New One" sort of thing.
Can someone guide me? I am fairly new to python in Houdini and Qt so a little explanation will be hugely helpful. Also, why can't I use from PySide6 import?? Why do I have to use from PySide2?? Because otherwise Houdini is throwing errors.
For the same thing what used to do in maya is
# Check To See If Window Exists
if cmds.window(winID, exists=True):
cmds.deleteUI(winID)
Trying to do the same thing inside Houdini.
I don't have Maya or Houdini, so I can't help you too much.
According to https://www.sidefx.com/docs/houdini/hom/cb/qt.html
It looks like you can access Houdini's main window. The main reason the window is duplicated or deleted is how python retains the reference to window_C. You might be able to retain the reference to just show the same widget over and over again by accessing the main Houdini window.
In the examples below we are using references a different way. You probably do not need your code that has
self.setParent(hou.qt.mainWindow(), QtCore.Qt.Window)
Create the widget once and keep showing the same widget over and over.
import hou
# Create the widget class
class someWidget(QtWidgets.QWidget):
def __init__(self, parent=None, flags=QtCore.Qt.Window): # Note: added parent as an option
super(someWidget,self).__init__(parent, flags)
...
MAIN_WINDOW = hou.ui.mainQtWindow()
try:
MAIN_WINDOW.window_C.show()
except AttributeError:
# Widget has not been created yet!
# Save the widget reference to an object that will always exist and is accessible
# parent shouldn't really matter, because we are saving the reference to an object
# that will exist the life of the application
MAIN_WINDOW.window_C = someWidget(parent=MAIN_WINDOW)
MAIN_WINDOW.window_C.show()
To delete the previous window and create a new window.
import hou
# Create the widget class
class someWidget(QtWidgets.QWidget):
def __init__(self, parent=None, flags=QtCore.Qt.Window): # Note: added parent as an option
super(someWidget,self).__init__(parent, flags)
...
MAIN_WINDOW = hou.ui.mainQtWindow()
# Hide the previous window
try:
MAIN_WINDOW.window_C.close()
MAIN_WINDOW.window_C.deleteLater() # This is needed if you parent the widget
except AttributeError:
pass
# Create the new Widget and override the previous widget's reference
# Python's garbage collection should automatically delete the previous widget.
# You do not need to have a parent!
# If you do have a parent then deleteLater above is needed!
MAIN_WINDOW.window_C = someWidget() # Note: We do not parent this widget!
MAIN_WINDOW.window_C.show()
Another resource shows you can access the previous widget from the page level variable. https://echopraxia.co.uk/blog/pyqt-in-houdinimaya-basic This is possible, but seems odd to me. The module should only be imported once, so the page level variable "my_window" should never exist. However, it sounds like the Houdini plugin system either reloads the python script or re-runs the import. If that is the case every time you show a new window from the import of the script, you are creating a new window. If the previous window is not closed and deleted properly, Houdini could have an ever growing memory issue.
try:
my_window.close()
except (NameError, Exception):
pass # Normal python would always throw a NameError, because my_window is never defined
my_window = MyWindow()
#This is optional you can resize the window if youd like.
my_window.resize(471,577)
my_window.show()
PySide6
https://www.sidefx.com/docs/houdini/hom/cb/qt.html
The bottom of the page shows how to use PyQt5. The same would apply for PySide6. Houdini just happens to come with PySide2.

How to run PyQt5 GUIs in non-blocking threads?

I have a PyQt5 GUI class that I want to be able to create multiple instances of either from an interactive console or normal run. I need these GUIs to be non-blocking so that they can be used while subsequent code runs.
I've tried calling app.exec__() in separate threads for each GUI like this answer, but the program sometimes crashes as the comment on the answer warned it would:
Run pyQT GUI main app in seperate Thread
And now I'm trying to get the code below working which I made based on this answer:
Run Pyqt GUI main app as a separate, non-blocking process
But when I run it the windows pop and and immediately disappear
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
import time
class MainWindow(QtWidgets.QWidget):
def __init__(self):
# call super class constructor
super(MainWindow, self).__init__()
# build the objects one by one
layout = QtWidgets.QVBoxLayout(self)
self.pb_load = QtWidgets.QPushButton('Load')
self.pb_clear= QtWidgets.QPushButton('Clear')
self.edit = QtWidgets.QTextEdit()
layout.addWidget(self.edit)
layout.addWidget(self.pb_load)
layout.addWidget(self.pb_clear)
# connect the callbacks to the push-buttons
self.pb_load.clicked.connect(self.callback_pb_load)
self.pb_clear.clicked.connect(self.callback_pb_clear)
def callback_pb_load(self):
self.edit.append('hello world')
def callback_pb_clear(self):
self.edit.clear()
def show():
app = QtWidgets.QApplication.instance()
if not app:
app = QtWidgets.QApplication(sys.argv)
win = MainWindow()
win.show()
if __name__ == '__main__':
show()
show()
EDIT - I don't see how this question is a duplicate. The 'duplicate' questions are only slightly related and don't provide solutions to my problem at all.
I want to be able to create multiple instances of a GUI (MainWindow in my example) by calling the show() function from either an interactive session or script, and I want those windows to stay on my screen while subsequent code is running.
EDIT2 - When I run the code as a script I can do what I want by using multiprocessing, see this demo:
https://www.screencast.com/t/5WvJNVSLm9OR
However I still need help because I want it to also work in interactive Python console sessions, and multiprocessing does not work in that case.
It isn't necessary to use separate threads or processes for this. You just need a way to maintain a reference to each new window when importing the script in a python interactive session. A simple list can be used for this. It is only necessary to explictly start an event-loop when running the script from the command-line; in an interactive session, it will be handled automatically by PyQt.
Here is an implementation of this approach:
...
_cache = []
def show(title=''):
if QtWidgets.QApplication.instance() is None:
_cache.append(QtWidgets.QApplication(sys.argv))
win = MainWindow()
win.setWindowTitle(title)
win.setAttribute(QtCore.Qt.WA_DeleteOnClose)
win.destroyed.connect(lambda: _cache.remove(win))
_cache.append(win)
win.show()
if __name__ == '__main__':
show('Foo')
show('Bar')
sys.exit(QtWidgets.QApplication.instance().exec_())
This is a minor addendum to #ekhumoro's answer. I don't have enough reputation to only add a comment so I had to write this as an answer.
#ekhumoro's answer almost fully answers #Esostack's question, but doesn't work in the Ipython console. After many hours of searching for the answer to this question myself, I came across a comment from #titusjan in a three year old thread (here) also responding to a good answer from #ekhumoro.
The missing part to #ekhumoro's answer which results in the gui windows freezing for Ipython specifically is that Ipython should be set to use the qt gui at launch or once running.
To make this work with Ipython:
Launch Ipython with ipython --gui=qt5
In a running Ipython console run the magic command %gui qt5
To fix it from a Python script you can run this function
def fix_ipython():
from IPython import get_ipython
ipython = get_ipython()
if ipython is not None:
ipython.magic("gui qt5")

QLineEdit.setText only works once in function

I'm currently trying to program for my bachelor thesis. The main part works, so now I want to implement a user interface. I watched some tutorials and worked via trial and error, and my user interface also works. So far so good. But yesterday I changed a small thing and that doesn't do what i want. I have a button saying "start program", and a line-edit where i want to display the current status. My code is:
import sys
from PyQt4 import QtGui
from theguifile import Ui_MainWindow
import otherfile
class Main(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui=Ui_MainWindow()
self.ui.setupUi(self)
self.ui.mybutton.clicked.connect(self.runprogram)
def runprogram(self):
self.ui.mylineedit.setText('Running') # doesnt work
try:
otherfile.therealfunction() # works
except ErrorIwanttocatch:
self.ui.mylineedit.setText('thisErrorhappened') # works
else:
self.ui.mylineedit.setText('Success') # works
app = QtGui.QApplication(sys.argv)
window = Main()
sys.exit(app.exec_())
Everything works as I want except from lineedit.setText('Running'). What I want is that "Running" is displayed while otherfile.therealfunction is working. I guess I have to update the line-edit somehow? But until now I didn't figure out how I can do that. I also set the line-edit readonly because I don't want the user to be able to change it, so maybe that's a problem? I thought readonly would only affect what the user can do.
I am using Python3 and PyQt4 with Qt Designer.
Calling otherfile.therealfunction() will block all ui updates until the function completes. You can try forcing immediate ui updates like this:
def runprogram(self):
self.ui.mylineedit.setText('Running')
QtGui.qApp.processEvents()

pyqt+maya = fatal error when click to context menu

i use pyqt in autodesk maya. all work but when i try connect a context menu to my elements - maya get fatal error and closed.
import maya.OpenMayaUI as mui
import maya.api.OpenMaya as om
import sip
from PyQt4 import QtGui, QtCore, uic
#----------------------------------------------------------------------
def getMayaWindow():
ptr = mui.MQtUtil.mainWindow()
return sip.wrapinstance(long(ptr), QtCore.QObject)
#----------------------------------------------------------------------
form_class, base_class = uic.loadUiType('X:/tools/Maya/windows/2014/python/UI/perforceBrowserWnd.ui')
#----------------------------------------------------------------------
# main window class
#----------------------------------------------------------------------
class PerforceWindow(base_class, form_class):
def __init__(self, parent=getMayaWindow()):
super(base_class, self).__init__(parent)
self.setupUi(self)
# Popup Menu is not visible, but we add actions from above
self.popMenu = QtGui.QMenu( self )
self.popMenu.addAction("revert", self.on_action_revert)
self.popMenu.addAction("submit", self.on_action_submit)
self.filesListWgt.customContextMenuRequested.connect( self.filesListWgtMenuRequested )
#------------------------------------------------------------------
def filesListWgtMenuRequested(self, pos):
self.popMenu.exec_( self.filesListWgt.mapToGlobal(pos) )
def on_action_revert(self):
print('on_action_revert')
def on_action_submit(self):
print('on_action_submit')
#----------------------------------------------------------------------
# window
#----------------------------------------------------------------------
def perforceBrowser2():
perforceBrowserWnd = PerforceWindow()
perforceBrowserWnd.show()
perforceBrowser2()
dialog created in qtdesigner. i set attribute contentMenuPolicy in designer on QListWidtet. when i right click on QListWidtet or any element - i see a context menu. but if i click a menu or dismiss it - maya get fatal error
and i see log text - function on_action_revert is called. but after that - maya crashed.
what i doing wrong?
update:
i try simple test. replace a menu to simple call a function:
replace connect to:
self.filesListWgt.customContextMenuRequested.connect( self.on_action_revert )
def on_action_revert(self):
print('on_action_revert')
this crash maya too
I tested your code along with your UI file on PyQt in Maya 2013 as well as on PySide (using a QtShim) on Maya 2014 and your code ran fine. Please check your PyQt build for Maya 2014.
I recommend attempting to run your code using PySide on Maya 2014. To do this you do not need to change any of your code base. You just need to modify a few imports. It is worth checking out these: Take a look at this. You can use this to write code that is compatible in both PyQt and PySide. https://github.com/rgalanakis/practicalmayapython/blob/master/src/chapter5/qtshim.py
And to load your ui file in PySide environment take a look at this article: http://www.jason-parks.com/artoftech/?p=579
PyQt and PySide are both just python wrappers for the Qt framework. They are identical apart from a very few differences. So your code base never needs to change no matter what you use to run it in.
P.s. But for whatever reason you are so particular for using PyQt for 2014, Please use these guides to build it: http://download.autodesk.com/us/support/files/maya_documentation/pyqtmaya2014.pdf and this one: http://around-the-corner.typepad.com/adn/2013/04/building-sip-and-pyqt-for-maya-2014.html
Maya specific PyQt builds are maintained in this Github repo maintained by Marcus Ottosson. You can grab the specific build for yourself and add it to PYTHONPATH. I had the same issue in Maya 2015 and this helped.
https://github.com/pyqt

Alternative for tkinter's askopenfilename

Currently I am using tkinter's askopenfilename in a quicklist editor for Ubuntu to get a file's name and location. Although it works fine, the look and feel is not native.
Is there an easy alternative dialogue window, to navigate and get a file's name and location?
You could try with wxPython FileDialog:
>>> import wx
>>> d = wx.FileDialog(None)
>>> d.ShowModal()
5101
>>>
It gives a more OS specific look
wxPython is arriving soon to py3k as the Phoenix project and there are already snapshots for windows and mac (see my comment below). If you want something more stable you can use pyQt QtGui.QFileDialog.
import sys
from PyQt4 import QtGui
class Dialog(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
filename = QtGui.QFileDialog.getOpenFileName()
print filename
app = QtGui.QApplication(sys.argv)
dialog = Dialog()
You have a more complete example here.
Zenity
Zenity's File Selection Dialog provides an easy and natively looking solution with the --file-selection option. The dialog provides a number of options.
See also Zenity's man pages.
In its simplest form:
#!/usr/bin/env python3
import subprocess
try:
file = subprocess.check_output(["zenity", "--file-selection"]).decode("utf-8").strip()
print(file)
except subprocess.CalledProcessError:
pass
Gtk's FileChooserDialog
Another option is Gtk's FileChooserDialog, which produces, as one might expect, perfectly natively looking file chooser dialog windows.

Resources