create tkinter button with OOP on a same window: issue - python

I am a beginner in Python. I created a GUI with nice buttons. To do this I did a change of images: when the mouse hovers the button and when the mouse leaves the button. I did this with this pretty ugly code, but it works:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
root.title("My first Python GUI")
root.geometry("1130x800")
canvas = Canvas(root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
button_1_onHover = Image.open("Buttons/button1_hover.png")
button_1_onLeave = Image.open("Buttons/button1_leave.png")
button_2_onHover = Image.open("Buttons/button2_hover.png")
button_2_onLeave = Image.open("Buttons/button2_leave.png")
root.button_1_onLeave = ImageTk.PhotoImage(button_1_onLeave)
root.button_1_onHover = ImageTk.PhotoImage(button_1_onHover)
root.button_2_onLeave = ImageTk.PhotoImage(button_2_onLeave)
root.button_2_onHover = ImageTk.PhotoImage(button_2_onHover)
def on_enter(event):
button1.config(image=root.button_1_onHover)
def on_leave(leave):
button1.config(image=root.button_1_onLeave)
def on_enter2(event):
button2.config(image=root.button_2_onHover)
def on_leave2(leave):
button2.config(image=root.button_2_onLeave)
button1 = Button(root, image=root.button_1_onLeave, bg="#a9dfbf", width=400, height=150, bd=0, relief="sunken", activebackground="#a9dfbf")
button2 = Button(root, image=root.button_2_onLeave, bg="#a9dfbf", width=400, height=150, bd=0, relief="sunken", activebackground="#a9dfbf")
canvas.create_window(300, 150, window=button1)
canvas.create_window(300, 350, window=button2)
button1.bind("<Enter>", on_enter)
button1.bind("<Leave>", on_leave)
button2.bind("<Enter>", on_enter2)
button2.bind("<Leave>", on_leave2)
root.mainloop()
This is the visual result:
visual result of the ugly code (it work)
BUT...
The problem is that to make a single button, it takes 15 lines of code.
If I want to create 10 buttons, it becomes incredibly repetitive and unpleasant.
Being a beginner, I heard about object-oriented programming, and so I turned my code into a class that I called NewButton:
from tkinter import *
from PIL import Image, ImageTk
class NewButton:
def __init__(self, imageHover, imageLeave, width, height, hposition, vposition):
self.root = Tk()
self.root.title("My first Python GUI")
self.root.geometry("1130x800")
canvas = Canvas(self.root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
self.width = width
self.height = height
self.hposition = hposition
self.vposition = vposition
self.imageHover = Image.open(f"Buttons/{imageHover}.png")
self.imageLeave = Image.open(f"Buttons/{imageLeave}.png")
self.root.imageLeave = ImageTk.PhotoImage(self.imageLeave)
self.root.imageHover = ImageTk.PhotoImage(self.imageHover)
self.button = Button(self.root, image=self.root.imageLeave, bg="#a9dfbf", width=self.width, height=self.height, bd=0, relief="sunken", activebackground="#a9dfbf")
canvas.create_window(self.hposition, self.vposition, window=self.button)
def on_enter(event):
self.button.config(image=self.root.imageHover)
def on_leave(leave):
self.button.config(image=self.root.imageLeave)
self.button.bind("<Enter>", on_enter)
self.button.bind("<Leave>", on_leave)
self.root.mainloop()
NewButton("button1_hover","button1_leave",400,150,300,150)
NewButton("button2_hover","button2_leave",400,150,300,350)
In the constructor of my class, I define the image used on hover, the image used on leave, the width of the button, its height, as well as the position of this button (horizontal position and vertical position).
Still in the constructor, I placed my 2 functions which change the image according to the enter/leave state.
Then I create my buttons as NewButton objects and give the characteristics of the button.
When I run my code, python creates the buttons for me, but in a different window.
This is the visual result:
result with POO code (not working)
What I want is to put all the buttons that I create on the same window, and that's not what I get with my code.
Can you tell me what's wrong?
Thank you (and sorry for my frenglish!)
SOLUTION by acw1668 that i try (it doesn't work):
His suggestion: "You need to create root and canvas outside the class and pass canvas to the class instance instead."
what I have done:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
root.title("My first Python GUI")
root.geometry("1130x800")
canvas = Canvas(root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
class NewButton:
def __init__(self, canvas, imageHover, imageLeave, width, height, hposition, vposition):
self.canvas = canvas
self.width = width
self.height = height
self.hposition = hposition
self.vposition = vposition
self.imageHover = Image.open(f"Buttons/{imageHover}.png")
self.imageLeave = Image.open(f"Buttons/{imageLeave}.png")
self.canvas.imageLeave = ImageTk.PhotoImage(self.imageLeave)
self.canvas.imageHover = ImageTk.PhotoImage(self.imageHover)
self.button = Button(self.canvas, image=self.canvas.imageLeave, bg="#a9dfbf", width=self.width, height=self.height, bd=0, relief="sunken", activebackground="#a9dfbf")
canvas.create_window(self.hposition, self.vposition, window=self.button)
def on_enter(event):
self.button.config(image=self.canvas.imageHover)
def on_leave(leave):
self.button.config(image=self.canvas.imageLeave)
self.button.bind("<Enter>", on_enter)
self.button.bind("<Leave>", on_leave)
self.canvas.mainloop()
NewButton(canvas,"button1_hover","button1_leave",400,150,300,150)
NewButton(canvas,"button2_hover","button2_leave",400,150,300,350)

You need to create root and canvas outside the class and then pass canvas to the class:
from tkinter import *
from PIL import Image, ImageTk
class NewButton:
def __init__(self, canvas, imageHover, imageLeave, width, height, hposition, vposition):
self.canvas = canvas
self.width = width
self.height = height
self.hposition = hposition
self.vposition = vposition
imageHover = Image.open(f"Buttons/{imageHover}.png")
imageLeave = Image.open(f"Buttons/{imageLeave}.png")
self.imageLeave = ImageTk.PhotoImage(imageLeave)
self.imageHover = ImageTk.PhotoImage(imageHover)
self.button = Button(canvas, image=self.imageLeave, bg="#a9dfbf", width=self.width, height=self.height, bd=0, relief="sunken", activebackground="#a9dfbf")
self.button.bind("<Enter>", self.on_enter)
self.button.bind("<Leave>", self.on_leave)
self.item_id = canvas.create_window(self.hposition, self.vposition, window=self.button)
def on_enter(self, event):
self.button.config(image=self.imageHover)
def on_leave(self, event):
self.button.config(image=self.imageLeave)
root = Tk()
root.title("My first Python GUI")
root.geometry("1130x800")
canvas = Canvas(root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
NewButton(canvas,"button1_hover","button1_leave",400,150,300,150)
NewButton(canvas,"button2_hover","button2_leave",400,150,300,350)
root.mainloop()

Related

How to arrange panels in tkinter through classes?

I am relatively new in programming with python and I'm now trying to master the use of classes and inheritance in Tkinter a bit. In the code here I try to arrange two canvas panels above each other and to place data panel beside these two canvas panels. I tried to do that by defining a leftFrame in which the canvas panels are placed and a rightFrame for the data panel. However, it fails to show both canvas panels. I hope somebody can show me the right way.
import tkinter as tk
class Data():
def __init__(self):
self.borderSize = 8
class Frame():
def __init__(self, master):
self.leftFrame = tk.Frame(master)
self.leftFrame.grid(row=0, column=0)
self.rightFrame = tk.Frame(master)
self.rightFrame.grid(row=0, column=1)
class CanvasPanel(Frame):
def __init__(self,master, width, height, row, column, bg=None):
super().__init__(master)
self.borderFrame = tk.Frame(self.leftFrame, border = data.borderSize)
self.borderFrame.grid(row=row, column=column)
self.cWidth = width
self.cHeight = height
self.canvas = tk.Canvas(self.borderFrame, width=self.cWidth, height=self.cHeight,
borderwidth = 0, highlightthickness=0, bg=bg)
self.canvas.pack()
self.canvas.create_rectangle(0,0,width, height)
class DataPanel(Frame):
def __init__(self, master, width, height, row, column, bg = None):
super().__init__(master)
self.borderFrame = tk.Frame(self.rightFrame, border = data.borderSize)
self.borderFrame.grid(row=row, column=column)
self.dataFrame = tk.Frame(self.borderFrame, width = width, height = height,bg=bg)
self.dataFrame.pack()
data = Data()
root = tk.Tk()
root.title("PANELS")
canvas1 = CanvasPanel(root,600,300,0,0,'yellow')
canvas2 = CanvasPanel(root,600,300,1,0,'red')
dataPanel = DataPanel(root,100,600,0,0,'light grey')
root.mainloop()
The red and yellow frames in your code are overlapping with each other.
You dont need to write so many classes for such simple example. Placement of frames or other widgets in root window can be easily done via functions using single class. I have edited your code to illustrate the same -
import tkinter as tk
class Frame():
def __init__(self, master):
self.borderSize = 8
self.leftFrame = tk.Frame(master)
self.leftFrame.grid(row=0, column=0)
self.rightFrame = tk.Frame(master)
self.rightFrame.grid(row=0, column=1)
self.canvas_panel(600, 300, 0, 0, 'yellow')
self.canvas_panel(600, 300, 1, 0, 'red')
self.panel_frame(100, 600, 0, 0, 'light grey')
def canvas_panel(self, width, height, row, column, bg=None):
self.borderFrame = tk.Frame(self.leftFrame, border=self.borderSize)
self.borderFrame.grid(row=row, column=column)
self.cWidth = width
self.cHeight = height
self.canvas = tk.Canvas(self.borderFrame, width=self.cWidth, height=self.cHeight,
borderwidth=0, highlightthickness=0, bg=bg)
self.canvas.pack()
self.canvas.create_rectangle(0, 0, width, height)
def panel_frame(self, width, height, row, column, bg=None):
self.borderFrame = tk.Frame(self.rightFrame, border=self.borderSize)
self.borderFrame.grid(row=row, column=column)
self.dataFrame = tk.Frame(self.borderFrame, width=width, height=height, bg=bg)
self.dataFrame.pack()
root = tk.Tk()
root.title("PANELS")
frame = Frame(root)
root.mainloop()
The root of the problem lies in the fact that Frame is putting leftFrame and rightFrame in the master at fixed coordinates. Each time you create a new panel it overlays previously created panels because they all are placed at the same coordinates.
You don't need Frame. Instead, your panels should inherit from tkFrame, and the code that creates the panels should be responsible for putting them in the left or right frame.
For example, the CanvasPanel should look something like this:
class CanvasPanel(BasePanel):
def __init__(self,master, width, height, bg=None):
super().__init__(master)
self.borderFrame = tk.Frame(self, border = data.borderSize)
self.borderFrame.grid(row=row, column=column)
self.cWidth = width
self.cHeight = height
self.canvas = tk.Canvas(self.borderFrame, width=self.cWidth, height=self.cHeight,
borderwidth = 0, highlightthickness=0, bg=bg)
self.canvas.pack()
self.canvas.create_rectangle(0,0,width, height)
You should make similar changes to DataPanel (ie: placing borderFrame directly in self).
You can now use these classes like you would any other tkinter widget: you first create an instance, and then you add it to the window. You don't need to create a leftFrame or rightFrame, because your code is in control of where the widgets are placed.
root = tk.Tk()
canvas1 = CanvasPanel(root, width=600, height=300, bg='yellow')
canvas2 = CanvasPanel(root, width=600, height=300, bg='red')
dataPanel = DataPanel(root, width=100, height=600, bg='light grey')
canvas1.grid(row=0, column=0, sticky="nsew")
canvas2.grid(row=1, column=0, sticky="nsew")
dataPanel.grid(row=0, column=1, rowspan=2, sticky="nsew")

Python Tkinter Scrollbar just resizes the frame it is in instead of making the canvas scrollable

I´d like to make a scrollbar to scroll over a canvas filled with pics. The Problem i´m facing is, that instead of using the scrollbar the frame just gets bigger. No matter what I do the Frame always resizes with the the canvas filled with pictures. If I force the the maxsize to any value the Frame won't get bigger, but the Scrollbar then isn't scrollable either.
Here is the Code i wrote:
from Tkinter import *
from tkFileDialog import *
from PIL import Image, ImageTk
class Gui(Frame):
def __init__(self, master=None):
Frame.__init__(self,master)
self.pack(expand=False, fill=BOTH)
self.master.title("Picscroll")
self.master.minsize(400,400)
scroll = Picscroll(self)
scroll.pack(fill=Y, side=LEFT)
class Picscroll(Frame):
def __init__(self, master=None):
Frame.__init__(self,master)
canvas = Canvas(self)
canvas.config(scrollregion=canvas.bbox("all"))
yscrollbar = Scrollbar(self, width=16, orient=VERTICAL)
yscrollbar.pack(side=RIGHT, fill=Y)
yscrollbar.config(command=canvas.yview)
canvas.config(yscrollcommand=yscrollbar.set)
canvas.pack(side=LEFT, expand=True, fill = BOTH)
size = 64, 64 #rendering TestPic
File = r"Download.png"
img = Image.open(File)
img.thumbnail(size)
render = ImageTk.PhotoImage(img)
for i in range(20): #filling canvas with testpics
for j in range(2):
label1 = Label(canvas, image=render)
label1.image = render
label1.grid(column=j, row=i)
a = Gui()
a.mainloop()
Please excuse my English skills

Python Tkinter Canvas shall fill Tk() window initially or when resized, but does not

If this is my code:
from tkinter import *
class Program:
def __init__(self):
self.tk = Tk()
self.tk.attributes("-topmost", 1)
self.canvas = Canvas(self.tk, height=500, width=500, highlightthickness=0)
self.canvas.pack(fill="both", expand=1)
self.width = 500
self.height = 500
self.tk.bind("<Configure>", self.resize)
self.input = Entry(self.tk)
self.input.pack(fill="x", expand=1)
self.button = Button(self.tk, text="Start!", command=self.pressed)
self.button.pack(fill="x", expand=1)
self.pressed = 0
self.start = time()
def mainloop(self):
while 1:
self.canvas.delete("all")
self.canvas.create_rectangle(0, 0, self.width, self.height, fill="#4dffff", width=0)
self.tk.update()
def pressed(self):
self.pressed = 1
def resize(self, event):
self.width = event.width
self.height = event.height
Program().mainloop()
If I run it then this window opens:
If I then move the window it looks like this:
As soon as I resize it (by fullscreen for instance) I get this window:
How would I have to change the code to consistentely have the result where the canvas (I mean the colored part of it) covers the uncovered part of the Tk() window?
You have a lot of wrong and/or unnecessary code that is causing all sorts of problems. It boils down to the fact that you shouldn't be creating your own mainloop. Use the one that Tkinter provides unless you deeply understand why that is the case.
Start with this code before trying to do anything else:
from tkinter import *
from time import time
class Program:
def __init__(self):
self.tk = Tk()
self.tk.attributes("-topmost", 1)
self.canvas = Canvas(self.tk, height=500, width=500, highlightthickness=0)
self.input = Entry(self.tk)
self.button = Button(self.tk, text="Start!", command=self.pressed)
self.button.pack(side="bottom", fill="x", expand=0)
self.input.pack(side="bottom", fill="x", expand=0)
self.canvas.pack(fill="both", expand=1)
self.pressed = 0
self.start = time()
def begin(self):
self.tk.mainloop()
def pressed(self):
self.pressed = 1
Program().begin()

Scrollable Frame Python Tkinter

I am working n a project that has a scroll able frame. It lets me add widgets to the frame but I can not get the frame to scroll and show the rest of the widgets. I have compared my code to other scroll able frames online and I could not notice the difference. Any one see the solution.
Code:
from Tkinter import *
import ttk
import os
class GUI(Frame):
def __init__(self, parent):
Frame.__init__(self,parent)
self.pack(fill=BOTH, expand=YES)
def gameView(self):
self.mainFrame = Frame(self)
self.mainFrame.pack(side=TOP)
self.scroller = ttk.Scrollbar(self.mainFrame, orient=VERTICAL)
self.scroller.pack(side=RIGHT, fill=Y)
self.canvas = Canvas(self.mainFrame, bd=0)
self.canvas.pack(fill=BOTH, side=LEFT)
self.viewArea = Frame(self.canvas, bg="Pink")
self.viewArea.pack(side=TOP, fill=BOTH)
self.canvas.config(yscrollcommand=self.scroller.set)
self.scroller.config(command=self.canvas.yview)
self.canvas.create_window((0,0), window=self.viewArea, anchor=NW, width=783, height=650)
self.viewArea.bind("<Configure>", self.scrollCom)
self.itemHolder = Frame(self.viewArea, bg="Pink")
self.itemHolder.pack(side=TOP)
self.gameGather()
def scrollCom(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"), width=783, height=650)
def gameGather(self):
for i in range(0, 50):
label = Label(self.viewArea, text="Pie")
label.pack(side=TOP)
root = Tk()
root.title("School Vortex 2.0")
root.geometry("800x650")
root.resizable(0,0)
gui = GUI(root)
gui.gameView()
root.mainloop()
When you put the window on the canvas you are explicitly giving it a height and a width. Because of that, the actual width and height of the frame is completely ignored. Because the frame is almost exactly the height of the canvas, there's nothing to scroll.
If you remove the width and height options from the call to create_window your frame will be scrollable.

creating digital time in canvas tkinter

I am newbie in Python using tkinter and I have a problem that I cant solve.Digital time
I want to put digital time in the the upper right corner of my application (Please see the picture). I tried to search on net on how to create a digital time but it is on global root and frame configuration and I cant find a digital clock made for canvas. I also want to put my buttons in middle using grid, but I have no luck finding a solution. Can any one please help me? Ill paste my code here.
from tkinter import *
from tkinter import ttk
from datetime import date
import time
import sys
class main_menu(object):
def __init__(self, root):
self.root = root
self.root.title('System')
self.root.geometry('780x488')
self.background = PhotoImage(file='images/bg.png')
self.canvas = Canvas (root)
self.canvas.grid(sticky=N+S+W+E)
self.canvas.create_image(0,0, image=self.background, anchor="nw")
self.scan_photo = PhotoImage (file='images/scan.png')
self.logs_photo = PhotoImage (file='images/logs.png')
self.settings_photo = PhotoImage (file='images/settings.png')
self.scan_btn = Button (self.canvas, image=self.scan_photo, borderwidth=0, command=self.StartScan)
self.scan_win = self.canvas.create_window(225, 100, anchor="nw", window=self.scan_btn)
self.logs_btn = Button (self.canvas, image=self.logs_photo, borderwidth=0, command=self.Logs)
self.logs_win = self.canvas.create_window(225, 200, anchor="nw", window=self.logs_btn)
self.settings_btn = Button (self.canvas, image=self.settings_photo, borderwidth=0, command=self.Settings)
self.settings_win = self.canvas.create_window(225, 300, anchor="nw", window=self.settings_btn)
self.today = date.today()
self.format = self.today.strftime("%b. %d, %Y")
self.canvas.create_text(730, 30, text=self.format, font=("Helvetica", 10))
self.InstructionsLabel = Label(root, text="""
tadahhhhhh""", fg="black", font=("Calibri", 14))
self.Return_photo = PhotoImage (file='images/back_24x24.png')
self.ReturnMenu_btn = Button (self.canvas, image=self.Return_photo, background='white',activebackground='white', borderwidth=0, command=self.MainMenu)
self.ReturnMenu_win = self.canvas.create_window(0, 0, anchor="nw", window=self.ReturnMenu_btn)
###self.ReturnMenu = Button(root, image=self.back_photo, command=self.MainMenu, )
self.MainMenu()
def MainMenu(self):
self.RemoveAll()
self.ReturnMenu_btn.grid_remove()
self.scan_btn.grid(padx=215)
self.logs_btn.grid(padx=215)
self.settings_btn.grid(padx=215)
def StartScan(self):
self.RemoveAll()
def Logs(self):
self.RemoveAll()
self.ReturnMenu.grid()
def Settings(self):
self.RemoveAll()
self.ReturnMenu.grid()
def RemoveAll(self):
self.scan_btn.grid_remove()
self.logs_btn.grid_remove()
self.settings_btn.grid_remove()
self.InstructionsLabel.grid_remove()
self.ReturnMenu_btn.grid_remove()
if __name__ == '__main__':
root = Tk()
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
main_menu = main_menu(root)
root.mainloop()
To place the time in the upper right corner you need to know the width of the canvas. So use canvas.winfo_width() to get the width of the canvas and subtract some number to place it at the desired position.
If you want the time to stay at the top-right even if the window is resized then bind Configure to a function and move the text using .coords or .moveto.
Sample code(this code will make sure that the time is always at the upper right corner).
from tkinter import font
class MainMenu:
def __init__(self, root):
...
self.time = self.canvas.create_text(0, 0, text=self.format, font=("Helvetica", 10))
self.canvas.bind('<Configure>', self.adjustTimePosition)
...
def adjustTimePosition(self, event):
family, size = self.canvas.itemcget(self.time, 'font').split() # get the font-family and font size
text = self.canvas.itemcget(self.time, 'text')
txt_font = font.Font(family=family, size=size)
width, height = txt_font.measure(text), txt_font.metrics("ascent") # measures the width and height of the text
self.canvas.coords(self.time, self.canvas.winfo_width()-width, height) # moves the text

Categories

Resources