Pragana's Tcl Guide

Old notes

More skinable tk widgets

Buttons are easy to create, as we have seen in the last installment of this series. What about more complex widgets? Both Xmms and Winamp® feature a nice volume control. Is this possible with tcl code too?
Of course! This time I will show you how to do this. Let's look at the bitmap for the several volume level states of the control:

volume bitmap

The image have two main sections. The first section is an array of images for the background of the control, for a quantized number of volume settings. The second section have the slider button in two states, idle or while dragging the control. The background images moreover, are 28, what means we must approximate to one of two, when doing a fine adjust at the actual volume level.
The code to import those bitmaps in our tcl program is very easy. Each bitmap is 15 pixels high, so we may write a loop to place each slice in a tk's image object, which we will name is0, is1, ... is27.

	set steps 28
set sw [image width isc]
set sh 15

for {set i 0} {$i < $steps} {incr i} {
set img is${i}
image create photo $img
$img copy isc -from 0 [expr $i * $sh] \
$sw [expr ($i+1) * $sh - 2]

Now we need to capture the slider images, as well. The code is not much more difficult. We just have to take care of the offsets and determinate
the size of the slider images before coding. We will get those images asib0 and ib1.
image create photo ib0
image create photo ib1
ib0 copy isc -from 0 [expr $steps * $sh + 2 ] 14 [expr $steps * $sh + 13]
ib1 copy isc -from 15 [expr $steps * $sh + 2 ] 28 [expr $steps * $sh + 13]

Now we need to display both images, one for the background of the controle, and the other for the slider. As we want the slider displayed over the background, the choice of geometry managers will be the placer. Then we create a container frame and place both images as labels, the slider image over the background.

	frame .bts -width 100 -height 50

label .lb0 -image is0 -highlightthickness 0 -border 0
label .lb1 -image ib1 -highlightthickness 0 -border 0

place .lb0 -x 0 -y 0 -in .bts
place .lb1 -x 30 -y 1 -in .bts

pack .bts -padx 5 -pady 15

Here is how it looks like, just after being displayed:
Now, look at the same widget, as we move the slider:
Notice that, in the top image, the first background is being displayed. The slider gets its first bitmap, or ib1, because the other will show only when the mouse grabs it. The next step now is to define the bindings, so it will behave as expected. There are two kind of bindings we need. First, binding for the slider motion, so we set our background bitmap reflecting its position change, and also place the slider in a new location when the mouse buttons is released. The other binding is for when the user clicks outside of the slider, in the background directly, which should result in a quick change of the volume.
Let's look at the bindings for everything:

bind .lb1 <1> {+
set x %X
.lb1 config -image ib0
bind .lb1 <ButtonRelease-1> {+
.lb1 config -image ib1
bind .lb1 <B1-Motion> {+
set x1 %X
array set a [place info .lb1]
set newx [expr $a(-x) + $x1 - $x]
set maxx [expr $sw - 15]
if {$newx > $maxx} {
set newx $maxx
if {$newx < 0} {
set newx 0
set k [expr $newx * $steps / [winfo width .lb0]]
.lb0 config -image is$k
place .lb1 -x $newx
set x $x1
bind .lb0 <1> {+
#puts "changed %W -> %x %y"
set k [expr %x * $steps / [winfo width .lb0]]
.lb0 config -image is$k
set newx %x
set maxx [expr $sw - 16]
if {%x > $maxx} {
set newx $maxx
place .lb1 -x $newx -anchor nw

Ok, this is not practical to use as a widget yet, but I hope you got the idea. Some suggestions to change this short tutorial in a real world widget are, first change all the local variables into array components or regular namespace variables, where each instance of the widget have its own private variables. Second, when changing the slider position, store the (possibly scaled) value of its new position in a user defined variable for the official position (like -variable in regular tk's scale widget). This is done near the end of .lb1 <B1-Motion> binding. Third, create code for transparent creation and destruction of the widget.

You may get the full source code of this volume control here, its volume.bmp image file, and while you are still here, the source for our previous skinable button tutorial

That's all fellows. Happy hacking!

Back Home