I want to display the status read or unread of a row by makeing the text (of a column in the row) bold or not.
The status of the row (or the data in the model) can be part of the model or somewhere else. I am flexible at this point.
How can I make the text of a specific cell bold depending on the underlying (dynamic) data, which is in this example the value 1?
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class View(Gtk.TreeView):
def __init__(self, model):
Gtk.TreeView.__init__(self, model)
col = Gtk.TreeViewColumn('Text',
Gtk.CellRendererText(),
text=0)
self.append_column(col)
# bold text
# Gtk.CellRendererText(weight_set=True, weight=700),
class Model(Gtk.ListStore):
def __init__(self):
Gtk.ListStore.__init__(self, str, int)
self.append(['foo 1', 0])
self.append(['foo 2', 1]) # bold
self.append(['foo 3', 0])
self.append(['foo 4', 0])
self.append(['foo 5', 1]) # bold
class Window(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.set_default_size(100, 200)
# model & view
self.model = Model()
self.view = View(self.model)
# layout
self.layout = Gtk.Grid()
self.add(self.layout)
self.layout.attach(self.view, 0, 0, 1, 2)
self.connect('destroy', Gtk.main_quit)
self.show_all()
if __name__ == '__main__':
win = Window()
Gtk.main()
As is often in Gtk, this is very very simple, just not very obvious. You really need to know where to look and read the related docs carefully.
class View(Gtk.TreeView):
def __init__(self, model):
Gtk.TreeView.__init__(self, model)
renderer = Gtk.CellRendererText()
col = Gtk.TreeViewColumn(
'Text', renderer, text=0, weight_set=True)
col.set_cell_data_func(renderer, datafunc)
self.append_column(col)
The first key is the TreeViewColumn.set_cell_data_func method. This allows you to set an intercept function that you can use to modify the properties of the cell before it is rendered. Here's an example one I wrote which does what you want:
def datafunc(col, renderer, model, titer, data):
val = model.get_value(titer, 1)
if val:
renderer.set_property("weight", 700)
As you can see, it receives the TreeViewColumn, CellRenderer, TreeModel and TreeIter involved, as well as custom data, which I've ommited.
The first step is to get the values of the current column. For that, we give the model the treeiter (which saves the "current row", kinda), and the column id we want (in this case the second column, 1).
The rest is pretty simple, we use that value to decide if we need to set the "weight" property on the CellRenderer.
You used 700 here, but I'd recommend you to use Pango.Weight.BOLD instead for clarity. This requires from gi.repository import Pango of course.
After a short discussion with the Gtk maintainers, I've found that there is another, cleaner way to do this. Since both answers work, I've decided to put this as separate answer.
The problem with the other answer is that the cell_data_func gets called for every row. Over time, this will get really slow if you have many rows.
The solution here is to add another column to your model that you then bind to the relevant attribute(s).
Currently, you do the following:
col = Gtk.TreeViewColumn(
'Text', renderer, text=0, weight_set=True)
This binds the text property of the CellRenderer to column 0 of your model.
Now, we can bind this to any other property too. For example, bind column 1 to the weight property:
col = Gtk.TreeViewColumn(
'Text', renderer, text=0, weight=1, weight_set=True)
You can then set this column to different Pango.Weight values to change the weight of the text.
class Model(Gtk.ListStore):
def __init__(self):
...
self.append(['foo 1', Pango.Weight.BOLD])
self.append(['foo 2', Pango.Weight.BOOK])
...
If you want to set additional properties, you can also set the markup property (which parses the string as pango markup and allows you to change the font, color, etc of some parts of the text) and the attributes property, which you can use to set many style attributes at once with the pango.AttrList type.
Related
As the title suggests I'm looking to get a combobox in a QTableView.
I've looked at several other questions that deal with comboboxes in tableviews, but they mostly concern comboboxes as editors and that is not what I'm looking for.
I would like the combobox to be visible at all times and get its data from the underlying model. It doesn't have to set data in the model.
I tried to adapt this example of a progress bar delegate: How to include a column of progress bars within a QTableView?
Leading to this code:
class ComboDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
combo = QStyleOptionComboBox()
combo.rect = option.rect
combo.currentText = 'hallo' # just for testing
combo.editable = True
combo.frame = False
QApplication.style().drawComplexControl(QStyle.CC_ComboBox, combo, painter)
But this gives me a greyed out, non functional combobox.
How do you get a functional combobox there?
As the name suggests, the paint() function only draws a combobox, it does not create one.
If you want a persistent widget set for an item, and that widget doesn't need to update the model, then you should use setIndexWidget().
A basic implementation on a static model could be like this:
class SomeWidget(QWidget):
def __init__(self):
# ...
self.table.setModel(someModel)
for row in range(someModel.rowCount()):
combo = QComboBox(editable=True)
combo.addItem('hallo')
self.table.setIndexWidget(someModel.index(row, 2), combo)
If the model can change at runtime (and is possibly empty at startup), then you need to connect to the rowsInserted signal:
class SomeWidget(QWidget):
def __init__(self):
# ...
self.updateCombos()
someModel.rowsInserted.connect(self.updateCombos)
def updateCombos(self):
for row in range(self.table.model().rowCount()):
index = someModel.index(row, 2)
if self.table.indexWidget(index):
continue
combo = QComboBox(editable=True)
combo.addItem('hallo')
self.table.setIndexWidget(index, combo)
Then you can access any combo based on the index row:
def getComboValue(self, row):
index = self.table.model().index(row, 2)
widget = self.table.indexWidget(index)
if isinstance(widget, QComboBox):
return widget.currentText()
Remember: whenever you're studying the documentation, you must also review the documentation of all inherited classes. In this case you should not only read the docs about QTableView, but also the whole inheritance tree: QTableView > QAbstractItemView > QAbstractScrollArea > QFrame > QWidget > QObject and QPaintDevice.
I have an interface with two tab: in the first one i ask the user to enter parameters and in the second one i want to print the following QTableWidget.
So basically on the first tab i have a QPushButton that i called process and normally, when i push on it , i want to send the information to the second Tab.
Right now i just tried to show a new window with the QTableWidget and the good parameters :
class Parameters(QWidget):
def __init__(self):
super(Parameters, self).__init__()
self.matrixsize = QLineEdit()
bouton = QPushButton("define matrix_size")
bouton.clicked.connect(self.appui_bouton)
self.halfmatrix = QCheckBox()
self.halfmatrix.toggled.connect(self.on_checked)
self.define_matrix_size = QGroupBox('Define Parameters')
layout = QGridLayout()
layout.addWidget(self.matrixsize, 0, 0, 1, 1, )
layout.addWidget(bouton, 0, 1, 1, 1)
layout.addWidget(QLabel('select half size mode'
), 1, 0, 1, 1)
layout.addWidget(self.halfmatrix, 1, 1, 1, 1)
self.define_matrix_size.setLayout(layout)
process = QPushButton('process')
process.clicked.connect(self.process)
self.matrix = QTableWidget()
self.layout = QGridLayout()
self.layout.addWidget(self.define_matrix_size)
self.layout.addWidget(matrix)
self.layout.addWidget(process)
self.setLayout(self.layout)
def matrix_size(self):
if self.matrixsize.text() == "":
return 0
else:
return int(self.matrixsize.text())
def appui_bouton(self):
taille = self.matrixsize()
self.matrix.deleteLater()
if self.halfmatrix.isChecked():
self.on_checked()
else:
self.matrix = QTableWidget()
self.matrix.setColumnCount(taille)
self.matrix.setRowCount(taille)
self.layout.addWidget(self.matrix)
self.update()
self.setLayout(self.layout)
def keyPressEvent(self, qKeyEvent):
print(qKeyEvent.key())
if qKeyEvent.key() == Qt.Key_Return or qKeyEvent.key() == Qt.Key_Enter:
self.appui_bouton()
else:
super().keyPressEvent(qKeyEvent)
def on_checked(self):
taille = self.matrixsize()
if taille == 0:
pass
else:
if self.halfmatrix.isChecked():
size = int(taille / 2)
self.matrix.deleteLater()
self.matrix = QTableWidget()
self.matrix.setColumnCount(size)
self.matrix.setRowCount(size)
self.layout.addWidget(self.matrix, 3, 0, 20, 4)
self.update()
self.setLayout(self.layout)
else:
self.appui_bouton()
def process (self):
layout = QHBoxLayout()
test = self.matrix
test.setLayout(layout)
test.show()
So in order to clarify what i said: i have a Window on which you get some parameters (size,...) , when you select those parameters, let's say you take matrixsize==5, then a 5x5 table is added to the window. This table can be after this fill by others parameters (i cut them on the code) by a system of drag and drop.
So now that i got a built table, i want to be able to open a new window with just the table by clicking on the ''process'' button.
So i don't want a dynamical table, i just want a table that keeps the same property (for instance if the matrix has dragonly enable then the new matrix should have the same) . I want to keep every information containing in the cells
I hope i am enoughly clear that is my first time asking questions (after many times reading some answers of course^^)
thanks for your answer and advice !
You can just create a new QTableWidget with no parent (which makes it a top level window), and then show it:
class Parameters(QWidget):
# ...
def process(self):
rows = self.matrix.rowCount()
columns = self.matrix.columnCount()
self.newTable = QTableWidget(rows, columns)
for row in range(rows):
for column in range(columns):
source = self.matrix.item(row, column)
if source:
self.newTable.setItem(row, column, QTableWidgetItem(source))
self.newTable.show()
Note that I created the new table as an instance attribute. This allows to avoid the garbage collection in case it was a local variable (resulting in the widget showing and disappearing right after), but has the unfortunate effect that if you click on the process button again and a window already exists, it gets deleted and "overwritten" with a new window. If you want to have more process windows at the same time, you could add them to a list:
class Parameters(QWidget):
def __init__(self):
super(Parameters, self).__init__()
# ...
self.processTables = []
def process(self):
rows = self.matrix.rowCount()
columns = self.matrix.columnCount()
# note that now "newTable" is *local*
newTable = QTableWidget(rows, columns)
self.processTables.append(newTable)
# ...
Some suggestions about your code:
there's absolutely no need to create a new table each time you want to change its size; just use setRowCount and setColumnCount on the existing one, and if you don't want to keep previous values, use clear();
don't use two functions that do almost the same things (appui_bouton and on_checked) and call each other, just use one function that checks for both aspects;
don't call update() unnecessarily: when you change the properties of a widget (or add a new widget to a layout) update is called already; while it's not an actual issue (Qt automatically manages when updates actually happen, avoiding repainting if not necessary), calling it just adds unnecessary noise to your code;
be more careful when adding widgets to a grid layout (I'm referring to the code on on_checked): don't use the rowSpan and columnSpan if not required; also, using a value that high is completely useless, as there are no other widgets in that row, and there's actually only one column in that layout; also, don't call setLayout() again;
if you need a numerical value, then use a QSpinBox, not a QLineEdit.
The function to update the existing table can be rewritten more easily, and you should connect both the button and the checkbox to it:
class Parameters(QWidget):
def __init__(self):
super(Parameters, self).__init__()
self.matrixsize = QSpinBox()
bouton = QPushButton("define matrix_size")
bouton.clicked.connect(self.appui_bouton)
self.halfmatrix = QCheckBox()
self.halfmatrix.toggled.connect(self.appui_bouton)
# ...
def appui_bouton(self):
taille = self.matrixsize.value()
if self.halfmatrix.isChecked():
taille //= 2
if not taille:
return
self.matrix.setColumnCount(taille)
self.matrix.setRowCount(taille)
I wrote a subclass with tkinter widgets. In a for-loop i place few of them in a Frame. This Frame also contains a Label and a Entry.
Now i want to destroy all of my subclass widgets but NOT the Label and the Entry.
I tried it like this:
for child in self.frame.winfo_children():
if child.winfo_class() == "???":
[...]
But I wasnt able to figure out what i have to use, so i will use ??? as a placeholder for this.
I place them in a rule with this Code:
db.execute("SELECT * FROM UsedSystems")
rows = db.fetchall()
i = 0
for row in rows:
image_path = activepath+rows[i][0]
name = rows[i][1]
performance = rows[i][2]
project = rows[i][3]
date = rows[i][4]
self.e10 = CustomWidget(self.frame, image_path, name, performance, project, date)
self.e10.grid(row=1+i,column=0, columnspan=2)
i+=1
Try using the isinstance built-in function to check the class (as shown below):
for child in self.frame.winfo_children():
if not (isinstance (child, Label) or isinstance (child, Entry)):
child.destroy ()
This will destroy any widget if they are not a Label and Entry. However, it cannot distinguish between different Label widgets (for example) and will leave BOTH.
I have three columns in my wx.ListCtrl(size=(-1,200)). I would like the columns to fill up the width of the ListCtrl after its created. Ideally, the first column can expand to fill up the extra space available. The second and third columns don't need to expand, and preferably will not change in width (formatting ocd).
Currently, each ListCtrl column is set up using (width=-1).
I have a feeling I can use this section of the code to my advantage...
# Expand first column to fit longest entry item
list_ctrl.SetColumnWidth(0, wx.LIST_AUTOSIZE)
Pseudo-code (perhaps):
# After wx.ListCtrl creation
Get width of ListCtrl control
Get width of each ListCtrl column
Calculate unused width of ListCtrl
Set first column width to original width + unused width
Added:
Given the following example, I don't understand how to initiate the autowidthmixin. Currently, I am trying to put the listctrl inside a foldpanel. The foldpanel is a class and a function within the class creates the listctrl. I am not even confident that this can be done given the structure of my code at the moment!
class MyPanel(wx.Panel):
def __init__(self, parent, dictionary):
self.dictionary = dictionary
"""Constructor"""
wx.Panel.__init__(self, parent)
# Layout helpers (sizers) and content creation (setPanel)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.mainSizer)
list_ctrl = self.setPanel()
self.mainSizer.Add(list_ctrl, 0, wx.ALL | wx.EXPAND, 5)
self.GetSizer().SetSizeHints(self)
def setPanel(self):
index = 0
list_ctrl = wx.ListCtrl(self, size=(-1, 200),
style=wx.LC_REPORT | wx.BORDER_SUNKEN)
list_ctrl.InsertColumn(0, "Variable", format=wx.LIST_FORMAT_LEFT, width=-1)
list_ctrl.InsertColumn(1, "x", format=wx.LIST_FORMAT_RIGHT, width=-1)
list_ctrl.InsertColumn(2, u"\u03D0", format=wx.LIST_FORMAT_RIGHT, width=-1)
for key, value in self.dictionary.iteritems():
list_ctrl.InsertStringItem(index, str(key))
list_ctrl.SetStringItem(index, 1, ("%.2f" % value[0]))
list_ctrl.SetStringItem(index, 2, ("%.8f" % value[1]))
index += 1
list_ctrl.SetColumnWidth(0, wx.LIST_AUTOSIZE)
list_ctrl.SetColumnWidth(1, wx.LIST_AUTOSIZE)
list_ctrl.SetColumnWidth(2, wx.LIST_AUTOSIZE)
return list_ctrl
You need to use the ListCtrlAutoWidthMixin mixin class. The wxPython demo application has an example in the ListCtrl demo. According to the documentation, you can use its setResizeColumn method to tell it which column to resize. The default is the last column.
EDIT (07/05/2012): In your code, create a ListCtrl class similar to the one in the demo. It would look something like this:
class TestListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
def __init__(self, parent, ID, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0):
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
listmix.ListCtrlAutoWidthMixin.__init__(self)
self.setResizeColumn(0)
Then when you instantiate it, you'd just call list_ctrl = TestListCtrl(arg1, arg2...argN)
Note that I included a call to setResizeColumn() in my code above. It's not tested, but it should work.
I'm rewriting this post to clarify some things and provide a full class definition for the Virtual List I'm having trouble with. The class is defined like so:
from wx import ListCtrl, LC_REPORT, LC_VIRTUAL, LC_HRULES, LC_VRULES, \
EVT_LIST_COL_CLICK, EVT_LIST_CACHE_HINT, EVT_LIST_COL_RIGHT_CLICK, \
ImageList, IMAGE_LIST_SMALL, Menu, MenuItem, NewId, ITEM_CHECK, Frame, \
EVT_MENU
class VirtualList(ListCtrl):
def __init__(self, parent, datasource = None,
style = LC_REPORT | LC_VIRTUAL | LC_HRULES | LC_VRULES):
ListCtrl.__init__(self, parent, style = style)
self.columns = []
self.il = ImageList(16, 16)
self.Bind(EVT_LIST_CACHE_HINT, self.CheckCache)
self.Bind(EVT_LIST_COL_CLICK, self.OnSort)
if datasource is not None:
self.datasource = datasource
self.Bind(EVT_LIST_COL_RIGHT_CLICK, self.ShowAvailableColumns)
self.datasource.list = self
self.Populate()
def SetDatasource(self, datasource):
self.datasource = datasource
def CheckCache(self, event):
self.datasource.UpdateCache(event.GetCacheFrom(), event.GetCacheTo())
def OnGetItemText(self, item, col):
return self.datasource.GetItem(item, self.columns[col])
def OnGetItemImage(self, item):
return self.datasource.GetImg(item)
def OnSort(self, event):
self.datasource.SortByColumn(self.columns[event.Column])
self.Refresh()
def UpdateCount(self):
self.SetItemCount(self.datasource.GetCount())
def Populate(self):
self.UpdateCount()
self.datasource.MakeImgList(self.il)
self.SetImageList(self.il, IMAGE_LIST_SMALL)
self.ShowColumns()
def ShowColumns(self):
for col, (text, visible) in enumerate(self.datasource.GetColumnHeaders()):
if visible:
self.columns.append(text)
self.InsertColumn(col, text, width = -2)
def Filter(self, filter):
self.datasource.Filter(filter)
self.UpdateCount()
self.Refresh()
def ShowAvailableColumns(self, evt):
colMenu = Menu()
self.id2item = {}
for idx, (text, visible) in enumerate(self.datasource.columns):
id = NewId()
self.id2item[id] = (idx, visible, text)
item = MenuItem(colMenu, id, text, kind = ITEM_CHECK)
colMenu.AppendItem(item)
EVT_MENU(colMenu, id, self.ColumnToggle)
item.Check(visible)
Frame(self, -1).PopupMenu(colMenu)
colMenu.Destroy()
def ColumnToggle(self, evt):
toggled = self.id2item[evt.GetId()]
if toggled[1]:
idx = self.columns.index(toggled[2])
self.datasource.columns[toggled[0]] = (self.datasource.columns[toggled[0]][0], False)
self.DeleteColumn(idx)
self.columns.pop(idx)
else:
self.datasource.columns[toggled[0]] = (self.datasource.columns[toggled[0]][0], True)
idx = self.datasource.GetColumnHeaders().index((toggled[2], True))
self.columns.insert(idx, toggled[2])
self.InsertColumn(idx, toggled[2], width = -2)
self.datasource.SaveColumns()
I've added functions that allow for Column Toggling which facilitate my description of the issue I'm encountering. On the 3rd instance of this class in my application the Column at Index 1 will not display String values. Integer values are displayed properly. If I add print statements to my OnGetItemText method the values show up in my console properly. This behavior is not present in the first two instances of this class, and my class does not contain any type checking code with respect to value display.
It was suggested by someone on the wxPython users' group that I create a standalone sample that demonstrates this issue if I can. I'm working on that, but have not yet had time to create a sample that does not rely on database access. Any suggestions or advice would be most appreciated. I'm tearing my hair out on this one.
Are you building on the wxPython demo code for virtual list controls? There are a couple of bookkeeping things you need to do, like set the ItemCount property.
One comment about your OnGetItemText method: Since there's no other return statement, it will return None if data is None, so your test has no effect.
How about return data or "" instead?
There's a problem with the native object in Windows. If GetImg returns None instead of -1 the list has a problem with column 1 for some reason. That from Robin over on the Google Group post for this issue.