Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set the position of a MessageBox using tkinter

I have been looking all over to see if I can find any help with this and haven't gotten anywhere My program is a simple tkinter menu that is set to be in a default position in the top left corner of the screen, however when I press the X button it loads the message box in the center of the screen.

How do I make it so that it snaps the message box to the corner?

root = Tk()
root.geometry('%dx%d+%d+%d' % (300, 224, 0, 0))
root.resizable(0,0)
def exitroot():
    if tkMessageBox.askokcancel("Quit", "Are you sure you want to quit?"):
        with open(settings, 'wb') as csvfile:
            writedata = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
            writedata.writerow([setpass])
            writedata.writerow([opcolour] + [bkcolour])
            writedata.writerow([menu_background_status] + [menu_internet_status])
        root.destroy()
root.protocol("WM_DELETE_WINDOW", exitroot)`

If any extra code is needed then let me know, and thanks in advance.

like image 473
Mechaniccg Avatar asked Nov 17 '25 16:11

Mechaniccg


2 Answers

Here is a drop-in replacement for messagebox.askokcancel. Mike - SMT's answer doesn't pause execution nor return an answer, and the second example requires refactoring your program further, so I modified his first example to be used without changing standard tkinter code at all (except calling my function, and adding the geometry option which my function accepts):

    answer = messagewindow_askokcancel(
        "Quit",
        "Are you sure you want to quit?",
        parent=root,
        geometry="+0+0",
        # geometry="+{}+{}".format(root.winfo_x(), root.winfo_y()),
    )

Also, the answer set the position to the parent window at all times but I've placed it at the top left of the screen as you asked (commented part uses top left of window). Also, this version has no size in the geometry setting, so the window will automatically size to the text and text will not be cut off if made larger or more text is added. Using this class, other functions with different buttons can be written similarly.

To use the call above, place this code at the top of your (or any) program:

import tkinter as tk
from collections import OrderedDict

class MessageWindow(tk.Toplevel):
    """An independent window that can be positioned.
    Based on https://stackoverflow.com/a/53839951/4541104 but configurable.
    """
    def __init__(self, title, message, answers=None, **options):
        parent = options.get('parent')
        if parent:
            super().__init__(parent)
        else:
            super().__init__()
        # self.answer = None  # a result of None would indicate *closed*
        # (doesn't change default behavior much--you could still do "if"
        # on self.answer like with messagebox.askokcancel's return)
        self.answer = False  # imitate messagebox.askokcancel behavior
        # (return False if dialog closed without pressing a button).
        if not answers:
            answers = OrderedDict(OK=True)
        self.details_expanded = False
        self.title(title)
        geometry = options.get('geometry')
        if geometry:
            self.geometry(geometry)
        self.resizable(False, False)
        self.rowconfigure(0, weight=0)
        self.rowconfigure(1, weight=1)
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)
        tk.Label(self, text=message).grid(row=0, column=0, columnspan=3,
                                          pady=(7, 7), padx=(7, 7), sticky="ew")
        column = 0
        for text, value in answers.items():
            column += 1
            tk.Button(
                self,
                text=text,
                command=lambda v=value: self.set_value(v),
                # ^ v=value to force early binding (store this value
                #   so value of last iteration isn't used for all).
            ).grid(row=1, column=column, sticky="e")

    def set_value(self, value):
        self.answer = value
        self.destroy()


def messagewindow_askokcancel(title, message, **options):
    """A drop-in replacement for askokcancel but you can set x and y.
    For options not listed below, see messagebox.askokcancel documentation.

    Keyword arguments:
        parent (tk.Widget): The window to block.
        geometry (Optional[string]): The window size and/or position
            (not available in messagebox, so a TopLevel is used).
    """
    answer = None
    messagewindow = MessageWindow(
        title,
        message,
        answers=OrderedDict(OK=True, Cancel=False),
        **options,
    )
    # See https://stackoverflow.com/a/31439014/4541104
    messagewindow.master.wait_window(messagewindow)
    # ^ Using master allows us to know what Tk to stop without global(s)
    return messagewindow.answer
like image 189
Poikilos Avatar answered Nov 20 '25 05:11

Poikilos


You will need to build a custom Toplevel() window and then tell it to re-position to the corner of the root window. We can do this with the Toplevel() class and the winfo() methods.

import tkinter as tk
# import Tkinter as tk # for Python 2.X


class MessageWindow(tk.Toplevel):
    def __init__(self, title, message):
        super().__init__()
        self.details_expanded = False
        self.title(title)
        self.geometry("300x75+{}+{}".format(self.master.winfo_x(), self.master.winfo_y()))
        self.resizable(False, False)
        self.rowconfigure(0, weight=0)
        self.rowconfigure(1, weight=1)
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)
        tk.Label(self, text=message).grid(row=0, column=0, columnspan=3, pady=(7, 7), padx=(7, 7), sticky="ew")
        tk.Button(self, text="OK", command=self.master.destroy).grid(row=1, column=1, sticky="e")
        tk.Button(self, text="Cancel", command=self.destroy).grid(row=1, column=2, padx=(7, 7), sticky="e")

root = tk.Tk()
root.geometry("300x224")
root.resizable(0, 0)

def yes_exit():
    print("do other stuff here then root.destroy")
    root.destroy()

def exit_root():
    MessageWindow("Quit", "Are you sure you want to quit?")

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

Results:

enter image description here

Personally I would build this all in one class inheriting from Tk(), make the buttons even with ttk buttons and use a label to reference the built in question image located at ::tk::icons::question like this:

import tkinter as tk
import tkinter.ttk as ttk
# import Tkinter as tk # for Python 2.X

class GUI(tk.Tk):
    def __init__(self):
        super().__init__()
        self.geometry("300x224")
        self.resizable(0, 0)
        self.protocol("WM_DELETE_WINDOW", self.exit_window)

    def yes_exit(self):
        print("do other stuff here then self.destroy")
        self.destroy()

    def exit_window(self):
        top = tk.Toplevel(self)
        top.details_expanded = False
        top.title("Quit")
        top.geometry("300x100+{}+{}".format(self.winfo_x(), self.winfo_y()))
        top.resizable(False, False)
        top.rowconfigure(0, weight=0)
        top.rowconfigure(1, weight=1)
        top.columnconfigure(0, weight=1)
        top.columnconfigure(1, weight=1)
        tk.Label(top, image="::tk::icons::question").grid(row=0, column=0, pady=(7, 0), padx=(7, 7), sticky="e")
        tk.Label(top, text="Are you sure you want to quit?").grid(row=0, column=1, columnspan=2, pady=(7, 7), sticky="w")
        ttk.Button(top, text="OK", command=self.yes_exit).grid(row=1, column=1, sticky="e")
        ttk.Button(top, text="Cancel", command=top.destroy).grid(row=1, column=2, padx=(7, 7), sticky="e")

if __name__ == "__main__":
    GUI().mainloop()

Results:

enter image description here

like image 38
Mike - SMT Avatar answered Nov 20 '25 06:11

Mike - SMT



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!