1
votes

I have a few buttons and an area where I want to draw some things. I have been trying to create a surface for drawing with Cairo since Gtk's Drawing Area widget doesn't seem to know how to draw basic shapes (or I just failed to find how it does it).

I found some basic tutorials on this (for example: http://zetcode.com/gui/gtksharp/drawing/ or http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cairo/tutorial/) but can't figure out how to make the surface for drawing in my app, along with buttons and labels, not just a new blank window used only for Cairo drawing.

This is how my app looks for now:

enter image description here

So, I want the blank part in the middle to be my drawing area.

I tried this which I think should make the Drawing Area widget I have in my app drawable with Cairo:

enter image description here

I don't quite understand what I should do here.

I just need to draw some basic circles and lines on it so I don't really need Cairo, any help with how I could do any drawing with Gtk# would be greatly appreciated!

3

3 Answers

3
votes

You're on the right track. Unfortunately GTK# and Cairo have a few quirks. First, I would highly recommend using an EventBox widget instead of DrawingArea. Counter intuitively, I've never had much luck getting a DrawingArea to work properly. An EventBox works in much the same way except that you can make the background transparent by setting VisibleWindow to false.

public class YourWidget : EventBox
{
    public YourWidget() {
        Visible = true;
        VisibleWindow = false;
        ExposeEvent += OnExpose;
    }
}

Next, the ordinates used when drawing in an event box are based on the origin of the parent window, i.e. the entire application window. Each widget object contains some properties, Allocation, that specify where it is within the parent window as well as how large it is. In order to draw within the limits of the custom widgets window, you have to reference off these points. This is only for custom widgets the extend the EventBox widget. If memory serves, widgets using a DrawingArea are reference from the widget location. Lastly, I would contain the Cairo Context within a using block so you don't forget to dispose of the object.

protected void OnExpose(object sender, ExposeEventArgs args) {
    using (Context cr = Gdk.CairoHelper.Create(this.GdkWindow)) {
          int top = Allocation.Top;
          int left = Allocation.Left;  

          cr.Rectangle(left + 8, top + 8, 10, 10);
          cr.SetSourceRGB(255, 0, 0);
          cr.Fill();
    }
}

Also, its been awhile since I've started a new GTK# application, but you may have to add the Mono.Cairo package to the project's references. Finally, I've used EventBox based widgets to create several custom widgets for a touch application that may also serve as a reference to get you started. I hope this all helps.

2
votes

There's a few things I can add here. You can use the DrawingArea, I've never had the same trouble as Skyler, but Cairo will draw on pretty much any widget or the base window. I use DrawingAreas quite a bit and I've also extended base widget and used cairo to render.

The key pointers I can give:

  • Use the Expose event of the widget to get a reference to the GdkWindow that Cairo will need to draw to. For example:

    protected void OnExpose (object o, Gtk.ExposeEventArgs args)
    {
        Draw (args.Event.Window);
    }
    
  • In your drawing operation, use the Cairo Helper to create a Cairo Context.

    private void Draw (Gdk.Window w)
    {
        double _bg = (55.0 / 255.0);
        using (Cairo.Context _c = Gdk.CairoHelper.Create (w)) {
            _c.LineWidth = 2;
            _c.SetSourceRGB (_bg, _bg, _bg);
            _c.Rectangle (0, 0, this.WidthRequest, this.HeightRequest);
            _c.StrokePreserve ();
            _c.SetSourceRGBA (_bg, _bg, _bg, 0.5);
            _c.Fill ();
            _c.Dispose ();
        }
    }
    
  • Use Dispose() at the end or you'll run into memory issues.

If you want I can also show you how to use the Pango text rendering libraries. They're a bit more complicated but much better than using the SetText() op in the cairo libraries.

Cheers, M

1
votes

You can draw without Cairo, but that is considered obsolete, at least using C#.

The most thorough info (not much, though) can be found here: http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cairo/

Personally, and as mentioned by #muszeo, I use a customized DrawingArea:

public class MyWidget: Gtk.DrawingArea {       
    public MyWidget(int width, int height)
    {
        this.Width = width;
        this.Height = height;
        this.SetSizeRequest( width, height );
        this.ExposeEvent += (o, args)  => this.OnExposeDrawingArea();
    }

    /// <summary>
    /// Redraws the widget
    /// </summary>
    private void OnExposeDrawingArea()
    {
        using (var canvas = Gdk.CairoHelper.Create( this.GdkWindow ))
        {
            // Draw with the canvas
            // /* i.e. */ canvas.LineTo( 100, 100 );
            canvas.Stroke();

            // Clean
            canvas.GetTarget().Dispose();
        }
    }
}

Then you only need to create your widget in a window (maybe a dialog?):

var dlg = new Gtk.Dialog( "Demo", this, Gtk.DialogFlags.Modal );
var swScroll = new Gtk.ScrolledWindow();

MyWidget widget = new MyWidget( 512, 512 );
swScroll.AddWithViewport( widget );
dlg.VBox.PackStart( swScroll, true, true, 5 );
dlg.AddButton( Gtk.Stock.Close, Gtk.ResponseType.Close );
dlg.ShowAll();
dlg.Run(); 

I keep a repo with various Gtk# simple demoes, one of it being a very basic Chart class that uses Cairo in order to draw graphics:

http://github.com/baltasarq/GtkSharpDemo

Hope this helps.