Reputation: 12110
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.]
Upvotes: 1
Views: 3048
Reputation: 39
Tinki version works flawlessly. The only thing which I do not like is the private variable. This can be eliminated by using a return value in the InvestigateChildrenNodes function.
private bool InvestigateChildNodes(TreeModel model, TreeIter iter)
{
TreeIter childIter;
model.IterChildren(out childIter, iter);
bool result = false;
do
{
if (model.GetValue(childIter, 0).ToString().Contains(FilterEntry.Text))
{
result = true;
break;
}
if (model.IterHasChild(childIter))
{
result = InvestigateChildNodes(model, childIter);
if (result)
break;
}
} while (model.IterNext(ref childIter));
return result;
}
Upvotes: 0
Reputation: 1526
In order to reach correct functionality of your code, I suggest you to modify it in following manner:
1.Add new field private filterBool = false;
to your class
2.Modify your FilterTree
method to this state:
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;
if (model.IterHasChild(iter))
{
filerBool = false;
investigateChildNodes(model, iter); //method checking if currently investigated
//node has any child fulfilling filter contitions
return filerBool;
}
return false;
}
3.Add missing method
private void investigateChildNodes(TreeModel model, TreeIter iter)
{
TreeIter childIter;
model.IterChildren(out childIter, iter);
do
{
if (model.GetValue(childIter, 0).ToString().IndexOf(filterEntry.Text) > -1)
filerBool = true;
if (model.IterHasChild(childIter))
investigateChildNodes(model, childIter);
} while (model.IterNext(ref childIter));
}
With this modification every node is checked for possible child nodes, which might fulfill filtering conditions. If any is detected, the node is not discarded.
Upvotes: 1
Reputation: 6806
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#.
Upvotes: 1