I'm attempting to add a set of push buttons to a toolbar that can be toggled. When there are more buttons than can be displayed, the chevron appears, but it is greyed out and doesn't show the remaining contents.
Initially I had:
toolbar = QtGui.QToolbar()
newButton = QtGui.QPushButton('name')
newButton.toggled.connect(myAction)
toolbar.addWidget(newButton)
I read that I need to create a customWidgetAction, so I have tried the following:
toolbar = QtGui.QToolbar()
newButton = QtGui.QPushButton()
widgetAction = QtGui.QWidgetAction(newButton)
widgetAction.toggled.connect(myAction)
newWidget = widgetAction.createWidget(newButton)
toolbar.addWidget(newButton)
However using this code, the button doesn't appear in the toolbar. Any pointers on what I'm doing wrong?
I see the same behavior. If widgets are added to a toolbar and the toolbar cannot display them all, it will display a chevron but the chevron will be greyed out. The chevron will not be greyed out for actions though and they will be displayed in a drop down fashion.
My example just using standard QActions:
from PySide import QtGui
app = QtGui.QApplication([])
tb = QtGui.QToolBar()
#tb.addWidget(QtGui.QPushButton('AAA'))
#tb.addWidget(QtGui.QPushButton('BBB'))
#tb.addWidget(QtGui.QPushButton('CCC')) # will not be shown in case the toolbar is too short, chevron will be greyed
tb.addAction(QtGui.QAction('AAA', tb))
tb.addAction(QtGui.QAction('BBB', tb))
tb.addAction(QtGui.QAction('CCC', tb))
tb.addAction(QtGui.QAction('DDD', tb))
tb.addAction(QtGui.QAction('EEE', tb))
tb.resize(50, 40)
tb.show()
app.exec_()
and if you want to connect the action to something useful the pattern is:
toolbar = QtGui.QToolBar()
action = QtGui.QAction(icon, 'Create new scenario', toolbar)
action.triggered.connect(..)
toolbar.addAction(action)
QWidgetAction seems to be slightly more complex than just QAction although it should work as well. If you do not need the added functionality, rather use QAction and a simple icon.
Related
I'm trying to understand why QLineEdit "editingFinished" signals are generated when other widgets are selected. In the example below the "on_lineedit" method is called when the combo box is selected. Why?
import sys
from PyQt5 import QtWidgets
class MyApp(QtWidgets.QDialog):
def __init__(self, *args):
super().__init__(*args)
# create combobox:
combobox = QtWidgets.QComboBox(self)
combobox.addItems(['Item 1', 'Item 2'])
# create line edit
lineedit = QtWidgets.QLineEdit(self)
lineedit.editingFinished.connect(self.on_lineedit)
# layout:
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget( combobox )
vbox.addWidget( lineedit )
self.setLayout(vbox)
def on_lineedit(self):
print('on_lineedit')
app = QtWidgets.QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
I know that this issue can be avoided by connecting the "textChanged" signal instead of the "editingFinished" signal like this:
lineedit.textChanged.connect(self.on_lineedit)
and I've seen similar issues raised elsewhere (links below) but I still don't understand why the "editingFinished" signal is generated when the combobox is selected.
Qt qspinbox editingFinished signal on value changed
Suppress QLineEdit editingFinished signal when certain button is clicked
From http://doc.qt.io/archives/qt-4.8/qlineedit.html#editingFinished
This signal is emitted when the Return or Enter key is pressed or the line edit loses focus.
The signal is emitted because it is designed to be. The other widget your click on is not really relevant here, what is relevant is that it the line edit loses focus and it is that which causes the signal to be emitted. Clicking on another widget is just one of many ways your line edit might lose focus.
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'm developing an application using the Qt Designer and PyQt4, I need to make several screens where each screen I capture user-specific data, for that I need to implement a next button and a back button similar to
where the current screen closes and the following opens when the user clicks next or if he clicks back, the screen closes and opens the previous screen, I made an example with only the next buttons and back to exemplify, if I was not clear:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Frm(QWidget):
def __init__(self, parent = None):
super(Frm, self).__init__(parent)
next = QPushButton('Next >', self)
back = QPushButton('< Back', self)
hbox = QHBoxLayout()
hbox.addWidget(back)
hbox.addWidget(next)
self.setLayout(hbox)
if __name__ == '__main__':
import sys
root = QApplication(sys.argv)
app = Frm(None)
app.show()
root.exec_()
In short: How do I implement a function that calls another screen and close the current at the click of a button?
First about a misconception: you do usually not create/show one screen (window) and close another, you usually only exchange the content of a wizard-like dialog window upon actions like pressing the buttons. The window is alive the whole time until the multiple page task is finished.
So I take it your question is really about:
How to exchange a widget in a layout?
Since you may still use PyQt4 which does not yet have QLayout.replaceWidget, it's best to just use methods removeWidget and addWidget of QLayout and since addWidget adds a widget to the end of the layout items list, I prefer a dedicated layout just for the interchangeable content of your wizard (see also: How to add an Item to the specific index in the layout).
Example code using PyQt5 but easily transferrable to PyQt4. Only the next button is implemented.
from PyQt5 import QtWidgets
class MyWizard(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# vertical layout, wraps content layout and buttons layout
vertical_layout = QtWidgets.QVBoxLayout()
self.setLayout(vertical_layout)
# content widget and layout
self.content_layout = QtWidgets.QVBoxLayout() # could be almost any layout actually
self.content = QtWidgets.QLabel('Page1') # customize with your content
self.content_layout.addWidget(self.content)
vertical_layout.addLayout(self.content_layout)
# back, forward buttons wraped in horizontal layout
button_layout = QtWidgets.QHBoxLayout()
button_layout.addStretch()
back_button = QtWidgets.QPushButton('Back')
back_button.clicked.connect(self.back_button_clicked)
button_layout.addWidget(back_button)
forward_button = QtWidgets.QPushButton('Forward')
forward_button.clicked.connect(self.forward_button_clicked)
button_layout.addWidget(forward_button)
vertical_layout.addLayout(button_layout)
def back_button_clicked(self):
"""
The back button is clicked.
"""
pass
def forward_button_clicked(self):
"""
The forward button is clicked.
"""
# remove old content
self.content_layout.removeWidget(self.content)
self.content.deleteLater()
# create new content
self.content = QtWidgets.QLabel('Another Page')
# add new content
self.content_layout.addWidget(self.content)
app = QtWidgets.QApplication([])
wizard = MyWizard()
wizard.setWindowTitle('MyWizard Example')
wizard.setFixedSize(600, 400)
wizard.show()
app.exec_()
And it looks like:
However, as already written in the comment by Marius, there is quite extensive support for such dialogs in Qt using QWizard. So I strongly recommend to use that instead. The example above is only to demonstrate the ability of inserting and removing widgets in layouts.
You should definitely use QWizard for such problems!
There is a QWizard class that allows you to create wizards in Qt and PyQt. It implements all the functionality you want, and lots more. All you do is design your pages by extending QWizardPage, and add them to the wizard. This is much simpler than doing the whole thing from scratch as you propose.
Old question but missing example with existing API
You don't need to create all structure of an Wizard by yourself. Qt (and PyQt) already provides a class called QWizard.
Basic example:
wizard = QtWidgets.QWizard()
page1 = QtWidgets.QWizardPage()
page1.setTitle('Page 1 is best!')
page1.setSubTitle('1111111111')
lineEdit = QtWidgets.QLineEdit()
hLayout1 = QtWidgets.QHBoxLayout(page1)
hLayout1.addWidget(lineEdit)
wizard.addPage(page1)
Complete example and some explanation:
https://www.youtube.com/watch?v=kTJ1QULxXjg
https://impatientprogrammer.net/2018/07/06/pyside-pyqt-qwizard-in-3-minutes/
This bad boy just does not want to change size to fill the dock widget area. I have tried all sorts of variations with QSizePolicy but nothing seems to work. The size of the QWebView always stays the same. Do I need to write a resize() callback?
Here's what I have right now:
self.helpwindow = QtGui.QDockWidget("Doc Browser")
self.helpwindow.setAllowedAreas(QtCore.Qt.RightDockWidgetArea)
self.helpwindow.setFeatures(QtGui.QDockWidget.DockWidgetClosable | QtGui.QDockWidget.DockWidgetFloatable)
helpAction = self.helpwindow.toggleViewAction()
helpAction.setText("&Help Browser")
helpAction.setShortcut(QtGui.QKeySequence("F1"))
helpMenu.addAction(helpAction)
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.helpwindow)
helpbrowser = QtWebKit.QWebView(self.helpwindow)
indexpath = resource_filename(__name__,"help/index.html")
url = QtCore.QUrl("file://" + indexpath)
helpbrowser.load(url)
helpbrowser.show()
helpbrowser.updateGeometry()
helpbrowser.update()
helpbrowser.setMinimumWidth(400)
helpbrowser.setMinimumHeight( self.helpwindow.height())
sizepolicy = helpbrowser.sizePolicy()
sizepolicy.setVerticalPolicy(QtGui.QSizePolicy.MinimumExpanding)
sizepolicy.setHorizontalPolicy(QtGui.QSizePolicy.MinimumExpanding)
I'm using PyQT4 but C++ solutions gladly accepted.
Setting only paren't wont make QDockWidget manage child widget. You need to call:
helpwindow.setWidget(helpbrowser)
I have very simple window where I have 2 buttons - one for cancel, one for apply. How to set the button for apply as default one? (When I press enter, "apply" button is pressed)
However, I want to set focus to the first input widget (I can't use grab_focus() on the button)
Any suggestions?
Edit:
After wuub's answer it works visually good. However, when I press the button in different widget, it doesn't run callback of the default button.
Example code:
import os, sys, pygtk, gtk
def run(button, window):
dialog = gtk.MessageDialog(window, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "OK")
dialog.run()
dialog.destroy()
window = gtk.Window()
window.connect("destroy", gtk.main_quit)
vbox = gtk.VBox(spacing = 10)
entry = gtk.Entry()
vbox.pack_start(entry)
button = gtk.Button(stock = gtk.STOCK_SAVE)
button.connect("clicked", run, window)
button.set_flags(gtk.CAN_DEFAULT)
window.set_default(button)
vbox.pack_start(button)
window.add(vbox)
window.show_all()
gtk.main()
EDIT2: Every input which can activate default widget must be ran
widget.set_activates_default(True)
http://www.pygtk.org/docs/pygtk/class-gtkdialog.html#method-gtkdialog--set-default-response
http://www.pygtk.org/docs/pygtk/class-gtkwindow.html#method-gtkwindow--set-default