如何禁用 Tkinter canvas 对象

How to disable a Tkinter canvas object

在我的项目中,我使用带有背景 gif 作为图像的 Tkinter 按钮。现在我需要添加一个 "icon",作为 base64 字符串给出。 Tkinter Button 不提供添加图标或第二张图片的选项。这就是我使用 canvas 创建自定义按钮的原因。代码如下:

from Tkconstants import DISABLED
from Tkinter import Tk, Canvas
import Tkinter
import base64

import ImageTk


_FONTCOLOR = "#FFFFFF"
_BGCOLOR = "#787878"
_ICONDATA = '''iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZ
 G9iZSBJbWFnZVJlYWR5ccllPAAAAUJJREFUeNrsVc1qhDAQHq20FnRP7kF68iB9D+99Dp/J5+jdR9Bz8VwU
 PChbQbdo00yYLLuSrW4x9NKBjy+RL5nMZ34MxhjoDBM0h/YExoy3DCYnRtxx3BMbV8QTxxdVvaT7JGYWiV2
 OPbE1GywH9RwDh83xqEiCupHjg6Mmnixa+T5JkrelmuM4fimK4nVJF4bhM6cjLkqWu1vp69P8Q9u2MI7j/P
 OO5hV+yn9wEVEUqRI8yAZOmuc5NE0DWZbBMAznupN95o276KQryxI8z4MgCMD3fajrWqn79Tnoug5c1xVtZ
 LRq04Om8H3bBI7jQN/3oo2M/U0ToO9VVQlrkLH/UwJ2y/HHsG0b97vYRcjYV+mss5N6EWmaqhIc5zZdsWaS
 SUzqHFZW8L5Sd5CLNqgKbXeR9ttU/3vw/yb/eYJvAQYA4v5708p9noAAAAAASUVORK5CYII='''


class DesktopBtn(Tkinter.Button):

    def __init__(self, parent, buttonName, connector=None, **options):
        '''
        @param buttonName: Name of the button

        '''
        Tkinter.Button.__init__(self, parent, **options)
        self._imagePath = 'button.gif'
        self._BtnPresspath = 'buttonP.gif'
        self._BtnPressImage = Tkinter.PhotoImage(file=self._BtnPresspath)
        self._image = Tkinter.PhotoImage(file=self._imagePath)
        self.bind('<ButtonPress-1>', self._on_pressed)
        self.bind('<ButtonRelease-1>', self._on_release)
        self._parent = parent
        self._btnName = buttonName
        self._connector = connector
        self.config(width=70,
                    height=65,
                    borderwidth=0,
                    compound=Tkinter.CENTER,
                    font=("Arial", 9, "bold"),
                    foreground=_FONTCOLOR,
                    activebackground=_BGCOLOR,
                    text=buttonName,
                    wraplength=64,
                    image=self._image,
                    command=self._onClickSwitch,
                    state="disabled")

    def _on_pressed(self, event):
        if self.cget("state") != "disabled":
            self.config(relief="flat")
            self.config(image=self._BtnPressImage)

    def _on_release(self, event):
        if self.cget("state") != "disabled":
            self.config(image=self._image)

    def _onClickSwitch(self):
        self.config(relief="flat")
        if self._connector:
            self._connector.switchDesktop(self._btnName,
                                          "test")

    def getButtonName(self):
        return self._btnName

    def setConnector(self, connector):
        self._connector = connector


class CustomButton(Canvas):
    def __init__(self, parent, buttonname=None, icon=None, command=None):
        Canvas.__init__(self, parent, borderwidth=0, highlightthickness=0)
        self.command = command
        self._imagePath = 'button.gif'
        self._BtnPresspath = 'buttonP.gif'
        self._icon = icon
        self._BtnPressImage = Tkinter.PhotoImage(file=self._BtnPresspath)
        self._image = Tkinter.PhotoImage(file=self._imagePath)
        self.bgimage = self.create_image(35, 35, image=self._image)
        self.text = buttonname
        if self._icon:
            self._icondata = base64.b64decode(self._icon)
            self._iconimage = ImageTk.PhotoImage(data=self._icondata)
            self.create_image(35, 35, image=self._iconimage)
        if self.text and self._icon:
            self.create_text(35, 63, anchor="s",
                             state=DISABLED,
                             text=self.text,
                             font=("arial", 9, "bold"),
                             fill=_FONTCOLOR)
        elif not self._icon:
            self.create_text(35, 45, anchor="s",
                             state=DISABLED,
                             text=self.text,
                             font=("arial", 9, "bold"),
                             fill=_FONTCOLOR)
        self.configure(width=70, height=70, state=DISABLED)
#         if self.cget("state") == "disabled":
#             pass
#         else:
        self.bind("<ButtonPress-1>", self._on_press)
        self.bind("<ButtonRelease-1>", self._on_release)

    def _on_press(self, event):
        self.itemconfig(self.bgimage,image=self._BtnPressImage)
        print "pressed"

    def _on_release(self, event):
        self.itemconfig(self.bgimage,image=self._image)
        if self.command is not None:
            self.command()

tk = Tk()
but = DesktopBtn(tk, "test")
but.pack()
butt_blank = CustomButton(tk)
butt_text = CustomButton(tk, buttonname="test")
butt_icon = CustomButton(tk, icon=_ICONDATA)
butt_icon_text = CustomButton(tk, icon=_ICONDATA, buttonname="test")
butt_blank.pack()
butt_text.pack()
butt_icon.pack()
butt_icon_text.pack()
tk.mainloop()

第一个按钮(class)是我以前用的Tkinter Button。现在我只有问题了。我如何像正常 Tkinter Button 一样禁用我的 canvas 自定义按钮。它应该是灰色的并且忽略鼠标事件。 根据 Tkinter 8.5 reference 在 create_image 甚至 canvas 对象上使用 DISABLED 状态,我的自定义 Button 的行为应该像旧 Button 一样。我正在使用 Python 2.7.

以下是使用的按钮图像(button.gif 和 buttonP.gif):

如评论中所述,一种解决方法是制作合成图像。
一个简单的例子是:

#!python3

import tkinter as tk
from PIL import Image, ImageTk

root = tk.Tk()
bgim = Image.open("bg.gif")
bgphoto = ImageTk.PhotoImage(bgim)

button1 = tk.Button(root, image=bgphoto)
button1.pack()

newim = Image.open("bg.gif").convert('RGBA') # ensure both images are in a mode that supports transparency
iconim = Image.open("Icon.png").convert('RGBA')
newim.paste(iconim, (-30,-40), iconim) # paste second image into first image
newphoto = ImageTk.PhotoImage(newim)
button2 = tk.Button(root, image=newphoto)
button2.pack()

root.mainloop()

bg.gif 是来自原始 post 的图像之一,另一个是一个简单的透明 png 图标。

有关合并图像的更多信息,请参阅此 post

这是另一个解决方法,它在其他项目的顶部绘制一个 rectangle,用白色填充它,在我的示例中 stippleBitmap 填充它 gray50self.create_rectangle(-1,-1,70,70, stipple="gray50", fill="white")

代码如下所示:

from Tkconstants import DISABLED, NORMAL
from Tkinter import Tk, Canvas
import Tkinter
import base64

import ImageTk


_FONTCOLOR = "#FFFFFF"
_BGCOLOR = "#787878"
_ICONDATA = '''iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZ
 G9iZSBJbWFnZVJlYWR5ccllPAAAAUJJREFUeNrsVc1qhDAQHq20FnRP7kF68iB9D+99Dp/J5+jdR9Bz8VwU
 PChbQbdo00yYLLuSrW4x9NKBjy+RL5nMZ34MxhjoDBM0h/YExoy3DCYnRtxx3BMbV8QTxxdVvaT7JGYWiV2
 OPbE1GywH9RwDh83xqEiCupHjg6Mmnixa+T5JkrelmuM4fimK4nVJF4bhM6cjLkqWu1vp69P8Q9u2MI7j/P
 OO5hV+yn9wEVEUqRI8yAZOmuc5NE0DWZbBMAznupN95o276KQryxI8z4MgCMD3fajrWqn79Tnoug5c1xVtZ
 LRq04Om8H3bBI7jQN/3oo2M/U0ToO9VVQlrkLH/UwJ2y/HHsG0b97vYRcjYV+mss5N6EWmaqhIc5zZdsWaS
 SUzqHFZW8L5Sd5CLNqgKbXeR9ttU/3vw/yb/eYJvAQYA4v5708p9noAAAAAASUVORK5CYII='''


class DesktopBtn(Tkinter.Button):

    def __init__(self, parent, buttonName, connector=None, **options):
        '''
        @param buttonName: Name of the button

        '''
        Tkinter.Button.__init__(self, parent, **options)
        self._imagePath = 'button.gif'
        self._BtnPresspath = 'buttonP.gif'
        self._BtnPressImage = Tkinter.PhotoImage(file=self._BtnPresspath)
        self._image = Tkinter.PhotoImage(file=self._imagePath)
        self.bind('<ButtonPress-1>', self._on_pressed)
        self.bind('<ButtonRelease-1>', self._on_release)
        self._parent = parent
        self._btnName = buttonName
        self._connector = connector
        self.config(width=70,
                    height=65,
                    borderwidth=0,
                    compound=Tkinter.CENTER,
                    font=("Arial", 9, "bold"),
                    foreground=_FONTCOLOR,
                    activebackground=_BGCOLOR,
                    text=buttonName,
                    wraplength=64,
                    image=self._image,
                    command=self._onClickSwitch
                    )

    def _on_pressed(self, event):
        if self.cget("state") != "disabled":
            self.config(relief="flat")
            self.config(image=self._BtnPressImage)

    def _on_release(self, event):
        if self.cget("state") != "disabled":
            self.config(image=self._image)

    def _onClickSwitch(self):
        self.config(relief="flat")
        if self._connector:
            self._connector.switchDesktop(self._btnName,
                                          "test")

    def getButtonName(self):
        return self._btnName

    def setConnector(self, connector):
        self._connector = connector


class CustomButton(Tkinter.Canvas):
    def __init__(self, parent, buttonname=None, icon=None, command=None, **options):
        Tkinter.Canvas.__init__(self, parent, borderwidth=0, highlightthickness=0,**options)
        self.command = command
        self._imagePath = 'button.gif'
        self._BtnPresspath = 'buttonP.gif'
        self._icon = icon
        self._BtnPressImage = Tkinter.PhotoImage(file=self._BtnPresspath)
        self._image = Tkinter.PhotoImage(file=self._imagePath)
        self.bgimage = self.create_image(35, 35, image=self._image)
        self.text = buttonname
        if self._icon:
            self._icondata = base64.b64decode(self._icon)
            self._iconimage = ImageTk.PhotoImage(data=self._icondata)
            self.create_image(35, 35, image=self._iconimage)
        if self.text and self._icon:
            self.create_text(35, 63, anchor="s",
                             text=self.text,
                             font=("arial", 8, "bold"),
                             fill=_FONTCOLOR)
        elif not self._icon:
            self.create_text(35, 45, anchor="s",
                             text=self.text,
                             font=("arial", 8, "bold"),
                             fill=_FONTCOLOR)
        self.configure(width=70, height=70)
        self._activation()

    def disable(self):
        self.config(state=DISABLED)
        self._activation()

    def enable(self):
        self.config(state=NORMAL)
        self._activation()

    def _activation(self):
        if self.cget("state") == "disabled":
            self.create_rectangle(-1,-1,70,70, stipple="gray50", fill="white")
            self.unbind("<ButtonPress-1>")
            self.unbind("<ButtonRelease-1>")
        else:
            self.bind("<ButtonPress-1>", self._on_press)
            self.bind("<ButtonRelease-1>", self._on_release)

    def _on_press(self, event):
        self.itemconfig(self.bgimage, image=self._BtnPressImage)
        print "pressed"

    def _on_release(self, event):
        self.itemconfig(self.bgimage, image=self._image)
        if self.command is not None:
            self.command()

tk = Tk()
but = DesktopBtn(tk, "test")
but.config(state="disabled")
but.pack()
butt_blank = CustomButton(tk)
butt_text = CustomButton(tk, buttonname="test", state=DISABLED)
butt_icon = CustomButton(tk, icon=_ICONDATA)
butt_icon_text = CustomButton(tk, icon=_ICONDATA, buttonname="test")
butt_blank.pack()
butt_text.pack()
butt_icon.pack()
butt_icon_text.disable()
butt_icon_text.pack()
tk.mainloop()

结果几乎相同,但是您必须实现两种方法来禁用和启用此 "widget"。这就是为什么我仍在等待我的问题的解决方案。