Setting a single row of a CellRendererToggle to in

2019-06-27 04:06发布

问题:

On Gtk+3, I'm using a TreeModel to store nested information and I display it with a CellRendererText and a CellRendererToggle. The user can clic on every toggle button, and when there are nested ones and they are not in the same state, I want the ones of upper level to reflect the 'inconsistent' state. How can I set this property for one element ?

For more clarity, an example of what I want to achieve from the Transmission bittorrent client :

I know how to set all the buttons of the cell renderer to the inconsistent state with myCellRendererToggle.set_properties(inconsistent=True) but it seems I can't access one single element from here;

I know how to access a particular row of my TreeStore model, but I can only set «True» and «False» values.

My code is fairly close to the official documentation so you can help me with it : https://python-gtk-3-tutorial.readthedocs.org/en/latest/cellrenderers.html#cellrenderertoggle (I use a treeStore instead of a listStore)

And this is my code :

class HelloMyApp:

def __init__(self):

    # Set the Glade file
    self.builder = Gtk.Builder()
    self.builder.add_from_file(GLADEFILE)


    dic = {
        "on_button1_clicked" : self.btnValidate_clicked,
        "on_MainWindow_destroy" : self.quit,
        "on_window1_delete_event" : self.quit,

        }

    self.builder.connect_signals(dic)

    window = self.builder.get_object("window1")

    treeview1 = self.builder.get_object("treeview1")

    ######## This is my model : it stores a string and a boolean. #########
    self.treeModel = Gtk.TreeStore(str, bool)

    # Example on how to insert data in the model
    treeIter = self.treeModel.append(None, ['example one', True])
    self.treeModel.append(treeIter, [' simple elt', True])
    treeIter = self.treeModel.append(treeIter, ['example two', False])
    self.treeModel.append(treeIter, ['under example two', True])


    select = treeview1.get_selection()
    select.set_mode(Gtk.SelectionMode.BROWSE)

    select.connect("changed", self.on_tree_selection_changed, buf) 

    # Using one column of text and another column with the toggle buttons
    renderer = Gtk.CellRendererText()
    column = Gtk.TreeViewColumn("Title", renderer, text=0)
    treeview1.append_column(column)

    ######  Here is the CellRendereToggle  ################
    renderer_toggle = Gtk.CellRendererToggle()
    renderer_toggle.connect("toggled", self.on_cell_toggled)
    column_toggle = Gtk.TreeViewColumn("Installer", renderer_toggle, active=1)
    treeview1.append_column(column_toggle)

    treeview1.set_model(self.treeModel)


    window.show_all()


if __name__ == "__main__":

    HelloMyApp = HelloMyApp()
    Gtk.main()

Thanks !

edit : to answer Marcus : I can't figure it out, when I change the property of my CellRendererToggle inside the function, it changes every row.

edit for the solution : as Marcu pointed out, we have to set the property in every case, that's why I added the else part.

def cellRenderer_func(column, cellRenderer, treeModel, treeIter, userData):
    if 'cat' in treeModel.get_value(treeIter, 0): 
        # it happens only ones in my model, 
        # so here I am in a row I want to change to inconsistent.
        cellRenderer.set_property('inconsistent',True) 
        # I was expecting that changes the box of that row but it affects every row. 

    else: 
        cellRenderer.set_property('inconsistent', False) # and that's ok now.

I have to try again. I wonder… do we really have to do it manually, can't it be a feature out of the box of the treeView ?

回答1:

It is possible to set the property of one element via using a cell data function. Since I have done this only in C yet, I can only pass you the link to the documentation of PyGTK, I haven't seen the relevant docs for this feature for PyGObject yet.

For PyGTK is was documented here, this page gives you an example at the bottom, the use for cell data functions for PyGTK was also featured in this document.

If for example you would like to do the same as in Transmission, you could do the following: Since you receive the current iter as a parameter of the cell data function, you could loop through all of its children there and check the status of the children. You would then know which state to set at the parent node. Now the main point is that setting a property inside the cell data function will affect only this single cell, and not all elements of the treeview.

I can give you also a visual example for my own application:

I have the column "Value" in this treeview. If the value inside the column "Menu Element" is set to "enabled" and the value inside the column "Type" is "option", a checkbox is shown instead of text (I have highlighted such a row). The example image also shows an active search, which highlights search results. Both is possible with a cell data function to set the property of one element as you asked for in your question.

Edit

I've done some example code. The main point is that the cell property is always set for every cell. So not just "if ..., then set property" but "if, set property like this, else, set property like that". (It's based on the old PyGTK documentation, but it should clarify things anyway).

#!/usr/bin/env python

# example basictreeview.py

import pygtk
pygtk.require('2.0')
import gtk

class BasicTreeViewExample:

    def set_status(self, column, cell, model, iter):
        if 'inconsistent' in model.get_value(iter, 0):
            cell.set_property('inconsistent',True) 
        else:
            cell.set_property('inconsistent',False)
        return

    def delete_event(self, widget, event, data=None):
        gtk.main_quit()
        return False

    def __init__(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)

        self.window.set_title("Basic TreeView Example")

        self.window.set_size_request(200, 200)

        self.window.connect("delete_event", self.delete_event)

        self.treestore = gtk.TreeStore(str)

        for parent in range(4):
            piter = self.treestore.append(None, ['parent %i' % parent])
            for child in range(3):
                if child == 1:
                    self.treestore.append(piter, ['consistent'])
                else:
                    self.treestore.append(piter, ['inconsistent'])

        self.treeview = gtk.TreeView(self.treestore)

        self.tvcolumn0 = gtk.TreeViewColumn('Column 0')
        self.tvcolumn1 = gtk.TreeViewColumn('Column 1')

        self.treeview.append_column(self.tvcolumn0)
        self.treeview.append_column(self.tvcolumn1)

        self.text = gtk.CellRendererText()
        self.toggle = gtk.CellRendererToggle()

        self.tvcolumn0.pack_start(self.text, True)
        self.tvcolumn1.pack_start(self.toggle, True)

        self.tvcolumn0.add_attribute(self.text, 'text', 0)
        self.tvcolumn1.set_cell_data_func(self.toggle, self.set_status)

        self.window.add(self.treeview)

        self.window.show_all()

def main():
    gtk.main()

if __name__ == "__main__":
    tvexample = BasicTreeViewExample()
    main()


回答2:

I've had the same problem recently and found that a cell data function is not needed if you prefer to set the property directly from a column in your model. The updates needed in the code from the question that will make it happen should be as follows:

self.treeModel = Gtk.TreeStore(str, bool, bool)
...
column_toggle = Gtk.TreeViewColumn("Installer", renderer_toggle, active=1, inconsistent=2)

and then set the inconsistent value in the new column as needed.

As a hint about when to choose a cell data function or a new column in your model, take the following into account (Gtk+ By Example):

Your cell data function is going to be called every single time a cell in that (renderer) column is going to be rendered. Go and check how often this function is called in your program if you ever use one. If you do time-consuming operations within a cell data function, things are not going to be fast, especially if you have a lot of rows.



标签: python gtk gtk3