Search code examples
gtkprogress-barvala

Vala force refresh progressbar


I've made an aplication with vala where at some point I have to process a lot of files. I've created a window to choose a folder and then I get the paths of files and make some proces on them.

I've added a progress bar to this window to show how many files have been processed but for some reason it remains always empty. Code about window:

        this.files_window = new Gtk.Window();
        this.files_window.window_position = Gtk.WindowPosition.CENTER;
        this.files_window.destroy.connect (Gtk.main_quit);
        // VBox:
        Gtk.Box vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 5);
        this.files_window.add (vbox);
        // Buttons to open and close
        Gtk.Button cancel = new Gtk.Button.with_label ("Cancel");
        Gtk.Button select = new Gtk.Button.with_label ("Select");
        vbox.add (select);
        vbox.add (cancel);
        // proogress bar
        this.progress_bar = new Gtk.ProgressBar();
        vbox.add(this.progress_bar);
        // conect select to method do_stuff
        select.clicked.connect (do_stuff);
        this.files_window.show_all ();

As you can see, I connect the button "select" to the method "do_stuff" where I get the paths of selected files and make some process.

I update correctlly the fraction of the progres bar because I've added some prints to know if the value is correct and it is. It's just that the windows is not refreshing, possibly because all the work it is doing with the process of the files. Here is the code about do_stuff() method:

       // some proces to get paths of files in the list sfiles
       double fraction = 0.0;
       this.progress_bar.set_fraction (fraction);
       int processed_files = 0;
       foreach (string sfile in sfiles) {
            do_some_proces_to_file(sfile);
            processed_files += 1;
            fraction = (double)processed_files/(double)sfiles.length;
            this.progress_bar.set_fraction (fraction);
            stdout.printf("Real fraction: %f\n", this.progress_bar.get_fraction());
        }

The printf shows that the value of the progres bar is being updated but in the window the bar is always empty.

Am I missing something? Is it the correct way to do the progres bar? Should I made another thread to do the stuff?


Solution

  • As @nemequ says, your code is blocking the main loop thread (which handles both user input and scheduling/drawing widget updates), hence it the progress bar is not updated until the method completes.

    Using a thread is one way solve the problem, however using threads can lead to a lot of bugs however since it can be difficult to make even simple interactions between threads safe.

    An async method avoids this by interleaving the code with the other work being done by the main loop. An async version of your do_stuff() would be pretty straight-forward to write, simply declare it async and put a yield in the for loop somewhere:

    public async void do_stuff() {
        ...
        foreach (string sfile in sfiles) {
            // all of this is as before
            do_some_proces_to_file(sfile);
            processed_files += 1;
            fraction = (double)processed_files/(double)sfiles.length;
            this.progress_bar.set_fraction (fraction);
    
            // Schedule the method to resume when idle, then
            // yield control back to the caller
            Idle.add(do_stuff.callback);
            yield;
        }
    }
    

    You can then kick it off from your click handler by calling: do_stuff.begin().