# How to bind canvas events to tags?

**URL:** <https://www.ruby-forum.com/t/how-to-bind-canvas-events-to-tags/175249>\
**Category:** Ruby\
**Created:** [September 10, 2009, 7:50am UTC](https://www.ruby-forum.com/t/how-to-bind-canvas-events-to-tags/175249 "2009-09-10T07:50:46Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Josef\_W](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/c0e974/32.png) [@Josef\_W](https://www.ruby-forum.com/u/Josef_W)\
**Post date:** [September 10, 2009, 7:50am UTC](https://www.ruby-forum.com/t/how-to-bind-canvas-events-to-tags/175249/1 "2009-09-10T07:50:46Z")

</div>

Hello,

In Perl/Tk, I can use

```
$canv->bind("foobar", '<1>' => sub {
    xxxx;
});

```

to bind a canvas event to all items with the “foobar” tag. This is a  
very  
powerful construct, so I’d like to use it in ruby, too. But ruby’s  
canvas.bind method seems to accept only two arguments: the event and the  
proc to be called. How would I do the same in ruby?

BTW: How do I find out which arguments are accepted by Ruby/Tk methods?  
There seem to be no man pages and the “Translating from Perl/Tk  
Documentation” in the pragmatic programmers book is not very  
helpful,  
also.

---

<div class="post-metadata">

**Author:** ![Josef\_W](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/c0e974/32.png) [@Josef\_W](https://www.ruby-forum.com/u/Josef_W)\
**Post date:** [September 10, 2009, 7:28pm UTC](https://www.ruby-forum.com/t/how-to-bind-canvas-events-to-tags/175249/2 "2009-09-10T19:28:39Z")

</div>

Josef W. wrote:

> ```
> $canv->bind("foobar", '<1>' => sub {
> 
> ```

```
   canvas.itembind(tag, '1', '%x %y') do |x, y|

```

In this case, we’re binding mouse single click events (I assume that’s  
the same as perl’s ‘\<1\>’) and we’re grabbing the pointer location into  
block args x and y.

---

<div class="post-metadata">

**Author:** ![Josef\_W](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/c0e974/32.png) [@Josef\_W](https://www.ruby-forum.com/u/Josef_W)\
**Post date:** [September 11, 2009, 6:40pm UTC](https://www.ruby-forum.com/t/how-to-bind-canvas-events-to-tags/175249/3 "2009-09-11T18:40:46Z")

</div>

On Thu, Sep 10, 2009 at 10:27:54AM -0700, Joel VanderWerf wrote:

> Josef W. wrote:
> 
> > ```
> > $canv->bind("foobar", '<1>' => sub {
> > 
> > ```
> 
> ```
> canvas.itembind(tag, '1', '%x %y') do |x, y|
> 
> ```
> 
> In this case, we’re binding mouse single click events (I assume that’s the  
> same as perl’s ‘\<1\>’) and we’re grabbing the pointer location into block  
> args x and y.

Yeah, that works. Thanks!

BTW: do you have a description of the “%x %y” formatting specification?

---

<div class="post-metadata">

**Author:** ![Josef\_W](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/c0e974/32.png) [@Josef\_W](https://www.ruby-forum.com/u/Josef_W)\
**Post date:** [September 11, 2009, 7:24pm UTC](https://www.ruby-forum.com/t/how-to-bind-canvas-events-to-tags/175249/4 "2009-09-11T19:24:05Z")

</div>

Josef W. wrote:

> BTW: do you have a description of the “%x %y” formatting specification?

[http://www.tcl.tk/man/tcl8.4/TkCmd/bind.htm#M41](http://www.tcl.tk/man/tcl8.4/TkCmd/bind.htm#M41)

---

<div class="post-metadata">

**Author:** ![Josef\_W](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/c0e974/32.png) [@Josef\_W](https://www.ruby-forum.com/u/Josef_W)\
**Post date:** [September 11, 2009, 9:00pm UTC](https://www.ruby-forum.com/t/how-to-bind-canvas-events-to-tags/175249/5 "2009-09-11T21:00:41Z")

</div>

On Sat, Sep 12, 2009 at 02:23:30AM +0900, Joel VanderWerf wrote:

> Josef W. wrote:
> 
> > BTW: do you have a description of the “%x %y” formatting specification?
> 
> [Tk Built-In Commands - bind manual page](http://www.tcl.tk/man/tcl8.4/TkCmd/bind.htm#M41)

Ough. Every time I think I get closer (remember? I am trying to  
implement  
a zoomable canvas), I find there is a lot more work to do :-()

My first attempt to adopt event handling to the zoom operation was like  
this:

class TkEvent::Event  
# FIXME: need a better name than “nonzoomed”. Recommendations?  
attr\_accessor :x\_nonzoomed, :y\_nonzoomed  
end

class TkZoomCanvas \< TkCanvas  
def itembind(tag, ev, cb)  
super(tag, ev, proc{ |e|  
if e.x then e.x\_nonzoomed = e.x/@zoom ; end  
if e.y then e.y\_nonzoomed = e.y/@zoom ; end  
cb.call e  
})  
end

```
def bind(ev, cb)
  # looks similar to itembind
end

```

end

Now, itembind (and bind?) seem to accept various forms:

1. canvas.itembind(“mytag”, “1”, proc{|e| … })  
This form works fine with above code

2. canvas.itembind(“mytag”, “1”, “%x %y”) {|x, y| … }  
In this case, I will have to build the argument list. Seems to be  
a  
tedious task. Maybe there already exist a Tk method I could use to  
build that list?

Are this the only two existing forms?

---

<div class="post-metadata">

**Author:** ![Josef\_W](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/c0e974/32.png) [@Josef\_W](https://www.ruby-forum.com/u/Josef_W)\
**Post date:** [September 13, 2009, 8:42pm UTC](https://www.ruby-forum.com/t/how-to-bind-canvas-events-to-tags/175249/6 "2009-09-13T20:42:09Z")

</div>

Josef W. wrote:

> Ough. Every time I think I get closer (remember? I am trying to implement  
> a zoomable canvas), I find there is a lot more work to do :-()

Do you need to exactly replicate the TkCanvas interface in the zoomable  
canvas? Why not get creative and develop a class with a new (possibly  
better) interface? (And maybe use delegation rather than inheritance.)  
The TkCanvas interface is close to Tcl/Tk’s canvas, which is great for  
documentation and porting, but it’s not necessarily very ruby-like.

---

<div class="post-metadata">

**Author:** ![Josef\_W](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/c0e974/32.png) [@Josef\_W](https://www.ruby-forum.com/u/Josef_W)\
**Post date:** [September 17, 2009, 10:01pm UTC](https://www.ruby-forum.com/t/how-to-bind-canvas-events-to-tags/175249/7 "2009-09-17T22:01:19Z")

</div>

On Mon, Sep 14, 2009 at 03:41:34AM +0900, Joel VanderWerf wrote:

> Josef W. wrote:
> 
> > Ough. Every time I think I get closer (remember? I am trying to implement  
> > a zoomable canvas), I find there is a lot more work to do :-()
> 
> Do you need to exactly replicate the TkCanvas interface in the zoomable  
> canvas? Why not get creative and develop a class with a new (possibly  
> better) interface? (And maybe use delegation rather than inheritance.) The  
> TkCanvas interface is close to Tcl/Tk’s canvas, which is great for  
> documentation and porting, but it’s not necessarily very ruby-like.

Thanks for your suggestion (and sorry for my late reply).

Well, as you might have already guessed, I’m very new to ruby, so I  
don’t  
yet have a good feeling about what is “very ruby like”. I’d love to see  
suggestions.

For the particular topic about the binding, after some thought, I’ve  
come  
to the conclusion that extending the format string is not the best idea.  
The format string consists of one-letter formats. I’d pollute this  
one-letter-namespace if I would introduce my own letters. Thus, I  
decided  
to let the base class do the job if a string is passed.

Attached is my current implementation of the zoomable+scrollable canvas.  
It works fine, AFAICS. I’d love to hear any suggestions about how to  
make  
it more ruby-like.

#!/usr/bin/ruby

require ‘tk’

class TkEvent::Event  
attr\_accessor :x\_nonzoom, :y\_nonzoom  
end

class TkScrolledCanvas \< TkCanvas  
include TkComposite  
attr\_reader :zoom

def initialize\_composite(keys={})  
@zoom = 1.0

```
@h_scr = TkScrollbar.new(@frame)
@v_scr = TkScrollbar.new(@frame)

@canvas = TkCanvas.new(@frame)
@path = @canvas.path

@canvas.xscrollbar(@h_scr)
@canvas.yscrollbar(@v_scr)

TkGrid.rowconfigure(@frame, 0, :weight=>1, :minsize=>0)
TkGrid.columnconfigure(@frame, 0, :weight=>1, :minsize=>0)

@canvas.grid(:row=>0, :column=>0, :sticky=>'news')
@h_scr.grid(:row=>1, :column=>0, :sticky=>'ew')
@v_scr.grid(:row=>0, :column=>1, :sticky=>'ns')

delegate('DEFAULT', @canvas)
delegate('background', @frame, @h_scr, @v_scr)
delegate('activeforeground', @h_scr, @v_scr)
delegate('troughcolor', @h_scr, @v_scr)
delegate('repeatdelay', @h_scr, @v_scr)
delegate('repeatinterval', @h_scr, @v_scr)
delegate('borderwidth', @frame)
delegate('relief', @frame)

delegate_alias('canvasborderwidth', 'borderwidth', @canvas)
delegate_alias('canvasrelief', 'relief', @canvas)

delegate_alias('scrollbarborderwidth', 'borderwidth', @h_scr, 

```

@v\_scr)  
delegate\_alias(‘scrollbarrelief’, ‘relief’, @h\_scr, @v\_scr)

```
configure(keys) unless keys.empty?

```

end

def zoom\_by zf  
zf = Float(zf)  
@zoom \*= zf

```
vf = (1 - 1/zf) / 2

x0, x1 = xview ; xf = x0 + vf * (x1-x0)
y0, y1 = yview ; yf = y0 + vf * (y1-y0)

scale 'all', 0, 0, zf, zf
configure :scrollregion => bbox("all")

xview "moveto", xf
yview "moveto", yf

```

end

def zoom\_to z  
zoom\_by(z/@zoom)  
end

def bind(ev, cb)  
if cb.class == String  
super  
else  
super(ev, proc{ |e| process\_event(e, cb) })  
end  
end

def itembind(tag, ev, cb)  
if cb.class == String  
super  
else  
super(tag, ev, proc{ |e| process\_event(e, cb) })  
end  
end

def coords(tag, \*args)  
newargs = adjust\_coords(@zoom, args)  
ret = super(tag, \*newargs)  
return ret unless ret.class == Array  
ret.collect { |v| v / @zoom }  
end

def move(tag, x, y)  
super(tag, x\*@zoom, y\*@zoom)  
end

def create(type, \*args)  
newargs = adjust\_coords(@zoom, args)  
super(type, \*newargs)  
end

private

def process\_event(e, cb)  
if e.x then e.x\_nonzoom=e.x/@zoom ; end  
if e.y then e.y\_nonzoom=e.y/@zoom ; end  
cb.call e  
end

def adjust\_coords(mul, args)  
args.collect do |arg|  
arg.class == Array ? arg.collect { |v| v \* mul } : arg  
end  
end  
end

class TkcItem  
alias orig\_initialize initialize

def initialize(parent, \*args)  
if parent.class == TkScrolledCanvas  
zoom = parent.zoom  
newargs = args.collect do |arg|  
arg.class == Array ? arg.collect { |v| v \* zoom } : arg  
end  
else  
newargs = args  
end  
orig\_initialize parent, \*newargs  
end

def bind(ev, cb)  
super(ev, proc{ |e|  
if @parent.class == TkScrolledCanvas  
zoom = @parent.zoom  
if e.x then e.x\_nonzoom=e.x/zoom ; end  
if e.y then e.y\_nonzoom=e.y/zoom ; end  
cb.call e  
end  
})  
end  
end
