'''`[http://www.tcl.tk/man/tcl/TkCmd/canvas.htm%|%canvas]`''', a [Tk] command, creates and manipulates canvas [widget%|%widgets]. ** See Also ** [Alternative Canvases]: lists widgets and toolkits with similar scope to the canvas widget [canvas woes]: [Tk examples]: [Category Animation]: [Category Games]: [Category Toys]: [tkpath]: SVG-like rendering for the canvas ** Documentation ** [http://www.tcl.tk/man/tcl/TkCmd/canvas.htm%|%official reference]: [http://www.linuxjournal.com/article.php?sid=7390%|%The Tk Canvas Widget]: by [Derek Fountain] , '''Linux Journal''' ,2004-02 ** Description ** The [canvas] widget is Tk's workhorse for 2D graphical display, and can handle both bitmap and vector graphics. It was inspired by Joel Bartlett's ezd program, which provides structured graphics in a Scheme environment. A canvas is one of the most powerful concepts in Tk. It acts as a drawing plane for lines, rectangles, ovals, polygons, [canvas text%|%text], arcs (e.g. pieslices) as well as container widget to group other widgets, and it provides the ability to group elements together for creation, deletion, moving, etc. ** History ** [http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.45.4520%|%Don't Fidget with Widgets, Draw!]: by Joel Bartlett ,1991 ** Item Types ** [canvas text]: ** Examples ** Look at the pages for each item type for more demos. Arranged in order from simple to complex: [A minimal starfield]: [canvashelp]: expanding/collapsing text item [Alternative buttons on canvas]: [Canvas presentation graphics]: [Example Canvas Widget]: 2004-04-29 [Canvas pixel painting]: [Canvas rectangle marking]: [Sun, moon, and stars]: [Simple Canvas Demo]: [Canvas item selections]: [Canvas item selection by mouse click]: [A symmetric doodler]: [Tkeyes]: an xeyes clone [Canvas buttons]: button-like behaviour on canvas items, by [KBK%|%Kevin Kenny] [Canvas buttons in 3-D]: 3d buttons, by [KBK%|%Kevin Kenny] [Drawing and editing polygons]: [Bounding boxes of characters in canvases]: [Notes on a canvas]: [Crosshairs on a canvas]: [Canvas object dropper]: [XML Graph to canvas] [Simple XML report writer]: ** Grouping Items ** Atomic canvas tag item groups with tags Tk's canvas has a lot of support for manipulating multiple items at once by giving a few items the same tag, but I find sometimes I would like to group a bunch of items together, and treat them as if they were only one item. For example the ''current'' tag only applies to the item first under the mouse, so if you have a graphic composite of say, a text and a rectangle with the same tag, you can not use the most generic code: ====== bind .c <1> {set oldX %x ; set oldY %y} bind .c {%W move current [expr %x-$oldX] [expr %y-$oldY]} ====== because only one of the items, the rectangle or the text, will move. To enable both of the items to be moved at once, I implemented an idea I saw in [Zinc], atomic groups. To do this I added two methods to the ''canvas'', tagconf and tagcget, which allow you to modify the behaviour of the tag as opposed to the items under the tag. I propose an option `-atomic` which defaults to off. When it's turned on, if an individual item with a tag's whose `l-atomic` is true is passed to any method that operates on multiple items (basically move and scale), all of the items possesing the atomic tag have the passed tag added to their list of tags. ** Tips ** Unlike frames, canvases come with a 2-pixel `-highlightthickness` ... set them to zero if you want the canvas to pack/grid/etc. like a frame does. ** Anti-Aliasing ** Anti-aliasing for line, polygons, oval items (X only, requires the Cairo lib): http://phpsource.net/?page=tk ** Moving Things Around ** [Drawing and editing polygons]: [A tiny drawing program]: [TclBrix] [Flipping a canvas]: ** 3-D ** [3-D Boxes (Support for Canvas Buttons in 3-D)]: [3D polyhedra with simple tk canvas]: ** Layout ** [Getting the Canvas View Area in Pixels]: [Canvas microjustification]: ** Geometrical Calculations ** [Canvas geometrical calculations]: [Affine transforms on a canvas]: [Canvas Rotation]: [Canvas zooming]: ** Polygons ** [Drawing rounded rectangles]: [Rectangle conversion]: [Drawing rounded polygons]: [Round Polygons]: ** Transparency ** [Jeffrey Hobbs] wrote in c.l.t on 2001-12-21: I think the point is that a '''completely transparent''' rectangle is essentially a fully filled for bindings, which allows you to create "hot spots" of bindings that don't require visual extras (like for imagemaps). If an outline of fill is specified, then you have a semantically different box, and triggers only occur for the visible portion. ---- [jal_frezie] Would it be possible to allow a canvas to have a transparent background? This would be an easy way to implement hierarchies of graphical items, as you could have a child canvas in a window item on its parent canvas with no graphical indication of the distinction between the items on the two canvases. Thus you could replicate the most useful bit of [Zinc], with the only change to the language being that the cammand "`.canvas configure -bg {}`" would set the background transparent rather than raising an exception. I have looked at the code implementing canvases, but not very much, and I imagine this change would be quite simple to implement. [DKF]: The current rendering engine (except possibly on OSX) isn't up to handling alpha-blended windows, and you can only efficiently do non-rectangular windows with an [Managed and shaped toplevel%|%extension%|%]. ** Polygons from lines ** Polygon items, even if displayed transparent with -fill {}, cover the underlying items, so tag bindings to them don't fire. Get truly transparent polygons by constructing them explicitly from lines: ====== proc myPolygon {c coords {color black}} { foreach {x0 y0} $coords break foreach {x1 y1} [concat [lrange $coords 2 end] $x0 $y0] { $c create line $x0 $y0 $x1 $y1 -fill $color -tag markup set x0 $x1; set y0 $y1 } } ====== [DKF]: You can do this more easily by using the fact that lines need not just be point-to-point entities. ====== proc myPolygon2 {c coords {color black}} { # Close the path first set p0 [lrange $coords 0 1] set pEnd [lrange $coords end-1 end] if {$p0 ne $pEnd} { eval lappend coords $p0 } # Now create the item # note that the result of this command (the item id) is the proc result too $c create line $coords -fill $color -tag markup } ====== ** Scrolling ** [Minimal scrolling canvas]: [scrolling explained]: [Kinetic scrolling]: *** Panning *** [Canvas scrolling by dragging mouse]: Adding panning to a canvas is simple. Suppose you have a canvas widget (with or without scrollbars, that doesn't matter). The only thing to add are two small bindings: ====== canvas .c pack .c bind .c {%W scan mark %x %y} bind .c {%W scan dragto %x %y 1} ====== That's all. Pressing the mouse button 1 will initiate panning and subsequent moving pans the widget. You do not need to take care of scaling or other factors since all is done in window coordinates. Note the number '1' as the last argument to 'scan dragto'. This is the gain factor which defaults to 10. Setting it to '1' make the command behave like the cursor sticks to the point where you pressed the mouse button. If you want to have fun, try to remove the first of the two bindings or set the gain to other values ... :-) ** Finding Visible or Partly Visible Items ** [MAK]: This function will return all of the tags for items that are currently visible (either entirely visible if partial is 0 or partly off-screen if partial is 1) within the canvas, provided you've got your scroll region set correctly. ====== proc canvasVisibleTags { hWnd {partial 1} } { foreach { xmin ymin xmax ymax } [$hWnd cget -scrollregion] break foreach { y1 y2 } [$hWnd yview] break foreach { x1 x2 } [$hWnd xview] break set top [expr {($ymax - $ymin) * $y1 + $ymin}] set bot [expr {($ymax - $ymin) * $y2 + $ymin}] set left [expr {($xmax - $xmin) * $x1 + $xmin}] set right [expr {($xmax - $xmin) * $x2 + $xmin}] if {$partial} { return [$hWnd find overlapping $left $top $right $bot] } else { return [$hWnd find enclosed $left $top $right $bot] } } ====== ** Canvas see #1 - move view to make items visible ** [MAK]: I didn't see any "see" functions for the canvas on the Wiki, so here is a somewhat reformatted (for my own aesthetics) version of a similar function found in an old usenet post [http://groups.google.com/groups?q=group:comp.lang.tcl+%2Bcanvas+%2Bsee&hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=ua3eycnrol.fsf%40isip01.isip.msstate.edu&rnum=10] with the added capability that you can specify more than one item as what you want to scroll to. Useful if you have multiple items that together comprise one logical item (in my case, in my own tree widget where I want to "see" the expand/collapse button, the icon, and the label rather than just the label). This algorithm will scroll only as far as necessary to make the specified items visible, rather than centering on the specified items. ====== proc canvasSee { hWnd items } { set bbox [eval $hWnd bbox $items] if {$bbox == ""} { return } foreach { x1 y1 x2 y2 } $bbox break foreach { top bottom } [$hWnd yview] break foreach { left right } [$hWnd xview] break foreach { x_min y_min x_max y_max } [$hWnd cget -scrollregion] break set width [expr {$x_max - $x_min}] set right [expr {$right * $width}] set left [expr {$left * $width}] set height [expr {$y_max - $y_min}] set top [expr {$top * $height}] set bottom [expr {$bottom * $height}] if { $x1 < $left } { $hWnd xview moveto [expr {double($x1-$x_min)/$width}] } elseif {$x2 > $right} { $hWnd xview moveto [expr {double($x2-$x_min-($right-$left))/$width}] } if { $y1 < $top } { $hWnd yview moveto [expr {double($y1-$y_min)/$height}] } elseif {$y2 > $bottom} { $hWnd yview moveto [expr {double($y2-$y_min-($bottom-$top))/$height}] } } ====== ** Canvas see #2 - move view to make items visible ** [MAK] This is another similarly reformatted and extended algorithm from another usenet post [http://groups.google.com/groups?q=group:comp.lang.tcl+%2Bcanvas+%2Bsee&hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=3C030476.4DC6C733%40yahoo.com&rnum=1] -- unlike the above, this one tends to center the specified items within the visible area of the canvas. ====== proc canvasSee { hWnd items } { set box [eval $hWnd bbox $items] if {$box == ""} { return } if {[string match {} [$hWnd cget -scrollregion]] } { # People really should set -scrollregion you know... foreach {x y x1 y1} $box break set x [expr round(2.5 * ($x1+$x) / [winfo width $hWnd])] set y [expr round(2.5 * ($y1+$y) / [winfo height $hWnd])] $hWnd xview moveto 0 $hWnd yview moveto 0 $hWnd xview scroll $x units $hWnd yview scroll $y units } else { # If -scrollregion is set properly, use this foreach { x y x1 y1 } $box break foreach { top btm } [$hWnd yview] break foreach { left right } [$hWnd xview] break foreach { p q xmax ymax } [$hWnd cget -scrollregion] break set xpos [expr (($x1+$x) / 2.0) / $xmax - ($right-$left) / 2.0] set ypos [expr (($y1+$y) / 2.0) / $ymax - ($btm-$top) / 2.0] $hWnd xview moveto $xpos $hWnd yview moveto $ypos } } ====== ** Tiled Backgrounds ** [DKF]: Canvases don't directly support tiled backgrounds, but you can easily fix this by using a [tiled image] underneath everything else. ** `-highlightcolor` ** [Robert Heller] provided the following sample of how to use the canvas's -highlightcolor option (slightly corrected by [Jeff Hobbs]): ====== canvas .thecanvas pack .thecanvas .thecanvas create oval 0 0 50 50 -fill red -outline blue -tag theOval .thecanvas itemconfigure theOval -fill \ [tk_chooseColor -initialcolor [.thecanvas itemcget theOval -fill]] .thecanvas itemconfigure theOval -outline \ [tk_chooseColor -initialcolor [.thecanvas itemcget theOval -outline]] proc Rand255 {} { return [expr int(rand() * 256)] } .thecanvas itemconfigure theOval \ -fill [format {#%02x%02x%02x} [Rand255] [Rand255] [Rand255]] .thecanvas itemconfigure theOval \ -outline [format {#%02x%02x%02x} [Rand255] [Rand255] [Rand255]] .thecanvas configure -highlightcolor [format {#%02x%02x%02x} [Rand255] [Rand255] [Rand255]] focus .thecanvas ====== ** Geting the Canvas ID ** [MG] 2005-02-20: Would it be useful for the [[canvas bind ...]] command to accept an extra substitution to the regular ones, for the canvas id of the item being clicked, and the name of the tag that invoked the binding? I know it's (always?) possible to get the id with [[$canvas find overlapping %x,%y]], but I'd guess it's something that's needed in pretty much every instance where canvas tags have bindings, so it would probably be much quicker and easier (in terms of learning how to do it and actual execution time) if it were done with a substitution? [Peter Newman] 2005-02-20: ====== set myCanvasItemsCanvasID [.myCanvas create whatever xxx] ; ... .myCanvas bind $myCanvasItemsCanvasID "MyEventHandler ${myCanvasItemsCanvasID} %x %y" ; ... proc MyEventHandler { myCanvasItemsCanvasID mouseX_inWindow mouseY_inWindow } { xxx ... xxx } ; ====== That's even faster that an extra substitution to the regular ones, since the substitution is made only once, when the binding script is compiled, rather than every time the event occurs. [MG]: That only works if you're binding to a single canvas item. If you're binding to a tag (which is what I meant, though didn't say too clearly). Then you can do something like ====== pack [canvas .c] for {set i 1} {$i < 10} {incr i} { .c create image [expr {$i*5}] 20 -image test -tags [list clickable] } .c bind clickable {puts "You pressed canvas item %I on canvas %W"} ====== [RS]: The canvas item being clicked has the "current" tag... [MG] Thanks, RS. Don't recall ever seeing that before :) Scott Hill 2005-0-01: The tag current is managed automatically by Tk; it applies to the current item, which is the topmost item whose drawn area covers the position of the mouse cursor. If the mouse is not in the canvas widget or is not over an item, then no item has the current tag. ** Exporting a Canvas ** [Exporting a canvas to other formats]: [can2svg]: [Pdf canvas]: 2007-08-03 [pdf4tcl]: [Printing a canvas under Windows]: [Serializing a canvas widget]: [MG]: I think all you need to do, assuming you have the Img package is this: ====== package require Img pack [canvas .c -height 50 -width 50] .c create rectangle 0 0 25 25 -fill blue .c create rectangle 25 25 50 50 -fill green raise . ;# if there's anything over the window on-screen, it'll be obscured in the image image create photo theCanvas -format window -data .c theCanvas write /your/path/to/image.gif -format gif ====== ** Clipping ** [Clipping graphical objects in a canvas widget]: [MAK] 2005-01-26: I just noticed something about the way window canvas items are clipped that's quite interesting. I'm not sure if it's a bug or not, but at least it's the same on different platforms: window items are not clipped to the canvas, but rather to their parent window. A demonstration: ====== frame .f -bg blue canvas .c2 -bg red -width 100 grid rowconfigure .f 0 -weight 1 -minsize 0 grid rowconfigure .f 1 -weight 0 -minsize 0 grid columnconfig .f 0 -weight 1 -minsize 0 grid columnconfig .f 1 -weight 0 -minsize 0 grid [canvas .f.c -bg yellow -width 100 \ -xscrollcommand ".f.x set" -yscrollcommand ".f.y set"] \ -row 0 -column 0 -sticky news grid [scrollbar .f.y -orient vertical -command ".f.c yview"] \ -row 0 -column 1 -sticky news grid [scrollbar .f.x -orient horizontal -command ".f.c xview"] \ -row 1 -column 0 -sticky news grid columnconfig . 0 -weight 1 -minsize 0 grid columnconfig . 1 -weight 1 -minsize 0 grid rowconfigure . 0 -weight 1 -minsize 0 grid .f -row 0 -column 0 -sticky news grid .c2 -row 0 -column 1 -sticky news checkbutton .cb -text "012345678901234567890123456789" -bg green checkbutton .f.cb -text "012345678901234567890123456789" -bg green checkbutton .f.c.cb -text "012345678901234567890123456789" -bg green .f.c create window 0 0 -anchor nw -window .cb .f.c create window 0 30 -anchor nw -window .f.cb .f.c create window 0 60 -anchor nw -window .f.c.cb ====== This script creates two canvases side by side - one red and yellow with scrollbars. It then creates three identical checkbuttons with green backgrounds - one that's a child of the toplevel, one that's a child of the frame with the canvas and scrollbars, and one that's a child of the canvas itself. Notice that the topmost checkbutton is not clipped at all - it overlaps both canvases, while the middle checkbutton is clipped to the frame and overlaps the scrollbars, and the bottom checkbutton is clipped to the canvas. However, that bottom checkbutton still overlaps the margin of the canvas. I'm tempted to think it's a bug or an oversight, but on the other hand I think I might find it very useful in working around Aqua's overrideredirect problems in one case. Anyway, I found it interesting and didn't see mention of this behavior in the manpage. [DKF]: This is general window-clipping behaviour, and is (and should be) that way on all platforms (and is useful/relevant with the [text] widget too). It is also why widgets embedded in a canvas should usually be children of the canvas itself. If you look carefully, you'll notice that the third widget overlaps the border of the canvas (it becomes more visible if you use a more visible - thicker and solid - border and highlight ring). A wrapper [frame] is sometimes necessary to prevent that sort of nonsense... ** Feature Reequests ** *** -elide canvas *** [AMG]: I'd like an -elide option for canvas items, similar to the [text] widget. This option, when set to true, would hide the item and inhibit user interaction. Currently, hiding an option can be done in one of four ways: * Move it out of the visible region. * Delete it. * Reset its colors to empty string. * Lower it behind an opaque background item. All four options require the script to remember how to recreate or otherwise reestablish the item. Adding an -elide option would allow the full item configuration to persist inside the canvas widget, simplifying the script. Unlike moving or deleting the item, [[bbox]] will still work. ** Discussion ** [Zinc] looks interesting. On the zinc site there are source & unix binaries. Has anyone successfully compiled it for windows? - Ian Gay Christophe Mertz: As mentionned in [zinc], since 3.2.9x version, TkZinc is now compiled for windows. We are also looking for port on OSX... Any volunteer? ** Bugs ** * [Canvas postscript - text rendering bug] ---- [LV]: What can people tell me about the canvas `-state` attribute? For instance, on the perl/tk mailing list recently, the following Tcl code was used to demonstrate what appears to be a Tk canvas bug: ====== package require Tk pack [canvas .c] set w [.c create window 50 50 -window [ label .c.label -text Hello ] -state hidden] set l [.c create line 30 60 70 60 -state hidden] set i 0 proc repeatproc {} { global i w l incr i if { $i%2 == 0 } { .c itemconfigure $w -state hidden .c itemconfigure $l -state hidden } else { .c itemconfigure $w -state normal .c itemconfigure $l -state normal } after 1000 repeatproc } after 1000 repeatproc ====== <> Tk syntax help | Arts and Crafts of Tcl-Tk Programming | Command | Graphics | Widget