Version 2 of Combobox Enhancements

Updated 2016-02-13 20:26:07 by bll

Combobox Enhancements

bll 2015-9-1

Problem 1. The standard combobox has a fixed width. This is not particularly useful for localized text. Some localized text may be much longer than the default and other text may be much shorter. This set of routines adjusts the width of the combobox to match the width of the longest value in the combobox. It also resolves the problem of text updates and forgetting to adjust the corresponding combobox width.

  variable vars

  # create a standard combobox and adjust the width of the combobox to
  # the longest value.
  # arguments are exactly the same as ttk::combobox
  proc comboBox { nm args } {
    ttk::combobox $nm {*}$args
    set wid [getComboBoxWidth $nm]
    $nm configure -width $wid
    return $nm
  }

  # update the combobox values.   
  # The width will need to be recalculated.
  proc updComboBoxValues { nm values } {
    $nm configure -values $values
    set wid [getComboBoxWidth $nm]
    $nm configure -width $wid
  }

  # calculates the width needed to hold the longest value.
  # uses a cache to save processing time.
  proc getComboBoxWidth { nm } {
    variable vars

    set values [$nm cget -values]
    set svalues [join $values {}]
    if { [info exists vars(cb.list.cache.$svalues)] } {
      set wid $vars(cb.list.cache.$svalues)
    } else {
      set pwid 0
      set font [$nm cget -font]
      foreach {val} $values {
        set pwid [expr {max($pwid,[font measure $font $val])}]
      }
      set wid [expr {$pwid / [font measure $font 0] + 1}]
      set vars(cb.list.cache.$svalues) $wid
    }
    return $wid
  }

Problem 2. Comboboxes cannot return values associated with the labels. For example, you might want to return 1-12 for the labels January-December.

For localized text, it is much easier to return a standard value than to have to handle many different languages. For example, a simple yes/no drop down would be ja/nein in german, oui/non in french, etc. Having code that checks for every possible return value for every localized combobox would be insane. Reverse translating each combobox isn't as bad, but gets messy as the number of comboboxes grows.

An example from HTML: You can have:

   <option value="volvo">Volvo</option>

The display name 'Volvo' returns the value 'volvo'.

  #
  #   -textvariable : 
  #       the variable name used to save the return value
  #   -returnvalues : a list of values to return.  
  #       The selected combobox value is mapped to the corresponding value in this list.
  #       The length of this list must match the length of the -values list.
  #   -displayvalue : the default value to set the combobox to.
  #       Normally, this would be whatever value -textvariable has. 
  #       But the value of -textvariable is from -returnvalues, not -values.
  #
  proc assocComboBox { nm args } {
    foreach {k} [list -textvariable -returnvalues -displayvalue] {
      set idx [lsearch -exact $args $k]
      if { $idx != -1 } {
        set data($k) [lindex $args [expr {$idx+1}]]
        set args [lreplace $args $idx [expr {$idx+1}]]
      }
    }
    uiutils::comboBox $nm {*}$args
    $nm set $data(-displayvalue)
    bind $nm <<ComboboxSelected>> +[list uiutils::setAssocComboBoxVal \
        $nm $data(-textvariable) $data(-returnvalues)]
    bind $nm <Tab> +[list uiutils::setAssocComboBoxVal \
        $nm $data(-textvariable) $data(-returnvalues)]
    bindComboKeyHandler $nm
    return $nm
  }

  # sets the return value (from $vallist) for the associated combobox to the selection corresponding
  # to the combobox value (-values).
  proc setAssocComboBoxVal { nm vv vallist } {
    upvar $vv v

    set curr [$nm current]
    set v [lindex $vallist $curr]
  }

  # update the display and return values for an associated combobox.
  proc updAssocComboBoxValues { nm args } {
    foreach {k} [list -textvariable -returnvalues -values] {
      set idx [lsearch -exact $args $k]
      if { $idx != -1 } {
        set data($k) [lindex $args [expr {$idx+1}]]
        set args [lreplace $args $idx [expr {$idx+1}]]
      }
    }
    $nm configure -values $data(-values)
    set wid [getComboBoxWidth $nm]
    $nm configure -width $wid
    bind $nm <<ComboboxSelected>> +[list uiutils::setAssocComboBoxVal \
        $nm $data(-textvariable) $data(-returnvalues)]
    bind $nm <Tab> +[list uiutils::setAssocComboBoxVal \
        $nm $data(-textvariable) $data(-returnvalues)]
  }

bll 2016-2-13 cleaned up API.