The Artima Developer Community
Sponsored Link

Agile Buzz Forum
Horizontal Scrollbar Plus Plus

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
Horizontal Scrollbar Plus Plus Posted: Jan 24, 2008 1:52 PM
Reply to this message Reply

This post originated from an RSS feed registered with Agile Buzz by James Robertson.
Original Post: Horizontal Scrollbar Plus Plus
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'll address the horizontal scrollbar.

The Lateral Basics

As we noted in the last posting, there are two basic pieces of information that a scrollbar needs. For a horizontal scrollbar it is the maximum width and the current left offset.

Each of these has an accessor on our Artist.

For the maximum width the accessor is #maximumItemWidth, and for the offset it is #leftOffset. As you see, and saw in the last posting, some of the names come from the notion of dealing with a list of some kind. In Widgetry, there are scrollbars in three general places, on EnumerationPanes (ListBox, Grid and TreeView), TextEdit and Form.

While it seems that having a list oriented set of API names may be constraining, in fact you can think of a TextEdit as just a list of lines of text. That leaves only the Form and our new ScrollingCanvas sitting somewhat outside the meme. How we are dealing with that here is exactly how it is dealt with in the Form. More on that in later postings

We start off with giving our ScrollingCanvasArtist its first two horizontal scrollbar APIs:

	ScrollingCanvasArtist>>maximumItemWidth
		^self frame bounds width
		
	ScrollingCanvasArtist>>leftOffset
		^0

Like we did for the vertical scrollbar, our maximum size for now is the total width of the frame. For the offset, we start off by faking it with 0. Note that where the vertical scrollbar is 1 based for the offset (there it was topItemIndex) the horizontal scrollbar is 0 based.

The horizontal scrollbar needs one more bit of information in order to show up. This is called the #rightEdgeOffset. This is the amount of extra space the to the right of the longest visible item (in our case, the total width) should be chopped off in order to balance out any pane specific added left offset. In EnumerationPanes and the TextEdit, we don't display the items or the text fully to the left edge of the pane, and instead these have a bit of extra padding on the left. When doing calculations for how big thumbs are and so on, we need to compensate for that.

Our pane though will always display with no initial left offset, so our #rightEdgeOffset is 0.

	ScrollingCanvasArtist>>rightEdgeOffset
		^0

Now if we turn on the horizontalScrollbar in our CustomWidgetWork:

	CustomWidgetWork>>hookupInterface

		customWidget drawActionBlock:
			[:aGraphicsContext :pane |
			| oldLineWidth oldPaint |
			oldLineWidth := aGraphicsContext lineWidth.
			oldPaint := aGraphicsContext paint.
			aGraphicsContext lineWidth: 5.
			aGraphicsContext paint: ColorValue red.
			aGraphicsContext displayLineFrom: 0 @ 0 to: pane frame bounds corner.
			aGraphicsContext lineWidth: 10.
			aGraphicsContext paint: ColorValue blue.
			aGraphicsContext displayLineFrom: pane frame bounds bottomLeft to: pane frame bounds topRight.
			aGraphicsContext paint: oldPaint.
			aGraphicsContext lineWidth: oldLineWidth].
		"Optional Border code:"
		"customWidget borderType: #ridged"
		"customWidget borderType: #raised"
		"customWidget borderType: #etched"
		"customWidget borderType: #lowered"
		"customWidget borderType: #line"
		"customWidget border: TwistBorder new"
		"customWidget verticalScrollbar: true."
		customWidget horizontalScrollbar: true

And then execute CustomWidgetWork open we see:

And if we uncomment the line customWidget verticalScrollbar: true, and open our tester again, we see this:

Horeshoes and Hand Grenades

As we see, the horizontal scrollbar is already compensating for the fact that the vertical scrollbar has taken up some of the inner edge of our pane and showing us that it is ready to scroll. On the other hand, the vertical scrollbar isn't showing the same thing, even though it should. Also if you click on the horizontal scrollbar, nothing happens.

The reason the vertical scrollbar isn't showing the same compensation is that last time we told it that the total scroll height was 1. As noted then, that was a fake value to get us started. However, the real value for now should be the total height of the pane, since that is the area we are drawing on. So if we change #totalScrollHeight to match what we did with #maximumItemWidth:

	ScrollingCanvasArtist>>totalScrollHeight
		^self frame bounds height

Now we see:

Lies, Damned Lines, Scrollbars

If we leave things as they are, there is nothing but fake numbers and sizes, and that isn't close to where we want to go. Our initial goal is to have a canvas that is possibly larger than the area of its frame and if so, have it be able to scroll. While we could easily just tell the drawing code we wrote to draw to coordinates outside the width and height of the frame, there is no way to tell the scrolling world how to deal with that.

Therefore, we are going to add a new attribute to our pane, which we'll call #contentsExtent. With this, we can specify a size of the contents of the pane that is independent of any frame that it might have.

We'll put the actual instance variable on the ScrollingCanvasArtist, since it will be used for drawing and calculating scrollbars. Since this is also a user supplied configuration value, we'll add accessors to the ScrollingCanvas pane itself so they don't have to ever talk to the artist directly.

So here are the changes to ScrollingCanvas

	ScrollingCanvas>>contentsExtent
		^self artist contentsExtent.

	ScrollingCanvas>>contentsExtent: aPointOrNil 
		| oldValue |
		oldValue := self artist contentsExtent.
		self artist contentsExtent: aPointOrNil.
		oldValue = self artist contentsExtent ifFalse: [self announce: PreferredExtentChanged]

Note here that for the setting method, we first get the existing value from the artist, set the new value and then test if it has changed, and if so, announce PreferredExtentChanged.

As we did with when we turn on the scrollbars, we always announce PreferredExtentChanged whenever we do something to the pane that might make the preferred extent change. Back to that in a moment.

For our ScrollingCanvasArtist, we add the contentsExtent instance variable:

	Smalltalk.Widgetry defineClass: #ScrollingCanvasArtist
		superclass: #{Widgetry.CanvasArtist}
		indexedType: #none
		private: false
		instanceVariableNames: 'interiorDecoration contentsExent '
		classInstanceVariableNames: ''
		imports: ''
		category: 'CustomWidgetry'

And then the accessor methods:

	ScrollingCanvasArtist>>contentsExtent
		^contentsExtent ifNil: [self frame bounds extent]

	ScrollingCanvasArtist>>contentsExtent: aPointOrNil 
		contentsExtent := aPointOrNil

Note here that we set the default to be in effect the frame bounds extent. This is so that if a user doesn't set the contents extent, it will still be usable.

Pottery Barn Rule

Next we want to use this value in our existing scrollbar support methods. So we change #maximumItemWidth, #listSize and #totalScrollHeight to use this value:

	ScrollingCanvasArtist>>maximumItemWidth
		^self contentsExtent x

	ScrollingCanvasArtist>>listSize
		^self contentsExtent y

	ScrollingCanvasArtist>>totalScrollHeight
		^self contentsExtent y

If we open our CustomWidgetWork we don't see any difference, which is exactly what we want. So, let's change the #hookupInterface to use our new value:

	CustomWidgetWork>>hookupInterface

		customWidget drawActionBlock:
			[:aGraphicsContext :pane |
			| oldLineWidth oldPaint |
			oldLineWidth := aGraphicsContext lineWidth.
			oldPaint := aGraphicsContext paint.
			aGraphicsContext lineWidth: 5.
			aGraphicsContext paint: ColorValue red.
			aGraphicsContext displayLineFrom: 0 @ 0 to: pane frame bounds corner.
			aGraphicsContext lineWidth: 10.
			aGraphicsContext paint: ColorValue blue.
			aGraphicsContext displayLineFrom: pane frame bounds bottomLeft to: pane frame bounds topRight.
			aGraphicsContext paint: oldPaint.
			aGraphicsContext lineWidth: oldLineWidth].
		"Optional Border code:"
		"customWidget borderType: #ridged"
		"customWidget borderType: #raised"
		"customWidget borderType: #etched"
		"customWidget borderType: #lowered"
		"customWidget borderType: #line"
		"customWidget border: TwistBorder new"
		customWidget contentsExtent: 500 @ 500.
		customWidget horizontalScrollbar: true.
		customWidget verticalScrollbar: true.

Now if we open our CustomWidgetWork we see this:

Smoke Them If You Got Them

We see our scrollbars are now showing the effect of having what seems to be a bigger area to scroll, however our drawing doesn't take advantage of it. Therefore, we have one change to make, and that is to our #drawActionBlock.

Instead of using the frame bounds for the extent, we'll use the new #contentsExtent:

	CustomWidgetWork>>hookupInterface

		customWidget drawActionBlock:
			[:aGraphicsContext :pane |
			| oldLineWidth oldPaint |
			oldLineWidth := aGraphicsContext lineWidth.
			oldPaint := aGraphicsContext paint.
			aGraphicsContext lineWidth: 5.
			aGraphicsContext paint: ColorValue red.
			aGraphicsContext displayLineFrom: 0 @ 0 to: pane contentsExtent.
			aGraphicsContext lineWidth: 10.
			aGraphicsContext paint: ColorValue blue.
			aGraphicsContext displayLineFrom: (0 @ pane contentsExtent y) to: (pane contentsExtent x @ 0).
			aGraphicsContext paint: oldPaint.
			aGraphicsContext lineWidth: oldLineWidth].
		"Optional Border code:"
		"customWidget borderType: #ridged"
		"customWidget borderType: #raised"
		"customWidget borderType: #etched"
		"customWidget borderType: #lowered"
		"customWidget borderType: #line"
		"customWidget border: TwistBorder new"
		customWidget contentsExtent: 500 @ 500.
		customWidget horizontalScrollbar: true.
		customWidget verticalScrollbar: true.

Now this is what we see:

And if we resize our window, we see that indeed we are getting what we wanted:

Preferred Extent Again

Before we leave today, we want to talk again about the preferred extent. The basic notion of this is what is the minimum size on the screen a pane will take up given its current contents and attributes. For instance, the preferred extent for a CheckBox with no label is the size of the check image. If we add a label, then the preferred extent changes to include the size of the label plus the gap between the check image and the label.

There is a #preferredExtent method in the CanvasArtist which we inherit from that just gives the size of any border and any interior decoration. But that isn't enough for our ScrollingCanvas. We want to include the contents extent if there is any.

We thus add our own #preferredExtent method to our ScrollingCanvasArtist:

	ScrollingCanvasArtist>>preferredExtent

		| totalExtent |
		totalExtent := super preferredExtent.
		contentsExent ifNotNil: [totalExtent := totalExtent + contentsExent].
		^totalExtent

This makes it possible for someone to create a pane with a #contentsExtent, add borders and turn on scrollbars, and then ask the pane #preferredExtent to determine how big the whole thing would be to make sure that everything could be seen. The mechanism is already on Pane, so we don't have to have a forwarding method from our pane to the artist.

Next Time

We'll look into making the vertical scrollbar actually work, which will mean creating a custom Agent for our pane.

And So It Goes
Sames

Read: Horizontal Scrollbar Plus Plus

Topic: Reforming the News Previous Topic   Next Topic Topic: What's in Store This Week

Sponsored Links



Google
  Web Artima.com   

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