스윙/SWT 통합
요약
종종 스윙과 SWT가 기술 경쟁을 하는 것처럼 보여진다. 몇몇 사람들은 클라이언트 어플리케이션을 위해 하나의 UI 툴킷을 사용하는 것을 고집하기도 한다. 그러나 현실에서는 극단적인 생각은 오히려 비현실적이다. 하나의 어플리케이션에서도 각각의 기술이 공존해서 사용될 필요가 있는 경우가 있다. 두 가지 기술을 섞어서 사용하는 것이 간단한 일은 아닐지라도, 할 수는 있다. 게다가 두 기술을 자연스럽게 통합할 수도 있다. 이 문서에서는 자연스러운 스윙/SWT 통합을 위해 필요한 단계들을 설명한다. 여기서는 SWT 기반의 RCP 어플리케이션에 스윙 컴포넌트를 포함시키는 경우를 예로 들었다.
원문 :http://www.eclipse.org/articles/Article-Swing-SWT-Integration/index.html
소개
대부분의 현존하는 독립적인 자바 클라이언트들은 스윙 컴포넌트들로 이루어진 것들이다. 이런 것들을 이클립스 RCP로 전환하기 위한 흥미로운 내용들이 있다고 할지라도, 큰 어플리케이션을 전체적으로 마이그레이션 하는데에는 많은 비용이 들어간다. 몇몇 스윙 컴포넌트들을 유지함으로써 이런 마이그레이션에 드는 비용을 줄일 수 있다. 시간이 지나면서 필요에 따라 스윙 컴포넌트들을 SWT로 점차적으로 바꾸어 나가면 된다.
예를 들어, SAS에서는 어플리케이션의 필요에 따라 보다 나은 테이블을 만들기 위해 스윙 JTable
컴포넌트를 확장해서 사용했다. 이 컴포넌트를 SWT로 바꾸는데는 많은 노력이 필요할 것이다. 동일한 SWT 테이블을 기다리는 대신에, 아래 스크린샷에서 처럼 스윙 테이블을 RCP 어플리케이션에 사용할 수 있다. 아래는 윈도우 XP에서 돌아가는 RCP 어플리케이션이다. 두 개의 테이블은 스윙 컴포넌트이며 다른 모든 컴포넌트들은 SWT로 만들었다.
그림 1. 스윙 테이블 컴포넌트 포함하기
마찬가지로 그래프와 차트를 표현하기 위해 복잡한 스윙 컴포넌트들이 많이 있었다. 역시, 스윙과 SWT를 혼용했으며, RCP로 마이그레이션 할 필요가 있을 때마다 따로 따로 전환할 수 있다고 생각했다. 아래 스크린샷에는 스윙 기반 그래프 컴포넌트를 포함한 RCP 어플리케이션을 보여준다. 그래프 컴포넌트 이외의 것들은 모두 SWT 컴포넌트이다. (이 어플리케이션이 RCP 어플리케이션 처럼 보이지 않을 수 있겠지만, RCP 어플리케이션이다. 달라보이는 이유는 이클립스 프리젠테이션 API 와 다른 확장된 워크벤치 모양을 많이 사용했기 때문이다.)
그림2. 그래픽 스윙 컴포넌트 포함하기
RCP에 스윙 컴포넌트를 사용하는 것은 두 개의 큰 UI 툴킷이 하나의 어플리케이션에서 공존할 수 있음을 말해준다. 통합의 과정에서 UI 전체적인 일관성을 위해서 약간의 노력이 필요하긴 하다.
SWT에서는 SWT/AWT 브릿지 [2] 라고 하는 스윙/SWT 통합을 위한 기본적인 구조를 제공한다. 그러나, 이 브릿지는 RCP 어플리케이션에 스윙 컴포넌트를 포함하는 과정의 첫 번째 단계일 뿐이다. 이 문서에서는 효과적인 통합을 위해 필요한 추가적인 단계를 설명하는데에 주력할 것이다. 이런 추가적인 단계가 없으면, SWT/AWT 브릿지는 제품으로 사용할 어플리케이션에는 불충분하다. 추가적인 단계를 적용함으로써 RCP 어플리케이션에 스윙 컴포넌트를 제대로 포함시킬 수 있을 것이다.
두 개의 독립적인 UI 툴킷을 통합하는 것은 복잡한 작업이다. 그리고 제대로 된 결과를 얻지 못할 수도 있다. 이 문서에서는 실제 어플리케이션에서 사용하기 위해 제대로 된 통합 기술(물론 약간의 꼼수를 통해)을 설명하는데에 주력한다.
이렇게 복잡한 작업이긴 하지만 아래 설명된 대부분의 내용들은 스윙 컴포넌트를 포함할 때 마다 사용할 수 있도록 하나의 공통 클래스로 작성할 수 있다. 구현을 돕기 위해 추가적인 헬퍼 클래스들이 있다. 이 문서에 예제들이 있으며 추가적인 정보는 부록 A, 예제 코드에서 확인할 수 있다.
SWT/AWT 브릿지 사용하기
SWT/AWT 브릿지는 이클립스 3.0 이후부터 SWT의 한 부분으로 제공된다. 매우 간단한 API 이며org.eclipse.swt.awt 패키지에 위치한다.
여기서는 SWT 컴포넌트 내에 AWT 프레임(frame)을 포함시키는데에 중점을 둔다. 이 과정은 SWT/AWT 브릿지의 반정도 밖에 안된다. 그럼에도 불구하고, 아래 설명된 내용들은 반대의 경우(AWT 프레임에 SWT 컴포넌트 포함하기)에도 적용할 수 있다.. |
아주 간단하게 보면, SWT 콤포지트(composite)에 AWT 프레임을 포함하는데는 단지 두 줄의 코드만 있으면 된다.
- Composite composite = new Composite(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND);
- Frame frame = SWT_AWT.new_Frame(composite);
org.eclipse.swt.Composite 의 인스턴스는 SWT.EMBEDDED 스타일로 만들어져야 한다. 이 스타일이 콤포지트 내부에 AWT 프레임을 포함할 것임을 알려주기 때문이다. new_Frame() 메쏘드를 사용하면 프레임을 얻을 수 있다. 이 프레임에 AWT 나 스윙 컴포넌트를 올릴 수 있을 것이다.
리턴된 프레임은 일반적인 AWT 프레임이 아니다. 대게 네이티브 어플리케이션에 포함된 java.awt.Frame 의 서브 클래스이다. 사실, 브라우저 내의applets에 사용된 프레임이라고 할 수도 있겠다.
위의 코드는 보기와는 달리 단순한 것이다. 두 개의 툴킷을 통합하는 것을 SWT 내부에서 관리를 한다고 하지만, 브릿지의 구현은 극히 일부분만 처리되어 있다. 실제로는, 더 나은 일관된 통합을 위해 추가로 해주어야할 것들이 많이 있다. 이러한 추가적인 단계는 아래섹션“Building on the SWT/AWT Bridge”에서 설명한다.
플랫폼 고려사항
SWT/AWT 브릿지를 사용하는데에 있어서 플랫폼 별로 버젼 제한이 있다.
표 1. 최소한의 버젼 요구사항
Platform | JRE Version | Eclipse Version |
---|---|---|
Windows | 1.4 | 3.0 |
Linux/GTK | 1.5 | 3.0 |
Mac OS X | 1.5.0 Release 4 | 3.2 |
Mac OS X 에서는 SWT 호환 라이브러리를 추가로 설치해주어야 한다. 상세 내용은 SWTFAQ를 보면 된다. 이 문서를 작성하고 있는 지금에도 Mac OS X 에서의 SWT/AWT 브릿지는 아직 완벽하지 못하다. 상세 내용은 SWT 버그145890를 참조하라. |
현재 리눅스/GTK 플랫폼에서 GTK 룩앤필(look and feel)과 자바 6를 사용하는 경우에 SWT/AWT 브릿지가 행(hangs) 걸리는 현상이 있다. 상세 내용은 이클립스 버그91157와 썬 버그6386791를 참조하라. |
Multiple Event Threads
스윙/SWT 통합에는 중요한 쓰레드 문제가 있다. 각각의 UI 툴킷은 자체적인 이벤트 큐를 가지고 있고, 각 이벤트 큐는 서로 다른 쓰레드에서 처리된다. 대부분의 SWT API들은 SWT 이벤트 쓰레드로 부터 호출되어야 한다. 스윙에서도 강제적이진 않지만 비슷한 제한이 있다. 이런 문제가 툴킷의 혼용에 있어서 중대한 결점이 된다. 그리고, 코드를 보다 복잡하게 만들기도 한다.
어플리케이션은 현재 쓰레드를 알고 있어야 하며, 필요에 따라 적절한 UI 툴킷 쓰레드에서 실행하기 위해 스케쥴되어야 한다. 아래는 AWT 이벤트 쓰레드에서 스케쥴하기 위해 사용하는 방법이다:
- javax.swing.SwingUtilities.invokeLater()
- javax.swing.SwingUtilities.invokeAndWait()
SWT 이벤트 쓰레드에서 스케쥴하기 위해 사용하는 방법이다:
- org.eclipse.swt.widgets.Display.asyncExec()
- org.eclipse.swt.widgets.Display.syncExec()
워커 쓰레드(worker thread)에서 오래걸리는 작업을 수행하는 중에도 UI 의 응답을 처리할 수 있도록 동일한 툴킷 환경 내에서 사용되는, 같은 기능을 하는 API들이다. 스윙/SWT 통합에서 하나의 이벤트 쓰레드에서 다른 이벤트 쓰레드로 작업을 전달하기 위한 추가적인 목적으로 사용되기도 한다.
여러개의 이벤트 쓰레드를 사용하는 것은 데드락(deadlock)의 위험을 증가시킨다. 블락 현상을 피하기 위해, 가능하다면 이벤트 쓰레드에서 스케쥴링을 할 때 다른 이벤트 쓰레드를 이용하는 것을 피해야 한다. 다시 말해 SWT 이벤트 쓰레드에서 SwingUtilities.invokeAndWait 를 호출하는 것을 피하고, AWT 이벤트 쓰레드에서 Display.syncExec 를 호출하는 것을 피하라는 것이다. 그렇지 않으면, 한 쓰레드가 다른 쪽으로 블락킹 호출(blocking call)을 하고 있는 동안 또 다른 방향으로 블락킹 호출을 하게 되면 데드락(deadlock)이 발생할 것이다.
Building on the SWT/AWT Bridge
만약 SWT/AWT 브릿지 API 만을 단독으로 사용한다면, 스윙 컴포넌트는 당신의 어플리케이션에 최소한으로만 통합될 것이다. 앞으로의 섹션에서는 통합 과정에서의 문제를 상세히 설명하고 해결책을 제시할 것이다. 모든 해결책에는 스윙/SWT 통합을 도와주는 추가 코드들이 포함된다. 반가운 소식은 이런 코드들이 하나의 일반화된 임베디드 콤포지트 위젯으로 (그리고 몇몇 지원 클래스들로) 캡슐화할 수 있다는 것과, 어플리케이션의 다른 코드들과 분리될 수 있다는 것이다.
스윙 룩앤필(Look and Feel) 설정하기
다음은 윈도우 환경에서 기초적인 SWT/AWT 브릿지를 사용했을 경우에 발생하는 아주 기본적인 화면상의 문제를 보여주고 있다.
그림 3. 스윙 컴포넌트의 기초적인 임베딩
여기에는 바로 보이는 문제점이 있다. 우측 상단에 위치한 스윙 JTable 의 테이블 헤더와 스크롤바가 SWT 기반의 Error Log 뷰의 것과는 아주 다르다.
예제에서는, JTable 이 기본적인 스윙룩앤필(look and feel) (메탈)을 표시하고 있다. 반면에, SWT 컴포넌트는 네이티브 룩앤필을 나타내는 네이티브 위젯에 기반을 두고 있다. 스윙 룩앤필을 네이티브 환경에 맞게 바꿀 필요가 있다. SWT/AWT 브릿지가 자동적으로 네이티브 룩앤필을 설정할 수 없기 때문에, 당신이 직접 처리해주어야 한다.
- import javax.swing.UIManager;
...
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
...
위 코드는 AWT나 스윙 컴포넌트를 만들기 전에 한 번만 호출하면 된다.
리눅스/GTK 개발자들에게: 사용중인 윈도우 매니저에 따라서 스윙 룩앤필이 GTK 룩앤필로 설정되지 않을 수도 있다. GTK 룩앤필을 설정할 때에는 보다 명확하게 처리해줄 필요가 있다:
|
Creating a Root Pane Container
SWT_AWT.new_Frame() 메소드는 SWT 컴포지트에 포함된 java.awt.Frame 의 서브클래스의 인스턴스를 리턴한다. 이 임베디드된 프레임을 스윙에 적용할 때에 따라야할 규칙들이 있다.
- 임베디드 프레임의 첫번째 자식(child)는 무거운(heavyweight) 컴포넌트이어야 한다. 그리고 이 컴포넌트는 전체 프레임에 꽉차야 한다. 무거운 컴포넌트는 마우스의 위치와 동작을 정확하게 파악할 수 있게 한다. 추가 정보는 썬(Sun)의 버그4982522를 참조하라.
- 스윙에서 사용할 수 있도록 프레임을 좀 더 제대로 만들고자 한다면,root pane컨테이너(javax.swing.RootPaneContainer)를 만들어야 한다. root pane 은 모든 스윙 윈도우의 기반이 된다. 그리고 포함되는 모든 스윙 컴포넌트를 배치(layering) 할 수 있도록 해준다.
- 스윙 구현을 가정한다면, root pane 컨테이너는 JFrame, JDialog, JWindow 혹은 JApplet 의 인스턴스이어야 한다. 이들 중에서 임베디드 될 수 있는 것은 JApplet 밖에 없다.
이런 규칙을 만족시키기 위해, 임베디드 된 프레임의 자식(child)으로 JApplet 을 만든다. JApplet 을 사용한다는 것이 실제 애플릿의 생명주기를 가진 애플릿을 사용한다는 의미는 아니지만; 브라우저에 포함된 애플릿을 표시하는 컨테이너와 같은 의미로 생각하면 될 것 이다.
- Composite composite = new Composite(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND);
- Frame frame = SWT_AWT.new_Frame(composite);
- JApplet applet = new JApplet();
- frame.add(applet);
임베디드 된 프레임은 윈도우 (java.awt.Window의 서브클래스)이다. 즉, 이것이 자원을 소비한다는 말이다. 이런 이유로 임베디드된 프레임을 사용할 때에는 작은 것들 보다는 큰 컴포넌트를 대체하는데에 사용하도록 권장한다. |
깜빡임(Flicker) 줄이기
추가적인 조치없이 SWT/AWT 브릿지를 사용하게 되면, 어플리케이션 윈도우를 리사이징할 때 임베디드 된 AWT 프레임의 지나친 깜빡임 현상이 발생할 수 있다. 이유는 다음과 같다:
- 무거운(heavyweight) 컴포넌트 (여기서는 javax.swing.JApplet)는 컴포넌트 상하 관계를 표시한다. 일반적인 AWT 구현에서는 리사이즈 이벤트가 발생할 때 마다 컴포넌트의 배경을 지운다.
- 리사이즈 이벤트는 독립적인 스윙 어플리케이션에서 보다 임베디드 된 프레임에서 더 많이 발생한다. 이런 차이는 리사이즈 이벤트가 SWT 콤포지트에서 AWT 프레임으로 전달되기 때문이다.
윈도우 환경에서, 깜빡임 현상을 줄이기 위해 sun.awt.noerasebackground 를 설정한다. 이 알려지지 않은 속성이 AWT 구현에 의한 반복적으로 배경을 지우는 동작을 멈추어 준다. 이것은 AWT나 스윙 컴포넌트가 인스턴스화 되기 전에 설정되어야 한다.
- System.setProperty("sun.awt.noerasebackground", "true");
sun.awt.noerasebackground 속성은 윈도우 이외의 환경에서는 사용되지 않는다. 다른 환경에서 이런 문제를 해결하는 방법을 더 찾아보아야 한다. |
이것은 필요한 작업이긴 하지만, sun.awt.noerasebackground 을 설정하는 것은 부정적인 결과를 초래하기도 한다. 배경을 지우지 않도록 하게됨으로써 리사이즈 작업 중이나 스윙 컴포넌트를 최초에 표시하기 전에 그려진 내용들(때때로cheese라고 부르는 것들)이 남아있게 된다.
이런 현상들은 부모 SWT 콤포지트에 리사이즈 리스너를 추가함으로써 해결한다. 크기가 커지거나 그려지지 않았던 영역이 드러나는 경우, 리스너에서 배경색으로 채워주도록 한다. 치즈(cheese) 현상은 즉시 제거할 수 있으며, 임베디드된 스윙 컴포넌트가 다시 그리는 동안 딜레이 되더라도 표면적인 향상을 제공한다.
- class CleanResizeListener extends ControlAdapter {
private Rectangle oldRect = null;
public void controlResized(ControlEvent e) {
// Prevent garbage from Swing lags during resize. Fill exposed areas
// with background color.
Composite composite = (Composite)e.widget;
Rectangle newRect = composite.getClientArea();
if (oldRect != null) {
int heightDelta = newRect.height - oldRect.height;
int widthDelta = newRect.width - oldRect.width;
if ((heightDelta > 0) || (widthDelta > 0)) {
GC gc = new GC(composite);
try {
gc.fillRectangle(newRect.x, oldRect.height, newRect.width, heightDelta);
gc.fillRectangle(oldRect.width, newRect.y, widthDelta, newRect.height);
} finally {
gc.dispose();
}
}
}
oldRect = newRect;
}
}
Tab Traversal
스윙/SWT 통합에서 고려해야할 사항으로 탭(tab) 키에 의한 다른 툴킷에서의 컴포넌트 간의 트래버싱(traversing) 동작이 있다. 이 문제를 해결하기 위해 SWT/AWT 브릿지에서 AWT 쪽에 추가적인 작업이 필요하다. 제대로 된 동작을 지원하기 위해 임베디드 된 AWT 프레임에 java.awt.FocusTraversalPolicy 의 서브클래스를 구현해야 한다. 프레임 내에서의 일반화된 정책은 기본적인 정책을 따라야 하지만, 다음과 같은 경우에는 SWT 컴포넌트로 제어권을 넘겨주어야 한다:
- 프레임 내의 마지막 컴포넌트에서 탭 키(tabbing)를 누른 경우
- 프레임 내의 첫 컴포넌트에서 쉬프트+탭 키(back-tabbing)를 누른 경우
일반적으로 전후 트래버싱의 구현은 간단하다. 하지만 적절한 getDefaultComponent() 메소드 구현에는 약간의 노력이 필요하다. 이 메소드는 임베디드 된 AWT 프레임 내에 포커스를 받을 것이 없는 경우에 null을 리턴해야만 한다. 이것은 AWT가 메소드를 호출하는 방법에 대한 암묵적인 확신을 기반으로 처리된다. 그리고, SWT로 트래버싱한 직후에 AWT에 포커스를 전달하는 것은 피하는 것이 좋다. 추가 정보는,부록 A, 예제 코드의 EmbeddedChildFocusTraversalPolicy 클래스내의 주석을 참조하라.
포커스 이동 정책에 대한 추가 정보는 스윙 관련 문서AWT Focus Subsystem를 참조하라.
모달 다이얼로그
스윙 컴포넌트에서 모달 다이얼로그를 띄웠을 때, 이 다이얼로그는 어플리케이션 전체적으로 모달 상태이어야만 한다. SWT와 AWT가 서로 다른 이벤트 쓰레드를 사용하고 있기 때문에, 각각의 다이얼로그는 서로 모달 처리가 되지 않는다. 스윙 모달 다이얼로그가 보여지고 있는 동안에는 SWT 이벤트 쓰레드가 강제적으로 비활성화되어야 한다. 이것은 스윙 다이얼로그가 보여지고 있는 동안에 SWT 모달 다이얼로그를 같이 띄워줌으로써 쉽게 해결할 수 있다.
- java.awt.Dialog awtDialog = ...
- Shell shell = new Shell(parent, SWT.APPLICATION_MODAL | SWT.NO_TRIM);
- shell.setSize(0, 0);
- shell.addFocusListener(new FocusAdapter() {
- public void focusGained(FocusEvent e) {
- awtDialog.requestFocus();
- awtDialog.toFront();
- }
- public void focusGained(FocusEvent e) {
- });
0 x 0 사이즈는 쉘(shell)이 화면상에 보여지는 것을 막아준다. 포커스 리스너는 SWT 창이 포커스를 얻게되는 상황에서 스윙 다이얼로그로 포커스를 옮겨주는 역할을 한다. (예를 들면, 사용자가 윈도우 매니져를 통해 당신의 어플리케이션으로 포커스를 옮길 수 있다.)
리눅스/GTK에서는 사이즈가 0*0 인 SWT 다이얼로그가 점으로 표시될 수도 있다. focusGained() 메소드에 아래 코드를 추가해서 숨겨주도록 한다. 이 작업은 윈도우에서는 필요가 없으며, 윈도우에 적용시 번쩍이는 현상이 발생할 수도 있다. 따라서, 필요한 경우에만 사용하도록 한다.
|
위에 있는 코드를 통해 확실한 모달 동작을 처리할 수 있다. 하지만 어떻게 호출할 것인가? 가장 쉬운 방법은 스윙 모달 다이얼로그가 생성되는 곳에서 마다 호출을 하는 것이다. 그러나, 이 방법은 당신의 스윙 컴포넌트 라이브러리가 수정될 수 밖에 없으며, 스윙 컴포넌트 라이브러리가 SWT 코드와 독립적이라고 이야기할 수 없게 한다.
대신, 스윙 모달 다이얼로그가 열리거나 보여질 때마다 모든 AWT 윈도우 이벤트의 리스너를 처리함으로써 보다 명확하게 모달 동작을 처리할 수 있다. 아래 코드에서 리스너에 대한 처리를 했으며 예제 코드에 모두 구현되어 있다. 추가정보는부록 A, 예제 코드를 참조하라.
- class AwtDialogListener implements AWTEventListener, ComponentListener {
private final Display display;
AwtDialogListener(Display display) {
this.display = display;
Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.WINDOW_EVENT_MASK);
}
private void handleRemovedDialog(Dialog awtDialog, boolean removeListener) {
// An AWT dialog has been closed or set invisible. Close the SWT dialog
...
}
private void handleAddedDialog(final Dialog awtDialog) {
// An AWT dialog has been opened or set visible. Open the SWT dialog
// and add this as a component listener on the dialog to catch
// visibility changes
...
}
private void handleOpenedWindow(WindowEvent event) {
Window window = event.getWindow();
if (window instanceof Dialog) {
handleAddedDialog((Dialog)window);
}
}
private void handleClosedWindow(WindowEvent event) {
// Dispose-based close
Window window = event.getWindow();
if (window instanceof Dialog) {
// Remove dialog and component listener
handleRemovedDialog((Dialog)window, true);
}
}
private void handleClosingWindow(WindowEvent event) {
// System-based close
// (see example code for full implementation)
...
}
public void eventDispatched(AWTEvent event) {
switch (event.getID()) {
case WindowEvent.WINDOW_OPENED:
handleOpenedWindow((WindowEvent)event);
break;
case WindowEvent.WINDOW_CLOSED:
handleClosedWindow((WindowEvent)event);
break;
case WindowEvent.WINDOW_CLOSING:
handleClosingWindow((WindowEvent)event);
break;
default:
break;
}
}
public void componentHidden(ComponentEvent e) {
Object obj = e.getSource();
if (obj instanceof Dialog) {
// Remove dialog but keep listener in place so that we know if/when it is set visible
handleRemovedDialog((Dialog)obj, false);
}
}
public void componentShown(ComponentEvent e) {
Object obj = e.getSource();
if (obj instanceof Dialog) {
handleAddedDialog((Dialog)obj);
}
}
...
}
팝업 메뉴 없애기
임베디드된 AWT 프레임에서 표시된 컨텍스트 메뉴는 프레임 외부를 클릭했을 때 사라지지 않는다. 이러한 AWT 제한은 잘 알려져 있는 것이지만, SWT의 팝업과 함께 동시에 여러개의 팝업이 표시되는 등의 눈에 띄는 문제가 된다.
그림 4. 두 개의 팝업
이 문제는 다른 것들과 함께 썬버그4311449와4290715에 작성되어 있다.
이 문제에 대한 부분적인 처리 방법은 다음과 같다:
- 사용자가 다른 윈도우를 활성화 시킬 때 마다 스윙 팝업을 수동적으로 닫아준다. 임베디드된 프레임에 윈도우 포커스 이벤트를 달아주고, 포커스 로스트(lost) 이벤트가 발생할 때, 프레임에 포함된 컴포넌트들에서 javax.swing.JPopupMenu 의 인스턴스를 찾아볼 수 있다.
이 방법은 JRE 1.4 이하에 적용할 수 있다; 1.5 이상을 사용중이라면 필요없는 작업이다. |
사용자가 SWT 메뉴를 활성화 시킬 때 마다 스윙 팝업을 수동적으로 닫아준다. SWT에 대해 디스플레이 필터를 달아주고, SWT 메뉴가 보여질 때 마다 발생된 이벤트를 전달한다. 필터의 이벤트 핸들러에서는 위에서 말한대로 표시된 스윙 팝업을을 닫아주면 된다.
이러한 방법을 사용하더라도 몇 가지 문제가 남아있다; 예를 들어, 사용자가 워크벤치 윈도우의 타이틀바를 건드리는 경우에는 스윙 팝업이 없어지지 않는다.
시스템 설정 동기화
사용자가 윈도우 제어판에서 폰트 설정을 바꾸었을 때, 스윙 컴포넌트에 항상 제대로 적용되지 않는다. 이 문제는 SWT 와 통합할 때에 특히 발견되기 쉬운 문제이다. 해결 방법은 시스템 폰트가 바뀔 때 스윙 윈도우 룩앤필에 수동적으로 반영해주면 된다.
다행히, 이클립스 3.2에서 시스템 설정의 변화를 알아낼 수 있게 해주는 SWT 이벤트가 추가되었다. 따라서, 스윙 폰트 변화는 다음과 같이 처리할 수 있다.
- Display display = ...
Listener settingsListener = new Listener() {
public void handleEvent(Event event) {
handleSettingsChange();
}
};
display.addListener(SWT.Settings, settingsListener);
스윙 컴포넌트의 폰트를 바꾸는 것은 개별 컴포넌트 마다 폰트를 바꾸어 주는 것보다 룩앤필을 통해서 처리하는 것이 가장 좋다.
- private void handleSettingsChange() {
...
org.eclipse.swt.graphics.Font currentSystemFont = ...;
FontData fontData = currentSystemFont.getFontData()[0];
int resolution = Toolkit.getDefaultToolkit().getScreenResolution();
int awtFontSize = (int)Math.round((double)fontData.getHeight() * resolution / 72.0);
// The style constants for SWT and AWT map exactly, and since they are int constants, they should
// never change. So, the SWT style is passed through as the AWT style.
java.awt.Font awtFont = new java.awt.Font(fontData.getName(), fontData.getStyle(), awtFontSize);
FontUIResource fontResource = new FontUIResource(awtFont);
UIManager.put("Button.font", fontResource);
UIManager.put("CheckBox.font", fontResource);
UIManager.put("ComboBox.font", fontResource);
... // many more similar calls
Container contentPane = ... // content pane from the root pane
SwingUtilities.updateComponentTreeUI(contentPane);
}
}
무엇보다도 SWT 폰트는 AWT 폰트와 동일하게 바뀌어진다. AWT 폰트 크기는 항상 72 dpi 해상도라고 보면 된다. 실제 표시될 때에는 화면 해상도에 따라 폰트 사이즈를 결정한다. 따라서, 다양한 스윙 컴포넌트에 따른 폰트를 바꾸어 주려면 룩앤필의 javax.swing.UIManager 를 이용하도록 한다.
완전한 구현은 예제 코드(Appendix A, Example Code)를 보도록 한다.
키스트로크(Keystroke) 충돌
당신의 RCP 어플리케이션에서 전반적으로 사용하는 액션의 키매핑과 임베디드된 스윙 컴포넌트에서의 키매핑이 같은 경우, 예기치 못한 결과를 발생시킬 수 있다. org.eclipse.ui.bindings 확장점을 통해 정의된 키스트로크는 비록 스윙 컴포넌트에 포커스가 있다고 하더라도 스윙 컴포넌트에서 정의된 것 보다 먼저 처리된다. 이 경우에, RCP 바인딩은 디스플레이 필터를 통해 관리된다. 따라서 키스트로크는 스윙 컴포넌트에 전달되기 이전에 처리되어 버려진다. 결과적으로 임베디드된 컴포지트 단계에서는 간단하고 일반적인 해결책은 없다. 대신에, 다음과 같은 방법으로 이런 혼동을 피할 수 있다:
- org.eclipse.ui.contexts 확장점을 이용해서 스윙 컴포넌트가 포커스를 가지고 있는 동안에 키 바인딩 구성을 비활성화 시킨다.
- 스윙 컴포넌트의 액션을 같은 키스트로크로 매핑시킨 RCP 액션으로 복사한다. RCP 액션의 바인딩은 org.eclipse.ui.contexts 확장을 통해 처리할 수 있다. RCP 액션에서 스윙 액션을 호출하도록 구현할 수 있다. 이 접근은 스윙 컴포넌트 밖에서도 사용가능한 액션을 원한다면 꼭 필요하다. (예를 들어, 어플리케이션의 메인 메뉴 등)
때로는 임베디드된 스윙 컴포넌트와 윈도우 시스템 간에 키스트로크 충돌도 발생한다. 예를 들어, Shift-F10 을 누르면 여러 스윙 컴포넌트에서 컨텍스트(팝업) 메뉴를 띄울 것이다. 윈도우에서는, 컨텍스트 메뉴가 없는 경우, F10 키스트로크 (shitf 키와 상관없이)의 기본 동작인 어플리케이션의 메인 메뉴에 포커스를 가져갈 것이다. 임베디드된 스윙 컴포넌트가 있다면 이 두가지 동작이 충돌을 일으킬 것이다. 스윙 컴포넌트가 Shift-F10에 대한 적절한 처리를 했다고 하더라도, SWT 쉘이 그 처리를 모르기 때문에 윈도우에게 처리되지 않은 키스트로크라고 알려주게 된다. 윈도우는 포커스를 메인 메뉴바로 가져가게 되고, 팝업은 포커스를 잃게 될 것이다.
다행히, 이 충돌은 해결할 수 있다. 여기 기본적인 윈도우 동작이 F10키가 눌려질 때(pressed)가 아니라 놓여질 때(released)에 발생한다는 것을 이용한 코드가 있다. 임베디드 컴포지트에 키리스너를 달아서 Shift-F10 키스트로크가 놓여질 때에 처리를 해버림으로써 윈도우에게 전달되지 않도록 한다.
- public void keyReleased(KeyEvent e) {
if (e.keyCode == SWT.F10 && (e.stateMask & SWT.SHIFT) != 0) {
e.doit = false;
}
}
기타 해결방법
썬 버그 6411042에 따르면, AWT/스윙 컴포넌트를 임베디드된 프레임에 붙일 때 SWT 이벤트 쓰레드를 사용하면 메모리 릭(leak)이 발생한다. 이것을 피하기 위해서는 AWT 이벤트 쓰레드에서 컴포넌트를 만들고 붙여야 한다. 단순하게 javax.swing.SwingUtilities.invokeLater()를 이용해서 생성 코드를 호출하면 된다. 앞에서 이야기한대로, 스윙 API를 사용할 때에는 AWT 이벤트 쓰레드를 사용하는 습관을 들일 필요가 있다.
남겨진 문제
ClearType
윈도우 JRE 1.5 이전 환경에서는, 스윙 시스템 룩앤필이ClearType 안티알리아싱을 제대로 지원하지 않는 것 처럼 보인다. 일반적으로, ClearType을 활성화하면 스윙과 SWT 컨트롤에서 같은 폰트와 크기를 사용하더라도 모양이 달라보인다. 임시 해결책으로 swing.aatext 속성을 "true"로 설정하는 방법이 있다. 이 숨겨진 속성은 Java 6 이전 환경에서 폰트 안티알리아싱을 가능하게 해주긴 하지만, 단순히 안티알리아싱을 켜는 것 뿐이다. 윈도우 시스템 설정과 완벽하게 일치하지 않는다. 스윙 역시 자체 안티알리아싱 알고리즘을 가지고 있기 때문에, 네이티브하게 표시되는 폰트와의 차이는 있을 수 밖에 없다.
이 문제는 Java 6에서해결되었다. (추가 정보는 버그4726365와4871297을 참조하라).
아래 스크린샷은 이 문제를 보여주고 있다. 좌측에 SWT 기반의 뷰 두 개와 우측에 스윙 테이블 컴포넌트가 있다.
그림 5. ClearType 폰트의 차이
커서 동기화
SWT 커서의 변화는 임베디드된 스윙 컴포넌트에 반영되지 않는다. 바뀐 SWT 커서가 임베디드된 프레임 위로 움직이면 기본적인 화살표 커서로 바뀐다.
이 문제는 곧 해결될 것이다. 이클립스 3.3에서 새로운 API를 통해 현재 커서를 얻을 수 있다. (버그133943를 참조하라).
결론
RCP 어플리케이션 내에 스윙 컴포넌트를 자연스럽게 통합시키는 작업은 가능하다. 그렇지만 단순하게 SWT/AWT 브릿지를 사용하는 것 이상의 노력이 필요하다. 위에서 언급한 것 처럼 실제 어플리케이션에서 SWT/AWT 브릿지가 더 나은 동작을 할 수 있도록 모듈화 하는 방법을 사용해서 구현해둘 수 있다.부록 A, 예제 코드에는 모든 추가적인 동작들을 구현해둔 예제 코드가 있다.
A. 예제 코드
"Building on the SWT/AWT Bridge" 섹션에서 설명한 스윙/SWT 통합의 아이디어들은 예제에 모두 포함되어 있다[1]. 이 예제에는 임베디드된 스윙 컴포넌트를 간단하고 확실하게 만들 수 있는 API도 포함하고 있다. 예제 코드는 SWT 3.2 이상에서 실행된다.
"File -> Import..."로 임포트(import)할 때, "Plug-ins and Fragments"를 선택한다. "Projects with Source Folders"를 선택해서 플러그인을 임포트한다. 컴파일러 설정을 최소 1.4 버젼에 호환 가능하도록 설정한다(Preferences -> Java -> Compiler). 그렇지 않으면 예제 코드 상의 assert 구문이 컴파일되지 않을 것이다.
API를 위해 Javadoc을 이용할 수 있다. 스윙 컴포넌트를 포함시키기 위해EmbeddedSwingComposite에 대한 Javadoc을 참조하라. 임베딩(embedding) 없이 스윙 다이얼로그를 표시하기 위해서는AwtEnvironment에 대한 Javadoc을 참조하라.
Resources
[1]Example Code.
[3]Java Look and Feel Information.
[4]Swing Root Pane Documentation.
Legal Notices
Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both.
Other company, product, or service names may be trademarks or service marks of others.
'Eclipse' 카테고리의 다른 글
GMF: Beyond the Wizards (0) | 2007.08.03 |
---|---|
GMF: Beyond the Wizards (0) | 2007.07.14 |
Eclipse on Swing (0) | 2007.06.07 |