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