combobox.wgt
来自「一个跨平台的TCL/TK可视开发环境类似VC. TCL/TK是一个跨平台的脚本」· WGT 代码 · 共 2,104 行 · 第 1/5 页
WGT
2,104 行
proc ::combobox2::SetBindings {w} {
upvar ::combobox2::${w}::widgets widgets
upvar ::combobox2::${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 combobox2 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. I
bind $widgets(entry) <Tab> \
"::combobox2::tkTabToWindow \[tk_focusNext $widgets(entry)\]; break"
bind $widgets(entry) <Shift-Tab> \
"::combobox2::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> \
"::combobox2::Select $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 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 \
"::combobox2::HandleEvent $widgets(this) $event"
}
}
# ::combobox2::Build --
#
# This does all of the work necessary to create the basic
# combobox2.
#
# 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 ::combobox2
#
# Returns:
#
# the name of the widget
proc ::combobox2::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 ::combobox2::$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)
}
# import the widgets and options arrays into this proc so
# we don't have to use fully qualified names, which is a
# pain.
upvar ::combobox2::${w}::widgets widgets
upvar ::combobox2::${w}::options options
# this is our widget -- a frame of class Combobox2. Naturally,
# it will contain other widgets. We create it here because
# we need it to be able to set our default options.
set widgets(this) [frame $w -class Combobox2 -takefocus 0]
set widgets(entry) [entry $w.entry -takefocus 1 -width 8]
set widgets(button) [label $w.button -takefocus 0]
# this defines all of the default options. We get the
# values from the option database. Note that if an array
# value is a list of length one it is an alias to another
# option, so we just ignore it
foreach name [array names widgetOptions] {
if {[llength $widgetOptions($name)] == 1} continue
set optName [lindex $widgetOptions($name) 0]
set optClass [lindex $widgetOptions($name) 1]
set value [option get $w $optName $optClass]
set options($name) $value
}
# if -value is set to null, we'll remove it from our
# local array. The assumption is, if the user sets it from
# the option database, they will set it to something other
# than null (since it's impossible to determine the difference
# between a null value and no value at all).
if {[info exists options(-value)] \
&& [string length $options(-value)] == 0} {
unset options(-value)
}
# we will later rename the frame's widget proc to be our
# own custom widget proc. We need to keep track of this
# new name, so we'll define and store it here...
set widgets(frame) ::combobox2::${w}::$w
# gotta do this sooner or later. Might as well do it now
pack $widgets(entry) -side left -fill both -expand yes
pack $widgets(button) -side right -fill y -expand no
# I should probably do this in a catch, but for now it's
# good enough... What it does, obviously, is put all of
# the option/values pairs into an array. Make them easier
# to handle later on...
array set options $args
# now, the dropdown list... the same renaming nonsense
# must go on here as well...
set widgets(popup) [toplevel $w.top]
set widgets(listbox) [listbox $w.top.list]
set widgets(vsb) [scrollbar $w.top.vsb]
pack $widgets(listbox) -side left -fill both -expand y
# fine tune the widgets based on the options (and a few
# arbitrary values...)
# NB: we are going to use the frame to handle the relief
# of the widget as a whole, so the entry widget will be
# flat. This makes the button which drops down the list
# to appear "inside" the entry widget.
$widgets(vsb) configure \
-command "$widgets(listbox) yview" \
-highlightthickness 0
$widgets(button) configure \
-highlightthickness 0 \
-borderwidth 1 \
-relief raised \
-width [expr {[winfo reqwidth $widgets(vsb)] - 2}]
$widgets(entry) configure \
-borderwidth 0 \
-relief flat \
-highlightthickness 0
$widgets(popup) configure \
-borderwidth 1 \
-relief sunken
$widgets(listbox) configure \
-selectmode browse \
-background [$widgets(entry) cget -bg] \
-yscrollcommand "$widgets(vsb) set" \
-exportselection false \
-borderwidth 0
# trace variable ::combobox2::${w}::entryTextVariable w \
# [list ::combobox2::EntryTrace $w]
# do some window management foo on the dropdown window
wm overrideredirect $widgets(popup) 1
wm transient $widgets(popup) [winfo toplevel $w]
wm group $widgets(popup) [winfo parent $w]
wm resizable $widgets(popup) 0 0
wm withdraw $widgets(popup)
# this moves the original frame widget proc into our
# namespace and gives it a handy name
rename ::$w $widgets(frame)
# now, create our widget proc. Obviously (?) it goes in
# the global namespace. All combobox2 widgets will actually
# share the same widget proc to cut down on the amount of
# bloat.
proc ::$w {command args} \
"eval ::combobox2::WidgetProc $w \$command \$args"
# ok, the thing exists... let's do a bit more configuration.
if {[catch "::combobox2::Configure $widgets(this) [array get options]" error]} {
catch {destroy $w}
error $error
}
return ""
}
# ::combobox2::HandleEvent --
#
# this proc handles events from the entry widget that we want
# handled specially (typically, to allow navigation of the list
# even though the focus is in the entry widget)
#
# Arguments:
#
# w widget pathname
# event a string representing the event (not necessarily an
# actual event)
proc ::combobox2::HandleEvent {w event} {
upvar ::combobox2::${w}::widgets widgets
upvar ::combobox2::${w}::options options
upvar ::combobox2::${w}::oldValue oldValue
# for all of these events, if we have a special action we'll
# do that and do a "return -code break" to keep additional
# bindings from firing. Otherwise we'll let the event fall
# on through.
switch $event {
"<Any-KeyPress>" {
# if the widget is editable, clear the selection.
# this makes it more obvious what will happen if the
# user presses <Return> (and helps our code know what
# to do if the user presses return)
if {$options(-editable)} {
$widgets(listbox) see 0
$widgets(listbox) selection clear 0 end
$widgets(listbox) selection anchor 0
$widgets(listbox) activate 0
}
}
"<FocusIn>" {
set oldValue [$widgets(entry) get]
}
"<FocusOut>" {
if {![winfo ismapped $widgets(popup)]} {
# did the value change?
# set newValue [set ::combobox2::${w}::entryTextVariable]
set newValue [$widgets(entry) get]
if {$oldValue != $newValue} {
CallCommand $widgets(this) $newValue
}
}
}
"<1>" {
set editable [::combobox2::GetBoolean $options(-editable)]
if {!$editable} {
if {[winfo ismapped $widgets(popup)]} {
$widgets(this) close
return -code break;
} else {
if {$options(-state) != "disabled"} {
$widgets(this) open
return -code break;
}
}
}
}
"<Double-1>" {
if {$options(-state) != "disabled"} {
$widgets(this) toggle
return -code break;
}
}
"<Tab>" {
if {[winfo ismapped $widgets(popup)]} {
::combobox2::Find $widgets(this) 0
return -code break;
} else {
::combobox2::SetValue $widgets(this) [$widgets(this) get]
}
}
"<Escape>" {
# $widgets(entry) delete 0 end
# $widgets(entry) insert 0 $oldValue
if {[winfo ismapped $widgets(popup)]} {
$widgets(this) close
return -code break;
}
}
"<Return>" {
# did the value change?
# set newValue [set ::combobox2::${w}::entryTextVariable]
set newValue [$widgets(entry) get]
if {$oldValue != $newValue} {
CallCommand $widgets(this) $newValue
}
if {[winfo ismapped $widgets(popup)]} {
::combobox2::Select $widgets(this) \
[$widgets(listbox) curselection]
return -code break;
}
}
"<Next>" {
$widgets(listbox) yview scroll 1 pages
set index [$widgets(listbox) index @0,0]
$widgets(listbox) see $index
$widgets(listbox) activate $index
$widgets(listbox) selection clear 0 end
$widgets(listbox) selection anchor $index
$widgets(listbox) selection set $index
}
"<Prior>" {
$widgets(listbox) yview scroll -1 pages
set index [$widgets(listbox) index @0,0]
$widgets(listbox) activate $index
$widgets(listbox) see $index
$widgets(listbox) selection clear 0 end
$widgets(listbox) selection anchor $index
$widgets(listbox) selection set $index
}
"<Down>" {
if {[winfo ismapped $widgets(popup)]} {
tkListboxUpDown $widgets(listbox) 1
return -code break;
} else {
if {$options(-state) != "disabled"} {
$widgets(this) open
return -code break;
}
}
}
"<Up>" {
if {[winfo ismapped $widgets(popup)]} {
tkListboxUpDown $widgets(listbox) -1
return -code break;
} else {
if {$options(-state) != "disabled"} {
$widgets(this) open
return -code break;
}
}
}
}
return ""
}
# ::combobox2::DestroyHandler {w} --
#
# Cleans up after a combobox2 widget is destroyed
#
# Arguments:
#
# w widget pathname
#
# Results:
#
# The namespace that was created for the widget is deleted,
# and the widget proc is removed.
proc ::combobox2::DestroyHandler {w} {
# If the widget actually being destroyed is of class Combobox2,
# crush the namespace and kill the proc. Get it? Crush. Kill.
# Destroy. Heh. Danger Will Robinson! Oh, man! I'm so funny it
# brings tears to my eyes.
if {[string compare [winfo class $w] "Combobox2"] == 0} {
upvar ::combobox2::${w}::widgets widgets
upvar ::combobox2::${w}::options options
# delete the namespace and the proc which represents
# our widget
namespace delete ::combobox2::$w
rename ::$w {}
}
return
}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?