views:

329

answers:

2

Ok, so I'm not real sure about lots of things in Shoes, but my trial and error approach has failed me so far on this one.

I've got a class that does some sort of computation that takes a while, and I want to throw up a progress bar for the user to look at while it finishes. My computationally intensive method yields its percent complete if passed a block:

class MathyStuff

  def initialize()
  end

  ## Some expensive, time consuming method which yields it's percent complete
  def expensiveMethod(&block)
    0.upto(100) do |i|
      0.upto(100000) do |j|
        k = j;
      end

      yield i.to_f/100;
    end
  end

end

Here's what I'd like to say in Shoes:

require 'MathyStuff.rb'

Shoes.app do

  @myMathyStuff = MathyStuff.new();

  button("Do expensive mathy thing...") do
    window() do
      @progress = progress();
      @myMathyStuff.expensiveMethod() {|percent| @progress.fraction = percent;}
    end
  end

end

But it doesn't seem to work. I've tried with/without the window call, I've tried animate() in various ways, I even tried calling Thread.new and passing it the window block, having them converse via Shoes.APPS()[0].get/setPercent methods; nothing seems to work properly.

Maybe I'm not using the progress bar the way it's meant to be used. Then again, what else would a progress bar be for? ;-)

+4  A: 

First of all, sharing data between two windows in Shoes is a royal pain. I don't recommend it. Instead, hide the contents of the first window and bring up the progress bar in its place.

Second, we'll extend MathyStuff to switch it from processing a block to providing a percent attribute, so we can access it from an animation thread:

class MathyStuff
  attr_accessor :percent

  def expensiveMethodWrapper
    @percent = 0.0
    expensiveMethod {|x| @percent = x}
  end
end

Shoes.app do

  @myMathyStuff = MathyStuff.new();
  @window_slot = stack do
    button("Do expensive mathy thing...") do
      @window_slot.toggle
      @progress_slot = flow do
        @progress = progress :width => 1.0
      end
    end
    Thread.new do
        @myMathyStuff.expensiveMethodWrapper
    end
    @animate = animate do
      @progress.fraction = @myMathyStuff.percent
      if @myMathyStuff.percent == 1.0
        @progress_slot.remove
        @window_slot.toggle
        @animate.stop
      end
    end
  end
end
Pesto
Thanks, that helped a lot. It seems that GUI programming, even in Shoes, requires a different way of thinking about things. I'll be back with more questions soon I'm sure!
Shawn
+1  A: 

As I understand it, things like the progress bar need to be redrawn to screen after being fed a percentage value, this is what you'd use animate for in this case.

If you just want to do what you've stated in your question, then this approach - although not very flexible - does work for your example. But because it separates your progress logic from your actual method, you can only change the percentage values before and after you run those methods. So, since you're just running an iteration 100 times, then you can do it effectively this way.

class Mathy
  def foo
    100000.times do |bar|
      foo = bar
    end
  end
end

Shoes.app do
  @mathy = Mathy.new
  button("Run") do
    @p = progress
    animate do |percent|
      break if percent > 100
      @mathy.foo
      @p.fraction = percent.to_f / 100
    end
  end
end

If your method is doing more than just repeating the same iteration, then yes, you'd want to yield its progress frequently from within the method. Then, in order to return that progress from the method while it's running, you could put it in a separate thread as Pesto suggested, and just poll it for the progress in your animate block. Using an attr_accessor for returning the percentage is also a good idea. Hope that helps.

Mike Richards