Brijesh
Brijesh

Reputation: 320

How to edit button text in same place in python tkinter?

I am building a 8 puzzle solver in tkinter and I want it to be user interactive like user can change the states accordingly .
So I have created a window with button like below enter image description here

Now want that if I click on button 1 it should become editable like user can change the text of that button . Can you please tell me how can I do that ?
Here is the code for creating the window and adding buttons . Sorry , It is a bit messy .


from tkinter import *

window  = Tk()
window.geometry("500x500")
window.resizable(0,0)

btn_frame = Frame(window , width = 500 , height = 500 , bd = 2)
btn_frame.pack()
one = Button(btn_frame, text = "1" , width = 10 , height = 3)
one.grid(column = 0 , row = 0 , padx = 1 , pady = 1)
two = Button(btn_frame, text = "2" , width = 10 , height = 3)
two.grid(column = 1 , row = 0 , padx = 1 , pady = 1)
three = Button(btn_frame, text = "3" , width = 10 , height = 3)
three.grid(column = 2 , row = 0 , padx = 1 , pady = 1)

four = Button(btn_frame, text = "4" , width = 10 , height = 3)
four.grid(column = 0 , row = 1 , padx = 1 , pady = 1)
five = Button(btn_frame, text = "5" , width = 10 , height = 3)
five.grid(column = 1 , row = 1 , padx = 1 , pady = 1)
six = Button(btn_frame, text = "6" , width = 10 , height = 3)
six.grid(column = 2 , row = 1 , padx = 1 , pady = 1)

seven = Button(btn_frame, text = "7" , width = 10 , height = 3)
seven.grid(column = 0 , row = 2 , padx = 1 , pady = 1)
eight = Button(btn_frame, text = "8" , width = 10 , height = 3)
eight.grid(column = 1 , row = 2 , padx = 1 , pady = 1)
nine = Button(btn_frame, text = "" , width = 10 , height = 3)
nine.grid(column = 2 , row = 2 , padx = 1 , pady = 1)


list = [[one , two , three ], [four , five , six] , [seven , eight , nine]]

#window.after(5000 , window.destroy)
window.mainloop()

Upvotes: 0

Views: 1162

Answers (1)

Giova
Giova

Reputation: 2005

Considering the fact a Button is just a different looking Label with acommand argument automatically bound to <Button-1> event (which is a single left mouse button click); I'll use a Label to demonstrate how Entry overlaying actually solves your issue.

Basically, I'll create a custom widget inheriting from Label, which looks like a Button, and when clicked turns into an Entry that accepts text. When the user press Enter on the Entry it's value is transferred to the Label and the overlay Entry disappear from view. To define that custom widget I'll take advantage of object oriented paradigm (OOP) and its inheritance concept.

Once done, in your code you will just need to use WriteLabel instead of Button. If you then need further customization of the behaviour of the "Buttons" you can modify the edit method: directly or by deriving a new widget that overrides the edit method (maybe using the one implemented by its ancestors before defining its own behaviour).

from tkinter import Tk, Entry, Label, StringVar
from typing import Any, Dict, Tuple


class WriteLabel(Label):
    """
    Just a writable label widget.
    """

    def __init__(self, master: Any = None, *args: Tuple[Any], **kwargs: Any):
        """
        Constructor.

        :param master:  The parent of the widget.
        :type master:   Any.

        :param args:    Additional positional arguments.
        :type args:     Tuple[Any].

        :param kwargs:  Additional keyword arguments.
        :type kwargs:   Any.
        """
        super().__init__(*args, master=master, **kwargs)

        self._parent = master
        self._value = StringVar()
        self._entry_value = StringVar()

        if 'text' in kwargs:
            self._entry_value.set(value=kwargs['text'])

        self.config(
            textvariable=self._value,
            relief='raised'
        )

        self._entry = Entry(
            textvariable=self._entry_value,
            justify='center'
        )

        self.behaviours()
        self.update()

    def behaviours(self) -> None:
        """
        Sets the binding for interested events by defining in fact the behaviour of the widget.

        :return: None.
        :rtype: None.
        """
        self.bind('<Button-1>',self.edit)
        self.bind('<Configure>', self.save)
        self._entry.bind('<FocusOut>', self.save)
        self._entry.bind('<Return>', self.save)

    def edit(self, _) -> None:
        """
        It place the entry as overlay on top of the current widget.

        :param _:   The event object [unused].
        :type _:    Event.

        :return: None.
        :rtype: None.
        """
        self._entry.place_forget()
        self._entry_value.set(value=self._value.get())
        self._entry.place(
            x=self.winfo_x(),
            y=self.winfo_y(),
            width=self.winfo_width(),
            height=self.winfo_height()
        )
        self._entry.focus_set()

    def save(self, _) -> None:
        """
        It hides the entry and copy its actual value to the current widget.

        :param _:   The event object [unused].
        :type _:    Event.

        :return: None.
        :rtype: None.
        """
        self._entry.place_forget()
        self._value.set(value=self._entry_value.get())
        self.update()


if __name__ == '__main__':
    from tkinter import *

    window = Tk()
    window.title('Application')
    window.geometry("500x500")
    window.resizable(width=False, height=False)

    btn_frame = Frame(window, width=500, height=500, bd=2)
    btn_frame.pack()

    one = WriteLabel(btn_frame, text="1", width=10, height=3)
    one.grid(column=0, row=0, padx=1, pady=1)
    two = WriteLabel(btn_frame, text="2", width=10, height=3)
    two.grid(column=1, row=0, padx=1, pady=1)
    three = WriteLabel(btn_frame, text="3", width=10, height=3)
    three.grid(column=2, row=0, padx=1, pady=1)

    four = WriteLabel(btn_frame, text="4", width=10, height=3)
    four.grid(column=0, row=1, padx=1, pady=1)
    five = WriteLabel(btn_frame, text="5", width=10, height=3)
    five.grid(column=1, row=1, padx=1, pady=1)
    six = WriteLabel(btn_frame, text="6", width=10, height=3)
    six.grid(column=2, row=1, padx=1, pady=1)

    seven = WriteLabel(btn_frame, text="7", width=10, height=3)
    seven.grid(column=0, row=2, padx=1, pady=1)
    eight = WriteLabel(btn_frame, text="8", width=10, height=3)
    eight.grid(column=1, row=2, padx=1, pady=1)
    nine = WriteLabel(btn_frame, text="", width=10, height=3)
    nine.grid(column=2, row=2, padx=1, pady=1)

    items = [[one, two, three], [four, five, six], [seven, eight, nine]]
    [i.save(None) for j in items for i in j]

    window.mainloop()

Here a gif gift show how it looks and works on Windows 10:

Demonstration in action

Upvotes: 1

Related Questions