Application: Building a notes application (as an intro to GUI development in Python) which includes feature of a scrollbar to scroll through a textbox
Problem: I can't actually seem to scroll down through the textbox. I don't seem to get the grayed rectangle which lets me control the scrollbar and scroll up/down through the textbox
#importing necessary packages
from tkinter import *
from tkinter import font
from tkinter import ttk
#set up main window
root = Tk()
root.title("Notes")
root.geometry("400x650")
#functions
#functions to change all widget button's backgrounds when user hovers over it and leaves it
def enter_button(e):
e.widget.config(background = "#D4D4D4")
#SystemButtonFace is default colour
def leave_button(e):
e.widget.config(background = "SystemButtonFace")
#clear text in text-box
def clear():
#delete all text from text_box
text_box.delete(1.0,END)
def bold_it():
#create font
try:
bold_font = font.Font(text_box, text_box.cget("font"))
bold_font.configure(weight = "bold")
#creating tag called "bold" which bolds textll upon condition
text_box.tag_configure("bold", font = bold_font)
#creating a bold tag which highlights first character
bold_tag = text_box.tag_names("sel.first")
#condition for checking to see if tag is applied or not
#in the first highlighted character
#if tag is applied, remove the bold from first-highlighted text
#- last highlighted text
#"bold" needs to be matched in the tag
if "bold" in bold_tag:
text_box.tag_remove("bold","sel.first","sel.last")
else:
text_box.tag_add("bold","sel.first", "sel.last")
except TclError:
pass
def italics_it():
try:
#create a font
italics_font = font.Font(text_box, text_box.cget("font"))
italics_font.configure(slant = "italic")
#create a tag called "italic"
text_box.tag_configure("italics", font = italics_font)
italics_tag = text_box.tag_names("sel.first")
#condition to see whether tag has been applies or not
if "italics" in italics_tag:
text_box.tag_remove("italics", "sel.first","sel.last")
else:
text_box.tag_add("italics", "sel.first", "sel.last")
except TclError:
pass
#frames
top_frame = LabelFrame(root, padx = 30, pady = 10)
button_frame = LabelFrame(root, padx = 30, pady = 10)
text_frame = LabelFrame(root, padx = 10, pady = 10)
bottom_frame = LabelFrame(root, borderwidth = 0, highlightthickness = 5)
top_frame.grid(row = 0 , column = 0)
button_frame.grid(row = 1, column = 0, pady = 10)
text_frame.grid(row = 2, column = 0, pady = 1)
bottom_frame.grid(row = 3, column = 0, pady = 3)
#labels, textboxes, buttons
#top_frame content
Notes_label = Label(top_frame, text = "Notes", fg = "black", font = 1, padx = 141)
Notes_label.grid(row = 0 , column = 0)
save_button = Button(top_frame, text = "save")
#padx increases distance between buttons
#button_frame content
#bold button
#the ideal is that if u press ctrl + b, the bold_button is pressed by itself
#rn, it's gonna be a highlight technique
bold_button = Button(button_frame, text = "B", padx = 4, pady = 2, command = bold_it)
bold_button.grid(row = 0, column = 0)
#italicsize button
italics_button = Button(button_frame, text = "I", padx = 4, pady = 2, command = italics_it)
italics_button.grid(row = 0, column = 2, padx = 15)
#text_box frame button
text_box = Text(text_frame, width = 45, height = 27)
text_box.grid(row = 0, column = 0)
#text_box frame content
main_scrollbar = ttk.Scrollbar(text_frame, orient = "vertical", command = text_box.yview)
main_scrollbar.grid(row = 0, column = 1)
text_box["yscrollcommand"] = main_scrollbar.set
clear_button = Button(bottom_frame, text = "clear", padx = 2, pady = 2, command = clear)
clear_button.grid(row = 0, column = 0, padx = 15, pady = 10)
save_button = Button(bottom_frame, text = "save note", padx = 2, pady = 2)
save_button.grid(row = 0, column =1, padx = 15, pady = 10)
#binding all buttons for changing colours when user hovers over it and leaves it
bold_button.bind("<Enter>", enter_button)
bold_button.bind("<Leave>", leave_button)
italics_button.bind("<Enter>", enter_button)
italics_button.bind("<Leave>", leave_button)
clear_button.bind("<Enter>", enter_button)
clear_button.bind("<Leave>", leave_button)
save_button.bind("<Enter>", enter_button)
save_button.bind("<Leave>", leave_button)
# main program loop
root.mainloop()
here's an image of the problem image of problem
I would also be very grateful if one could explain the concept of scrollbar.set and like yview and why they are both needed for the scrollbar to work. Tutorials and videos don't seem to explain the concept, but just implement it
In line 145. You're missing sticky
main_scrollbar.grid(row = 0, column = 1, sticky=NS)
Output:
Related
i want it to expand the size accordingly if the window is expanded, so far i use the grid.row/column configure to make its weight =1 but it will be all messed up it i expand the window.
import time
import tkinter as tk
#Initialise the window
clock = tk.Tk()
clock.title('Easy CLock')
clock.configure(bg='#121212')
clock.columnconfigure(0, weight = 1)
clock.rowconfigure(0, weight = 1)
border_effects = {
"flat": tk.FLAT,
"sunken": tk.SUNKEN,
"raised": tk.RAISED,
"groove": tk.GROOVE,
"ridge": tk.RIDGE,
}
#Logo will be under the main parent
logo = tk.PhotoImage(file = r'C:\Users\User\VSC\Alarm\Logo1.png')
logo_size = logo.subsample(5)
#Time and Date function
def time_date():
# current time
current_time = time.strftime('%H:%M:%S')
current_date = time.strftime(r'%m/%d/%Y')
clock.after(200, time_date)
#Displays the time
c_time = tk.Label(f_time, text = current_time, fg='white', bg='#121212', font=('Verdana', 30))
c_date = tk.Label(f_time, text = current_date, font=('Verdana', 10), fg='white', bg='#121212')
c_time.grid(column=0, row=0)
c_date.grid(column=0, row=1)
#alarm button command
def alarm_func():
#Alarm label
c_clicked = tk.Label(f_alarm, text='Alarm Interface', fg='white', bg='#121212')
c_clicked.grid(column=0, row=1, sticky = 'N')
def recall_frame(event):
if event == f_alarm:
event.grid_forget()
f_time.grid(column=0, row =1, columnspan = 4, sticky = 'N')
elif event == f_time:
event.grid_forget()
f_alarm.grid(column=0, row=1, columnspan = 4, rowspan = 2)
def back_func():
pass
#Creating Frames
f_time = tk.Frame(clock) #Clock Button
f_alarm = tk.Frame(clock) #Alarm Buttton
#configure the frames
f_time.configure(bg = '#121212')
f_alarm.configure(bg = '#121212')
#Setting label in the frame
f_lbl = tk.Label(clock, text= ' Simplistic Clock', image = logo_size, font=('Verdana', 30), fg='white', bg='#121212', compound = tk.LEFT)
time_but = tk.Button(clock, text='Clock', command= lambda :[time_date(), recall_frame(f_alarm)], bg='#f39c12', width = 15, relief = border_effects['ridge'])
alarm_but = tk.Button(clock, text = 'Alarm', command = lambda :[alarm_func(), recall_frame(f_time)], bg='#f39c12', width = 15, relief = border_effects['ridge'])
quit_but = tk.Button(clock, text='Exit', command = clock.quit, bg='#f39c12', width = 15, relief = border_effects['ridge'])
back_but = tk.Button(clock, text = 'Back To Home', command = back_func, bg='#f39c12', width = 15, relief = border_effects['ridge'])
f_lbl.config(borderwidth = 4, relief = border_effects['sunken'])
f_lbl.grid_columnconfigure(0, weight = 1)
#Putting it on the frames
f_lbl.grid(column = 0, row = 0, columnspan = 4, sticky = 'N')
time_but.grid(column = 0, row = 3)
alarm_but.grid(column = 1, row = 3)
back_but.grid(column = 2, row = 3)
quit_but.grid(column = 3, row = 3)
clock.mainloop()
also, why does the border in the f_lbl, simplistic clock not fully extended through out all 4 column since i put columnspan = 4 , and weight = 1 , should't it expand fully through all 4 columns?
I've looked at all the other questions and answers and couldn't find one that fit what I'm trying to do.
Code:
class GameWin:
def __init__(self, master):
self.master = master
self.master.title("Title")
self.main_frame = Frame(self.master, bd = 10, bg = uni_bg)
self.main_frame.grid()
self.left_frame = Frame(self.main_frame, bd = 10, bg = uni_bg)
self.left_frame.grid(column = 0, row = 0)
self.right_frame = Frame(self.main_frame, bd = 10, bg = uni_bg)
self.right_frame.grid(column = 1, row = 0)
self.right_frame.columnconfigure(0, weight = 1)
self.right_frame.rowconfigure(0, weight = 1)
self.right_frame.columnconfigure(1, weight = 1)
self.right_frame.rowconfigure(1, weight = 1)
self.web = Text(self.left_frame, font = (uni_font, 12), wrap = WORD, bg = uni_bt_bg, fg = uni_fg)
self.web.grid(column = 0, row = 0, padx = 5, pady = 5)
self.output = Text(self.left_frame, font = (uni_font, 12), wrap = WORD, bg = "black", fg = uni_fg)
self.output.grid(column = 0, row = 1, padx = 5, pady = 5, sticky = "ew")
self.output.configure(state = "disabled")
self.input = Entry(self.left_frame, font = (uni_font, 12))
self.input.grid(column = 0, row = 2, sticky = "ew")
self.input.bind('<Return>', self.submit)
self.notepad = Text(self.right_frame, font = (uni_font, 12), wrap = WORD, bg = uni_fg, fg = "black", width = 42)
self.notepad.grid(column = 0, row = 0, pady = 5, rowspan = 2)
self.sys_info = Text(self.right_frame, font = (uni_font, 12), wrap = WORD, bg = uni_bg, fg = uni_fg, width = 35, height = 11, bd = 0)
self.sys_info.tag_configure('center', justify='center')
self.sys_info.grid(column = 1, row = 0, pady = 5)
self.sys_info.insert(END, "NAME", "center")
self.sys_info.configure(state = "disabled")
self.trace = Text(self.right_frame, font = (uni_font, 12), wrap = WORD, bg = uni_bg, fg = uni_fg, width = 35, height = 11)
self.trace.grid(column = 1, row = 1, pady = 5)
self.email = Text(self.right_frame, font = (uni_font, 12), wrap = WORD, bg = uni_bt_bg, fg = uni_fg)
self.email.grid(column = 0, row = 2, pady = 5, columnspan = 2)
self.email.configure(state = "disabled")
self.respond = Entry(self.right_frame, font = (uni_font, 12))
self.respond.grid(column = 0, row = 3, columnspan = 2, sticky = "ew")
self.respond.bind('<Return>', self.do_respond)
def submit(self, event):
self.output.configure(state = "normal")
self.output.configure(state = "disabled")
pass
def do_respond(self, event):
pass
Image of current screen: https://i.imgur.com/P2B6E5y.png
First thing I'm trying to figure out is how to not explicitly state the size of the 3 text widgets in the top right. Because everyone's screen is differently sized. If I don't explicitly state the size, they expand and everything goes wacko (since the default text widget is big). I want the widgets to automatically downscale to fit within the column (the same width as the big grey bottom right text widget). Is this even possible?
Second is for the frames and widgets to fill up all the space in the window. Whether it's fullscreen (like in the pic) or a smaller window (and hopefully keep their size relative to each other). There's a lot of empty space at the edges of the window and I want to get rid of that. I've tried everything I can think of but I can't get them to fill that space.
I tried putting the top 3 widgets each in their own frame, limiting the size of the frames relative to the window size, and setting the widgets to fill that frame but it doesn't work. Code I used to try this: https://pastebin.com/3YWK9Xg2
class GameWin:
def __init__(self, master):
self.master = master
self.master.title("Hacker")
win_width = self.master.winfo_width()
win_height = self.master.winfo_height()
self.main_frame = Frame(self.master, bd = 10, bg = uni_bg)
self.main_frame.grid(sticky = "nsew")
self.left_frame = Frame(self.main_frame, bd = 10, bg = uni_bg, height = int(win_height), width = int(win_width/2))
self.left_frame.grid(column = 0, row = 0, rowspan = 3)
self.left_frame.grid_propagate(False)
self.note_frame = Frame(self.main_frame, bd = 10, bg = uni_bg, height = int(win_height/2), width = int(win_width/4))
self.note_frame.grid(column = 1, row = 0, rowspan = 2, sticky = "n")
self.note_frame.grid_propagate(False)
self.sys_frame = Frame(self.main_frame, bd = 10, bg = uni_bg, height = int(win_height/4), width = int(win_width/4))
self.sys_frame.grid(column = 2, row = 0, sticky = "n")
self.sys_frame.grid_propagate(False)
self.trace_frame = Frame(self.main_frame, bd = 10, bg = uni_bg, height = int(win_height/4), width = int(win_width/4))
self.trace_frame.grid(column = 2, row = 1, sticky = "n")
self.trace_frame.grid_propagate(False)
self.bottom_right_frame = Frame(self.main_frame, bd = 10, bg = uni_bg, height = int(win_height/2), width = int(win_width/2))
self.bottom_right_frame.grid(column = 1, row = 2, columnspan = 2)
self.bottom_right_frame.grid_propagate(False)
self.web = Text(self.left_frame, font = (uni_font, 12), wrap = WORD, bg = uni_bt_bg, fg = uni_fg)
self.web.grid(column = 0, row = 0, padx = 5, pady = 5)
self.output = Text(self.left_frame, font = (uni_font, 12), wrap = WORD, bg = "black", fg = uni_fg)
self.output.grid(column = 0, row = 1, padx = 5, pady = 5, sticky = "ew")
self.input = Entry(self.left_frame, font = (uni_font, 12))
self.input.grid(column = 0, row = 2, sticky = "ew")
self.input.bind('<Return>', self.submit)
self.notepad = Text(self.note_frame, font = (uni_font, 12), wrap = WORD, bg = uni_fg, fg = "black")
self.notepad.pack(fill = BOTH, expand = YES)
self.sys_info = Text(self.sys_frame, font = (uni_font, 12), wrap = WORD, bg = uni_bg, fg = uni_fg)
self.sys_info.tag_configure('center', justify='center')
self.sys_info.grid(sticky = "nsew")
self.sys_info.insert(END, "NAME", "center")
self.sys_info.configure(state = "disabled")
self.trace = Text(self.trace_frame, font = (uni_font, 12), wrap = WORD, bg = uni_bg, fg = uni_fg)
self.trace.grid(sticky = "nsew")
self.email = Text(self.bottom_right_frame, font = (uni_font, 12), wrap = WORD, bg = uni_bt_bg, fg = uni_fg)
self.email.grid(row = 0, pady = 5, columnspan = 2, sticky = "nsew")
self.email.configure(state = "disabled")
self.respond = Entry(self.bottom_right_frame, font = (uni_font, 12))
self.respond.grid(row = 1, columnspan = 2, sticky = "ew")
self.respond.bind('<Return>', self.do_respond)
def submit(self, event):
self.output.configure(state = "normal")
self.output.configure(state = "disabled")
def do_respond(self, event):
pass
and picture of the result: https://i.imgur.com/IVnw65x.png
Here is the full code: https://pastebin.com/Gm2ePqFH. I want it to look like it is in the first picture, without having to explicitly state the size of each text widget. And I want to get it to all stay the same size relative to the window.
If you want widgets to shrink down to the size of the column, the strategy that has worked best for me is to make the widget very small and then use the layout manager (pack, place, or grid) make them bigger to fit. You can either make the widget 1x1 if that's truly a minimum size you will accept, or you can set it to what you think the absolute minimum should be (for example, 4 lines of 20 characters, 20 lines of 10 characters, etc).
So, start by making those widgets as small as possible.
Next, make sure you use the sticky attribute so that the widgets grow to fill their allotted space. You also need to make sure you use the sticky attribute for self.right_frame so that it fills its space too.
Finally, make sure that you call rowconfigure and columnconfigure to set a positive weight on any widget that has children managed by grid. You aren't doing that for self.master, nor are you doing it for self.left_frame and self.right_frame
As a rule of thumb, if you're only putting one or two widgets in a frame and you want those widgets to fill the frame, it's much better to use pack than grid, simply because you don't have to remember to give the rows and columns weights.
For example, you can use pack to manage the left and right frames. You can probably use pack to put GameWin inside of its master, too.
Also, don't try to solve all of your layout problems at once. In your case, I would tackle the problem like this:
Start with just your mainframe and get it so that it fills the containing window. Make sure you manually resize the window to watch it grow and shrink.
Next, add your left and right frames. Make sure they grow and shrink to fit mainframe.
Next, focus on just the widgets in the left frame. Add them, and make sure they shrink and fit.
Then, focus on the right frame. Add them, and make sure they shrink and fit.
Finally, a word of advice. Group your calls to grid together. As your code is written now, you need to fix a whole bunch of lines scattered over the entire file. With some reorganization, almost all of the lines of code you need to change will be in one block of code.
For example, instead of this:
self.web = Text(...)
self.web.grid(...)
self.output = Text(...)
self.output.grid(...)
self.input = Text(...)
self.input.grid(...)
self.notepad = Text(...)
self.notepad.grid(...)
Do this:
self.web = Text(...)
self.output = Text(...)
self.input = Text(...)
self.notepad = Text(...)
self.web.grid(...)
self.output.grid(...)
self.input.grid(...)
self.notepad.grid(...)
With that, at a glance you can answer the question "how are my widgets defined?", and "how are my widgets laid out". With the way you have it now, those questions are very difficult to answer.
I'm new to Python and just started venturing into doing GUIs. I created a Tkinter window that is pretty basic: it has 3 Entry bars and 3 File Dialog buttons. When you chose the 3rd directory, the GUI file automatically makes a call to a separate file and receives a large text block which is then displayed in a Text box.
The whole thing works correctly, but my problem is that after receiving and inserting the text response, Tkinter stops working and doesn't allow the user to scroll down.
I read that one reason this happens is because people use both .pack( ) and .grid( ), but I'm not mixing those two functions.
Thanks in advance for any help!
Here's my GUI file
from tkinter import *
from tkinter import filedialog
from gui_GPSExtractor import *
import os
class Application(Frame):
def __init__(self, master):
""" Initialize Frame """
Frame.__init__(self, master)
self.grid( )
self.startGUI( )
""" Create Labels, Text Boxes, File Dialogs, and Buttons """
def startGUI(self):
# Label for Scan Path
self.dLabel = Label(self, text = "Scan Path")
self.dLabel.grid(row = 0, column = 0, columnspan = 2, sticky = W)
# Entry for Scan Path
self.dEntry = Entry(self, width = 60)
self.dEntry.grid(row = 1, column = 0, sticky = W)
# Button for Scan Path Directory Browse
self.dButton = Button(self, text = "Browse", command = lambda: self.browseFiles("d"))
self.dButton.grid(row = 1, column = 1, sticky = W)
# Label for CSV Path
self.cLabel = Label(self, text = "CSV Path")
self.cLabel.grid(row = 3, column = 0, columnspan = 2, sticky = W)
# Entry for CSV Path
self.cEntry = Entry(self, width = 60)
self.cEntry.grid(row = 4, column = 0, sticky = W)
# Button for CSV Path Directory Browse
self.cButton = Button(self, text = "Browse", command = lambda: self.browseFiles("c"))
self.cButton.grid(row = 4, column = 1, sticky = W)
# Label for Log Path
self.lLabel = Label(self, text = "Log Path")
self.lLabel.grid(row = 6, column = 0, columnspan = 2, sticky = W)
# Entry for Log Path
self.lEntry = Entry(self, width = 60)
self.lEntry.grid(row = 7, column = 0, sticky = W)
# Button for Log Path Directory Browse
self.lButton = Button(self, text = "Browse", command = lambda: self.browseFiles("l"))
self.lButton.grid(row = 7, column = 1, sticky = W)
# Text Box for Results
self.resultText = Text(self, width = 60, height = 30, wrap = WORD, borderwidth = 3, relief = SUNKEN)
self.resultText.grid(row = 9, column = 0, columnspan = 2, sticky = "nsew")
# Scrollbar for Text Box
self.scrollBar = Scrollbar(self, command = self.resultText.yview)
self.scrollBar.grid(row = 9, column = 2, sticky = "nsew")
self.resultText["yscrollcommand"] = self.scrollBar.set
def browseFiles(self, btnCalling):
if(btnCalling == "d"):
self.dName = filedialog.askdirectory(initialdir = "/python3-CH05")
self.dEntry.delete(0, END)
self.dEntry.insert(0, self.dName)
elif(btnCalling == "c"):
self.cName = filedialog.askdirectory(initialdir = "/python3-CH05")
self.cEntry.delete(0, END)
self.cEntry.insert(0, self.cName)
elif(btnCalling == "l"):
self.lName = filedialog.askdirectory(initialdir = "/python3-CH05")
self.lEntry.delete(0, END)
self.lEntry.insert(0, self.lName)
output = extractGPS(self.dName, self.cName, self.lName)
self.resultText.delete(0.0, END)
self.resultText.insert(0.0, output)
# Start the GUI
root = Tk( )
root.title("Python gpsExtractor")
root.geometry("650x650")
app = Application(root)
root.mainloop( )
I want to create frame over the label and been trying lots of code but it is not working out. Also trying to make checkbutton to left side of the screen with no frame. Can anyone help me? Thank you
I got far as this
But I want to make it look like this with the frame
show_status = Label(dashboard, bd = 5, text = 'Even', fg = 'black',
font = ('Arial', 70), width = 8)
def update_dashboard():
three_buttons = Label(dashboard, relief = 'groove')
Alpha_button = Checkbutton(three_buttons, text = 'Alpha',
variable = alpa_1,
command = update_dashboard)
Beta_button = Checkbutton(three_buttons, text = 'Beta',
variable = beta_2,
command = update_dashboard)
Gamma_button = Checkbutton(three_buttons, text = 'Gamma',
variable = gemma_3,
command = update_dashboard)
Alpha_button.grid(row = 1, column = 0, sticky = 'w')
Beta_button.grid(row = 1, column = 2, sticky = 'w')
Gamma_button.grid(row = 1, column = 4, sticky = 'w')
margin = 5 # pixels
show_status.grid(padx = margin, pady = margin, row = 1,
column = 1, columnspan = 2,)
three_buttons.grid(row = 4, column = 2, sticky = W)
dashboard.mainloop()
You can use a Frame or a Canvas and draw the rest of the widgets on it. Let us use the Frame by relying on the grid layout manager.
To have that effect you are looking for, you simply need to span the label over the 3 columns of the check button widgets using the option the columnspan option.
Full program
Here is a simple solution using the object oriented concepts:
'''
Created on May 8, 2016
#author: Billal Begueradj
'''
import Tkinter as Tk
class Begueradj(Tk.Frame):
'''
Dislay a Label spanning over 3 columns of checkbuttons
'''
def __init__(self, parent):
'''Inititialize the GUI
'''
Tk.Frame.__init__(self, parent)
self.parent=parent
self.initialize_user_interface()
def initialize_user_interface(self):
"""Draw the GUI
"""
self.parent.title("Billal BEGUERADJ")
self.parent.grid_rowconfigure(0,weight=1)
self.parent.grid_columnconfigure(0,weight=1)
self.parent.config(background="lavender")
# Create a Frame on which other elements will be attached to
self.frame = Tk.Frame(self.parent, width = 500, height = 207)
self.frame.pack(fill=Tk.X, padx=5, pady=5)
# Create the checkbuttons and position them on the second row of the grid
self.alpha_button = Tk.Checkbutton(self.frame, text = 'Alpha', font = ('Arial', 20))
self.alpha_button.grid(row = 1, column = 0)
self.beta_button = Tk.Checkbutton(self.frame, text = 'Beta', font = ('Arial', 20))
self.beta_button.grid(row = 1, column = 1)
self.gamma_button = Tk.Checkbutton(self.frame, text = 'Gamma', font = ('Arial', 20))
self.gamma_button.grid(row = 1, column = 2)
# Create the Label widget on the first row of the grid and span it over the 3 checbbuttons above
self.label = Tk.Label(self.frame, text = 'Even', bd = 5, fg = 'black', font = ('Arial', 70), width = 8, relief = 'groove')
self.label.grid(row = 0, columnspan = 3)
# Main method
def main():
root=Tk.Tk()
d=Begueradj(root)
root.mainloop()
# Main program
if __name__=="__main__":
main()
Demo
Here is a screenshot of the running program:
I'm working on the GUI for a simple quiz app using Tkinter in Python 2.7.
Thus far, I have begun to set up my frame. I've put a scrollbar inside of a Text widget named results_txtbx to scroll up and down a list noting the player's performance on each question. I've been using grid since it's easier for me to manage.
from Tkinter import *
class Q_and_A:
def __init__(self, master):
frame = Frame(master)
Label(master).grid(row = 4)
results_txtbx = Text(master)
results_scrbr = Scrollbar(results_txtbx)
results_scrbr.grid(sticky = NS + E)
results_txtbx.config(width = 20, height = 4, wrap = NONE, yscrollcommand = results_scrbr.set)
results_txtbx.grid(row = 3, column = 1, padx = 12, sticky = W)
root = Tk()
root.wm_title("Question and Answer")
root.resizable(0, 0)
app = Q_and_A(root)
root.mainloop()
What happens is that when it runs, results_txtbx resizes to fit the scrollbar. Is there any way to make it keep its original size using grid?
You don't want to use a text widget as the master for a scrollbar. Like any other widget, if you pack or grid the scrollbar in the text widget, the text widget will shrink or expand to fit the scrollbar. That is the crux of your problem.
Instead, create a separate frame (which you're already doing), and use that frame as the parent for both the text widget and the scrollbars. If you want the appearance that the scrollbars are inside, set the borderwidth of the text widget to zero, and then give the containing frame a small border.
As a final usability hint, I recommend not making the window non-resizable. Your users probably know better what size of window they want than you do. Don't take that control away from your users.
Here's (roughly) how I would implement your code:
I would use import Tkinter as tk rather than from Tkinter import * since global imports are generally a bad idea.
I would make Q_and_A a subclass of tk.Frame so that it can be treated as a widget.
I would make the whole window resizable
I would separate widget creation from widget layout, so all my layout options are in one place. This makes it easier to write and maintain, IMO.
As mentioned in my answer, I would put the text and scrollbar widgets inside a frame
Here's the final result:
import Tkinter as tk
class Q_and_A(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, borderwidth=1, relief="sunken")
self.label = tk.Label(self)
self.results_txtbx = tk.Text(self, width=20, height=4, wrap="none",
borderwidth=0, highlightthickness=0)
self.results_scrbr = tk.Scrollbar(self, orient="vertical",
command=self.results_txtbx.yview)
self.results_txtbx.configure(yscrollcommand=self.results_scrbr.set)
self.label.grid(row=1, columnspan=2)
self.results_scrbr.grid(row=0, column=1, sticky="ns")
self.results_txtbx.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
root = tk.Tk()
root.wm_title("Question And Answer")
app = Q_and_A(root)
app.pack(side="top", fill="both", expand=True)
root.mainloop()
Set results_scrbr.grid(row = 3, column = 2) next to results_txtbx.grid(row = 3,column = 1, padx = 4), sticky is not needed because window is not resizable, and i lowered the padx so scrollbar is closer to text.
Also to make the results_txtbx vertically scrollable, add results_scrbr.config(command=results_txtbx.yview)
Here is a working code...
from Tkinter import *
class Q_and_A:
def __init__(self, master):
frame = Frame(master)
Label(master).grid(row = 4)
results_txtbx = Text(master)
results_scrbr = Scrollbar(master)
results_scrbr.grid(row = 3, column = 2)
results_scrbr.config(command=results_txtbx.yview)
results_txtbx.config(width = 20, height = 4,
wrap = NONE, yscrollcommand = results_scrbr.set)
results_txtbx.grid(row = 3, column = 1, padx = 4)
root = Tk()
root.wm_title("Question and Answer")
root.resizable(0, 0)
app = Q_and_A(root)
root.mainloop()
My implemented solution:
I needed to add more widgets to the app, so I bound the Scrollbar and Text widgets to another label and put that in the proper column the code (trimmed for readability) is below:
import Tkinter as tk
class Q_and_A(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.label = tk.Label(self)
#Set up menu strip
self.main_menu = tk.Menu(self)
self.file_menu = tk.Menu(self.main_menu, tearoff = 0)
self.file_menu.add_command(label = "Exit", command = self.quit)
self.main_menu.add_cascade(label = "File", menu = self.file_menu)
self.master.config(menu = self.main_menu)
#Set up labels
self.question_lbl = tk.Label(self, text = "Question #: ", padx = 12, pady = 6)
self.question_lbl.grid(row = 0, sticky = "w")
tk.Label(self, text = "Hint: ").grid(row = 1, sticky = "w", padx = 12, pady = 6)
tk.Label(self, text = "Answer: ").grid(row = 2, sticky = "w", padx = 12, pady = 6)
tk.Label(self, text = "Results: ").grid(row = 3, sticky = "nw", padx = 12, pady = 6)
tk.Label(self).grid(row = 4)
#Set up textboxes
self.question_txtbx = tk.Entry(self)
self.question_txtbx.config(width = 60)
self.question_txtbx.grid(row = 0, column = 1, padx = 12, columnspan = 3, sticky = "w")
self.help_txtbx = tk.Entry(self)
self.help_txtbx.config(width = 40)
self.help_txtbx.grid(row = 1, column = 1, columnspan = 2, padx = 12, sticky = "w")
self.answer_txtbx = tk.Entry(self)
self.answer_txtbx.config(width = 40)
self.answer_txtbx.grid(row = 2, column = 1, columnspan = 2, padx = 12, sticky = "w")
self.results_label = tk.Label(self)
self.results_txtbx = tk.Text(self.results_label, width = 10, height = 4, wrap = "none", borderwidth = 1, highlightthickness = 1)
self.results_scrbr = tk.Scrollbar(self.results_label, orient = "vertical", command = self.results_txtbx.yview)
self.results_txtbx.configure(yscrollcommand = self.results_scrbr.set)
self.label.grid(row = 1)
self.results_label.grid(row = 3, column = 1, padx = 11, sticky = "w")
self.results_scrbr.grid(row = 0, column = 1, sticky = "nse")
self.results_txtbx.grid(row = 0, column = 0, sticky = "w")
root = tk.Tk()
root.wm_title("Question and Answer")
#A note: The window is non-resizable due to project specifications.
root.resizable(0, 0)
app = Q_and_A(root)
app.pack(side = "top", fill = "both")
root.mainloop()
I'll keep storage in nested labels as a reference for myself for when I need to group things close together, unless there's some reason it should be avoided. Worked very well here. Thanks to Bryan for the advice.