Python Tkinter with Turtle and Scrollbars - python

I have been having some problems implementing scrollbars into my Tkinter program which uses turtle. I am able to create and add the turtle canvas to my Tkinter program just fine, however, the scrollbars don't seem to fit visually even though they can be dragged. The canvas also starts offset meaning you have to scroll it to the centre every time. Below is an example of this issue.
I know that I could use turtles built-in "Scrolledcanvas" however this would mean I am unable to style the scrollbars using ttk. If possible I would remove the scrollbars entirely, and just simply centre the graphics and make them fit the canvas at all times, however, this seemed too complex for me.
I have provided a working example below for anyone to take a look at.
import tkinter as tk
from tkinter import ttk
import turtle
root = tk.Tk()
aFrame = tk.Canvas(root)
aFrame.pack()
aCanvas = tk.Canvas(aFrame)
aCanvas.grid(row=0, column=0, sticky="")
aScreen = turtle.TurtleScreen(aCanvas)
aXScrollbar = ttk.Scrollbar(aFrame, orient="horizontal", command=aCanvas.xview)
aXScrollbar.bind("<Configure>",
lambda e: aCanvas.configure(
scrollregion=aCanvas.bbox("all")
))
aXScrollbar.grid(row=1, column=0, sticky="ew")
aYScrollbar = ttk.Scrollbar(aFrame, orient="vertical", command=aCanvas.yview)
aYScrollbar.bind("<Configure>",
lambda e: aCanvas.configure(
scrollregion=aCanvas.bbox("all")
))
aYScrollbar.grid(row=0, column=1, sticky="ns")
aTurtle = turtle.RawTurtle(aScreen)
aTurtle.goto(0, 0)
aTurtle.fillcolor('blue')
aTurtle.begin_fill()
for i in range(4):
aTurtle.forward(150)
aTurtle.right(90)
aTurtle.end_fill()
root.mainloop()
Thanks for any help!

Related

Tkinter scrollbar is not scrolling

As part of a tkinter app I'm building using Python 3.8, I need a particular tab in a Notebook to be scrollable. The notebook needs to remain at a fixed size, but the problem is that there will be cases in which the contents of the tab will exceed the size of the notebook.
The scrollbar appears as it should, but scrolling appears to have no effect on the contents of the tab. It looks like it thinks it's scrolling something but I do not know what. Here's an isolated example of a tab with a scrollbar which has no effect:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
tabs = ttk.Notebook(root, width=200, height=650)
tab_options = tk.Frame(tabs)
tabs.add(tab_options, text="Options")
main_frame = tk.Frame(tab_options)
main_frame.pack()
canvas = tk.Canvas(main_frame)
canvas.pack(side="left",fill="both",expand=1)
scrollbar = ttk.Scrollbar(main_frame,orient="vertical",command=canvas.yview)
scrollbar.pack(side="right", fill="y",expand=1)
lf_options = tk.Frame(canvas)
lf_options.pack()
canvas.configure(yscrollcommand=scrollbar.set)
canvas.configure(scrollregion=(0,0,200,1000))
for i in range(50):
ttk.Label(lf_options, text=str(i)).pack()
tabs.pack()
root.mainloop()
I imagine it's something to do with how I'm hooking up the frames to the canvas but I cannot for the life of me get it to work. I've seen suggestions about setting scrollregion to
canvas.bbox("all")
but I don't understand how to associate that with the maximum height that can be displayed, i.e. the height of the notebook itself. Using that as the scrollregion also just makes the scrollbar unscrollable.
I know there are many similar questions on here, but I have not found any of those examples to work in this case.
Any advice would be greatly appreciated. Thanks!
It seems more logical to use a tk.Listbox for this purpose, see below for example an edited version of your code. Here the scrollbar works just as expected!
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
tabs = ttk.Notebook(root, width=200, height=650)
tab_options = tk.Frame(tabs)
tabs.add(tab_options, text="Options")
listbox = tk.Listbox(tab_options)
for i in range(50):
listbox.insert(tk.END, f"Number {i}")
listbox.pack(side="left", fill="both", expand=1)
scrollbar = ttk.Scrollbar(tab_options, orient="vertical", command=listbox.yview)
scrollbar.pack(side="right", fill="y", expand=1)
listbox.configure(yscrollcommand=scrollbar.set)
tabs.pack()
root.mainloop()

Do frames in Tkinter contain local grid systems?

Very simple question that I cannot find the answer to on Stack Exchange. (Only this misleading thread: Python3 Tkinter: Grids Within Grids? Frames Alongside Other Frames? which sadly does not answer the question).
I am using tkinter with Python to attempt a program with a GUI. Unfortunately, I completely do not understand the grid system.
I was under the impression that each frame contains its own grid system, where 0,0 is the top-left of that frame.
I created this code:
root = Tk()
main_frame = Frame(root, width='1520', height='1080', bg='#a1a1a1').grid(row=0, column=1)
side_frame = Frame(root, width='400', height='1080', bg='#757575').grid(row=0, column=0)
header_label = Label(main_frame, text='Heading', font=('Agency', 48), bg='#a1a1a1', fg='#ffffff').grid(row=0, column=0)
And hoped that because the label is set to (0,0) in the main frame, that the label would appear in the main frame. However, it didn't. I find that a bit odd and upsetting. Below is a picture of what is instead happening - the header label is appearing in the 'side_frame'.
Faulty layout picture
Could someone please explain to me how the grid system works? I wasn't really having trouble until I tried to add a scrollbar to the main_frame - I then realised that there's some serious awkwardness embedded within tkinter and I'd like to properly understand it.
Thanks!
The problem is that you're setting main_frame and side_frame to None, so passing that as the parent for any other widget will make that widget a child of the root widget.
That is because in python x=y().z() sets the value of x to z(). Thus, when you do main_frame = Frame(...).grid(...) sets main_frame to the result of .grid(...), and grid always returns None.
You should separate widget creation from widget layout, and this is one of the reasons why. The other big reason is that it makes the code easier to read and visualize.
main_frame = Frame(root, width='1520', height='1080', bg='#a1a1a1')
side_frame = Frame(root, width='400', height='1080', bg='#757575')
main_frame.grid(row=0, column=1)
side_frame.grid(row=0, column=0)

Python's Tkinter Entry not appearing on window

I am trying to make a window that would take an input through an entry and that would be either a web address or ip address and i would use a loop to update the text of a label to show the current ping every second. But I'm stuck at the very beginning because my entry would not appear on my window. Here is my code:
import tkinter as tk
from tkinter import *
window = tk.Tk()
window.title("Server Status")
window.geometry('400x600')
window.resizable(0,0)
canvas = tk.Canvas(window,height=600,width=1000,bg='#263D42')
canvas.pack()
txtf=tk.Entry(window, width=10)
txtf.pack()
window.mainloop()
Where am I going wrong? I have tried it with several changes but still cant get it to appear there. Any help would be appreciated.
Thanks.
Your entry is below the canvas, but because (1) your window geometry specifies a smaller size than that requested for the canvas, and (2) you set it to be non resizable, you can never access it.
Choose how to resolve this conflict; the example below sets the size of the canvas, and lets the window resize to enclose all its widgets.
import tkinter as tk
window = tk.Tk()
window.title("Server Status")
canvas = tk.Canvas(window, height=600, width=1000, bg='#263D42')
canvas.pack()
txtf = tk.Entry(window, width=10)
txtf.pack()
window.mainloop()

Python tkinter label image not drawn unless a message widget is behind it

I have a transparent tkinter message widget and a label, one containing text the other an image, the image is only drawn in the parts where the text label is behind it. 1
I'm using python tkinter on windows 7 to draw the labels on the desktop on a transparent window, so what you're seeing in the background is my regular windows desktop, which might have something to do with it
Here are snippets of code, but there is alot more that's not relevant I think
x = Tk()
label = tkinter.Message(x, textvariable=text, font=('Terminal','10'), fg='white', bg='green', width=800, anchor='n')
label.master.overrideredirect(True)
label.master.geometry('+30+30')
label.master.lift()
label.master.wm_attributes('-transparentcolor', 'green')
label.pack()
Then this is inside a function which is called to display the plot
new_plot = Label(x, image=plot_image, fg='white', bg='green', anchor='n', width=640, height=480)
new_plot.image = plot_image
new_plot.master.wm_attributes('-transparentcolor', 'green')
new_plot.place(x = 20, y = 30, width=640, height=480)
new_plot.update()
Then at the end
x.mainloop()
I hope this is all that's relevant to the problem
I suspect it has to do with the fact that a tkinter window by default is 1x1 pixel in size. When you use place, tkinter will not expand the window to fit its contents.
The reason it works with the messagebox is because you are adding the messagebox to the root window with pack which will cause the window to grow.
The simple solution is to use new_plot.pack(...) instead of new_plot.place(...).

Resize Tkinter Listbox widget when window resizes

I'm new to Tkinter, and I've got a Listbox widget that I'd like to automatically-resize when changing the main window's size.
Essentially I would like to have a fluid height/width Listbox. If someone can point me to some documentation or provide a bit a code / insight, I'd appreciate it.
You want to read up on the geometry managers pack and grid, which lets you place widgets in a window and specify whether they grow and shrink or not. There's a third geometry manager, place, but it's not used very often.
Here's a simple example:
import tkinter as tk
root = tk.Tk()
scrollbar = tk.Scrollbar(root, orient="vertical")
lb = tk.Listbox(root, width=50, height=20, yscrollcommand=scrollbar.set)
scrollbar.config(command=lb.yview)
scrollbar.pack(side="right", fill="y")
lb.pack(side="left",fill="both", expand=True)
for i in range(0,100):
lb.insert("end", "item #%s" % i)
root.mainloop()
If you wish to use grid instead of pack, remove the two lines that call pack and replace them with these four lines:
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
scrollbar.grid(row=0, column=1, sticky="ns")
lb.grid(row=0, column=0, sticky="nsew")
Note that with grid you have to take the extra step to configure the weight for the row and column that contains the listbox, otherwise tkinter won't allocate any extra space to the widget.
The two main ways to allow a listbox to stretch when the window is resized are using the .pack() or .grid() methods.
SPECS:
Windows 7, Python 3.8.1, tkinter version: 8.6
.pack()
I found the easiest way to do this is by using the .pack() method, and utilizing the fill= & expand=True options.
import tkinter as tk
root=tk.Tk() #Creates the main window
listbox=tk.Listbox(root) #Create a listbox widget
listbox.pack(padx=10,pady=10,fill=tk.BOTH,expand=True) #fill=tk.BOTH, stretch vertically and horizontally
#fill=tk.Y, stretch vertically
#fill=tk.X, stretch horizontally
If your listbox is placed in a frame, the frame will also need to use the fill= & expand=True options.
import tkinter as tk
root=tk.Tk()
frame1=tk.Frame(root)
frame1.pack(fill=tk.BOTH, expand=True)
listbox=tk.Listbox(frame1)
listbox.pack(padx=10,pady=10,fill=tk.BOTH,expand=True)
.grid()
The alternative technique is to use the .grid() method and utilize thesticky= option. In addition, you will need to configure the row and column that the listbox resides in.
import tkinter as tk
root=tk.Tk() #create window
root.columnconfigure(0,weight=1) #confiugures column 0 to stretch with a scaler of 1.
root.rowconfigure(0,weight=1) #confiugures row 0 to stretch with a scaler of 1.
listbox=tk.Listbox(root)
listbox.grid(row=0,column=0,padx=5,pady=5,sticky='nsew')
The sticky option causes the listbox to stick to the "North" (Top), "South" (Bottom), "East" (Right), and "West" (Left) sides of the cell as it is stretched.
If your listbox is placed within a frame, you will need to configure the column and row that the frame is in, along with configure the column and row that the listbox is in.
import tkinter as tk
root=tk.Tk() #create window
root.columnconfigure(0,weight=1)
root.rowconfigure(0,weight=1)
frame1=tk.Frame(root)
frame1.grid(row=0,column=0,sticky='nsew')
frame1.columnconfigure(0,weight=1)
frame1.rowconfigure(0,weight=1)
listbox=tk.Listbox(frame1)
listbox.grid(row=0,column=0,padx=5,pady=5,sticky='nsew')
.pack() & .grid()
Now there is one other technique, but some people frown on it. The third technique is to utilize the .pack() method and .grid() method in the same script. You can mix different geometry management method in the same script as long as only a one management type is used per container. You can see an example of this below.
import tkinter as tk
root=tk.Tk() #create window
frame1=tk.Frame(root) #container: root
frame1.pack(fill=tk.BOTH,expand=True)
frame1.columnconfigure(0,weight=1)
frame1.rowconfigure(0,weight=1)
frame1.rowconfigure(1,weight=1)
listbox=tk.Listbox(frame1) #container: frame1
listbox.grid(row=0,rowspan=2,column=0,padx=5,pady=5,sticky='nsew')
btn1=tk.Button(frame1,text='Demo1') #container: frame1
btn1.grid(row=0,column=1, padx=5, pady=5)
btn2=tk.Button(frame1,text='Demo2') #container: frame1
btn2.grid(row=1,column=1, padx=5, pady=5)
frame2=tk.Frame(root) #container: root
frame2.pack()
btn3=tk.Button(frame2,text='Demo3') #container: frame2
btn3.grid(row=0,column=0)
You can see above that the frames used .pack() while the listbox and buttons used .grid(). This was possible because the frames resided within the root container, while the listbox and buttons resided within their respective frames.
To check you tkinter version use:
import tkinter as tk
print(tk.TkVersion)
If you would like to learn about the differences between fill and expand, please see the following link.
https://effbot.org/tkinterbook/pack.htm

Categories