Python - Tkinter: Scrollbar not aligning properly - python

I am working on a python tkinter desktop applicaiton. I need a scrollbar on the right side of the frame with a vertical orientation. I am trying to display a ttk scrollbar but it does not seem to display properly. My table disappears and the height of the scrollbar is not correct either. Also, if possible, the scrollbar needs to appear only when the TreeView overflows and when it doesnt overflow, then the scrollbar should not be displayed.
import tkinter
from turtle import color, width
import win32com.client
import sys
import subprocess
import time
from tkinter import*
from tkinter import ttk
from tkinter import messagebox
class TestingGui():
def __init__(self):
print("testing")
def registerUser(self):
userName = "tim"
userAge = "36"
userGender = "male"
userPosition = "softeware engineer"
userInfo = [userName.upper(),userAge,userGender,userPosition]
tree.column(0,anchor='center')
tree.column(1,anchor='center')
tree.column(2,anchor='center')
tree.column(3,anchor='center')
tree.insert('',0,values=userInfo)
if __name__ == '__main__':
window = Tk()
window.title('Dashboard')
window.geometry('925x500+300+200')
window.configure(bg="#fff")
window.resizable(False,False)
################### Frame (Top)[start] #####################################
frameTop = Frame(window,width=860,height=60,bg='white')
frameTop.place(x=40,y=40)
uploadExcelBtn = Button(frameTop,width=19,pady=7,text='Upload Excel',bg='#787c82',fg='white',cursor='hand2',border=0).place(x=715,y=13)
excelFileInputField = Entry(frameTop,width=58,fg='black',border=1,bg='white',font=('Microsoft YaHei UI Light',15,'bold'))
excelFileInputField.place(x=8,y=14)
################### Frame (Top)[end] #######################################
################### Table (Center)[start] #####################################
columns = ('name','age','gender','position')
frameCenter = Frame(window,width=860,height=315,bg='#f0f0f1')
frameCenter.place(x=40,y=110)
treeScroll = ttk.Scrollbar(frameCenter,orient="vertical")
treeScroll.pack(side=RIGHT,fill="y")
tree = ttk.Treeview(frameCenter,height=13,columns=columns,show="headings",selectmode='browse',yscrollcommand=treeScroll.set)
tree.heading('name',text='Name')
tree.heading('age',text='Age')
tree.heading('gender',text='Gender')
tree.heading('position',text='Position')
tree.place(x=30,y=10)
treeScroll.config(command=tree.yview)
################### Table (Center)[end] #######################################
################### Frame (Bottom)[start] #####################################
frameBottom = Frame(window,width=860,height=60,bg='white')
frameBottom.place(x=40,y=430)
addUserBtn = Button(frameBottom,width=19,pady=7,text='Add User',bg='#57a1f8',fg='white',cursor='hand2',border=0,command= lambda : TestingGui().registerUser()).place(x=30,y=15)
################### Frame (Bottom)[end] #######################################
mainloop()

The use of place can be tricky that's why you should use it only if other geometrymanager fail to achieve what you want. The benefit of the other two geometrymanagers by tkinter is that they calculate cells or parcels for you that you can use and easily recognize by just look at your layout.
I took the time to change your script and placed comments next to changed lines, that should explain what and why I think those changes are necessary.
#import only what you need and avoid wildcard imports due naming conflicts
import tkinter as tk #as tk as short for tkinter
from tkinter import ttk
class TestingGui():
def __init__(self):
print("testing")
def registerUser(self):
userName = "tim"
userAge = "36"
userGender = "male"
userPosition = "softeware engineer"
userInfo = [userName.upper(),userAge,userGender,userPosition]
tree.column(0,anchor='center')
tree.column(1,anchor='center')
tree.column(2,anchor='center')
tree.column(3,anchor='center')
tree.insert('',0,values=userInfo)
if __name__ == '__main__':
window = tk.Tk()
## bonus: leading tk. symbols you are using tk
window.title('Dashboard')
window.geometry('925x500+300+200')
window.configure(bg="#fff")
window.resizable(False,False)
#Window Content
topframe = tk.Frame(window,width=860,height=60,bg='white')
centerframe = tk.Frame(window,width=860,height=315,bg='#f0f0f1')
bottomframe = tk.Frame(window,width=860,height=60,bg='white')
topframe.pack(side=tk.TOP, padx=(40,0),pady=(40,0),fill=tk.X)
centerframe.pack(side = tk.TOP, fill= tk.BOTH, padx=(40,0))
bottomframe.pack(side=tk.BOTTOM, padx=(40,0), fill= tk.X)
## fill = stretch in master
## padx/y are offsets like x/y in place but using their parcels
## keeping content together helps for an overview of content
#frameTop Content
input_field = tk.Entry(
topframe, width=58, fg='black', border=1, bg='white',
font=('Microsoft YaHei UI Light',15,'bold'))
input_field.pack(side=tk.LEFT)
upload_button = tk.Button(
topframe, width=19, pady=7, text='Upload Excel',
bg='#787c82',fg='white',cursor='hand2',border=0)
upload_button.pack(side=tk.TOP)
## seperate the constructor from the geometry method to keep a reference
## split lines for readability compare PEP-8 Style Guide
## dont use camelcase variable names compare PEP-8 Style Guide
## the order of packing matters in this geometry manager
#centerframe Content
treeScroll = ttk.Scrollbar(centerframe,orient="vertical")
treeScroll.pack(side=tk.RIGHT,fill="y")
#tk.RIGHT = tkinter constant
columns = ('name','age','gender','position')
tree = ttk.Treeview(
centerframe, height=13, columns=columns,
show="headings",selectmode='browse',yscrollcommand=treeScroll.set)
tree.heading('name',text='Name')
tree.heading('age',text='Age')
tree.heading('gender',text='Gender')
tree.heading('position',text='Position')
tree.pack(side=tk.TOP, padx=(30,0),pady=10)
treeScroll.config(command=tree.yview)
#bottomframe Content
add_user_button = tk.Button(
bottomframe, width=19, pady=7,text='Add User',
bg='#57a1f8',fg='white',cursor='hand2', border=0)
#command= lambda : TestingGui().registerUser())#DONT DO THIS!
## Only use lambda if needed
## It makes little to no sense to initiate a class in a lambda expression
add_user_button.pack(padx=(30,0),side=tk.LEFT)
#start mainloop
tk.mainloop()#indention correcture

Related

Frame in Tkinter Popup Putting Content into Main Window instead of Popup

I am trying to create an app using the Tkinter Python library, and I created a preferences popup. I want to add checkboxes to it, and I want to do it in Frames via pack().
I want something like this:
Expected Result (IK it's Edited but Proof of Concept)
This is what I'm getting:
Actual Result (Look at Bottom of Image)
This is what I wrote:
# Import Libraries
from tkinter import *
from tkinter import ttk
from tkinter import simpledialog, messagebox
from tkinter.filedialog import asksaveasfile
from pygame import mixer as playsound
from datetime import datetime as date
from time import sleep
import pyttsx3
import json
import os
# Set Initial Window
window = Tk()
window.title("TTSApp")
window.geometry('500x580')
window.resizable(width=False,height=False)
playsound.init()
# Settings and Menu
preferences = {}
def preferencesHandler():
if os.path.exists('preferences.pref'):
preferences = {'AutoSave':True,'AutoSavePrompt':True,'AutoSaveAutomaticLoad':False}
with open('preferences.pref', 'w') as pref:
json.dump(preferences, pref)
else:
preferences = json.load(open('preferences.pref', 'r'))
pref.close()
sessionOptions = {'SessionName':'Untitled','VoiceSpeed':100}
def topmenucommands_file_newsession():
messagebox.showerror("New Session", "I haven't done this yet... you shouldn't even be able to see this...")
def topmenucommands_file_preferences():
preferencesWin = Toplevel(window)
preferencesWin.geometry("350x500")
preferencesWin.title("Preferences")
preferences_autosave = BooleanVar()
preferences_autosaveprompt = BooleanVar()
preferences_autosaveautomaticload = BooleanVar()
def topmenucommands_file_preferences_changed(*args):
with open('preferences.pref') as pref:
preferences['AutoSave'] = preferences_autosave.get()
preferences['AutoSavePrompt'] = preferences_autosaveprompt.get()
preferences['AutoSaveAutomaticLoad'] = preferences_autosaveautomaticload.get()
json.dump(preferences, pref)
pref.close()
Label(preferencesWin, text="Preferences", font=('helvetica', 24, 'bold')).pack()
autosave_container = Frame(preferencesWin,width=350).pack()
Label(autosave_container, text="Create Autosaves:", font=('helvetica', 12, 'bold')).pack(side=LEFT)
ttk.Checkbutton(autosave_container,command=topmenucommands_file_preferences_changed,variable=preferences_autosave,onvalue=True,offvalue=False).pack(side=RIGHT)
window.wait_window(preferencesWin)
pref.close()
def topmenucommands_session_renamesession():
topmenucommands_session_renamesession_value = simpledialog.askstring(title="Rename Session",prompt="New Session Name:")
sessionOptions['SessionName'] = topmenucommands_session_renamesession_value
topmenu = Menu(window)
topmenu_file = Menu(topmenu, tearoff=0)
#topmenu_file.add_command(label="New Session")
#topmenu_file.add_command(label="Save Session")
#topmenu_file.add_command(label="Save Session As...")
topmenu_file.add_command(label="Preferences", command=topmenucommands_file_preferences)
topmenu.add_cascade(label="File", menu=topmenu_file)
topmenu_session = Menu(topmenu, tearoff=0)
topmenu_session.add_command(label="Rename Session", command=topmenucommands_session_renamesession)
topmenu.add_cascade(label="Session", menu=topmenu_session)
# Create All of the Widgets and Buttons and Kiknacks and Whatnot
# Input Window
inputText = Text(window,height=20,width=62)
inputText.pack()
# Label for Speed Slider
speedText = Label(window, text='Voice Speed', fg='black', font=('helvetica', 8, 'bold'))
speedText.pack()
# Speed Slider
speed = Scale(window, from_=50, to=200, length=250, tickinterval=25, orient=HORIZONTAL, command=speedslidersavestate)
speed.set(100)
speed.pack()
# Dropdown for Voice Selection
voice = OptionMenu(window, voiceSelection, *voiceNames.keys())
voice.pack()
# Warning/Notice Label
warning = Label(window, text='', fg='red', font=('helvetica', 12, 'bold'))
warning.pack()
# Container for All Preview and Save (and PreviewRaw)
buttons = Frame(window)
buttons.pack()
# PreviewRaw Button; Huh... There's Nothing Here
# Preview Button
preview = Button(buttons,text='Preview',height=5,width=25,command=preview)
preview.pack(side=LEFT)
# Save Button
save = Button(buttons,text='Save to File',height=5,width=25,command=save)
save.pack(side=RIGHT)
window.config(menu=topmenu)
preferencesHandler()
window.mainloop()
Did I do something wrong or is there a better way to go about this or is this question a mess (this is my first time doing this)? Also, I clipped out all of the unnecessary content.
Edit: Added More Code
I figured it out. Apparently, I needed to pack() the Frame separately.
The answer was:
autosave_container = Frame(preferencesWin,width=350)
autosave_container.pack()
Instead of:
autosave_container = Frame(preferencesWin,width=350).pack()

tkinter - insert text in ScrolledText from external function

I am new to tkinter GUI programming. I tried to search my problem, but I was unable to formulate the correct question without a description of my problem.
I designed a small GUI - for the example here - with a button and a ScrolledText item.
import tkinter as tk
from tkinter import messagebox as msg
from tkinter.ttk import Notebook
from tkinter import filedialog
import tkinter.scrolledtext as tkscrolled
import do_something as ds
import os
import time
class Fatt(tk.Tk):
def __init__(self):
super().__init__()
# window setup
self.title("Test Gui")
self.geometry("1024x768")
self.resizable(0, 0)
# tab
self.notebook = Notebook(self)
# define tabs
res_avg_tab = tk.Frame(self.notebook)
# group nodal averaging
group_avg = tk.LabelFrame(res_avg_tab, text="Perform nodal averaging of all selected DB files")
group_avg.pack(padx=10, pady=10)
# nodal averaging button
self.avg_button = tk.Button(group_avg, text="Perform Nodal Averaging",
command=self.nodal_avg, bg="lightgrey", fg="black", width=50)
self.avg_button.pack(side=tk.TOP, pady=10, padx=10)
# scrolled log-text window
# group LOG
group_log = tk.LabelFrame(res_avg_tab, text="Result Averaging Output (LOG)")
group_log.pack(padx=10, pady=10)
self.avg_log = tkscrolled.ScrolledText(group_log, bg="white", fg="black", height=13, width=110)
self.avg_log.pack(side=tk.TOP, fill=tk.X, padx=10, pady=10)
# status-bar
self.status_text = tk.StringVar(res_avg_tab)
self.status_text.set("---")
self.status = tk.Label(res_avg_tab, textvar=self.status_text,
bd=1, relief=tk.SUNKEN, anchor=tk.W)
self.status.pack(side=tk.BOTTOM, fill=tk.BOTH)
# add everything to tabs
self.notebook.add(res_avg_tab, text="Average Results")
self.notebook.pack(fill=tk.BOTH, expand=True)
def show_cmb_file_creator(self):
pass
def nodal_avg(self):
sel_dbs = ["file1", "file2", "file3"]
# write file-list to log-window
self.avg_log.insert(tk.INSERT, "\nSelected Files for Nodal Averaging:\n")
for i in sel_dbs:
self.avg_log.insert(tk.INSERT, i+'\n')
self.avg_log.see(tk.END)
# if yes --> average results
if msg.askyesno("Nodal Averaging", "Perform nodal averaging with selected db-results?"):
start = time.time()
self.status_text.set("processing...")
self.config(cursor="wait")
self.avg_log.insert(tk.INSERT, "Start nodal averaging - this may take some time...\n")
class_obj = ds.DoSomething(i, self.avg_log)
for i in sel_dbs:
class_obj.do_something()
end = time.time()
overall_time_str = " Overall Averaging RUNTIME: {0:.2f} sec ({1:.1f} min) ".format(end-start, (end-start)/60.0)
self.avg_log.insert(tk.INSERT, "\n{0:*^80}".format(overall_time_str))
self.avg_log.see(tk.END)
self.status_text.set("---")
self.config(cursor="")
def browse_dir(self):
pass
def copy_to_clipboard(self, text=None):
pass
if __name__=="__main__":
fatt = Fatt()
fatt.mainloop()
The button "avg_button" executes the function "nodal_avg" and the main purpose of this function is to instantiate an external class and run a method.
class_obj = ds.DoSomething(i, self.avg_log)
for i in sel_dbs:
class_obj.do_something()
This class contains the main logic of my software and it contains a lot of print outputs.
import tkinter.scrolledtext as tkscrolled
class DoSomething:
def __init__(self, my_file, outp_print=print):
self.my_file = my_file
self.outp_print = outp_print
# my-print function
# for tkinter-log output
def myprint(self, text):
if self.outp_print==print:
print(text)
elif isinstance(self.outp_print, tkscrolled.ScrolledText):
self.outp_print.insert("end", text+'\n')
else:
print("myprint - ERROR: {0}".format(str(self.outp_print)))
def do_something(self):
for i in range(0,100000):
self.myprint("{0:d} - printed output".format(i))
I would like to print the output of the class/method to the ScrolledText window, but I also like to maintain the classic print functionality. Therefore I use the "myprint" method - which is able to use print or ScrolledText.insert for printing (I do not know if this is a smart approach?!).
If I run the code it basically works - but the ScrolledText window does not update on every print, only when the method in the external class is finished - then the output appears.
So my question is - how can I continuously update my ScrolledText window with my output string?
Thank you very much.
Best Regards
Michael

Tkinter Image icon not working or aligning properly in a Labelframe

This question concerns Python's Tkinter.
I first produced this GUI, a simple two-column set of rows in a Labelframe, with an icon on the right:
The above behaviour was correct and expected, based on this following code:
import tkinter as tk
import tkinter.ttk as ttk
from PIL import Image, ImageTk
root = tk.Tk()
icon_colours_fp = r"D:\Dropbox\coding\python\experiments\icon_component.gif"
icon_col = tk.PhotoImage(file=icon_colours_fp)
# icon_col = ImageTk.PhotoImage(Image.open(icon_colours_fp))
tk.Label(root, text="Past").grid(row=0, column=0)
tk.Label(root, text="Today").grid(row=1, column=0)
tk.Label(root, text="Future").grid(row=2, column=0)
_b = ttk.Button(root, image=icon_col)
_b['image'] =icon_col
_b.grid(row=0, column=1)
root.mainloop()
I then re-wrote the code as a class, hoping to produce something similar within a Labelframe:
import tkinter as tk
import tkinter.ttk as ttk
from PIL import Image, ImageTk
class Options(tk.Frame):
def __init__(self, parent):
super().__init__()
main_labelframe = ttk.LabelFrame(parent, text="Test Labelframe")
main_labelframe.pack(fill=tk.BOTH, expand=1)
frame_1 = tk.Frame(main_labelframe)
frame_1_sep = ttk.Separator(main_labelframe, orient=tk.VERTICAL)
frame_2 = tk.Frame(main_labelframe)
frame_1.pack(side=tk.LEFT)
frame_1_sep.pack(side=tk.LEFT, fill=tk.BOTH)
frame_2.pack(side=tk.LEFT)
tk.Label(frame_1, text="Past").grid(row=0, column=0)
tk.Label(frame_1, text="Today").grid(row=1)
tk.Label(frame_1, text="Future").grid(row=2)
icon_colours_fp = r"D:\Dropbox\coding\python\experiments\icon_component.gif"
icon_col = tk.PhotoImage(file=icon_colours_fp)
_b = ttk.Button(frame_2, image=icon_col)
_b['image'] = icon_col
_b.grid(row=0, column=0)
class Gui(tk.Tk):
def __init__(self):
super().__init__()
options = Options(self)
options.pack()
gui = Gui()
gui.mainloop()
The code then failed, in two respects:
The icon fails to appear.
The ttk Button becomes misaligned. (It appears in the centre, whereas by the grid, it should appear at the top.)
The failed code appears as follows:
I have experimented: among others, I changed the geometry manager to .pack(), and changed the parent of ttk.Button, but without success. Would appreciate some pointers as to where I've gone wrong, especially as to the disappearing icon.
You didn't keep a reference to the image. Easiest way here is to change:
icon_col = tk.PhotoImage(file=icon_colours_fp)
b = ttk.Button(frame_2, image=icon_col)
_b['image'] = icon_col
To:
self.icon_col = tk.PhotoImage(file=icon_colours_fp)
b = ttk.Button(frame_2, image=self.icon_col)

Issue with Combobox

excuse the greeness. Trying to build GUI where option selected from Combobox populates text box. Nothing happening. First time programming so appreciate i have made a lot of errors here.
import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
# function to display course selected
def courseDisplay():
box = course.get()
print(box)
# Create instance
win = tk.Tk()
win.resizable(130,130)
win.title("RaceCourse GUI")
# create combobox
course = tk.StringVar()
courseChosen = ttk.Combobox(win,width=60,textvariable=course,state='readonly')
courseChosen['values'] = ("Choose a course","Ascot", "Bath", "Chester")
courseChosen.grid(column=5, row=1,rowspan = 3, columnspan = 3,padx = 300, pady = 40)
courseChosen.current(0)
courseChosen.bind("<<ComboboxSelected>>", courseDisplay)
# create scrolled Text control
scrolW = 46
scrolH = 10
box = scrolledtext.ScrolledText(win, width=scrolW, height=scrolH, wrap=tk.WORD)
box.grid(column=5, row=8, columnspan=3,padx = 300,pady = 10)
# Start GUI
win.mainloop()
Since function courseDisplay is called when some event occurs on combobox (namely, when some option is selected), it should accept one variable (usually called event). So, your function should look like this:
def courseDisplay(event=None):
box = course.get()
print(box)
Of course, You should add another logic for showing test in textbox instead of print.

Tkinter python GUI issue

I am trying to create a menu for my term project and I am running into a issue.
I want a left pane for my controls and on the right there will be visulization but when i run the code an un wanted space is created on the top left area because of the t1.pack() and c1.pack()
Please help me how can i get two clear panes.
Let me know if you didnt get the issue.
Thanks in advance :)
__author__ = 'sagar'
try:
import tkinter as tk # for python 3
except:
#import Tkinter as tk # for python 2
from Tkinter import *
import ttk as ttk
import ctypes
import tkMessageBox
user32 = ctypes.windll.user32
WIDTH,HEIGHT = user32.GetSystemMetrics(0),user32.GetSystemMetrics(1)
WIDTH -= 50
HEIGHT -= 75
class Application: #pygubu.TkApplication
def __init__(self,root):
self.root = root
self.fr_main = Frame(self.root,background="grey",highlightcolor="red")
self.fr_main.grid(row=0,column=0,ipadx=(5*WIDTH)/100,ipady=HEIGHT)
# self.fr_main.pack()
self.btn_Simulator = Button(self.fr_main,text="Simulator")
self.btn_Simulator.grid(row=0,column=0,pady=10)
# self.btn_Simulator.pack()
self.btn_Analysis = Button(self.fr_main,text="Analysis")
self.btn_Analysis.grid(row=0,column=1,pady=10)
# self.btn_Analysis.pack()
self.fr_second = Frame(self.root,background="black",highlightcolor="red")
self.fr_second.grid(row=0,column=1,ipadx=(40*WIDTH)/100,ipady=HEIGHT)
self.nb_main = ttk.Notebook(self.fr_second)
self.nb_main.pack(expand=1,fill=BOTH)
f1 = Frame(self.nb_main)
f2 = Frame(self.nb_main)
self.nb_main.add(f2,text="Canvas")
self.nb_main.add(f1,text="Calculation")
t1 = Text(f1)
c1 = Canvas(f2)
c1.configure(background="black")
t1.pack(expand=1,fill=BOTH)
c1.pack(expand=1,fill=BOTH)
def __controlState__(self,control,state):
control.configure(state =state)
def showName(self,Enableframe,DisableFrame):
for child in Enableframe.winfo_children():
Enableframe.config(relief = RAISED)
self.__controlState__(child,"active")
for child in DisableFrame.winfo_children():
self.__controlState__(child,"disable")
if __name__ == '__main__':
root = Tk()
app = Application(root)
root.title("Network Simulator")
root.geometry('%dx%d+%d+%d' % (WIDTH,HEIGHT,0,0))
root.mainloop()
Please help me how can i get two clear panes.
The absolute simplest way to get two frames side-by-side that take up the whole height of the window is to use pack with appropriate options:
self.fr_main.pack(side="left", fill="both", expand=True)
self.fr_second.pack(side="left", fill="both", expand=True)
The key is that the first one needs to be on the left or right side, filling in the "y" direction. After that, the second one can be on any side as long as it fills in both directions.
If you want to use grid, make sure to include the sticky option so that the frames fill the space given to them:
self.fr_main.grid(row=0, column=0, sticky="nsew")
self.fr_second.grid(row=0, column=0, sticky="nsew")
With grid, you'll also want to give the columns weight so that grid knows how to allocate extra space
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_columnconfigure(1, weight=1)

Categories