This is a short tutorial presenting the drag & drop support in the Tablelist package. The relevant tablelist subcommands and default bindings are explained with the aid of two demo scripts showing how Tablelist, combined with the TkDND extension or BWidget's drag & drop framework, can make Tcl applications much more user-friendly.
package require tkdnd 2.7 set tblBody [$tbl bodypath] tkdnd::drag_source register $tblBody DND_Text 1 bind $tblBody <<DragInitCmd>> { onTblDragInit %W } bind $tblBody <<DragEndCmd>> { onTblDragEnd %W %A } proc onTblDragInit w { ... } proc onTblDragEnd {w action} { ... }
package require BWidget DragSite::register [$tbl bodypath] -dragevent 1 \ -draginitcmd tblDragInitCmd -dragendcmd tblDragEndCmd proc tblDragInitCmd {dragSrc rootX rootY top} { ... } proc tblDragEndCmd {dragSrc dropTarget op dataType data result} { ... }
DEFINITION: A tablelist widget is viewed as a drag source for mouse button 1 if its body component was registered as such via the
tkdnd::drag_source register
or the BWidgetDragSite::register
command, or the tablelist's-customdragsource
option was set to true.DRAG SOURCE SUPPORT VIA THE DEFAULT BINDINGS:
- If the selection mode is
extended
then pressing mouse button 1 on a selected item or element normally deselects all the other items or elements (depending on the selection type). However, if the tablelist is a drag source for mouse button 1, then the other items or elements will only be deselected when releasing mouse button 1 over the clicked item or element.- Similarly, if the selection mode is
multiple
then pressing mouse button 1 on a selected item or element normally deselects that item or element (depending on the selection type). However, if the tablelist is a drag source for mouse button 1, then the clicked item or element will only be deselected when releasing mouse button 1 over the same item or element.- Whenever the mouse leaves the tablelist window with button 1 down. the default bindings normally perform an automatic scrolling, just like in the case of the Tk listbox widget. However, if the tablelist is a drag source for mouse button 1, then the automatic scrolling will be suppressed, in order to avoid any conflicts with the drag operation.
package require tkdnd 2.7 tkdnd::drop_target register $tbl DND_Text bind $tbl <<DropEnter>> { onTblDropEnterOrPos %W %X %Y %a %b } bind $tbl <<DropPosition>> { onTblDropEnterOrPos %W %X %Y %a %b } bind $tbl <<DropLeave>> { %W hidetargetmark } bind $tbl <<Drop>> { onTblDrop %W %A %D } proc onTblDropEnterOrPos {tbl rootX rootY actions buttons} { ... } proc onTblDrop {tbl action data} { ... }
package require BWidget foreach w [list [$tbl bodypath] [$tbl targetmarkpath]] { DropSite::register $w -dropovercmd tblDropOverCmd -dropcmd tblDropCmd \ -droptypes [list TABLELIST_DATA {copy {}}] proc tblDropOverCmd {dropTarget dragSrc event rootX rootY op dataType data} { ... } proc tblDropCmd {dropTarget dragSrc rootX rootY op dataType data} { ... }
pathName targetmarkpos
y ?-any|-horizontal|-vertical?
pathName showtargetmark
before|inside index
pathName hidetargetmark
pathName targetmarkpath
EU_TkDND.tcl
...tblStates
and tblCaps
as drag sourcesset tblStatesBody [$tblStates bodypath] tkdnd::drag_source register $tblStatesBody DND_Text 1 bind $tblStatesBody <<DragInitCmd>> { onTblStatesDragInit %W } bind $tblStatesBody <<DragEndCmd>> { onTblDragEnd %W %A } set tblCapsBody [$tblCaps bodypath] tkdnd::drag_source register $tblCapsBody DND_Text 1 bind $tblCapsBody <<DragInitCmd>> { onTblCapsDragInit %W } bind $tblCapsBody <<DragEndCmd>> { onTblDragEnd %W %A } proc onTblStatesDragInit w { set tbl [tablelist::getTablelistPath $w] set rows [$tbl curselection] if {[llength $rows] == 1} { set items [list [$tbl get $rows]] } else { set items [$tbl get $rows] } foreach item $items { lappend states [lindex $item 0] } # Remember the drag source global dragSrc set dragSrc $w return [list {copy} {DND_Text} $states] } proc onTblCapsDragInit w { set tbl [tablelist::getTablelistPath $w] set rows [$tbl curselection] set item [$tbl get $rows] set capital [lindex $item 0] # Remember the drag source global dragSrc set dragSrc $w return [list {copy} {DND_Text} $capital] } proc onTblDragEnd {w action} { # Forget the drag source global dragSrc unset dragSrc if {![string equal $action "refuse_drop"]} { ;# accepted set tbl [tablelist::getTablelistPath $w] set rows [$tbl curselection] $tbl selection clear $rows foreach row $rows { $tbl rowconfigure $row -foreground red3 } } }
tblEU
as a drop targettkdnd::drop_target register $tblEU DND_Text bind $tblEU <<DropEnter>> { onTblEUDropEnterOrPos %W %X %Y %a %b } bind $tblEU <<DropPosition>> { onTblEUDropEnterOrPos %W %X %Y %a %b } bind $tblEU <<DropLeave>> { %W hidetargetmark } bind $tblEU <<Drop>> { onTblEUDrop %W %A %D } proc onTblEUDropEnterOrPos {tbl rootX rootY actions buttons} { # Refuse the drop if the drag source is not # the body component of tblStates or tblCaps global dragSrc tblStates tblCaps if {![info exists dragSrc] || (![string equal $dragSrc [$tblStates bodypath]] && ![string equal $dragSrc [$tblCaps bodypath]])} { return refuse_drop } global place row set y [expr {$rootY - [winfo rooty $tbl]}] if {[string equal $dragSrc [$tblStates bodypath]]} { # Dragging a list of states. # The following line will set place to "inside": foreach {place row} [$tbl targetmarkpos $y -vertical] {} if {$row >= 0 && [$tbl depth $row] == 2} { # The y-position is inside a state item - enforce "before" foreach {place row} [$tbl targetmarkpos $y -horizontal] {} } if {$row < 0 || $row >= [$tbl size] || ([string equal $place "before"] && [$tbl depth $row] == 1)} { # The y-position is outside all rows or before a year item $tbl hidetargetmark return refuse_drop } else { $tbl showtargetmark $place $row return copy } } else { # Dragging a capital city. # The following line will set place to "inside": foreach {place row} [$tbl targetmarkpos $y -vertical] {} if {$row < 0 || [$tbl depth $row] == 1} { # The y-position is outside all rows or is inside a year item $tbl hidetargetmark return refuse_drop } else { $tbl showtargetmark $place $row return copy } } } proc onTblEUDrop {tbl action data} { handleTblEUDrop $tbl $data ;# see EU_common.tcl return $action }
EU_BWidget.tcl
...tblStates
and tblCaps
as drag sourcesDragSite::register [$tblStates bodypath] -dragevent 1 \ -draginitcmd tblStatesDragInitCmd -dragendcmd tblDragEndCmd DragSite::register [$tblCaps bodypath] -dragevent 1 \ -draginitcmd tblCapsDragInitCmd -dragendcmd tblDragEndCmd proc tblStatesDragInitCmd {dragSrc rootX rootY top} { set tbl [tablelist::getTablelistPath $dragSrc] set rows [$tbl curselection] if {[llength $rows] == 1} { set items [list [$tbl get $rows]] } else { set items [$tbl get $rows] } foreach item $items { lappend states [lindex $item 0] } return [list TABLELIST_DATA {copy} $states] } proc tblCapsDragInitCmd {dragSrc rootX rootY top} { set tbl [tablelist::getTablelistPath $dragSrc] set rows [$tbl curselection] set item [$tbl get $rows] set capital [lindex $item 0] return [list TABLELIST_DATA {copy} $capital] } proc tblDragEndCmd {dragSrc dropTarget op dataType data result} { if {$result != 0} { ;# accepted set tbl [tablelist::getTablelistPath $dragSrc] set rows [$tbl curselection] $tbl selection clear $rows foreach row $rows { $tbl rowconfigure $row -foreground red3 } } }
tblEU
as drop
targetsforeach w [list [$tblEU bodypath] [$tblEU targetmarkpath]] { DropSite::register $w -dropovercmd tblEUDropOverCmd -dropcmd tblEUDropCmd \ -droptypes [list TABLELIST_DATA {copy {}}] } proc tblEUDropOverCmd {dropTarget dragSrc event rootX rootY op dataType data} { # Refuse the drop if the drag source is not # the body component of tblStates or tblCaps global tblStates tblCaps if {![string equal $dragSrc [$tblStates bodypath]] && ![string equal $dragSrc [$tblCaps bodypath]]} { return 0 ;# refuse the drop } # $event may be "enter", "motion", or "leave" set tbl [tablelist::getTablelistPath $dropTarget] if {[string equal $event "leave"]} { set newWidget [winfo containing -displayof $dropTarget $rootX $rootY] if {![string equal $newWidget [$tbl targetmarkpath]] && ![string equal $newWidget [$tbl bodypath]]} { $tbl hidetargetmark return 2 ;# refuse the drop and re-invoke the callback on motion } } global place row set y [expr {$rootY - [winfo rooty $tbl]}] if {[string equal $dragSrc [$tblStates bodypath]]} { # Dragging a list of states. # The following line will set place to "inside": foreach {place row} [$tbl targetmarkpos $y -vertical] {} if {$row >= 0 && [$tbl depth $row] == 2} { # The y-position is inside a state item - enforce "before" foreach {place row} [$tbl targetmarkpos $y -horizontal] {} } if {$row < 0 || $row >= [$tbl size] || ([string equal $place "before"] && [$tbl depth $row] == 1)} { # The y-position is outside all rows or before a year item $tbl hidetargetmark DropSite::setcursor dot return 2 ;# refuse the drop and re-invoke the callback on motion } else { $tbl showtargetmark $place $row DropSite::setcursor based_arrow_down return 3 ;# accept the drop and re-invoke the callback on motion } } else { # Dragging a capital city. # The following line will set place to "inside": foreach {place row} [$tbl targetmarkpos $y -vertical] {} if {$row < 0 || [$tbl depth $row] == 1} { # The y-position is outside all rows or is inside a year item $tbl hidetargetmark DropSite::setcursor dot return 2 ;# refuse the drop and re-invoke the callback on motion } else { $tbl showtargetmark $place $row DropSite::setcursor based_arrow_down return 3 ;# accept the drop and re-invoke the callback on motion } } } proc tblEUDropCmd {dropTarget dragSrc rootX rootY op dataType data} { set tbl [tablelist::getTablelistPath $dropTarget] handleTblEUDrop $tbl $data ;# see EU_common.tcl return 1 ;# accept the drop }
handleTblEUDrop
proc handleTblEUDrop {tbl data} { $tbl hidetargetmark global place row if {[string equal $place "before"]} { # Dropping before a state item: Insert new state # items as siblings before the one indicated by $row set parent [$tbl parentkey $row] set childIdx [$tbl childindex $row] foreach state $data { $tbl insertchild $parent $childIdx [list $state ""] incr childIdx } } elseif {[$tbl depth $row] == 1} { # Dropping inside a year item: Append new state items to # the list of children of the year item indicated by $row foreach state $data { $tbl insertchild $row end [list $state ""] } $tbl expand $row -partly } else { # Dropping inside a state item: Update the # capital city of the state indicated by $row $tbl cellconfigure $row,end -text $data } }