Change position of checkbox relative to text in Tkinter's Checkbutton - python

How can I change where the text is relative to the checkbox for a Tkinter Checkbutton?
By default, the text is to the right of the checkbox. I would like to change that, so the text is on the left or above of the checkbox.
I know this can be done by creating a Label with the required text and delicately positioning the two near each other, but I'd prefer to avoid that method.
Some sample code:
from Tkinter import *
root = Tk()
Checkbutton(root, text="checkButon Text").grid()
root.mainloop()

Well, I don't think you can do it directly, but you can do something that looks as it should. It is the Label-solution, but I altered it slightly, so the resulting compound of Checkbutton and Label is treated as a single Widget as wrapped in a Frame.
from Tkinter import *
class LabeledCheckbutton(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.checkbutton = Checkbutton(self)
self.label = Label(self)
self.label.grid(row=0, column=0)
self.checkbutton.grid(row=0, column=1)
root = Tk()
labeledcb = LabeledCheckbutton(root)
labeledcb.label.configure(text="checkButton Text")
labeledcb.grid(row=0, column=0)
root.mainloop()
When you create multiple frames (with their respective content - Checkbutton and Label) you can handle them easily. That way you would just have to position the Frames like you would do it with the Checkbuttons.

Related

How to display widgets on a frame that is mounted on canvas in Tkinter?

I created a main root with two frames.
-One frame is for program toolbar.
-Other frame is for canvas where data will be displayed and a scrollbar widget.
-Inside of the canvas is a third smaller frame which will be used for scrolling trough data.
However, when I try to define new widgets and place them on that third smaller frame, nothing happens. I'm defining new widgets inside of a function call of a button command. I have also tried declaring everything as global variables but without luck.
Hint: I tried placing the code from the function to the top level of the code and it works. Also, if I try to mount these widgets on the toolbar frame it also works. It seems that the only thing I can't do is to mount these new widgets on the small frame that is inside the canvas.
I used a simple for loop to create labels just for testing.
Could anyone tell what I am doing wrong?
from tkinter import *
from tkinter import ttk
#Creating main window
root = Tk()
root.resizable(width=False, height=False)
#Defining Background
toolbar = Frame(root, width=613, height=114)
toolbar.grid(row=0, column=0)
background_frame = Frame(root, width=615, height=560)
background_frame.grid(row=1, column=0)
background = Canvas(background_frame, width=615, height=560)
background.pack(side=LEFT, fill=BOTH, expand=1)
scroll_bar = ttk.Scrollbar(background_frame, orient=VERTICAL, command=background.yview)
scroll_bar.pack(side=RIGHT, fill=Y)
background.configure(yscrollcommand=scroll_bar.set)
background.bind('<Configure>', lambda e:background.configure(scrollregion = background.bbox('all')))
second_frame = Frame(background)
background.create_window(150,100, window=second_frame, anchor='nw')
def confirm1():
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
show_labels = Button(toolbar, text= "Show labels", fg="black", command=confirm1)
show_labels.grid(row=0, column=2)
root.mainloop()
Picture of the app so far
I surely can't reproduce the issue with your current code, but looking at the previous edit it is pretty clear what your problem is.
(taken from your previous edit)
def confirm1():
global background_image1
background.delete('all') # <--- this line of code
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
Here you delete all your items from your canvas:
background.delete('all')
hence no item appears.
You should instead delete only those items that you want to remove by passing the id or tags to delete method. You can delete multiple items together at once by giving the same tags.
Another option would be to recreate the frame item again on canvas using create_window (Do note: your frame is not deleted/destroyed, it's only removed from canvas)

Automatically updating a value through a OptionMenu tkinter object

I would like to write a tkinter app that will automatically update a value based on the current state of the OptionMenu object. Here's what I have so far
from tkinter import *
root = Tk()
def show():
myLabel=Label(root,text=clicked.get()).pack()
clicked=StringVar()
clicked.set("1")
drop = OptionMenu(root,clicked,"1","2","3")
drop.pack()
myButton = Button(root,text="show selection",command=show)
root.mainloop()
In this version, the text can only be updated by clicking a button. How can I make the text update automatically, without this "middle man"?
You can simply assign clicked to the textvariable of the Label, then whenever an option is selected, the label will be updated:
import tkinter as tk
root = tk.Tk()
clicked = tk.StringVar(value="1")
drop = tk.OptionMenu(root, clicked, "1", "2", "3")
drop.pack()
tk.Label(root, textvariable=clicked).pack()
root.mainloop()
After changing some things, i got it working.
It is better to use the config() function to change item's attributes, and another important thing is to not pack() the objects (the Label, in this case) in the same line that the variable declaration.
Like so, you'll be able to change the text. Here is your code updated!
from tkinter import *
def show():
myLabel.config(text = clicked.get())
root = Tk()
clicked=StringVar( value="1")
myLabel=Label(root, text="click the button at the bottom to see this label text changed")
myLabel.pack()
drop = OptionMenu(root, clicked, "1","2","3")
drop.pack()
myButton = Button(root, text="show selection", command=show)
myButton.pack()
root.mainloop()

How to shrink a frame in tkinter after removing contents?

Most of the topics I came across deals with how to not shrink the Frame with contents, but I'm interested in shrinking it back after the destruction of said contents. Here's an example:
import tkinter as tk
root = tk.Tk()
lbl1 = tk.Label(root, text='Hello!')
lbl1.pack()
frm = tk.Frame(root, bg='black')
frm.pack()
lbl3 = tk.Label(root, text='Bye!')
lbl3.pack()
lbl2 = tk.Label(frm, text='My name is Foo')
lbl2.pack()
So far I should see this in my window:
Hello!
My name is Foo
Bye!
That's great, but I want to keep the middle layer interchangeable and hidden based on needs. So if I destroy the lbl2 inside:
lbl2.destroy()
I want to see:
Hello!
Bye!
But what I see instead:
Hello!
███████
Bye!
I want to shrink frm back to basically non-existence because I want to keep the order of my main widgets intact. Ideally, I want to run frm.pack(fill=tk.BOTH, expand=True) so that my widgets inside can scale accordingly. However if this interferes with the shrinking, I can live without fill/expand.
I've tried the following:
pack_propagate(0): This actually doesn't expand the frame at all past pack().
Re-run frm.pack(): but this ruins the order of my main widgets.
.geometry(''): This only works on the root window - doesn't exist for Frames.
frm.config(height=0): Oddly, this doesn't seem to change anything at all.
frm.pack_forget(): From this answer, however it doesn't bring it back.
The only option it leaves me is using a grid manager, which works I suppose, but not exactly what I'm looking for... so I'm interested to know if there's another way to achieve this.
When you destroy the last widget within a frame, the frame size is no longer managed by pack or grid. Therefore, neither pack nor grid knows it is supposed to shrink the frame.
A simple workaround is to add a small 1 pixel by 1 pixel window in the frame so that pack still thinks it is responsible for the size of the frame.
Here's an example based off of the code in the question:
import tkinter as tk
root = tk.Tk()
lbl1 = tk.Label(root, text='Hello!')
lbl1.pack()
frm = tk.Frame(root, bg='black')
frm.pack()
lbl3 = tk.Label(root, text='Bye!')
lbl3.pack()
lbl2 = tk.Label(frm, text='My name is Foo')
lbl2.pack()
def delete_the_label():
lbl2.destroy()
if len(frm.winfo_children()) == 0:
tmp = tk.Frame(frm, width=1, height=1, borderwidth=0, highlightthickness=0)
tmp.pack()
root.update_idletasks()
tmp.destroy()
button = tk.Button(root, text="Delete the label", command=delete_the_label)
button.pack()
root.mainloop()
Question: Shrink a Frame after removing the last widget?
Bind to the <'Expose'> event and .configure(height=1) if no children.
Reference:
Expose
An Expose event is generated whenever all or part of a widget should be redrawn
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
tk.Label(self, text='Hello!').pack()
self.frm = frm = tk.Frame(self, bg='black')
frm.pack()
tk.Label(self, text='Bye!').pack()
tk.Label(frm, text='My name is Foo').pack()
self.menubar = tk.Menu()
self.config(menu=self.menubar)
self.menubar.add_command(label='delete', command=self.do_destroy)
self.menubar.add_command(label='add', command=self.do_add)
frm.bind('<Expose>', self.on_expose)
def do_add(self):
tk.Label(self.frm, text='My name is Foo').pack()
def do_destroy(self):
w = self.frm
if w.children:
child = list(w.children).pop(0)
w.children[child].destroy()
def on_expose(self, event):
w = event.widget
if not w.children:
w.configure(height=1)
if __name__ == "__main__":
App().mainloop()
Question: Re-run frm.pack(): but this ruins the order of my main widgets.
frm.pack_forget(), however it doesn't bring it back.
Pack has the options before= and after. This allows to pack a widget relative to other widgets.
Reference:
-before
Use its master as the master for the slaves, and insert the slaves just before other in the packing order.
Example using before= and self.lbl3 as anchor. The Frame are removed using .pack_forget() if no children and get repacked at the same place in the packing order.
Note: I show only the relevant parts!
class App(tk.Tk):
def __init__(self):
...
self.frm = frm = tk.Frame(self, bg='black')
frm.pack()
self.lbl3 = tk.Label(self, text='Bye!')
self.lbl3.pack()
...
def on_add(self):
try:
self.frm.pack_info()
except:
self.frm.pack(before=self.lbl3, fill=tk.BOTH, expand=True)
tk.Label(self.frm, text='My name is Foo').pack()
def on_expose(self, event):
w = event.widget
if not w.children:
w.pack_forget()
Tested with Python: 3.5 - 'TclVersion': 8.6 'TkVersion': 8.6

Updating a label from an entry field on button push with tkinter in Python 3.5.2

I am trying to create a window with a line label, an entry field, a current value label, and an "Update Value" button.
Here is an example:
This is what I have so far. I can get the entered value to print to console, but I can't seem to work out how to get an entered value and change the currentValue Label to reflect that value by pressing the button:
from tkinter import*
main=Tk()
#StringVar for currentValue in R0C2
currentValue = StringVar(main, "0")
#Called by the setValues button, looks for content in the entry box and updates the "current" label
def setValues():
content = entry.get()
print(content)
#This kills the program
def exitProgram():
exit()
#Title and window size
main.title("Title")
main.geometry("350x200")
#Descriptions on the far left
Label(main, text="Duration (min): ").grid(row=0, column=0)
#Entry boxes for values amidship
entry=Entry(main, width=10)
entry.grid(row=0, column=1)
#Displays what the value is currently set to.
currentValue = Label(textvariable=currentValue)
currentValue.grid(row=0,column=2)
#Takes any inputted values and sets them in the "Current" column using def setValues
setValues=Button(text='Set Values',width=30,command=setValues)
setValues.grid(row=9, column=0, columnspan=2)
#Red button to end program
exitButton=Button(main, text='Exit Program',fg='white',bg='red',width=30, height=1,command=exitProgram)
exitButton.grid(row=20, column = 0, columnspan=2)
main.mainloop()
There are a couple of problems with your code.
Firstly, you are overwriting the setValues function with the setValues Button widget, and similarly, you are overwriting the currentValue StringVar with the currentValue Label.
To set a StringVar, you use its .set method.
Don't use plain exit in a script, that's only meant to be used in an interactive interpreter session, the proper exit function is sys.exit. However, in a Tkinter program you can just call the .destroy method of the root window.
Here's a repaired version of your code.
import tkinter as tk
main = tk.Tk()
#StringVar for currentValue in R0C2
currentValue = tk.StringVar(main, "0")
#Called by the setValues button, looks for content in the entry box and updates the "current" label
def setValues():
content = entry.get()
print(content)
currentValue.set(content)
#This kills the program
def exitProgram():
main.destroy()
#Title and window size
main.title("Title")
main.geometry("350x200")
#Descriptions on the far left
tk.Label(main, text="Duration (min): ").grid(row=0, column=0)
#Entry boxes for values amidship
entry = tk.Entry(main, width=10)
entry.grid(row=0, column=1)
#Displays what the value is currently set to.
currentValueLabel = tk.Label(textvariable=currentValue)
currentValueLabel.grid(row=0,column=2)
#Takes any inputted values and sets them in the "Current" column using def setValues
setValuesButton = tk.Button(text='Set Values',width=30,command=setValues)
setValuesButton.grid(row=9, column=0, columnspan=2)
#Red button to end program
exitButton = tk.Button(main, text='Exit Program',fg='white',bg='red',width=30, height=1,command=exitProgram)
exitButton.grid(row=20, column = 0, columnspan=2)
main.mainloop()
BTW, it's a Good Idea to avoid "star" imports. Doing from tkinter import * dumps 130 names into your namespace, which is unnecessary and creates the possibility of name collisions, especially if you do star imports from several modules. It also makes the code less readable, since the reader has remember which names you defined and which ones came from the imported module(s).
In my opinion the easiest way to do this would be using an object orientated method. This way you could declare a button with a command that calls a def which runs self.label.configure(text=self.entry.get()).
This can be seen below:
import tkinter as tk
class App:
def __init__(self, master):
self.master = master
self.label = tk.Label(self.master)
self.entry = tk.Entry(self.master)
self.button = tk.Button(self.master, text="Ok", command=self.command)
self.label.pack()
self.entry.pack()
self.button.pack()
def command(self):
self.label.configure(text=self.entry.get())
root = tk.Tk()
app = App(root)
root.mainloop()
The above creates a label, entry and button. The button has a command which calls a def within the class App and updates the value of the label to be the text contained within the entry.
This all works very smoothly and cleanly and more importantly is drastically easier (in my opinion) to read and update in the future.
From your code you are setting the 'currentValue', which is a StringVar:
#StringVar for currentValue in R0C2
currentValue = StringVar(main, "0")
to an object Label further down in your code. You cannot do this!
#Displays what the value is currently set to.
currentValue = Label(textvariable=currentValue) ** this line is wrong
currentValue.grid(row=0,column=2)
You should name the label something different like:
#Displays what the value is currently set to.
lblCurrentValue = Label(textvariable=currentValue)
lblCurrentValue.grid(row=0,column=2)
Then in your "setValues" method you should use 'StringVar.set(value) to update the label like so:
def setValues():
content = entry.get()
currentValue.set(entry.get())------------------Here I set the value to the entry box value
print(content)
I tend to avoid stringVar and just use:
Label.config(text='*label's text*')
If you need more help I can post you my solution but try and solve it first becasue its the best way to learn. My tip is to make sure you are using correct naming conventions. In tkinter I tend to use lbl..., entryBox... etc before widgets so I know what they are and not to confuse them with variables.

How do you keep a widget in view while scrolling?

I am using Tix to automatically create a scroll bar as the content changes. I want to keep a button or two in the user's view while they scroll through the contents of the application.
I haven't seen this question for Tkinter/Tix yet so I thought I'd ask.
The following code will create a sample of the problem where the button is at a fixed point in the window, and is subject to being scrolled.
from Tkinter import *
import Tix
class some_GUI:
def __init__(self, root):
sw= Tix.ScrolledWindow(root, scrollbar=Tix.AUTO)
sw.pack(fill=Tix.BOTH, expand=1)
frame1 = Frame(sw.window)
frame1.grid(row = 0, column = 1)
frame2 = Frame(sw.window)
frame2.grid(row = 0, column = 2)
def quit():
root.quit()
for i in range(0,300):
label1 = Label(frame1, text = "foo")
label1.grid(row = i, column = 0)
button = Button(frame2, text = "Quit", command = quit)
button.pack()
root = Tix.Tk()
display = some_GUI(root)
root.mainloop()
I want the button(s) to be in "frame2" and centered vertically relative to the application's window. I tried using winfo_height/winfo_width to find the frame's height/ width to work with update, but these values didn't change with the addition of the labels and button.
Attempted/possible solutions:
I put frame2 in sw.subwidgets_all[1] by doing the following:
frame1.pack(side = LEFT)
frame2 = Frame(sw.subwidgets_all()[1])
frame2.pack(side = RIGHT)
button = Button(frame2, text = "Quit", command = quit)
button.pack(side = RIGHT)
This allows the fixed position relative to the application, but the window resizes relative to the button's parent instead of frame1. Another drawback is that the horizontal scrollbar is only relative to frame1.
Find the midpoint of the scrollbar and update the position of the buttons relative to those coordinates using place(maybe?) not sure how to accomplish this, and seeing SO solutions in general I think this might be an inefficient way of doing this.
EDIT: Although this isn't exactly what I had in mind, the following code works as per falsetru's suggestion in the comments:
from Tkinter import *
import Tix
class some_GUI:
def __init__(self, root):
def quit():
root.quit()
frame2 = Frame(root)
frame2.pack(side = RIGHT)
button = Button(frame2, text = "Quit", command = quit)
button.pack()
frame1 = Frame(root)
frame1.pack(side = LEFT)
sw= Tix.ScrolledWindow(frame1, scrollbar=Tix.AUTO)
sw.pack(fill=Tix.BOTH, expand=1)
for widget in sw.subwidgets_all():
print widget
for i in range(0,300):
label1 = Label(sw.window, text = "foo")
label1.grid(row = i, column = i)
print root.winfo_toplevel()
for widget in sw.subwidgets_all():
print widget
root = Tix.Tk()
display = some_GUI(root)
root.mainloop()
You can put the button out of ScrollWindows:
import Tix
from Tkinter import *
def build_ui(root):
sw = Tix.ScrolledWindow(root, scrollbar=Tix.AUTO)
sw.pack(side=LEFT, fill=Tix.BOTH, expand=1)
for i in range(300):
label1 = Label(sw.window, text="foo")
label1.grid(row=i, column=0)
button = Button(root, text="Quit", command=root.quit)
button.pack(side=RIGHT)
root = Tix.Tk()
build_ui(root)
root.mainloop()
The second option you mentioned could be the one that satisfies your situation, however that is computationally expensive as you will need to delete the button(s) and redraw them over and over relatively to the scrollbar up/down motion. Not only this is ugly by design but it can be an obstacle for any further scalability of your application or even lead to unexpected bugs if your application runs some serious operations.
The only realistic solution I see for your problem is to fix the button(s) on (for example the bottom of) the upper canvas (or whatever region you want to set) and outside the scrollable region as #falsetru commented you.

Categories

Resources