Tags
We've seen that every canvas item can be referred to by a unique id number. There is another handy and powerful way to refer to items on a canvas, using tags.
A tag is just an identifier of your creation, something meaningful to your program. You can attach tags to canvas items; each item can have any number of tags. Unlike item id numbers, which are unique for each item, many items can share the same tag.
What can you do with tags? We saw that you can use the item id to modify a canvas item (and we'll see soon there are other things you can do to items, like move them around, delete them, etc.). Any time you can use an item id, you can use a tag. For example, you can change the color of all items having a specific tag.
Tags are a good way to identify collections of items in your canvas (items in a drawn line, items in a palette, etc.). You can use tags to correlate canvas items to particular objects in your application (for example, tag all canvas items that are part of the robot with id #X37 with the tag "robotX37"). With tags, you don't have to keep track of the ids of canvas items to refer to groups of items later; tags let Tk do that for you.
You can assign tags when creating an item using the tags
item configuration
option. You can add tags later with the addtag
method or remove them with the
dtags
method. You can get the list of tags for an item with the gettags
method or return a list of item id numbers having the given tag with the find
command.
For example:
// cargo run --example canvas_tags use tk::*; use tk::canvas::*; use tk::cmd::*; fn main() -> TkResult<()> { let tk = make_tk!()?; let root = tk.root(); let canvas = root.add_canvas(())?.pack(())?; let _tag1 = canvas.create_line( &[ (10.0,10.0), (20.0,20.0) ], -tags("firstline drawing") )?; let tag2 = canvas.create_rectangle( 30.0, 30.0, 40.0, 40.0, -tags("firstline drawing") )?; canvas.addtag( "rectangle", SearchSpec::WithTag( tag2.clone().into() ))?; canvas.addtag( "polygon", SearchSpec::WithTag( item_tag( "drawing" ).into() ))?; let tags = canvas.gettags( tag2.clone() )?; for name in &[ "drawing", "rectangle", "polygon" ] { assert!( tags.iter().find( |&tag| tag.0.as_str() == *name ).is_some() ); } canvas.dtag( tag2.clone(), Some( ItemTag( "polygon".to_owned() )))?; let tags = canvas.gettags( tag2.clone() )?; for name in &[ "drawing", "rectangle" ] { assert!( tags.iter().find( |&tag| tag.0.as_str() == *name ).is_some() ); } assert!( tags.iter().find( |&tag| tag.0.as_str() == "polygon" ).is_none() ); let items = canvas.find( SearchSpec::WithTag( item_tag( "drawing" ).into() ))?; assert_eq!( items.get_elements()?.map( |item| item.get_string() ).collect::<Vec<_>>(), vec![ "1".to_owned(), "2".to_owned() ]); Ok( main_loop() ) }
As you can see, methods like withtag
accept either an individual item or a
tag; in the latter case, they will apply to all items having that tag (which
could be none). The addtag
and find
methods have many other options,
allowing you to specify items near a point, overlapping a particular area, etc.
Let's use tags first to put a border around whichever item in our color palette is currently selected.
// cargo run --example canvas_a_simple_sketchpad_border_around_selected use std::os::raw::c_double; use tcl::*; use tk::*; use tk::canvas::*; use tk::cmd::*; fn set_color<Inst:TkInstance>( tk: &Tk<Inst>, canvas: &TkCanvas<Inst>, color: &Obj ) -> TkResult<()> { tk.set( "color", color.clone() ); canvas.dtag( item_tag( "all" ), Some( item_tag( "paletteSelected" )))?; canvas.itemconfigure( item_tag( "palette" ), -outline("white") )?; canvas.addtag( "paletteSelected", SearchSpec::WithTag( item_tag( &format!( "palette{}", color.clone() )).into() ))?; canvas.itemconfigure( item_tag( "paletteSelected" ), -outline("#999999") )?; Ok(()) } fn main() -> TkResult<()> { let tk = make_tk!()?; let root = tk.root(); let canvas = root .add_canvas(())? .grid( -sticky("nwes") -column(0i32) -row(0i32) )?; root.grid_columnconfigure( 0, -weight(1) )?; root.grid_rowconfigure( 0, -weight(1) )?; Widget::bind( &canvas, event::button_press_1(), "set lastx %x; set lasty %y" )?; Widget::bind( &canvas, event::button_1().motion(), tclosure!( tk, |evt_x:c_double, evt_y:c_double| -> TkResult<()> { let last_x = tk.get_double("lastx")?; let last_y = tk.get_double("lasty")?; let color = tk.get("color")?; set_color( &tk, &canvas, &color )?; canvas.create_line( &[ (last_x,last_y), (evt_x,evt_y) ], -fill(color) )?; tk.set( "lastx", evt_x ); tk.set( "lasty", evt_y ); Ok(()) } ))?; let id = canvas.create_rectangle( 10.0, 10.0, 30.0, 30.0, -fill("red") -tags("palette palettered") )?; canvas.bind( id, event::button_press_1(), tclosure!( tk, || { tk.set( "color", "red" ); Ok(()) }))?; let id = canvas.create_rectangle( 10.0, 35.0, 30.0, 55.0, -fill("blue") -tags("palette paletteblue") )?; canvas.bind( id, event::button_press_1(), tclosure!( tk, || { tk.set( "color", "blue" ); Ok(()) }))?; let id = canvas.create_rectangle( 10.0, 60.0, 30.0, 80.0, -fill("black") -tags("palette paletteblack paletteSelected") )?; canvas.bind( id, event::button_press_1(), tclosure!( tk, || { tk.set( "color", "black" ); Ok(()) }))?; set_color( &tk, &canvas, &Obj::from("black") )?; canvas.itemconfigure( item_tag( "palette" ), -width(5) )?; Ok( main_loop() ) }
The canvas itemconfigure
method provides another way to change the properties
of a canvas item. The advantage over dealing with the canvas item object
directly is that we can specify a tag, so that the change we're making applies
to all items having that tag. Without this, we could use gettags
to get all
the items, iterate through them, and set the option, but itemconfigure
is more
convenient.
Let's also use tags to make the current stroke being drawn appear more prominent. When the mouse button is released, we'll return the line to normal.
#![allow(unused)] fn main() { Widget::bind( &canvas, event::button_1().motion(), tclosure!( tk, |evt_x:c_double, evt_y:c_double| -> TkResult<()> { // ... canvas.create_line( &[ (last_x,last_y), (x,y) ], -fill(color) -width(5) -tags("currentline") )?; tk.set( "lastx", x ); tk.set( "lasty", y ); // ... Ok(()) } ))?; Widget::bind( &canvas, event::button_1().button_pelease(), tclosure!( tk, || ->TkResult<()> { Ok( canvas.itemconfigure( item_tag( "currentline" ), -width(1) )? ) }) )?; }
Run Example
cargo run --example canvas_tags
cargo run --example canvas_a_simple_sketchpad_border_around_selected
cargo run --example canvas_a_simple_sketchpad_more_prominent