Tkinter tkSimpleDialog.Dialog resize - python

I am creating a class that derives from tkSimpleDialog.Dialog.
I have implemented the body(self, master) method, where I create some widgets (buttons, labels) and layout them using grid().
I set up the grid layout to stretch, but it never takes the full window. Furthermore, if I resize the window, the widgets will stay in place and not move.
Here's the initial layout:
And here's what happens when I resize the window:
Here's my code:
import Tkinter
import tkSimpleDialog
class Test(tkSimpleDialog.Dialog):
# ########################################
def __init__(self, parent):
tkSimpleDialog.Dialog.__init__(self, parent, "?")
# ########################################
def body(self, master):
Tkinter.Label(master, text="Hello").grid(row=0, column=0, columnspan=2, sticky="nsew")
Tkinter.Button(master, text="ONE").grid(row=1, column=0, sticky="nsew")
Tkinter.Button(master, text="TWO").grid(row=1, column=1, sticky="nsew")
master.columnconfigure(0, weight=1)
master.columnconfigure(1, weight=1)
master.rowconfigure(0, weight=1)
master.rowconfigure(1, weight=1)
self.resizable(height=True, width=True)
if __name__ == "__main__":
Test(Tkinter.Tk("test"))
The desired behavior is: buttons ONE and TWO will stretch to fill the window. Any suggestion?
Thanks!

This appears to be a bug in the Dialog class. In the code where it packs the main part of the dialog it does this:
body.pack(padx=5, pady=5)
Note that it doesn't include an expand or fill attribute, so the body isn't going to grow to fill the extra room in its parent.

Related

Is their a grid_remember()? Reversible grid_forget()?

I am confused on the documentation surrounding the tkinter "grid_forget()"
I know that this function does not permanently delete the widget ascribed to it, however I do not know how to call it again. Further, if the widget is forgotten in a frame, can it be called back to the same the frame?
You can call grid() with no parameters to reverse the effects of grid_remove().
In the following example there is a label that is placed at the top of the window with grid. There is a toggle button that will alternate between calling grid and grid_remove to show that calling grid with no parameters will restore the message exactly as it was.
Notice, for example, that both the row, column, and columnspan attributes are remembered when the message reappears.
import tkinter as tk
class Example():
def __init__(self):
self.root = tk.Tk()
self.root.grid_rowconfigure(2, weight=1)
self.root.grid_columnconfigure(1, weight=1)
self.toolbar = tk.Frame(self.root)
self.toggle = tk.Button(self.toolbar, text="Toggle the message",
command=self.toggle_message)
self.toggle.pack(side="left")
# simulate a typical app with a navigation area on the left and a main
# working area on the right
self.navpanel = tk.Frame(self.root, background="bisque", width=100, height=200)
self.main = tk.Frame(self.root, background="white", width=300, height=200, bd=1, relief='sunken')
self.message = tk.Label(self.root, text="Hello, world!")
self.toolbar.grid(row=0, column=0, columnspan=2)
self.message.grid(row=1, column=0, columnspan=2)
self.navpanel.grid(row=2, column=0, sticky="nsew")
self.main.grid(row=2, column=1, sticky="nsew")
def start(self):
self.root.mainloop()
def toggle_message(self):
if self.message.winfo_viewable():
self.message.grid_remove()
else:
self.message.grid()
if __name__ == "__main__":
Example().start()
If you change the code from using grid_remove to using grid_forget, restoring the label will not put it back in the same place or with the same options. That is the main distinction between grid_remove and grid_forget -- grid_forget literally forgets the grid options whereas grid_remove removes the widget but remembers the settings.
Here is a simple example to illustrate what is happening when you remove a widget from the grid then re-grid it. You simply need to re apply the grid the same way you would have done in the first place. You can even chose a different grid location if you like. Though I am not sure if you can change the container it was originally assigned to. If not then it will only be able to be re-added to the original container the widget was assigned to.
import tkinter as tk
root = tk.Tk()
some_label = tk.Label(root, text="IM HERE!")
some_label.grid(row=0, column=0, columnspan=2)
def forget_label():
some_label.grid_forget()
def return_label():
some_label.grid(row=0, column=0, columnspan=2)
tk.Button(root, text="Forget Label", command=forget_label).grid(row=1, column=0)
tk.Button(root, text="Return Label", command=return_label).grid(row=1, column=1)
root.mainloop()

Expand one widget vertically while locking another with Tkinter/ttk

I have a treeview inside of a frame that sits on top of another frame containing buttons. I would like the top frame to expand when I resize the window but keep the button frame from doing the same.
Code in Python 2.7.5:
class MyWindow(Tk.Toplevel, object):
def __init__(self, master=None, other_stuff=None):
super(MyWindow, self).__init__(master)
self.other_stuff = other_stuff
self.master = master
self.resizable(True, True)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
# Top Frame
top_frame = ttk.Frame(self)
top_frame.grid(row=0, column=0, sticky=Tk.NSEW)
top_frame.grid_columnconfigure(0, weight=1)
top_frame.grid_rowconfigure(0, weight=1)
top_frame.grid_rowconfigure(1, weight=1)
# Treeview
self.tree = ttk.Treeview(top_frame, columns=('Value'))
self.tree.grid(row=0, column=0, sticky=Tk.NSEW)
self.tree.column("Value", width=100, anchor=Tk.CENTER)
self.tree.heading("#0", text="Name")
self.tree.heading("Value", text="Value")
# Button Frame
button_frame = ttk.Frame(self)
button_frame.grid(row=1, column=0, sticky=Tk.NSEW)
button_frame.grid_columnconfigure(0, weight=1)
button_frame.grid_rowconfigure(0, weight=1)
# Send Button
send_button = ttk.Button(button_frame, text="Send",
command=self.on_send)
send_button.grid(row=1, column=0, sticky=Tk.SW)
send_button.grid_columnconfigure(0, weight=1)
# Close Button
close_button = ttk.Button(button_frame, text="Close",
command=self.on_close)
close_button.grid(row=1, column=0, sticky=Tk.SE)
close_button.grid_columnconfigure(0, weight=1)
I make the instance elsewhere like this:
window = MyWindow(master=self, other_stuff=self._other_stuff)
What I have tried:
Tried locking resizability which only made the buttons disappear. I also tried changing weights around but my current configuration is the only way everything shows up on screen.
What it should always look like no matter how long the height:
What I want to prevent:
Thanks in advance.
The problem isn't that the button frame is growing, it's that the top frame is growing but isn't using all of it's space. This is because you are giving row 1 of top_frame a weight of 1 but you don't put anything in row 1. Extra space is being allocated to row 1 because of its weight, but row 1 is empty.
An easy way to visualize this is to change top_frame to a tk (rather than ttk) frame, and temporarily give it a distinctive background color. You will see that when you resize the window, top_frame fills the window as a whole, but that it is partially empty.
Create top_frame like this:
top_frame = Tk.Frame(self, background="pink")
... yields a screen like the following image when you resize the window. Note that the pink top_frame is showing through, and that button_frame remains its preferred size.
You can fix this by simply removing this one line of code:
top_frame.grid_rowconfigure(1, weight=1)

Tkinter window in canvas doesn't fill its parent in height

I would like to have my scrollbar in the bottom of the frame and my text widgets filling the whole frame above the scrollbar. I found some solution about the width configuration here but when I try to replace width with height, it does not work correctly.
from tkinter import *
from tkinter import ttk
class MainView(Frame):
def FrameHeight(self, event):
canvas_height = event.height
self.canvas.itemconfig(self.canvas_frame, height=canvas_height)
def OnFrameConfigure(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
sensorsFrame = Frame(self)
sensorsFrame.grid(row=0, sticky="nsew")
sensorsFrame.grid_columnconfigure(0, weight=1)
sensorsFrame.grid_rowconfigure(0, weight=1)
self.canvas = Canvas(sensorsFrame)
self.sensorsStatsFrame = Frame(self.canvas)
self.canvas.grid_rowconfigure(0, weight=1)
self.sensorsStatsFrame.grid_rowconfigure(0, weight=1)
myscrollbar = Scrollbar(sensorsFrame,orient=HORIZONTAL,command=self.canvas.xview)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.pack(fill=BOTH, expand=1)
myscrollbar.pack(fill=X, expand=1)
test0 = Text(self.sensorsStatsFrame, state=DISABLED)
test1 = Text(self.sensorsStatsFrame, width=150)
test0.grid(column=0, row=0, sticky="nsew")
test1.grid(column=1, row=0, sticky="nsew")
self.canvas_frame = self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
self.sensorsStatsFrame.bind("<Configure>", self.OnFrameConfigure)
#When I try to use what i found
#self.canvas.bind('<Configure>', self.FrameHeight)
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()
Step 1: Remove space above and below the scrollbar
The expand option determines how tkinter handles unallocated space. Extra space will be evenly allocated to all widgets where the value is 1 or True. Because it's set to 1 for the scrollbar, it is given some of the extra space, causing the padding above and below the widget.
What you want instead is for all of the space to be allocated only to the canvas. Do this by setting expand to zero on the scrollbar:
myscrollbar.pack(fill=X, expand=0)
Step 2: call a function when the canvas changes size
The next problem is that you want the inner frame to grow when the canvas changes size, so you need to bind to the <Configure> event of the canvas.
def OnCanvasConfigure(self, event):
<code to set the size of the inner frame>
...
self.canvas.bind("<Configure>", self.OnCanvasConfigure)
Step 3: let the canvas control the size of the inner frame
You can't just change the size of the inner frame in OnCanvasConfigure, because the default behavior of a frame is to shrink to fit its contents. In this case you want the contents to expand to fit the frame rather than the frame shrink to fit the contents.
There are a couple ways you can fix this. You can turn geometry propagation off for the inner frame, which will prevent the inner widgets from changing the size of the frame. Or, you can let the canvas force the size of the frame.
The second solution is the easiest. All we have to do is use the height of the canvas for the frame height, and the sum of the widths of the inner text widgets for the frame width.
def OnCanvasConfigure(self, event):
width = 0
for child in self.sensorsStatsFrame.grid_slaves():
width += child.winfo_reqwidth()
self.canvas.itemconfigure(self.canvas_frame, width=width, height=event.height)
Step 4: fix the scrollbar
There's still one more problem to solve. If you resize the window you'll notice that tkinter will chop off the scrollbar if the window gets too small. You can solve this by removing the ability to resize the window but your users will hate that.
A better solution is to cause the text widgets to shrink before the scrollbar is chopped off. You control this by the order in which you call pack.
When there isn't enough room to fit all of the widgets, tkinter will start reducing the size of widgets, starting with the last widget added to the window. In your code the scrollbar is the last widget, but if instead you make it the canvas, the scrollbar will remain untouched and the canvas will shrink instead (which in turn causes the frame to shrink, which causes the text widgets to shrink).
myscrollbar.pack(side="bottom", fill=X, expand=0)
self.canvas.pack(fill=BOTH, expand=1)
Changing pack layout to grid layout for self.canvas and myscrollbar makes it work.
from tkinter import *
from tkinter import ttk
class MainView(Frame):
def FrameHeight(self, event):
canvas_height = event.height
self.canvas.itemconfig(self.canvas_frame, height = canvas_height)
def OnFrameConfigure(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
sensorsFrame = Frame(self)
sensorsFrame.grid(row=0, sticky="nsew")
sensorsFrame.grid_rowconfigure(0, weight=1)
sensorsFrame.grid_columnconfigure(0, weight=1)
self.canvas = Canvas(sensorsFrame, bg="blue")
self.sensorsStatsFrame = Frame(self.canvas)
self.canvas.grid_rowconfigure(0, weight=1)
self.sensorsStatsFrame.grid_rowconfigure(0, weight=1)
myscrollbar = Scrollbar(sensorsFrame,orient=HORIZONTAL,command=self.canvas.xview)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.grid(row=0, sticky="nsew")
myscrollbar.grid(row=1, sticky="nsew")
test0 = Text(self.sensorsStatsFrame, state=DISABLED, bg="red")
test1 = Text(self.sensorsStatsFrame, width=150)
test0.grid(column=0, row=0, sticky="nsew")
test1.grid(column=1, row=0, sticky="nsew")
self.canvas_frame = self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
self.sensorsStatsFrame.bind("<Configure>", self.OnFrameConfigure)
self.canvas.bind('<Configure>', self.FrameHeight)
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()

How to horizontally center a widget using grid()?

I am using grid() to place widgets in a tkinter window. I am trying to put a label on the horizontal center of a window and have it stay there, even if the window is resized. How could I go about doing this?
I don't want to use pack(), by the way. I would like to keep using grid().
There's no trick -- the widget is centered in the area allocated to it by default. Simply place a label in a cell without any sticky attributes and it will be centered.
Now, the other question is, how to get the area it is allocated to be centered. That depends on many other factors, such as what other widgets are there, how they are arranged, etc.
Here's a simple example showing a single centered label. It does this by making sure the row and column it is in takes up all extra space. Notice that the label stays centered no matter how big you make the window.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="This should be centered")
label.grid(row=1, column=1)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(1, weight=1)
if __name__ == "__main__":
root = tk.Tk()
Example(root).grid(sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
You can get a similar effect by giving a weight to all rows and columns except the one with the label.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="This should be centered")
label.grid(row=1, column=1)
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(2, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(2, weight=1)
if __name__ == "__main__":
root = tk.Tk()
Example(root).grid(sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
There is nothing special required. A widget will be in the middle of it's parent automatically. What is required to to tell the parent to fill all available space.
from tkinter import *
root = Tk()
root.geometry("500x500+0+0")
frmMain = Frame(root,bg="blue")
startbutton = Button(frmMain, text="Start",height=1,width=4)
startbutton.grid()
#Configure the row/col of our frame and root window to be resizable and fill all available space
frmMain.grid(row=0, column=0, sticky="NESW")
frmMain.grid_rowconfigure(0, weight=1)
frmMain.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
This uses grid rather than pack to place the widgets and the grid is configured to fill the entire size of the window. The button will appear in the centre regardless of the size of the window.
This worked for me:
lbl1 = Label(self, text='some text')
lbl1.grid(row=1, column=1, sticky='e') # right aligned
# sticky='w' # left aligned
# sticky='' # centered (or no sticky)
There is another way although it is not .grid it is .place. I found that when using .grid and sticky to centre, the widget would not centre if you were only using one column.
label1 = Label(loginWindow, text="Please Log In", font=("Arial", 25))
label1.place(anchor = CENTER, relx = .5, rely = .2)
Where relx and rely is the % of the screen it is located, and anchor=CENTER centres all the widgets.

How to add scrollbar to text widget?

How to add scrollbar to text widget if text-widget inside top-level widget and added to layout via grid geometry manager.
I mean i got this inside "toplevel" window/dialog:
ttk.Label(toplevel,text="Text Area").grid(row=8,sticky=E)
self.TextAreaCCOrder=Text(toplevel,height=10,width=50 ).grid(row=8,column=1)
PS: I'm noob :)
Here's an example that creates a frame with a scrollbar and a text widget:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# create the text and scrollbar widgets
text = tk.Text(self, wrap="word")
vsb = tk.Scrollbar(self, orient="vertical")
# connect them to each other
text.configure(yscrollcommand=vsb.set)
vsb.configure(command=text.yview)
# use grid to arrange the widgets (though pack is simpler if
# you only have a single scrollbar)
vsb.grid(row=0, column=1, sticky="ns")
text.grid(row=0, column=0, sticky="nsew")
# configure grid such that the cell containing the text
# widget grows and shrinks with the window
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
if __name__ == "__main__":
root = tk.Tk()
frame = Example(parent=root)
frame.pack(side="top", fill="both", expand=True)
root.mainloop()

Categories