I'm trying to create the same numbered variable, but there's something that stops it. But I haven't figured out what it could be yet.
i.e.
set txt0 ""
set txt1 ""
set txt3 ""
So I'm trying to do this dynamically with every click on the button. See my code:
frame .top.tab
button .top.tab.btnTab -text "+" -command { bell ; add }
frame .top.tool
.top.tool configure -relief "raised"
frame .top.panel
.top.panel configure -bg "white"
set n 0
proc add {} {
global n
set txt$n ""
entry .top.tool.ent$n -textvar txt$n
button .top.tool.btn$n -text txt$n -command " remove $n ; .top.panel.lbl$n config -textvar $txt$n "
pack .top.tool.ent$n .top.tool.btn$n -side left
incr n
label .top.panel.lbl$n -text "" -bg "white"
pack .top.panel.lbl$n -fill both -expand yes -anchor e
}
pack .top.tab -side top -fill x -anchor nw
pack .top.tab.btnTab -side right
proc remove { number } {
set total 2
for { set i 0 } { $i < $total } { incr i } {
pack forget .top.panel.lbl$i
}
pack forget .top.panel.lbl$total
pack .top.panel.lbl$number -fill both -expand yes -anchor e
}
pack .top.tool -side top -fill x -anchor nw
pack .top.panel -side top -fill both -expand yes -anchor sw
What could it be?
I know this around the variable $txt$n
You're creating a local variable with the name you want, but Tk won't bind anything to local variables as widgets typically outlast stack frames. You also want to be careful about when you are dealing with the name of a variable versus the current content of the variable.
In the simple case, the best approach is to use an element of a global array:
proc add {} {
global n txt
set txt($n) ""
entry .top.tool.ent$n -textvar txt$n
button .top.tool.btn$n -text txt$n -command \
" remove $n ; .top.panel.lbl$n config -textvar txt($n) "
pack .top.tool.ent$n .top.tool.btn$n -side left
incr n
label .top.panel.lbl$n -text "" -bg "white"
pack .top.panel.lbl$n -fill both -expand yes -anchor e
}
In more complex cases, consider using a TclOO object to hold the state; handles to those are usually "simple" words (unless you take special steps to make them not be; most programmers simply aren't that devious normally).
With the observation and suggestions given in the response of Donal Fellows, I did the necessary changes and worked as expected. In the lines where the property -textvar has just been able to make the exchange of its value by the txt($n) variable that Donal Fellows pointed at the answer. But it has not worked out in my code, since I had to make another change incr n, change its position of the middle of the logic to the end of the proc add scope was vital. Ready, the logic worked as expected. Thanks in advance.
before
after
Related
I am trying to create a TK GUI in TCL that will provide users with the ability to see the current value of a configuration file. This GUI will provide the user the ability to change the config files values to enabled or disabled from radio buttons in the GUI. My configuration file will be a variable length because addition items can be added and I need the GUI to build based on the number of entries in the file. To accomplish this I am trying to loop through the configuration file when creating the GUI and using a variable for each set of radio buttons that can define the user selected value for each Item in the config file.
The configuration file is a simple interface to have the items name associated with a enable (E) or disable (D) value.
Item1 D
Item2 E
Item3 D
Below is the code that I have to generate the radio button for each item in the config with the option to Enable, Disable, or No Change. Here I am trying to create the dynamic variable for each item that will store the selection of the radio button. Besides the current code below I also tried other variations such as -variable selItem${mVal}.
label .optionSelection.c${mVal}_1 -text $mVal
radiobutton .optionSelection.c${mVal}_2 -text "Enable" -variable selItem$mVal \
-value "Enable" -justify left
radiobutton .optionSelection.c${mVal}_3 -text "Disable" -variable selItem$mVal \
-value "Disable" -justify left
radiobutton .optionSelection.c${mVal}_4 -text "No Change" -variable selItem$mVal \
-value "No Change" -justify left
label .optionSelection.c${mVal}_5 -text [dict get $configDict $mVal] \
-textvariable curState${mVal}_5
To get the value of the selItem$mVal (selItemItem1) I have tried to get the value to print with the line below. I have different combination of parenthesis and brackets to create the variable $selItemItem1 so that I can get the value of the selected radio button for that item.
puts "$mVal Variable is $selItem$mVal"
Right now I am just trying to get the variable to print so that I can make it global and reference the value in other procs in the code. I did some research into using either arrays or dictionaries as the variables for the radio buttons. These methods seem like they would be cleaner but I was unable to find examples of how an array or dictionary can be set by the variable.
References Used
tcl: how to use the value of a variable to create a new variable
TCL, How to name a variable that includes another variable
https://www.tutorialspoint.com/tcl-tk/tcl_variables.htm
You definitely want to be using arrays here. To use an array, simply use arrayname($index) as the variable name, and use $arrayname($index) to access the value in the array.
Below is a simple proof of concept on how one might go about writing a configuration screen. I used an = sign in the configuration file to separate the label from the value rather than a space. This code will not work properly if the value contains an = sign.
I also added some descriptive names to display for the user.
This can be expanded on to allow for other types of configuration options, definitely change to present a better user experience, etc.
package require Tk
proc init { } {
global config
global descriptions
set descriptions(Item1) {Item 1 Label}
set descriptions(Item2) {Config B}
set descriptions(Item3) {Item 3}
foreach name [array names descriptions] {
set config($name) D
}
}
proc displayOptions { } {
global config
global descriptions
ttk::frame .optionSel
ttk::label .optionSel.empty -text {}
ttk::label .optionSel.head_on -text On
ttk::label .optionSel.head_off -text Off
grid .optionSel
grid .optionSel.empty .optionSel.head_on .optionSel.head_off
set fh [open t.txt r]
while { [gets $fh line] >= 0 } {
lassign [split $line =] name value
set config($name) $value
}
close $fh
foreach name [array names descriptions] {
ttk::label .optionSel.lab${name} -text $descriptions($name)
ttk::radiobutton .optionSel.c${name}_on -value E -variable config($name)
ttk::radiobutton .optionSel.c${name}_off -value D -variable config($name)
grid .optionSel.lab${name} .optionSel.c${name}_on .optionSel.c${name}_off \
-sticky w
}
ttk::button .optionSel.save -text { Save } -command ::saveOptions
grid .optionSel.save
}
proc saveOptions { } {
global descriptions
global config
set fh [open t.txt w]
foreach name [array names descriptions] {
puts $fh "$name=$config($name)"
}
close $fh
}
init
displayOptions
Using the REBOL/View 2.7.8 Core, I would like to prepare a view layout beforehand by automatically assigning words to various layout items, as in the following example.
Instead of
prepared-view: [across
cb1: check
label "Checkbox 1"
cb2: check
label "Checkbox 2"
cb3: check
label "Checkbox 3"
cb4: check
label "Checkbox 4"
]
view layout prepared-view
I would thus like the words cb1 thru cb5 to be created automatically, e.g.:
prepared-view2: [ across ]
for i 1 4 1 [
cbi: join "cb" i
cbi: join cbi ":"
cbi: join cbi " check"
append prepared-view2 to-block cbi
append prepared-view2 [
label ]
append prepared-view2 to-string join "Checkbox " i
]
view layout prepared-view2
However, while difference prepared-view prepared-view2 shows no differences in the block being parsed (== []), the second script leads to an error:
** Script Error: cb1 word has no context
** Where: forever
** Near: new/var: bind to-word :var :var
I've spent hours trying to understand why, and I think somehow the new words need to be bound to the specific context, but I have not yet found any solution to the problem.
What do I need to do?
bind prepared-view2 'view
view layout prepared-view2
creates the correct bindings.
And here's another way to dynamically create layouts
>> l: [ across ]
== [across]
>> append l to-set-word 'check
== [across check:]
>> append l 'check
== [across check: check]
>> append l "test"
== [across check: check "test"]
>> view layout l
And then you can use loops to create different variables to add to your layout.
When you use TO-BLOCK to convert a string to a block, that's a low-level operation that doesn't go through the "ordinary" binding to "default" contexts. All words will be unbound:
>> x: 10
== 10
>> code: to-block "print [x]"
== [print [x]]
>> do code
** Script Error: print word has no context
** Where: halt-view
** Near: print [x]
So when you want to build code from raw strings at runtime whose lookups will work, one option is to use LOAD and it will do something default-ish, and that might work for some code (the loader is how the bindings were made for the code you're running that came from source):
>> x: 10
== 10
>> code: load "print [x]"
== [print [x]]
>> do code
10
Or you can name the contexts/objects explicitly (or by way of an exemplar word bound into that context) and use BIND.
Suppose you have defined two functions in a module (i.e. a .psm1 file):
function f1{
param($x1)
$a1 = 10
f2 $x1
}
function f2{
param($x2)
$a2 = 100
& $x2
}
Now suppose you run the following:
PS C:\> $a0 = 1
PS C:\> $x0 = {$a0+$a1+$a2}
PS C:\> f1 $x0
1
$x2 keeps the context of the command line despite being invoked inside $f2. This holds if you change & to ..
Replacing $xn with $xn.GetNewClosure() in the module then calling f1 captures the value of 100 but not 10:
PS C:\> f1 $x0
101
PS C:\> f1 $x0.GetNewClosure
101
This happens because calling .GetNewClosure() inside f2 "overwrites" the value of $a1 captured in f1.
Is there a way to selectively capture variables in scriptblocks? Working from the example, is there a way to capture both $a1 inside f1 and $a2 inside f2?
Further Reading
PowerShell scopes are not simple. Consider the possibilities from this incomplete list of factors:
there can be any combination of global and module scope hierarchies active at any time
. and & invocation affects scope differently,
the sophisticated flow control afforded by the pipeline means that multiple scopes of the begin, process, and end scriptblocks of different or the same scope hierarchies, or even multiple invocations of the same function can be active simultaneously
In other words, a working description of PowerShell scope resists simplicity.
The about_Scopes documentation suggests the matter is far simpler than it, in fact, is. Perhaps analysing and understanding the code from this issue would lead to a more complete understanding.
I was hoping there was a built-in way of achieving this. The closest thing I found was [scriptblock]::InvokeWithContext(). Handling the parameters for InvokeWithContext() manually gets pretty messy. I managed to encapsulate the mess by defining a couple of helper functions in another module:
function ConvertTo-xScriptblockWithContext{
param([parameter(ValueFromPipeline=$true)]$InputObject)
process{
$InputObject | Add-Member -NotePropertyMembers #{variablesToDefine=#()}
{$InputObject.InvokeWithContext(#{},$InputObject.variablesToDefine)}.GetNewClosure() |
Add-Member -NotePropertyMembers #{ScriptBlockWithContext=$InputObject} -PassThru
}}
function Add-xVariableToContext{
param(
[parameter(ValueFromPipeline=$true)]$InputObject,
[parameter(position=1)]$Name,
[parameter(position=2)]$Value
)
process{
$exists = $InputObject.ScriptBlockWithContext.variablesToDefine | ? { $_.Name -eq $Name }
if ($exists) { $exists = $Value }
else{ $InputObject.ScriptBlockWithContext.variablesToDefine += New-Object 'PSVariable' #($Name,$Value) }
}}
Then, f1 and f2 add variables to the scriptblock's context using Add-xVariableToContext as it passes through:
function f1{
param($x1)
$a1 = 10
$x1 | Add-xVariableToContext 'a1' $a1
f2 $x1
}
function f2{
param($x2)
$a2 = 100
$x2 | Add-xVariableToContext 'a2' $a2
& $x2
}
Notice that $x2 is invoked like any other scriptblock so it can be safely used with the variables added to its context by anything that accepts scriptblocks. Creating new scriptblocks, adding $a0 to their context, and passing them to f1 looks like this:
$a0 = 1
$x0a,$x0b = {$a0+$a1+$a2},{$a0*$a1*$a2} | ConvertTo-xScriptblockWithContext
$x0a,$x0b | Add-xVariableToContext 'a0' $a0
f1 $x0a
f1 $x0b
#111
#1000
I'm using a console menu to restore deleted items from the AD recycle bin.
There are 4 menu options, each of them requiring user input (read-host) but it needs to be in the correct format or the restore won't work. Is there a way to define constraints for each variable/read-host in the console menu?
Here is my menu
#Keep looping and running the menu until the user selects Q (or q).
Do {
#use a Switch construct to take action depending on what menu choice
#is selected.
Switch (Show-Menu $menu " `nActive Directory Restore AD Tree Menu" -clear) {
"1" {Write-Host " `nRestore AD Tree using LastKnownRDN" -ForegroundColor Yellow
$RDN=Read-Host " `nEnter LastKnownRDN of deleted object (eg. STAFF) "
.\Restore_ADTree.ps1 Restore-ADTree -lastknownRDN $RDN
sleep -seconds 2
}
"2" {Write-Host "Restore AD Tree using LastKnownRDN and LastKnownParent" -ForegroundColor Green
$RDN=Read-Host " `nEnter LastKnownRDN of deleted object (eg. STAFF) "
$LNP=Read-Host " `nEnter LastKnownParent of deleted object in quotes (eg. "OU=Staff,DC=xxxx,DC=xxxx,DC=xxxx,DC=xxxx")"
.\Restore_ADTree.ps1 Restore-ADTree -lastknownRDN $RDN -lastknownParent $LNP
sleep -seconds 5
}
"3" {Write-Host "Restore AD Tree Using Identity Distinguished Name" -ForegroundColor Magenta
$DName=Read-Host " `nEnter the Distinguished Name of deleted object (eg. "OU=Test,OU=Staff,DC=xxxx,DC=xxxx,DC=xxxx,DC=xxxx") "
.\Restore_ADTree.ps1 Restore-ADTree -identity $DName
sleep -seconds 2
}
"4" {Write-Host "Restore AD Tree Using Identity GUID"
$GUID=Read-Host " `nEnter the GUID of deleted object (eg. b48290aa-e14f-4417-9c03-560a546d18b9) "
.\Restore_ADTree.ps1 Restore-ADTree -identity $GUID
sleep -seconds 2
}
"Q" {Write-Host "Goodbye" -ForegroundColor Cyan
Return
}
Default {Write-Warning "Invalid Choice. Try again."
sleep -milliseconds 750}
} #switch
} While ($True)
So for 2. I'd like to ensure $LNP is in quotes
For 3. I'd like the input to at least contain DC=XXXX,DC=XXXX,DC=XXXX and to be enclosed in quotes
For 4. I'd like to ensure it's in xxxxxxxx-xxxx-xxxxx-xxxxx-xxxxxxxxxxxx no special characters.
I'd also like to be able to write to the console if they got it wrong with a message about what format it needs to be in.
If someone could please point me in the right direction I'd really appreciate it.
Thanks,
Amelia
You can create your own Read-Host implementation, something like this:
function MyRead-Host($prompt, $regex)
{
while(!$inputOk) { $inputOk = (IsInputOk (Read-Host -Prompt $prompt) $regex) }
}
function IsInputOk([string] $string, [string] $regex)
{
if ($string -eq $regex) { return $true } return $false
}
This particular implementation will ask user for input until it will be equal to $regex variable. You should do some smart Regex matching if you want to have generic function, otherwise yo can write own validator for each case
I have a proc that creates a new window, asking the user to give a database name.. And I want the function, after it's closed to return a value.
How do I make a window to return a value to it's calling proc? I tried calling it using:
puts "dbug:: [set top [new_db_window]]"
The puts is to see the result. It doesn't work. Prints an empty sting ("dbug::") as the window is created and an error "can't read "::new_db_window": no such variable" when I hit the 'ok' button.
The code for the proc is:
proc new_db_window {} {
toplevel .new_db_menu
wm title .new_db_menu "New Data Base"
# Main Frame
frame .new_db_menu.frm -relief "groove" -bd 2
grid .new_db_menu.frm
if {[info exists db_name]} {
unset db_name
}
set ::new_db_window:db_name "Data_Base"
# The Name Entry
set frm_top [frame .new_db_menu.frm.top]
set lbl [label .new_db_menu.frm.top.label -text "Database Name:" -width 15]
set entr [entry .new_db_menu.frm.top.entry -textvariable ::new_db_window:db_name -width 15]
# The buttons
set b_ok [button .new_db_menu.frm.ok -image icon_v -command {return [new_db_ok_button]}]
set b_no [button .new_db_menu.frm.cancel -image icon_x -command {new_db_cancel_button}]
set sep_w [label .new_db_menu.frm.sep_w -text "" -width 1]
set sep_e [label .new_db_menu.frm.sep_e -text "" -width 1]
grid $lbl -row 1 -column 1 -sticky w
grid $entr -row 1 -column 2 -sticky w
grid $frm_top -row 1 -column 1 -columnspan 4
grid $sep_w -row 2 -column 1 -sticky w
grid $b_ok -row 2 -column 2 -sticky w
grid $b_no -row 2 -column 3 -sticky e
grid $sep_e -row 2 -column 4 -sticky e
bind .new_db_menu <Key-KP_Enter> {return [new_db_ok_button]}
bind .new_db_menu <Return> {return [new_db_ok_button]}
bind .new_db_menu <Escape> {new_db_cancel_button}
# catch presses on the window's `x` button
wm protocol .new_db_menu WM_DELETE_WINDOW {
new_db_cancel_button
}
# make the top window unusable
focus $entr
grab release .
grab set .new_db_menu
}
proc new_db_ok_button {} {
new_db_cancel_button
return "$::new_db_window:db_name"
}
proc new_db_cancel_button {} {
grab set .
destroy .new_db_menu
}
One way would be to just use tkwait window $yourwindow to wait until the user closes the window. The window itself should probably use some variable passed to it by the client code to manage user input. For instance, if you need the user to input a database name, use the entry widget and bind it to a variable using its -textvariable option. After the window is closed, and tkwait in the client code returns, read the value of that variable.
Another approach is to not use modal windows and turn into event-driven control flow. That is, make your inquiry window to receive the name of a procedure which should be called when the user accepts its input (and that input is validated) and do any further processing there instead of posting a window and waiting until the user deals with it.
The relevant manual pages are: tkwait and options (for -textvariable).