How to center a Label in a scrollable Frame in tkinter? - python

I am trying to create a label (for now and different widgets in the future) inside a scrollable tkinter Frame but it always shows in the upper left corner of the window. My code is shown below and any help would be appreciated. Thank you.
import tkinter as tk
from tkinter import *
from tkinter import ttk
class ScrollableFrame(ttk.Frame):
def __init__(self, container, *args, **kwargs):
super().__init__(container, *args, **kwargs)
self.canvas = tk.Canvas(self)
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
self.scrollable_frame = ttk.Frame(self.canvas)
self.scrollable_frame.bind(
"<Configure>",
lambda e: self.canvas.configure(
scrollregion=self.canvas.bbox("all")
)
)
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor=NW)
self.canvas.pack(fill="both")
def _on_mousewheel(self, event):
self.canvas.yview_scroll(-1*int(event.delta/120), "units")
if __name__ == "__main__":
root = tk.Tk()
root.geometry("600x150")
frame = ScrollableFrame(root)
for i in range(20):
a = tk.Label(frame.scrollable_frame, text="Number: "+str(i))
a.pack(side=TOP, anchor=W, fill=X, expand=YES)
frame.pack(fill="both")
root.mainloop()

Related

How to create a sidebar with buttons to hide and show frames in Tkinter

I'm trying to create a basic application which has a sidebar that contains menu buttons. When each button is pressed, it changes the contents of the main content area to display the relevant content. I have created the layout, shown below. For reference, I have also added a wireframe of how I want the GUI to look.
The problem is that I cannot get the buttons to hide/unhide the relevant frames. How would this be done? And a further question, how would I embed a matplotlib chart into one of these frames? More specifically, when I press the 'Markets' button, the program would display a matplotlib chart of the Facebook stock, using an API. Any help would be appreciated.
import tkinter as tk
from tkinter import *
from tkinter import ttk
from tkinter.ttk import *
# Define the function for raising a frame ontop of another, in order for the
# respective frame to be displayed in the mainarea
def raise_frame(frame):
frame.tkraise()
# Initialise the tkinter root window, name it, and set its size
root = Tk()
root.geometry('500x500')
root.title('Stock Trading Simulator')
# Create the tkinter frame for the sidebar, and place it in the root window
sidebar = tk.Frame(root, width=200, bg='white', height=500, relief='raised', borderwidth=2)
sidebar.pack(expand=False, fill='both', side='left', anchor='nw')
# Create the tkinter frame for the main content area
mainarea = tk.LabelFrame(root, bg='#CCC', width=500, height=500)
mainarea.pack(expand=True, fill='both', side='right')
# Create the tkinter frame for the market
marketFrame = tk.Frame(mainarea, relief='sunken', bg='Red')
# Create the tkinter frame for the portfolio
portfolioFrame = Frame(mainarea, relief='sunken')
# Create the tkinter frame for the portfolio pending orders
pendingOrdersFrame = Frame(mainarea, relief='sunken')
# Create the tkinter frame for the portfolio trade history
tradeHistoryFrame = Frame(mainarea, relief='sunken')
for frame in (marketFrame, portfolioFrame, pendingOrdersFrame, tradeHistoryFrame):
frame.grid(row=0, column=0, sticky='news')
# Create button widgets for sidebar
btnMarket = Button(sidebar, text = 'Markets',
command=lambda:raise_frame(marketFrame)).grid(row = 0, column = 0,)
btnPortfolio = Button(sidebar, text = 'Portfolio',)
btnPortfolioSummary = Button(sidebar, text = 'Summary',)
btnPortfolioPendingOrders = Button(sidebar, text = 'Pending Orders',)
btnPortfolioTradeHistory = Button(sidebar, text = 'Trade History',)
# Map buttons to the tkinter grid to display them in the sidebar frame
btnPortfolio.grid(row = 2, column = 0)
btnPortfolioSummary.grid(row = 3, column = 0, padx=(15,0))
btnPortfolioPendingOrders.grid(row = 4, column = 0, padx=(15,0))
btnPortfolioTradeHistory.grid(row = 5, column = 0, padx=(15,0))
root.mainloop()
See the example, to make other frames just follow the model : frm_ctt_(...).py
Note: I suggest you install and use ttkbootstrap
python -m pip install git+https://github.com/israel-dryer/ttkbootstrap
main.py
from tkinter import *
#from tkinter import ttk
from ttkbootstrap import ttk
from frm_ctt_0 import *
#python -m pip install git+https://github.com/israel-dryer/ttkbootstrap
#pip3 install git+https://github.com/israel-dryer/ttkbootstrap
#https://ttkbootstrap.readthedocs.io/en/latest/gettingstarted/installation/
#class Functions(Tk):
class Functions(ttk.Window):
def unload_frame_content(self):
#self.panedwindow_left.remove(self.frame_content)
self.frame_content.pack_forget()
return
def load_content_0(self, event=None):
self.unload_frame_content()
self.frame_right0()
return
def load_content_1(self, event=None):
self.unload_frame_content()
self.frame_right1()
return
class MainFrame(Functions):
def __init__(self):
#Tk.__init__(self)
ttk.Window.__init__(self, themename='pulse')
self.geometry('500x300')
self.title('Tkinter')
self.set_frame_left()
self.frame_right0()
return
def set_frame_left(self):
self.frame_left = ttk.Frame(self)
self.frame_left.pack(side=LEFT, fill=Y)
self.frame_ctt_left = ttk.Frame(self.frame_left, bootstyle='info')
self.frame_ctt_left.pack(fill=BOTH, expand=1, ipadx=10)
self.btn0 = ttk.Button(self.frame_ctt_left, text='One', command=self.load_content_0)
self.btn0.pack(pady=2)
self.btn1 = ttk.Button(self.frame_ctt_left, text='Two', command=self.load_content_1)
self.btn1.pack(pady=2)
return
def frame_right0(self):
self.frame_content = ttk.Frame(self)
self.frame_content.pack(fill=BOTH, expand=1)
#self.panedwindow_left.add(self.frame_content)
data= {'var_text': 'Hellow World!'}
self.ctt = Content0(self.frame_content, data=data)
self.ctt.pack(fill=BOTH, expand=1)
return
def frame_right1(self):
self.frame_content = ttk.Frame(self)
self.frame_content.pack(fill=BOTH, expand=1)
#self.panedwindow_left.add(self.frame_content)
ttk.Label(self.frame_content, text='Content Two').pack()
return
if __name__== '__main__':
app = MainFrame()
app.mainloop()
frm_ctt_0.py
from tkinter import *
import ttkbootstrap as ttk
class Func(ttk.Frame):
def test(self):
print('hi')
class Content0(Func):
def __init__(self, parent, *args, **kwargs):
self.data = kwargs.pop('data', None)
ttk.Frame.__init__(self, parent, *args, **kwargs)
self.set_widgets()
return
def set_widgets(self):
self.frm_r = ttk.Frame(self)
self.frm_r.place(relx=0, rely=0, relwidth=1, relheight=1)
self.frm_r_top = ttk.Frame(self.frm_r)
self.frm_r_top.pack(side=TOP)
ttk.Label(self.frm_r_top, text=self.data['var_text']).pack()
self.btn = ttk.Button(self.frm_r_top, text='Click', command=self.test)
self.btn.pack()

Scrolling Notebook Tabs Tkinter

I want to make a lot of notebook tabs, and I want to put them in canvas and to add a horizontal scrollbar so that I can scroll trough them.
I set the canvas size, but canvas size keep changing when I add new tab. Also, scrollbar does not work, can you tell me what am I doing wrong?
The program does not show me any error. This is the code:
from tkinter import *
from tkinter import ttk
myApp = Tk()
myApp.title(" Program ")
myApp.geometry("900x500")
CanvasTabs = Canvas(myApp, width=50, height=50)
CanvasTabs.grid(row=0,column=0)
tabs = ttk.Notebook(CanvasTabs, width=100, height=100)
tab1 = ttk.Frame(tabs)
tabs.add(tab1,text=" Tab 1 ")
tab2 = ttk.Frame(tabs)
tabs.add(tab2,text=" Tab 2 ")
tab3 = ttk.Frame(tabs)
tabs.add(tab3,text=" Tab 3 ")
tab4 = ttk.Frame(tabs)
tabs.add(tab4,text=" Tab 4 ")
hbar=Scrollbar(CanvasTabs,orient=HORIZONTAL)
hbar.pack(side=TOP,fill=X)
hbar.config(command=CanvasTabs.xview)
CanvasTabs.config(xscrollcommand=hbar.set)
tabs.pack(expand=1, fill="both")
myApp.mainloop()
I code a widget to fix the problem. Here is a real solution:
https://github.com/muhammeteminturgut/ttkScrollableNotebook
# -*- coding: utf-8 -*-
# Copyright (c) Muhammet Emin TURGUT 2020
# For license see LICENSE
from tkinter import *
from tkinter import ttk
class ScrollableNotebook(ttk.Frame):
def __init__(self,parent,*args,**kwargs):
ttk.Frame.__init__(self, parent, *args)
self.xLocation = 0
self.notebookContent = ttk.Notebook(self,**kwargs)
self.notebookContent.pack(fill="both", expand=True)
self.notebookTab = ttk.Notebook(self,**kwargs)
self.notebookTab.bind("<<NotebookTabChanged>>",self._tabChanger)
slideFrame = ttk.Frame(self)
slideFrame.place(relx=1.0, x=0, y=1, anchor=NE)
leftArrow = ttk.Label(slideFrame, text="\u25c0")
leftArrow.bind("<1>",self._leftSlide)
leftArrow.pack(side=LEFT)
rightArrow = ttk.Label(slideFrame, text=" \u25b6")
rightArrow.bind("<1>",self._rightSlide)
rightArrow.pack(side=RIGHT)
self.notebookContent.bind( "<Configure>", self._resetSlide)
def _tabChanger(self,event):
self.notebookContent.select(self.notebookTab.index("current"))
def _rightSlide(self,event):
if self.notebookTab.winfo_width()>self.notebookContent.winfo_width()-30:
if (self.notebookContent.winfo_width()-(self.notebookTab.winfo_width()+self.notebookTab.winfo_x()))<=35:
self.xLocation-=20
self.notebookTab.place(x=self.xLocation,y=0)
def _leftSlide(self,event):
if not self.notebookTab.winfo_x()== 0:
self.xLocation+=20
self.notebookTab.place(x=self.xLocation,y=0)
def _resetSlide(self,event):
self.notebookTab.place(x=0,y=0)
self.xLocation = 0
def add(self,frame,**kwargs):
if len(self.notebookTab.winfo_children())!=0:
self.notebookContent.add(frame, text="",state="hidden")
else:
self.notebookContent.add(frame, text="")
self.notebookTab.add(ttk.Frame(self.notebookTab),**kwargs)
def forget(self,tab_id):
self.notebookContent.forget(tab_id)
self.notebookTab.forget(tab_id)
def hide(self,tab_id):
self.notebookContent.hide(tab_id)
self.notebookTab.hide(tab_id)
def identify(self,x, y):
return self.notebookTab.identify(x,y)
def index(self,tab_id):
return self.notebookTab.index(tab_id)
def insert(self,pos,frame, **kwargs):
self.notebookContent.insert(pos,frame, **kwargs)
self.notebookTab.insert(pos,frame,**kwargs)
def select(self,tab_id):
self.notebookContent.select(tab_id)
self.notebookTab.select(tab_id)
def tab(self,tab_id, option=None, **kwargs):
return self.notebookTab.tab(tab_id, option=None, **kwargs)
def tabs(self):
return self.notebookContent.tabs()
def enable_traversal(self):
self.notebookContent.enable_traversal()
self.notebookTab.enable_traversal()
Taking Bryan's example on this post and modifying it to include your Notebook code we get a functioning scrollbar that will allow you to scroll over your Notebook widget if it exceeds the limit of the window.
Bryan's example uses the pack() geometry manager however I personally find grid() easier to visualize so I replace pack with grid() in my example.
UPDATE:
import tkinter as tk
import tkinter.ttk as ttk
class Example(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.canvas = tk.Canvas(self, borderwidth=0)
self.frame = tk.Frame(self.canvas)
self.vsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
self.vsb.grid(row=1, column=0, sticky="nsew")
self.canvas.configure(xscrollcommand=self.vsb.set)
self.canvas.grid(row=0, column=0, sticky="nsew")
self.canvas.create_window((3,2), window=self.frame, anchor="nw", tags="self.frame")
self.frame.bind("<Configure>", self.frame_configure)
self.populate()
def populate(self):
tabs = ttk.Notebook(self.frame, width=100, height=100)
for tab in range(50):
tabs.add(ttk.Frame(tabs), text=" Tab {} ".format(tab))
tabs.grid(row=0, column=0, sticky="ew")
def frame_configure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
app = Example()
app.mainloop()
Updated results:
Per your request in the comments here is a Non-OOP example:
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
canvas = tk.Canvas(root, borderwidth=0)
frame = tk.Frame(canvas)
vsb = tk.Scrollbar(root, orient="horizontal", command=canvas.xview)
vsb.grid(row=1, column=0, sticky="nsew")
canvas.configure(xscrollcommand=vsb.set)
canvas.grid(row=0, column=0, sticky="nsew")
canvas.create_window((3,2), window=frame, anchor="nw", tags="frame")
tabs = ttk.Notebook(frame, width=100, height=100)
for tab in range(50):
tabs.add(ttk.Frame(tabs), text=" Tab {} ".format(tab))
tabs.grid(row=0, column=0, sticky="ew")
def frame_configure(event):
global canvas
canvas.configure(scrollregion=canvas.bbox("all"))
frame.bind("<Configure>", frame_configure)
root.mainloop()

Text widgets inline in scrollable frame

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()

Tkinter canvas not scrolling

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.

The horizontal scrollbar didn't work in Tkinter

I want to create a GUI program base on tkinter. One of the widgets is Text. I want to add a horizontal scrollbar in it, but it didn't work.
Where did I make a mistake?
from Tkinter import *
import tkFont
class DpWin(object):
def run(self):
root=Tk()
root.geometry('768x612')
title='dp'
root.title(title)
xscrollbar = Scrollbar(root, orient=HORIZONTAL)
xscrollbar.pack(side=BOTTOM, fill=X)
yscrollbar = Scrollbar(root)
yscrollbar.pack(side=RIGHT, fill=Y)
text = Text(root,xscrollcommand=xscrollbar.set,yscrollcommand=yscrollbar.set)
text.pack()
xscrollbar.config(command=text.xview)
yscrollbar.config(command=text.yview)
text.insert(END,'a'*999)
mainloop()
def start(self):
self.b_start.config(state=DISABLED)
self.b_stop.config(state=ACTIVE)
def stop(self):
self.b_stop.config(state=DISABLED)
self.b_start.config(state=ACTIVE)
if __name__=='__main__':
win=DpWin()
win.run()
I've modified your code according to here. There are 2 main differences.
I made it so the textbox doesn't wrap. If you wrap text, there is nothing for the horizontal scrollbar to scroll to.
I used the grid geometry manager on a frame to keep the scrollbars and text widgets together. The advantage to using .grid is that you actually get scrollbars which are the correct width/height (something you can't achieve with pack).
...
from Tkinter import *
import tkFont
class DpWin(object):
def run(self):
root=Tk()
root.geometry('768x612')
title='dp'
root.title(title)
f = Frame(root)
f.pack()
xscrollbar = Scrollbar(f, orient=HORIZONTAL)
xscrollbar.grid(row=1, column=0, sticky=N+S+E+W)
yscrollbar = Scrollbar(f)
yscrollbar.grid(row=0, column=1, sticky=N+S+E+W)
text = Text(f, wrap=NONE,
xscrollcommand=xscrollbar.set,
yscrollcommand=yscrollbar.set)
text.grid(row=0, column=0)
xscrollbar.config(command=text.xview)
yscrollbar.config(command=text.yview)
text.insert(END, 'a'*999)
mainloop()
def start(self):
self.b_start.config(state=DISABLED)
self.b_stop.config(state=ACTIVE)
def stop(self):
self.b_stop.config(state=DISABLED)
self.b_start.config(state=ACTIVE)
if __name__=='__main__':
win=DpWin()
win.run()
There is one comment regarding making both x and y scrollbars work within the pack framework. Here is a minimal example:
import tkinter as tk
from tkinter import X, Y, BOTTOM, RIGHT, LEFT, Y, HORIZONTAL
class TextExample(tk.Frame):
def __init__(self, master=None):
super().__init__()
sy = tk.Scrollbar(self)
sx = tk.Scrollbar(self, orient=HORIZONTAL)
editor = tk.Text(self, height=500, width=300, wrap='none')
sx.pack(side=BOTTOM, fill=X)
sy.pack(side=RIGHT, fill=Y)
editor.pack(side=LEFT, fill=Y)
sy.config(command=editor.yview)
sx.config(command=editor.xview)
self.pack()
def main():
root = tk.Tk()
root.geometry("800x500+0+0")
app = TextExample(master=root)
root.mainloop()
if __name__ == '__main__':
main()

Categories

Resources