What signal can I catch to detect when a column changes size in a gtk.TreeView? I can't seem to find it in the docs.
gtk.TreeViewColumns aren't widgets so they unfortunately don't have a dedicated signal for size changes. But you can register a callback function that receives "width" change notifications:
def onColWidthChange(col, width):
# Note that "width" is a GParamInt object, not an integer
...
col.connect("notify::width", onColWidthChange)
In the example, col must be a gtk.TreeViewColumn object. If you don't initialize the columns in code, you can use gtk.TreeView.get_column to get these objects.
If you only need notifications when the treeview changes its size, you can use its "size-allocate" signal instead.
Related
I have a QTreeView widget on a QWizardPage (say, qwpage2) where the flags for one of the columns of information depends on the state of a QRadioButton on another QWizardPage (say, qwpage1).
The original properties of the treeview column are set in the initializePage method of qwpage2. If I Next > through the pages, everything works fine. However, if I < Back from qwpage2, change the radiobutton on qwpage1, and Next > to qwpage2, the flags are not updated.
I have something like the following (in PyQt5) on qwpage1:
rbtn1 = QRadioButton()
rbtn2 = QRadioButton()
self.registerField ("remove_checkbox", rbtn1)
self.registerField ("add_checkbox", rbtn2)
self.checked_choice.addButton(rbtn1)
self.checked_choice.addButton(rbtn2)
and on qwpage2
add_checkbox = self.field("add_checkbox")
p = QStandardItem()
if add_checkbox:
p.setFlags(p.flags() | Qt.ItemIsUserCheckable)
else:
p.setFlags(Qt.NoItemFlags)
How can I change the flag state (essentially the presence of checkboxes) associated with my treeview column if the page has been initialized previously?
Thank you for your help
The problem is not about whenever you go back to the page after going backward from the next one, as initializePage() is always called, no matter if the "previous" page was the previous or the next in the page index (which makes sense, as page order doesn't always have to follow the "plain index order", since it's returned from QWizard.nextId(), which by default calls QWizardPage.nextId() of the current page and could even return an index that's less than the current).
That said, there's one case for which initializePage() is only called once, and that happens if you set the QWizard.IndependentPages option. If you really need to use that, I think that the only alternative is to set values by overriding the page's showEvent(event), and only if not event.spontaneous() (otherwise it will always process everything even when the page is shown again after being minimized).
What really matters here is that the checkbox of an item delegate is usually shown if a Qt.CheckStateRole is set for its index, because setting Qt.ItemIsUserCheckable only means that the user can set the item state, not that the checkbox is visible.
In fact, unless some specific OS/QtStyle comes in action, setting that flag won't have any effect at all, and even if that happens, disabling it once the check state is set to any of the three states (Unchecked, PartiallyChecked or Checked) won't make any difference: it will be shown anyway.
While this might seem a bit counterintuitive, its behavior is clear from the source code, where the StyleOptionViewItem feature HasCheckIndicator is set to True when and only the data(role=CheckStateRole) is not "Null", as in None for Python.
Slighlty unrelated note: be aware that if you're using more advanced models (such as QSql ones), an "unset" value (as in a "Null" QVariant, eg. the field has no data set) is not always Python's None, but a "QPyNullVariant".
Considering the aforementioned notions, you should set the model and its items in the __init__ of the QWizardPage, then use initializePage only to set its flags.
class Page2(QtWidgets.QWizardPage):
def __init__(self, parent=None):
QtWidgets.QWizardPage.__init__(self, parent)
layout = QtWidgets.QGridLayout()
self.setLayout(layout)
self.tree = QtWidgets.QTreeView()
layout.addWidget(self.tree)
self.model = QtGui.QStandardItemModel()
self.tree.setModel(self.model)
self.model.dataChanged.connect(self.setCurrentState)
self.addCheckItem = QtGui.QStandardItem('item')
self.model.appendRow(self.addCheckItem)
# remember the default flags
self.defaultFlags = self.addCheckItem.flags()
# set the current "add_checkbox" value to None, which means that it
# has *no* state set at all, not even an Unchecked one
self.currentState = None
def setCurrentState(self, topLeft, bottomRight):
# remember the new check state
self.currentState = self.addCheckItem.checkState()
def initializePage(self):
if self.field('add_checkbox'):
# apply the new flags to allow the user to set the check state
self.addCheckItem.setFlags(
self.defaultFlags | QtCore.Qt.ItemIsUserCheckable)
# set the state if it has been previously set
if self.currentState is None:
self.addCheckItem.setCheckState(QtCore.Qt.Unchecked)
else:
self.addCheckItem.setCheckState(self.currentState)
else:
# prevent notifying setCurrentState() slot that we're changing the
# value, while still remembering the check state;
# note that blogking model signals is not a good practice, as it
# prevents the view to receive model changes, which usually results
# in painting, size, scrolling and mouse interaction issues, but we
# can ignore that in this case, since those changes are only taken
# into account once the view is shown, assuming that the view will
# update once it will be shown, and that will only happen *after*
# initializePage returns
self.model.blockSignals(True)
self.addCheckItem.setData(None, QtCore.Qt.CheckStateRole)
self.model.blockSignals(False)
Given that I have an instance of QTableView (or a subclass thereof), connected to a subclass of QAbstractTableModel (or functionally equivalent model + view), is it possible to get a list of the indexes of all rows currently visible to the user (i.e. those not falling outside the current scroll range)?
It would be great if the solution scales to different window/screen sizes.
You can obtain the item position using QAbstractItemView::visualRect. It is in the viewport coordinates, so we need to check if it is in the viewport rect. Here is an example:
viewport_rect = QRect(QPoint(0, 0), self.view.viewport().size())
for row in range(0, self.model.rowCount()):
rect = self.view.visualRect(self.model.index(row, 0))
is_visible = viewport_rect.intersects(rect)
This example works only with one column, but you can add a for loop for iterate over all columns.
In this code items are considered visible if they are partially visible. If you want to get only items that are completely visible, use contains instead of intersects.
CODE: http://pastebin.com/W4uXmazw
I would like to memorize how to get values from any wx widget with event handling after clicking a wx.Button.
In my program i have two fields, the new filename and the contents.
What are the steps i have to take in order to get the values from each field?
From there, i can use pythons f.open and f.write methods to complete my application.
Thanks!
If you want to get value of a widget, then you need to make that widget accessible throughout the entire class. To do that, you need to make the variable for the widget into an instance variable. So instead of adding the text control directly to the sizer, you'll want to do something like this:
self.newfilename = wx.TextCtrl(panel,-1), 0, wx.TOP, 5)
self.contents = wx.TextCtrl(panel,-1,size=(390,150),style = wx.TE_MULTILINE|wx.TE_PROCESS_TAB)
Then in your button's event handler, you can just do something like this:
valueOne = self.newfilename.GetValue()
contents = self.contents.GetValue()
The other way to do it would be to use your panel. If you use "self.panel", then you could grab all its children via its GetChildren method and then iterate over the list and use Python's "isinstance" builtin to check what kind of widget you're accessing. If you have set the widget's name, you can check that too.
What is the approach to update widgets in a wxPanel based on events from other controls on same panel?
Scenario 1 is updating the list of a comboBox based on what has been selected from another comboBox , where both are in same panel.
Scenario 2 is showing a new control/widget in a panel based on an event.
Basically creating new controls is easy but I dont know how to refresh/update my panel so immedialtly shows them.
Scenario 1
To change the choices of a combobox self.cbx you can use any of the following methods:
self.cbx.SetItems(choices) where choices is the full list of choices.
self.cbx.SetString(n, string) that sets the string at position n.
InsertItems(items, pos) Inserts the list of strings in the items argument into the list box before the position in the pos argument.
Note that the method Set(choices) of listboxes does not exist for the list in comboboxes. You must use SetItems(choices) instead (this is not clearly indicated in some textbooks).
If you want these changes to occur as a result of a selection in another combobox self.cbx_1 , just get the event (self.Bind(wx.EVT_COMBOBOX, on_combo_1, self.cbx_1)) of the first combobox, process your data as you like in the corresponding self.on_combo method and use one of the above methods to modify the second combobox.
For example:
def on_combo_1(self, evt):
"append cbx_1 selection to cbx if not already in cbx"
selection = self.cbx_1.GetStringSelection()
cbx_choices = self.cbx.GetItems()
if selection not in cbx_choices:
cbx_choices.append(selection)
self.cbx.SetItems(cbx_choices)
The fact the comboboxes are in the same or different panel is irrelevant for that.
Scenario 2
Normally you put your widgets inside sizers. To hide or made visible elements on the sizer you call the methods Show, Hide or Layout:
Show(self, item, show=True, recursive=false)
Shows or hides an item managed by the sizer. To make a sizer item disappear or reappear, use Show followed by Layout. The item parameter can be either a window, a sizer, or the zero-based index of the item. Use the recursive parameter to show or hide an item in a subsizer. Returns True if the item was found.
Hide(self, item, recursive)
A convenience method for Show (item, False, recursive).
Layout(self)
This method will force the recalculation and layout of the items controlled by the sizer using the current space allocated to the sizer. Normally this is called automatically from the owning window's EVT_SIZE handler, but it is also useful to call it from user code when one of the items in a sizer change size, or items are added or removed.
References: wxPython in Action, Noel Rappin and Robin Dunn
For scenario one, you'd do something like the following (assuming the first combobox is bound to its EVT_COMBOBOX:
value = self.cboOne.GetValue()
if value == "something":
self.cboTwo.SetItems(someList)
For showing a new widget, you could create it and then use Show()/Hide() as necessary. If the widget is in a sizer, then use the Sizer's Append or Insert methods. It also has a Detach method that can be used to hide widgets or you just call Hide itself. See the documentation for more information: http://www.wxpython.org/docs/api/wx.Sizer-class.html
I am trying to code the following: Two Columns. One contains a itemId, the other one contains a typeId. I want to render the itemId only when the typeId equals a specific value.
class IDRenderer(gtk.CellRendererText):
def __init__(self):
gtk.CellRendererText.__init__(self)
def do_render(self,window, widget, background_area, cell_area, expose_area, flags):
if ----} Condition to ask for value of the typeId - Cell {-----:
gtk.CellRendererText.do_render(self, window, widget, background_area, cell_area,
expose_area, flags)
gobject.type_register(IDRenderer)
I don't know how to get the iter of the currently rendered row which i need to determine the value of the typeId. Is this even possible?
I now found out, thanks to a nice guy on #pygtk on gimpIRC:
You can do that, with binding so called cell data functions to the corresponding gtk.TreeViewColumn as done here in this example
def renderId(celllayout, cell, model, iter):
if model.get_value(iter,1) == 3:
cell.set_property('visible',True)
else:
cell.set_property('visible',False)
treeviewcolumn = gtk.TreeViewColumn()
renderer = gtk.CellRendererText()
treeviewcolumn.add_attribute(renderer,'text',0)
treeviewcolumn.set_cell_data_func(renderer,renderId)
I ommited some code relevant to render a complete treeview, but i think it shows what i wanted to do and how to do it.
The column renderes the value in the first column (0) of the model only if the value in the second modelcolumn (1) equals 3
I hope this could help someone some time.
It's not possible as far as I know. You need to use properties of the custom renderer which will be set automatically by the code calling the rendering function. (Like the text property of CellRendererText -- the rendering code doesn't get the text from the tree model, but the tree model sets the text property of the renderer before calling the rendering code.)