创建此 class 的两个实例会产生奇怪的行为 tkinter python
Creating two instances of this class, creates weird behavior tkinter python
我创建了一个继承自 ttk.Entry
的自定义占位符条目 class。问题是,它只适用于实例化一次,如果我为同一个条目创建两个实例,它将开始显示一些奇怪的效果。
Class及其用法:
import tkinter as tk
from tkinter import ttk
class PlaceholderEntry(ttk.Entry):
'''
Custom modern Placeholder Entry box, takes positional argument master and placeholder\n
Use acquire() for getting output from entry widget\n
Use shove() for inserting into entry widget\n
Use remove() for deleting from entry widget\n
Use length() for getting the length of text in the widget\n
BUG 1: Possible bugs with binding to this class\n
BUG 2: Anomalous behaviour with config or configure method
'''
def __init__(self, master, placeholder, **kwargs):
# style for ttk widget
self.s = ttk.Style()
self.s.configure('my.TEntry')
# init entry box
ttk.Entry.__init__(self, master, style='my.TEntry', **kwargs)
self.text = placeholder
self.__has_placeholder = False # placeholder flag
# add placeholder if box empty
self._add()
# bindings of the widget
self.bind('<FocusIn>', self._clear)
self.bind('<FocusOut>', self._add)
self.bind_all('<Key>', self._normal)
self.bind_all('<Button-1>', self._cursor)
def _clear(self, *args): # method to remove the placeholder
if self.get() == self.text and self.__has_placeholder: # remove placeholder when focus gain
self.delete(0, tk.END)
self.s.configure('my.TEntry', foreground='black',
font=(0, 0, 'normal'))
self.__has_placeholder = False # set flag to false
def _add(self, *args): # method to add placeholder
if self.get() == '' and not self.__has_placeholder: # if no text add placeholder
self.s.configure('my.TEntry', foreground='grey',
font=(0, 0, 'bold'))
self.insert(0, self.text) # insert placeholder
self.icursor(0) # move insertion cursor to start of entrybox
self.__has_placeholder = True # set flag to true
def _normal(self, *args): # method to set the text to normal properties
self._add() # if empty add placeholder
if self.get() == self.text and self.__has_placeholder: # clear the placeholder if starts typing
self.bind('<Key>', self._clear)
self.icursor(-1) # keep insertion cursor to the end
else:
self.s.configure('my.TEntry', foreground='black',
font=(0, 0, 'normal')) # set normal font
def acquire(self): # custom method to get the text
if self.get() == self.text and self.__has_placeholder:
return 'None'
else:
return self.get()
def shove(self, index, string): # custom method to insert into entry
self._clear()
self.insert(index, string)
def remove(self, first, last): # custom method to remove from entry
if self.get() != self.text:
self.delete(first, last)
self._add()
elif self.acquire() == self.text and not self.__has_placeholder:
self.delete(first, last)
self._add()
def length(self):
if self.get() == self.text and self.__has_placeholder:
return 0
else:
return len(self.get())
def _cursor(self, *args): # method to not allow user to move cursor when placeholder exists
if self.get() == self.text and self.__has_placeholder:
self.icursor(0)
# MRE
if __name__ == '__main__':
root = tk.Tk()
e1 = PlaceholderEntry(root,'First Entry')
e1.pack()
e2 = PlaceholderEntry(root,'Second Entry')
e2.pack()
root.mainloop()
在这里,单击其中一个条目并将焦点切换到下一个条目时,您会注意到两个实例之间有些关联的“行为”吗?尽管单个实例工作得很好。这是我的第一个 OOP 项目,所以我不确定哪里出了问题。
提前致谢:D
你有两个问题,都与你的 class 依赖于全局数据的事实有关:你正在使用 bind_all
有效地创建全局绑定,并且你对两者使用相同的样式普通和占位符,但样式是全局的。
第一个问题是您对 bind_all
的使用。每次您创建一个实例时,该实例的 bind_all
将替换 来自前一个实例的绑定。这两个实例都将继承一个绑定,该绑定仅调用最后创建的实例的 _normal
和 _cursor
方法。
作为一般经验法则,像这样的 class 应该始终只在其自身上创建绑定。您需要将 self.bind_all
更改为 self.bind
.
self.bind('<Key>', self._normal)
self.bind('<Button-1>', self._cursor)
第二个问题是您对样式的使用。您在两种状态下为两个小部件使用单一样式,但由于样式是全局的,因此当您更改一个小部件中的样式时,它会影响另一个。
与其重新配置单一样式,不如配置两种样式并在它们之间切换。例如,您可以为正常情况创建一个 my.TEntry
样式,并为要显示占位符的情况创建一个 placeholder.TEntry
样式。
首先在 __init__
方法中配置样式:
def __init__(self, master, placeholder, **kwargs):
# style for ttk widget
self.s = ttk.Style()
self.s.configure('my.TEntry', foreground='black', font=(0, 0, 'normal'))
self.s.configure('placeholder.TEntry', foreground='grey', font=(0, 0, 'bold'))
接下来,无需重新配置样式定义,只需交换小部件上的样式即可。例如,_clear
看起来像这样:
def _clear(self, *args): # method to remove the placeholder
if self.get() == self.text and self.__has_placeholder:
self.configure(style="my.TEntry")
...
同样,_add
应该看起来像这样:
def _add(self, *args): # method to add placeholder
if self.get() == '' and not self.__has_placeholder: # if no text add placeholder
self.configure(style="placeholder.TEntry")
...
最后,您的逻辑中有一个错误,可能是由于误解了绑定的工作原理。当您绑定到 <Key>
时,该绑定发生在默认绑定之前。因此,您在 self._add
中检查条目是否为空的检查发生在您刚刚键入的键被插入之前。因此,代码认为条目小部件是空的,并将样式切换为具有占位符。因此,它添加了占位符文本,然后对密钥进行默认处理并插入密钥。
该特定问题至少有三种解决方案。
- 您可以在
<KeyRelease>
而不是 <Key>
上进行绑定,因为这将在默认绑定插入字符后触发。
- 您可以在默认绑定之后添加一个自定义绑定标签,这样默认绑定先发生,然后您的绑定发生。
- 你可以坚持
bind_all
。在那种情况下,您只需要对每个小部件进行一次绑定而不是一次绑定,并且您需要调整您的函数以使用 event.widget
而不是 self
.
This answer and This answer 更详细地描述了绑定标签的工作原理。
我创建了一个继承自 ttk.Entry
的自定义占位符条目 class。问题是,它只适用于实例化一次,如果我为同一个条目创建两个实例,它将开始显示一些奇怪的效果。
Class及其用法:
import tkinter as tk
from tkinter import ttk
class PlaceholderEntry(ttk.Entry):
'''
Custom modern Placeholder Entry box, takes positional argument master and placeholder\n
Use acquire() for getting output from entry widget\n
Use shove() for inserting into entry widget\n
Use remove() for deleting from entry widget\n
Use length() for getting the length of text in the widget\n
BUG 1: Possible bugs with binding to this class\n
BUG 2: Anomalous behaviour with config or configure method
'''
def __init__(self, master, placeholder, **kwargs):
# style for ttk widget
self.s = ttk.Style()
self.s.configure('my.TEntry')
# init entry box
ttk.Entry.__init__(self, master, style='my.TEntry', **kwargs)
self.text = placeholder
self.__has_placeholder = False # placeholder flag
# add placeholder if box empty
self._add()
# bindings of the widget
self.bind('<FocusIn>', self._clear)
self.bind('<FocusOut>', self._add)
self.bind_all('<Key>', self._normal)
self.bind_all('<Button-1>', self._cursor)
def _clear(self, *args): # method to remove the placeholder
if self.get() == self.text and self.__has_placeholder: # remove placeholder when focus gain
self.delete(0, tk.END)
self.s.configure('my.TEntry', foreground='black',
font=(0, 0, 'normal'))
self.__has_placeholder = False # set flag to false
def _add(self, *args): # method to add placeholder
if self.get() == '' and not self.__has_placeholder: # if no text add placeholder
self.s.configure('my.TEntry', foreground='grey',
font=(0, 0, 'bold'))
self.insert(0, self.text) # insert placeholder
self.icursor(0) # move insertion cursor to start of entrybox
self.__has_placeholder = True # set flag to true
def _normal(self, *args): # method to set the text to normal properties
self._add() # if empty add placeholder
if self.get() == self.text and self.__has_placeholder: # clear the placeholder if starts typing
self.bind('<Key>', self._clear)
self.icursor(-1) # keep insertion cursor to the end
else:
self.s.configure('my.TEntry', foreground='black',
font=(0, 0, 'normal')) # set normal font
def acquire(self): # custom method to get the text
if self.get() == self.text and self.__has_placeholder:
return 'None'
else:
return self.get()
def shove(self, index, string): # custom method to insert into entry
self._clear()
self.insert(index, string)
def remove(self, first, last): # custom method to remove from entry
if self.get() != self.text:
self.delete(first, last)
self._add()
elif self.acquire() == self.text and not self.__has_placeholder:
self.delete(first, last)
self._add()
def length(self):
if self.get() == self.text and self.__has_placeholder:
return 0
else:
return len(self.get())
def _cursor(self, *args): # method to not allow user to move cursor when placeholder exists
if self.get() == self.text and self.__has_placeholder:
self.icursor(0)
# MRE
if __name__ == '__main__':
root = tk.Tk()
e1 = PlaceholderEntry(root,'First Entry')
e1.pack()
e2 = PlaceholderEntry(root,'Second Entry')
e2.pack()
root.mainloop()
在这里,单击其中一个条目并将焦点切换到下一个条目时,您会注意到两个实例之间有些关联的“行为”吗?尽管单个实例工作得很好。这是我的第一个 OOP 项目,所以我不确定哪里出了问题。
提前致谢:D
你有两个问题,都与你的 class 依赖于全局数据的事实有关:你正在使用 bind_all
有效地创建全局绑定,并且你对两者使用相同的样式普通和占位符,但样式是全局的。
第一个问题是您对 bind_all
的使用。每次您创建一个实例时,该实例的 bind_all
将替换 来自前一个实例的绑定。这两个实例都将继承一个绑定,该绑定仅调用最后创建的实例的 _normal
和 _cursor
方法。
作为一般经验法则,像这样的 class 应该始终只在其自身上创建绑定。您需要将 self.bind_all
更改为 self.bind
.
self.bind('<Key>', self._normal)
self.bind('<Button-1>', self._cursor)
第二个问题是您对样式的使用。您在两种状态下为两个小部件使用单一样式,但由于样式是全局的,因此当您更改一个小部件中的样式时,它会影响另一个。
与其重新配置单一样式,不如配置两种样式并在它们之间切换。例如,您可以为正常情况创建一个 my.TEntry
样式,并为要显示占位符的情况创建一个 placeholder.TEntry
样式。
首先在 __init__
方法中配置样式:
def __init__(self, master, placeholder, **kwargs):
# style for ttk widget
self.s = ttk.Style()
self.s.configure('my.TEntry', foreground='black', font=(0, 0, 'normal'))
self.s.configure('placeholder.TEntry', foreground='grey', font=(0, 0, 'bold'))
接下来,无需重新配置样式定义,只需交换小部件上的样式即可。例如,_clear
看起来像这样:
def _clear(self, *args): # method to remove the placeholder
if self.get() == self.text and self.__has_placeholder:
self.configure(style="my.TEntry")
...
同样,_add
应该看起来像这样:
def _add(self, *args): # method to add placeholder
if self.get() == '' and not self.__has_placeholder: # if no text add placeholder
self.configure(style="placeholder.TEntry")
...
最后,您的逻辑中有一个错误,可能是由于误解了绑定的工作原理。当您绑定到 <Key>
时,该绑定发生在默认绑定之前。因此,您在 self._add
中检查条目是否为空的检查发生在您刚刚键入的键被插入之前。因此,代码认为条目小部件是空的,并将样式切换为具有占位符。因此,它添加了占位符文本,然后对密钥进行默认处理并插入密钥。
该特定问题至少有三种解决方案。
- 您可以在
<KeyRelease>
而不是<Key>
上进行绑定,因为这将在默认绑定插入字符后触发。 - 您可以在默认绑定之后添加一个自定义绑定标签,这样默认绑定先发生,然后您的绑定发生。
- 你可以坚持
bind_all
。在那种情况下,您只需要对每个小部件进行一次绑定而不是一次绑定,并且您需要调整您的函数以使用event.widget
而不是self
.
This answer and This answer 更详细地描述了绑定标签的工作原理。