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
Related
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()
My objective is to solve the problem of the grid exceeding the window(shown as figure.1)
enter image description here
My program function is creating a grid that number of columns defined by user.
I tried using canvas to solve this problem, but it still doesn't work successfully.
It doesn't show the full grid in the canvas.(shown as figure.2)
enter image description here
Below is my code, could you please help solve the problems or give me some advice.
Thanks a lot.
Code:
import tkinter as tk
import tkinter.messagebox
import tkinter.filedialog
MainWindow = tk.Tk()
MainWindow.title('Helloworld')
MainWindow.geometry('1000x800')
def btn_generate():
global EntryNamelist
global Entrycoordinatelist
global EntryLabellist
con_num = en_condition_num.get()
if con_num != '':
#### Grid Body
for i in range(1,int(con_num) +1 ):
lb_name = tk.Label(fm_grid, text="Condition" + str(i) )
lb_name.grid(row=i, column=0, padx=2, pady=1, ipadx=20, ipady=5)
En_name = tk.Entry(fm_grid, bd = 2,width = 10,font=('Ubuntu', 10))
En_name.grid(row=i, column=1, padx=2, pady=1, ipadx=35, ipady=5)
En_coor = tk.Entry(fm_grid, bd = 2,width = 10,font=('Ubuntu', 10))
En_coor.grid(row=i, column=2, padx=2, pady=1, ipadx=200, ipady=5)
else:
tk.messagebox.showerror("Error", "Please input a num of conditions")
fm_main = tk.Frame()
fm3 = tk.Frame(fm_main)
lb_condition = tk.Label(fm3,text = 'Please input the number of condition')
lb_condition.pack(side="left")
en_condition_num = tk.Entry(fm3, bd = 2,width = 5)
en_condition_num.pack()
fm3.pack()
btn_generate = tk.Button(fm_main,text="Generate Grid",command=btn_generate)
btn_generate.pack()
lb_en = tk.Label(fm_main,text = '')
lb_en.pack()
def myfunction(event):
canvas.configure(scrollregion=canvas.bbox("all"),width=200,height=200)
canvas=tk.Canvas(fm_main)
fm_grid = tk.Frame(canvas)
fm_grid.pack()
myscrollbar=tk.Scrollbar(fm_main,orient="vertical",command=canvas.yview)
canvas.configure(yscrollcommand=myscrollbar.set)
myscrollbar.pack(side="right",fill="y")
canvas.pack(side="left")
canvas.create_window((4,4),window=fm_grid,anchor='nw')
fm_grid.bind("<Configure>",myfunction)
fm_main.pack()
MainWindow.mainloop()
Question: It doesn't show the full grid in the canvas
You have to sync, the width of the Canvas with the width of the Frame inside.
Note: fm_grid = tk.Frame(canvas, bg='blue') is shown in 'blue'.
Dont's:
Remove fm_grid.pack(), you layout with: canvas.create_window(....
Also, i recommend not to use a offset (4, 4), because you have to calculate with this offset on every canvas.configure(..., width=width + 4. Use (0, 0) instead.
# fm_grid.pack()
...
canvas.create_window((4,4),window=fm_grid,anchor='nw')
Useless, to create dynamically window:
Your usage of canvas.bbox is useless, because it's the dimension you want to layout this widget.
Using a fixed width=200, smaller than fm_grid.width will allways cut the fm_grid content, it's not dynamically either.
canvas.configure(scrollregion=canvas.bbox("all"),width=200,height=200)
How to sync the width of the Canvas with the width of the Frame inside?
You bound fm_grid.bind("<Configure>", therefore the event.widget is fm_grid, the Frame inside.
Get the dimensions of the event.widget from there w.winfo_... and build a bbox tuple to set scrollregion.
Use width to set canvas.width, to be in sync with the event.widget.winfo_width().
class ScrollCanvas(tk.Canvas):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
def create_window(self, child):
super().create_window((0, 0), window=child, anchor='nw')
child.bind("<Configure>", self.on_configure)
def on_configure(self, event):
w = event.widget
bbox = x, y, width, height = 0, 0, w.winfo_width(), w.winfo_height()
self.configure(scrollregion=bbox, width=width)
Tested with Python: 3.5 - 'TclVersion': 8.6 'TkVersion': 8.6
I am using the following code, I am having 2 issues,
1. When I try to maximize the window, the Entry widget is not aligned/maximized along with main window and see a gap between scrolled text and Entry widget.
2. Second I am trying to set my cursor in Entry widget when I try to open or whenever my app is active but it's not working for some reason. Any Idea what am I making mistakes?
import tkinter as tk
from tkinter import scrolledtext
class Main:
def __init__(self, master):
self.master = master
master.title("Main")
width = master.winfo_screenwidth()
height = master.winfo_screenheight()
master.minsize(width=1066, height=766)
master.maxsize(width=width, height=height)
self.frame = tk.Frame(self.master)
text_area = scrolledtext.ScrolledText(self.master,width=75,height=35)
text_area.pack(side="top",fill='both',expand=True)
text_entry = tk.Entry(self.master,width=65)
text_entry.pack(side="top",fill=X, expand=True,ipady=3, ipadx=3)
text_entry.configure(foreground="blue",font=('Arial', 10, 'bold', 'italic'))
text_entry.focus()
self.frame.pack()
def initial(self):
print ("initializing")
def main():
root = tk.Tk()
app = Main(root)
root.mainloop()
if __name__ == '__main__':
main()
I can address the issue of your entry field not expanding properly.
That is because you have fill=X and this is not a valid input for fill. Instead use fill="x". I believe your 2nd issue with the entry field having a large gap is because you have set expand = True instead change that to expand = False.
That said I prefer to use the grid() geometry manager instead. Take a look at my below example of how to do this with grid and weights.
When using the grid() manager you can tell each widget exactly where you want it along a grid. The use of weights is for telling a row or column how much if any it should expand with the window. This combined with sticky="nsew" will help us control stuff expands within the window.
import tkinter as tk
from tkinter import scrolledtext
class Main(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Main")
width = self.winfo_screenwidth()
height = self.winfo_screenheight()
self.minsize(width=1066, height=766)
self.maxsize(width=width, height=height)
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=0)
self.columnconfigure(0, weight=1)
text_area = scrolledtext.ScrolledText(self,width=75,height=35)
text_area.grid(row=0, column=0, ipady=3, ipadx=3, sticky="nsew")
text_entry = tk.Entry(self,width=65)
text_entry.grid(row=1, column=0, ipady=3, ipadx=3, sticky="ew")
text_entry.configure(foreground="blue",font=('Arial', 10, 'bold', 'italic'))
text_entry.focus()
def initial(self):
print ("initializing")
if __name__ == '__main__':
root = Main()
root.mainloop()
Update:
To clarify on your issue with fill and expand I have updated your code with the correction so you can see it working.
import tkinter as tk
from tkinter import scrolledtext
class Main:
def __init__(self, master):
self.master = master
master.title("Main")
width = master.winfo_screenwidth()
height = master.winfo_screenheight()
master.minsize(width=1066, height=766)
master.maxsize(width=width, height=height)
self.frame = tk.Frame(self.master)
text_area = scrolledtext.ScrolledText(self.master,width=75,height=35)
text_area.pack(side="top",fill='both',expand=True)
text_entry = tk.Entry(self.master,width=65)
text_entry.pack(side="top",fill="x", expand=False, ipady=3, ipadx=3)
text_entry.configure(foreground="blue",font=('Arial', 10, 'bold', 'italic'))
text_entry.focus()
self.frame.pack()
def initial(self):
print ("initializing")
def main():
root = tk.Tk()
app = Main(root)
root.mainloop()
if __name__ == '__main__':
main()
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.
I created a Frame and then a Canvas.
What I want to do next is to add a Button on the Canvas.
However, when I packed the Button I cannot see the Canvas!
Here is what I tried:
from Tkinter import Tk, Canvas, Frame, Button
from Tkinter import BOTH, W, NW, SUNKEN, TOP, X, FLAT, LEFT
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Layout Test")
self.config(bg = '#F0F0F0')
self.pack(fill = BOTH, expand = 1)
#create canvas
canvas1 = Canvas(self, relief = FLAT, background = "#D2D2D2",
width = 180, height = 500)
canvas1.pack(side = TOP, anchor = NW, padx = 10, pady = 10)
#add quit button
button1 = Button(canvas1, text = "Quit", command = self.quit,
anchor = W)
button1.configure(width = 10, activebackground = "#33B5E5",
relief = FLAT)
button1.pack(side = TOP)
def main():
root = Tk()
root.geometry('800x600+10+50')
app = Example(root)
app.mainloop()
if __name__ == '__main__':
main()
The Tkinter pack manager tries to resize the parent widget to the correct size to contain its child widgets, and no larger, by default. So the canvas is there - but it's precisely the same size as the button, and thus invisible.
If you want to place a widget on a canvas without causing the canvas to dynamically resize, you want the Canvas.create_window() function:
# ... snip ...
button1 = Button(self, text = "Quit", command = self.quit, anchor = W)
button1.configure(width = 10, activebackground = "#33B5E5", relief = FLAT)
button1_window = canvas1.create_window(10, 10, anchor=NW, window=button1)
This will create your button with upper-left corner at (10, 10) relative to the canvas, without resizing the canvas itself.
Note that you could replace the window argument with a reference to any other Tkinter widget. One caveat, though: the named widget must be a child of the top-level window that contains the canvas, or a child of some widget located in the same top-level window.
you can use button1.place(x=0,y=0) geometry manager instead of pack(side =TOP)
pack resize the master widget to makes it large enough to hold the child widget
http://effbot.org/tkinterbook/pack.htm#Tkinter.Pack.pack_propagate-method
http://effbot.org/tkinterbook/place.htm
I had the exact same problem. There isn't an official way that I know, but here's a way around it:
from Tkinter import *
root = Tk()
def clicked(event):
print("pressed")
canvas1 = Canvas(root, relief = FLAT, background = "#D2D2D2")
canvas1.pack()
buttonBG = canvas1.create_rectangle(0, 0, 100, 30, fill="grey40", outline="grey60")
buttonTXT = canvas1.create_text(50, 15, text="click")
canvas1.tag_bind(buttonBG, "<Button-1>", clicked) ## when the square is clicked runs function "clicked".
canvas1.tag_bind(buttonTXT, "<Button-1>", clicked) ## same, but for the text.
root.mainloop()