views:

39

answers:

1

I have read the "GtkSharp TreeView tutorial" wherein the author describes how to setup and use a TreeModelFilter for an underlying ListStore ( under the tutorial section "Filtering Data"). The technique doesn't seem to work for an underlying hierarchical TreeStore. I want to filter a multilevel TreeStore and show the results in a TreeView. Its giving me a real hard time. Are there any tutorials, samples, or suggestions for doing it ?

Following is the code. Its basically the same code as the tutorial except for changes to deal with construction and population of a TreeStore rather than a ListStore. {The TreeStore is used to save "names" and "email addresses" of contacts , divided into (and saved as) children of the roots "friends" and "relatives" }

// compilation requires references to:
// gtk-sharp, atk-sharp and glib-sharp


using System;
using Gtk;

public class TreeViewExample
{
    public static void Main()
    {
        Gtk.Application.Init();
        new TreeViewExample();
        Gtk.Application.Run();
    }

    Gtk.Entry filterEntry;
    Gtk.TreeModelFilter filter;

    public TreeViewExample()
    {
        // Create a Window
        Gtk.Window window = new Gtk.Window("TreeView Example");
        window.SetSizeRequest(500, 200);
        window.DeleteEvent += delegate { Application.Quit(); };

        // Create an Entry used to filter the tree
        filterEntry = new Gtk.Entry();

        // Fire off an event when the text in the Entry changes
        filterEntry.Changed += OnFilterEntryTextChanged;

        // Create a nice label describing the Entry
        Gtk.Label filterLabel = new Gtk.Label("Search:");

        // Put them both into a little box so they show up side by side
        Gtk.HBox filterBox = new Gtk.HBox();
        filterBox.PackStart(filterLabel, false, false, 5);
        filterBox.PackStart(filterEntry, true, true, 5);

        // Create our TreeView
        Gtk.TreeView tv = new Gtk.TreeView();

        // Create a box to hold the Entry and Tree
        Gtk.VBox box = new Gtk.VBox();

        // Add the widgets to the box
        box.PackStart(filterBox, false, false, 5);
        box.PackStart(tv, true, true, 5);
        window.Add(box);

        //setting up columns and renderers
        Gtk.TreeViewColumn nameColumn = new Gtk.TreeViewColumn { Title = "Name" };
        Gtk.CellRendererText nameCell = new Gtk.CellRendererText();
        nameColumn.PackStart(nameCell, true);
        Gtk.TreeViewColumn emailColumn = new Gtk.TreeViewColumn { Title = "Email" };
        Gtk.CellRendererText emailCell = new Gtk.CellRendererText();
        emailColumn.PackStart(emailCell, true);

        // Add the columns to the TreeView
        tv.AppendColumn(nameColumn);
        tv.AppendColumn(emailColumn);

        // Tell the Cell Renderers which items in the model to display
        nameColumn.AddAttribute(nameCell, "text", 0);
        emailColumn.AddAttribute(emailCell, "text", 1);

        // Create a model that will hold two strings 
        Gtk.TreeStore contacts = new Gtk.TreeStore(typeof(string), typeof(string));

        // Add some hierarchical data
        Gtk.TreeIter treeiter;

        //first root
        treeiter = contacts.AppendValues("FRIENDS");

        // 2 children of first root
        contacts.AppendValues(treeiter, "Ogre", "[email protected]");
        contacts.AppendValues(treeiter, "Bee", "[email protected]");

        // second root
        treeiter = contacts.AppendValues("RELATIVES");

        // 3 children of second root
        contacts.AppendValues(treeiter, "Mommy", "[email protected]");
        contacts.AppendValues(treeiter, "Daddy", "[email protected]");
        contacts.AppendValues(treeiter, "tom", "[email protected]");

        filter = new Gtk.TreeModelFilter(contacts, null);
        // Specify the function that determines which rows to filter out and which ones to display
        filter.VisibleFunc = new Gtk.TreeModelFilterVisibleFunc(FilterTree);

        // Assign the filter as our treeview's model
        tv.Model = filter;

        // Show the window and everything on it
        window.ShowAll();
    }

    private void OnFilterEntryTextChanged(object o, System.EventArgs args)
    {
        // Since the filter text changed, tell the filter to re-determine which rows to display
        filter.Refilter();
    }

    private bool FilterTree(Gtk.TreeModel model, Gtk.TreeIter iter)
    {
        string contactname = model.GetValue(iter, 0).ToString();
        if (filterEntry.Text == "")
            return true;
        if (contactname.IndexOf(filterEntry.Text) > -1)
            return true;
        else
            return false;
    }
}

[I am using mono 2.6.4 /monodevelop 2.4 / gtk-sharp 2.12 on windows vista.]

A: 

It seems that when filtering rows in a tree model, a row is only visible if ALL its parents are visible too. Since your filter function hides the parent nodes, it will not display the child nodes even if the text matches. I have modified your code to illustrate this problem:

Now, one of the parent nodes begins with 'test'. If you type 'test' you'll see the filtering works correctly.

using System;
using Gtk;

public class TreeViewExample
{

public static void Main ()

{

    Gtk.Application.Init ();

    new TreeViewExample ();

    Gtk.Application.Run ();

}


Gtk.Entry filterEntry;
Gtk.TreeModelFilter filter;



public TreeViewExample ()
{

    // Create a Window

    Gtk.Window window = new Gtk.Window ("TreeView Example");

    window.SetSizeRequest (500,200);

    window.DeleteEvent+=delegate {Application.Quit();};



    // Create an Entry used to filter the tree

    filterEntry = new Gtk.Entry ();



    // Fire off an event when the text in the Entry changes

    filterEntry.Changed += OnFilterEntryTextChanged;



    // Create a nice label describing the Entry

    Gtk.Label filterLabel = new Gtk.Label ("Search:");



    // Put them both into a little box so they show up side by side

    Gtk.HBox filterBox = new Gtk.HBox ();

    filterBox.PackStart (filterLabel, false, false, 5);

    filterBox.PackStart (filterEntry, true, true, 5);



    // Create our TreeView

    Gtk.TreeView tv = new Gtk.TreeView ();



    // Create a box to hold the Entry and Tree

    Gtk.VBox box = new Gtk.VBox ();



    // Add the widgets to the box

    box.PackStart (filterBox, false, false, 5);

    box.PackStart (tv, true, true, 5);



    window.Add (box);





    //setting up columns and renderers





    Gtk.TreeViewColumn nameColumn = new Gtk.TreeViewColumn{Title="Name"}; 

    Gtk.CellRendererText nameCell = new Gtk.CellRendererText ();        

    nameColumn.PackStart (nameCell, true);









    Gtk.TreeViewColumn emailColumn = new Gtk.TreeViewColumn {Title="Email"}; 

    Gtk.CellRendererText emailCell = new Gtk.CellRendererText ();

    emailColumn.PackStart (emailCell, true);







    // Add the columns to the TreeView

    tv.AppendColumn (nameColumn);

    tv.AppendColumn (emailColumn);







    // Tell the Cell Renderers which items in the model to display

    nameColumn.AddAttribute (nameCell, "text", 0);

    emailColumn.AddAttribute (emailCell, "text", 1);







    // Create a model that will hold two strings 

    Gtk.TreeStore contacts = new Gtk.TreeStore (typeof (string), typeof (string));





    // Add some hierarchical data



    Gtk.TreeIter treeiter;





    //first root

    treeiter= contacts.AppendValues("testFRIENDS"); 



        // 2 children of first root

        contacts.AppendValues(treeiter, "testOgre", "[email protected]");

        contacts.AppendValues(treeiter, "testBee", "[email protected]");







    // second root

    treeiter= contacts.AppendValues("RELATIVES"); 



        // 3 children of second root

        contacts.AppendValues (treeiter,"Mommy","[email protected]");

        contacts.AppendValues (treeiter,"Daddy", "[email protected]");

        contacts.AppendValues (treeiter,"tom", "[email protected]");









    filter = new Gtk.TreeModelFilter (contacts, null);



    // Specify the function that determines which rows to filter out and which ones to display

    filter.VisibleFunc = new Gtk.TreeModelFilterVisibleFunc (FilterTree);



    // Assign the filter as our treeview's model

    tv.Model = filter;



    // Show the window and everything on it

    window.ShowAll ();

}



private void OnFilterEntryTextChanged (object o, System.EventArgs args)

{

    // Since the filter text changed, tell the filter to re-determine which rows to display

    filter.Refilter ();

}



private bool FilterTree (Gtk.TreeModel model, Gtk.TreeIter iter)

{

    string contactname = model.GetValue (iter, 0).ToString ();



    if (filterEntry.Text == "")

        return true;



    if (contactname.IndexOf (filterEntry.Text) > -1)

        return true;

    else

        return false;

}

}

The easiest solution with your current structure would be having the filter function always return TRUE for the 'container' nodes (Friends and Relatives), based upon a value in a hidden column in the model. It will not look exactly look the way you want, but it will work.

The GTK+ Treeview Tutorial, though not updated for some time,is still a VERY useful resource for all your TreeView needs. The code and examples are in C, but most of it still applies to GTK#.

axel_c
Thanks for the advise. I guess I have to learn a lot about the Gtk# TreeView/TreeModel design pattern and other facilities in the library before I can start to pull off this level of customization and fine grained control over databinding in Gtk#.
mumtaz
I edited my post to link to a tutorial that will be very useful to you.
axel_c
Thanks a lot ... I am on it.
mumtaz