TheSpecUIframework:Chapter 09

From 흡혈양파의 번역工房
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
Tips 과 Tricks

Tips 과 Tricks

이 장에는 Spec 을 사용하여 사용자 인터페이스를 작성할 때 사용할 수 있는 많은 기능의 작은 예를 모아두었습니다. 간단하게 구성하기 위해서 어떤 예제들은 동적 Spec(8장 참조) 을 사용했습니다만, Spec 의 동적기능을 사용하지 않아도 이런 작업들은 수행될 수 있습니다. 대부분의 예제는 자가설명(auto-explicative)이 가능한 짧은 코드 조각입니다.

다른 UI 프레임워크의 통합

이 섹션은, Pharo 에 있는 여러 UI 프레임 워크를 Spec 과 함께 작동시키는 여러 가지 방법을 보여줍니다.

Spec UI 에 Morph 를 포함하기

Morph 는 Spec UI 안에 위젯으로서 포함될 수 있습니다. 이렇게 하기 위해서, Morph 인스턴스에 asSpecAdapter 메시지를 보내면 UI 에서 사용할 수 있는 Spec 위젯을 반환해 줍니다.


아래에서는, CalendarMorph 를 일반 Spec UI 로 바꾸는 코드를 보여주며, SpecCalendar new openWithSpec 으로 열 수 있습니다:

ComposableModel subclass: #SpecCalendar
	instanceVariableNames: 'morph'
	classVariableNames: ''
	package: 'Spec-BuildUIWithSpec'
SpecCalendar >> initializeWidgets
	morph := (CalendarMorph openOn: Date today) asSpecAdapter
SpecCalendar class >> defaultSpec
	^SpecLayout composed add: #morph; yourself
SpecCalendar >> extent
	^220@200
SpecCalendar >> title
	^'SpecCalendar'.


Spec UI 에 Glamour UI 를 포함하기

Glamour 로 작성된 사용자 인터페이스는, 브리지 클래스인 GlamourPresentationModel 덕분에 Spec UI 에 포함될 수 있습니다. 이 클래스는 Playground 또는 인스펙터(Inspector) 같은 매력적인 사용자 인터페이스를 래핑하는 ComposableModel 의 하위 클래스입니다.


Pharo 5 에서 이 기능을 사용하려면 먼저 통합 패키지를 불러들여야 합니다:

Gofer it
		smalltalkhubUser: 'jfabry' project: 'Playground';
		package: 'Spec-Glamour';
		load


GlamourPresentationModel 은 기본적으로 Playground 를 래핑합니다. 즉, GlamourPresentationModel new openWithSpec 을 실행하면 Glamour Playground 에서 Spec 창을 열 수 있습니다.


다른 Glamour UI 를 사용하려면 GlamourPresentationModel 이 열리기 전에 presentationClass:startOn: 메시지를 보내야하며, Glamour UI 의 클래스와 UI 에 표시할 데이터를 인수로 사용해야합니다. 예를들어 아래처럼 하면 42 에 대한 인스펙터가 열립니다.

| cm |
 cm := GlamourPresentationModel new.
 cm presentationClass: GTInspector startOn: 42.
 cm openWithSpec


GT Playground 는 열리는 시점에서 약간 특이한 데이터를 필요로 하는데, 이 데이터는 아래처럼 GTPlayPage 인스턴스가 되어야합니다.

| cm |
 cm := GlamourPresentationModel new.
 cm presentationClass: GTPlayground startOn: (GTPlayPage new saveContent: '42').
 cm openWithSpec


GT Inspector 에 대한 Spec presentation 만들기

앞서 언급한 방법의 반대도 가능합니다. GT Inspector 탭을 위한 코드를 작성하고, 그 탭이 Spec UI 를 보여주는것도 가능합니다. 이렇게 하려면 탭의 display: 블록이 buildWithSpec 메시지를 보내서, 완전히 설정된 ComposableModel 인스턴스로 실행(evaluate)되어야 합니다.


예를 들어, 다음 코드는 컬렉션의 모든 항목을 ListModel 로 표시하는 OrderedCollection 의 탭입니다.

OrderedCollection >> gtInspectorItemsAsListIn: composite
	<gtInspectorPresentationOrder: 10>
	 composite spec
	 	 title: 'AsList';
	 	 display: [ :elt | | cm |
	 	 	 cm := ListModel new.
	 	 	 cm items: self.
	 	 	 cm buildWithSpec.
	 	 	 cm]


Gnome3 notice header.png
위의 내용과 앞의 팁을 결합하면, Spec 및 Glamour UI 사이에서 무한 인스펙터 재귀를 수행할 수 있습니다.
Gnome3 notice footer.png


Object >> gtInspectorRecursive: composite
	<gtInspectorPresentationOrder: 10>
	 composite spec
	 	 title: 'Recursion!';
	 	 display: [ :elt | | cm |
	 	 	 cm := GlamourPresentationModel new.
	 	 	 cm presentationClass: GTInspector startOn: self.
	 	 	 cm buildWithSpec.
	 	 	 cm]


목록, 트리 그리고 표(table)

이 섹션에서는 목록, 트리 및 표에 대한 모든 팁을 모았습니다.

스크롤 가능한 위젯 목록

ListModel 은 단순한 텍스트 이상의 것을 표시할 수 있으며, 모든 종류의 위젯을 시각화 할 수 있습니다. displayBlock: 위젯에 buildWithSpec 메시지를 보내면 됩니다. 예를 들어, 그림 9-1 처럼 Files 패키지의 클래스 주석을 표시하는 스크롤 가능한 버튼 목록을 보시면 됩니다.

ListModel new
	displayBlock: [ :x | x buildWithSpec ];
	items:
		('Files' asPackage classes
			collect: [ :cls |
				ButtonModel new	icon: cls systemIcon; label: cls name;
					action: [TextModel new text: cls comment; openWithSpec]]);
	openWithSpec


그림 8-1 스크롤 가능한 위젯목록


자동 갱신되지 않는 목록 항목

목록에 있는 컬렉션에 항목을 추가하면 위젯을 업데이트하지 않습니다. 새로 고침은 목록의 항목이 items: 메시지를 사용하여 설정 될 때만 발생됩니다. 정렬 블록이 지정된 경우 항목을 설정하면 정렬됩니다.

|container count|
 count :=0.
 container := DynamicComposableModel new.
 container instantiateModels:  #(#list ListModel #plus ButtonModel).
 container plus label: '+1'.
 container plus action: [ | items |
   count := count + 1.
   items := container list getList asOrderedCollection.
   items add: count asString.
   container list items: items ].
 container layout:
   (SpecRowLayout composed add: #list ; add: #plus ; yourself).
 container openWithSpec


트리에서 선택 영역 설정하기

TreeModel 에 선택에 대한 내용을 프로그램적으로 설정한다고 해서, 선택한 항목이 자동으로 강조 표시되는것은 아니며, 항목을 역으로 강조시킨다고 해도 자동적으로 선택되는 것은 아닙니다. 따라서, 다음의 예제처럼 트리 항목을 선택하고 강조 표시하기 위해 두 작업을 모두 수행해야합니다. whenBuiltDo: 블록의 마지막 두 문에 주목해주세요. 첫 번째 항목은 선택한 항목을 설정하며 두 번째 항목은 강조를 표시합니다.

| tree chblock |
 tree := TreeModel new.
 chblock := [:cl | | subs node |
   subs := cl subclasses.
   node := TreeNodeModel new.
   node content: cl;
     hasChildren: [ subs isNotEmpty  ];
     children: [ subs collect: [:sub | chblock value: sub ] ] ].
 tree roots: 
       (Collection subclasses collect: [:cl| chblock value: cl ]). 
 tree whenBuiltDo: [ |sel|
   sel := tree roots third.
   tree selectedItem: sel.
   sel selected: true.].
 tree openWithSpec


표(table)처럼 보이는 view

표처럼 보이는 위젯을 사용자에게 표현할 수 있는 방법에는 여러가지가 있습니다. 가장 직접적인 방법은 아래처럼 MultiColumnListModel 을 사용하는 것입니다. 표시 블록(display block)의 기본 동작은 배열을 printString 으로 바꾸기 때문에 표시 블록을 항목 자체를 반환하도록 설정해야합니다.

MultiColumnListModel new
	items: {
		{'Origin' . 'Destination' . 'Start Time' . 'Stop Time'} .
		{'Santiago' . 'Paris' . '15:30' . '11:15'} .
		{'Paris' . 'Santiago' . '23:30' . '8:00'} . };
	displayBlock: [ :x | x ];
	openWithSpec.


또는 TreeModel 을 사용할 수 있기 때문에, 보다 멋진 서식 옵션을 사용할 수도 있습니다.(예제를 제공해준 Nicolai Hess 에게 감사드립니다)

|r m col1 col2 col3|
 r := FileLocator vmDirectory.
 m := TreeModel new.
 m roots: r allFiles.
 m rootNodeHolder: [ :item |
    TreeNodeModel new
        content: item;
        icon: Smalltalk ui icons smallLeftFlushIcon ].
 m title: r fullName.
 col1 := TreeColumnModel new
    displayBlock: [ :node | node content basename ];
    headerLabel:'Name'.
 col2 := TreeColumnModel new
    displayBlock: [ :node | node content creationTime ];
    headerLabel:'Time'.
 col3 := TreeColumnModel new
    displayBlock: [ :node | node content permissions];
    headerLabel:'Permissions'.
  m columns: {col1. col2 . col3}.
  m openWithSpec.


기반 위젯 라이브러리 사용하기

이 섹션에서는 Spec 에서 기반 위젯 라이브러리 (Pharo 6 의 Morphic 및 이전 버전)의 일부 기능을 사용하는 방법을 보여줍니다.


Gnome3 notice header.png
경고: 이런 방법을 따르면 UI 를 기반 위젯 라이브러리에 단단하게 결합시킬 수 있습니다. 결과적으로, 이 라이브러리에 변경 사항이 생기거나, Spec 이 해당 위젯에 다른 라이브러리를 사용하는 경우 UI 가 손상될 수도 있습니다.
Gnome3 notice footer.png


위젯의 모양을 사용자 정의하기

Spec 에서 위젯의 ​​모양을 사용자 정의할 수는 없지만, 기본 위젯 라이브러리가 모양을 사용자 정의할 수 있는 경우라면, 위젯이 작성된 직후에 위젯에 메시지를 보낼 수 있습니다. 이를 위해 위젯을 whenBuiltDo: 메시지로 구성하십시오. 인수는 하나의 인수를 취하는 블록으로서, WidgetBuilt 알림(announcement)의 인스턴스가됩니다.


예를 들어, 아래 코드는 Morphic 을 사용한다고 가정했을때, 레이블이 빨간색으로 렌더링되고 풍선 글꼴을 기울임 모양으로 사용합니다.

|container|
  container := DynamicComposableModel new.
  container instantiateModels:  #(#red LabelModel).
  container red label: 'I am red'.
  container red whenBuiltDo: [:ann|
	  ann widget color: (Color red);
         font: BalloonMorph balloonFont emphasis: 2].
  container layout: (SpecLayout composed add: #red; yourself).
  container openWithSpec


정기적인 호출

Morphic 은 morph 가 규칙적인 간격으로 실행되는 호출을 위해, 스스로 등록 할 수있게 해주는 스테핑(stepping) 메커니즘을 구현합니다. 이 메커니즘은의 예를 들자면, 윈도우의 내용을 매 n 밀리 초마다 자동 새로 고침하는 경우를 들 수 있는데, Spec 은 CompositeModel 의 메서드인 defaultWindowModelClass 를 오버라이드해서 TickingWindowModel 을 반환함으로써 이것을 지원합니다:

WatchpointWindow >> defaultWindowModelClass
	^ TickingWindowModel


이 오버라이드 셋을 사용하면, 모든 stepTime 밀리 초마다 Morphic 인프라에서 step 메서드를 호출합니다. step 의 기본 구현은 아무 작업도 수행하지 않으며, stepTime 은 1000 을 반환합니다. 따라서 필요한 조치를 구현하려면 최소한 step 은 재정의해야 합니다. 예를 들어, WatchPointWindow 는 stepTime 은 무시하지 않으며, 다음처럼 step 을 구현해서 표시된 목록을 새로 고치고 있습니다.

WatchpointWindow >> step
	self refreshItems
WatchpointWindow >> refreshItems
	| max values |
	values := self watchpoint values.
	max := values size.
	list items: (values copyFrom: (1 max: max - numItems) to: max) reversed.


Spec UI 의 기능 테스트

버튼 클릭 수, 목록 선택 등을 프로그래밍 방식으로 시뮬레이트하여 Spec UI 를 테스트할 수 있습니다. 또한 UI 를 전혀 열지 않아도, 이 작업은 수행할 수 있습니다.


예를 들어 2 장의 CustomerSatisfaction UI 의 세 가지 버튼을 테스트하는 아래 코드를 살펴보십시오.

| cs |
 cs := CustomerSatisfaction new.
 cs buildWithSpec.
 cs buttonHappy performAction.
 self assert: [cs screen label = 'Happy' ].
 cs buttonNeutral performAction.
 self assert:[ cs screen label = 'Neutral' ].
 cs buttonBad performAction.
 self assert: [cs screen label = 'Badz' ].


코드의 세째줄부터 창을 열지 않고 UI 를 작성하고 있습니다. 이후, 각 버튼에 대해 프로그래밍 방식으로 클릭하며 화면에 표시된 레이블이 예상 값과 일치하는지를 확인합니다. 마지막 테스트는 화면에 표시되는 라벨이 'Badz' 가 아니라 'Bad' 이기 때문에 실패하게 됩니다.


그러나 Spec 의 여러 위젯을 테스트하기 위한 API 가 별도로 있는것은 아닙니다. 때문에, 각 위젯에 대해 테스트 할 위젯의 조작내용을 프로그래밍 방식으로 시뮬레이션하기 위해서 호출할 메소드를 설정해야 합니다. 예를 들어, 다음의 코드는 목록 선택을 사용해서 3 장의 ProtocolBrowser 텍스트 필드의 일부 동작을 테스트하고 있습니다.

| pb | 
 pb := ProtocolBrowser new.
 pb buildWithSpec.
 pb viewer models list setSelectedIndex: 1.
 self assert: [ pb text text isEmpty ].
 pb viewer api methods setSelectedIndex: 1.
 self assert: [(pb text text copyFrom: 1 to: 6) = 'action' ].


Gnome3 notice header.png
앞의 코드는 ProtocolBrowser 의 내부 구조 및 재사용하는 위젯에 크게 의존하기 때문에 깔끔한 것은 아닙니다. 깔끔하게 프로그래밍 하려면 UI 테스트를 작성할 수 있어야 하는, 내부를 노출하는 테스트용 API 를 별도로 만드는 것이 좋습니다. 허지만 테스트용 API 의 작성은 이 내용의 범위를 벗어납니다.
Gnome3 notice footer.png


Gnome3 notice header.png
테스트를 수행하는 동안 UI 가 열려있어야 하는 경우라면, buildWithSpec 의 전송을 openWithSpec 으로 변경하고 코드 끝에 pb window close 를 추가하면 UI 는 닫히고 열리게 됩니다.
Gnome3 notice footer.png


Notes