Reputation: 320
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
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
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:
Upvotes: 1