I have problem with set in Scrollbar inside Text widget in Tkinter. I know, that it's preferable to use grid to locate widgets but I want to set my widget in absolute location (x,y - red dot on GUI picture) with specified height and width.
My code:
from Tkinter import *
from ttk import *
class NotebookDemo(Frame):
def __init__(self):
Frame.__init__(self)
self.pack(expand=1, fill=BOTH)
self.master.title('Sample')
self.master.geometry("650x550+100+50")
self._initUI()
def _initUI(self):
self._createPanel()
def _createPanel(self):
# create frame inside top level frame
panel = Frame(self)
panel.pack(side=TOP, fill=BOTH, expand=1)
# create the notebook
nb = Notebook(panel)
nb.pack(fill=BOTH, expand=1, padx=2, pady=3)
self._FirstTab(nb)
def _FirstTab(self, nb):
# frame to hold content
frame = Frame(nb)
#textbox
txtOutput = Text(frame, wrap = NONE, height = 17, width = 70)
txtOutput.place(x=10, y=75)
#button
btnStart = Button(frame, text = 'Start', underline=0)
btnStart.place(x=220, y=380)
#scrollbar
#vscroll = Scrollbar(frame, orient=VERTICAL, command=txtOutput.yview)
#txtOutput['yscroll'] = vscroll.set
#vscroll.pack(side=RIGHT, fill=Y)
#txtOutput.pack(fill=BOTH, expand=Y)
#add to notebook (underline = index for short-cut character)
nb.add(frame, text='TAB 1', underline=0, padding=2)
if __name__ == '__main__':
app = NotebookDemo()
app.mainloop()
If I uncomment this part of code (set Scrollbar):
vscroll = Scrollbar(frame, orient=VERTICAL, command=txtOutput.yview)
txtOutput['yscroll'] = vscroll.set
vscroll.pack(side=RIGHT, fill=Y)
My Scrollbar is located inside all window, not inside Text box:
But of course I want to have the Scrollbar inside the Text box widget (black border).
If I use pack function to textbox:
txtOutput.pack(fill=BOTH, expand=Y)
text widget fill in the whole window...:
I really don't know how fix this problem.
Any help will be appreciated.
Thank you!
EDIT:
Of course I can use place method with Scrollbar too, but I can't change length of them, because it hasn't attribute length.
vscroll.place(x=573, y=75)
While I rarely recommend place, it is quite powerful when you take advantage of the configuration options. For example, you can use in_ to specify a widget that this widget is to be placed relative to. You can use relx to specify a relative x coordinate, and you can use relheight to specify a height.
In your case you can try something like this:
vscroll.place(in_=txtOutput, relx=1.0, relheight=1.0, bordermode="outside")
If you want the illusion that the scrollbar is embedded inside the text widget as is (or used to be) common on some platforms, I recommend placing the text widget and scrollbar in a frame.You can use pack to put the widgets in the frame, and continue to use place to place the combination anywhere you want.
For example:
txtFrame = Frame(frame, borderwidth=1, relief="sunken")
txtOutput = Text(txtFrame, wrap = NONE, height = 17, width = 70, borderwidth=0)
vscroll = Scrollbar(txtFrame, orient=VERTICAL, command=txtOutput.yview)
txtOutput['yscroll'] = vscroll.set
vscroll.pack(side="right", fill="y")
txtOutput.pack(side="left", fill="both", expand=True)
txtFrame.place(x=10, y=75)
Different geometry managers like place and pack don't mix so well. I see four options for you:
Use a parent frame
Create a new Frame that you place at the exact same position as you did with the text box. In this frame, you can use another geometry manager (I'd prefer pack) to make the layout appear as you want.
Use ScrolledText
Use the ScrolledText Tkinter module to have the solution above in a premade form. Note that this widget doesn't use ttk so the scrollbar style does not really adapt to the OS' look. Just use import ScrolledText and replace the Text creation in your code with ScrolledText.ScrolledText(...).
Use place for the scrollbar
If you are using place for the text widget, use place for the scrollbar too. place has options that allow you to place a widget relative to another widget both in location and size (ie: you can place the scrollbar along the right edge of the text widget, and cause it to be exactly as tall as the text widget). See Bryan's answer.
Don't use place
Simple as that. Use grid or pack instead, unless you really need to use place.
Related
I've been creating an app where there are Clients that I can add to a table, the problem is, I need a scrollbar to scroll through all the clients since the app Height is limited and the clients aren't.
Using tkinter I found a way to create a "table" using Entry and grid, but what if I want to create 100 rows? they would be outside of the view, so that's why the need of a scrollbar.
For those of you who know Java, I wanted to create something similar to Jtable, it has a method to create row, delete row, and it generates automatically that scrollbar as soon as the JTable runs out of space.
I've tried to use TkTable from ttk and mess around with some properties, but I preferred how Entries look.
root = Tk()
root.geometry("1200x900")
for i in range(10):
e = Entry(relief=RIDGE)
e.grid(row=i, column=2, sticky=N)
root.mainloop()
I created a root = Tk() and used root to grid them.
You'll see 10 Entries on top of the other.
When a window contains many widgets, they might not all be visible. However, neither a window (Tk or Toplevel instance) nor a Entry are scrollable.
One solution to make the window content scrollable is to put all the widgets in a Frame, and then, embed this Frame in a Canvas using the create_window method.
from tkinter import *
root = Tk()
canvas = Canvas(root)
scroll_y = Scrollbar(root, orient="vertical", command=canvas.yview)
frame = Frame(canvas)
# group of widgets
for i in range(100):
e = Entry(frame, relief=RIDGE, width = 100)
e.grid(row=i, column=2, sticky=N)
# put the frame in the canvas
canvas.create_window(0, 0, anchor='nw', window=frame)
# make sure everything is displayed before configuring the scrollregion
canvas.update_idletasks()
canvas.configure(scrollregion=canvas.bbox('all'),
yscrollcommand=scroll_y.set)
canvas.pack(fill='both', expand=True, side='left')
scroll_y.pack(fill='y', side='right')
root.mainloop()
output:
I want to have an entry and I want to have a listbox of fixed size under it which is fixed. and I want to have another listbox of dynamic height. That will appear and disappear in time and also change in size. I want the second listbox (which is actually a dropdown) to be shown over the other listbox which I want it to be fixed. My code for changing the size etc is correct and works perfectly with pack() but then it will move the other listbox up and down as it's size changes. And when I change pack() to place(...) it's not shown at all anymore.
Here is my code:
from tkinter import *
root = Tk()
entry = Entry(
root,
width=50
)
frame = Frame(
root,
height=10,
width=50,
background="#caeaa9"
)
dropdown = Listbox(
frame,
background="#11FF11",
height=5,
width=50
)
listbox = Listbox(
frame,
background="#FF1111",
height=10,
width=50
)
entry.pack()
listbox.pack()
dropdown.place()
frame.pack()
mainloop()
But the dropdown does not appear when I run it. What am I missing?
By the way, I want the top border of the dropdown to be exactly on the top border of the listbox and both of them right under the entry.
I highly recommend you don't use the place geometry manager. Ever. If you want to create larger or more complex interfaces, having to place widgets is terrible. I suggest using grid instead:
import tkinter as tk
# Avoid wildcard imports!
root = tk.Tk()
entry = tk.Entry(
root,
width=50
)
frame = tk.Frame(
root,
background="#caeaa9"
)
listbox = tk.Listbox(
frame,
background="#FF1111",
height=10,
width=50
)
dropdown = tk.Listbox(
frame,
background="#11FF11",
height=5,
width=50
)
entry.pack()
listbox.grid(column=0, row=0, sticky=N)
dropdown.grid(column=0, row=0, sticky=N)
frame.pack()
root.mainloop()
You have two problems:
you aren't telling place where to place the widget
the stacking order (z-index) of the dropdown is behind (lower) than the other listbox, so it will appear under the listbox.
give explicit coordinates to place
I'm not entirely clear where you want the dropdown to appear. Since you say "over" the other listbox, I suggest you use the in_ parameter to make the coordinates relative to the other listbox, and then use other place arguments to place it exactly where you want.
Example:
In the following example I make the dropdown exactly the same width, but half the height of the other listbox.
dropdown.place(in_=listbox, x=0, y=0, anchor="nw", relwidth=1.0, relheight=.5)
Fix the stacking order
All widgets have a stacking order. Some people call this a z-index. By default the order is the order in which the widgets are created. Since you create the dropdown before the other listbox, the other listbox has a higher stacking order. That means that it will appear above the dropdown.
A simple solution is to create the dropdown last. If you don't want to do that, you can call the lift method of the widget to raise its stacking order. The argument for lift is the name of a widget you want to be above.
Example:
dropdown.lift(listbox)
Tkinter place is one of the three geometry manager in tkinter. it allows us to specify the position of tkinter widgets in terms of x and y coordinate.
Here is the code example :
from tkinter import *
window = Tk()
Button(window,text = "Click Me").place(x = 50,y = 50)
window.mainloop()
For more info kindly refer this tutorial on tkinter place
I want to put a small image and other widgets over a canvas on which an image is displayed. I've tried options such ascompound and other things.
Background picture is fine and the small image that I want to put over the background image shows fine but it's always top or bottom of the window. I want it to be placed over any area of background image. I've tried many options of all the geometry manager (pack, grid, place) but none of them works. Please help, here's my code :
from Tkinter import *
root = Tk()
root.iconbitmap('E:/a.ico')
root.title('Unick Locker')
canvas = Canvas(root, width=730, height=600)
canvas.grid()
bgImg = PhotoImage(file="E:/a.gif")
canvas.create_image(370, 330, image=bgImg)
login = PhotoImage(file="E:/login.gif")
lo = Label(root, image=login)
lo.grid()
root.mainloop()
In order to add any widgets over or the foreground of any background image or canvas, the row and column values of all the widgets must be same as of the background image. so, my above mentioned program would be like this :
from Tkinter import *
root = Tk()
root.iconbitmap('E:/a.ico')
root.title('Unick Locker')
canvas = Canvas(root, width=730, height=600)
canvas.grid(row=0, column=0)
bgImg = PhotoImage(file="E:/a.gif")
canvas.create_image(370, 330, image=bgImg)
login = PhotoImage(file="E:/login.gif")
lo = Label(root, image=login)
lo.grid(row=0, column=0)
root.mainloop()
I tried putting the same row and column values to the widgets in grid() methods which I wanted to put over the image, and it worked fine as I wanted :-)
Have you considered using the paste method, which lets you define the position of the pasted image through a box argument?
See http://effbot.org/imagingbook/imagetk.htm.
Please also take a look at this thread: Tkinter, overlay foreground image on top of a background image with transparency, which seems very similar to your issue.
You are looking to draw the widgets over the canvas, this means you must specify the canvas as the parent widget, not the root as you did. For that, modify lo = Label(root, image=login) to lo = Label(canvas, image=login)
Also, do not forget to specify the rows and columns where you want to position the different widgets. This means you need to write, for example, lo.grid(row=0, column=0) instead of lo.grid(). For the moment you do not see big problems because you have only one label widget. But if you try to add an other widget without mentioning the exact positions (rows and columns) you will get unexpected results.
This question isn't about images at all, it's just a basic layout problem. You'll have the same issues with or without images. The problem is simply that you aren't giving any options to grid, so it naturally puts things at the top. Tkinter also has the behavior that a containing widget (eg: your canvas) will shrink or expand to exactly fit its contents.
Here's a version that creates several widgets over a background image. Notice the use of options to pack and grid, and the use of grid_rowconfigure and grid_columnconfigure to specify how extra space is allocated.
from Tkinter import *
root = Tk()
canvas = Canvas(root, width=730, height=600)
canvas.pack(fill="both", expand=True)
bgImg = PhotoImage(file="E:/a.gif")
canvas.create_image(370, 330, image=bgImg)
l1 = Label(canvas, text="Hello, world")
e1 = Entry(canvas)
t1 = Text(canvas)
l1.grid(row=0, column=0, sticky="ew", padx=10)
e1.grid(row=1, column=1, sticky="ew")
t1.grid(row=2, column=2, sticky="nsew")
canvas.grid_rowconfigure(2, weight=1)
canvas.grid_columnconfigure(2, weight=1)
root.mainloop()
root = Tk()
descriptionFrame = Frame(root)
definitionFrame = LabelFrame(descriptionFrame, text="Definition")
definitionScroll = Scrollbar(definitionFrame)
definitionCanvas = Canvas(definitionFrame, width=30, height=4, yscrollcommand=definitionScroll.set)
definitionScroll.config(command=definitionCanvas.yview)
definitionLabel = Label(definitionCanvas, text="n/a")
descriptionFrame.pack()
definitionFrame.pack()
definitionScroll.pack(side=RIGHT, fill=Y)
definitionCanvas.pack(side=LEFT, fill=BOTH, expand=True)
definitionLabel.pack(fill=BOTH, expand=True)
root.mainloop()
I have this code. The Canvas is set to have a width of 30 and height of 4, but when I run this, it ignores the width and height of the Canvas and the resulting window is sized around the Label instead. I've tried using pack_propagate(False) on every single Frame in the code, but it doesn't affect anything for the definitionFrame, but when I use it on descriptionFrame it results in an empty window. How would I create a GUI where all the frames and the window are sized to the Canvas size of width 30 and height 4?
Thanks.
To answer your specific question of how to stop a frame (or any container widget) from resizing to fit its contents, you call either pack_propagate(False) or grid_propagate(False) depending on which geometry manager you are using. If you've tried that and it wasn't working, you did it wrong. Since you didn't post that code we can't diagnose what went wrong.
When you call pack_propagate(False) you have to make sure that widget has an appropriate size. Labels and buttons will have a default size to fit their text, but a frame will have a default size of 1x1, making the contents nearly invisible. If using this on a frame, then, make sure to give it an explicit width and height.
only Listbox, Text, Canvas, and Entry are scrollable by default; Canvas could work, but is a bit overkill IMO, so here's something that seems like what you want using Text
#!/usr/bin/python
from Tkinter import *
root = Tk()
descriptionFrame = Frame(root)
definitionFrame = LabelFrame(descriptionFrame, text="Definition")
definitionScroll = Scrollbar(definitionFrame)
definitionText = Text(definitionFrame, width=30, height=4, yscrollcommand=definitionScroll.set)
definitionScroll.config(command=definitionText.yview)
definitionText.delete("1.0", END) # an example of how to delete all current text
definitionText.insert("1.0", "n/a") # an example of how to add new text to the text area
descriptionFrame.pack(fill=BOTH, expand=True)
definitionFrame.pack(fill=BOTH, expand=True)
definitionScroll.pack(side=RIGHT, fill=Y)
definitionText.pack(side=LEFT, fill=BOTH, expand=True)
root.mainloop()
What I did was set make my root un-resizable: root.resizable(False, False), then I defined width and height of the canvas: Canvas(root, height=500, width=1500)
and finally placed my frame with a relative width and height of 1: frame.place(relheight=1, relwidth=1). Then I placed all my widgets with specific y/x values and pack(), but you could do the same process if you want everything sized to the canvas; I feel like this is a simpler method.
Something like this, would make the widget appear normally:
Label(self, text = 'hello', visible ='yes')
While something like this, would make the widget not appear at all:
Label(self, text = 'hello', visible ='no')
You may be interested by the pack_forget and grid_forget methods of a widget. In the following example, the button disappear when clicked
from Tkinter import *
def hide_me(event):
event.widget.pack_forget()
root = Tk()
btn=Button(root, text="Click")
btn.bind('<Button-1>', hide_me)
btn.pack()
btn2=Button(root, text="Click too")
btn2.bind('<Button-1>', hide_me)
btn2.pack()
root.mainloop()
One option, as explained in another answer, is to use pack_forget or grid_forget. Another option is to use lift and lower. This changes the stacking order of widgets. The net effect is that you can hide widgets behind sibling widgets (or descendants of siblings). When you want them to be visible you lift them, and when you want them to be invisible you lower them.
The advantage (or disadvantage...) is that they still take up space in their master. If you "forget" a widget, the other widgets might readjust their size or orientation, but if you raise or lower them they will not.
Here is a simple example:
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frame = tk.Frame(self)
self.frame.pack(side="top", fill="both", expand=True)
self.label = tk.Label(self, text="Hello, world")
button1 = tk.Button(self, text="Click to hide label",
command=self.hide_label)
button2 = tk.Button(self, text="Click to show label",
command=self.show_label)
self.label.pack(in_=self.frame)
button1.pack(in_=self.frame)
button2.pack(in_=self.frame)
def show_label(self, event=None):
self.label.lift(self.frame)
def hide_label(self, event=None):
self.label.lower(self.frame)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
I know this is a couple of years late, but this is the 3rd Google response now for "Tkinter hide Label" as of 10/27/13... So if anyone like myself a few weeks ago is building a simple GUI and just wants some text to appear without swapping it out for another widget via "lower" or "lift" methods, I'd like to offer a workaround I use (Python2.7,Windows):
from Tkinter import *
class Top(Toplevel):
def __init__(self, parent, title = "How to Cheat and Hide Text"):
Toplevel.__init__(self,parent)
parent.geometry("250x250+100+150")
if title:
self.title(title)
parent.withdraw()
self.parent = parent
self.result = None
dialog = Frame(self)
self.initial_focus = self.dialog(dialog)
dialog.pack()
def dialog(self,parent):
self.parent = parent
self.L1 = Label(parent,text = "Hello, World!",state = DISABLED, disabledforeground = parent.cget('bg'))
self.L1.pack()
self.B1 = Button(parent, text = "Are You Alive???", command = self.hello)
self.B1.pack()
def hello(self):
self.L1['state']="normal"
if __name__ == '__main__':
root=Tk()
ds = Top(root)
root.mainloop()
The idea here is that you can set the color of the DISABLED text to the background ('bg') of the parent using ".cget('bg')" http://effbot.org/tkinterbook/widget.htm rendering it "invisible". The button callback resets the Label to the default foreground color and the text is once again visible.
Downsides here are that you still have to allocate the space for the text even though you can't read it, and at least on my computer, the text doesn't perfectly blend to the background. Maybe with some tweaking the color thing could be better and for compact GUIs, blank space allocation shouldn't be too much of a hassle for a short blurb.
See Default window colour Tkinter and hex colour codes for the info about how I found out about the color stuff.
I'm also extremely late to the party, but I'll leave my version of the answer here for others who may have gotten here, like I did, searching for how to hide something that was placed on the screen with the .place() function, and not .pack() neither .grid().
In short, you can hide a widget by setting the width and height to zero, like this:
widget.place(anchor="nw", x=0, y=0, width=0, height=0)
To give a bit of context so you can see what my requirement was and how I got here.
In my program, I have a window that needs to display several things that I've organized into 2 frames, something like this:
[WINDOW - app]
[FRAME 1 - hMainWndFrame]
[Buttons and other controls (widgets)]
[FRAME 2 - hJTensWndFrame]
[other Buttons and controls (widgets)]
Only one frame needs to be visible at a time, so on application initialisation, i have something like this:
hMainWndFrame = Frame(app, bg="#aababd")
hMainWndFrame.place(anchor="nw", x=0, y=0, width=480, height=320)
...
hJTensWndFrame = Frame(app, bg="#aababd")
I'm using .place() instead of .pack() or .grid() because i specifically want to set precise coordinates on the window for each widget. So, when i want to hide the main frame and display the other one (along with all the other controls), all i have to do is call the .place() function again, on each frame, but specifying zero for width and height for the one i want to hide and the necessary width and height for the one i want to show, such as:
hMainWndFrame.place(anchor="nw", x=0, y=0, width=0, height=0)
hJTensWndFrame.place(anchor="nw", x=0, y=0, width=480, height=320)
Now it's true, I only tested this on Frames, not on other widgets, but I guess it should work on everything.
For hiding a widget you can use function pack_forget() and to again show it you can use pack() function and implement them both in separate functions.
from Tkinter import *
root = Tk()
label=Label(root,text="I was Hidden")
def labelactive():
label.pack()
def labeldeactive():
label.pack_forget()
Button(root,text="Show",command=labelactive).pack()
Button(root,text="Hide",command=labeldeactive).pack()
root.mainloop()
I was not using grid or pack.
I used just place for my widgets as their size and positioning was fixed.
I wanted to implement hide/show functionality on frame.
Here is demo
from tkinter import *
window=Tk()
window.geometry("1366x768+1+1")
def toggle_graph_visibility():
graph_state_chosen=show_graph_checkbox_value.get()
if graph_state_chosen==0:
frame.place_forget()
else:
frame.place(x=1025,y=165)
score_pixel = PhotoImage(width=300, height=430)
show_graph_checkbox_value = IntVar(value=1)
frame=Frame(window,width=300,height=430)
graph_canvas = Canvas(frame, width = 300, height = 430,scrollregion=(0,0,300,300))
my_canvas=graph_canvas.create_image(20, 20, anchor=NW, image=score_pixel)
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.config(command=graph_canvas.yview)
vbar.pack(side=RIGHT,fill=Y)
graph_canvas.config(yscrollcommand=vbar.set)
graph_canvas.pack(side=LEFT,expand=True,fill=BOTH)
frame.place(x=1025,y=165)
Checkbutton(window, text="show graph",variable=show_graph_checkbox_value,command=toggle_graph_visibility).place(x=900,y=165)
window.mainloop()
Note that in above example when 'show graph' is ticked then there is vertical scrollbar.
Graph disappears when checkbox is unselected.
I was fitting some bar graph in that area which I have not shown to keep example simple.
Most important thing to learn from above is the use of frame.place_forget() to hide and frame.place(x=x_pos,y=y_pos) to show back the content.
For someone who hate OOP like me (This is based on Bryan Oakley's answer)
import tkinter as tk
def show_label():
label1.lift()
def hide_label():
label1.lower()
root = tk.Tk()
frame1 = tk.Frame(root)
frame1.pack()
label1 = tk.Label(root, text="Hello, world")
label1.pack(in_=frame1)
button1 = tk.Button(root, text="Click to hide label",command=hide_label)
button2 = tk.Button(root, text="Click to show label", command=show_label)
button1.pack(in_=frame1)
button2.pack(in_=frame1)
root.mainloop()
import tkinter as tk
...
x = tk.Label(text='Hello', visible=True)
def visiblelabel(lb, visible):
lb.config(visible=visible)
visiblelabel(x, False) # Hide
visiblelabel(x, True) # Show
P.S. config can change any attribute:
x.config(text='Hello') # Text: Hello
x.config(text='Bye', font=('Arial', 20, 'bold')) # Text: Bye, Font: Arial Bold 20
x.config(bg='red', fg='white') # Background: red, Foreground: white
It's a bypass of StringVar, IntVar etc.