Contents
-
Vala GTK+ 2.x Examples
- Basic Sample
- Setting an Application Icon
- Layout
- Synchronizing Widgets
- Toolbar, Scrollable Text View and File Chooser
- Creating a Dialog
- Loading User Interface from XML File
- Tips and Tricks
- TreeView with ListStore
- TreeView with TreeStore
- TreeView with CellRendererToggle
- Code Snippet: Hide the cursor
- Clipboard
Vala GTK+ 2.x Examples
Basic Sample
This sample demonstrates how to create a toplevel window, set its title, size and position, how to add a button to this window and how to connect signals with actions.
using Gtk;
int main (string[] args) {
Gtk.init (ref args);
var window = new Window ();
window.title = "First GTK+ Program";
window.set_default_size (300, 50);
window.position = WindowPosition.CENTER;
window.destroy.connect (Gtk.main_quit);
var button = new Button.with_label ("Click me!");
button.clicked.connect (() => {
button.label = "Thank you";
});
window.add (button);
window.show_all ();
Gtk.main ();
return 0;
}
All GTK+ classes are inside the Gtk namespace. You must initialize every GTK+ program with Gtk.init ().
Compile and Run
$ valac --pkg gtk+-2.0 gtk-hello.vala $ ./gtk-hello
Setting an Application Icon
try {
// Either directly from a file ...
window.icon = new Gdk.Pixbuf.from_file ("my-app.png");
// ... or from the theme
window.icon = IconTheme.get_default ().load_icon ("my-app", 48, 0);
} catch (Error e) {
stderr.printf ("Could not load application icon: %s\n", e.message);
}
Layout
Synchronizing Widgets
You can use signals in order to synchronize the values of widgets. In this example a spin button and a horizontal scale will get interlocked.
using Gtk;
public class SyncSample : Window {
private SpinButton spin_box;
private HScale slider;
public SyncSample () {
this.title = "Enter your age";
this.position = WindowPosition.CENTER;
this.destroy.connect (Gtk.main_quit);
set_default_size (300, 20);
spin_box = new SpinButton.with_range (0, 130, 1);
slider = new HScale.with_range (0, 130, 1);
spin_box.adjustment.value_changed.connect (() => {
slider.adjustment.value = spin_box.adjustment.value;
});
slider.adjustment.value_changed.connect (() => {
spin_box.adjustment.value = slider.adjustment.value;
});
spin_box.adjustment.value = 35;
var hbox = new HBox (true, 5);
hbox.add (spin_box);
hbox.add (slider);
add (hbox);
}
public static int main (string[] args) {
Gtk.init (ref args);
var window = new SyncSample ();
window.show_all ();
Gtk.main ();
return 0;
}
}
Compile and Run
$ valac --pkg gtk+-2.0 gtk-sync-sample.vala $ ./gtk-sync-sample
Toolbar, Scrollable Text View and File Chooser
A simple text file viewer:
using Gtk;
public class TextFileViewer : Window {
private TextView text_view;
public TextFileViewer () {
this.title = "Text File Viewer";
this.position = WindowPosition.CENTER;
set_default_size (400, 300);
var toolbar = new Toolbar ();
var open_button = new ToolButton.from_stock (Stock.OPEN);
open_button.is_important = true;
toolbar.add (open_button);
open_button.clicked.connect (on_open_clicked);
this.text_view = new TextView ();
this.text_view.editable = false;
this.text_view.cursor_visible = false;
var scroll = new ScrolledWindow (null, null);
scroll.set_policy (PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
scroll.add (this.text_view);
var vbox = new VBox (false, 0);
vbox.pack_start (toolbar, false, true, 0);
vbox.pack_start (scroll, true, true, 0);
add (vbox);
}
private void on_open_clicked () {
var file_chooser = new FileChooserDialog ("Open File", this,
FileChooserAction.OPEN,
Stock.CANCEL, ResponseType.CANCEL,
Stock.OPEN, ResponseType.ACCEPT);
if (file_chooser.run () == ResponseType.ACCEPT) {
open_file (file_chooser.get_filename ());
}
file_chooser.destroy ();
}
private void open_file (string filename) {
try {
string text;
FileUtils.get_contents (filename, out text);
this.text_view.buffer.text = text;
} catch (Error e) {
stderr.printf ("Error: %s\n", e.message);
}
}
public static int main (string[] args) {
Gtk.init (ref args);
var window = new TextFileViewer ();
window.destroy.connect (Gtk.main_quit);
window.show_all ();
Gtk.main ();
return 0;
}
}
Note: Older versions of Vala use the deprecated Gtk.STOCK_FOO format for icon specifiers rather than the newer Stock.FOO format.
Compile and Run
$ valac --pkg gtk+-2.0 gtk-text-viewer.vala $ ./gtk-text-viewer
If you want to reuse your file dialog setup or add additional functionality you can subclass FileChooserDialog. This one remembers the last folder:
using Gtk;
public class OpenFileDialog : FileChooserDialog {
private string last_folder;
public OpenFileDialog () {
this.title = "Open File";
this.action = FileChooserAction.OPEN;
add_button (Stock.CANCEL, ResponseType.CANCEL);
add_button (Stock.OPEN, ResponseType.ACCEPT);
set_default_response (ResponseType.ACCEPT);
if (this.last_folder != null) {
set_current_folder (this.last_folder);
}
}
public override void response (int type) {
if (type == ResponseType.ACCEPT) {
this.last_folder = get_current_folder ();
}
}
public static void main (string[] args) {
Gtk.init (ref args);
var ofd = new OpenFileDialog ();
if (ofd.run () == ResponseType.OK) {
stdout.printf ("filename = %s\n".printf (ofd.get_filename ()));
}
}
}
Creating a Dialog
This example demonstrates how to create a dialog by subclassing Dialog.
using Gtk;
public class SearchDialog : Dialog {
private Entry search_entry;
private CheckButton match_case;
private CheckButton find_backwards;
private Widget find_button;
public signal void find_next (string text, bool case_sensitivity);
public signal void find_previous (string text, bool case_sensitivity);
public SearchDialog () {
this.title = "Find";
this.has_separator = false;
this.border_width = 5;
set_default_size (350, 100);
create_widgets ();
connect_signals ();
}
private void create_widgets () {
// Create and setup widgets
this.search_entry = new Entry ();
var search_label = new Label.with_mnemonic ("_Search for:");
search_label.mnemonic_widget = this.search_entry;
this.match_case = new CheckButton.with_mnemonic ("_Match case");
this.find_backwards = new CheckButton.with_mnemonic ("Find _backwards");
// Layout widgets
var hbox = new HBox (false, 20);
hbox.pack_start (search_label, false, true, 0);
hbox.pack_start (this.search_entry, true, true, 0);
this.vbox.pack_start (hbox, false, true, 0);
this.vbox.pack_start (this.match_case, false, true, 0);
this.vbox.pack_start (this.find_backwards, false, true, 0);
this.vbox.spacing = 10;
// Add buttons to button area at the bottom
add_button (Stock.HELP, ResponseType.HELP);
add_button (Stock.CLOSE, ResponseType.CLOSE);
this.find_button = add_button (Stock.FIND, ResponseType.APPLY);
this.find_button.sensitive = false;
show_all ();
}
private void connect_signals () {
this.search_entry.changed.connect (() => {
this.find_button.sensitive = (this.search_entry.text != "");
});
this.response.connect (on_response);
}
private void on_response (Dialog source, int response_id) {
switch (response_id) {
case ResponseType.HELP:
// show_help ();
break;
case ResponseType.APPLY:
on_find_clicked ();
break;
case ResponseType.CLOSE:
destroy ();
break;
}
}
private void on_find_clicked () {
string text = this.search_entry.text;
bool cs = this.match_case.active;
if (this.find_backwards.active) {
find_previous (text, cs);
} else {
find_next (text, cs);
}
}
}
int main (string[] args) {
Gtk.init (ref args);
var dialog = new SearchDialog ();
dialog.destroy.connect (Gtk.main_quit);
dialog.show ();
Gtk.main ();
return 0;
}
Compile and Run
$ valac --pkg gtk+-2.0 gtk-search-dialog.vala $ ./gtk-search-dialog
Loading User Interface from XML File
Instead of hand coding your application's user interface you can create it comfortably with a user interface designer such as Glade and save it as XML file. Your application can load the UI from this file at runtime with the help of the Gtk.Builder class. It can even connect all signals to their callback methods if you have declared them in Glade. Here's a sample UI file: sample.ui
Since version 3.6.0 Glade can save and edit files directly in the new format for Gtk.Builder. If you use an older version of Glade you have to convert first:
$ gtk-builder-convert sample.glade sample.ui Wrote sample.ui
This example code works with the UI file linked above:
using Gtk;
public void on_button1_clicked (Button source) {
source.label = "Thank you!";
}
public void on_button2_clicked (Button source) {
source.label = "Thanks!";
}
int main (string[] args) {
Gtk.init (ref args);
try {
var builder = new Builder ();
builder.add_from_file ("sample.ui");
builder.connect_signals (null);
var window = builder.get_object ("window") as Window;
window.show_all ();
Gtk.main ();
} catch (Error e) {
stderr.printf ("Could not load UI: %s\n", e.message);
return 1;
}
return 0;
}
Compile and Run
You have to add the package gmodule-2.0 so that auto-connection of signals will work:
$ valac --pkg gtk+-2.0 --pkg gmodule-2.0 gtk-builder-sample.vala
Note: If you don't make the callback methods public you will get method never used warnings at this point.
$ ./gtk-builder-sample
Connecting callbacks
If you declare the callback methods inside a class and/or namespace you have to prefix the callback method in Glade with the namespace/class name in lower case letters and with underscores. For example, Foo.MyBar.on_button_clicked would be foo_my_bar_on_button_clicked in Glade:
If you want the callback methods to be instance methods instead of static methods you have to annotate them with the [CCode(instance_pos=-1)] attribute and pass the instance to connect_signals(...) instead of null:
using Gtk;
namespace Foo {
public class MyBar {
[CCode (instance_pos = -1)]
public void on_button1_clicked (Button source) {
source.label = "Thank you!";
}
[CCode (instance_pos = -1)]
public void on_button2_clicked (Button source) {
source.label = "Thanks!";
}
}
}
// ...
var object = new Foo.MyBar ();
builder.connect_signals (object);
// ...
Attention: When using Gtk.Builder's signal auto-connection feature all handlers must have the full signatures of their corresponding signals, including the signal sender as first parameter. Otherwise you will get segmentation faults at runtime.
On Windows you have to add G_MODULE_EXPORT to the callbacks otherwise signal handlers won't be found. Use [CCode(cname="G_MODULE_EXPORT callback_name")] as a workaround. (cf. Bug 541548)
using Gtk;
[CCode (cname = "G_MODULE_EXPORT on_button1_clicked")]
public void on_button1_clicked (Button source) {
source.label = "Thank you!";
}
[CCode (cname = "G_MODULE_EXPORT on_button2_clicked")]
public void on_button2_clicked (Button source) {
source.label = "Thanks!";
}
int main (string[] args) {
Gtk.init (ref args);
try {
var builder = new Builder ();
builder.add_from_file ("sample.ui");
builder.connect_signals (null);
var window = builder.get_object ("window") as Window;
window.show_all ();
Gtk.main ();
} catch (Error e) {
stderr.printf ("Could not load UI: %s\n", e.message);
return 1;
}
return 0;
}
Tips and Tricks
gtkparasite allows you to inspect and modify your application's user interface at runtime. It's like Firebug for GTK+. Most distributions provide a package for gtkparasite. Launch with $ GTK_MODULES=gtkparasite appname. There's documentation and a screencast on the website.
You can follow GTK+ and friends on Twitter: @gtktoolkit
A blog posting with some general UI design tips
TreeView with ListStore
using Gtk;
public class TreeViewSample : Window {
public TreeViewSample () {
this.title = "TreeView Sample";
set_default_size (250, 100);
var view = new TreeView ();
setup_treeview (view);
add (view);
this.destroy.connect (Gtk.main_quit);
}
private void setup_treeview (TreeView view) {
/*
* Use ListStore to hold accountname, accounttype, balance and
* color attribute. For more info on how TreeView works take a
* look at the GTK+ API.
*/
var listmodel = new ListStore (4, typeof (string), typeof (string),
typeof (string), typeof (string));
view.set_model (listmodel);
view.insert_column_with_attributes (-1, "Account Name", new CellRendererText (), "text", 0);
view.insert_column_with_attributes (-1, "Type", new CellRendererText (), "text", 1);
var cell = new CellRendererText ();
cell.set ("foreground_set", true);
view.insert_column_with_attributes (-1, "Balance", cell, "text", 2, "foreground", 3);
TreeIter iter;
listmodel.append (out iter);
listmodel.set (iter, 0, "My Visacard", 1, "card", 2, "102,10", 3, "red");
listmodel.append (out iter);
listmodel.set (iter, 0, "My Mastercard", 1, "card", 2, "10,20", 3, "red");
}
public static int main (string[] args) {
Gtk.init (ref args);
var sample = new TreeViewSample ();
sample.show_all ();
Gtk.main ();
return 0;
}
}
Compile and Run
$ valac --pkg gtk+-2.0 gtk-treeview-liststore.vala $ ./gtk-treeview-liststore
TreeView with TreeStore
using Gtk;
public class TreeViewSample : Window {
public TreeViewSample () {
this.title = "TreeView Sample";
set_default_size (250, 100);
var view = new TreeView ();
setup_treeview (view);
add (view);
this.destroy.connect (Gtk.main_quit);
}
private void setup_treeview (TreeView view) {
var store = new TreeStore (2, typeof (string), typeof (string));
view.set_model (store);
view.insert_column_with_attributes (-1, "Product", new CellRendererText (), "text", 0, null);
view.insert_column_with_attributes (-1, "Price", new CellRendererText (), "text", 1, null);
TreeIter root;
TreeIter category_iter;
TreeIter product_iter;
store.append (out root, null);
store.set (root, 0, "All Products", -1);
store.append (out category_iter, root);
store.set (category_iter, 0, "Books", -1);
store.append (out product_iter, category_iter);
store.set (product_iter, 0, "Moby Dick", 1, "$10.36", -1);
store.append (out product_iter, category_iter);
store.set (product_iter, 0, "Heart of Darkness", 1, "$4.99", -1);
store.append (out product_iter, category_iter);
store.set (product_iter, 0, "Ulysses", 1, "$26.09", -1);
store.append (out product_iter, category_iter);
store.set (product_iter, 0, "Effective Vala", 1, "$38.99", -1);
store.append (out category_iter, root);
store.set (category_iter, 0, "Films", -1);
store.append (out product_iter, category_iter);
store.set (product_iter, 0, "Amores Perros", 1, "$7.99", -1);
store.append (out product_iter, category_iter);
store.set (product_iter, 0, "Twin Peaks", 1, "$14.99", -1);
store.append (out product_iter, category_iter);
store.set (product_iter, 0, "Vertigo", 1, "$20.49", -1);
view.expand_all ();
}
public static int main (string[] args) {
Gtk.init (ref args);
var sample = new TreeViewSample ();
sample.show_all ();
Gtk.main ();
return 0;
}
}
Compile and Run
$ valac --pkg gtk+-2.0 gtk-treeview-treestore.vala $ ./gtk-treeview-treestore
TreeView with CellRendererToggle
using Gtk;
public class ListSample : Gtk.Window {
private ListStore list_store;
private TreeView tree_view;
private enum Columns {
TOGGLE,
TEXT,
N_COLUMNS
}
public ListSample () {
this.title = "List Sample";
this.destroy.connect (Gtk.main_quit);
set_size_request (200, 200);
list_store = new ListStore (Columns.N_COLUMNS, typeof (bool), typeof (string));
tree_view = new TreeView.with_model (list_store);
var toggle = new CellRendererToggle ();
toggle.toggled.connect ((toggle, path) => {
var tree_path = new TreePath.from_string (path);
TreeIter iter;
list_store.get_iter (out iter, tree_path);
list_store.set (iter, Columns.TOGGLE, !toggle.active);
});
var column = new TreeViewColumn ();
column.pack_start (toggle, false);
column.add_attribute (toggle, "active", Columns.TOGGLE);
tree_view.append_column (column);
var text = new CellRendererText ();
column = new TreeViewColumn ();
column.pack_start (text, true);
column.add_attribute (text, "text", Columns.TEXT);
tree_view.append_column (column);
tree_view.set_headers_visible (false);
TreeIter iter;
list_store.append (out iter);
list_store.set (iter, Columns.TOGGLE, true, Columns.TEXT, "item 1");
list_store.append (out iter);
list_store.set (iter, Columns.TOGGLE, false, Columns.TEXT, "item 2");
add (tree_view);
}
}
void main (string[] args) {
Gtk.init (ref args);
var sample = new ListSample ();
sample.show_all ();
Gtk.main ();
}
Compile and run
$ valac --pkg gtk+-2.0 gtk-treeview-listsample.vala $ ./gtk-treeview-listsample
Code Snippet: Hide the cursor
This will create an invisible cursor and apply it to the current window (assuming this is a Gtk.Window)
var pix_data = "#define invisible_cursor_width 1\n#define invisible_cursor_height 1\n#define invisible_cursor_x_hot 0\n#define invisible_cursor_y_hot 0\nstatic unsigned short invisible_cursor_bits[] = {\n0x0000 };";
// this.window refers to a Gdk.Window, which is available after show_all is called
var pix = Gdk.Pixmap.create_from_data(this.window, pix_data, 1, 1, 1, col_black, col_black);
this.window.set_cursor(new Cursor.from_pixmap(pix, pix, col_black, col_black, 0, 0));
Clipboard
Basic example use of the clipboard:
using Gtk;
int main (string[] args) {
Gtk.init (ref args);
var window = new Window ();
window.title = "Clipboard";
window.set_default_size (300, 20);
window.destroy.connect (Gtk.main_quit);
var entry = new Entry ();
window.add (entry);
window.show_all ();
var display = window.get_display ();
var clipboard = Clipboard.get_for_display (display, Gdk.SELECTION_CLIPBOARD);
// Get text from clipboard
string text = clipboard.wait_for_text ();
entry.text = text ?? "";
// If the user types something ...
entry.changed.connect (() => {
// Set text to clipboard
clipboard.set_text (entry.text, -1);
});
Gtk.main ();
return 0;
}
Compile and run
$ valac --pkg gtk+-2.0 gtk-clipboard-sample.vala $ ./gtk-clipboard-sample
Note: copy some text before running.