PyQt5 Progress bar to fill the whole QGroupBox? - python

I am trying to get a progress bar to fill the whole width of a QGroupBox.
So far it looks like:
I am trying to get it to go all the way across. Here is the code:
def progress(self):
gBox = QGroupBox('Progress')
progress_bar = QProgressBar(gBox)
progress_bar.setRange(0, 1)
# progress_bar.setGeometry(30, 40, 200, 25)
hbox = QHBoxLayout()
hbox.addWidget(progress_bar)
hbox.addStretch(1)
gBox.setLayout(hbox)
return gBox
Do I need to stretch the QGroupBox or the QHBoxLayout?

According to the docs:
void QBoxLayout::addStretch(int stretch = 0)
Adds a stretchable space (a QSpacerItem) with zero minimum size and
stretch factor stretch to the end of this box layout.
That is, a spacer is added, that spacer is added to the end, so it pushes the widget to take the size of sizeHint() compressing it.
In your case you do not need it, so remove it.
def progress(self):
gBox = QGroupBox('Progress')
progress_bar = QProgressBar(gBox)
progress_bar.setRange(0, 1)
hbox = QHBoxLayout()
hbox.addWidget(progress_bar)
gBox.setLayout(hbox)
return gBox

Related

How to word wrap the header contents of QTableWidget in PyQt5 Python

I am working on PyQt5 where I have a QTableWidget. It has a header column which I want to word wrap. Below is how the table looks like:
As we can see that the header label like Maximum Variation Coefficient has 3 words, thus its taking too much column width. How can wrap the words in the header.
Below is the code:
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
# Main Window
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'PyQt5 - QTableWidget'
self.left = 0
self.top = 0
self.width = 300
self.height = 200
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.createTable()
self.layout = QVBoxLayout()
self.layout.addWidget(self.tableWidget)
self.setLayout(self.layout)
# Show window
self.show()
# Create table
def createTable(self):
self.tableWidget = QTableWidget()
# Row count
self.tableWidget.setRowCount(3)
# Column count
self.tableWidget.setColumnCount(2)
self.tableWidget.setHorizontalHeaderLabels(["Maximum Variation Coefficient", "Maximum Variation Coefficient"])
self.tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
self.tableWidget.setItem(0, 0, QTableWidgetItem("3.44"))
self.tableWidget.setItem(0, 1, QTableWidgetItem("5.3"))
self.tableWidget.setItem(1, 0, QTableWidgetItem("4.6"))
self.tableWidget.setItem(1, 1, QTableWidgetItem("1.2"))
self.tableWidget.setItem(2, 0, QTableWidgetItem("2.2"))
self.tableWidget.setItem(2, 1, QTableWidgetItem("4.4"))
# Table will fit the screen horizontally
self.tableWidget.horizontalHeader().setStretchLastSection(True)
self.tableWidget.horizontalHeader().setSectionResizeMode(
QHeaderView.Stretch)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I tried adding this self.tableWidget.setWordWrap(True) but this doesnt make any change. Can anyone give some good solution. Please help. Thanks
EDIT:
Also tried this :
self.tableWidget.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignHCenter | Qt.Alignment(QtCore.Qt.TextWordWrap))
But it didnt worked
In order to achieve what you need, you must set your own header and proceed with the following two assumptions:
the header must provide the correct size hint height according to the section contents in case the width of the column is not sufficient;
the text alignment must include the QtCore.Qt.TextWordWrap flag, so that the painter knows that it can wrap text;
Do note that, while the second aspect might be enough in some situations (as headers are normally tall enough to fit at least two lines), the first point is mandatory as the text might require more vertical space, otherwise some text would be cut out.
The first point requires to subclass QHeaderView and reimplement sectionSizeFromContents():
class WrapHeader(QtWidgets.QHeaderView):
def sectionSizeFromContents(self, logicalIndex):
# get the size returned by the default implementation
size = super().sectionSizeFromContents(logicalIndex)
if self.model():
if size.width() > self.sectionSize(logicalIndex):
text = self.model().headerData(logicalIndex,
self.orientation(), QtCore.Qt.DisplayRole)
if not text:
return size
# in case the display role is numeric (for example, when header
# labels are not defined yet), convert it to a string;
text = str(text)
option = QtWidgets.QStyleOptionHeader()
self.initStyleOption(option)
alignment = self.model().headerData(logicalIndex,
self.orientation(), QtCore.Qt.TextAlignmentRole)
if alignment is None:
alignment = option.textAlignment
# get the default style margin for header text and create a
# possible rectangle using the current section size, then use
# QFontMetrics to get the required rectangle for the wrapped text
margin = self.style().pixelMetric(
QtWidgets.QStyle.PM_HeaderMargin, option, self)
maxWidth = self.sectionSize(logicalIndex) - margin * 2
rect = option.fontMetrics.boundingRect(
QtCore.QRect(0, 0, maxWidth, 10000),
alignment | QtCore.Qt.TextWordWrap,
text)
# add vertical margins to the resulting height
height = rect.height() + margin * 2
if height >= size.height():
# if the height is bigger than the one provided by the base
# implementation, return a new size based on the text rect
return QtCore.QSize(rect.width(), height)
return size
class App(QWidget):
# ...
def createTable(self):
self.tableWidget = QTableWidget()
self.tableWidget.setHorizontalHeader(
WrapHeader(QtCore.Qt.Horizontal, self.tableWidget))
# ...
Then, to set the word wrap flag, there are two options:
set the alignment flag on the underlying model with setHeaderData() for each existing column:
# ...
model = self.tableWidget.model()
default = self.tableWidget.horizontalHeader().defaultAlignment()
default |= QtCore.Qt.TextWordWrap
for col in range(self.tableWidget.columnCount()):
alignment = model.headerData(
col, QtCore.Qt.Horizontal, QtCore.Qt.TextAlignmentRole)
if alignment:
alignment |= QtCore.Qt.TextWordWrap
else:
alignment = default
model.setHeaderData(
col, QtCore.Qt.Horizontal, alignment, QtCore.Qt.TextAlignmentRole)
Use a QProxyStyle to override the painting of the header, by applying the flag on the option:
# ...
class ProxyStyle(QtWidgets.QProxyStyle):
def drawControl(self, control, option, painter, widget=None):
if control in (self.CE_Header, self.CE_HeaderLabel):
option.textAlignment |= QtCore.Qt.TextWordWrap
super().drawControl(control, option, painter, widget)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle(ProxyStyle())
ex = App()
sys.exit(app.exec_())
Finally, consider that:
using setSectionResizeMode with ResizeToContents or Stretch, along with setStretchLastSection, will always cause the table trying to use as much space as required by the headers upon showing the first time;
by default, QHeaderView sections are not clickable (which is a mandatory requirement for sorting) and the highlightSections property is also False; both QTableView and QTableWidget create their headers with those values as True, so when a new header is set you must explicitly change those aspects if sorting and highlighting are required:
self.tableWidget.setHorizontalHeader(
WrapHeader(QtCore.Qt.Horizontal, self.tableWidget))
self.tableWidget.horizontalHeader().setSectionsClickable(True)
self.tableWidget.horizontalHeader().setHighlightSections(True)
both sorting and section highlighting can create some issues, as the sort indicator requires further horizontal space and highlighted sections are normally shown with a bold font (but are shown normally while the mouse is pressed); all this might create some flickering and odd behavior; unfortunately, there's no obvious solution for these problems, but when using the QProxyStyle it's possible to avoid some flickering by overriding the font style:
def drawControl(self, control, option, painter, widget=None):
if control in (self.CE_Header, self.CE_HeaderLabel):
option.textAlignment |= QtCore.Qt.TextWordWrap
if option.state & self.State_Sunken:
option.state |= self.State_On
super().drawControl(control, option, painter, widget)

How to set selecting slider for a QTableWidget?

I'm looking for solution of merge discrete slider and QTableWidget (see attached screenshot). Slider is used as selecting pointer(instead of default selecting highlighter). How it can be implemented using Qt (PyQt)?
Small premise. Technically, according to StackOverflow standards, your question is not a very good one. I'll explain it at the end of this answer.
Getting what you are asking for is not easy, most importantly because sliders are not built for that purpose (and there are many UX reasons for which you should not do that, go to User Experience to ask about them).
The trick is to create a QSlider that has the table widget as a parent. Creating a widget with a parent ensures that the child widget will always be enclosed within the parent boundaries (this is only false for QMainWindow and QDialog descendants), as long as the widget is not added to the parent layout. This allows you to freely set its geometry (position and size).
In the following example I'm adding an internal QSlider, but the main issue about this widget is aligning it in such a way that its value positions are aligned with the table contents.
class GhostHeader(QtWidgets.QHeaderView):
'''
A "fake" vertical header that does not paint its sections
'''
def __init__(self, parent):
super().__init__(QtCore.Qt.Vertical, parent)
self.setSectionResizeMode(self.Fixed)
def paintEvent(self, event):
pass
class SliderTable(QtWidgets.QTableWidget):
def __init__(self, rows=0, columns=0, parent=None):
super().__init__(rows, columns, parent)
self.horizontalHeader().setStretchLastSection(True)
self.setHorizontalHeaderLabels(['Item table'])
self.setVerticalHeader(GhostHeader(self))
# create a slider that is a child of the table; there is no layout, but
# setting the table as its parent will cause it to be shown "within" it.
self.slider = QtWidgets.QSlider(QtCore.Qt.Vertical, self)
# by default, a slider has its maximum on the top, let's invert this
self.slider.setInvertedAppearance(True)
self.slider.setInvertedControls(True)
# show tick marks at each slider value, on both sides
self.slider.setTickInterval(1)
self.slider.setTickPosition(self.slider.TicksBothSides)
self.slider.setRange(0, max(0, self.rowCount() - 1))
# not necessary, but useful for wheel and click interaction
self.slider.setPageStep(1)
# disable focus on the slider
self.slider.setFocusPolicy(QtCore.Qt.NoFocus)
self.slider.valueChanged.connect(self.selectRowFromSlider)
self.slider.valueChanged.connect(self.updateSlider)
self.verticalScrollBar().valueChanged.connect(self.updateSlider)
self.model().rowsInserted.connect(self.modelChanged)
self.model().rowsRemoved.connect(self.modelChanged)
def selectRowFromSlider(self, row):
if self.currentIndex().isValid():
column = self.currentIndex().column()
else:
column = 0
self.setCurrentIndex(self.model().index(row, column))
def modelChanged(self):
self.slider.setMaximum(max(0, self.rowCount() - 1))
self.updateSlider()
def updateSlider(self):
slider = self.slider
option = QtWidgets.QStyleOptionSlider()
slider.initStyleOption(option)
style = slider.style()
# get the available extent of the slider
available = style.pixelMetric(style.PM_SliderSpaceAvailable, option, slider)
# compute the space between the top of the slider and the position of
# the minimum value (0)
deltaTop = (slider.height() - available) // 2
# do the same for the maximum
deltaBottom = slider.height() - available - deltaTop
# the vertical center of the first item
top = self.visualRect(self.model().index(0, 0)).center().y()
# the vertical center of the last
bottom = self.visualRect(self.model().index(self.model().rowCount() - 1, 0)).y()
# get the slider width and adjust the size of the "ghost" vertical header
width = self.slider.sizeHint().width()
left = self.frameWidth() + 1
self.verticalHeader().setFixedWidth(width // 2 + left)
viewGeo = self.viewport().geometry()
headerHeight = viewGeo.top()
# create the rectangle for the slider geometry
rect = QtCore.QRect(0, headerHeight + top, width, headerHeight + bottom - top // 2)
# adjust to the values computed above
rect.adjust(0, -deltaTop + 1, 0, -deltaBottom)
# translate it so that its center will be between the vertical header and
# the table contents
rect.translate(left, 0)
self.slider.setGeometry(rect)
# set the mask, in case the item view is scrolled, so that the top of the
# slider won't be shown in the horizontal header
visible = self.rect().adjusted(0, viewGeo.top(), 0, 0)
mask = QtGui.QPainterPath()
topLeft = slider.mapFromParent(visible.topLeft())
bottomRight = slider.mapFromParent(visible.bottomRight() + QtCore.QPoint(1, 1))
mask.addRect(QtCore.QRectF(topLeft, bottomRight))
self.slider.setMask(QtGui.QRegion(mask.toFillPolygon(QtGui.QTransform()).toPolygon()))
def currentChanged(self, current, previous):
super().currentChanged(current, previous)
if current.isValid():
self.slider.setValue(current.row())
def resizeEvent(self, event):
# whenever the table is resized (even when first shown) call the base
# implementation (which is required for correct drawing of items and
# selections), then update the slider
super().resizeEvent(event)
self.updateSlider()
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
self.table = SliderTable()
self.table.setRowCount(4)
self.table.setColumnCount(1)
self.table.setHorizontalHeaderLabels(['Item table'])
layout.addWidget(self.table)
for row in range(self.table.rowCount()):
item = QtWidgets.QTableWidgetItem('item {}'.format(row + 1))
item.setTextAlignment(QtCore.Qt.AlignCenter)
self.table.setItem(row, 0, item)
Why this question is not that good?
Well, it's dangerously close to the "I don't know how to do this, can you do it for me?" limit. You should provide any minimal, reproducible example (it doesn't matter if it doesn't work, you should do some research and show your efforts), and the question is a bit vague, even after some clarifications in the comment sections.
Long story short: if it's too hard and you can't get it working, you probably still need some studying and exercise before you can achieve it. Be patient, study the documentation: luckily, Qt docs are usually well written, so it's just a matter of time.

How to position labels with move command based on their center instead of left corner in PyQt5

Hi I have several QLabels in a Qwidget. I am given a design that I need to create in Qt and it has only few labels actually. But one of labels' text my change. So text length is also changable. I used move() command it takes left corner point as reference point. I need to take center point of Label as reference point I guess.
class App(QWidget):
def __init__(self):
super().__init__()
self.left = 0
self.top = 0
self.width = 480
self.height = 800
self.initUI()
def initUI(self):
#self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
# Create widget
main_page = QLabel(self)
main_pixmap = QPixmap('main_page')
main_page.setPixmap(main_pixmap)
#logo
logo = QLabel(self)
logo_pixmap = QPixmap('logo')
logo.setPixmap(logo_pixmap)
logo.move(159,63)
#Date
today = format_date(datetime.datetime.now(),"dd MMMM", locale = 'tr').upper()
day = format_date(datetime.datetime.now(), "EEEE", locale = 'tr').upper()
date = QLabel(self)
date.setText(day + " " + today )
date.setFont(QFont("Lato", 24))
date.setStyleSheet('color:white')
self.move_to_center()
#Clock
clock = QLabel(self)
clock.setText(strftime('%H:%M'))
clock.setFont(QFont("Lato", 90))
clock.setStyleSheet('color:white')
clock.move(71,222)
How can I dynamicly put a label horizantally middle of a Qwidget?
Edit:
When I used layouts, labels lines up one after another as below
The main problem with centering widgets is that they can alter their size according to their contents.
The simpler solution for your case is to create a label that has a fixed width and has its text center aligned:
clock = QLabel(self)
clock.setText(strftime('%H:%M'))
clock.setFixedWidth(self.width())
clock.move(0, 222)
clock.setAlignment(Qt.AlignCenter)
While this is fine, there is a couple of problems:
if the label has more than one line and you don't want it to span over the whole width, the alignment will always be centered (which can be ugly);
if the original text is on one line and the new text has more, it won't be updated properly (unless you call label.adjustSize() after every text change)
Another solution is to create a subclass of QLabel that automatically repositions itself as soon as it's shown or the text is changed:
class CenterLabel(QLabel):
vPos = 0
def setText(self, text):
super(CenterLabel, self).setText(text)
if self.parent():
self.center()
def setVPos(self, y):
# set a vertical reference point
self.vPos = y
if self.parent():
self.center()
def center(self):
# since there's no layout, adjustSize() allows us to update the
# sizeHint based on the text contents; we cannot use the standard
# size() as it's unreliable when there's no layout
self.adjustSize()
x = (self.parent().width() - self.sizeHint().width()) / 2
self.move(x, self.vPos)
That said, I still think that using a layout is a better and simpler solution. You just have to create the "background" pixmap with the main widget as a parent and without adding it to the layout, then set the layout and add everything else using layout.addSpacing for the vertical spacings between all widgets.
The only issue here is that if a label text changes its line count, all subsequent widgets will be moved accordingly. If that's the case, just set a fixed height for the widget which will be equal to the distance between the top of the widget and the top of the next, then add the widget to the layout ensuring that it is horizontally centered and top aligned.
def initUI(self):
#self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self._width, self._height)
self.background = QLabel(self)
self.background.setPixmap(QPixmap('background.png'))
layout = QVBoxLayout()
self.setLayout(layout)
# set spacing between items to 0 to ensure that there are no added margins
layout.setSpacing(0)
# add a vertical spacing for the top margin;
# I'm just using random values
layout.addSpacing(20)
main_page = QLabel(self)
main_pixmap = QPixmap('main_page')
main_page.setPixmap(main_pixmap)
# add the widget ensuring that it's horizontally centered
layout.addWidget(main_page, alignment=Qt.AlignHCenter)
#logo
logo = QLabel(self)
logo_pixmap = QPixmap('logo')
logo.setPixmap(logo_pixmap)
layout.addWidget(logo, alignment=Qt.AlignHCenter)
layout.addSpacing(50)
#Date
today = format_date(datetime.datetime.now(),"dd MMMM", locale = 'tr').upper()
day = format_date(datetime.datetime.now(), "EEEE", locale = 'tr').upper()
date = QLabel(self)
date.setText(day + " " + today )
date.setFont(QFont("Lato", 24))
date.setStyleSheet('color:white')
# set a fixed height equal to the vertical position of the next label
date.setFixedHeight(100)
# ensure that the label is always on top of its layout "slot"
layout.addWidget(date, alignment=Qt.AlignHCenter|Qt.AlignTop)
#Clock
clock = QLabel(self)
clock.setText(strftime('%H:%M'))
clock.setFont(QFont("Lato", 90))
clock.setStyleSheet('color:white')
layout.addWidget(clock, alignment=Qt.AlignHCenter|Qt.AlignTop)
# add a bottom "stretch" to avoid vertical expanding of widgets
layout.addStretch(1000)

Empty spaces between Widgets even with setSpacing(0)

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
data = {'dia': 23, 'mes': 8, 'ano': 2017}
class FormConfigBkpBD(QWidget):
senha = None
pathLine = None
def __init__(self, parent=None,instancia=None):
super(FormConfigBkpBD, self).__init__(parent)
mainLayout = QVBoxLayout()
mainLayout.setSpacing(0)
mainLayout.setContentsMargins(0, 0, 0, 0)
self.senha = QLineEdit()
self.senha.setEchoMode(QLineEdit.Password)
self.senha.setFixedSize(385, 25)
self.pathLine = QLineEdit()
self.pathLine.setFixedSize(355,25)
self.senha.setContentsMargins(0,0,0,0)
self.pathLine.setContentsMargins(0,0,0,0)
mainLayout.addWidget(self.pathLine,0,Qt.AlignTop)
mainLayout.addWidget(self.senha,0,Qt.AlignTop)
self.setWindowTitle("ConfiguraĆ§Ć£o de Backup - Banco de Dados")
self.setFixedSize(460,640)
self.setLayout(mainLayout)
def main():
app = QApplication(sys.argv)
bd = FormConfigBkpBD()
bd.show()
app.exec()
if __name__ == "__main__" :
main()
The above code shows only two widgets (QLineEdit), the original window has much more widgets, this is only for post here.
Why, even with setSpacing(0) and setContentsMargins(0,0,0,0) there's a space between widgets?
It's because of the combination between the fixed size of your window and the layout you are using. The vertical layout tries to distribute evenly (unless told otherwise) all of its children vertically and fill the whole window.
Here you have two widgets so the first one goes in the first cell and the second one goes in the second cell. Since you haven't set any alignment the default behaviour is present that is top left corner of each cell is where the widget is positioned. The rest is just filling the free space of the parent window which the layout is part of.
If you want not space between the widgets
either remove the fixed size of the window
or put more widgets in the layout until it's "full".
The first option might not be what you want to do (otherwise you would have probably not set it to fixed size in the first place). The second can be achieved by simply using a QSpacerItem (vertical one) that will take care of the empty space for you and push the widgets to the top where you want these to be:
mainLayout.addStretch(1)
You can also add the spacer manually if you need more control over its behaviour:
self.spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) # or Fixed
mainLayout.addItem(self.spacer)

Positioning widgets to QBoxLayout

I'm using PySide to build a GUI
I have a QBoxLayout that I have add some Widgets to, the problem is I want control their positions which I was not able to do, I tried what have been provided in the documentation page which is
addWidget( widg, int streatch, int alignment)
but it is not giving me what I want so the final look that I want is something like this
------------------------------------------------------------ ^^ **
&&&&&&
if the line ____ represents the whole window/ layout
I would like the widget which I named wid in my code to be like the dashed line --------
and the sliders to be in the same place as ^^
and finally the button to be in &&&&&
My second question is I want to add some labels to the sliders and to the como-box and I would like to determine the position how can I do that
here is my code
self.wid = GLWidget()
mainLayout = QtGui.QHBoxLayout()
mainLayout.addWidget(self.wid)
self.xSlider = self.createSlider()
self.ySlider = self.createSlider()
mainLayout.addWidget(self.xSlider)
mainLayout.addWidget(self.ySlider)
self.btn = QtGui.QPushButton('OK')
self.btn.resize(self.btn.sizeHint())
mainLayout.addWidget(self.btn)
self.combo = QtGui.QComboBox()
mainLayout.addWidget(self.combo)
self.setLayout(mainLayout)
self.setWindowTitle(self.tr("Hello GL"))
self.setGeometry(350, 350, 1000, 1000)
You could use a QGridLayout instead of QHBoxLayout and use QGridLayout's setColumnMinimumWidth method to dictate your required widget width.Like that:
mainLayout = QtGui.QGridLayout()
mainLayout.setColumnMinimumWidth(0, 100) #set column 0 width to 100 pixels
button = QtGui.QPushButton('OK')
mainLayout.addWidget(button, 0, 0) #adds the button to the widened column
and then continue to add the rest of the widgets.

Categories

Resources