Using a GTK# TreeView Widget in C# – Very Simply

I’ve been experimenting with using GTK# and C# to build GUI applications which will run on Linux, Windows and Mac. Monodevelop has a graphical form designer for Gtk# front ends, but it’s not as simple as building WinForms applications under Visual Studio. An example is the TreeView widget – it’s designed from an MVC perspective, with a ListStore or TreeStore object containing the model (or data), various objects collectively representing the view (columns, cells, cell renderers etc) and the controller functions allow you to sort and filter the data. All the building needs to be done in code rather than at design time. This is a lot to wrap your head around if you simply want to display some data in a tabular format, as I did, when I wanted to display the parsed contents of a log file!

To make this easier for me in future, I’ve written a small class which hides just about everything. Read on…

Slap a TreeView widget on your form, then in the form’s constructor, ¬†you can simply do this:

SimpleTreeView tree=new SimpleTreeView(controlOnForm);
tree.AddColumn("Col1"); tree.AddColumn("Col2"); // adding columns
tree.AddData(string, ...); // add data, one string per column
tree.Finish(); // finalise the process with this

All cells are string data only, there’s no sorting or anything. Just a grid of data. The code for the class is below. Yes, it’s rough around the edges, yes, it could do with some tidying, and I’m sure much improvement, but that’s left as an exercise for the reader :) …

using System;
using System.Collections.Generic;

namespace tim{
  // MCD Marengo 2012
  // This class assumes that you have a GTK tree view on a form - just
  // instantiate this class with that control and this does all the
  // hard stuff. Note all columns are treated as text.
  // To use:
  // SimpleTreeView tree=new SimpleTreeView(controlOnForm);
  // tree.AddColumn("Col1"); tree.AddColumn("Col2"); // adding columns
  // tree.AddData(string, ...); // add data, one string per column
  // tree.Finish(); // finalise the process with this
  public class SimpleTreeView{
    List<Gtk.TreeViewColumn> _cols;
    List<Gtk.CellRendererText> _cells;
    Gtk.TreeView _tree;
    Gtk.ListStore _list=null;
    bool _colsAdded=false;

    public SimpleTreeView(Gtk.TreeView tree){
      _tree=tree;
      _cols=new List<Gtk.TreeViewColumn>();
      _cells=new List<Gtk.CellRendererText>();
    }

    public void AddColumn(string colName){
      if(_colsAdded == true){
        // can't add columns after you've started adding data.
        throw new Exception("Cannot add columns after data has been added");
      }
      _cols.Add(new Gtk.TreeViewColumn()); 
      _cols[_cols.Count - 1].Title = colName;
    }

    public void AddData(params string[] fieldData){
      // it is assumed when this is called that the user has finished adding columns.
      _colsAdded = true;
      if(_cols.Count != fieldData.Length){
        throw new Exception("Mismatch on number of columns defined and items of data passed");
      }

      if(_list == null){
        Type[] t = new Type[_cols.Count];
        for(int n=0; n<_cols.Count; ++n){
          t[n] = typeof(string);
        }
        _list = new Gtk.ListStore(t);
      }
      _list.AppendValues(fieldData);
    }

    public void Finish(){
      for(int n=0; n<_cols.Count; ++n){
        _cells.Add(new Gtk.CellRendererText());
        _cols[n].PackStart(_cells[_cells.Count-1], true);
      }
      for(int n=0; n<_cols.Count; ++n){
        _cols[n].AddAttribute(_cells[n], "text", n);
      }
      for(int n=0; n<_cols.Count; ++n){
        _tree.AppendColumn(_cols[n]);
      }
      _tree.Model=_list;
    }

    public List<string> GetSelected(){
      // returns the cols of the current line
      Gtk.TreeSelection selection =_tree.Selection;
      Gtk.TreeModel model;
      Gtk.TreeIter iter;
      List<string> rc=new List<string>();
      if(selection.GetSelected(out model, out iter)){
        for(int n=0; n<_cols.Count; n++){
          rc.Add(model.GetValue(iter,n).ToString());
        }
      }
      return rc;
    }
  }
}

Comments 11

  1. Peter G Holm wrote:

    My compiler (I use monodev 4.0) gives me a CS0201 from this part of the code.

    if(_list == null){
    Type[] t = new Type[_cols.Count];
    for(int n=0; n<_cols.Count; ++n){
    t[n] = typeof(string);
    }

    The compiler halt at the for statement. But I belivive it’s the row above it complains about.

    *sc0201 Only assignment, call, increment, decrement, and new object expressions can be used as a statement

    Posted 24 Apr 2013 at 08:05
  2. Martyn wrote:

    Interesting, does this tiny test program fail as well? It works for me in MonoDevelop 3.0.3.2 and Visual Studio 2012:

    Type[] t = new Type[3];
    t[0] = typeof(string);
    t[1] = typeof(int);
    t[2] = typeof(decimal);
    foreach (var x in t)
    {
    Console.WriteLine(x);
    }

    Posted 24 Apr 2013 at 08:26
  3. Peter G Holm wrote:

    No, it works well.
    Just to simplivatcate the problem with the code I coied from you, I added the class to the same file as the consoleapplcaton. Without tryng to acces the class, so no errors could be because of that.

    Still the same problem.
    If you wish I can send the project and the build log.

    Posted 24 Apr 2013 at 11:37
  4. Peter G Holm wrote:

    I found that, afterall – the problem is in the for-statement.

    I commented out _cols.Count; .

    for(int n=0; n< /* _cols.Count;*/ ++n).

    AFAIK, that does no harm.?

    But now it halts on the List declaration at the very beginning.

    List _cols;
    ***CS0305: Using the generic type `Stack’ requires `1′ type argument(s)***

    Maybe i am not implanting the code right. I am very new to MonoDevelop, and to gui-programming in general. So any advices would be avesome.

    Is it to much ask for a working project.?

    Other than that- your code seems to be very good, and I am sure that I could earn a lot from it If I only can get it to work,

    Posted 24 Apr 2013 at 18:11
  5. Peter G Holm wrote:

    Of course the for state-ment is like this.

    for(int n=0; n</*_cols.Count;*/ ++n){

    the n< thing was a typo. If you want, edit away the typo from the last post and remove this one.

    Posted 24 Apr 2013 at 18:14
  6. Martyn wrote:

    Looking at the code in Pastebin which you sent privately it looks like you’re hitting problems in the code you’re copying from this site where the “less than” symbol (<) is being copied as “&lt;” (which is an HTML code for it).

    Instead of commenting out parts of that for loop, search your code for the string “&lt;” and replace it with “<”.

    The confusing thing is when you post your code here any HTML entities such as &lt; get displayed as the character they represent :)

    Posted 24 Apr 2013 at 19:56
  7. Peter G Holm wrote:

    That was very funny. In my browser I see “&lt” in your for-loops.

    After reading you last post here – I installed Opera – same result. Chromium – Same result. And finally Google Chrome, and now I see your code as it should be :).

    But Ok, I copie-pasted the code from Chrome. And now no errors within the foor-loops.

    But the compiler still complains about the
    List _cols;
    List _cells;
    part
    giving the error:
    “Error CS0305: Using the generic type `System.Collections.Generic.List’ requires `1′ type argument(s) (CS0305) (A6)”

    Posted 24 Apr 2013 at 21:09
  8. Martyn wrote:

    Sorry Peter, I’m starting to see the problem here – not sure how but some of the code is lost because of HTML interpretation.

    As we are using System.Collections.Generic at the top that’s a clue that we’re not using non-generic collections, and those lines you quote have no type parameter.

    They should actually read:

    List<Gtk.TreeViewColumn> _cols;
    List<Gtk.CellRendererText> _cells;

    As angle brackets are used in HTML to specify tags I can see why they disappeared! I’m going to replace the whole code section as I see other bits like this aren’t displaying.

    Edit: The code in the article has now been replaced.

    Posted 25 Apr 2013 at 08:30
  9. Peter G Holm wrote:

    That sound great.
    If you have further patience with me :), so please can you tell
    where is the ‘Forms Constructor ‘ , you are referring to.?
    Also I would like to know, where am I supposed to add your class.

    Finally, this
    ‘SimpleTreeView tree=new SimpleTreeView(controlOnForm);’
    the controlOnForm, is that the same as the name I put in properties-pane in stetic designer.?
    If so, how am I supposed to access it – as it iss burried within the autogenerated gtk-gui/MainWindow – file.?

    Posted 25 Apr 2013 at 13:33
  10. Martyn wrote:

    Assuming you’ve got a form called MainWindow then in MainWindow.cs in your project explorer you’ll see some code like this:

    using System;
    using Gtk;
    
    public partial class MainWindow: Gtk.Window
    {	
    	public MainWindow (): base (Gtk.WindowType.Toplevel)
    	{
    		Build ();
    	}
    	
    	protected void OnDeleteEvent (object sender, DeleteEventArgs a)
    	{
    		Application.Quit ();
    		a.RetVal = true;
    	}
    }
    

    The function with the Build() in it is the class’s constructor (i.e. the form’s constructor). You can put your code in there after the Build() line.

    Any controls you’ve added to the form (in this case the TreeView) will be in scope here (note it’s a partial class, the rest of the class is built by the designer) so, assuming you’ve given the name “tvTest” to it in the stetic designer then you can use

    SimpleTreeView tree=new SimpleTreeView(tvTest);
    
    Posted 25 Apr 2013 at 14:20
  11. Peter G Holm wrote:

    Thank you sir.! Your code works well and it seems that I can use it in my project. It makes it much easier to work with the treeview.

    In case that other newcomers like me are in doubt how to use your class, maybe it’s a good idea you tell the newcomer that you have written about it in the blog.

    I guess one side-effect of that could be that more people also writes in the blog :-)

    Posted 25 Apr 2013 at 19:36

Post a Comment

Your email is never published nor shared. Required fields are marked *