1

in my project i was using Tkinter buttons with a background gif as image. Now i have a requirement to add an "icon", given as a base64 string. The Tkinter Button doesn't provide a option to add a icon, or a second image. That's why i have created a custom Button using canvas. Code below:

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()

The first button(class) is the Tkinter Button i used to use. Now i have only problem. How do i disable my canvas custom Button like the the normal Tkinter Button. It should be grayed and ignore mouse events. According to Tkinter 8.5 reference using the DISABLED state on create_image or even the canvas object, my custom Button should behave like the old Button. I'm using Python 2.7.

Here are the Button images used (button.gif and buttonP.gif): button.gif buttonP.gif

VRage
  • 1,458
  • 1
  • 15
  • 27
  • rather than reinvent the wheel, would you not be better off using the python image library to merge the images into one? that way you could just use the standard (reliable) widget? – James Kent Jul 30 '15 at 09:35
  • @JamesKent well this is one possible workaround. But still i want to know why the disabled state doesn't work. Maybe later i want to add some fency effects to the Button, thus i will need canvas anyway. – VRage Jul 30 '15 at 09:42

2 Answers2

1

as mentioned in the comments one workaround would be to make a composite image.
a simple example of this is:

#!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()

the bg.gif is one of the images from the origional post, the other was a simple png icon with transparency.

for more info on merging images see this post

Community
  • 1
  • 1
James Kent
  • 5,763
  • 26
  • 50
  • Please explain how making the image a composite with alpha affects the button's behavior. – martineau Jul 30 '15 at 10:01
  • it doesn't, it would allow him to subclass the button (like he is already doing) to allow using a background image and an icon together, the transparency seemed a logical thing to include, as for neatness of the resulting image that would work best. – James Kent Jul 30 '15 at 10:29
0

Here is another workaround, it draws a rectangle on top of the other items, fill it with white and stipple it with a Bitmap in my example gray50: self.create_rectangle(-1,-1,70,70, stipple="gray50", fill="white")

This is how the code looks like:

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()

The result is almost the same, but you have to implement two methods for disabling and enabling this "widget". That's why i'm still waiting for a solution for my Question.

VRage
  • 1,458
  • 1
  • 15
  • 27