Tkinter winfo_reqwidth not proper value - python

I am trying to write a application where i will get the size of the screen and then resize GUI to take full screen accordingly.
Also, i have 3 frames inside that application. The three frames will have separate widgets. Now based on the screen size available i would like to change the layout of the widgets a bit and also the size of text and wrap length of the texts.
I understood that frame will not have proper size until it have a widget, so i am adding dummy canvas widget. But then also the width of the frames are not getting proper. Am i not doing the thing properly ? If you run the below code frame 1 and frame 2 are having same widths even though visually they are not at all same.
import tkinter as tk
from win32api import GetSystemMetrics
class MainApplication:
def __init__(self):
self.root = tk.Tk()
self.width = GetSystemMetrics(0)
self.height = GetSystemMetrics(1)
self.root.geometry('{}x{}'.format(self.width, self.height))
frame1 = tk.Frame(self.root,bg='blue')
frame1.grid(row=0, column=0, sticky="nsew",rowspan=2)
self.root.grid_columnconfigure(0, weight=5000)
frame1.grid_rowconfigure(0,weight=1)
frame1.grid_columnconfigure(0,weight=1)
self.canvas = tk.Canvas(frame1,bg='blue')
self.canvas.grid(row=0, column=0, sticky="nsew")
self.canvas.update()
print(frame1.winfo_reqwidth())
self.frame2 = tk.Frame(self.root,bg='red')
self.root.grid_columnconfigure(1, weight=1)
self.frame2.grid(row=0, column=1, sticky="nsew")
self.frame2.grid_rowconfigure(0, weight=1)
self.frame2.grid_columnconfigure(0, weight=1)
self.canvas1 = tk.Canvas(self.frame2,bg='red')
self.canvas1.grid(row=0, column=0, sticky="nsew")
self.canvas1.update()
print(self.frame2.winfo_reqwidth())
self.frame3 = tk.Frame(self.root, bg='green')
self.frame3.grid(row=1,column=1,sticky="nsew")
self.root.grid_rowconfigure(1,weight=5000)
self.frame3.grid_rowconfigure(0, weight=1)
self.frame3.grid_columnconfigure(0, weight=1)
self.canvas2 = tk.Canvas(self.frame3,bg='green')
self.canvas2.grid(row=0, column=0, sticky="nsew")
self.canvas2.update()
self.root.mainloop()
if __name__ == '__main__':
a = MainApplication()

Related

Why isn't my frame showing up on my window?

import tkinter
from tkinter import ttk
class GUI(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
self.geometry("500x500")
self.title("Red Frame Example")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
red_frame_style = ttk.Style()
red_frame_style.configure("RedFrame.TFrame", background = "red")
red_frame = ttk.Frame(self, style="RedFrame.TFrame", width="250", height="250")
red_frame.grid_rowconfigure(0, weight=1)
red_frame.grid_columnconfigure(0, weight=1)
red_frame.grid(row=0, column=0, sticky="n")
button = tkinter.Button(red_frame, text='Stop', width=25, command=self.destroy)
button.grid(row = 1, column = 0)
gui = GUI()
gui.mainloop()
I am trying to get a 250 by 250 red frame to show up on my window in tkinter. Answers to this question suggested splitting a line like red_frame = tkinter.Frame(self, style="RedFrame.TFrame", width="250", height="250").grid(row=0, column=0, sticky="n") into 2 different lines, which is what I tried to do in my code below, but the red frame still did not appear. What am I doing wrong here, and how can I fix it?

How can I create this layout with Python3 using Tkinter with the grid geometry manager?

I am trying to create the following layout:
This is my code:
from . import FlowPane,JsonPane,PropertiesPane,ToolbarPane
import tkinter as tk
class ConflixEditor(tk.Tk):
def __init__(self, args):
super().__init__()
self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})
self.minsize(width=1024, height=768)
self.title('Conflix Editor')
# Widget Creation
self.frame = tk.Frame(self)
self.toolbarPane = ToolbarPane.ToolbarPane(self.frame, bg='black')
self.flowPane = FlowPane.FlowPane(self.frame, bg='red')
self.propertiesPane = PropertiesPane.PropertiesPane(self.frame, bg='blue')
self.jsonPane = JsonPane.JsonPane(self.frame, bg='green')
# Widget Layout
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(2, weight=1)
self.frame.grid(row=0, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
self.toolbarPane.grid(row=0, column=0, columnspan=3, rowspan=2, sticky=tk.N+tk.E+tk.W)
self.flowPane.grid(row=2, column=0, columnspan=2, rowspan=5, sticky=tk.N+tk.S+tk.W)
self.propertiesPane.grid(row=2, column=2, columnspan=1, rowspan=5, sticky=tk.N+tk.E+tk.S)
self.jsonPane.grid(row=7, column=0, columnspan=3, rowspan=3, sticky=tk.E+tk.S+tk.W)
The constructors for FlowPane, JsonPane, PropertiesPane, ToolbarPane all take two parameters: the parent widget and the background color.
Instead of getting the desired result above, I am getting the following result:
What am I doing wrong? How can I create the desired layout? Note that the background colors are just temporary to confirm that each widget is using the correct amount of space. This is eventually going to be an application for designing and building Netflix Conductor workflows. I want to have a toolbar with menus and buttons in the black area, a Canvas in the red area for displaying the flow-chart elements that represent tasks in the workflows, a Treeview for viewing properties in the blue area, and a Text Editor in the green area at the bottom for viewing/editing the raw JSON.
You need to:
specify height option for toolbarPane and jsonPane;
specify width option for propertiesPane;
add tk.E to sticky option for flowPane;
use grid_rowconfigure() and grid_columnconfigure() for self and self.frame as well
Below is an updated code:
class ConflixEditor(tk.Tk):
def __init__(self, *args):
super().__init__()
#self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})
self.minsize(width=1024, height=768)
self.title('Conflix Editor')
# make self.frame use all the space of root window
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
# Widget Creation
self.frame = tk.Frame(self)
self.toolbarPane = ToolbarPane.ToolbarPane(self.frame, bg='black', height=100) # added height option
self.flowPane = FlowPane.FlowPane(self.frame, bg='red')
self.propertiesPane = PropertiesPane.PropertiesPane(self.frame, bg='blue', width=250) # added width option
self.jsonPane = JsonPane.JsonPane(self.frame, bg='green', height=200) # added height option
# Widget Layout
self.frame.grid_columnconfigure(0, weight=1) # used self.frame instead of self
self.frame.grid_rowconfigure(2, weight=1) # used self.frame instead of self
self.frame.grid(row=0, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
self.toolbarPane.grid(row=0, column=0, columnspan=3, rowspan=2, sticky=tk.N+tk.E+tk.W)
self.flowPane.grid(row=2, column=0, columnspan=2, rowspan=5, sticky=tk.N+tk.E+tk.S+tk.W) # added tk.E
self.propertiesPane.grid(row=2, column=2, columnspan=1, rowspan=5, sticky=tk.N+tk.E+tk.S)
self.jsonPane.grid(row=7, column=0, columnspan=3, rowspan=3, sticky=tk.E+tk.S+tk.W)

Tkinter canvas & scrollbar with grid

I have a canvas in a frame
photoFrame = Frame(centerFrame, width=250, height=190, bg="#EBEBEB")
photoFrame.grid(row=0, column=1, sticky="nsew")
photoCanvas = Canvas(photoFrame, bg="#EBEBEB")
photoCanvas.grid(row=0, column=0, sticky="nsew")
and I try to put a scrollbar to my canvas with this
photoScroll = Scrollbar(photoFrame, orient=VERTICAL)
photoScroll.config(command=photoCanvas.yview)
photoCanvas.config(yscrollcommand=photoScroll.set)
photoScroll.grid(row=0, column=1, sticky="ns")
The scrollbar appears but it's disabled. Can you help me please ?
Sorry for my bad english.
In a for loop I add lots of Image button with this code
element = Button(photoCanvas, image = listPhotos[i], borderwidth=0, height = 200, width = 200, bg="#EBEBEB")
element.grid(row=rowPhoto, column=columnPhoto, padx=5, pady=5, sticky="nsew")
Finnally I have this
root = Tk()
photoFrame = Frame(root, width=250, height=190, bg="#EBEBEB")
photoCanvas = Canvas(photoFrame, bg="#EBEBEB")
photoCanvas.grid(row=0, column=0, sticky="nsew")
for i in range(0, len(listPhotos), 1):
element = Button(photoCanvas, image = listPhotos[i], borderwidth=0, height = 200, width = 200, bg="#EBEBEB")
element.grid(row=rowPhoto, column=columnPhoto, padx=5, pady=5, sticky="nsew")
photoScroll=Scrollbar(photoFrame,orient=VERTICAL)
photoScroll.config(command=photoCanvas.yview)
photoCanvas.config(yscrollcommand=photoScroll.set)
photoScroll.grid(row=0, column=1, sticky="ns")
in my app, the purple rectangle is the next frame and I need a vertical scrollbar
Say if you have some questions
One way to scroll a group of widgets is to put them (with grid of pack) inside a frame and put this frame inside a canvas.
The two key elements (besides connecting the scrollbar to the canvas) for the scrolling to work are:
Use canvas.create_window(x, y, window=frame) to put the frame inside the canvas so that it is treated like a canvas item.
Update the canvas scrollregion each time the size of the frame changes (for instance after adding a new widget) with canvas.configure(scrollregion=canvas.bbox('all')).
Here is an adaptation of the code of the question Python Tkinter scrollbar for frame, but using the widgets name from the OP's question and grid instead of pack:
import tkinter as tk
def update_scrollregion(event):
photoCanvas.configure(scrollregion=photoCanvas.bbox("all"))
root = tk.Tk()
photoFrame = tk.Frame(root, width=250, height=190, bg="#EBEBEB")
photoFrame.grid()
photoFrame.rowconfigure(0, weight=1)
photoFrame.columnconfigure(0, weight=1)
photoCanvas = tk.Canvas(photoFrame, bg="#EBEBEB")
photoCanvas.grid(row=0, column=0, sticky="nsew")
canvasFrame = tk.Frame(photoCanvas, bg="#EBEBEB")
photoCanvas.create_window(0, 0, window=canvasFrame, anchor='nw')
for i in range(10):
element = tk.Button(canvasFrame, text='Button %i' % i, borderwidth=0, bg="#EBEBEB")
element.grid(padx=5, pady=5, sticky="nsew")
photoScroll = tk.Scrollbar(photoFrame, orient=tk.VERTICAL)
photoScroll.config(command=photoCanvas.yview)
photoCanvas.config(yscrollcommand=photoScroll.set)
photoScroll.grid(row=0, column=1, sticky="ns")
canvasFrame.bind("<Configure>", update_scrollregion)
root.mainloop()

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()

Python Tkinter Canvas Many create_window() Items Not Scrolling with Scrollbar

Why doesn't the scrollbar initiate when create_window frame objects start to exceed the bottom self.container window?
My understanding is that widgets are scrollable if they are embedded on the canvas using create_window. For context, I don't want to create a scrolling frame - put all your widget in a frame, use create_window to add that frame to the canvas - because I intend to move these frame objects around on the canvas and leverage a lot of canvas capabilities. According to Effbot, You cannot draw other canvas items on top of a widget., so if I had a scrolling frame, I wouldn't be able to put widgets on top of that.
So how do I scroll the canvas that contains many create_window objects, or, what am I doing wrong below?
import tkinter as tk
class Canvas_Scrollbar_CreateWindow(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.parent.columnconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.block_count = 0
self.button = tk.Button(self, text='Add', command=self.addblock)
self.button.grid(row=0, column=0, columnspan=2, sticky='new')
self.container = tk.Frame(self)
self.container.grid(row=1, column=0, sticky='nsew')
self.canvas = tk.Canvas(self.container, width=200, height=450)
self.scrollbar = tk.Scrollbar(self.container,
orient='vertical',command=self.canvas.yview)
self.canvas.config(yscrollcommand=self.scrollbar.set)
self.canvas.grid(row=0, column=0, sticky='nsew')
self.scrollbar.grid(row=0, column=1, sticky='nse')
self.container.bind('<Configure>', self.handle_scroll)
def addblock(self):
self.block = tk.Frame(self.canvas, bd=1, relief='solid')
self.block.columnconfigure(0, weight=1)
self.canvas.create_window((0, (self.block_count*25)),
window=self.block, anchor="nw",
width=200, height=24)
self.block_count += 1
def handle_scroll(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
root = tk.Tk()
app = Canvas_Scrollbar_CreateWindow(root)
app.grid(row=0, column=0, sticky='ew')
root.mainloop()
You must re-configure scrollregion when you add items to the canvas.

Categories