ttkwidgets Timeline widget start and end bug - python

I'm trying to make a timeline widget using ttkwidgets but want the timeline to represent years. When I changed the start and finish to be years (ex. 2000 - 2010) I noticed that the markers I made did not show up on the timeline. Does anyone know how to fix this bug?
Here's the code I have:
import tkinter as tk
from ttkwidgets import TimeLine # have to install
window = tk.Tk()
timeline = TimeLine(
window,
categories={'Q' + str(key): {"text": "Category Q{}".format(key)} for key in range(0, 5)},
height=100,
start = 2000.0,
finish = 2010.0,
extend=True
)
menu = tk.Menu(window, tearoff=False)
menu.add_command(label="Some Action", command=lambda: print("Command Executed"))
timeline.tag_configure("Q1", right_callback=lambda *args: print(args), menu=menu, foreground="green",
active_background="yellow", hover_border=2, move_callback=lambda *args: print(args))
timeline.create_marker("Q1", 2001.0, 2002.0, background="white", text="Change Color", tags=("Q1",), iid="1")
timeline.create_marker("Q2", 2002.0, 2003.0, background="green", text="Change Category", foreground="white", iid="2",
change_category=True)
timeline.draw_timeline()
timeline.grid()
window.mainloop()
I believe the issue is calculating the pixel positions in the create_marker() function (lines 498-499)
https://github.com/TkinterEP/ttkwidgets/blob/master/ttkwidgets/timeline.py
I tried changing those lines to
x1 = (start - self._start) / self._resolution * self._zoom_factor
x2 = (self._finish - (self._finish - finish) - self._start) / self._resolution * self._zoom_factor
But now I seem to have introduced another bug since the code is not running consistently (I get a Python is not responding message)

Related

How to update tkinter texbox in Python during program runtime

I am writing a Python GUI program using tkinter to read data from an instrument based on a list of setpoints and to show the results of each reading in a tkinter scrolled textbox as the program is running, once the user clicks the "Start" button.
However, my program only shows the results at the end of all loops, rather than update the textbox after each loop. In actual usage, the program might run for many tens of minutes due to the large number of test points.
I tried using the .after method, to allow the GUI to update, but the program does not update the textbox during each loop.
How can I modify my code so that the tkinter textbox in the GUI is updated during each loop?
Here is my simplified code using random results as a sample instrument reading:
import tkinter as tk
import tkinter.scrolledtext as tkscrolled
from tkinter import ttk
def start_measurement():
import random
test_points = [tp for tp in range(1,5)]
for setpoint in test_points:
data_reading = random.normalvariate(setpoint,0.05/2)
data_results.insert(tk.INSERT,"Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
data_results.after(1000)
data_results.insert(tk.INSERT,"\nDone\n")
root = tk.Tk()
# Create main frame for entire program: mainframe
mainframe = ttk.Frame(root, padding="10 10 10 10", style='bgstyle.TFrame')
mainframe.grid(row=0, column=0, sticky=(tk.N, tk.W, tk.E, tk.S))
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
# Create Data Output Text Box with Vertical Scrollbar
data_label = ttk.Label(mainframe, text="Results")
data_label.grid(row=1, column=0, padx=(30,30), pady=(20,0), sticky=(tk.S,tk.W,tk.E))
data_results = tkscrolled.ScrolledText(mainframe, width=100, height=20)
data_results.grid(row=2, column=0, padx=(30,30), pady=(0,20), sticky=(tk.N,tk.W,tk.E))
start_button = ttk.Button(mainframe, width=15, text="Start", command=lambda:start_measurement())
start_button.grid(row=0, column=0, padx=(30,30), pady=(20,0), sticky=(tk.N, tk.W))
root.mainloop()
data_results.after(1000) behaves like time.sleep(1). So the updates are not shown until tkinter mainloop() takes back the control after the function exits.
You should use .after() like below:
def start_measurement(setpoint=1):
import random
if setpoint < 5:
start_button.config(state=tk.DISABLED) # prevent multiple executions
data_reading = random.normalvariate(setpoint, 0.05/2)
data_results.insert(tk.INSERT, "Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
# execute this function one second later with updated setpoint
data_results.after(1000, start_measurement, setpoint+1)
else:
data_results.insert(tk.INSERT, "\nDone\n")
start_button.config(state=tk.NORMAL) # enable for next execution
You can use root.after like this -
data_results.after(1000,lambda:data_results.insert(tk.INSERT,"\nDone\n"))
So, after approximately 1 second it inserts Done. And you should keep this out of the for loop -
def start_measurement():
test_points = [tp for tp in range(1,5)]
for setpoint in test_points:
data_reading = random.normalvariate(setpoint,0.05/2)
data_results.insert(tk.INSERT,"Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
data_results.after(1000,lambda:data_results.insert(tk.INSERT,"\nDone\n"))
Change your code as follows.
def start_measurement():
import random
test_points = [tp for tp in range(1,5)]
for setpoint in test_points:
data_reading = random.normalvariate(setpoint,0.05/2)
data_results.insert(tk.INSERT,"Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
data_results.update()
data_results.after(1000)
data_results.insert(tk.INSERT,"\nDone\n")
Add data_results.update() to your code.This updates your textbox every second.After all iteration You get 'Done' displayed.

Why isn't the label animation working till the last value of the loop?

I am new to python and I have been learning tkinter recently. So I thought with myself that using the grid_forget() function I can remove a widget and redefine it. I thought of this animation that changes the padding of a label so it would create space (kind of like moving the label but not exactly). However, the animation does not work at all. The program freezes until the label reaches the last value of the padding. How can I fix this? Or is there a better way to animate a label moving in the screen?
Here is my code:
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='------')
lbl.grid(row=0, column=0)
def animation():
padding = 0
while padding < 31:
lbl.grid_forget()
padding += 1
lbl.grid(row=0, column=0, padx=padding)
time.sleep(0.2)
# alternative: root.after(200, lambda: lbl.grid(row=0, column=0, padx=padding))
btn = Button(root, text='Animate', command=animation)
btn.grid(row=1, column=1)
root.mainloop()
You need to update the screen for changes to be shown.
Here is a working version using the .update() method:
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='------')
lbl.grid(row=0, column=0)
def animation():
padding = 0
while padding < 31:
lbl.grid_forget()
padding += 1
lbl.grid(row=0, column=0, padx=padding)
root.update()
time.sleep(0.2)
# alternative: root.after(200, lambda: lbl.grid(row=0, column=0, padx=padding))
btn = Button(root, text='Animate', command=animation)
btn.grid(row=1, column=1)
root.mainloop()
Here is a way I also use to animate stuff on the screen, I am not able to understand what you were trying to achieve with your code snippet above, I tried making some changes to it but I feel this way is much better and let's you get more control of your window.
This uses the widely used Canvas widget in the tkinter library.
The Canvas is a general purpose widget, You can use it for a lot of things. Visit the hyper link for more clarity
Here is a short example of how you would create text on the screen.
from tkinter import *
root = Tk()
root.title("My animation")
c = Canvas(root)
x = 20
y = 20 #Instead of using row and column, you simply use x and y co-ordinates
#We will use these co-ordinates to show where the text is in the starting
my_text = c.create_text(x,y,text = '-----')
c.pack()
# This is all you need to create this text on your screen!
root.mainloop()
The idea is that you put your canvas up on your window , and then place whatever you want on it.
There are a lot more attributes that you can add to make your text look even better. Here is an in-depth tutorial on it.
Now that we have made your text widget, It is now time to move it around. Let us move it to 90,20 From our initial position which is 20,20
Here is how we will do it. If we simply move to text object to 90,90, We won't see any animations, it will just directly have it there. So what we will do is first create it at 21,20. Then 22,20. And so on...
We do this really fast till we reach 90,20
This looks like we are moving the text
from tkinter import *
import time
root = Tk()
root.title("My animation")
c = Canvas(root)
x = 20
y = 20 #Instead of using row and column, you simply use x and y co-ordinates
#We will use these co-ordinates to show where the text is in the starting
my_text = c.create_text(x,y,text = 'weee')
c.pack()
def animation():
y = 0.1
x = 0
for _ in range(1000):
c.move(my_text,x,y)
root.update()
anlabel = Button(root,text = 'Animate!',command = animation).pack()
root.mainloop()
This is not only applicable to text, but everything (like other images)that is there on the canvas. The canvas also has Events which will let you use mouse-clicks and other keys on the computer too.
I have made some changes from the previous code, But it is executable and you can try it for yourself to see how it works. increasing the value in time.sleep() makes the animation slower, the lesser the value, the faster.
Are you sure you aren't trying to do something more like the below example? Animating the padding on one of your widgets is going to screw up the rest of your display.
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='')
lbl.grid(row=0, column=0)
def animation(step=12):
step = 12 if step < 0 else step
lbl['text'] = ' ------ '[step:step+6]
root.after(200, lambda: animation(step-1))
Button(root, text='Animate', command=animation).grid(row=1, column=0, sticky='w')
root.mainloop()

How to make a questionnaire scrollable in Python tkinter?

I am new to Python and have been working on a school project. The idea is to take the Alcohol Use Disorders Identification Test (AUDIT) and let the user know whether or not he should seek professional help (if he scores 14+).
I got stuck today. I'm using tkinter, and a set of 10 questions with radioboxes. However, I can't proceed since the questions do not fit on the screen and for the love of God I can't make it scrollable. I've tried everything I could find, setting a class, trying to work with a frame or a canvas, etc.
The questions look like this:
"""imports"""
import tkinter as tk
app = tk.Tk()
app.title("AUDIT")
from tkinter import *
from tkinter import ttk
canvas = tk.Canvas(app)
scroll_y = tk.Scrollbar(app, orient="vertical", command=canvas.yview)
frame = tk.Frame(canvas)
# group of widgets
"""question 1"""
o1 = Text(app, height = 1, width =100)
o1.insert(INSERT, "Jak často pijete alkoholické nápoje (včetně piva)")
o1.insert(END, "?")
o1.pack()
OTAZKA1 = [
("Nikdy", "0"),
("Jednou za měsíc či méně často", "1"),
("2-4x za měsíc", "2"),
("2-3x týdně", "3"),
("4x nebo vícekrát týdně", "4"),
]
o1 = StringVar()
o1.set("0") #initialize
for text, mode in OTAZKA1:
a = Radiobutton(app, text = text, variable = o1, value = mode)
a.pack(anchor=W)
"""question 2"""
o2 = Text(app, height = 2, width =100)
o2.insert(INSERT, "Kolik standardních sklenic alkoholického nápoje vypijete během typického dne, kdy pijete")
o2.insert(END, "?")
o2.pack()
OTAZKA2 = [
("Nejvýše 1", "0"),
("1,5 až 2", "1"),
("2,5 až 3", "2"),
("3,5 až 5", "3"),
("5 a více", "4"),
]
o2 = StringVar()
o2.set("0") #initialize
for text, mode in OTAZKA2:
b = Radiobutton(app, text = text, variable = o2, value = mode)
b.pack(anchor=W)
All the way up to question 10. I know this may not the the most efficient way but it's the first code I've ever written.
How can I add the scroll bar to make all of the questions visible?
Thank you, have a great day.
You need to:
1) use Canvas.create_window(x, y, window=widget) method
2) set the canvas scrollregion attribute to Canvas.bbox("all") (but before you must update the window, otherwise you'll get the wrong result"
3) attach the scrollbar to the canvas the right way (see the code below)
I can also give you some advice:
1) It's better to use OOP for the GUI. For the small apps, it may not be the problem, but if your app is more complex and big, you'll be much easier to work with the OOP code, than with the procedural one.
2) Did you notice your code has a lot of similar parts? Remember: in almost all cases, there is no need to create a lot of similar variables manually (it's also called the DRY principle (don't repeat yourself). You may generate everything automatically using loops. Sometimes you need to use the enumerate function from Python built-ins. If you won't use the DRY principle, you will spend much more time for the development.
3) It's better to avoid overriding already imported things by importing another module. In your code, you do:
from tkinter import *
from tkinter.ttk import *
Both of these modules contain Button, Entry, Scrollbar, Radiobutton etc. So you may get everything mixed up. It's better to do like this:
from tkinter import *
from tkinter.ttk import only, what, you, need
or even better:
import tkinter as tk
import tkinter.ttk as ttk # or: from tkinter import ttk
4) You may adjust the size of everything in Canvas manually, and deny resizing of the window (either by width and/or height).
Here is the rewritten code:
from tkinter import *
from tkinter import messagebox
from tkinter.ttk import Button, Scrollbar, Radiobutton
# Create questions as a list of dictionaries
# The GUI will be generated automatically
questions = [
{"question": "Jak často pijete alkoholické nápoje (včetně piva)?",
"answers": ("Nikdy", "Jednou za měsíc či méně často",
"2-4x za měsíc", "2-3x týdně",
"4x nebo vícekrát týdně")},
{"question": "Kolik standardních sklenic alkoholického nápoje vypijete během typického dne, kdy pijete?",
"answers": ("Nejvýše 1", "1,5 až 2", "2,5 až 3", "3,5 až 5", "5 a více")},
]
class App(Tk):
def __init__(self):
super().__init__()
self.title("AUDIT") # set the window title
self.resizable(False, True) # make window unresizable by width
canv_frame = Frame(self) # create the canvas frame
# create the Canvas widget
# highlightthickness=0 removes the black border when the canvas gets focus
self.canv = Canvas(canv_frame, highlightthickness=0, width=420)
# add scrolling when mouse wheel is rotated
self.canv.bind_all("<MouseWheel>",
lambda event: self.canv.yview_scroll(-1 * (event.delta // 120), "units"))
self.canv.pack(fill=BOTH, expand=YES, side=LEFT) # pack the Canvas
# Create a scrollbar
# command=self.canv.yview tells the scrollbar to change the canvas yview
# and canvas's yscrollcommand=self.yscrollbar.set tells the canvas to update
# the scrollbar if canvas's yview is changed without it.
self.yscrollbar = Scrollbar(canv_frame, command=self.canv.yview)
self.canv["yscrollcommand"] = self.yscrollbar.set
self.yscrollbar.pack(fill=Y, side=LEFT) # pack the Scrollbar
for question_id, question in enumerate(questions, 1):
qaframe = Frame(self.canv) # create the question-answers (QA) frame
text = Text(qaframe, width=50, height=3) # create the Text widget for question
text.insert(END, question["question"]) # insert the question text there
text.pack(fill=X) # pack the text widget
aframe = Frame(qaframe) # create the answers frame
# Create the question variable and add it to the variables list
question_var = IntVar(self)
question["variable"] = question_var
# create the radiobuttons
for answer_id, answer in enumerate(question["answers"]):
Radiobutton(aframe, variable=question_var, text=answer, value=answer_id).pack()
aframe.pack(fill=Y) # pack the answers frame
self.canv.create_window(210, question_id * 175, window=qaframe) # insert the QA frame into the Canvas
canv_frame.pack(fill=BOTH, expand=YES) # pack the canvas frame
Button(self, text="Submit", command=self.submit).pack(fill=X) # create the "Submit" button
self.update() # update everything to get the right scrollregion.
self.canv.configure(scrollregion=self.canv.bbox("all")) # set the canvas scrollregion
def submit(self):
sum_ = 0 # initially, the sum_ equals 0
for question in questions:
sum_ += question["variable"].get() # and then, we add all the questions answers
messagebox.showinfo("Result", "Your result is %s!" % sum_) # show the result in an info messagebox
if __name__ == "__main__": # if the App is not imported from another module,
App().mainloop() # create it and start the mainloop

how to make Tkinter windows appear when opened rather than start minimized?

How can I open Tkinter windows (such as entry, text...) and make them appear on the screen when they are opened rather than start minimized ?
I don't really know how to start... I have some windows but they are opened minimized. I searched on the internet, but found nothing that may be relevant. how can I do it ?
using python on windows (both Python 3 and Python 2)
thanks for the help in advance !
EDIT: the problem now as I mentioned in a comment here is that I have to force the window to be showed. But when I do so, the window does not start centered even if I use a function to center it that worked before.
code:
def center(toplevel):
toplevel.update_idletasks()
w = toplevel.winfo_screenwidth()
h = toplevel.winfo_screenheight()
size = tuple(int(_) for _ in toplevel.geometry().split('+')[0].split('x'))
x = w/2 - size[0]/2
y = h/2 - size[1]/2
toplevel.geometry("%dx%d+%d+%d" % (size + (x, y)))
def paste_func():
global text_box
text_box.insert(END, top.clipboard_get())
button_pressed()
def button_pressed(x=0):
# This function determines which button was pressed, and closes this menu/message box/etc...
global pressed
pressed = x
destroy_top()
def destroy_top():
# This function closes this menu/message box/etc...
global top
top.iconify()
top.withdraw()
top.quit()
def get_text():
global pressed
global top
global text_box
pressed = 0
top = Tk()
top.withdraw()
top.rowconfigure(0, weight=0)
top.columnconfigure(0, weight=0)
top.config(height=0, width=0)
top.protocol('WM_DELETE_WINDOW', lambda: button_pressed(-1))
text_box = Entry(top, width=50)
text_box.focus_set()
text_box.grid(row=0, column=0)
but = Button(top, text='Enter', command=button_pressed)
but.grid(row=0, column=1)
paste = Button(top, text='Paste', command=paste_func)
paste.grid(row=0, column=2)
top.deiconify()
text_box.focus_set()
top.after(0, top.focus_force())
center(top)
top.mainloop()
if pressed == -1:
exit()
return text_box.get('1.0', index2=END)
The window.focus_force() method does this:
Force the input focus to the widget. This is impolite. It's better to wait for the window manager to give you the focus. See also .grab_set_global() below.
Sometimes if this doesn't work, you can manually force it like so:
from Tkinter import *
window = Tk()
window.after(2000, window.focus_force)
window.mainloop()
Sometimes you will have issues on Macs which can require some additional finagling but this should work fine elsewhere (OP has not specified any information about environment).

Python Tkinter Text Alignment

I am using Tkinter with Python2 to create a new window by clicking on the button. In this new window I want to Display text. Now I have Troubles with the alignment, is it possible to align it to the left? It is always centered and neither anchor=LEFT nor sticky="NSEW" help.
import tkinter as tki
btn3 = tki.Button(self.root, text="HELP", command=self.help, fg="black", bg="white", font=("Courier",22))
btn3.grid(row=1, column=2, padx=10, pady=10, sticky="NSEW" )
def help(self):
self.text = """ Hello.
You can find help here to the following subjects:
- getting started
- installation
- FAQ."""
self.top = tki.Toplevel()
self.top.title("Help")
self.label1 = tki.Label(self.top, text = self.text, height = 0, width=80, fg="black", bg="white", font=("Courier",18))
self.label1.pack()
When you use anchor = n you need to put anchor = "n" i.e. with the n in quotes. You're getting the global name is not defined error because of this.
Alternatively, use anchor = tki.N. Because you used import tkinter as tki you must prefix tkinter variables with tki. in order for python to know what you're referring to. However, I think you might want to try justify=tki.LEFT also if that doesn't work.

Categories