How to update variable in pysimplegui?

I have a code here in pysimplegui, and I want to update the SO variable. The problem is that it doesn’t want to update, despite the printers doing so correctly and the presence of a return thingy. I don’t know that’s wrong, since the code looks good. Here’s the code:

import PySimpleGUI as sg
import re

sg.theme('DefaultNoMoreNagging')

items_files = [  [sg.Text(text = "", key = "SelT"), sg.Button(pad = (0, 0), tooltip = 'Delete', key = 'DelS'), sg.Button(pad = (0, 0), tooltip = 'Print', key = 'Prin')]  ]

tab2 = [  [sg.Button(key = 'Rect', pad = (0, 0), tooltip = 'Rectangle')]  ]

BC = 'white'
CW = 600
CH = 450
LXM = (CW / 2)
LYM = (CH / 2)

mid_left = [  [sg.Text(text = "X:"), sg.Input(default_text = "0", key = "TLX", size = (3, 1))],
              [sg.Text(text = "Y:"), sg.Input(default_text = "0", key = "TLY", size = (3, 1))],
              [sg.Text(text = "Scale X:"), sg.Input(default_text = "0", key = "TSX", size = (3, 1))],
              [sg.Text(text = "Scale Y:"), sg.Input(default_text = "0", key = "TSY", size = (3, 1))]  ]

can = sg.Canvas(size = (CW, CH), background_color = BC, border_width = 1, key = "canvas")

mid_screen = [  [sg.Frame("Insert Values", mid_left, expand_x = True, expand_y = True), can]  ]

layout = [  [sg.Frame("", items_files, pad = (1, 1), expand_x = False, expand_y = False), sg.TabGroup([[sg.Tab('Insert', tab2)]])],
            [mid_screen]  ]

N = 0
OL = [0]
SO = None
window = sg.Window('title', layout, finalize = True)
tkc = can.TKCanvas
while True:
    event, values = window.read()
    if event in (sg.WIN_CLOSED, 'Exit'):
        break

    TLX = values["TLX"]
    TLY = values["TLY"]
    TSX = values["TSX"]
    TSY = values["TSY"]
    LT = 2.5
    FC = 'red'
    LC = 'black'

    def sel(e):
        LY = SO
        print (e)
        P = re.findall(r'\d+', str(e.widget.find_withtag("current")))
        print (P)
        LY = str(P[0])
        print (LY)
        window["SelT"].update(LY)
        return LY

    def move(e):
        tkc.moveto(e.widget.find_withtag('current'), e.x, e.y)

    if event == 'Rect':
        OL.insert(N, tkc.create_rectangle((float(LXM) + float(TLX) + float(TSX)), (float(LYM) + float(TLY) + float(TSY)), (float(LXM) + float(TLX) - float(TSX)), (float(LYM) + float(TLY) - float(TSY)), fill = FC, outline = LC, width = LT,  tag = 'R' + str(N)))
        tkc.tag_bind((N + 1), '<1>', sel)
        tkc.tag_bind((N + 1), '<B1-Motion>', move)
        N = N + 1
        print (OL)
        print (N)

    if event == 'Prin':
        print (SO)
    
    if event == 'DelS':
        tkc.delete(OL[int(SO)])
        OL.pop(int(SO))

window.close()

(I added the “print” button to help in debugging).

It’s not that it “doesn’t want” to update, it’s that the only place you’re assigning to it is when you initialise it with SO = None.

Then, the variable should be reassigned, and the function does that. How do I reassign the SO variable with a new value?

Assignment uses the “=“ symbol, but it is directional: the name on the left gets the value of the name on the right. Try this in the interpreter:

x = “x value”
y = None
print(x, y)
x = y
print(x, y)

Versus this:

x = “x value”
y = None
print(x, y)
# different order!
y = x
print(x, y)

Thanks, but I am still confused. If I just do away with LY and do SO instead, it doesn’t work, as the print function still returns None. Same results with SO = LY (even with return). So, the question is how to make the sel function return a number that will be assigned to SO (or any other way of reassigning SO in said function if you know).

Personally I do not know much about pysimplegui, so I’m not sure how the return value of the “sel” function is used. I would try replacing the return LY at the end of the “sel” function with SO = LY and see if that does what you expect? The structure of “sel” becomes:

  1. Get the value of SO and put it in LY
  2. Do whatever queries and processing to get a new value for LY
  3. Assign the updated value of LY to SO

Because SO is declared outside the function, the change to the variable should be visible outside the function also.

Do you mean…

def sel(e):
   LY = SO
   print (e)
   P = re.findall(r'\d+', str(e.widget.find_withtag("current")))
   print (P)
   LY = str(P[0])
   print (LY)
   window["SelT"].update(LY)
   SO = LY

Sadly, it didn’t work (returns an error). Even just deleting LY = SO results in no results (SO still says None). Can you make another one up?

Yes, that is exactly what I meant. Sorry to hear that results in an error - I would have to learn more about pysimplegui before I could make another suggestion. Someone with more experience with that library may have a better suggestion.

I’m guessing that the error is UnboundLocalError. That’s because if you assign to a name in a function, Python assumes that the name is local to that function unless you tell it otherwise. It would need global SO in the function.

Errors have messages for a reason. Even if you do not understand them, when explaining a problem on the Internet it is a good idea to show them, completely. Python stack traces should be formatted like multi-line code, because they are designed to display nicely in a terminal window.

1 Like

Sorry, here:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:...\Python311\Lib\tkinter\__init__.py", line 1948, in __call__
    return self.func(*args)
           ^^^^^^^^^^^^^^^^
  File "C:...", line 185, in sel
    LY = SO
         ^^
UnboundLocalError: cannot access local variable 'SO' where it is not associated with a value

As I thought, and I’ve already told you how to fix that: put global SO in the function.

But the problem is that I tested with by putting it above the LY = SO, it returned an error when I clicked the object:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\...\Python311\Lib\tkinter\__init__.py", line 1948, in __call__
    return self.func(*args)
           ^^^^^^^^^^^^^^^^
  File "C:\...\Python311\...", line 186, in sel
    LY = SO
         ^^
NameError: name 'SO' is not defined

Then, if I remove the LY = SO, it would result in no progress (SO still says None).
So the solution would have to be more than just “global SO”; it might not even require a global variable if possible. Can you come up with another solution?

To be clear, you want the global variable SO (the one that you first mentioned shortly above the while True: loop) to get modified, when the sel function is called? And you tried putting global SO as the first line of the sel function?

You understand that, if you want to change the variable, you still need the SO = LY part (which is not in the original code)? returning a value from a button/mouse/keyboard callback is not useful, because it is not returned “to” anywhere useful - the callback function gets called internally by Tkinter, which will discard the return value. Similarly, the initial assignment LY = SO does not accomplish anything, because the code will simply replace the LY value later anyway. Doing this has no effect on SO.

Putting that together:

def sel(e):
    global SO
    print (e)
    P = re.findall(r'\d+', str(e.widget.find_withtag("current")))
    print (P)
    LY = str(P[0])
    print (LY)
    window["SelT"].update(LY)
    SO = LY

No results (SO still says Nope when I print it using the Prin event). Can you come up with another one that updates SO? The bug reminds me of something like an infinite loop repeating a variable assignment.

Did you try to check that the sel function gets called? What print outputs do you see when it is called - are the e, P and LY values as you expect? What do you expect the code window["SelT"].update(LY) to do? Does that happen? Did you make sure that you call this function before trying to check the SO value?

You did notice that the sel function doesn’t get bound until a Rect event is processed, right? Is that intentional? Why?

Did you try to check that the sel function gets called? What print outputs do you see when it is called - are the e, P and LY values as you expect?

All the prints are for debugging. It’s to see if a program works properly or not. Without these, I would not be able to compare.

  • The e is part of the drag and drop feature and is also essential for the functioning of the selection system. It is the only parameter, because the bind function needs a parameter.
  • The P is a value to be extracted by LY.
  • The LY is the value to replace the old value in SO. Reassigning SO with LY is the main goal of this question.

What do you expect the code window["SelT"].update(LY) to do? Does that happen? Did you make sure that you call this function before trying to check the SO value?

It’s to let the user know which object is clicked. I am thinking by “checking” the SO value, you print it out first before the window["SelT'].update(LY) function?

You did notice that the sel function doesn’t get bound until a Rect event is processed, right? Is that intentional? Why?

It’s part of the drag and drop feature. Without it, it would not work properly

That’s not what I asked. When you try using the program as intended, does it appear that the e, P and LY values are getting printed, as one might expect? Do the values that you see look right?

I mean: how is the GUI supposed to update when window["SelT"].update(LY) runs? For example, is there a text field where the text will change? Then: when you try using the program as intended, does that update happen at the point in the process that you expect it to? Does the value appear correct?

I am talking about the handling of the 'Prin' event, yes.

So to make sure I understand, the 'Rect' event is supposed to happen when the user clicks the mouse to start the drag? Did you make sure that actually happens?

That’s not what I asked. When you try using the program as intended, does it appear that the e, P and LY values are getting printed, as one might expect? Do the values that you see look right?

It did, and the values are alright. Even the print (LY) says so. Even the SO in the function says so if you reassign SO inside the sel function. The question is how to reassign a variable OUTSIDE that sel function.
I am not sure wether it’s something wrong in the bind key (tkc.tag_bind((N + 1), '<1>', sel), but I don’t know.

I mean: how is the GUI supposed to update when window["SelT"].update(LY) runs? For example, is there a text field where the text will change? Then: when you try using the program as intended, does that update happen at the point in the process that you expect it to? Does the value appear correct?

The value is correct as intended. Did you run the program?
I think you gave me a spark of Idea. Instead of trying to reassign the SO variable by a problematic function (in this case, sel), why not just extract the contents from the "SelT" text, convert it into an integer (already done in DelS), and then either…

  • reassign the SO with said result, or
  • directly into the OL list, like OL[int(Extracted text)]).

That way, I don’t have to run into a complex problem like that. Can you give me a method to extract the contents of a PySimpleGui text element (usually with get() as in inputs, but I don’t know)?

I am talking about the handling of the 'Prin' event, yes.

The code already does so.

So to make sure I understand, the 'Rect' event is supposed to happen when the user clicks the mouse to start the drag? Did you make sure that actually happens?

The Rect event is when you click the button. The functions will be bound to the keys, where one “selects” the square (the sel function), while the other drags the square around (the move function).

I got it! I have found a solution!
Here is a workable model. The "SelT" is an input that has it’s ‘value’ assigned to SO, meaning SO will always return the contents of "SelT", no matter the changes. If you click on a canvas object, the sel function activates and changes the input of "SelT" by said canvas object’s tag, which is a number, meaning the SO contains the number needed for the deletion system. Now for the deletion system, a button with the tag of DelF deletes the canvas object, and the item on the list and the variable A is added 1, so when there is 2 and OL is [0, 2] (therefore, the location of 2 in OL is OL[1], the 2 gets substracted by A (which is 1 because the 1 is deleted from OL) becoming 1, allowing the 2 to be deleted from OL, becoming [0]. It should be noted that the insert() became append(), because appending a list means adding an item to the end of the list, which is far more convenient than in the insert() method.

import PySimpleGUI as sg
import re

sg.theme('DefaultNoMoreNagging')

items_files = [  [sg.Input(default_text = "0", key = "SelT"), sg.Text(text = "", key = "SelT"), sg.Button(pad = (0, 0), tooltip = 'Delete', key = 'DelS'), sg.Button(pad = (0, 0), tooltip = 'Print', key = 'Prin')]  ]

tab2 = [  [sg.Button(key = 'Rect', pad = (0, 0), tooltip = 'Rectangle')]  ]

BC = 'white'
CW = 600
CH = 450
LXM = (CW / 2)
LYM = (CH / 2)

mid_left = [  [sg.Text(text = "X:"), sg.Input(default_text = "0", key = "TLX", size = (3, 1))],
              [sg.Text(text = "Y:"), sg.Input(default_text = "0", key = "TLY", size = (3, 1))],
              [sg.Text(text = "Scale X:"), sg.Input(default_text = "0", key = "TSX", size = (3, 1))],
              [sg.Text(text = "Scale Y:"), sg.Input(default_text = "0", key = "TSY", size = (3, 1))]  ]

can = sg.Canvas(size = (CW, CH), background_color = BC, border_width = 1, key = "canvas")

mid_screen = [  [sg.Frame("Insert Values", mid_left, expand_x = True, expand_y = True), can]  ]

layout = [  [sg.Frame("", items_files, pad = (1, 1), expand_x = False, expand_y = False), sg.TabGroup([[sg.Tab('Insert', tab2)]])],
            [mid_screen]  ]

N = 0
A = 0
OL = [0]
window = sg.Window('title', layout, finalize = True)
tkc = can.TKCanvas
while True:
    event, values = window.read()
    if event in (sg.WIN_CLOSED, 'Exit'):
        break

    SO = values["SelT"]
    
    TLX = values["TLX"]
    TLY = values["TLY"]
    TSX = values["TSX"]
    TSY = values["TSY"]
    LT = 2.5
    FC = 'red'
    LC = 'black'

    def sel(e):
        print (e)
        P = re.findall(r'\d+', str(e.widget.find_withtag("current")))
        print (P)
        LY = str(P[0])
        print (LY)
        window["SelT"].update(LY)

    def move(e):
        tkc.moveto(e.widget.find_withtag('current'), e.x, e.y)

    if event == 'Rect':
        OL.append(tkc.create_rectangle((float(LXM) + float(TLX) + float(TSX)), (float(LYM) + float(TLY) + float(TSY)), (float(LXM) + float(TLX) - float(TSX)), (float(LYM) + float(TLY) - float(TSY)), fill = FC, outline = LC, width = LT,  tag = 'R' + str(N)))
        tkc.tag_bind((N + 1), '<1>', sel)
        tkc.tag_bind((N + 1), '<B1-Motion>', move)
        N = N + 1
        print (OL)
        print (N)

    if event == 'Prin':
        print (SO)
    
    if event == 'DelS':
        print (OL[int(SO) - A])
        print (OL)
        tkc.delete(OL[int(SO) - A])
        OL.pop(int(SO) - A)
        A = A + 1
        print (OL)

window.close()

As such, while it does need some refining, I consider this case closed.