PyQt, Don't change qtablewidgetitem font during editing - python

I tried change QTableWidgetItem font, and complete that.
But, In Editmode, not change font.
(PS. I shouldn't give same style, so i don't use qss style sheet)
Below code, If Input F1 key, Size up current cell font size.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class MainWindow(QMainWindow):
def __init__(self, **kwargs):
super().__init__()
self.init_main()
self.init_table()
self.resize(500,500)
self.show()
def init_main(self):
self.main_widget = QWidget()
self.main_layout = QHBoxLayout()
self.main_widget.setLayout(self.main_layout)
self.setCentralWidget(self.main_widget)
def init_table(self):
self.table = QTableWidget(5, 5, self)
self.table.resize(500, 500)
# init QTableWidgetItem in QTableWidget (5 x 5),
[[self.table.setItem(row, col,QTableWidgetItem("text")) for row in range(5)] for col in range(5)]
def keyPressEvent(self, e):
# If input F1, resize font-size in current item.
if e.key() == Qt.Key_F1:
cur = self.table.currentItem()
font = cur.font()
font.setPointSize(30)
cur.setFont(font)
if __name__ == "__main__":
app = QCoreApplication.instance()
if app is None:
app = QApplication(sys.argv)
window = MainWindow()
app.exec_()

Setting the font on an item just changes the font for that item, not for its editor.
What you need to do is to create an item delegate (which is an object that is responsible of showing items and provide interaction with the underlying model, including the appropriate editor).
Since setting the font on a QTableWidgetItem equals to set the Qt.FontRole of the index, you can easily access that from the createEditor() function, call the base implementation to get the editor that is going to be returned, and apply the font to it if a font is set.
class FontDelegate(QStyledItemDelegate):
def createEditor(self, parent, opt, index):
editor = super().createEditor(parent, opt, index)
font = index.data(Qt.FontRole)
if font is not None:
editor.setFont(font)
return editor
class MainWindow(QMainWindow):
# ...
def init_table(self):
self.table = QTableWidget(5, 5)
self.main_layout.addWidget(self.table)
[[self.table.setItem(row, col, QTableWidgetItem("text")) for row in range(5)] for col in range(5)]
self.table.setItemDelegate(FontDelegate(self.table))
# ...
Unrelated note: the table should be added to the layout (as I did in the above code), and not just created as a child of the main window.

Related

Animation to hide rows in a

I'm trying to create an animation to hide unselected rows in a QTableWidget.
I want the height of the rows to decrease until it is 0, for a smoother transition.
I found the following in C++ that does exactly what I'm trying to achieve, but I cannot get the same result in Python: https://forum.qt.io/topic/101437/animate-qtablewidget/4
I noticed they use Q_PROPERTY, but I honestly do not understand how it works...
Here what I came up for now (the animation is not working):
`
import sys
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from PySide2.QtCore import *
class Example(QWidget):
def __init__(self):
super().__init__()
self.resize(600,200)
self.initUI()
def initUI(self):
layout = QHBoxLayout()
self.table = QTableWidget()
self.table.setColumnCount(3)
tableLabels = ["First Name", "Surname", "Age"]
self.table.setHorizontalHeaderLabels(tableLabels)
self.table.setRowCount(3)
self.table.verticalHeader().setMinimumSectionSize(1)
users = {
'0': ["peter", "parker", "19"],
'1': ["bruce", "banner", "42"],
'2': ["barry", "allen", "35"]
}
for row, data in users.items():
for column, value in enumerate(data):
self.table.setItem(int(row), column, QTableWidgetItem(value))
button1 = QPushButton("Hide")
button1.clicked.connect(lambda: self.hide_row())
layout.addWidget(self.table)
layout.addWidget(button1)
self.setLayout(layout)
self.show()
def hide_row(self):
for i in [x for x in range(self.table.rowCount()) if x != self.table.currentRow()]:
self.rowHeight = QPropertyAnimation(self.table.item(i, 0), b"geometry")
self.rowHeight.setDuration(400)
self.rowHeight.setStartValue(QRect(0, 0, 0, self.table.rowHeight(i)))
self.rowHeight.setEndValue(QRect(0, 0, 0, 0))
self.rowHeight.start()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
`
Any idea how to achieve this goal in Python?
A Qt animation requires that the parent (or target, in the case of a Property Animation) inherits from QObject, and table widget items do not.
Also, the geometry property should exist for the target object, and items do not have such a property.
The solution is to use a QVariantAnimation and use the valueChanged signal to set the row height on the table.
class Example(QWidget):
def __init__(self):
super().__init__()
self.resize(600,200)
self.initUI()
self.animation = QVariantAnimation(self)
self.animation.setDuration(400)
self.animation.valueChanged.connect(self.animationChanged)
self.animation.finished.connect(self.finished)
def animationChanged(self, height):
self.table.setRowHeight(self.currentRow, height)
def finished(self):
self.table.setRowHidden(self.currentRow, True)
# reset the height, so that we can use it as a "stored" value in case
# we want to show the row again
self.table.setRowHeight(self.currentRow, self.currentHeight)
def hide_row(self):
# ignore if the animation is already resizing a row
if self.animation.state():
return
self.currentRow = self.table.currentRow()
self.currentHeight = self.table.rowHeight(self.currentRow)
self.animation.setStartValue(self.currentHeight)
self.animation.setEndValue(1)
self.animation.start()
# ...

QListWidget does not resize itself

In the following code, I'm hiding non-selected items in a QListWidget. (self.field is an
instance of QListWidget).
# Make selected items visible and other items hidden:
for i in range(self.field.count()):
self.field.item(i).setHidden(not self.field.item(i).isSelected())
When I hide non-selected items, they are essentially whited out, i.e., the space does not close up (see below). Is there any way to close up the whitespace?
Here is an example of a list widget with a dynamic height. This is achieved by subclassing QListWidget and overriding viewportSizeHint and minimumSizeHint. The size adjust policy is set to AdjustToContents so that the list widget will update its size when its contents have changed. The horizontal scroll bar is set to ScrollBarAlwaysOff to avoid the extra vertical space added at the bottom of the list widget that is reserved for the horizontal scroll bar if it were to be shown.
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import Qt
class ListWidget(QtWidgets.QListWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
def minimumSizeHint(self) -> QtCore.QSize:
return QtCore.QSize(-1, -1)
def viewportSizeHint(self) -> QtCore.QSize:
if self.model().rowCount() == 0:
return QtCore.QSize(self.width(), 0)
height = sum(self.sizeHintForRow(i) for i in range(self.count()) if not self.item(i).isHidden())
width = super().viewportSizeHint().width()
return QtCore.QSize(width, height)
class Widget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
# create and populate ListWidget instance
self.list_widget = ListWidget()
self.list_widget.setSelectionMode(QtWidgets.QListWidget.MultiSelection)
self.list_widget.addItems('Aardvark Bear Cat Donkey Echidna Ferret Goose'.split())
# setup rest of gui
self.select_button = QtWidgets.QPushButton('Select')
self.clear_button = QtWidgets.QPushButton('Clear selection')
vlayout = QtWidgets.QVBoxLayout(self)
vlayout.addWidget(QtWidgets.QLabel('Choose your mascots'))
vlayout.addWidget(self.list_widget)
# stretch added to layout to accommodate for varying height of list widget
vlayout.addStretch(1)
hlayout = QtWidgets.QHBoxLayout()
hlayout.addWidget(self.select_button)
hlayout.addWidget(self.clear_button)
self.select_button.setDefault(True)
vlayout.addLayout(hlayout)
self.select_button.clicked.connect(self.select)
self.clear_button.clicked.connect(self.clear_selection)
def select(self):
for i in range(self.list_widget.count()):
self.list_widget.item(i).setHidden(not self.list_widget.item(i).isSelected())
def clear_selection(self):
self.list_widget.clearSelection()
for i in range(self.list_widget.count()):
self.list_widget.item(i).setHidden(False)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
win = Widget()
win.show()
app.exec()
Screenshot:

PyQt: Adding user input into list

I am trying to make a list box where a user can enter more items to the list. I have a list box set up with an add button. The add button opens up a user input box where it will direct the user to enter a value. However, I am have issue with passing on the value of user input to the add onto the list. Any suggestions would help. Below is my code:
List box
from input_box import *
class list_form(QtGui.QWidget):
def __init__(self,list_of_items,open_text,parent= None):
super(list_form, self).__init__()
global output_path
output_path = output_path_i
grid = QtGui.QGridLayout()
grid.setSpacing(10)
self.widget = QtGui.QWidget()
self.layout = QtGui.QGridLayout(self.widget)
open_message = QtGui.QLabel(open_text)
grid.addWidget(open_message,0,0,2,4)
self.lst = QtGui.QListWidget()
grid.addWidget(self.lst,3, 0,1,4)
for i in list_of_items:
self.lst.addItem(str(i))
self.setLayout(grid)
add = QtGui.QPushButton('Add')
grid.addWidget(add,50,0)
add.clicked.connect(self.add_button)
def add_button(self):
self.input_box = input_box()
self.input_box.setWindowTitle("Window 2")
self.input_box.show()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = list_form(list(xrange(100)),"List of Values")
window.setWindowTitle('Window 1')
window.show()
sip.setdestroyonexit(False)
sys.exit(app.exec_())
Input box
import sys
from PyQt4 import QtCore, QtGui
import sip
class input_box(QtGui.QWidget):
def __init__(self,parent= None):
super(input_box, self).__init__()
grid = QtGui.QGridLayout()
grid.setSpacing(10)
self.widget = QtGui.QWidget()
self.layout = QtGui.QGridLayout(self.widget)
open_message = QtGui.QLabel("Enter Value:")
grid.addWidget(open_message,0,0,2,3)
self.txt = QtGui.QLineEdit()
grid.addWidget(self.txt,2, 0,1,2)
self.setLayout(grid)
save = QtGui.QPushButton('Save')
grid.addWidget(save,50,0)
a = save.clicked.connect(self.save)
save.clicked.connect(self.close)
cancel = QtGui.QPushButton('Cancel')
grid.addWidget(cancel,50,1)
cancel.clicked.connect(self.close)
def save(self):
value = self.txt.text()
return value
If you want to get data from a window you should use a QDialog instead of a QWidget, connect the clicked signal of save and cancel to the accept and reject slot, respectively:
input_box.py
from PyQt4 import QtCore, QtGui
class Input_Box(QtGui.QDialog):
def __init__(self,parent= None):
super(Input_Box, self).__init__(parent)
open_message = QtGui.QLabel("Enter Value:")
self.txt = QtGui.QLineEdit()
save = QtGui.QPushButton('Save', clicked=self.accept)
cancel = QtGui.QPushButton('Cancel', clicked=self.reject)
grid = QtGui.QGridLayout(self)
grid.setSpacing(10)
grid.addWidget(open_message, 0, 0)
grid.addWidget(self.txt, 1, 0, 1, 2)
grid.addWidget(save, 2, 0)
grid.addWidget(cancel, 2, 1)
self.setFixedSize(self.sizeHint())
def save(self):
value = self.txt.text()
return value
Then you use the exec_() method that returns a code if it is called accept or reject, and according to that the data must be obtained and added. On the other hand, do not use global variables because they are a headache when debugging.
from PyQt4 import QtCore, QtGui
from input_box import Input_Box
class list_form(QtGui.QWidget):
def __init__(self,list_of_items,open_text,parent= None):
super(list_form, self).__init__()
open_message = QtGui.QLabel(open_text)
self.lst = QtGui.QListWidget()
self.lst.addItems([str(i) for i in list_of_items])
add = QtGui.QPushButton('Add', clicked=self.add_button)
grid = QtGui.QGridLayout(self)
grid.setSpacing(10)
grid.addWidget(open_message)
grid.addWidget(self.lst)
grid.addWidget(add)
#QtCore.pyqtSlot()
def add_button(self):
input_box = Input_Box()
input_box.setWindowTitle("Window 2")
if input_box.exec_() == QtGui.QDialog.Accepted:
val = input_box.save()
it = QtGui.QListWidgetItem(val)
self.lst.addItem(it)
self.lst.scrollToItem(it)
if __name__ == "__main__":
import sys
import sip
app = QtGui.QApplication(sys.argv)
window = list_form(list(range(100)),"List of Values")
window.setWindowTitle('Window 1')
window.show()
sip.setdestroyonexit(False)
sys.exit(app.exec_())
The advantage of using QDialog is the decoupling between the classes, for example there is no need to pass a QListWidget as the other answer suggests making it possible that you can use the same dialog for other purposes.
You need to arrange for the action taken when the save button is pressed to pass information back to the list widget. There's more than one way to do it, but just returning the data won't get it done.
Here's an example of the sort of thing that will work - though other approaches are possible.
Change the input_box constructor so it keeps a reference to the list widget which it expects:
class input_box(QtGui.QWidget):
def __init__(self, list_widget, parent= None):
super(input_box, self).__init__()
self.list_widget = list_widget
...
Change the call to the constructor to provide that information:
def add_button(self):
self.input_box = input_box(self.lst)
self.input_box.setWindowTitle("Window 2")
self.input_box.show()
And then use that information in the save method to add to the list widget:
def save(self):
value = self.txt.text()
self.list_widget.addItem(value)
Bingo!
An alternative approach could be to arrange for the input_box to emit a signal with the new value in it, and connect that to a slot on the list_form, or on the list_widget. Or in the input_box you could navigate via its parent to the list_widget. But I think mine is simple and straightforward.

What kind of signal is emitted when QScrollArea entry is selected/clicked?

I'm having though time figuring out what kind of signal is emitted in following situation:
Basicly that's QScrollArea that holds multiple QTableWidgets:
class ScrollArea(QtGui.QScrollArea):
def __init__(self):
super(ScrollArea, self).__init__()
self.scroll_widget = QtGui.QWidget()
self.scroll_layout = QtGui.QVBoxLayout()
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setWidgetResizable(True)
self.__create_content()
self.setWidget(self._content_widget)
self.scroll_layout.addWidget(self)
self.scroll_widget.setLayout(self.scroll_layout)
def __create_content(self):
self._content_widget = QtGui.QWidget()
self._content_widget_layout = QtGui.QVBoxLayout()
self._content_widget.setLayout(self._content_widget_layout)
def add_item(self, item):
self._content_widget_layout.addWidget(item)
I'm using Plastique style for QApplication. As it can be seen from the above picture, when an item is clicked inside QScrollArea, blue border appears. What I would like to know is which signal is emitted when the border is drawn? I need this information so I can append a row to the selected QTableWidget whenever a button (on the left side) is clicked.
Also you can see that there is a 'x' inside each table, when 'x' is pressed that QTableWidget gets removed from QScrollArea. If there is a solution for previous problem, I could also remove QTableWidget depending on user selection rather than user clicking the 'x'.
To get the widget that has the focus you can use the focusChanged signal of QApplication:
from PyQt4 import QtCore, QtGui
class HorizontalHeader(QtGui.QHeaderView):
def __init__(self, parent=None):
super(HorizontalHeader, self).__init__(QtCore.Qt.Horizontal, parent)
self.button = QtGui.QToolButton(self, text="x")
self.sectionResized.connect(self.handleSectionResized)
def handleSectionResized(self):
last_ix = self.count() - 1
pos = QtCore.QPoint(self.sectionViewportPosition(last_ix) + self.sectionSize(last_ix) , 0)
self.button.move(pos)
def showEvent(self, event):
self.handleSectionResized()
super(HorizontalHeader, self).showEvent(event)
class TableView(QtGui.QTableView):
def __init__(self, *args, **kwargs):
super(TableView, self).__init__(*args, **kwargs)
header = HorizontalHeader(self)
header.button.clicked.connect(self.deleteLater)
self.setHorizontalHeader(header)
QtGui.qApp.focusChanged.connect(self.onFocusChanged)
def onFocusChanged(self, old, new):
if new == self:
self.deleteLater()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
scrollArea = QtGui.QScrollArea()
scrollArea.setWidgetResizable(True)
widget = QtGui.QWidget()
scrollArea.setWidget(widget)
lay = QtGui.QVBoxLayout(widget)
for i in range(10):
w = TableView()
model = QtGui.QStandardItemModel(4, 2, w)
w.setModel(model)
lay.addWidget(w)
scrollArea.show()
sys.exit(app.exec_())

PyQt QVBoxLayout and missing widgets?

I am trying to set up a window that has a text input & a combo box. At the moment I just want to see the text & the selection displayed under the appropriate widget.
I have used QVBoxLayout() as I will be adding more stuff later & thought it would be a simple way of laying out the window.
Unfortunately only the combo box ever gets displayed. The code:
from PyQt4 import QtCore, QtGui
import sys
class Polyhedra(QtGui.QMainWindow):
def __init__(self):
super(Polyhedra, self).__init__()
self.initUI()
def initUI(self):
# Poly names
self.pNames = QtGui.QLabel(self)
polyNameInput = QtGui.QLineEdit(self)
# polyName entry
polyNameInput.textChanged[str].connect(self.onChanged)
# Polytype selection
self.defaultPolyType = QtGui.QLabel("Random polyhedra", self)
polyType = QtGui.QComboBox(self)
polyType.addItem("Random polyhedra")
polyType.addItem("Spheres")
polyType.addItem("Waterman polyhedra")
polyType.activated[str].connect(self.onActivated)
# Layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(polyNameInput)
vbox.addWidget(self.pNames)
vbox.addWidget(polyType)
vbox.addWidget(self.defaultPolyType)
vbox.addStretch()
# Set up window
self.setGeometry(500, 500, 300, 300)
self.setWindowTitle('Pyticle')
self.show()
# Combo box
def onActivated(self, text):
self.defaultPolyType.setText(text)
self.defaultPolyType.adjustSize()
# Poly names
def onChanged(self, text):
self.pNames.setText(text)
self.pNames.adjustSize()
def main():
app = QtGui.QApplication(sys.argv)
ex = Polyhedra()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
So whats going on here? Am I missing some important directive to QVBoxLayout()?
Using Python 2.7 on Win 7 x64 machine with PyQt 4.
EDIT: Additional problem (still related to missing widgets)
I have amended the code following the clarification below. I then added more widgets when a certain option in the combobox is chosen (see below) but these widgets dont show. I attempted to add a child widget to 'widget' called 'ranPolyWidget' to take a numerical input.
# Combo box
def onActivated(self, text):
if text=="Random polyhedra":
self.randomSeedLbl = QtGui.QLabel("Seed: ", self)
randomSeed = QtGui.QLineEdit(self)
randomSeed.textChanged[str].connect(self.setSeed)
ranPolyWidget = QtGui.QWidget(self.widget)
rbox = QtGui.QVBoxLayout(ranPolyWidget)
rbox.addWidget(randomSeed)
self.layout().addWidget(ranPolyWidget)
self.show()
else:
self.defaultPolyType.setText(text)
self.defaultPolyType.adjustSize()
Same issue as before, no widgets. I am missing something pretty fundamental arent I?
You're forgetting to set it to the widget or main window, so since the QComboBox is the last one made, it's the only one displayed. Basically, everything is added to the layout, but the layout is "free-floating", and so it does not display properly. You need to bind the layout to a QWidget, which I do here. For most widgets, you can can do this by the QtGui.QVBoxLayout(widget) or by widget.setLayout(layout).
Alternatively, if you want multiple layouts on a widget, you can do have a parent layout and then add each child layout to the main layout.
EDIT: This is a better answer:
Make a widget, set layout to widget and set as central widget.
QMainWindow-s don't like you using the builtin layout or overriding it.
widget = QtGui.QWidget()
vbox = QtGui.QVBoxLayout(widget)
self.setCentralWidget(widget)
Old Answer:
self.layout().addLayout(vbox).
This should fix your issue:
Changes I made:
Since QMainWindow already has a layout, add in a widget (28G) and then set the VBoxLayout to the widget and add it to the main window.
from PyQt4 import QtCore, QtGui
import sys
class Polyhedra(QtGui.QMainWindow):
def __init__(self):
super(Polyhedra, self).__init__()
self.initUI()
def initUI(self):
# Poly names
self.pNames = QtGui.QLabel(self)
polyNameInput = QtGui.QLineEdit(self)
# polyName entry
polyNameInput.textChanged[str].connect(self.onChanged)
# Polytype selection
self.defaultPolyType = QtGui.QLabel("Random polyhedra", self)
polyType = QtGui.QComboBox(self)
polyType.addItem("Random polyhedra")
polyType.addItem("Spheres")
polyType.addItem("Waterman polyhedra")
polyType.activated[str].connect(self.onActivated)
# Layout
widget = QtGui.QWidget()
vbox = QtGui.QVBoxLayout(widget)
vbox.addWidget(polyNameInput)
vbox.addWidget(self.pNames)
vbox.addWidget(polyType)
vbox.addWidget(self.defaultPolyType)
vbox.addStretch()
# Set up window
self.setGeometry(500, 500, 300, 300)
self.setWindowTitle('Pyticle')
self.layout().addWidget(widget)
self.show()
# Combo box
def onActivated(self, text):
self.defaultPolyType.setText(text)
self.defaultPolyType.adjustSize()
# Poly names
def onChanged(self, text):
self.pNames.setText(text)
self.pNames.adjustSize()
def main():
app = QtGui.QApplication(sys.argv)
ex = Polyhedra()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
EDIT:
For adding new widgets, you should add them to the layout of the central widget and parent them to that widget.
Here's how I'd restructure your full code:
from PyQt4 import QtCore, QtGui
import sys
class CentralWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(CentralWidget, self).__init__(parent)
# set layouts
self.layout = QtGui.QVBoxLayout(self)
# Poly names
self.pNames = QtGui.QLabel(self)
polyNameInput = QtGui.QLineEdit(self)
# polyName entry
polyNameInput.textChanged[str].connect(self.onChanged)
# Polytype selection
self.defaultPolyType = QtGui.QLabel("Random polyhedra", self)
polyType = QtGui.QComboBox(self)
polyType.addItem("Random polyhedra")
polyType.addItem("Spheres")
polyType.addItem("Waterman polyhedra")
polyType.activated[str].connect(self.onActivated)
self.layout.addWidget(polyNameInput)
self.layout.addWidget(self.pNames)
self.layout.addWidget(polyType)
self.layout.addWidget(self.defaultPolyType)
self.layout.addStretch()
def onActivated(self, text):
'''Adds randSeed to layout'''
if text=="Random polyhedra":
self.randomSeedLbl = QtGui.QLabel("Seed: ", self)
randomSeed = QtGui.QLineEdit(self)
randomSeed.textChanged[str].connect(self.setSeed)
self.layout.addWidget(randomSeed)
else:
self.defaultPolyType.setText(text)
self.defaultPolyType.adjustSize()
# Poly names
def onChanged(self, text):
self.pNames.setText(text)
self.pNames.adjustSize()
class Polyhedra(QtGui.QMainWindow):
def __init__(self):
super(Polyhedra, self).__init__()
# I like having class attributes bound in __init__
# Not very Qt of me, but it's more
# so I break everything down into subclasses
# find it more Pythonic and easier to debug: parent->child
# is easy when I need to repaint or delete child widgets
self.central_widget = CentralWidget(self)
self.setCentralWidget(self.central_widget)
# Set up window
self.setGeometry(500, 500, 300, 300)
self.setWindowTitle('Pyticle')
self.show()
# Combo box
def onActivated(self, text):
'''Pass to child'''
self.central_widget.onActivated(text)
def main():
app = QtGui.QApplication(sys.argv)
ex = Polyhedra()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Categories

Resources