Wrapping a Tkinter program in a scroll bar - python

I am trying to incorporate a scrollbar in my Tkinter window so as I add more things the user can scroll down. From what I've read so far it seems I need to use a Listbox widget but running into trouble adding the scrollbar as it is only getting added to the bottom of the program and not wrapping the entire thing? My code is below.
import Tkinter as Tk
from Tkinter import StringVar
class SampleApp(Tk.Tk):
def __init__(self):
Tk.Tk.__init__(self)
self.button = Tk.Button(self, text="Get", command=self.on_button)
self.button.pack()#place(x=150, y=600)
####Name#####
labelText=StringVar()
labelText.set(" Name")
labelDir=Tk.Label(self, textvariable=labelText, height=1)
labelDir.pack()
directory=StringVar(None)
self.name =Tk.Entry(self,textvariable=directory,width=25)
self.name .pack()
def on_button(self):
with open('filename.html', 'w') as myfile:
myfile.write('The button worked')
app = SampleApp()
scrollbar = Tk.Scrollbar(app)
scrollbar.pack( side = Tk.RIGHT, fill=Tk.Y )
app.mainloop()

The issue is that the Scrollbar widget is only implemented for Canvas, Listbox, and Text widgets. The Scrollbar must be configured to one of those widgets, and then you add widgets to that parent. Unfortunately, it's not the most straightforward widget to use. See Effbot's Scrollbar page for an example.

Related

Don't know how to apply mainloop() in my app

I have programmed a 2600 lines application which worked great when running from the IDE. Now I have created an executable via Pyinstaller and now the GUI does not start. The application starts and disappears quickly. I do not get any errors (anymore, solved them), however this problem remains. I think it has to do with missing the mainloop() in my application, which I don't know how to apply in this particular case. Usually its like this:
root = tk.Tk()
root.mainloop()
In my case I created a class for my window adding a menubar and label as statusbar (the latter not shown in my code below). This makes me assigning this class being a Tk() to main_window. Where do I put the mainloop() without getting an error?
I already tried:
main_window.mainloop()
since main_window is the window where all frames put on to, but then I get the following error in the IDE:
main_window.mainloop() AttributeError: 'AppWindow' object has no attribute 'mainloop'
How do I apply the mainloop() into my application without getting the above mentioned error? Or how do I get my GUI to be working in a different manner? Both answers are welcome.
Here is the necessary code to know:
import tkinter as tk
class AppWindow():
def __init__(self, master):
self.master = master
master.title("Basic Application")
master.geometry("1060x680")
master.grid_propagate(False)
#create drop down menu
self.menubar = tk.Menu(master) # main menubar
#Add filemenu
self.filemenu = tk.Menu(self.menubar, tearoff=0) #sub menu
self.filemenu.add_separator() #create a bar in the menu
self.filemenu.add_command(label="Quit", command=master.destroy) #Add submenu item
self.menubar.add_cascade(label="File", menu=self.filemenu) #Add submenu to menubar
self.master.config(menu=self.menubar) #Show menu
class FrameOne(tk.Frame):
def __init__(self, parent):
super().__init__()
self["borderwidth"]=5
self["relief"]="ridge"
self.create_widgets() #Function which creates all widgets
self.position_widgets() #Function which position all widgets
def create_widgets(self): #Function which creates all widgets
pass
def position_widgets(self): #Function which position all widgets
pass
#Create a window as defined in the AppWindow class
main_window = AppWindow(tk.Tk())
#Create a Frame as defined in class FrameOne
first_frame = FrameOne(main_window)
first_frame.grid(row=0, column=0) #Positioning Frame on Window
main_window.mainloop() #THIS PROVIDES AN ERROR | GUI DOES NOT START WITHOUT
mainloop is a method on the tkinter root window and on tkinter itself. Like the error says, it's not a method of your AppWindow class.
In your case you should do it like this:
root = tk.Tk()
main_window = AppWindow(root)
root.mainloop()

autoscroll of text and scrollbar in python text box

I have a tkinter 'Text' and 'Scrollbar' working fine. In my program in the text window automatically lines will keep on adding. So When a new line of text is inserted and data reached out of limit I would like the text and scrollbar to be scrolled to the bottom automatically, so that the latest line of text is always shown. How to do this?
Also how to link the scroll of text window and scroll bar, because when I do scrolling over the text window scroll wont happen. Only way I observed is to drag the scroll bar.
scrollbar = Tkinter.Scrollbar(group4.interior())
scrollbar.pack(side = 'right',fill='y')
Details1 = Output()
outputwindow = Tkinter.Text(group4.interior(), yscrollcommand = scrollbar.set,wrap = "word",width = 200,font = "{Times new Roman} 9")
outputwindow.pack( side = 'left',fill='y')
scrollbar.config( command = outputwindow.yview )
outputwindow.yview('end')
outputwindow.config(yscrollcommand=scrollbar.set)
outputwindow.insert('end',Details1)
In the program the function output() will continuously send data, which should scroll
Thanks in advance,
You can cause the text widget to scroll to any location with the see which takes an index.
For example, to make the last line of the widget visible you can use the index "end":
outputwindow.see("end")
Here's a complete working example:
import time
try:
# python 2.x
import Tkinter as tk
except ImportError:
# python 3.x
import tkinter as tk
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.text = tk.Text(self, height=6, width=40)
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.text.yview)
self.text.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.text.pack(side="left", fill="both", expand=True)
self.add_timestamp()
def add_timestamp(self):
self.text.insert("end", time.ctime() + "\n")
self.text.see("end")
self.after(1000, self.add_timestamp)
if __name__ == "__main__":
root =tk.Tk()
frame = Example(root)
frame.pack(fill="both", expand=True)
root.mainloop()
Take a look at Text.see(...) method.
TextWidget.insert(tk.END, str(new_txt))
TextWidget.see(tk.END)
I used this pattern to add (aka insert) text new_txt to my output window and scroll (see) to the bottom (tk.END)
Add this after the insert
TextBox.yview(END)
although very useful, using Text.see(...) in the above manner defeats the purpose of having a scrollbar. Better would be insert text at "0." instead of tk.END. That way, it initially autoscrolls, but you can drag it down to anywhere and it will stay there (while updating its range as more text is added, but the text you see remains constant until you scroll elsewhere). Once you want it to autoscroll again, you simply slide the scrollbar all the way back to the top.
Another solution (which wouldn't affect the flow direction of incoming text) would be to add some extra logic, such as a checkbox, then everytime you update Text, check that checkbox's state before you decide whether or not to call Text.see(...).
I don't know if there is a more elegant solution.

Python: Tkinter: Multi line scrolling entry box

I would like to make a Tkinter window able to ask for a multi line entry
(so the user will add one or more lines of text)
And then when we clic on the button be able to retrieve the values entered by user for further use.
Until now I have this script:
from Tkinter import *
import ScrolledText
class EntryDemo:
def __init__(self, rootWin):
#Create a entry and button to put in the root window
self.textfield = ScrolledText(rootWin)
#Add some text:
self.textfield.delete(0,END)
self.textfield.insert(0, "Change this text!")
self.textfield.pack()
self.button = Button(rootWin, text="Click Me!", command=self.clicked)
self.button.pack()
def clicked(self):
print("Button was clicked!")
eText = self.textfield.get()
print("The Entry has the following text in it:", eText)
#Create the main root window, instantiate the object, and run the main loop
rootWin = Tk()
#app = EntryDemo( rootWin )
rootWin.mainloop()
But it didn't seem to work, A window appear with nothing inside.
Could you help me?
#########EDIT
New code:
from Tkinter import *
import ScrolledText
class EntryDemo:
def __init__(self, rootWin):
self.textfield = ScrolledText.ScrolledText(rootWin)
#Add some text:
#self.textfield.delete(0,END)
self.textfield.insert(INSERT, "Change this text!")
self.textfield.pack()
self.button = Button(rootWin, text="Click Me!", command=self.clicked)
self.button.pack()
def clicked(self):
eText = self.textfield.get(1.0, END)
print(eText)
rootWin = Tk()
app = EntryDemo( rootWin )
rootWin.mainloop()
Sorry if it look like done with no effort by some down voters (even if I spent more than a day on it) but the Multi line text entry is not exactly what we can call well documented to learn by ourself.
Your first problem is that you commented out the app = EntryDemo( rootWin ) call, so you're not actually doing anything but creating a Tk() root window, then starting its main loop.
If you fix that, your next problem is that you're trying to use the ScrolledText module as if it were a class. You need the ScrolledText.ScrolledText class.
If you fix that, your next problem is that you're trying to delete from an empty text field, which is going to raise some kind of Tcl index error, and then you're also trying to insert at position 0 in an empty text field, which will raise the same error. There's no reason to do the delete at all, and for the insert you probably want to use INSERT as the position.
You still have multiple problems after that, but fixing these three will get your edit box up and displayed so you can start debugging everything else.
A working example based on your code. Note the comment above that both the file you import and the class within the file are named "ScrolledText"
from Tkinter import *
import ScrolledText
class EntryDemo:
def __init__(self, rootWin):
#Create a entry and button to put in the root window
self.textfield = ScrolledText.ScrolledText(rootWin)
self.textfield.pack()
#Add some text:
self.textfield.delete('1.0', END)
self.textfield.insert('insert', "Change this text!")
self.button = Button(rootWin, text="Click Me!",
command=self.clicked)
self.button.pack()
def clicked(self):
print("Button was clicked!")
eText = self.textfield.get('1.0', END)
print("The Entry has the following text in it:", eText)
self.textfield.delete('1.0', END)
rootWin = Tk()
app = EntryDemo( rootWin )
rootWin.mainloop()

tkinter Toplevel window appears not to react to some methods

I have code for selecting backup that should be loaded. It opens Toplevel window and lets you select one of the backups to load. When this window opens, I would like to block input to original window, so the only way to get back to original window is by closing the new Toplevel window.
The part of code that I hoped would work:
from tkinter import *
class BackupsGui:
def __init__(self, parent):
top = Toplevel()
self.top = top
Some more code and __init__ ending with:
top.update_idletasks()
top.overrideredirect(True)
top.mainloop()
or:
top.transient(parent)
top.mainloop()
Niether code part appears to change Toplevel interaction in any way, nor does changing if top.mainloop() precedes top.transient() or top.update_idletasks().
What do I miss?
transient and overrideredirect have nothing to do with event handling. If you want to block all input except for the toplevel, you need to call grab_set on the toplevel window. This will cause all events to be sent to that window.
Run the following code, and notice that if you don't check the box, you can continue to create new windows and change the value of the checkbox. Once checked, the next window grabs all events, preventing you from interacting with the other windows.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.do_grab = tk.BooleanVar()
cb = tk.Checkbutton(self, text="New window grabs all events",
variable=self.do_grab, onvalue=True, offvalue=False)
cb.pack()
new_button = tk.Button(self, text="New window", command=self.on_click)
new_button.pack()
def on_click(self):
self.top = tk.Toplevel(self)
button = tk.Button(self.top, text="dismiss", command=self.top.destroy)
do_grab = self.do_grab.get()
if do_grab:
label = tk.Label(self.top, wraplength=200,
text="This window grabs all events")
else:
label = tk.Label(self.top, wraplength = 200,
text="This window does NOT grab all events")
label.pack(fill="x")
button.pack()
if do_grab:
self.top.grab_set()
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

Changing ttk widget text color

I've searched all over, but have yet to find a simple example showing how to change a minor element of a ttk widget style and applying it dynamically (after widget creation).
I have some ttk checkbuttons representing some configuration items, and a ttk button used to update a remote system. The checkbuttons are initialized to the state of the remote system.
If the user modifies any of the checkbuttons, I want both the checkbutton text and the update button text to become red, to remind the user that the checkbutton state no longer matches the remote state, and that the update button should be pressed to send the modified configuration to the remote system.
When the update button is pressed, the text color of the update button and all checkbuttons reverts to black.
I know this is possible (and is probably easy), but how?
Edit: Modifying the background color would also be OK.
You will need to create a custom style, and then apply that style to the widget. To create a custom style, first get an instance of ttk.Style, and then use the configure method to derive a new style from an existing one. The following example creates a new style named "Red.TCheckbutton":
style = ttk.Style()
style.configure("Red.TCheckbutton", foreground="red")
Next, you simply associate this style with the widget when you want the color to change:
my_checkbutton.configure(style="Red.TCheckbutton")
The best resource for learning how to work with the ttk styles is tkdocs.com. Specifically, https://www.tkdocs.com/tutorial/styles.html.
Here's a complete working example:
import ttk
import Tkinter as tk
class ExampleApp(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.var1 = tk.StringVar()
self.var2 = tk.StringVar()
f1 = ttk.Frame(self)
red_button = ttk.Button(f1, text="Red", command=self.make_red)
default_button = ttk.Button(f1, text="Default", command=self.make_default)
red_button.pack(side="left")
default_button.pack(side="left")
f2 = ttk.Frame(self)
self.cb_one = ttk.Checkbutton(f2, text="Option 1", variable=self.var1,
onvalue=1, offvalue=0)
self.cb_two = ttk.Checkbutton(f2, text="Option 2", variable=self.var2,
onvalue=1, offvalue=0)
self.cb_one.pack(side="left")
self.cb_two.pack(side="left")
f1.pack(side="top", fill="x")
f2.pack(side="top", fill="x")
style = ttk.Style()
style.configure("Red.TCheckbutton", foreground="red")
def make_red(self):
self.cb_one.configure(style="Red.TCheckbutton")
self.cb_two.configure(style="Red.TCheckbutton")
def make_default(self):
self.cb_one.configure(style="TCheckbutton")
self.cb_two.configure(style="TCheckbutton")
if __name__ == "__main__":
root = tk.Tk()
ExampleApp(root).pack(fill="both", expand=True)
root.mainloop()
You can use to change the background color using:
import tkinter as tk
root = tk.Tk()
root.configure(bg='blue')

Categories