TheSpecUIframework:Chapter 09

From 흡혈양파의 번역工房
Revision as of 20:36, 26 August 2017 by Onionmixer (talk | contribs) (TheSpecUIframework 9장 내용완료)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
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