I am making a login program in TCL. I have the following code:
package require Tk
set usr [open "$env(HOME)\\AppData\\Roaming\\basicfile\\USR.txt" r]
set pwd [open "$env(HOME)\\AppData\\Roaming\\basicfile\\PWD.txt" r]
set numtries [expr 3.0]
proc log {} {
if {$::numtries > 0.0} {
if {[.e get] == [read $::usr]} {
if {[.e1 get] == [read $::pwd]} {
close $::usr
close $::pwd
pack forget .l
pack forget .e
pack forget .l1
pack forget .e1
pack forget .b
.l configure -text "Login Successful!"
pack .l
} else {
set ::numtries [expr $::numtries - 1.0]
if {$::numtries <= 0.0} {
.b configure -state disabled
}
}
} else {
set ::numtries [expr $::numtries - 1.0]
if {$::numtries <= 0.0} {
.b configure -state disabled
}
}
} else {
.b configure -state disabled
}
if {$::numtries <= 0.0} {
.b configure -state disabled
}
}
ttk::label .l -text "Enter your name:"
ttk::entry .e
ttk::label .l1 -text "Enter your password:"
ttk::entry .e1 -show "*"
ttk::button .b -text "Login" -command log
pack .l
pack .e
pack .l1
pack .e1
pack .b
This program opens a folder, basicfile, located in the AppData folder of Windows. When it runs, the application works fine, except for one thing. The line that seems to be erroneous is set numtries [expr 3.0]. The program is made so that if the username and password are incorrect it subtracts from this variable, and if the variable reaches 0, the button is disabled. However, when I run the program and press the button three times with an incorrect username and password in the input boxes, it goes to the line .l configure -text "Login Successful!" and bypasses the login part. My question is, why does it disregard the login code and still go in? I should also mention the fact that if I change the line set numtries [expr 3.0] to set numtries [expr 2.0], the program works fine. Thanks!
The problem could possibly be this. The read command reads the whole file at once, so the next time you read from the files you get empty strings. If your name and password fields are empty, they will be equal to the empty strings read from your files, and the login will be successful.
Something like this ought to work:
set dir $env(HOME)/AppData/Roaming/basicfile
set uf [open [file join $dir USR.txt]]
set pf [open [file join $dir PWD.txt]]
set usr [read -nonewline $uf]
set pwd [read -nonewline $pf]
close $uf
close $pf
set numtries 3
ttk::label .nl -text "Enter your name:"
ttk::entry .ne
ttk::label .pl -text "Enter your password:"
ttk::entry .pe -show "*"
ttk::button .b -text "Login" -command log
pack {*}[winfo children .]
proc log {} {
global numtries usr pwd
if {[incr numtries -1] > 0} {
if {([.ne get] eq $usr) && ([.pe get] eq $pwd)} {
destroy {*}[winfo children .]
ttk::label .l -text "Login successful"
pack .l
} else {
.ne delete 0 end
.pe delete 0 end
}
} else {
destroy {*}[winfo children .]
ttk::label .l -text "Too many tries"
pack .l
}
}
If you have an older, obsolete version of Tcl, pack {*}[winfo children .] and the eq operator won't work: try eval pack [winfo children .] (likewise with destroy) and == instead. Or better yet, upgrade.
Documentation: close, destroy, file, global, if, incr, open, pack, proc, read, set, ttk::button, ttk::entry, ttk::label, winfo
Your program seems to be incomplete. Anyway, try this (update the path of usr.txt and pwd.txt):
package require Tk
set usr [open "usr.txt" r]
set pwd [open "pwd.txt" r]
set numtries 3
proc log {} {
puts "Attempt # $::numtries"
if {$::numtries <= 0} {
close $::usr
close $::pwd
.l configure -text "Max retries exceeded..."
.b configure -state disabled
pack forget .e
pack forget .e1
return
}
if {([.e get] != [gets $::usr]) || \
([.e1 get] != [gets $::pwd])} {
incr ::numtries -1
return
}
.l configure -text "Login Successful!"
pack forget .e
pack forget .e1
pack forget .b
close $::usr
close $::pwd
}
ttk::label .l -text "Enter your name:"
pack .l
ttk::entry .e
ttk::entry .e1
pack .e
pack .e1
button .b -text submit -command log
.b configure -state active
pack .b
Related
I was making some GUI at TCL in ModelSim, but when i run that it causes some STRANGE error
# invalid command name "W"
# while executing
# "$w nearest $y"
# (procedure "ListSelectEnd" line 2)
It was kinda odd, because almost similar expression was just before that one. That's the code:
global a
proc ScrolledListbox { parent args } {
frame $parent
eval {listbox $parent.list \
-yscrollcommand [list $parent.sy set] \
-xscrollcommand [list $parent.sx set]} $args
scrollbar $parent.sx -orient horizontal \
-command [list $parent.list xview]
scrollbar $parent.sy -orient vertical \
-command [list $parent.list yview]
pack $parent.sx -side bottom -fill x
pack $parent.sy -side right -fill y
pack $parent.list -side left -fill both -expand true
return $parent.list
}
#-------------------------------------------
proc ListSelect { parent choices } {
global a
frame $parent
ScrolledListbox $parent.choices -width 20 -height 5 \
-setgrid true
ScrolledListbox $parent.picked -width 20 -height 5 \
-setgrid true
pack $parent.choices $parent.picked -side left \
-expand true -fill both
bind $parent.choices.list <ButtonPress-1> \
{ListSelectStart %W %y}
bind $parent.choices.list <ButtonRelease-1> \
lappend a [ListSelectEnd %W %y .top.f.picked.list]
eval {$parent.choices.list insert 0} $choices
}
#----------------------------------------
proc ListSelectStart { w y } {
$w select anchor [$w nearest $y]
}
#-----------------------------------------
proc ListSelectEnd { w y list } {
$w select set anchor [$w nearest $y]
foreach i [$w curselection] {
$list insert end [$w get $i]
lappend listin [$w get $i]
}
return $listin
}
#--------------------------------------------
proc tosignal {parent val} {
global a
for {set i 0} {$i<[llength $a]} {incr i} {
force -freeze sim:/chema_tb/m1/[lindex $a $i] $val 0
}
run 1000 ns
destroy $parent
return 1
}
#------------------------------------------------
proc form {} {
global a
toplevel .top
set filename signalfile.txt
set in [open $filename]
while {[gets $in var]>-1} {
lappend spisn [lindex $var 0]
}
ListSelect .top.f $spisn
button .top.okb -text OK -width 20 -height 2 -font {-size 15 -family Times -weight bold} \
-fg blue -anchor center -command {tosignal .top 0 }
pack .top.f .top.okb -expand true
}
I'll be really thankful if you could help me. :)
The problem is with these lines:
bind $parent.choices.list <ButtonRelease-1> \
lappend a [ListSelectEnd %W %y .top.f.picked.list]
That performs immediate execution of what appears to be a callback, and passes very unlikely arguments to bind too. I bet what you want is either this:
bind $parent.choices.list <ButtonRelease-1> \
{ListSelectEnd %W %y .top.f.picked.list}
or this:
bind $parent.choices.list <ButtonRelease-1> {
ListSelectEnd %W %y .top.f.picked.list
}
or this:
bind $parent.choices.list <ButtonRelease-1> \
[list ListSelectEnd %W %y .top.f.picked.list]
In this case, it doesn't matter which you use. (The first two are equivalent except for whitespace. The third one is much more useful when you're binding variables in the script, but you're not doing that in this case.)
I am beginner with running a tcl/tk script. In my script i have created a popup window to select a file to open and then this file path is given to the source function. I was expecting this script to run stepwise but instead source function is running before i select any file. I also tried using vwait function. Unfortunately it is not running in the 1st run. But in the 2nd run script is working as desire. Can anybody help me to run this script?
destroy .buttons
toplevel .buttons -width 400 -height 100 -background red -relief ridge -borderwidth 8 -padx 10 -pady 10
wm title .buttons "Select a file containing nodes coordinates"
wm geometry .buttons 350x81
set count 0
proc add_button {title command} {
global count
button .buttons.$count -text $title -command $command
pack .buttons.$count -side top -pady 1 -padx 1 -fill x
incr count
}
set types { {{TCL Scripts} {.tcl}} }
add_button "File name" {set accept_button [tk_getOpenFile -filetypes $types]
puts "the path is: $accept_button"
destroy .buttons}
add_button "Exit" {destroy .buttons}
#puts above------------------------
#vwait [namespace which -variable accept_button]
#puts below-----------------------
source "$accept_button"
puts "the src is: $accept_button"
Looks like you are missing the idea of event-driving programming in Tk.
Lets try to find out what is going on in your script. When you run it, the only things are should be done: construct window with widgets for user and bind scripts to widgets events. That is all. After that program is doing nothing but waiting for users action. The command that you bind to a button does not evaluated instantly.
In you case, all work with selected file should be after user have chose it. You should run file reading from button's command. Try to run this script with tclsh
package require Tk
destroy .buttons
toplevel .buttons -width 400 -height 100 -background red -relief ridge -borderwidth 8 -padx 10 -pady 10
wm title .buttons "Select a file containing nodes coordinates"
wm geometry .buttons 350x81
set count 0
proc add_button {title command} {
global count
button .buttons.$count -text $title -command $command
pack .buttons.$count -side top -pady 1 -padx 1 -fill x
incr count
}
set types { {{TCL Scripts} {.tcl}} }
add_button "File name" {set accept_button [tk_getOpenFile -filetypes $types]
puts "the path is: $accept_button"
what_program_should_do_after_file_is_chosen $accept_button
destroy .buttons}
add_button "Exit" {destroy .buttons}
proc what_program_should_do_after_file_is_chosen {path} {
puts "You've chose file: $path"
}
vwait forever
Can someone help me on this situation? I am trying to make a GUI that is used for colors demo of all RGB matrix in the canvas. Unfortornately, the GUI is not responding and it doesn't change colors as expected until the loop is finished. Is there anything wrong? I often encounter this problem if I configure a widget in a loop.
package require Tk
package require math
proc changeColor {rM gM bM} {
for {set r 0} {$r<=$rM} {incr r} {
for {set g 0} {$g<=$gM} {incr g} {
for {set b 0} {$b<=$bM} {incr b} {
set rHex [format %02X $r]
set gHex [format %02X $g]
set bHex [format %02X $b]
set mark #
set color [append mark $rHex $gHex $bHex]
.cv config -bg $color
.lb config -text "[format %03d $r] [format %03d $g] [format %03d $b]"
after 500
}
}
}
}
canvas .cv
ttk::label .lb
ttk::button .bt -text Run -command {changeColor 255 255 255}
grid .cv -row 0 -column 0 -sticky news
grid .lb -row 1 -column 0 -sticky we
grid .bt -row 2 -column 0
Code_Snapshot
GUI_Snapshot
Tk (and Tcl) processes no events at all during a synchronous after 500. It just stops the process for that 500 ms.
You need to instead process events for that time. Replace the after 500 with:
after 500 {set go_on yes}
vwait go_on
Be aware that the go_on there is global, and that this can cause problems with code-reentrancy. You'll want to disable the button that runs the procedure while your code is running.
Or you can use Tcl 8.6 and convert everything to be a coroutine. Then you'll be able to do an asynchronous sleep without danger of filling the stack:
proc changeColor {rM gM bM} {
for {set r 0} {$r<=$rM} {incr r} {
for {set g 0} {$g<=$gM} {incr g} {
for {set b 0} {$b<=$bM} {incr b} {
set rHex [format %02X $r]
set gHex [format %02X $g]
set bHex [format %02X $b]
set mark #
set color [append mark $rHex $gHex $bHex]
.cv config -bg $color
.lb config -text "[format %03d $r] [format %03d $g] [format %03d $b]"
####### Notice these two lines... ########
after 500 [info coroutine]
yield
}
}
}
}
##### Also this one needs to be altered #####
ttk::button .bt -text Run -command {coroutine dochange changeColor 255 255 255}
# Nothing else needs to be altered
I made a simple program accepting short user inputs and storing them in a file. With another button displaying each input in the file as a button. I am trying to create another proc that deletes the button and also the string in the file when i click on the button generated by the string. How do i do that? i tried regsub on the variable holding the values but it seems to delete them once and not every time.
Code to grab current directory
catch { set abspath [file readlink [info script]]}
if { ![info exists abspath]} { set abspath $argv0 }
if { [regexp {^(\S+)\/\S+} $abspath matched dir]} { set BIN $dir }
file mkdir $BIN/debug
if {[file exists $BIN/debug/debug.txt]} { close [open $BIN/debug/debug.txt "w"]}
GUI Code
label .lbl -text "Enter something"
entry .en -justify center
button .sub -text "SUBMIT" -command "submit .en"
button .sho -text "SHOW" -command sho
button .cl -text "CLEAR" -command clear
grid .lbl -columnspan 3
grid .en -columnspan 3
grid .sub .sho .cl
Submit Procedure
proc submit {ent} {
global BIN
if {![file isdirectory $BIN/debug]} { file mkdir $BIN/debug }
set input [$ent get]
if {$input == "" || [string is space -strict $input]} {
$ent delete 0 end
.lbl configure -text "No empty strings"
} else {
set fp [open $BIN/debug/debug.txt a+]
$ent delete 0 end
puts $fp $input
close $fp
}
}
Clear Procedure
proc clear {} {
global BIN
if {[file exists $BIN/debug/debug.txt]} { close[open $BIN/debug/debug.txt "w"] }
}
Procedure to generate button for each item in file
proc sho {} {
global BIN
global filedat
set w.gui
if {[info exists filedat]} { set filedat "" }
toplevel $w
wm title "values"
wm overrideredirect $w 1
bind $w <Button-3> "destroy $w"
if {[file exists $BIN/debug/debug.txt]} {
set fp [open $BIN/debug/debug.txt r]
while {[gets $fp data] > -1} {
lappend filedat $data
}
close $fp
if {[info exist filedat]} {
set dcount 0
foreach item $filedat {
button $w.bn$dcount -text "$item" -font [list arial 10] -anchor w -fg white -bg black -command "del $item"
grid $w.bn$dcount -sticky w
incr dcount
}
} else {
label $w.nthLabel -text "Nothing in file" -bg black -fg white
grid $w.nthLabel
}
}
}
Procedure to delete string (currently not working as expected)
proc del {st} {
global filedat
regsub -all $st $filedat "" filedat2
puts $filedat2
}
When you delete the string with your dep proc you saved the new string in the variable filedat2.
The global variable filedat is never changed.
If you want to remove the string from your global variable, you have to pass this variable to regsub, instead of filedat2.
regsub -all $st $filedat "" filedat
Or if you prefer to save it in a temporal variable to perform some test you could use filedat2 and then assign the variable again:
regsub -all $st $filedat "" filedat2
# ... the tests
if {[isOk]} {
# update the variable
set filedat $filedat2
} else {
# leave the previous value
puts "some error here"
}
The code to reproduce my problem is given below. I named the file as test.tcl
#-------------------------------------------------------------------
# test.tcl
#-------------------------------------------------------------------
namespace eval Gui {
}
proc Gui::test {} {
toplevel .test
wm title .test "Test"
wm resizable .test 0 0 ;# not resizable
# create a frame to hold the check widgets
set f [frame .test.boolean -borderwidth 10]
pack $f -side top
# OK and Cancel buttons
button .test.ok -text "OK" -command [list Gui::Ok .test ]
button .test.cancel -text "Cancel" -command [list Gui::cancel .test ]
pack .test.cancel .test.ok -side right
bind .test <Return> {Gui::Ok .test ; break}
bind .test <Escape> {Gui::cancel .test ; break}
}
proc Gui::Ok { arg } {
set x [list puts "hello world!"]
eval $x
destroy $arg
}
proc Gui::cancel { arg } {
destroy $arg
}
#-------------------------------------------------------------------
# Gui main window
#-------------------------------------------------------------------
proc Gui::initialize { } {
# build the frame which contains menu options
frame .mbar -relief raised -bd 2
frame .mdummy -width 200 -height 240
pack .mbar .mdummy -side top -fill x
# menu options
menubutton .mbar.command -text Command -underline 0 -menu .mbar.command.menu
pack .mbar.command -side left
# menu under command options
menu .mbar.command.menu -tearoff 0
.mbar.command.menu add command -label "Test..." -command [list Gui::test]
}
#-------------------------------------------------------------------
# main code
#-------------------------------------------------------------------
Gui::initialize
When I type
% wish
% source test.tcl
%
and then I click Command -> Test ... -> OK which gives me
% hello world!
I don't get the prompt % back after it prints hello world!. Though I can still execute tcl commands in that space. For example:
% hello world!
puts "hi"
hi
%
which returns the prompt.
My question:
How to get the prompt % back after tcl/tk executes the eval command which prints hello world!
The prompt % came from tcl interpreter and shown in the terminal just because it's in interactive mode. If you run your script as wish test.tcl you will never get %.
You can implement your own interactive mode and call it after all initialization steps of your app. Here the example how it can be done:
proc Gui::interactive {} {
set prompt1 "tcl>"
set prompt2 "?"
set cmd {}
set prompt "$prompt1 "
fconfigure stdin -blocking false -buffering line
fileevent stdin readable {set Gui::stdinReady 1}
while true {
puts -nonewline $prompt
flush stdout
vwait Gui::stdinReady
set str [gets stdin]
lappend cmd $str
set cmdStr [join $cmd "\n"]
if {[info complete $cmdStr]} {
set cmd {}
if {$cmdStr != ""} {
if {[catch {eval $cmdStr} result]} {
puts stderr "ERROR: $result"
} elseif {$result != ""} {
puts $result
}
}
set prompt "$prompt1 "
} else {
set prompt "$prompt2 "
}
if {[eof stdin]} {
puts ""
break
}
}
}
Just call this function after Gui::test execution and you;ll get your own prompt.
But even with this example the prompt will not be redrawn if a text will be printed to the terminal from some other procedure.
You never lost the % prompt. Here's what's happening:
You have a prompt:
%
Then you print a string on that same line:
% hello world!
Your "current" prompt is still that same thing. The following command is "on" that prompt:
puts "hi"
Which, because it is running in tclsh and because you just inserted a newline, comes on the next line:
hi
And you get another prompt:
%
You didn't get "another" prompt from your GUI thing because puts "hello world" wasn't processed by tclsh directly. Basically, as far as tclsh is concerned, the "hello world" came from Mars and screwed up your terminal. It doesn't even know that is there.
Maybe a better way to explain it is this: If your puts "hello world" was printing to a file, then you would still have your % prompt. But someone took those characters and shoved them onto your display (including the newline).