2015年4月
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    
無料ブログはココログ

« 2011年12月 | トップページ | 2012年2月 »

2012年1月の4件の記事

2012年1月31日 (火)

ようやく1本書き上げました(8) - application 全体に関係するイベント

前回例として挙げた、MyPanelOutOrScan の panel で、[出力せず Scan のみ] ボタンをクリックした場合のイベント処理を考えて見ます。

panel_url の text_ctrl_url から文字列を取得して、RadioBox から、html の内容を処理する下請けオブジェクトを特定して、下請けオブジェクト経由で web から取得した文字列を MyPanelAbstract の text_ctrl_view と text_ctrl_file に設定するという処理が必要になります。今回の application では、この処理を発生させるきっかけは、 [出力せず Scan のみ] ボタンたげですが、同じ処理をメニューから実行させるようにも実装できます。同じ処理を application の複数の場所から実行する設計でも全くおかしくないので、処理するルーチンは、どこからでも呼び出しやすい場所に記述するべきでしょう。

いくつかやりようはあるのかも知れませんが、とりあえず今回は、MyApp#scan_web( evt ) として実装してみます。なんらかのイベントに対するハンドラとして割り付けることを想定しているので、引数として Event を受けます。

  # 指定された url を scan し、rd file と、debug log を取得。
  # 取得内容を note のそれぞれのページに、UTF-8 変換して表示。
  # rd および log の出力 file 名入力欄に dafault file 名を設定。
  def scan_web( evt )
    journal = Journals[ get_ui_data( :select_journal_index ) ]
    url = get_ui_data( :url )
    if ( url.empty? )
      Wx::MessageDialog.new( @frame_main, "url を指定してください.", "Error",
        Wx::OK | Wx::ICON_ERROR ).show_modal
      return( false )
    end

    begin
      journal.scan_web_page( url )
    rescue
      Wx::MessageDialog.new( @frame_main, $!.inspect,
        "Error", Wx::OK | Wx::ICON_ERROR ).show_modal
      raise if ( $DEBUG )
      get_ui_ctrl( :text_view_rd ).clear
      get_ui_ctrl( :text_specify_rd ).clear
      get_ui_ctrl( :text_view_log ).clear
      get_ui_ctrl( :text_specify_log ).clear
      return( false )
    end

    @abstract_output = journal.abstract
    abstract = get_ui_ctrl( :text_view_abstract )
    abstract.clear; abstract.change_value( NKF.nkf( '-w', @abstract_output ) )

    get_ui_ctrl( :text_specify_abstract ).set_value( journal.default_out_file_name )

    @log = journal.log
    log = get_ui_ctrl( :text_view_log )
    log.clear; log.change_value( NKF.nkf( '-w', @log ) )

    get_ui_ctrl( :text_specify_log ).set_value( 'scan.log' )
    true
  end

注意が必要な点として、wxRbuy では日本語は UFT-8 でないと表示が乱れますので、上記で強調表示した行で、下請けルーチンから得た文字列を UFT-8 に変換してから値を設定しています。文字コードの変換にはいくつか方法があるはずですが、添付ライブラリの NKF だと、返還前の文字コードが何であっても、それなりに変換してくれるので、ちょっと古いライブラリらしいのですが使っています。ruby 1.9 組み込みの文字コード変換機能では、元の文字コードが確定していないと使い辛かったり、厳密すぎて変換に失敗することなどがあるので、比較的ルーズなこのライブラリにしました。

ここで、FrameMain および、その下の各 panel から情報を得なければ処理が進みませんので、MyApp#get_ui_data( tag ), MyApp#get_ui_ctrl( tag ) という method を経由して、それぞれ data の値と、data を操作する control を取得します。実態は、完全に下請けに出すだけの wrapper で、このようにしました。

  def get_ui_data( *args ); @frame_main.get_ui_data( *args ); end
  def get_ui_ctrl( *args ); @frame_main.get_ui_ctrl( *args ); end

実体となる FrameMain#get_ui_data と FrameMain#get_ui_ctrl は、こんな感じで実装してみました。

  # Frame 自身の control 或いは、Frame に貼り付けた Panel などの
  # 下部構造に属する contol のうち、外部から読み出しを要するものに
  # 対する access を集約する method. 書き込みはできない。
  def get_ui_data( tag )
    case tag
    when :url; @panel_url.text_ctrl_url.get_value

    when :select_journal_name; @rbox_select_journal.get_string_selection
    when :select_journal_index; @rbox_select_journal.get_selection

    when :dir; @panel_out_or_scan.text_ctrl_dir.get_value

    when :abstract_specified; @panel_abstract.text_ctrl_file.get_value
    when :log_specified; @panel_log.text_ctrl_file.get_value
    else; nil
    end
  end

  # Frame 自身の control 或いは、Frame に貼り付けた Panel などの
  # 下部構造に属する contol のうち、外部から書き込みを要するものに
  # 対する access を集約する method.
  # 読み出しのみでよいものは、get_ui_data で。
  def get_ui_ctrl( tag )
    case tag
    when :text_specify_abstract; @panel_abstract.text_ctrl_file
    when :text_specify_log; @panel_log.text_ctrl_file
    when :text_view_abstract; @panel_abstract.text_ctrl_view
    when :text_view_log; @panel_log.text_ctrl_view
    else; nil
    end
  end

まだ、各 panel から情報を吸い上げられるような method を実装していないので、すぐに動作する code ではありませんが、雰囲気はつかんでもらえると思います。

さて、それでは、どうやって MyApp#scan_web を呼び出すか。まず、一番安直な方法から。実際のところ、今回くらいの規模であれば、これが正解かとも思うのですが・・・。


MyPanelOutOrScan#initialize の中に記述します


    evt_button( find_by_name( 'btn_scan_only' ) ) { |e| $my_app.scan_web( e ) }

source code の最後で、MyApp.new したときの返り値を、グローバル変数としての $my_app に代入しておくのがミソです。これで、この先この application が複雑になっても、グローバル変数を経由して、どこからでも呼び出すことができます。

このグローバル変数経由でイベント処理を行うパターンでのソースコードはこのようになります。


GUI_08a.rb


# encoding: utf-8

require "wx"

SUBs = Dir.glob( File.dirname( __FILE__ ) + '/sub*.rb' )
SUBs.each{ |s| require_relative s }
Journals = SUBs.map{ |s| File.basename( s, '.*' ).upcase }.map{ |j| self.class.const_get(j).new }

# XmlResource を利用するための function
module XrcUtil
module_function
  # Wx::App で最初に xrc file を取り込む function.
  # この function を実行する source file からの相対 path で指定する。
  def load_resource_relative( xrc )
    Wx::XmlResource.get.load( File.dirname( __FILE__ ) + '/' + xrc )
  end

  # Wx::Window を継承した object で、xrc 内で定義されているものを
  # 'name' field の値で識別して返す。
  def find_by_name( name )
    Wx::Window.find_window_by_name( name, self )
  end

  # もともとの load_subclass method が、load する window の種類に
  # よって使い分けを要求されるため、自動判別するために wrap する。
  def load_subclass( name, parent = nil )
    case self
    when Wx::Dialog
      Wx::XmlResource.get.load_dialog_subclass( self, parent, name )
    when Wx::Frame
      Wx::XmlResource.get.load_frame_subclass( self, parent, name )
    when Wx::Wizard
      Wx::XmlResource.get.load_wizard_subclass( self, parent, name )
    when Wx::Panel
      Wx::XmlResource.get.load_panel_subclass( self, parent, name )
    end
  end
end

class MyApp < Wx::App
  def on_init
    XrcUtil.load_resource_relative( 'MyProject2.xrc' )
    @frame_main = FrameMain.new
    @frame_main.show
  end

  # 指定された url を scan し、abstract file と、debug log を取得。
  # 取得内容を note のそれぞれのページに、UTF-8 変換して表示。
  # abstract および log の出力 file 名入力欄に dafault file 名を設定。
  def scan_web( evt )
    journal = Journals[ get_ui_data( :select_journal_index ) ]
    url = get_ui_data( :url )
    if ( url.empty? )
      Wx::MessageDialog.new( @frame_main, "url を指定してください.", "Error",
        Wx::OK | Wx::ICON_ERROR ).show_modal
      return( false )
    end

    begin
      journal.scan_web_page( url )
    rescue
      Wx::MessageDialog.new( @frame_main, $!.inspect,
        "Error", Wx::OK | Wx::ICON_ERROR ).show_modal
      raise if ( $DEBUG )
      get_ui_ctrl( :text_view_rd ).clear
      get_ui_ctrl( :text_specify_rd ).clear
      get_ui_ctrl( :text_view_log ).clear
      get_ui_ctrl( :text_specify_log ).clear
      return( false )
    end

    @abstract_output = journal.abstract
    abstract = get_ui_ctrl( :text_view_abstract )
    abstract.clear; abstract.change_value( NKF.nkf( '-w', @abstract_output ) )
    get_ui_ctrl( :text_specify_abstract ).set_value( journal.default_out_file_name )

    @log = journal.log
    log = get_ui_ctrl( :text_view_log )
    log.clear; log.change_value( NKF.nkf( '-w', @log ) )
    get_ui_ctrl( :text_specify_log ).set_value( 'scan.log' )
    true
  end

  def get_ui_data( *args ); @frame_main.get_ui_data( *args ); end
  def get_ui_ctrl( *args ); @frame_main.get_ui_ctrl( *args ); end
end

class FrameMain < Wx::Frame
  def initialize
    super( nil, -1, $0, :size => [450,500] )
    set_background_colour( Wx::Colour.new( 240, 240, 255 ) )
    sizer_top = Wx::BoxSizer.new( Wx::VERTICAL )
    set_sizer( sizer_top )

    sizer_top.add( @panel_url = MyPanelUrl.new( self ), 0, Wx::EXPAND )
    sizer_top.add( @rbox_select_journal = make_rbox, 0,
      Wx::LEFT | Wx::RIGHT | Wx::EXPAND, 5 )
    sizer_top.add( @panel_out_or_scan = MyPanelOutOrScan.new( self ), 0, Wx::EXPAND )
    sizer_top.add( make_view_note, 1, Wx::EXPAND )
  end

  def make_rbox
    Wx::RadioBox.new( self, -1, 'Select Journal',
      :choices => Journals.map{ |j| j.name.encode(__ENCODING__) },
      :style => Wx::RA_SPECIFY_COLS, :major_dimension => 2
    )
  end

  def make_view_note
    note = Wx::Notebook.new( self, -1 )
    note.add_page( @panel_abstract = MyPanelAbstract.new( note, -1 ), 'file 出力', true )
    note.add_page( @panel_log = MyPanelLog.new( note, -1 ), 'debug log', false )
    note
  end

  # Frame 自身の control 或いは、Frame に貼り付けた Panel などの
  # 下部構造に属する contol のうち、外部から読み出しを要するものに
  # 対する access を集約する method. 書き込みはできない。
  def get_ui_data( tag )
    case tag
    when :url; @panel_url.text_ctrl_url.get_value

    when :select_journal_name; @rbox_select_journal.get_string_selection
    when :select_journal_index; @rbox_select_journal.get_selection

    when :dir; @panel_out_or_scan.text_ctrl_dir.get_value

    when :abstract_specified; @panel_abstract.text_ctrl_file.get_value
    when :log_specified; @panel_log.text_ctrl_file.get_value
    else; nil
    end
  end

  # Frame 自身の control 或いは、Frame に貼り付けた Panel などの
  # 下部構造に属する contol のうち、外部から書き込みを要するものに
  # 対する access を集約する method.
  # 読み出しのみでよいものは、get_ui_data で。
  def get_ui_ctrl( tag )
    case tag
    when :text_specify_abstract; @panel_abstract.text_ctrl_file
    when :text_specify_log; @panel_log.text_ctrl_file
    when :text_view_abstract; @panel_abstract.text_ctrl_view
    when :text_view_log; @panel_log.text_ctrl_view
    else; nil
    end
  end
end

class MyPanel < Wx::Panel
  include XrcUtil
end

class MyPanelUrl < MyPanel
  def initialize( *args )
    super
    load_subclass( 'panel_url' )
    @text_ctrl_url = find_by_name( 'text_ctrl_url' )

    evt_button( find_by_name( 'btn_url_paste' ) ){ |evt|
      @text_ctrl_url.clear
      @text_ctrl_url.paste
    }
  end
  attr :text_ctrl_url
end

class MyPanelOutOrScan < MyPanel
  def initialize( *args )
    super
    load_subclass( 'panel_out_or_scan' )
    @text_ctrl_dir = find_by_name( 'text_ctrl_dir' )
    evt_button( find_by_name( 'btn_browse_dir' ) ) { |e| browse_dir( e ) }
    evt_button( find_by_name( 'btn_scan_only' ) ) { |e|
      $my_app.scan_web( e )
    }
  end
  attr :text_ctrl_dir

  def browse_dir( evt )
    dlg = Wx::DirDialog.new( self, 'Directory 選択', @text_ctrl_dir.get_value )
    if ( dlg.show_modal == Wx::ID_OK )
      @text_ctrl_dir.clear
      @text_ctrl_dir.set_value( dlg.get_path )
    end
  end
end

# note の abstract および log 表示ページ用の Panel
# 処理内容が殆ど共通なので、それぞれのページで異なる
# 部分のみを、ここから継承して記述を加える。
class MyPanelAbstractOrLogBase < MyPanel
  def initialize( *args )
    xrc = args.shift
    super( *args )
    load_subclass( xrc )
    @text_ctrl_file = find_by_name( 'text_ctrl_file' )
    @text_ctrl_view = find_by_name( 'text_ctrl_view' )
  end
  attr :text_ctrl_file, :text_ctrl_view
end

class MyPanelAbstract < MyPanelAbstractOrLogBase
  def initialize( *args )
    super( 'panel_abstract', *args )
  end
end

class MyPanelLog < MyPanelAbstractOrLogBase
  def initialize( *args )
    super( 'panel_log', *args )
  end
end

$my_app = MyApp.new
$my_app.main_loop

これはこれでよいとして、もう少し複雑な application になると、管理が難しいかも知れないと思い、別の方法も考えてみました。次回は、その別の方法で書いてみます。

P.S. 考えてみたら、$my_app をグローバル変数にして参照しなくても、Wx.get_app()  で参照すれぱ、ソースのどこからでも参照できますね。

2012年1月24日 (火)

ようやく1本書き上げました(7) - panel 内完結のイベント

Img_6_03_2

イベント処理の手始めとして、panel_url 内の [Paste] ボタンをクリックすると、クリップボードから文字列の貼り付けが行われるようにしてみます。

"Button の右クリック" の回なんかを参照して欲しいのですが、MyPanelUrl の中で、

evt_button(id) { |event| handler( event ) }

の形式でイベント処理を割り付けます。id の部分は、もともとの wxWidgets つまり c++ で実装するときには、個別の整数で widget を指定するのですが、wxRuby では、widget そのものを id の代わりに書いても、内部で処理系が対応してくれます。

今回は panel_url の中の、btn_url_paste をクリックすれば、text_ctrl_url に文字列を貼り付ければよいわけです。念の為、既に text_ctrl_url に入力されていた文字列があれば、それを消去してから貼り付けます。Wx::TextCtrl のドキュメント を参照すると、以下のような記述になります。

    evt_button( btn_url_paste ) ){ |evt|
      text_ctrl_url.clear
      text_ctrl_url.paste
    }

ところで、ここで使った 'btn_url_paste' と 'text_ctrl_url' は、もちろん画面に表示されている Wx::Button と Wx::TextCtrl の instance ですが、これらはソースコードのなかでは定義されていません。どうやって引っ張ってくればよいのでしょうか。

これらは XRC file の中で定義されているものなもので、XmlResource の仕組みを使って引っ張ってきます。例によって、XmlResource のドキュメント を参照して使うのですが、既に前回、このための準備を済ませています。

module XrcUtil
module_function
  (中略)

  # Wx::Window を継承した object で、xrc 内で定義されているものを
  # 'name' field の値で識別して返す。
  def find_by_name( name )
    Wx::Window.find_window_by_name( name, self )
  end

  (中略)
end

この XrcUtil.find_by_name() がそうです。XRC の中で定義した名前を使って、その widget (へのポインタ?) を引っ張ってくることができます。既に、全ての Panel 系の class は MyPanel を継承させており、MyPanel は XrcUtil を include しているので、以下のような記述でイベント処理を実装できます。このとき名前空間は、'panel_url' の中から探してくるので、たとえば panel_url と panel_out_or_scan の中で同じ名前の button が定義されていても、ちゃんと panle_url の中の button にイベントを割り付けられます。

class MyPanelUrl < MyPanel
  def initialize( *args )
    super
    load_subclass( 'panel_url' )
    @text_ctrl_url = find_by_name( 'text_ctrl_url' )


    evt_button( find_by_name( 'btn_url_paste' ) ){ |evt|
      @text_ctrl_url.clear
      @text_ctrl_url.paste
    }

  end
end

同様に、panel_out_or_scan 内の [Browse] ボタンにイベントを割り付けると、このようになります。

class MyPanelOutOrScan < MyPanel
  def initialize( *args )
    super
    load_subclass( 'panel_out_or_scan' )
    @text_ctrl_dir = find_by_name( 'text_ctrl_dir' )
    evt_button( find_by_name( 'btn_browse_dir' ) ) { |e| browse_dir( e ) }
  end

  def browse_dir( evt )
    dlg = Wx::DirDialog.new( self, 'Directory 選択', @text_ctrl_dir.get_value )
    if ( dlg.show_modal == Wx::ID_OK )
      @text_ctrl_dir.clear
      @text_ctrl_dir.set_value( dlg.get_path )
    end
  end
end

これらは、イベントを処理するのに必要な情報が panel の中で完結しています。ですから、MyPanelOutOrScan の instance method として実装するのが適当です。では、[出力せず Scan のみ] ボタンをクリックした場合の処理は、どこでどのように実装するべきなのでしょうか?

これを処理するには、panel_url の text_ctrl から url 文字列を、RadioBox から選択されている Journal を取得してきて、更にその結果を panel_abstract の text_ctrl 二つ、panel_log の text_ctrl 二つに出力する必要があります。

MyPanelOutOrScan 内の method から、各 panel にアクセスして情報をやり取りしてもよいのですが、たとえば同じ処理をメニューからも実行できるようにするかもしれず、そうなるとどこかアプリケーション全体から同じようにアクセスできるところに method (handler ?) を記述するべきと思われます。

次回は、この、全体から共用すべきと思われるイベントの処理を扱ってみます。

2012年1月21日 (土)

ようやく1本書き上げました(6) - wxFormBuilder を使う - 続・本編

XRC file を使うために、もうひとつ細工をします。リソースを取り込むときに、直接 Wx::XmlResource.get.load() とか、Wx::XmlResource.get.load_frame_subclass() とか書くのはちょっと面倒なので、以下のような Module を作ります。

# XmlResource を利用するための function
module XrcUtil
module_function
  # Wx::App で最初に xrc file を取り込む function.
  # この function を実行する source file からの相対 path で指定する。
  def load_resource_relative( xrc )
    Wx::XmlResource.get.load( File.dirname( __FILE__ ) + '/' + xrc )
  end

  # Wx::Window を継承した object で、xrc 内で定義されているものを
  # 'name' field の値で識別して返す。
  def find_by_name( name )
    Wx::Window.find_window_by_name( name, self )
  end

  # もともとの load_subclass method が、load する window の種類に
  # よって使い分けを要求されるため、自動判別するために wrap する。
  def load_subclass( name, parent = nil )
    case self
    when Wx::Dialog
      Wx::XmlResource.get.load_dialog_subclass( self, parent, name )
    when Wx::Frame
      Wx::XmlResource.get.load_frame_subclass( self, parent, name )
    when Wx::Wizard
      Wx::XmlResource.get.load_wizard_subclass( self, parent, name )
    when Wx::Panel
      Wx::XmlResource.get.load_panel_subclass( self, parent, name )
    end
  end
end

それぞれの働きは、コメントにある通りです。これを使って Panel 単位で作成した XRC リソースを使います。手始めに、url_panel と our_or_scan_panel だけを表示してみます。


GUI_06a.rb


# encoding: utf-8

require "wx"

# XmlResource を利用するための function
module XrcUtil
module_function
  # Wx::App で最初に xrc file を取り込む function.
  # この function を実行する source file からの相対 path で指定する。
  def load_resource_relative( xrc )
    Wx::XmlResource.get.load( File.dirname( __FILE__ ) + '/' + xrc )
  end

  # Wx::Window を継承した object で、xrc 内で定義されているものを
  # 'name' field の値で識別して返す。
  def find_by_name( name )
    Wx::Window.find_window_by_name( name, self )
  end

  # もともとの load_subclass method が、load する window の種類に
  # よって使い分けを要求されるため、自動判別するために wrap する。
  def load_subclass( name, parent = nil )
    case self
    when Wx::Dialog
      Wx::XmlResource.get.load_dialog_subclass( self, parent, name )
    when Wx::Frame
      Wx::XmlResource.get.load_frame_subclass( self, parent, name )
    when Wx::Wizard
      Wx::XmlResource.get.load_wizard_subclass( self, parent, name )
    when Wx::Panel
      Wx::XmlResource.get.load_panel_subclass( self, parent, name )
    end
  end
end

class MyApp < Wx::App
  def on_init
    XrcUtil.load_resource_relative( 'MyProject2.xrc' )
    @frame_main = FrameMain.new
    @frame_main.show
  end
end

class FrameMain < Wx::Frame
  def initialize
    super( nil, -1, $0 )
    sizer_top = Wx::BoxSizer.new( Wx::VERTICAL )
    set_sizer( sizer_top )

    sizer_top.add( @panel_url = MyPanelUrl.new( self ), 0, Wx::EXPAND )
    sizer_top.add( @panel_out_or_scan = MyPanelOutOrScan.new( self ), 0, Wx::EXPAND )
  end
end

class MyPanel < Wx::Panel
  include XrcUtil
end

class MyPanelUrl < MyPanel
  def initialize( *args )
    super
    load_subclass( 'panel_url' )
  end
end

class MyPanelOutOrScan < MyPanel
  def initialize( *args )
    super
    load_subclass( 'panel_out_or_scan' )
  end
end


$my_app = MyApp.new
$my_app.main_loop

実行するとこのような画面になります。

Img_6_01_3

ここに、Journal を選択する、RadioBox を追加します。

まず、FrameMain に code を追加します。

class FrameMain < Wx::Frame
  def initialize
    super( nil, -1, $0 )
    set_background_colour( Wx::Colour.new( 240, 240, 255 ) )
    sizer_top = Wx::BoxSizer.new( Wx::VERTICAL )
    set_sizer( sizer_top )

    sizer_top.add( @panel_url = MyPanelUrl.new( self ), 0, Wx::EXPAND )
    sizer_top.add( @rbox_select_journal = make_rbox, 0,
      Wx::LEFT | Wx::RIGHT | Wx::EXPAND, 5 )
    sizer_top.add( @panel_out_or_scan = MyPanelOutOrScan.new( self ), 0, Wx::EXPAND )
  end

  def make_rbox
    Wx::RadioBox.new( self, -1, 'Select Journal',
      :choices => Journals.map{ |j| j.name.encode(__ENCODING__) },
      :style => Wx::RA_SPECIFY_COLS, :major_dimension => 2
    )
  end
end

また、ソースの冒頭に以下を追加します。

SUBs = Dir.glob( File.dirname( __FILE__ ) + '/sub*.rb' )
SUBs.each{ |s| require_relative s }
Journals = SUBs.map{ |s| File.basename( s, '.*' ).upcase }.map{ |j| self.class.const_get(j).new }

実行すると、このような画面になります。目的であった、wxFormBuilder で作成したレイアウトと、ソースコードの中に記載したレイアウトが混在できています。

Img_6_02_2

このまま、一気に画面レイアウトを仕上げてしまいましょう。

FrameMain を更に編集します。

class FrameMain < Wx::Frame
  def initialize
    super( nil, -1, $0, :size => [450,500] )
    set_background_colour( Wx::Colour.new( 240, 240, 255 ) )
    sizer_top = Wx::BoxSizer.new( Wx::VERTICAL )
    set_sizer( sizer_top )

    sizer_top.add( @panel_url = MyPanelUrl.new( self ), 0, Wx::EXPAND )
    sizer_top.add( @rbox_select_journal = make_rbox, 0,
      Wx::LEFT | Wx::RIGHT | Wx::EXPAND, 5 )
    sizer_top.add( @panel_out_or_scan = MyPanelOutOrScan.new( self ), 0, Wx::EXPAND )
    sizer_top.add( make_view_note, 1, Wx::EXPAND )
  end

(中略)

  def make_view_note
    note = Wx::Notebook.new( self, -1 )
    note.add_page( @panel_abstract = MyPanelAbstract.new( note, -1 ), 'file 出力', true )
    note.add_page( @panel_log = MyPanelLog.new( note, -1 ), 'debug log', false )
    note
  end
end

initialize の super() で :size 属性を指定しています。これがないと、window サイズが小さすぎて、全てを表示てしてくれません。それぞれの widget に必要なサイズを問い合わせて、合計して画面サイズを割り出す方法もあるはずなのですが、面倒なので、直接指定しました。

更に、以下の class を追加します。今後、イベント処理などのコードを追加する都合で、共通の親 class から分岐するようにしています。

# note の abstract および log 表示ページ用の Panel
# 処理内容が殆ど共通なので、それぞれのページで異なる
# 部分のみを、ここから継承して記述を加える。
class MyPanelAbstractOrLogBase < MyPanel
  def initialize( *args )
    xrc = args.shift
    super( *args )
    load_subclass( xrc )
  end
end

class MyPanelAbstract < MyPanelAbstractOrLogBase
  def initialize( *args )
    super( 'panel_abstract', *args )
  end
end

class MyPanelLog < MyPanelAbstractOrLogBase
  def initialize( *args )
    super( 'panel_log', *args )
  end
end

実行すると、こうなります。

Img_6_03_2

ようやく画面レイアウトが実装できました。次からはイベント処理の割り付けを行うことになります。ここまでをまとめたソースコードは以下のようになります。


GUI_06b.rb


# encoding: utf-8

require "wx"

SUBs = Dir.glob( File.dirname( __FILE__ ) + '/sub*.rb' )
SUBs.each{ |s| require_relative s }
Journals = SUBs.map{ |s| File.basename( s, '.*' ).upcase }.map{ |j| self.class.const_get(j).new }

# XmlResource を利用するための function
module XrcUtil
module_function
  # Wx::App で最初に xrc file を取り込む function.
  # この function を実行する source file からの相対 path で指定する。
  def load_resource_relative( xrc )
    Wx::XmlResource.get.load( File.dirname( __FILE__ ) + '/' + xrc )
  end

  # Wx::Window を継承した object で、xrc 内で定義されているものを
  # 'name' field の値で識別して返す。
  def find_by_name( name )
    Wx::Window.find_window_by_name( name, self )
  end

  # もともとの load_subclass method が、load する window の種類に
  # よって使い分けを要求されるため、自動判別するために wrap する。
  def load_subclass( name, parent = nil )
    case self
    when Wx::Dialog
      Wx::XmlResource.get.load_dialog_subclass( self, parent, name )
    when Wx::Frame
      Wx::XmlResource.get.load_frame_subclass( self, parent, name )
    when Wx::Wizard
      Wx::XmlResource.get.load_wizard_subclass( self, parent, name )
    when Wx::Panel
      Wx::XmlResource.get.load_panel_subclass( self, parent, name )
    end
  end
end

class MyApp < Wx::App
  def on_init
    XrcUtil.load_resource_relative( 'MyProject2.xrc' )
    @frame_main = FrameMain.new
    @frame_main.show
  end
end

class FrameMain < Wx::Frame
  def initialize
    super( nil, -1, $0, :size => [450,500] )
    set_background_colour( Wx::Colour.new( 240, 240, 255 ) )
    sizer_top = Wx::BoxSizer.new( Wx::VERTICAL )
    set_sizer( sizer_top )

    sizer_top.add( @panel_url = MyPanelUrl.new( self ), 0, Wx::EXPAND )
    sizer_top.add( @rbox_select_journal = make_rbox, 0,
      Wx::LEFT | Wx::RIGHT | Wx::EXPAND, 5 )
    sizer_top.add( @panel_out_or_scan = MyPanelOutOrScan.new( self ), 0, Wx::EXPAND )
    sizer_top.add( make_view_note, 1, Wx::EXPAND )
  end

  def make_rbox
    Wx::RadioBox.new( self, -1, 'Select Journal',
      :choices => Journals.map{ |j| j.name.encode(__ENCODING__) },
      :style => Wx::RA_SPECIFY_COLS, :major_dimension => 2
    )
  end

  def make_view_note
    note = Wx::Notebook.new( self, -1 )
    note.add_page( @panel_abstract = MyPanelAbstract.new( note, -1 ), 'file 出力', true )
    note.add_page( @panel_log = MyPanelLog.new( note, -1 ), 'debug log', false )
    note
  end
end

class MyPanel < Wx::Panel
  include XrcUtil
end

class MyPanelUrl < MyPanel
  def initialize( *args )
    super
    load_subclass( 'panel_url' )
  end
end

class MyPanelOutOrScan < MyPanel
  def initialize( *args )
    super
    load_subclass( 'panel_out_or_scan' )
  end
end

# note の abstract および log 表示ページ用の Panel
# 処理内容が殆ど共通なので、それぞれのページで異なる
# 部分のみを、ここから継承して記述を加える。
class MyPanelAbstractOrLogBase < MyPanel
  def initialize( *args )
    xrc = args.shift
    super( *args )
    load_subclass( xrc )
  end
end

class MyPanelAbstract < MyPanelAbstractOrLogBase
  def initialize( *args )
    super( 'panel_abstract', *args )
  end
end

class MyPanelLog < MyPanelAbstractOrLogBase
  def initialize( *args )
    super( 'panel_log', *args )
  end
end

$my_app = MyApp.new
$my_app.main_loop

ようやく1本書き上げました(5) - wxFormBuilder を使う - 本編

今回のソース全体は、去年のうちに晒してしまうつもりだったのですが、結局年を越してしまいました。へたをすると一月も終わりそうです。筆が遅くて申し訳ない。

さて、前回保存した MyProject1.XRC を使って、url_part だけを表示するコードを書くと、以下のようになります。


GUI_05a.rb


# encoding: utf-8

require "wx"

class MyApp < Wx::App
  def on_init
    Wx::XmlResource.get.load( File.dirname( __FILE__ ) + '/' + 'MyProject1.xrc' )
    @frame_main = FrameMain.new
    @frame_main.show
  end
end

class FrameMain < Wx::Frame
  def initialize
    super( nil, -1, $0 )
    Wx::XmlResource.get.load_frame_subclass( self, nil, 'MyFrame1' )
  end
end

$my_app = MyApp.new
$my_app.main_loop

MyApp 内で Wx::XmlResource.get.load() を使ってリソースファイルを指定し、Frame 内で  Wx::XmlResource.get.load_frame_subclass() を使って、具体的なリソースを取り込みます。( "wxRubyでGUIプログラミング--XRCを使う " および、XmlResource のドキュメント を参照 )

これを実行すると、このような画面になります。

Img_5_01

さて、これを踏まえて、"ようやく1本書き上げました(3)" までで作った画面を、一度そっくりそのまま wxFormBuilder で作ろうとしてみました。

wxFormBuilder で project の top に作った Frame の下に、BoxSizer や RadioBox を順に並べていけば同じような画面ができるはずだったのですが、ひとつ障害になったのは、RadioBox が、一旦生成後にメンバーを追加できないことです。

wxFormBuilder で、画面要素として RadioBox を追加すると、選択肢のメンバーをプロパティシートで「固定条件として」指定する形になります。画面設計の段階で選択肢が固定できる場合は問題ないのですが、今回は、対象となる Journal が増えれば、下請けの script を追加すれば、動的に選択肢が増える実装を目指しています。これは wxFormBuilder では実現できないわけです。

この問題を回避するために、いくつか試行錯誤して、一応の解答を見つけました。要は RadioBox はソースコードの中に記述して、それ以外の widget のレイアウトを wxFormBuilder で作成すれば、それぞれのいいとこ取りができます。

具体的には、これまでは project の下に Frame を作成し、そこに直接画面の構成要素を追加していきましたが、これからは project の下に複数の Panel をぶら下げます。ひとつひとつの Panel が、たとえばこれまでの url_part や out_or_scan_part になるように仕上げていきます。そして、ソースコードに、Panel 単位で取り込んでいきます。

まずリソースファイルを作ります。wxFormBuilder の左の pane はこのようになります。

Img_5_02

それぞれの項目について、以下のようにプロパティを設定します。


MyProject2

  name : MyProject2, file : MyProject2, relative_path : on, code_generation : XRC

  panel_url

    name : panel_url

    sizer_url

      name : sizer_url, orient : wxHORIZONTAL

    text_url

      name : text_url, lebel :      url :, proportoin : 0, flag : wxALIGN_CENTER_VERTICAL, wxALL

    text_ctrl_url

      name : text_ctrl_url, proportion : 1, flag : wxALIGN_CENTER_VERTICAL, wxALL

    btn_url

      name : btn_url, label : Paste, proportion : 1, flag : wxALIGN_CENTER_VERTICAL, wxALL

  panel_out_or_scan

    name : panel_out_or_scan

    sizer_out_or_scan

      name : sizer_out_or_scan, orient : wxVERTICAL

      sizer_dir

        name : sizer_dir, orient : wxHORIZONTAL, proportion : 0, flag : wxEXPAND

        text_dir

          name : text_dir, label : 出力 dir :, proportion : 0, flag : wxALIGN_CENTER_VERTICAL, wxALL

        text_ctrl_dir

          name : text_ctrl_dir,  proportion : 1, flag : wxALIGN_CENTER_VERTICAL, wxALL

        btn_browse_dir

          name : btn_browse_dir, label : Browse, proportion : 0, wxALIGN_CENTER_VERTICAL, wxALL

      sizer_btn

        name : sizer_btn, orient :  wxHORIZONTAL, proportion : 1,  flag : wxEXPAND

        spacer

          proportion : 1, flag : wxEXPAND

        btn_out_default_file

          name : btn_out_default_file, label : Scan → Default file で出力, proportion : 0, flag : wxALIGN_CENTER_VERTICAL, wxALL

        spacer

          proportion : 1, flag : wxEXPAND

        btn_scan_only

          name : btn_scan_only, label : 出力せず Scan のみ, proportion : 0, flag : wxALIGN_CENTER_VERTICAL, wxALL

        spacer

          proportion : 1, flag : wxEXPAND

panel_abstract

    name : panel_abstract

    sizer_abstract

      name : sizer_abstract, orient : wxVERTICAL

      sizer_file

        name : sizer_file, orient : wxHORIZONTAL, proportion : 0, flag : wxEXPAND

        btn_out_file

          name : btn_out_file, label : file を出力, proportion : 0, flag : wxALIGN_CENTER_VERTICAL, wxALL

        text_file

          name : text_file, label :     file :, proportion : 0, flag : wxALIGN_CENTER_VERTICAL, wxALL

        text_ctrl_file

          name : txt_ctrl_file, proportion : 1, flag : wxALIGN_CENTER_VERTICAL, wxALL

        btn_file_browse

          name : btn_file_browse, label : Browse, proportion : 0, flag : wxALIGN_CENTER_VERTICAL, wxALL

      text_ctrl_view

        name : text_ctrl_view, style : wxTE_MULTILINE, proportion : 1, flag : wxEXPAND

    panel_log

      プロパティは、panel_abstract とほとんど同じなので、省略。


これまで出て来なかった、panel_abstract や panle_log といったものもありますが、今後使うときに説明します。

上記のような project を作成したら、file に save. 続いて、File メニューから Generate Code を実行して、'MyProject2.xrc' を、これまで作成した GUI_xx.rb とおなじdirectoryに保存してください。念の為、保存したものを以下から download できるようにしておきます。

「MyProject2.xrc」をダウンロード

とりあえず、今日はここまでです。

« 2011年12月 | トップページ | 2012年2月 »