<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://trans.onionmixer.net/wiki/index.php?action=history&amp;feed=atom&amp;title=TheSpecUIframework%3AChapter_07</id>
	<title>TheSpecUIframework:Chapter 07 - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://trans.onionmixer.net/wiki/index.php?action=history&amp;feed=atom&amp;title=TheSpecUIframework%3AChapter_07"/>
	<link rel="alternate" type="text/html" href="https://trans.onionmixer.net/wiki/index.php?title=TheSpecUIframework:Chapter_07&amp;action=history"/>
	<updated>2026-05-01T12:04:02Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.3</generator>
	<entry>
		<id>https://trans.onionmixer.net/wiki/index.php?title=TheSpecUIframework:Chapter_07&amp;diff=5439&amp;oldid=prev</id>
		<title>Onionmixer: TheSpecUIframework 7장 내용 추가</title>
		<link rel="alternate" type="text/html" href="https://trans.onionmixer.net/wiki/index.php?title=TheSpecUIframework:Chapter_07&amp;diff=5439&amp;oldid=prev"/>
		<updated>2017-08-23T10:49:15Z</updated>

		<summary type="html">&lt;p&gt;TheSpecUIframework 7장 내용 추가&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;;고급 위젯&lt;br /&gt;
&lt;br /&gt;
==고급 위젯==&lt;br /&gt;
&lt;br /&gt;
Spec 의 일부 사용자 인터페이스 요소는, 예를 들어 버튼이나 라벨 등에 비해 고급 기능을 제공하므로 설정이 복잡합니다. 이 장에서는 4 가지 고급 위젯을 보여주고 어떻게 사용하고 구성 할 수 있는지를 보여줍니다.  먼저 텍스트 입력 위젯에 대해 토론한 다음에, 라디오 버튼과 탭을 처리하고 툴바와 팝업 메뉴로 마무리합니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===TextModel===&lt;br /&gt;
&lt;br /&gt;
Spec 에서 사용자가 텍스트를 입력하거나 여러 줄 텍스트를 표시하는 것은 TextModel 을 사용하여 수행됩니다. 이제부터 TextModel 모델의 일반적인 구성 및 한 줄짜리 TextInputFieldModel 에서 제공하는 추가 기능에 대해 설명합니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====수정할 수 없는 텍스트 입력 필드====&lt;br /&gt;
&lt;br /&gt;
TextModel 의 텍스트 필드는 기본적으로 편집이 가능하지만, UI 가 편집 불가능한 여러 줄의 텍스트를 표시하는 경우에도 유용합니다. 이를 위해 간단한 예를 들어, 다음과 같이 모델에 &amp;quot;disable&amp;quot;을 보냅니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
  | cm |&lt;br /&gt;
  cm := TextModel new.&lt;br /&gt;
  cm text: Object comment.&lt;br /&gt;
  cm disable.&lt;br /&gt;
  cm openWithSpec.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
위의 예제는 Object 클래스의 클래스 주석을 편집 할 수 없는 방식으로 보여줍니다. 텍스트는 계속 선택, 복사 및 검색 할 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====노란색 삼각형을 제거하고, 각 편집 작업을 수행====&lt;br /&gt;
&lt;br /&gt;
TextModel 은 필드의 오른쪽 위에 노란색 삼각형을 배치해서, 텍스트를 편집 할 때 시각적 피드백을 제공합니다. 또한 위젯이 제거 된 경우, 위젯이 포함 된 창을 닫으면 확인 대화 상자가 사용자에게 표시됩니다. 이 &amp;#039;has been edited&amp;#039; 플래그는 모델에서 &amp;#039;aceept&amp;#039; 메시지를 호출하여 재설정됩니다. accept 메시지를 보내는 것은, 위젯이 닫혀있을 때 변경된 텍스트가 손실되지 않았는지 확인하도록 위젯에 지시하는 것을 의미하지만, 이것으로 사용자가 가능한 손실에 대해 통보받을 수 있는것은 아닙니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
예를 들어, 아래 코드를 실행하고 UI 가 몇 개의 문자를 입력하면 열립니다. 3 초후 삼각형은 사라지며, 창 닫기는 사용자의 확인을 요구하지 않습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
  | cm |&lt;br /&gt;
  cm := TextModel new.&lt;br /&gt;
  cm openWithSpec.&lt;br /&gt;
  [3 seconds wait. cm accept] fork&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
accept 호츨에서 실제로 발생되는 것은 별도로 설정할 수 있습니다. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
또한 각 키 입력시 자동으로 수락하도록 모델을 구성하여, 편집 플래그의 기능을 효과적으로 제거 할 수 있습니다. 이런 작동은  &amp;quot;autoAccept : true&amp;quot;를 보내서 수행됩니다. 기본 동작은 모델의 &amp;quot;text&amp;quot; 필드를 사용자 인터페이스에 있는 텍스트로 설정하는 것입니다. 이 동작을 추가하려면 모델에 acceptBlock: 메시지를 보내십시오. 이 메시지는 인수가 하나인 블록을 사용합니다. 블록의 인수는 사용자 인터페이스에있는 텍스트입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
다음 예제에서는 autoAccept 및 acceptBlock: 메시지를 결합해서, 텍스트를 편집 할 때마다 변형된 텍스트를 표시하는 텍스트 필드를 만드는 방법을 보여줍니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
  | cm |&lt;br /&gt;
  cm := TextModel new.&lt;br /&gt;
  cm autoAccept: true.&lt;br /&gt;
  cm acceptBlock: [ :txt | GrowlMorph openWithContents: txt.].&lt;br /&gt;
  cm openWithSpec.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====키보드 단축키====&lt;br /&gt;
&lt;br /&gt;
텍스트 입력 필드에서는 복사 및 붙여넣기 동작 이전에 키보드 바로 가기를 제공해서 다른 작업을 제공할 필요가 경우가 있습니다. 이러한 사용자 지정 작업 정의는 bindKeyCombination:action: 메시지를 사용하여 수행됩니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{GNOME3Notice|키보드 단축키는 반드시 Command(MAC의 단축키)를 동반하는 변경자(modifier)와 결합되어야 할 필요는 없습니다. 예를 들어, 단순히 문자 x 또는 공백 문자가 되어도 상관은 없습니다.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
예를 들어, 아래 코드는 command-i를 누르면 텍스트에 대한 속성을 엽니다. 위젯에 입력 된 문자를 항상 유지하려면 &amp;quot;text&amp;quot; 에 자동수신(auto-accept)을  &amp;quot;true&amp;quot; 로 설정해야합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
  | cm |&lt;br /&gt;
  cm := TextModel new.&lt;br /&gt;
  cm autoAccept: true.&lt;br /&gt;
  cm bindKeyCombination: $i command toAction: [ cm text inspect ].&lt;br /&gt;
  cm openWithSpec&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
bindKeyCombination:action: 메서드는 실제로 ComposableModel 에 정의되어 있으므로 모든 UI 위젯에서 사용할 수 있습니다. 다른 표준 위젯에서는 키보드 단축키가 아닌 키보드 작업을 묶는 데 사용됩니다. 예를 들어, ButtonModel 의 초기설정자(initializer)의 마지막 줄은, 다음과 같이 버튼 동작의 실행을 스페이스 바에 할당합니다:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
	self bindKeyCombination: Character space toAction: [ self action ].&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====단일행 입력 필드 추가기능====&lt;br /&gt;
&lt;br /&gt;
TextInputFieldModel 은 단일행 입력을위한 TextModel 의 하위 클래스입니다. Enter 키 또는 Return 키를 눌렀을때, 캐리지 리턴(carriage return)이 발생되는것이 아니라 필드의 승인(accept) 작업이 실행됩니다. 그러나 이 위젯은 여러 줄의 텍스트를 표시하는 것이 가능합니다(예 : 여러 줄 텍스트를 붙여 넣는 경우). 하지만 텍스트의 수직 공간이 부족한 상황이라면 스크롤바는 표시되지 않습니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
또한 유용한 몇 가지 기능을 추가로 가지고 있습니다:&lt;br /&gt;
&lt;br /&gt;
* 흐릿하게 나오는 텍스트(ghost text)는 ghostText: 메시지로 설정할 수 있습니다.&lt;br /&gt;
* 별표만 표시하는 암호 필드는 beEncrypted 메시지를 보내 지정합니다.&lt;br /&gt;
* Entry completion can be added by using the ==entryCompletion:== message. The argument must be an ==EntryCompletion== instance, and we refer to that class for examples. &lt;br /&gt;
* 입력 완료는 entryCompletion: 메시지를 사용하여 추가 할 수 있습니다. 인수는 EntryCompletion 인스턴스가 되어야하며, 예제에서는 해당 클래스를 참조합니다.&lt;br /&gt;
* 입력 필드에 acceptOnCR:false 를 보내면 Enter 또는 Return 키를 눌러도 승인(accept) 작업은 실행되지 않으며, 결과적으로 사용자는 여러 줄의 텍스트를 입력 할 수 있게 됩니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===RadioButtonModel===&lt;br /&gt;
&lt;br /&gt;
라디오 버튼을 사용하면 그룹에서 최대 하나의 옵션만을 선택하게 할 수 있으며, 드롭 다운 메뉴와는 다르게 이 그룹의 모든 항목이 화면에 표시됩니다. Spec 의 RadioButtonModel 은 RadioButtonGroup 을 사용하여 그룹을 관리합니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
UI 예제로서 그림 7-1 에 표시된 기본적인 세탁기 제어 패널을 보여 드리겠습니다. 라디오 버튼에는 두 개의 그룹이 있습니다. 하나는 직물의 종류를 위한 것이고 다른 하나는 물의 온도(섭씨)를 위한 것입니다. 마지막으로 여분의 헹굼주기를 선택할 수 있는 라디오 버튼이 하나 더 있습니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
UI 의 코드는 RadioButtonExample 클래스에 있으며, 이 클래스의 정의 및 레이아웃은 간단합니다:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
ComposableModel subclass: #RadioButtonExample&lt;br /&gt;
	instanceVariableNames: &amp;#039;rinse f1 f2 f3 t1 t2 t3&amp;#039;&lt;br /&gt;
	classVariableNames: &amp;#039;&amp;#039;&lt;br /&gt;
	package: &amp;#039;Spec-BuildUIWithSpec&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
RadioButtonExample class &amp;gt;&amp;gt; defaultSpec&lt;br /&gt;
	^SpecColumnLayout composed&lt;br /&gt;
		newRow: [:r | &lt;br /&gt;
			r newColumn: [:c | c add: #f1 ; add: #f2 ; add: #f3 ];&lt;br /&gt;
			newColumn: [:c | c add: #t1 ; add: #t2 ; add: #t3 ]];&lt;br /&gt;
		newRow: [:r | r add: #rinse ];&lt;br /&gt;
		yourself&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
RadioButtonExample &amp;gt;&amp;gt; extent&lt;br /&gt;
	^160@150&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[image:Wash.png|none|thumb|그림 7-1 기본적인 세탁기 제어 패널의 스크린샷]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
initializeWidgets 메소드에서 fabric 에 대한 라디오 버튼인 f1 부터 f3 을 fabric 버튼 그룹에 추가하고, 온도 버튼 t1 에서 t3 까지를 temperature 버튼 그룹으로 설정합니다. 이러한 버튼 그룹은 UI 에서 사용되는 것은 때문에, 클래스의 인스턴스 변수가 아닌 메서드의 로컬 변수로 사용됩니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
아래의 코드에서 예로 든 방법을 찾을 수 있습니다. 먼저 두개의 버튼 그룹을 만든 다음 rinse 버튼을 만든 다음에, 다른 temperature 및 fabric 버튼을 만들어서 그룹으로 추가합니다. rinse 버튼은 선택 취소되도록 구성되며, f1 및 t1 버튼은 해당 그룹의 기본 버튼으로서, UI 가 열릴 때 선택되게 됩니다. 보다 명확하게 알아보기 위해서는, 더 의미가 있는 변수 이름을 사용하는 것이 좋습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
RadioButtonExample &amp;gt;&amp;gt; initializeWidgets&lt;br /&gt;
	| fabric temperature |&lt;br /&gt;
	fabric := RadioButtonGroup new.&lt;br /&gt;
	temperature := RadioButtonGroup new.&lt;br /&gt;
	&lt;br /&gt;
	rinse := self newRadioButton.&lt;br /&gt;
	rinse label: &amp;#039;Rinse Extra&amp;#039;;&lt;br /&gt;
		canDeselectByClick: true.&lt;br /&gt;
	&lt;br /&gt;
	f1 := self newRadioButton.&lt;br /&gt;
	f1 label: &amp;#039;Cotton&amp;#039;.&lt;br /&gt;
	fabric addRadioButton: f1; default: f1.&lt;br /&gt;
	&lt;br /&gt;
	f2 := self newRadioButton.&lt;br /&gt;
	f2 label: &amp;#039;Synthetic&amp;#039;.&lt;br /&gt;
	fabric addRadioButton: f2.&lt;br /&gt;
	&lt;br /&gt;
	f3 := self newRadioButton.&lt;br /&gt;
	f3 label: &amp;#039;Delicate&amp;#039;.&lt;br /&gt;
	fabric addRadioButton: f3.&lt;br /&gt;
	&lt;br /&gt;
	t1 := self newRadioButton.&lt;br /&gt;
	t1 label: &amp;#039;60&amp;#039;.&lt;br /&gt;
	temperature addRadioButton: t1;	default: t1.&lt;br /&gt;
	&lt;br /&gt;
	t2 := self newRadioButton.&lt;br /&gt;
	t2 label: &amp;#039;40&amp;#039;.&lt;br /&gt;
	temperature addRadioButton: t2.&lt;br /&gt;
	&lt;br /&gt;
	t3 := self newRadioButton.&lt;br /&gt;
	t3 label: &amp;#039;30&amp;#039;.&lt;br /&gt;
	temperature addRadioButton: t3&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
마지막으로 서로 다른 fabric 버튼을 클릭 할 때, 발생 되어야 하는 로직을 포함합니다. Synthetic 버튼을 선택하면, 60 도로 설정된 온도는 40 도로 낮아집니다. Delicate 버튼을 선택하면 온도는 30 도로 낮아지며, 다른 fabric 버튼은 사용할 수 없게 된다는 것을 그림 7-1 에서 확인할 수 있습니다. 반대로 다른 버튼을 눌러서 Delicate 버튼이 선택되지 않은 상황이라면, 다른 fabric 버튼은 다시 활성화됩니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
RadioButtonExample &amp;gt;&amp;gt; initializePresenter&lt;br /&gt;
&lt;br /&gt;
	f2 activationAction: [ t1 state ifTrue: [ t2 state: true ] ].&lt;br /&gt;
	f3 activationAction: [&lt;br /&gt;
		t1 disable; state: false.&lt;br /&gt;
		t2 disable; state: false.&lt;br /&gt;
		t3 state: true ].&lt;br /&gt;
	f3 deactivationAction: [ t1 enable. t2 enable ]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
이 기능을 통해 &amp;quot;RadioButtonModel&amp;quot;및 &amp;quot;RadioButtonGroup&amp;quot;의 주요 기능을 시연 해 보았습니다. 이 클래스의 추가 기능에 대해서는 구현된(implementation) 소스 코드를 참조하십시오.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===TabModel===&lt;br /&gt;
&lt;br /&gt;
위젯들이 같이 작동하도록 만들어진 두 번째 클래스 집합은 TabManagerModel 및 TabModel 클래스입니다. 탭이있는 UI 에서 TabManagerModel 은 다른 탭의 구성을 가지고 있으며, TabModel 은 탭 자체를 나타냅니다. 두개의 클래스가 어떻게 협력하는지 보여주기 위해서, 그림 7-2 처럼 섹션 7-2 의 세탁기 예제를 세탁 건조기로 확장해 보겠습니다. 이 예제는 탭이 있는 UI 를 가지고 있으며, 하나의 탭 은 세척 부분을 위한 것이고, 두 번째 탭은 스핀 사이클 및 건조이며, 세 번째 탭은 정보를 표시하게 됩니다.&lt;br /&gt;
&lt;br /&gt;
[[image:TabMgr.png|none|thumb|그림 7-2 세탁 건조기 제어패널의 스크린샷]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
탭을 사용하려면, 오직 TabManagerModel 만 UI의 인스턴스 변수로 유지하며 레이아웃에 추가해야합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
ComposableModel subclass: #TabMgrExample&lt;br /&gt;
	instanceVariableNames: &amp;#039;tabmgr&amp;#039;&lt;br /&gt;
	classVariableNames: &amp;#039;&amp;#039;&lt;br /&gt;
	package: &amp;#039;Spec-BuildUIWithSpec&amp;#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
TabMgrExample &amp;gt;&amp;gt; extent&lt;br /&gt;
	^250@160&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
TabMgrExample class &amp;gt;&amp;gt; defaultSpec&lt;br /&gt;
	^SpecLayout composed&lt;br /&gt;
		add: #tabmgr;&lt;br /&gt;
		yourself&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
탭을 표시하려면, 다음에 보이는것 처럼 addTab: 메시지를 사용해서 탭 관리자에 추가해야 합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
TabMgrExample &amp;gt;&amp;gt; initializeWidgets&lt;br /&gt;
	| tab |&lt;br /&gt;
	tabmgr := self newTabManager.&lt;br /&gt;
	tab := self newTab.&lt;br /&gt;
	tab model: RadioButtonExample new.&lt;br /&gt;
	tab label: &amp;#039;Wash&amp;#039;; closeable: false;&lt;br /&gt;
		icon: Smalltalk ui icons smallScreenshot.&lt;br /&gt;
	tabmgr addTab: tab.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
TabManagerModel 을 만든 뒤에, 코드는 TabModel 을 만들고 RadioButtonExample 클래스의 인스턴스를 표시하도록 구성합니다. model: 메시지는 Spec 의 UI 재사용 원칙에 따라 ComposableModel 을 사용합니다. 마지막으로 탭의 추가 속성을 설정해서 레이블, 아이콘 및 탭을 닫을 수 없도록 만듭니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
지금까지가, UI 의 첫 번째 버전을 가지고 7-2 절의 UI 를 탭으로 보여주는 데 필요한 것입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
TabManagerModel 은 동적이며, 새 탭을 추가하고 UI 가 열려있을 때는 탭을 제거 할 수도 있습니다. 이렇게 하려면 addTab: 및 removeTab: 메시지를 TabManagerModel 에 보내서, 인수로 탭을 추가하고 없애면 됩니다. 닫을 수 있는 탭에는 사용자가 UI 에서 탭을 제거 할 수 있는 닫기 버튼이 있습니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
사용자 인터페이스의 두 번째 부분을 추가해서, 옷을 건조시키기 위해 다음과 같이 &amp;quot;initializeWidgets&amp;quot;를 확장합니다:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
TabMgrExample &amp;gt;&amp;gt; initializeWidgets&lt;br /&gt;
	[...]&lt;br /&gt;
&lt;br /&gt;
	tab := self newTab.&lt;br /&gt;
	tab model: (self dryModel).&lt;br /&gt;
	tab label: &amp;#039;Dry&amp;#039;; closeable: false;&lt;br /&gt;
		icon: Smalltalk ui icons smallNew.&lt;br /&gt;
	tabmgr addTab: tab.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
이 탭은 일반적인 경우에, UI 의 각 탭에 대한 UI 클래스가 있음을 의미하는 ComposableModel 의 인스턴스로 구성되어야 합니다.&lt;br /&gt;
하지만 간단한 UI 탭을 위해서 클래스를 생성하는것은, 너무 무거울 수도 있습니다. 때문에, 동적 Spec(8 장 참조)을 사용해서 탭의 내용을 만드는 것도 가능합니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
예를 들어, dryModel 의 코드는 DynamicComposableModel 을 구성해서 두 개의 슬라이더를 표시합니다. 첫 번째는 회전주기(spin cycle)의 최대 속도를 선택하며, 두 번째는 건조주기(drying cycle)가 지속되는 시간을 선택합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
TabMgrExample &amp;gt;&amp;gt; dryModel&lt;br /&gt;
	| model |&lt;br /&gt;
&lt;br /&gt;
	model := DynamicComposableModel new.&lt;br /&gt;
	model instantiateModels: #(spin SliderModel dry SliderModel).&lt;br /&gt;
	&lt;br /&gt;
	model spin label: &amp;#039;Spin speed&amp;#039;; min: 400; max: 1600; quantum: 400.&lt;br /&gt;
	model dry label: &amp;#039;Dry time&amp;#039;; min: 0; max: 120; quantum: 10.&lt;br /&gt;
	&lt;br /&gt;
	model layout: (&lt;br /&gt;
		SpecColumnLayout composed&lt;br /&gt;
			add: #spin height: 30;	add: #dry height: 30;&lt;br /&gt;
			yourself).&lt;br /&gt;
	^model.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{GNOME3Notice|정말 단순한 UI 가 아니라면 DynamicComposableModel 은 사용하지 않는 것이 좋습니다. DynamicComposableModel 의 사용에 대한 더 많은 참고 사항은 8.3 절에 있습니다.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
UI 구성의 의 마지막으로 info 탭이 있습니다. 이 탭을 사용하면 TabManager 가 선택한 탭을 가져 오는 기능을 보여주고, 탭 선택에 대한 작업을 수행 할 수 있습니다. info 탭은 인스턴스 변수로 유지되며, 탭을 생성하기 전에 초기화하는 status 텍스트 필드를 포함합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
ComposableModel subclass: #TabMgrExample&lt;br /&gt;
	instanceVariableNames: &amp;#039;tabmgr status&amp;#039;&lt;br /&gt;
	classVariableNames: &amp;#039;&amp;#039;&lt;br /&gt;
	package: &amp;#039;Spec-BuildUIWithSpec&amp;#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
TabMgrExample &amp;gt;&amp;gt; initializeWidgets&lt;br /&gt;
&lt;br /&gt;
	[...]&lt;br /&gt;
	&lt;br /&gt;
	self createStatus.&lt;br /&gt;
	tab := self newTab.&lt;br /&gt;
	tab model: status.&lt;br /&gt;
	tab label: &amp;#039;Info&amp;#039;; closeable: false;&lt;br /&gt;
		icon: Smalltalk ui icons smallInfo.&lt;br /&gt;
	tabmgr addTab: tab.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
createStatus 메서드에서, TextModel 을 인스턴스화하고, 사용자가 그것을 편집할 수 없게 하며, 초기 텍스트를 설정합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
TabMgrExample &amp;gt;&amp;gt; createStatus&lt;br /&gt;
&lt;br /&gt;
	status := TextModel new.&lt;br /&gt;
	status disable;&lt;br /&gt;
		text: &amp;#039;Welcome to Washing Machine 2.0!\History: Wash &amp;#039; withCRs.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
마지막으로 initializePresenter 에서 탭을 선택하면, 현재 선택된 탭의 레이블이 상태 텍스트로 추가됩니다. 이 작업은 탐색기록을 생성하며, 세탁 건조기의 UI 를 완성합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
TabMgrExample &amp;gt;&amp;gt; initializePresenter&lt;br /&gt;
&lt;br /&gt;
	tabmgr whenTabSelected: [&lt;br /&gt;
		status text: (String streamContents: [:s |&lt;br /&gt;
			s nextPutAll: status text.&lt;br /&gt;
			s nextPutAll: &amp;#039; &amp;gt; &amp;#039;.&lt;br /&gt;
			s nextPutAll: tabmgr selectedTab label])].&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===툴바와 팝업메뉴===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Spec 의 툴바와 팝업 메뉴는 세가지 클래스 간의 공동 작업 결과입니다:&lt;br /&gt;
&lt;br /&gt;
* 다른 MenuGroupModel 인스턴스를 포함한 MenuModel 및 그 인스턴스를 구분자(splitter)로 나누어 표시.&lt;br /&gt;
* 여러개의 MenuGroupModel 인스턴스 각각은, MenuItemModel 인스턴스를 포함.&lt;br /&gt;
* 특정한 외형 및 동작을 가지는 메뉴 항목을 나타내는 다양한 MenuItemModel인스턴스.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
현재, Spec 은 UI 의 연관(contextual) 메뉴를 직접 지원하지 않습니다. 하지만 UI 에 툴바를 추가하는 것은 레이아웃에 추가되는 다른 위젯 일 뿐이므로 쉽습니다. 예를 들어, Pharo 5 의 표준인 WatchpointWindow 클래스를 생각해 보십시오. 이 클래스는 menu 인스턴스 변수에 포함 된 메뉴를 가지며, 레이아웃 메소드는 툴바를 위젯 열의 첫 번째 위젯으로 배치합니다:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
WatchpointWindow &amp;gt;&amp;gt; defaultSpec&lt;br /&gt;
	^ SpecColumnLayout composed&lt;br /&gt;
			add: #menu height: self toolbarHeight;&lt;br /&gt;
			add: #list;&lt;br /&gt;
			add: #inspectIt height: self toolbarHeight&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
MenuModel 클래스는 프로그래머가 인스턴스화 해야하는 유일한 클래스입니다. 그룹을 만들려면 블록을 인수로 사용해서 MenuModel 에 addGroup: 을 보냅니다. 이 작업을 통해 그룹이 만들어져 메뉴에 추가됩니다. 블록은 생성된 그룹의 인스턴스 하나만 인수로 받습니다. 마찬가지로 addItem: 을 MenuGroupModel 에 보내면, 새로운 MenuItemModel 이 만들어지며 그룹에 추가됩니다. addItem: 의 인수는 1 인수 블록이며, 이 인수는 생성된 메뉴 항목입니다. 이 설명은 조금 복잡합니다만, 결과 코드는 다음 예제처럼 매우 읽기 쉽습니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
MenuItemModel 은 다음과 같은 기본 설정 메서드를 제공합니다:&lt;br /&gt;
&lt;br /&gt;
* name: 메뉴 항목의 텍스트를 설정.&lt;br /&gt;
* icon: 아이콘을 설정.&lt;br /&gt;
* description: 툴팁을 추가.&lt;br /&gt;
* shortcut: 항목에 대한 shortcut 을 추가.&lt;br /&gt;
* action: 항목이 선택 될 때 실행할 블록을 추가.&lt;br /&gt;
* subMenu: 이 아이템에 대응하는 하위 메뉴를 나타내는 MenuModel 을 포함&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
MenuItemModel 은 도구 모음 및 메뉴 항목으로 사용되기 때문에, 텍스트없이 또는 아이콘없이 메뉴 항목을 만들 수 있으며, 툴팁은 툴바 및 메뉴 항목으로 사용될 때 어느 경우에도 작동합니다. 마지막 두 가지 기본설정 옵션이 있는데, 동작을 실행하거나 하위 메뉴를 여는 것은 &amp;#039;&amp;#039;&amp;#039;상호 배타적이지 않습&amp;#039;&amp;#039;&amp;#039;니다. 예를 들어 하위 메뉴가 있는 항목 위로 마우스를 가져 가면 하위 메뉴가 열리기는 하지만, 여전히 메뉴 항목 자체를 선택할 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
메뉴 구성을 보여주기 위해서, 이전 섹션의 세탁기 예제를 확장해 보겠습니다. 기술자가 기계를 문제 해결하거나 수리할 때 사용할 메뉴가 추가되었습니다. 이렇게 하기 위해서, menu 인스턴스 변수와 접근자를 populateMenu 메소드와 TabMgrExample 클래스에 추가합니다. initializeWidgets 메소드에는 self populateMenu 행이 추가되며 이 메소드의 코드는 다음과 같습니다:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
TabMgrExample  &amp;gt;&amp;gt; populateMenu&lt;br /&gt;
	| submenu |&lt;br /&gt;
&lt;br /&gt;
	menu := MenuModel new.&lt;br /&gt;
	submenu := MenuModel new.&lt;br /&gt;
&lt;br /&gt;
	submenu addGroup: [ :group |&lt;br /&gt;
			group addItem: [ :item |&lt;br /&gt;
				item name: &amp;#039;Soft Reset&amp;#039;;&lt;br /&gt;
					action: [ status text: &amp;#039;History: Wash &amp;#039;.];&lt;br /&gt;
					icon: Smalltalk ui icons exception ].&lt;br /&gt;
			group addItem: [ :item|&lt;br /&gt;
				item name: &amp;#039;Hard Reset&amp;#039;;&lt;br /&gt;
					action: [ GrowlMorph openWithContents: &amp;#039;Just pull the plug!&amp;#039; ];&lt;br /&gt;
					icon: Smalltalk ui icons smallError ] ].&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
submenu 라는 임시 변수를 정의한 후, 위의 코드는 menu 및 submenu 변수를 새로운 MenuModel 인스턴스로 초기화 합니다. 예를 들어, 코드는 우선 하위 메뉴를 구성해서, 소프트 및 하드 리셋 메뉴 항목에 각각의 아이콘과 작업을 추가합니다. 이 메뉴 항목의 코드는 간단합니다(GrowlMorph 는 우리의 세탁기에서도 작동한다고 가정하겠습니다).&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
이제 메인 메뉴의 정의를 계속하겠습니다. 이전에 말했듯이, 마우스 오른쪽 버튼을 클릭 할 때 나타나는 팝업 메뉴를 만드는 쉬운 방법은 없습니다. buildWithSpecAsPopup popUpInWorld 를 MenuModel 인스턴스로 보내면 pop-up menu 를 표시 할 수 있지만, 이 코드를 마우스 오른쪽 버튼으로 클릭하게 하는 것은 간단하지 않습니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
여기에서 사용하는 해결 방법은, 메뉴가 선택 될 때에 대한 팝업 메뉴 항목을 정의하는 것입니다. 이런 처리는 메뉴가 보이지 않을 때 메뉴 항목을 선택할 수 있는 방법이 없기때문에, 마치 닭과 달걀의 문제처럼 보일수도 있습니다. 하지만 그렇지는 않습니다. 왜냐하면 메뉴 항목에 바로 가기 키를 연결할 수 있기 때문에 바로 가기를 누르면 메뉴는 나타납니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
아래 코드에서는 populateMenu 메서드를 계속 진행하고 주 메뉴를 채웁니다. 첫 번째 항목은 메뉴 공개에 대한 항목입니다. Windows 및 Linux 에서는 &amp;#039;&amp;#039;&amp;#039;control-r&amp;#039;&amp;#039;&amp;#039; 을 누르며, &amp;#039;&amp;#039;&amp;#039;$r meta&amp;#039;&amp;#039;&amp;#039; 키 조합으로 지정된대로, Mac 에서는 &amp;#039;&amp;#039;&amp;#039;command-r&amp;#039;&amp;#039;&amp;#039; 을 눌러서 발동됩니다. 이 메뉴 항목은 툴팁을 지정하는 방법도 보여줍니다. 그 외의 나머지 코드는 간단합니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
	menu addGroup: [ :group |&lt;br /&gt;
		group addItem: [ :item |&lt;br /&gt;
			item name: &amp;#039;Reveal this menu&amp;#039;;&lt;br /&gt;
				action: [menu buildWithSpecAsPopup popUpInWorld];&lt;br /&gt;
				description: &amp;#039;This entry exits to have a shortcut for this menu.&amp;#039;;&lt;br /&gt;
				shortcut: $r meta ].&lt;br /&gt;
		group addItem: [ :item |&lt;br /&gt;
			item name: &amp;#039;Status Info&amp;#039;;&lt;br /&gt;
				action: [ GrowlMorph openWithContents: tabmgr selectedTab label];&lt;br /&gt;
				icon: Smalltalk ui icons help] ].&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
마지막으로 주 메뉴에 두 번째 그룹을 추가해서, 위의 두 항목과 아래 항목 사이에 구분자(splitter)를 표시합니다. 마지막 항목은 메서드의 시작 부분에서 만들어진 하위 메뉴를 보여줍니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;smalltalk&amp;quot;&amp;gt;&lt;br /&gt;
	menu addGroup: [ :group |&lt;br /&gt;
		group addItem: [ :item |&lt;br /&gt;
			item name: &amp;#039;Actions&amp;#039;;&lt;br /&gt;
				subMenu: submenu ]].&lt;br /&gt;
	&lt;br /&gt;
	menu applyTo: self. &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
위 메소드의 마지막 부분에서 applyTo: 메소드가 메뉴로 전송되어 정의된 바로 가기(shortcuts)가 TabMgrExample 위젯에 등록됩니다. 메뉴 항목의 바로 가기를 누르면, 해당 메뉴 항목의 동작이 시작(trigger)됩니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
메뉴를 UI 에 연결하려 할 때 다른 방법이 필요한건 아닙니다. 예제에서는 툴바 위젯이 표시되지 않기 때문에, 레이아웃 메소드에 메뉴를 추가 할 필요는 없습니다. 하지만 도구 모음을 추가 할 경우, 이 섹션의 시작 부분에있는 WatchpointWindow 예제에서 처럼 menu 를 레이아웃에 추가해야합니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
마지막으로 메뉴가 완전히 정적인 것만은 아닙니다. enabled:false 를 보내면 메뉴 항목을 비활성화 하게 되며, 반대의 경우라면 enabled:true 를 보내면 됩니다. 또한, 항목 및 그룹을 추가 및 제거해서 메뉴의 구조를 변경하는 것도 가능합니다. 위의 예제에서, 메뉴는 각 팝업에서 다시 작성되기 떄문에 변경 사항은 즉시 표시됩니다. 대조적으로, 툴바로 사용되는 경우에, 메뉴는 위젯이기 때문에 8 장에서 논의된 것처럼 UI 를 다시 만들어야 합니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===결론===&lt;br /&gt;
&lt;br /&gt;
이 장에서는 고급 위젯을 구성하고 사용하는 방법을 살펴봤습니다. TextModel 과 그 하위 클래스인 TextInputFieldModel, RadioButtonModel, 그리고 그룹화 기능 및 TabModel 과 탭 관리자, 그리고 다양한 클래스들을 포함하는 MenuModel 등을 살펴봤습니다. 일부 예제에서는 동적 Spec 을 사용했으며, 이에 대한 자세한 내용은 다음 장에서 설명하겠습니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Notes==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:TheSpecUIframework]]&lt;/div&gt;</summary>
		<author><name>Onionmixer</name></author>
	</entry>
</feed>