📄 combobox.tcl
字号:
# Copyright (c) 1998-2003, Bryan Oakley# All Rights Reservered## Bryan Oakley# oakley@bardo.clearlight.com## combobox v2.3 August 16, 2003## a combobox / dropdown listbox (pick your favorite name) widget # written in pure tcl## this code is freely distributable without restriction, but is # provided as-is with no warranty expressed or implied. ## thanks to the following people who provided beta test support or# patches to the code (in no particular order):## Scott Beasley Alexandre Ferrieux Todd Helfter# Matt Gushee Laurent Duperval John Jackson# Fred Rapp Christopher Nelson# Eric Galluzzo Jean-Francois Moine Oliver Bienert## A special thanks to Martin M. Hunt who provided several good ideas, # and always with a patch to implement them. Jean-Francois Moine, # Todd Helfter and John Jackson were also kind enough to send in some # code patches.## ... and many others over the years.package require Tk 8.0package provide combobox 2.3namespace eval ::combobox { # this is the public interface namespace export combobox # these contain references to available options variable widgetOptions # these contain references to available commands and subcommands variable widgetCommands variable scanCommands variable listCommands}# ::combobox::combobox --## This is the command that gets exported. It creates a new# combobox widget.## Arguments:## w path of new widget to create# args additional option/value pairs (eg: -background white, etc.)## Results:## It creates the widget and sets up all of the default bindings## Returns:## The name of the newly create widgetproc ::combobox::combobox {w args} { variable widgetOptions variable widgetCommands variable scanCommands variable listCommands # perform a one time initialization if {![info exists widgetOptions]} { Init } # build it... eval Build $w $args # set some bindings... SetBindings $w # and we are done! return $w}# ::combobox::Init --## Initialize the namespace variables. This should only be called# once, immediately prior to creating the first instance of the# widget## Arguments:## none## Results:## All state variables are set to their default values; all of # the option database entries will exist.## Returns:# # empty stringproc ::combobox::Init {} { variable widgetOptions variable widgetCommands variable scanCommands variable listCommands variable defaultEntryCursor array set widgetOptions [list \ -background {background Background} \ -bd -borderwidth \ -bg -background \ -borderwidth {borderWidth BorderWidth} \ -buttonbackground {buttonBackground Background} \ -command {command Command} \ -commandstate {commandState State} \ -cursor {cursor Cursor} \ -disabledbackground {disabledBackground DisabledBackground} \ -disabledforeground {disabledForeground DisabledForeground} \ -dropdownwidth {dropdownWidth DropdownWidth} \ -editable {editable Editable} \ -elementborderwidth {elementBorderWidth BorderWidth} \ -fg -foreground \ -font {font Font} \ -foreground {foreground Foreground} \ -height {height Height} \ -highlightbackground {highlightBackground HighlightBackground} \ -highlightcolor {highlightColor HighlightColor} \ -highlightthickness {highlightThickness HighlightThickness} \ -image {image Image} \ -listvar {listVariable Variable} \ -maxheight {maxHeight Height} \ -opencommand {opencommand Command} \ -relief {relief Relief} \ -selectbackground {selectBackground Foreground} \ -selectborderwidth {selectBorderWidth BorderWidth} \ -selectforeground {selectForeground Background} \ -state {state State} \ -takefocus {takeFocus TakeFocus} \ -textvariable {textVariable Variable} \ -value {value Value} \ -width {width Width} \ -xscrollcommand {xScrollCommand ScrollCommand} \ ] set widgetCommands [list \ bbox cget configure curselection \ delete get icursor index \ insert list scan selection \ xview select toggle open \ close entryset subwidget \ ] set listCommands [list \ delete get \ index insert size \ ] set scanCommands [list mark dragto] # why check for the Tk package? This lets us be sourced into # an interpreter that doesn't have Tk loaded, such as the slave # interpreter used by pkg_mkIndex. In theory it should have no # side effects when run if {[lsearch -exact [package names] "Tk"] != -1} { ################################################################## #- this initializes the option database. Kinda gross, but it works #- (I think). ################################################################## # the image used for the button... if {$::tcl_platform(platform) == "windows"} { image create bitmap ::combobox::bimage -data { #define down_arrow_width 12 #define down_arrow_height 12 static char down_arrow_bits[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xfc,0xf1,0xf8,0xf0,0x70,0xf0,0x20,0xf0, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00; } } } else { image create bitmap ::combobox::bimage -data { #define down_arrow_width 15 #define down_arrow_height 15 static char down_arrow_bits[] = { 0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80, 0x00,0x80,0xf8,0x8f,0xf0,0x87,0xe0,0x83, 0xc0,0x81,0x80,0x80,0x00,0x80,0x00,0x80, 0x00,0x80,0x00,0x80,0x00,0x80 } } } # compute a widget name we can use to create a temporary widget set tmpWidget ".__tmp__" set count 0 while {[winfo exists $tmpWidget] == 1} { set tmpWidget ".__tmp__$count" incr count } # get the scrollbar width. Because we try to be clever and draw our # own button instead of using a tk widget, we need to know what size # button to create. This little hack tells us the width of a scroll # bar. # # NB: we need to be sure and pick a window that doesn't already # exist... scrollbar $tmpWidget set sb_width [winfo reqwidth $tmpWidget] set bbg [$tmpWidget cget -background] destroy $tmpWidget # steal options from the entry widget # we want darn near all options, so we'll go ahead and do # them all. No harm done in adding the one or two that we # don't use. entry $tmpWidget foreach foo [$tmpWidget configure] { # the cursor option is special, so we'll save it in # a special way if {[lindex $foo 0] == "-cursor"} { set defaultEntryCursor [lindex $foo 4] } if {[llength $foo] == 5} { set option [lindex $foo 1] set value [lindex $foo 4] option add *Combobox.$option $value widgetDefault # these options also apply to the dropdown listbox if {[string compare $option "foreground"] == 0 \ || [string compare $option "background"] == 0 \ || [string compare $option "font"] == 0} { option add *Combobox*ComboboxListbox.$option $value \ widgetDefault } } } destroy $tmpWidget # these are unique to us... option add *Combobox.elementBorderWidth 1 widgetDefault option add *Combobox.buttonBackground $bbg widgetDefault option add *Combobox.dropdownWidth {} widgetDefault option add *Combobox.openCommand {} widgetDefault option add *Combobox.cursor {} widgetDefault option add *Combobox.commandState normal widgetDefault option add *Combobox.editable 1 widgetDefault option add *Combobox.maxHeight 10 widgetDefault option add *Combobox.height 0 } # set class bindings SetClassBindings}# ::combobox::SetClassBindings --## Sets up the default bindings for the widget class## this proc exists since it's The Right Thing To Do, but# I haven't had the time to figure out how to do all the# binding stuff on a class level. The main problem is that# the entry widget must have focus for the insertion cursor# to be visible. So, I either have to have the entry widget# have the Combobox bindtag, or do some fancy juggling of# events or some such. What a pain.## Arguments:## none## Returns:## empty stringproc ::combobox::SetClassBindings {} { # make sure we clean up after ourselves... bind Combobox <Destroy> [list ::combobox::DestroyHandler %W] # this will (hopefully) close (and lose the grab on) the # listbox if the user clicks anywhere outside of it. Note # that on Windows, you can click on some other app and # the listbox will still be there, because tcl won't see # that button click set this {[::combobox::convert %W -W]} bind Combobox <Any-ButtonPress> "$this close" bind Combobox <Any-ButtonRelease> "$this close" # this helps (but doesn't fully solve) focus issues. The general # idea is, whenever the frame gets focus it gets passed on to # the entry widget bind Combobox <FocusIn> {::combobox::tkTabToWindow \ [::combobox::convert %W -W].entry} # this closes the listbox if we get hidden bind Combobox <Unmap> {[::combobox::convert %W -W] close} return ""}# ::combobox::SetBindings --## here's where we do most of the binding foo. I think there's probably# a few bindings I ought to add that I just haven't thought# about...## I'm not convinced these are the proper bindings. Ideally all# bindings should be on "Combobox", but because of my juggling of# bindtags I'm not convinced thats what I want to do. But, it all# seems to work, its just not as robust as it could be.## Arguments:## w widget pathname## Returns:## empty stringproc ::combobox::SetBindings {w} { upvar ::combobox::${w}::widgets widgets upvar ::combobox::${w}::options options # juggle the bindtags. The basic idea here is to associate the # widget name with the entry widget, so if a user does a bind # on the combobox it will get handled properly since it is # the entry widget that has keyboard focus. bindtags $widgets(entry) \ [concat $widgets(this) [bindtags $widgets(entry)]] bindtags $widgets(button) \ [concat $widgets(this) [bindtags $widgets(button)]] # override the default bindings for tab and shift-tab. The # focus procs take a widget as their only parameter and we # want to make sure the right window gets used (for shift- # tab we want it to appear as if the event was generated # on the frame rather than the entry. bind $widgets(entry) <Tab> \ "::combobox::tkTabToWindow \[tk_focusNext $widgets(entry)\]; break" bind $widgets(entry) <Shift-Tab> \ "::combobox::tkTabToWindow \[tk_focusPrev $widgets(this)\]; break" # this makes our "button" (which is actually a label) # do the right thing bind $widgets(button) <ButtonPress-1> [list $widgets(this) toggle] # this lets the autoscan of the listbox work, even if they # move the cursor over the entry widget. bind $widgets(entry) <B1-Enter> "break" bind $widgets(listbox) <ButtonRelease-1> \ "::combobox::Select [list $widgets(this)] \ \[$widgets(listbox) nearest %y\]; break" bind $widgets(vsb) <ButtonPress-1> {continue} bind $widgets(vsb) <ButtonRelease-1> {continue} bind $widgets(listbox) <Any-Motion> { %W selection clear 0 end %W activate @%x,%y %W selection anchor @%x,%y %W selection set @%x,%y @%x,%y # need to do a yview if the cursor goes off the top # or bottom of the window... (or do we?) } # these events need to be passed from the entry widget # to the listbox, or otherwise need some sort of special # handling. foreach event [list <Up> <Down> <Tab> <Return> <Escape> \ <Next> <Prior> <Double-1> <1> <Any-KeyPress> \ <FocusIn> <FocusOut>] { bind $widgets(entry) $event \ [list ::combobox::HandleEvent $widgets(this) $event] } # like the other events, <MouseWheel> needs to be passed from # the entry widget to the listbox. However, in this case we # need to add an additional parameter catch { bind $widgets(entry) <MouseWheel> \ [list ::combobox::HandleEvent $widgets(this) <MouseWheel> %D] }}# ::combobox::Build --## This does all of the work necessary to create the basic# combobox. ## Arguments:## w widget name# args additional option/value pairs## Results:## Creates a new widget with the given name. Also creates a new# namespace patterened after the widget name, as a child namespace# to ::combobox## Returns:## the name of the widgetproc ::combobox::Build {w args } { variable widgetOptions if {[winfo exists $w]} { error "window name \"$w\" already exists" } # create the namespace for this instance, and define a few # variables namespace eval ::combobox::$w { variable ignoreTrace 0 variable oldFocus {} variable oldGrab {} variable oldValue {} variable options variable this variable widgets set widgets(foo) foo ;# coerce into an array set options(foo) foo ;# coerce into an array unset widgets(foo) unset options(foo) }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -