QToolBar size and alignment with hidden actions - python

I'm adding a toolbar to a widget, and dynamically hiding actions (they are also used in context menus). I'm encountering some problem with alignment/size. In the image below, Test 1 is "correct", and I want the other two to have only the "C" button, right-aligned (as in Test 3, but without the extra left space).
The code shows my attempt. Test 2 simply uses .setVisible(False) on the action A and B, while Test 3 does the same and adds a couple of empty QLabel on the left (if I add only one), the C does not end up right-aligned. It looks like QToolBar wants a minimum of 3 buttons or something.
Any idea how to fix this?
#!/usr/bin/env python
from PyQt4.QtGui import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
stylesheet = 'QToolButton{padding: 0; margin: 0} QToolBar{border: 1px solid black}'
layout = QVBoxLayout()
a = QAction('A', self)
b = QAction('B', self)
c = QAction('C', self)
toolbar = QToolBar()
toolbar.addAction(a)
toolbar.addAction(b)
toolbar.addAction(c)
toolbar.setStyleSheet(stylesheet)
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Test 1'))
hbox.addStretch(1)
hbox.addWidget(toolbar)
group = QGroupBox()
group.setLayout(hbox)
layout.addWidget(group)
a = QAction('A', self)
b = QAction('B', self)
c = QAction('C', self)
toolbar = QToolBar()
toolbar.addAction(a)
toolbar.addAction(b)
toolbar.addAction(c)
a.setVisible(False)
b.setVisible(False)
toolbar.setStyleSheet(stylesheet)
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Test 2'))
hbox.addStretch(1)
hbox.addWidget(toolbar)
group = QGroupBox()
group.setLayout(hbox)
layout.addWidget(group)
a = QAction('A', self)
b = QAction('B', self)
c = QAction('C', self)
toolbar = QToolBar()
toolbar.addWidget(QLabel(''))
toolbar.addWidget(QLabel(''))
toolbar.addAction(a)
toolbar.addAction(b)
toolbar.addAction(c)
a.setVisible(False)
b.setVisible(False)
toolbar.setStyleSheet(stylesheet)
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Test 3'))
hbox.addStretch(1)
hbox.addWidget(toolbar)
group = QGroupBox()
group.setLayout(hbox)
layout.addWidget(group)
layout.addStretch(1)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.show()
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())

To dynamically hide actions in a QToolbar, you cannot use setVisible(False) on the action since it will only "hide" the action from view but the button is inherently still there which is why you have the extra white space. Even though the action is hidden, there is still padding from a hidden object which gives you the undesired empty space. Similarly, when you add empty QLabels, this "hidden" widget also leaves white space since the physical area of the widget is still there.
Considering this, a solution is to remove the action from the QToolbar using removeAction(). This way, the actual object will be removed from the layout so there is no white space. Here's an example that shows how to add and remove actions in a QToolbar using handlers. You can check what objects are in the Qtoolbar with .actions() and depending on what action you want to remove, you can simply pass this to removeAction(). I'll leave it up to you to implement error handling when there are no more actions to add or no more actions to remove.
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
self.stylesheet = 'QToolButton{padding: 0; margin: 0} QToolBar{border: 1px solid black}'
self.main_layout = QGridLayout()
self.layout = QVBoxLayout()
self.a = QAction('A', self)
self.b = QAction('B', self)
self.c = QAction('C', self)
self.action_table = {1: self.a,
2: self.b,
3: self.c
}
self.test1_toolbar = QToolBar()
self.test1_toolbar.addAction(self.a)
self.test1_toolbar.addAction(self.b)
self.test1_toolbar.addAction(self.c)
self.test1_toolbar.setStyleSheet(self.stylesheet)
self.test1_hbox = QHBoxLayout()
self.test1_hbox.addWidget(QLabel('Test 1'))
self.test1_hbox.addStretch(1)
self.test1_hbox.addWidget(self.test1_toolbar)
self.test1_group = QGroupBox()
self.test1_group.setLayout(self.test1_hbox)
self.layout.addWidget(self.test1_group)
self.test2_toolbar = QToolBar()
self.test2_toolbar.addAction(self.a)
self.test2_toolbar.addAction(self.b)
self.test2_toolbar.addAction(self.c)
self.test2_toolbar.setStyleSheet(self.stylesheet)
self.test2_add_button = QPushButton('Add')
self.test2_add_button.clicked.connect(self.test2_add_action)
self.test2_remove_button = QPushButton('Remove')
self.test2_remove_button.clicked.connect(self.test2_remove_action)
self.test2_hbox = QHBoxLayout()
self.test2_hbox.addWidget(QLabel('Test 2'))
self.test2_hbox.addStretch(1)
self.test2_hbox.addWidget(self.test2_toolbar)
self.test2_group = QGroupBox()
self.test2_group.setLayout(self.test2_hbox)
self.layout.addWidget(self.test2_group)
self.test3_toolbar = QToolBar()
self.test3_toolbar.addAction(self.a)
self.test3_toolbar.addAction(self.b)
self.test3_toolbar.addAction(self.c)
self.test3_toolbar.setStyleSheet(self.stylesheet)
self.test3_hbox = QHBoxLayout()
self.test3_hbox.addWidget(QLabel('Test 3'))
self.test3_hbox.addStretch(1)
self.test3_hbox.addWidget(self.test3_toolbar)
self.test3_group = QGroupBox()
self.test3_group.setLayout(self.test3_hbox)
self.layout.addWidget(self.test3_group)
self.layout.addStretch(1)
self.widget = QWidget()
self.main_layout.addLayout(self.layout,0,0,1,1)
self.main_layout.addWidget(self.test2_add_button,0,1,1,1)
self.main_layout.addWidget(self.test2_remove_button,0,2,1,1)
self.widget.setLayout(self.main_layout)
self.setCentralWidget(self.widget)
self.show()
def test2_add_action(self):
objects = len(self.test2_toolbar.actions())
self.test2_toolbar.addAction(self.action_table[objects + 1])
def test2_remove_action(self):
objects = len(self.test2_toolbar.actions())
self.test2_toolbar.removeAction(self.action_table[objects])
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())

Related

PyQt5 Example of Using `QSizePolicy` to Shrink a Widget

I am trying to stop a widget from expanding by setting its size policy but things are not working.
The following code runs:
from PyQt5.QtWidgets import*
import pyqtgraph as pg
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.layout = QHBoxLayout()
self.left_widget = pg.GraphicsLayoutWidget()
self.layout.addWidget(self.left_widget)
self.right_widget = RightWidget()
#self.right_widget.groupbox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.layout.addWidget(self.right_widget)
self.widget = QWidget()
self.widget.setLayout(self.layout)
self.setCentralWidget(self.widget)
class RightWidget(QWidget):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.groupbox = QGroupBox()
self.groupbox.setTitle("some title")
#self.groupbox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.groupboxlayout = QVBoxLayout()
self.button1 = QPushButton ("1")
self.groupboxlayout.addWidget(self.button1)
self.button2 = QPushButton ("2")
self.groupboxlayout.addWidget(self.button2)
self.button3 = QPushButton ("3")
self.groupboxlayout.addWidget(self.button3)
self.groupbox.setLayout(self.groupboxlayout)
self.layout.addWidget(self.groupbox)
self.button4 = QPushButton ("4")
self.layout.addWidget(self.button4)
self.button5 = QPushButton ("5")
self.layout.addWidget(self.button5)
self.setLayout(self.layout)
if __name__ == '__main__':
import sys
system_app = QApplication(sys.argv)
main = MainWindow()
main.show()
system_app.exec()
It gives:
The specific choice of widgets should not matter. The point is that, there is a large widget on the left and there are smaller widgets on the right that is not large enough to fill the column. I want to modify the above code to shrink the widgets on the right hand side. That is:
In words, I want the widgets to shrink to top and bottom of the page in a way taking minimum space (and hence leaving the middle empty).
I attempted to use QSizePolicy to achieve the desired result. The commented lines seem to have no impact on the page at all. I don't know what went wrong.

Programming a GUI with PyQt in Python3

I'm asked to create a GUI with PyQt without using any Qtdesigner for my assignment. But now I'm facing a problem. In this pic GUI screenshoot
as you can see, there is a spinbox "Anzahl der Schicht". What I want to do is, when the user sets the value for this spinbox, the area which below it will show the corresponding rows of the input(the combination of QLineEdit,QSlider Widget,QLineEdit and 2 QSpinboxes in a row).
For example, the pic that I uploaded, means the value of spinbox "Anzahl der Schicht" is 3, so there are 3 rows below it. If the value is 4, there should be 4 rows. There is no limited value for the spinbox. How can I make this kind of dynamic effect for the GUI?
Update on 05.07.2019
Thanks for all useful replies. The following code and pic GUI version2 is my current status. So far I can't connect the Qspinbox to the class Widget() for adding or deleting the rows. So I just use a button "add widget" to implement what I want.
class ExampleWidget(QtWidgets.QGroupBox):
def __init__(self, numAddWidget):
QtWidgets.QGroupBox.__init__(self)
self.numAddWidget = numAddWidget
self.initSubject()
self.organize()
self.setFlat(True)
self.setStyleSheet("border: 1px solid transparent")
def initSubject(self):
self.shiftname =QtWidgets.QLineEdit() # Eingabefeld init
self.shiftname.setText('0')
self.shiftpercent = QtWidgets.QSlider()
self.shiftpercent.setOrientation(QtCore.Qt.Horizontal)
self.carnum =QtWidgets.QLineEdit() # Eingabefeld init
self.carnum.setText('0')
self.start = QtWidgets.QTimeEdit()
self.start.setDisplayFormat("HH:mm")
self.end = QtWidgets.QTimeEdit()
self.end.setDisplayFormat("HH:mm")
def organize(self):
grid = QtWidgets.QGridLayout(self)
self.setLayout(grid)
grid.addWidget(self.shiftname, 0,0)
grid.addWidget(self.shiftpercent, 0,1)
grid.addWidget(self.carnum, 0,2)
grid.addWidget(self.start, 0,3)
grid.addWidget(self.end, 0,4)
class Widget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.numAddWidget = 1
self.initUi()
def initUi(self):
self.layoutV = QtWidgets.QVBoxLayout(self)
self.area = QtWidgets.QScrollArea(self)
self.area.setWidgetResizable(True)
self.scrollAreaWidgetContents = QtWidgets.QWidget()
self.layoutH = QtWidgets.QHBoxLayout(self.scrollAreaWidgetContents)
self.gridLayout = QtWidgets.QGridLayout()
self.layoutH.addLayout(self.gridLayout)
self.area.setWidget(self.scrollAreaWidgetContents)
self.add_button = QtWidgets.QPushButton("Add Widget")
self.layoutV.addWidget(self.add_button)
self.layoutV.addWidget(self.area)
self.add_button.clicked.connect(self.addWidget)
self.widget = ExampleWidget(self.numAddWidget)
self.gridLayout.addWidget(self.widget)
def addWidget(self):
self.numAddWidget += 1
self.widget = ExampleWidget(self.numAddWidget)
self.gridLayout.addWidget(self.widget)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Here is a way. In this example the widgets (QLineEdits in this case) are stored in a list, Widget.items. When the value of the spin box changes extra widgets are appended to this list and added to the vertical layout if necessary. The widgets are then shown or hidden depending whether the current number of visible widgets is greater or smaller than the value of the spin box.
from PyQt5 import QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__()
self.resize(500,500)
self.items = []
self.item_count = 0
self.item_factory = QtWidgets.QLineEdit
group_box = QtWidgets.QGroupBox()
self.item_layout = QtWidgets.QVBoxLayout(group_box)
self.item_layout.addStretch(2)
self.spin_box = QtWidgets.QSpinBox(self)
self.spin_box.valueChanged.connect(self.set_item_count)
h_layout = QtWidgets.QHBoxLayout(self)
h_layout.addWidget(group_box, 2)
h_layout.addWidget(self.spin_box, 0)
def set_item_count(self, new_count:int):
n_items = len(self.items)
for ii in range(n_items, new_count):
item = self.item_factory(self)
self.items.append(item)
self.item_layout.insertWidget(n_items, item)
for ii in range(self.item_count, new_count):
self.item_layout.itemAt(ii).widget().show()
for ii in range(new_count, self.item_count):
self.item_layout.itemAt(ii).widget().hide()
self.item_count = new_count
if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = Widget()
window.show()
app.exec()

Adding QScrollArea to QTabWidget

I'm trying to add a scroll area to a QTabWideget.
At the moment I've set it up with two different tabs and the scrollArea is added to the second tab.
When I run my program, items are added to the scrollArea and the scroll bar is visible(policy set to always show), but it's greyed out.
Code:
class MyTableWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
# Initialize tab screen
self.tabs = QTabWidget()
self.tab1 = QWidget()
self.tab2 = QScrollArea()
self.tabs.setMaximumWidth(300)
self.tabs.setMaximumHeight(100)
# Add tabs
self.tabs.addTab(self.tab1,"Tab 1")
self.tabs.addTab(self.tab2,"Tab 2")
# Create first tab
# ...
# Create second tab
self.tab2.layout = QFormLayout(self)
self.tab2.setWidgetResizable(True)
self.tab2.setVerticalScrollBar(QScrollBar())
self.tab2.setVerticalScrollBarPolicy(2)
self.tab2.setFixedSize(100, 70)
self.t1 = QLabel('Test1', self)
self.t2 = QLabel('Test2', self)
self.t3 = QLabel('Test3', self)
self.t4 = QLabel('Test4', self)
self.t5 = QLabel('Test5', self)
self.t6 = QLabel('Test6', self)
self.tab2.layout.addRow(self.t1)
self.tab2.layout.addRow(self.t2)
self.tab2.layout.addRow(self.t3)
self.tab2.layout.addRow(self.t4)
self.tab2.layout.addRow(self.t5)
self.tab2.layout.addRow(self.t6)
self.tab2.setLayout(self.tab2.layout)
# Add tabs to widget
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
Code above turns out like this:
All squished together. I would like to be able to scroll and add more data without squishing whats there already.
Also, can I make the scroll area have the same background as seen in the picture below?
You do not have to replace the QScrollArea layout but add a new widget that has the QFormLayout as shown below.
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, \
QTabWidget, QScrollArea, QFormLayout, QLabel
class MyTableWidget(QWidget):
def __init__(self, parent=None):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
self.tabs = QTabWidget()
self.tab1 = QWidget()
self.tab2 = QScrollArea()
self.tabs.addTab(self.tab1, 'Tab 1')
self.tabs.addTab(self.tab2, 'Tab 2')
content_widget = QWidget()
self.tab2.setWidget(content_widget)
flay = QFormLayout(content_widget)
self.tab2.setWidgetResizable(True)
self.t1 = QLabel('Test1')
self.t2 = QLabel('Test2')
self.t3 = QLabel('Test3')
self.t4 = QLabel('Test4')
self.t5 = QLabel('Test5')
self.t6 = QLabel('Test6')
flay.addRow(self.t1)
flay.addRow(self.t2)
flay.addRow(self.t3)
flay.addRow(self.t4)
flay.addRow(self.t5)
flay.addRow(self.t6)
self.layout.addWidget(self.tabs)
self.resize(300, 100)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = MyTableWidget()
w.show()
sys.exit(app.exec_())

Getting the tops of side-by-side widgets to align using PySide

In the code below, the top of the QTextEdit and QGraphicsView widgets are not aligned when using QHBoxLayout. However, if you comment out QTextEdit and uncomment the other QGraphicsView setup, the top of the widgets align perfectly. Here are my questions:
What causes this alignment issue to occur and how can it be fixed?
Are issues like this best avoided by using Qt Creator?
Is the whole QGraphicsView() --> QGraphicsScene() --> QWidget() necessary to place graphics next to other widgets?
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__()
# Create Widget1
widget1 = QTextEdit()
#widget1 = QWidget()
#view1 = QGraphicsView()
#scene1 = QGraphicsScene(0,0,200,500)
#view1.setScene(scene1)
#layout = QHBoxLayout()
#layout.addWidget(view1)
#widget1.setLayout(layout)
# Create Widget2
widget2 = QWidget()
view2 = QGraphicsView()
scene2 = QGraphicsScene(0,0,200,500)
view2.setScene(scene2)
layout = QHBoxLayout()
layout.addWidget(view2)
widget2.setLayout(layout)
# Layout of Side by Side windows
container = QWidget()
layout = QHBoxLayout()
layout.addWidget(widget1)
layout.addWidget(widget2)
container.setLayout(layout)
# Scroll Area Properties
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(False)
scroll.setWidget(container)
# Scroll Area Layer add
vLayout = QVBoxLayout(self)
vLayout.addWidget(scroll)
self.setLayout(vLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = Widget()
dialog.show()
app.exec_()
The layouts have a default margin. So if one widget is in a layout, and its neighbour is not, they will not be aligned. To remove the default margin, you can do this:
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
However, in your example, the container widget and layout for the QGraphicsView aren't doing anything useful. So you could remove those, and along with some other simplifications, arrive at this:
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__()
widget1 = QTextEdit()
widget2 = QGraphicsView()
widget2.setScene(QGraphicsScene(0, 0, 200, 500, widget2))
container = QWidget()
layout = QHBoxLayout(container)
layout.addWidget(widget1)
layout.addWidget(widget2)
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(False)
scroll.setWidget(container)
vLayout = QVBoxLayout(self)
vLayout.addWidget(scroll)
Using Qt Designer is certainly very useful when experimenting with the layouts of a complex application. However, the code it generates is usually quite verbose compared with what you can achieve when coding by hand. For long-term maintainability, though, using Qt Designer seems the best option.

Is there any way to show widget it two different tabs?

I have QTabWidget, and layout with some widgets and sub layouts that I want to show in this QTabWidget tabs. what I want to do is to add this layout to the first tab (as default), and if the user moves to the next tab, I want to show the exact same layout, and to add some widgets near it.
this is the layout that I am talking about:
self.right_tab_layout = QVBoxLayout()
self.right_tab_widget = QWidget()
self.right_tab_title_label = QLabel("Select full files path:")
self.simoderev_layout = QHBoxLayout()
self.simoderev_widget = QWidget()
self.simoderev_checkbox = QCheckBox("use simoderev as base: ")
self.simoderev_combobox = QComboBox()
self.paths_label = QLabel("paths:")
self.right_tab_widget.setLayout(self.right_tab_layout)
self.simoderev_widget.setLayout(self.simoderev_layout)
self.simoderev_widget.setMaximumWidth(250)
self.simoderev_layout.addWidget(self.simoderev_checkbox)
self.simoderev_layout.addWidget(self.simoderev_combobox)
self.simoderev_layout.setContentsMargins(0,0,0,0)
self.right_tab_layout.addWidget(self.right_tab_title_label)
self.right_tab_layout.addWidget(self.simoderev_widget)
self.right_tab_layout.addWidget(self.paths_label)
is there any way to do this ?
If you just want the widgets in the tabs to look the same, you should create a custom Widget class and put an instance of that class in each tab.
Your custom widget could look like:
class CustomWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(CustomWidget, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
self.title_label = QtGui.QLabel("Select full files path:")
self.simoderev_widget = QtGui.QWidget()
simoderev_layout = QtGui.QHBoxLayout(self.simoderev_widget)
self.simoderev_checkbox = QtGui.QCheckBox("use simoderev as base: ")
self.simoderev_combobox = QtGui.QComboBox()
self.simoderev_widget.setMaximumWidth(250)
simoderev_layout.addWidget(self.simoderev_checkbox)
simoderev_layout.addWidget(self.simoderev_combobox)
simoderev_layout.setContentsMargins(0,0,0,0)
self.paths_label = QtGui.QLabel("paths:")
layout.addWidget(self.title_label)
layout.addWidget(self.simoderev_widget)
layout.addWidget(self.paths_label)
If you want them to be the same, here's a hacky solution. You should connect the currentChanged signal of your tabwidget to a slot that will move your custom widget from one tab to the other.
class MyTabWidget(QtGui.QTabWidget):
def __init__(self, parent = None):
super(MyTabWidget, self).__init__(parent)
self.subwidget = CustomWidget(self)
self.left_tab_widget = QtGui.QWidget()
self.leftLayout = QtGui.QVBoxLayout(self.left_tab_widget)
self.leftLayout.addWidget(self.subwidget)
self.right_tab_widget = QtGui.QWidget(self)
self.rightLayout = QtGui.QVBoxLayout(self.right_tab_widget)
label = QtGui.QLabel("Some additional data", self.right_tab_widget)
self.rightLayout.addWidget(label)
self.addTab(self.left_tab_widget, "Left Tab")
self.addTab(self.right_tab_widget, "Right Tab")
self.currentChanged.connect(self.onCurrentChanged)
def onCurrentChanged(self, index):
if index == 0:
self.leftLayout.addWidget(self.subwidget)
else:
self.rightLayout.addWidget(self.subwidget)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
widget = MyTabWidget()
widget.show()
app.exec_()

Categories

Resources