2

I'm using a TreeView in GTK# and now I need to use a custom cell renderer.

Although I got almost everything to work, there's one missing piece: The cell renderer must know what to render.

The data to render is a custom class. I want to retrieve a field from that class.

Here's what I have so far

public class TreeItemCellRenderer : CellRenderer
{
    public override void GetSize (Widget widget, ref Gdk.Rectangle cell_area, out int x_offset, out int y_offset, out int width, out int height)
    {
        base.GetSize (widget, ref cell_area, out x_offset, out y_offset, out width, out height);

        height = 16;
    }

    protected override void Render (Gdk.Drawable window,
        Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gdk.Rectangle expose_area, CellRendererState flags)
    {
        base.Render (window, widget, background_area, cell_area, expose_area, flags);

        using (var layout = new Pango.Layout(widget.PangoContext)) {
            layout.Alignment = Pango.Alignment.Left;
            layout.SetText("Hello, World!");

            StateType state = flags.HasFlag(CellRendererState.Selected) ?
                widget.IsFocus ? StateType.Selected : StateType.Active : StateType.Normal;

            window.DrawLayout(widget.Style.TextGC(state), cell_area.X, cell_area.Y, layout);
        }
    }
}

Now I want to draw my custom field instead of "Hello, World!". Some googling suggested SetProperty/GetProperty is what I need, but:

var col = new TreeViewColumn("TreeItem", renderer);
col.AddAttribute(renderer, "tree-item", 0);

Throws an error on runtime since TreeItemCellRenderer has no property named "tree-item". Also, it is worth noting that SetProperty is not virtual.

So how should I do it?

gpoo
  • 8,408
  • 3
  • 38
  • 53
luiscubal
  • 24,773
  • 9
  • 57
  • 83

3 Answers3

3

Actually, the only problem is that defining the value/data to render in your custom cell renderer is not done in the renderer itself, but outside, in a listener method.

In your widget class, where you define the TreeView and its columns and CellRenderers, you can also define a TreeCellDataFunc, which is nothing but a wickedly hidden name for a listener function that is called before a cell is rendered. But its name already has the "Data" inside and that is what you're supposed to do within that function: Supply the data to render.

TreeItemCellRenderer cellRenderer = new TreeItemCellRenderer();
column.SetCellDataFunc(cellRenderer, new Gtk.TreeCellDataFunc(AwesomeDataFunction));

/**
* Supplies the TreeItemCellRenderer with data for the current cell.
* */
private void AwesomeDataFunction(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
{
    (cell as TreeItemCellRenderer).<some self-declared property> = <any fitting value>;
}

You will likely want use the iter and model to get the data that you want. The actual Render() function of the CellRenderer is called after that function.

All in all, I think that whole TreeView/CellRenderer model is a mess. While it does work and you can do most things with it, it is completely unclear what you are supposed to do to achieve your goals. That one TreeView tutorial is okay, but really only shows the tip of the iceberg. Why no Widgets are used for the cell rendering is beyond me. It would have been far easier.

TheSHEEEP
  • 2,961
  • 2
  • 31
  • 57
1

You just declare a property and use it like in this example.

class CellRendererSimple : CellRenderer
{

//property asigned to by treeviewcolumn
//eg. TreeViewColumn tvc = new TreeViewColumn("Column 0", new CellRendererSimple(),"tree_item",0);
[GLib.Property("tree_item")] //this line seems to be nessasary for the property to be recognised by the treeviewcolumn.
    public string tree_item
    {
        get;
        set;
    }

    protected override void Render(Gdk.Drawable window, Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gdk.Rectangle expose_area, CellRendererState flags)
    {
        //this seems to be the minimum to render text
        GC gc = widget.Style.TextGC(StateType.Normal);
        Pango.Layout layout = new Pango.Layout(widget.PangoContext);
        layout.SetText(tree_item);//add the text to thelayout.
        //this is the actuall rendering
        window.DrawLayout(gc, expose_area.X, expose_area.Y, layout);
    }
}
bigM
  • 11
  • 2
0

I haven't found an actual good answer, but I did find a workaround.

My goal has always been to display information while keeping a "pointer" to the class that owns that value. So here's what I did:

  1. Declare only one column (the "label")
  2. Append rows, each with two columns - the label to display and the class instance. That way the first row is displayed but the second is hidden

Now, getting the second column of each row will give me the value I want, without having to deal with custom renderers and without having random data appearing in the tree.

I know this doesn't answer the question, but it does solve my particular problem. In case someone else ends up in this page, I hope this information is still helpful. But if you do find a better solution, feel free to post it anyway.

luiscubal
  • 24,773
  • 9
  • 57
  • 83