Tkinter experts, I'm having trouble getting a Canvas to scroll. This is my second GUI, and I've done something similar before, so I don't know what I'm doing wrong. I'd appreciate any help you can offer.
Here's a minimal version of what I'm trying to do. I'm using python 3.4.3 on Windows 10.
import tkinter as tk
import tkinter.font as tk_font
import tkinter.ttk as ttk
import random
def get_string_var(parent, value=''):
var = tk.StringVar(parent)
var.set(value)
return var
class SummaryFrame(ttk.Frame):
def __init__(self, parent, **kwargs):
ttk.Frame.__init__(self, parent, **kwargs)
var_names = ['label_'+str(num) for num in range(1, 20)]
self.vars = {}
for name in var_names:
self.vars[name] = get_string_var(self)
self._add_summary_labels(self, self.vars, 1)
#staticmethod
def _add_summary_labels(frame, vars, start_row):
current_row = start_row
for name in vars:
tk.Label(frame, text=name, anchor=tk.N+tk.W).grid(row=current_row, column=0, sticky=tk.N+tk.S+tk.W+tk.E)
text_label = tk.Label(frame, wraplength=200, textvariable=vars[name], anchor=tk.N+tk.W, justify=tk.LEFT)
text_label.grid(row=current_row, column=1, sticky=tk.W)
current_row += 1
def set_summary_fields(self, info):
for name in info:
if name in self.vars:
self.vars[name].set(info[name])
class OuterFrame(ttk.Frame):
def __init__(self, parent, **kwargs):
ttk.Frame.__init__(self, parent, **kwargs)
self.canvas = tk.Canvas(self)
scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.canvas.yview)
self.canvas.configure(yscrollcommand=scrollbar.set)
self.summary = SummaryFrame(self.canvas)
self.summary.pack(fill=tk.BOTH, expand=1)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
scrollbar.pack(side=tk.LEFT, fill=tk.Y, expand=1)
label_text = {}
for i in range(1, 20):
label_text['label_'+str(i)] = "information " * random.randint(1, 20)
self.set_fields(label_text)
def set_fields(self, info):
self.summary.set_summary_fields(info)
self.canvas.configure(scrollregion=(1, 1, self.summary.winfo_width(), self.summary.winfo_height()))
if __name__ == "__main__":
root = tk.Tk()
frame = OuterFrame(root)
frame.pack(fill=tk.BOTH, expand=1)
root.mainloop()
The scrollbar should change when the contents of the inner SummaryFrame expands, but doesn't. It remains grayed out and inoperable. What am I doing wrong?
Short answer: you are configuring the scrollregion to be one pixel by one pixel.
You are configuring the scrollregion based on the width and height of the self.summary, but you are doing this before the window has a chance to be mapped to the screen. The width and height, therefore, are both one.
You need to wait for the window to be drawn before computing the width and height of the window.
Related
I'm building Minesweeper in python with Tkinter and built the adjustable board size with a for loop using grid to position each value on the window.
Unfortunately I cannot find a way to now reposition the entire grid i.e., I would like to be able to move the game board within the window to place other widgets around it (currently it's auto positioned is the top left corner).
Part of my code is:
import tkinter as tk
import configparser
class MainWindow:
def __init__(self, master):
"""
"""
self.master = master
self.config = configparser.SafeConfigParser()
self.master.geometry("600x600")
self.master.resizable(width=False, height=False)
self.buttons = []
for row in range(0, 9):
self.buttons.append([])
for col in range(0, 9):
self.button = tk.Button(text=" ", font=("Georgia", "10", "bold"), width=4, height=2,
background="lightsteelblue", relief="raised")
self.button.bind("<Button-3>")
self.button.grid(row=row, column=col, sticky=tk.N+tk.W+tk.S+tk.E)
self.buttons[row].append(self.button)
if __name__ == '__main__':
root = tk.Tk()
root.configure(background="cornflowerblue")
app = MainWindow(root)
root.mainloop()
This part of the code returns the following:
Which shows the issue I'm having. I would like the ability to move the board within the window (for example, by using place) so that I can enlarge the frame and add features around the hopefully centralised board.
You should move the buttons into a frame. You can them move the entire group of widgets at once. Even better, create a class specifically for the grid. It would look something like this:
class GridFrame(tk.Frame):
def __init__(self, parent, rows=10, columns=10, **kwargs):
super().__init__(parent, **kwargs)
self.buttons = []
self.grid_rowconfigure([*range(rows)], weight=1, uniform="row")
self.grid_columnconfigure([*range(columns)], weight=1, uniform="column")
for row in range(rows):
self.buttons.append([])
for col in range(columns):
button = tk.Button(
self,
text=" ",
font=("Georgia", "10", "bold"),
width=4,
height=2,
background="lightsteelblue",
relief="raised",
)
button.grid(row=row, column=col, sticky=tk.N + tk.W + tk.S + tk.E)
self.buttons[row].append(button)
You can use this just like you use any other widget. For example:
class MainWindow:
def __init__(self, master):
""" """
self.master = master
self.config = configparser.SafeConfigParser()
self.master.geometry("600x600")
self.board = GridFrame(self.master, rows=10, columns=10, borderwidth=0)
self.board.grid(row=1, column=1, sticky="nsew")
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()
A simple quiz game
I got this code and I need scrollbars, I tried to search how to add it on stackoverflow (ScrolledWindow with tix...) but I still can't get something that works properly. Could someone help me?
from tkinter import *
from random import randint
root = Tk()
root.title("Quiz")
root.geometry("400x300")
class Window:
def __init__(self, question, answer):
self.text = [question, answer]
self.createLabel()
# self.createText()
self.createEntry()
self.createButton()
def retrieve_input(self):
# inputValue = self.textBox.get("1.0", "end-1c")
# print(inputValue)
if self.mystring.get() == self.text[1]:
print("Esatto. è " + self.text[1])
self.left['text'] = "Esatto"
def createLabel(self):
self.labelframe = LabelFrame(root, text="Domanda:")
self.labelframe.pack(fill="both", expand="yes")
self.left = Label(self.labelframe, text=self.text[0])
self.left.pack()
def createText(self):
self.textBox = Text(height=1)
self.textBox.pack()
def createEntry(self):
self.mystring = StringVar()
self.myentry = Entry(root, textvariable=self.mystring).pack()
def createButton(self):
self.but = Button(text="Click", command=self.retrieve_input)
self.but.pack()
for i in range(10):
one = randint(1, 10)
two = randint(1, 10)
Window("Quanto fa " + str(one) + "+" + str(two) + "?", str(one + two))
root.mainloop()
output
With ScrolledFrame it can look like this
I renamed Window into Question because it makes more sense
I use self.question and self.answer instead of self.text = [question, answer] to make it more readable.
I put classes and functions before root = tk.Tk() to make it more readable.
I use import tkinter as tk instead of from tkinter import * to make it more readable.
Question gets inner frame from ScrolledFrame and use as parent for LabelFrame. Other widgets use labelframe as parent.
BTW: you had entry = Entry(..).pack() which assign None to entry because pack()/grid()/place() returns None. I put pack() in next line and now I can get text directly from Entry (without StringVar)
Code
import tkinter as tk
from random import randint
# --- classes ---
class ScrolledFrame(tk.Frame):
def __init__(self, parent, vertical=True, horizontal=False):
super().__init__(parent)
# canvas for inner frame
self._canvas = tk.Canvas(self)
self._canvas.grid(row=0, column=0, sticky='news') # changed
# create right scrollbar and connect to canvas Y
self._vertical_bar = tk.Scrollbar(self, orient='vertical', command=self._canvas.yview)
if vertical:
self._vertical_bar.grid(row=0, column=1, sticky='ns')
self._canvas.configure(yscrollcommand=self._vertical_bar.set)
# create bottom scrollbar and connect to canvas X
self._horizontal_bar = tk.Scrollbar(self, orient='horizontal', command=self._canvas.xview)
if horizontal:
self._horizontal_bar.grid(row=1, column=0, sticky='we')
self._canvas.configure(xscrollcommand=self._horizontal_bar.set)
# inner frame for widgets
self.inner = tk.Frame(self._canvas, bg='red')
self._window = self._canvas.create_window((0, 0), window=self.inner, anchor='nw')
# autoresize inner frame
self.columnconfigure(0, weight=1) # changed
self.rowconfigure(0, weight=1) # changed
# resize when configure changed
self.inner.bind('<Configure>', self.resize)
self._canvas.bind('<Configure>', self.frame_width)
def frame_width(self, event):
# resize inner frame to canvas size
canvas_width = event.width
self._canvas.itemconfig(self._window, width = canvas_width)
def resize(self, event=None):
self._canvas.configure(scrollregion=self._canvas.bbox('all'))
class Question:
def __init__(self, parent, question, answer):
self.parent = parent
self.question = question
self.answer = answer
self.create_widgets()
def get_input(self):
value = self.entry.get()
print('value:', value)
if value == self.answer:
print("Esatto. è " + self.answer)
self.label['text'] = "Esatto"
def create_widgets(self):
self.labelframe = tk.LabelFrame(self.parent, text="Domanda:")
self.labelframe.pack(fill="both", expand=True)
self.label = tk.Label(self.labelframe, text=self.question)
self.label.pack(expand=True, fill='both')
self.entry = tk.Entry(self.labelframe)
self.entry.pack()
self.button = tk.Button(self.labelframe, text="Click", command=self.get_input)
self.button.pack()
# --- main ---
root = tk.Tk()
root.title("Quiz")
root.geometry("400x300")
window = ScrolledFrame(root)
window.pack(expand=True, fill='both')
for i in range(10):
one = randint(1, 10)
two = randint(1, 10)
Question(window.inner, "Quanto fa {} + {} ?".format(one, two), str(one + two))
root.mainloop()
looking through blogs, tutorials and such, I have not been able to understand why the following code will not display a Label in a Frame. I can perform this task well when I do not define a class, but I want to create this application to be able to create Frames and other widgets more dynamically. Here is the code. init works fine and displays a Frame in the root window appropriately. the code runs with out any errors. I have added print statements and print(type()) statements through out the add_heading function, but still no label gets displayed. Hopefully someone can tell me what I am doing wrong. Thanks in advance
#!/usr/bin/python3
from tkinter import *
class CompFrame(Tk):
def __init__(self, parent, rows, columns, title):
Frame.__init__(self, parent)
self.root = parent
f_w = screen_width/3
f_h = screen_height * .90
self = LabelFrame(root, text=title, width=f_w, height=f_h, bg="light grey")
self.grid_columnconfigure(columns, weight=1)
self.grid(row=rows, column=columns)
self.grid_propagate(False)
def add_heading(self, title):
label_width=12
ftitle = Label(self, text=title)
ftitle.configure(font='Helvetica 24 bold', width=label_width)
ftitle.place(x=".5i", y=".2i")
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
root.geometry("%dx%d%d%d" % (screen_width,screen_height,0,0))
Calibrator = PhotoImage(file="image1.png")
Feeeder = PhotoImage(file="image2.PNG")
frame1 = CompFrame(root, 0, 0, "Component 1")
frame1.add_heading("NATURAL")
root.mainloop()
The self = LabelFrame(root ... part is guilty, maybe try something like this to keep control on the labelframe :
from tkinter import *
class CompFrame(Frame): # Edit with Bryan Oakley comment
def __init__(self, parent, rows, columns, title):
Frame.__init__(self, parent)
self.root = parent
f_w = screen_width/3
f_h = screen_height * .90
self.labelframe = LabelFrame(root, text=title, width=f_w, height=f_h, bg="light grey")
self.labelframe.grid_columnconfigure(columns, weight=1)
self.labelframe.grid(row=rows, column=columns)
self.labelframe.grid_propagate(False)
def add_heading(self, title):
label_width=12
ftitle = Label(self.labelframe, text=title)
ftitle.configure(font='Helvetica 24 bold', width=label_width)
ftitle.place(x=5, y=2)
I would like to align text widgets horizontally and be able to scroll left and right the frame where are placed these widgets. The code below is almost what I want except the fact that my scrollbar doesn't work.
I found some example where it is said not to use pack or grid in Canvas. However, if I use place layout, my widgets simply disapear.
from tkinter import *
class MainView(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.canvas = Canvas(self)
self.sensorsStatsFrame = Frame(self.canvas)
myscrollbar = Scrollbar(self,orient=HORIZONTAL,command=self.canvas.xview)
myscrollbar.pack(side=BOTTOM,fill=X)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.pack(side=TOP, fill=BOTH)
test0 = Text(self.sensorsStatsFrame, bg="red", state=DISABLED, width=150)
test1 = Text(self.sensorsStatsFrame, bg="green")
test0.pack(side=LEFT)
test1.pack(side=LEFT)
self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
self.canvas.config(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
root = Tk()
main = MainView(root)
main.pack(fill="both", expand=1)
root.wm_geometry("1100x500")
root.wm_title("MongoDB Timed Sample Generator")
root.mainloop()
I would like to align text widgets horizontally and be able to scroll left and right the frame where are placed these widgets.
If i didn't misunderstand you, you should add an event function to your codes.
from tkinter import *
class MainView(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.canvas = Canvas(self)
self.sensorsStatsFrame = Frame(self.canvas)
myscrollbar = Scrollbar(self,orient=HORIZONTAL,command=self.canvas.xview)
myscrollbar.pack(side=BOTTOM,fill=X)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.pack(side=TOP, fill=BOTH)
test0 = Text(self.sensorsStatsFrame, bg="red", state=DISABLED, width=150)
test1 = Text(self.sensorsStatsFrame, bg="green")
test0.pack(side=LEFT)
test1.pack(side=LEFT)
self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
# Call the function like the below.
self.sensorsStatsFrame.bind("<Configure>", self.onFrameConfigure)
# Add below function to your codes.
def onFrameConfigure(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
root = Tk()
main = MainView(root)
main.pack(fill="both", expand=1)
root.wm_geometry("1100x500")
root.wm_title("MongoDB Timed Sample Generator")
root.mainloop()