In python3 tkinter, the wigdet frame doesn't show in interface - python-3.x

I use the same format of frame but it doesn't show in the interface, hope someone could tell me the solution, thanks.
class Interface(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.master.title("measurement")
self.grid()
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = Frame(self,relief=GROOVE,bg='white')
self.master.Frame1.grid(column=1,row=9)
self.can =Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = Canvas(self.master, width=150, height=120, background='snow')
ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = Tk()
window.resizable(False, False)
Interface(window).mainloop()

I can't figure out why you have 2 Canvas's, but the problem is that you aren't placing them on their respective parents. I cut out a lot of the code that seemed unnecessary and restructured your code to make it more logical:
class Interface(Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
self.Frame1 = Frame(self, relief=GROOVE)
self.Frame1.grid()
self.canvas = Canvas(self.Frame1, bg="ivory", width=200, height=150)
self.canvas.grid()
self.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
root = Tk()
# Tk configurations are not relevant to
# the Interface and should be done out here
root.title('Measurement')
root.geometry('700x400+100+50')
root.resizable(False, False)
Interface(root).pack()
root.mainloop()

i think I don't really understand your problem, you don't see your frame because you don't have any widget in it, that's all
import tkinter as tk
class Interface(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self,parent)
self.master.title("measurement")
self.grid(row=0, column=0)
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = tk.Frame(self,relief='groove',bg='white')
self.master.Frame1.grid(column=1,row=9)
labelExemple =tk.Label(self.master.Frame1, text="Exemple")
labelExemple.grid(row=0,column=0)
self.can = tk.Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = tk.Canvas(self.master, width=150, height=120, background='snow')
self.ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = tk.Tk()
window.resizable(False, False)
Interface(window).mainloop()
PS : use import tkinter as tk instead of from tkinter import *

There are several problems with those few lines of code, almost all having to do with the way you're using grid:
you aren't using the sticky option, so widgets won't expand to fill the space they are given
you aren't setting the weight for any rows or columns, so tkinter doesn't know how to allocate unused space
you aren't using grid or pack to put the canvases inside of frames, so the frames stay their default size of 1x1
The biggest problem is that you're trying to solve all of those problems at once. Layout problems are usually pretty simple to solve as long as you're only trying to solve one problem at a time.
Start by removing all of the widgets from Interface. Then, give that frame a distinctive background color and then try to make it fill the window (assuming that's ultimately what you want it to do). Also, remove the root.resizable(False, False). It's rarely something a user would want (they like to be able to control their windows), plus it makes your job of debugging layout problems harder.
Once you get your instance of Interface to appear, add a single widget and make sure it appears too. Then add the next, and the next, adding one widget at a time and observing how it behaves.

Related

Python scrollable frame and canvas window sizing

I have been trying to find a way to size a frame inside of a canvas window for quite a while to no avail. I finally came across some posts that helped me begin to understand the problem, and eventually dug up a post that gave the solution below:
import tkinter as tk
def onCanvasConfigure(e):
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
root=tk.Tk()
canvas = tk.Canvas(root, background="blue")
frame = tk.Frame(canvas, background="red")
canvas.pack(expand=True, fill="both")
canvas.create_window((0,0), window=frame, anchor="nw", tags="frame")
canvas.bind("<Configure>", onCanvasConfigure)
root.mainloop()
This completely solves my problem....if I don't have the GUI in a function, which I need to. I have multiple different GUI's that would need to implement this solution. I have come across other solutions that use OOP, but I haven't yet wrapped my head around OOP. I've also found a way to make the above code work inside of a program myself:
import tkinter as tk
def onCanvasConfigure(e):
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
def test():
window=tk.Tk()
global canvas
canvas = tk.Canvas(master=window)
frame=tk.Frame(master=canvas, background='red')
canvas.pack(expand=True, fill=tk.BOTH)
canvas.create_window((0,0), window=frame, anchor=tk.NW, tags = 'frame')
canvas.bind("<Configure>", onCanvasConfigure)
root.mainloop()
test()
However, this requires the use of a global variable, which I would rather avoid. Is there anything I'm missing that would help me resize the frame inside of the canvas window? If you have any pointers to where I might even find this information that would also be helpful.
The event object that is passed in has a reference to the widget that received the event. So, just replace your global canvas with e.widget, or initialize a local variable named canvas:
def onCanvasConfigure(e):
canvas = e.widget
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
If it helps, here's an object-oriented version of your application code. Other than the implementation differences, it should behave the same way as the functional version.
import tkinter as tk
class App(tk.Tk): # create a base app class that inherits from Tk
def __init__(self):
super().__init__() # initialize Tk
self.canvas = tk.Canvas(master=self)
self.frame = tk.Frame(master=self.canvas, background='red')
self.canvas.pack(expand=True, fill=tk.BOTH)
self.canvas.create_window(
(0,0),
window=self.frame,
anchor=tk.NW,
tags='frame',
)
self.canvas.bind('<Configure>', self.on_canvas_configure)
def on_canvas_configure(self, event):
self.canvas.itemconfig(
'frame',
height=self.canvas.winfo_height(),
width=self.canvas.winfo_width(),
)
if __name__ == '__main__':
root = App() # instantiate the App class
root.mainloop() # start the app
Since everything here is contained within the App class, you can avoid globals (thanks to self!)

Message widget not filling frame using tkinter

I'm creating a simple user dialog window with a basic text on top and a tree view with one column below, that gives the user a couple of choices. A button at the bottom is used to confirm the selection.
Now I can't get the Message widget, which I use to display the instructions, to fill the Frame I've created for it. Meanwhile, the Treeview widget fills the Frame as I want it to.
Many proposed solutions on other StackOverflow questions state, that putting my_message.pack(fill=tk.X, expand=True) should work. It doesn't in my case.. In a different scenario it is recommended to put my_frame.columnconfigure(0, weight=1), which doesn't help either.
Here is the code:
import tkinter as tk
from tkinter import ttk
class MessageBox(object):
""" Adjusted code from StackOverflow #10057662. """
def __init__(self, msg, option_list):
root = self.root = tk.Tk()
root.geometry("400x400")
root.title('Message')
self.msg = str(msg)
frm_1 = tk.Frame(root)
frm_1.pack(expand=True, fill=tk.X, ipadx=2, ipady=2)
message = tk.Message(frm_1, text=self.msg)
message.pack(expand=True, fill=tk.X) # <------------------------------------ This doesn't show the desired effect!
frm_1.columnconfigure(0, weight=1)
self.tree_view = ttk.Treeview(frm_1)
self.tree_view.heading("#0", text="Filename", anchor=tk.CENTER)
for idx, option in enumerate(option_list):
self.tree_view.insert("", idx+1, text=option)
self.tree_view.pack(fill=tk.X, padx=2, pady=2)
choice_msg = "Long Test string to show, that my frame is unfortunately not correctly filled from side to side, as I would want it to."
choices = ["Test 1", "Test 2", "Test 3"]
test = MessageBox(choice_msg, choices)
test.root.mainloop()
I'm slowly going nuts, because I know that there is probably something very basic overruling the correct positioning of the widget, but I've been trying different StackOverflow solutions and browsing documentation for hours now with no luck.
Try to set a width of message in the tk.Message constructor, something like this:
message = tk.Message(frm_1, text=self.msg, width=400-10) # 400 - is your window width
message.pack() # In that case you can delete <expand=True, fill=tk.X>
The problem you are facing is a feature: The Message widget tries to lay out the text in one of two ways:
according to an aspect (width-to-height ratio in percent)
according to a maximum width (lines will be broken if longer)
Both of these goals seem not to cooperate well with the automatic resizing a Message widget experiences from a grid or pack layout manager. What can be done is to bind a handler to the resize event of the widget to adjusts the width option dynamically. Also, there are better options to use with the pack layout manager than shown in OP.
I derived an AutoMessage widget to get the event handler out of the way:
import tkinter as tk
from tkinter import ttk
class AutoMessage(tk.Message):
"""Message that adapts its width option to its actual window width"""
def __init__(self, parent, *args, **options):
tk.Message.__init__(self, parent, *args, **options)
# The value 4 was found by experiment, it prevents text to be
# displayed outside of the widget (exceeding the right border)
self.padx = 4 + 2 * options.get("padx", 0)
self.bind("<Configure>", self.resize_handler)
def resize_handler(self, event):
self.configure(width=event.width - self.padx)
class MessageBox(object):
"""Adjusted code from StackOverflow #10057662."""
def __init__(self, msg, option_list):
root = self.root = tk.Tk()
root.geometry("400x400")
root.title("Message")
self.msg = str(msg)
self.frm_1 = tk.Frame(root)
self.frm_1.pack(side=tk.TOP, fill=tk.X, padx=2, pady=2)
self.message = AutoMessage(self.frm_1, text=self.msg, anchor=tk.W)
self.message.pack(side=tk.TOP, fill=tk.X)
self.frm_1.columnconfigure(0, weight=1)
self.tree_view = ttk.Treeview(self.frm_1)
self.tree_view.heading("#0", text="Filename", anchor=tk.CENTER)
for idx, option in enumerate(option_list):
self.tree_view.insert("", idx + 1, text=option)
self.tree_view.pack(fill=tk.X, padx=2, pady=2)
choice_msg = "Long Test string to show, that my frame is unfortunately not correctly filled from side to side, as I would want it to."
choices = ["Test 1", "Test 2", "Test 3"]
test = MessageBox(choice_msg, choices)
test.root.mainloop()

Python Tkinter GUI File Menu Not Displaying though GUI is operational

I'm relatively new to Python and I'm sure this is an error with the structure of my code, but I cannot seem to get the filemenu to display in my GUI. Can someone tell me what errors I have made with the filemenu inclusion? Also, I am sorry, but the spacing after copying and pasting is a little off. The class indentation level is proper on my side. I am using Python 3.71
Any other comments on better or more Pythonic ways to accomplish what I have here are also welcome and thank you for your help in advance!
from tkinter import *
from tkinter import ttk
import tkinter.scrolledtext as tkst
import os
import tkinter as tk
from functools import partial
from PIL import Image, ImageTk
class UserGui(tk.Tk):
def __init__(self,parent):
self.parent=parent
self.widgets()
def widgets(self):
self.parent.configure(bg='white')
self.frame1_style = ttk.Style()
self.frame1_style.configure('My.TFrame', background='white')
self.frame2_style = ttk.Style()
self.frame2_style.configure('My2.TFrame',background='white')
self.parent.title("TGUI")
self.frame1 = ttk.Frame(self.parent, style='My.TFrame') #Creating Total Window Frame 1
self.frame1.grid(row=0, column=0, sticky=(N, S, E, W))
self.frame2 = ttk.Frame(self.parent, width=100, height=20, style='My2.TFrame')
self.frame2.grid(row=0, column=6, padx=20, pady=5)
#Menu Creation
self.menu1 = tk.Menu(self.parent, tearoff=0)
self.parent.config(menu=self.menu1)
self.fileMenu = tk.Menu(self.menu1, tearoff=0)
self.fileMenu.add_command(label="Open", command=self.donothing)
self.fileMenu.add_command(label="Save", command=self.donothing)
self.fileMenu.add_separator()
self.fileMenu.add_command(label="Exit", command=self.parent.quit)
self.fileMenu.add_cascade(label="File", menu=self.menu1)
self.editMenu = tk.Menu(self.menu1, tearoff=0)
self.editMenu.add_command(label="Cut", command=self.donothing)
self.editMenu.add_command(label="Copy", command=self.donothing)
self.editMenu.add_command(label="Paste", command=self.donothing)
self.editMenu.add_cascade(label="Edit", menu=self.menu1)
def donothing(self):
filewin = Toplevel(self.parent)
button = Button(filewin, text="Do nothing button")
button.pack()
def main():
root=tk.Tk()
ug=UserGui(root)
root.mainloop()
if __name__ == '__main__':
main()
Edit 1,2,3: I have corrected the add_cascade option for menu with menu=self.menu1 and I still do not have a file menu displaying.
EDIT: I'm sorry I didn't notice the Python-3 tag in time, it's all the same except when inherriting you would call super().__init__ instead of the Frame.__init__ directly. That would make it more Py3-like. Even so, this should still work.
Weirdly, pushing the menu.config down to the run function worked for me - even though it looks like it should work the way you did it.
def main():
root=tk.Tk()
ug=UserGui(root)
root.config(menu=ug.fileMenu)
root.mainloop()
if __name__ == '__main__':
main()
Oterwise there are some things you can work on to make it more OOP like and readable. THis is how I usually handle making GUIs. The idea is to split the GUI's into Frames that then do simmilar things. I.e. your app could have left and right Frame where the RightFrame would hold the textbox ad the left Frame would actually have 2 sub frames - one for the names and dropdowns and the other for the buttons. That way each individual functionality is handled by the Frames themselves and it's not all in one giant class, the elements in those Frames are placed relative to the Frame's grid itself, while all the Frames are placed in the MainFrame's grid. This lets you split a lot of code into modules as well and helps with maintainability.
The sub-frames emit "global" events (events bothering other frames) by propagating them through the MainFrame, that's why they all carry a self.parent - their parent frame, and a self.root - the MainFrame. The MainFrame is also the Frame in which I like to put something like self.data which itself is a class on its own (outside Tkinter) that handles all the data input/output and logic so that you don't clutter the GUI code logic with data calculations and logic. Ideally the Data class would handle data errors and GUI would only then have to handle any errors in logic (such as selecting two impossible-to-combine options from the dropdown menus.
from tkinter import *
from tkinter import ttk
class SubFrame(Frame):
def __init__(self, parent, text="Top Right"):
Frame.__init__(self)
self.pack()
self.parent = parent
self.root = parent.root
self.label=Label(self, text=text).pack()
class RightFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, relief=RAISED, borderwidth=1)
self.pack(side=RIGHT, fill=BOTH, expand=1)
self.root = parent
self.label = Label(self, text="Right Frame").pack()
class LeftFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, relief=RAISED, borderwidth=1)
self.pack(side=LEFT, fill=BOTH, expand=1)
self.root = parent
self.label = Label(self, text="Left Frame").pack()
#again Frames which would have a parent class RightFrame and root MainFrame
self.subFrame1 = SubFrame(self)
self.subFrame2 = SubFrame(self, text="Top Right SubFrame 2")
class MainFrame(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry("1100x600")
self.title("Working Example")
self.leftFrame = LeftFrame(self)
self.rightFrame = RightFrame(self)
#self.data = MagicalDataHandlingClass()
def run():
app = MainFrame()
app.mainloop()
EDIT answer to comments that are too long to fit
The call to Frame.__init__(...) is made because the class definition looks like class LeftFrame(Frame). Usually to declare a class what you would write is just class LeftFrame. When you add the bit in the () what is happening is called inheritance. When you inherit from a class (called parent), your class (called child) inherits all of the methods and attributes of parent. But much like you have to initialize your class to get an object, i.e. lf = LeftFrame(...) the parent class has to be initialized too. In Python this initialization is done by calling the special dunder __init__(...) function. So that call to Frame.__init__(...) happens because you need to tell the parent class what are all the values it needs to work properly. In Python 3 however it is recommended that instead of instantiating the parent by name like that you use the super function like super().__init__(....). This happens for a lot of complicated reasons most of which you probably don't have to worry about for a while yet (such as what if you inherit from multiple classes at the same time, what if you inherit from a class that inherited from a different one, etc...). I wouldn't try to feel overwhelmed by understanding the complete power of super() if you're just starting because 99% of the time in Python 3 just doing super().__init__(...) will do exactly what you want even if you don't understand. If you feel like getting in over your head Raymond Hettinger has a good writeup of Super is Super and why exactly it's so much better than old way.
I will post this answer for completeness considering #JasonHarper has not copied it to an answer format and I want others to be able to benefit from the post.
The key was the object that I was calling the add_cascade on the child Menu widget object instead of the main Menu widget object called self.menu1. The key was changing:
self.fileMenu.add_cascade(label="File", menu=self.menu1)
to :
self.menu1.add_cascade(label="File", menu=self.fileMenu)
This was the proper way of adding the fileMenu Menu object to the total Menu widget object of self.menu1.

Python3 GUI change frame background color

I am relatively new at python and I am building this GUI interface. The code represents something I got mostly online but have been able to edit for my own uses pretty successfully. Anyways, I want to full background to be a different color than just the gray. As you can see, I expanded the widget and can change the widget background color, but the main stuff is still gray.
I assume this means I need to change the Frame background color? Not shown in the code are my several attempts using .configure(), styles, etc to accomplish my goal. I was successful a few ways to make the background of the text portions a different color, but not the overall.
I assume I am missing something small in syntax and welcome any help getting it to work - thanks!
import tkinter
from tkinter import tt
class Adder(ttk.Frame):
"""The adders gui and functions."""
def __init__(self, parent, *args, **kwargs):
ttk.Frame.__init__(self, parent, *args, **kwargs)
self.root = parent
self.init_gui()
def init_gui(self):
"""Builds GUI."""
self.root.title('Number Adder')
self.grid(column=0, row=0, sticky='nsew')
self.num1_entry = ttk.Entry(self, width=5)
self.num1_entry.grid(column=1, row = 2)
ttk.Label(self, text='Number Adder').grid(column=0, row=0,
columnspan=4)
ttk.Label(self, text='Number one').grid(column=0, row=2,
sticky='w')
for child in self.winfo_children():
child.grid_configure(padx=5, pady=5)
if __name__ == '__main__':
root = tkinter.Tk()
root.geometry('200x100')
root.configure(background = 'blue')
Adder(root)
root.mainloop()
To set background color for a widget you first have to create a style. Then you have to configure the style background color and associate it with the type of widget you are using it on. Make theese three lines the last lines of your __init__() function:
style = ttk.Style() # Create style
style.configure("Blue.TFrame", background="blue") # Set bg color
self.config(style='Blue.TFrame') # Apply style to widget
The name has to end with a section that identifies the type of widget, in this case a Frame => "TFrame". Blue is the name I gave it to separate if from other frame styles I might have. If you just want the one style you can call it "TFrame".
Have a look at TkDocs:Styles and Themes

Scheduling order of events with tkinter OOP

I am using tkinter in python 3.6 to display a blue square in the middle of a window. At each click the blue square should disappear and reappear after 2 seconds on a different random location. When running the following code, the blue square (referred as stimulus) does not disappear. Everything else seem to work properly.
This is the code:
import tkinter as TK
import random as RAN
class THR:
def __init__(self, root):
self.root = root
self.root.config(background='black')
self.screenYpixels = 600
self.screenXpixels = 1024
self.ITI = 2000
self.background = TK.Canvas(root, width=1024, height=600, bg='black',
bd=0, highlightthickness=0, relief='ridge')
self.background.pack()
self.newtrial()
def newtrial(self):
self.xpos = RAN.randrange(200, 1000)
self.ypos = RAN.randrange(100, 500)
self.stimulus = TK.Canvas(root,width=100,height=100,bg='blue', bd=0,
highlightthickness=0, relief='ridge')
self.stimulus.place(x=self.xpos, y=self.ypos, anchor="c")
self.stimulus.bind("<Button-1>", self.response)
self.exitbutton()
def response(self, event):
self.stimulus.place_forget()
self.intertrialinterval()
def intertrialinterval(self, *args):
self.root.after(self.ITI,self.newtrial())
def exitbutton(self):
self.exitButton = TK.Button(self.root, bg="green")
self.exitButton.place(relx=0.99, rely=0.01, anchor="c")
self.exitButton.bind("<Button-1>", self.exitprogram)
def exitprogram(self, root):
self.root.quit()
root = TK.Tk()
THR(root)
root.mainloop()
Here a list of things I tried but that did not work
Using time.sleep instead of root.after
Changing the way a newtrial() is called
Putting the sleep or after in different places
The last couple of hours searching the web for similar problems / solutions did not really help. I would like to fix this and at the same time understand what am I doing wrong.
As it turns out the list of links on the right of this webpage provided me with the solution just 3 minutes after posting the question.
This is the original thread Python Tkinter after event OOPS implementation
and here I write the solution:
dropping the parenthesis in the function called with the after method. So this self.root.after(self.ITI,self.newtrial()) is self.root.after(self.ITI,self.newtrial)
Unfortunately I still do not understand how that fixed the problem..

Resources