https://appframework.dev.java.net/
https://appframework.dev.java.net/intro/index.html
http://weblogs.java.net/blog/hansmuller/archive/ts-3399-final.pdf
스윙 API가 너무 복잡하니까 단순하게 해보자는 것입니다. 일단 다음과 같이 JFrame이나 content pane 같은 개념이 사라집니다.
public class SingleFrameExample1 extends SingleFrameApplication { public void startup(String[] args) { JLabel label = new JLabel(" Hello World "); label.setFont(new Font("LucidaSans", Font.PLAIN, 32)); show(label); } public static void main(String[] args) { launch(SingleFrameExample1.class, args); } }
리소스로 label 의 설정을 분리 가능합니다. 먼저 코딩은 다음과 같이 합니다.
public class SingleFrameExample2 extends SingleFrameApplication { public void startup(String[] args) { JLabel label = new JLabel(); label.setName("label"); show(label); } public static void main(String[] args) { launch(SingleFrameExample2.class, args); } }
그리고 별도의 파일로 label을 정의합니다.
label.opaque = true label.background = 0, 0, 0 label.foreground = 255, 255, 255 label.text = Hello World label.font = Lucida-PLAIN-48 label.icon = earth.png
버튼이나 메뉴를 누르면 하는 동작은 다음과 같이 정의한다음
/** * Load the specified file into the textPane or popup an error * dialog if something goes wrong. */ @Action public void open() { JFileChooser chooser = new JFileChooser(); int option = chooser.showOpenDialog(getMainFrame()); if (option == JFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); textPane.setPage(file.toURI().toURL()); // error handling omitted for clarity } } /** * Replace the contents of the textPane with the value of the * "defaultText" resource. */ @Action public void close() { ApplicationContext ac = ApplicationContext.getInstance(); String ac.getResourceMap(getClass()).getString("defaultText"); textPane.setText(defaultText); }
메뉴에다가 action을 붙일때는 다음과 같이 합니다.
openMenuItem.setAction(getAction("open")); closeMenuItem.setAction(getAction("close"));
물론 메뉴의 설정도 리소스 파일로 합니다.
open.Action.text = &Open... open.Action.accelerator = control O open.Action.shortDescription = open a document close.Action.text = &Close close.Action.shortDescription = close the document
백그라운드 쓰레드로 하는 작업도 다음과 같이 단순히 합니다.
class DoNothingTask extends Task { @Override protected Void doInBackground() throws InterruptedException { for(int i = 0; i < 10; i++) { setMessage("Working... [" + i + "]"); Thread.sleep(150L); setProgress(i, 0, 9); } Thread.sleep(150L); return null; } @Override protected void done() { setMessage(isCancelled() ? "Canceled." : "Done."); } } [/code] Task는 SwingWorker를 확장한 클래스입니다. 이처럼 백그라운드 작업시 UI의 응답성을 보장하기 위해서는 UI가 응답하는 Event Dispatching Thread(EDT)에서는 긴 시간을 필요로하는 작업을 하면 안됩니다. 대신 별도의 쓰레드로 작업을 한 뒤 그 별도의 쓰레드에서 UI갱신을 할 때는 다음과 같이 했었습니다. [code lang="java"] void printTextField() throws Exception { // 백그라운드 쓰레드 final String[] myStrings = new String[2]; Runnable getTextFieldText = new Runnable() { public void run() { myStrings[0] = textField0.getText(); myStrings[1] = textField1.getText(); } }; SwingUtilities.invokeAndWait // 이벤트 디스패치 쓰레드를 통해 UI작업을 수행하도록 함 (getTextFieldText); System.out.println(myStrings[0] + " " + myStrings[1]); // 그 뒤 이 쓰레드에서 UI의 값을 얻어올 수 있다 } [/code] 물론 복잡하고 귀찮은 일입니다. Java6부터는 이를 <a href="http://java.sun.com/javase/6/docs/api/javax/swing/SwingWorker.html">SwingWorker</a>란 클래스로 분리했습니다. [code lang="java"] final JLabel label; class MeaningOfLifeFinder extends SwingWorker<String, Object> { @Override public String doInBackground() { // 여기서 긴 시간의 작업 return findTheMeaningOfLife(); } @Override protected void done() { // 여기서 긴 시간의 작업이 끝나고 UI를 갱신할 코드 try { label.setText(get()); } catch (Exception ignore) { } } } (new MeaningOfLifeFinder()).execute(); // 백그라운드 쓰레드 시작
Task는 이 새로운 방식을 받아들인 클래스입니다.
이제야 Swing이 쓸만해진 상태에 다다른듯해 보이네요.