I wrote a class in python tkinter that you can use to create buttons that change their images on leave, enter and button-1 event.
The script:
from tkinter import *
class ImageButton:
def CreateButton(self, image, root):
# --- image setup ---
self.ImageName = image
self.ButtonRoot = root
self.normal = PhotoImage(file="Images/" + self.ImageName + "/normal.png")
self.hover = PhotoImage(file="Images/" + self.ImageName + "/hover.png")
self.clicked = PhotoImage(file="Images/" + self.ImageName + "/clicked.png")
# --- button setup ---
self.Button = Button(self.ButtonRoot, image=self.normal)
self.Button.bind("<Leave>", self.normal_event)
self.Button.bind("<Enter>", self.hover_event)
self.Button.bind("<Button-1>", self.click_event)
return self.Button
def normal_event(self, event):
self.Button.config(image=self.normal)
def hover_event(self, event):
self.Button.config(image=self.hover)
def click_event(self, event):
self.Button.config(image=self.clicked)
Root = Tk()
Image = ImageButton()
FirstButton = Image.CreateButton("scann_device", Root)
FirstButton.grid(row=0, column=0)
SecondButton = Image.CreateButton("get_device_info", Root)
SecondButton.grid(row=1, column=0)
Root.mainloop()
So if you create a button with that class, you can do anything you want. It only binds it to the events I mentioned above. If you create 1 button, it works and you see it on your window, but if you create 2 buttons and grid/pack them, it wont work. It looks like this. The second button also gets the hitbox of the first button.
You only initiate one instance of Image. Therefore, in your second call to CreateButton(), all class objects (self.normal, self.ImageName, self.Button) are overwritten with the new values.
You are probably better off inheriting a class from the tkinter Button and adding your own functions. See this example:
from tkinter import *
class ImageButton(Button):
def __init__(self, image, root, *args, **kwargs):
super().__init__(root, *args, **kwargs)
# --- image setup ---
self.image_name = image
self.normal = PhotoImage(file="Images/" + self.image_name + "/normal.png")
self.hover = PhotoImage(file="Images/" + self.image_name + "/hover.png")
self.clicked = PhotoImage(file="Images/" + self.image_name + "/clicked.png")
# --- button setup ---
self.config(image=self.normal)
self.bind("<Leave>", self.normal_event)
self.bind("<Enter>", self.hover_event)
self.bind("<Button-1>", self.click_event)
def normal_event(self, event):
self.config(image=self.normal)
def hover_event(self, event):
self.config(image=self.hover)
def click_event(self, event):
self.config(image=self.clicked)
root = Tk()
FirstButton = ImageButton("scann_device", root)
FirstButton.grid(row=0, column=0)
SecondButton = ImageButton("get_device_info", root)
SecondButton.grid(row=1, column=0)
root.mainloop()
Related
I'm working with python 3.8 and tkinter to create a GUI that uses a ttk notebook (tabbed window). I want to have tooltips displayed for the individual tabs but I can't see how to add the tooltip for the tab. I have a class defined that takes an object and text and adds the tooltip and it works for buttons and other objects.
I have example code here:
'''
At no time were Katz, Dawgs, Mice or Squirrels harmed in any way during the development of this application. But...
Well just lets say that skunk next door had it coming!
'''
import tkinter as tk
from tkinter import ttk
class CreateToolTip(object):
"""
create a tooltip for a given widget
"""
def __init__(self, widget, text='widget info'):
self.waittime = 250 #miliseconds
self.wraplength = 180 #pixels
self.widget = widget
self.text = text
self.widget.bind("<Enter>", self.enter)
self.widget.bind("<Leave>", self.leave)
self.widget.bind("<ButtonPress>", self.leave)
self.id = None
self.tw = None
def enter(self, event=None):
self.schedule()
def leave(self, event=None):
self.unschedule()
self.hidetip()
def schedule(self):
self.unschedule()
self.id = self.widget.after(self.waittime, self.showtip)
def unschedule(self):
id = self.id
self.id = None
if id:
self.widget.after_cancel(id)
def showtip(self, event=None):
x = y = 0
x, y, cx, cy = self.widget.bbox("insert")
x += self.widget.winfo_rootx() + 25
y += self.widget.winfo_rooty() + 20
# creates a toplevel window
self.tw = tk.Toplevel(self.widget)
# Leaves only the label and removes the app window
self.tw.wm_overrideredirect(True)
self.tw.wm_geometry("+%d+%d" % (x, y))
label = tk.Label(self.tw, text=self.text, justify='left',
background="cornsilk", relief='solid', borderwidth=1,
wraplength = self.wraplength)
label.pack(ipadx=1)
def hidetip(self):
tw = self.tw
self.tw= None
if tw:
tw.destroy()
class MyCanvas(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tabControl = ttk.Notebook(self)
self.geometry('600x300')
self.title('An Indepth study of the Differences Between Katz and Dawgs!')
self.tab = {}
self.tab['dawg tab'] = ttk.Frame(self.tabControl)
self.tabControl.add(self.tab['dawg tab'], text='dawgs', state='normal')
self.tab['katz tab'] = ttk.Frame(self.tabControl)
self.tabControl.add(self.tab['katz tab'], text='katz', state='normal')
self.tabControl.enable_traversal()
btn1 = tk.Button(self.tab['dawg tab'], text='Squirrel')
btn1.grid(row=1, column=0)
CreateToolTip(btn1, 'Despised by dawgs!!')
btn2 = tk.Button(self.tab['katz tab'], text='Mice')
btn2.grid(row=1, sticky='EW')
CreateToolTip(btn2, 'Katz find them a tasty treat.')
self.tabControl.grid(row=0, column=0, sticky="W")
# testing ...
if __name__ == '__main__':
root = MyCanvas()
root.mainloop()
Any advice or constructive comments would be greatly appreciated. And yes, the tooltip class was totally plagiarized from someone else here on stack overflow.
Cheers!!
So after a few days and lots of help from a friend here is what I have that actually works:
'''
At no time were Katz, Dawgs, Mice or Squirrels harmed in any way during the development of this application. But...
Well lets just say that skunk next door had it coming!
'''
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.scrolledtext as tkst
class MyCanvas(ttk.Notebook):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tooltips = dict()
self.tab_list = dict()
self.current_tab = None
self.waittime = 250 #miliseconds
self.wraplength = 180 #pixels
self.id = None
self.tw = None
self.bind('<Motion>', self.motion)
self.bind('<ButtonPress>', self.leave)
self.line_number = 0
def leave(self, event=None):
self.unschedule()
self.hide_tip()
def schedule(self):
self.unschedule()
self.id = self.after(self.waittime, self.show_tip)
def unschedule(self):
id = self.id
self.id = None
if id:
self.after_cancel(id)
def show_tip(self):
if self.tw:
return
if self.current_tab in self.tooltips.keys():
x = y = 0
x, y, cx, cy = self.bbox('insert')
x += self.winfo_rootx() + 25
y += self.winfo_rooty() - 20
# creates a toplevel window
self.tw = tk.Toplevel(self)
# Leaves only the label and removes the app window
self.tw.wm_overrideredirect(True)
self.tw.wm_geometry(f'+{x}+{y}')
self.tw.wm_attributes('-topmost', True)
label = ttk.Label(self.tw, text=self.tooltips[self.current_tab], background='cornsilk')
label.pack()
def add(self, *args, **kwargs):
if 'tooltip' in kwargs.keys():
tooltip = kwargs['tooltip']
del kwargs['tooltip']
else:
tooltip = None
self.tab_list[kwargs.get('text')] = args[0]
super().add(*args, **kwargs)
if tooltip and ('text' in kwargs.keys()):
self.set_tooltip(kwargs['text'], tooltip)
def set_tooltip(self, tab_text, tooltip):
for idx in range(self.index('end')):
if self.tab(idx, 'text') == tab_text:
self.tooltips[tab_text] = tooltip
return True
return False
def motion(self, event):
if self.identify(event.x, event.y) == 'label':
index = self.index(f'#{event.x},{event.y}')
if self.current_tab != self.tab(index, 'text'):
self.current_tab = self.tab(index, 'text')
self.show_tip()
else:
self.hide_tip()
self.current_tab = None
def hide_tip(self):
tw = self.tw
self.tw = None
if tw:
tw.destroy()
def insert_text_to_tab(self, **kwargs):
string_var = tk.StringVar()
if 'tab' in kwargs.keys():
the_tab = kwargs.get('tab')
string_var.set(kwargs.get('data'))
the_label = tk.Label(self.tab_list[the_tab], textvariable=string_var, background='lightblue')
the_label.pack()
def get_scrolled_text_widget(parent):
st_widget = tkst.ScrolledText(parent)
return st_widget
if __name__ == '__main__':
root = tk.Tk()
root.title('An Indepth study of the Differences Between Katz and Dawgs!')
canvas = MyCanvas(root)
canvas.pack(fill='both', expand=1)
canvas.add(get_scrolled_text_widget(canvas), text='Dawgz', tooltip='A quadraped that doesn\'t look like a kat')
canvas.add(get_scrolled_text_widget(canvas), text='Katz', tooltip='A quadraped that doesn\'t look like a dawg')
canvas.insert_text_to_tab(data='Dawgz hate squirrels!!', tab='Dawgz')
canvas.insert_text_to_tab(data='Katz find mice a tasty treat.', tab='Katz')
root.mainloop()
So it would appear that I was thinking about it a bit wrong and I had only to think of each tabbed entry as an object instead of an attribute (I'm sure the python peanut gallery are going to be all over me for saying that) the solution is quit straight forward.
Cheers!!
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I have a map and it is saved as html file.
is there any way i can display it in a application using tkinter?
I saw some answers mentioning tkhtml, but I've found little information about it. If anyone could please give me a light, and a insight of how and where to aim my code...
thank you
Yes, you can both embed HTML and open full webpages (with CSS and javascript even) in tkinter. With the cefpython module you can embed a full-blown Chromium browser in a tk window. Below is a working example of displaying a local HTML file (change the HTML file location at the line commented with #todo)
Update August 2020: this works with Python 2.7 / 3.4 / 3.5 / 3.6 / 3.7
Update March 2021 Python 3.8 and 3.9 are now also supported https://github.com/cztomczak/cefpython/releases/tag/v66.1
# Example of embedding CEF Python browser using Tkinter toolkit.
# This example has two widgets: a navigation bar and a browser.
#
# NOTE: This example often crashes on Mac (Python 2.7, Tk 8.5/8.6)
# during initial app loading with such message:
# "Segmentation fault: 11". Reported as Issue #309.
#
# Tested configurations:
# - Tk 8.5 on Windows/Mac
# - Tk 8.6 on Linux
# - CEF Python v55.3+
#
# Known issue on Linux: When typing url, mouse must be over url
# entry widget otherwise keyboard focus is lost (Issue #255
# and Issue #284).
from cefpython3 import cefpython as cef
import ctypes
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
import sys
import os
import platform
import logging as _logging
# Fix for PyCharm hints warnings
WindowUtils = cef.WindowUtils()
# Platforms
WINDOWS = (platform.system() == "Windows")
LINUX = (platform.system() == "Linux")
MAC = (platform.system() == "Darwin")
# Globals
logger = _logging.getLogger("tkinter_.py")
# Constants
# Tk 8.5 doesn't support png images
IMAGE_EXT = ".png" if tk.TkVersion > 8.5 else ".gif"
class MainFrame(tk.Frame):
def __init__(self, root):
self.browser_frame = None
self.navigation_bar = None
# Root
root.geometry("900x640")
tk.Grid.rowconfigure(root, 0, weight=1)
tk.Grid.columnconfigure(root, 0, weight=1)
# MainFrame
tk.Frame.__init__(self, root)
self.master.title("Tkinter example")
self.master.protocol("WM_DELETE_WINDOW", self.on_close)
self.master.bind("<Configure>", self.on_root_configure)
self.setup_icon()
self.bind("<Configure>", self.on_configure)
self.bind("<FocusIn>", self.on_focus_in)
self.bind("<FocusOut>", self.on_focus_out)
# NavigationBar
self.navigation_bar = NavigationBar(self)
self.navigation_bar.grid(row=0, column=0,
sticky=(tk.N + tk.S + tk.E + tk.W))
tk.Grid.rowconfigure(self, 0, weight=0)
tk.Grid.columnconfigure(self, 0, weight=0)
# BrowserFrame
self.browser_frame = BrowserFrame(self, self.navigation_bar)
self.browser_frame.grid(row=1, column=0,
sticky=(tk.N + tk.S + tk.E + tk.W))
tk.Grid.rowconfigure(self, 1, weight=1)
tk.Grid.columnconfigure(self, 0, weight=1)
# Pack MainFrame
self.pack(fill=tk.BOTH, expand=tk.YES)
def on_root_configure(self, _):
logger.debug("MainFrame.on_root_configure")
if self.browser_frame:
self.browser_frame.on_root_configure()
def on_configure(self, event):
logger.debug("MainFrame.on_configure")
if self.browser_frame:
width = event.width
height = event.height
if self.navigation_bar:
height = height - self.navigation_bar.winfo_height()
self.browser_frame.on_mainframe_configure(width, height)
def on_focus_in(self, _):
logger.debug("MainFrame.on_focus_in")
def on_focus_out(self, _):
logger.debug("MainFrame.on_focus_out")
def on_close(self):
if self.browser_frame:
self.browser_frame.on_root_close()
self.master.destroy()
def get_browser(self):
if self.browser_frame:
return self.browser_frame.browser
return None
def get_browser_frame(self):
if self.browser_frame:
return self.browser_frame
return None
def setup_icon(self):
resources = os.path.join(os.path.dirname(__file__), "resources")
icon_path = os.path.join(resources, "tkinter"+IMAGE_EXT)
if os.path.exists(icon_path):
self.icon = tk.PhotoImage(file=icon_path)
# noinspection PyProtectedMember
self.master.call("wm", "iconphoto", self.master._w, self.icon)
class BrowserFrame(tk.Frame):
def __init__(self, master, navigation_bar=None):
self.navigation_bar = navigation_bar
self.closing = False
self.browser = None
tk.Frame.__init__(self, master)
self.bind("<FocusIn>", self.on_focus_in)
self.bind("<FocusOut>", self.on_focus_out)
self.bind("<Configure>", self.on_configure)
self.focus_set()
def embed_browser(self):
window_info = cef.WindowInfo()
rect = [0, 0, self.winfo_width(), self.winfo_height()]
window_info.SetAsChild(self.get_window_handle(), rect)
self.browser = cef.CreateBrowserSync(window_info,
url="file:///J:\q.htm") #todo
assert self.browser
self.browser.SetClientHandler(LoadHandler(self))
self.browser.SetClientHandler(FocusHandler(self))
self.message_loop_work()
def get_window_handle(self):
if self.winfo_id() > 0:
return self.winfo_id()
elif MAC:
# On Mac window id is an invalid negative value (Issue #308).
# This is kind of a dirty hack to get window handle using
# PyObjC package. If you change structure of windows then you
# need to do modifications here as well.
# noinspection PyUnresolvedReferences
from AppKit import NSApp
# noinspection PyUnresolvedReferences
import objc
# Sometimes there is more than one window, when application
# didn't close cleanly last time Python displays an NSAlert
# window asking whether to Reopen that window.
# noinspection PyUnresolvedReferences
return objc.pyobjc_id(NSApp.windows()[-1].contentView())
else:
raise Exception("Couldn't obtain window handle")
def message_loop_work(self):
cef.MessageLoopWork()
self.after(10, self.message_loop_work)
def on_configure(self, _):
if not self.browser:
self.embed_browser()
def on_root_configure(self):
# Root <Configure> event will be called when top window is moved
if self.browser:
self.browser.NotifyMoveOrResizeStarted()
def on_mainframe_configure(self, width, height):
if self.browser:
if WINDOWS:
ctypes.windll.user32.SetWindowPos(
self.browser.GetWindowHandle(), 0,
0, 0, width, height, 0x0002)
elif LINUX:
self.browser.SetBounds(0, 0, width, height)
self.browser.NotifyMoveOrResizeStarted()
def on_focus_in(self, _):
logger.debug("BrowserFrame.on_focus_in")
if self.browser:
self.browser.SetFocus(True)
def on_focus_out(self, _):
logger.debug("BrowserFrame.on_focus_out")
if self.browser:
self.browser.SetFocus(False)
def on_root_close(self):
if self.browser:
self.browser.CloseBrowser(True)
self.clear_browser_references()
self.destroy()
def clear_browser_references(self):
# Clear browser references that you keep anywhere in your
# code. All references must be cleared for CEF to shutdown cleanly.
self.browser = None
class LoadHandler(object):
def __init__(self, browser_frame):
self.browser_frame = browser_frame
def OnLoadStart(self, browser, **_):
if self.browser_frame.master.navigation_bar:
self.browser_frame.master.navigation_bar.set_url(browser.GetUrl())
class FocusHandler(object):
def __init__(self, browser_frame):
self.browser_frame = browser_frame
def OnTakeFocus(self, next_component, **_):
logger.debug("FocusHandler.OnTakeFocus, next={next}"
.format(next=next_component))
def OnSetFocus(self, source, **_):
logger.debug("FocusHandler.OnSetFocus, source={source}"
.format(source=source))
return False
def OnGotFocus(self, **_):
"""Fix CEF focus issues (#255). Call browser frame's focus_set
to get rid of type cursor in url entry widget."""
logger.debug("FocusHandler.OnGotFocus")
self.browser_frame.focus_set()
class NavigationBar(tk.Frame):
def __init__(self, master):
self.back_state = tk.NONE
self.forward_state = tk.NONE
self.back_image = None
self.forward_image = None
self.reload_image = None
tk.Frame.__init__(self, master)
resources = os.path.join(os.path.dirname(__file__), "resources")
# Back button
back_png = os.path.join(resources, "back"+IMAGE_EXT)
if os.path.exists(back_png):
self.back_image = tk.PhotoImage(file=back_png)
self.back_button = tk.Button(self, image=self.back_image,
command=self.go_back)
self.back_button.grid(row=0, column=0)
# Forward button
forward_png = os.path.join(resources, "forward"+IMAGE_EXT)
if os.path.exists(forward_png):
self.forward_image = tk.PhotoImage(file=forward_png)
self.forward_button = tk.Button(self, image=self.forward_image,
command=self.go_forward)
self.forward_button.grid(row=0, column=1)
# Reload button
reload_png = os.path.join(resources, "reload"+IMAGE_EXT)
if os.path.exists(reload_png):
self.reload_image = tk.PhotoImage(file=reload_png)
self.reload_button = tk.Button(self, image=self.reload_image,
command=self.reload)
self.reload_button.grid(row=0, column=2)
# Url entry
self.url_entry = tk.Entry(self)
self.url_entry.bind("<FocusIn>", self.on_url_focus_in)
self.url_entry.bind("<FocusOut>", self.on_url_focus_out)
self.url_entry.bind("<Return>", self.on_load_url)
self.url_entry.bind("<Button-1>", self.on_button1)
self.url_entry.grid(row=0, column=3,
sticky=(tk.N + tk.S + tk.E + tk.W))
tk.Grid.rowconfigure(self, 0, weight=100)
tk.Grid.columnconfigure(self, 3, weight=100)
# Update state of buttons
self.update_state()
def go_back(self):
if self.master.get_browser():
self.master.get_browser().GoBack()
def go_forward(self):
if self.master.get_browser():
self.master.get_browser().GoForward()
def reload(self):
if self.master.get_browser():
self.master.get_browser().Reload()
def set_url(self, url):
self.url_entry.delete(0, tk.END)
self.url_entry.insert(0, url)
def on_url_focus_in(self, _):
logger.debug("NavigationBar.on_url_focus_in")
def on_url_focus_out(self, _):
logger.debug("NavigationBar.on_url_focus_out")
def on_load_url(self, _):
if self.master.get_browser():
self.master.get_browser().StopLoad()
self.master.get_browser().LoadUrl(self.url_entry.get())
def on_button1(self, _):
"""Fix CEF focus issues (#255). See also FocusHandler.OnGotFocus."""
logger.debug("NavigationBar.on_button1")
self.master.master.focus_force()
def update_state(self):
browser = self.master.get_browser()
if not browser:
if self.back_state != tk.DISABLED:
self.back_button.config(state=tk.DISABLED)
self.back_state = tk.DISABLED
if self.forward_state != tk.DISABLED:
self.forward_button.config(state=tk.DISABLED)
self.forward_state = tk.DISABLED
self.after(100, self.update_state)
return
if browser.CanGoBack():
if self.back_state != tk.NORMAL:
self.back_button.config(state=tk.NORMAL)
self.back_state = tk.NORMAL
else:
if self.back_state != tk.DISABLED:
self.back_button.config(state=tk.DISABLED)
self.back_state = tk.DISABLED
if browser.CanGoForward():
if self.forward_state != tk.NORMAL:
self.forward_button.config(state=tk.NORMAL)
self.forward_state = tk.NORMAL
else:
if self.forward_state != tk.DISABLED:
self.forward_button.config(state=tk.DISABLED)
self.forward_state = tk.DISABLED
self.after(100, self.update_state)
if __name__ == '__main__':
logger.setLevel(_logging.INFO)
stream_handler = _logging.StreamHandler()
formatter = _logging.Formatter("[%(filename)s] %(message)s")
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
logger.info("CEF Python {ver}".format(ver=cef.__version__))
logger.info("Python {ver} {arch}".format(
ver=platform.python_version(), arch=platform.architecture()[0]))
logger.info("Tk {ver}".format(ver=tk.Tcl().eval('info patchlevel')))
assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this"
sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
root = tk.Tk()
app = MainFrame(root)
# Tk must be initialized before CEF otherwise fatal error (Issue #306)
cef.Initialize()
app.mainloop()
cef.Shutdown()
I've managed to render simple html tags using the link provided by #j_4321
just pip3 install tkinterhtml
and, from the package example:
from tkinterhtml import HtmlFrame
import tkinter as tk
root = tk.Tk()
frame = HtmlFrame(root, horizontal_scrollbar="auto")
frame.set_content("<html></html>")
If you have the content save in a file, or manually input. OR:
frame.set_content(urllib.request.urlopen("http://thonny.cs.ut.ee").read().decode())
to request and render it.
Hope it helps :)
I'm creating two rectangles. I want to delete rectangles from the canvas by right-clicking. The code is able to delete only 1 rectangle but not the other one. I used tag_bind("Button1") function but only bottom one is getting deleted.
Tag_bind function should be able to get the id and delete any of the selected rectangles but it is not happening.
#import sys, os, string, time
import tkinter
tk = tkinter
root =tk.Tk()
root.title ("Drag-N-Drop Demo")
# A Python example of drag and drop functionality within a single Tk widget.
# The trick is in the bindings and event handler functions.
# Tom Vrankar twv at ici.net
canvas =tk.Canvas ( width =256, height =256,
relief =tk.RIDGE, background ="white", borderwidth =1)
class CanvasDnD (tk.Frame):
def __init__ (self, master):
self.master =master
self.loc =self.dragged =0
tk.Frame.__init__ (self, master)
id=canvas.create_rectangle(75,75,100,100,tags="DnD")
canvas.tag_bind(id,"<ButtonPress-1>")
id=canvas.create_rectangle(100,100,125,125,tags="DnD")
canvas.tag_bind(id,"<ButtonPress-1>")
canvas.pack (expand =1, fill =tk.BOTH)
canvas.tag_bind ("DnD", "<ButtonPress-1>", self.down)
canvas.tag_bind ("DnD", "<ButtonRelease-1>", self.chkup)
canvas.tag_bind ("DnD", "<Enter>", self.enter)
canvas.tag_bind ("DnD", "<Leave>", self.leave)
self.popup = tk.Menu(root, tearoff=0)
self.popup.add_command(label="delete",command=lambda: self.dele(id))
root.bind("<Button-3>", self.do_popup)
def do_popup(self,event):
# display the popup menu
try:
self.popup.tk_popup(event.x_root, event.y_root, 0)
finally:
# make sure to release the grab (Tk 8.0a1 only)
self.popup.grab_release()
# empirical events between dropee and target, as determined from Tk 8.0
# down.
# leave.
# up, leave, enter.
def down (self, event):
#print ("Click on %s" %event.widget.itemcget (tk.CURRENT, "text"))
self.loc =1
self.dragged =0
event.widget.bind ("<Motion>", self.motion)
def motion (self, event):
root.config (cursor ="exchange")
cnv = event.widget
cnv.itemconfigure (tk.CURRENT, fill ="blue")
x,y = cnv.canvasx(event.x), cnv.canvasy(event.y)
a,b = cnv.canvasx(event.x + 25), cnv.canvasy(event.y+25)
got = event.widget.coords (tk.CURRENT, x, y, a, b)
def leave (self, event):
self.loc =0
def enter (self, event):
self.loc =1
if self.dragged ==event.time:
self.up (event)
def chkup (self, event):
event.widget.unbind ("<Motion>")
root.config (cursor ="")
self.target =event.widget.find_withtag (tk.CURRENT)
#event.widget.itemconfigure (tk.CURRENT, fill =self.defaultcolor)
if self.loc: # is button released in same widget as pressed?
self.up (event)
else:
self.dragged =event.time
def up (self, event):
event.widget.unbind ("<Motion>")
if (self.target ==event.widget.find_withtag (tk.CURRENT)):
print("1")
# print ("Select %s" %event.widget.itemcget (tk.CURRENT, "text"))
else:
event.widget.itemconfigure (tk.CURRENT, fill ="blue")
self.master.update()
time.sleep (.1)
print ("%s Drag-N-Dropped onto %s" \
%(event.widget.itemcget (self.target, "text")),
event.widget.itemcget (tk.CURRENT, "text"))
event.widget.itemconfigure (tk.CURRENT, fill =self.defaultcolor)
def dele(self,id):
canvas.delete(id)
CanvasDnD (root).pack()
root.mainloop()
Question: first I will select that rectangle with "Button 1" and then I will right-click and delete
Create the rectangles ...
canvas.create_rectangle(75, 75, 100, 100, tags="DnD")
canvas.create_rectangle(100, 100, 125, 125, tags="DnD")
Bind event "<ButtonPress-1>" to the Canvas
canvas.bind("<ButtonPress-1>", self.on_button_1)
Prepare the popup, to delete items with tag='DELETE'
self.popup.add_command(label="delete",
command=lambda: canvas.delete(canvas.find_withtag('DELETE')))
Define the event "<ButtonPress-1>" callback.
Here, the matching item get added tags='DELETE' and outlined 'red'.
def on_button_1(self, event):
iid = canvas.find_enclosed(event.x - 26, event.y - 26, event.x + 26, event.y + 26)
canvas.itemconfigure(iid, tags='DELETE', outline='red')
I have been working on my first GUI in tkinter - I am using Windows. My goal right now is to have buttons that accomplish these goals:
The buttons are highlighted when moused over.
The button remains highlighted if clicked.
Only one button can be "selected" (click-highlighted) at a time.
I initially thought that I had accomplished this! But I realize now that my work is not complete.
Here is what I am seeing:
I mouse over button A. It becomes highlighted! (GOOD)
I click on button A. It stays highlighted! (GOOD)
I mouse over button B. It becomes highlighted! (GOOD)
I click on button B. It stays highlighted! The highlight from A is removed! (GOOD)
I mouse over button A. It does not highlight. (BAD)
I am calling the default_coloring class function on button A when I click on button B. However, this appears to turn off the highlighting functions of button A, and the button no longer functions correctly according to the three rules I listed at the top.
How do I ensure that the buttons continue to function normally, even after the command is called? Am I approaching this the wrong way?
import tkinter as tk
blue = '#0000BB'
white = '#FFFFFF'
class HoverButton(tk.Button):
def __init__(self, master, position = None, **kw):
tk.Button.__init__(self,master=master,**kw)
self.defaultBackground = self["background"]
self.defaultForeground = self["foreground"]
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
self.bind("<Button-1>", self.hover_click)
self.state = 0
self.position = position
def on_enter(self, e):
if self.state == 0:
self['background'] = self['activebackground']
self['foreground'] = self['activeforeground']
def on_leave(self, e):
if self.state == 2:
self.state = 0
if self.state == 0:
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
def hover_click(self, e):
self.state += 1
self.state = self.state % 3
if self.state == 2:
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
def default_coloring(self):
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
class AddOnFrame(tk.Frame):
def __init__(self, master):
self.selectedbutton = None
super().__init__(master)
games = ['A','B','C']
self.objs = list()
self['bg'] = blue
for i in range(3):
self.objs.append(HoverButton(self,position = i, text = games[i].upper(), activebackground = white,activeforeground = blue,fg = white, bg = blue, borderwidth=0, relief = 'flat', highlightbackground = white))
self.objs[i]['command'] = lambda c=i: self._hover_button_clicked(self.objs[c])
self.objs[i].grid(row = i, column = 0, sticky = tk.W + tk.E)
self.blanklabel = tk.Label(self, text = '', background = white)
self.blanklabel.grid(row = 0, column = 1,rowspan = 10, sticky = tk.N + tk.E + tk.W + tk.S)
self.grid_columnconfigure(1, weight=1, minsize=10)
self.grid_columnconfigure(2, weight=1, minsize=500)
self.grid_columnconfigure(3, weight=1, minsize=500)
self.grid_columnconfigure(4, weight=1, minsize=500)
self.pack(expand = True)
def _hover_button_clicked(self, HoverButton):
self.lastbutton = self.selectedbutton
if self.lastbutton != None:
self.objs[self.lastbutton].default_coloring()
self.selectedbutton = HoverButton.position
window = tk.Tk()
window.geometry('1750x950')
window['bg'] = blue
window.title('Testing')
lf = AddOnFrame(window)
lf['bg'] = blue
window.mainloop()
I think I found the main source of the problem. When another button is clicked, you restore color of the last clicked button, but you do not reset its state. Change your default_coloring function to:
def default_coloring(self):
self.state = 0
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
But you should also prevent default_coloring if same button is pressed again:
def _hover_button_clicked(self, HoverButton):
self.lastbutton = self.selectedbutton
if (self.lastbutton != None) and (self.lastbutton != HoverButton.position):
self.objs[self.lastbutton].default_coloring()
self.selectedbutton = HoverButton.position
After cursory inspection, this sequence seems to be the problem:
When a button is clicked, the AddOnFrame._hover_button_clicked
method is invoked.
AddOnFrame.selectedbutton is initially None, which means the
if-statement in AddOnFrame._hover_button_clicked will not be
executed the first time. This is why the buttons seem to work the
first time you click them, but not after that.
However, the next time it is invoked (the next time a button is
pressed), AddOnFrame.selectedbutton is not None, and will never
be None again, meaning that from now on, every click will result in
a call to that HoverButton's default_coloring method.
default_coloring is invoked as soon as a button is clicked, which
results in a quick flash from the active color to the default color,
and the button does not stay highlighted.
The quick fix:
Basically, don't do the default_coloring stuff. It seems to be hurting you more than it's helping. Not really sure why you're doing it in the first place (all that stuff with setting the command, the lambda, the whole _hover_button_clicked method) since the buttons seem to be setting their colors back to the default just fine when on_leave or hover_click are invoked. You can fix your problem by changing the body of your HoverButton.default_coloring function to this:
def default_coloring(self):
return
The real fix would be some restructuring of your code.
EDIT I'm offering this to help you simplify things:
import tkinter as tk
colors = {
"white": "#FFFFFF",
"blue": "#0000BB"
}
class HoverButton(tk.Button):
def __init__(self, *args, **kwargs):
tk.Button.__init__(self, *args, **kwargs)
self.is_selected = False
self.is_highlighted = False
self["borderwidth"] = 0
self["relief"] = tk.FLAT
self["font"] = ("United Sans Cd Bk", 30)
self["activeforeground"] = colors["blue"]
self["activebackground"] = colors["white"]
self["highlightbackground"] = colors["white"]
self.recolor()
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
self.bind("<Button-1>", self.on_click)
def recolor(self):
self["background"] = [colors["blue"], colors["white"]][self.is_highlighted]
self["foreground"] = [colors["white"], colors["blue"]][self.is_highlighted]
def on_enter(self, *args):
self.is_highlighted = True
self.recolor()
def on_leave(self, *args):
if self.is_selected:
return
self.is_highlighted = False
self.recolor()
def on_click(self, *args):
self.is_selected = not self.is_selected
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Window")
self.geometry("256x256")
self.resizable(width=False, height=False)
self["background"] = colors["blue"]
button_labels = ["A", "B", "C"]
self.buttons = []
for row, button_label in enumerate(button_labels):
button = HoverButton(text=button_label)
button.grid(row=row, column=0, sticky=tk.W)
self.buttons.append(button)
def main():
application = Application()
application.mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
I've looked into embedding a pygame window inside a tkinter window (Reference: Embed Pygame in Tkinter)
I wanted to use this to embed a snapshot (once that works possibly a livefeed) made by the pygame.camera module
In the comments it is said the code should work with Linux (running on raspbian) when commenting out os.environ['SDL_VIDEODRIVER'] = 'windib'
However I can't get the embedding to work nor making a snapshot with pygame, and I can't figure out what's causing the issue. Here's the code I wrote:
import pygame as pg
import pygame.camera
import tkinter as tk
import os
import threading as th
tk.Frame = tk.LabelFrame
def start():
root = tk.Tk()
run = Viewer(root)
return run
class Viewer(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.screen_width = parent.winfo_screenwidth()
self.screen_height = parent.winfo_screenheight()
self.startup(self.screen_width, self.screen_height)
def startup(self, width, height):
self.parent.protocol('WM_DELETE_WINDOW', self.parent.destroy)
Viewer.embed = tk.Frame(self.parent, width=650, height=490)
Viewer.embed.pack(side = tk.LEFT)
self.buttonFrame = tk.Frame(self.parent, width=100, height=490)
self.buttonFrame.pack(side = tk.RIGHT)
self.refresh = tk.Button(self.buttonFrame,
text='Refresh',
command=self.refresh)
self.refresh.pack()
def refresh(self):
self.c = Capture()
self.c.snap()
class Capture(Viewer):
def __init__(self):
os.environ['SDL_WINDOWID'] = str(Viewer.embed.winfo_id())
self.size = (640,480)
self.display = pg.display.set_mode(self.size)
self.clist = pg.camera.list_cameras()
if not self.clist:
raise ValueError("Sorry, no cameras detected.")
self.cam = pg.camera.Camera(self.clist[0], self.size)
self.cam.start()
self.snapshot = pg.surface.Surface(self.size, 0, self.display)
self.event = th.Thread(target=self.eventCatcher)
self.event.start()
def snap(self):
self.snapshot = self.cam.get_image(self.snapshot)
def eventCatcher(self):
closed = False
while not closed:
events = pg.event.get()
for e in events:
if e.type == pg.QUIT:
self.cam.stop()
closed = True
pg.init()
pg.camera.init()
main = start()
main.mainloop()
You have to use pygame after you set os.environ['SDL_WINDOWID']
os.environ['SDL_WINDOWID'] = str(...)
pygame.init()
EDIT: it works on Linux Mint 18.2
import pygame as pg
import pygame.camera
import tkinter as tk
import os
import threading as th
#tk.Frame = tk.LabelFrame
class Viewer(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent # there is self.master which keeps parent
self.parent.protocol('WM_DELETE_WINDOW', self.parent.destroy)
self.screen_width = parent.winfo_screenwidth()
self.screen_height = parent.winfo_screenheight()
self.embed = tk.Frame(self.parent, width=650, height=490)
self.embed.pack(side='left')
self.buttonFrame = tk.Frame(self.parent, width=100, height=490)
self.buttonFrame.pack(side='right')
self.parent.update() # need it to get embed.winfo_id() in Capture
self.c = Capture(self)
self.refreshButton = tk.Button(self.buttonFrame,
text='Refresh',
command=self.refresh)
self.refreshButton.pack()
def refresh(self):
self.c.snap()
class Capture():
def __init__(self, parent):
os.environ['SDL_WINDOWID'] = str(parent.embed.winfo_id())
pg.display.init()
pg.camera.init()
self.size = (640,480)
self.display = pg.display.set_mode(self.size)
self.display.fill(pg.Color(255,255,255))
pg.display.update()
self.clist = pg.camera.list_cameras()
if not self.clist:
raise ValueError("Sorry, no cameras detected.")
print('cameras:', self.clist)
self.cam = pg.camera.Camera(self.clist[0], self.size)
self.cam.start()
self.snapshot = pg.surface.Surface(self.size, 0, self.display)
self.event = th.Thread(target=self.eventCatcher)
self.event.start()
def snap(self):
print('snap ready:', self.cam.query_image())
self.cam.get_image(self.snapshot)
self.display.blit(self.snapshot, self.snapshot.get_rect())
pg.display.update()
def eventCatcher(self):
closed = False
while not closed:
events = pg.event.get()
for e in events:
if e.type == pg.QUIT:
self.cam.stop()
closed = True
root = tk.Tk()
run = Viewer(root)
root.mainloop()