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)
Related
My problem is I can't find the command to change the background color of DataViewListCtrl for selected text/row/item in DataViewListCtrl object.
I looked into the documentation but there's no apparent reference.
https://docs.wxpython.org/wx.dataview.DataViewCtrl.html#wx-dataview-dataviewctrl
I'm using the sample_one.py script from this reference:
https://wiki.wxpython.org/How%20to%20add%20a%20menu%20bar%20in%20the%20title%20bar%20%28Phoenix%29
The DataViewListCtrl has this example code:
self.dvlc = dv.DataViewListCtrl(
self,
style=dv.DV_MULTIPLE
| dv.DV_ROW_LINES
| dv.DV_HORIZ_RULES
| dv.DV_VERT_RULES,
)
self.dvlc.SetBackgroundColour("#ffffff")
self.dvlc.SetForegroundColour("black")
What I'm looking to do is similar to Tkinter example below (change the selected text background color to blue):
style.configure(
"Treeview", background="#FFFFDD", foreground="black", fieldbackground="#FFFFDD"
)
style.map("Treeview", background=[("selected", "#F0FFFF")])
I haven't been able to try a workaround yet as I'm not sure how to get the needed command/value, but on my end the selected DataViewListCtrl items have a #242424 color (I checked with an eyedropper from a screenshot).
I found the 2 parameters IsSelected and IsRowSelected in the doc:
https://docs.wxpython.org/wx.dataview.DataViewListCtrl.html#wx.dataview.DataViewListCtrl.IsRowSelected
https://docs.wxpython.org/wx.dataview.DataViewCtrl.html#wx.dataview.DataViewCtrl.IsSelected
and tested as
self.dvlc = dv.DataViewListCtrl(
self,
style=dv.DV_MULTIPLE
| dv.DV_ROW_LINES
| dv.DV_HORIZ_RULES
| dv.DV_VERT_RULES,
)
# Give it some columns.
self.dvlc.AppendTextColumn("Id", width=40)
self.dvlc.AppendTextColumn("Artist", width=170)
self.dvlc.AppendTextColumn("Title", width=260)
self.dvlc.AppendTextColumn("Genre", width=80)
# Load the data. Each item (row) is added as a sequence
# of values whose order matches the columns.
for itemvalues in musicdata:
self.dvlc.AppendItem(itemvalues)
# — 1st test with same result as 2nd below
# if self.dvlc.IsSelected == True:
# self.dvlc.SetBackgroundColour("#0066ff")
# else:
# self.dvlc.SetBackgroundColour("#F0FFFF")
if self.dvlc.IsRowSelected == True:
self.dvlc.SetBackgroundColour("#0066ff")
else:
self.dvlc.SetBackgroundColour("#F0FFFF")
self.dvlc.SetForegroundColour("black")
I tried also without the == True explicit as
# — 1st test with same result as 2nd below
# if self.dvlc.IsSelected:
# self.dvlc.SetBackgroundColour("#0066ff")
# else:
# self.dvlc.SetBackgroundColour("#F0FFFF")
if self.dvlc.IsRowSelected:
self.dvlc.SetBackgroundColour("#0066ff")
else:
self.dvlc.SetBackgroundColour("#F0FFFF")
and the unexpected blue background displays by default (when not selected).
Is there some documented "selected" parameter or otherwise known working method to be able to capture the selected background color and change it in WxPython?
You will have to use a custom renderer if you want to change the way the selected items background is rendered -- and you will have to overwrite the entire background entirely, as the default one will still be drawn, you will just be able to paint over it.
Alternatively, you can define a custom wxRendererNative-derived object and override its DrawItemSelectionRect() method and then set it as the renderer to use globally. This should be simpler, but it will change selection drawing in all controls using this function.
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.
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.
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.
I'm preparing a PDF report of a list of items, which essentially is a long table, with some cells filled with data and some cells empty. This report will be printed out and the user will fill the empty cells on paper. She will also add new rows and fill in new data.
So I will provide some (e.g. 3) empty rows as a grid to fill in data. But if there is still room left on the page, I'd like to fill it with empty rows.
How can I add as many rows as fit on the last page?
Different approach:
Maybe this behaviour is easier achieved when using a Paragraph with a one-row-table for each row. But I need the first row repeated on every page (which is easy in tables with repeatRows=1).
Any ideas?
I'll have to go retrieve some of my code, but I seem to recall having to do things by measuring the current X / Y Position, calculate that against whatever margin I was using, and then determine whether or not more information could fit or if I needed a new page. My project was word wrapping long blocks of text, which is similar but not quite analogous. I'll update with some code here shortly.
def newline(self, options, text = ''):
if getattr(self, 'lpp', None) == self.lines[self.pages]:
self.newpage()
if getattr(self, 'y', None) > self.h - self.bm * inch:
self.newpage()
In this case I had attributes for lpp (Lines Per Page) which may have been set, so I first checked to see if that value existed, and if so, if I was at the line count for the current page. If there was no restriction on total lines per page then I tested for what my Y position would be and what the bottom margin was. If necessary, patch in a page. There's some left out here, but that's a general idea.
def newline(self, options, text = ''):
if getattr(self, 'lpp', None) == self.lines[self.pages]:
self.newpage()
if getattr(self, 'y', None) > self.h - self.bm * inch:
self.newpage()
self.addLine()
self.putText(self.x, self.h - self.y, text)
def putText(self, x, y, text):
# If we actually place some text then we want to record that.
if len(text.strip()) > 0 and not self.hasText[self.pages]:
self.hasText[self.pages] = True
# Something here to handle word wrap.
if self.wrap:
lines = self._breakScan(text)
if len(lines) > 1:
self.c.drawString(x, y, lines[0])
self.newline('', ' '.join(lines[1:]))
elif lines:
self.c.drawString(x, y, lines[0])
else:
self.c.drawString(x, y, text)
Here, self.c is my canvas. I'm keeping track of how many lines I've put down on the page because there are times where we're re-wrapping a document that may contain page breaks, all in our custom markup.
Depending on where this table ends in the document, a quick, hacky solution might be to just stick enough rows in the table to make sure it fills the page and spills over onto the next page. Then, after building the document, cut off the last page and create a new PDF just missing that page.
Of course, this only works if the table is at the end of the document (or, if you're using the commercial version of ReportLab, you could stitch together PDFs, in which case it wouldn't matter), and it's kind of ugly like I said, but it gets the job done..