 |  |
|
난이도 : 중급 Randy Hudson, Software developer, IBM
2003 년 7 월 29 일 2007 년 12 월 31 일 수정 Graphical Editing Framework (GEF)를 사용하여 Eclipse 기반 애플리케이션을 구현하는 초기 단계를 설명합니다. GEF는 상태 다이어그램, 액티비티 다이어그램, 클래스 다이어그램, AWT용 GUI 빌더, Swing과 SWT, 프로세스 플로우 에디터를 포함하여, Eclipse용 다양한 애플리케이션을 구현하는데 사용되었습니다. Eclipse와 GEF는 모두 오픈 소스 기술입니다. 또한 IBM WebSphere® Studio Workbench에도 포함되어 있습니다.
편집자 주: "Graphical Editing Framework를 사용하여 Eclipse 기반 애플리케이션 만들기" 의 업데이트 버전은 2007년 6월에 업데이트 되었습니다. 이 버전은 참조용으로 사용할 수 있습니다. 새로운 기술자료에서 GEF의 새로운 기능을 이해해 보십시오.
Graphical Editing Framework (GEF)의 사용 단계를 설명한다. 각 단계를 완전히 설명하는 대신, 애플리케이션 모델의 하위 세트를 사용하여 이를 작동시키는 방법을 설명할 것이다. 이를테면, 초기에는 연결을 무시하거나 애플리케이션의 그래픽 엘리먼트 유형의 하위 세트에만 초점을 맞출 수도 있다.
GEF 개요
GEF는 여러분이 그래픽으로 디스플레이 및 편집하고자 하는 모델이 있는 것으로 간주한다. 이를 수행하기 위해, GEF는 Eclipse 워크벤치 어디에서나 사용될 수 있는 뷰어(EditPartViewer 유형)를 제공한다. JFace 뷰어와 마찬가지로, GEF 뷰어는 SWT Control에 대한 어댑터이다. 비슷한 점이 이것뿐만이 아니다. GEF 뷰어는 Model-View-Controller (MVC) 아키텍처에 기반하고 있다.
컨트롤러는 뷰와 모델을 이어준다. (그림 1) 각 컨트롤러 또는 EditPart는 모델을 뷰에 매핑하고, 모델을 수정한다. EditPart는 모델을 관찰하고 뷰를 업데이트 하여 모델 상태에 변경 사항들을 반영한다. EditPart 는 사용자와 상호 작동하는 객체들이다. EditPart는 나중에 상세히 설명하도록 하겠다.
그림 1. Model-View-Controller
GEF는 두 개의 뷰어 유형들을 제공한다. 그래픽 기반과 트리(tree) 기반이다. 각각 다양한 유형의 뷰를 담고 있다. 그래픽 뷰어는 SWT Canvas에 그려진 그림들을 사용한다. 그림들은 Draw2D 플러그인에서 정의되는데, 이것은 GEF의 일부로서 포함된다. TreeViewer는 뷰에 SWT Tree 와 TreeItem을 사용한다.
1단계: 고유의 모델 가져오기
GEF는 모델에 대해 아무것도 모르고 있다. 아래 기술된 프로퍼티에 부합한다면 어떤 모델 유형이든 작동한다.
모델 안에 무엇이 있는가?
모델에는 모든 것이 있다. 모델은 유일하게 영속적이고 복원된다. 애플리케이션은 이 모델에 모든 중요한 데이터를 저장해야 한다. 편집, 실행 취소, 재실행 동안 지속되는 유일한 것은 모델이다. 그림과 EditPart는 가비지 컬렉션 되고 시간이 지나면 재생성 된다.
사용자가 EditPart와 상호 작동할 때, 모델은 EditPart로 직접 조작될 수 없다. 대신, 변경 사항들을 캡슐화 하는 Command가 생성된다. Command는 사용자의 인터랙션의 유효성 검사를 수행하고 실행 취소와 재실행 작동을 지원한다.
엄밀히 말하면, Command는 모델의 개념적인 부분이다. 그 자체로는 모델이 아니고, 모델이 편집되는 방식이 된다. Command는 실행 취소가 가능한 변경 사항들을 수행한다. 이상적으로는, Command는 모델에 대해서만 알고 있어야 한다. EditPart나 그림을 참조하지 않도록 해야 한다. 마찬가지로, Command는 사용자 인터페이스(팝업 다이얼로그)를 호출하지 않아야 한다.
두 개의 모델
단순한 GEF 애플리케이션은 다이어그램을 그릴 수 있는 에디터이다. (여기에서, 다이어그램은 클래스 다이어그램이 아닌 단순한 그림이다.) 다이어그램은 특정 모양으로 모델링 될 수 있다. 모양은 위치, 색상에 대한 프로퍼티를 갖고, 어려 모양들의 그룹을 형성한다. 지금까지 특이한 사항은 없고 이전 요구 사항이 쉽게 관리된다. (그림 2)
그림 2. 단순한 모델
또 다른 GEF 애플리케이션은 UML 에디터(클래스 다이어그램 에디터)이다. 이 다이어그램의 한 가지 중요한 정보 위치는 클래스가 나타나는 (x, y) 위치이다. 이 모델은 x와 y 프로퍼티를 갖기 때문에 클래스를 기술해야 한다고 생각하면 된다. 대부분의 개발자들은 모델에 이치에 맞지 않은 애트리뷰트를 채우지 않도록 해야 한다. 이 같은 애플리케이션에서 "비즈니스" 모델(business model)이라는 용어는 중요한 상세 의미들이 저장되는 기본 모델을 의미한다. 다이어그램 정보는 뷰 모델에 저장된다. (비즈니스 모델의 특정 "뷰"를 의미한다. 객체는 하나의 다이어그램에서 여러 번 보여진다.) 가끔씩, 작업 공간에 분할(split)이 반영된다. 여기에서, 다른 리소스들은 다이어그램과 비즈니스 모델을 개별적으로 저장하는데 사용된다. 같은 비즈니스 모델에 대한 여러 다이어그램들이 있을 수도 있다. (그림 3)
그림 3. 비즈니스와 뷰 모델로 분할
모델이 두 부분들로 분할되거나 여러 리소스들로 분할되든, GEF에는 문제가 되지 않는다. 모델이라는 용어는 전체 애플리케이션 모델을 의미한다. 스크린 상의 객체는 모델의 다중 객체들에 상응한다. GEF는 이 같은 매핑을 쉽게 핸들하도록 디자인 된다.
공지 전략
뷰에 대한 업데이트는 모델로부터 온 공지의 결과이다. 모델은 공지 장치를 제공해야 하고, 이 장치는 애플리케이션에 해당 업데이트로 매핑 되어야 한다. 예외는 읽기 전용 모델 또는 파일 시스템이나 원격 연결 같은 공지할 수 없는 모델이다.
공지 전략은 분산(객체 별) 또는 중앙화(도메인 별) 된다. 도메인 공지는 모델에서의 모든 객체의 변경 사항을 알고 있고 이러한 변경 사항들을 도메인 리스너에게 알린다. 애플리케이션이 이러한 공지 모델을 사용한다면 뷰어에 도메인 리스너를 추가해야 한다. 그 리스너가 변경 사항 공지를 받으면, 영향을 받는 EditPart를 검색하고 변경 사항들을 처리한다. 애플리케이션이 분산 공지를 사용하면 각 EditPart는 고유의 리스너를 해당 모델 객체에 추가한다.
2 단계: 뷰 정의하기
다음 단계는 Draw2D 플러그인의 그림을 사용하여 모델을 디스플레이 하는 방법을 결정하는 단계이다. 일부 그림들은 모델의 객체들 중 하나를 디스플레이 하기 위해 직접 사용될 수 있다. 예를 들어, Label 그림은 Image와 String을 디스플레이 하는데 사용될 수 있다. 가끔씩, 원하는 결과가 다중 그림들, 레이아웃 매니저, 보더를 합성하여 만들어진다. 마지막으로, 애플리케이션에 맞는 방식으로 그려지는 그림을 만든다.
그림과 레이아웃을 합성 또는 구현하는 부분은 Draw2D 개발자 가이드(GEF SDK)를 참조하기 바란다.
GEF에서 Draw2D를 사용하면 프로젝트 관리가 더 쉬워지고, 다음 가이드라인을 따라가면 변화하는 요구 사항들에 보다 유연하게 대처할 수 있다:
-
바퀴를 다시 만들지 말라. 제공된 레이아웃 매니저들을 결합하여 대부분의 작업을 할 수 있다. 툴바 레이아웃(수직 또는 수평 방향)과 보더 레이아웃을 결합하여 여러 그림들을 합성할 수 있다. 마지막 보루로써, 레이아웃 매니저를 직접 작성할 수도 있다. GEF에서 제공되는 팔레트를 참조할 수 있다. 이 팔레트는 Draw2D의 표준 그림들과 레이아웃을 사용하여 실행된다.
-
EditPart와 그림을 명확히 분리하라. EditPart가 여러 그림, 레이아웃, 보더들의 복합적인 구조를 사용한다면 많은 상세들을 EditPart에 숨긴다. 좋은 생각은 아니지만 EditPart에서 모든 것을 구현할 수 있다. 하지만, 이렇게 하면 컨트롤러와 뷰가 명확히 분리되지 않는다. EditPart는 그림 구조를 잘 알고 있기 때문에 그 구조를 비슷한 EditPart와 함께 재사용 하는 것은 불가능 하다. 또한 모양이나 구조를 변경하면 예기치 않은 버그가 생긴다. 대신, 상세 구조를 숨기는 Figure 고유의 하위 클래스를 만들어야 한다. 그런 다음, EditPart(컨트롤러)가 뷰를 업데이트 하기 위해 사용하는 하위 클래스에 최소 API를 정의한다. 영역의 분리(separation of concerns)라고도 하는 이러한 관행으로 재사용률이 높아지고 버그도 적어진다.
-
그림에서 모델 또는 EditPart를 참조하지 말라. 그림은 EditPart나 모델에 액세스 해서는 안된다. 어떤 경우, EditPart는 스스로를 리스너로서 그림에 추가하지만, 이는 EditPart로서가 아닌 리스너로서 간주 될 것이다. 이러한 디커플링(de-coupling) 관행으로 재사용률이 높아진다.
-
콘텐트 페인(pane)을 사용하라. 가끔씩 다른 그래픽 엘리먼트를 저장 할 컨테이너가 있지만 컨테이너 외부를 꾸며야 한다. 예를 들어, UML 클래스는 일반적으로 박스로 보여지는데, 여기에서, 윗 부분은 클래스 이름과 일부 표준들로 레이블링 되고, 아래 부분은 애트리뷰트와 메소드를 위해 보존된다. 이는 여러 그림들을 합성하여 수행된다. 첫 번째 그림은 클래스용 타이틀 박스이고, 또 다른 그림은 콘텐트 페인이 될 것이다. 이 그림에는 결국 애트리뷰트와 메소드용 그림이 포함될 것이다. 나중에 EditPart 구현을 작성할 때 콘텐트 페인이 모든 자식 엘리먼트에 대한 부모로서 사용되어야 한다는 것을 나타내는 것은 간단하다.
 |

|
3 단계: EditPart 작성하기
다음에는, 모델과 뷰를 컨트롤러 또는 EditPart로 연결할 것이다. 이것은 GEF에 "프레임웍"을 설치하는 단계이다. 제공된 클래스들은 추상적이기 때문에, 클라이언트는 실제로 코드를 작성해야 한다. 하위 클래스를 만드는 것은 익숙한 일이고 모델에서 뷰를 매핑하는 가장 유연하고 단순한 방법일 것이다.
하위 클래스 만들기에 제공되는 세 개의 기본 구현들이 있다. 트리 뷰어에 나타나는 EditPart에는 AbstractTreeEditPart를 사용한다. 그래픽 뷰어에 AbstractGraphicalEditPart와 AbstractConnectionEditPart를 확장한다. 여기에서는 그래픽 EditPart에만 집중하기로 한다. 트리 뷰어에서도 같은 원리가 적용된다.
EditPart 라이프 사이클
EditPart를 작성하기 전에, 이들이 어디에서 오고, 더 이상 필요하지 않게 되었을 때는 어디로 가는지를 알아두는 것이 도움이 된다. 각 뷰어는 EditPart를 만들 때 팩토리를 사용하여 설정된다. 뷰어의 콘텐트를 설정할 때 그 뷰어에 대한 인풋을 나타내는 모델 객체를 제공한다. 인풋은 최상위 모델 객체이고, 이곳에서 다른 모든 객체들이 트래버스 될 수 있다. 뷰어는 팩토리를 사용하여 인풋 객체에 대한 EditPart 콘텐트를 만든다. 이때부터, 뷰어에 있는 각 EditPart는 자식과 (연결) EditPart를 설치 및 관리하면서, 새로운 EditPart가 필요할 때 뷰어가 설치 될 때까지 EditPart 팩토리로 위임한다. 새로운 모델 객체들은 사용자에 의해 추가되고, 객체가 나타나는 EditPart는 상응하는 EditPart를 만든다. 따라서, 각 EditPart가 만들어져서 부모 EditPart에 추가된 후에, 같은 일이 뷰에 발생한다. (그림 또는 트리 아이템)
EditPart는 사용자가 상응하는 모델 객체를 제거하자마자 파기된다. 사용자가 삭제를 실행 취소하면 다시 생성되어 복원된 객체를 나타내는 것은 다른 EditPart이다. EditPart는 장기 정보를 포함할 수 없고 Command에 의해 참조되어서는 안되기 때문이다.
여러분의 최초 EditPart: 콘텐트 EditPart
여러분이 작성한 최초의 EditPart는 다이어그램에 상응하는 EditPart이다. EditPart는 뷰어의 콘텐트로서 참조된다. 이는 모델의 최 상위 엘리먼트에 상응하고, 뷰어의 루트 EditPart에 의해 만들어 진다. (그림 4) 루트는 연결 레이어, 핸들 레이어, 줌(zoom) 또는 뷰어 레벨의 기타 기능 등 다양한 그래픽 레이어를 제공함으로써 콘텐트의 기반이 된다. 루트의 기능들은 모델 객체에 종속되지 않고, GEF는 루트에 사용 준비가 된 여러 구현들을 제공한다.
그림 4. 뷰어의 EditPart
이 그림은 화려하지도 않고 다이어그램의 자식들만 포함하게 될 빈 패널일 뿐이다. 이 그림은 불투명 하고, 다이어그램의 자식들을 배치 할 레이아웃 매니저에 의해 초기화 되어야 한다. 하지만, 이것은 구조를 갖게 된다. 다이어그램의 자식들은 리턴된 자식 모델 객체의 리스트에 의해 결정된다. Listing 1은 XYLayout을 사용하여 자식들을 배치하는 불투명한 그림을 만드는 콘텐트 EditPart 샘플이다.
Listing 1. 콘텐트 EditPart의 초기 구현
public class DiagramContentsEditPart extends AbstractGraphicalEditPart {
protected IFigure createFigure() {
Figure f = new Figure();
f.setOpaque(true);
f.setLayoutManager(new XYLayout());
return f;
}
protected void createEditPolicies() {
...
}
protected List getModelChildren() {
return ((MyModelType)getModel()).getDiagramChildren();
}
}
|
다이어그램 상의 아이템을 결정하기 위해 getModelChildren()이 실행된다. 이 메소드는 자식 모델 객체들(다이어그램의 노드들)의 리스트를 리턴한다. 수퍼 클래스는 모델 객체들의 리스트를 사용하여 상응하는 EditPart를 만든다. 새롭게 생성된 EditPart는 자식 EditPart 리스트에 추가된다. 이는 각각의 그림을 다이어그램의 그림에 추가한다. 기본적으로, 빈 리스트가 리턴될 것이고, 이는 자식이 없음을 나타낸다.
그래픽 요소가 강화된 EditPart
나머지 EditPart(다이어그램에 아이템 표현하기)는 그래픽으로 디스플레이 될 데이터를 갖게 될 것이다. 연결과 종속 연결 등 고유의 구조도 갖게 된다. 많은 GEF 애플리케이션들은 아이콘들간 연결로 아이콘을 레이블링 한다. EditPart는 Label을 그림으로서 사용할 것이고 모델은 레이블에 이름, 아이콘, 연결을 제공한다.
Listing 2. "노드" EditPart의 초기 구현
public class MyNodeEditPart extends AbstractGraphicalEditPart {
protected IFigure createFigure() {
return new Label();
}
protected void createEditPolicies() {
...
}
protected List getModelSourceConnections() {
MyModel node = (MyModel)getModel();
return node.getOutgoingConnections();
}
protected List getModelTargetConnections() {
MyModel node = (MyModel)getModel();
return node.getIncomingConnections();
}
protected void refreshVisuals() {
MyModel node = (MyModel)getModel();
Label label = (Label)getFigure();
label.setText(node.getName());
label.setIcon(node.getIcon());
Rectangle r = new Rectangle(node.x, node.y, -1, -1);
((GraphicalEditPart) getParent()).setLayoutConstraint(this, label, r);
}
}
|
새로운 메소드 refreshVisuals()가 오버라이드 된다. 이 메소드는 모델에서 데이터를 사용하여 그림을 업데이트 해야 할 때 호출된다. 이 경우, 모델의 이름과 아이콘은 레이블에 반영된다. 하지만 더 중요한 것은, 이 레이블은 레이아웃 제약 조건을 부모에게 전달함으로써 배치된다. 콘텐트 EditPart에서, 우리는 XY 레이아웃 매니저를 사용했다. 이 레이아웃은 Rectangle 제약 조건을 사용하여 자식 그림들을 어디에 배치할 지를 결정한다. "-1"의 넓이와 높이는 그 그림의 권장 크기를 나타낸다.
 |
팁 No. 1
그림들은 setBounds(...) 메소드를 사용하여 배치되어서는 안된다. XYLayout같은 레이아웃 매니저를 사용하면 스크롤바가 정확하게 업데이트 된다. 또한, XYLayout은 관련 제약 조건을 절대 위치로 변환하고, 그림을 권장 사이즈로 자동 조절하는데 사용될 수 있다. (다시 말해서, 제약 조건의 넓이와 높이는 -1이다.)
|
|
refreshVisuals() 메소드는 EditPart의 초기화 동안 한번만 호출되고 다시는 호출되지 않는다. 모델 공지에 응답할 때 그림을 업데이트 하기 위해 refreshVisuals()를 호출하는 것은 애플리케이션의 몫이다. 성능을 향상시키려면 각 모델 애트리뷰트용 코드를 고유의 메소드("스위치"를 가진 단일 메소드)로 분해해야 한다.
또 한가지 재미있는 차이점은 연결 지원용 코드이다. getModelChildren()와 마찬가지로, getModelSourceConnections()와 getModelTargetConnections()는 노드들 간 링크를 나타내며 모델 객체들을 리턴해야 한다. 수퍼클래스는 필요할 경우, 상응하는 EditPart를 만들고 이들을 소스 리스트와 대상 연결 EditPart에 추가한다. 연결은 노드 엔드(end)에서 노드에 의해 언급되지만, EditPart는 한번만 생성되어야 한다. GEF는 뷰어에 이미 연결되었는지를 검사하여 연결이 한번만 만들어지도록 한다.
연결하기
연결 EditPart 구현도 다르지 않다. AbstractConnectionEditPart를 하위 클래스로 나누는 것으로 시작한다. 전과 마찬가지로, refreshVisuals()는 애트리뷰트를 모델에서 그림으로 매핑한다. 제약 조건이 전과는 약간 다르더라도 연결 역시 제약 조건이 있다. 여기에서, 제약 조건들은 연결 라우터에 의해 사용되어 연결을 바꾼다. 더욱이, 연결 EditPart 그림은 Draw2D Connection이어야 하고, 이는 한 개 이상의 요구 사항들(연결 앵커)을 사용한다.
연결은 ConnectionAnchor에 의해 각 끝에서 이루어 진다. 따라서, 연결 EditPart나 노드 구현에서 어떤 앵커들을 사용할 것인지를 나타내야 한다. 기본적으로, GEF는 노드 EditPart가 NodeEditPart 인터페이스를 구현함으로써 앵커를 제공하는 것으로 간주한다. 이렇게 하는 이유는 앵커 선택은 각 엔드에서 노드에 의해 사용되는 그림에 의존하기 때문이다. 연결 EditPart는 노드에 의해 사용되는 그림에 대해 전혀 모른다. 또 다른 이유는, 사용자가 연결을 할 때 연결 EditPart가 존재하지 않기 때문에 노드는 피드백을 보여줄 수 있어야 한다. Listing 2에 더하여, Listing 3에서는 필수 앵커 지원을 추가했다.
Listing 3. "노드" EditPart에 앵커 지원 추가하기
public class MyNodeEditPart
extends AbstractGraphicalEditPart
implements NodeEditPart
{
...
public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getSourceConnectionAnchor(Request request) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connection) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(Request request) {
return new ChopboxAnchor(getFigure());
}
...
}
|
 |
팁 No. 2
NodeEditPart 인터페이스를 실제로 구현해야 한다. 그렇지 않으면 메소드가 절대로 호출되지 않는다.
|
|
연결을 받는 메소드는 기존 연결 EditPart에 앵커를 설정할 때 사용되는 메소드이다. 다른 두 개의 메소드들은 Request를 취한다. 이러한 메소드들은 사용자가 새로운 연결을 만들 때 편집하는 동안 사용된다. 예를 들어, chopbox 앵커는 모든 경우에 리턴된다. chopbox 앵커는 라인이 노드의 그림의 바운딩 박스와 교차하는 라인을 찾는다.
연결 EditPart를 구현하는 것은 비교적 간단하다. 이는 그림을 만드는데 필수적인 것은 아니다. PolylineConnection으로도 충분하기 때문이다. (Listing 4)
Listing 4. 연결 EditPart의 초기 구현
public class MyConnectionEditPart extends AbstractConnectionEditPart {
protected void createEditPolicies() {
...
}
protected void refreshVisuals() {
PolylineConnection figure = (PolylineConnection)getFigure();
MyConnection connx = (MyConnection)getModel();
figure.setForegroundColor(MagicHelper.getConnectionColor(connx));
figure.setRoutingConstraint(MagicHelper.getConnectionBendpoints(connx));
}
}
|
 |
팁 No. 3
더 중요한 것은, ConnectionEditParts를 사용할 때와 사용하지 않을 때를 구분하는 것이다. 연결 EditPart는 사용자가 선택하고 상호 작동 할 수 있는 무엇인가 있을 경우에 사용된다. 모델에 있는 객체와 직접적인 상관 관계가 있고 스스로 삭제될 수 있다.
라인을 그려야 하는 노드나 컨테이너가 있다면, 그림의 페인트 메소드에 라인을 그리거나 Polyline 그림을 포함하고 있는 그림을 합성한다.
연결은 소스와 대상을 언제나 갖고 있어야 한다. 소스나 대상이 없는 연결이 필요하다면 AbstractGraphicalEditPart를 확장하고 연결 그림을 사용하는 것이 더 낫다.
|
|
모델 리스닝
생성 후에, EditPart는 모델로부터 변경 공지를 리스닝 해야 한다. GEF는 모델 중립적이기 때문에 모든 애플리케이션들은 고유의 리스너를 추가하고 결과 공지를 핸들해야 한다. 공지를 받으면 핸들러는 제공된 메소드들 중 하나를 호출하여 재생해야 한다. 예를 들어, 자식이 삭제될 경우, refreshChildren()을 호출하면 상응하는 EditPart와 이것의 그림도 삭제된다. 단순한 애트리뷰트 변경의 경우, refreshVisuals()가 사용될 수 있다. 앞서 언급했던 것처럼, 메소드는 여러 부분들로 분해되어 모든 디스플레이 된 애트리뷰트가 필요 이상으로 업데이트 되는 것을 방지해야 한다.
리스너를 추가하고 이들을 제거하는 것을 잊으면 메모리 누수의 원인이 된다. 이와 같은 이유로, 리스너를 추가 및 제거했던 포인트는 API에서 확실히 제거되어야 한다. EditPart는 activate()를 확장하여 나중에 제거되어야 하는 리스너를 추가한다. deactivate()를 확장함으로써 같은 리스너를 제거한다. Listing 5는 모델 공지를 위해 노드 EditPart 구현을 추가하는 모습이다.
Listing 5. "노드" EditPart에서 모델 변경 사항 리스닝 하기
public class MyNodeEditPart
extends AbstractGraphicalEditPart
implements NodeEditPart, ModelListener
{
...
public void activate() {
super.activate();
((MyModel)getModel()).addModelListener(this);
}
public void deactivate() {
((MyModel)getModel()).removeModelListener(this);
super.deactivate();
}
public void modelChanged(ModelEvent event) {
if (event.getChange().equals("outgoingConnections"))
refreshSourceConnections();
else if (event.getChange().equals("incomingConnections"))
refreshTargetConnections();
else if (event.getChange().equals("icon")
|| event.getChange().equals("name"))
refreshVisuals();
}
...
}
|
모델 편집하기
지금까지, EditPart가 생성되는 방법, 비주얼을 생성하는 방법, 모델이 변할 때 업데이트 되는 방법을 설명했다. 이것 외에도, EditPart는 모델을 변경하는데 있어 주요한 역할을 한다. 하나의 Command에 대한 요청이 EditPart로 보내질 때 발생한다. 리퀘스트를 통해 EditPart에게 마우스 드래그 같은 피드백을 보여줄 것을 요청한다. EditPart는 해당 요청을 지원, 방지, 또는 무시한다. 지원 또는 금지되는 요청 유형들은 EditPart의 작동을 결정한다.
지금까지는 모델의 구조와 프로퍼티를 뷰에 매핑하는 방법에 초점을 맞췄다. 이는 EditPart 클래스에서 여러분이 해야 할 일이었다. 작동은 EditPolicies라고 하는 헬퍼 세트에 의해 결정된다. 예제에서는 createEditPolicies() 메소드를 무시했다. 이 메소드를 실행하면 EditPart로 훨씬 더 많은 일이 수행된다. 물론 애플리케이션의 모델을 수정하는 방법에 대한 편집 정책을 제공해야 한다.
편집 작동이 접속 가능하기 때문에 다양한 EditPart 구현을 개발할 때 모델을 뷰에 매핑하고 모델 업데이트를 핸들링 하는 태스크에 기반하여 클래스 계층을 만들 수 있다.
4 단계: 조합하기
모델을 그래픽으로 디스플레이 하는데 필요한 모든 부분들이 갖춰졌다. 마지막으로, IEditorPart를 사용할 것이다. 하지만, GEF의 뷰어 역시 뷰, 다이얼로그에서 사용되거나 컨트롤을 둘 수 있는 곳에서 사용할 수 있다. 이 단계에서, UI 플러그인이 있어야 하는데, 이것은 개방된 리소스에 대한 에디터와 파일 확장을 정의한다. 모델은 같은 플러그인 또는 개별 플러그인에서 정의된다. 또한, 아직 편집 기능이 없기 때문에 사전 설치된 모델이 필요하다.
샘플 모델 데이터를 제공하는 여러 방법들이 있다. 이 예제의 목적상, 에디터가 열릴 때 코드에 모델을 만들고, 파일의 실제 콘텐트는 무시한다. 이를 위해, 테스트 팩토리가 있다고 가정한다. 또는 리소스에 데이터를 채우는 마법사를 만들 수 있다. (일반 마법사는 빈 다이어그램만 만든다.) 마지막으로, 텍스트 에디터에서 문서의 콘텐트를 작성할 수 있다.
이제 샘플 모델이 있기 때문에, 모델을 디스플레이 할 에디터 부분을 만들어 보자. 가장 빠른 방법은 GEF의 GraphicalEditor의 하위 클래스를 만들거나 복사하는 것이다. 이 클래스는 ScrollingGraphicalViewer의 인스턴스를 만들고 캔버스를 만들어서 에디터의 컨트롤로서 사용한다. 이것은 GEF 시작을 돕는 편리한 클래스이다. Eclipse 에디터의 경우, 회의적인 팀 환경, 삭제 또는 이동된 리소스 같은 많은 고려해야 할 사항들이 있다.
Listing 6은 에디터 구현 샘플이다. 구현되어야 하는 여러 추상 메소드들이 있다. 모델 영속성과 마커는 무시하도록 하겠다. 다이어그램이 그래픽 뷰어에 나타나도록 하려면 두 가지를 수행해야 한다. 먼저, 자신의 EditPart 팩토리에 뷰어를 설정하여 3 단계에서 EditPart를 만들고 다이어그램 모델 객체에 있는 것을 뷰어로 전달한다.
Listing 6. 에디터 일부 구현하기
public class MyEditor extends GraphicalEditor {
public MyEditor() {
setEditDomain(new DefaultEditDomain(this));
}
protected void configureGraphicalViewer() {
super.configureGraphicalViewer(); //Sets the \
viewer's background to System "white"
getGraphicalViewer().setEditPartFactory(new MyGraphicalEditpartFactory());
}
protected void initializeGraphicalViewer() {
getGraphicalViewer().setContents(MagicHelper.constructSampleDiagram());
}
public void doSave(IProgressMonitor monitor) {
...
}
public void doSaveAs() {
...
}
public void gotoMarker(IMarker marker) {
...
}
public boolean isDirty() {
...
}
public boolean isSaveAsAllowed() {
...
}
}
|
다음 단계
모델을 그래픽 에디터에 디스플레이 했다. 하지만, 이는 그저 기초적인 것에 불과하다. 간단히 편집 정책만 언급했다. GEF SDK에서 제공하는 개발자 문서에는 편집 정책에 대해 자세히 설명되어 있다. GEF 홈 페이지에는 각각의 편집 정책 유형 사용법을 설명하는 예제도 들어있다. (참고자료)
GEF 역시 팔레트를 제공한다. 이 팔레트에는 다이어그램에 객체를 생성할 수 있는 툴 세트가 디스플레이 된다. 사용자는 툴을 활성화 하거나, 드래그&드롭을 사용하여 팔레트에서 직접 아이템을 가져올 수 있다. 콘텐트에 대한 커스터마이징 역시 지원된다.
여러 JFace 액션 역시도 GEF에서 사용할 수 있다. 실행 취소(undo), 정렬(align), 삭제(delete) 같은 것을 메뉴, 툴바, 콘텍스트 메뉴에서 사용할 수 있다.
마지막으로, 여러분의 애플리케이션은 아웃라인 뷰와 프로퍼티 뷰를 지원해야 한다. 아웃라인 뷰는 검색과 제한된 편집용으로 사용된다. GEF의 TreeViewer와 개요 윈도우가 사용될 수 있다. 프로퍼티 시트에서는 사용자가 현재 선택된 것의 상세한 속성들을 보고 편집할 수 있다.
선택한 것을 보고, 사용자가 변경을 할 수 있도록 하려면, 편집 정책들을 EditPart에 추가해야 한다. GEF 홈 페이지를 참조하거나, GEF SDK에 있는 개발자 문서를 참조하라.
GEF와 Eclipse 워크벤치에서 제공하는 추가 기능에 대한 상세한 부분은 이 글에서는 설명하지 않겠지만, 관심 있는 부분은 간략히 소개하겠다:
-
팔레트 -- 툴의 팔레트는 말 그대로 다이어그램에 새로운 객체를 생성하는 수단이다. GEF에도 팔레트가 있는데, 드래그&드롭, 다중 드로어, 레이아웃 설정, 콘텐트 커스터마이징 등을 지원한다.
-
액션 바 -- Editor와 View는 툴바, 메뉴, 콘텍스트 메뉴에 Action을 제공한다. GEF는 여러 가지 재사용 가능한 액션 구현을 제공하지만, 이것은 이들을 어디에나 디스플레이 하는 애플리케이션에 달려있다.
-
프로퍼티 시트 -- 프로퍼티 시트는 선택된 아이템들의 상세한 프로퍼티를 디스플레이 한다. GEF의 경우, EditPart 또는 모델에서 프로퍼티 시트 지원을 추가할 수 있다.
-
아웃라인 -- 아웃라인 뷰는 다이어그램의 구조를 보여주는데 사용되지만, 일반적인 용도로도 사용될 수 있다. GEF의 TreeViewer도 아웃라인 뷰에서 종종 사용된다.
참고자료 교육
제품 및 기술 얻기
토론
필자소개  | |  | Randy Hudson은 IBM Research Triangle Park의 소프트웨어 엔지니어이다. Graphical Editing Framework (GEF)의 기술 리더로서 내부 프로젝트를 오픈 소스 기술로 전향하는 것을 돕고 있다. 현재, 가용성, 그래픽 편집, 그래프 레이아웃, 엣지 라우팅 분야에서 일하고 있다. (buchu at nc.rr.com) |
기사에 대한 평가
|  |