Qt and PyQt tablewidget growing row count - python

I am learning Python and trying to write a program to help my dad.
I want it to be excel-like, and it looks great for now, but I have no idea how to make the number of rows (not the columns) in a tableWidget to grow while someones crolls down...
Can I do it using QtDesigner or I have to write the code in the .py file?
Any help is appreciated...
Sorry if I am asking silly questions, I an a noob really...

Here is sort of a proof-of-concept example:
class Widget(QtGui.QWidget):
def __init__(self):
super(Widget, self).__init__()
self.resize(600,400)
layout = QtGui.QVBoxLayout(self)
self.table = QtGui.QTableWidget(20,10)
self.vBar = self.table.verticalScrollBar()
self._vBar_lastVal = self.vBar.value()
layout.addWidget(self.table)
self.vBar.valueChanged.connect(self.scrollbarChanged)
def scrollbarChanged(self, val):
bar = self.vBar
minVal, maxVal = bar.minimum(), bar.maximum()
avg = (minVal+maxVal)/2
rowCount = self.table.rowCount()
# scrolling down
if val > self._vBar_lastVal and val >= avg:
self.table.insertRow(rowCount)
# scrolling up
elif val < self._vBar_lastVal:
lastRow = rowCount-1
empty = True
for col in xrange(self.table.columnCount()):
item = self.table.item(lastRow, col)
if item and item.text():
empty=False
break
if empty:
self.table.removeRow(lastRow)
self._vBar_lastVal = val
You have to rely on the vertical scroll bar of the table widget to signal information about it changing value. So we connect its valueChanged(int) signal to our method.
The scrollbarChanged SLOT will receive the value of the scrollbar. What I am doing here is checking the min and max value of the scrollbar at that moment, and seeing if the current position is at least in the middle. We also check if the current scrollbar value is greater than the last time, because we only want to add rows on a down scroll. If these conditions are true, then we insert a new row.
The act of shrinking it back down is a bit more involved because I am sure you will need to check the last row to make sure it is empty and only remove it if so. My version gives you the rough idea but the math logic would probably need more work. But what it is doing is going through every item in the last row to see if its None or an empty value. If the whole row is empty, it removes it. Thus, you do get a reducing effect when scrolling back up.
Hope this gives you a starting point! Feel free to ask about the code if you need more detailed explanation of any parts.

Related

can you change the stacking order of a window using its id?

I'm trying to make a procedure that updates the stacking order of randomly created windows based on their y value (so that things that are closer to the top of the screen display under things that are lower down).
Basically I have a button that creates a window by calling a class statement. Each time a window is created, its id and y value get put in an array, which would get sorted and looped through to update the stacking order.
I figured having the id of each window that is created would be enough to change the variables of specific windows, using the id of the window.lift(), but it looks like you can't point to an object with its id like that. If I'm not mistaken I can't just use the class name because I don't want to change the whole class, just the specific window.
Anybody know what I can use instead of the id to get this to work? Is it just impossible the way I have it planned?
Here's my code in case it's helpful, unfinished because obviously it doesn't work:
import tkinter as tk
import random
# creates a new window
def makewndw(event):
a = testwindow()
# create main window - click to create a new window
mainwindow = tk.Tk()
mainwindow.bind('<Button-1>', makewndw)
# this sorts the windows
class stacker():
stackerwindow = tk.Toplevel()
yarray = [{'ident': 0, 'ypos': 0}]
def arupdate(self):
#sort array by ypos
self.yarray.sort(key=lambda x: x.get('ypos'))
# print(self.yarray)
# get length of array
arlen = len(self.yarray)
# loop through array between pos 1 and end
for x in range(1,arlen):
# extract dictionary from that position
tempex = self.yarray[x]
# populate temp list with values from tempex
templist = []
for i in tempex.values():
templist.append(i)
# get just the id
tempid = templist[0]
# use id to update stacking order
tempid.lift()
# ^^^ this is where i'm suck because clearly this isn't working
self.stackerwindow.after(10, self.arupdate)
class testwindow():
def __init__(self):
# create a window
self.window = tk.Toplevel()
self.y = random.randrange(100, 500)
self.window.geometry('200x200+800+{y}'.format(y=str(self.y)))
# give info to sorter
self.infdict = {'ident': id(self.window), 'ypos': self.y}
stacker.yarray.append(self.infdict)
st = stacker()
st.arupdate()
mainwindow.mainloop()
I'm a beginner so I'm sorry if the answer here is obvious, and sorry about how messy I'm sure my code is.

Clearing the background color of rows that are dropped into a QTableView

I have two QTableViews with different QStandardItemModels that are placed next to each other on my layout. One of the tableviews accepts drops from the other and that has been working well so far.
This is how the model is set up:
self.auto_queue_model = QStandardItemModel()
self.auto_queue.setModel(self.auto_queue_model)
The table rows that are dropped into the tableview come with two different background colors. I am having some trouble implementing a way for those background colors to be deleted or reset once the rows are dropped to the second tablewidget.
I have tried installing an event filter like so:
self.auto_queue.installEventFilter(self)
def eventFilter(self, source, event):
if source.objectName() == 'auto_queue':
if event.type() == QEvent.Drop:
for row in range(0, self.auto_queue_model.rowCount()):
item = self.auto_queue_model.item(row, 0)
if item:
item.setBackground(QtGui.QColor(70, 70, 70))
return super().eventFilter(source, event)
But I can not get PyQt to recognize the event.type() as QEvent.Drop even though it is in the documentation
https://doc.qt.io/qt-5/dnd.html
I have also tried connecting a signal to a function like so:
self.auto_queue_model.rowsInserted.connect(self.resetAutoQueueColors)
def resetAutoQueueColors(self, index, column, row):
print(column,row, self.auto_queue_model.rowCount())
for row in range(0, self.auto_queue_model.rowCount()):
item = self.auto_queue_model.item(row, 0)
if item:
item.setBackground(QtGui.QColor(70, 70, 70))
I have tried both changing the background of the dropped row(s) that are received by the resetAutoQueueColors function individually and looping over all rows like in the sample above. The problem seems to be that the signal is fired before the rows are actually added to the model. It then changes the background of all rows except for the ones I have dropped. The new rows are added to the rowCount(), but the item at that index location returns None at the time of the execution of the function.
i have also tried to subclass the QTableView and emitting a signal, but have been unsuccessful.
Also, I am wondering if there is a way to reset the background color instead of setting it explicitly.
Any help would be much appreciated.

Make QTableWidget columns expand up to and stop expanding at specific width

I'm trying to customize a QTableWidget in a specific way, but I'm not entirely sure how to go about it. I want the QTableWidget, when it is expanded, to expand according to the following rules:
All columns will expand equally, much like the functionality of the setResizeMode(QHeaderView.Stretch)
However, once the columns reach a specific width, I want all the columns to expand to a maximum width (say 150 pixels) and the rest of the space give to the last column, much like the functionality of setStretchLastSection(True)
Shrinking the table backwards should follow the rules in reverse.
I've tried overriding the resizeEvent for the QTableWidget, but that doesn't seem to work, especially when setResizeMode and setStretchLastSection are used. Do I have to update the QHeaderView instead? How do I achieve this?
I've got a class where I've tried to implement this. It works, but only after the table size has been updated. The process of displaying the table messes with the size and I have to update the size myself to have it reapply the resizing properties I want.
class DataTable(QTableWidget):
def __init__(self, data, header):
# Call the super class method
super().__init__(6,4) # Make if 6 rows by 4 columns for this example
# Put the data in the table
# Removed from MWE
# Configure the table properties
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.horizontalHeader().setResizeMode(QHeaderView.Stretch)
#self.horizontalHeader().setStretchLastSection(True)
self.resizeColumnsToContents()
self.resizeRowsToContents()
self.setMinimumWidth(self.horizontalHeader().length()+self.verticalHeader().width())#, self.verticalHeader().length() + self.horizontalHeader().height())
def resizeEvent(self, event):
if self.columnWidth(3) > 100: # 3 is the last column in this MWE
for i in range(3):
self.horizontalHeader().setResizeMode(i,QHeaderView.Fixed)
self.horizontalHeader().setResizeMode(3,QHeaderView.Stretch)
else:
for i in range(3):
self.horizontalHeader().setResizeMode(i,QHeaderView.Stretch)
self.horizontalHeader().setResizeMode(3,QHeaderView.Stretch)
super().resizeEvent(event)

PyQt - get list of all checked in QTreeWidget

I am building a simple application to open up a folder of data and plot that data. Importing the data updates a QTreeWidget that shows which signals are available to plot. Ex:
The QTreeWidget is populated after the data is imported using:
def update_treeWidget(self):
headers = self.df['voltage recording'].columns[1:]
sweeps = self.df['voltage recording'].index.levels[0]
for header in headers:
parent_item = QtWidgets.QTreeWidgetItem(self.treeWidget)
parent_item.setCheckState(0, QtCore.Qt.Unchecked)
parent_item.setText(0, header)
for sweep in sweeps:
child_item = QtWidgets.QTreeWidgetItem(parent_item)
child_item.setCheckState(0, QtCore.Qt.Unchecked)
child_item.setText(0, sweep)
It is unclear to me, though, how I would check the checked status for any of the items now in the tree.
So my two questions are:
How would I refer to any part of that tree to check it's checked status (either the parent, or the various children of a parent)
Is there a way to simply return a list of all of the boxes that are checked? If I can figure out the answer to Question 1 I can obviously create a function where, every time a box is checked it is added to a list (and removed from that list if it is unchecked). But since the goal here is simply to plot all of the checked signals, the most straightforward thing to me (logically anyway) is upon hitting the "Plot" button the interface first checks to see which signals boxes are checked and then plots those signals.
In all of the examples I have seem the tree items are explicitly declared (i.e. item_1, item_2), so referring to them is simple. Because of how I am populating the tree, though, I don't understand how to do that.
If this is an incorrect approach to populating the tree in the first place, please let me know and/or point me in the direction of a more correct approach.
Thanks
Edit:
This is very similar to:
PyQT QTreeWidget iterating
which is what I based my answer off of.
Figured it out. This at least achieves what I need it to achieve:
def find_checked(self):
checked = dict()
root = self.treeWidget.invisibleRootItem()
signal_count = root.childCount()
for i in range(signal_count):
signal = root.child(i)
checked_sweeps = list()
num_children = signal.childCount()
for n in range(num_children):
child = signal.child(n)
if child.checkState(0) == QtCore.Qt.Checked:
checked_sweeps.append(child.text(0))
checked[signal.text(0)] = checked_sweeps
return checked
Five years later I was searching for an answer to your second question. While dan_g's code was very helpful I believe it will not list all items if the tree depth is greater than 2. I created a recursive version that will list all selected items regardless of their level in the tree.
def get_selected_items(self):
checked_items = []
def recurse(parent_item):
for i in range(parent_item.childCount()):
child = parent_item.child(i)
grand_children = child.childCount()
if grand_children > 0:
recurse(child)
if child.checkState(0) == Qt.Checked:
checked_items.append(child.text(0))
recurse(self.ui.treeWidget.invisibleRootItem())
return checked_items
Or in my case if you only want to list selected items at the leaves of the tree just add an else statement.
def get_selected_leaves(self):
checked_items = []
def recurse(parent_item):
for i in range(parent_item.childCount()):
child = parent_item.child(i)
grand_children = child.childCount()
if grand_children > 0:
recurse(child)
else:
if child.checkState(0) == Qt.Checked:
checked_items.append(child.text(0))
recurse(self.ui.treeWidget.invisibleRootItem())
return checked_items
In either case the function calls itself whenever it finds an item that has children until it traverses the entire tree.
Next solution can be used:
for item in self.QTreeWidget.findItems("", Qt.MatchContains | Qt.MatchRecursive):
if (item.rowCount() == 0 and item.checkState()>0):
print (item.text(),item.checkState())

pyQt QTableView select a noncontiguous set of rows

I have a QTableView, which is created this way:
self.preset_delegate = PresetDelegate() # used to provide a combobox for making a selection from a set of options, column 0
self.model_filelist = QtGui.QStandardItemModel()
self.model_filelist.setHorizontalHeaderLabels(HEADER_LABELS)
self.list_filelist = QtGui.QTableView()
self.list_filelist.horizontalHeader().setResizeMode( QtGui.QHeaderView.Interactive )
self.list_filelist.setItemDelegateForColumn(0, self.preset_delegate )
self.list_filelist.setModel( self.model_filelist )
self.list_filelist.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection )
When the user presses a button, I would like to algorithmically select rows from the table. The selection will not be contiguous. For the purpose of our discussion, it could be any random subset of rows in the model/table.
This is pseudo-code for what I'm using to make the selection:
files = [str(self.model_filelist.data( self.model_filelist.index(x,1)).toString()) for x in range(self.model_filelist.rowCount())]
self.list_filelist.clearSelection()
for x in match_set:
match_index = files.index( x )
model_index = self.model_filelist.index(match_index,1) # first column is okay
self.list_filelist.selectionModel().select( model_index, QtGui.QItemSelectionModel.Select | QtGui.QItemSelectionModel.Current )
My problem is the with selection model flag on the very last line. Whether I use SelectCurrent, ToggleCurrent or Select | Current, or Toggle | Current, I only get the last item in my match_set remaining selected at the end of the loop. As the loop executes, the selection is changed from one item to the other, rather than adding the new row to the set of the selection. I hope that makes sense.
I thought for sure that SelectCurrent flag was the way to do this, but it's not working for me. Any suggestions? (python 2.6.7, Fedora 14, Qt4.4??? I can't be sure)
As per comments to the question. I did not solve why the original code failed, but I found this to work:
Use the version of select() that takes a QItemSelection object, and load that object with QItemSelectRange objects that wrap ModelIndex to the rows I'm interested in.

Categories

Resources