Exporting a GtkSharp TreeView to CSV

All packaged up in nice little utility methods.
Published on Friday, April 9, 2010

I recently had to create some functionality to export a TreeView widget to a CSV file for further analysis. Since I tend to think about generic behavior, I decided to code up a method that would take any arbitrary TreeView and perform the export operation. Luckily, the TreeView widget and the attached TreeModel both contain a lot of functionality for accessing the data and it's presentation. I decided that I wanted the exported CSV file to represent the perspective of the model as currently represented in the TreeView including column visibility and sort order. This led to the trickiest part of the process. Because a CellRenderer can be customized using cell data functions (such as those added by a call to TreeViewColumn.SetCellDataFunc), I had to pull the content to export from the CellRenderer as opposed to pulling directly from the TreeModel. Turns out there's a method to take the TreeIter from a TreeModel and apply it to all the CellRenderers in a given TreeViewColumn. Since I really only care about textual content, I decided to only export those columns that contain CellRendererText renderers.

After working out the algorithm to fetch what needed to be exported I thought I was ready to roll. Turns out that the CSV pseudo-standard is pretty complex though (the RFC is here), and I quickly got bogged down in writing all kinds of special cases for escaping, quoting, etc. Thankfully, someone else had already been down this road and I was able to find the excellent KBCsv library which will write and read formatted CSV files. My only complaint was that it used another utility library purely for convenience in exception generation and null checking (I already use a ton of libraries in our application and I'd prefer not to add any unnecessarily). I replaced the calls to the utility library with the language equivalents, but that's totally a personal preference.

Without further adieu, I present the TreeViewHelper.ExportToCsv and TreeViewHelper.ExportToCsvFile methods...

using System;
using System.Collections.Generic;
using System.IO;
using Gtk;
using Kent.Boogaart.KBCsv;

namespace DaveAGlick {  public static class TreeViewHelper  {   public static bool ExportToCsv(TreeView treeView, Window parent)   {    FileChooserDialog fcd = new FileChooserDialog("Export File", parent, FileChooserAction.Save,    "Cancel", ResponseType.Cancel, "Export", ResponseType.Accept);    fcd.DoOverwriteConfirmation = true;    FileFilter filter = new FileFilter ;    filter.AddPattern("*.csv");    fcd.AddFilter(filter);    if (fcd.Run() == (int)ResponseType.Accept)    {     string path = fcd.Filename;     fcd.Destroy();     return ExportToCsvFile(treeView, path);    }    fcd.Destroy();    return false;   }

  public static bool ExportToCsvFile(TreeView treeView, string path)   {    //Get the iterator    TreeIter iter;    if (treeView.Model.GetIterFirst(out iter))    {     //Create the stream     using (StreamWriter streamWriter = new StreamWriter(path, false))     {      //Create the CSV writer      using (CsvWriter csvWriter = new CsvWriter(streamWriter))      {       List<string> headers = new List<string>();       List<string> values = new List<string>();

      //Traverse the tree       do       {        values.Clear();        foreach (TreeViewColumn column in treeView.Columns)        {         //Only output visible columns         if (column.Visible)         {          //Loop through CellRenderers to make sure we have a CellRendererText          string value = null;          column.CellSetCellData(treeView.Model, iter, false, false);          foreach (CellRenderer renderer in column.CellRenderers)          {           CellRendererText text = renderer as CellRendererText;           if (text != null)           {            //Setting value indicates this column had a CellRendererText and should be included            if (value == null)            {             value = String.Empty;            }

           //Add the header if the first time through            if (headers != null)            {             headers.Add(column.Title);            }

           //Append to the value            if (text.Text != null)            {             value += text.Text;            }           }          }          if (value != null)          {           values.Add(value);          }         }        }

       //Output the header        if (headers != null)        {         csvWriter.WriteHeaderRecord(headers.ToArray());         headers = null;        }

       //Output the values        csvWriter.WriteDataRecord(values.ToArray());       } while (treeView.Model.IterNext(ref iter));      }     }     return true;    }    return false;   }  } }