Python Tkinter Scrollbar Shaky Scrolling - python
Introduction: My Python Tkinter application is designed to have a scrollbar on the side so that if the window is resized, the application can still be viewed via the scrollbar. I do this by putting a frame with all my content inside a canvas, with a scrollbar controlling the canvas. On window resize, I have a function called resizeCanvas which resizes the canvas.
Problem: After I resize the window, the scrollbar sort of works but it seems to jump around and fidget like its having a seizure. However, at initialization the scroll bar works smoothly. So resizing the window seems to be the problem. Any suggestions on why the scroll bar is behaving like this?
Application Hierarchy:
Tkinter root
frame (just a container for the canvas)
canvas (scroll bar is attached to this canvas)
frame (content is in this frame)
NOTE: Python 2.7.2
Code Snippit:
myframe=Frame(root,width=winx,height=winy)
myframe.place(x=0,y=0)
canvas = Tkinter.Canvas(myframe,width=winx,height=winy)
frame = Frame(canvas,width=winx,height=winy)
myscrollbar=Scrollbar(myframe,orient="vertical")
myscrollbar.configure(command=canvas.yview)
canvas.configure(yscrollcommand=myscrollbar.set)
myscrollbar.pack(side="left",fill="y")
canvas.pack(side="left")
canvas.create_window((0,0),window=frame,anchor='nw')
frame.bind("<Configure>", initializeCanvas)
bind("<Configure>", resizeCanvas)
def initializeCanvas(self, event):
canvas.configure(scrollregion=self.canvas.bbox("all"),width=winx,height=winy)
def resizeCanvas(self, event):
update()
cwidth = self.winfo_width()
cheight = self.winfo_height()
canvas.config(width=cwidth, height=cheight)
Entire Code:
import sys
#external python files are in the includes folder
sys.path.insert(0, 'includes')
import webbrowser
import os
import Tkinter
import tkFileDialog
import tkMessageBox
import ConfigParser
from ttk import *
import tkFont
import Tix
# configurations held in this variable
config = ConfigParser.RawConfigParser()
# pady padding for create buttons
py = 15
# padx padding for create buttons
px = 5
# padding on left side for indenting elements
indentx = 25
winx = 815
winy = 515
# array to hold features
FEATURES =['']
# wrapper class for GUI
class AppTk(Tkinter.Tk):
def __init__(self, parent):
Tkinter.Tk.__init__(self, parent)
self.parent = parent
self.settings = "./settings/settings.cfg"
self.initialize_gui()
self.minsize(winx, 100)
sizex = winx
sizey = winy
posx = 100
posy = 100
self.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))
try:
self.iconbitmap('./imgs/favicon.ico')
except Exception, e:
print "\n****Error occurred (GUI_MBD_File_Creator.init): favicon not found!"
# Setup grid of elements in GUI
# action: on start of application
def initialize_gui(self):
# ----------------------------------------------------
# START Settings frame initialization
# ----------------------------------------------------
self.myframe=Frame(self,width=winx,height=winy)
self.myframe.place(x=0,y=0)
self.canvas = Tkinter.Canvas(self.myframe,width=winx,height=winy)
self.frame = Frame(self.canvas,width=winx,height=winy)
self.myscrollbar=Scrollbar(self.myframe,orient="vertical")
self.myscrollbar.configure(command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.myscrollbar.set)
self.myscrollbar.pack(side="left",fill="y")
self.canvas.pack(side="left")
self.canvas.create_window((0,0),window=self.frame,anchor='nw')
self.frame.bind("<Configure>",self.initializeCanvas)
self.bind("<Configure>",self.resizeCanvas)
frameFont = tkFont.Font(size=13, weight=tkFont.BOLD)
self.frameSettings = Tkinter.LabelFrame(self.frame, text="Settings: fill these out first", relief="groove", borderwidth="3", font=frameFont)
self.frameSettings.grid(sticky="EW", padx=px, pady=(5,15))
labelSpreadsheet = Label(self.frameSettings, text="1. Spreadsheet Path:")
labelSpreadsheet.grid(row=0, column=0, sticky='W', padx=(indentx,0))
variableSpreadsheet = Tkinter.StringVar()
self.entrySpreadsheet = Entry(self.frameSettings, textvariable=variableSpreadsheet, width=90, state=Tkinter.DISABLED)
self.entrySpreadsheet.grid(row=0, column=1, sticky='W', padx=px, pady=5)
self.entrySpreadsheet.svar = variableSpreadsheet
buttonSpreadsheet = Button(self.frameSettings, text="Browse...")
buttonSpreadsheet.grid(row=0, column=2, sticky='W', padx=(0,10))
labelPath = Label(self.frameSettings, text="2. Root Save Path:")
labelPath.grid(row=1, column=0, sticky='W', padx=(indentx,0))
variablePath = Tkinter.StringVar()
self.entryPath = Entry(self.frameSettings, textvariable=variablePath, width=90, state=Tkinter.DISABLED)
self.entryPath.grid(row=1, column=1, sticky='W', padx=px, pady=(5,10))
self.entryPath.svar = variablePath
buttonPath = Button(self.frameSettings, text="Browse...")
buttonPath.grid(row=1, column=2, sticky='W', padx=(0,10), pady=(0,5))
# ----------------------------------------------------
# START Creation Menu frame initialization
# ----------------------------------------------------
self.frameCreationIndividual = Tkinter.LabelFrame(self.frame, text="Feature Files Creation Menu", relief="groove", borderwidth="3", font=frameFont)
self.frameCreationIndividual.grid(sticky="EW", padx=px, pady=(5,15))
labelReq = Label(self.frameCreationIndividual, text="3. Feature(s):")
labelReq.grid(row=0, column=0, sticky='NW', pady=(5,0), padx=(indentx,15))
self.scrollbarReq = Scrollbar(self.frameCreationIndividual)
self.scrollbarReq.grid(row=1, column=3, rowspan=16, sticky="NSW", pady=(0,15),padx=(0,20))
variableSelectAll = Tkinter.IntVar()
self.checkSelectAll = Checkbutton(self.frameCreationIndividual, text = "Select All", variable = variableSelectAll, onvalue = 1, offvalue = 0)
self.checkSelectAll.grid(row=0, column=1, columnspan=2, sticky='NE', padx=px, pady=(5,0))
self.checkSelectAll.svar = variableSelectAll
labelReq = Label(self.frameCreationIndividual, text="4. Files:")
labelReq.grid(row=0, column=5, sticky='NW', pady=(5,0), padx=15)
variableIndividualFFS = Tkinter.IntVar()
self.checkIndividualFFS = Checkbutton(self.frameCreationIndividual, text = "Create Feature File", variable = variableIndividualFFS, onvalue = 1, offvalue = 0)
self.checkIndividualFFS.grid(row=1, column=5, sticky='NW', padx=15)
self.checkIndividualFFS.svar = variableIndividualFFS
variableIndividualSFS = Tkinter.IntVar()
self.checkIndividualSFS = Checkbutton(self.frameCreationIndividual, text = "Create SubFeature Files", variable = variableIndividualSFS, onvalue = 1, offvalue = 0)
self.checkIndividualSFS.grid(row=2, column=5, sticky='NW', padx=15)
self.checkIndividualSFS.svar = variableIndividualSFS
variableIndividualDO = Tkinter.IntVar()
self.checkIndividualDO = Checkbutton(self.frameCreationIndividual, text = "Create Doc Outline", variable = variableIndividualDO, onvalue = 1, offvalue = 0)
self.checkIndividualDO.grid(row=3, column=5, sticky='NW', padx=15)
self.checkIndividualDO.svar = variableIndividualDO
variableIndividualDWR = Tkinter.IntVar()
self.checkIndividualDWR = Checkbutton(self.frameCreationIndividual, text = "Create Doc With Requirements", variable = variableIndividualDWR, onvalue = 1, offvalue = 0)
self.checkIndividualDWR.grid(row=4, column=5, sticky='NW', padx=(15,30))
self.checkIndividualDWR.svar = variableIndividualDWR
self.buttonIndividualAll = Button(self.frameCreationIndividual, text="Create...", width=43)
self.buttonIndividualAll.grid(row=1, column=6, rowspan=4, sticky='NESW', padx=px)
# ----------------------------------------------------
# START Entire System Creation frame initialization
# ----------------------------------------------------
self.frameCreationSystem = Tkinter.LabelFrame(self.frame, text="System Creation Menu", relief="groove", borderwidth="3", font=frameFont)
self.frameCreationSystem.grid(sticky="EW", padx=px, pady=15)
self.buttonLAIF = Button(self.frameCreationSystem, text="Create Layers/App Integration Files", width=35)
self.buttonLAIF.grid(row=11, column=0, sticky='NESW', ipady=5, padx=(indentx,0), pady=(16,8))
self.buttonDO = Button(self.frameCreationSystem, text="Create Entire Doc Outline")
self.buttonDO.grid(row=12, column=0, sticky='NESW', ipady=5, padx=(indentx,0), pady=(8,10))
# ----------------------------------------------------
# START Feature Tab Creation Frame initialization
# ----------------------------------------------------
self.frameCreationNew = Tkinter.LabelFrame(self.frame, text="Feature Tab Creation Menu", relief="groove", borderwidth="3", font=frameFont)
self.frameCreationNew.grid(sticky="EW", padx=px, pady=(15,5))
labelIssueSpreadsheet = Label(self.frameCreationNew, text="2. Feature Spreadsheet Path:")
labelIssueSpreadsheet.grid(row=0, column=0, sticky='W', padx=(indentx,0))
variableIssueSpreadsheet = Tkinter.StringVar()
self.entryIssueSpreadsheet = Entry(self.frameCreationNew, textvariable=variableIssueSpreadsheet, width=83, state=Tkinter.DISABLED)
self.entryIssueSpreadsheet.grid(row=0, column=1, sticky='W', padx=px, pady=5)
self.entryIssueSpreadsheet.svar = variableIssueSpreadsheet
buttonIssueSpreadsheet = Button(self.frameCreationNew, text="Browse...")
buttonIssueSpreadsheet.grid(row=0, column=2, sticky='W')
labelFeatureTab = Label(self.frameCreationNew, text="3. Feature Name:")
labelFeatureTab.grid(row=1, column=0, sticky='W', padx=(indentx,0))
variableFeatureTab = Tkinter.StringVar()
self.entryFeatureTab = Entry(self.frameCreationNew, textvariable=variableFeatureTab, width=83)
self.entryFeatureTab.grid(row=1, column=1, sticky='W', padx=px, pady=5)
self.entryFeatureTab.svar = variableFeatureTab
labelFeatureAbbrv = Label(self.frameCreationNew, text="4. Feature Abbreviation:")
labelFeatureAbbrv.grid(row=2, column=0, sticky='W', padx=(indentx,0))
variableFeatureAbbrv = Tkinter.StringVar()
self.entryFeatureAbbrv = Entry(self.frameCreationNew, textvariable=variableFeatureAbbrv, width=83)
self.entryFeatureAbbrv.grid(row=2, column=1, sticky='W', padx=px, pady=5)
self.entryFeatureAbbrv.svar = variableFeatureAbbrv
self.buttonNewFeature = Button(self.frameCreationNew, text="Create Feature Tab", width=35)
self.buttonNewFeature.grid(row=3, column=0, columnspan =2, sticky='NWS', ipady=5, pady=(8,10), padx=(indentx,0))
# ----------------------------------------------------
# START general purpose methods
# ----------------------------------------------------
def initializeCanvas(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"),width=winx,height=winy)
def resizeCanvas(self, event):
self.update()
cwidth = self.winfo_width()
cheight = self.winfo_height()
self.canvas.config(scrollregion=self.canvas.bbox("all"), width=cwidth, height=cheight)
# ----------------------------------------------------
# Initialize application
# ----------------------------------------------------
if __name__ == "__main__":
app = AppTk(None)
app.title("MBD File Creator")
app.mainloop()
While there may be other errors, you have one very critical flaw: you are creating a binding for the <Configure> event to self. Because self is the instance of the root window, every widget you create inherits this binding. Your resizeCanvas method is literally being called hundreds of times at startup, and hundreds of times when the window is resized.
You also have the problem where you are calling update in an event handler. As a general rule, this is bad. In effect, it's like calling mainloop in that it will not return until all events are processed. If your code causes more events to be processed (such as reconfiguring a window and causing the <configure> event to fire, you end up in a recursive loop.
You probably need to remove the calls to self.update() and/or replace them with the less dangerous self.update_idletasks(). You also need to either remove the <Configure> binding on self, or in the method you need to check for which widget caused the event to fire (ie: check that the event.widget is the root window).
Try first to save the canvas.create_window to a variable as following:
self.windows_item = canvas.create_window((0,0),window=frame,anchor='nw')
then at the end of resizeCanvas to call the following method:
def update(self):
"Update the canvas and the scrollregion"
self.update_idletasks()
canvas.config(scrollregion=canvas.bbox(self.windows_item))
Hope it helps!
Most of it found here: https://stackoverflow.com/a/47985165/2160507
Related
How do I create a scrollbar when using a grid in tkinter?
I'm a little bit stuck on this problem regarding my program. I tried adding as many comments as possible to give a sense of what everything does in the code, but essentially. The program has a field and value entry box. When the "add field/value button" is clicked, more of the entry widgets are added. If this keeps occurring then obviously it'll go off screen. So I've limited the size of the application, but the problem then is I need a scrollbar. I've tried looking it up, but my frame uses grid, and everywhere they use pack which isn't compatible in this case. I get the scrollbar to appear, however it doesn't seem to work. I've seen some people use canvas, and more than one frame, etc. I'm missing something important but I don't know how do the exact same thing with a grid. Think you experts can lend me hand to get it working? from tkinter import * import tkinter as tk class Insert(tk.Tk): def __init__(self): tk.Tk.__init__(self) container = tk.Frame(self) container.grid_columnconfigure(0, weight=1) container.grid_rowconfigure(0, weight=1) container.pack(side="top", fill="both", expand=True) self.frameslist = {} for frame in (Create,): frame_occurrence = frame.__name__ active_frame = frame(parent=container, controller=self) self.frameslist[frame_occurrence] = active_frame active_frame.grid(row=0, column=0, sticky="snew") self.show_frame("Create") def show_frame(self, frame_occurrence): active_frame = self.frameslist[frame_occurrence] active_frame.tkraise() class Create(tk.Frame): def __init__(self, parent, controller): tk.Frame.__init__(self, parent) self.controller = controller #For all widgets (nested list, 2 widgets per row) self.inputlist = [] #For just the entries self.newinputlist = [] #Create two labels, add them into the inputlist to be iterated labels = [tk.Label(self, text="Field"), tk.Label(self, text="Values")] self.inputlist.append(labels[:]) #Insert the labels from the list for toplabels in range(1): self.inputlist[toplabels][0].grid(row=toplabels, column=0, padx=10, pady=5) self.inputlist[toplabels][1].grid(row=toplabels, column=1, padx=10, pady=5) #Create the first two entry boxes, append them to the inputlist, and newinput list first_entries = [tk.Entry(self, borderwidth=5), tk.Text(self, borderwidth=5, height= 5, width=20)] self.newinputlist.append(first_entries[:]) self.inputlist.append(first_entries[:]) #Insert the entries from the newinputlist for x in range(0, len(self.newinputlist) + 1): self.newinputlist[0][x].grid(row=1, column=x, padx=10, pady=5) #Create two buttons (Both share same row), append them to list button_input_1 = [tk.Button(self, text="ADD FIELD/VALUE", command=self.add_insert), tk.Button(self, text="BACK")] self.inputlist.append(button_input_1[:]) #Insert buttons at the bottom of the grid for button in range(len(self.inputlist) - 2, len(self.inputlist)): self.inputlist[button][0].grid(row=button, column=0, padx=10, pady=5) self.inputlist[button][1].grid(row=button, column=1, padx=10, pady=5) def add_insert(self): #Create two new entries, append them to the list add_input = [tk.Entry(self, borderwidth=5), tk.Text(self, borderwidth=5, height= 5, width=20)] self.inputlist.insert(-1, add_input) self.newinputlist.append(add_input) #Because there are new entry boxes, old grid should be forgotten for widget in self.children.values(): widget.grid_forget() #Use the index for the row, get all widgets and place them again for index, widgets in enumerate(self.inputlist): widget_one = widgets[0] widget_two = widgets[1] widget_one.grid(row=index, column=0, padx=10, pady=5) widget_two.grid(row=index, column=1, padx=10) #Create scrollbar when this button is pressed scrollbar = tk.Scrollbar(self, orient="vertical") scrollbar.grid(row=0, column=2, stick="ns", rowspan=len(self.inputlist) + 1) if __name__ == "__main__": app = Insert() app.maxsize(0, 500) app.mainloop()
You could create a Canvas and insert your Entry objects into a Frame. Here is a simplified example that creates a 2D bank of Buttons using the canvas.create_window. import tkinter as tk root = tk.Tk() # essential to enable full window resizing root.rowconfigure(0, weight = 1) root.columnconfigure(0, weight = 1) # scrollregion is also essential when using scrollbars canvas = tk.Canvas( root, scrollregion = "0 0 2000 1000", width = 400, height = 400) canvas.grid(row = 0, column = 0, sticky = tk.NSEW) scroll = tk.Scrollbar(root, orient = tk.VERTICAL, command = canvas.yview) scroll.grid(row = 0, column = 1, sticky = tk.NS) canvas.config(yscrollcommand = scroll.set) # I've used a labelframe instead of frame so button are neatly collected and named frame = tk.LabelFrame(root, labelanchor = tk.N, text = "Buttonpad") # Note I've placed buttons in frame for fila in range(20): for col in range(5): btn = tk.Button(frame, text = f"{fila}-{col}") btn.grid(row = fila, column = col, sticky = tk.NSEW) # Frame is now inserted into canvas via create_window method item = canvas.create_window(( 2, 2 ), anchor = tk.NW, window = frame ) root.mainloop()
Resizing the Canvas equal to the frame size in tkinter python
I have two questions related to this attached code. This code is a part of my project in which i have to manage the attendance of 50 (or more) students. When you will run this piece of code, you will see that there is a extra white space (that might be of the canvas) inside the Label Frame i.e. Attendance_Frame. All I wanted is that the there should be no extra white space and the scrollbar, instead of being at the extreme right, should be at the place where the labels end. I have searched for the answer to my question and saw a similar case. But there, the person wanted the frame to expand to the canvas size. Link (Tkinter: How to get frame in canvas window to expand to the size of the canvas?). But in my case, I want the canvas size to be equal to frame size (although the frame lies inside the canvas) The other thing I want is that all the check boxes should initially be 'checked' (showing the present state) and when I uncheck random checkboxes (to mark the absent), and click the 'Submit' button (yet to be created at the bottom of the window), I should get a list with 'entered date' as first element and the roll numbers i.e. 2018-MC-XX as other elements. For example : ['01/08/2020', '2018-MC-7', '2018-MC-11', '2018-MC-23', '2018-MC-44']. Actually my plan is when i will get a list i will easily write it to a text file. from tkinter import * from tkcalendar import DateEntry root = Tk() root.geometry('920x600+270+50') root.minsize(920,600) Attendance_frame = Frame(root) ### Consider it a Main Frame Attendance_frame.pack() attendaceBox = LabelFrame(Attendance_frame, text = 'Take Attendance', bd = 4, relief = GROOVE, labelanchor = 'n',font = 'Arial 10 bold', fg = 'navy blue', width = 850, height = 525) # A Label Frame inside the main frame attendaceBox.pack_propagate(0) attendaceBox.pack(pady = 15) dateFrame = Frame(attendaceBox) # A small frame to accommodate date entry label & entry box dateFrame.pack(anchor = 'w') font = 'TkDefaultFont 10 bold' date_label = Label(dateFrame, text = 'Enter Date : ', font = font).grid(row = 0, column = 0, sticky = 'w', padx = 10, pady = 10) date_entry = DateEntry(dateFrame, date_pattern = 'dd/mm/yyyy', showweeknumbers = FALSE, showothermonthdays = FALSE) date_entry.grid(row = 0, column = 1, sticky = 'w') noteLabel = Label(attendaceBox, text = 'Note: Uncheck the boxes for absentees').pack(anchor = 'w', padx = 10, pady = 5) canvas = Canvas(attendaceBox, borderwidth=0, background="#ffffff") checkFrame = Frame(canvas, width = 100, height = 50) vsb = Scrollbar(canvas, orient="vertical", command=canvas.yview) canvas.configure(yscrollcommand=vsb.set) vsb.pack(side="right", fill="y") canvas.pack(side="left", fill="both", expand=True) canvas.pack_propagate(0) canvas.create_window((4,4), window=checkFrame, anchor="nw") def onFrameConfigure(canvas): '''Reset the scroll region to encompass the inner frame''' canvas.configure(scrollregion=canvas.bbox("all")) checkFrame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas)) for i in range(0,51): # A loop to create Labels of students roll numbers & names c = Checkbutton(checkFrame, text = f"{'2018-MC-'+str(i+1)} Student {i+1}") c.grid(row = i, column = 0, padx = 10, sticky = 'w') mainloop()
If you are creating a vertical list of items, you don't need to use a frame inside the canvas. The inner frame adds some unnecessary complexity. Instead, create the checkbuttons directly on the canvas with create_window. You also need to configure the scrollregion attribute so that the scrollbar knows how much of the virtual canvas to scroll. Finally, to have them selected you should assign a variable to each checkbutton, and make sure that the value is the proper value. By default checkbuttons use the values 0 and 1, so setting the variable to 1 will make it selected. vars = [] for i in range(0,51): var = IntVar(value=1) vars.append(var) x0, y0, x1, y1 = canvas.bbox("all") or (0,0,0,0) c = Checkbutton(canvas, text = f"{'2018-MC-'+str(i+1)} Student {i+1}", variable=var) canvas.create_window(2, y1+4, anchor="nw", window=c) canvas.configure(scrollregion=canvas.bbox("all"))
Your all questions answer is here: You should try this. import tkinter as tk from tkcalendar import DateEntry root = tk.Tk() root.geometry('920x600+270+50') root.minsize(960, 600) root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) Attendance_frame = tk.Frame(root) Attendance_frame.grid(row=0, column=0, sticky='nsew') attendaceBox = tk.LabelFrame(Attendance_frame, text='Take Attendance', bd=4, relief='groove', labelanchor='n', font='Arial 10 bold', fg='navy blue', width=850, height=525) attendaceBox.grid(row=0, column=0, sticky='nsew', padx=15) Attendance_frame.columnconfigure(0, weight=1) Attendance_frame.rowconfigure(0,weight=1) dateFrame = tk.Frame(attendaceBox) # A small frame to accommodate date entry label & entry box dateFrame.grid(row=0, column=0, sticky='nsew') font = 'TkDefaultFont 10 bold' date_label = tk.Label(dateFrame, text='Enter Date : ', font=font) date_label.grid(row=0, column=0, sticky='w', padx=10, pady=10) date_entry = DateEntry(dateFrame, date_pattern='dd/mm/yyyy', showweeknumbers=False, showothermonthdays=False) date_entry.grid(row=0, column=1, sticky='w') noteLabel = tk.Label(attendaceBox, text='Note: Uncheck the boxes for absentees', anchor='w') noteLabel.grid(row=1, column=0, sticky='nsew') attendaceBox.rowconfigure(2, weight=1) canvas = tk.Canvas(attendaceBox, width=200, borderwidth=0, background="#ffffff") # You can set width of canvas according to your need canvas.grid(row=2, column=0, sticky='nsew') canvas.columnconfigure(0, weight=1) canvas.rowconfigure(0, weight=1) vsb = tk.Scrollbar(attendaceBox, orient="vertical", command=canvas.yview) vsb.grid(row=2, column=1, sticky='nsew') canvas.configure(yscrollcommand=vsb.set) checkFrame = tk.Frame(canvas, bg='green') canvas.create_window((0, 0), window=checkFrame, anchor="nw", tags='expand') checkFrame.columnconfigure(0, weight=1) for i in range(0, 51): # A loop to create Labels of students roll numbers & names c = tk.Checkbutton(checkFrame, anchor='w', text=f"{'2018-MC-' + str(i + 1)} Student {i + 1}") c.grid(row=i, column=0, sticky='nsew') c.select() canvas.bind('<Configure>', lambda event: canvas.itemconfigure('expand', width=event.width)) checkFrame.update_idletasks() canvas.config(scrollregion=canvas.bbox('all')) root.mainloop() Get Selected Value vars = [] for i in range(0, 51): # A loop to create Labels of students roll numbers & names var = tk.IntVar() c = tk.Checkbutton(checkFrame, variable=var, anchor='w', text=f"{'2018-MC-' + str(i + 1)} Student {i + 1}") c.grid(row=i, column=0, sticky='nsew') c.select() vars.append(var) def state(): print(list(map((lambda var: var.get()), vars)))
Importing Tkinter and rowspan/sticky question
I am writing a program to generate draft emails, and I am trying to set it up with a tkinter GUI. I'm on Python 3.5 with tkinter 8.6. My issue is that I cannot get rowspan to work. I want to have the first row span a few rows, but when I add 'rowspan' the row doesn't change size. If I add 'sticky=tK.N+tK.S', I get errors that those mean nothing. So then I started messing with how I was importing tkinter, trying it as importing from, importing as tk, importing *, but everything I change breaks soemthing else, and still doesn't get the north-south stickiness that I am looking for so that the row will stretch out and fill the space. I am pretty sure that this is an issue with how I am importing tkinter, and any advice would be tremendously appreciated. I also can't get tkinter to work if I use 'import tkinter as tk" because then I get errors like name 'StringVar' is not defined". I tried fixing that by moving where my root was declared, but that created issues with GUI. Help! Thanks :) import os # pull in GUI stuff import tkinter as tk #import Tk, Label, Button, W, E, N, S, StringVar, OptionMenu, Entry, Text, END, WORD from PIL import Image, ImageTk path = os.getcwd() # init GUI class GUI: def __init__(self, master): self.master = master master.title("Email Draft Builder") # set logo image f = os.getcwd() + "\\cgs_logo.gif" # lock in image for use outside this section im = Image.open(f) ph = ImageTk.PhotoImage(im) # vars to show dynamic text for displays 1 and 2 self.box1_titleText = tk.StringVar() self.box2_titleText = tk.StringVar() self.box1_content = tk.StringVar() self.box2_content = tk.StringVar() # main/container pane info self.label = tk.Label(master, text="Email Draft Composer", image = ph, bg = "#ffffff") self.label.image = ph self.label.grid(columnspan = 3, row = 0, rowspan = 2, sticky=tk.N+ tk.S, column=1) # ROW 1 self.readLast_button = tk.Button(master, text="Read Training File", command=self.dataOps) self.readLast_button.grid(row=3, column=0,sticky=tk.W) # ROW 2 self.file_button = tk.Button(master, text="Unused", command=self.chooseSite) self.file_button.grid(row=4, column=0,sticky=tk.W) # ROW 3 self.pullSite_button = tk.Button(master, text="Show Site Info:", command=self.pullSite) self.pullSite_button.grid(row=5, column=0,sticky=tk.W) self.getSite = tk.Entry(master) self.getSite.grid(row=5, column=1,sticky=tk.W) # ROW 4 self.sendEmail_button = tk.Button(master, text="Build Email", command=self.sendEmail) self.sendEmail_button.grid(row=6, column=0,sticky=tk.W) recipients = ["mgancsos#cogstate.com","mgancsos#gmail.com","kkiernan#cogstate.com;mchabon#cogstate.com","ashortland#cogstate.com"] self.receiver = tk.StringVar() self.receiver.set(recipients[0]) self.menu1 = tk.OptionMenu(master, self.receiver, *recipients) self.menu1.grid(row=6, column=1,sticky=tk.W) # ROW 5 self.close_button = tk.Button(master, text="Close", command=root.destroy) self.close_button.grid(row=7, column=0,sticky=tk.W) # ROW 6 self.box1_title = tk.Label(master, textvariable=self.box1_titleText, bg = "#fffff0", borderwidth=1, relief = "groove", width = 15) self.box1_title.grid(columnspan=1, row = 8, column=0, sticky=tk.W) self.box1_pane = tk.Label(master, textvariable=self.box1_content, bg = "#fffff0", borderwidth=1, relief = "groove", width = 55) self.box1_pane.grid(columnspan=1, row = 8, column=1, sticky=tk.W) # ROW 7 self.box2_title = tk.Label(master, textvariable=self.box2_titleText, bg = "#ffff00", borderwidth=1, relief = "groove", width = 15) self.box2_title.grid(columnspan=1, row = 9, column=0, sticky='nw') self.box2_pane = tk.Label(master, textvariable=self.box2_content, bg = "#ffffff", borderwidth=1, relief = "groove", width = 55) self.box2_pane.grid(columnspan=1, row = 9, column=1, sticky='NW') # ROW 8 self.display1 = tk.Text(master, wrap=tk.WORD) self.display1.grid(columnspan=1, row = 10, column=1, sticky=tk.W) def dataOps(self): return(1) def chooseSite(self): return(1) def pullSite(self): return(1) def sendEmail(self): return(1) root = tk.Tk() root.geometry('800x800') root["bg"] = "#ffffff" my_gui = GUI(root) root.mainloop()
Python Tkinter: Scrolling two text boxes at the same time; lines get out of sync
I have a Text widget, textbox, where the user can type, and another Text widget, linenumbers, directly to the left of it which displays line numbers. I have it working, but with one problem: the more lines I create, the more the two Text widgets get out of sync when scrolled. Here's a gif showing what happens. And here's my code (scroll_both and update_scroll came from another StackOverflow thread): class MainGUI: def scroll_both(self, action, position, type=None): self.textbox.yview_moveto(position) self.linenumbers.yview_moveto(position) def update_scroll(self, first, last, type=None): self.textbox.yview_moveto(first) self.linenumbers.yview_moveto(first) self.scrollbar_y.set(first, last) def on_textbox_update(self, event): modified = self.textbox.edit_modified() if modified: #If the number of lines in the textbox has changed: if self.linecount != self.textbox.index("end").split('.')[0]: #Update textbox's linecount self.linecount = self.textbox.index("end").split('.')[0] # Clear the line count sidebar self.linenumbers.config(state="normal") self.linenumbers.delete(0.0, "end") # Fill it up again for LineNum in range(1, int(self.linecount)): self.linenumbers.insert(self.linenumbers.index("end"), str(LineNum)+"\n", "justifyright") if len(str(LineNum)) == 1: self.linenumbers.config(width=2) else: self.linenumbers.config(width=len(str(LineNum))) self.linenumbers.config(state="disabled") def __init__(self, master): self.master = master Grid.rowconfigure(master, 1, weight=1) Grid.columnconfigure(master, 2, weight=1) self.linenumbers = Text(master, width=2, borderwidth=0, takefocus=0, padx=6, fg="gray") self.linenumbers.config(font="TkTextFont") self.linenumbers.tag_configure("justifyright", justify="right") self.linenumbers.grid(row=0, column=1, rowspan=2, sticky=NSEW) self.textbox = Text(master, padx=6, borderwidth=0, wrap=NONE) self.textbox.config(font="TkTextFont") self.textbox.grid(row=0, column=2, rowspan=2, sticky=NSEW) self.textbox.bind("<<Modified>>", self.on_textbox_update) self.scrollbar_y = ttk.Scrollbar(master) self.scrollbar_y.grid(row=0, column=3, rowspan=2, sticky=NSEW) self.scrollbar_y.config(command=self.scroll_both) self.textbox.config(yscrollcommand=self.update_scroll) self.linenumbers.config(yscrollcommand=self.update_scroll) root = Tk() MainGUI = MainGUI(root) root.mainloop()
python tkinter and scope of stringvar()
Python noob here. I've created a simple tkinter app with 6 frames. Using multiple frames was the easiest way I could find to get all of the widgets to line up. I want to have a text entry field in one of the frames that can be updated by other functions in the code. However, no matter what I try, I cannot get blah.set( "blah" ) to work. I'm not using the self/parent stuff because I have not been able to figure that out yet. Here's what I've got: def Next () : glob_current_company_display.set( "test" ) def makeWindow () : win = Tk() win.title('Finder') win.geometry('+842+721') # ************************************************************************ # Frame 2 frame2 = Frame(win) button_03 = Button(frame2, text="Next", width=10, command=Next) button_03.pack(side=LEFT) # ************************************************************************ # Frame 5 frame5 = Frame(win) co_name_label = Label(frame5, text="Company Name: ", justify=LEFT) co_name_label.pack(side=LEFT) global glob_current_company_display glob_current_company_display = StringVar() co_name_entry = Entry(frame5, width=50, textvariable=glob_current_company_display) co_name_entry.pack(side=LEFT) # ************************************************************************ # Pack the frames frame5.pack(side=TOP, pady=5, padx=5) frame2.pack(side=TOP, pady=5, padx=5) return win win = makeWindow() win.mainloop()
You probably should investigate the "self/parent stuff". It's comes in very handy for this kind of stuff. For now: you could put the creation of the global variable before the button_03 that has Next() as command (which has a reference to te global in it). from Tkinter import * def Next () : glob_current_company_display.set( "test" ) def makeWindow () : win = Tk() win.title('Finder') win.geometry('+842+721') global glob_current_company_display glob_current_company_display = StringVar() # ************************************************************************ # Frame 2 frame2 = Frame(win) button_03 = Button(frame2, text="Next", width=10, command=Next) button_03.pack(side=LEFT) # ************************************************************************ # Frame 5 frame5 = Frame(win) co_name_label = Label(frame5, text="Company Name: ", justify=LEFT) co_name_label.pack(side=LEFT) co_name_entry = Entry(frame5, width=50, textvariable=glob_current_company_display) co_name_entry.pack(side=LEFT) # ************************************************************************ # Pack the frames frame5.pack(side=TOP, pady=5, padx=5) frame2.pack(side=TOP, pady=5, padx=5) return win win = makeWindow() win.mainloop()