TheSpecUIframework:Chapter 09
- 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]
위의 내용과 앞의 팁을 결합하면, Spec 및 Glamour UI 사이에서 무한 인스펙터 재귀를 수행할 수 있습니다. |
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
자동 갱신되지 않는 목록 항목
목록에 있는 컬렉션에 항목을 추가하면 위젯을 업데이트하지 않습니다. 새로 고침은 목록의 항목이 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 및 이전 버전)의 일부 기능을 사용하는 방법을 보여줍니다.
경고: 이런 방법을 따르면 UI 를 기반 위젯 라이브러리에 단단하게 결합시킬 수 있습니다. 결과적으로, 이 라이브러리에 변경 사항이 생기거나, Spec 이 해당 위젯에 다른 라이브러리를 사용하는 경우 UI 가 손상될 수도 있습니다. |
위젯의 모양을 사용자 정의하기
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' ].
앞의 코드는 ProtocolBrowser 의 내부 구조 및 재사용하는 위젯에 크게 의존하기 때문에 깔끔한 것은 아닙니다. 깔끔하게 프로그래밍 하려면 UI 테스트를 작성할 수 있어야 하는, 내부를 노출하는 테스트용 API 를 별도로 만드는 것이 좋습니다. 허지만 테스트용 API 의 작성은 이 내용의 범위를 벗어납니다. |
테스트를 수행하는 동안 UI 가 열려있어야 하는 경우라면, buildWithSpec 의 전송을 openWithSpec 으로 변경하고 코드 끝에 pb window close 를 추가하면 UI 는 닫히고 열리게 됩니다. |