Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations IamaSherpa on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

problem implementing Scrollable frame with buttons 1

Status
Not open for further replies.

fabien

Technical User
Sep 25, 2001
299
AU
Hi!

Thanks to Ken I have implemented a frame in which I can add/delete buttons. The problem is that I need to have a scrollable frame otherwise the frame will keep growing and I won't be able to select the buttons at the bottom. This was solved by inserting the button frame inside a canvas. My problem now is that the scrollbar does not work, the frame keeps growing anyway. I have tried inserting
$cv configure -scrollregion [$cv bbox all] (see code below) in InsertFrame proc but this does not make any difference.

Please help!!!

#!/usr/local/bin/wish
#exec wish "$0" "$@"
#-----------------------------------------------------------------------------

##############################
frame .top -borderwidth 2
pack .top -fill x
#

#
#MENUS
#

menu .top.menuBar -tearoff 0
set f .top.menuBar.file
set fc .top.menuBar.func
set h .top.menuBar.help
set edit .top.menuBar.edit

#File menu
.top.menuBar add cascade -menu $f -label "File" -font {Helvetica 14 bold} -underline 0
menu $f -tearoff 1
$f add command -label "New" -font {Helvetica 12 bold} -underline 0 -command {}
$f add command -label "Open" -font {Helvetica 12 bold} -underline 0 -command {}
$f add command -label "Save" -font {Helvetica 12 bold} -underline 0 -command {}
$f add separator
$f add command -label "Exit" -font {Helvetica 12 bold} -underline 1 -command {exit} -accelerator "Meta-q"

#Edit
.top.menuBar add cascade -menu $edit -label "Edit" -font {Helvetica 14 bold} -underline 0
menu $edit -tearoff 1
$edit add command -label "Add before" -font {Helvetica 12 bold} -underline 0 -command {}
$edit add command -label "Add after" -font {Helvetica 12 bold} -underline 0 -command {}
$edit add command -label "Delete" -font {Helvetica 12 bold} -underline 0 -command {}

#Functions
.top.menuBar add cascade -menu $fc -label "Functions" -font {Helvetica 14 bold} -underline 0
menu $fc -tearoff 1
$fc add command -label "Import" -font {Helvetica 12 bold} -underline 0 -command {}

#help menu
.top.menuBar add cascade -menu $h -label "Help" -font {Helvetica 14 bold} -underline 0
menu $h -tearoff 1
$h add command -label "About.." -font {Helvetica 12 bold} -command {About} -underline 0

global f h fc edit


# bind accelerator keys
bind . <Meta-q> {exit}
# for frame select
bind . <ButtonPress-1> {
FrameSelect %W
}

. configure -menu .top.menuBar

# main window
#
wm title . &quot;ZMAP MACRO BUILDER&quot;
wm geometry . 400x300
wm minsize . 400 300
####


##
###
#function frame
set incfr 0
# Initialize a counter to help us create
# unique frame names on demand.
set frameCounter [incr incfr]
global incfr frameCounter





#
# Lower frame holds text output
#

#frame .logfr
#set log [text .logfr.log -width 50 -height 5 # -borderwidth 3 -relief sunken -setgrid true # -yscrollcommand {.logfr.scroll set} -font {courier 9}]
#scrollbar .logfr.scroll -command {.logfr.log yview}
#pack .logfr.scroll -side right -fill y
#pack .logfr.log -side left -fill both -expand true
#pack .logfr -side top -fill both -expand true

###########
# FUNCTIONS
############


###
# MAKE PROCESS FRAME
###
proc MakeFrame {w} {
global first
global cv

# make frame
puts &quot;frame: $w&quot;
frame $w -borderwidth 4
set item [$cv create window 1c 1c -window $w -anchor nw]
pack $w -side top -fill x

#checkbutton
checkbutton $w.cb -text &quot;Empty &quot; -variable dummy -command {} -padx 10
#button
button $w.b -text &quot;Parameters...&quot; -width 10 -command {} -padx 20
pack $w.cb $w.b -padx 5 -side left

update
if {$first } {
puts &quot;in first&quot;
set first 0

$cv configure -width [expr [winfo reqwidth $w.b] +10]
$cv configure -height [expr [winfo reqheight $w.b] * 5]
$cv configure -yscrollincrement [winfo reqheight $w.b]
set toto [expr [winfo reqwidth $w.b] +10]
puts $toto
set toto [expr [winfo reqheight $w.b] * 5]
puts $toto


}
$cv configure -scrollregion [$cv bbox all]
# $cv configure -scrollregion [$cv bbox $item]
# $cv yview 0
}

###############

proc mkScrollCanvas {w f width height } {
set c [canvas $w.c -width $width -height $height -bd 4 -relief ridge -yscrollcommand &quot;$w.yscroll set&quot;]
set s [scrollbar $w.yscroll -relief sunken -command &quot;$w.c yview&quot;]
set l [label $w.l -text &quot;xxx&quot;]
pack $l -side top -fill x -in $f
pack $s -side right -fill y -padx 1 -in $f
pack $c -side top -fill both -expand 1 -in $f
return $c
}



##################################
# FrameSelect
#
# Called by a binding to select a frame.
##################################

proc FrameSelect {w} {
global selected

puts &quot;in FrameSelect&quot;

# If the user clicked on a child widget
# rather than its parent frame, cycle up
# to the parent frame. Note that this
# simple algorithm *doesn't* allow for
# another frame to be nested in our
# containing frame. It also relies on you
# not changing the frame's class to
# anything other than &quot;Frame&quot; to work
# properly.

while {([winfo class $w] != &quot;Frame&quot;)
&& (&quot;$w&quot; != &quot;.&quot;)} {

set w [winfo parent $w]
}

# &quot;De-select&quot; whatever is currently
# selected. This also handles &quot;toggling&quot;
# an already selected frame by clicking on
# it again.

if {[info exists selected(current)]} {
$selected(current) configure -relief $selected(relief)
}

#if {[info exists selected(current)]
#&& ($selected(current) == $w)} {

# The user clicked on the frame already
# selected. Simply &quot;de-select&quot; the frame
# and return.

#unset selected(current)
#return

#} else {

# Highlight the selected frame by
# changing its border to groove. To be
# visible, the frame must have its
# border width (-bd) set to a value
# greater than 2.

set selected(relief) [$w cget -relief]
set selected(current) $w

$w configure -relief groove
#}
}

# Create a symbolic bindtag called
# &quot;selectable&quot;, that invokes our FrameSelect
# procedure in response to a left-click.

bind selectable <ButtonPress-1> {
FrameSelect %W
}


##################################
# InsertFrame
#
# Create a frame on demand. If another frame
# is selected, the new frame is packed
# before the selected frame; otherwise, the
# new frame is packed after all existing
# frames.
##################################

proc InsertFrame {} {
global selected
global frameCounter
global cv

set i [incr frameCounter]
#display first frame (default)
set f $cv.fr$i
puts $f
MakeFrame $f

if {[info exists selected(current)]} {

# If a frame is selected, insert the new
# frame in front of the selected one in
# the packing order.

pack $f -before $selected(current) -padx 2 -pady 2 -anchor w

} else {

# If no frame is selected, pack the new
# frame after all other frames.

pack $f -padx 2 -pady 2 -anchor w
}

$cv configure -scrollregion [$cv bbox all]

# Insert our &quot;selectable&quot; bindtag at the
# beginning of the bindtag path for the
# frame.

bindtags $f [linsert [bindtags $f] 0 selectable]

# Uncomment the following lines if you
# want the frame toggling binding active
# for the contents of the frame as well.

# bindtags $f.cb [linsert [bindtags $f.cb] 0 selectable]
# bindtags $f.b [linsert [bindtags $f.b] 0 selectable]

pack $f.cb -padx 2 -pady 2 -anchor w
pack $f.b -padx 2 -pady 2


}

##################################
# DeleteFrame
#
# Delete the currently selected frame. If no
# frame is selected, nothing happens.
##################################

proc DeleteFrame {} {
global selected
if {[info exists selected(current)]} {
destroy $selected(current)
unset selected(current)
}
}

# Define virtual events, which allows us to
# use mutiple bindings to perform the same
# task.

event add <<Insert>> <Control-KeyPress-a> <KeyPress-Insert>

event add <<Delete>> <Control-KeyPress-d> <KeyPress-Delete>

# Bind to the toplevel window, so that these
# bindings are active no matter which widget
# has focus.

bind . <<Insert>> InsertFrame
bind . <<Delete>> DeleteFrame


###### MAIN #########
#set first frame (default)
frame .f -height 1
pack .f

set first 1
set cv [mkScrollCanvas .f .f 0 0]
#display first frame (default)
MakeFrame $cv.fr1


#frames
# for buttons
frame .btfr -borderwidth 10
pack .btfr -fill x

#
# Action Buttons
#

set but [button .btfr.run -background #88ff88 -activebackground #88ff88 -text &quot;Run&quot; -command Run]
button .btfr.quit -background #ffbbbb -activebackground #ffbbbb -text &quot;Quit&quot; -command exit
pack .btfr.run -side left -padx 100
pack .btfr.quit -side left

######################
 
I don't know if this is the only problem, but this will definitely cause trouble. In your MakeFrame procedure, you've got:

Code:
set item [$cv create window 1c 1c     -window $w -anchor nw]
pack $w -side top -fill x

I think I've said this before, but let me emphasize it here: if you're using the canvas as a widget manager, don't pack or grid the widget. It's the canvas's create window operation that handles the display of the managed widget. Putting that pack command in there as well screws things up.

So the solution? Delete that one pack command. Then see if it works the way you want it to. - Ken Jones, President, ken@avia-training.com
Avia Training and Consulting, 866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Thanks Ken but when I do this I get an error
when inserting a new button with InsertFrame
&quot;error: window: &quot;.f.c.fr1&quot; isn't packed

I have tried commenting out all the pack, but then all the button disappear. Any other ideas?

Thanks,

 
I took a closer look at your code, and realized that you're not constructing and using your scrollable frame properly. It's a bit tricky, which is why I usually encourage people to use one of the scrollable frame megawidgets -- such as those provided by BWidgets or [ignore][incr Tcl][/ignore] -- rather than writing their own. These megawidget implementations have been around long enough to have most of the bugs pounded out, and some nice features for managing the widgets.

When constructing your own scrollable frame implementation, the structure you need to create is:[ul][li]A &quot;hull&quot; frame to manage all the components[/li][li]A canvas and accompanying scrollbars as direct children of the hull frame[/li][li]A &quot;contents&quot; frame managed by the canvas[/li][/ul]The &quot;contents&quot; frame will contain everything you want to scroll. In essense, it's truly the scrollable frame. But we need the &quot;hull&quot; frame to handle all the other widgets that controll the scrolling.

Here's an updated version of the frame insertion/deletion program I posted before. But I've also included a scrollable frame implementation, and create all of the &quot;component&quot; frames within the scrollable frame. I think you should be able to adapt this example to your purposes.

Code:
##################################
# ScrollableFrame
#
# Create a scrollable frame &quot;megawidget.&quot;
# The widget name provided becomes the
# &quot;hull&quot; frame, containing all the
# components of the scrollable frame. The
# actual &quot;manager&quot; frame for the scrollable
# contents is $w.c.contents.
##################################

proc ScrollableFrame {w} {

    # Create a &quot;hull&quot; frame to contain to
    # contain all other widgets composing
    # the scrollable frame.

    frame $w

    canvas $w.c  -height 100 -width 50         -xscrollcommand [list $w.xbar set]         -yscrollcommand [list $w.ybar set]
    scrollbar $w.xbar -orient horizontal         -command [list $w.c xview]
    scrollbar $w.ybar -orient vertical         -command [list $w.c yview]

    # Create the frame that will actually
    # hold all children of the scrollable
    # frame. All children should be packed
    # or gridded into this frame, *not* the
    # hull frame or the canvas!

    frame $w.c.contents

    # Tell the canvas to display the frame,
    # anchoring the upper-left hand corner
    # of the frame in the upper-left hand
    # corner of the canvas

    $w.c create window 0 0 -anchor nw         -window $w.c.contents

    # Use grid to display the canvas and its
    # scrollbars. Handle resizing properly.

    grid $w.c -row 0 -column 0         -sticky nsew -pady 4 -padx 2
    grid $w.ybar -row 0 -column 1         -sticky ns -pady 4 -padx 2
    grid $w.xbar -row 1 -column 0         -sticky ew -pady 4 -padx 2
    grid columnconfigure $w 0 -weight 1
    grid rowconfigure $w 0 -weight 1

    # Detect <Configure> events on the frame
    # to detect when it is first displayed
    # and any time is is resized. In either
    # case, recompute the visible bounds of
    # the canvas and update its -scrollregion
    # attribute.
    
    bind $w.c.contents <Configure>         [list UpdateCanvas $w.c]
    
    return $w
}

##################################
# UpdateCanvas
#
# A &quot;helper&quot; procedure called automatically
# when the contents of a scrolled frame
# resizes. It updates the -scrollregion of
# the component canvas so as to update the
# component scrollbars appropriately.
##################################

proc UpdateCanvas {c} {
    $c configure -scrollregion [$c bbox all]
}


##################################
# FrameSelect
# 
# Called by a binding to select a frame.
##################################

proc FrameSelect {w} {
  global selected

  # If the user clicked on a child widget
  # rather than its parent frame, cycle up
  # to the parent frame. Note that this
  # simple algorithm *doesn't* allow for
  # another frame to be nested in our
  # containing frame. It also relies on you
  # not changing the frame's class to
  # anything other than &quot;Frame&quot; to work
  # properly.

  while {([winfo class $w] != &quot;Frame&quot;)
         && (&quot;$w&quot; != &quot;.&quot;)} {

    set w [winfo parent $w]
  }

  # &quot;De-select&quot; whatever is currently
  # selected. This also handles &quot;toggling&quot;
  # an already selected frame by clicking on
  # it again.

  if {[info exists selected(current)]} {
    $selected(current) configure       -relief $selected(relief)
  }

  if {[info exists selected(current)]
       && ($selected(current) == $w)} {

    # The user clicked on the frame already
    # selected. Simply &quot;de-select&quot; the frame
    # and return.

    unset selected(current)
    return

  } else {

    # Highlight the selected frame by
    # changing its border to groove. To be
    # visible, the frame must have its
    # border width (-bd) set to a value
    # greater than 2.

    set selected(relief) [$w cget -relief]
    set selected(current) $w

    $w configure -relief groove
  }
}

# Create a symbolic bindtag called
# &quot;selectable&quot;, that invokes our FrameSelect
# procedure in response to a left-click.

bind selectable <ButtonPress-1> {
  FrameSelect %W
}

# Initialize a counter to help us create
# unique frame names on demand.

set frameCounter 0

##################################
# InsertFrame
# 
# Create a frame on demand within the
# specified parent. If another frame is
# selected, the new frame is packed before
# the selected frame; otherwise, the new
# frame is packed after all existing frames.
##################################

proc InsertFrame {parent} {
  global selected
  global frameCounter

  set i [incr frameCounter]

  set f [frame $parent.f$i -bd 2]

  radiobutton $f.r -text &quot;Option $i&quot;     -variable x -value $i
  button $f.b -text &quot;Waffle&quot;

  # Insert our &quot;selectable&quot; bindtag at the
  # beginning of the bindtag path for the
  # frame.

  bindtags $f     [linsert [bindtags $f] 0 selectable]

  # Uncomment the following lines if you
  # want the frame toggling binding active
  # for the contents of the frame as well.

  # bindtags $f.r     [linsert [bindtags $f.r] 0 selectable]
  # bindtags $f.b     [linsert [bindtags $f.b] 0 selectable]

  pack $f.r -padx 2 -pady 2 -anchor w
  pack $f.b -padx 2 -pady 2

  if {[info exists selected(current)]} {

    # If a frame is selected, insert the new
    # frame in front of the selected one in
    # the packing order.

    pack $f -before $selected(current)       -padx 2 -pady 2 -fill x

  } else {

    # If no frame is selected, pack the new
    # frame after all other frames.

    pack $f -padx 2 -pady 2 -fill x
  }
}

##################################
# DeleteFrame
# 
# Delete the currently selected frame. If no
# frame is selected, nothing happens.
##################################

proc DeleteFrame {} {
  global selected
  if {[info exists selected(current)]} {
    destroy $selected(current)
    unset selected(current)
  }
}

# Define virtual events, which allows us to
# use mutiple bindings to perform the same
# task.

event add <<Insert>>   <Control-KeyPress-a> <KeyPress-Insert>
event add <<Delete>>   <Control-KeyPress-d> <KeyPress-Delete>

# Create a scrollable frame in which we'll
# do our frame creation and deletion.

ScrollableFrame .workspace
pack .workspace -expand yes -fill both

# Bind to the toplevel window, so that these
# bindings are active no matter which widget
# has focus.  Insert the frame we create in
# our scrollable frame's content frame.

bind . <<Insert>> {
  InsertFrame .workspace.c.contents
}
bind . <<Delete>> DeleteFrame
- Ken Jones, President, ken@avia-training.com
Avia Training and Consulting, 866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Thanks for this Ken this is exactly what I was lokking after.

Thanks for your help again!

Fabien
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top