The Artima Developer Community
Sponsored Link

Agile Buzz Forum
How To Build A GUI With Pollock - Hooking Up The Interface

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
James Robertson

Posts: 29924
Nickname: jarober61
Registered: Jun, 2003

David Buck, Smalltalker at large
How To Build A GUI With Pollock - Hooking Up The Interface Posted: Mar 11, 2004 12:22 PM
Reply to this message Reply

This post originated from an RSS feed registered with Agile Buzz by James Robertson.
Original Post: How To Build A GUI With Pollock - Hooking Up The Interface
Feed Title: Pollock
Feed URL: http://www.cincomsmalltalk.com/rssBlog/pollock-rss.xml
Feed Description: Pollock - the next VW GUI
Latest Agile Buzz Posts
Latest Agile Buzz Posts by James Robertson
Latest Posts From Pollock

Advertisement

Today we are going to hook up all our ClassHierarchyBrowser's panes. Let's start by hooking up the TreeView pane to the middle ListBox:

	hookupInterface
		(self paneAt: #TreeView1) when: #selectionChanged send: #fillProtocolList to: self.

-+-+-+-

	fillProtocolList
		(self paneAt: #TreeView1) selection ifNotNil: 
			[:value | (self paneAt: #ListBox1) list: value organization categories]

Well, we got some 'splainin' to do! As stated in How To ... The Window, there are a set of messages that the Pollock framework calls in your UserInterface subclass. All you have to do is fill them in with the right stuff. After we have created the panes, in our #createInterface method, we now want to tell them how to interact with each other, and we do this in the #hookupInterface method.

At the end of How To ... The Panes we said we get back to how we get our hands on widgets after we've added them to our UserInterface, without having to necessarily hold on to the panes in instance variables. As you might surmise, the method #paneAt: does the trick for us. However, we need to go back a step to explain a bit of detail.

Whenever a pane is added as a subpane to another pane via #addComponent:, it first checks that pane to see if it has an "ID." A pane ID is just a symbol, that uniquely identifies that pane from all other sibling subpanes in the owning pane. (Read that twice if you just got confused) By default, when you create a new pane, it does not have an ID. When you add a pane without an ID as a subpane to another pane, the Pollock framework automatically gives that pane a unique ID, by combining the name of the pane's class, with an integer. For each instance of a new pane of the same class as another pane, the integer is incremented. Given this set of rules, the resulting panes that we added previously to our window have the following default IDs:

  • #TreeView1 - Our Class Hierarchy pane
  • #ListBox1 - Our protocols pane
  • #ListBox2 - Our method names pane
  • #TextEdit1 - Our pane that displays the source for a method

Under the hood, these were added these to our WidgetInventory. So, the start of #hookupInterface gets our TreeView pane by using the #paneAt: method. Next we send the #when:send:to: message to that pane. This message is part of the "Trigger Event" framework that has been a part of VisualWorks for quite a long time. For those of you unfamiliar with it (or are more familiar with the dependency "Update/Change" framework mechanism used in the Wrapper framework), I'll attempt to give you a bit of an overview here.

While the "Trigger Event" framework has been a part of VisualWorks for many years, it was never used by the Wrapper GUI framework. Instead the Wrapper framework used the older "Update/Change" framework. Both are examples of a kind of a "Publish/Subscribe" pattern. It's easier to describe "Trigger Event" by first describing "Update/Change"

In "Update/Change", you explicitly set one object as a dependent of another object by sending #addDependent: to the "Publishing" object, passing in the "Subscriber" object. Then when you send "self changed" (or any other of the various changed messages), the "Publishing" object would iterate over all of it's dependents, and send them an "update" message. Most of the changed messages allow you to pass information, such as a what is called an "aspect", which is just a symbol, as well as an optional parameter. There are comparable "update" messages, that pass the aspect and optional parameter. Object has default implementations of the changed and update methods, so if you do nothing else, when you set up the dependency by sending #addDependent:, nothing bad happens... but then, nothing of value happens either.

In order for a dependent object to meaningfully participate in a changed action, it has to implement it's own update method, that will be called instead of the default do nothing method in Object. The typical method is #update:with:from:. If you implement this, no matter which of the changed methods was called, you will get values in the three parameters to this method. The the "update:" argument is the aspect (which may be nil), the "with:" argument is the parameter if passed, and the final "from:" value is the object which sent the changed message to begin with. Your #update:with:from: will probably take on a very procedural style, since you'll write a lot of "If Aspect Is X and From Is Y Do Z With Parameter."

This begins to show what the general problems are about the "Update/Changed" mechanism. It gets worse though. Your dependent object will be called on every invocation of the "Publishing" objects changed message, even if your object doesn't care about that particular change. So, you write code in your #update:with:from: that in effect says "If I don't care about this change, ignore it." (and maybe even send the #update:with:from: to my super class, just in case it does care about it). When you start having a system with a lot of objects in complex dependency relationships between them, you start having a whole slew of messages flying around the system. The problem is worse in a GUI framework like Wrapper. UI elements, their models and model handlers are bound up in tight dependency relationships under the hood. You, the developer add to this by supplying your own model objects that coordinate between your model and the UI elements, and you quickly see a storm of update/change messages screaming around your UI system. Remember, every object in a dependency is sent the update message because there is no way for the sending object to know if its dependents really care.. and on and on.

The Trigger Event system eliminates these message flurries by setting up a system where explicit information is given about who wants a message, and what event triggers that message, at the time of Subscribing. In turn, the "Publisher" instead of simply sending out an ignorant "changed" message, sends a #triggerEvent: message, passing the name of the event that it is invoking.

All of Pollock uses the Trigger Event framework, and does not use the Update/Change framework. So much so, that if Pollock were to be given an older Update/Change object, it simply will ignore any of the "changed" messages it might send, and not interact with it at all.

Back to the framework itself. This framework is basically the same framework that was in VisualSmalltalk Enterprise, which by all accounts is the originator of the Trigger Event system used, to some extent or another, by many current Smalltalk dialects.

An object which uses Trigger Event system in VisualWorks has the option to limit a Subscriber to only a set of published events, or if it can Subscribe blindly to any event, even ones that the Publisher doesn't explicitly say it can trigger. This is determined by if the class of the object answers true or false to the #ambivalentEventChecking message. By default, objects inherited from Object answer true to this. All Pollock objects that do trigger events, answer false to this.

If an object's class answers false, then it must also implement a method named #constructEventsTriggered, that answers a collection of the events that it triggers. At runtime, if the object answers false #ambivalentEventChecking, and an Subscribing object attempts to subscribe to an event that the Publishing object does not list in it's #constructEventsTriggerd collection (via a when:send:... message), then the Publishing object will raise an error.

If an object's class answers true (which is the default for non-Pollock classes), then it need not implement the #constructEventsTriggered method (although it can but will be ignored). A Subscribing object can then subscribe any event in the world. If Publishing object does not trigger that event, nothing happens. If it does, then, well, it does. This allows the developer to start out without having to know all the events a Publishing object will trigger, and allow the incremental development of these interaction interfaces.

The question might now arise, "How do you know what Trigger Events a Pollock object triggers?" The simple answer is to go to the class side of that object and look at it's #constructEventsTriggered method (or the documentation for Pollock, when it arrives... in fact, there is extensive documentation about what events and how the Trigger Event system works in Wrapper and it's widgets).

Let's look at the result for sending #constructEventsTriggered to ListBox and TreeView:

	ListBox:
		#aboutToChangeList #aboutToChangeSelection #aboutToGetFocus #aboutToLoseFocus
		#addedAsSubpane #backTabbed #clicked #doubleClicked #dragDisplayEmphasis: 
		#dragDrop: #dragEnterAcceptDrop: #dragHideEmphasis: #dragLeaving: 
		#dragMoveAcceptDrop: #dragSourceBeginDrag: #dragSourceMove: #gettingFocus 
		#losingFocus #popupMenuClosed #popupMenuItemSelected: #popupMenuOpened 
		#rightClicked #scrollDown #scrollLeft #scrollRight #scrollUp #selectionChanged 
		#selectionChangeFinished #selectionChanging #selectionListChanged 
		#selectionListChanging #tabbed
		
	TreeView:
		#aboutToChangeList #aboutToChangeSelection #aboutToEndEditingItem: 
		#aboutToGetFocus #aboutToLoseFocus #aboutToStartEditingItem: #addedAsSubpane 
		#backTabbed #clicked #doubleClicked #dragDisplayEmphasis: #dragDrop: 
		#dragEnterAcceptDrop: #dragHideEmphasis: #dragLeaving: #dragMoveAcceptDrop: 
		#dragSourceBeginDrag: #dragSourceMove: #endEditOfItem: #gettingFocus 
		#itemContracted #itemExpanded #losingFocus #popupMenuClosed 
		#popupMenuItemSelected: #popupMenuOpened #rightClicked #scrollDown #scrollLeft 
		#scrollRight #scrollUp #selectionChanged #selectionChangeFinished #selectionChanging 
		#selectionListChanged #selectionListChanging #startingEditOfItem: #subpaneSelected: 
		#tabbed

Let's face it, looking at it in an Inspector is much more comprehendible than the above.

The two have a lot of events in common. For our purposes in the ClassHierarchyBrowser, all we really care about is the #selectionChanged event, which both support. And that, at last after all the above, is the event we subscribe to for the TreeView. We send "when: #selectionChanged send: #fillProtocolList to: self." to our TreeView. That event is triggered as soon as a new selection is made in the tree. In our #when:sent:to", we are in effect saying "When the TreeView triggers the #selectionChanged event, send the message #fillProtocolList to me".

So we wrote the #fillProtocolList to get the TreeView, and send the #selection message to it. That answers back the current object that is selected, or nil if there is no selected object. In our case, we will get back the Class that is selected (or nil). We wrap that knowledge in the #ifNotNil:, and then, using a bit of Smalltalk reflection, " "value organization categories" we use the list: method of the ListBox to set the new list to be viewed. Pollock takes care of the rest, and if we start up our Class Hierarchy Browser:

	ClassHierarchyBrowser open

We now see that selecting an item in the tree, fills the protocol pane. To hook up the protocol pane to the method list pane, we change our #hookupInterface and add one more method:

	hookupInterface
		(self paneAt: #TreeView1) when: #selectionChanged send: #fillProtocolList to: self.
		(self paneAt: #ListBox1) when: #selectionChanged send: #fillMethodList to: self.

-+-+-+-

	fillMethodList
		(self paneAt: #TreeView1) selection ifNotNil: 
			[:classValue | (self paneAt: #ListBox1) selection ifNotNil: 
				[:protocolValue | (self paneAt: #ListBox2) list: 
					(classValue organization listAtCategoryNamed: protocolValue)]]

Finally, we hook up a change to the method list selection to the TextEdit, to show the code for the selected method:

	hookupInterface
		(self paneAt: #TreeView1) when: #selectionChanged send: #fillProtocolList to: self.
		(self paneAt: #ListBox1) when: #selectionChanged send: #fillMethodList to: self.
		(self paneAt: #ListBox2) when: #selectionChanged send: #displayMethodSource to: self
	
-+-+-+-

	displayMethodSource
		(self paneAt: #TreeView1) selection ifNotNil: 
			[:classValue | (self paneAt: #ListBox2) selection ifNotNil: 
				[:methodValue | (self paneAt: #TextEdit1) model value: (classValue sourceCodeAt: methodValue)]]

We now have a working, if simple, Class Hierarchy Browser. Maybe too simple. We should be able to select multiple protocols, and see the combined list of methods. We'll do that, with just a couple of lines of code, next time


And So It Goes
Sames

Read: How To Build A GUI With Pollock - Hooking Up The Interface

Topic: Iteration plan whiteboard Previous Topic   Next Topic Topic: Learning from the gamers

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use