JRubyでFilthyRichClients その2

描画をカスタマイズするときは、普通はpaintComponentをオーバーライドするんだけど、
その部品の子供や境界線にも影響を与えたいときはpaintをオーバーライドしてもいい。
swing部品を半透明するなどのように、部品全てに影響を与えたいときはpaintのオーバーライドが使えるみたい。
下のコードでは、paintをオーバーライドしてJButtonを半透明にする。一度、BufferdImageに本来の絵を描いてから、
AlphaCompositeで半透明を設定して、BufferdImageの絵を描画している。

include Java

include_class %w( JFrame JPanel JButton SwingUtilities ).map{|s| 'javax.swing.' + s}
include_class %w( AlphaComposite Color ).map{|s| 'java.awt.' + s}

class TranslucentButton < JButton

  def initialize(text)
    super(text)
    setOpaque(false)
    @btn_image = nil
  end

  def paint(g)
    if(@btn_image == nil ||
       @btn_image.getWidth != getWidth ||
       @btn_image.getHeight != getHeight)
      @btn_image = getGraphicsConfiguration().createCompatibleImage(getWidth, getHeight)
    end

    # super呼び出しで、@btn_imageにボタンの絵を描画する。
    g_btn = @btn_image.getGraphics
    g_btn.setClip(g.getClip)
    super(g_btn)
    g_btn.dispose
    
    # AlphaCompositeを半透明(0.5)に設定して@btn_imageをグラフィクスに描画する。
    g.setComposite(AlphaComposite.getInstance(AlphaComposite::SRC_OVER, 0.5))
    g.drawImage(@btn_image, 0, 0, nil)
  end
end

class CheckBoard < JPanel
  CHECK_SIZE = 60.0

  def paintComponent(g)
    # 背景を白で塗っておく
    g.setColor(Color.white)
    g.fillRect(0, 0, getWidth, getHeight)
    g.setColor(Color.black)

    #左上と右下のブロックを黒で塗る
    (getWidth / CHECK_SIZE).ceil.times do |i|
      (getHeight / CHECK_SIZE).ceil.times do |j|
        g.fillRect(CHECK_SIZE*i               , CHECK_SIZE*j               , CHECK_SIZE/2, CHECK_SIZE/2)
        g.fillRect(CHECK_SIZE*i + CHECK_SIZE/2, CHECK_SIZE*j + CHECK_SIZE/2, CHECK_SIZE/2, CHECK_SIZE/2)
      end
    end
  end
end

SwingUtilities.invokeLater do
  panel = CheckBoard.new
  panel.add(TranslucentButton.new("TranslucentButton"))
  f = JFrame.new("TranslucentButton")
  f.add(panel)
  f.setSize(300, 300)
  f.setVisible(true)
  f.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)
end

実行結果


重い処理のあつかい

重い処理をEDT上で行うと、画面がフリーズしてしまうので、EDT上では重い処理はやらない。
以下は良くない書き方。

include Java

include_class %w( JFrame JButton SwingUtilities ).map{|s| 'javax.swing.' + s}

class FreezeEDT < JFrame
  def initialize(title)
    super(title)
    freezer = JButton.new("Freeze")
    freezer.addActionListener do
      puts "重い処理  start"
      sleep 3
      puts "重い処理 finish"
    end
    add(freezer)
    pack
  end
end

SwingUtilities.invokeLater do
  FreezeEDT.new("FreezeEDT").setVisible(true)
end

実行結果


ボタンをクリックすると、addActionListenerに渡しているブロックが実行され、画面がフリーズする。



なので、

  • 重い処理をするときは、EDT上でやらないで、スレッドを生成して処理させる。
  • スレッドの中にSwing部品を更新するような処理を入れるときは、その処理をSwingUtilities.invokeLaterに渡す。

以下はこれをふまえたやり方。

include Java

include_class %w( JFrame JLabel JButton SwingUtilities SwingConstants ).map{|s| 'javax.swing.' + s}
include_class 'java.awt.BorderLayout'

class SwingThreading < JFrame
  def initialize(title)
    super(title)
    count = 0
    label = JLabel.new(count.to_s, SwingConstants::CENTER)
    add(label, BorderLayout::CENTER)
    
    btn = JButton.new("Increment")
    btn.addActionListener do
      Thread.start do
        sleep 3 # 重い処理
        count += 1
        SwingUtilities.invokeLater{ label.setText(count.to_s) }
      end
    end
    add(btn, BorderLayout::SOUTH)
    
    pack
  end
end

SwingUtilities.invokeLater do
  SwingThreading.new("SwingThreading").setVisible(true)
end

実行結果


画面がフリーズすることは無くなった。ただ、ボタンを連続で押すと以前の処理が終了していなくても、
新たに処理が追加されてしまうので、それが嫌ならチェックするコードも必要になる。