wxRuby dialog separate from main Ruby script execution


#1

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


#2

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


#3

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


#4

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


#5

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


#6

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


#7

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