여전히 많은 이클립스 개발자들은 SWT클래스를 상속받습니다. 이 관행은 사실 Swing으로부터 전해져 온 것인데, Swing이 실패한 주요원인 중하나 입니다. 구체적 내용에 대하여는 차차 언급합니다. 이 글을 읽기 전에 먼저 SWT가 상속을 불허하는 이유를 읽어 보시기 바랍니다.

UI 재사용성 허구

여러분이 만든 UI, 예를 들어 검색 폼이나 입력 폼. 그 UI가 실제로 재사용된 적이 있나요? 혹은 그 UI가 다른 모델이나 컨트롤러와도 함께 일할 수 있나요? 그렇다고 대답하실 수 있는 분은 상당히 드물겁니다. 그렇다고 대답된 UI 들은 대체로, 전용성과 풍부한 경험이 떨어질 것 또한 분명하죠. 재사용의 단위는 위짓이나 컨트롤일 뿐, 전체 화면 설계가 아닙니다.

결국 화면 설계 클래스를 정의하는 모듈화는 의미가 없습니다. UI의 상속은 결코 일어나지 않을 일들에 대한 가치를 중시하는 행동 중 하나입니다. UI는 목적에 따라 그 특성이 매우 다르기 때문에, 한 UI가 상속받아 필요한 부분만 추가하거나 변경한 경우, 부모와 자식이 모두 가치가 있을 확률이 거의 없습니다.

상속 그 자체의 문제점

상속은 오랫동안 유용한 OOP도구로 인정 받아왔습니다, 하지만 세상이 점 점 더 복잡해짐에 따라 여러가지 문제가 발생합니다.

첫 째, 상속은 부모 클래스에 대하여, 너무 많은 지식과 이해를 필요로 합니다. 이는 객체지향에 위배됩니다. 객체지향에서 한 영역내에서 다른 객체는 역할 수행자로 취급되며, 내부는 철저히 블랙박스로 취급되어야 합니다. 그러나 상속을 받기 위해서는 부모의 모든 것을 알고 있어야 합니다. 언제 protected 메서드가 호출되는지, 내부 필드나 상태가 어떤 경우에 어떤 시나리오로 업데이트 되는지, 어떤 메소드를 오버라이드 해도 되는지, 어떤 경우 반드시 슈퍼 메소드를 호출해야 하는지, 그리고 이 과정은 모든 부모 체계를 따라가며 필요해 집니다. 이것인 일반적으로 객체가 협력하는 패턴에 위배되며, 객체성에 손실을 가져옵니다.

둘 째, 부모가 변경될 때 자식은 그 변화를 안전하게 감당할 수 없습니다. 여기에 관해서 *연약한 부모 문제*를 읽어 보세요.

간단히 말해서, 이 일련의 과정이 안전하게 수행되는 것은 불가능 합니다. 다시 한 번 강조합니다. 확장이 의도된 클래스와 자세한 방법이 기술된 문서가 있지 않은이상, 상속을 통해 정확히 자기가 원하는 부분만 부작용 없이 확장하는 것은 *불가능*합니다. 단지 그것은 운에 의지하는 해킹일 뿐입니다.

컴포지트 패턴

그러나 우리는 우리가 필요로 하는 것들을 골라서 조립해서 사용할 경우, 우리는 각 컴포넌트에 대해 우리가 필요로 하는 것만 이해하면 됩니다. 각 컴포넌트는 버전업이 되더라도 퍼블릭 인터페이스에 기술된 약속을 항상 지켜줄 것입니다. 조립에 의해 나타날 수 있는 패턴은 무한대이며, 필요에 따라 다양하기 때문에, 이에 대한 모듈화는 무의미합니다.

필요한 UI를 조립한다는 것은 UI를 직접 정의하는 것과는 전혀 다른 아이디어 입니다. 이미 존재하는 UI들을 조립하는 것은 뷰가 아니라 컨트롤러에 가깝습니다. JFace는 바로 이러한 패턴에 아주 충실합니다. 쓸만하고 강력한 트리를 만들기 위해서 TreeViewer는 SWT의 Tree를 상속받지 않습니다. 다만 이를 이용할 뿐입니다. 마찬가지로 JFace의 많은 Dialog들 역시, SWT 의 Shell을 상속받지 않습니다. 마찬가지로 이를 이용할 뿐입니다.

기억하세요, UI를 상속받아 특화 하지말고, 조립만 하십시오.

그런데 많은 경우, 컴포넌트를 단지 조립하기 위해서 Shell이나 Composite를 상속받는 실수를 범하곤 합니다. 이 경우에는 다음과 같은 문제가 있을 수 있습니다.

상속을 통한 컴포넌트 조립의 문제점

예를 들어 Person 객체에 대해 이름과 나이를 표시하는 두개의 Label을 갖는 Composite를 상속받는 PersonView라는 클래스를 만들었다고 해 봅시다.

코드:

public class PersonView extends Composite {
	private Text nameText;
	private Text ageText;

	/**
	 * Create the composite.
	 * @param parent
	 * @param style
	 */
	public PersonView(Composite parent, int style) {
		super(parent, style);
		setLayout(new GridLayout(2, false));
		
		Label nameLabel = new Label(this, SWT.NONE);
		nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
		nameLabel.setText("이름");
		
		nameText = new Text(this, SWT.BORDER);
		nameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
		
		Label ageLabel = new Label(this, SWT.NONE);
		ageLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
		ageLabel.setText("나이");
		
		ageText = new Text(this, SWT.BORDER);
		ageText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
	}

	@Override
	protected void checkSubclass() {
		// SWT의 상속 불허 제약을 방지.
	}
}

이 클래스는 다음과 같은 문제가 있습니다:

  • PersonView는 너무나 많은 public API를 갖게 될 것입니다. 왜냐하면 Composite가 가진 public API 가 그대로 노출 될 것이기 때문이죠. 그러나 대부분의 개발자가 갖는 주요 관심사는 setInput(Person person)이라는 메서드 한 개 일 것입니다. 결국 여러분은 너무 쓰기 어려운 콤포넌트를 공급한 셈입니다.
  • 여러분은 Person 객체의 name이나 age값이 변경될 때 자동으로 업데이트 되면 좋겠다고 생각할 것입니다. 그러나 이를 구현하는 순간 이로 인해 여러분의 UI는 모델과의 응집성을 피할 수 없게 됩니다. 이 클래스 자체가 UI 이기 때문입니다. 그렇다고 이러한 기능을 외부에 작성하는 것도 애매모호해 집니다. 이 클래스의 주요임무가 Person객체를 보여주는 것인데 그것이 외부에 작성된다면 롤자체가 불분명해지기 때문입니다. 결국 MVC가 뒤죽박죽인 클래스가 나올 수 밖에 없습니다.

네이티브별 상이한 구현 체계

다음은 Cocoa SWT의 Control#getSize()의 내부입니다:

public Point getSize () {
	checkWidget();
	NSRect rect = topView().frame();
	return new Point((int)rect.width, (int)rect.height);
}

일반적인 JNI가 동일한 자바 클래스에 시스템 라이브러리만 바뀌는 것과 달리 SWT는 아예 그 클래스 자체가 달라지며, 다이나믹 네이밍 룩업으로 연결됩니다. NSRect라는 클래스가 보이는데 이는 오로지 맥용 SWT에만 존재합니다. 따라서, 여러분이 SWT의 동작을 수정할 때 protected 메서드등을 사용하거나 동작을 변경하는 것은 매우 위험한 행동입니다.

가능한한 그러한 작업을 수행하지 마십시오. 꼭 해야 한다면, 퍼블릭 클래스 스키마에 포함되는 메서드만 사용하십시오.

JFace 스타일 일반 원리

이제 우아함에 대해 좀 이야기 해 봅시다.

JFace -> SWT UI: 생성/조립
사용자 -> SWT UI: 상호작용
SWT UI -> JFace: 이벤트 전달
JFace->JFace: 컨트롤링
JFace->SWT UI: UI 업데이트
SWT UI -> JFace: 디스포즈 이벤트
JFace -> JFace: 사용한 자원들 정리

JFace에서는 필요한 위짓들을 직접 조립합니다. 대게는 생성자나 create(Composite parent)메서드를 통해 그러한 일을 수행합니다. 그리고 이러한 UI명세는 별개의 클래스로 모듈화 하지 않습니다 단지 create과정중에 조립될 뿐입니다.

일단 UI가 만들어지고 나면 JFace객체는 컨트롤링에만 집중하며, 그 과정중에 몇몇 헬퍼 객체의 도움을 받을 수도 있습니다. 예를 들어 LabelProvider나 ContentProvider등이 그렇습니다.

이러한 방식의 장점은 다음과 같습니다:

  • JFace 객체는 정확히 사용자가 관심을 가지는 메서드만을 나열 할 수 있다. (컨트롤러 작성을 쉽게 함)
  • JFace 객체는 모델의 언어와 뷰의 언어를 중간에서 번역하는 역할을 수행함으로써 모델과 뷰를 분리 시켜준다. 사용자는 SWT에 대해 몰라도 되며, 자신의 모델과 비즈니스 로직에만 충실하면 된다.
  • UI의 생명주기로 부터 JFace객체는 자유롭다.

UI의 배치나 레이아웃 데이터 지정등을 위해 대부분의 JFace객체는 getControl()을 제공하며 이를 통해 UI 구성작업 따로 분리 시킬 수 있습니다.

앞서의 예제를 JFace스타일로 작성하면 다음과 같습니다:

public class PersonView {
	private Composite composite;
	private Text ageText;
	private Text nameText;

	/**
	 * Create the composite.
	 * 
	 * @param parent
	 * @param style
	 * @wbp.parser.entryPoint
	 */
	public void create(Composite parent, int style) {
		this.composite = new Composite(parent, style);
		this.composite.setLayout(new GridLayout(2, false));

		Label nameLabel = new Label(this.composite, SWT.NONE);
		nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
		nameLabel.setText("이름");

		this.nameText = new Text(this.composite, SWT.BORDER);
		this.nameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));

		Label ageLabel = new Label(this.composite, SWT.NONE);
		ageLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
		ageLabel.setText("나이");

		this.ageText = new Text(this.composite, SWT.BORDER);
		this.ageText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));

		this.composite.addDisposeListener(new DisposeListener() {
			@Override
			public void widgetDisposed(DisposeEvent e) {
				dispose();
			}
		});
	}

	protected void dispose() {

	}

	public Control getControl() {
		return this.composite;
	}
}

이 클래스는 부모 클래스가 없기 때문에 깔끔한 API의 공급이 가능하며, UI관련 처리는 getControl() 을 통해 가능합니다.

create메서드에 @wbp.parser.entryPoint 라는 어노테이션이 달려있는데, 이는 윈도우즈 빌더 프로가 이곳이 UI 를 조립하는 지점임을 알게 해 줍니다.

그렇다면 SWT의 상속은 전혀 불필요한가?

그렇지는 않습니다. SWT가 반대하는 것은 이미 의도된 컨트롤의 동작을 변경하거나, 단순 조립 목적으로 상속하는 것입니다. 그러나 전혀 새로운 종류의 위짓이 필요하다면 Canvas 를 상속받아 만들어야 합니다. 그러나 이 경우, 기술적으로 비록 상속이라고 하더라도, 개념상으로는 새로운 종류의 위짓을 만드는 것입니다.

SWT 상속이 필요한 경우의 예

Posted by 지이이이율
,

베지어 곡선을 이용해서 애니메이션 커브를 줄곧 계산했었는데요, 애니메이션 커브에서 베지어 연산은 좀 비효율적인 것 같아서, 그래퍼로 좀 정리를 했습니다. 위에서 부터 차례대로 다음과 같으며 꽤 괜찮은 성능을 보이는 함수 입니다.

  • Ease In
  • Ease Out
  • Ease In Out
Posted by 지이이이율
,

XML 1.1을 사용하면 1.0에서는 사용할 수 없던 제어문자를 어트리뷰트 값으로 사용할 수 있기 때문에, XML1.1로 변경하는 작업을 최근 진행했다. 그러자 이상하게도 간헐적으로 EMF가 모델을 XML로 부터 읽어들이지 못하는 문제가 발생했다.

주요 증세

  • PackageNotFound 예외
  • 패키지 URI가 널일 수 없다는 예외
  • 이상한 명칭의 EClass 를 찾지 못했다는 예외

원인

사실 이 문제가 XML 버전과 연관이 있다는 사실을 알아내는데만도 반나절 이상의 시간을 소비해야 했다.

문제는 JVM에 기본 포함된 저시스 SAX파서가 XML 1.1의 경우 몇몇 어트리뷰트를 토큰 레벨에서 부터 제대로 스캔하지 못하는 경우가 발생했기 때문이었다. 이로인해 EMF XML 계층에 잘못된 Sax Event가 전달되어, 그 중에서도 xsi:type 어트리뷰트 값이 엉뚱한 값이 들어가 타입명으로 부터 EClass를 찾지 못하거나, 어트리뷰트 값으로 부터 패키지 URI를 파싱하지 못해 문제가 발생한다.

이 문제는 EMF의 XMLLoadImpl을 개인화 하여, 파서를 교체하여 해결 할 수 있다. 필자의 경우, 문제를 해결하기 위해 저시스 2.11 (XML 1.1스키마용 베타) 버전을 다운 받아 빌드 패스에 넣고, 아래와 같이 XMLLoadImpl을 개인화하여 문제를 수정했다.

수정 코드:

public class MyXMLLoadImpl extends XMILoadImpl {
	@Override
	protected SAXParser makeParser() throws 
		ParserConfigurationException, SAXException {
		SAXParserFactory f = 
			SAXParserFactory.newInstance(
				SAXParserFactoryImpl.class.getCanonicalName(), 
				getClass().getClassLoader()
			);
		return f.newSAXParser();
	}
}

EMF 로딩 커스터마이징 하기도 함께 읽어 보세요.

'EMF' 카테고리의 다른 글

How to Customize EMF Loading  (0) 2010.09.27
EMF 트랜잭션 관리  (0) 2009.06.08
Posted by 지이이이율
,

오늘은 다른 사람이 만든 커맨드를 수행하되, 워크밴치 셀렉션을 직접 바꾸지 않고도 개발자가 원하는 셀렉션 모델에 대해 그 비즈니스 로직이 수행되게 하는 방법을 배워 보겠습니다.

커맨드의 수행 과정

CCI -> 핸들러 서비스: 커맨드 제출
핸들러 서비스 -> 평가 서비스: 컨텍스트 요청
평가 서비스 -> 핸들러 서비스: 컨텍스트 공급
핸들러 서비스 -> 핸들러 서비스: 핸들러 결정
핸들러 서비스 -> 핸들러: 실행 이벤트
핸들러 -> 핸들러 : 비즈니스 로직 수행

CCI (커맨드 컨트리뷰션 아이템)

JFace 를 공부하신 분이라면 ActionContributionItem(ACI) 을 기억 하실 겁니다. ACI가 액션을 툴바, 메뉴, 컴포지트등에 표현하고 액션의 상태가 변경될 때마다 표현한 UI를 업데이트하며, 사용자가 UI를 누르면 Action 을 수행하게 하는 임무를 갖고 있는 것 처럼, CCI는 커맨드를 UI에 기여하고 업데이트 시키며, UI가 눌렸을 때 커맨드를 수행합니다.

단, 액션은 비즈니스 로직을 직접 가진 반면, 커맨드는 스트링일 뿐입니다.

커맨드가 커맨드 플랫폼에 제출 되면, 핸들러 서비스는 현재 상황 (IEvaluationContext)에 따라 적절한 핸들러를 선택하고, 커맨드를 처리하게 합니다.

핸들러들은 확장을 통해 기술 되는데 특정 뷰가 활성 상태일 때, 또는 워크벤치의 선택 모델이 특정 타입일 때와 같은 특정 문맥(context)에서 활성화 되고 enable 되어야 한다는 선언(코어 익스프레션)도 함께 가지고 있습니다. 핸들러가 결정되면 핸들러의 execute(ExecutionEvent)가 호출되고, 이 안에 비즈니스 로직이 기술됩니다.

CCI들은 이러한 문맥에 따라 가용한 핸들러가 없는 경우, 기여한 UI를 disable 시키는 등의 작업도 수행합니다. 이렇게 커맨드가 화면상에 표현된 것들을 UIElement 라고 부릅니다.

핸들러

핸들러는 실행 될 때 ExecutionEvent를 공급 받습니다. 이 안에는 어플리케이션의 현재 문맥 상태, 파라미터 값과 같은 여러 정보가 들어 있으며 핸들러의 구현자는 HanlderUtil을 이용하여 이 정보를 보다 쉽게 억세스 할 수 있습니다.

우리의 목표는 핸들러의 비즈니스 로직을 이용하되, 핸들러가 워크벤치 셀렉션을 처리하는 것이 아닌 우리가 원하는 모델을 처리해 주길 원하는 것입니다. 그러나 핸들러와 커맨드는 우리가 만든 것이 아니므로, 내부 구현을 바꾸지 않고 이를 달성할 가장 좋은 방법은 핸들러의 입력 모델인 Execution Event 를 조작하여 공급하는 것입니다.

코드

IHandlerService handlerService =
	 (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class);

ParameterizedCommandFactory factory 
	= new ParameterizedCommandFactory("someCommandId");

ParameterizedCommand command = factory.buildCommand();

IEvaluationService evaluationService = (IEvaluationService) 
	PlatformUI.getWorkbench().getService(IEvaluationService.class);

IEvaluationContext state = evaluationService.getCurrentState();
	
StructuredSelection selection = // 셀렉션을 만듬
state.addVariable("selection", selection);

handlerService.executeCommandInContext(command, null, state);

4~7 라인은 수행할 커맨드 ID를 이용하여 커맨드를 만드는 과정입니다.

ParameterizedCommandFactory는 파라미터 커맨드를 쉽게 만들 수 있게 해주는 유틸리티로, 이클립스의 기본 요소가 아닙니다. 여기에서 다운로드 받을 수 있습니다.

9~15 라인은 해당 커맨드가 수행될 환경을 조작하는 작업입니다.

우선 평가 서비스로 부터 현재 상태를 얻어 selecton의 값을 우리가 원하는 형태로 고쳐 씁니다. 만약 핸들러가 <with />익스 프레션으로 다른 변수를 접근하여 enableWhen이나 activeWhen에 사용한다면 그런 변수도 여기서 줄 수 있습니다.

17 라인에서는 조작된 환경을 바탕으로 커맨드를 제출 하여 수행하게 합니다. 두번째 인자가 null인 이유는 원래 이 자리엔 최초 이 일이 발생시킨 원인인 UI Event를 넣는 자리인데, 이 경우 프로그래밍으로 커맨드를 수행하는 것이므로, 이벤트가 따로 존재하지 않습니다.

의미와 응용

Action 개체는 UI와 비즈니스 로직의 결합도가 굉장히 심합니다. ACI계층이 있어도 이 ACI는 Action으로 부터 텍스트, 레이블, 아이콘등을 가져오기 때문에, 초기 화면을 구성할 때 비즈니스 로직이 담긴 Action 클래스들을 모두 로드해야 합니다. 반면 Action은 run 을 직접 호출함으로써 개발자는 손 쉽게 비즈니스 로직만 리유즈가 가능하며, 해당 액션의 필드를 변경하여 동작을 수정해가며 재사용 가능합니다. (물론 이 Action 클래스에 대한 가시성이 있다는 전제하에)

그러나 핸들러는 기본적으로 비공개성향 (대부분 인터널 패키지)이 강하고 커맨드 ID만 외부에 노출되어 입맛에 맞게 그 비즈니스 로직을 리유즈 하는 것이 난해합니다. 이 포스트에서 익힌 테크닉을 이용하면 네비게이터에서 숨겨져 보이지 않는 파일을 선택하고 삭제 명령을 수행한다던가, GUI테스트 시에 목업으로 사용한다던가, 특정 핸들러가 다른 핸들러들을 순차적으로 이용하여 데이터를 가공해 나간다던가 하는 식의 응용이 가능할 것입니다.

'PDE' 카테고리의 다른 글

빌드 자동화 하기 #1 - Hello Ant  (2) 2011.03.07
이클립스 플러그인에 DLL 포함시키기  (0) 2010.12.10
제품 빌드시 자주 발생하는 문제들  (0) 2010.11.26
Bundle과 Resource  (0) 2010.10.28
네이쳐와 빌더  (0) 2010.10.15
Posted by 지이이이율
,

이 씨리즈의 문서는 이클립스 피쳐 프로젝트를 자동 빌드하고, 서명, 업데이트 사이트 및 리포지터리를 자동으로 업데이트 하는 방법을 익히게 하는 것을 목적으로 합니다. 초보적인 Ant이해를 가진 이클립스 플러그인 개발자를 대상으로 합니다.

ANT 파일 만들기

우선 빌드를 위한 빈 자바 프로젝트를 HelloAntBuild라는 이름으로 만듭니다. 자바 프로젝트로 만드는 이유는, Ant스크립트로 처리할 수 없는 일괄작업을 Java로 기술해야만 하는 경우를 대비하기 위해서 입니다.

그리고 HelloAnt.build.xml 라는 파일을 만들고 다음의 내용을 입력합니다:



	 
		Hello Ant!
	

XML 또는 텍스트 에디터를 닫고, 이 파일을 Ant Editor로 엽니다. 아웃라인 뷰에서, build를 우클릭한 뒤, [Run As] / [Ant Build] 를 차례로 선택하여, 화면에 Hello Ant!가 잘 찍히는 지 확인합니다.

수행결과

Eclipse Ant Task 사용하기

이클립스에서는 Ant에서 사용할 수 있는 유용한 태스크 태그(동작)와 프로퍼티(변수)들을 공급합니다. 단, 이를 사용하기 위해서는 앤트가 실행중인 이클립스와 동일한 JVM에서 수행되어야 합니다. 아웃라인 뷰에서 build를 우클릭한 뒤, [Run As] / [Ant Build ...] 을 선택하여, 앤트 실행 구성 편집기를 엽니다.

JRE 탭에서 Run in the Same JRE as the workspace를 선택합니다.

이 앤트 파일의 실행 구성을 팀원들과 공유하기 위하여, Common 탭 Save as 항목에서 실행 구성이 저장될 위치 (워크 스페이스 내 현재 프로젝트)를 선택해 줍니다. 앞으로 이 프로젝트를 체크아웃 받아 빌드를 실행하는 다른 사람도, 자동으로 이 실행 구성을 이용하게 됩니다.

이제 Run 또는 Debug 버튼을 눌러 앤트를 실행시켜보면, 이전과 마찬가지 실행결과를 보여주지만, 네비게이터를 살펴보면 프로젝트 안에 "프로젝트이름 HeloAnt.build.xml.launch" 라는 파일이 하나 생겨 있을 것입니다. 이 파일이 앤트 실행 구성을 담고 있습니다.

이클립스 프로퍼티 사용해 보기

앤트 파일을 다시 열고, 다음의 내용을 추가합니다:


	현재 실행중인 이클립스의 위치는 ${eclipse.home.location} 입니다.

이제 이 타깃을 실행 해 봅시다. 현재 실행중인 이클립스의 위치가 보인다면 잘 따라오고 계신겁니다 :)

플러그인 빌드 하기

하나의 플러그인이 빌드 되는 과정은 매우 단순합니다.

  1. 플러그인 디펜던시에 있는 모든 플러그인과, 그 플러그인들의 디펜던시를 모두 얻어 클래스 패스에 추가한 상태로, 모든 자바 파일을 컴파일 한다
  2. build.properties 및 manifest.mf 파일의 내용에 따라, 컴파일 결과물 및 리소스를 jar파일에 집어 넣는다.
  3. 필요한 경우 완성된 jar 파일을 서명

말은 쉽지만, 1번 단계부터 막막할 겁니다.

build.properties와, manifest.mf파일을 자동으로 분석해서 해야할 일을 자동으로 생성해 주는 녀석이 있다면 아주 편리하겠죠? 다행히 eclipse.buildScript 라는 태스크가 그 일을 지원합니다. 우리는 이 태스크 태그를 이용하여, 플러그인을 빌드하는 스크립트를 생성 한 뒤에 실행 할 것입니다. 이 전략을 그림으로 그려보면 다음과 같습니다:

Main Script -> Sub Script* : generate
Sub Script* -> Sub Script* : run
Main Script -> Sub Script* : delete

연습용 플러그인 만들기

이제 자동빌드를 연습해볼 마루타 프로젝트인 HelloAntPlugin 플러그인 프로젝트를 하나 만듭니다. 특별히 아무런 구현이나 내용이 없어도 됩니다.

플러그인 프로젝트를 만들었으면, 빌드 프로젝트의 HelloAnt.build.xml을 다시 열어 build-plugin-test라는 타깃을 앤트파일에 추가하고 아래의 내용을 채워 넣으세요:


	<eclipse.buildScript 
		elements="plugin@HelloAntPlugin" 
		baselocation="${eclipse.home}" 
		builddirectory="${basedir}/../" />
	<eclipse.refreshLocal resource="/" depth="infinite" />

eclipse.buildscript 태스크는 다음과 같은 속성을 가집니다:

  • elements : 앤트 빌드 스크립트를 생성할 대상. 타입@ID 형태로 지정합니다. 우리가 빌드할 것은 플러그인이고, 아이디는 HelloAntPlugin이므로 plugin@HelloAntPlugin이 됩니다.
  • baselocation : 대상을 빌드하는데 필요한 디펜던시를 어디에서 부터 얻을지 알려줍니다. 설치된 이클립스의 경로를 알려줬습니다. 개발 타겟이 다른경우, 이곳에 타겟 이클립스의 경로를 지정하면 됩니다.
  • builddirectory : 빌드 대상 요소를 찾는 공간을 의미합니다. ${basedir}은 기본값으로 실행될 앤트파일이 담긴 디렉토리가 되는데, 그것의 부모폴더로 기술했으므로 워크스페이스의 위치가 될 것입니다. (빌드 프로젝트의 부모는 워크스페이스이므로)

나머지 자세한 속성들은 이클립스 온라인 도움말에서 확인하십시오.

eclipse.refreshLocal 태그는 워크스페이스를 갱신하는 역할을 합니다. 앤트는 이클립스와 독립적인 플랫폼이어서 IResource를 이용하여 작업하지 않습니다. 표준 File API 나 커맨드 라인 명령도구를 이용하므로, 워크스페이스의 싱크가 깨집니다. 빌드 작업이 끝나면 꼭 이 태스크 태그를 이용하여 워크스페이스를 갱신 시켜 주어야 합니다.

새로 추가한 타겟을 실행해 보면, HelloAntPlugin 프로젝트 밑에 build.xml 과 javaCompiler...args 파일이 추가된 것을 알 수 있습니다.

생성된 스크립트 실행 해 보기

생성된 build.xml을 열어 보면 뒷골이 당겨 올 겁니다. 이 앤트스크립트를 역공학 하실 수 있는 분은 시도해 보시기 바랍니다.

생성된 build.xml을 열어서, 아웃라인 뷰에서 "@dot"를 선택하고 우클릭 한 뒤, [Debug As] / [Ant Build ...]을 선택 한 뒤, 실행 구성에서 앞서 말씀드린 것 처럼 Workspace 와 동일한 JRE를 사용하게 한 상태에서 실행 해 봅시다. 이 타깃은 소스를 컴파일 하는 역할을 합니다.

실행이 끝나면 이번에는 build.update.jar를 선택 한 뒤 다시 실행합니다.

실행이 완료되면, 플러그인 프로젝트를 우클릭 하고, [Refresh]를 선택하여 워크스페이스를 갱신합니다. eclipse.buildScript가 생성한 스크립트는 무인 실행용이어서, 사람이 결과물을 확인하지 않기 때문에 eclipse.refreshLocal을 자동으로 호출해주지 않습니다. 워크스페이스를 갱신해보면 위 그림처럼, 플러그인이 빌드된 jar파일과, 각 컴파일된 자바 클래스 파일이 구조대로 담긴 @dot 폴더가 생겼을 것입니다.

지금은 우리가 생성된 스크립트를 사람 손으로 실행 했지만, 이번에는 메인스크립트가 스크립트를 생성한 뒤 자동으로 실행하게 해 봅시다.

다른 스크립트 호출하기

이제 원래 빌드프로젝트로 돌아와 HelloAnt.build.xml을 다시 열고 build-plugin-test-2라는 타깃을 만들고 다음의 내용을 추가합니다:

 

	스크립트 생성중
	<eclipse.buildScript elements="plugin@HelloAntPlugin" baselocation="${eclipse.home}" builddirectory="${basedir}/../" />

	스크립트 실행중
	<ant antfile="../HelloAntPlugin/build.xml" target="@dot" inheritall="false" />
	<ant antfile="../HelloAntPlugin/build.xml" target="build.update.jar" inheritall="false" />
	<eclipse.refreshLocal resource="/" depth="infinite" />

ANT 태스크 태그

ant 태그의 속성은 매우 직관적입니다. antfile은 실행할 팔일을, 그리고 타깃을 지정했습니다. inheritall의 값으로 false를 줬는데, 이것은 메인스크립트에 선언된 프로퍼티를 호출될 스크립트에게 전달하겠느냐는 의미가 됩니다. 자식스크립트는 ${basedir}이 자기자신이 있는 곳이라고 생각하고 작성되었을 것이기 때문에, 보통은 프로퍼티를 상속해 주는 것은 위험합니다. 명시적으로 필요한 속성만 상속하는 것이 좋습니다. 이 방법은 나중에 소개합니다.

새로만든 타깃을 실행 해 보면, 플러그인 프로젝트 밑에 jar가 생성되는 것을 확인 할 수 있습니다.

수고하셨습니다, 다음 시간에는 feature.xml로 부터 모든 플러그인을 자동빌드하고 피쳐역시 빌드하는 방법과, 찌꺼기 파일들을 안전하게 제거하는 방법에 대해 알아보겠습니다.

좋아요를 누르지 않으면 이 씨리즈의 다음 문서가 업데이트 되지 않을 수도 있습니다.

Posted by 지이이이율
,

메뉴 컨트리뷰션을 이용하여 커맨드를 기여 한 경우, CommandContributionItem이 메뉴 매니저나, 툴바 매니저등에 포함되어 해당 커맨드를 기여하게 됩니다. 이 때, 스타일이 SWT.DROP_DOWN 인 경우, 이 기여의 ID가 메뉴 아이디로 사용될 수 있게 됩니다.

문제는 이 주소를 통해 기여한 커맨드 역시, CommandContributionItem을 통해 기여 되는 데, 이들은 영영 dispose 되지 않는 다는 점입니다. 이 문제는 CommandContributionItem#openDropDownMenu() 를 보면 더 확실해 집니다.

/**
 * Determines if the selection was on the dropdown affordance and, if so,
 * opens the drop down menu (populated using the same id as this item...
 * 
 * @param event
 * 		The SWT.Selection event to be tested
 * 
 * @return true iff a drop down menu was opened
 */
private boolean openDropDownMenu(Event event) {
	Widget item = event.widget;
	if (item != null) {
		int style = item.getStyle();
		if ((style & SWT.DROP_DOWN) != 0) {
			if (event.detail == 4) { // on drop-down button
				ToolItem ti = (ToolItem) item;

				final MenuManager menuManager = new MenuManager();
				Menu menu = menuManager.createContextMenu(ti.getParent());
				if (workbenchHelpSystem != null) {
					workbenchHelpSystem.setHelp(menu, helpContextId);
				}
				menuManager.addMenuListener(new IMenuListener() {
					public void menuAboutToShow(IMenuManager manager) {
						String id = getId();
						if (dropDownMenuOverride != null) {
							id = dropDownMenuOverride;
						}
						menuService.populateContributionManager(
								menuManager, "menu:" + id); //$NON-NLS-1$
					}
				});

				// position the menu below the drop down item
				Point point = ti.getParent().toDisplay(
						new Point(event.x, event.y));
				menu.setLocation(point.x, point.y); // waiting for SWT
				// 0.42
				menu.setVisible(true);
				return true; // we don't fire the action
			}
		}
	}

	return false;
}

메뉴를 만들기 위해 임시로 메뉴매니저를 사용하고, 플랫폼에 메뉴 서비스를 통해 기여시키지만, 릴리즈 및 디스포즈가 없습니다. menuManager가 로컬 변수 이므로, 집단 고립되서 GC에 수집될 것 같지만, 실제로는 그렇지 않습니다. 왜냐하면, 메뉴서비스와 이미 연결되어 버렸기 때문이지요.

사소한 릭일 것 같지만, CommandContributionItem은 핸들러 서비스, 커맨드 서비스와 결합할 뿐만 아니라 상황에 따라서는, 에디터의 셀렉션, 에디터 등과 같은 매우 비싼 객체를 참조하고 있는 경우도 심심찮게 발생합니다.

https://bugs.eclipse.org/bugs/show_bug.cgi?id=336584

Posted by 지이이이율
,

컬럼 위치를 드래그로 변경

TreeViewerColum viewerColumn = ...
viewerColumn.getColumn().setMovalble(true);

의외로 많은 사람들이 이 기능을 직접 구현하기 위해 삽을 듭니다. 또한 setColumnOrder()와 getColumnOrder()로 상태를 보존 할 수 있습니다.

컬럼에 정렬 아이콘 표시하기

TreeViewer viewer = ...;
viewer.getTree().setSortColumn(column);
viewer.getTree().setSortDirection(isAscending ? SWT.DOWN : SWT.UP);

이 역시, 많은 사람들이 column의 setIcon()을 통햐 직접 지정하는 실수를 많이 저지르고 있습니다. 이 두 메서드는 소팅 컬럼의 아이콘과는 별개로 위쪽 또는 아래방향 화살표의 정렬 아이콘을 추가적으로 표시할 뿐, 실제 정렬기능을 수행하지는 않습니다. 실제 정렬기능은 setSorter(ViewerSorter)를 이용하세요. 진입점은 column 위젯의 selection 이벤트 입니다.

Posted by 지이이이율
,

이 글은 SWT, the standard widget toolkit의 챕터1 섹션2의 내용을 일부 발췌하여, 이해하기 쉽게끔, 의미상으로 번역되고 편집되었습니다.

연약한 부모 문제

객체지향 언어에서 상속은, 소위 *연약한 부모 문제*라는 문제 때문에, 완벽하게 안전하다고 볼 수 없습니다.

*연약한 부모*라는 말은 C++세계에서 유래 했는데, 보통은 부모 클래스에 새로운 메서드나 필드를 추가하면, 그를 원래부터 상속받던 자식 클래스들이 모두 재 컴파일하기 전까지 작동하지 않거나, 메모리 할당량이 부정확해지던 문제를 지칭합니다.

자바의 경우 부모 클래스가 바뀌더라도, 네임 룩업 매커니즘이 알아서 정적인 클래스 호환문제를 자동으로 해결합니다. 심지어 개발자는 그 존재조차 알 필요가 없습니다. 그러나 연약한 부모 문제는 다이나믹한 영역도 가지고 있습니다.

예를 들어 자식 클래스는 부모 클래스가 내부 구현상으로 호출해줄 public 메서드를 오버라이드하여 확장되었을 수 있습니다. 이경우 정적인 클래스 스키마 뿐 만 아니라, 그 메서드를 호출 해 줄 것이라는 *사실*에 근거하여 확장이 이루어 진 것입니다. 그런데 부모 클래스의 구현이 변경되어 더이상 그 메서드를 호출해 주지 않는다면, 정적인 컴파일에는 문제가 없지만, 오작동하게 될 것입니다. 이런 경우 문제를 찾아내는 것은 매우 고된일이 될 것입니다.

SWT는 플랫폼 별 내부구현이 매우 다르고, 변화의 속도가 빠르기 때문에, 부모 클래스가 어떻게 작동한다 하는 *사실*에 근거하여 상속했다가는 큰 낭패를 볼 수 있습니다. 이러한 이유로 SWT의 위젯 클래스들은 임의 상속을 허용하지 않습니다.

강제 임시 상속

보통은 금지 되어있는상속을 꼭 받아야 하겠다면, 위젯의 checkSubclass()를 오버라이드 해야 합니다. 이 메서드는 기본적으로 허가된 확장이 아닐 경우 SWTException을 던지며, 생성된 직후에 호출 됩니다. 아래는 Label을 상속받아 setText를 오버라이드 한 예입니다:

Label label = new Label(shell, SWT.NONE) {
    protected void checkSubclass() {
        // 아무일도 안하게 오버라이드 함.
    }

    public void setText(String string) {
        System.out.println("Setting the string");
        super.setText(string);
    }
};

자바는 class 앞에 final이라는 키워드를 이용하여 간단하게 상속이 불가능하다고 태깅할 수 있습니다. 사실 아주 예전의 SWT는 이 키워드를 사용했습니다. 불행하게도, 이 방법은 지나치게 유연성이 부족함이 증명되었습니다. 특정한 경우 SWT의 클래스에서 에러가 발생되더라도, 개발자들이 이를 우회할 방법이 없었던 것입니다. 개발자는 임시 상속을 통한 패치나, 버전 호환성을 위한 트릭을 사용할 수 없이, 하염없이 SWT팀이 문제를 수정하고 릴리즈 할 때까지 기다려야 했습니다.

이러한 이유로 SWT 위젯들은, 다소 이상해 보이는 상속 제약이 있지만, 이러한 뒷 배경이 있습니다.

Posted by 지이이이율
,

위와 같은 구조 [os/플랫폼명/아키텍쳐명/]로 폴더를 만들고 DLL을 추가하면 됩니다. 물론 다른 플랫폼에서는 SO나 다른 라이브러리 파일을 추가하셔야 합니다.

플랫폼 명 및 아키텍트명에 대한 상수가 org.eclipse.core.runtime.Platform 에 선언되어있으니 참고하시기 바랍니다.

Posted by 지이이이율
,

이 문서는 큰 스케일에서도 트리나 테이블로 만들어진 뷰가 성능을 유지할 수 있는 방법을 설명합니다.

필요 선수지식

  • SWT Table 또는 Tree
  • JFace TableViewer 또는 TreeViewer
  • 글쓴이가 왠지 잘 생긴 사람일 것 같은 예감

기존 트리/테이블 뷰어의 문제점

JFace 뷰어 에서는 특정 모델에 대한 UI(트리아이템이나 테이블 아이템)를 업데이트 하기 위해서, 개발자가 UI와 모델의 관계를 일일히 고민하지 않아도 된다. 특정 모델과 연결된 UI를 갱신하기 위해서는 단지 아래와 같은 코드를 이용하면 된다:

// myModel 에 해당하는 테이블 로우(TableItem)을 갱신한다.
tableViewer.update(myModel, null);

개발자가 UI를 신경쓰지 않고 모델 레벨에서만, 집중력을 유지할 수 있도록 하는 이 우아한 방식의 문제점은, 모델의 갯수만큼 뷰(TableItem/TreeItem)을 만든다는 점이다. 모델이 아주 많은 구성요소를 갖거나, 그 구조의 변경이 많이 일어나는 경우, UI 비용도 그만큼 높아져 필연적으로 성능문제가 나타난다.

모델의 변경이 복잡하게 일어나더라도 모델은 대게 경량 객체이기 때문에 문제가 되지 않지만, UI갱신은 GDI자원을 이용할 뿐만 아니라, UI스레드에서만 수행되기 때문에, 조금만 과부하가 걸려도 사용자는 성능저하를 겪게 된다.

SWT.VIRTUAL

이클립스 3.2 부터, 트리와 테이블은 SWT.VIRTUAL 이라는 플래그를 새롭게 지원하기 시작했다.

SWT 레벨에서 VIRTUAL 플래그는 물리적으로 단순히 트리 아이템이나 테이블 아이템이 Widget.setData(Object) 메서드를 통해 data를 소유할 수 있도록 허가하는 역할 밖에 하지 않는다.

하지만, 이를 허가함으로 인하여 각각의 트리 아이템이나, 테이블 아이템들은 입력 모델을 가질 수 있게 되어, 결과적으로 마이크로 뷰어 역할을 수행할 수 있는 기반을 갖게 된다. 예를 들어, 아이템을 그대로 둔 체 data만 교체한다면, 테이블 아이템이나 트리 아이템은 완벽하게 스스로 MVC구조를 갖춘 재사용가능한 뷰어 역할을 할 수 있을 것이다.

JFace에서의 VIRTUAL 플래그

테이블 뷰어나 트리뷰어는 SWT.VIRTUAL 플래그가 있는 경우, 화면에 *보이는 만큼만* 아이템을 만들고, 스크롤이 되거나, 모델이 변경되면, UI를 새로 만들거나 파기하지 않고, *재사용*하게끔 한다. 이 전략은 사용하는 GDI 자원을 크게 줄여, 눈에 띄는 성능 향상을 가져온다. 물론 이러한 전략이 가능한 이유는 앞절에서 설명한 바와 같이, TableItem이나 TreeItem이 data를 가질 수 있기 때문이다.

TableViewer tableViewer = new TableViewer(container, SWT.VIRTUAL);

이 성능향상 전략의 핵심 아이디어는, 보이는 부분만 UI자원을 만들고 재 사용한다는데 있다. 일반적으로 이러한 작업을 SWT 수준에서 수행하는 것은 매우 까다롭고 힘든 일이다. 업데이트 해야하는 UI의 위치를 계산하고, 새로이 연결될 모델을 계산하여 업데이트 하는 일은 만만한 일이 아니다. JFace에 대한 숙련자가 아니라면, 실제로 그러한 일들이 어떻게 SWT수준에서 구현되는지, Keving Maltby가 작성한 튜토리얼을 참조해 볼 것을 권한다.

물론 TableViewer와, TreeViewer는 SWT.VIRTUAL 플래그만 주면 마법처럼 그러한 일들을 알아서 하는 고마운 친구들이다.

해시 룩업을 이용하여 매핑 및 탐색 속도를 향상

트리뷰어나 테이블 뷰어는, 어떤 방법으로 모델을 특정할 수 있는지 알지 못하기 때문에, 일일히 아이템들을 돌아다니며 정확히 비교해 보는 방법을 쓴다. StructuredViewer.setUseHashlookup(boolean) 메서드를 이용하여 모델 비교시 hash 값을 사용하도록 설정하면, 엘리먼트(모델) 과 아이템(뷰)를 매핑하고 탐색하는 속도가 월등히 빨라진다.

TableViewer myViewer = new TableViewer(container, SWT.VIRTUAL);
myViewer.setUseHashLookup(true);  // 인풋을 주기전에 호출 해 줘야 한다.

보통은 그런 경우가 드물지만, 여러분의 모델 객체가 값 객체 처럼, 여러객체가 동일 정체성을 가질 수 있다면, 여러분의 모델은 충분히 빠르고 신뢰할 수 있을 만한 hashCode() 메서드와, equals() 메서드를 구현해야 한다.

값 객체의 예:

/* 아래의 객체들은 서로 다른 레퍼런스이며, 다른 주소를 갖지만 의미적으로 동일한 객체들이다. */
Color c1 = new Color(d, 255, 0, 0);
Color c2 = new Color(d, 255, 0, 0);
Color c3 = d.getSystemColor(SWT.COLOR_RED);

이클립스에서는 IResource, IPath등이 대표적인 값 객체이다.

LazyContentProvider

게으름뱅이 전략(Lazy Pattern)은 총 계산량이 줄거나 실질적인 성능이 빨라지는 것은 아니지만, 적절한 시점으로 로드를 분산함으로써, 사용자가 성능저하를 느끼지 못하도록 만드는 재미있는 트릭이다.

예를 들어 100장의 섬네일이 있고, 각각 클릭하면 큰 그림이 열린다고 하자. 이 때, 미리 모든 큰 그림을 로드해 두면, 프로그램이 시작할 때 사용자는 100장의 그림들이 로드되는 동안 기다리다가, 입이 툭 튀어나올 것이다.

하지만 사용자가 클릭할 때 마다, 한장의 이미지만 그때 그때 로드하게 하면, 사용자는 그 시간을 눈치채지 못하고, 즉시 작동한다고 여기게 된다. 따라서, 사용자가 100장의 그림을 전부 클릭한다고 가정했을 때, 계산량이나 성능은 동일하지만 경험적 성능에는 큰 차이가 발생하게 된다.

한줄로 요약하면: 해야 할 일은 미룰 수 있을 때 까지 최대한 미루자. -ㅂ-/

Lazy Table Viewer

그러나, 막상 레이지 컨텐트 프로바이더의 인터페이스를 열어보면 당혹스러울 것이다.

public interface ILazyContentProvider extends IContentProvider {
	public void updateElement(int index);
	
	/* 이하의 메서드는 IContentProvider에서 선언 된 것이다. */
	public void dispose();
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput);
}

기존에 익숙한 IStructuredContentProvider나 ITreeContentProvider와는 전혀 다른 모양새를 하고 있다.

일반적인 컨텐트 프로바이더들은 피동적으로 JFace뷰어를 보조한다. 트리뷰어나 테이블 뷰어가 어떤 모델을 갱신하려고 할 때, 컨텐트 프로바이더에게 정보를 요청하고, 그 정보를 이용하여 뷰어가 갱신 작업을 수행한다:

진입점 -> 트리뷰어 : 업데이트 요청
트리뷰어 -> 컨텐트 프로바이더: 자식정보 요청
컨텐트 프로바이더 -> 트리뷰어 : 자식 정보
트리뷰어 -> 트리 : UI갱신

그러나 레이지 컨텐트 프로바이더들은 능동적으로 일한다. 뷰어가 갱신시점을 판단하고 만약 연결된 컨텐트 프로바이더가 레이지인 경우, 갱신 작업을 컨텐트 프로바이더에게 위임하게 된다. 따라서 컨텐트 프로바이더는 직접 UI를 갱신 해 주어야 한다. SWT 수준으로 이러한 갱신을 수행하는 것은 너무 복잡하기 때문에, JFace 트리뷰어와 테이블 뷰어는 이 때 사용할 API들을 제공한다.

진입점 -> 트리뷰어 : 업데이트 요청
트리뷰어 -> 컨텐트 프로바이더: 업데이트 요청
컨텐트 프로바이더 -> 트리 : UI 갱신

테이블 뷰어의 갱신 API:

public void setItemCount(int count);
public void replate(Object element, int index);

테이블 뷰어는 스크롤이 되거나, 명시적인 업데이트 요청을 받은 경우 레이지 컨텐트 프로바이더의 updateElement 메서드를 호출한다. 만약 스크롤이 일어나 3줄의 로우가 더 보이게 되는 상황이라면 그 일이 3번 일어난다. 이 때 item의 가상의 순서인 index를 전달 해 준다. 가상(Virtual)의 순서라고 설명한 이유는, 모델의 수만큼 아이템이 만들어지는 것이 아니라 화면에 보이는 수 만큼만 아이템이 만들어지기 때문이다.

개발자는 그러한 사실을 고려하지 않아도 되게끔 가상의 index를 이용하여 의사소통한다. 레이지 컨텐트 프로바이더는 그 위치에 표시되어야 할 모델을 계산 한 다음, TableViewer#replace(Object, index) 메서드를 이용하여 직접 업데이트 한다.

setItemCount 메서드를 이용하여, 전체 아이템(테이블 로우 UI)의 갯수를 지정할 수 있다. 실제로 그만큼 로우 UI가 만들어지지는 않으며, 단지 스크롤바를 적당하게 표시하는 데 등에 사용된다.

그러나 *가상*이든 *실제로 만들어지지 않는다는 사실*등에 유의할 필요는 없다. 그 만큼의 Item이 존재한다라고 생각하는 것이, 일을 쉽게 만들 뿐만 아니라, 이 플래그의 이름이 Virtual인 이유도 그런한 의도라고 볼 수 있다.

Lazy Tree Viewer

레이지 트리뷰어는 ILazyTreeCotnentProvider 나 ILazyTreePathContentProvider 를 컨텐트 프로바이더로 사용한다. 이 문서에서는 ILazyTreeCotnentProvider 를 기준으로 설명한다.

public interface ILazyTreeContentProvider extends IContentProvider {
	// 특정 트리 아이템과 연결된 모델을 교체해야 할 때 콜백.
	// TreeViewer#replace 를 이용해서 해당 작업을 해야 한다.
	public void updateElement(Object parent, int index);

	// 특정 노드의 자식의 수를 갱신해야 할 때 콜백 
	// 트리뷰어의 setChildCount 메서드를 이용해서 처리.
	public void updateChildCount(Object element, int currentChildCount);

	public Object getParent(Object element);

	/* 이하의 메서드는 IContentProvider에서 선언 된 것이다. */
	public void dispose();
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput);
}

트리 뷰어의 레이지 컨텐트 프로바이더도, 테이블과 큰 차이는 없다. 단지 모든 부모 노드에 대해 처리할 추가적인 작업들이 몇가지 더 있다.

트리뷰어의 갱신 API

public void replace(Object parentElementOrTreePath, int index, Object element);
public void setChildCount(Object elementOrTreePath, int count);

정리

트리나 테이블의 성능을 높이는 방법을 다시 정리하면 다음과 같다.

  1. SWT.VIRTUAL 플래그를 이용하여 필요한 만큼만 Item을 만들도록 함
  2. 뷰어가 모델과 UI를 맵할 때 해시를 사용하게 함
  3. 레이지 컨텐트 프로바이더를 사용함

코드로 나타내면 다음과 같다:

TableViewer myViewer = new TableViewer(container, SWT.VIRTUAL);
myViewer.setUseHashLookup(true);
myViewer.setContentProvider(new MyLazyProvider());

3번의 경우 기존 코드 수정이 필요한데, 이것이 부담스럽다면 1, 2번만 사용해도 눈에 띄는 성능향상을 볼 수 있다. 하지만 지금 "좋아요" 버튼을 누르지 않으면 아무것도 빨라지지 않는다.

'JFace/SWT' 카테고리의 다른 글

TreeViewer, 컬럼 이동 및, 정렬 표시  (1) 2011.01.10
SWT의 사정 - 위젯 상속을 불허하는 이유  (1) 2011.01.07
SWT, JFace 그리고 Dispose  (0) 2010.11.26
SWT에서의 한영전환  (0) 2010.09.29
Canvas와 더블 버퍼링  (0) 2010.08.16
Posted by 지이이이율
,