Advanced Controls in Mono Winforms

In this part of the Mono Winforms tutorial, we introduce some more advanced controls. Namely the ListBox, the ListView and the TreeView control.

ListBox Control

The ListBox control is used to display a list of items. Users can select one or more items by clicking on them.

listbox.cs
using System;
using System.Drawing;
using System.Windows.Forms;

class MForm : Form {

    private StatusBar sb;

    public MForm() {
        Text = "ListBox";
        Size = new Size(210, 210);

        ListBox lb = new ListBox();
        lb.Parent = this;
        lb.Items.Add("Jessica");
        lb.Items.Add("Rachel");
        lb.Items.Add("Angelina");
        lb.Items.Add("Amy");
        lb.Items.Add("Jennifer");
        lb.Items.Add("Scarlett");

        lb.Dock = DockStyle.Fill;
        lb.SelectedIndexChanged += new EventHandler(OnChanged);

        sb = new StatusBar();
        sb.Parent = this;

        CenterToScreen();
    }


    void OnChanged(object sender, EventArgs e) {
        ListBox lb = (ListBox) sender;
        sb.Text = lb.SelectedItem.ToString();
    }
}

class MApplication {
    public static void Main() {
        Application.Run(new MForm());
    }
}

Our example shows a listbox with six names. The selected item is shown in the statusbar.

ListBox lb = new ListBox();
lb.Parent = this;

ListBox control is created.

lb.Items.Add("Jessica");

This is how we add a new item to the ListBox control. The control has the Items property. The property is a reference to the list of items in a listbox. Using this reference, we can add, remove or get count of items of the listbox.

lb.SelectedIndexChanged += new EventHandler(OnChanged);

SelectedIndexChanged event is triggered, when we select an item.

ListBox lb = (ListBox) sender;
sb.Text = lb.SelectedItem.ToString();

Inside the OnChange() method, we get the reference to the listbox and set the selected text to the statusbar.

ListBox
Figure: ListBox

ListView

ListView control is used to display collections of items. It is a more sophisticated control than the ListBox control. It can display data in various views is mostly used to display data in multicolumn views.

listview.cs
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Collections.Generic;


public class Actress
{
    public string name;
    public int year;
 
    public Actress(string name, int year)
    {
        this.name = name;
        this.year = year;
    }
}

class MForm : Form {

    private StatusBar sb;

    public MForm() {
        Text = "ListView";
        Size = new Size(350, 300);


        List<Actress> actresses = new List<Actress>();

        actresses.Add(new Actress("Jessica Alba", 1981));
        actresses.Add(new Actress("Angelina Jolie", 1975));
        actresses.Add(new Actress("Natalie Portman", 1981));
        actresses.Add(new Actress("Rachel Weiss", 1971));
        actresses.Add(new Actress("Scarlett Johansson", 1984));


        ColumnHeader name = new ColumnHeader();
        name.Text = "Name";
        name.Width = -1;
        ColumnHeader year = new ColumnHeader();
        year.Text = "Year";

        SuspendLayout();

        ListView lv = new ListView();
        lv.Parent = this;
        lv.FullRowSelect = true;
        lv.GridLines = true;
        lv.AllowColumnReorder = true;
        lv.Sorting = SortOrder.Ascending;
        lv.Columns.AddRange(new ColumnHeader[] {name, year});
        lv.ColumnClick += new ColumnClickEventHandler(ColumnClick);

        foreach (Actress act in actresses) {
            ListViewItem item = new ListViewItem();
            item.Text = act.name;
            item.SubItems.Add(act.year.ToString());
            lv.Items.Add(item);
        }

        lv.Dock = DockStyle.Fill;
        lv.Click += new EventHandler(OnChanged);

        sb = new StatusBar();
        sb.Parent = this;
        lv.View = View.Details;

        ResumeLayout();

        CenterToScreen();
    }

    void OnChanged(object sender, EventArgs e) {
        ListView lv = (ListView) sender;
        string name = lv.SelectedItems[0].SubItems[0].Text;
        string born = lv.SelectedItems[0].SubItems[1].Text;
        sb.Text = name + ", " + born;
    }

    void ColumnClick(object sender, ColumnClickEventArgs e)
    {
        ListView lv = (ListView) sender;

        if (lv.Sorting == SortOrder.Ascending) {
            lv.Sorting = SortOrder.Descending;
        } else {
            lv.Sorting = SortOrder.Ascending;
        }   
    }
}

class MApplication {
    public static void Main() {
        Application.Run(new MForm());
    }
}

In our example, we have a listview with two columns. In the first column, we display the name of the actress. In the second one their date of birth. The data is store in a List collection. By selecting a row, the data in a row is displayed in the statusbar. Also, by clicking on the column header, the data is sorted.

public class Actress
{
...
}

We use the Actress class to store our data.

List<Actress> actresses = new List<Actress>();

actresses.Add(new Actress("Jessica Alba", 1981));
actresses.Add(new Actress("Angelina Jolie", 1975));
...

We create and fill our collection with items.

ColumnHeader name = new ColumnHeader();
name.Text = "Name";
name.Width = -1;

For each column in a listview, we create a ColumnHeader. By setting the Width to -1, the width of the column is equal to the longest item in the column.

ListView lv = new ListView();
lv.Parent = this;

ListView control is created.

lv.FullRowSelect = true;
lv.GridLines = true;
lv.AllowColumnReorder = true; 
lv.Sorting = SortOrder.Ascending;

Here we set four properties of the control. This code lines enable full row selection, show grid lines, allow column reordering by dragging the columns and sort the data in ascending order.

lv.Columns.AddRange(new ColumnHeader[] {name, year});

Here we add two ColumnHeaders to the ListView control.

foreach (Actress act in actresses) {
    ListViewItem item = new ListViewItem();
    item.Text = act.name;
    item.SubItems.Add(act.year.ToString());
    lv.Items.Add(item);
}

This cycle populates the listview control. Each row is added to the listview as a ListViewItem class.

lv.View = View.Details;

The ListView control can have different views. Different views display data differently.

ListView lv = (ListView) sender;
string name = lv.SelectedItems[0].SubItems[0].Text;
string born = lv.SelectedItems[0].SubItems[1].Text;
sb.Text = name + ", " + born;

Inside the OnChanged() method, we get the data from the selected row and show it on the statusbar.

if (lv.Sorting == SortOrder.Ascending) {
    lv.Sorting = SortOrder.Descending;
} else {
    lv.Sorting = SortOrder.Ascending;
}   

Here we toggle the sorting order of the column.

ListView
Figure: ListView

TreeView

TreeView control displays hierarchical collection of items. Each item in this control is represented by a TreeNode object.

treeview.cs
using System;
using System.Drawing;
using System.Windows.Forms;

class MForm : Form {

    StatusBar sb;

    public MForm() {
        Text = "TreeView";
        Size = new Size(250, 250);

        TreeView tv = new TreeView();

        TreeNode root = new TreeNode();
        root.Text = "Languages";

        TreeNode child1 = new TreeNode();
        child1.Text = "Python";

        TreeNode child2 = new TreeNode();
        child2.Text = "Ruby";

        TreeNode child3 = new TreeNode();
        child3.Text = "Java";

        root.Nodes.AddRange(new TreeNode[] {child1, child2, child3});

        tv.Parent = this;
        tv.Nodes.Add(root);
        tv.Dock = DockStyle.Fill;
        tv.AfterSelect += new TreeViewEventHandler(AfterSelect);

        sb = new StatusBar();
        sb.Parent = this;

        CenterToScreen();
    }

    void AfterSelect(object sender, TreeViewEventArgs e)
    {
        sb.Text = e.Node.Text;
    }
}

class MApplication {
    public static void Main() {
        Application.Run(new MForm());
    }
}

This is a very simple demonstration of the TreeView control. We have one root item and three children.

TreeView tv = new TreeView();

We create the TreeView control.

TreeNode root = new TreeNode();
root.Text = "Languages";
...
tv.Nodes.Add(root);

Here we create a root node.

TreeNode child1 = new TreeNode();
child1.Text = "Python";

Child node is created in a similar way.

root.Nodes.AddRange(new TreeNode[] {child1, child2, child3});

Child nodes are plugged into the Nodes property of the root node.

TreeView
Figure: TreeView

Directories

The following code example will examine the TreeView control more in-depth.

directories.cs
using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO;       

public class MForm : Form
{
  private TreeView tv;
  private Button expand;
  private Button expandAll;
  private Button collapse;
  private Button collapseAll;
  private StatusBar sb;

  private const string HOME_DIR = "/home/vronskij";

  public MForm()
  {
    Size = new Size(400, 400);
    Text = "Directories";

    tv = new TreeView();

    SuspendLayout();

    tv.Parent = this;
    tv.Location = new Point(10,10);
    tv.Size = new Size(ClientSize.Width - 20, Height - 200);
    tv.Anchor = AnchorStyles.Top | AnchorStyles.Left | 
          AnchorStyles.Right ;


    tv.FullRowSelect = false;    
    tv.ShowLines = true;      
    tv.ShowPlusMinus = true;    
    tv.Scrollable = true;      
    tv.AfterSelect += new TreeViewEventHandler(AfterSelect);

    expand = new Button();
    expand.Parent = this;
    expand.Location = new Point(20, tv.Bottom + 20);
    expand.Text = "Expand";
    expand.Anchor = AnchorStyles.Left | AnchorStyles.Top;
    expand.Click += new EventHandler(OnExpand);

    expandAll = new Button();
    expandAll.Parent = this;
    expandAll.Location = new Point(20, expand.Bottom + 5);
    expandAll.Text = "Expand All";
    expandAll.Anchor = AnchorStyles.Left | AnchorStyles.Top;
    expandAll.Click += new EventHandler(OnExpandAll);

    collapse = new Button();
    collapse.Parent = this;
    collapse.Location = new Point(expandAll.Right + 5, expand.Top );
    collapse.Text = "Collapse";
    collapse.Anchor = AnchorStyles.Left | AnchorStyles.Top;
    collapse.Click += new EventHandler(OnCollapse);

    collapseAll = new Button();
    collapseAll.Parent = this;
    collapseAll.Location = new Point(collapse.Left, collapse.Bottom + 5);
    collapseAll.Text = "Collapse All";
    collapseAll.Anchor = AnchorStyles.Left | AnchorStyles.Top;
    collapseAll.Click += new EventHandler(OnCollapseAll);

    sb = new StatusBar();
    sb.Parent = this;


    ShowDirectories(tv.Nodes, HOME_DIR);

    ResumeLayout();

    CenterToScreen();
  }


  void AfterSelect(object sender, TreeViewEventArgs e)
  {
      sb.Text = e.Node.Text;
  }


  void ShowDirectories(TreeNodeCollection trvNode, string path)
  {
      DirectoryInfo dirInfo = new DirectoryInfo(path);
      if (dirInfo != null)
      {
          DirectoryInfo[] subDirs = dirInfo.GetDirectories();
          TreeNode tr = new TreeNode(dirInfo.Name);

          if (subDirs.Length > 0)
          {
              foreach (DirectoryInfo dr in subDirs)
              {   
                  if (!dr.Name.StartsWith("."))
                      ShowDirectories(tr.Nodes, dr.FullName);               
              }
          }
          trvNode.Add(tr);
      }
  }


  void OnExpand(object sender, EventArgs e)
  {
    tv.SelectedNode.Expand();
  }

  void OnExpandAll(object sender, EventArgs e)
  {
    tv.ExpandAll();
  }

  void OnCollapse(object sender, EventArgs e)
  {
    tv.SelectedNode.Collapse();
  }

  void OnCollapseAll(object sender, EventArgs e)
  {
    tv.CollapseAll();
  }

  static void Main() 
  {
    Application.Run(new MForm());
  }

}

Our code example shows the directories of the specified home directory in a TreeView control. The application starts with some delay, because it reads the directory structure of the home directory first. We have also four buttons on the form. The buttons expand and collapse nodes programatically.

tv.Scrollable = true; 

We make the treeview control scrollable, because the control shows lots of directories.

ShowDirectories(tv.Nodes, HOME_DIR);

The ShowDirectories() method fills the nodes of the treview control with directories available in the specified home directory.

if (subDirs.Length > 0)
{
...
}

We check if there are any subdirectories.

foreach (DirectoryInfo dr in subDirs)
{   
    if (!dr.Name.StartsWith("."))
        ShowDirectories(tr.Nodes, dr.FullName);               
}

We loop through all directories. For this, we use the recursion algorithm. We also skip the hidden directories. They begin with a dot on Unix systems.

trvNode.Add(tr);

This code line actually adds the directory to the treeview control.

void OnExpand(object sender, EventArgs e)
{
  tv.SelectedNode.Expand();
}

All four buttons have events plugged to a method. Here is a method for the Expand button. It calls the Expand() method of the currently selected node.

Directories
Figure: Directories

In this part of the Mono Winforms tutorial, we covered several advanced controls available in Winforms library.