Tkinter visual with Fill=X - python

the problem is realy simple, all funcions .packs(fill=X) for some reason don't work and in other hand if I use .pack(fill=X,pady=10) only pady works and fill=X is ignored. I hope that someone can explain to me why this is happing.
from tkinter import *
import json
import time
#opens file where pinger saves information about websites
def openfile():
d={}
with open('test.json', 'r') as f:
for line in f:
line_ = json.loads(line)
name = list(line_.keys())[0]
status = list(line_[name].keys())[0]
ip = line_[name][status]
d[name] = {'name':name, 'status':status, 'ip':ip}
f.close()
return d
#GUI main class
class GUI(Tk):
def __init__(self):
self.tk = Tk()
self.tk.configure(background='white')
self.tk.wm_state('zoomed')
self.label = {}
self.topFrame = Frame(self.tk, bg="white")
self.tk.after(5000, self.task)
self.title = Frame(self.tk, bg="white")
self.title.pack (side=TOP)
#self.topFrame.pack(side=TOP, expand=True)
self.topFrame.pack(side=TOP)
self.titlelbl= Label(self.title, text="Website Status: Green - Online, Red - Offline \n",bg="white", font=("Helvetica", 24))
self.titlelbl.pack(fill=X,pady=10)
#Funcion which creates and updates frame
def task(self):
i = 0
list={}
list = openfile()
if self.label != {}:#deleting previous information that we could refresh
for ele in self.label:
self.label[ele].destroy()
self.label= {}
for elem in list:#creating visual status if server is online text is in color green and opsite red
if list[elem]["status"]=="true":
lb = Label(self.topFrame, text=list[elem]["name"], fg="green",bg="white", font=("Helvetica", 24))
if list[elem]["status"]=="false":
lb = Label(self.topFrame, text=list[elem]["name"], fg="red",bg="yellow", font=("Helvetica", 24))
lb.pack(fill=X,pady=10)
self.label[elem] = lb
self.tk.after(5000, self.task)#Program refresh main window works every 5s
#forever loop
Gui= GUI()
Gui.mainloop()

You never expand / fill the frame widget, so the frame is only as large as necessary for it to hold all of it's children.
The padding options will increase the size (padding) of the children, and you'll be able to see the difference. Whereas with fill there's nothing for the child widget to fill it's already taking up as much space as it can in it's master (self.topFrame)

Related

I want to bind a function to a label in tkinter, been boggling me

I am still learning so bare with me
Basic explanation of the program:
Using Python3, and the tkinter module, I am making a little GUI just to learn the basics.
In the GUI there is a button, the idea is that when you click the button, a random verse of text (from a txt file) is displayed in another widget within the gui (a label in this case).
I can get the program to pull random verses from the text file, and print them in the shell, but not in the gui widget.
The code requires 2 files, the .py and the .txt
I uploaded them to a github repo so it may be easier to get an idea of the program there
Link to the project on github
However I will put the .py code here if it is more convenient that way but it won't run without the txt file.
import random
import tkinter as tk
from tkinter import *
class MainApplication(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
def random_line():
line_num = 0
selected_line = ''
with open('dhammapada.txt') as f:
while 1:
line = f.readline()
if not line: break
line_num += 1
if random.uniform(0, line_num) < 1:
selected_line = line
return(selected_line.strip())
def print_a_verse():
print('\n',random_line())
btn_result = Button(self, fg='Gold', text='New Verse', bg='Black', font='freesansbold, 16', command=print_a_verse) #textvariable=cvt_to, font='freesansbold, 16', fg='Blue')
btn_result.pack(fill=X,side=BOTTOM)#fill=BOTH, expand=1)
lbl_one = Label(self, bg='DarkGrey', fg='White', text='Dhammapada', font='freesansbold, 22')
lbl_one.pack(fill=BOTH, expand=1)
lbl_thr = Label(self, bg='DarkGrey', fg='White', text='The Dhammapada \nSiddartha Gautama - Buddha', font='freesansbold, 18')
lbl_thr.pack(fill=BOTH, expand=1)
lbl_two = Label(self, bg='DarkGrey', fg='Grey')
lbl_two.pack(fill=BOTH, expand=1)
if __name__ == "__main__":
root = tk.Tk()
root.minsize(400,400)
#root.configure(bg='Black')
root.title('Python - Dhammapada Verses')
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
I started that repo a few years back when I first got into coding, I was self-teaching online then because of pretty severe epilepsy I had to pretty much abandon sitting in front of a screen for hours so stopped it all. I think it was 2016. This is the first program I have looked at since, and the subject matter of the book has helped me a lot over the years.
I hope somebody can point me in the right direction. I may be making a mess of the whole thing already!
EDIT:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
Try this:
import tkinter as tk
from tkinter import *
class MainApplication(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
btn_result = Button(self, fg='Gold', text='New Verse', bg='Black', font='freesansbold, 16', command=self.print_a_verse) #textvariable=cvt_to, font='freesansbold, 16', fg='Blue')
btn_result.pack(fill=X,side=BOTTOM)#fill=BOTH, expand=1)
lbl_one = Label(self, bg='DarkGrey', fg='White', text='Dhammapada', font='freesansbold, 22')
lbl_one.pack(fill=BOTH, expand=1)
lbl_thr = Label(self, bg='DarkGrey', fg='White', text='The Dhammapada \nSiddartha Gautama - Buddha', font='freesansbold, 18')
lbl_thr.pack(fill=BOTH, expand=1)
lbl_two = Label(self, bg='DarkGrey', fg='Grey')
lbl_two.pack(fill=BOTH, expand=1)
def random_line(self):
return "testing text" # Just for testing
line_num = 0
selected_line = ''
with open('dhammapada.txt') as f:
while 1:
line = f.readline()
if not line: break
line_num += 1
if random.uniform(0, line_num) < 1:
selected_line = line
return(selected_line.strip())
def print_a_verse(self):
print(self.random_line())
if __name__ == "__main__":
root = tk.Tk()
root.minsize(400, 400)
#root.configure(bg='Black')
root.title('Python - Dhammapada Verses')
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
I moved all of the widget definitions inside your __init__ method and added self where appropriate to make sure it doesn't raise any errors. I would also suggest that you look at a quick object oriented programming tutorial.
I would start by only reading from the text file once, and storing all verses in a list. The way your code is written now, any time you call random_line, you're opening the file and closing it, which seems unnecessary.
Your method of selecting a random line isn't perfect, and could also be simplified - you read through the file, line by line, and occassionally decide to reassign selected_line depending on whether some random number is below an arbitrary threshold. There are several issues with this - there is no mechanism that prevents selected_line from being overwritten multiple times (which is unnecessary). There is also no guarantee that selected_line will ever be overwritten from its initial empty state. Additionally, the random number is generated via random.uniform(0, line_num) - since line_num increases with each iteration, the likelyhood that the random number will be below your threshold diminishes with each iteration. As a result, lines that are closer to the top of the file are more likely to be chosen.
The solution is to simply use random.choice to randomly select a verse from a list of all possible verses:
import tkinter as tk
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Random Verse")
self.geometry("256x256")
self.resizable(width=False, height=False)
self.button = tk.Button(self, text="Click", command=self.on_click)
self.button.pack()
self.label = tk.Label(self, text="", wraplength=200)
self.label.pack()
self.verses = self.get_verses("dhammapada.txt")
def get_verses(self, file_name):
with open(file_name, "r") as file:
verses = file.read().splitlines()
return verses
def on_click(self):
from random import choice
self.label["text"] = choice(self.verses)
def main():
Application().mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())

Why is my tkinter layout changing?

I made this tkinter application, and I have a bug where the layout changes and the button at the bottom disappears.
I am unable to reproduce the bug 100%. It happens at random times.
For this reason, this SSCCE may contain more than it needs. It's still a lot less than my original app.
from random import random
from threading import Thread, Lock
from time import sleep
from tkinter import Tk, LEFT, RIGHT, BOTH, X, Y, Listbox, Scrollbar, VERTICAL, END
from tkinter.ttk import Frame, Button, Style, Entry
class ListManager:
def __init__(self, ui):
self.entry_list = []
self.ui = ui # to notify of list update
self.timer = ListManager.interval + 1
self.stop = False
interval = 1 # between updates
#staticmethod
def api_request() -> dict:
new_list = ["line"]
while random() > .4:
new_list.append("line")
return {"data": new_list}
def get_entries(self):
r = self.api_request()
self.entry_list = []
for line in r["data"]:
self.entry_list.append(line)
def run(self):
self.timer = ListManager.interval + 1
while not self.stop:
if self.timer > ListManager.interval:
self.get_entries()
if self.ui is None:
print("entries:", len(self.entry_list))
for entry in self.entry_list:
print(entry)
else:
self.ui.receive(self.entry_list)
self.timer = 0
else:
self.timer += 1
sleep(1)
class UI(Frame):
def __init__(self):
super().__init__()
self.style = Style()
self.listbox = None
self.name_entry = None
self.entries = []
self.mutex = Lock()
self.init_pack()
def init_pack(self):
# TODO: investigate open button disappearing (probably need to repack in receive function)
self.master.title("list")
self.style.theme_use("default")
add_streamer_frame = Frame(self)
add_button = Button(add_streamer_frame, text="Add Line")
add_button.pack(side=RIGHT, padx=5, pady=5)
self.name_entry = Entry(add_streamer_frame)
self.name_entry.pack(fill=X, padx=5, expand=True)
add_streamer_frame.pack(fill=X)
list_frame = Frame(self)
self.listbox = Listbox(list_frame, height=20)
self.listbox.configure(font=("Courier New", 12, "bold"))
scrollbar = Scrollbar(self.listbox, orient=VERTICAL)
self.listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=self.listbox.yview)
scrollbar.pack(side=RIGHT, fill=Y)
self.listbox.pack(fill=BOTH, padx=5, expand=True)
list_frame.pack(fill=BOTH, expand=True)
self.pack(fill=BOTH, expand=True)
open_button = Button(self, text="Open")
open_button.pack(side=LEFT, padx=5, pady=5)
def receive(self, entries):
self.mutex.acquire()
self.listbox.delete(0, END)
self.entries = []
for entry in entries:
self.listbox.insert(END, entry)
self.entries.append(entry.split(" ")[0])
self.mutex.release()
def main():
root = Tk()
root.geometry("800x400+300+300")
app = UI()
tl = ListManager(app)
thread = Thread(target=tl.run)
thread.start()
root.mainloop()
tl.stop = True
thread.join()
if __name__ == "__main__":
main()
Here's what it normally looks like most of the time, and should look like:
Here's what it looks like after the bug:
Add:
self.ui.update_idletasks()
after:
sleep(1)
or:
Replace:
self.listbox = Listbox(list_frame, height=20)
with:
self.listbox = Listbox(list_frame)
# or self.listbox = Listbox(list_frame, height=17)
It must be that when too much is going on there's no time spent on updating the GUI. I couldn't reproduce the 2nd screenshot directly without lowering sleep argument as low as 0.001.
The root of your problem seems to be that each time something's added into the listbox its height(winfo_height which is in pixels) is recalculated and resized down from 20 character height, to 17, for which there isn't always time for before a new insertion.
Basically, your widget does not necessarily has a fixed 20 character height at all times, it resizes based on its content and other widgets, unless propagation is disabled by calling self.listbox.pack_propagate(False).
Add:
print(self.ui.listbox.winfo_height())
after:
sleep(1)
to see its height changing for yourself.

Python Tkinter Listbox file implementation

I am currently trying to insert data from an external file which I have defined with the function "browse" as shown below, and these data to implement inside the Listboxes which I have divided in 3 parts, the data in the external file is simply some dates and numbers which I will show below.
Could anyone help me figure out how do I implement data inside a Listbox as shown in the pictures below. P.S datesel() function is date selection to pick up dates manually after implementing the file inside of the listbox in which I am struggling to solve. Lastly Date Listboxes must be only integer as show in the picture 2.Data file link
Current results in 1st picture
Results that I need 2nd picture
Data file picture #3 that I implement through Button 1
from Tkinter import *
import tkFont , time
import ttk,csv,sys,os
import Tkinter,tkFileDialog
class Application(Frame):
def __init__(self,root):
Frame.__init__(self,root)
self.root=root
self.function1()
self.function2()
def function1(self): #tell the machine we are having new widget
self.frame1 = Frame(self.root)
self.frame3 = Frame(self.root, bg="lightblue", borderwidth=2, relief=GROOVE)
self.label1 = Label(self.frame1, text="Upload Acitivity File:")
self.first_button = Button(self.frame1, text="Button 1",command=self.browse)
#Start date selector
self.Sdate = Label(self.frame3, text="Start Date(dd/mm/yy): ")
self.Slb = Listbox(self.frame3, width = 6,height = 8,selectmode='multiple',exportselection=0)
self.Slb2 = Listbox(self.frame3, width=6, height=8,selectmode='multiple',exportselection=0)
self.Slb3 = Listbox(self.frame3, width=6, height=8,selectmode='multiple',exportselection=0)
def function2(self): # # tell the machine where our widget will be
self.frame1.pack()
self.frame3.pack()
self.label1.pack(padx=5, pady=10, side=LEFT)
self.first_button.pack(side=LEFT)
#start and end date
self.Sdate.grid(row =0,column=0)
self.Slb.grid(row =0,column=1)
self.Slb2.grid(row=0, column=2)
self.Slb3.grid(row=0, column=3)
def browse(self):
file = tkFileDialog.askopenfile(mode='rb', title='Choose a file')
self.data = csv.reader(file)
self.datalist = []
for row in self.data:
if len(row) != 0:
self.datalist = self.datalist + [row]
self.datalist.sort()
print self.datalist
def datesel(self):
# get selected line index
self.index = self.Slb.curselection()[0]
# get the line's text
self.seltext = self.Slb.get(self.index)
print self.seltext
self.index2 = self.Slb2.curselection()[0]
self.seltext2 = self.Slb2.get(self.index2)
print self.seltext2
self.index3 = self.Slb3.curselection()[0]
self.seltext3 = self.Slb3.get(self.index3)
print self.seltext3
self.Slist = [self.seltext,self.seltext2,self.seltext3]
self.Slist.sort(0,END)
print self.Slist
def main():
parent=Tk() # main frame
parent.geometry('600x600+300+30') #The height and width of the program
parent.title('Motion Tracker')#assign the title of the program
app = Application(parent)
parent.mainloop()#keeps the program running for infinite amount of time
main()
I don't understand why you want the day, month, and year in separate Listboxes, but anyway, here's some code that shows how to fill 3 Listboxes in the way your 2nd picture shows. I've reduced the code to the bare minimum, and I've hard-coded the filename into the class, but that's easy to fix.
import Tkinter as tk
class Application(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.pack()
self.root = root
self.do_listboxes()
def do_listboxes(self):
tk.Button(self, text="Get dates", command=self.fill_dates).pack()
self.Slb1 = tk.Listbox(self, width=6, height=8, selectmode='multiple', exportselection=0)
self.Slb2 = tk.Listbox(self, width=6, height=8, selectmode='multiple', exportselection=0)
self.Slb3 = tk.Listbox(self, width=6, height=8, selectmode='multiple', exportselection=0)
self.Slb1.pack(side=tk.LEFT)
self.Slb2.pack(side=tk.LEFT)
self.Slb3.pack(side=tk.LEFT)
def fill_dates(self):
fname = 'dkebz.txt'
# Use sets to get rid of duplicated days, months, and years
days, mons, yrs = set(), set(), set()
with open(fname, 'r') as f:
#Skip header
next(f)
for row in f:
# Extract the date from the row
date = row.split(',')[1]
# Extract the day, month, and year from the date
day, mon, yr = date.split('/')
days.add(day)
mons.add(mon)
yrs.add(yr)
# Populate the list boxes
for day in sorted(days):
self.Slb1.insert(tk.END, day)
for mon in sorted(mons):
self.Slb2.insert(tk.END, mon)
for yr in sorted(yrs):
self.Slb3.insert(tk.END, yr)
def main():
parent = tk.Tk()
parent.title('Motion Tracker')
app = Application(parent)
parent.mainloop()
main()
I've used import Tkinter as tk rather than from Tkinter import *. The "star" import dumps 175 Tkinter names into your namespace (in Python 3, 136 names), which is messy and can cause name collisions, especially if you do other "star" imports. Using import Tkinter as tk means that you have to prefix each Tkinter name with tk., so it involves a little more typing, but it makes the code easier to read because it's clear where each name comes from.
For that, you just need to modify the browse method. I have hard-coded the data to reflect what your file image showed. So if the data is exactly what you provided, this works. If it works for you, uncomment the 2 lines I commented, and delete the self.data list definition I wrote.
def browse(self):
file = tkFileDialog.askopenfile(title='Choose a file')
try:
with open(file, 'r') as reader:
self.data = list(reader)
except:
raise
self.datalist = []
for row in self.data:
if len(row) != 0 and not row.startswith('Activity'):
self.datalist = self.datalist + [row]
activity, date, steps = row.split(',')
month, day, year = date.split('/')
self.Slb.insert(END, month)
self.Slb2.insert(END, day)
self.Slb3.insert(END, year)

bring window to top level

I want to present several questions, one after another. The first question is shown as I like, with the cursor set in the entry field. Then I destroy the window and call the function again to create a new window. This time the window is not shown in the front and therefore I first have to click on the screen in order to have the cursor set to the entry field. Also the escape key does not work until I click on the screen to bring the window to the top. I'd be very happy for your help!
Thank you in advance!
Here's my code:
from Tkinter import *
def text_input_restricted(fn,question, nr_letters, limit, len_min, len_max,keys, justify):
class MyApp():
def validate(root, S):
return all(c in keys for c in S)
def __init__(self, q= None):
#save response after "next"-button has been clicked
def okClicked():
lines = e.get()
if len_min < len(lines) < len_max:
lines = unicode(lines).encode('utf-8')
datFile = open(fn, "a")
datFile.write(" '%s'"%(lines))
datFile.close()
self.root.destroy()
self.root = Tk()
vcmd = (self.root.register(self.validate), '%S')
#quit if escape-key has been pressed
self.root.bind('<Escape>', lambda q: quit())
#colors
color = '#%02x%02x%02x' % (200, 200, 200)
self.root.configure(bg=color)
#set window size to screen size
RWidth=MAXX
RHeight=MAXY
self.root.geometry(("%dx%d")%(RWidth,RHeight))
#remove buttons (cross, minimize, maximize)
self.root.overrideredirect(1)
#remove title
self.root.title("")
#item
labelWidget = Label(self.root,text=question, font=("Arial", int(0.02*MAXX)), bd=5, bg=color, justify="center")
labelWidget.place(x=0, y=RHeight/40,width=RWidth)
#"next"-button
ok_width = RWidth/15
ok_height = RWidth/15
okWidget = Button(self.root, text= "next", command = okClicked, font=("Arial",int(0.015*MAXX)), bd=5, justify="center")
okWidget.place(x=RWidth/2-ok_width/2,y=13*RHeight/40, width=ok_width,height=ok_height)
def callback(sv):
c = sv.get()[0:limit]
sv.set(c)
sv = StringVar()
width=nr_letters * int(0.02*MAXX)*1.3
sv.trace("w", lambda name, index, mode, sv=sv: callback(sv))
e = Entry(self.root, textvariable=sv,font=("Arial", int(0.02*MAXX)),justify=justify,validate="key", validatecommand=vcmd)
e.place(x=RWidth/2-width/2, y=9*RHeight/40, width=width)
#show cursor
e.focus_set()
self.root.mainloop()
MyApp()
MAXX=1366
MAXY=768
fn = "D:/test.dat"
text_input_restricted(fn = fn, question=u"f for female, m for male", nr_letters=1, limit =1, len_min =0, len_max=2, keys = 'fm', justify="center")
text_input_restricted(fn = fn, question="How old are you?", nr_letters=2,limit=2, len_min = 1, len_max = 3, keys = '1234567890',justify="center")
In Tk you use the raise command to bring a window to the front of the Z-order. However, raise is a keyword in Python so this has been renamed to lift. Provided your application is still the foreground application you can call the lift() method on a toplevel widget. If the application is not the foreground application then this will raise the window but only above other windows from the same application. On Windows this causes the taskbar icon for your application to start flashing.
You might do better to destroy the contents of the toplevel and replace them. Or even better - create a number of frames holding each 'page' of your application and toggle the visibility of each frame by packing and pack_forgetting (or grid and grid forget). This will avoid loosing the focus completely - you can just set the focus onto the first widget of each frame as you make it visible.

Need help placing buttons on a PhotoImage .gif

Here is my program as of yet:
from tkinter import *
from collections import deque
class App():
def __init__(self, *images):
self.root = Tk()
self.root.title("Skin")
self.image_dict = {image: PhotoImage(file=image) for image in images}
self.image_queue = deque(images)
b = Button(self.root, text="Click here to see the diagram!", command=self.change_image)
b.pack(fill=X)
self.label = Label(self.root, image=self.image_dict["1.gif"])
self.label.image = self.image_dict["1.gif"]
self.label.pack()
def change_image(self):
self.image_queue.rotate(-1)
next_image = self.image_queue[0]
self.label.configure(image=self.image_dict[next_image])
self.label.image = self.image_dict[next_image]
if __name__ == "__main__":
app = App('1.gif', '2.gif')
app.root.mainloop()
What this does is when you run the scipt, a window comes up diplaying "1.gif", and a button. When you click the button, "1.gif" changes to "2.gif". "1.gif" is a blank diagram, "2.gif" is a diagram with labels showing what each part of the diagram is.
Now for the next stage of my program, I need some way to add multiple invisible buttons, or something like it, over each word on the diagram on "2.gif", and when you click on it, I need a seperate window to come up with text on it. Is there any way to implement that into my current program? I have no idea where to start. Thank you!
I think you will be better off using a Canvas to hold your image(s) rather
than a Label. You can then place 'hotspots' over the diagram and bind
events to them. eg. something like:
from tkinter import *
class App():
def __init__(self):
self.root = Tk()
self.messages = {}
self.canvas = Canvas(self.root, bd=0, highlightthickness=0)
self.image = PhotoImage(file='2.gif')
self.canvas.create_image(0,0, image=self.image, anchor='nw')
w,h = self.image.width(), self.image.height()
self.canvas.config(width=w, height=h)
self.canvas.pack()
self.balloon = b = Toplevel(self.root)
b.withdraw()
b.wm_overrideredirect(1)
self.msg = Label(b, bg='black', fg='yellow', bd=2, text='')
self.msg.pack()
self.set_up_hotspots()
def set_up_hotspots(self):
box1 = self.canvas.create_polygon(50,100,100,100,100,150,50,150,
outline='blue', fill='')
#note: outline can be '' also ;)
self.messages[box1] = "The machine head consists of a "\
"Flynn mechanism,\na Demmel crank, "\
"and a heavy-duty frobulator. "
box2 = self.canvas.create_polygon(150,100,200,100,200,150,150,150,
outline='red', fill='')
self.messages[box2] = "And now for something completely different"
for item in [box1, box2]:
for event, handler in [('<Enter>', self.on_hotspot_enter),
('<Leave>', self.on_hotspot_leave)]:
self.canvas.tag_bind(item, event, handler)
def on_hotspot_enter(self, event):
if not self.balloon.winfo_ismapped():
txt = self.messages[event.widget.find_withtag('current')[0]]
self.msg.config(text=txt)
x,y = event.x_root, event.y_root
self.balloon.geometry('+%d+%d' %(x+16,y))
self.balloon.deiconify()
def on_hotspot_leave(self, event):
if self.balloon.winfo_ismapped():
self.balloon.withdraw()
if __name__ == "__main__":
app = App()
app.root.mainloop()

Categories