Window re-sizes when making column elements visible, but does not re-size when hiding row elements PySimpleGUI - row

I am creating a program that is used to log time. Upon startup it shows one row for entry and allows the user to add rows as needed. The extra rows are hidden at first and shown when the user hits the '+' button, when this happens, the window expands.
There is also the option to remove row elements using a '-' button, however, when this is done, the window does not shrink.
I'm fairly new to PySimpleGUI and am not sure how this would be implemented. Following is the code used to add and remove rows as needed:
if event == '-AddRow-':
NumberOfRows += 1
window['-Column' + str(NumberOfRows) + '-'].update(visible=True)
if event == '-SubtractRow-':
NumberOfRows -= 1
window['-Column' + str(NumberOfRows) + '-'].update(visible=False)
Example of my what my program is currently doing

The space for a container, like a row or a Column element will still be occupied if visible=False and nothing in the container. Use helper function sg.pin in your layout, it will put a small element in the container.
import PySimpleGUI as sg
layout = [
[sg.Button('+'), sg.Button('-')]] + [
[sg.pin(sg.Input(visible=(i==0), key=('INPUT', i)))] for i in range(10)
]
window = sg.Window('Title', layout)
i = 0 # Maximum index for visible element
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == '+':
i = min(9, i+1)
window[('INPUT', i)].update(visible=True)
elif event == '-':
i = max(0, i-1)
window[('INPUT', i+1)].update(visible=False)
window.close()

Related

Understanding & Converting ThinkScripts CompoundValue Function

I'm currently converting a ThinkScript indicator to C#, however, I've run into this CompoundValue function and I'm unsure how to covert it.
The documents reads :
Calculates a compound value according to following rule: if a bar
number is greater than length then the visible data value is returned,
otherwise the historical data value is returned. This function is used
to initialize studies with recursion.
Example Use:
declare lower;
def x = CompoundValue(2, x[1] + x[2], 1);
plot FibonacciNumbers = x;
My interpretation:
Based on description and example. It appears we are passing a calculation in x[1] + x[2] and it performing this calculation on the current bar and the previous bar (based on first param of 2). I'm unsure what the parameter 1 is for.
My Question:
Please explain what this function is actually doing. If possible, please illustrate how this method works using pseudo-code.
For the TLDR; crowd, some simple code that hopefully explains what the CompoundValue() function is trying to do, and which might help in converting it's functionality:
# from: Chapter 12. Past/Future Offset and Prefetch
# https://tlc.thinkorswim.com/center/reference/thinkScript/tutorials/Advanced/Chapter-12---Past-Offset-and-Prefetch
# According to this tutorial, thinkScript uses the highest offset, overriding
# all lower offsets in the script - WOW
declare lower;
# recursive addition using x[1] is overridden by 11 in the plot for
# Average(close, 11) below; SO `x = x[1] + 1` becomes `x = x[11] + 1`
def x = x[1] + 1;
# using CompoundValue, though, we can force the use of the *desired* value
# arguments are:
# - length: the number of bars for this variable's offset (`1` here)
# - "visible data": value to use IF VALUES EXIST for a bar (a calculation here)
# - "historical data": value to use IF NO VALUE EXISTS for a bar (`1` here)
def y = CompoundValue(1, y[1] + 1, 1);
# *plotting* this Average statement will change ALL offsets to 11!
plot Average11 = Average(close, 11);
# `def`ing the offset DOES NOT change other offsets, so no issue here
# (if the `def` setup DID change the offsets, then `x[1]` would
# become `x[14]`, as 14 is higher than 11. However, `x[1]` doesn't change.
def Average14 = Average(close, 14);
plot myline = x;
plot myline2 = y;
# add some labels to tell us what thinkScript calculated
def numBars = HighestAll(BarNumber());
AddLabel(yes, "# Bars on Chart: " + numBars, Color.YELLOW);
AddLabel(yes, "x # bar 1: " + GetValue(x, numBars), Color.ORANGE);
AddLabel(yes, "x # bar " + numBars + ": " + x, Color.ORANGE);
AddLabel(yes, "y # bar 1: " + GetValue(y, numBars), Color.LIGHT_ORANGE);
AddLabel(yes, "y # bar " + numBars + ": " + y, Color.ORANGE);
Now, some, er, lots of details...
First, a quick note on "offset" values:
thinkScript, like other trading-related languages, uses an internal looping system. This is like a for loop, iterating through all the "periods" or "bars" on a chart (eg, 1 bar = 1 day on a daily chart; 1 bar = 1 minute on a 1 minute intraday chart, etc). Every line of code in thinkScript is run for each and every bar in the chart or length of time specified in the script.
As noted by the OP, x[1] represents an offset of one bar before the current bar the loop is processing. x[2] represents two bars before the current bar, and so on. Additionally, it's possible to offset into the future by using negative numbers: x[-1] means one bar ahead of the current bar, for example.
These offsets work similarly to the for loop in C#, except they're backwards: x[0] in C# would represent the current x value, as it would in thinkScript; however, moving forward in the loop, x[1] would be the next value, and x[-1] wouldn't exist because, well, there is no past value before 0. (In general, of course! One can definitely loop with negative numbers in C#. The point is that positive offset indices in thinkScript represent past bars, while negative offset indices in thinkScript represent future bars - not the case in C#.)
Also important here is the concept of "length": in thinkScript, length parameters represent the distance you want to go - like the offset, but a range instead of one specific bar. In my example code above, I used the statement plot Average11 = Average(close, 11); In this case, the 11 parameter represents plotting the close for a period of 11 bars, ie, offsets x[0] through x[10].
Now, to explain the CompoundValue() function's purpose:
The Chapter 12. Past/Future Offset and Prefetch thinkScript tutorial explains that thinkScript actually overrides smaller offset or length values with the highest value in a script. What that means is that if you have two items defined as follows:
def x = x[1] + 1;
plot Average11 = Average(close, 11);
thinkScript will actually override the x[1] offset with the higher length used in the Average statement - therefore causing x[1] to become x[11]!
Yike! That means that the specified offsets, except the highest offset, mean nothing to thinkScript! So, wait a minute - does one have to use all the same offsets for everything, then? No! This is where CompoundValue() comes in...
That same chapter explains that CompoundValue() allows one to specify an offset for a variable that won't be changed, even if a higher offset exists.
The CompoundValue() function, with parameter labels, looks like this:
CompoundValue(length, "visible data", "historical data")
As the OP noted, this isn't really particularly clear. Here's what the parameters represent:
length: the offset number of bars for this variable.
In our example, def x = x[1] + 1, there is a 1 bar offset, so our statement starts as CompoundValue(length=1, ...). If instead, it was a larger offset, say 14 bars, we'd put CompoundValue(length=14, ...)
"visible data": the value or calculation thinkScript should perform if DATA IS AVAILABLE for the current bar.
Again, in our example, we're using a calculation of x[1] + 1, so CompoundValue(length=1, "visible data"=(x[1] + 1), ...). (Parentheses around the equation aren't necessary, but may help with clarity.)
"historical data": the value to use if NO DATA IS AVAILABLE for the current bar.
In our example, if no data is available, we'll use a value of 1.
Now, in thinkScript, parameter labels aren't required if the arguments are in order and/or defaults are supplied. So, we could write this CompoundValue statement like this without the labels:
def y = CompoundValue(1, y[1] + 1, 1);
or like this with the labels:
def y = CompoundValue(length=1, "visible data"=(y[1] + 1), "historical data"=1);
(Note that parameter names containing spaces have to be surrounded by double quotes. Single-word parameter names don't need the quotes. Also, I've placed parens around the equation just for the sake of clarity; this is not required.)
In summary: CompoundValue(...) is needed to ensure a variable uses the actual desired offset/number of bars in a system (thinkScript) that otherwise overrides the specified offsets with a higher number if present.
If all the offsets in a script are the same, or if one is using a different programming system, then CompoundValue() can simply be broken down into its appropriate calculations or values, eg def x = x[1] + 1 or, alternatively, an if/else statement that fills in the values desired at whatever bars or conditions are needed.
Please let me provide two equivalent working versions of the code in thinkscript itself. We use this approach to prove equivalence by subtracting the equivalent outputs from each other - the result should be 0.
# The original Fibonacci code with a parameter "length" added.
# That parameter is the first parameter of the CompoundValue function.
declare lower;
def length = 2;
def x = CompoundValue(length, x[1] + x[2], 1);
# plot FibonacciNumbers = x;
# Equivalent code using the `if` statement:
def y;
if(BarNumber() > length){
# Visible data. This is within the guarded branch of the if statement.
# Historical data y[1] (1 bar back) and y[2] (2 bars back) is available
y = y[1] + y[2];
}else{
# Not enough historical data so we use the special case satisfying the
# original rule.
y = 1;
}
plot FibonacciNumbersDiff = y - x;
Thinkscript "recursion" is a somewhat inflated term. The function name CompoundValue is not very helpful so it may create confusion.
The version using the if statement is more useful in general because when walking through the time series of bars, we often need a program structure with multiple nested if statements - this cannot be done with the CompoundValue function. Please see my other articles which make use of this in the context of scanning.
In Java, using the same structure, it looks like this:
int size = 100;
int length = 2;
int[] values = new int[size];
for(int index = 1; index < size; index++){
if(index > length){
values[index] = values[index - 1] + values[index - 2];
}else{
values[index] = 1;
}
}
The fundamental difference is the for loop which is not present in the thinkscript code. thinkscript provides the loop in a kind of inversion of control where it executes user code multiple times, once for each bar.

thinkscript if function useless in important case

The thinkscript if function fails to branch as expected in an important case. The following test case can be used to reproduce this severe bug / defect.
In a nutshell, an if statement may normally be used to prevent a function call from being executed if one of its function parameters is invalid. We show that this is not the case. In fact, both branches are executed, including the branch not meeting the if condition.
This absolutely defeats the purpose of the test of the if condition, the test that every if statement in every language has.
Following is some sample code that shows the problem on a chart. The result can be seen by clicking on the "i" message icon blinking in the left top corner of the chart:
Folding: 'from' cannot be greater than 'to': 1 > -1.
# Get the current offset from the right edge from BarNumber()
# BarNumber(): The current bar number. On a chart, we can see that the number increases
# from left 1 to number of bars e.g. 140 at the right edge.
def barNumber = BarNumber();
def barCount = HighestAll(barNumber);
# rightOffset: 0 at the right edge, i.e. at the rightmost bar,
# increasing from right to left.
def rightOffset = barCount - barNumber;
# This script gets the minimum value from data in the offset range between startIndex
# and endIndex. It serves as a functional but not direct replacement for the
# GetMinValueOffset function where a dynamic range is required. Expect it to be slow.
script getMinValueBetween {
input data = low;
input startIndex = 0;
input endIndex = 0;
plot minValue = fold index = startIndex to endIndex with minRunning = Double.POSITIVE_INFINITY do Min(GetValue(data, index), minRunning);
}
# Call this only once at the last bar.
script buildConditions {
input startIndex = 1;
input endIndex = -1;
# Since endIndex < startIndex, getMinValueBetween() should never
# be executed. However it is executed nevertheless.
plot minValue = if (endIndex > startIndex) then getMinValueBetween(low, startIndex, endIndex) else close[startIndex];
}
plot scan;
if (rightOffset == 0) {
scan = buildConditions();
} else {
scan = 0;
}
declare lower;
The question has the answer in its first sentence.
One might contemplate using the if statement (vs the if function). However, that is broken as demonstrated in
thinkscript if statement failure
At least as of April 2021, the documentation for the if reserved word says:
... while the if-expression always calculates both then and else branches, the if-statement only calculates the branch defined by whether the condition is true or false.
(bolding and italics mine)
Definitely confusing and unexpected behavior!

Dynamically assign button callback - Python

So I recently started on Python, I love it. I wanted to create a calculator os my first project, I decided to add the buttons with a loop. But the callback of the button is always the last state of the variable that I would like to pass as the argument for the callback function. Here's my code.
def addButtons():
buttons = []
label = ""
num = 0
posX = 4
posY = 1
for i in range(12):
if (i % 4 < 3):
num += 1
label = str(num)
else:
label = "Blank"
if (i % 4 == 0):
posY += 1
if (posX < 3):
posX += 1
else:
posX = 0
buttons.append(
Button(buttonPanel, text=label, command=lambda: function(i), relief="flat", borderwidth=0, sticky=w, background="#fff", activebackground="#eee"))
buttons[i].grid(column=posX, row=posY, sticky=N + S + E + W)
buttonPanel.grid_columnconfigure(posX, weight=1)
buttonPanel.grid_rowconfigure(posY, weight=1)
functions.append(label)
def function(obj): # the button's functions are identified by this function and executed respectively
print obj
So, When I click a button I always get 11 from the console, no matter what button. Is there a way that I can dynamically set the callback parameter?
Thanks for any help
- Jacob
The problem you have is that when you write lambda: function(i), the lambda function doesn't save the current value of i. Rather, it only looks it up in the outer namespace when it gets called. That's why you see the last value of i in all of your callbacks, since that's the value it had when it was last modified.
You can avoid this problem by binding the name i to a specific value in the lambda:
lambda i=i: function(i)
This is sort of an abuse of default arguments, but it does exactly what you want. The current value of i in the outer namespace gets bound as the default value for a different i variable in the lambda function's namespace. This happens when the lambda is defined, so it doesn't matter that the outer namespace's i changes values later on.
Another way of doing essentially the same thing is to use the partial type defined in the functools module of the standard library. It lets you bind some arguments to a function that can be called later. This is most useful when you expect some additional arguments later, but it can be helpful in this situation too. Instead of the lambda, you'd pass partial(function, i) as the callback.

Python / print and assign random number every time

I'm trying to generate a random integral and assign it to the variable.
import random
import time
Op = lambda: random.randint(1300, 19000)
op = "https://duckduckgo.com/html?q="
variable = int(Op())
grow = 0
while x < 3:
print(Op())
grow = grow + 1
time.sleep(1)
In here everything works fine, function "print" prints different result every time with 3 attempts.
However when I want to format this code like this:
Op = lambda: random.randint(1300, 19000)
op = "https://duckduckgo.com/html?q="
Op1 = int(Op())
pop = str("{}{}").format(op, Op1)
grow = 0
while grow < 3:
print(pop)
grow = grow + 1
time.sleep(1)
Then the function print gives me the same number three times.
For example:
>>>https://duckduckgo.com/html?q=44543
>>>https://duckduckgo.com/html?q=44543
>>>https://duckduckgo.com/html?q=44543
And I would like to get three random numbers. For example:
>>>https://duckduckgo.com/html?q=44325
>>>https://duckduckgo.com/html?q=57323
>>>https://duckduckgo.com/html?q=35691
I was trying to use %s - %d formatting but the result is the same.
Because you never changes the value of 'pop'.
In you first example you are creating instance of Op in every iteration but in second example you created instance once outside the loop and print the same value.
Try this:
Op = lambda: random.randint(1300, 19000)
op = "https://duckduckgo.com/html?q="
grow = 0
while grow < 3:
pop = str("{}{}").format(op, int(Op()))
print(pop)
grow = grow + 1
time.sleep(1)
Lambda functions are by definition anonymous. If you need to "remember" a lambda's procedure, just use def statement. But actually you don't even need this:
import random
import time
url_base = "https://duckduckgo.com/html?q={}"
grow = 0
while grow < 3:
print(url_base.format(random.randint(1300, 19000))
grow = grow + 1
time.sleep(1)
Your main problem is that you are trying to assign fixed values to variables and expect them to behave like procedures.
You need to apply randomness at every iteration. Instead you calculate a random number once and plug it in to every loop.

User score not updated on loop

I'm creating a pedagogical tool which helps students memorize the resistor color code. I'm using Tkinter for the GUI. This is the problematic function in my code:
score = 0
def nextResistor():
global score
global timeLeft
x = random.randint(0,9)
y = random.randint(0,9)
z = random.randint(0,9)
t = random.randint(0,2)
xcolor = getColor(x)
ycolor = getColor(y)
zcolor = getColor(z)
tcolor, tol = getTolerance(t)
if timeLeft > 0:
entryBox1.focus_set()
entryBox2.focus_set()
entryBox3.focus_set()
entryBox4.focus_set()
xguess = entryBox1.get()
yguess = entryBox2.get()
zguess = entryBox3.get()
tguess = entryBox4.get()
# Show the label
if xguess == x and yguess == y and zguess == z and tguess == tol:
score = score + 1
else:
score += 0
print(str(xguess), str(yguess), str(zguess), str(tguess))
entryBox1.delete(0, Tkinter.END)
entryBox2.delete(0, Tkinter.END)
entryBox3.delete(0, Tkinter.END)
entryBox4.delete(0, Tkinter.END)
rlabel.config(text=xcolor + " " + ycolor + " " + zcolor + " " + tcolor)
scoreLabel.config(text="Score: " + str(score))
print(str(score))
For clarification, my function getColor() simply returns a string corresponding to the integer argument it is given. getTolerance() does the same thing, except it also returns an integer tol. Even when the user input is the correct guess, i.e. the condition is true, the score value is not changed. I'm currently printing guesses and scores to help my debugging process, but I've had no luck. Here's a sample of my output:
I suspect this is something simple, but I'm ripping out my hair trying to find it. The main loop in Tkinter essentially just re-runs this program while the time is still greater than zero.
EDIT: The real problem is that xguess, yguess, and so forth does not actually match what the user inputs. How do I resolve this?
EDIT #2: There is an offset between when the random answer is generated and when the user is prompted for the corresponding answer. In the program, the user is prompted to press 'Enter' to begin the loop. In the screensheet below, we can clearly see that the answer is given in the loop before the user is prompted for said answer. The output is in the form (answer, guess).
call
xguess = entryBox1.get()
yguess = entryBox2.get()
zguess = entryBox3.get()
tguess = entryBox4.get()
after the user has pressed return or create a button for submit then check then. The reason is you are querying the boxes when nextResistor is called, not necessarily when the user is done.