PyQt WebEngineView interferes with MainMenu - python

I'm trying to create an application that contains a web browser within it, but when I add the web browser my menu bar visually disappears but functionally remains in place. The following are two images, one showing the "self.centralWidget(self.web_widget)" commented out, and the other allows that line to run. If you run the example code, you will also see that while visually the entire web page appears as if the menu bar wasn't present, you have to click slightly below each entry field and button in order to activate it, behaving as if the menu bar was in fact present.
Web Widget Commented Out
Web Widget Active
Example Code
import os
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
class WebPage(QWebEngineView):
def __init__(self, parent=None):
QWebEngineView.__init__(self)
self.current_url = ''
self.load(QUrl("https://facebook.com"))
self.loadFinished.connect(self._on_load_finished)
def _on_load_finished(self):
print("Url Loaded")
class MainWindow(QMainWindow):
def __init__(self, parent=None):
# Initialize the Main Window
super(MainWindow, self).__init__(parent)
self.create_menu()
self.add_web_widet()
self.show()
def create_menu(self):
''' Creates the Main Menu '''
self.main_menu = self.menuBar()
self.main_menu_actions = {}
self.file_menu = self.main_menu.addMenu("Example File Menu")
self.file_menu.addAction(QAction("Testing Testing", self))
def add_web_widet(self):
self.web_widget = WebPage(self)
self.setCentralWidget(self.web_widget)
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.showMaximized()
sys.exit(app.exec_()) # only need one app, one running event loop
Development Environment
Windows 10, PyQt5, pyqt5-5.9
EDIT
The problem doesn't seem to be directly related to the menu bar. Even removing the menu bar the issue still occurs. That said, changing from showMaximized() to showFullScreen() does seem to solve the problem.

I no longer believe this is an issue with PyQt5 specifically but rather a problem with the graphics driver. Specifically, if you look at Atlassian's HipChat application it has a similar problem which is documented here:
https://jira.atlassian.com/browse/HCPUB-3177
Some individuals were able to solve the problem by running the application from the command prompt with the addendum "--disable-gpu" but that didn't work for my python application. On the other hand, rolling back the Intel(R) HD Graphics Driver did solve my problem. Version 21.20.16.4627 is the one that seems to be causing problems.

Related

QWebEngineView not loading with default Windows elevation

I'm having a bizarre issue where this simple QWebEngine code runs perfectly fine as a regular user on Windows 10 (loads the page fully), but when I elevate my own default user account it stops loading the page. The progress output will go from 0->100 and not call loadFinished on the browser or show any output on the page. I've tried running it as an elevated standard Administrator built-in account and it seemed to work oddly enough.
Here's the unloaded page:
and here's it working properly:
Running with os.environ["QT_DEBUG_PLUGINS"] = "1" doesn't show any discrepancies between the elevated and non-elevated processes.
import sys
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QMainWindow
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.browser = QWebEngineView()
self.browser.setUrl(QUrl('https://www.google.com'))
self.browser.loadProgress.connect(self.on_load_progress)
self.setCentralWidget(self.browser)
self.show()
def on_load_progress(self, progress: int):
print(f'loading progress:[{progress}]...')
app = QApplication(sys.argv)
main_win = MainWindow()
app.exec()
After trying many things and going through many logs, the only partial solution I've found to this problem is adding --no-sandbox to the QApplication argument list to make the Chromium webdriver work at all. For my app this is enough but running without sandboxing is less than ideal for a broader solution.
For those interested I found the answer thanks to this thread (has a lot more relevant info than my descriptions).

In PyQt, how does one get a shared menu and toolbar to talk to the currently active subwindow?

I have an application which has a main window, which can have multiple subwindows. I would like to have one set of QActions in the main window that interact with the currently selected window. For example, the application might be a text editor, and clicking file->save should save the text file the user is currently working on. Additionally, some QActions are checkable, so their checked state should reflect the state of the currently active window.
Here is a minimum working example that has the basic functionality I want, but I suspect there is a better way to do it (further discussion below the code).
import sys
import PyQt4.QtGui as QtGui
class DisplayWindow(QtGui.QWidget):
def __init__(self, parent=None, name="Main Window"):
# run the initializer of the class inherited from
super(DisplayWindow, self).__init__()
self.myLayout = QtGui.QFormLayout()
self.FooLabel = QtGui.QLabel(self)
self.FooLabel.setText(name)
self.myLayout.addWidget(self.FooLabel)
self.setLayout(self.myLayout)
self.is_foo = False
def toggle_foo(self):
self.is_foo = not self.is_foo
if self.is_foo:
self.FooLabel.setText('foo')
else:
self.FooLabel.setText('bar')
class WindowActionMain(QtGui.QMainWindow):
def __init__(self):
super(WindowActionMain, self).__init__()
self.fooAction = QtGui.QAction('Foo', self)
self.fooAction.triggered.connect(self.set_foo)
self.fooAction.setCheckable(True)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(self.fooAction)
self.toolbar = self.addToolBar('File')
self.toolbar.addAction(self.fooAction)
self.centralZone = QtGui.QMdiArea()
self.centralZone.subWindowActivated.connect(
self.update_current_window)
self.setCentralWidget(self.centralZone)
self.create_dw("Window 1")
self.create_dw("Window 2")
def create_dw(self, name):
dw = DisplayWindow(name=name)
self.centralZone.addSubWindow(dw)
dw.show()
def update_current_window(self):
""" redirect future actions to affect the newly selected window,
and update checked statuses to reflect state of selected window"""
current_window = self.centralZone.activeSubWindow()
if current_window:
self.current_dw = self.centralZone.activeSubWindow().widget()
self.fooAction.setChecked(self.current_dw.is_foo)
def set_foo(self):
self.current_dw.toggle_foo()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
ex = WindowActionMain()
ex.show()
sys.exit(app.exec_())
My actual version of DisplayWindow could be useful in many different projects, and I want to package it up so that you don't have to add a lot of code to the main window to use it. Therefore, DisplayWindow, all of its functionality and a list of available actions should be in one module, which would be imported in WindowActionMain's module. I should then be able to add more actions for DisplayWindow without changing any code in WindowActionMain. In particular, I don't want to have to write a little function like WindowActionMain.set_foo(self) just to redirect each action to the right place.
Yes, this is possible by handling the QMenu's aboutToShow signal
and considering the QGuiApplication's focusWindow (or however you get that in Qt4).
Example below shows a generic 'Window' menu acting on the frontmost window.
http://doc.qt.io/qt-4.8/qmenu.html#aboutToShow
http://doc.qt.io/qt-5/qguiapplication.html#focusWindow
def on_windowMenu_aboutToShow(self):
self.windowMenu.clear()
self.newWindowAction = QtWidgets.QAction(self)
self.newWindowAction.setShortcut("Ctrl+n")
self.newWindowAction.triggered.connect(self.on_newWindowAction)
self.newWindowAction.setText("New Window")
self.windowMenu.addAction(self.newWindowAction)
self.windowMenu.addSeparator()
playerWindows = [w for w in self.topLevelWindows() if w.type()==QtCore.Qt.Window and w.isVisible()]
for i, w in enumerate(playerWindows):
def action(i,w):
a = QtWidgets.QAction(self)
a.setText("Show Window {num} - {title}".format(num=i+1, title=w.title()))
a.triggered.connect(lambda : w.requestActivate())
a.triggered.connect(lambda : w.raise_())
self.windowMenu.addAction(a)
action(i,w)
self.windowMenu.addSeparator()
self.closeWindowAction = QtWidgets.QAction(self)
self.closeWindowAction.setShortcut("Ctrl+w")
self.closeWindowAction.triggered.connect(lambda : self.focusWindow().close())
self.closeWindowAction.setText("Close")
self.windowMenu.addAction(self.closeWindowAction)

Why is my PyQt4 program exiting the main loop for no apparent reason?

I'm (trying to) make a small program that resides in the system tray and checks a list of Twitch channels to see if they're online every once in a while.
I'm currently doing the GUI (in PyQt4), but it's exiting for no reason.
Here's my code so far:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
class TwitchWatchTray(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
super(TwitchWatchTray, self).__init__(icon, parent)
self.menu = QtGui.QMenu(parent)
settings_action = self.menu.addAction("Settings")
settings_action.triggered.connect(self.open_settings)
self.menu.addSeparator()
exit_action = self.menu.addAction("Exit")
exit_action.triggered.connect(QtCore.QCoreApplication.instance().quit)
self.setContextMenu(self.menu)
self.show()
def open_settings(self):
settings = SettingsDialog()
settings.show()
class SettingsDialog(QtGui.QWidget):
def __init__(self):
super(SettingsDialog, self).__init__()
self.resize(300, 300)
self.setWindowTitle('TwitchWatch Settings')
vbox = QtGui.QHBoxLayout()
self.channels_list = QtGui.QListView(self)
vbox.addWidget(self.channels_list)
self.add_box = QtGui.QLineEdit(self)
vbox.addWidget(self.add_box)
self.setLayout(vbox)
self.show()
def main():
app = QtGui.QApplication(sys.argv)
widget = QtGui.QWidget()
tw = TwitchWatchTray(QtGui.QIcon("icon.png"), widget)
app.exec_()
print("Done!")
if __name__ == '__main__':
main()
When I right click the tray icon and click "Settings", it flashes a white box (my dialog), then immediately exits and prints "Done!".
Why is this, and how do I fix it?
There are two reasons why your code exits immediately after you open the settings dialog.
The first problem is with your open_settings method:
def open_settings(self):
settings = SettingsDialog()
settings.show()
This creates a dialog and makes it visible. show() returns immediately after showing the window; it doesn't wait for the window to be closed. The settings variable goes out of scope at the end of the method, and this causes the reference count of your SettingsDialog to drop to zero and hence become eligible for garbage collection. When Python deletes the SettingsDialog object, PyQt will delete the underlying C++ object, and this is what causes the dialog to close again.
I would recommend having your settings dialog subclass QDialog rather than QWidget (it is a dialog, after all). Instead of calling settings.show() you can then call settings.exec_(). settings.exec_() does wait for the dialog to be closed before it returns. It also returns QDialog.Accepted or QDialog.Rejected depending on whether the user clicked OK or Cancel. I'd also recommend getting rid of the call to self.show() in your SettingsDialog constructor.
The second problem is that your QApplication is set to quit when the last window is closed. This is the default behaviour, which is what a lot of applications need, but not yours. Even if your dialog stayed open and you could close it, you wouldn't want your application to exit immediately after you close the settings dialog. Call app.setQuitOnLastWindowClosed(False) to fix this.

QDialog - Prevent Closing in Python and PyQt

I have a login screen dialog written using pyqt and python and it shows a dialog pup up when it runs and you can type in a certin username and password to unlock it basicly. It's just something simple I made in learning pyqt. I'm trying to take and use it somewhere else but need to know if there is a way to prevent someone from using the x button and closing it i would like to also have it stay on top of all windows so it cant be moved out of the way? Is this possible? I did some research and couldn't find anything that could help me.
Edit:
as requested here is the code:
from PyQt4 import QtGui
class Test(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self)
self.textUsername = QtGui.QLineEdit(self)
self.textPassword = QtGui.QLineEdit(self)
self.loginbuton = QtGui.QPushButton('Test Login', self)
self.loginbuton.clicked.connect(self.Login)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.textUsername)
layout.addWidget(self.textPassword)
layout.addWidget(self.loginbuton)
def Login(self):
if (self.textUsername.text() == 'Test' and
self.textPassword.text() == 'Password'):
self.accept()
else:
QtGui.QMessageBox.warning(
self, 'Wrong', 'Incorrect user or password')
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
if Test().exec_() == QtGui.QDialog.Accepted:
window = Window()
window.show()
sys.exit(app.exec_())
Bad news first, it is not possible to remove the close button from the window, based on the Riverbank mailing system
You can't remove/disable close button because its handled by the
window manager, Qt can't do anything there.
Good news, you can override and ignore, so that when the user sends the event, you can ignore or put a message or something.
Read this article for ignoring the QCloseEvent
Also, take a look at this question, How do I catch a pyqt closeEvent and minimize the dialog instead of exiting?
Which uses this:
class MyDialog(QtGui.QDialog):
# ...
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
# when you want to destroy the dialog set this to True
self._want_to_close = False
def closeEvent(self, evnt):
if self._want_to_close:
super(MyDialog, self).closeEvent(evnt)
else:
evnt.ignore()
self.setWindowState(QtCore.Qt.WindowMinimized)
You can disable the window buttons in PyQt5.
The key is to combine it with "CustomizeWindowHint",
and exclude the ones you want to be disabled.
Example:
#exclude "QtCore.Qt.WindowCloseButtonHint" or any other window button
self.setWindowFlags(
QtCore.Qt.Window |
QtCore.Qt.CustomizeWindowHint |
QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowMinimizeButtonHint
)
Result with QDialog:
Reference: https://doc.qt.io/qt-5/qt.html#WindowType-enum
Tip: if you want to change flags of the current window, use window.show()
after window.setWindowFlags,
because it needs to refresh it, so it calls window.hide().
Tested with QtWidgets.QDialog on:
Windows 10 x32,
Python 3.7.9,
PyQt5 5.15.1
.
I don't know if you want to do this but you can also make your window frameless. To make window frameless you can set the window flag equal to QtCore.Qt.FramelessWindowHint

How to provide X11 with a wm_name for a Qt4 window?

Background
I am about to resolve another issue, which consists in reserving screen-space for a Qt Window on X11. For this i use PyQt4 and Python-Xlib.
Situation
The application i want to save screen space for is a frameless Qt Window, 25px high and screen-wide, it's a panel in fact. The simplified code looks like this:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
# this module i made myself (see below):
import myXwindow
class QtPanel(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# here i explicitely name the window:
self.setWindowTitle('QtPanel')
self.resize(QtGui.QDesktopWidget().screenGeometry().width(), 25)
self.move(0,0)
self.setWindowFlags(QtCore.Qt.Widget |
QtCore.Qt.FramelessWindowHint |
QtCore.Qt.WindowStaysOnTopHint)
self.setAttribute(QtCore.Qt.WA_X11NetWmWindowTypeDock)
def main():
app = QtGui.QApplication(sys.argv)
panel = QtPanel()
panel.show()
xwindow = myXwindow.Window(str(panel.windowTitle()))
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Now to reserve space for this QtPanel application, i need to call on xlib. Here below i will just show how i intend to 'grab' my window from X. The following module is imported in the code here above to get the Xwindow of my panel:
from Xlib.display import Display
from Xlib import X
class Window(object):
def __init__(self, title):
self._title = title
self._root = Display().screen().root
self._window = self.find_window()
def find_window(self):
my_window = None
display = Display()
windowIDs = self._root.get_full_property(
display.intern_atom('_NET_CLIENT_LIST'),
X.AnyPropertyType).value
print 'looking for windows:'
count = 0
for windowID in windowIDs:
count += 1
window = display.create_resource_object('window', windowID)
title = window.get_wm_name()
print 'window', count, ':', title
if self._title in title:
my_window = window
return my_window
Problem
Now when i run the QtPanel application, it should return a list of names of all the windows currently displayed by X. Despite having been named explicitly in the code (see here above) the QtPanel application either has no name (i.e. None or '') or is named 'x-nautilus-desktop', here is a sample of what was returned:
$ python ./Qtpanel.py
looking for windows:
window 1 : Bottom Expanded Edge Panel
window 2 : cairo-dock
window 3 : x-nautilus-desktop
window 4 : ReserveSpace - NetBeans IDE 6.9
window 5 : How to provide X11 with a wm_name for a Qt4 window? - Stack Overflow - Mozilla Firefox
Question
How can i 'name' my Qt Application or Qt toplevel window so it shows properly on X?
And/or: how can i identify my application in X other than with the application's name?
Although i don't quite know what the unnamed window is (and it seems related to launching the QtPanel application, the Xlib script is not able to find the window on display because it is queried before the event loop QApplication._exec(). A thread may be required to do that outside of the main loop.

Categories

Resources