Search code examples
gtkgtk3vala

How to pass widget and data from one file to another file in different class?


Beginner-level questions. I’m creating a counter application (first application from The 7 Tasks). I created this application in one file and it is working fine. Following is the code.

class Application : Gtk.Application {
  public int val = 0;

  public Application() {
    Object(
      application_id: "com.github.uname.counter",
      flags: ApplicationFlags.FLAGS_NONE
    );
  }
  
  protected override void activate() {
    var window = new Gtk.ApplicationWindow(this);
    window.default_height = 30;
    window.default_width = 300;
    window.title = "Counter";
    
    var grid = new Gtk.Grid();
    grid.column_homogeneous = true;
    grid.row_homogeneous = true;
    grid.row_spacing = 5;
    grid.column_spacing = 5;
    
    var entry = new Gtk.Entry();
    entry.text = val.to_string();
    entry.editable = false;
    grid.attach(entry, 0, 0, 1, 1);

    var button1 = new Gtk.Button.with_label("Counter");
    grid.attach(button1, 1, 0, 1, 1);
    
    button1.clicked.connect (() => {
      this.val = this.val + 1;
      entry.text = this.val.to_string();
    });

    window.add(grid);
    window.show_all();
  }
  
  public static int main(string[] args) {
    var application = new Application();
    return application.run(args);
  }
}

Now, I'm trying to divide the above code into separate files such as Application.vala, Entry.vala, and Button.vala. Here is the code for these files.

Code for Application.vala.

class Application : Gtk.Application {
  public int val = 0;

  public Application() {
    Object(
      application_id: "com.github.chauhankiran.counter",
      flags: ApplicationFlags.FLAGS_NONE
    );
  }
  
  protected override void activate() {
    var window = new Gtk.ApplicationWindow(this);
    window.default_height = 30;
    window.default_width = 300;
    window.title = "Counter";
    
    var grid = new Gtk.Grid();
    grid.column_homogeneous = true;
    grid.row_homogeneous = true;
    grid.row_spacing = 5;
    grid.column_spacing = 5;
    
    var entry = new Entry(val);
    grid.attach(entry, 0, 0, 1, 1);

    var button1 = new Button(val);
    grid.attach(button1, 1, 0, 1, 1);

    window.add(grid);
    window.show_all();
  }
  
  public static int main(string[] args) {
    var application = new Application();
    return application.run(args);
  }
}

Code for Entry.vala.

public class Entry : Gtk.Entry {
  public Entry(int val) {
    text = val.to_string();
  }

  construct {
    editable = false;
  }
}

Code for Button.vala.

public class Button : Gtk.Button {
  // Is it correct?
  public int val;

  public Button(int val) {
    this.val = val;
  }

  construct {
    label = "Counter";
  }

  // How to write this within Button.vala from Application.vala?
  // How to get entry widget in this class?
  button1.clicked.connect (() => {
    this.val = this.val + 1;
    entry.text = this.val.to_string();
  });
}

Now, I have the following questions.

  1. Entry.vala accepts val as initial value. I don't know how to pass it in construct. So, I used public object method. Is it correct way?
  2. In Button.vala I need val as well access to entry so that I can get access to entry in Button.vala? Or this is incorrect way to do the code? If that is that is the case, please suggest correct way. Currently separate files code throws error as I don’t know how to connect and pass the information correctly.

Solution

  • The 7 Tasks are a good exercise to learn, and you seem to be off to a great start!

    1. Entry.vala accepts val as initial value. I don't know how to pass it in construct. So, I used public object method. Is it correct way?

    The preferred way to handle construction in Vala is using GObject-style construction: https://wiki.gnome.org/Projects/Vala/Tutorial#GObject-Style_Construction

    What you're doing would technically work, but using the GObject-style you'd end up with something like the following:

    public class Entry : Gtk.Entry {
      public Entry(int val) {
        Object (
          text: val.to_string(),
          editable: false
        );
      }
    }
    

    The important things to note here are:

    1. This only works for properties declared as construct or set
    2. The syntax is slightly different than what you were doing (property: value vs. member = value)

    And one other little optimization:

    • editable is also a property that can be set in the constructor, so no need for a construct block here!

    Notice that you can make some similar changes to your Button class as well!

    1. In Button.vala I need val as well access to entry so that I can get access to entry in Button.vala? Or this is incorrect way to do the code? If that is that is the case, please suggest correct way. Currently separate files code throws error as I don’t know how to connect and pass the information correctly.

    Currently your Button code has references to the Entry in it. I would advise against this from an object-oriented programming (OOP) perspective (you may hear people toss around terms like Single-Responsibility or Separation of Concerns, etc.), but the gist is that the button should just focus on being what it is: a button. A button doesn't need to be aware of the presence of the entry, and the entry doesn't need to exist in order to have a button. Your logic of handling what happens between widgets when the button is clicked should happen at a level above. In this case, that would be in your Application class where you've created both of those widgets:

    ...
    var entry = new Entry(val);
    grid.attach(entry, 0, 0, 1, 1);
    
    var button1 = new Button(val);
    grid.attach(button1, 1, 0, 1, 1);
    
    button1.clicked.connect (() => {
      // Update the entry
    });
    ...
    

    Let's take a quick look at your button:

    public class Button : Gtk.Button {
      // Is it correct?
      public int val;
      ...
    

    It's not wrong, and there are many ways to do what you're doing. So let's roll with it as is.

    At this point you've got your button which updates an internal int value every time it's clicked and you now need to update the entry to display the new value. Currently you have:

    button1.clicked.connect (() => {
      this.val = this.val + 1;
      entry.text = this.val.to_string();
    });
    

    Since this is now being handled in the Application class, you'll need to change those references to this, since you want to reference and update val from the button, not the variable in Application that you're using for the initial value:

    button1.clicked.connect (() => {
      button1.val = button1.val + 1;
      entry.text = button1.val.to_string();
    });
    

    Hopefully that helped a bit, you were 99% of the way there with splitting it into multiple classes! Keep it up, and good luck on the next tasks!