Ishan Jindal
Ishan Jindal

Reputation: 208

End a running function using inner function with tkinter input GUI form, and display a warning message when exiting the GUI in between

I want the f2 to end not itself, but rather completely the parent function f1 while being executed, with a command. I know that return is used to end a function, but it doesn't work here at a sub-level.

So my question is what are these commands (or sets of lines) and how can I implement them in my code? Example snippet here:

def f1:
    do something
    def f2:
        do something
        # need a command here
    f2()
    do something

f1()

It is noteworthy that the code shall be running a while True: loop at the time of discontinuing function. I used tkinter library button to execute a sub function (which means that the sub-function cannot return a value to a variable), but am unable to end the main function from within that set of code.

here is the tkinter code:

tk.Button(root, text='Click me', command=f2)

Here command = f2 executes f2() when tk.Button is pressed, but the value is not returned anywhere. Probably a local or global variable flag can be used inside f2...

Way to quit the most outer function from an inner function? -- This doesn't solve my problem since I must not define a class or error in my code. Is there another method to do so?

EDIT: I think I am unable to convey the problem I am facing properly.

At this point it is just a mess 😓

import tkinter as tk
from tkinter import messagebox as msgbox
from PIL import ImageTk
lst = []
cnt = 0


black = '#%02x%02x%02x' % (50, 50, 50)
white = '#%02x%02x%02x' % (240, 240, 240)
red = '#%02x%02x%02x' % (255, 80, 80)
yellow = '#%02x%02x%02x' % (255, 220, 80)
green = '#%02x%02x%02x' % (120, 255, 150)
blue = '#%02x%02x%02x' % (0, 220, 240)
purple = '#%02x%02x%02x' % (120, 80, 255)

window_icon = 'icon.jpg'


######## Non-iterables ########

def set_root():
    global root
    root = tk.Tk()  # create only one instance for Tk()
    root.withdraw()


def root_attributes():
    root.iconphoto(True, ImageTk.PhotoImage(file=window_icon))
    root.attributes("-topmost", True)


#################################


def root_win():
    global cnt
    cnt += 1

    set_root()

    if cnt == 1:
        root_attributes()
        
    global lst
    root.deiconify()

    w_root = 500
    h_root = 320

    pos_right = round(root.winfo_screenwidth() / 2 - w_root / 2)
    pos_down = round(root.winfo_screenheight() / 2 - h_root / 2)

    root.title('Enter the values')
    root.resizable(width=False, height=False)
    root.geometry('{}x{}+{}+{}'.format(w_root, h_root, pos_right, pos_down))
    root.configure(bg=white)

    tk.Label(root, text='Enter the values', font=('bold', 30), bg=white, fg=black).place(x=70, y=20)
    tk.Label(root, text='Enter width here:', font=('bold', 15), bg=white, fg=black).place(x=50, y=100)
    tk.Label(root, text='Enter height here:', font=('bold', 15), bg=white, fg=black).place(x=50, y=140)

    val1 = tk.Entry(root, bd=0, font=('bold', 15))
    val1.place(x=280, y=102, width=170)

    val2 = tk.Entry(root, bd=0, font=('bold', 15))
    val2.place(x=280, y=142, width=170)

    lbl = tk.Label(root, text='Min: 5, Max: 100', font=('bold', 15), bg=white, fg=purple)
    lbl.place(x=170, y=260)

    def enter():
        global lst
        if val1.get() == '' and val2.get() == '':
            lbl.config(text='Please enter width and height!')
            lbl.place(x=80, y=260)

        elif val1.get() == '':
            lbl.config(text='Please enter a width!')
            lbl.place(x=145, y=260)

        elif val2.get() == '':
            lbl.config(text='Please enter a height!')
            lbl.place(x=140, y=260)

        else:
            wid, hit = 0, 0
            try:
                wid = round(float(val1.get()))
                hit = round(float(val2.get()))
            except:
                lbl.config(text='Please enter value from 5 to 100!')
                lbl.place(x=70, y=260)

            if not 5 <= wid <= 100 or not 5 <= hit <= 100:
                lbl.config(text='Please enter value from 5 to 100!')
                lbl.place(x=70, y=260)

            else:
                lbl.config(text='INPUT ACCEPTED !!!!')
                lbl.place(x=130, y=260)
                lst = [wid, hit]
                root.deiconify()

    def clr():
        val1.delete(0, 'end')
        val2.delete(0, 'end')
        lbl.config(text='Min: 5, Max: 100')
        lbl.place(x=170, y=260)

    enter = tk.Button(root, text='Enter', font=('bold', 15), bd=0, fg=black, bg=green, activebackground=blue,
                      command=enter)
    enter.place(x=300, y=200)
    enter.configure(width=8)

    clear = tk.Button(root, text='Clear', font=('bold', 15), bd=0, fg=black, bg=red, activebackground=yellow,
                      command=clr)
    clear.place(x=100, y=200)
    clear.configure(width=8)

    root.mainloop()

# set_root()

root_win()

if not lst:
    action = msgbox.askyesno(title='Exit prompt', message='Are you sure you want to exit?\nYes: Exit\nNo: Restart\n',
                             icon='warning', default='no')
    if not action:  # Returns True or False
        root_win()
    else:
        quit()

print(lst)

I expect the code to form a GUI for input of 2 values, and if the values do not meet requirements, it should continue GUI interface untill requirements are met. Also, if user closes the GUI in between, there should be a confirm dialogue box to exit or restart "global function". Thing is, root.destroy() help exit global function but some lines are not iterable, like iconphoto. It gives an error.

Upvotes: 2

Views: 293

Answers (3)

Ishan Jindal
Ishan Jindal

Reputation: 208

Never mind folks, I found the solution I needed:

import tkinter as tk
from tkinter import messagebox as msgbox
from PIL import ImageTk

lst = []


black = '#%02x%02x%02x' % (50, 50, 50)
white = '#%02x%02x%02x' % (240, 240, 240)
red = '#%02x%02x%02x' % (255, 80, 80)
yellow = '#%02x%02x%02x' % (255, 220, 80)
green = '#%02x%02x%02x' % (120, 255, 150)
blue = '#%02x%02x%02x' % (0, 220, 240)
purple = '#%02x%02x%02x' % (120, 80, 255)

window_icon = 'icon.jpg'


def root_win():
    global lst

    root = tk.Tk()  # create only one instance for Tk()

    w_root = 500
    h_root = 320

    pos_right = round(root.winfo_screenwidth() / 2 - w_root / 2)
    pos_down = round(root.winfo_screenheight() / 2 - h_root / 2)

    root.iconphoto(True, ImageTk.PhotoImage(file=window_icon))
    root.attributes("-topmost", True)
    root.title('Enter the values')
    root.resizable(width=False, height=False)
    root.geometry('{}x{}+{}+{}'.format(w_root, h_root, pos_right, pos_down))
    root.configure(bg=white)

    tk.Label(root, text='Enter the values', font=('bold', 30), bg=white, fg=black).place(x=70, y=20)
    tk.Label(root, text='Enter width here:', font=('bold', 15), bg=white, fg=black).place(x=50, y=100)
    tk.Label(root, text='Enter height here:', font=('bold', 15), bg=white, fg=black).place(x=50, y=140)

    val1 = tk.Entry(root, bd=0, font=('bold', 15))
    val1.place(x=280, y=102, width=170)

    val2 = tk.Entry(root, bd=0, font=('bold', 15))
    val2.place(x=280, y=142, width=170)

    lbl = tk.Label(root, text='Min: 5, Max: 100', font=('bold', 15), bg=white, fg=purple)
    lbl.place(x=170, y=260)

    def enter():
        global lst
        if val1.get() == '' and val2.get() == '':
            lbl.config(text='Please enter width and height!')
            lbl.place(x=80, y=260)

        elif val1.get() == '':
            lbl.config(text='Please enter a width!')
            lbl.place(x=145, y=260)

        elif val2.get() == '':
            lbl.config(text='Please enter a height!')
            lbl.place(x=140, y=260)

        else:
            wid, hit = 0, 0
            try:
                wid = round(float(val1.get()))
                hit = round(float(val2.get()))
            except:
                pass

            if not 5 <= wid <= 100 or not 5 <= hit <= 100:
                lbl.config(text='Please enter value from 5 to 100!')
                lbl.place(x=70, y=260)

            else:
                # lbl.config(text='INPUT ACCEPTED !!!!')
                # lbl.place(x=130, y=260)
                lst = [wid, hit]
                root.destroy()

    def clr():
        val1.delete(0, 'end')
        val2.delete(0, 'end')
        lbl.config(text='Min: 5, Max: 100')
        lbl.place(x=170, y=260)

    def closing():
        if msgbox.askyesno(title='Exit prompt', message='Are you sure you want to exit?',
                           icon='warning', default='no'):
            root.destroy()
            print('Have a nice day!')
            quit()

    ext = tk.Button(root, text='Cancel', font=('bold', 15), bd=0, fg=black, bg=red, activebackground=yellow,
                    command=closing)
    ext.place(x=60, y=200)
    ext.configure(width=7)

    clear = tk.Button(root, text='Clear', font=('bold', 15), bd=0, fg=black, bg=yellow, activebackground=red,
                      command=clr)
    clear.place(x=200, y=200)
    clear.configure(width=7)

    enter = tk.Button(root, text='Enter', font=('bold', 15), bd=0, fg=black, bg=green, activebackground=blue,
                      command=enter)
    enter.place(x=340, y=200)
    enter.configure(width=7)

    root.protocol("WM_DELETE_WINDOW", closing)
    root.mainloop()


root_win()
print(lst)

The key was this line set:

    def closing():
        if msgbox.askyesno(title='Exit prompt', message='Are you sure you want to exit?',
                           icon='warning', default='no'):
            root.destroy()
            print('Have a nice day!')
            quit()

Upvotes: 1

defarm
defarm

Reputation: 69

There are a couple of ways I would tackle this - and it depends on the complexity of the problem.

Simple calculations can be achieved like so:

def foo(*args,**kwargs):
    bar = lambda ... : ...
    do something
    final = bar(x)
    return final   

foo()

If the function has added complexity, it makes sense to use a class (or classes depending on the complexity) like so:

class thing_youre_classifying(object):
    def __init__(self):
        ...

    def __any_other_boilerplate_methods_needed__(self):
        ...

    def foo(self):
        do something

    def bar(self):
        do something
        x = f1()

thing = thing_youre_classifying()
thing.bar()

This is mostly for readability (and probably other important things).

Or, if foo and bar are wildly different (say in foo you're parsing a database and bar you're doing statistical analysis), you'll want to check out class inheritance.

class foo(object):
    def __init__(self, *args, **kwargs)
        initialize each instance of foo with args and kwargs

class bar(foo):
    def __init__(self):
        foo.__init__(self, *args, **kwargs)

instance = bar()
instance.foo()

I want to point out that I did not include *args or **kwargs in bar's boilerplate init function. That could also underline the difference between foo and bar.

The last thing that comes to mind is using decorators.

"One of the main uses of decorators is that they allow us to add functionality to existing functions, without modifying the original function. This capability extends to class methods as well." -anonymous professor

def foo():
    def modifier():
        return foo.method_you_want()
    return modifier

@foo
def bar():
    do something

My gut tells me decorators are going to be the cleanest way to do this.

Upvotes: 0

Wizard.Ritvik
Wizard.Ritvik

Reputation: 11611

One way can be to raise an exception from f2, then catch that exception in f1 and then return early:

def f1():
    # do something
    def f2():
        print('Hello')
        # need a command here
        raise StopIteration()
        print('World!')

    try:
        f2()
    except StopIteration:
        return

    print('World')
    # do something

f1()

Outputs:

Hello

I'm not exactly sure where you're defining the while loop mentioned. If it's outside of f1 entirely, I'd delegate handling of the error within such a loop itself - or you could even wrap the while loop with a try-except if absolutely needed. If you go with latter approach, I'd suggest creating a custom exception class and then catching that specific error; this way you can be sure you're handling only the error that you've raised from within f2 for example.

Upvotes: 2

Related Questions