GTK# tutorial

This is GTK# tutorial for the C# programming language. This tutorial is suitable for beginners and more advanced programmers.

GTK#

GTK# is a wrapper over the GTK+ for the C# programming language. The library facilitates building graphical GNOME applications using Mono or any other compliant CLR. Applications built using Gtk# will run on many platforms including Linux, Microsoft Windows and Mac OS X. GTK# is part of the Mono initiative.

This is an introductory GTK# programming tutorial. The tutorial is targeted for the C# programming language. It has been created and tested on Linux. The GTK# programming tutorial is suited for novice and intermediate programmers. Here are the images used in this tutorial.

GTK+

GTK+ is a library for creating graphical user interfaces. The library is created in C programming language. The GTK+ library is also called the GIMP Toolkit. Originally, the library was created while developing the GIMP image manipulation program. Since then, the GTK+ became one of the most popular toolkits under Linux and BSD Unix. Today, most of the GUI software in the open source world is created in Qt or in GTK+. The GTK+ is an object oriented application programming interface. The object oriented system is created with the Glib object system, which is a base for the GTK+ library. The GObject also enables to create language bindings for various other programming languages. Language bindings exist for C++, Python, Perl, Java, C# and other programming languages.

The GTK+ itself depends on the following libraries.

  • Glib
  • Pango
  • ATK
  • GDK
  • GdkPixbuf
  • Cairo

The Glib is a general purpose utility library. It provides various data types, string utilities, enables error reporting, message logging, working with threads and other useful programming features. The Pango is a library which enables internationalisation. The ATK is the accessibility toolkit. This toolkit provides tools which help physically challenged people work with computers. The GDK is a wrapper around the low-level drawing and windowing functions provided by the underlying graphics system. On Linux, GDK lies between the X Server and the GTK+ library. Recently, much of its functionality have been delegated to the Cairo library. The GdkPixbuf library is a toolkit for image loading and pixel buffer manipulation. Cairo is a library for creating 2D vector graphics. It has been included in GTK+ since version 2.8.

Gnome and XFce desktop environments have been created using the GTK+ library. SWT and wxWidgets are well known programming frameworks that use GTK+. Prominent software applications that use GTK+ include Firefox or Inkscape.

GTK#

GTK# is a wrapper over the GTK+ for the C# programming language. The library facilitates building graphical GNOME applications using Mono or any other compliant CLR. Gtk# is an event-driven system like any other modern windowing library where every widget in an application has handler methods that get called when particular events happen. Applications built using Gtk# will run on many platforms including Linux, Microsoft, Windows and Mac OS X. GTK# is part of the Mono initiative. There are basically two widget toolkits in Mono: Winforms and the GTK#. The GTK# is considered to be native for the Linux/Unix operating system.

Compiling GTK# applications

We use the gmcs compiler to compile our GTK# applications.

$ gmcs -pkg:gtk-sharp-2.0 -r:/usr/lib/mono/2.0/Mono.Cairo.dll application.cs

The above line compiles a GTK# application that also uses the Cairo library.

First steps in GTK#

In this part of the GTK# programming tutorial, we will do our first steps in programming. We will create simple programs.

Simple example

The first code example is a simple one that shows a centered window.

center.cs

using Gtk;

class SharpApp : Window {

    public SharpApp() : base("Center")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);

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

        Show();    
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();        
        Application.Run();
    }
}

The code example shows a small window in the center of the screen.

$ gmcs -pkg:gtk-sharp-2.0 centelabel.csr.cs

Here is how we compile the code example.

using Gtk;

Now we can use the objects from the Gtk namespace directly. We can write Window instead of Gtk.Window.

class SharpApp : Window {
Our application is based on the SharpApp class. This class inherits from the Window class.

public SharpApp() : base("Center")
{
    ...   
}

This is the constructor. It builds our application. It also calls its parent constructor through the base() keyword.

SetDefaultSize(250, 200);

This line sets a default size for our window.

SetPosition(WindowPosition.Center);

This line centers the window on the screen.

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

We plug a delegate to the DeleteEvent. This event is triggered, when we click on the close button in the titlebar. Or press Alt+F4. Our delegate quits the application for good.

Show();

Now we show the window. The window is not visible, until we call the Show() method.

public static void Main()
{
    Application.Init();
    new SharpApp();        
    Application.Run();
}

The Main() method is the entry point to the application. It initiates and runs the program.

Icon

In the next example, we show the application icon. Most window managers display the icon in the left corner of the titlebar and also on the taskbar.

icon.cs

using Gtk;
using System;

class SharpApp : Window {

    public SharpApp() : base("Icon")
    {
        SetDefaultSize(250, 160);
        SetPosition(WindowPosition.Center);
        SetIconFromFile("web.png");

        DeleteEvent += new DeleteEventHandler(OnDelete);

        Show();      
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }

    void OnDelete(object obj, DeleteEventArgs args)
    {
        Application.Quit();
    }
}


The code example shows the application icon.

SetIconFromFile("web.png");

The SetIconFromFile() method sets an icon for the window. The image is loaded from disk in the current working directory.

DeleteEvent += new DeleteEventHandler(OnDelete);

This is another way, how we can plug an event handler to an event. It is just a bit more verbose.

void OnDelete(object obj, DeleteEventArgs args)
{
    Application.Quit();
}

This is an event handler for the delete event.

Buttons

In the next example, we will further enhance our programming skills with the GTK# library.

buttons.cs

using Gtk;

class SharpApp : Window
{

    public SharpApp() : base("Buttons")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);

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

        Fixed fix = new Fixed();

        Button btn1 = new Button("Button");
        btn1.Sensitive = false;
        Button btn2 = new Button("Button");
        Button btn3 = new Button(Stock.Close);
        Button btn4 = new Button("Button");
        btn4.SetSizeRequest(80, 40);

        fix.Put(btn1, 20, 30);
        fix.Put(btn2, 100, 30);
        fix.Put(btn3, 20, 80);
        fix.Put(btn4, 100, 80);

        Add(fix);
        ShowAll();
    }


    public static void Main() 
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

We show four different buttons on the window. We will see a difference between container widgets and child widgets and will change some properties of child widgets.

Fixed fix = new Fixed();

Fixed widget is a non visible container widget. Its purpose is to contain other child widgets.

Button btn1 = new Button("Button");

A Button is a child widget. Child widgets are placed inside containers.

btn1.Sensitive = false;

We make this button insensitive. This means, we cannot click on it. Graphically the widget is grayed out.

Button btn3 = new Button(Stock.Close);

The third button shows an image inside its area. The GTK# library has a built-in stock of images that we can use.

btn4.SetSizeRequest(80, 40);

Here we change the size of the button.

fix.Put(btn1, 20, 30);
fix.Put(btn2, 100, 30);
...

Here we place button widgets inside fixed container widget.

Add(fix);

We set the Fixed container to be the main container for our Window widget.

ShowAll();

We can either call ShowAll() method, or we call Show() method on each of the widgets. Including containers.

In this chapter, we created first programs in GTK# programming library.

Layout management in GTK#

In this chapter we will show how to lay out our widgets in windows or dialogs.

When we design the GUI of our application, we decide what widgets we will use and how we will organise those widgets in the application. To organise our widgets, we use specialised non visible widgets called layout containers. In this chapter, we will mention Alignment, Fixed, VBox and Table.

Fixed

The Fixed container places child widgets at fixed positions and with fixed sizes. This container performs no automatic layout management. In most applications, we do not use this container. There are some specialised areas, where we use it. For example games, specialised applications that work with diagrams, resizable components that can be moved (like a chart in a spreadsheet application), small educational examples.

fixed.cs

using Gtk;
using System;

class SharpApp : Window {

    private Gdk.Pixbuf rotunda;
    private Gdk.Pixbuf bardejov;
    private Gdk.Pixbuf mincol;

    public SharpApp() : base("Fixed")
    {
        SetDefaultSize(300, 280);
        SetPosition(WindowPosition.Center);
        ModifyBg(StateType.Normal, new Gdk.Color(40, 40, 40));
        DeleteEvent += delegate { Application.Quit(); };

        try {
            bardejov = new Gdk.Pixbuf("bardejov.jpg");
            rotunda = new Gdk.Pixbuf("rotunda.jpg");
            mincol = new Gdk.Pixbuf("mincol.jpg");
        } catch {
            Console.WriteLine("Images not found");
            Environment.Exit(1);
        }

        Image image1 = new Image(bardejov);
        Image image2 = new Image(rotunda);
        Image image3 = new Image(mincol);


        Fixed fix = new Fixed();

        fix.Put(image1, 20, 20);
        fix.Put(image2, 40, 160);
        fix.Put(image3, 170, 50);

        Add(fix);
        ShowAll();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In our example, we show three small images on the window. We explicitly specify the x, y coordinates, where we place these images.

ModifyBg(StateType.Normal, new Gdk.Color(40, 40, 40));

For better visual experience, we change the background colour to dark gray.

bardejov = new Gdk.Pixbuf("bardejov.jpg");

We load the image from the disk to the Gdk.Pixbuf object.

Image image1 = new Image(bardejov);
Image image2 = new Image(rotunda);
Image image3 = new Image(mincol);

The Image is a widget that is used to display images. It takes Gdk.Pixbuf object in the constructor.

Fixed fix = new Fixed();

We create the Fixed container.

fix.Put(image1, 20, 20);

We place the first image at x=20, y=20 coordinates.

Add(fix);

Finally, we add the Fixed container to the Window.

Alignment

The Alignment container controls the alignment and the size of its child widget.

alignment.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("Alignment")
    {
        SetDefaultSize(260, 150);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        VBox vbox = new VBox(false, 5);
        HBox hbox = new HBox(true, 3);

        Alignment valign = new Alignment(0, 1, 0, 0);
        vbox.PackStart(valign);

        Button ok = new Button("OK");
        ok.SetSizeRequest(70, 30);
        Button close = new Button("Close");

        hbox.Add(ok);
        hbox.Add(close);

        Alignment halign = new Alignment(1, 0, 0, 0);
        halign.Add(hbox);

        vbox.PackStart(halign, false, false, 3);

        Add(vbox);

        ShowAll();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In the code example, we place two buttons into the right bottom corner of the window. To accomplish this, we use one horizontal box and one vertical box and two alignment containers.

Alignment valign = new Alignment(0, 1, 0, 0);

This will put the child widget to the bottom.

vbox.PackStart(valign);

Here we place the Alignment widget into the vertical box.

HBox hbox = new HBox(true, 3);
...
Button ok = new Button("OK");
ok.SetSizeRequest(70, 30);
Button close = new Button("Close");

hbox.Add(ok);
hbox.Add(close);

We create a horizontal box and put two buttons inside it.

Alignment halign = new Alignment(1, 0, 0, 0);
halign.Add(hbox);

vbox.PackStart(halign, false, false, 3);

This will create an alignment container that will place its child widget to the right. We add the horizontal box into the alignment container and pack the alignment container into the vertical box. We must keep in mind that the alignment container takes only one child widget. That is why we must use boxes.

Table

The Table widget arranges widgets in rows and columns.

calculator.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("Calculator")
    {
        SetDefaultSize(250, 230);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        VBox vbox = new VBox(false, 2);

        MenuBar mb = new MenuBar();
        Menu filemenu = new Menu();
        MenuItem file = new MenuItem("File");
        file.Submenu = filemenu;
        mb.Append(file);

        vbox.PackStart(mb, false, false, 0);

        Table table = new Table(5, 4, true);

        table.Attach(new Button("Cls"), 0, 1, 0, 1);
        table.Attach(new Button("Bck"), 1, 2, 0, 1);
        table.Attach(new Label(), 2, 3, 0, 1);
        table.Attach(new Button("Close"), 3, 4, 0, 1);

        table.Attach(new Button("7"), 0, 1, 1, 2);
        table.Attach(new Button("8"), 1, 2, 1, 2);
        table.Attach(new Button("9"), 2, 3, 1, 2);
        table.Attach(new Button("/"), 3, 4, 1, 2);

        table.Attach(new Button("4"), 0, 1, 2, 3);
        table.Attach(new Button("5"), 1, 2, 2, 3);
        table.Attach(new Button("6"), 2, 3, 2, 3);
        table.Attach(new Button("*"), 3, 4, 2, 3);

        table.Attach(new Button("1"), 0, 1, 3, 4);
        table.Attach(new Button("2"), 1, 2, 3, 4);
        table.Attach(new Button("3"), 2, 3, 3, 4);
        table.Attach(new Button("-"), 3, 4, 3, 4);

        table.Attach(new Button("0"), 0, 1, 4, 5);
        table.Attach(new Button("."), 1, 2, 4, 5);
        table.Attach(new Button("="), 2, 3, 4, 5);
        table.Attach(new Button("+"), 3, 4, 4, 5);

        vbox.PackStart(new Entry(), false, false, 0);
        vbox.PackEnd(table, true, true, 0);

        Add(vbox);
        ShowAll();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}


We use the Table widget to create a calculator skeleton.

Table table = new Table(5, 4, true);

We create a table widget with 5 rows and 4 columns. The third parameter is the homogeneous parameter. If set to true, all the widgets in the table are of same size. The size of all widgets is equal to the largest widget in the table container.

table.Attach(new Button("Cls"), 0, 1, 0, 1);

We attach a button to the table container. To the top-left cell of the table. The first two parameters are the left and right sides of the cell, the last two parameters are the top and left sides of the cell.

vbox.PackEnd(table, true, true, 0);

We pack the table widget into the vertical box.

Windows

Next we will create a more advanced example. We show a window, that can be found in the JDeveloper IDE.

windows.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("Windows")
    {
        SetDefaultSize(300, 250);
        SetPosition(WindowPosition.Center);
        BorderWidth = 15;
        DeleteEvent += delegate { Application.Quit(); };

        Table table = new Table(8, 4, false);
        table.ColumnSpacing = 3;

        Label title = new Label("Windows");

        Alignment halign = new Alignment(0, 0, 0, 0);
        halign.Add(title);

        table.Attach(halign, 0, 1, 0, 1, AttachOptions.Fill, 
            AttachOptions.Fill, 0, 0);

        TextView wins = new TextView();
        wins.ModifyFg(StateType.Normal, new Gdk.Color(20, 20, 20));
        wins.CursorVisible = false;
        table.Attach(wins, 0, 2, 1, 3, AttachOptions.Fill | AttachOptions.Expand,
            AttachOptions.Fill | AttachOptions.Expand, 1, 1);

        Button activate = new Button("Activate");
        activate.SetSizeRequest(50, 30);
        table.Attach(activate, 3, 4, 1, 2, AttachOptions.Fill, 
            AttachOptions.Shrink, 1, 1);

        Alignment valign = new Alignment(0, 0, 0, 0);
        Button close = new Button("Close");
        close.SetSizeRequest(70, 30);
        valign.Add(close);
        table.SetRowSpacing(1, 3);
        table.Attach(valign, 3, 4, 2, 3, AttachOptions.Fill,
            AttachOptions.Fill | AttachOptions.Expand, 1, 1);

        Alignment halign2 = new Alignment(0, 1, 0, 0);
        Button help = new Button("Help");
        help.SetSizeRequest(70, 30);
        halign2.Add(help);
        table.SetRowSpacing(3, 6);
        table.Attach(halign2, 0, 1, 4, 5, AttachOptions.Fill, 
            AttachOptions.Fill, 0, 0);

        Button ok = new Button("OK");
        ok.SetSizeRequest(70, 30);
        table.Attach(ok, 3, 4, 4, 5, AttachOptions.Fill, 
            AttachOptions.Fill, 0, 0);

        Add(table);
        ShowAll();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

The code example shows, how we can create a similar window in GTK#.

Table table = new Table(8, 4, false);
table.ColumnSpacing = 3;

The example is based on the Table container. There will be 3 px space between columns.

Label title = new Label("Windows");

Alignment halign = new Alignment(0, 0, 0, 0);
halign.Add(title);

table.Attach(halign, 0, 1, 0, 1, AttachOptions.Fill, 
    AttachOptions.Fill, 0, 0);

This code creates a label that is aligned to the left. The label is placed in the first row of the Table container.

TextView wins = new TextView();
wins.ModifyFg(StateType.Normal, new Gdk.Color(20, 20, 20));
wins.CursorVisible = false;
table.Attach(wins, 0, 2, 1, 3, AttachOptions.Fill | AttachOptions.Expand,
    AttachOptions.Fill | AttachOptions.Expand, 1, 1);

The text view widget spans two rows and two columns. We make the widget non editable and hide the cursor.

Alignment valign = new Alignment(0, 0, 0, 0);
Button close = new Button("Close");
close.SetSizeRequest(70, 30);
valign.Add(close);
table.SetRowSpacing(1, 3);
table.Attach(valign, 3, 4, 2, 3, AttachOptions.Fill,
    AttachOptions.Fill | AttachOptions.Expand, 1, 1);

We put the close button next to the text view widget into the fourth column. (we count from zero) We add the button into the alignment widget, so that we can align it to the top.

Menus in GTK#

In this part of the GTK# programming tutorial, we will work with menus.

A menubar is one of the most common parts of the GUI application. It is a group of commands located in various menus. While in console applications we have to remember various arcane commands, here we have most of the commands grouped into logical parts. These are accepted standards that further reduce the amount of time spending to learn a new application.

Simple menu

In our first example, we will create a menubar with one file menu. The menu will have only one menu item. By selecting the item the application quits.

simplemenu.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("Simple menu")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        MenuBar mb = new MenuBar();

        Menu filemenu = new Menu();
        MenuItem file = new MenuItem("File");
        file.Submenu = filemenu;

        MenuItem exit = new MenuItem("Exit");
        exit.Activated += OnActivated;
        filemenu.Append(exit);

        mb.Append(file);

        VBox vbox = new VBox(false, 2);
        vbox.PackStart(mb, false, false, 0);

        Add(vbox);

        ShowAll();
    }

    void OnActivated(object sender, EventArgs args)
    {
        Application.Quit();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

This is a small example with minimal menubar functionality.

MenuBar mb = new MenuBar();

MenuBar widget is created.

Menu filemenu = new Menu();
MenuItem file = new MenuItem("File");
file.Submenu = filemenu;

Toplevel MenuItem is created.

MenuItem exit = new MenuItem("Exit");
exit.Activated += OnActivated;
filemenu.Append(exit);

Exit MenuItem is created and appended to the File MenuItem.

mb.Append(file);

Toplevel MenuItem is appended to the MenuBar widget.

VBox vbox = new VBox(false, 2);
vbox.PackStart(mb, false, false, 0);

Unlike in other toolkits, we have to take care of the layout management ourselves. We put the menubar into the vertical box.

Image menu

In the next example, we will further explore the menus. We will add images and accelerators to our menu items. Accelerators are keyboard shortcuts for activating a menu item.

imagemenu.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("Image menu")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        MenuBar mb = new MenuBar();

        Menu filemenu = new Menu();
        MenuItem file = new MenuItem("File");
        file.Submenu = filemenu;

        AccelGroup agr = new AccelGroup();
        AddAccelGroup(agr);

        ImageMenuItem newi = new ImageMenuItem(Stock.New, agr);
        newi.AddAccelerator("activate", agr, new AccelKey(
            Gdk.Key.n, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
        filemenu.Append(newi);

        ImageMenuItem open = new ImageMenuItem(Stock.Open, agr);
        open.AddAccelerator("activate", agr, new AccelKey(
            Gdk.Key.n, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
        filemenu.Append(open);

        SeparatorMenuItem sep = new SeparatorMenuItem();
        filemenu.Append(sep);

        ImageMenuItem exit = new ImageMenuItem(Stock.Quit, agr);
        exit.AddAccelerator("activate", agr, new AccelKey(
            Gdk.Key.q, Gdk.ModifierType.ControlMask, AccelFlags.Visible));

        exit.Activated += OnActivated;
        filemenu.Append(exit);

        mb.Append(file);

        VBox vbox = new VBox(false, 2);
        vbox.PackStart(mb, false, false, 0);
        vbox.PackStart(new Label(), false, false, 0);

        Add(vbox);

        ShowAll();
    }

    void OnActivated(object sender, EventArgs args)
    {
        Application.Quit();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

  这一步并没有出现菜单图标!

Our example shows a toplevel menu item with three sublevel menu items. Each of the menu items has a image and an accelerator. The accelerator for the quit menu item is active.

AccelGroup agr = new AccelGroup();
AddAccelGroup(agr);

To work with accelerators, we create a global AccelGroup object. It will be used later.

ImageMenuItem newi = new ImageMenuItem(Stock.New, agr);
ImageMenuItem is created. The image comes from the stock of images.

exit.AddAccelerator("activate", agr, new AccelKey(
    Gdk.Key.q, Gdk.ModifierType.ControlMask, AccelFlags.Visible));

This creates an Ctrl+Q accelerator for the exit menu item.

SeparatorMenuItem sep = new SeparatorMenuItem();
filemenu.Append(sep);

These lines create a separator. It is used to group menu items into logical groups.

CheckMenuItem

A CheckMenuItem is a menu item with a check box. It can be used to work with boolean properties.

checkmenuitem.cs

using Gtk;
using System;

class SharpApp : Window {

    private Statusbar statusbar;

    public SharpApp() : base("Check menu item")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        MenuBar mb = new MenuBar();

        Menu filemenu = new Menu();
        MenuItem file = new MenuItem("File");
        file.Submenu = filemenu;

        Menu viewmenu = new Menu();
        MenuItem view = new MenuItem("View");
        view.Submenu = viewmenu;

        CheckMenuItem stat = new CheckMenuItem("View Statusbar");
        stat.Toggle();
        stat.Toggled += OnStatusView;
        viewmenu.Append(stat);

        MenuItem exit = new MenuItem("Exit");
        exit.Activated += OnActivated;
        filemenu.Append(exit);

        mb.Append(file);
        mb.Append(view);

        statusbar = new Statusbar();
        statusbar.Push(1, "Ready");

        VBox vbox = new VBox(false, 2);
        vbox.PackStart(mb, false, false, 0);
        vbox.PackStart(new Label(), true, false, 0);
        vbox.PackStart(statusbar, false, false, 0);

        Add(vbox);

        ShowAll();
    }

    void OnStatusView(object sender, EventArgs args)
    {
        CheckMenuItem item = (CheckMenuItem) sender;

        if (item.Active) {
            statusbar.Show();
        } else {
            statusbar.Hide();
        }
    }

    void OnActivated(object sender, EventArgs args)
    {
        Application.Quit();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In our code example we show a check menu item. If the check box is activated, the statusbar widget is shown. If not, the statusbar is hidden.

CheckMenuItem stat = new CheckMenuItem("View Statusbar");
CheckMenuItem widget is created.

stat.Toggle();

The Toggle() method checks/unchecks the check menu item.

if (item.Active) {
    statusbar.Show();
} else {
    statusbar.Hide();
}

Depending on the state of the CheckMenuItem, we show or hide the statusbar widget.

Our final example demonstrates how to create a submenu in GTK#.

submenu.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("Submenu")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        MenuBar mb = new MenuBar();

        Menu filemenu = new Menu();
        MenuItem file = new MenuItem("File");
        file.Submenu = filemenu;

        // submenu creation
        Menu imenu = new Menu();

        MenuItem import = new MenuItem("Import");
        import.Submenu = imenu;

        MenuItem inews = new MenuItem("Import news feed...");
        MenuItem ibookmarks = new MenuItem("Import bookmarks...");
        MenuItem imail = new MenuItem("Import mail...");

        imenu.Append(inews);
        imenu.Append(ibookmarks);
        imenu.Append(imail);

        // exit menu item
        MenuItem exit = new MenuItem("Exit");
        exit.Activated += OnActivated;

        filemenu.Append(import);
        filemenu.Append(exit);
        mb.Append(file);

        VBox vbox = new VBox(false, 2);
        vbox.PackStart(mb, false, false, 0);
        vbox.PackStart(new Label(), false, false, 0);

        Add(vbox);

        ShowAll();
    }

    void OnActivated(object sender, EventArgs args)
    {
        Application.Quit();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

Submenu creation.

Menu imenu = new Menu();

A submenu is a Menu.

MenuItem import = new MenuItem("Import");
import.Submenu = imenu;

It is a submenu of a menu item, which belogs to toplevel file menu.

MenuItem inews = new MenuItem("Import news feed...");
MenuItem ibookmarks = new MenuItem("Import bookmarks...");
MenuItem imail = new MenuItem("Import mail...");

imenu.Append(inews);
imenu.Append(ibookmarks);
imenu.Append(imail);

Submenu has its own menu items.

In this chapter of the GTK# programming library, we showed, how to work with menus.

Toolbars in GTK#

In this part of the GTK# programming tutorial, we will work with toolbars.

Menus group commands that we can use in application. Toolbars provide a quick access to the most frequently used commands. A toolbar is a horizontal or vertical panel with buttons. These buttons have images or images and text. By clicking on the toolbar button we perform an action.

Simple toolbar

Next we create a simple toolbar.

toolbar.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("Toolbar")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        Toolbar toolbar = new Toolbar();
        toolbar.ToolbarStyle = ToolbarStyle.Icons;

        ToolButton newtb = new ToolButton(Stock.New);
        ToolButton opentb = new ToolButton(Stock.Open);
        ToolButton savetb = new ToolButton(Stock.Save);
        SeparatorToolItem sep = new SeparatorToolItem();
        ToolButton quittb = new ToolButton(Stock.Quit);

        toolbar.Insert(newtb, 0);
        toolbar.Insert(opentb, 1);
        toolbar.Insert(savetb, 2);
        toolbar.Insert(sep, 3);
        toolbar.Insert(quittb, 4);

        quittb.Clicked += OnClicked;

        VBox vbox = new VBox(false, 2);
        vbox.PackStart(toolbar, false, false, 0);

        Add(vbox);

        ShowAll();
    }

    void OnClicked(object sender, EventArgs args)
    {
        Application.Quit();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

The example shows a toolbar and four tool buttons.

Toolbar toolbar = new Toolbar();

A Toolbar widget is created.

toolbar.ToolbarStyle = ToolbarStyle.Icons;

On toolbar, we show only icons. No text.

ToolButton newtb = new ToolButton(Stock.New);

A ToolButton with an image from stock is created.

SeparatorToolItem sep = new SeparatorToolItem(); 

This is a separator. It can be used to group toolbar buttons into logical groups.

toolbar.Insert(newtb, 0);
toolbar.Insert(opentb, 1);
...

Toolbar buttons are inserted into the toolbar widget.

Toolbars

In the second example, we show two toolbars. Many applications have more than one toolbar. We show, how we can do it in GTK#.

toolbars.cs

using Gtk;
using System;

class SharpApp : Window
{


    public SharpApp() : base("Toolbars")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        Toolbar upper = new Toolbar();
        upper.ToolbarStyle = ToolbarStyle.Icons;

        ToolButton newtb = new ToolButton(Stock.New);
        ToolButton opentb = new ToolButton(Stock.Open);
        ToolButton savetb = new ToolButton(Stock.Save);

        upper.Insert(newtb, 0);
        upper.Insert(opentb, 1);
        upper.Insert(savetb, 2);

        Toolbar lower = new Toolbar();
        lower.ToolbarStyle = ToolbarStyle.Icons;

        ToolButton quittb = new ToolButton(Stock.Quit);
        quittb.Clicked += OnClicked;
        lower.Insert(quittb, 0);


        VBox vbox = new VBox(false, 2);
        vbox.PackStart(upper, false, false, 0);
        vbox.PackStart(lower, false, false, 0);

        Add(vbox);

        ShowAll();
    }

    void OnClicked(object sender, EventArgs args)
    {
        Application.Quit();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

Our applications shows two toolbars.

Toolbar upper = new Toolbar();
...
Toolbar lower = new Toolbar();

We create two Toolbar widgets.

upper.Insert(newtb, 0);
...
lower.Insert(quittb, 0);

Each of them has its own tool buttons.

VBox vbox = new VBox(false, 2);
vbox.PackStart(upper, false, false, 0);
vbox.PackStart(lower, false, false, 0);

Toolbars are packed into the vertical box, one after the other.

Undo red

The following example demonstrates, how we can inactivate toolbar buttons on the toolbar. It is a common practise in GUI programming. For example the save button. If we save all changes of our document to the disk, the save button is inactivated in most text editors. This way the application indicates to the user, that all changes are already saved.

ndoredo.cs

using Gtk;
using System;

class SharpApp : Window {

    private int count = 2;
    private ToolButton undo;
    private ToolButton redo;

    public SharpApp() : base("Undo redo")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        Toolbar toolbar = new Toolbar();
        toolbar.ToolbarStyle = ToolbarStyle.Icons;

        undo = new ToolButton(Stock.Undo);
        redo = new ToolButton(Stock.Redo);
        SeparatorToolItem sep = new SeparatorToolItem();
        ToolButton quit = new ToolButton(Stock.Quit);

        toolbar.Insert(undo, 0);
        toolbar.Insert(redo, 1);
        toolbar.Insert(sep, 2);
        toolbar.Insert(quit, 3);

        undo.Clicked += OnUndo;
        redo.Clicked += OnRedo;
        quit.Clicked += OnClicked;

        VBox vbox = new VBox(false, 2);
        vbox.PackStart(toolbar, false, false, 0);
        vbox.PackStart(new Label(), false, false, 0);

        Add(vbox);

        ShowAll();
    }

    void OnUndo(object sender, EventArgs args)
    {
        count -= 1;

        if (count <= 0) {
            undo.Sensitive = false;
            redo.Sensitive = true;
        }
    }

    void OnRedo(object sender, EventArgs args)
    {
        count += 1;

        if (count >= 5) {
            redo.Sensitive = false;
            undo.Sensitive = true;
        }
    }

    void OnClicked(object sender, EventArgs args)
    {
        Application.Quit();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

Our example creates undo and redo buttons from the GTK# stock resources. After several clicks each of the buttons is inactivated. The buttons are grayed out.

private int count = 2;

The count variable decides, which button is activated and deactivated.

undo = new ToolButton(Stock.Undo);
redo = new ToolButton(Stock.Redo);

We have two tool buttons. Undo and redo tool buttons. Images come from the stock resources.

undo.Clicked += OnUndo;
redo.Clicked += OnRedo;

We plug a method for the Clicked event for both tool buttons.

if (count <= 0) {
    undo.Sensitive = false;
    redo.Sensitive = true;
}

To activate a widget, we set its Sensitive property to true. To inactivate it, we set it to false.

In this chapter of the GTK# programming library, we mentioned toolbars.

Events in GTK#

In this part of the GTK# programming tutorial, we will talk about events.

GTK# library is an event driven system. All GUI applications react to events. The applications start a main loop, which continuously checks for newly generated events. If there is no event, the application waits and does nothing. Events are generated mainly by the user of an application. But they can be generated by other means as well, e.g. Internet connection, window manager or a timer.

Simple event example

The next example shows, how we react to two basic events.

quitbutton.cs

using Gtk;
using System;

class SharpApp : Window {

    public SharpApp() : base ("Button")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);

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

        Fixed fix = new Fixed();

        Button quit = new Button("Quit");
        quit.Clicked += OnClick;
        quit.SetSizeRequest(80, 35);

        fix.Put(quit, 50, 50);
        Add(fix);
        ShowAll();
    }

    void OnClick(object sender, EventArgs args)
    {
        Application.Quit();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}


In our code example, we react to two events: Delete event and Clicked event.

The delete event is triggered, when we close the window. By default, the application does not quit, when we click on the close button in the titlebar.

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

When we use the delegate keyword, we can write in line code that will react to this particular event.

quit.Clicked += OnClick;

Here we specify that we react to Clicked event, with the OnClick() method.

void OnClick(object sender, EventArgs args)
{
    Application.Quit();
}

Here is the OnClick() method. It takes two parameters. The first parameter is the object, which triggered this event. In our case it is the quit button. The second parameter gives us various additional information about the event. The event arguments depend always on the type of the event. The signature of each of the method can be found in the reference manual of the GTK# library. http://www.go-mono.com/docs/

Moving window

The next example shows, how we react to move events of a window. We show the current position of the upper left corner of our window in the titlebar.

move.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("")
    {
        SetDefaultSize(250, 150);
        SetPosition(WindowPosition.Center);

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

    protected override bool OnConfigureEvent(Gdk.EventConfigure args)
    {
        base.OnConfigureEvent(args);
        Title = args.X + ", " + args.Y;
        return true;
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In the previous example, we have plugged a delegate or a method to an event. In GTK#, many of the events have a handler method already. In this case, we can override the method. This is the case, of our code example.

protected override bool OnConfigureEvent(Gdk.EventConfigure args)
{
    base.OnConfigureEvent(args);
    Title = args.X + ", " + args.Y;
    return true;
}

Here we override the predefined OnConfigureEvent() method. Configure event is triggered, when we resize or move a widget. Note that the first line of the method calls the default method. Without this line, the program would not behave correctly. The next line sets the x, y coordinates of the window to the title of the window.

EnterNotifyEvent

EnterNotifyEvent is emitted, when we enter the area of a widget with a mouse pointer.

enter.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("Enter")
    {
        SetDefaultSize(200, 150);
        SetPosition(WindowPosition.Center);

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

        Button button = new Button("Button");
        button.EnterNotifyEvent += OnEnter;

        Fixed fix = new Fixed();
        fix.Put(button, 20, 20);

        Add(fix);

        ShowAll();
    }


    void OnEnter(object sender, EnterNotifyEventArgs args)
    {
        Button button = (Button) sender;
        button.ModifyBg(StateType.Prelight, new Gdk.Color(220, 220, 220));
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}


We will change the background colour of the button widget, once we hover a mouse pointer over it.

button.EnterNotifyEvent += OnEnter;

We plug the OnEnter() method to to the EnterNotifyEvent.

Button button = (Button) sender;
button.ModifyBg(StateType.Prelight, new Gdk.Color(220, 220, 220));

We get the button widget and modify the colour of its background.

Disconnecting an event handler

We can disconnect a handler method from an event. Next code example demonstrates such a case.

disconnect.cs

using Gtk;
using System;

class SharpApp : Window {

    Button button;

    public SharpApp() : base("Disconnect")
    {
        SetDefaultSize(250, 150);
        SetPosition(WindowPosition.Center);

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

        button = new Button("Button");

        CheckButton cb = new CheckButton("connect");
        cb.Toggled += OnToggled;

        Fixed fix = new Fixed();
        fix.Put(button, 30, 50);
        fix.Put(cb, 130, 50);

        Add(fix);

        ShowAll();
    }


    void OnClick(object sender, EventArgs args)
    {
        Console.WriteLine("Click");
    }

    void OnToggled(object sender, EventArgs args)
    {
        CheckButton cb = (CheckButton) sender;
        if (cb.Active) {
            button.Clicked += OnClick;
        } else {
            button.Clicked -= OnClick;
        }
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In the code example, we have a button and a check box. We show “Click” text in the console, when we click on the button and the check box is active. The check box connects or disconnects a handler method from the button Clicked event.

CheckButton cb = new CheckButton("connect");
cb.Toggled += OnToggled;

We have a check box. This widget has a Toggled event. We plug a OnToggled() method to this event.

CheckButton cb = (CheckButton) sender;
if (cb.Active) {
    button.Clicked += OnClick;
} else {
    button.Clicked -= OnClick;
}

These lines connect or disconnect an event handler, based on the state of the check box widget.

This chapter was about events in GTK#.

Widgets in GTK#

In this part of the GTK# programming tutorial, we will introduce some GTK# widgets.

Widgets are basic building blocks of a GUI application. Over the years, several widgets became a standard in all toolkits on all OS platforms. For example a button, a check box or a scroll bar. The GTK# toolkit’s philosophy is to keep the number of widgets at a minimum level. More specialised widgets are created as custom GTK# widgets.

Label

The Label widget shows text.

label.cs

using Gtk;

class SharpApp : Window {

   string text = @"Meet you downstairs in the bar and heard
your rolled up sleeves and your skull t-shirt
You say why did you do it with him today?
and sniff me out like I was Tanqueray

cause you're my fella, my guy
hand me your stella and fly
by the time I'm out the door
you tear men down like Roger Moore

I cheated myself
like I knew I would
I told ya, I was trouble
you know that I'm no good";


    public SharpApp() : base("You know I'm No Good")
    {
        BorderWidth = 8;
        SetPosition(WindowPosition.Center);

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

        Label lyrics = new Label(text);
        Add(lyrics);

        ShowAll();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

The code example shows some lyrics on the window.

    string text = @"Meet you downstairs in the bar and heard
your rolled up sleeves and your skull t-shirt
...

In C# programming language, multiline string is preceded with the @ character.

BorderWidth = 8;
The Label is surrounded by some empty space.

Label lyrics = new Label(text);
Add(lyrics);

The Label widget is created and added to the window.

CheckButton

CheckButton is a widget that has two states: on and off. The On state is visualised by a check mark. It is used to denote some boolean property.

checkbutton.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("CheckButton")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);

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

        CheckButton cb = new CheckButton("Show title");
        cb.Active = true;
        cb.Toggled += OnToggle;

        Fixed fix = new Fixed();
        fix.Put(cb, 50, 50);

        Add(fix);
        ShowAll();
    }


    void OnToggle(object sender, EventArgs args) 
    {
        CheckButton cb = (CheckButton) sender;

        if (cb.Active) {
            Title = "CheckButton";
        } else {
            Title = " ";
        }
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}


We will display a title in the titlebar of the window, depending on the state of the CheckButton.

CheckButton cb = new CheckButton("Show title");
CheckButton widget is created.

cb.Active = true;

The title is visible by default, so we check the check button by default.

CheckButton cb = (CheckButton) sender;

Here we cast the sender object to CheckButton class.

if (cb.Active) {
    Title = "CheckButton";
} else {
    Title = " ";
}

Depending on the Active property of the CheckButton, we show or hide the title of the window.

ComboBox

ComboBox is a widget that allows the user to choose from a list of options.

combobox.cs

using Gtk;
using System;

class SharpApp : Window {


    Label label;

    public SharpApp() : base("ComboBox")
    {
       string[] distros = new string[] {"Ubuntu",
            "Mandriva",
            "Red Hat",
            "Fedora",
            "Gentoo" };


        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);
        BorderWidth = 7;
        DeleteEvent += delegate { Application.Quit(); };

        Fixed fix = new Fixed();

        ComboBox cb = new ComboBox(distros);
        cb.Changed += OnChanged;
        label = new Label("-");

        fix.Put(cb, 50, 30);
        fix.Put(label, 50, 140);
        Add(fix);

        ShowAll();
    }

    void OnChanged(object sender, EventArgs args)
    {
        ComboBox cb = (ComboBox) sender;
        label.Text = cb.ActiveText;
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

The example shows a combo box and a label. The combo box has a list of six options. These are the names of Linux distros. The label widget shows the selected option from the combo box.

string[] distros = new string[] {"Ubuntu",
    "Mandriva",
    "Red Hat",
    "Fedora",
    "Gentoo" };

This is an array of strings that will be shown in the ComboBox widget.

ComboBox cb = new ComboBox(distros);

The ComboBox widget is created. The constructor takes the array of strings as a parameter.

void OnChanged(object sender, EventArgs args)
{
    ComboBox cb = (ComboBox) sender;
    label.Text = cb.ActiveText;
}

Inside the OnChanged() method, we get the selected text out of the combo box and set it to the label.

Image

The next example introduces the Image widget. This widget displays pictures.

image.cs

using Gtk;
using System;

class SharpApp : Window {

    Gdk.Pixbuf castle;

    public SharpApp() : base("Red Rock")
    {
        BorderWidth = 1;
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        try {
            castle = new Gdk.Pixbuf("redrock.png");
        } catch {
            Console.WriteLine("Image not found");
            Environment.Exit(1);
        }

        Image image = new Image(castle);
        Add(image);

        ShowAll();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}


We show the Red Rock castle in the window.

try {
    castle = new Gdk.Pixbuf("redrock.png");
} catch {
    Console.WriteLine("Image not found");
    Environment.Exit(1);
}

We create the Gdk.Pixbuf widget. We put the constructor between the try and catch keywords to handle possible errors.

Image image = new Image(castle);
Add(image);

Image widget is created and added to the window.

In this chapter, we showed the first pack of basic widgets of the GTK# programming library.

In this part of the GTK# programming tutorial, we continue introducing GTK# widgets.

We will cover the Entry widget, the Scale widget, ToggleButton, and Calendar widget.

Entry

The Entry is a single line text entry field. This widget is used to enter textual data.

entry.cs

using Gtk;
using System;

class SharpApp : Window {

    Label label;

    public SharpApp() : base("Entry")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);
        BorderWidth = 7;
        DeleteEvent += delegate { Application.Quit(); };

        label = new Label("...");

        Entry entry = new Entry();
        entry.Changed += OnChanged;

        Fixed fix = new Fixed();
        fix.Put(entry, 60, 100);
        fix.Put(label, 60, 40);

        Add(fix);

        ShowAll();
    }

    void OnChanged(object sender, EventArgs args)
    {
        Entry entry = (Entry) sender;
        label.Text = entry.Text;
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

This example shows an entry widget and a label. The text that we key in the entry is displayed immediately in the label control.

Entry entry = new Entry();

Entry widget is created.

entry.Changed += OnChanged;

If the text in the Entry widget is changed, we call the OnChanged() method.

void OnChanged(object sender, EventArgs args)
{
    Entry entry = (Entry) sender;
    label.Text = entry.Text;
}

We get the text from the Entry widget and set it to the label.

Scale

The Scale is a widget that lets the user graphically select a value by sliding a knob within a bounded interval. Our example will show a volume control.

hscale.cs

using Gtk;
using System;

class SharpApp : Window {

    Gdk.Pixbuf mute, min, med, max;
    Image image;

    public SharpApp() : base("Scale")
    {
        SetDefaultSize(260, 150);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        HScale scale = new HScale(0, 100, 1);
        scale.SetSizeRequest(160, 35);
        scale.ValueChanged += OnChanged;

        LoadPixbufs();

        image = new Image(mute);

        Fixed fix = new Fixed();
        fix.Put(scale, 20, 40);
        fix.Put(image, 219, 50);

        Add(fix);

        ShowAll();
    }

    void LoadPixbufs() 
    {
        try {
            mute = new Gdk.Pixbuf("mute.png");
            min = new Gdk.Pixbuf("min.png");
            med = new Gdk.Pixbuf("med.png");
            max = new Gdk.Pixbuf("max.png");
        } catch {
            Console.WriteLine("Error reading Pixbufs");
            Environment.Exit(1);
        }
    }

    void OnChanged(object obj, EventArgs args)
    {
        HScale scale = (HScale) obj;
        double val = scale.Value;

        if (val == 0) {
            image.Pixbuf = mute;
        } else if (val > 0 && val < 30) {
            image.Pixbuf = min;
        } else if (val > 30 && val < 80) {
            image.Pixbuf = med;
        } else {
            image.Pixbuf = max;
        }
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In the example above, we have HScale and Image widgets. By dragging the scale we change the image on the Image widget.

HScale scale = new HScale(0, 100, 1);

HScale widget is created. The parameters are lower boundary, upper boundary and step.

HScale scale = (HScale) obj;
double val = scale.Value;

In the OnChange() method we obtain the value of the scale widget.

if (val == 0) {
    image.Pixbuf = mute;
} else if (val > 0 && val <= 30) {
    image.Pixbuf = min;
} else if (val > 30 && val < 80) {
    image.Pixbuf = med;
} else {
image.Pixbuf = max;
}

Depending on the obtained value, we change the picture in the image widget.

ToggleButton

ToggleButton is a button that has two states: pressed and not pressed. You toggle between these two states by clicking on it. There are situations where this functionality fits well.

togglebuttons.cs

using Gtk;
using System;

class SharpApp : Window {

    DrawingArea darea;
    Gdk.Color col;

    public SharpApp() : base("ToggleButtons")
    {
        col = new Gdk.Color(0, 0, 0);

        SetDefaultSize(350, 240);
        SetPosition(WindowPosition.Center);
        BorderWidth = 7;
        DeleteEvent += delegate { Application.Quit(); };

        ToggleButton red = new ToggleButton("Red");
        red.SetSizeRequest(80, 35);
        red.Clicked += OnRed;

        ToggleButton green = new ToggleButton("Green");
        green.SetSizeRequest(80, 35);
        green.Clicked += OnGreen;

        ToggleButton blue = new ToggleButton("Blue");
        blue.SetSizeRequest(80, 35);
        blue.Clicked += OnBlue;

        darea = new DrawingArea();
        darea.SetSizeRequest(150, 150);
        darea.ModifyBg(StateType.Normal, col);

        Fixed fix = new Fixed();
        fix.Put(red, 30, 30);
        fix.Put(green, 30, 80);
        fix.Put(blue, 30, 130);
        fix.Put(darea, 150, 30);

        Add(fix);

        ShowAll();
    }

    void OnRed(object sender, EventArgs args) 
    {
        ToggleButton tb = (ToggleButton) sender;

        if (tb.Active) {
            col.Red = 65535; 
        } else {
            col.Red = 0;
        }

        darea.ModifyBg(StateType.Normal, col);         
    }

    void OnGreen(object sender, EventArgs args) 
    {
        ToggleButton tb = (ToggleButton) sender;

        if (tb.Active) {
            col.Green = 65535; 
        } else {
            col.Green = 0;
        }

        darea.ModifyBg(StateType.Normal, col);
    }

    void OnBlue(object sender, EventArgs args) 
    {
        ToggleButton tb = (ToggleButton) sender;

        if (tb.Active) {
            col.Blue = 65535; 
        } else {
            col.Blue = 0;
        }

        darea.ModifyBg(StateType.Normal, col);
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In our example, we show three toggle buttons and a DrawingArea. We set the background colour of the area to black. The toggle buttons will toggle the red, green and blue parts of the colour value. The background colour will depend on which toggle buttons we have pressed.

col = new Gdk.Color(0, 0, 0);

This is the colour value that is going to be updated with the toggle buttons.

ToggleButton red = new ToggleButton("Red");
red.SetSizeRequest(80, 35);
red.Clicked += OnRed;

The ToggleButton widget is created. We set its size to 80x35 pixels. Each of the toggle buttons has its own handler method.

darea = new DrawingArea();
darea.SetSizeRequest(150, 150);
darea.ModifyBg(StateType.Normal, col);

The DrawingArea widget is the widget that displays the colour, mixed by the toggle buttons. At start, it shows black colour.

if (tb.Active) {
    col.Red = 65535; 
} else {
    col.Red = 0;
}

We update the red part of the colour according to the value of the Active property.

darea.ModifyBg(StateType.Normal, col);

We update the colour of the DrawingArea widget.

Calendar

Our final widget is the Calendar widget. It is used to work with dates.

calendar.cs

using Gtk;
using System;

class SharpApp : Window {

    private Label label;

    public SharpApp() : base("Calendar")
    {
        SetDefaultSize(300, 270);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        label = new Label("...");

        Calendar calendar = new Calendar();
        calendar.DaySelected += OnDaySelected;

        Fixed fix = new Fixed();
        fix.Put(calendar, 20, 20);
        fix.Put(label, 40, 230);

        Add(fix);

        ShowAll();
    }

    void OnDaySelected(object sender, EventArgs args)
    {
        Calendar cal = (Calendar) sender;
        label.Text = cal.Month + 1 + "/" + cal.Day + "/" + cal.Year;
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

We have the Calendar widget and a Label. The selected day from the calendar is shown in the label.

Calendar calendar = new Calendar();

Calendar widget is created.

Calendar cal = (Calendar) sender;
label.Text = cal.Month + 1 + "/" + cal.Day + "/" + cal.Year;

In the OnDaySelected() method we get the referece to the Calendar widget, and update the label to the currently selected date.

In this chapter, we finished talking about the GTK# widgets.

In this part of the GTK# programming tutorial, we will introduce some more advanced widgets in the GTK#. We will cover IconView, ListView and TreeView widgets.

IconView

The IconView is a widget which displays a list of icons in a grid.

iconview.cs

using System;
using System.IO;
using Gtk;


public class SharpApp : Window
{
    const int COL_PATH = 0;
    const int COL_DISPLAY_NAME = 1;
    const int COL_PIXBUF = 2;
    const int COL_IS_DIRECTORY = 3;

    DirectoryInfo root = new DirectoryInfo("/");
    Gdk.Pixbuf dirIcon, fileIcon;
    ListStore store;
    ToolButton upButton;

    public SharpApp() : base("IconView")
    {
        SetDefaultSize(650, 400);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        VBox vbox = new VBox(false, 0);
        Add(vbox);

        Toolbar toolbar = new Toolbar();
        vbox.PackStart(toolbar, false, false, 0);

        upButton = new ToolButton(Stock.GoUp);
        upButton.IsImportant = true;
        upButton.Sensitive = false;
        toolbar.Insert(upButton, -1);

        ToolButton homeButton = new ToolButton(Stock.Home);
        homeButton.IsImportant = true;
        toolbar.Insert(homeButton, -1);

        fileIcon = GetIcon(Stock.File);
        dirIcon = GetIcon(Stock.Open);

        ScrolledWindow sw = new ScrolledWindow();
        sw.ShadowType = ShadowType.EtchedIn;
        sw.SetPolicy(PolicyType.Automatic, PolicyType.Automatic);
        vbox.PackStart(sw, true, true, 0);

        store = CreateStore();
        FillStore();

        IconView iconView = new IconView(store);
        iconView.SelectionMode = SelectionMode.Multiple;

        upButton.Clicked += new EventHandler(OnUpClicked);
        homeButton.Clicked += new EventHandler(OnHomeClicked);

        iconView.TextColumn = COL_DISPLAY_NAME;
        iconView.PixbufColumn = COL_PIXBUF;

        iconView.ItemActivated += OnItemActivated;
        sw.Add(iconView);
        iconView.GrabFocus();

        ShowAll();
    }

    Gdk.Pixbuf GetIcon(string name)
    {
        return Gtk.IconTheme.Default.LoadIcon(name, 48, (IconLookupFlags) 0);
    }

    ListStore CreateStore()
    {
        ListStore store = new ListStore(typeof (string), 
            typeof(string), typeof (Gdk.Pixbuf), typeof(bool));

        store.SetSortColumnId(COL_DISPLAY_NAME, SortType.Ascending);

        return store;
    }

    void FillStore()
    {
        store.Clear();

        if (!root.Exists)
            return;

        foreach (DirectoryInfo di in root.GetDirectories())
        {
            if (!di.Name.StartsWith("."))
                store.AppendValues(di.FullName, di.Name, dirIcon, true);
        }

        foreach (FileInfo file in root.GetFiles())
        {
            if (!file.Name.StartsWith("."))
                store.AppendValues(file.FullName, file.Name, fileIcon, false);
        }
    }

    void OnHomeClicked(object sender, EventArgs a)
    {
        root = new DirectoryInfo(Environment.GetFolderPath(
            Environment.SpecialFolder.Personal));
        FillStore();
        upButton.Sensitive = true;
    }

    void OnItemActivated(object sender, ItemActivatedArgs a)
    {
        TreeIter iter;
        store.GetIter(out iter, a.Path);
        string path = (string) store.GetValue(iter, COL_PATH);
        bool isDir = (bool) store.GetValue(iter, COL_IS_DIRECTORY);

        if (!isDir)
            return;

        root = new DirectoryInfo(path);
        FillStore();

        upButton.Sensitive = true;
    }

    void OnUpClicked(object sender, EventArgs a)
    {
        root = root.Parent;
        FillStore();
        upButton.Sensitive = (root.FullName == "/" ? false : true);
    }

    public static void Main()
    {   
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

  MAC下无效!

This example shows icons of the currently selected directory. It has a toolbar and two buttons. Up button and home button. We use them to navigate through the file system.

ListStore store;

The ListStore is a columned list data structure. We use it to store our data for the IconView widget.

DirectoryInfo root = new DirectoryInfo("/");

This is the root directory. We start here. The root directory is the directory, which we show in the IconView widget.

ListStore store = new ListStore(typeof(string), 
    typeof(string), typeof(Gdk.Pixbuf), typeof(bool));

In the CreateStore() method, we create the ListStore object. It has four parameters. The path, the name of the directory or file, image and the boolean value determining if it is an directory or a file.

foreach (DirectoryInfo di in root.GetDirectories())
{
    if (!di.Name.StartsWith("."))
        store.AppendValues(di.FullName, di.Name, dirIcon, true);
}

In the FillStore() method, we fill the list store with data. Here, we find out all directories in the current path.

void OnHomeClicked(object sender, EventArgs a)
{
    root = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
    FillStore();
    upButton.Sensitive = true;
}

If we click on the home button, we get the reference to the home directory. Refill the list store. And make the up button active.

void OnItemActivated(object sender, ItemActivatedArgs a)
{ 
...
}

In the OnItemActivated() method, we react to an event, which is generated, when we click on a icon from the icon view widget.

string path = (string) store.GetValue(iter, COL_PATH);
bool isDir = (bool) store.GetValue(iter, COL_IS_DIRECTORY);

if (!isDir)
    return;

We get the path of the activated item. And we determine if it is a directory or a file. If it is a file, we return.

root = new DirectoryInfo(path);
FillStore();

upButton.Sensitive = true;

In case it is a directory, we replace the root with the current path, refill the store and make the up button sensitive.

void OnUpClicked(object sender, EventArgs a)
{
    root = root.Parent;
    FillStore();
    upButton.Sensitive = (root.FullName == "/" ? false : true);
}

If we click on the up button, we replace the root directory with its parent directory. Refill the list store. And the up button is activated if we are below the root (/) directory of the file system.

ListView

In the following example, we use the TreeView widget to show a list view. Again the ListStore is used to store data.

listview.cs

using System;
using System.Collections;
using Gtk;

public class Actress
{
    public string Name;
    public string Place;
    public int Year;

    public Actress(string name, string place, int year)
    {
        Name = name;
        Place = place;
        Year = year;
    }
}

public class SharpApp : Window
{
    ListStore store;
    Statusbar statusbar;

    enum Column
    {
        Name,
        Place,
        Year
    }

    Actress[] actresses =
    {
        new Actress("Jessica Alba", "Pomona", 1981),
        new Actress("Sigourney Weaver", "New York", 1949),
        new Actress("Angelina Jolie", "Los Angeles", 1975),
        new Actress("Natalie Portman", "Jerusalem", 1981),
        new Actress("Rachel Weissz", "London", 1971),
        new Actress("Scarlett Johansson", "New York", 1984) 
    };

    public SharpApp() : base ("ListView")
    {
        BorderWidth = 8;

        SetDefaultSize(350, 250);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        VBox vbox = new VBox(false, 8);


        ScrolledWindow sw = new ScrolledWindow();
        sw.ShadowType = ShadowType.EtchedIn;
        sw.SetPolicy(PolicyType.Automatic, PolicyType.Automatic);
        vbox.PackStart(sw, true, true, 0);

        store = CreateModel();

        TreeView treeView = new TreeView(store);
        treeView.RulesHint = true;
        treeView.RowActivated += OnRowActivated;
        sw.Add(treeView);

        AddColumns(treeView);

        statusbar = new Statusbar();

        vbox.PackStart(statusbar, false, false, 0);

        Add(vbox);
        ShowAll();
    }

    void OnRowActivated (object sender, RowActivatedArgs args) {

        TreeIter iter;        
        TreeView view = (TreeView) sender;   

        if (view.Model.GetIter(out iter, args.Path)) {
            string row = (string) view.Model.GetValue(iter, (int) Column.Name );
            row += ", " + (string) view.Model.GetValue(iter, (int) Column.Place );
            row += ", " + view.Model.GetValue(iter, (int) Column.Year );
            statusbar.Push(0, row);
        }
    }

    void AddColumns(TreeView treeView)
    {
        CellRendererText rendererText = new CellRendererText();
        TreeViewColumn column = new TreeViewColumn("Name", rendererText,
            "text", Column.Name);
        column.SortColumnId = (int) Column.Name;
        treeView.AppendColumn(column);

        rendererText = new CellRendererText();
        column = new TreeViewColumn("Place", rendererText, 
            "text", Column.Place);
        column.SortColumnId = (int) Column.Place;
        treeView.AppendColumn(column);

        rendererText = new CellRendererText();
        column = new TreeViewColumn("Year", rendererText, 
            "text", Column.Year);
        column.SortColumnId = (int) Column.Year;
        treeView.AppendColumn(column);
    }


    ListStore CreateModel()
    {
        ListStore store = new ListStore( typeof(string),
            typeof(string), typeof(int) );

        foreach (Actress act in actresses) {
            store.AppendValues(act.Name, act.Place, act.Year );
        }

        return store;
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
} 

In our example, we show a list of six actresses in the TreeView widget. Each of the rows shows the name, the place of born and the year of born for each of them.

public class Actress
{
    public string Name;
    public string Place;
    public int Year;
...
}

The Actress class is used for storing data about an actress.

ListStore CreateModel()
{
    ListStore store = new ListStore( typeof(string), 
        typeof(string), typeof(int) );

    foreach (Actress act in actresses) {
        store.AppendValues(act.Name, act.Place, act.Year );
    }

    return store;
}

In the CreateModel() method, we create the list store. The list store has three parameters. The name of the actress, the place of born and year of born. This is the data model of our TreeView widget.

TreeView treeView = new TreeView(store);
treeView.RulesHint = true;
treeView.RowActivated += OnRowActivated;

Here we create the TreeView widget, taking the list store as a parameter.

CellRendererText rendererText = new CellRendererText();
TreeViewColumn column = new TreeViewColumn("Name", rendererText, "text", Column.Name);
column.SortColumnId = (int) Column.Name;
treeView.AppendColumn(column);

In the AddColumns() method, we add three columns to our TreeView widget. The above code creates a column displaying names of the actresses.

if (view.Model.GetIter(out iter, args.Path)) {
    string row = (string) view.Model.GetValue(iter, (int) Column.Name );
    row += ", " + (string) view.Model.GetValue(iter, (int) Column.Place );
    row += ", " + view.Model.GetValue(iter, (int) Column.Year );
    statusbar.Push(0, row);
}

If we double click on an item, we display the whole row in the statusbar.

TreeView Tutorial

link: https://www.mono-project.com/docs/gui/gtksharp/widgets/treeview-tutorial/

Introduction

The GTK TreeView widget is used to display data in one of the most basic and intuitive ways possible: a list. Each row in the list can be separated into multiple columns, and rows in the list can contain child rows to create an expandable-list, or tree.

The TreeView can be extremely intimidating at first, but it is extremely powerful - especially when compared to the ListBox and ListView controls from the Windows Forms toolkit.

Model, View, Controller

The TreeView uses the Model-View-Controller (MVC) design pattern. Components of the TreeView are separated into these three categories: The Model, which stores data to be displayed, the View, which controls how to display the data, and the controller, which controls what data is displayed, how it is sorted, etc.

Model

The TreeView has two basic models: ListStore, which stores a flat set of data, and TreeStore, which stores data that can contain sub-items (used for creating a Tree). All TreeView Models implement the Gtk.TreeModel interface.

View

The View is made up of three different parts as well - The column, the cell renderers, and the widget itself.

The TreeView widget is responsible for laying out the columns and rows of data, as well as providing all the basic user-interaction functionality such as selecting items, etc.

Each TreeViewColumn contains at least one CellRenderer. Cell renderers are what actually display the data - items in the model are mapped to cell renderers.

The following cell renderers are available:

  • CellRendererText - Used to display text
  • CellRendererPixbuf - Used to display images
  • CellRendererProgress - Used to display a progress bar
  • CellRendererCombo - Used to display a drop-down box
  • CellRendererToggle - Used to display a check box

CellRenderers are separate from TreeViewColumns for added flexibility, allowing you to have an extremely fine-tuned treeview tailored to your application. For example you can pack an image and text into the same column, which often makes much more sense than creating a separate column for each.

Controller

Controllers modify how the data in the model is passed off to the View, and let you do things such as sorting and filtering the data.

Your first TreeView

Setting it up
Here is a basic example of how to use the TreeView and all its related components. In our example, we will show a simple list of song titles and artist names:

//file: TreeViewExample.cs
//mcs -pkg:gtk-sharp TreeViewExample.cs
public class TreeViewExample
{
    public static void Main ()
    {
        Gtk.Application.Init ();
        new TreeViewExample ();
        Gtk.Application.Run ();
    }

    public TreeViewExample ()
    {
        // Create a Window
        Gtk.Window window = new Gtk.Window ("TreeView Example");
        window.SetSizeRequest (500,200);

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

        // Add our tree to the window
        window.Add (tree);

        // Create a column for the artist name
        Gtk.TreeViewColumn artistColumn = new Gtk.TreeViewColumn ();
        artistColumn.Title = "Artist";

        // Create a column for the song title
        Gtk.TreeViewColumn songColumn = new Gtk.TreeViewColumn ();
        songColumn.Title = "Song Title";

        // Add the columns to the TreeView
        tree.AppendColumn (artistColumn);
        tree.AppendColumn (songColumn);

        // Create a model that will hold two strings - Artist Name and Song Title
        Gtk.ListStore musicListStore = new Gtk.ListStore (typeof (string), typeof (string));


        // Assign the model to the TreeView
        tree.Model = musicListStore;

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

Compile and run the application, and you will end up with this:

Cool! So we have our TreeView displaying our two desired columns, now lets add some data in there.

Adding some data

// Add some data to the store
musicListStore.AppendValues ("Garbage", "Dog New Tricks");

This inserts a new row into the Model. We specify the same number of arguments here as we defined when we constructed the Gtk.ListStore above.

As mentioned in the introduction, the TreeViewCells don’t actually render any of your data themselves - they just contain the Cell Renderers that do, so we need to create two Cell Renderers, one for each column:

// Create the text cell that will display the artist name
Gtk.CellRendererText artistNameCell = new Gtk.CellRendererText ();

// Add the cell to the column
artistColumn.PackStart (artistNameCell, true);

// Do the same for the song title column
Gtk.CellRendererText songTitleCell = new Gtk.CellRendererText ();
songColumn.PackStart (songTitleCell, true);

The TreeView doesn’t automatically know which cells are supposed to display which items from the Model, so we need to link it up:

// Tell the Cell Renderers which items in the model to display
artistColumn.AddAttribute (artistNameCell, "text", 0);
songColumn.AddAttribute (songTitleCell, "text", 1);

The first argument specifies which Cell Renderer we want to assign something to, the second specifies which of the cell renderer’s properties (converted to lowercase) we want to assign something to (CellRendererText.Text in our case), and the third argument specifies the position in the Model that the value should be obtained from. Above when we added a row to the model, we specified the artist first, then the song title, so we use that here. Remember the order of items in the Model is not automatically linked to the order of your columns in the TreeView in any way, so while keeping everything in the same order makes things a lot easier to manage and understand, it is not required.

You are not limited to assigning one property to the store per CellRenderer, you can call AddAttribute for the same CellRenderer as many times as you would like, to control the different properties of the CellRenderer.

WOOHOO! We now have this:

Here’s the complete code:

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

    public TreeViewExample ()
    {
        // Create a Window
        Gtk.Window window = new Gtk.Window ("TreeView Example");
        window.SetSizeRequest (500,200);

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

        // Add our tree to the window
        window.Add (tree);

        // Create a column for the artist name
        Gtk.TreeViewColumn artistColumn = new Gtk.TreeViewColumn ();
        artistColumn.Title = "Artist";

        // Create the text cell that will display the artist name
        Gtk.CellRendererText artistNameCell = new Gtk.CellRendererText ();

        // Add the cell to the column
        artistColumn.PackStart (artistNameCell, true);

        // Create a column for the song title
        Gtk.TreeViewColumn songColumn = new Gtk.TreeViewColumn ();
        songColumn.Title = "Song Title";

        // Do the same for the song title column
        Gtk.CellRendererText songTitleCell = new Gtk.CellRendererText ();
        songColumn.PackStart (songTitleCell, true);

        // Add the columns to the TreeView
        tree.AppendColumn (artistColumn);
        tree.AppendColumn (songColumn);

        // Tell the Cell Renderers which items in the model to display
        artistColumn.AddAttribute (artistNameCell, "text", 0);
        songColumn.AddAttribute (songTitleCell, "text", 1);

        // Create a model that will hold two strings - Artist Name and Song Title
        Gtk.ListStore musicListStore = new Gtk.ListStore (typeof (string), typeof (string));

        // Add some data to the store
        musicListStore.AppendValues ("Garbage", "Dog New Tricks");

        // Assign the model to the TreeView
        tree.Model = musicListStore;

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

Creating a Tree

To create a tree instead of a flat list, we use a Gtk.TreeStore as our model.

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

Then, when we add a value into the model we specify the parent iter as the first argument:

Gtk.TreeIter iter = musicListStore.AppendValues ("Dance");
musicListStore.AppendValues (iter, "Fannypack", "Nu Nu (Yeah Yeah) (double j and haze radio edit)");

iter = musicListStore.AppendValues ("Hip-hop");
musicListStore.AppendValues (iter, "Nelly", "Country Grammer");

And now we end up with this:

Here’s the complete example:

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

    public TreeViewExample ()
    {
        Gtk.Window window = new Gtk.Window ("TreeView Example");
        window.SetSizeRequest (500,200);

        Gtk.TreeView tree = new Gtk.TreeView ();
        window.Add (tree);

        Gtk.TreeViewColumn artistColumn = new Gtk.TreeViewColumn ();
        artistColumn.Title = "Artist";

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

        artistColumn.PackStart (artistNameCell, true);

        Gtk.TreeViewColumn songColumn = new Gtk.TreeViewColumn ();
        songColumn.Title = "Song Title";

        Gtk.CellRendererText songTitleCell = new Gtk.CellRendererText ();
        songColumn.PackStart (songTitleCell, true);

        tree.AppendColumn (artistColumn);
        tree.AppendColumn (songColumn);

        artistColumn.AddAttribute (artistNameCell, "text", 0);
        songColumn.AddAttribute (songTitleCell, "text", 1);

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

        Gtk.TreeIter iter = musicListStore.AppendValues ("Dance");
        musicListStore.AppendValues (iter, "Fannypack", "Nu Nu (Yeah Yeah) (double j and haze radio edit)");

        iter = musicListStore.AppendValues ("Hip-hop");
        musicListStore.AppendValues (iter, "Nelly", "Country Grammer");

        tree.Model = musicListStore;

        window.ShowAll ();
    }
}

Filtering Data

The TreeView makes it very easy to prevent certain rows from being displayed, without having to remove them from the model.

Lets say we are starting out with this set of data:

We place a TreeModelFilter between the View (TreeView) and the model (ListStore) that filters what data is passed from the model to the view.

// Create the filter and tell it to use the musicListStore as it's base Model
filter = new Gtk.TreeModelFilter (musicListStore, 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 tree's model
tree.Model = filter;

We then create the FilterTree method, which determines which rows are visible and which are hidden:

private bool FilterTree (Gtk.TreeModel model, Gtk.TreeIter iter)
{
    string artistName = model.GetValue (iter, 0).ToString ();
    if (artistName == "BT")
        return true;
    else
        return false;
}

And now we end up with this:

Here is a complete example demonstrating how you can use a text entry widget to control the filter.

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);

        // 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 ("Artist 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 tree = 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 (tree, true, true, 5);

        window.Add (box);

        // Create a column for the artist name
        Gtk.TreeViewColumn artistColumn = new Gtk.TreeViewColumn ();
        artistColumn.Title = "Artist";

        // Create the text cell that will display the artist name
        Gtk.CellRendererText artistNameCell = new Gtk.CellRendererText ();

        // Add the cell to the column
        artistColumn.PackStart (artistNameCell, true);

        // Create a column for the song title
        Gtk.TreeViewColumn songColumn = new Gtk.TreeViewColumn ();
        songColumn.Title = "Song Title";

        // Do the same for the song title column
        Gtk.CellRendererText songTitleCell = new Gtk.CellRendererText ();
        songColumn.PackStart (songTitleCell, true);

        // Add the columns to the TreeView
        tree.AppendColumn (artistColumn);
        tree.AppendColumn (songColumn);

        // Tell the Cell Renderers which items in the model to display
        artistColumn.AddAttribute (artistNameCell, "text", 0);
        songColumn.AddAttribute (songTitleCell, "text", 1);

        // Create a model that will hold two strings - Artist Name and Song Title
        Gtk.ListStore musicListStore = new Gtk.ListStore (typeof (string), typeof (string));

        // Add some data to the store
        musicListStore.AppendValues ("BT", "Circles");
        musicListStore.AppendValues ("Daft Punk", "Technologic");
        musicListStore.AppendValues ("Daft Punk", "Digital Love");
        musicListStore.AppendValues ("The Crystal Method", "PHD");
        musicListStore.AppendValues ("The Crystal Method", "Name of the game");
        musicListStore.AppendValues ("The Chemical Brothers", "Galvanize");

        // Instead of assigning the ListStore model directly to the TreeStore, we create a TreeModelFilter
        // which sits between the Model (the ListStore) and the View (the TreeView) filtering what the model sees.
        // Some may say that this is a "Controller", even though the name and usage suggests that it is still part of
        // the Model.
        filter = new Gtk.TreeModelFilter (musicListStore, 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 tree's model
        tree.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 artistName = model.GetValue (iter, 0).ToString ();

        if (filterEntry.Text == "")
            return true;

        if (artistName.IndexOf (filterEntry.Text) > -1)
            return true;
        else
            return false;
    }
}

Controlling how the model is used

The TreeView allows you write methods that extract specific data from your Model, and link your CellRenderers to them, rather than directly to the model.

This is one of the extremely useful features of TreeModel because it means you can store a reference any .NET object in the model that contains all the data you want your TreeView to display, instead of storing each piece of data individually as a string, so you don’t have to maintain it in two places.

In the preveous examples we inserted both the song and artist into each row of the ListStore. If the TreeView is displaying a large amount of data this can use a lot of memory, and if the backend data changes (song title changes, etc.) the TreeModel doesnt know about it and you have to keep it in-sync manually.

Lets take the following example, and say we want to create a TreeView to display the data:

using System.Collections;

public class Song
{
    public Song (string artist, string title)
    {
        this.Artist = artist;
        this.Title = title;
    }

    public string Artist;
    public string Title;
}

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

    ArrayList songs;

    public TreeViewExample ()
    {
        songs = new ArrayList ();

        songs.Add (new Song ("Dancing DJs vs. Roxette", "Fading Like a Flower"));
        songs.Add (new Song ("Xaiver", "Give me the night"));
        songs.Add (new Song ("Daft Punk", "Technologic"));

        Gtk.Window window = new Gtk.Window ("TreeView Example");
        window.SetSizeRequest (500,200);

        Gtk.TreeView tree = new Gtk.TreeView ();
        window.Add (tree);

        Gtk.TreeViewColumn artistColumn = new Gtk.TreeViewColumn ();
        artistColumn.Title = "Artist";
        Gtk.CellRendererText artistNameCell = new Gtk.CellRendererText ();
        artistColumn.PackStart (artistNameCell, true);

        Gtk.TreeViewColumn songColumn = new Gtk.TreeViewColumn ();
        songColumn.Title = "Song Title";
        Gtk.CellRendererText songTitleCell = new Gtk.CellRendererText ();
        songColumn.PackStart (songTitleCell, true);

        Gtk.ListStore musicListStore = new Gtk.ListStore (typeof (Song));
        foreach (Song song in songs) {
            musicListStore.AppendValues (song);
        }

        tree.Model = musicListStore;

        tree.AppendColumn (artistColumn);
        tree.AppendColumn (songColumn);

        window.ShowAll ();

    }
}

The TreeView doesnt automatically know how to link up the fields in the Song class to the different CellRenderers, so instead of using AddAttribute as we did in our first example, we use SetCellDataFunc:

artistColumn.SetCellDataFunc (artistNameCell, new Gtk.TreeCellDataFunc (RenderArtistName));
songColumn.SetCellDataFunc (songTitleCell, new Gtk.TreeCellDataFunc (RenderSongTitle));

We then create two methods, which extract the information that we want from the Song object and set the appropriate property of the CellRenderer, “Text” in our case:

private void RenderArtistName (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
{
    Song song = (Song) model.GetValue (iter, 0);
    (cell as Gtk.CellRendererText).Text = song.Artist;
}

private void RenderSongTitle (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
{
    Song song = (Song) model.GetValue (iter, 0);
    (cell as Gtk.CellRendererText).Text = song.Title;
}

We now have only one item per row in the store, but we display two columns in the tree!

Both of the example methods above demonstrate how to modify one property of the CellRenderer, but you are certainly not limited to only modifing one.

This example is fairly pointless, but it demonstrates how to modify multiple properties. A more practical use for this would be to change the color based on the state of an object:

private void RenderArtistName (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
{
    Song song = (Song) model.GetValue (iter, 0);
    if (song.Artist.StartsWith ("X") == true) {
        (cell as Gtk.CellRendererText).Foreground = "red";
    } else {
        (cell as Gtk.CellRendererText).Foreground = "darkgreen";
    }
    (cell as Gtk.CellRendererText).Text = song.Artist;
}


Here is the complete example:

using System.Collections;

public class Song
{
    public Song (string artist, string title)
    {
        this.Artist = artist;
        this.Title = title;
    }

    public string Artist;
    public string Title;
}

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

    ArrayList songs;

    public TreeViewExample ()
    {
        songs = new ArrayList ();

        songs.Add (new Song ("Dancing DJs vs. Roxette", "Fading Like a Flower"));
        songs.Add (new Song ("Xaiver", "Give me the night"));
        songs.Add (new Song ("Daft Punk", "Technologic"));

        Gtk.Window window = new Gtk.Window ("TreeView Example");
        window.SetSizeRequest (500,200);

        Gtk.TreeView tree = new Gtk.TreeView ();
        window.Add (tree);

        Gtk.TreeViewColumn artistColumn = new Gtk.TreeViewColumn ();
        artistColumn.Title = "Artist";
        Gtk.CellRendererText artistNameCell = new Gtk.CellRendererText ();
        artistColumn.PackStart (artistNameCell, true);

        Gtk.TreeViewColumn songColumn = new Gtk.TreeViewColumn ();
        songColumn.Title = "Song Title";
        Gtk.CellRendererText songTitleCell = new Gtk.CellRendererText ();
        songColumn.PackStart (songTitleCell, true);

        Gtk.ListStore musicListStore = new Gtk.ListStore (typeof (Song));
        foreach (Song song in songs) {
            musicListStore.AppendValues (song);
        }

        artistColumn.SetCellDataFunc (artistNameCell, new Gtk.TreeCellDataFunc (RenderArtistName));
        songColumn.SetCellDataFunc (songTitleCell, new Gtk.TreeCellDataFunc (RenderSongTitle));

        tree.Model = musicListStore;

        tree.AppendColumn (artistColumn);
        tree.AppendColumn (songColumn);

        window.ShowAll ();

    }

    private void RenderArtistName (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
    {
        Song song = (Song) model.GetValue (iter, 0);

        if (song.Artist.StartsWith ("X") == true) {
            (cell as Gtk.CellRendererText).Foreground = "red";
        } else {
            (cell as Gtk.CellRendererText).Foreground = "darkgreen";
        }

        (cell as Gtk.CellRendererText).Text = song.Artist;
    }

    private void RenderSongTitle (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
    {
        Song song = (Song) model.GetValue (iter, 0);
        (cell as Gtk.CellRendererText).Text = song.Title;
    }

}

Shortcuts - Writing Less Code

The Gtk TreeView API provides several convinence methods that makes it possible to create a basic tree using much less code than we used above.

Here is the same demo as first example on this page, using these methods, note that it is significantly less code:

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

    public TreeViewExample ()
    {
        Gtk.Window window = new Gtk.Window ("TreeView Example");
        window.SetSizeRequest (500,200);

        Gtk.TreeView tree = new Gtk.TreeView ();
        Gtk.ListStore musicListStore = new Gtk.ListStore (typeof (string), typeof (string));

        tree.AppendColumn ("Artist", new Gtk.CellRendererText (), "text", 0);
        tree.AppendColumn ("Title", new Gtk.CellRendererText (), "text", 1);

        musicListStore.AppendValues ("Garbage", "Dog New Tricks");

        tree.Model = musicListStore;

        window.Add (tree);
        window.ShowAll ();
    }
}

Editable Text Cells

Making an editable text cell (so the user can click on the cell and modify the value) is extremely easy.

First we must mark the cell as editable, and assign a method to handle the Edited event, which fires when the user is done editing:

artistNameCell.Editable = true;
artistNameCell.Edited += artistNameCell_Edited;

Our event handler method needs to update the model with the new value that the user entered:

private void artistNameCell_Edited (object o, Gtk.EditedArgs args)
{
    Gtk.TreeIter iter;
    musicListStore.GetIter (out iter, new Gtk.TreePath (args.Path));

    Song song = (Song) musicListStore.GetValue (iter, 0);
    song.Artist = args.NewText;
}

注意:此时的 musicListStore 是全局变量。
And that’s all there is to it!

Drawing icons in rows

For using Pixbuf you need, first to change the ListStore constructor, (notice the Gdk.Pixbuf parameter)

Gtk.ListStore musicListStore = new Gtk.ListStore (typeof (Gdk.Pixbuf), typeof (string),  typeof (string));

add the column that will render the pixbuf

tree.AppendColumn ("Icon", new Gtk.CellRendererPixbuf (), "pixbuf", 0);

and the AppendValues method with the correct parameters

musicListStore.AppendValues (new Gdk.Pixbuf ("TreeViewRupertIcon.png"), "Rupert", "Yellow bananas");

And then

Complete sample

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

    public TreeViewExample ()
    {
        Gtk.Window window = new Gtk.Window ("TreeView Example");
        window.SetSizeRequest (500,200);

        Gtk.TreeView tree = new Gtk.TreeView ();
        Gtk.ListStore musicListStore = new Gtk.ListStore (typeof (Gdk.Pixbuf),
            typeof (string),  typeof (string));

        tree.AppendColumn ("Icon", new Gtk.CellRendererPixbuf (), "pixbuf", 0);
        tree.AppendColumn ("Artist", new Gtk.CellRendererText (), "text", 1);
        tree.AppendColumn ("Title", new Gtk.CellRendererText (), "text", 2);

        musicListStore.AppendValues (new Gdk.Pixbuf ("TreeViewRupertIcon.png"),
            "Rupert", "Yellow bananas");

        tree.Model = musicListStore;

        window.Add (tree);
        window.ShowAll ();
    }
}

TreeView-Example1

In the last example of this chapter, we use the TreeView widget to show a hierarchical tree of data.

tree.cs

using Gtk;


public class SharpApp : Window
{

    public SharpApp() : base ("Tree")
    {
        SetDefaultSize(400, 300);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        TreeView tree = new TreeView();

        TreeViewColumn languages = new TreeViewColumn();
        languages.Title = "Programming languages";

        CellRendererText cell = new CellRendererText();
        languages.PackStart(cell, true);
        languages.AddAttribute(cell, "text", 0);

        TreeStore treestore = new TreeStore(typeof(string), typeof(string));

        TreeIter iter = treestore.AppendValues("Scripting languages");
        treestore.AppendValues(iter, "Python");
        treestore.AppendValues(iter, "PHP");
        treestore.AppendValues(iter, "Perl");
        treestore.AppendValues(iter, "Ruby");

        iter = treestore.AppendValues("Compiling languages");
        treestore.AppendValues(iter, "C#");
        treestore.AppendValues(iter, "C++");
        treestore.AppendValues(iter, "C");
        treestore.AppendValues(iter, "Java");

        tree.AppendColumn(languages);
        tree.Model = treestore;

        Add(tree);
        ShowAll();
    }

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

This time we use the TreeView widget to show hierarchical data.

TreeView tree = new TreeView();
TreeView widget is created.

TreeViewColumn languages = new TreeViewColumn();
languages.Title = "Programming languages";

It has one column named “Programming languages”.

CellRendererText cell = new CellRendererText();
languages.PackStart(cell, true);
languages.AddAttribute(cell, "text", 0);

We show textual data in the TreeView widget.

TreeStore treestore = new TreeStore(typeof(string), typeof(string));
To store the data, we use the TreeStore object.

TreeIter iter = treestore.AppendValues("Scripting languages");
treestore.AppendValues(iter, "Python");
treestore.AppendValues(iter, "PHP");

We append data to the tree. The TreeIter is for accessing data in a row.

tree.AppendColumn(languages);
A column is appended to the tree.

tree.Model = treestore;

Finally, we set a data model for the tree widget.

TreeView-Example2

using Gtk;


public class SharpApp : Window
{

    public SharpApp() : base("Tree")
    {
        SetDefaultSize(400, 300);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        TreeView tree = new TreeView();

        TreeViewColumn languages = new TreeViewColumn();
        languages.Title = "Programming languages";

        CellRendererText cell = new CellRendererText();
        languages.PackStart(cell, true);
        languages.AddAttribute(cell, "text", 0);

        TreeStore treestore = new TreeStore(typeof(string), typeof(string));

        TreeIter iter = treestore.AppendValues("Scripting languages");
        treestore.AppendValues(iter, "Python","23");
        treestore.AppendValues(iter, "PHP");
        treestore.AppendValues(iter, "Perl");
        treestore.AppendValues(iter, "Ruby");

        iter = treestore.AppendValues("Compiling languages");
        treestore.AppendValues(iter, "C#");
        treestore.AppendValues(iter, "C++");
        treestore.AppendValues(iter, "C");
        treestore.AppendValues(iter, "Java");

        tree.AppendColumn(languages);
        tree.AppendColumn("Data", new CellRendererText(), "text", 1);
        tree.Model = treestore;

        Add(tree);
        ShowAll();
    }

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

TreeView-Example3

双列树及排序。

class MainClass
{
    public static void Main (string[] args)
    {
        Application.Init ();
        var win = CreateTreeWindow();
        win.ShowAll ();
        Application.Run ();
    }

    public static Gtk.Window CreateTreeWindow()
    {
        Gtk.Window window = new Gtk.Window("Sortable TreeView");

        Gtk.TreeIter iter;
        Gtk.TreeViewColumn col;
        Gtk.CellRendererText cell;

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

        cell = new Gtk.CellRendererText();
        col = new Gtk.TreeViewColumn();
        col.Title = "Column 1";            
        col.PackStart(cell, true);
        col.AddAttribute(cell, "text", 0);
        col.SortColumnId = 0;

        tree.AppendColumn(col);

        cell = new Gtk.CellRendererText();
        col = new Gtk.TreeViewColumn();
        col.Title = "Column 2";            
        col.PackStart(cell, true);
        col.AddAttribute(cell, "text", 1);

        tree.AppendColumn(col);

        Gtk.TreeStore store = new Gtk.TreeStore(typeof (string), typeof (string));
        iter = store.AppendValues("BBB");
        store.AppendValues(iter, "AAA", "Zzz");
        store.AppendValues(iter, "DDD", "Ttt");
        store.AppendValues(iter, "CCC", "Ggg");

        iter = store.AppendValues("AAA");
        store.AppendValues(iter, "ZZZ", "Zzz");
        store.AppendValues(iter, "GGG", "Ggg");
        store.AppendValues(iter, "TTT", "Ttt");

        Gtk.TreeModelSort sortable = new Gtk.TreeModelSort(store);
        sortable.SetSortFunc(0, delegate(TreeModel model, TreeIter a, TreeIter b) {
            string s1 = (string)model.GetValue(a, 0);
            string s2 = (string)model.GetValue(b, 0);
            return String.Compare(s1, s2);
        });

        tree.Model = sortable;

        window.Add(tree);

        return window;
    }
}


In this chapter, we were talking about advanced GTK# widgets.

Dialogs in GTK#

In this part of the GTK# programming tutorial, we will introduce dialogs.

Dialog windows or dialogs are an indispensable part of most modern GUI applications. A dialog is defined as a conversation between two or more persons. In a computer application a dialog is a window which is used to “talk” to the application. A dialog is used to input data, modify data, change the application settings etc. Dialogs are important means of communication between a user and a computer program.

Message dialogs

Message dialogs are convenient dialogs that provide messages to the user of the application. The message consists of textual and image data.

messages.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("Messages")
    {
        SetDefaultSize(250, 100);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); }; 


        Table table = new Table(2, 2, true);

        Button info = new Button("Information");
        Button warn = new Button("Warning");
        Button ques = new Button("Question");
        Button erro = new Button("Error");

        info.Clicked += delegate {
            MessageDialog md = new MessageDialog(this, 
                DialogFlags.DestroyWithParent, MessageType.Info, 
                ButtonsType.Close, "Download completed");
            md.Run();
            md.Destroy();
        };

        warn.Clicked += delegate {
            MessageDialog md = new MessageDialog(this, 
                DialogFlags.DestroyWithParent, MessageType.Warning, 
                ButtonsType.Close, "Unallowed operation");
            md.Run();
            md.Destroy();
        };


        ques.Clicked += delegate {
            MessageDialog md = new MessageDialog(this, 
                DialogFlags.DestroyWithParent, MessageType.Question, 
                ButtonsType.Close, "Are you sure to quit?");
            md.Run();
            md.Destroy();
        };

        erro.Clicked += delegate {
            MessageDialog md = new MessageDialog (this, 
                DialogFlags.DestroyWithParent, MessageType.Error, 
                ButtonsType.Close, "Error loading file");
            md.Run();
            md.Destroy();
        };

        table.Attach(info, 0, 1, 0, 1);
        table.Attach(warn, 1, 2, 0, 1);
        table.Attach(ques, 0, 1, 1, 2);
        table.Attach(erro, 1, 2, 1, 2);

        Add(table);

        ShowAll();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In our example, we will show four kinds of message dialogs. Information, Warning, Question, and Error message dialogs.

Button info = new Button("Information");
Button warn = new Button("Warning");
Button ques = new Button("Question");
Button erro = new Button("Error");

We have four buttons. Each of these buttons will show a different kind of message dialog.

info.Clicked += delegate {
    MessageDialog md = new MessageDialog(this, 
        DialogFlags.DestroyWithParent, MessageType.Info, 
        ButtonsType.Close, "Download completed");
    md.Run();
    md.Destroy();
};

If we click on the info button, the Information dialog is displayed. The MessageType.Info specifies the type of the dialog. The ButtonsType.Close specifies the button to be displayed in the dialog. The last parameter is the message displayed. The dialog is displayed with the Run() method. The programmer must also call either the Destroy() or the Hide() method.

AboutDialog

The AboutDialog displays information about the application. AboutDialog can display a logo, the name of the application, version, copyright, website or licence information. It is also possible to give credits to the authors, documenters, translators and artists.

aboutdialog.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("About")
    {
        SetDefaultSize(300, 270);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); } ;

        Button button = new Button("About");
        button.Clicked += OnClicked;

        Fixed fix = new Fixed();
        fix.Put(button, 20, 20);
        Add(fix);

        ShowAll();
    }


    void OnClicked(object sender, EventArgs args)
    {
        AboutDialog about = new AboutDialog();
        about.ProgramName = "Battery";
        about.Version = "0.1";
        about.Copyright = "(c) Jan Bodnar";
        about.Comments = @"Battery is a simple tool for 
battery checking";
        about.Website = "http://www.zetcode.com";
        about.Logo = new Gdk.Pixbuf("battery.png");
        about.Run();
        about.Destroy();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

The code example uses a AboutDialog with some of its features.

AboutDialog about = new AboutDialog();

We create an AboutDialog.

about.ProgramName = "Battery";
about.Version = "0.1";
about.Copyright = "(c) Jan Bodnar";

By setting the properties of the dialog, we specify the name, version and the copyright.

about.Logo = new Gdk.Pixbuf("battery.png");

This line creates a logo.

FontSelectionDialog

The FontSelectionDialog is a dialog for selecting fonts. It is typically used in applications that do some text editing or formatting.

fontdialog.cs

using Gtk;
using System;

class SharpApp : Window {

    Label label;

    public SharpApp() : base("Font Selection Dialog")
    {
        SetDefaultSize(300, 220);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); } ;

        label = new Label("The only victory over love is flight.");
        Button button = new Button("Select font");
        button.Clicked += OnClicked;

        Fixed fix = new Fixed();
        fix.Put(button, 100, 30);
        fix.Put(label, 30, 90);
        Add(fix);

        ShowAll();
    }


    void OnClicked(object sender, EventArgs args)
    {
        FontSelectionDialog fdia = new FontSelectionDialog("Select font name");
        fdia.Response += delegate (object o, ResponseArgs resp) {

            if (resp.ResponseId == ResponseType.Ok) {
               Pango.FontDescription fontdesc = 
                   Pango.FontDescription.FromString(fdia.FontName);
               label.ModifyFont(fontdesc);
            }
        };

        fdia.Run();
        fdia.Destroy();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}


In the code example, we have a button and a label. We show the FontSelectionDialog by clicking on the button.

FontSelectionDialog fdia = new FontSelectionDialog("Select font name");

We create the FontSelectionDialog.

fdia.Response += delegate (object o, ResponseArgs resp) {

    if (resp.ResponseId == ResponseType.Ok) {
        Pango.FontDescription fontdesc = Pango.FontDescription.FromString(fdia.FontName);
        label.ModifyFont(fontdesc);
    }
};

If we click on the OK button, the font of the label widget changes to the one that we selected in the dialog.

ColorSelectionDialog

ColorSelectionDialog is a dialog for selecting a colour.

colordialog.cs

using Gtk;
using System;

class SharpApp : Window {

    Label label;

    public SharpApp() : base("Color Dialog")
    {
        SetDefaultSize(300, 220);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); } ;

        label = new Label("The only victory over love is flight.");
        Button button = new Button("Select color");
        button.Clicked += OnClicked;

        Fixed fix = new Fixed();
        fix.Put(button, 100, 30);
        fix.Put(label, 30, 90);
        Add(fix);

        ShowAll();
    }


    void OnClicked(object sender, EventArgs args)
    {
        ColorSelectionDialog cdia = new ColorSelectionDialog("Select color");
        cdia.Response += delegate (object o, ResponseArgs resp) {

            if (resp.ResponseId == ResponseType.Ok) {
               label.ModifyFg(StateType.Normal, cdia.ColorSelection.CurrentColor);
            }
        };

        cdia.Run();
        cdia.Destroy();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

The example is very similar to the previous one. This time we change the colour of the label.

ColorSelectionDialog cdia = new ColorSelectionDialog("Select color");

We create the ColorSelectionDialog.

cdia.Response += delegate (object o, ResponseArgs resp) {

    if (resp.ResponseId == ResponseType.Ok) {
    label.ModifyFg(StateType.Normal, cdia.ColorSelection.CurrentColor);
    }
};

If the user pressed OK, we get the color and modify the label’s colour.

In this part of the GTK# tutorial, we talked about dialogs.

Pango

In this part of the GTK# programming tutorial, we will explore the Pango library.

Pango is a free and open source computing library for rendering internationalised texts in high quality. Different font backends can be used, allowing cross-platform support. (wikipedia)

Pango provides advanced font and text handling that is used for Gdk and Gtk.

Simple example

In our first example, we show, how to change font for our label widget.

quotes.cs

using Gtk;
using System;

class SharpApp : Window {

    private Label label;

    public SharpApp() : base("Pango")
    {
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        string text = @"Excess of joy is harder to bear than any amount of sorrow.
The more one judges, the less one loves.
There is no such thing as a great talent without great will power. ";

        label = new Label(text);

        Pango.FontDescription fontdesc = Pango.FontDescription.FromString("Purisa 10");
        label.ModifyFont(fontdesc);

        Fixed fix = new Fixed();

        fix.Put(label, 5, 5);
        Add(fix);
        ShowAll();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In the above code example, we have a label widget with three quotations. We change its font to Purisa 10.

string text = @"Excess of joy is harder to bear than any amount of sorrow.
...

This is the text to show in the label.

Pango.FontDescription fontdesc = Pango.FontDescription.FromString("Purisa 10");

The FontDescription is used to specify the characteristics of a font to load. The FromString() method creates a new font description from a string representation.

label.ModifyFont(fontdesc);

We change the font of the label widget to Purisa 10.

System fonts

The next code example shows all available fonts in a TreeView widget.

systemfonts.cs

using System;
using Pango;
using Gtk;


public class SharpApp : Window
{
    ListStore store;
    FontFamily[] fam;

    public SharpApp() : base("System fonts")
    {
        BorderWidth = 8;

        SetDefaultSize(350, 250);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };


        ScrolledWindow sw = new ScrolledWindow();
        sw.ShadowType = ShadowType.EtchedIn;
        sw.SetPolicy(PolicyType.Automatic, PolicyType.Automatic);

        Context context = this.CreatePangoContext();
        fam = context.Families;

        store = CreateModel();

        TreeView treeView = new TreeView(store);
        treeView.RulesHint = true;
        sw.Add(treeView);

        CreateColumn(treeView);

        Add(sw);
        ShowAll();
    }

    void CreateColumn(TreeView treeView)
    {
        CellRendererText rendererText = new CellRendererText();
        TreeViewColumn column = new TreeViewColumn("FontName",
            rendererText, "text", Column.FontName);
        column.SortColumnId = (int) Column.FontName;
        treeView.AppendColumn(column);
    }


    ListStore CreateModel()
    {
        ListStore store = new ListStore( typeof(string) );

        foreach (FontFamily ff in fam) {
            store.AppendValues(ff.Name);
        }

        return store;
    }

    enum Column
    {
        FontName
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

The code example shows all available fonts on a system.

Context context = this.CreatePangoContext();

This code line creates a Pango.Context object. It contains global information about the rendering process of text.

fam = context.Families;

From the context object, we retrieve all available font families.

foreach (FontFamily ff in fam) {
    store.AppendValues(ff.Name);
}

During the model creation of the TreeView widget, we get all font names from the array of font families and put them into the list store.

Unicode

Pango is used to work with internationalised text.

unicode.cs

using Gtk;
using System;

class SharpApp : Window {


    public SharpApp() : base("Unicode")
    {
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        string text = @"Фёдор Михайлович Достоевский родился 30 октября (11 ноября)
1821 года в Москве.Был вторым из 7 детей. Отец, Михаил Андреевич, работал в 
госпитале для бедных. Мать, Мария Фёдоровна (в девичестве Нечаева),
происходила из купеческого рода.";

        Label label = new Label(text);

        Pango.FontDescription fontdesc = Pango.FontDescription.FromString("Purisa 10");
        label.ModifyFont(fontdesc);

        Fixed fix = new Fixed();

        fix.Put(label, 5, 5);
        Add(fix);
        ShowAll();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

We show some text in azbuka.

string text = @"Фёдор Михайлович Достоевский родился 30 октября ...

We can directly use the unicode text.

Label label = new Label(text);

We normally use it in the label widget.

Coloured text

In the final example, we will further explore Pango capabilities. We will draw a centered, coloured text on a DrawingArea widget.

coloured.cs

using System;
using Gtk;
using Pango;


public class SharpApp : Window
{

    public SharpApp () : base ("Australia")
    {
        SetDefaultSize(250, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        Gdk.Color white = new Gdk.Color(255, 255, 255);

        DrawingArea drawingArea = new DrawingArea();
        drawingArea.ModifyBg(StateType.Normal, white);
        drawingArea.ExposeEvent += OnExposeEvent;

        Add(drawingArea);

        ShowAll();
    }

    void OnExposeEvent (object sender, ExposeEventArgs a)
    {
        DrawingArea drawingArea = sender as DrawingArea;

        int width = drawingArea.Allocation.Width;

        Gdk.PangoRenderer renderer = Gdk.PangoRenderer.GetDefault(drawingArea.Screen);
        renderer.Drawable = drawingArea.GdkWindow;
        renderer.Gc = drawingArea.Style.BlackGC;

        Context context = drawingArea.CreatePangoContext();
        Pango.Layout layout = new Pango.Layout(context);

        layout.Width = Pango.Units.FromPixels(width);
        layout.SetText("Australia");

        FontDescription desc = FontDescription.FromString("Serif Bold 20");
        layout.FontDescription = desc;

        renderer.SetOverrideColor(RenderPart.Foreground, new Gdk.Color(200, 30, 30));
        layout.Alignment = Pango.Alignment.Center;
        renderer.DrawLayout(layout, 0, 0);

        renderer.SetOverrideColor(RenderPart.Foreground, Gdk.Color.Zero);
        renderer.Drawable = null;
        renderer.Gc = null;
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

We draw “Australia” text, horizontally centered, coloured in dark red colour.

Gdk.PangoRenderer renderer = Gdk.PangoRenderer.GetDefault(drawingArea.Screen);
renderer.Drawable = drawingArea.GdkWindow;
renderer.Gc = drawingArea.Style.BlackGC;

We get the default renderer for the screen, and set it up for drawing.

Context context = drawingArea.CreatePangoContext();
Pango.Layout layout = new Pango.Layout(context);

We create a Pango.Layout. It is a high level driver for laying out entire blocks of text.

layout.Width = Pango.Units.FromPixels(width);

We specify the layouts width.

layout.SetText("Australia");

We set our text.

FontDescription desc = FontDescription.FromString("Serif Bold 20");
layout.FontDescription = desc;

We specify a font for our layout.

renderer.SetOverrideColor(RenderPart.Foreground, new Gdk.Color(200, 30, 30));
layout.Alignment = Pango.Alignment.Center;

We set a color and alignment.

renderer.DrawLayout(layout, 0, 0);

We draw the Pango layout.

renderer.SetOverrideColor(RenderPart.Foreground, Gdk.Color.Zero);
renderer.Drawable = null;
renderer.Gc = null;

We clean up resources.

In this chapter of the GTK# programming library, we worked with Pango library.

Cairo入门

关于Cairo

Cairo是非常流行的开源2D图形渲染引擎库,它支持包括X-Windos,Win32,图像,pdf在内的各种输出设备。目前,Cairo已被广泛的使用在多个平台上来渲染图形界面,包括Firefox/Webkit-EFL/GTK+/Poppler等等

编译安装Cairo

我们可以直接从Ubuntu的上下载cairo的稳定版:
sudo apt-get install libcairo2-dev
这里,为了使用Cairo的最新版,我们选择从git上下载源代码,并编译安装。

git clone git://anongit.freedesktop.org/git/cairo
cd cairo
mkdir cairobuild
./autogen.sh #这里可以加上配置选项
make 
sudo make install

Cairo的基本绘图模型与概念

在使用Cairo编程前,我们应该对于Cairo的基本绘图模型做一了解。这些模包括:表面(surfac),源(source),遮盖(mask),路径(path),上下文(context)和函数(verb)。

表面(surface)

Surface是Cair绘图的目标区域,在Cairo中使用cairo_surface_t表示。前面讲到Cair支持多种输出设备,因此我们绘图的目标区域可能是一张png图象也可能是一个pdf文件。不同目标的绘制的底层实现各不相同,而surfac对这些绘图的目标进行了一个抽象。因此,我们在创建了相应的surface后,只需要调用统一的函数对surface进行绘制,而不需要关心其后端(backend)的具体实现。
源(source)

源(source)

Source指的是我们绘图是的具体的材料与格式,它包括画笔的粗细、颜色等等。在Cairo中,source不光可以是笔的颜色,也可以是一种图案(patter)比如渐变色,甚至可以是一个表面(surface)。

遮盖(mask)

Mask相当于我们在绘图过程,用一张挖空了某些部分的纸遮挡在画布上。这样,在绘图过程中,只有挖空的部分会被我们所使用的源影响到,其余部分不受影响。

路径(path)

Path是指Cairo的绘制表面上一些虚拟的路径,它可能是一条线段,一个闭合的四边形,也可能是更加复杂的曲线。Path可以由Cairo的函数(在Cairo中被称为verb)所创建。但是,由于Path只是虚拟的路径,所以对Path的创建并不代表对表面绘制。接下来,我们还需要使用绘制函数(Cairo里称为drawing verb)进行绘制。比如,我们可以通过cairo_rectangle函数创建一个闭合的长方形的路径,然后通过cairo_fill函数填充这个长方形。

上下文(context)

Context是Cairo的核心结构,在Cairo中使用cairo_t来表示。它记录了当前状态下,与绘制有关的各种信息,包括之前介绍过的表面、源、遮盖、字体等等。 在任何绘制之前,我们都必须先创建一个cair_t结构,同时将它绑定到一个绘制表面上(surface)。下面的代码段创建了一个cairo_t,并将其绑定到一个640x480的png图象上。

cairo_surface_t *surface;
cairo_t *cr;


surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 640, 480);
cr = cairo_create (surface);

函数(verb)

Cairo中与绘制相关的函数被称为verb。目前Cairo支持五种drawing verb,分别是stroke(画线/描边), fill(填充),text(文字),paint(滤镜),mask(遮盖/蒙板)。

描边(Stroke)
cairo_stroke() 操作就是一个虚拟画笔沿着特定的路径(path)描边,代表的是区域边线。
画笔所过之处使得源(Source)通过一个空心或实心的路径线蒙板(mask)传输到目标, 路径线取决于画笔的 线宽(line width), 表现样式(dash style), 和 线头(line caps).

填充(fill):
和Stroke相对应,代表的是区域内部。

文字(text):
cairo_show_text() 操作使用文本来形成蒙板. 把 cairo_show_text()当做是使用 cairo_text_path()创建路径然后使用 cairo_fill() 来传送(源到目标)应该比较容易理解一下. 如果你有大量文本要处理,那么使用 cairo_show_text() 缓存字形更为有效.

滤镜(paint):
paint相当于是整个源进行了一次操作,比如cairo_paint_with_alpha函数可以设置一个alpha值,来对整个图象进行灰度的减少。

蒙板(mask):
蒙版就是选框的外部(选框的内部就是选区)。蒙版一词本身即来自生活应用,也就是“蒙在上面的板子”的含义。

变换(transformation)

Cairo还提供类似OpenGL的坐标变换操作。变换操作包括:平移(cairo_translate),伸缩(cairo_scale),旋转(cairo_rotate)。我们也可以通过cairo_transform函数来指定一个复杂的变换。

例子

1.绘制一个矩形到rectangle.png图片上。

#include <cairo.h>

int
main (int argc, char *argv[])
{
    cairo_surface_t *surface;
    cairo_t *cr;

    int width = 640;
    int height = 480;
    surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
    cr = cairo_create (surface);

    /* Drawing code goes here */
    cairo_set_line_width (cr, 10); //设置绘画宽度
    cairo_set_source_rgb (cr, 0, 0, 0);  //设置绘画颜色
    cairo_rectangle (cr, width/4, height/4, width/2, height/2);  //设置绘画区域
    cairo_stroke (cr);  //画边线

    /* Write output and clean up */
    cairo_surface_write_to_png (surface, "rectangle.png");
    cairo_destroy (cr);
    cairo_surface_destroy (surface);

    return 0;
}

2.绘制helloworld到helloworld.pdf上。

#include <cairo.h>
#include <cairo-pdf.h>
int
main (int argc, char *argv[])
{
    cairo_surface_t *surface;
    cairo_t *cr;
    cairo_text_extents_t te;


   /* Prepare drawing area */
    int width = 200;
    int height = 120;

    surface = cairo_pdf_surface_create ("helloworld.pdf", width, height);
    cr = cairo_create (surface);


    /* Drawing code goes here */
    cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
    cairo_select_font_face (cr, "Georgia",
        CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size (cr, 20);
    cairo_text_extents (cr, "helloworld", &te);
    cairo_move_to (cr, width/2 - te.width / 2 - te.x_bearing,
          height/2 - te.height / 2 - te.y_bearing);
    cairo_show_text (cr, "helloworld");


    cairo_destroy (cr);
    cairo_surface_destroy (surface);


    return 0;
}

gcc编译

使用gcc编译调用了cairo的源文件时,应指定cairo的头文件目录和库文件所在目录。假设源文件文件名为cairotest.c,cairo.h等头文件所在目录为/usr/include/cairo,cairo库文件所在目录为/usr/local/lib,则可以使用以下命令编译:

gcc cairotest.c -o cairotest -I/usr/include/cairo -L/usr/local/lib/ -lcairo

我们也可以使用pkg-config来配置cairo的目录,然后使用如下命令来编译:

gcc -o cairotest $(pkg-config --cflags --libs cairo) cairotest.c

Drawing with Cairo in GTK#

In this part of the GTK# programming tutorial, we will do some drawing with the Cairo library.

Cairo is a library for creating 2D vector graphics. We can use it to draw our own widgets, charts or various effects or animations.

Simple drawing

The stroke operation draws the outlines of shapes and the fill operation fills the insides of shapes. Next we will demonstrate these two operations.

simpledrawing.cs

using Gtk;
using Cairo;
using System;

class SharpApp : Window {


    public SharpApp() : base("Simple drawing")
    {
        SetDefaultSize(230, 150);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };;

        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);

        cr.LineWidth = 9;
        cr.SetSourceRGB(0.7, 0.2, 0.0);

        int width, height;
        width = Allocation.Width;
        height = Allocation.Height;

        cr.Translate(width/2, height/2);
        cr.Arc(0, 0, (width < height ? width : height) / 2 - 10, 0, 2*Math.PI);
        cr.StrokePreserve(); //调用画笔画边线

        cr.SetSourceRGB(0.3, 0.4, 0.6);
        cr.Fill(); //调用填充画内部

        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In our example, we will draw a circle and will it with a solid color.

gmcs -pkg:gtk-sharp-2.0 -r:/usr/lib/mono/2.0/Mono.Cairo.dll  simple.cs

Here is how we compile the example.

DrawingArea darea = new DrawingArea();

We will be doing our drawing operations on the DrawingArea widget.

darea.ExposeEvent += OnExpose;

All drawing is done in a method that we plug into the ExposeEvent.

DrawingArea area = (DrawingArea) sender;
Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);

We create the Cairo.Context object from the GdkWindow of the drawing area. The context is an object that is used to draw on all Drawable objects.

cr.LineWidth = 9;

We set the width of the line to 9 pixels.

cr.SetSourceRGB(0.7, 0.2, 0.0);

We set the colour to dark red.

int width, height;
width = Allocation.Width;
height = Allocation.Height;

cr.Translate(width/2, height/2);

We get the width and height of the drawing area. We move the origin into the middle of the window.

cr.Arc(0, 0, (width < height ? width : height) / 2 - 10, 0, 2*Math.PI);
cr.StrokePreserve();

We draw the outside shape of a circle. The StrokePreserve() strokes the current path according to the current line width, line join, line cap, and dash settings. Unlike the Stroke(), it preserves the path within the cairo context.

cr.SetSourceRGB(0.3, 0.4, 0.6);
cr.Fill();

This fills the interior of the circle with some blue colour.

Basic shapes

The next example draws some basic shapes onto the window.

basicshapes.cs

using Gtk;
using Cairo;
using System;

class SharpApp : Window {


    public SharpApp() : base("Basic shapes")
    {
        SetDefaultSize(390, 240);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);
        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cc =  Gdk.CairoHelper.Create(area.GdkWindow);

        cc.SetSourceRGB(0.2, 0.23, 0.9);
        cc.LineWidth = 1;

        cc.Rectangle(20, 20, 120, 80);
        cc.Rectangle(180, 20, 80, 80);
        cc.StrokePreserve();
        cc.SetSourceRGB(1, 1, 1);
        cc.Fill();

        cc.SetSourceRGB(0.2, 0.23, 0.9);
        cc.Arc(330, 60, 40, 0, 2*Math.PI);
        cc.StrokePreserve();
        cc.SetSourceRGB(1, 1, 1);
        cc.Fill();

        cc.SetSourceRGB(0.2, 0.23, 0.9);
        cc.Arc(90, 160, 40, Math.PI/4, Math.PI);
        cc.ClosePath();
        cc.StrokePreserve();
        cc.SetSourceRGB(1, 1, 1);
        cc.Fill();

        cc.SetSourceRGB(0.2, 0.23, 0.9);
        cc.Translate(220, 180);
        cc.Scale(1, 0.7);        
        cc.Arc(0, 0, 50, 0, 2*Math.PI);
        cc.StrokePreserve();
        cc.SetSourceRGB(1, 1, 1);
        cc.Fill();          

        ((IDisposable) cc.Target).Dispose ();                                      
        ((IDisposable) cc).Dispose ();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In this example, we will create a rectangle, a square, a circle, an arc, and an ellipse. We draw outlines in blue colour, insides in white.

cc.Rectangle(20, 20, 120, 80);
cc.Rectangle(180, 20, 80, 80);
cc.StrokePreserve();
cc.SetSourceRGB(1, 1, 1);
cc.Fill();

These lines draw a rectangle and a square.

cc.Arc(330, 60, 40, 0, 2*Math.PI);

Here the Arc() method draws a full circle.

cc.Scale(1, 0.7);        
cc.Arc(0, 0, 50, 0, 2*Math.PI);

If we want to draw an oval, we do some scaling first. Here the Scale() method shrinks the y axis.

Colours

A colour is an object representing a combination of Red, Green, and Blue (RGB) intensity values. Cairo valid RGB values are in the range 0 to 1.

colors.cs

using Gtk;
using Cairo;
using System;

class SharpApp : Window {


    public SharpApp() : base("Colors")
    {
        SetDefaultSize(360, 100);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);

        cr.SetSourceRGB(0.2, 0.23, 0.9);
        cr.Rectangle(10, 15, 90, 60);
        cr.Fill();

        cr.SetSourceRGB(0.9, 0.1, 0.1);
        cr.Rectangle(130, 15, 90, 60);
        cr.Fill();

        cr.SetSourceRGB(0.4, 0.9, 0.4);
        cr.Rectangle(250, 15, 90, 60);
        cr.Fill();

        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

We draw three rectangles in three different colours.

cr.SetSourceRGB(0.2, 0.23, 0.9);

The SetSourceRGB() method sets a colour for the cairo context. The three parameters of the method are the colour intensity values.

cr.Rectangle(10, 15, 90, 60);
cr.Fill();

We create a rectangle shape and fill it with the previously specified colour.

Transparent rectangles

Transparency is the quality of being able to see through a material. The easiest way to understand transparency is to imagine a piece of glass or water. Technically, the rays of light can go through the glass and this way we can see objects behind the glass.

In computer graphics, we can achieve transparency effects using alpha compositing. Alpha compositing is the process of combining an image with a background to create the appearance of partial transparency. The composition process uses an alpha channel. (wikipedia.org, answers.com)

transparentrectangles.cs

using Gtk;
using Cairo;
using System;


class SharpApp : Window {


    public SharpApp() : base("Transparent rectangles")
    {
        SetDefaultSize(590, 90);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); } ;

        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);

        for ( int i = 1; i <= 10; i++) {
            cr.SetSourceRGBA(0, 0, 1, i*0.1);
            cr.Rectangle(50*i, 20, 40, 40);
            cr.Fill();  
        }

        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}


In the example we will draw ten rectangles with different levels of transparency.

cr.SetSourceRGBA(0, 0, 1, i*0.1);

The last parameter of the SetSourceRGBA() method is the alpha transparency.

Soulmate

In the next example, we draw some text on the window.

soulmate.cs

using Gtk;
using Cairo;
using System;

class SharpApp : Window
{


    public SharpApp() : base("Soulmate")
    {
        SetDefaultSize(420, 250);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea)sender;
        Cairo.Context cr = Gdk.CairoHelper.Create(area.GdkWindow);

        cr.SetSourceRGB(0.1, 0.1, 0.1);

        cr.SelectFontFace("Purisa", FontSlant.Normal, FontWeight.Bold);
        cr.SetFontSize(13);

        cr.MoveTo(20, 30);
        cr.ShowText("Most relationships seem so transitory");
        cr.MoveTo(20, 60);
        cr.ShowText("They're all good but not the permanent one");
        cr.MoveTo(20, 120);
        cr.ShowText("Who doesn't long for someone to hold");
        cr.MoveTo(20, 150);
        cr.ShowText("Who knows how to love without being told");
        cr.MoveTo(20, 180);
        cr.ShowText("Somebody tell me why I'm on my own");
        cr.MoveTo(20, 210);
        cr.ShowText("If there's a soulmate for everyone");

        ((IDisposable)cr.Target).Dispose();
        ((IDisposable)cr).Dispose();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

We display part of the lyrics from the Natasha Bedingfields Soulmate song.

cr.SelectFontFace("Purisa", FontSlant.Normal, FontWeight.Bold);

Here we specify the font that we use—Purisa bold.

cr.SetFontSize(13);

We specify the size of the font.

cr.MoveTo(20, 30);

We move to the point, where we will draw the text.

cr.ShowText("Most relationships seem so transitory");

The ShowText() method draws text onto the window.

In this chapter of the GTK# programming library, we were drawing with Cairo library.

In this part of the GTK# programming tutorial, we will continue drawing with the Cairo library.

Donut

In the following example we create an complex shape by rotating a bunch of ellipses.

donut.cs

using Gtk;
using Cairo;
using System;

class SharpApp : Window {


    public SharpApp() : base("Donut")
    {
        SetDefaultSize(350, 250);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);

        cr.LineWidth = 0.5;

        int width, height;
        width = Allocation.Width;
        height = Allocation.Height;

        cr.Translate(width/2, height/2);
        cr.Arc(0, 0, 120, 0, 2*Math.PI);
        cr.Stroke();

        cr.Save();

        for (int i = 0; i < 36; i++) {
            cr.Rotate( i*Math.PI/36);
            cr.Scale(0.3, 1);
            cr.Arc(0, 0, 120, 0, 2*Math.PI);
            cr.Restore();
            cr.Stroke();
            cr.Save();
        }

        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In this example, we create a donut. The shapes resembles a cookie, hence the name donut.

cr.Translate(width/2, height/2);
cr.Arc(0, 0, 120, 0, 2*Math.PI);
cr.Stroke();

In the beginning there is an ellipse.

for (int i = 0; i < 36; i++) {
    cr.Rotate( i*Math.PI/36);
    cr.Scale(0.3, 1);
    cr.Arc(0, 0, 120, 0, 2*Math.PI);
    cr.Restore();
    cr.Stroke();
    cr.Save();
}

After several rotations, there is a donut.

Gradients

In computer graphics, gradient is a smooth blending of shades from light to dark or from one colour to another. In 2D drawing programs and paint programs, gradients are used to create colourful backgrounds and special effects as well as to simulate lights and shadows. (answers.com)

gradients.csv

using Gtk;
using Cairo;
using System;

class SharpApp : Window {


    public SharpApp() : base("Gradients")
    {
        SetDefaultSize(340, 390);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);


        LinearGradient lg1 = new LinearGradient(0.0, 0.0, 350.0, 350.0);

        int count = 1;

        for (double j=0.1; j<1.0; j+= 0.1) {
            if (Convert.ToBoolean(count % 2)) {
                lg1.AddColorStop(j, new Color(0, 0, 0, 1));
            } else {
                lg1.AddColorStop(j, new Color(1, 0, 0, 1));
            }
        count++;
        }

        cr.Rectangle(20, 20, 300, 100);
        cr.Pattern = lg1;
        cr.Fill();

        LinearGradient lg2 = new LinearGradient(0.0, 0.0, 350.0, 0);

        count = 1;

        for (double i=0.05; i<0.95; i+= 0.025) {
            if (Convert.ToBoolean(count % 2)) {
                lg2.AddColorStop(i, new Color(0, 0, 0, 1));
            } else {
                lg2.AddColorStop(i, new Color(0, 0, 1, 1));
            }
        count++;
        }

        cr.Rectangle(20, 140, 300, 100);
        cr.Pattern = lg2;
        cr.Fill();

        LinearGradient lg3 = new LinearGradient(20.0, 260.0,  20.0, 360.0);
        lg3.AddColorStop(0.1, new Color (0, 0, 0, 1) );
        lg3.AddColorStop(0.5, new Color (1, 1, 0, 1) );
        lg3.AddColorStop(0.9, new Color (0, 0, 0, 1) );

        cr.Rectangle(20, 260, 300, 100);
        cr.Pattern = lg3;
        cr.Fill();


        lg1.Destroy();
        lg2.Destroy();
        lg3.Destroy();        

        ((IDisposable) cr.Target).Dispose ();                                      
        ((IDisposable) cr).Dispose ();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

In our example, we draw three rectangles with three different gradients.

LinearGradient lg1 = new LinearGradient(0.0, 0.0, 350.0, 350.0);

Here we create a linear gradient pattern. The parameters specify the line, along which we draw the gradient. In our case it is a vertical line.

LinearGradient lg3 = new LinearGradient(20.0, 260.0,  20.0, 360.0);
lg3.AddColorStop(0.1, new Color (0, 0, 0, 1) );
lg3.AddColorStop(0.5, new Color (1, 1, 0, 1) );
lg3.AddColorStop(0.9, new Color (0, 0, 0, 1) );

We define colour stops to produce our gradient pattern. In this case, the gradient is a blending of black and yellow colours. By adding two black and one yellow stops, we create a horizontal gradient pattern. What do these stops actually mean? In our case, we begin with black colour, which will stop at 1/10 of the size. Then we begin to gradually paint in yellow, which will culminate at the centre of the shape. The yellow colour stops at 9/10 of the size, where we begin painting in black again, until the end.

Puff

In the following example, we create a puff effect. The example will display a growing centered text that will gradually fade out from some point. This is a very common effect, which you can often see in flash animations.

puff.cs

using Gtk;
using Cairo;
using System;

class SharpApp : Window {


    private bool timer = true;
    private double alpha = 1.0;
    private double size = 1.0;
    private DrawingArea darea;


    public SharpApp() : base("Puff")
    {
        SetDefaultSize(350, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        GLib.Timeout.Add(14, new GLib.TimeoutHandler(OnTimer));

        darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    bool OnTimer() 
    { 
        if (!timer) return false;

        darea.QueueDraw();
        return true;
    }      

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);

        int x = Allocation.Width / 2;
        int y = Allocation.Height / 2;

        cr.SetSourceRGB(0.5, 0, 0);
        cr.Paint();

        cr.SelectFontFace("Courier", FontSlant.Normal, FontWeight.Bold);

        size += 0.8;

        if (size > 20) {
            alpha -= 0.01;
        }

        cr.SetFontSize(size);
        cr.SetSourceRGB(1, 1, 1); 

        TextExtents extents = cr.TextExtents("ZetCode");

        cr.MoveTo(x - extents.Width/2, y);
        cr.TextPath("ZetCode");
        cr.Clip();
        cr.Stroke();
        cr.PaintWithAlpha(alpha);

        if (alpha <= 0) {
            timer = false;
        }

        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

The example creates a growing and fading text on the window.

GLib.Timeout.Add(14, new GLib.TimeoutHandler(OnTimer));

Every 14 ms the OnTimer() method is called.

bool OnTimer() 
{ 
    if (!timer) return false;

    darea.QueueDraw();
    return true;
}      

In the OnTimer() method, we call the QueueDraw() method upon the drawing area, which triggers the ExposeEvent.

int x = Allocation.Width / 2;
int y = Allocation.Height / 2;

Coordinates of the middle point.

cr.SetSourceRGB(0.5, 0, 0);
cr.Paint();

We set the background color to dark red color.

size += 0.8;

Each cycle, the font size will grow by 0.8 units.

if (size > 20) {
    alpha -= 0.01;
}

The fading out begins after the font size is bigger than 20.

TextExtents extents = cr.TextExtents("ZetCode");

We get the text metrics.

cr.MoveTo(x - extents.Width/2, y);

We use the text metrics to center the text on the window.

cr.TextPath("ZetCode");
cr.Clip();

We get the path of the text and set the current clip region to it.

cr.Stroke();
cr.PaintWithAlpha(alpha);

We paint the current path and take alpha value into account.

Reflection

In the next example we show a reflected image. This beautiful effect makes an illusion as if the image was reflected in water.

reflection.cs

using Gtk;
using Cairo;
using System;

class SharpApp : Window {

    private ImageSurface surface;
    private int imageWidth;
    private int imageHeight;
    private int gap;
    private int border;

    public SharpApp() : base("Reflection")
    {

        try {
            surface = new ImageSurface("slanec.png");
        } catch {
            Console.WriteLine("File not found");
            Environment.Exit(1);
        } 

        imageWidth = surface.Width;
        imageHeight = surface.Height;
        gap = 40;
        border = 20;

        SetDefaultSize(300, 350);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);

        int width = Allocation.Width;
        int height = Allocation.Height;

        LinearGradient lg = new LinearGradient(width/2, 0, width/2, height*3);
        lg.AddColorStop(0, new Color(0, 0, 0, 1));
        lg.AddColorStop(height, new Color(0.2, 0.2, 0.2, 1));

        cr.Pattern = lg;
        cr.Paint();

        cr.SetSourceSurface(surface, border, border);
        cr.Paint();

        double alpha = 0.7;
        double step = 1.0 / imageHeight;

        cr.Translate(0, 2 * imageHeight + gap);
        cr.Scale(1, -1);

        int i = 0;


        while(i < imageHeight) {
            cr.Rectangle(new Rectangle(border, imageHeight-i, imageWidth, 1));

            i++;

            cr.Clip();
            cr.SetSource(surface, border, border);

            cr.PaintWithAlpha(alpha-=step);
            cr.ResetClip();
        }


        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

The example shows a reflected castle.

LinearGradient lg = new LinearGradient(width/2, 0, width/2, height*3);
lg.AddColorStop(0, new Color(0, 0, 0, 1));
lg.AddColorStop(height, new Color(0.2, 0.2, 0.2, 1));

cr.Pattern = lg;
cr.Paint();

The background is filled with a gradiet paint. The paint is a smooth blending from black to dark gray.

cr.Translate(0, 2 * imageHeight + gap);
cr.Scale(1, -1);

This code flips the image and translates it below the original image. The translation operation is necessary, because the scaling operation makes the image upside down and translates the image up. To understand what happens, simply take a photograph and place it on the table. Now flip it.

cr.Rectangle(new Rectangle(border, imageHeight-i, imageWidth, 1));

i++;

cr.Clip();
cr.SetSource(surface, border, border);

cr.PaintWithAlpha(alpha-=step);
cr.ResetClip();

Crucial part of the code. We make the second image transparent. But the transparency is not constant. The image gradually fades out. This is achieved with the GradientPaint.

Waiting

In this examle, we use transparency effect to create a waiting demo. We will draw 8 lines that will gradually fade out creating an illusion that a line is moving. Such effects are often used to inform users, that a lengthy task is going on behind the scenes. An example is streaming video over the internet.

waiting.cs

using Gtk;
using Cairo;
using System;

class SharpApp : Window {


    private double [,] trs = new double[,] {
        { 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
        { 1.0, 0.0,  0.15, 0.30, 0.5, 0.65, 0.8, 0.9 },
        { 0.9, 1.0,  0.0,  0.15, 0.3, 0.5, 0.65, 0.8 },
        { 0.8, 0.9,  1.0,  0.0,  0.15, 0.3, 0.5, 0.65},
        { 0.65, 0.8, 0.9,  1.0,  0.0,  0.15, 0.3, 0.5 },
        { 0.5, 0.65, 0.8, 0.9, 1.0,  0.0,  0.15, 0.3 },
        { 0.3, 0.5, 0.65, 0.8, 0.9, 1.0,  0.0,  0.15 },
        { 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0,  0.0, }
    };

    private short count = 0;
    private DrawingArea darea;

    public SharpApp() : base("Waiting")
    {
        SetDefaultSize(250, 150);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        GLib.Timeout.Add(100, new GLib.TimeoutHandler(OnTimer));

        darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    bool OnTimer() 
    { 
        count += 1;
        darea.QueueDraw();
        return true;
    }        


    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);

        cr.LineWidth = 3;
        cr.LineCap = LineCap.Round;

        int width, height;
        width = Allocation.Width;
        height = Allocation.Height;

        cr.Translate(width/2, height/2);

        for (int i = 0; i < 8; i++) {
            cr.SetSourceRGBA(0, 0, 0, trs[count%8, i]);
            cr.MoveTo(0.0, -10.0);
            cr.LineTo(0.0, -40.0);
            cr.Rotate(Math.PI/4);
            cr.Stroke();
        }

        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

We draw eight lines with eight different alpha values.

GLib.Timeout.Add(100, new GLib.TimeoutHandler(OnTimer));

We use a timer function to create animation.

private double [,] trs = new double[,] {
    { 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
    ...
};

This is a two dimensional array of transparency values used in this demo. There are 8 rows, each for one state. Each of the 8 lines will continuosly use these values.

cr.LineWidth = 3;
cr.LineCap = LineCap.Round;

We make the lines a bit thicker, so that they are better visible. We draw the lines with rouded caps.

cr.SetSourceRGBA(0, 0, 0, trs[count%8, i]);

Here we define the transparency value for a line.

cr.MoveTo(0.0, -10.0);
cr.LineTo(0.0, -40.0);
cr.Rotate(Math.PI/4);
cr.Stroke();

These code lines will draw each of the eight lines.

In this chapter of the GTK# programming library, we did some more advanced drawing with the Cairo library.

Custom widget in GTK#

Toolkits usually provide only the most common widgets like buttons, text widgets, sliders etc. No toolkit can provide all possible widgets.

More specialised widgets are created by client programmers. They do it by using the drawing tools provided by the toolkit. There are two possibilities: a programmer can modify or enhance an existing widget, or he can create a custom widget from scratch.

Burning widget

This is an example of a widget that we create from scratch. This widget can be found in various media burning applications, like Nero Burning ROM.

burning.cs

using Gtk;
using Cairo;
using System;

class Burning : DrawingArea
{

    string[] num = new string[] { "75", "150", "225", "300", 
        "375", "450", "525", "600", "675" };

    public Burning() : base()
    {
        SetSizeRequest(-1, 30);
    }

    protected override bool OnExposeEvent(Gdk.EventExpose args)
    {

        Cairo.Context cr = Gdk.CairoHelper.Create(args.Window);
        cr.LineWidth = 0.8;

        cr.SelectFontFace("Courier 10 Pitch", 
            FontSlant.Normal, FontWeight.Normal);
        cr.SetFontSize(11);

        int width = Allocation.Width;

        SharpApp parent = (SharpApp) GetAncestor (Gtk.Window.GType);        
        int cur_width = parent.CurValue;

        int step = (int) Math.Round(width / 10.0);

        int till = (int) ((width / 750.0) * cur_width);
        int full = (int) ((width / 750.0) * 700);

        if (cur_width >= 700) {

            cr.SetSourceRGB(1.0, 1.0, 0.72);
            cr.Rectangle(0, 0, full, 30);
            cr.Clip();
            cr.Paint();
            cr.ResetClip();

            cr.SetSourceRGB(1.0, 0.68, 0.68);
            cr.Rectangle(full, 0, till-full, 30);    
            cr.Clip();
            cr.Paint();
            cr.ResetClip();

        } else { 

            cr.SetSourceRGB(1.0, 1.0, 0.72);
            cr.Rectangle(0, 0, till, 30);
            cr.Clip();
            cr.Paint();
            cr.ResetClip();
       }  

       cr.SetSourceRGB(0.35, 0.31, 0.24);

       for (int i=1; i<=num.Length; i++) {

           cr.MoveTo(i*step, 0);
           cr.LineTo(i*step, 5);    
           cr.Stroke();

           TextExtents extents = cr.TextExtents(num[i-1]);
           cr.MoveTo(i*step-extents.Width/2, 15);
           cr.TextPath(num[i-1]);
           cr.Stroke();
       }

        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();

        return true;
    }
}


class SharpApp : Window {

    int cur_value = 0;
    Burning burning;

    public SharpApp() : base("Burning")
    {
        SetDefaultSize(350, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };

        VBox vbox = new VBox(false, 2);

        HScale scale = new HScale(0, 750, 1);
        scale.SetSizeRequest(160, 35);
        scale.ValueChanged += OnChanged;

        Fixed fix = new Fixed();
        fix.Put(scale, 50, 50);

        vbox.PackStart(fix);

        burning = new Burning();
        vbox.PackStart(burning, false,