Make column width take up available space in wxPython ListCtrl - python

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.

Related

How can I print a QTableWidget by clicking on QPushButton

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)

Make only some rows bold in a Gtk.TreeView

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.

PyQt: Initialize default values for reset

I have a MainUI, that has a few buttons, line-edits and checkboxes. Most of the widgets I have set with a given state - e.g. all checkboxes are checked by default, and all line-edit fields have a default value of "1.0".
Currently I have a pushbutton called "reset everything". As its name implies, any changes made in these checkboxes or line-edit fields will be reverted to be checked and have a value of "1.0".
One way I can think of resetting the values, is by creating a function where I have to re-type the variable names of the affected widgets, along with their default state - which is similar to what I did when I created them. But I don't think this is a practical method.
My question here is: what is the best way for me to store the default values and then revert them?
So far I have created a class that contains the creation of the main ui:
class MainUI(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setWindowTitle('UI MANAGER')
self.setModal(False)
self.init_main_ui()
self.resize(QtCore.QSize(600, 350))
# UI-Configurations
def init_main_ui(self):
self.check1 = QtGui.QCheckBox("chk_box1")
self.check2 = QtGui.QCheckBox("chk_box2")
self.check3 = QtGui.QCheckBox("chk_box3")
self.check1.setChecked(True)
self.check2.setChecked(True)
self.check3.setChecked(True)
self.max_label = QtGui.QLabel("MIN Val")
self.max_input = QtGui.QLineEdit("0.0")
self.min_label = QtGui.QLabel("MAX Val")
self.min_input = QtGui.QLineEdit("1.0")
...
The simplest approach would be to refactor your existing code so that the initial values are set by a separate method:
class MainUI(QtGui.QDialog):
...
def init_main_ui(self):
self.check1 = QtGui.QCheckBox("chk_box1")
self.check2 = QtGui.QCheckBox("chk_box2")
self.check3 = QtGui.QCheckBox("chk_box3")
self.max_label = QtGui.QLabel("MIN Val")
self.max_input = QtGui.QLineEdit()
self.min_label = QtGui.QLabel("MAX Val")
self.min_input = QtGui.QLineEdit()
...
self.reset_main_ui()
def reset_main_ui(self):
self.check1.setChecked(True)
self.check2.setChecked(True)
self.check3.setChecked(True)
self.max_input.setText("0.0")
self.min_input.setText("1.0")
...

double valued gauge in wxpython

Does anyone know of a double valued gauge in wxPython? I would like to show the range of a set of data that has been selected, I have the max and min values of the gauge and then I'd like to highlight the region of the gauge between two values. The normal wx.Gauge highlights from the gauge minimum (always the bottom of the gauge) to the set value.
Thanks
I'm not aware of any built-in widgets with the appearance of wx.Gauge that can do what you're looking for.
One option with a different appearance is RulerCtrl, which can give you two value indicators on a scale with whatever range you want. It just takes a bit of extra work to adapt it since not all of its properties are exposed in the listed methods. Quick example:
import wx
import wx.lib.agw.rulerctrl as rc
class MinMax(rc.RulerCtrl):
def __init__(self, parent, range_min, range_max, orient=wx.HORIZONTAL):
rc.RulerCtrl.__init__(self, parent, orient)
self.SetRange(range_min, range_max)
self.LabelMinor(False)
self.range_min = range_min
self.range_max = range_max
self.AddIndicator(wx.NewId(), range_min)
self.AddIndicator(wx.NewId(), range_max)
self.Bind(rc.EVT_INDICATOR_CHANGING, self.OnIndicatorChanging)
def GetMinIndicatorValue(self):
return self._indicators[0]._value
def GetMaxIndicatorValue(self):
return self._indicators[1]._value
def SetMinIndicatorValue(self, value):
# Value must be within range and <= Max indicator value.
if value < self.range_min or value > self.GetMaxIndicatorValue():
raise ValueError('Out of bounds!')
self._indicators[0]._value=value
self.Refresh()
def SetMaxIndicatorValue(self, value):
# Value must be within range and >= Min indicator value.
if value > self.range_max or value < self.GetMinIndicatorValue():
raise ValueError('Out of bounds!')
self._indicators[1]._value=value
self.Refresh()
def OnIndicatorChanging(self, evt):
# Eat the event so the user can't change values manually.
# Do some validation here and evt.Skip() if you want to allow it.
# Related: EVT_INDICATOR_CHANGED
pass
class MainWindow(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.minmax = MinMax(self, 0, 100)
self.minmax.SetSpacing(20)
self.minmax.SetMinIndicatorValue(30)
self.minmax.SetMaxIndicatorValue(84)
self.Show()
app = wx.App(redirect=False)
frame = MainWindow(None, wx.ID_ANY, "Range Indicator")
app.MainLoop()

wxPython ListCtrl Column Ignores Specific Fields

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.

Categories

Resources