Forum: wxRuby wxRuby dialog separate from main Ruby script execution

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
565490b01c149a92e080a6fa07ee105c?d=identicon&s=25 David Muir (dhm)
on 2009-01-15 11:29
Hi,

We have quite a large codebase in Ruby which uses Log4r to log errors,
warnings etc.  I have been trying to create a log window dialog using
wxRuby that can be popped up if a piece of code generates an error or
warning in one of our scripts.

E.g.
   GUI::LogWindow::show_dialog( ) do
     # Do something that will log messages.
   end

   def LogWindow::show_dialog( log, auto_popup = false, title = 'Log',
&block )
  log_window_thread = Thread.new do
    begin
      LogDialogApp.new( log, auto_popup, title ).main_loop( )
    rescue Exception => ex
      throw unless ( 'exit' == ex.message )
    end
  end

  yield if ( block_given? )
  log_window_thread.join( )
   end

My current problem is that when the wxRuby's main_loop is invoked no
Ruby code gets executed.  Is there any way around this?  Or is this a
limitation of using wxRuby?

Any suggestions appreciated.
Cheers,
David
3396e4a3df8a840faec520af8555a400?d=identicon&s=25 Mario Steele (Guest)
on 2009-01-15 12:58
(Received via mailing list)
Well, given the example, it's kinda confusing how you are setting this
Log
Window class up, and how things are truely initializing.  First of all,
you
really should be starting the main_loop at the top level of your code,
not
inside a Window class method, even if it's a module/singleton method.

Second, if your using threads, you need to setup a point for wxRuby to
break
from it's processing of code internally in the main_loop, to yield back
control to Ruby under Ruby 1.8.  Ruby 1.9 will work perfectly fine, as
the
threads are system native threads, not Green threads as in 1.8.

Third, if you have a single point of entry into your own application,
that
starts everything off, it would be best to call that method inside the
on_init of your App instance inside a thread, and then do the previous
method of yielding control back to ruby from wxRuby.  You can yield the
control back to Ruby, by doing Wx::Timer.every(55) { Thread.pass }.
This
will execute a timer every 55 milliseconds, that will call Thread.pass
to
give the next thread a bit of the CPU Scheduling, before returning back
to
the wxRuby main_loop.

Lastely, if you don't have a single point of entry into your
application,
then I would suggest making one, and doing as I described above, as this
will make things easier to track, should something go wrong.

hth,

Mario
06f6780c99d4a8dd71f2b474082ea9ce?d=identicon&s=25 Alex Fenton (Guest)
on 2009-01-15 13:01
(Received via mailing list)
David Muir wrote:
> My current problem is that when the wxRuby's main_loop is invoked no
> Ruby code gets executed.  Is there any way around this?  Or is this a
> limitation of using wxRuby?

It's a limitation of Ruby 1.8's green threads with C-based event loops.
There is a workaround - add something like this in your App.run block /
App#on_init method:

Wx::Timer.every(25) { Thread.pass }

Alternately, use Ruby 1.9 which I find does not have this problem,
because there are real system threads underneath Ruby threads. However
you should not update a GUI from the non-main thread.

For further, have a look at the etc/threaded.rb sample in the
distribution, and previous discussions on this list on wxRuby + threads.

hth
a
565490b01c149a92e080a6fa07ee105c?d=identicon&s=25 David Muir (dhm)
on 2009-01-15 13:23
Hi,

Thanks for the swift responses guys.

The reason I don't have a single point of entry, and spawned a thread
for the UI, is because I want this as a bolt on to existing scripts.

Such that existing scripts can be run with displaying a log when the
end-user runs it or not displaying a UI when an automated process is
running the script(s).

Having said that I'll have a go at refactoring the log window taking
your comments on board, and checking other threads, to see if I can work
something out.

Cheers,
Dave
06f6780c99d4a8dd71f2b474082ea9ce?d=identicon&s=25 Alex Fenton (Guest)
on 2009-01-15 15:30
(Received via mailing list)
David Muir wrote:
> The reason I don't have a single point of entry, and spawned a thread
> for the UI, is because I want this as a bolt on to existing scripts.
>
> Such that existing scripts can be run with displaying a log when the
> end-user runs it or not displaying a UI when an automated process is
> running the script(s).

I don't know Log4r but you can define custom Loggers etc. Define one
that call Wx::log_message, Wx::log_warn etc in response to the Log4r
calls in your core file. Your UI shell is going to look something like
the structure below:

hth
alex
__

require 'wx'

### Custom outputter /logger here

# What to run
scripts = [ 'foo.rb' ]

Wx::App.run do
  Wx::log_message('Starting')
  t = Thread.new do
    scripts.each do | script |
      Wx::log_message("Running #{script}")
      load script
    end
  end

  Wx::Timer.every(25) { Thread.pass }
  true
end
565490b01c149a92e080a6fa07ee105c?d=identicon&s=25 David Muir (dhm)
on 2009-01-16 13:19
Hi guys,

I had some success yesterday so thought I should complete this thread by
showing how I got it working.

The LogWindowDialog class is not included because it includes a lot of
details that are irrelevant for the topic.  In that class I create a
Log4r outputter and formatter to pass LogEvent objects straight to the
dialog which are then serialised and output into a TextCtrl widget.

Seems to work fairly well with the few examples I've tried.  If the Ruby
script kicks off an external app the UI will be unresponsive.

Code below.  Hope it helps others.
Cheers,
Dave

#
# == Description
# Generic log window dialog that integrates with our Pipeline::Log
# objects for displaying log messages to users.
#
# === Example Usage
#   # To auto-popup the display of the log dialog:
#   Pipeline::GUI::LogWindow::show_dialog( true ) do
#     # Do something that will log messages, dialog only displayed on
#     # errors.
#   end
#
#   # To force the display of the log dialog:
#   Pipeline::GUI::LogWindow::show_dialog( ) do
#      # Do something that will log messages
#   end
#
class LogWindow < Wx::App

  #---------------------------------------------------------------------
  # Class Methods
  #---------------------------------------------------------------------
  def initialize( log, auto_popup, title = 'Log', &block )
    super( )
    @log = log
    @auto_popup = auto_popup
    @title = title
    @proc = Proc.new( ) do
      yield
    end
  end

  #
  # Use this method to explicitly display a log dialog box.  Only
  # log messages generated after this call will be displayed.
  #
  def LogWindow::show_dialog( auto_popup = false, log =
LogSystem::instance().rootlog, title = 'Log' )
    begin
      dlg = LogWindow.new( log, auto_popup, title ) do
        yield if ( block_given? )
      end
      dlg.main_loop( )
    rescue Exception => ex
      throw ex unless ( 'exit' == ex.message )
    end
  end

  #---------------------------------------------------------------------
  # Instance Methods
  #---------------------------------------------------------------------
  def on_init( )
    Wx::Timer::every( 25 ) do
      Thread::pass( )
    end
    dlg = LogWindowDialog::new( @log, @title, @auto_popup )
    dlg.show( ) unless ( @auto_popup )

    script_thread = Thread.new do
      @proc.call( )
      exit( ) if ( @auto_popup and ( not dlg.is_shown ) )
    end
    script_thread.join( )
  end
end

#
# == Description
# Log window dialog.
#
class LogWindowDialog < Wx::Dialog
  ...
end
06f6780c99d4a8dd71f2b474082ea9ce?d=identicon&s=25 Alex Fenton (Guest)
on 2009-01-17 12:50
(Received via mailing list)
David Muir wrote:
> I had some success yesterday so thought I should complete this thread by
> showing how I got it working.
>

Thanks for sharing this.


> Seems to work fairly well with the few examples I've tried.  If the Ruby
> script kicks off an external app the UI will be unresponsive.
>

The trick is to call Wx::app.yield or Wx::safe_yield (the latter in SVN
only), while an external task is running, to tell Wx to process events
and update the UI

alex
This topic is locked and can not be replied to.