I am working on a Python project that uses Qt Designer to build interface. when working on building a plugin capability, I was able to allow dynamic loading of user plugins and create a new QMenu item to add to the main menubar. The problem is that there seems to be no way of removing that top level QMenu once it is added to the main menubar. I researched/searched quite a bit on this topic and it seems that every solution related to this topic is for removing sub-menu items from a QMenu via removing its actions, not for removing that dynamically-added QMenu itself. I hope someone would point out this to be a simple thing, and provide a code snippet to demo how this is done.
Achayan's solution above crashes on python2 qt4 (Windows) for the deletion
Better way for it is to to use the clear function.
Adding to the solution above,
def removeMenu():
self.main_menu.clear()
Hope this will give you idea for what you upto. And I took some part from another post, which is same qmenu thing
import sys
# This is bad, but Iam lazy
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.main_menu = self.menuBar()
widget = QWidget()
self.menuList = []
layout2 = QVBoxLayout(widget)
self.menuButton = QPushButton("Add Menu")
self.menuRmButton = QPushButton("Remove Menu")
layout2.addWidget(self.menuButton)
layout2.addWidget(self.menuRmButton)
self.menuButton.clicked.connect(self.create_menu)
self.menuRmButton.clicked.connect(self.removeMenu)
self.setCentralWidget(widget)
def create_menu(self):
menu2 = self.main_menu.addMenu('Menu 1')
self.menuList.append(menu2)
Action1=QAction('Menu 1 0',self)
Action1.triggered.connect(self.action_1)
menu2.addAction(Action1)
Action2=QAction('Menu 1 1',self)
Action2.triggered.connect(self.action_2)
menu2.addAction(Action2)
def removeMenu(self):
if self.menuList:
for eachMenu in self.menuList:
menuAct = eachMenu.menuAction()
self.main_menu.removeAction(menuAct)
# just for safe side
menuAct.deleteLater()
eachMenu.deleteLater()
def action_1(self):
print('Menu 1 0')
def action_2(self):
print('Menu 1 1')
if __name__ == '__main__':
app=QApplication(sys.argv)
new=MyWindow()
new.show()
app.exec_()
Related
I have been struggling to learn PyQt5 (and object oriented programming). In my current script I need to create a tabbed interface but can't seem to manage it. I suspect the problem is related to OOP (I am a novice). "self" seems to be the problem, and I kind of know what that means but not enough to be able to fix it. Below is my latest attempt. It seems like I am using the "wrong self", from elsewhere in the script. I want very much to understand object oriented programming - thanks in advance to anyone kind enough to help!
Some of the code/errors are:
code:
tabbar = QTabBar()
tab1 = QTabWidget()
tabbar.addTab(tab1, 'tab1')
error:
TypeError: arguments did not match any overloaded call:
addTab(self, str): argument 1 has unexpected type 'QTabWidget'
addTab(self, QIcon, str): argument 1 has unexpected type 'QTabWidget'
And here's the code:
class App(QMainWindow):
def launch(self, filepath):
subprocess.run(filepath)
def newLauncher(self, matrix):
pass # cut for brevity
def __init__(self):
super(App, self).__init__()
tabbar = QTabBar()
tab1 = QTabWidget()
index = tabbar.addTab(tab1, 'tab1')
self.initUI()
def initUI(self):
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
It is good that you want to learn about OOP, but that is not the main problem in this case, but it seems that you do not read the documentation. If we check that it is a QTabBar it would see that it refers to the top part of the QTabWidget, it is that part with buttons.
You do not have to use QTabBar but QTabWidget, QTabWidget has the addTab method that requires as a first parameter the widget that will be displayed on a page, and as a second parameter a title that will appear on the buttons.
Another mistake that I see in your code is that you create the widget but not setting it as part of another widget are just local variables that we know are deleted when the function is finished.
Since you are using QMainWindow you must set QTabWidget as part of a central widget, for this we can use the layouts.
import sys
from PyQt5.QtWidgets import *
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
centralWidget = QWidget()
lay = QVBoxLayout(centralWidget)
tab = QTabWidget()
lay.addWidget(tab)
for i in range(5):
page = QWidget()
tab.addTab(page, 'tab{}'.format(i))
self.setCentralWidget(centralWidget)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())
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.
In reference to a previous question, I need some help with keeping references in my application.
First a snippet from my code.
from PyQt4 import QtGui
import os, os.path
import sys
class mainWindowHandler():
equationEditor = []
_listview = None
_window = None
def __init__(self):
return
def showAddEquation(self):
"""Creates a new instance of the dynamic editor for adding an equation"""
#create a horizontal split layout
window = QtGui.QWidget()
layout = QtGui.QHBoxLayout()
current = len(self.equationEditor) - 1
de = QtGui.QPushButton(str(current))
self.equationEditor.append(de)
de.clicked.connect(self.clicked)
#fill list view with items from equation collection
listview = QtGui.QListWidget()
for equation in self.equationEditor:
item = QtGui.QListWidgetItem()
item.setText(equation.text())
listview.addItem(item)
layout.addWidget(listview)
layout.addWidget(de)
window.setWindowTitle("Equation {}".format(str(current))
window.setLayout(layout)
self._window = window
self._listview = listview
window.show()
return window
def clicked(self):
"""Method for handling the button events in the solver settings\n
signal = the button hit\n"""
return self.showAddEquation()
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
window = ewh.showAddEquation()
sys.exit(app.exec_())
The application will (later) create a window that allows the manipulation of certain settings - in my code example represented by the QPushButton. These settings are later written to a txt-file, but until then I save them in form of there widget. I simply add the widget to a collection and recall them from there. That works well on the Python-level.
Now, I have a button that creates a new instance of the window from inside of the window itself. That works too. But only until the third instance. At that point I loose the reference to my QPushButton on the Qt-level. I get the
wrapped C/C++ object of type `QPushButton` has been deleted
error when trying to retrieve the buttons from my collection (equationEditor). In Python they are still there, but obviously the corresponding Qt-objects where destroyed because I somewhere mishandled the references.
Can someone point out a better solution or how I can keep the references?
Thanks...
Edit:
As there seem to be some confusions I will try to explain the functionality a bit more in detail.
The program starts and creates a window "Equation 1" with a QListViewand a QPushButton "1". In the list view all available QPushButton are listed (at start only 1 item). In my actual program the QPushButton is QWidget with some text fields and the QPushButton.
If the user clicks "1" then the button "1" should disappear and a new instance of QPushButton named "2" should appear at the position of "1". Additionally, the listview should now hold two items "1" and "2" and the window should have the title "Equation 2". If it is a new window or the same as before with new contents is not relevant. Both variants would be okay. The former is the way it is implemented at the moment. Visible should by only one window at a time.
All instances of QPushButton should by collected in a small list (called equationEditor) to keep them in the memory. In my actual program this is used for saving all changes made in the widgets without writing the changes to a temp file.
Later, if the user selects item "1" in the QListView then the current visible QPushButton should be replaced by the QPushButton "1" (from the collection equationEditor) or if he selects the second item the QPushButton "2" should be shown.
Why?
The widget that will be used later contains a lot of editable data. As the user could edit that at any time it is easier to keep the widgets in memory without showing them instead of repopulating all the data again. As soon as the user selects one in the QListView the corresponding widget should be shown in the window so that he can edited the data in the widget again.
It's quite hard to understand what exactly you're trying to do. Looking at your code I wonder why it is even working twice before failing.
Btw. I just saw, that there is a quite accurate description of why it's failing in the previous post, given by Schollii
Anyways, I think you should make a new class for the equation window. The main class can then keep track of all opened windows in the equationEditor list. It can also add the values of the other opened windows to a new one, once created.
Here is how it would look like
from PyQt4 import QtGui
import os, os.path
import sys
class ShowAddEquation(QtGui.QWidget):
"""Creates a new instance of the dynamic editor for adding an equation"""
def __init__(self,parent=None):
super(ShowAddEquation, self).__init__(parent=parent)
#create a horizontal split layout
layout = QtGui.QHBoxLayout()
self.current = 0
self.de = QtGui.QPushButton(str(self.current))
self.listview = QtGui.QListWidget()
layout.addWidget(self.listview)
layout.addWidget(self.de)
self.setWindowTitle("Equation Editor")
self.setLayout(layout)
self.show()
def setCurrent(self, current):
self.current=current
self.de.setText(str(self.current))
class mainWindowHandler():
equationEditor = []
def __init__(self):
return
def clicked(self):
se = ShowAddEquation()
self.equationEditor.append(se)
se.de.clicked.connect(self.clicked)
current = len(self.equationEditor) - 1
se.setCurrent(current)
for equation in self.equationEditor:
item = QtGui.QListWidgetItem()
item.setText(str(equation.current))
se.listview.addItem(item)
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
ewh.clicked()
sys.exit(app.exec_())
So, after understanding the approach given in the first answer I have solved my problem. Here is the working code
# -*- coding: utf-8 -*-
"""
Created on Sat Sep 3 14:31:15 2016
"""
from PyQt4 import QtGui
from PyQt4 import QtCore
import os, os.path
import sys
class mainWindowHandler():
equationEditor = []
_listview = None
_window = None
def __init__(self):
return
def showAddEquation(self):
"""Creates a new instance of the dynamic editor for adding an equation"""
#create a horizontal split layout
self._window = QtGui.QWidget()
layout = QtGui.QHBoxLayout()
self._listview = QtGui.QListWidget()
layout.addWidget(self._listview)
self._listview.clicked[QtCore.QModelIndex].connect(self.changed)
self._window.setLayout(layout)
#populate the right side of the layout with a button
self.clicked()
self._window.show()
return self._window
def clicked(self):
"""Make a new button instance and add it to the window and the collection"""
window = self._window
layout = window.layout()
current = len(self.equationEditor) - 1
de = QtGui.QPushButton(str(current))
self.equationEditor.append(de)
de.clicked.connect(self.clicked)
#close the currently shown button
item = layout.takeAt(1)
if item is not None:
item.widget().close()
layout.addWidget(de)
#fill list view with items from material collection
item = QtGui.QListWidgetItem()
item.setText(de.text())
self._listview.addItem(item)
self._window.setWindowTitle("Equation Editor {}".format(str(current)))
def changed(self, index):
"""hide the object on the right side of the layout and show the button at position index in the collection"""
layout = self._window.layout()
item = layout.takeAt(1)
item.widget().close()
# insert the selected button from the collection
de = self.equationEditor[index.row()]
layout.insertWidget(1, de)
self._window.setWindowTitle("Equation Editor {}".format(str(index.row() - 1)))
de.show()
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
window = ewh.showAddEquation()
sys.exit(app.exec_())
I'm writing a GUI in PyQt4 (and migrating to PyQt5). This is how I start my GUI:
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Fusion')) # <- Choose the style
myGUI = MyMainWindow("First GUI")
app.exec_()
Default Styles in PyQt4 :
Apparently, PyQt4 has the following styles:
'Windows'
'WindowsXP'
'WindowsVista'
'Motif'
'CDE'
'Plastique'
'Cleanlooks'
Default Styles in PyQt5 :
PyQt5 has the following styles:
'Windows'
'WindowsXP'
'WindowsVista'
'Fusion'
Custom styles?
None of these styles has proper support for HiDpi displays (4k and the like). For example, scrollbars are too small( see this post: How to resize the scrollbar from a QTextEdit in PyQt?). And I didn't even mention the problems for those people with unsharp eyesight..
Do you know a style (preferably open-source) that provides good support for 4k displays or for people with eyesight problems?
If so, how can one download this style, and install it?
Thank you so much.
I got the answer (or let's say a workaround) through another question:
How to make Icon in QMenu larger (PyQt)?
The most straightforward way to create a new QStyle is deriving it from an existing one. PyQt provides the QProxyStyle class for that purpose. Below is the example that I've also given in the How to make Icon in QMenu larger (PyQt)? question. In this example, a custom QStyle is created (derived from "Fusion" style), and the custom style provides very big icons for the QMenu.
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
# Create a custom "QProxyStyle" to enlarge the QMenu icons
#-----------------------------------------------------------
class MyProxyStyle(QProxyStyle):
pass
def pixelMetric(self, QStyle_PixelMetric, option=None, widget=None):
if QStyle_PixelMetric == QStyle.PM_SmallIconSize:
return 40
else:
return QProxyStyle.pixelMetric(self, QStyle_PixelMetric, option, widget)
# This is the main window class (with a simple QMenu implemented)
# ------------------------------------------------------------------
class TestWindow(QMainWindow):
def __init__(self):
super(TestWindow, self).__init__()
# 1. Set basic geometry and color.
self.setGeometry(100, 100, 400, 400)
self.setWindowTitle('Hello World')
palette = QPalette()
palette.setColor(QPalette.Window, QColor(200, 200, 200))
self.setPalette(palette)
# 2. Create the central frame.
self.centralFrame = QFrame()
self.centralFrame.setFrameShape(QFrame.NoFrame)
self.setCentralWidget(self.centralFrame)
# 3. Create a menu bar.
myMenuBar = self.menuBar()
fileMenu = myMenuBar.addMenu("&File")
testMenuItem = QAction(QIcon("C:\\my\\path\\myFig.png"), "&Test", self)
testMenuItem.setStatusTip("Test for icon size")
testMenuItem.triggered.connect(lambda: print("Menu item has been clicked!"))
fileMenu.addAction(testMenuItem)
# 4. Show the window.
self.show()
# Start your Qt application based on the new style
#---------------------------------------------------
if __name__== '__main__':
app = QApplication(sys.argv)
myStyle = MyProxyStyle('Fusion') # The proxy style should be based on an existing style,
# like 'Windows', 'Motif', 'Plastique', 'Fusion', ...
app.setStyle(myStyle)
myGUI = TestWindow()
sys.exit(app.exec_())
Just copy-paste the code snippet, and paste it in a *.py file. Of course, you should replace the path to the icon with a valid path on your local computer. Just provide a complete path ("C:..") to be 100% sure that Qt finds the icon drawing.
Try it out, and you should get the following window:
I need to update the existing menu items for a system tray application. At first when the app loads, there will be two menu items. Later when I click a button these menu items need to be replaced with new menu items. How can I achieve that ? Here is my code.
from PySide.QtGui import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.tray = QSystemTrayIcon(QApplication.style().standardIcon(QStyle.SP_DriveDVDIcon), self)
self.m = QMenu()
self.m.addAction('First')
self.m.addAction('Second')
self.tray.setContextMenu(self.m)
self.tray.show()
p = QPushButton("Click Me", self)
self.setCentralWidget(p)
p.clicked.connect(self.onClick)
def onClick(self):
self.m.clear()
self.m.addAction('First')
self.m.addAction('Third')
self.tray.setContextMenu(self.m)
app = QApplication(sys.argv)
w = MainWindow()
w.show();
sys.exit(app.exec_())
However this is not working. If I try removing self.m.clear()the new menu items will append to the existing (Which is the normal behaviour in this case). Isn't menu.clear() clears the current menu & the new menu should be populated here ?
I have seen this similar question Qt QSystemTrayIcon change menu items and the solution doesn't work for me. I am running Ubuntu 14.04.
I figured it out, the problem is due to the self.tray.setContextMenu(self.m). Remove this line from onClick method. This should work fine on Ubuntu.