Tkinter Grid Geometry Resizing issue - python-3.x

Browse Button in Right Hand side Panel is being separated when resizing the window. I want the whole thing to stay together and resizing equally.
import tkinter as tk
from tkinter import ttk
from tkinter import *
class AppLayout(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.masterPane = tk.PanedWindow(self )
self.leftPane = tk.Frame(self.masterPane,relief = 'raised',bg='black',width =100)
self.masterPane.add(self.leftPane)
self.rightPane = tk.Frame(self.masterPane)
self.masterPane.add(self.rightPane)
self.masterPane.pack(fill = 'both',expand = True)
name_entry = tk.Entry(self.rightPane,font =('calibre',10,'normal'))
Browse_Button = tk.Button(self.rightPane,text = 'Browse')
Upload_Button = tk.Button(self.rightPane,text = 'Upload')
name_entry.grid(row=1,column=1)
Browse_Button.grid(row=1,column=2)
Upload_Button.grid(row=1,column=1,pady =(50,0))
self.rightPane.columnconfigure(1, weight=1)
self.rightPane.rowconfigure(1, weight=1)
app = AppLayout()
app.mainloop()

There are four things you need to change:
don't double-import tkinter. It's enough to import * from it.
configure the second column of the rightPane to extend when you resize the window.
Put the button at the western side (W) of the second column by adding sticky=W to the .grid() method.
Glue the Entry widget to the Eastern and Western side of column 1. So, it will get wider when this column extends.
With the following code, it works.
You can also use the .colmnconfigure() and .rowconfigure() method on frame widgets to specify how the other pane extends and how the app resizes vertically.
from tkinter import ttk
from tkinter import *
class AppLayout(Tk):
def __init__(self):
Tk.__init__(self)
self.masterPane = PanedWindow(self )
self.leftPane = Frame(self.masterPane,relief = 'raised',bg='black',width =100)
self.masterPane.add(self.leftPane)
self.rightPane = Frame(self.masterPane)
self.rightPane.columnconfigure(2, weight=1)
self.masterPane.add(self.rightPane)
self.masterPane.pack(fill = 'both',expand = True)
name_entry = Entry(self.rightPane,font =('calibre',10,'normal'))
Browse_Button = Button(self.rightPane,text = 'Browse')
Upload_Button = Button(self.rightPane,text = 'Upload')
name_entry.grid(row=1,column=1,sticky=W+E)
Browse_Button.grid(row=1,column=2, sticky=W)
Upload_Button.grid(row=1,column=1,pady =(50,0))
self.rightPane.columnconfigure(1, weight=1)
self.rightPane.rowconfigure(1, weight=1)
app = AppLayout()
app.mainloop()

Related

Tkinter how update main window combobox values from Toplevel list python3.8

I have 3 modules (small, dont worry).
main_module = it has a combobox and a button. Comobobox list must be update each time a list (in module2) increases in number of names (combo values). Button calls the second window (module2)-->
myapp_second_window.py which has a entry box and another button. We write a name in the entry, push the button...voila..the list increases. In the origina app the list is created automatically when (2) is called.
Now I pass the list to a Pages.variable that is in -->
my_pages_to_connect_modules.
So, when app start I can populate combobox calling (2) to generate a Pages.variable list or populate combobox with json previously written.
The problem? --> how populate combobox while app is running. I mean, we go to (2) create a new name in entry come back to (1) and it is already there.
main_module
import tkinter as tk
from tkinter import*
from tkinter import ttk
import myapp_second_window
from myapp_second_window import SecondClass
root= Tk()
root.geometry("500x500")
root.title('myAPP_Main_Window')
class MainClass:
def __init__(self, parent,myapp_second_window):
self.parent = parent
self.my_widgets1()
def call_second_page (self):
Window2 = tk.Toplevel(root)
Window2.geometry('400x300')
myapp_second_window.SecondClass(Window2)
def my_widgets1(self):
self.field1_value = StringVar()
self.field1 = ttk.Combobox(self.parent, textvariable=self.field1_value)
self.field1['values'] = [1,2] # Pages.variable comes Here
self.field1.grid( row=0, column=0)
self.myButton = tk.Button(self.parent, text = "Call Second module", command = self.call_second_page)
self.myButton.grid(row=2, column=0)
if __name__ == '__main__':
app = MainClass(root, myapp_second_window)
root.mainloop()
myapp_second_window.py
import tkinter as tk
from tkinter import*
from tkinter import ttk
root= Tk()
root.minsize(550,450)
root.maxsize(560,460)
root.title('myAPP_Second_Window')
class SecondClass:
def init(self, parent):
self.parent = parent
self.my_widgets()
self.names = []
def my_widgets(self):
mylabel = Label(self.parent, text='Insert new name in next widget:')
mylabel.grid(column=0, row=0, sticky=W, pady=3)
button1 = tk.Button(self.parent, text="Click to enter Names in list", command=self.addToList)
button1.grid(column=3, row=0, sticky=W, pady=3)
self.name = StringVar()
valueEntry = tk.Entry(self.parent, textvariable= self.name)
valueEntry.grid(row=1, column=0, sticky=W, pady=3)
def addToList(self):
self.names.append(self.name.get())
print('listentries', self.names)
Pages.list_of_names = self.names
my_pages_to_connect_modules.
class Pages():
list_of_names = " "
It`s been challenging to me, every help is welcome. But please dont say just that I must update main window, I need to know how. Thanks to all of you.

Tkcalendar DateEntry - Allow Widget to Return No Selection as Blank

I have created a GUI where the user can select a date from a drop down using the tkcalendar DateEntry widget. I would like to allow the user the option of not selecting a date and leaving this widget blank. However, even if no date is selected the widget returns the current date.
Is there a way to configure the DateEntry to allow for no selection rather than defaulting to the current date if the user does not select a date?
Below is a subset of my code:
import pandas as pd
from tkinter import *
from tkinter.ttk import *
import tkinter as tk
from tkcalendar import DateEntry
class Window(Frame):
def __init__(self, master):
Frame.__init__(self,master)
master.title('Solar Master Project Tracking')
# create canvas for scrollable window
canvas = Canvas(root)
canvas.grid(row=1,column=0, columnspan=2)
# create vertical scrollbar and connect it to the canvas
scrollBar = tk.Scrollbar(root, orient='vertical', command = canvas.yview)
scrollBar.grid(row=1, column=2, sticky = 'ns')
canvas.configure(yscrollcommand=scrollBar.set)
def update_scroll_region(event):
canvas.configure(scrollregion=canvas.bbox("all"))
def _on_mousewheel(event):
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# create a frame for the widgets in the scrollable canvas
scroll_frame = Frame(canvas)
scroll_frame.bind("<Configure>", update_scroll_region)
canvas.create_window(0,0, anchor='nw', window = scroll_frame)
canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Proposal Date
self.L18 = Label(scroll_frame, text="Proposal Date:",font=('TKDefaultFont', 8, 'bold'))
self.L18.grid(row=21, column=0, sticky=W)
self.prop_date_selection = DateEntry(scroll_frame, width = 25, background = 'LightCyan3',
foreground ='white',borderwidth=2)
self.prop_date_selection.grid(row=21, column=1,sticky=E)
self.prop_date_selection.delete(0,"end")
# SUBMIT INFORMATION
self.button = tk.Button(root, text="Insert / Update Project",font=('TKDefaultFont', 10, 'bold'),
relief=RAISED, command = self.store_user_inputs, bg = "gray80")
self.button.grid(row=25, column = 0, columnspan=8, sticky = 'EW')
# STORE USER INPUT
def store_user_inputs(self):
prop_date_selection = self.prop_date_selection.get_date()
global params
params = [prop_date_selection]
root.destroy()
if __name__ == "__main__":
root = Tk()
Window(root)
root.mainloop()
You can create a class inheriting from tkcalendar.DateEntry and modify the get_date() method to return None when the DateEntry is empty:
import tkcalendar
class DateEntry(tkcalendar.DateEntry):
def get_date(self):
if not self.get():
return None
self._validate_date()
return self.parse_date(self.get())
For those who find this post.
Modifying get_date() didn't change anything in my case. But the following did:
class MyDateEntry(tkcalendar.DateEntry):
def _validate_date(self):
if not self.get():
return True # IMPORTANT!!! Validation must return True/False otherwise it is turned off by tkinter engine
return super()._validate_date()

how can I fit the widgets to the entire frame to enable resizing in tkinter?

I have been able to resize the GUI, it stretches left right, and the content resizes the widgets, but there seems to be a cut off point which results in just the GUI widgets resizing, how can I make the widgets of the frame resize over the whole frame...
from tkinter import *
from tkinter import ttk
class MainWindow:
def __init__(self,master):
self.master = master
self.master.geometry('300x300')
self.container = Frame(self.master)
self.container.grid(row=0,column=0,sticky='NSEW')
self.container['bg']='skyblue'
self.chip_frame = Frame(self.container)#,height=100,width=100)
self.random = Frame(self.container)#,height=100,width=100)
self.fish_frame = Frame(self.container)#,height=100,width=100)
self.cat_frame = Frame(self.container)#,height=100,width=100)
self.cow_frame = Frame(self.container)#,height=100,width=100)
self.brad_frame = Frame(self.container)#,height=100,width=100)
self.chip_frame.grid(row=0,column=0,sticky='NSEW')
self.random.grid(row=0,column=1,sticky='NSEW')
self.fish_frame.grid(row=0,column=2,sticky='NSEW')
self.cat_frame.grid(row=1,column=0,sticky='NSEW')
self.cow_frame.grid(row=1,column=2,sticky='NSEW')
self.brad_frame.grid(row=0,column=3,sticky='NSEW',rowspan=2)
self.fish_frame['bg']='orange'
self.chip_frame['bg']='pink'
self.random['bg']='black'
self.cat_frame['bg']='Blue'
self.cow_frame['bg']='Green'
self.brad_frame['bg']='Red'
for i in range(4):
self.container.columnconfigure(i,weight=1)
for i in range(4):
self.container.rowconfigure(i,weight=1)
root = Tk()
root.title('Nathans APP')
root.columnconfigure(0,weight=1)
root.rowconfigure(0,weight=1)
app = MainWindow(root)
root.mainloop()

python3 & tkinter - creating a zoom button to zoom on the loaded image

I would like to create a zoom button. On clicking on that zoom button, the image would be zoomed in by a factor represented by an integer (1,2,3,4,5...). With this piece of code, by clicking on the zoom button, another panel is created underneath the already loaded picture. Inside it is blank. What would be needed is to:
1. kill the first (non-zoomed window) and 2. load the zoomed image on the updated panel
from tkinter import *
from tkinter.filedialog import askopenfilename
import tkinter as tk
event2canvas = lambda e, c: (c.canvasx(e.x), c.canvasy(e.y))
root = Tk()
#setting up a tkinter canvas with scrollbars
frame = Frame(root, bd=2, relief=SUNKEN)
frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(0, weight=1)
xscroll = Scrollbar(frame, orient=HORIZONTAL)
xscroll.grid(row=1, column=0, sticky=E+W)
yscroll = Scrollbar(frame)
yscroll.grid(row=0, column=1, sticky=N+S)
canvas = Canvas(frame, bd=0, xscrollcommand=xscroll.set,yscrollcommand=yscroll.set)
canvas.grid(row=0, column=0, sticky=N+S+E+W)
xscroll.config(command=canvas.xview)
yscroll.config(command=canvas.yview)
frame.pack(fill=BOTH,expand=1)
#adding the image
image_str="image.png"
image = tk.PhotoImage(file=image_str)
image = image.zoom(1,1)
canvas.create_image(0,0,image=image,anchor="nw")
canvas.config(scrollregion=canvas.bbox(ALL))
def zoomin():
root = Tk()
frame = Frame(root, bd=2, relief=SUNKEN)
frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(0, weight=1)
xscroll = Scrollbar(frame, orient=HORIZONTAL)
xscroll.grid(row=1, column=0, sticky=E+W)
yscroll = Scrollbar(frame)
yscroll.grid(row=0, column=1, sticky=N+S)
canvas = Canvas(frame, bd=0, xscrollcommand = xscroll.set, yscrollcommand = yscroll.set)
canvas.grid(row=0, column=0, sticky=N+S+E+W)
xscroll.config(command=canvas.xview)
yscroll.config(command=canvas.yview)
frame.pack(fill=BOTH,expand=1)
image = tk.PhotoImage(file=image_str)
image = image.zoom(1,1)
canvas.create_image(0,0,image=large_img,anchor="nw")
canvas.config(scrollregion=canvas.bbox(ALL))
toolbar = Frame(root, bg="blue")
insertButt = Button(toolbar, text="zoomin", command=lambda:zoomin())
insertButt.pack(side = LEFT, padx=2, pady=2)
toolbar.pack(side=TOP, fill = X)
#function to be called when mouse is clicked
def printcoords(event):
#outputting x and y coords to console
print (event.x,event.y)
#mouseclick event
canvas.bind("<Button 1>",printcoords)
#mouseclick event
canvas.bind("<ButtonPress-1>",printcoords)
canvas.bind("<ButtonRelease-1>",printcoords)
root.mainloop()
I would like to thank #Symon for his stackoverflow question. I largely inspired myself from his code
Well, the reason that the function zoomin(img) does not work properly is that it returns in the first line:
def zoomin(img):
return # Function returns here
... rest of code is never executed
I suspect this is due to the function being run when you create the button, not when you press it. Try cretating the button in this way instead:
insertButt = Button(toolbar, text="zoomin", command=lambda:zoomin(img))
Now the button will call zoomin(img) when it's pressed and not when the button is created.
Zooming with Tkinter
PhotoImage zoom only allows integer values, wich makes it a bit limited. But here's an example:
from tkinter import *
root = Tk()
root.geometry('300x200')
field = Canvas(root, bg='tan1', highlightthickness=0)
field.grid(sticky='news')
photo = PhotoImage(file='test.gif')
field.create_image(0, 0, image=photo, anchor='nw')
scale = 1
def zoom(event=None):
global scale, photo
scale = scale * 2
field.delete('all')
photo = photo.zoom(x=scale, y=scale)
field.create_image(0, 0, image=photo, anchor='nw')
field.image = photo
root.bind('z', zoom) # Bind "z" to zoom function
root.mainloop()
If you want to zoom by float you'll have to import a module for that. Pillow seems popular. But I haven't worked with any of them so you'll have to research them yourself.

python tkinter image layers (paste / unpaste image on background)

i have a background image using tkinter canvas,
and i'm adding images on top of it.
so far so good it works well. but what i would like to do is to be able to remove some of the forground images on demand. and when i remove some of them i would like to see the background behind them as it were before adding those forground images on it.
that would be like: paste 5 foreground images and then remove 1 or 2 of them.
so this program i have to far, adds little white filled circles at random position.
if i keep a handle on every little white circles (i can put them in variables and have them all in a list, and get their coordinates later for example). how can i remove some of them and get to see my background behind the removed whites circles ?
is it even possible ?
#!/usr/bin/env python3
from tkinter import *
from PIL import Image, ImageTk
from random import *
class App(object):
def __init__(self):
self.root = Tk()
self.canvas = Canvas(self.root, height=222, width=227)
self.canvas.grid()
# small nature landscape
self.backgnd = PhotoImage( file = "images/nature.png" )
# small white circle
self.mycloud = PhotoImage( file = "images/white.png" )
backgnd_width = (self.backgnd.width()/2)
backgnd_height = (self.backgnd.height()/2)
self.canvas.create_image(backgnd_width,backgnd_height,image=self.backgnd)
def cloud(self):
pos_x = randint(1,220)
pos_y = randint(1,220)
self.canvas.create_image(pos_x,pos_y, image=self.mycloud)
app = App()
app.cloud()
app.cloud()
app.cloud()
app.cloud()
app.cloud()
app.root.mainloop()
in case it might help others here's a working solution.
i added a button that will remove each object placed on the canvas, one a a time.
(thanks for the help, Bryan Oakley)
#!/usr/bin/env python3
from tkinter import *
from PIL import Image, ImageTk
from tkinter import ttk
from random import *
class App(object):
def __init__(self):
self.root = Tk()
self.canvas = Canvas(self.root, height=300, width=227)
self.canvas.grid()
self.mylist=[]
self.backgnd = PhotoImage( file = "images/nature.png" )
self.mycloud = PhotoImage( file = "images/white.png" )
backgnd_width = (self.backgnd.width()/2)
backgnd_height = (self.backgnd.height()/2)
self.canvas.create_image(backgnd_width,backgnd_height,image=self.backgnd)
# button to remove things on the canvas
button_del = ttk.Button(self.root, text='Del')
button_del['command'] = self.rem
button_del.place(x=100, y=250)
def cloud(self):
# add 5 object at random position on the canvas
for idx in range(5):
pos_x = randint(1,220)
pos_y = randint(1,220)
self.mylist.append(self.canvas.create_image(pos_x,pos_y, image=self.mycloud))
def rem(self):
# delete elements placed on the canvas
self.canvas.delete(self.mylist[-1])
self.mylist.pop()
app = App()
app.cloud()
app.root.mainloop()
made a few changes to make above code compatible with python 2:
from Tkinter import *
from PIL import Image, ImageTk
import ttk
from random import *
class App(object):
def __init__(self):
self.root = Tk()
self.canvas = Canvas(self.root, height=300, width=227)
self.canvas.grid()
self.mylist=[]
self.backgnd = ImageTk.PhotoImage( Image.open("sunshine.jpg") )
self.mycloud = ImageTk.PhotoImage( Image.open("Y.png") )
backgnd_width = (self.backgnd.width()/2)
backgnd_height = (self.backgnd.height()/2)
self.canvas.create_image(backgnd_width,backgnd_height,image=self.backgnd)
# button to remove things on the canvas
button_del = ttk.Button(self.root, text='Del')
button_del['command'] = self.rem
button_del.place(x=100, y=250)
def cloud(self):
# add 5 object at random position on the canvas
for idx in range(5):
pos_x = randint(1,220)
pos_y = randint(1,220)
self.mylist.append(self.canvas.create_image(pos_x,pos_y, image=self.mycloud))
def rem(self):
# delete elements placed on the canvas
self.canvas.delete(self.mylist[-1])
self.mylist.pop()
app = App()
app.cloud()
app.root.mainloop()

Resources