Question
How can I have a scrollbar which moves an entire Tkinter frame? Note: I am using Python 2.7.3.
Code and Explanation
I have this code for defining the scrollbar
scrollbar = Scrollbar(soeg)
scrollbar.pack(side=RIGHT, fill="y")
And this code for defining the textframes
h = 0
s = 0
for i in dom_nodup:
abc = dom_nodup[h]
text = Text(soeg, bg="brown", fg="white", height="10", width="60")
text.insert(INSERT, "%s \n" % abc[0])
text.insert(END, "%s \n\n\n" % abc[1])
text.pack()
h += 1
s += 1
A new textframe is created for each text entity for later easier overview (planning on having a button to show/hide the input).
The scrollbar is present but is not functional
I would recommend that you use the ScrolledText widget. It automatically adds a scrollbar to each text widget, and has the same arguments as Text. Here is a brief example of how to do it.
from Tkinter import * #Import the Tkinter module
from ScrolledText import ScrolledText #import the scrolled text module
message = "I \n am \n scroll \n able. \n\n\n\n\n\n Yes I am!"
class Application(Frame): #Create a frame for the widgets
def __init__(self, master): #initialize the grid and widgets
Frame.__init__(self,master)
self.grid()
self.widgets()
def widgets(self):
self.mytext = ScrolledText(self, width = 10) #Creates the widget
self.mytext.grid() #Places it
root = Tk()
root.title("My Text Example")
#make my screen dimensions work
root.geometry("500x1000")
app = Application(root)
root.mainloop()
For more information, please see the Tkinterbook and this question.
To make a scrollbar functional you must do two things: you must tell it which scrollable widget to scroll, and you must tell the scrollable widget which scrollbar to update with the current position.
scrollbar.configure(command=text.yview)
text.configure(yscrollcommand=scrollbar.set)
Related
I was curious if it is possible to create an clickable element that contains multiple elements?
ttk.Button appears to take text or an image.
I would like to have a clickable element that will have 8 text items and 2 images inside it. Clicking anywhere in that element will trigger the same backend method.
Any code examples would be helpful as still wading through TKinter -- only my second project to use it.
Use bindtags and give the same tag for all widgets that you want to be clickable, then use bind_class to bind all the widgets.
Here's an example
import tkinter as tk
def clicked(event):
print("Clicked !")
class ClickableElement(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bindtags('Click') # or pass a list if you want to have multiple tag names
for x in range(8):
lbl = tk.Label(self, text=f"Text {x}")
lbl.bindtags('Click')
lbl.pack()
root = tk.Tk()
root.geometry("200x200")
frame = ClickableElement(root) # or tk.Frame(root, class_='Click')
frame.pack(fill='both', expand=True)
frame2 = tk.Frame(root, bg='red')
frame2.pack(expand=True, fill='both')
root.bind_class('Click', "<Button-1>", clicked)
root.mainloop()
The above example will make both text and Frame clickable. You can expand on this to include images. Alternatively, You can use bind on each widget inside the frame.
What you can do is get the children of the tk.Frame and bind them to a function.
from tkinter import *
from tkinter import messagebox
class ClickFrame:
def __init__(self,root):
self.root = root
self.root.geometry("700x500")
Label(self.root,text="Clickable Frame!",font=("arial",15)).pack(fill=X,side=TOP)
self.Frame1 = Frame(self.root,bg="light grey")
self.Frame1.pack(fill=BOTH,expand=1,padx=100,pady=100)
for i in range(8):
Label(self.Frame1,text=f"This is Label {i}").pack(pady=5,anchor="w",padx=5)
self.Frame1.bind("<Button-1>",self.detect_click)
for wid in self.Frame1.winfo_children():
wid.bind("<Button-1>",self.detect_click)
def detect_click(self,event,*args):
messagebox.showerror("Clicked",f"Clicked the widget {event.widget}")
print(event.widget,type(event.widget))
root=Tk()
ob=ClickFrame(root)
root.mainloop()
You also can use bind_all() to bind . However, this will bind everything in the window.
from tkinter import *
from tkinter import messagebox
class ClickFrame:
def __init__(self,root):
self.root = root
self.root.geometry("700x500")
Label(self.root,text="Clickable Frame!",font=("arial",15)).pack(fill=X,side=TOP)
self.Frame1 = Frame(self.root,bg="light grey")
self.Frame1.pack(fill=BOTH,expand=1,padx=100,pady=100)
for i in range(8):
Label(self.Frame1,text=f"This is Labe {i}").pack(pady=5,anchor="w",padx=5)
self.Frame1.bind_all("<Button-1>",self.detect_click)
def detect_click(self,event,*args):
messagebox.showerror("Clicked",f"Clicked the widget {event.widget}")
root=Tk()
ob=ClickFrame(root)
root.mainloop()
I am working on this table in tkinter made from a bunch of treeveiw widgets. The idea is to get a table where I can add lines, select lines and edit them. In the code below you can add lines to the table by pushing the button. I now want to control the height of each row by configuring the style. However, when I use style the alignment of the treeview widgets is messed up, see attached picture. Any suggestions how to fix this?
EDIT: The problem is the added space between the widgets.
The code for the table is:
from tkinter import *
from tkinter import ttk
class MyApp(Tk):
def __init__(self):
super(MyApp, self).__init__()
self.geometry('950x500+100+100')
self.NewTree = []
label = Label(self,text='Table with some data', font=("Arial Bold", 25))
label.pack()
self.addLine()
master_frame = Frame(self, bd=3, relief=RIDGE)
master_frame.pack(side=BOTTOM)
# Create a frame for the canvas and scrollbar(s).
frame2 = Frame(master_frame)
frame2.pack(side = BOTTOM)
# Add a canvas in that frame.
self.canvas = Canvas(frame2)
self.canvas.grid(row=0, column=0)
# Create a vertical scrollbar linked to the canvas.
vsbar = Scrollbar(frame2, orient=VERTICAL, command=self.canvas.yview)
vsbar.grid(row=0, column=1, sticky=NS)
self.canvas.configure(yscrollcommand=vsbar.set)
# Create a frame on the canvas to contain the buttons.
self.table_frame = Frame(self.canvas)
# Create canvas window to hold the buttons_frame.
self.canvas.create_window((0,0), window=self.table_frame, anchor=NW)
def addLine(self):
#Make button for adding step
bt = Button(self,text='Add Line',command=lambda: self.addLineMethod())
bt.config(width=9, height=1)
bt.pack()
def addLineMethod(self):
lineNumber = int(len(self.NewTree)/5)
for index in range(5):
s = ttk.Style()
s.configure('MyStyle.Treeview', rowheight=25)
self.NewTree.append(ttk.Treeview(self.table_frame, height=1,show="tree",columns=("0"),style='MyStyle.Treeview'))
#Works fine when using this line instead of those above
#self.NewTree.append(ttk.Treeview(self.table_frame, height=1,show="tree",columns=("0")))
self.NewTree[index+5*lineNumber].grid(row=lineNumber, column=index+1)
self.NewTree[index+5*lineNumber]['show'] = ''
item = str(index+5*lineNumber)
self.NewTree[index+5*lineNumber].column("0", width=180, anchor="w")
self.NewTree[index+5*lineNumber].insert("", "end",item,text=item,values=('"Text text text"'))
self.table_frame.update_idletasks() # Needed to make bbox info available.
bbox = self.canvas.bbox(ALL) # Get bounding box of canvas with Buttons.
# Define the scrollable region as entire canvas with only the desired
# number of rows and columns displayed.
self.canvas.configure(scrollregion=bbox, width=925, height=200)
app = MyApp()
app.mainloop()
Her is a picture of the table with some lines.
Put the style configuration in the __init__() function and the effect will go away. I'm not clear as to why this works.
def __init__(self):
...
s = ttk.Style()
s.configure('MyStyle.Treeview', rowheight=20)
I'm making a card game (called monster master) to develop my python skills, specifically OOP.
I have a GUI that has a few static objects: Player 1's side of the table, Player 2's side, a wee line in the middle and I'm now trying to implement an 'end turn' button.
I have tried a lot of different things to try to get this button to display, but I just can't get it to appear even if there are no errors. Just saying that there are a few commented out lines that I've temporarily taken away for the sake of trying to understand the problem with this button.
Here's the code that I'm currently using to try:
def RunGame():
class App():
"""docstring for App"""
def draw():
# Setting up canvas dimensions
canvas_width = 640
canvas_height = 480
master = Toplevel()
master.title("Monster Master by Charles Cameron - Game")
master.resizable(width=False, height=False)
master.geometry("640x480")
w = Canvas(master,
width=canvas_width,
height=canvas_height)
w.pack()
# Drawing static objects
CentrePoints = [(0, canvas_height/2), (canvas_width/2, canvas_height/2),
(canvas_width, canvas_height/2)]
#Left, centre and right centres (save me from retyping them)
Player1Area = w.create_rectangle(CentrePoints[0], canvas_width,
canvas_height, fill="#303AFE") #Player1 Area
Player2Area = w.create_rectangle(0, 0, CentrePoints[2],
fill="#C31B1B") #Player2 Area
Barrier = w.create_line(CentrePoints[0], CentrePoints[2],
fill="#0090E3", width=20) # Centre barrier
# class GameBtn():
# class EndTurnBtn():
# def __init__(self, BtnName, master):
BtnName = Button(master, bg="white", command=lambda:print("Clicked!"))
image = ImageTk.PhotoImage(file="imgs\EndTurn.png")
BtnName.config(image=image, width="70", height="70")
BtnName.pack(side=RIGHT)
# ChangeTurn = GameBtn.EndTurnBtn('ChangeTurn', master)
master.mainloop()
Window = App()
App.draw()
The button code for the actual button worked fine when I tried it in its own script but stopped working when I put it inside this program.
Hope it's not too dumb a question to ask, quite an amateur at python still and honestly can't find the answer anywhere online.
Many thanks
Your button exists, but it's past the edge of the window. This is because you made your window 640x480, and then completely filled it with a 640x480 canvas. Remove the master.geometry("640x480") line and the window will stretch to contain both your canvas and your button.
You might be thinking "but I don't want the button to appear to the side of the canvas. I want the button to be on the canvas. The canvas only really exists because I wanted to color sections of the background". Embedding widgets on a canvas is possible using create_window (see How to place a widget in a Canvas widget in Tkinter?), but it may be more practical to create colored backgrounds by stacking Frame objects together and giving them individual colors. Example:
from tkinter import Tk, Frame, Button
root = Tk()
root.geometry("640x480")
top_player_frame = Frame(root, height=230, bg="red")
barrier = Frame(root, height=20, bg="green")
bottom_player_frame = Frame(root, height = 230, bg="blue")
#configure column 0 so frames can stretch to fit width of window
root.columnconfigure(0, weight=1)
top_player_frame.grid(row=0, sticky="we")
barrier.grid(row=1, sticky="we")
bottom_player_frame.grid(row=2, sticky="we")
bottom_player_end_turn_button = Button(bottom_player_frame, text="End Turn")
#use `place` here because `pack` or `grid` will collapse the frame to be only as tall as all the widgets it contains, i.e. just this button
bottom_player_end_turn_button.place(x=10,y=10)
root.mainloop()
Result:
I am trying to display an image in python using the tkinter canvas option. However, if I input it in a class, like below, it doesn't give an error but also doesn't show my image. The buttons are displayed correctly though. Also, if I take the code for generating this image out of the class it works correctly. I can't seem to find out what the problem is.
import Tkinter as tk
from Tkinter import *
class Board(tk.Frame):
def __init__(self,parent):
frame = Frame(parent)
frame.pack()
tk.Frame.__init__(self,parent)
frame2 = Frame(frame)
frame2.pack()
c=Canvas(frame2)
c.pack(expand=YES,fill=BOTH)
background=PhotoImage(file='Board.gif')
c.create_image(100,100,image=background,anchor='nw')
button = Button(frame, text="Next turn", command=self.next_turn)
button.pack()
button = Button(frame, text="Roll the dice", command=self.roll)
button.pack()
....
root = Tk()
board = Board(root)
board.pack()
root.mainloop()
You have to keep a reference to the PhotoImage. This is just and example (you can also use self.background instead of c.background):
c = Canvas(frame2)
c.pack(expand=YES,fill=BOTH)
c.background = PhotoImage(file='Board.gif')
c.create_image(100,100,image=c.background,anchor='nw')
Hello and happy new year,
i'm trying to build a user interface and have a
problem with the Tix.ScrolledListbox.
(Python 2.6.5, Tix 8.4.3, Windows XP)
I wanted to use it to show items of varying number,
depending on a previous choice made by the user.
It's a GIS thing: the user picks a layer from a
ComboBox, presses a button and the Listbox
shows all fieldnames retrieved from the attribute
table. So for some layers there are 5, for others
30 fields. In principle it works.
But the scrollbar next to the listbox remains
grey with no function.
In a small test snippet, where, after pressing
a button, a random (0..100) number of items is shown
in the listbox the scrollbar works.
I have no idea.
Anybody had this before?
Edit: The following samplecode shows a not scrollable
scrolledListbox when arcpy is imported
import Tix
import random
import arcpy
class SampleApp(object):
def __init__(self):
self.window = Tix.Tk()
#listbox
self.lbx = Tix.ScrolledListBox(self.window, width = 30)
self.lbx.listbox.insert(Tix.END, " ")
self.lbx.listbox.bind("<<ListboxSelect>>", self.Choose)
#button to generate new list
self.btn = Tix.Button(self.window, text = "new list",
command = self.NewList)
#label shows chosen list item
self.lbl = Tix.Label(self.window, text = " ", bg = "white")
#pack
self.btn.pack(pady = 10)
self.lbx.pack(side="top", fill="both", expand=True, pady = 10)
self.lbl.pack(pady = 10)
self.window.mainloop()
#function to generate new listbox items on button command
def NewList(self):
self.lbx.listbox.delete(0, Tix.END)
r = random.randint(1, 30)
for i in range(r):
self.lbx.listbox.insert(Tix.END, i)
#event to show selected item in label
def Choose(self, event):
widget = event.widget
selection = widget.curselection()
value = widget.get(selection[0])
self.lbl.config(text = value)
sa = SampleApp()
Did you attach the scrollbar to the listbox?
from Tkinter import *
root = Tk()
scrollbar = Scrollbar(root)
scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(root)
listbox.pack()
for i in range(100):
listbox.insert(END, i)
# attach listbox to scrollbar
listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=listbox.yview)
mainloop()
stolen from: http://effbot.org/zone/tkinter-scrollbar-patterns.htm
edit: Tix.ScrolledListBox works differently so don't mix it up with the above solution.