Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Can't bind mouse buttons to canvas

343 views
Skip to first unread message

tcl...@roadrunner.com

unread,
Oct 8, 2010, 1:57:10 PM10/8/10
to
This is my first post. I'm a novice Tcl programmer, although I have
experience in other languages. I wonder if you could help me with a
problem I have scrolling a canvas with a wheel mouse.
The application is many years old, but we have recently ported it from
Unix to Red Hat Linux 5.2 with Tcl 8.4.19.
The canvas scrolls as expected using the scrollbar and the left and
middle mouse buttons. The Linux machines have a wheel mouse with which
we want to use to scroll the canvas. The Unix machines have the old
style 3-button mouse. The problem is that when I try to bind any of
the mouse buttons to the canvas they don't seem to be recognized. I
can scroll the canvas by binding it to the <Enter> (or <Exit>) event
and then calling a scrolling routine when the cursor enters the
canvas, but binding to any mouse button has no effect. I can also bind
the canvas to any of the alphanumeric keys successfully.

I read on the internet that the default mouse wheel bindings were
improved in Tcl 8.6. Could the version I'm using account for the
behavior which I'm seeing?

Any suggestions would be appreciated.

Thanks in advance.

jr4412

unread,
Oct 8, 2010, 4:36:45 PM10/8/10
to

can you use the mousewheel in other applications? it used to be
necessary to enable the mousewheel with 'ZAxisMapping' in the X
servers config file.

tcl...@roadrunner.com

unread,
Oct 8, 2010, 5:02:55 PM10/8/10
to
> servers config file.- Hide quoted text -
>
> - Show quoted text -

Yes, I can use the mousewheel with a text widget which is next to the
canvas in the toplevel. There was no tcl software modification
required to cause this.

For the canvas, here's some code:

frame $w.c -bg lightblue
canvas $w.c.canvas -width 100 -height $hgt -borderwidth 0 -
yscrollcommand [list $w.c.yscroll set]
scrollbar $w.c.yscroll -orient vertical -command [list $w.c.canvas
yview]

set canvas_frame [frame $w.c.canvas.f -borderwidth 0 -background
lightblue
$w.c.canvas create window 0 0 -anchor nw -window $canvas_frame

A statement such as
bind $w.c.canvas <Enter> {MY_SCROLL_PROC %W }
can be used to scroll the canvas some number of lines when the cursor
enters the canvas

A statement such as
bind $w.c.canvas <Button_4> {bell}
will NOT ring the bell


I can use the frame $w.c.canvas.f created above in the following
statement
bind $w.c.canvas.f <Button_4> {bell}
to ring the bell, but the area of this frame is steadily diminished as
more widgets are written to the canvas. so I can't bind to this to
scroll.

Thanks.


drsc...@gmail.com

unread,
Oct 8, 2010, 9:40:14 PM10/8/10
to
On 10/8/2010 5:02 PM, tcl...@roadrunner.com wrote:
>
> A statement such as
> bind $w.c.canvas<Button_4> {bell}
> will NOT ring the bell
>


Did you mean to bind using <Button-4> instead of <Button_4>?


DrS


tklish

unread,
Oct 8, 2010, 11:06:50 PM10/8/10
to

DrS,
I mistyped the post; I used <Button-4>.
Thanks

Christian Rapp

unread,
Oct 9, 2010, 4:04:01 AM10/9/10
to

I think it is correct to bind to the frame in the canvas. What you
have to do is to define the scrollregion everytime the content in the
canvas changes.

bind $w.c.canvas <Configure> {
%W configure -scrollregion [%W bbox all]
}

Regards

tklish

unread,
Oct 9, 2010, 3:23:08 PM10/9/10
to

Christian,
Thanks, I will definitely try your suggestion. I mentioned above that
scrolling with the scrollbars works perfectly, regardless of the
amount of content in the canvas. It's the binding that I'm having
trouble with. If I bind a mouse button to the canvas, the binding
doesn't work. If I bind to the frame in the canvas, the bind works,
but not for the whole canvas. Will configuring the scrollregion affect
this?

Ralf Fassel

unread,
Oct 9, 2010, 3:34:17 PM10/9/10
to
* tklish <tcl...@roadrunner.com>

| It's the binding that I'm having trouble with. If I bind a mouse
| button to the canvas, the binding doesn't work.

This works fine for me in wish 8.5.8 on Linux/Opensuse 11.3:

pack [canvas .c -bg yellow] -fill both -expand yes
bind .c <Button-4> {puts 4}
bind .c <Button-5> {puts 5}

If I now scroll up with the scroll wheel over the canvas, '4' gets printed
in the console, scroll down -> '5'. Does this code work for you?

R'

tklish

unread,
Oct 9, 2010, 5:41:16 PM10/9/10
to

Ralf,
I don't have access to the system today, but a couple of days ago, I
could not bind the canvas to any mouse button as you suggest; only to
<Enter> and <Leave>. I used the bell instead of the puts in your
example. What you suggest does work however with the canvas frame as I
reported above. I will try your suggestion as soon as possible. I'm
glad to see it works on Linux 8.5.8 in case I need to upgrade.
Thanks so much.

jr4412

unread,
Oct 9, 2010, 6:42:33 PM10/9/10
to
On Oct 9, 10:41 pm, tklish <tcl...@roadrunner.com> wrote:
> On Oct 9, 12:34 pm, Ralf Fassel <ralf...@gmx.de> wrote:
> >   pack [canvas .c -bg yellow] -fill both -expand yes
> >   bind .c <Button-4> {puts 4}
> >   bind .c <Button-5> {puts 5}

further to my email, the following works on my machine (Tcl 8.4.19):

bind .c <ButtonPress-4> {.c yview scroll 1 units}
bind .c <ButtonPress-5> {.c yview scroll -1 units}

hth

tklish

unread,
Oct 9, 2010, 7:16:11 PM10/9/10
to

HTH,
Are using Linux? Also, is ButtonPress- equivalent to Button- ?
Thanks much.

jr4412

unread,
Oct 9, 2010, 7:25:40 PM10/9/10
to

yes, and yes.

the box is slackware (12.0), and <ButtonPress-N> is equivalent to
<Button-N> (or even a simple <N>).

btw, hth stands for 'hope that helps', my name (initials) is jr.

tklish

unread,
Oct 9, 2010, 10:44:55 PM10/9/10
to
> btw, hth stands for 'hope that helps', my name (initials) is jr.- Hide quoted text -

>
> - Show quoted text -


Thank you. I'll investigate further.

Donald Arseneau

unread,
Oct 10, 2010, 5:15:02 PM10/10/10
to
jr4412 <jr4...@gmail.com> writes:

> btw, hth stands for 'hope that helps', my name (initials) is jr.

By the way, there's no reason to take offense and insult
tklish, addressing him as Butt Terribly Wide.

;-)

--
Donald Arseneau as...@triumf.ca

jr4412

unread,
Oct 10, 2010, 6:33:11 PM10/10/10
to
> Donald Arseneau                          a...@triumf.ca

oh dear! there goes our last, best hope for peace :-)

(so you're into B5??)

tklish

unread,
Oct 11, 2010, 1:48:26 PM10/11/10
to

Ralf,
I tried the three code lines you suggested using Wish and they worked
on my Unix system. This indicates that my binding problem probably has
nothing to do with the system or Tcl version. Thank you. It probably
has more to do with all the widgets that are placed on the canvas. If
I bind the <Enter> to "all" widgets I get five or six widgets output
each time I move the mouse around the canvas. They range from . to .c
to several levels below .c:
.
.App
.App.left
.App.left.c
.App.left.c.canvas
.App.left.c.canvas.f
.App.left.c.canvas.f.left

Could the fact that these are all stacked above each other cause the
mouse binding for the canvas to not work?

Donald,
Actually, jr4412 had it right.
BetterThanWonderful

drsc...@gmail.com

unread,
Oct 11, 2010, 3:28:04 PM10/11/10
to
On 10/8/2010 11:06 PM, tklish wrote:

> DrS,
> I mistyped the post; I used<Button-4>.
> Thanks


Here is some code that I have used across several linux distributions
over the years. They have worked on them just fine. The delta is the
minimum value between two consecutive mouse wheel events:


bind all <Button-4> \
{event generate [focus -displayof %W] <MouseWheel> -delta 120}

bind all <Button-5> \
{event generate [focus -displayof %W] <MouseWheel> -delta -120}


You can then use MouseWheel as a bindable event on tk objects like this:

bind $w <MouseWheel> ...

DrS


tklish

unread,
Oct 11, 2010, 4:54:15 PM10/11/10
to


Thanks, DrS,
I will try this as soon as I can get computer time.
Tklish

tklish

unread,
Oct 11, 2010, 7:12:18 PM10/11/10
to
> Tklish- Hide quoted text -

>
> - Show quoted text -

I think I need to refine my question. Below I've added a small Tk
script in which I've created a canvas and two frames within it. I find
I need a separate binding to mouse button 2 for each of these widgets
in order to ring the bell with button 2 when it is over each of them.
(In actuality I will use buttons 4 and 5 and scroll instead of beep.)
Many objects are created on the canvas in the actual application and
the user may decide to use the mouse wheel when the cursor is over any
of them. Only the canvas widget has a scrollbar. Do I need a separate
connection to the scrollbar via a bind for every object on the
canvas? I could do this as they are created, but is there not some
way of using only one bind statement for the entire canvas as it
requires only one command to scroll the whole canvas? Or is this what
DrS is trying to tell me in the post above.

For illustration, I echo the window Id as I enter and exit the window.

Thanks in advance.


wm title . "Canvas Test"
set w .app
frame $w
set hgt 300

pack $w -anchor nw -expand 1 -fill both
pack $w -side left -anchor nw -fill y

frame $w.c -bg lightblue

canvas $w.c.canvas -width 300 -height $hgt -borderwidth 0 \
-yscrollcommand [list $w.c.yscroll set]


scrollbar $w.c.yscroll -orient vertical -command [list $w.c.canvas
yview]

pack $w.c.yscroll -side right -anchor e -fill y
pack $w.c.canvas -anchor nw -expand 1 -fill both
pack $w.c -anchor nw -expand 1 -fill both


frame $w.c.canvas.f -borderwidth 3 -background pink
$w.c.canvas create window 0 0 -anchor nw -window $w.c.canvas.f

frame $w.c.canvas.f.left -height $hgt -width 20 -background lightblue

grid $w.c.canvas.f.left

bind $w.c.canvas <Button-2> {bell}
bind $w.c.canvas.f <Button-2> {bell}
bind $w.c.canvas.f.left <Button-2> {bell}

bind all <Enter> {puts "Entering %W"}
bind all <Leave> {puts "Leaving %W"}

Ralf Fassel

unread,
Oct 12, 2010, 4:34:08 AM10/12/10
to
* tklish <tcl...@roadrunner.com>

| .App.left.c.canvas
| .App.left.c.canvas.f
| .App.left.c.canvas.f.left
>
| Could the fact that these are all stacked above each other cause the
| mouse binding for the canvas to not work?

I think so. If the frame f.left gets an event, the canvas won't 'see'
it. This is different for 'window' canvas items and for other
canvas items:

pack [canvas .c -bg yellow] -fill both -expand yes

bind .c <1> {puts canvas}
.c create oval 20 20 100 100 -fill red
.c create window 200 200 -window [frame .c.f -height 50 -width 50 -bg green]
.c bind all <1> {puts canvas-item}

If you click on the canvas, 'canvas' gets printed.
If you click on the red circle, 'canvas-item' and 'canvas' get printed
(both bindings trigger).
If you click on the green rectangle, nothing happens (the event goes to
the frame, not to the canvas).

R'

Koen Danckaert

unread,
Oct 12, 2010, 5:03:01 AM10/12/10
to
tklish wrote:
> of them. Only the canvas widget has a scrollbar. Do I need a separate
> connection to the scrollbar via a bind for every object on the
> canvas? I could do this as they are created, but is there not some
> way of using only one bind statement for the entire canvas as it
> requires only one command to scroll the whole canvas?

Not easy to do this for a single canvas, but you can use a global binding to "all" to make the mousewheel work everywhere.


proc MouseWheel {w D X Y} {
# search upward for widget which can perform yview
for {} {[winfo toplevel $w] ne $w} {set w [winfo parent $w]} {
# do not scroll canvas with undefined scrollregion
if {![catch {$w cget -scrollregion} sr] && $sr eq ""} {continue}
if {![catch {$w yview scroll [expr {-$D/30}] units}]} {break}
}
}


# Remove existing MouseWheel bindings
foreach class {Text Listbox} {
bind $class <MouseWheel> {}
if {[tk windowingsystem] eq "x11"} {
bind $class <4> {}
bind $class <5> {}
}
}

# Bind MouseWheel to all
bind all <MouseWheel> {MouseWheel %W %D %X %Y}
if {[tk windowingsystem] eq "x11"} {
bind all <4> {MouseWheel %W 120 %X %Y}
bind all <5> {MouseWheel %W -120 %X %Y}
}


--Koen

tklish

unread,
Oct 12, 2010, 1:59:22 PM10/12/10
to


Ralf, Koen,
Thanks so much for the explanation. I got the canvas to scroll by
binding each object as it's created. The bind returns %W, which I
manipulate, knowing that all the widgets are children of the canvas
and I delete all extraneous characters at the end of the %W string to
get the name of the canvas alone and using it to scroll the entire
canvas. In my situation it might not be a good idea to bind to all,
since besides the canvas there are all kinds of other widgets in the
main window, probably another 20 or so. And more children are created
on the canvas as the application continues, based on user input.
Koen, I will save the code you provided since I think it will be handy
in the future.
Many thanks to all who helped me. I was sure that the bindings worked
some other way or that my version of Tcl was responsible, but it just
turned out that I didn't understand how Tk works.
Tklish

Ralf Fassel

unread,
Oct 12, 2010, 6:08:11 PM10/12/10
to
* tklish <tcl...@roadrunner.com>

| I got the canvas to scroll by binding each object as it's created. The
| bind returns %W, which I manipulate, knowing that all the widgets are
| children of the canvas and I delete all extraneous characters at the
| end of the %W string to get the name of the canvas alone and using it
| to scroll the entire canvas.

Hmmm. Can't you just add the canvas' name directly to the bind?

set canvas .c
set frame $canvas.frame
pack [canvas $canvas]
$canvas create window 0 0 -window [frame $frame -width 100 -height 100 -bg red]
# someproc called with two parameters: widget name of click, and the fixed canvas name.
# Just make sure the canvas' name does not include any %-signs.
bind $frame <1> [list someproc %W $canvas]
proc someproc {w canvas} {
puts "button on widget $w, canvas is $canvas"
}

HTH
R'

tklish

unread,
Oct 12, 2010, 6:23:22 PM10/12/10
to
Ralf, I think I'm doing more or less as you say:
I started binding all the widgets to Button-4 and Button-5 and
realized it was much easier to "bind all" to the buttons. I pass the
%W to the scrolling routine called by the bind and then in then
routine I filter out from %W any widget that doesn't contain "canvas"
in it. All widgets that are on the canvas contain "canvas" in their
path. I delete everything after the "canvas" to leave me with the name
of the canvas having the scrollbar. So I just have two bind statements
and 7 line proc to invoke the scrollbar and I'm done.
Thanks again.
0 new messages