I'm trying to create a custom scrollable text area. I created a DrawingArea
and a ScrollBar
inside a Grid
. I have attached the draw
event of DrawingArea
to this.on_draw
method which simply looks at ScrollBar
's value and moves the Cairo.Context
appropriately before drawing the Pango.Layout
.
The first problem is that this.on_draw
is getting invoked whenever the ScrollBar
is touched even though I have not registered any events with ScrollBar
. How do I prevent this, or check this?
The second problem is that even though this.on_draw
is invoked, the changes made to the Context
is not displayed unless the ScrollBar
value is near 0
or 100
(100 is the upper value of Adjustment). Why is this happening?
I did find out that if I connect
the value_changed
event of ScrollBar
to a method that calls queue_redraw
of DrawingArea
, it would invoke this.on_draw
and display it properly after it. But due to the second problem, I think this.on_draw
is getting invoked too many times unnecessarily. So, what is the "proper" way of accomplishing this?
using Cairo;
using Gdk;
using Gtk;
using Pango;
public class Texter : Gtk.Window {
private Gtk.DrawingArea darea;
private Gtk.Scrollbar scroll;
private string text = "Hello\nWorld!";
public Texter () {
GLib.Object (type: Gtk.WindowType.TOPLEVEL);
Gtk.Grid grid = new Gtk.Grid();
this.add (grid);
var drawing_area = new Gtk.DrawingArea ();
drawing_area.set_size_request (200, 200);
drawing_area.expand = true;
drawing_area.draw.connect (this.on_draw);
grid.attach (drawing_area, 0, 0);
var scrollbar = new Gtk.Scrollbar (Gtk.Orientation.VERTICAL,
new Gtk.Adjustment(0, 0, 100, 0, 0, 1));
grid.attach (scrollbar, 1, 0);
this.darea = drawing_area;
this.scroll = scrollbar;
this.destroy.connect (Gtk.main_quit);
}
private bool on_draw (Gtk.Widget sender, Cairo.Context ctx) {
ctx.set_source_rgb (0.9, 0.9, 0.9);
ctx.paint ();
var y_offset = this.scroll.get_value();
stdout.printf("%f\n", y_offset);
ctx.set_source_rgb (0.25, 0.25, 0.25);
ctx.move_to(0, 100 - y_offset);
var layout = Pango.cairo_create_layout(ctx);
layout.set_font_description(Pango.FontDescription.from_string("Sans 12"));
layout.set_auto_dir(false);
layout.set_text(this.text, this.text.length);
Pango.cairo_show_layout(ctx, layout);
return false;
}
static int main (string[] args) {
Gtk.init (ref args);
var window = new Texter ();
window.show_all ();
Gtk.main ();
return 0;
}
}
Also, please point out any (possibly unrelated) mistake if you find one in the above code.
The part that you are missing is that a draw
signal does not mean "redraw everything". Instead, GTK+ sets the clip region of the cairo context to the part that needs to be redrawn, so everything else you do doesn't have any effect. The cairo function cairo_clip_extents()
will tell you what that region is. The queue_draw_area()
method on GtkWidget will allow you to explicitly mark a certain area for drawing, instead of the entire widget.
But your approach to scrollbars is wrong anyway: you're trying to build the entire infrastructure from scratch! Consider using a GtkScrolledWindow instead. This automatically takes care of all the details of scrolling for you, and will give you the overlay scrollbars I mentioned. All you need to do is set the size of the GtkDrawingArea to the size you want it to be, and GtkScrolledWindow will do the rest. The best way to do this is to subclass GtkDrawingArea and override the get_preferred_height()
and/or get_preferred_width()
virtual functions (being sure to set both minimum and natural sizes to the sizes you want for that particular dimension). If you ever need to change this size later, call the queue_resize()
method of GtkWidget. (You probably could get away with just using set_size_request()
, but what I described is the preferred way of doing this.) Doing this also gives you the advantage of not having to worry about transforming your cairo coordinates; GtkScrolledWindow does this for you.