How to Squeeze the Column to minimum in QTableview in PyQt5? - python

If I have Table Like as mention Below,
And I want to do as below to squeeze all columns bear to minimum scrollbar size or without scrollbar,
In PyQt5 in QTableview how can I do align any content to center in cell and want to minimum scrollbar and if possible without scrollbar then also it is well good.
as like below image text are not align and I wish to do squeeze all columns as per image 1 and align text to center in PyQt5 in Python.

The trick is to use the Stretch resize mode of the horizontal header, which ensures that all columns fit the available size of the view. The only problem comes from the minimumSectionSize(), which by default is a value dependent on the font and the margin between the sort indicator and the text of each header section, so, even using Stretch, the columns wouldn't resize below that width.
By setting the minimum size to 0 we can prevent that behavior. Keep in mind, though, that even with not-so-narrow columns (under 16-18 pixels wide) you will not be able to see the header text at all, no matter if there could be enough space for the text to be shown: some space is always reserved to the header section separators and their margin.
About the text alignment, the standard approach is to use setTextAlignment on each item. If you need to do that constantly, just use a subclass of QStandardItem that automatically sets its alignment after initialization.
from PyQt5 import QtCore, QtGui, QtWidgets
class FitTable(QtWidgets.QTableView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
self.horizontalHeader().setMinimumSectionSize(0)
def resizeEvent(self, event):
super().resizeEvent(event)
if not self.model() or not self.model().columnCount():
return
# the text can be completely hidden on very narrow columns if the
# elide mode is enabled; let's disable it for widths lower than
# the average width of 3 characters
colSize = self.viewport().width() // self.model().columnCount()
if colSize < self.fontMetrics().averageCharWidth() * 3:
self.setTextElideMode(QtCore.Qt.ElideNone)
else:
self.setTextElideMode(QtCore.Qt.ElideRight)
class CenteredItem(QtGui.QStandardItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTextAlignment(QtCore.Qt.AlignCenter)
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QGridLayout(self)
self.table = FitTable()
layout.addWidget(self.table)
model = QtGui.QStandardItemModel()
self.table.setModel(model)
for row in range(5):
rowItems = []
for column in range(30):
# usually the text alignment is manually applied like this:
# item = QtGui.QStandardItem(str(column + 1))
#
# item.setTextAlignment(QtCore.Qt.AlignCenter)
#
# for convenience, I use a subclass that automatically does that
item = CenteredItem(str(column + 1))
rowItems.append(item)
model.appendRow(rowItems)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Related

How to make QTableWidget fill all available window space and show vertical scroll bar if it's larger, but avoid blank white space if it's smaller?

I want my QTableWidget to:
Fill all available space inside the window
Show vertical scroll bar if it's longer than the provided space
Adapt to changing window geometry
Do not show blank white space underneath, if it's smaller than the provided space (without stretching bottom row).
So basically what I want to see:
Short table without white space
Long table fills no less no more than available and shows scroll bar
But I can't achieve these two states simultaneously.
What I get instead:
Ugly white space beneath the table
Table stretches the window itself
The difference in the two behaviors is in calculating table height manually. If I do so, a long table stretches the window, but otherwise it tries to fill all unused space with blank white.
Here is my minimal code:
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from PyQt6.QtGui import *
import os
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 200, 100)
# self.tableLabel = QLabel() # I thought it might help somehow
self.table = QTableWidget(4, 1)
self.setTable(4)
self.table.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.fewRowsBtn = QPushButton('Few rows')
self.manyRowsBtn = QPushButton('Many rows')
self.button = QPushButton('Button')
self.button.setFixedWidth(120)
self.fewRowsBtn.clicked.connect(lambda: self.setTable(4))
self.manyRowsBtn.clicked.connect(lambda: self.setTable(20))
self.map = QLabel()
canvas = QPixmap(320, 320)
canvas.fill()
self.map.setPixmap(canvas)
leftLayout = QVBoxLayout()
leftLayout.addWidget(self.fewRowsBtn)
leftLayout.addWidget(self.manyRowsBtn)
leftLayout.addWidget(self.map)
rightLayout = QVBoxLayout()
rightLayout.addWidget(self.table)
rightLayout.addWidget(self.button) #, alignment=Qt.AlignmentFlag.AlignBottom)
generalLayout = QHBoxLayout()
generalLayout.addLayout(leftLayout)
generalLayout.addLayout(rightLayout)
container = QWidget()
container.setLayout(generalLayout)
self.setCentralWidget(container)
def setTable(self, rows):
self.table.setRowCount(rows)
# self.table.setFixedHeight(self.tableHeight())
def tableWidth(self):
tableWidth = self.table.verticalHeader().width() + \
self.table.horizontalHeader().length() + \
self.table.frameWidth() * 2
app.processEvents() # Otherwise verticalScrollBar().isVisible()
app.processEvents() # lags one event
if self.table.verticalScrollBar().isVisible():
tableWidth += self.table.verticalScrollBar().width()
return tableWidth
def tableHeight(self):
tableHeight = self.table.verticalHeader().length() + \
self.table.horizontalHeader().height() + \
self.table.frameWidth() * 2
return tableHeight
app = QApplication(os.sys.argv)
mywindow = MainWindow()
mywindow.show()
app.exec()
I tried playing with different size policies, but all of them give the behavior mentioned above.
I had a vague idea about placing the table in a label, but I don't know how it could help.
Additionally, one other behavior I don't understand is why adding top or bottom alignment for the button under the table makes available space half the height of the window. As if because of that the QVBoxLayout starts distributing space evenly between it's two items.
I think getting the value of the available space in a given window geometry migth help me adjust table size manually. So at least an advise how to do that could help me.
I use Python 3.9.12 and PyQt 6.3.0

Is there a better way to create a file browser with PyQt6?

I'm creating a file browser with PyQt6. This is what I'm thinking of doing right now:
from PyQt6 import QtWidgets as qtw
from PyQt6 import QtGui as qtg
class FileBrowserWidget(qtw.QScrollArea):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.current_items = []
self.main_widget = qtw.QWidget()
self.main_widget.setLayout(qtw.QVBoxLayout())
self.setWidget(self.main_widget)
def add_file(self, thumbnail: qtg.QPixmap, description: str):
item = qtw.QWidget()
item.setLayout(qtw.QHBoxLayout())
file_thumbnail_label = qtw.QLabel()
file_thumbnail_label.setPixmap(thumbnail)
file_description_label = qtw.QLabel(description)
item.layout().addWidget(file_thumbnail_label)
item.layout().addWidget(file_description_label)
self.current_items.append(item)
Note that this is just a rough sketch of the widget. All the code does is display a (thumbnail, description) pair for files inside a directory in a scrollable window. I also plan to implement pagination for it, with at least 25 rows (files) per page.
My questions are:
Is this the way to do it or is there some other better way to go about creating a file browser?
How would I go about implementing pagination to the file browser?
EDIT:
My apologies, it's not just any file browser, it's an image file browser.
Example image of what I'm thinking of creating:
A basic possibility is to use a QListWidget, with some customized settings and precautions when adding items:
the iconSize() must be big enough to show the thumbnails;
a bigger font for the view
the sizeHint() of each item must be specified in order to always respect the same row height and provide text elision;
the image must be scaled and "enlarged" to the icon size in order to keep vertical alignment of the text, otherwise images that have different widths will show the text starting at different positions;
class ImageListView(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.view = QtWidgets.QListWidget()
self.view.setIconSize(QtCore.QSize(64, 64))
bigFont = self.font()
bigFont.setPointSize(24)
self.view.setFont(bigFont)
self.addButton = QtWidgets.QPushButton('Add image(s)')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.addButton)
self.addButton.clicked.connect(self.addImage)
def addImage(self):
paths, _ = QtWidgets.QFileDialog.getOpenFileNames(self,
'Select image(s)', '', 'Images (*.png *.jpg *.jpeg)')
size = self.view.iconSize()
for path in paths:
source = QtGui.QPixmap(path)
if source.isNull():
continue
if source.width() > size.width() or source.height() > size.height():
source = source.scaled(size, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation)
# create an empty squared image to keep vertical alignment
square = QtGui.QPixmap(size)
square.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter(square)
rect = source.rect()
rect.moveCenter(square.rect().center())
qp.drawPixmap(rect, source)
qp.end()
name = QtCore.QFileInfo(path).baseName()
item = QtWidgets.QListWidgetItem(name)
item.setIcon(QtGui.QIcon(square))
item.setToolTip(path)
item.setSizeHint(size)
self.view.addItem(item)
For more advanced customization, you can still use a QListWidget, but you also need to set a custom item delegate and override its paint() method.

Resizing table to contents in PyQt5 [duplicate]

I've googled around but I'm not able to find a solution to my problem.
I have a QTableWidget with 2 columns and what I'm trying to do is to make them visible to the whole widget without the horizontal scrollbar to appear.
With a picture it should be all clear:
I have used Qt Designer to create the UI and some code to fill all the widgets and other stuff.
So, first I resized th2 2 columns to the content with:
self.statTable.resizeColumnToContents(0)
self.statTable.resizeColumnToContents(1)
and it works, but then the Widget is not resizing to the 2 columns width.
This has a very easy solution in PyQt5. All you need to do is set the size adjust policy on the table when initialising the UI, and it will automatically resize to fit the contents. This can either be done via Qt Designer (in the QAbstractScrollArea section of the Property Editor), or programmatically, like this:
self.statTable.setSizeAdjustPolicy(
QtWidgets.QAbstractScrollArea.AdjustToContents)
You then just need to do:
self.statTable.resizeColumnsToContents()
whenever the table is re-populated.
For PyQt4, everything has to be calculated manually, and a few hacks are also required to get completely consistent results. The demo script below works okay for me, but YMMV:
import random
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.table = QtGui.QTableWidget(5, 2, self)
self.button = QtGui.QPushButton('Populate', self)
self.button.clicked.connect(self.populate)
layout = QtGui.QGridLayout(self)
layout.addWidget(self.table, 0, 0)
layout.addWidget(self.button, 1, 0)
layout.setColumnStretch(1, 1)
def populate(self):
words = 'Red Green Blue Yellow Black White Purple'.split()
length = random.randint(2, len(words))
self.table.setRowCount(random.randint(3, 30))
for column in range(self.table.columnCount()):
for row in range(self.table.rowCount()):
item = QtGui.QTableWidgetItem(' '.join(
random.sample(words, random.randint(1, length))))
self.table.setItem(row, column, item)
self.table.setVisible(False)
self.table.verticalScrollBar().setValue(0)
self.table.resizeColumnsToContents()
self.table.setVisible(True)
self.setTableWidth()
def setTableWidth(self):
width = self.table.verticalHeader().width()
width += self.table.horizontalHeader().length()
if self.table.verticalScrollBar().isVisible():
width += self.table.verticalScrollBar().width()
width += self.table.frameWidth() * 2
self.table.setFixedWidth(width)
def resizeEvent(self, event):
self.setTableWidth()
super(Window, self).resizeEvent(event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(700, 150, 800, 400)
window.show()
sys.exit(app.exec_())
For the autoadjust settings on the table widget in Qt Designer, you canlook in the object inspector for the table widget you can drill down to it as shown below.
(PyQt5)
The issue for me is that my cells in the right-most column are multi-line (QPlainTextEdit), and I wanted word-wrapping... but I also wanted this right-most column to extend to fill the parent container.
It seems you can do everything you need in PyQt5 designer, in the Property editor for your QTableView:
in the "QTableView" section check "wordWrap"
in the "QAbstractScroll" section check "AdjustToContents" (as mentioned by Crap Phone)
in the "Header" section check "horizontalHeaderStretchLastSection"
This then generates the following sort of code:
self.history_table_view.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContentsOnFirstShow)
self.history_table_view.horizontalHeader().setStretchLastSection(True )
"word wrap = True" appears to be the default setting, so nothing is shown, but it would be this:
self.history_table_view.setWordWrap(True)
Use your_tablewidget.resizeColumnsToContents() every single time after you call your_tablewidget.setItem(). You don't need any other setting.
you know just try, tableWidget.resize(1200, 600) *you can change resolution but it is your answer...

How to set last column header to a fixed size and maintain it

A code below create QTreeWidget with 7 (actually 8) columns.
I want the last column #7 to be used as a spacer. So when a dialog is resized there is always a padding (a distance) between column #6 and right edge of the TreeWidget.
As it acts by default the last column with its header keeps resizing to infinity. Instead I would like the last column and its header to maintain its fixed size and column 6 would resize instead. It does look pretty ugly with the last column header ending abruptly at the right edge of the QTreewidget. If keeping the last header/column at fixed size is not an option I would like to know if there are any other ways to achieve the same: to keep last column with its header padded from the right edge of the Tree List widget.
Here is a default look:
And here is Photoshoped version of what I am looking for:
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Tree(QtGui.QTreeWidget):
def __init__(self, *args, **kwargs):
super(Tree, self).__init__()
names=['Zero','One','Two','Three','Four','Five','Six','Seven']
self.setColumnCount(len(names))
self.setHeaderLabels(names)
item=QtGui.QTreeWidgetItem(names)
self.addTopLevelItem(item)
self.setColumnWidth(7, 32)
self.resize(720,120)
self.show()
tree=Tree()
sys.exit(app.exec_())
OK, I have solution. First, disable stretch last Section and select resize column.
myQTreeWidget = QtGui.QTreeWidget()
.
.
.
myQHeaderView = myQTreeWidget.header()
myQHeaderView.setStretchLastSection(False)
myQHeaderView.setResizeMode(6, QtGui.QHeaderView.Stretch)
QHeaderView.setResizeMode method Reference : http://pyqt.sourceforge.net/Docs/PyQt4/qheaderview.html#setResizeMode
QHeaderView.setResizeMode enum Reference : http://pyqt.sourceforge.net/Docs/PyQt4/qheaderview.html#ResizeMode-enum
Implement your code;
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Tree(QtGui.QTreeWidget):
def __init__(self, *args, **kwargs):
super(Tree, self).__init__()
names=['Zero','One','Two','Three','Four','Five','Six','Seven']
self.setColumnCount(len(names))
self.setHeaderLabels(names)
item=QtGui.QTreeWidgetItem(names)
self.addTopLevelItem(item)
self.header().setStretchLastSection(False)
self.header().setResizeMode(6, QtGui.QHeaderView.Stretch)
self.resize(720,120)
self.show()
tree=Tree()
sys.exit(app.exec_())
Regards,

A QWidget like QTextEdit that wraps its height automatically to its contents?

I am creating a form with some QTextEdit widgets.
The default height of the QTextEdit exceeds a single line of text and as the contents' height exceeds the QTextEdit's height, it creates a scroll-bar to scroll the content.
I would like to override this behaviour to create a QTextEdit that would rather wrap its height to its contents. This means that the default height would be one line and that on wrapping or entering a new line, the QTextEdit would increase its height automatically. Whenever the contents height exceeds the QTextEdit's height, the latter should not create a scroll bar but simply increase in height.
How can I go about doing this? Thanks.
This is almost exactly like a question I answer the other day about making a QTextEdit adjust its height in reponse to content changes: PySide Qt: Auto vertical growth for TextEdit Widget
I am answering instead of marking a duplicate as I suspect its possible you want a variation on this. Let me know if you want me to expand this answer:
The other question had multiple parts. Here is the excerpt of the growing height widget:
class Window(QtGui.QDialog):
def __init__(self):
super(Window, self).__init__()
self.resize(600,400)
self.mainLayout = QtGui.QVBoxLayout(self)
self.mainLayout.setMargin(10)
self.scroll = QtGui.QScrollArea()
self.scroll.setWidgetResizable(True)
self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.mainLayout.addWidget(self.scroll)
scrollContents = QtGui.QWidget()
self.scroll.setWidget(scrollContents)
self.textLayout = QtGui.QVBoxLayout(scrollContents)
self.textLayout.setMargin(10)
for _ in xrange(5):
text = GrowingTextEdit()
text.setMinimumHeight(50)
self.textLayout.addWidget(text)
class GrowingTextEdit(QtGui.QTextEdit):
def __init__(self, *args, **kwargs):
super(GrowingTextEdit, self).__init__(*args, **kwargs)
self.document().contentsChanged.connect(self.sizeChange)
self.heightMin = 0
self.heightMax = 65000
def sizeChange(self):
docHeight = self.document().size().height()
if self.heightMin <= docHeight <= self.heightMax:
self.setMinimumHeight(docHeight)
the following code sets a QTextEdit widget to the height of the content:
# using QVBoxLayout in this example
grid = QVBoxLayout()
text_edit = QTextEdit('Some content. I make this a little bit longer as I want to see the effect on a widget with more than one line.')
# read-only
text_edit.setReadOnly(True)
# no scroll bars in this example
text_edit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
text_edit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
text_edit.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
# you can set the width to a specific value
# text_edit.setFixedWidth(400)
# this is the trick, we nee to show the widget without making it visible.
# only then the document is created and the size calculated.
# Qt.WA_DontShowOnScreen = 103, PyQt does not have this mapping?!
text_edit.setAttribute(103)
text_edit.show()
# now that we have a document we can use it's size to set the QTextEdit's size
# also we add the margins
text_edit.setFixedHeight(text_edit.document().size().height() + text_edit.contentsMargins().top()*2)
# finally we add the QTextEdit to our layout
grid.addWidget(text_edit)
I hope this helps.

Categories