JDK1.2以降では標準APIとなっています。
メーリングリスト、雑誌記事等では「Swingは遅い」とよく語られているが、本当のところどうなのだろうか?
"私のFreeBSD環境では、・・・200MHz Pentium MMXではさほどストレスは感じませんでした。また、Windows95環境のNetscape上でも動作をさせてますが、こちらはPentium MMX200MHz+64MBという環境でも実用的と感じる速度では動作しませんでした。"
(引用:技術評論社JAVA PRESS誌Vol1、「JFCのすべて」田口毅著)
同じCPUパワーだとWindows95の方が重く、UNIXのX Window上の方が軽快に動くようです。世の中Windows95ユーザ数が多いので、必然的に遅いという声が大きくなるのかもしれません。
1998.4にリリースされたJDK1.1.6はパフォーマンス・チューンが進み、またJITコンパイラを標準装備しており、Swingの実行性能も前に比べて大幅に向上しています。それでももったりとした重さは残っていますが・・・。
1999.8にプレリリースされたJDK1.3(β)ではHotSpot Client VMが搭載され、Swingライブラリもチューニングが進み、かなり性能が向上しています。また、マシン性能も1998初頭のPentium II 333MHzから1999夏にはPentium III 600MHzと倍以上に速くなっていることもあり、体感性能は大幅に向上していると考えられます。
2001.8にプレリリースされたJDK1.4β2ではSwing廻りのパフォーマンス向上が行われています。マシン性能も、CPUクロックについてはPentium IV 2.0GHzと1999年中頃からは3倍強、PCのネックであったメモリバスの帯域もRDRAM/DDRの登場で大きくなっています。
3Dグラフィックス・アクセラレータの急速な性能向上・低価格化により、WindowsならDirect3D、UnixならOpenGLを用いた高速な描画処理環境が広まっています。3Dグラフィックス・アクセラレータの性能はこの5年で10倍の向上が実現されており、Swing(Java2D)においてもこのグラフィックス・アクセラレータを内部で利用してより描画の高速化を実現するようになっています。
Swingライブラリは、スレッドセーフではありません。単一スレッド(イベントディスパッチスレッド)からのみアクセス可能な設計となっています。ただし、JComponentのrepaint(), revalidate(), invalidate()は任意のスレッドから呼び出せます。通常は、イベントリスナインタフェースで定義されるイベントハンドラメソッド(例えばactionPerformed, propertyChanged, paint, update)からだけSwingコンポーネントにアクセスするようにプログラムを記述します。もし、どうしても別スレッドからSwingにアクセスするには、SwingUtilitiesクラスのinvokeLater(),またはinvokeAndWait()を使います。
GUIアプリケーションでは、プラットフォームが使用しているウィンドウシステム(UNIXならX Window)上に、何らかのウィンドウを作成する必要があります。このウィンドウはJavaではなくOSもしくはウィンドウシステムを呼び出して作成するため、ヘビーウェイトコンポーネントと呼ばれます。(したがって、ヘビーウェイト/ライトウェイトは、処理が速い/遅いという意味ではありません)
Swingでは、以下の4つのコンポーネントがヘビーウェイトに該当します。プログラムではまずこのいずれかのクラスをインスタンス化し、その上にライトウェイトなGUI部品を貼っていくことになります。
ここでは、JFrameを使ったGUIアプリケーションの基本パターンを見ます。
JFrameクラスを継承または合成の形で使用します。継承の方が簡単ですが、その分制約もあります。
public class MyApplication extends JFrame { : }
Swingクラスは、javax.swingパッケージとして提供されます。
JFrameインスタンスに、ウィンドウクローズ時の処理方法を設定します。ウィンドウシステム上でクローズボタンもしくはウィンドウメニュー上でクローズを選んだ時にアプリケーションを終了させるためです。
JFrame frame = new MyApplication();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setDefaultCloseOperationで指定可能な定数を以下に示します。これらの定数は、javax.swing.WindowConstantsインタフェースで定義されます。しかし、JFrameクラスがWindowConstantsインタフェースをimplementsしているので、ものの本などではJFrameの定数としてコーディングされることが多い(むしろこちらの方が主流)です。
定数 | 内容 | windowClosing呼出し | windowClosed呼出し |
---|---|---|---|
DISPOSE_ON_CLOSE | (JInternalFrameのデフォルト)ウィンドウを消去し、ウィンドウのリソースを破棄する。WindowListenerのwindowClosingとwindowClosedが呼ばれる。 | ||
DO_NOTHING_ON_CLOSE | 何もしない。WindowListenerのwindowClosingが呼ばれる。windowsClosedが呼ばれるには、windowClosingの中でdispose()を呼ぶ必要がある。 | ||
EXIT_ON_CLOSE | JVMを終了する。WindowListenerのwindowClosingが呼ばれる。 | ||
HIDE_ON_CLOSE | (JFrame/JDialogのデフォルト)ウィンドウの表示を消去するがウィンドウそのものは存続し再表示が可能。WindowListenerのwindowClosingが呼ばれる。 |
ボタン、ラベル、リストなどのSwingコンポーネント(ライトウェイト)をJFrameのようなヘビーウェイトに貼る際は、AWT1.1以前のように直接addメソッドを呼ばず、JFrameが持つcontentPaneを取得し、このcontentPaneに対してaddメソッドを呼び出す必要があります(1)。
frame.getContentPane().add(button);
SwingコンポーネントはJContainerクラス上に配置できますが、ヘビーウェイトなウィンドウ(JFrame,JWindow, JDialog, JApplet)はJContainerを継承していません。
そこでこれらのクラスは内部にSwingコンポーネントを配置するためのJContainerを継承しているcontentPaneを保持し、コンポーネントの管理・レイアウトを行うようになっています。※JInternalFrameはライトウェイトコンポーネントですが、同様にcontentPaneに管理を委ねています。
コンポーネントを配置後、必要最小限の大きさとします。
frame.pack();
また、サイズを明示的に指定することも可能です。
frame.setSize(800, 600);
画面上の表示位置を指定することも可能です。指定する座標は、画面上におけるJFrameウィンドウの左上隅の位置です。
frame.setLocation(100, 100);
サイズと位置の両方を一度に明示的に指定することも可能です。
frame.setBounds(800, 600, 100, 100);
位置を指定しない場合、画面左上に表示されますが、以下の指定により画面中央に表示させることができます。
frame.setLocationRelativeTo(null);
ヘビーウェイトなウィンドウを、ウィンドウシステム上で可視化(表示)します。
frame.setVisible(true);
JDK 1.5 (Java 5.0)以降用のコード例。
/** * MyApplication.java * for Java 2 SE, v.1.5 * * @author Toru Takahashi * @version $Revision: 1.3 $ */ import javax.swing.JFrame; import javax.swing.JButton; import javax.swing.SwingUtilities; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; public class MyApplication extends JFrame { public MyApplication() { super("MyApplication"); helloButton = new JButton("Hello, new swing!"); helloButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { System.out.println("Hello, Kestrel!"); } }); add(helloButton); } private static void createAndShowGui() { JFrame frame = new MyApplication(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGui(); } }); } private JButton helloButton= null; } // MyApplication
イベントディスパッチスレッド以外でSwingコンポーネントをアクセスするのはスレッド安全性上危険。mainメソッドでSwingを書かない訳を参照。
以下のコードは危険な例。
public static void main(String[] args) { JFrame frame = new MyApplication(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); }
あるウィンドウを表示している時、特にダイアログで、他のウィンドウへのユーザー操作をできないように操作をブロックしたいことがあります。この制御をモーダル制御といいます。JDK 6から、ダイアログのプロパティModalTypeに以下のモーダル種類を設定することができるようになっています(java.awt.Dialog.ModalityType列挙型)。コンストラクタの引数またはsetModalityTypeメソッドを使います。
種類 | 内容 | 指定 |
---|---|---|
モードレス | 他のウィンドウをブロックしない | ModalityType.MODELESS |
ドキュメント・モーダル | 同じドキュメント(トップレベル・ウィンドウからの親子階層)の親方向のウィンドウをブロックする | ModalityType.DOCUMENT_MODAL |
アプリケーション・モーダル | 同じアプリケーションのウィンドウをブロックする(親方向のウィンドウを除く) | ModalityType.APPLICATION_MODAL |
ツールキット・モーダル | 同じツールキットのウィンドウをブロックする(親方向のウィンドウを除く) | ModalityType.TOOLKIT_MODAL |
JDK 5までのモーダル指定は、アプリケーション・モーダルとして扱われます。
特定のウィンドウをモーダルによるブロックから除外したい場合、そのウィンドウのModalExclusionTypeプロパティに除外を設定します(java.awt.Dialog.ModalExclusionType列挙型)。設定には、java.awt.WindowクラスのsetModalExclusionTypeメソッドを使います。
画面上に表示する大きさよりも大きいテキスト、イメージ、リストなどを表示したいときに一般的にスクロール機能付きコンポーネントが使われます。SwingではJScrollPaneクラスが提供されています。
JButtonクラスを使ったボタンは、デフォルトではフォーカスを示す枠が表示されます。下記の画面例で、ボタンの文字"I'm a Swing button!"を囲んでいる薄紫色の枠です。
このフォーカス枠表示は、AbstractButtonクラスの次のメソッドで有効/無効とすることができます。
public void setFocusPainted(boolean b) b - true フォーカス枠表示有功 false フォーカス枠表示無効
また、フォーカスを持つコンポーネントをプログラム中から指定することもできます。JComponentクラスのメソッド、requestFocus()です。
Swingでは、描画を更新するときに表示がちらつかないようにダブルバッファリングを行う機能を保持しています。Swingのダブルバッファは画面全体をカバーする単一のオフスクリーンバッファによって実現され、RepaintManagerが管理する(2)。ダブルバッファリングは個々のコンポーネント毎には制御されません。コンポーネントの収まるコンテナ階層の親の1つでダブルバッファリングが行われる場合、コンポーネントはRepaintManagerのオフスクリーンバッファに描画されてからオンスクリーンにコピーされます。
JComponentにsetDoubleBuffered(boolean)というメソッドがあるので、個々のコンポーネント毎にダブルバッファの制御が行えると思ってしまうが、それは誤りです。デフォルトでは、JRootPaneとJPanelだけがダブルバッファリング有効となっています。他のコンポーネントは大抵の場合このどちらかのクラスのコンテナ階層にaddされるので、自動的にダブルバッファリングされます。(例えそのコンポーネント自身をsetDoubleBuffered()メソッドで無効にしたとしても)
ダブルバッファリングを無効にするには、
RepaintManager rm = RepaintManager.currentManager(component);
rm.setDoubleBufferingEnabled(false);
いろいろ実験していて妙な現象に出逢いました。ダブルバッファリングが有効な時は、コンポーネントのpaint()メソッドに渡されるGraphicsオブジェクトは、SwingのRepaintManagerが管理しているオフスクリーンバッファ(BufferedImageオブジェクト)のGraphicsオブジェクトです。これは前述のとおり画面全体で1つだけ存在するので、全てのコンポーネントがこのオフスクリーンバッファへ描画していることになります。
そのせいか、paint()で受け取ったGraphicsオブジェクトのうち一部分だけ書き変える処理をすると、画面に描画されるときに本来そこにはないコンポーネントが表示されたりすることがあります。
import java.awt.BorderLayout; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; import javax.swing.JPanel; import javax.swing.RepaintManager; /** * DoubleBufferTest.java * * * Created: Wed Nov 15 01:03:05 2000 * * @author * @version */ public class DoubleBufferTest extends JFrame { JButton button = new JButton("Push Me"); final TestPanel panel = new TestPanel("Hello, Swing world!"); public DoubleBufferTest() { super("Swing double buffer test"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { panel.setVisible(true); } }); getContentPane().add(button, BorderLayout.SOUTH); getContentPane().add(panel, BorderLayout.CENTER); } private static void createAndShowGui() { JFrame frame = new DoubleBufferTest(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setBounds(100, 100, 300, 160); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGui(); } }); } class TestPanel extends JPanel { String message = null; TestPanel(String aMessage) { RepaintManager.currentManager(this) .setDoubleBufferingEnabled(true); setOpaque(true); message = aMessage; setVisible(false); } public void paintComponent(Graphics g) { g.drawString(message, 100, 60); } } } // DoubleBufferTest
setDoubleBufferingEnabled | setOpaque | 表示 |
---|---|---|
true | true | ボタンの幻影が現れる |
true | false | ボタンの幻影が現れない |
false | true | ボタンの幻影が現れない |
false | false | ボタンの幻影が現れない |
paintComponentをオーバーライドするときに、super.paintComponent(g)を喚ぶように修正すると、幻影は現れなくなります。不透明なコンポーネント(setOpaque==true)は、paintComponentをオーバーライドするときには、必ずsuper.paintComponentを喚ぶようにするという必要があることが結論です。
"RepaintManager.getOffscreenBuffer not erasing reused offscreen buffer"という問題が、SunのBug Databaseに登録されています。なお、Closed, will not be fixedなので、これはこのままということになっています。
JFrame等に自前のJava2D描画コンポーネントを貼ったします。オーバーライドしたpaintComponent(Graphics g)に渡されるGraphicsオブジェクトがデフォルトで保持しているAffineTransformオブジェクトの内容は、直感的に予想される[0 1 0][1 0 0](恒等変換)ではないのです。
JFrame等の描画領域全体をスクリーン座標系として使用していると思われ、引数で渡されてくるGraphics2DオブジェクトからgetTransform()で取り出したAffineTransformは、JFrameの全体(RootPane?)の中で自前コンポーネントが位置する座標分の並行移動が設定されています。
public MyCanvas extends JComponent { ... public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; Logger.global.info(g2d.getTransform().toString()); ... g2d.dispose(); } ... }
上述でログ出力を見ると例えば以下のような出力が得られます。
情報: AffineTransform[[1.0, 0.0, 0.0], [0.0, 1.0, 23.0]] 2005/06/19 12:03:52 \ jp.gr.java_conf.torutk.exp.java2d.affine.AffinePanel \ paintComponent
このデフォルトのアフィン変換では、自前コンポーネントの左上隅(0, 0)が(0, 23)に座標変換されていることになります。ちなみに上記の例は、JFrameのcontentPaneでCENTERに自前コンポーネントを貼り、setJMenuBarメソッドでJFrameにメニューバーを設定した場合である。丁度JMenuBarのメニュー分だけずれています。したがって、以下のように渡されてきたアフィン変換設定を無効にするコーディングはやってはいけません。
public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; g2d.setTransform( AffineTransform.getRotateInstance(Math.toRadians(30)) ); ...
渡されてきたアフィン変換に結合させるようにアフィン変換を設定しなくてはなりません。
public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; g2d.transform( AffineTransform.getRotateInstance(Math.toRadians(30)) ); ...
AWTには、Canvasというコンポーネントがあり、グラフィックスを描画するのに定番となっていました。ところが、SwingになるとCanvasに相当するクラスが見当たらりません。どうしたらよいのでしょうか?
Swingでは、JPanelを継承してCanvas相当のクラスとして使うのが簡単です。グラフィックス描画処理が、AWTのCanvasと違ってpaintメソッドではなく、paintComponentメソッドとなっている点に注意します。また、不透明(opaqueがtrue)であるコンポーネントの場合、paintComponentメソッドの中でまずsuper.paintComponent(g);を喚びだして背景描画を行った後、アプリケーションの描画処理を記述します。
Emacs JDEを開発に使用しているならば、FileメニューのJDE New→Other...を選び、ミニバッファでTemplate名を聞いてくるのでSwing Appと入力すると、Swingプログラムの雛型が生成されます。この雛型には、JPanelを継承したCanvasクラスが含まれているので参考になります。
package myswing; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Color; import java.awt.geom.Ellipse2D; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JMenuBar; import javax.swing.JMenu; import java.awt.event.ActionEvent; import javax.swing.AbstractAction; /** * MySwingApp.java * * * Created: Wed Mar 07 03:09:40 2001 * * @author * @version */ public class MySwingApp extends JFrame { class Canvas extends JPanel { public Canvas() { setSize(getPreferredSize()); Canvas.this.setBackground(Color.white); } public Dimension getPreferredSize() { return new Dimension(600, 600); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; Ellipse2D circle = new Ellipse2D.Double(0d, 0d, 100d, 100d); g2d.setColor(Color.red); g2d.translate(10, 10); g2d.draw(circle); g2d.fill(circle); } } public MySwingApp() { super("Swing Application generate by JDE"); setSize(300, 300); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} public void windowOpened(WindowEvent e) {} }); setJMenuBar(createMenu()); getContentPane().add(new JScrollPane(new Canvas())); } public static void main(String[] args) { MySwingApp f = new MySwingApp(); f.show(); } protected JMenuBar createMenu() { JMenuBar mb = new JMenuBar(); JMenu menu = new JMenu("File"); menu.add(new AbstractAction("Exit") { public void actionPerformed(ActionEvent e) { System.exit(0); } }); mb.add(menu); return mb; } } // MySwingApp
Windows等のGUIデスクトップでは、ウィンドウは階層的に重なり合っています。また、地図上にシンボルを表示するような場合、地図表示とシンボル表示を別々に扱うことができると便利であるし、再利用性が高くなります。このような表示には、JLayeredPaneを使うと便利です。
JLayeredPaneは、レイアウトマネージャが存在しない(null)ため、JLayeredPaneに登録するコンポーネントは明示的に大きさと位置を与えておかなければなりません。
JLayeredPaneオブジェクトにコンポーネントを追加するときは、add(Component c, Integer layer)メソッドでレイヤー番号をIntegerオブジェクトで指定します。このとき、Integer型ではなくint型でコーディングしてしまっても、コンパイルエラーとはならないので、注意が必要です。うっかりミスの範疇ですが、よく陥るので心されたし。
Swingのルックアンドフィールは動的に入れ換えができるような設計となっています。ルックアンドフィールは、GUI部品(ボタン、ウィンドウ枠、メニュー、ツリーなど)の見栄え、見た目の振舞いのことです。
JDKにはプラットフォームによって多少の差異があります。以下、OSとjavax.swing.UIManagerで取得したLookAndFeelを示します。
LookAndFeel種類 | Windows Vista | Solaris 10 x86 | Linux(Cent OS 4.4) |
---|---|---|---|
Metal | V | V | V |
Nimbus | V | V | V |
CDE/Motif | V | V | V |
GTK+ | - | V | V |
Windows | V | - | - |
Windows Classic | V | - | - |
Metalは、Java独自のルックアンドフィールを定義したもので、デフォルトのルックアンドフィールとなっています。また、テーマ(Theme)を設定することが可能で、使用するカラーセット、フォントをカスタマイズすることができるようになっています。
Nimubsは、JDK 6 Update5から追加されたJava独自のルックアンドフィールを定義したもので、Metalに比べて洗練されたデザインとなっています。
CDE/Motifは、X Window System上で採用されているMotifツールキットが提供するルックアンドフィールに似せたものです。CDEは、Common Desktop Environmentの略で、Motifをベースに幾つかのUNIXベンダー(IBM, HP, Sun)がデクストップ環境を統一した規格です。
GTK+は、X Window System上のGNOME等で使用されているツールキットが提供するルックアンドフィールに似せたものです。
Windowsは、MicrosoftのWindows XPに使用しているルックアンドフィールです。ClassicはWindows 2000スタイルです。
JDKに標準で添付されているデモプログラム(SwingSet2)を実行してみると、詳しい違いを見てとれますので、ぜひ試してください。
ルックアンドフィールを指定するには、javax.swing.UIManagerクラスを知る必要があります。
実行しているJava環境が提供しているルックアンドフィールの一覧を次のように取得します。下記は、JDEEに標準添付のBeanShellを使ってJavaクラスにアクセスした例です。
BeanShell 1.1a16 - by Pat Niemeyer (pat@pat.net) bsh % lafs = UIManager.getInstalledLookAndFeels(); bsh % print(lafs); Array: [Ljavax.swing.UIManager$LookAndFeelInfo;@329f3d { javax.swing.UIManager$LookAndFeelInfo[Metal javax.swing.plaf. metal.MetalLookAndFeel] javax.swing.UIManager$LookAndFeelInfo[CDE/Motif com.sun.java. swing.plaf.motif.MotifLookAndFeel] javax.swing.UIManager$LookAndFeelInfo[Windows com.sun.java. swing.plaf.windows.WindowsLookAndFeel] } bsh %
ここで得られる情報は、UIManagerクラスのインナークラスLookAndFeelInfo型の配列であり、さらにgetClassNameメソッドでクラス名文字列を得ています。
javax.swing.UIManagerクラスには目的に応じた種類のルックアンドフィールを取得するメソッドが用意されています。
前者は、Java独自のルックアンドフィールであるMetalを提供するクラス名を返却するメソッドであり、後者はプログラムが実行されているマシン(プラットフォーム)に近いルックアンドフィールを提供するクラス名を返却するメソッドです。
BeanShell 1.1a16 - by Pat Niemeyer (pat@pat.net) bsh % lafName = UIManager.getCrossPlatformLookAndFeelClassName(); bsh % print(lafName); javax.swing.plaf.metal.MetalLookAndFeel bsh % lafName = UIManager.getSystemLookAndFeelClassName(); bsh % print(lafName); com.sun.java.swing.plaf.windows.WindowsLookAndFeel bsh %
BeanShell 1.1a16 - by Pat Niemeyer (pat@pat.net) bsh % lafName = UIManager.getCrossPlatformLookAndFeelClassName(); bsh % print(lafName); javax.swing.plaf.metal.MetalLookAndFeel bsh % lafName = UIManager.getSystemLookAndFeelClassName(); bsh % print(lafName); javax.swing.plaf.metal.MetalLookAndFeel bsh %
Linux(GNOME)上で実行すると、SystemLookAndFeelはMetalになっているのがわかります。Solarisで実行した場合、CDE/Motifになっていました。この方法を使うと、どのプラットホーム上で実行されているかを事前に決め打ちせずに、実行時に指定することができます。
では、上述の方法で取得したルックアンドフィールへ切り換えてみます。
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
SwingUtilities.updateComponentTreeUI(frame);
ルックアンドフィールの切替は、javax.swing.UIManagerクラスを使用します。setLookAndFeel()メソッドで、指定するルックアンドフィールクラスのクラス名を引数に渡すか、あるいはLookAndFeelクラス自身を渡します。それから、画面に表示されている各GUI部品を切替えたルックアンドフィールで描画し直すために、javax.swing.SwingUtilitiesクラスのupdateComponentTreeUI()メソッドを呼びます。このとき引数には、UI部品の階層上最も上位のオブジェクト(例えばJFrameオブジェクト)を渡します。
/** * LookAndFeelTest.java * * Created: Sun Oct 07 22:51:09 2001 * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version */ import java.awt.BorderLayout; import java.awt.event.ActionEvent; import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.UIManager; public class LookAndFeelTest extends JFrame { /** Look And Feel情報を保持する */ UIManager.LookAndFeelInfo[] lookAndFeelInfos; // デモ用にいくつかのUIコンポーネントを貼り付ける JButton button = new JButton("Push Me!"); JLabel label = new JLabel("Swing Look And Feel Selecting Demo"); JCheckBox checkBox = new JCheckBox("Check Me!"); JTextField textField; public LookAndFeelTest() { super("Look & Feel Test"); setDefaultCloseOperation(EXIT_ON_CLOSE); lookAndFeelInfos = UIManager.getInstalledLookAndFeels(); setJMenuBar(createMenu()); getContentPane().add(label, BorderLayout.NORTH); getContentPane().add(button, BorderLayout.SOUTH); getContentPane().add(checkBox, BorderLayout.WEST); textField = new JTextField( UIManager.getLookAndFeel().getDescription()); getContentPane().add(textField, BorderLayout.CENTER); } /** * Look&Feel切替を行うメニュー作成 * 実行中のJava環境が持つLookAndFeel一覧を取得し、 * それをメニューに加える。 */ public JMenuBar createMenu() { JMenuBar menuBar = new JMenuBar(); JMenu lafMenu = new JMenu("LookAndFeel"); for (int i=0; i<lookAndFeelInfos.length; i++) { final String lafClassName = lookAndFeelInfos[i].getClassName(); lafMenu.add(new AbstractAction(lookAndFeelInfos[i].getName()) { public void actionPerformed(ActionEvent ev) { changeLookAndFeel(lafClassName); } }); } menuBar.add(lafMenu); return menuBar; } /** * ルックアンドフィールを切替るメソッド。 * 引数に指定された文字列のルックアンドフィールクラスをUIManagerに * 登録し、画面を描画し直す。 */ private void changeLookAndFeel(String lafClassName) { try { UIManager.setLookAndFeel(lafClassName); SwingUtilities.updateComponentTreeUI(this); textField.setText(UIManager.getLookAndFeel().getDescription()); this.pack(); } catch (Exception e) { e.printStackTrace(); } } private static void createAndShowGui() { JFrame window = new LookAndFeelTest(); window.pack(); window.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGui(); } }); } } // LookAndFeelTest
前節では、プログラム上でルックアンドフィールを切り換える方法を示します。それ以外にも、システム・プロパティに設定することでルックアンドフィールを指定する方法があります。
起動時に、デフォルトのLookAndFeelを指定するには、swing.propertiesファイルを作成し、そこに記述します。ファイルは、Java Runtime Environmentディレクトリ下のlibディレクトリに置きます(フォント関係のプロパティファイルがある場所です)。例えばJDK1.4をC:\jdk1.4にインストールした場合、C:\jdk1.4\jre\libの下となります。
# Swing properties swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel
classes> java \ -Dswing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel \ alpha.AppMain
Metal Look&Feelでは、使用するカラーセット(背景色、表示色など)やフォントをカスタマイズすることができます。デフォルトで適用されるテーマであるjavax.swing.plaf.metal.DefaultMetalThemeでは以下のような設定となっています。
設定項目 | デフォルト |
---|---|
コントロールテキストフォント | コントロールまたはDialg-BOLD 12ポイント |
メニューテキストフォント | コントロールまたはDialg-BOLD 12ポイント |
サブテキストフォント | スモールまたはDialog-PLAIN 10ポイント |
システムテキストフォント | システムまたはDialog-PLAIN 12ポイント |
ユーザテキストフォント | ユーザまたはDialog-PLAIN 12ポイント |
ウィンドウタイトルフォント | コントロールまたはDialg-BOLD 12ポイント |
プライマリカラー1 | #666699 |
プライマリカラー2 | #9999CC |
プライマリカラー3 | #CCCCFF |
セカンダリカラー1 | #666666 |
セカンダリカラー2 | #999999 |
セカンダリカラー3 | #CCCCCC |
自分好みのテーマを記述するには、テーマを記述したMetalThemeクラスのサブクラスを作成します。設定を楽にするには、DefaultMetalThemeクラスのサブクラスを作成し、変更したい項目だけオーバーライドするとよいでしょう。
public class MyTheme extends DefaultMetalTheme { public String getName() { return "Mine"; } private final ColorUIResource primary1 = new ColorUIResource(102, 102, 153); private final ColorUIResource primary2 = new ColorUIResource(128, 128, 192); private final ColorUIResource primary3 = new ColorUIResource(159, 159, 235); private FontUIResource controlFont = new FontUIResource("Dialog", Font.PLAIN, 18); private FontUIResource systemFont = new FontUIResource("Dialog", Font.PLAIN, 18); private FontUIResource userFont = new FontUIResource("Dialog", Font.PLAIN, 18); private FontUIResource smallFont = new FontUIResource("Dialog", Font.PLAIN, 14); protected ColorUIResource getPrimary1() { return primary1; } protected ColorUIResource getPrimary2() { return primary2; } protected ColorUIResource getPrimary3() { return primary3; } public FontUIResource getControlTextFont() { return controlFont; } public FontUIResource getSystemTextFont() { return systemFont; } public FontUIResource getUserTextFont() { return userFont; } public FontUIResource getMenuTextFont() { return controlFont; } public FontUIResource getWindowTitleFont() { return controlFont; } public FontUIResource getSubTextFont() { return smallFont; } }
そして、GUIを具現化する前に、Themeを設定します。
MetalLookAndFeel.setCurrentTheme(new MyTheme());
もし、GUIが具現化された後にThemeを変更するには、UIManager.setLookAndFeel()とSwingUtilities.updateComponentTreeUI()あたりを使って通知してあげなくてはならないようです。
Swingにおいて画像ファイルを読み書きする方法にはいくつかの方法があります。
画像を表示する(読み込みだけ)ならば、SwingのコンポーネントにHTML形式テキストを与えて、HTMLレンダリング機能を使って画像を表示させることができます。
例えば、JLabelに通常のテキストの代わりにHTMLテキストを与えると、そのHTMLテキストを表示するのではなく、HTMLテキストに従ってレンダリングした結果を表示します。画像ファイルをJLabelを使って表示するなら、下記のようになります。
JLabel imageLabel = new JLabel("<html><img src=\"http://somewhere/sample.jpeg\">");
ここで、JLabelのHTMLレンダリング機能を利用してファイル選択ダイアログで選択した画像ファイルを表示するプログラム例を紹介します。
/** * ImageViewer.java * * Created: Sun Mar 17 16:09:56 2002 * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version */ import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.io.File; import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; /** * 画像ファイルを表示するビューア */ public class ImageViewer extends JFrame { JLabel imageLabel; // 画像を表示するラベル public ImageViewer(String title) { super(title); imageLabel = new JLabel(); getContentPane().add(imageLabel, BorderLayout.CENTER); // 画像ファイル選択ダイアログを起動するボタン JButton openButton = new JButton("Open File"); openButton.addActionListener(new OpenAction()); getContentPane().add(openButton, BorderLayout.SOUTH); } /** * 選択された画像ファイルを表示する */ void readFromFile(File file) { String text = "<html><body><img src=\"file:" + file.getPath() + "\"></body>"; imageLabel.setText(text); pack(); } public static void main(String[] args) { JFrame f = new ImageViewer("Image Viewer"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.pack(); f.setVisible(true); } /** * ファイル選択ダイアログを表示する。 * ファイルが選択されたらreadFromFileメソッドを起動する */ class OpenAction extends AbstractAction { JFileChooser imageChooser = new JFileChooser(); OpenAction() { imageChooser.setMultiSelectionEnabled(false); } public void actionPerformed(ActionEvent ev) { int ret = imageChooser.showDialog(ImageViewer.this, "Select Image File"); if (ret == JFileChooser.APPROVE_OPTION) { readFromFile(imageChooser.getSelectedFile()); } } } } // ImageViewer
読み込んだ画像に加工を行いたい場合や、画像を別なファイルに書き込む場合は、HTMLレンダリング機能では不十分です。このときは、画像処理を行うために画像ファイルからImageオブジェクトへ読み込んだり、Imageオブジェクトから指定したフォーマットで画像ファイルに書き出したりするAPIを使用します。
Image I/Oを使用する(Java 2 SE, ver.1.4以降)
GIF(read only)、PNG、JPEG形式に対応
Java Advanced Imaging API
BMP、GIF、FPX、JPEG、PNG、PNM、TIFFの各形式に対応
Java 2 Standard Edition標準の画像I/O用APIです。対応フォーマットは少ないですが、プラグイン対応になっているため、今後サードパーティ等から多種のフォーマット用プラグインが登場すると思われます。
import javax.imageio.ImageIO; : File inFile = new File("sample.gif"); File outFile = new File("sample.png"); BufferedImage image = ImageIO.read(inFile); ImageIO.write(image, "PNG", outFile);
入力ファイルの形式はファイル名の拡張子ではなくちゃんとデータフォーマットを見て判断します。出力ファイルのフォーマットを指定する文字列には以下のものが指定可能です。このリストはjavax.imageio.ImageIO#getImageWriterFormatNamesメソッドで取り出すことができます。
JIMIを使うと、多種の画像フォーマットのファイルを簡単に読み書きできます。最も簡単なAPIは、com.sun.jimi.coreパッケージのJimiクラスが持つgetImageおよびputImageメソッドを使用するものです。引数に指定したファイル名の文字列の拡張子部分によって画像フォーマットを識別し、java.awt.Imageオブジェクトとファイルとの変換を行います。
import com.sun.jimi.core.Jimi; : String inFileName = "sample.jpeg"; String outFileName = "sample.tiff"; Image image = Jimi.getImage(inFileName); Jimi.putImage(image, outFileName);
Swingには、コンポーネントを配置する際に絶対座標ではなく、一定のルールに沿って配置するレイアウトを使用するのが一般的である。画面サイズやフォントは可変であり、その変更に柔軟に対応することができる。
種類 | 内容 | 備考 |
---|---|---|
FlowLayout | ||
CardLayout | ||
BorderLayout | ||
GridLayout | ||
GridBagLayout | ||
BoxLayout | ||
OverlayLayout | ||
SpringLayout | J2SE 5.0(Tiger)から追加 | |
GroupLayout | JavaSE 6(Mustang)から追加 |
HelloAgainプロジェクト Desktop編のアプリケーションで使用しており、レイアウト・マネージャ節に使用例を解説している。
Swing Application Framework(JSR-296)の解説ページで作成したサンプル・プログラムのひとつで使用しており、「GroupLayoutを用いた画面レイアウト」節に使用例を解説している。
ツリー構造のデータを表現するには、JTreeコンポーネントを使用すると便利です。身近なツリー構造のデータと言えば、ファイルシステムがそうです。JTreeコンポーネントに表示するデータにはいくつかの種類があります。簡単なものなら配列やVector型のデータを与えてもよいですが、複雑なデータの場合はインタフェースTreeModelまたはTreeNodeを実装するクラスを作成してJTreeに与えることになるでしょう。
Java 2 SE 5.0までのSwingライブラリのJTableにはある列をキーにソートする機能は含まれておりません。しかしながら、ソートはニーズが高い機能です。Java SE 6からは、javax.swing.table.TableRowSorterクラスが追加され、JTableをソートする機能が装備されるようになりました。
Java 2 SE 5.0までのJTableでソート機能を付加する方法については、SunのTutorial文書で紹介されています。
http://java.sun.com/docs/books/tutorial/uiswing/components/table.html#sorting
JTableは、Model-View分離構造で設計されています。Modelは、javax.swing.table.TableModelインタフェースを実装するクラス、Viewはjavax.swing.JTableクラスです。ソート機能を汎用的に付加する方法の一つとして、ModelとViewの間にソート機能を持つTableSorterクラスを挟んであげる方法が上記チュートリアルで紹介されている方法です。このTableSorterクラス自身もTableModelであり、これはデザインパターンのひとつデコレータパターンを適用した方法です。ソースコードTableSorter.javaは、Sunの上記チュートリアルからダウンロードできます。
TableModel model = new MyTableModel(); TableSorter sorter = new TableSorter(model); JTable table = new JTable(sorter); sorter.setTableHeader(table.getTableHeader());
AbstractTableModelを派生してTableModel実装を定義した際に、数値の列をソートすると、数値の大小ではなく、文字列としてソートされてしまいます。例えば、100より20の方が大きいと判断されます。これは、javax.swing.table.AbstractTableModelクラスの"Class getColumnClass(int columnIndex)"メソッドの実装が、常にObject.classを返却するためです。結果的に、ソート時に、toStringで取得した文字列を比較することになります。そこで、TableModel実装時に、このgetColumnClassを列に応じたクラスオブジェクトを返却するように定義します。次に、TableSorterクラスの"void setColumnComparator(Class, Comparator)"メソッドを呼んで、クラスオブジェクトとそれに対応する比較オブジェクトを設定します。
sorter.setColumnComparator(Integer.class, new Comparator() { public int compare(Object o1, Object o2) { return ((Integer)o1).compareTo((Integer)o2); } });
Keith R Bennett氏の記事"JTables with Class - MVC, Renderers, and the TableSorter"において、TableSorterを改良したNewTableSorterの例を示し、またそのソースコードNewTableSorter.javaが公開されています。
編集中のJTextFieldについて、背景色を編集中であることが一目瞭然となるように変更します。また、[Enter]キーを押さなくてもフォーカスが別のGUIコンポーネントに移った時点でActionイベントを発生させるようにします。
ここでは、以上の機能を追加したJTextField拡張クラスを作成します。
まず、編集中の状態は、JTextFieldにフォーカスが当たっている状態とします。フォーカスの状態は、FocusListenerをJTextFieldに登録することによって知ることができます。
private class TextFieldFocusListener implements FocusListener { /** * フォーカスが当たったときは編集中として背景色を変更する。 * @param anEvent <code>FocusEvent</code>オブジェクト */ public final void focusGained(final FocusEvent anEvent) { setBackground(editBackground); } /** * フォーカスを失ったときは編集完了として背景色を本来に戻し、 * テキスト入力完了イベント([Enter]キー入力状態)を発生させる。 * * @param anEvent <code>FocusEvent</code>オブジェクト */ public final void focusLost(final FocusEvent anEvent) { Color background = (Color)UIManager.get("TextField.background"); setBackground(background); postActionEvent(); } }
フォーカスが外れたときに、背景色を元の色に戻します。元の色はUIManagerから取得しています。あるいは、フォーカスが当たったときに(focusGainedメソッドで)JTextFieldからgetBackgroundメソッドで元の背景色を取得する方法もあります。
private final void init() { addFocusListener(new TextFieldFocusListener()); editBackground = Color.PINK; // デフォルトの編集中背景色 addActionListener(new ActionListener() { public void actionPerformed(ActionEvent anEvent) { transferFocus(); } } ); }
TextFieldの初期化時に、上述のFocusListener実装を登録するとともに、編集完了時に背景色を元に戻すため、ActionListenerでフォーカスを次のGUIコンポーネントへ移す処理を入れています。
Swingはシングルスレッドモデルです。したがって、Swingのイベントからディスパッチされたら速やかに処理を終えてスレッドをSwingに戻してあげなくてはなりません。どうしても、時間のかかる処理を行う必要がある場合には、別にスレッドを設けてそのスレッドに処理を行わせます。別スレッドからSwingを呼び出すときは、invokeLater()などを使ってEDT上で実行するようにします。
なお、JDK 6からはSwingWorkerクラスが追加され、別スレッドでの処理実行と経過および結果をSwingへ反映するのに便利な機能が利用できます。
ファイル選択ダイアログです。アプリケーション内で、ユーザーにディレクトリやファイルを指定してもらう場合に使用します。
デフォルトのファイル選択ダイアログの使い方です。次のサンプルコードは、ファイルを開く用途でファイル選択ダイアログを表示します。
import javax.swing.JFileChooser; : JFileChooser fileChooser = new JFileChooser(); int ret = fileChooser.showOpenDialog(frame); if (ret == JFileChooser.APPROVE_OPTION) { File file = fileChooser.getSelectedFile(); ... }
ファイルを保存するときは、上述のコードで呼び出すメソッド"showOpenDialog"を"showSaveDialog"に変更します。
この使用方法では、ユーザーのホームディレクトリ(Windowsならユーザーのマイドキュメント)を初期ディレクトリとし、可視なファイル・ディレクトリをすべて表示し、選択可能なのはファイルで、同時選択可能数は1つです。
ユーザがウィンドウの大きさを変更したことを感知して処理をしたい場合、java.awt.event.ComponentListenerを使う。componentResized()メソッドが呼ばれる。
JFrame f = new JFrame("ResizeAnnounce"); f.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent ev) { // リサイズされた時の処理 } });
フォントを変更したい場合、コンポーネントのsetFontメソッドを呼んで個別に変更することができます。しかし、画面全体のフォントを変更することは大変です。
Metal Look&Feelでは、次の方法で全体を変更することができます。
Metal Lookl&Feelでは、以下の4種類の用途に使用するフォントがシステムプロパティで定義されています。
種類 | プロパティ名 | デフォルト | 用途 |
---|---|---|---|
コントロール | swing.plaf.metal.controlFont | 12ptボールド | チェックボックス、タイトル |
スモール | swing.plaf.metal.smallFont | 10ptプレーン | ツールティップ、キーボードショートカット |
システム | swing.plaf.metal.systemFont | 12ptプレーン | ツリービュー |
ユーザ | swing.plaf.metal.userFont | 12ptプレーン | テキストフィールド、テーブル |
システムプロパティに、プロパティ名と指定したいフォントを設定することができます。フォントの指定方法は以下のとおりです。
フォント名
例) Dialog、Ariel、Times New Roman
フォント名-スタイル
例) Dialog-BOLD、Ariel-ITALIC、Times New Roman-BOLDITALIC (スタイルを指定しない場合はPLAIN)
フォント名-サイズ
例) Dialog-13、 Ariel-10、 Times New Roman-17
フォント名-スタイル-サイズ
例) Dialog-BOLD-18、Ariel-ITALIC-14、Times New Roman-BOLDITALIC-22
classes> java -Dswing.plaf.metal.controlFont=Dialog-14 \ alpha.AppMain
多言語環境のプログラムを作成する場合、日本語ロケールでは日本語以外の言語の文字が□(俗称トーフ)で表示されてしまいます。ここでは、日本語と韓国語の場合について試してみました。
日本語Windows 2000では、主ロケールの日本語以外に複数の言語環境を扱うことができます。ここでは韓国語の例を見てみます。まず、コントロールパネルの「地域のオプション」からシステムの言語設定のリストの韓国語にチェックを入れます。するとOSのメディアからフォント等をインストールします。また、韓国語を入力するために、韓国語IMEを同様に「地域のオプション」の[入力ロケール]タブにある[追加]ボタンを押し、入力ロケールとキーボードレイアウト/入力システムの双方に韓国語を選択して追加します。
まず、ワードパッド上に日本語、韓国語混じりの文字列を用意します。
この文字列をWindowsのクリップボードへコピーし、Swingで作ったJavaプログラム上のテキストフィールドに貼り付けます。
これは、Javaプログラム(Swingライブラリ)の中で使用している論理フォント(Dialog等)を、実際のフォントにマッピングする設定が不足しているためです。日本語ロケールでJavaを実行する場合、[Java Runtime Directory]/lib/font.properties.jaというファイルに記述される設定が使用されます。そこで、ここに、韓国語の設定を追加することにします。
例えば、日本語用フォント・プロパティ・ファイルの一部を見ると次のようになっています。
dialog.plain.0=Arial,ANSI_CHARSET dialog.plain.1=\uff2d\uff33 \u30b4\u30b7\u30c3\u30af,SHIFTJIS_CHARSET dialog.plain.2=WingDings,SYMBOL_CHARSET dialog.plain.3=Symbol,SYMBOL_CHARSET [中略] filename.\uff2d\uff33_\u660e\u671d=MSMINCHO.TTC filename.\uff2d\uff33_\u30b4\u30b7\u30c3\u30af=MSGOTHIC.TTC filename.Arial=ARIAL.TTF [中略] filename.WingDings=WINGDING.TTF filename.Symbol=SYMBOL.TTF [中略] fontcharset.dialog.1=sun.io.CharToByteMS932 fontcharset.dialog.2=sun.awt.windows.CharToByteWingDings fontcharset.dialog.3=sun.awt.CharToByteSymbol
Javaプログラム中で論理フォントdialog(.plain)が指定されていると、Arial,MSゴシック、WingDings、Symbolの順に該当する文字のフォントを調べていきます。ここで韓国語の文字に対応するフォントが見つからないため、□(トーフ)が表示されてしまいます。そこで、このフォントリストに韓国語のフォントを追加します。
dialog.plain.0=Arial,ANSI_CHARSET dialog.plain.1=\uff2d\uff33 \u30b4\u30b7\u30c3\u30af,SHIFTJIS_CHARSET dialog.plain.2=WingDings,SYMBOL_CHARSET dialog.plain.3=Symbol,SYMBOL_CHARSET dialog.plain.4=\uad74\ub9bc,HANGEUL_CHARSET [中略] filename.\uad74\ub9bc=gulim.TTC [中略] fontcharset.dialog.4=sun.io.CharToByteMS949
dialog.pain.4に、韓国語のフォント設定を追加しています。追加した設定については、Java 2に付属しているfont.properties.koから流用しています。
さきほどのプログラムを再起動すると、以下のように日本語と韓国語の混在した文字列が表示されます。
テキストフィールドに入力された日韓混在文字列を、java.lang.String型のインスタンスに代入してダイアログに表示させてもここでは正しく表示されています。
普通、Java SDKをインストールすると合わせてJREもインストールされます。上記のフォント設定は、JDK側にもJRE側にもあるので、片方だけ変更しても実行するJavaコマンドがもう一方の方であれば設定が反映されません。
Swing GUIにおいて、漢字表示は永遠の鬼門です。Swingでは、デフォルトでボールド表示を行うものが多いのですが、漢字のボールド表示は汚く見えます。
まずは、以下にWindows Vista上でJDK 6のデフォルト状態でアプリケーションを実行したときの画面を示します。(Metal Look and Feelのとき)
Windows Vistaでは、デフォルトでCleartypeが有効になっています。しかし、Java/SwingのGUIを表示させると日本語がCleartypeになっていないようです。
これは、Java実行環境が日本語表示で使用するフォント(MS明朝、MSゴシック)について、Windows OSでは小さいポイントのときにアンチエイリアスではなく埋め込みのビットマップ表示をするためです。(実験では24ポイント以上のときにアンチエイリアスが有効)。
対策として考えられるのは以下です。
それぞれの対策について調査していきます。
ボールド体表示しないようにする
システムプロパティ swing.boldMetal=false を設定する
アンチエイリアスを有効(ただしClearTypeではない)にする
システムプロパティ awt.useSystemAAFontSettings=onを設定すると、フォントがスムージング(ClearTypeではなく)されるようになります。ただし、システムプロパティを設定するため、そのJavaVM上で表示される全てに影響するため、きれいにClearType表示されている英数字フォントもClearTypeではなくなってしまいます。
Javaが使用するフォントを変更
JDK1.6はフォントの設定をJDK1.6をインストールしたディレクトリの下、jre/lib/fontconfig.bfcファイルから読み取って使用します。fontconfig.bfcファイルはバイナリですが、同じディレクトリにあるfontconfig.properties.srcから生成された内容です。
JDKのドキュメントによれば、テキストで記述できるフォント設定ファイル名fontconfig.propertiesが存在すれば、fontoconfig.bfcより優先されます。
そこで、ユーザーでフォント設定を変更するときは、fontconfig.properties.srcをfontconfig.propertiesにファイル名を変更して内容を修正します。
以下変更点を記載します。
設定内容 | 元の設定 | 修正した変更 |
---|---|---|
sansserif.plain.japanese | MS Gothic | Meieryo |
sansserif.bold.japanese | MS Gothic | Meiryo |
sansserif.italic.japanese | MS Gothic | Meiryo |
sansserif.bolditalic.japanese | MS Gothic | Meiryo |
monospaced.plain.japanese | MS Gothic | Meiryo |
monospaced.bold.japanese | MS Gothic | Meiryo |
monospaced.italic.japanese | MS Gothic | Meiryo |
monospaced.bolditalic.japanese | MS Gothic | Meiryo |
dialog.plain.japanese | MS Gothic | Meiryo |
dialog.bold.japanese | MS Gothic | Meiryo |
dialog.italic.japanese | MS Gothic | Meiryo |
dialog.bolditalic.japanese | MS Gothic | Meiryo |
dialoginput.plain.japanese | MS Gothic | Meiryo |
dialoginput.bold.japanese | MS Gothic | Meiryo |
dialoginput.italic.japanese | MS Gothic | Meiryo |
dialoginput.bolditalic.japanese | MS Gothic | Meiryo |
filename.Meiryo | - | MEIRYO.TTC |
AWT/Swingのイベント・ディスパッチ・スレッドで実行される処理において捕捉されない例外がスローされると、イベント・ディスパッチ・スレッドは停止し、コンソール(があれば)にスタックトレースが出力されます。イベント・ディスパッチ・スレッドは新しく生成し直されます。
このデフォルトの振舞いでは、コンソールがない場合にエラーの発生を検出しその原因を突き止めることが困難です。イベント・ディスパッチ・スレッドの中で未捕捉の例外が発生したときにでも、それを検知しアプリケーション側で任意のエラー処理をしたいところです。
手段としては以下が考えられます。
SunのJavaVMでは、イベント・ディスパッチ・スレッドで発生した例外をシステム・プロパティsun.awt.exception.handlerで指定したクラス名のvoid handle(Throwable)メソッドに渡して処理させる機構が用意されています。
簡単なサンプルを作成しました。画面にメッセージ文とボタンを表示し、ボタンを押すとそのリスナーで例外をスローします。
import javax.swing.JFrame; import javax.swing.JButton; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import java.awt.BorderLayout; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; public class ExceptionInEdt extends JFrame { private JTextArea explainTextArea = new JTextArea( "[例外発生]ボタンを押すと、EDT(Event Dispatch Thread)で実行される" + "ボタンのアクション処理で例外をスローします。" ); private JButton causeExceptionButton = new JButton("例外発生"); public ExceptionInEdt() { setLayout(new BorderLayout()); explainTextArea.setLineWrap(true); add(explainTextArea, BorderLayout.CENTER); causeExceptionButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { System.out.println( "ボタンが押されたイベントを受け取りました。" + "このスレッドのIDは " + Thread.currentThread().getId() + " です。今から例外を発生します。"); throw new IllegalStateException("in EDT"); } }); add(causeExceptionButton, BorderLayout.SOUTH); } private static void createShowGui() { JFrame frame = new ExceptionInEdt(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setBounds(120, 80, 480, 320); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createShowGui(); } }); } }
このプログラムをコンソール(コマンドプロンプト等)から実行すると、[例外実行]ボタンを押すたびに、例外のスタックトレースがコンソールに表示されます。また、ボタンのActionListenerが呼ばれるときのスレッドIDが毎回変わっているのが分かります。イベント・ディスパッチ・スレッドが未捕捉の例外スローで停止するたびに、新しいスレッドが作られています。
次に、例外ハンドラーを作成し登録して実行します。例外ハンドラーの例として以下のコードを作成します。
Can not access : src/swing/EdtExceptionHandler.java
システム・プロパティsun.awt.exception.handlerにこのクラス名を設定します。実行時にJavaVMオプションで指定するのが簡単です。
$ java -Dsun.awt.exception.handler=EdtExceptionHandler \ ExceptionInEdt
[例外実行]ボタンを押すたびに、エラーダイアログを表示します。なお、このとき、イベント・ディスパッチ・スレッドのスレッドIDは変化していません。
今まで知らなかった5つの事項:Swingを強化する - そうです、Swingを使用して素晴しいユーザー・インタフェースを作成することができます
Swingを洗練する4つの無料コンポーネント(Substance, SwingX, RSyntaxTextArea, Java look and feel Graphics Repository)の紹介と、Swingのスレッド処理について解説。
Swingコードをデバッグし、テストする - 他の人が作成したSwingコードを理解するためのツールと手法
Swing ExplorerでSwing GUIの内部を可視化したりSwingのスレッド処理をモニターする、FEST-Swingを使用してGUIの機能テストを作成する、などの解説があります。
Swingによる動的インタフェース設計 ー Swing APIの外部領域への旅
ウィジェットの無効化(setEnable)、数値ウィジェット(SpinnerやSlider)の範囲を動的に調整、メニューの更新、ウィンドウのサイズ変更
進歩したSynth - 最新のSwingルック・アンド・フィールを使うとカスタムUIが手軽に
Synthは、XMLで見た目を定義します。基礎、色とフォントの変更、画像、状態の処理、コンポーネント固有のプロパティ、カスタム・ペインター、パフォーマンスなどを解説しています。
Tigerを使いこなす:成長するAWT - マウスの位置決めとZオーダー
新たに追加されたクラスPointerInfo、MouseInfo、Zオーダーに関してコンテナーに追加されたメソッドsetComponentZOrder、getComponentZOrderについて解説しています。
Tigerを使いこなす:OceanとSynthがMetalに出会う ー JDK 5.0での新しいルック・アンド・フィール2点
Metal Look and Feelのデフォルトテーマが JDK 5.0において、従来のSteelからOceanになりました。また、新しいルック・アンド・フィール Synthが加わりました。
Merlinの魔術:不確定なプログレス・バー - 微妙な、でも重要な、JProgressBarの更新
基本的な使用方法の説明、新機能の不確定モードの解説。
カスタムレンダラー、JTableのデフォルトレンダラー、テーブルセルの編集