Header

  1. View current page

    Develop with pleasure!

Profile_image?t=1224205604&type=big
78

eclipse 워크벤치: 셀렉션 서비스 이용하기

Note: 2007/4/18 블로그에 작성한 글을 옮겨왔습니다.

 

eclipse.org의 많은 기술문서의 번역을 eclipse Plug-in & RCP 카페에서 확인할 수 있습니다.

원문: http://www.eclipse.org/articles/Article-WorkbenchSelections/article.html

번역 PDF 파일: WorkbenchSelectionSerivce.pdf

 

image001.jpg

eclipse 워크벤치: 셀렉션 서비스 이용하기 (Eclipse Workbench: Using the Selection Service)

요약

Eclipse 워크벤치(workbench)가 제공하는 셀렉션 서비스(selection service)는 워크벤치 윈도우(workbench window)의 다수의 파트(part)간의 효율적인 연결기법을 제공한다. 미리 정의된 셀렉션 메커니즘을 잘 이해하고 사용하면 플러그인의 설계를 깔끔하게 유지할 수 있다. 셀렉션 메커니즘이 적용된 플러그인은 워크벤치에 유연하게 통합가능하며 미래의 확장에 개방되어있다. [1]

  • 원문: By Marc R. Hoffmann, Mountainminds GmbH & Co. KG, hoffmann@mountainminds.com, April 14, 2006
  • 번역: 김성안, ccoroom@gmail.com, http://pragmatic.kr, 2007-04-17

 

서론

Eclipse 워크벤치는 IDE와 독립적인 어플리케이션을 위한 강력한 UI 프레임웍이다. 워크벤치는 높은 수준으로 통합되고 확장 가능한 사용자 인터페이스를 지원하기 위한 다수의 서비스를 제공한다. 워크벤치가 통합측면에서 제공하는 서비스 중 하나는 뷰 파트(view part)이다. 뷰 파트는 특정 객체에 대한 추가적인 정보를 제공하고 해당 객체가 워크벤치 윈도우의 어딘가에서 선택되면 자동적으로 내용을 갱신한다. 예를 들어, “프로퍼티(Properties)” 뷰는 워크벤치내의 어떤 요소가 선택되면 해당 요소에 대한 속성값 목록을 보여준다.

image002.jpg

전역 액션(global action)과 같이 워크벤치의 다른 영역에서도 현재의 셀렉션에 의존하기도 한다.[2]

 

플러그인 간의 “강결합(hard-wired)된 커뮤니케이션”을 하도록 구현하는 대신에 셀렉션 서비스를 이용할 수 있다. 셀렉션 서비스는 파트의 아이템 선택에 다른 파트가 반응하는 것으로부터 파트간의 결합도를 낮춘다.

 

본 문서는 셀렉션 서비스의 기능과 사용법을 개략적으로 살펴본다. 여기서 제공하는 샘플 플러그인은 워크벤치 셀렉션의 동작 방식을 보이며 디버깅을 위해서도 사용할 수 있다.

 

큰 그림

각각의 워크벤치 윈도우는 자신의 셀렉션 서비스 인스턴스를 가진다. 서비스는 현재 활성화된 파트의 셀렉션을 추적하고 변경 사항을 모든 등록된 리스너에 전달한다. 현재 활성화된 파트의 셀렉션을 변경하거나 다른 파트가 활성화되면 셀렉션 이벤트가 발생한다. 두 경우 모두 사용자에 의한 상호작용이나 직접 프로그래밍적인 방식으로 동작시킬 수 있다.

 

뷰는 트리나 테이블의 요소 또는 텍스트가 선택될 때 발생하는 셀렉션에 대해 어떤 파트에서 관심을 가지고 있는지 알 필요가 없다. 따라서 이미 존재하는 뷰의 셀렉션에 의존하는 새로운 뷰를 만들 수 있고 새로운 뷰를 생성하는 데는 이미 존재하는 뷰의 코드를 단 한 줄도 수정할 필요가 없다.

 

image003.gif

다음 절에서는 어디서 무슨 종류의 셀렉션이 발생하고 셀렉션이 어디로 전달되는지에 대해서 설명한다.

 

선택되어 셀렉션이 발생할 수 있는 것

사용자 관점에서 셀렉션은 테이블이나 트리 위짓(widget)과 같은 뷰어에서 선택에 의해 강조된 요소들이다. 에디터의 텍스트 중 일부도 셀력션이 될 수 있다. JFace의 MVC 구현이 도메인 모델 객체와 시각적 표현을 매핑시켜줌으로써 워크벤치 내의 각각의 시각적 요소는 뒷단에서는 자바 객체로 표현된다.

내부적으로 셀렉션은 워크벤치에서 선택된 그래픽 요소와 일치하는 모델 객체를 가지고 있는 자료 구조로 기본적으로 다음 두 종류의 셀렉션이 있다.

  • 객체 리스트

  • 텍스트

두 종류 모두 비어있는 리스트나 길이가 0인 텍스트 문자열을 가지는 비어있는 셀렉션일 수 있다. Eclipse 철학을 따라서 위의 자료구조는 인터페이스를 이용해 다음과 같이 정의된다.

image004.gif

IStructuredSelection은 객체의 집합을 가리키며 ITextSelectionIMarkSelection은 선택된 텍스트를 설명한다. 사용자의 편의를 위해 인터페이스의 기본적인 구현이 다음과 같이 제공된다.

  • org.eclipse.jface.viewers.StructuredSelection
  • org.eclipse.jface.text.TextSelection
  • org.eclipse.jface.text.MarkSelection

위 구현체들은 뷰어에서 저 수준의 SWT 이벤트를 ISelection 객체로 변환하기 위해 내부적으로 사용한다. 또, 다음과 같이 어플리케이션 코드 내에서 프로그래밍적으로 트리나 테이블의 요소를 선택하는 경우에도 유용하다.

  1.     ISelection sel = new StructuredSelection(presetElement);
  2.     treeviewer.setSelection(sel);

image005.gif함께 제공된 샘플 플러그인을 설치하고 다양한 뷰에서 어떤 종류의 셀렉션이 발생하는지 확인한다. "Workbench Selection"뷰는 선택된 요소나 텍스트와 함께 ISelection 구현 클래스를 보여준다.

 

워크벤치 윈도우에 셀렉션 알려주기

셀렉션을 제공하기 위한 셀렉션 프로바이더는 ISelectionProvider 인터페이스를 구현하며 모든 JFcae 뷰어는 셀렉션 프로바이더이다.

image006.gif

각각의 JFace 뷰어는 서로 다른 종류의 셀렉션을 사용하고 발생시킨다.

뷰어 셀렉션 종류
ComboViewer IStructuredSelection
ListViewer IStructuredSelection
TreeViewer IStructuredSelection
 +- CheckboxTreeViewer IStructuredSelection
TableViewer IStructuredSelection
 +- CheckboxTableViewer IStructuredSelection
TextViewer ITextSelection, IMarkSelection
 +- SourceViewer ITextSelection, IMarkSelection
     +- ProjectionViewer ITextSelection, IMarkSelection

사용자가 정의한 커스텀 뷰어도 ISelectionProvider 인터페이스를 구현하고 셀렉션 프로바이더가 될 수 있다.

뷰어를 가지고 있는 모든 워크벤치 파트는 각자의 뷰 사이트(view site)를 통해 뷰어를 셀렉션 프로바이더로 등록하는 것이 좋다.

  1.     getSite().setSelectionProvider(tableviewer);

당장은 다른 영역에서 사용하기 위한 셀렉션을 발생할 필요가 없을지라도 셀렉션 프로바이더로 등록함으로써 해당 플러그인을 자신이나 다른 플러그인 개발자들에 의한 미래의 확장에 대해서 열어둘 수 있다.

image007.gif자신의 뷰가 셀렉션 프로바이더로 제대로 등록되었는지 확인하기 위해 샘플 플러그인을 사용한다

 

현재 셀렉션 추적하기

일반적으로 워크벤치 윈도우는 여러 파트의 조립으로 구성된다. 윈도우를 구성하는 각각의 파트는 대부분 적어도 하나의 뷰어를 가진다. 워크벤치는 계속해서 윈도우 내의 현재 선택된 파트와 선택된 파트의 셀렉션을 추적한다. 워크벤치가 이러한 정보를 유지한다는 것으로부터 흥미로운 이야기가 시작된다. 플러그인의 구현에서 현재 선택된 파트와 파트의 셀렉션 정보에 접근하거나 셀렉션 변경에 대해 통보받기 위한 리스너를 등록할 수 있다는 것이다.

각각의 워크벤치 윈도우는 현재의 셀렉션을 추적하기 위해 ISelectionService 인터페이스의 구현을 가진다. 뷰 파트는 사이트를 통해 ISelectionService 구현의 참조를 얻을 수 있다.

  1. getSite().getWorkbenchWindow().getSelectionService()

셀렉션 서비스는 활성화된 파트나 특정 ID를 가지는 파트의 현재 선택을 안다.

  1.     ISelection getSelection()
  2.     ISelection getSelection(String partId)

일반적으로 뷰는 워크벤치 윈도우의 셀렉션 변화에 따라 반응한다. 이런 경우 윈도우의 현재 셀렉션 변경을 통보 받기 위해 ISelectionListener를 등록하는 것이 좋다.

  1.    void addSelectionListener(ISelectionListener listener)
       void removeSelectionListener(ISelectionListener listener)

이와 같은 방식으로 등록된 리스너는 활성화된 파트의 셀렉션이 변경되거나 다른 파트가 활성화되면 통보 받는다. 활성화된 파트의 셀렉션만이 전달되며 어플리케이션이 특정 파트에만 관심(해당 파트의 실제 활성화 여부와 상관없이)을 가지고 있다면 각각의 파트 id를 이용해 특정 파트를 위한 리스너를 등록할 수 있다.[3]

  1.     void addSelectionListener(String partId, ISelectionListener listener)

  2.     void removeSelectionListener(String partId, ISelectionListener listener)


이 방식은 현재 id에 해당하는 파트가 없어도 동작한다. 파트가 생성되자마자 파트의 초기 선택이 해당 파트에 등록된 리스너에 전달된다. 만약 리스너가 INullSelectionListener를 구현하면 파트가 소멸(dispose)될 때 null 셀렉션이 전달된다. (INullSelectionListener는 뒤에서 좀 더 자세히 다룬다)

 

셀렉션 리스너 구현하기

ISelectionListener (새 창으로 열기)는 하나의 메소드만을 가지는 간단한 인터페이스이다. 일반적인 구현은 다음과 같다.

  1.     private ISelectionListener mylistener = new ISelectionListener() {
           public void selectionChanged(IWorkbenchPart sourcepart, ISelection selection) {
    image008.gif       if (sourcepart != MyView.this &&
  2. image009.gif             selection instanceof IStructuredSelection) {
  3. image010.gif            doSomething(((IStructuredSelection) selection).toList());
    1.             }
              }
          };

위의 코드 조각에서 보인 것처럼 리스너를 구현할 때 요구사항에 따라 다음의 이슈를 고려한다.

  • 셀력션 리스너를 이용하면서 동시에 셀력션을 제공하는 경우 자신의 셀렉션 이벤트 처리를 제외시킨다image008.gif. 자신의 파트에서 사용자 셀렉션이 발생할 때 예상치 못한 결과를 피할 수 있다.
  • 처리할 수 있는 셀렉션 종류인지 아닌지 판단한다. (image009.gif)
  • 셀렉션으로부터 선택된 컨텐츠를 획득해 이를 처리한다. (image010.gif)

image007.gifISelectionListener 인터페이스와 JFace 뷰어에서 셀렉션 변경을 통보하기 위해 사용하는 ISelectionChangedListener 인터페이스를 혼용해서 사용하지 말자. [4]

셀렉션 리스너 제거하기

더 이상 이벤트를 처리할 수 없는 경우 셀렉션 리스너를 제거하는 걸 명심하자. 예를 들어, 뷰가 닫힐 때 뷰의 dispose() 메소드는 해당 뷰의 셀렉션 리스너를 삭제하기 위한 좋은 위치이다 image008.gif.[5]

  1.     public void dispose() {

  2.         ISelectionService s = getSite().getWorkbenchWindow().getSelectionService();

  3.         super.dispose();

    image008.gif     s.removeSelectionListener(mylistener);
  4.     }

 

셀렉션에 관한 추가적인 이슈

지금까지 대부분의 경우에 적합한 셀렉션 서비스의 핵심 기능에 초점을 맞춰서 진행했다. 이제부터 다룰 추가적인 이슈들은 구현 프로젝트에서 직면할 수 있는 문제들이다.

포스트 셀렉션

뷰를 탐색하다 보면 셀렉션 변경이 자주 발생한다. 특히, 긴 목록을 스크롤 하는데 키보드를 이용하거나 텍스트를 선택(drag)하기 위해 마우스를 사용하는 경우에 많은 셀렉션이 발생한다.[6] 이런 상황은 셀렉션 서비스의 리스너로 등록된 뷰어의 불필요한 갱신이 많이 발생하며 어플리케이션의 반응성이 떨어질 수 있다.

포스트 셀렉션 이벤트는 약간의 지연(delay)를 두고 전달된다. 지연시간 동안의 모든 중간과정의 셀렉션은 무시되고 오직 마지막 셀렉션만 전달된다. ISelectionService 인터페이스는 지연된 셀렉션 이벤트를 지원하는 리스너를 등록하기 위한 추가적인 메소드도 제공한다.

  1.     void addPostSelectionListener(ISelectionListener listener)
  2.     void removePostSelectionListener(ISelectionListener listener)
  3.     void addPostSelectionListener(String partId, ISelectionListener listener)
  4.     void removePostSelectionListener(String partId, ISelectionListener listener)


대부분의 뷰어는 성능과 관련된 이슈를 피하기 위해 이러한 방식으로 셀렉션 리스너를 등록하는 것이 좋을 것이다.

IPostSelectionProvider 인터페이스를 구현한 셀렉션 프로바이더는 이러한 방식이 지원 가능하다면(모든 JFace 뷰어는 지연된 이벤트를 지원한다) 지연된 이벤트를 전달할 책임을 가진다.
image005.gif 샘플 플러그인의 구조를 포스트 셀렉션 이벤트를 받도록 변경하고 어떤 경우에 이벤트가 발생하는지 확인해보자.

 

INullSelectionListener

ISelectionListener (새 창으로 열기) 인터페이스에 정의된 selectionChanged() 콜백(call-back) 메소드는 셀렉션이 발생한 파트와 함께 새로운 셀렉션을 인자 값으로 전달받는다.

  1.     public void selectionChanged(IWorkbenchPart part, ISelection selection);

INullSelectionListener (새 창으로 열기) 인터페이스는 ISelectionListener (새 창으로 열기)를 상속하지만 추가적인 메소드를 정의하지는 않는다. INullPointerListener는 null 인자가 전달되더라도 selectionChanged()가 통보 받기를 희망한다는 걸 표시하기 위한 순수한 마커(marker) 인터페이스로 사용된다. 셀렉션을 제공하는 곳이 하나도 없기 때문에 현재 셀렉션이 아무 것도 없다는 걸 알아야 할 상황이라면 유용하다. 다음이 그런 상황에 해당한다.

  • 활성화된 파트가 셀렉션 프로바이더를 설정하지 않았다.
  • 리스너를 등록한 특정 파트가 셀렉션 프로바이더를 등록하지 않았다.
  • 워크벤치 윈도우 안에 활성화된 페이지가 없다. 즉, 모든 파트가 닫혀있다.
  • 리스너를 등록한 특정파트가 닫혔다.

 

페이지의 셀렉션 서비스와 윈도우의 셀렉션 서비스

워크벤치 API를 신중히 학습한다면 두 개의 셀렉션 서비스가 있다는 사실을 발견할 수 있다. IWorkbenchPage (새 창으로 열기)는 ISelectionService (새 창으로 열기)이고 IWorkbenchWindow (새 창으로 열기)는 getSelectionService() 메소드를 가진다. 따라서 파트 구현에서 두 가지 방식으로 리스너를 등록할 수 있다.

  1.   getSite().getWorkbenchWindow().getSelectionService().addSelectionListener(l);

또는,

  1.   getSite().getPage().addSelectionListener(l);

eclipse 2.0부터 워크벤치 윈도우가 단일 페이지를 가지도록 제한되었기 때문에 두 가지 모두 완벽히 동일하다. 다만 리스너를 등록하고 삭제하는데 두 가지 셀렉션 서비스를 혼용해서 사용하지는 말자.

 

하나의 파트 내의 다중 셀렉션 프로바이더

파트의 사이트는 createPartControl() 메소드에서 등록될 오직 하나의 셀렉션 프로바이더를 받아들인다는 점을 주의한다.[7]

  1.     getSite().setSelectionProvider(provider);


셀렉션 프로바이더를 파트의 생명주기 동안 변경하는 것은 워크벤치에서 적절히 지원되지 않는다. "자바 계층(Java Hierarchy)" 뷰와 같이 셀렉션을 제공하는 다수의 뷰어를 가진 파트가 있다면 파트내의 활성화된 뷰어에 동적으로 위임하는 중재자 역할의 ISelectionProvider (새 창으로 열기)구현이 필요하다.

본 문서에서 제공하는 SelectionProviderIntermediate.java (새 창으로 열기)이 다중 셀렉션 프로바이더를 구현하는 시작점이 될 수 있다.

 

선택된 객체로 할 수 있는 작업

셀렉션 서비스가 서로 간의 셀렉션에 따라 상호작용하는 뷰 간의 결합도를 줄인다고 했다. 그렇지만 유용한 기능성을 제공하기 위해서는 뷰에서 선택된 객체를 다루는 작업이 여전히 남아있다. Eclipse 런타임 코어(runtime core)가 제공하는 어댑터 패턴(adapter pattern)을 살펴보자. 기존 모델 객체에 새로운 기능을 추가하는 것이 가능하고 반대로 새로 기여된 객체를 이용해 필요한 기능성을 제공하는 것이 가능하다.

예제 플러그인은 워크벤치 레이블 프로바이더(workbench label provider)를 이용해 이러한 방식을 수행한다. 워크벤치 레이블 프로바이더는 선택된 객체에 해당하는 아이콘과 텍스트 레이블을 얻어오기 위해서 IWorkbenchAdapter (새 창으로 열기)에 의존한다. 동일한 메커니즘이 “프로퍼티 뷰“에서 활용되고 있다. 더 자세한 사항은 "Take control of your properties" (새 창으로 열기) 문서를 참고한다.

 

예제 플러그인

위에서 설명한 기법을 보여주기 위한 작은 예제 플러그인을 제공한다. 자신의 셀렉션 프로바이더를 디버깅해볼 수 있는 추가적인 기능도 제공한다. 예제는 단순히 워크벤치의 현재 셀렉션을 그대로 보여주는 "Workbench Selection"이라는 새로운 뷰를 기여(contribute)하며 테이블이나 트리의 요소의 셀렉션 뿐 아니라 텍스트 셀렉션에 대해서도 동작한다.

 

예제 다운로드하고 동작하기

플러그인 프로젝트를 다운로드 하고 "File" 메뉴의 "Import..." 마법사를 통해 프로젝트를 워크스페이스로 가져온다. 마법사의 첫 번째 페이지에서 "Existing Project into Workspace"를 선택하고, 두 번째 페이지에서는 다운로드된 압축파일을 가져오기 위해 "Select archive file"를 선택한다.

예제 어플리케이션을 실행(launch)하기 위한 가장 빠른 방법은 플러그인 프로젝트에서 오른쪽 마우스를 클릭한 후 컨텍스트 메뉴에서 "Run As" → "Eclipse Application"를 선택하는 것이다. 실행된 워크벤치의 메뉴에서 "Window" → "Show View" → "Other..." 메뉴의 "Other" 카테고리에서 "Workbench Selection"을 선택해 활성화 시킨다.

이제 다양한 워크벤치 파트의 셀렉션을 이용할 수 있고 "Workbench Selection" 뷰가 셀렉션에 어떻게 반영하지는 볼 수 있다.

image011.gif

텍스트의 셀렉션에 대해서도 동일하게 동작한다.

image012.gif

예제는 SelectionView.java 파일에 위에서 논의된 기법이 적용된 단일 클래스로 구현되어있다. 예제 코드를 읽을 때 다음의 사항을 관심 있게 보자.

  • ISelectionListener 구현에서 자신의 뷰의 셀렉션에는 반응하지 않음을 확인한다.
  • 셀렉션이 발생한 파트의 제목과 ISelection의 구현 클래스가 뷰의 설명 영역에 위치한다. (뷰의 위쪽 탭 아래 회색 영역)
  • 객체 리스트와 텍스트 영역을 위한 두 개의 뷰어를 사용한다. PageBook을 이용해서 뷰어 구현을 스위칭한다.
  • 객체 리스트를 위한 뷰어는 자신을 셀렉션 프로바이더로 등록한다. "Workbench Selection" 뷰에 나타난 목록 중 하나의 요소를 선택하면 해당 요소의 속성이 “프로퍼티”뷰에 보임을 확인한다.

 

결론

Eclipse 워크벤치가 제공하는 메커니즘은 사용이 간단하면서도 강력하다. 셀렉션 기법을 사용하지 않는 플러그인은 워크벤치의 다른 요소들과 형편없게 통합되는 결과를 초래하고 확장하기도 어렵다. 이런 함정을 피하기 위해서는 셀렉션이 발생할 수 있는 순간에 직면하면 다음의 규칙을 따른다.

  • 뷰간의 직접적인 강결합 커뮤니케이션을 피한다. 어떤 뷰에서 다른 뷰의 선택에 응답해야 한다면 ISelectionService를 사용한다.
  • 협력적이고 미래의 확장에 대해서 열려있어라. 언제나 뷰어를 파트 사이트를 이용해 셀렉션 프로바이더로 등록한다.
  • 새로운 뷰를 생성하는 대신에 적합하다면 “프로퍼티” 뷰와 같은 셀렉션에 종속적인 기존 뷰를 활용한다.

 

참고문헌

 

본 문서의 문제점에 대해서 논의하고자 하면 bug 112193을 방문한다.

 

기타 제공되는 자료

본 문서는 다음의 자료를 제공한다.

 

[1] 역주: Selection, Provider 등은 플러그인 개발 시 인터페이스 이름 등으로 많이 사용되며 의사소통의 수단으로 사용되므로 선택, 제공자 대신 셀렉션, 프로바이더와 같이 번역했다.

[2] 역주: 대표적으로 IActionDelegate 인터페이스는 현재의 셀렉션에 따라 액션의 활성/비활성 상태의 변경 여부를 결정하는 등의 작업할 수 있도록 selectionChanged(IAction action, ISelection selection) 메소드를 제공한다.

[3] 역주: 활성화된 파트에 대한 사용자의 직접적인 셀렉션이 아닌 Job API나 스레드 등을 통해 비활성화된 파트에서의 셀렉션이 발생하고 이를 전달받아야 한다면, 전체 셀렉션에 대한 리스너 대신에 특정 파트에 대한 리스너를 이용한다. Eclipse가 관리하는 전체 셀렉션에 대한 리스너 목록은 활성화된 파트의 리스너 리스트에 추가되고, 파트가 비활성화되면 해당 파트의 리스너 목록에서 제거된다. 즉, 활성화된 상태에서 발생한 셀렉션만이 전체 셀렉션에 대한 리스너에 전달된다.

[4] 역주: 이 경우 인터페이스 이름도 비슷하지만 구현 메소드의 이름도 비슷하기 때문에 혼란을 주며, 하나의 셀렉션을 중복해서 처리할 수 있으므로 신중히 사용해야 한다.

[5] 역주: 이 방식은 eclipse 전체 리스너 목록에서 삭제하는 것이지 특정 파트에 등록된 리스너의 삭제와는 관계가 없다. 전체 셀렉션이 아니라 특정 파트에 대한 리스너를 등록한 경우 removeSelectionListener(id, listener)와 같이 특정 파트에 대한 셀렉션 리스너를 삭제해야 한다. eclipse는 전체 리스너 목록과 별도로 각각의 셀렉션 프로바이더가 리스너 목록을 유지한다.

[6] 역주: 키보드를 이용해 트리나 테이블을 스크롤 하면 실제 목적하는 요소까지 이동하는 경로에 있는 모든 요소에 대한 셀렉션이 발생한다. 또, 텍스트를 선택하는 것도 글자 하나가 선택 될 때 마다 셀렉션이 발생하므로 오버헤드가 있다.

[7] 역주: 이름이 add로 시작하지 않고 set으로 시작한다는 점을 기억한다.

 

History

Last edited on 08/12/2007 21:18 by 김성안

Comments (0)

You must log in to leave a comment. Please sign in.