sframe.tcl
README.md

sframe.tcl

  •   sframe.tcl
  • sframe NS
  •   sframe::procedures
    • new new : Creates a scrollable frame or window. path - path to the frame/window args - options Use the ttk theme's background for the canvas and toplevel
    • content content : Gets the path of the child frame suitable for content. path - path to the scrollable window/frame
    • resize resize : Makes adjustments when the the sframe is resized or the contents change size. path - path to the scrollable window/frame
    • scroll scroll : Handles mousewheel scrolling. path - path to the scrollable window/frame view - xview or yview D - scrolling units
    • checkScroll checkScroll : Checks whether the scrolling is possible. w - window
    • wheelScroll wheelScroll : Scrolls a window. w - window
    • wheelDelta wheelDelta : Generate mouse wheel events with deltas (for Linux). w - window ev - event delval - delta
  •   EONS sframe
  • EOF

apave

A library for GUI development with Tcl/Tk.

Docs:

Full description

sframe.tcl
#############################################################################
# Name:    sframe.tcl
# Authors: main code by Paul Walton, portions by Alex Plotnikov
# Date:    07/04/2022
# Brief:   Handles a ttk-compatible, scrollable frame widget.
# License: Tcl/Tk.
#
# Usage:
#     sframe new <path> ?-toplevel true?  ?-anchor nsew? ?-mode x|y|xy|both?
#       -> <path>
#
#     sframe content <path>
#       -> <path of child frame where the content should go>
#############################################################################

# ________________________ sframe NS _________________________ #

namespace eval sframe {
  namespace ensemble create
  namespace export *

  ## ________________________ sframe::procedures _________________________ ##

  proc new {path args} {
    # Creates a scrollable frame or window.
    #   path - path to the frame/window
    #   args - options

    # Use the ttk theme's background for the canvas and toplevel
    set bg [ttk::style lookup TFrame -background]
    if { [ttk::style theme use] eq "aqua" } {
      # Use a specific color on the aqua theme as 'ttk::style lookup' is not accurate.
      set bg "#e9e9e9"
    }

    # Create the main frame or toplevel.
    if { [dict exists $args -toplevel]  &&  [dict get $args -toplevel] } {
      toplevel $path  -bg $bg
    } else {
      ttk::frame $path
    }

    # Create a scrollable canvas with scrollbars which will always be the same size as the main frame.
    set mode both
    if { [dict exists $args -mode] } {
      set mode [dict get $args -mode]
    }
    switch -- [string tolower $mode] {
      both - xy - yx {
        set canvas [canvas $path.canvas -bg $bg -bd 0 -highlightthickness 0 -yscrollcommand [list $path.scrolly set] -xscrollcommand [list $path.scrollx set]]
        ttk::scrollbar $path.scrolly -orient vertical   -command [list $canvas yview]
        ttk::scrollbar $path.scrollx -orient horizontal -command [list $canvas xview]
      }
      y {
        set canvas [canvas $path.canvas -bg $bg -bd 0 -highlightthickness 0 -yscrollcommand [list $path.scrolly set]]
        ttk::scrollbar $path.scrolly -orient vertical   -command [list $canvas yview]
      }
      x {
        set canvas [canvas $path.canvas -bg $bg -bd 0 -highlightthickness 0 -xscrollcommand [list $path.scrollx set]]
        ttk::scrollbar $path.scrollx -orient horizontal -command [list $canvas xview]
      }
      default {
        return -code error "-mode option is invalid: \"$mode\" (valid are x, y, xy, yx, both)"
      }
    }

    # Create a container frame which will always be the same size as the canvas or content, whichever is greater.
    # This allows the child content frame to be properly packed and also is a surefire way to use the proper ttk background.
    set container [ttk::frame $canvas.container]
    pack propagate $container 0

    # Create the content frame. Its size will be determined by its contents. This is useful for determining if the
    # scrollbars need to be shown.
    set content [ttk::frame $container.content]

    # Pack the content frame and place the container as a canvas item.
    set anchor "n"
    if { [dict exists $args -anchor] } {
      set anchor [dict get $args -anchor]
    }
    pack $content -fill both -expand 1 -anchor $anchor
    $canvas create window 0 0 -window $container -anchor nw

    # Grid the scrollable canvas sans scrollbars within the main frame.
    grid $canvas   -row 0 -column 0 -sticky nsew
    grid rowconfigure    $path 0 -weight 1
    grid columnconfigure $path 0 -weight 1

    # Make adjustments when the sframe is resized or the contents change size.
    bind $path.canvas <Configure> [list [namespace current]::resize $path]

    # Mousewheel bindings for scrolling
    set w [winfo toplevel $path]
    catch {
      if {$::tcl_platform(platform) eq {unix}} {
        ::apave::bindToEvent $w <Button-4> \
          [namespace current]::wheelDelta $w <MouseWheel> 1
        ::apave::bindToEvent $w <Button-5> \
          [namespace current]::wheelDelta $w <MouseWheel> -1
        ::apave::bindToEvent $w <Shift-Button-4> \
          [namespace current]::wheelDelta $w <Shift-MouseWheel> 1
        ::apave::bindToEvent $w <Shift-Button-5> \
          [namespace current]::wheelDelta $w <Shift-MouseWheel> -1
      }
    }
    ::apave::bindToEvent $w <MouseWheel> \
      [namespace current]::wheelScroll $w [namespace current] scroll $path yview %D
    ::apave::bindToEvent $w <Shift-MouseWheel> \
      [namespace current]::wheelScroll $w [namespace current] scroll $path xview %D
    return $path
  }
  #_______________________

  proc content {{path ""}} {
    # Gets the path of the child frame suitable for content.
    #   path - path to the scrollable window/frame

    return $path.canvas.container.content
  }
  #_______________________

  proc resize {path} {
    # Makes adjustments when the the sframe is resized or the contents change size.
    #   path - path to the scrollable window/frame

    set canvas    $path.canvas
    set container $canvas.container
    set content   $container.content

    # Set the size of the container. At a minimum use the same width & height as the canvas.
    set width  [winfo width $canvas]
    set height [winfo height $canvas]

    # If the requested width or height of the content frame is greater then use that width or height.
    if { [winfo reqwidth $content] > $width } {
      set width [winfo reqwidth $content]
    }
    if { [winfo reqheight $content] > $height } {
      set height [winfo reqheight $content]
    }
    $container configure  -width $width  -height $height

    # Configure the canvas's scroll region to match the height and width of the container.
    set bg [lindex [::apave::obj csGet] 3]
    $canvas configure -scrollregion [list 0 0 $width $height] -bg $bg

    # Show or hide the scrollbars as necessary.
    # Horizontal scrolling.
    if {[winfo exists $path.scrollx]} {
      if { [winfo reqwidth $content] > [winfo width $canvas] } {
        grid $path.scrollx  -row 1 -column 0 -sticky ew
      } else {
        grid forget $path.scrollx
      }
    }
    # Vertical scrolling.
    if {[winfo exists $path.scrolly]} {
      if { [winfo reqheight $content] > [winfo height $canvas] } {
        grid $path.scrolly  -row 0 -column 1 -sticky ns
      } else {
        grid forget $path.scrolly
      }
    }
    return
  }
  #_______________________

  proc scroll {path view D} {
    # Handles mousewheel scrolling.
    #   path - path to the scrollable window/frame
    #   view - xview or yview
    #   D - scrolling units

    if { [winfo exists $path.canvas] } {
      $path.canvas $view scroll [expr {-$D}] units
    }
    return
  }
  #_______________________

  proc checkScroll {w} {
    # Checks whether the scrolling is possible.
    #   w - window

    set res yes
    catch {
      lassign [winfo pointerxy $w] rootX rootY
      if {[set win [winfo containing $rootX $rootY]] eq {}} {
        set win [focus]
      }
      if {[winfo exists $win]} {
        set ts [string tolower [winfo class $win]]
      } else {
        set ts -
      }
      if {$ts in {tablelist text listbox treeview}} {
        set res no
      }
    }
    return $res
  }
  #_______________________

  proc wheelScroll {w args} {
    # Scrolls a window.
    #   w - window

    catch {
      if {[checkScroll $w]} {
        {*}$args
      }
    }
  }
  #_______________________

  proc wheelDelta {w ev delval} {
    # Generate mouse wheel events with deltas (for Linux).
    #   w - window
    #   ev - event
    #   delval - delta

    catch {
      if {[checkScroll $w]} {
        event generate $w $ev -delta $delval
      }
    }
  }

  ## ________________________ EONS sframe _________________________ ##

}

# _____________________________ EOF _____________________________________ #
#RUNF1: C:/PG/github/pave/tests/test2_pave.tcl alt 0 9 12 "small icons"
#RUNF1: ../../../src/alited.tcl LOG=~/TMP/alited-DEBUG.log DEBUG

sframe.tcl