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"
}
Related
I create a mask which shows only a combobox. I want to color certain elements in red.
For several reasons I have to hold a given structure, so that I need three files for it. The first creates the toplevel, in the second the mask is created and there are also the methods to fill the combobox. The third file
includes all methods to create and to handle the combobox.
So this is the first file
(vmHelmert2.tcl):
#!/bin/sh
#\
exec vmwish "$0" ${1+"$#"}
package require itcl
auto_mkindex . vmCombobox2.itcl vmMaskHelmert2.itcl
lappend auto_path /myPath
namespace eval vmHelmert2 {
variable helmert
}
proc vmHelmert2::grundmaske {} {
set top [toplevel .top -class Helmert]
set frMain [frame $top.centrum]
pack $frMain -expand 1 -fill both
set helmertWidget [vmMaskHelmert2 #auto $frMain]
set helmert(widget) [$helmertWidget getThis]
}
vmHelmert2::grundmaske
This is the second file
(vmMaskHelmert2.itcl)
package require Itcl
::itcl::class vmMaskHelmert2 {
public method getThis {}
private method createMaskHelmert {w}
private method setAnsatzList {liste}
private method faerbeAnsatzListe {}
private variable pfd
private variable data
constructor {w} {
createMaskHelmert $w
return $this
}
destructor {
#puts "DESTRUCTOR wird jetzt gestartet."
}
}
::itcl::body vmMaskHelmert2::getThis {} {
return $this
}
::itcl::body vmMaskHelmert2::createMaskHelmert {w} {
set pfd(frMain) [frame $w.frMain]
pack $pfd(frMain) -anchor nw -expand 1 -fill both
set pfd(c_ansatznr) [vmCombobox2 $pfd(frMain).c_ansatznr \
-state normal \
-width 15\
-justify right]
pack $pfd(c_ansatznr) -side left
[$pfd(c_ansatznr) component combobox] configure -postcommand "[itcl::code $this faerbeAnsatzListe]"
set data(ansatzList) [list 1 0 2 1 3 1]
setAnsatzList $data(ansatzList)
}
::itcl::body vmMaskHelmert2::setAnsatzList {liste} {
# Alle Inhalte vorher loeschen
$pfd(c_ansatznr) delete entry 0 end
$pfd(c_ansatznr) delete list 0 end
foreach {einElement status} $liste {
$pfd(c_ansatznr) insert list end $einElement
}
return
}
::itcl::body vmMaskHelmert2::faerbeAnsatzListe {} {
foreach {elem state} $data(ansatzList) {
if { $state } {
# puts "TODO: Farbe Ansatz $elem verändern!!!"
$pfd(c_ansatznr) itemconfigure $elem red
}
}
}
And this is the last file for the combobox
(vmCombobox2.itcl):
package require Itcl
package require Iwidgets
itcl::class vmCombobox2 {
inherit itk::Widget
constructor {args} {}
destructor {}
public method insert {component index args}
public method delete {component first {last {}}}
public method itemconfigure {bez farbe}
private variable popdown
private method create {top}
protected method _deleteList {first {last {}}}
}
itcl::body vmCombobox2::constructor {args} {
ttk::style configure Combobox$this.TCombobox\
-selectbackground #52719c\
-borderwidth 1\
-insertwidth 2\
-selectforeground white\
-fieldbackground white
ttk::style map Combobox$this.TCombobox -background [list disabled #a3a3a3 readonly #a3a3a3]
ttk::style map Combobox$this.TCombobox -foreground [list disabled #d9d9d9 readonly #d9d9d9]
ttk::style map Combobox$this.TCombobox -arrowcolor [list disabled darkgrey readonly black]
create $itk_interior
itk_initialize {*}$args
# wenn -values vor -textvariable steht wird die Variable nicht initialisiert deshalb:
set idx [lsearch $args "-textvariable"]
if {$idx != -1} {
setVar [lindex [$itk_component(combobox) cget -values] end]
}
}
itcl::body vmCombobox2::create {top} {
# puts "createCombobox"
# Label
itk_component add label {
set label [label $top.label -anchor w]
set label
} {
rename -font -labelfont labelFont Font
}
# Frame fuer highlightthickness
itk_component add frame {
set frame [frame $top.frame -highlightcolor black]
set frame
} {
}
# combobox
itk_component add combobox {
set combobox [ttk::combobox $top.frame.combo -style Combobox$this.TCombobox]
set combobox
} {
keep -textvariable -values -cursor -exportselection -justify -height -state -width -takefocus -postcommand\
-invalidcommand -foreground
rename -validate -validateart validateArt ValidateArt
}
grid $itk_component(label) -row 0 -column 0 -sticky ne
grid $itk_component(frame) -row 0 -column 1 -sticky ew
pack $itk_component(combobox) -fill x -expand 1
grid columnconfigure $top 1 -weight 1
grid rowconfigure $top 0 -weight 1
# aufgeklappte Liste
set pd [ttk::combobox::PopdownWindow $itk_component(combobox)]
set popdown $pd.f.l
}
itcl::body vmCombobox2::_deleteList {first {last {}}} {
if {$last == {}} {
set last $first
}
set valueList [$itk_component(combobox) cget -values]
set newValuesList [lreplace $valueList $first $last]
# remove the item if it is no longer in the list
set text [$itk_component(combobox) get]
if {$text != ""} {
set index [lsearch -exact $newValuesList $text]
if {$index == -1} {
$itk_component(combobox) set ""
}
}
$itk_component(combobox) configure -values $newValuesList
return
}
itcl::body vmCombobox2::delete {component first {last {}}} {
switch -- $component {
entry {
if {$last == {}} {
#set last [expr {$first + 1}]
set last $first
}
set text [$itk_component(combobox) get]
set newText [string replace $text $first $last]
$itk_component(combobox) set $newText
}
list {
_deleteList $first $last
}
default {
error "falsches Combobox component \"$component\":\
zugelassen sind: entry or list."
}
}
}
itcl::body vmCombobox2::insert {component index args} {
set nargs [llength $args]
if {$nargs == 0} {
error "Kein Einfuegestring fuer parameter \"string\" in function\"vmCombobox2::insert\""
}
switch -- $component {
list {
if {$itk_option(-state) == "normal"} {
set aktuell [$itk_component(combobox) cget -values]
if {[lsearch -exact $aktuell $args] != -1} {
return
}
set neu [linsert $aktuell $index [join $args]]
$itk_component(combobox) configure -values $neu
}
}
default {error "Falsches vmCombobox2 component \"$component\": zugelassen be entry oder list."}
}
}
itcl::body vmCombobox2::itemconfigure {bez farbe} {
puts "content popdownListe >>[$popdown get 0 end]<<"
# index des Elements in popDownListe
set index [lsearch [$popdown get 0 end] $bez]
try {
$popdown itemconfigure $index -foreground red
} on error {err errOpts} {
puts "Error >>$err<<"
}
}
In the method vmCombobox2::itemconfigure I put the content of the popDownList. If the popDownList is opened for the first time, the content is empty and none of the elements are colored red (
content popdownListe
. I got the error
item number "-1" out of range
(for sure, the popDownList is empty). If I open it for the second time, the elements 2 and 3 are colored red as expected.
Is there a way to fill content to the popdown list before it is opened the first time?
One possible solution would be to configure the items after the list is displayed.
You need for this:
new Version of vmCombobox (check out svn)
Example:
!/bin/sh
\
exec vmwish "$0" ${1+"$#"}
package require Itcl
package require vmWidgets
package require vmTclTools
toplevel .t
frame .t.fr
pack .t.fr
global wms
variable var
set cb1 [vmCombobox .t.fr.li\
-textvariable ::wms(capabilitiesAddr)\
-selectioncommand getV\
-textfont {helvetica 10 bold}\
-labelfont {helvetica 10 bold}\
-values [list 1 2 3 33i 1000 7]\
-textvariable ::wms(var)\
-height 20\
-validate all\
-validate {valMass %P}\
-labeltext testcombobox\
]
pack $cb1
$cb1 insert list end jojo
set pd [$cb1 getPopdown]
$cb1 configure -postcallback [list configureLB $pd]
proc configureLB {pd} {
foreach i [$pd get 0 end] {
# hier Items konfigurieren
puts $i
}
$pd itemconfigure end -foreground red
}
To illustrate, what was meant with the answer here the changes I made in vmCombobox2.itcl and vmMaskHelmert2.itcl
vmMaskHelmert2.itcl
I added a new option -postcallback to the combobox and configured it in faerbeAnsatzListe.
:itcl::body vmMaskHelmert2::faerbeAnsatzListe {} {
set listIndex [list ]
foreach {elem state} $data(ansatzList) {
if { $state } {
# puts "TODO: Farbe Ansatz $elem verändern!!!"
set values [$pfd(c_ansatznr) getValues]
set index [lsearch $values $elem]
lappend listIndex $index
}
}
set pd [$pfd(c_ansatznr) getPopdown]
$pfd(c_ansatznr) configure -postcallback [list configureLB $pd $listIndex]
}
getValues is a method, which lists all items of the list in the combobox.
vmCombobox2.itcl
I added the option postcallback in vmCombobox2.itcl
itcl::class vmCombobox {
...
itk_option define -postcallback postCallback PostCallback ""
...
}
In constructor I added the following lines:
itcl::body vmCombobox::constructor {args} {
...
if {$idx != -1} {
setVar [lindex [$itk_component(combobox) cget -values] end]
}
...
set pd [ttk::combobox::PopdownWindow $itk_component(combobox)]
set oldTags [bindtags $pd]
set tagList [concat $oldTags "callBack$this"]
bind callBack$this <Map> [itcl::code $this popUpCallback]
bindtags $pd $tagList
bind $pd <Unmap> [::itcl::code $this clearAfterSelect]
And I added three more methods (I also declared them in class as public method)
itcl::body vmCombobox::getPopdown {} {
return $popdown
}
itcl::body vmCombobox::popUpCallback {} {
if {$itk_option(-postcallback) != ""} {
eval $itk_option(-postcallback)
}
}
::itcl::body vmCombobox::configureLB {pd listIndex} {
foreach index $listIndex {
$pd itemconfigure $index -foreground red
}
}
For me it worked, with the changes I can colour certain items.
I want to pass a list into a procedure but not sure how to. I've looked at some examples of how to do this but the examples are too complicated and I don't understand them. The list and procedure are shown below.
set RS_CheckBoxes [list kl15_cb din1_cb din2_cb din3_cb din4_cb \
dinnc_cb ain1_cb ain2_cb ain3_cb ain4_cb a_cb \
b_cb u_cb v_cb w_cb sin_cb cos_cb th1_cb th2_cb hvil_cb]
tk::button .rs.enter -height 2 -text "Enter" -width 10 -command {x $RS_CheckBoxes}
proc x {$RS_CheckBoxes} {
if {$RS_CheckBoxes} {
puts "ON"
} else {
puts "OFF"
}
}
At present I'm using the below code but want to reduce the amount of lines.
tk::button .relSel.b -height 2 -text "Enter" -width 10 -command {if {$kl15_cb} {
puts "$kl15_cb"
} else {
puts "$kl15_cb"
}
if {$dinnc_cb} {
puts "$dinnc_cb"
} else {
puts "$dinnc_cb"
}
if {$din1_cb} {
puts "$din1_cb"
} else {
puts "$din1_cb"
}
if {......... etc}
............. etc
Your description is not entirely clear. Do you want to pass a list of global variable names into a proc and then print ON or OFF based on their boolean value?
Currently you just seem to be printing the value of the variables in a very complicated way.
if {$dinnc_cb} {
puts "$dinnc_cb"
} else {
puts "$dinnc_cb"
}
is equal to puts $dinnc_cb unless you want the code to throw an error when the value cannot be interpreted as a boolean.
If my understanding is correct, try this:
proc x {varlist} {
foreach varname $varlist {
upvar #0 $varname var
puts "$varname: [lindex {ON OFF} [expr {!$var}]]"
}
}
The upvar creates a link from the global variable in $varname to the local variable var. You can then use that to check the global variable.
To include a check that the variable is actually set:
proc x {varlist} {
foreach varname $varlist {
upvar #0 $varname var
if {[info exists var]} {
puts "$varname: [lindex {OFF ON} [string is true -strict $var]]"
} else {
puts "$varname: INDETERMINATE"
}
}
}
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
Help me through one of my problem where i seized my thinking to proceed further.
I am creating a radiobutton within foreach loop.Also wanted to have a -command option different for each these buttons, since i am not sure how many buttons will be created(it always dynamic) i put it in a foreach loop.
Further, I am attaching a proc as a variable so whenever any of these radio button is clicked the function body of proc will execute, but this code doesn't functioning properly and throwing error when radiobutton is selected.
I think variables $elem and $w are not getting passed to proc
proc check_lib_name {} {
global ENTRYfilename5 ENTRYfilename f mylist elem w
cd $ENTRYfilename
set dirnames [glob -type d *]
set b 7
set mylist {}
foreach f $dirnames {
set r 2
lappend mylist $f
}
set w 0
foreach elem $mylist {
radiobutton .top.d.$elem -text $elem -command $elem.$w -value $elem.abc
grid .top.d.$elem -row $b -column $r -sticky nsew
incr b
incr w
}
#### Proc attached with radiobuttons
proc $elem.$w {} {
global ENTRYfilename5 ENTRYfilename elem w
cd $elem
set rrpath [glob -type d *]
puts $rrpath
set del "/"
set klpath [concat [string trim $ENTRYfilename][string trim $del][string trim $elem][string trim $del][string trim $rrpath]]
puts $klpath
cd $klpath
exec [myRun.sh]
}
}
After the foreach loop ran, the value of $elem and $w will be the last value they had, which also means you have only 1 proc created and this is for the last radio button.
I don't think it is a good idea to make 1 proc for each ration button. Use 1 generic proc that takes arguments.
...
radiobutton .top.d.$elem -text $elem -command [list radiofunction $elem $w] -value $elem.abc
...
then
proc radiofunction {elem w} {...}
radiofunction is only an example name.
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).