Related
I have a frame with multiple child elements, that are placed in it using the grid() geometry manager.
How can I modify the code below to make the frame responsive?
content.grid(column=0, row=0, sticky='nwse')
userButt.grid(column=2, row=1, sticky='nwse')
favoButt.grid(column=3, row=1, sticky='nwse')
locaButt.grid(column=4, row=1, sticky='nwse')
histScal.grid(column=5, row=1, sticky='nwse')
As a rule of thumb, whenever you use grid you should always give at least one row and one column a non-zero weight so that tkinter knows where to allocate extra space. A weight of 0 (zero) is assigned by default.
The two most common cases are where you have a "hero" widget (eg: a text widget, canvas widget, etc) that should grow and shrink as necessary, or you want everything to resize equally. For the case where one widget gets all the extra space, give a weight just to the row and column where that widget is placed. If you want everything to resize equally, give each row and each column a weight.
Assuming that the parent of your widgets content, userButt, etc are root, you might do it like this:
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
In the above example, all extra space would go to row zero and column 0.
Suppose you have a window that is managed by grid system and you want to make it responsive knowing the total number of rows and column you used. Let's say the total number of rows =6 and the total number of columns =10 making the window responsive under grid management system can be done as follows.
n_rows =6
n_columns =10
for i in range(n_rows):
root.grid_rowconfigure(i, weight =1)
for i in range(n_columns):
root.grid_columnconfigure(i, weight =1)
You need to use grid_rowconfigure(<row number>,weight=<row weight>) and grid_columnconfigure to stretch rows/columns when container is being stretched. Also use grid(sticky=NSEW) attribute to stretch items inside a grid
from tkinter import *
root = Tk()
for i in range(10):
root.grid_rowconfigure(i, weight=1)
for j in range(10):
root.grid_columnconfigure(j, weight=1)
Button(root, text=f'Button {i}-{j}').grid(row=i, column=j, sticky=NSEW)
root.mainloop()
I have a Tkinter ttk.Scale in a GUI. What I want to do is add some labels on the top or bottom (or side if its vertical) that show the values the user is selecting through. I have a minimum working example shown below.
My problem comes from the fact that I cannot figure out how to get the Scale to line up with the labels. It is set to span all the columns of the labels, but the Scale widget comes with a length keyword that automatically forces it to be 100 pixels in size. I can't know, a priori, what to set that length to and I can't figure out how to tell it to fill its grid space.
Is there a way to tell the Scale to choose a length such that it fills its grid space?
import tkinter as tk
from tkinter import ttk
def show_values():
print (w1.get())
def scaleFunc(val):
scaleVal = float(w1.get())
if int(scaleVal) != scaleVal:
w1.set(round(float(val)))
root = tk.Tk()
for i,text in enumerate(['O','B','A','F','G','K','M','L']):
ttk.Label(root, text = text).grid(row=0,column=i,padx=10)
w1 = ttk.Scale(root, to=7, command=scaleFunc, length=None)
w1.grid(row = 1, column = 0, columnspan = 8,ipadx=0)
ttk.Button(root, text='Show', command=show_values).grid(row=2,column=0,columnspan=8)
root.mainloop()
I suppose I should have played around a bit more. It looks like the Scale widget listens to the sticky keyword in the grid manager.
w1 = ttk.Scale(root, to=7, command=scaleFunc, length=None)
w1.grid(row=1,column=0,columnspan=8,padx=(10,7),sticky='NSEW')
You can use either one of these solutions:
Solution 1: w1.grid(row = 1, column = 0, columnspan=8, sticky='ew')
Solution 2: w1 = ttk.Scale(root, to=7, command=scaleFunc, length=250)
The 1st solution is cleaner.
I re-created the game Lights Out using Python and Tkinter and as far as I know there are no bugs but it is very slow especially if you set the grid size to be much higher then 10x10 (I have a slider in game that lets you do this.) I was just wondering if you had any ideas on how I could get it to run faster.
#Nicholas Eckstein
#Lights Out
#11/20/14
import random
import math
from tkinter import *
from tkinter import ttk
Lastx, lasty = 0,0
GridSize="410x520"
def reset():#Resets the grid size to the size set in the slider and randomizes cells.
global grid
global GridSize
gridMaker()
canvas.delete("all")#Clears the screen
ResetMin=math.trunc(len(grid)/3) ##Picks random amount of cells to switch states.
ResetMax=math.trunc(len(grid)/2) #Amount chosen is relative to the grid size.
ResetAmount=random.randint(ResetMin,ResetMax) ##(Random amount in between 1/2 and 1/3 of the cells.
iterate=0
while iterate<ResetAmount:#Picks random cells to switch states until iterate==ResetAmount
#cell=random.choice(grid)#All cells exist in a list of lists called grid.
#cell.pop(2) #A single list inside the Grid List is a cell.
#cell.append(1) #This Cell consists of 2 ranges and a state. [range(105, 125), range(5, 25), 0]
#iterate+=1 #The first range is the width of the cell, the second range is for the height, and the last number is for the state.
#The grid list looks something like this: [[range(105, 125), range(5, 25), 0], [range(125, 145), range(5, 25), 0], [range(145, 165), range(5, 25), 0]...]
cell=random.choice(grid)
cellx=cell[0][5]
celly=cell[1][5]
iterate+=1
CellSwitcher(cellx,celly)
GridSize=str((len(grid)/2)*20)+"x"+str(((len(grid)/2)*20)+110)#This sets the gridsize to the size determined by the slider
art()
def art():#Takes the information from the Grid list and "draws" the cells.
for cell in grid:
if cell[2]==1:
canvas.create_rectangle(cell[0][0],cell[1][0],cell[0][19],cell[1][19],fill="white")
canvas.create_rectangle(cell[0][0]+2,cell[1][0]+2,cell[0][19],cell[1][19],fill="black",outline="black")
else:
canvas.create_rectangle(cell[0][0],cell[1][0],cell[0][19],cell[1][19],fill="black")
canvas.create_rectangle(cell[0][0]+2,cell[1][0]+2,cell[0][19],cell[1][19],fill="white",outline="white")
def xy(event):#Takes the position of the mouse click
global lastx, lasty
lastx, lasty = event.x, event.y
CellSwitcher(lastx,lasty)
def CellSwitcher(lastx,lasty):#Switches the states of the cells neighboring the cell you clicked.
for coord in grid:
if lastx in coord[0] and lasty in coord[1]:
if coord[2]==0:
coord.pop(2)
coord.append(1)
else:
coord.pop(2)
coord.append(0)
if [coord[0],range(coord[1][0]+20,coord[1][19]+21),0] in grid: ####
grid[grid.index([coord[0],range(coord[1][0]+20,coord[1][19]+21),0])].pop(2) #
grid[grid.index([coord[0],range(coord[1][0]+20,coord[1][19]+21)])].append(1) #
elif [coord[0],range(coord[1][0]+20,coord[1][19]+21),1] in grid: # Switch Top Neighbor's state
grid[grid.index([coord[0],range(coord[1][0]+20,coord[1][19]+21),1])].pop(2) #
grid[grid.index([coord[0],range(coord[1][0]+20,coord[1][19]+21)])].append(0) #
####
if [coord[0],range(coord[1][0]-20,coord[1][19]-19),0] in grid: ####
grid[grid.index([coord[0],range(coord[1][0]-20,coord[1][19]-19),0])].pop(2) #
grid[grid.index([coord[0],range(coord[1][0]-20,coord[1][19]-19)])].append(1) #
elif [coord[0],range(coord[1][0]-20,coord[1][19]-19),1] in grid: # Switch Bottom Neighbor's state
grid[grid.index([coord[0],range(coord[1][0]-20,coord[1][19]-19),1])].pop(2) #
grid[grid.index([coord[0],range(coord[1][0]-20,coord[1][19]-19)])].append(0) #
####
if [range(coord[0][0]+20,coord[0][19]+21),coord[1],0] in grid: ####
grid[grid.index([range(coord[0][0]+20,coord[0][19]+21),coord[1],0])].pop(2) #
grid[grid.index([range(coord[0][0]+20,coord[0][19]+21),coord[1]])].append(1) #
elif [range(coord[0][0]+20,coord[0][19]+21),coord[1],1] in grid: # Switch Right Neighbor's state
grid[grid.index([range(coord[0][0]+20,coord[0][19]+21),coord[1],1])].pop(2) #
grid[grid.index([range(coord[0][0]+20,coord[0][19]+21),coord[1]])].append(0) #
####
if [range(coord[0][0]-20,coord[0][19]-19),coord[1],0] in grid: ####
grid[grid.index([range(coord[0][0]-20,coord[0][19]-19),coord[1],0])].pop(2) #
grid[grid.index([range(coord[0][0]-20,coord[0][19]-19),coord[1]])].append(1) #
elif [range(coord[0][0]-20,coord[0][19]-19),coord[1],1] in grid: # Switch Left Neighbor's state
grid[grid.index([range(coord[0][0]-20,coord[0][19]-19),coord[1],1])].pop(2) #
grid[grid.index([range(coord[0][0]-20,coord[0][19]-19),coord[1]])].append(0) #
####
art()
root = Tk()#Create the window
root.geometry(GridSize)#Set Window Size
root.resizable(0,0)#Stop people from resizing the window
root.title("Lights Out")
canvas = Canvas(root,background=root.cget('bg'))#Create the part of the window that draws the grid
canvas.bind("<Button-1>", xy)#Detect clicking and send coordinates of mouse
canvas.pack(fill=BOTH, expand=YES)#Resize canvas to window size and allign.
SizeLabel = Label(root,text="Grid Size")#Write the "reset" label
SizeLabel.pack()#Allign Label
Size = Scale(root,orient=HORIZONTAL,length=400,width=20,sliderlength=60,from_=1,to=20,tickinterval=1)#Create, orientate, and set the size of the slider
Size.set(10)#Set starting position for slider
Size.pack()#Allign Slider
Reset = Button(root,text ="Reset",command = reset)#Create the reset button
Reset.pack()#Allign the reset button
def gridMaker():#This function creates the grid list.
global grid
grid=[]
xCoord=205-(int(math.trunc(Size.get())/2)*20)#Centers the grid
yCoord=5
iterate=0
while yCoord<Size.get()*20:
grid.append([range(xCoord,xCoord+20),range(yCoord, yCoord+20),0])#Adds a cell to the grid list with the ranges based on what xCoord and yCoord are.
if Size.get()%2==1:#Tests to see if the grid size is odd or even
if xCoord<205+(int(math.trunc(Size.get())/2)*20):
xCoord+=20
else:
xCoord=205-(int(math.trunc(Size.get())/2)*20)
yCoord+=20
else:
if xCoord<205+(int(math.trunc(Size.get())/2)*20)-20:
xCoord+=20
else:
xCoord=205-(int(math.trunc(Size.get())/2)*20)
yCoord+=20
gridMaker()#Draws the grid
reset()#Adds Randomizes Cell States
root.mainloop()
Your CellSwitcher function iterates over all items in the cell, when it eventually only modifies nine cells (itself and its 8 neighbors), right? Why iterate over every single cell? If you know the cell that was clicked (eg: row 3, column 2) you can easily compute the neighboring cells. So, part of the answer is to remove the iteration over all of the cells and replace it with a direct lookup of the clicked-on cell and its neighbors.
Also, your reset function calls CellSwitcher which seems like overkill. If you're randomly setting the color of each cell, why go through CellSwitcher, since it changes the colors of all its neighbors?
Perhaps the biggest culprit is that you are recreating all of the canvas objects on each call to CellSwitcher, without deleting any of the old objects. There's no reason to do that -- create all of the canvas objects just once and then change them with the itemconfig method of the canvas.
The canvas has performance problems when you have lots of items. In your case, after the GUI first comes up you've already created 9800 canvas items. Click on a single cell and the canvas now has 10,200 items. And so on. The canvas can pretty easily handle thousands of items, even tens of thousands. However, when I move the slider to 20 you end up creating a whopping 125,600 objects on the canvas which will definitely cause the canvas to under-perform.
I'm getting myself thoroughly confused about how Gtk controls widget sizes inside a container such as a GtkBox. Please can someone guide me through the intricacies for this question?
I have a GtkBox containing two widgets - in the example these are just two GtkButtons.
The first widget should just fill the space available inside the GtkBox container.
The second widget I want to force always to be a physical square - thus as the square enlarges, the first widget will shrink in terms of width.
Some example code will help here:
from gi.repository import Gtk
def on_check_resize(window):
boxallocation = mainbox.get_allocation()
width = 0
height = 0
if boxallocation.height >= boxallocation.width:
width = boxallocation.height
height = width
if boxallocation.width >= boxallocation.height:
width = boxallocation.width
height = width
secondbtn_allocation = secondbtn.get_allocation()
secondbtn_allocation.width = width
secondbtn_allocation.height = height
secondbtn.set_allocation(secondbtn_allocation)
win = Gtk.Window(title="Containers Demo")
win.set_border_width(10)
win.connect("delete-event", Gtk.main_quit)
mainbox = Gtk.Box()
firstbtn = Gtk.Button(label="just fills the space")
mainbox.pack_start(firstbtn, True, True, 0)
secondbtn = Gtk.Button(label="square")
mainbox.pack_start(secondbtn, False, True, 1)
win.add(mainbox)
win.connect("check_resize", on_check_resize)
win.show_all()
initial = firstbtn.get_allocation()
initial.height = initial.width
firstbtn.set_size_request(initial.width, initial.height)
Gtk.main()
When you expand the window - the GtkBox will likewise expand. That's good. The first button similarly also expands. Good also. However, the second button never is square even though I'm using the set_allocation method for the GtkWidget.
I cannot use the set_size_request (or could I?) because when I shrink the window, the window cannot resize due to the altered minimum size of the second button.
The reason why I'm trying to figure out how containers manage spacing is that I'm looking to eventually implement something like this iTunes example:
i.e. you can see the cover is always square - but the music details will shrink or expand to depending upon the space available.
Instead of Gtk.Box, use Gtk.Grid and set the hexpand and vexpand properties on the buttons that you pack into the grid.
Also, consider using a Gtk.AspectFrame for the square button.
I'm building a GUI with Tkinter and ttk and using matplotlib in order to creat interactive plots - again, like millions other people do.
Even though most problems I encountered so far are well documented, this one seems rare:
When plotting in 3d and adjusting the axis scale with set_lim() commands afterwards, the plotted line exceeds the coordinate-system which looks not good. Also, I'm not happy with the frame that seems to be a little to small. Here is an example:
# Missmatch.py
"""Graphical User Interface for plotting the results
calculated in the script in Octave"""
# importing libraries
import matplotlib, ttk, threading
matplotlib.use('TkAgg')
import numpy as nm
import scipy as sc
import pylab as pl
import decimal as dc
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
from oct2py import octave as oc
import Tkinter as tki
class CS:
"""CS - Controlset. This part creates the GUI with all important
Elements. Major changes and calculations will be executed
in the Calculation-Class in a seperate thread. This prevents the
GUI from hanging"""
def __init__(self,parent):
"""Building the main GUI"""
self.ThisParent=parent
### Entire Window
# Mainframe that contains everything.
self.main=tki.Frame(parent)
# Pack manager to expand the mainframe as the windowsize changes.
self.main.pack(fill=tki.BOTH, expand=tki.YES)
# Configure the grid of the mainframe so that only the top left
# cell grows if the users expands the window.
self.main.grid_rowconfigure(0, weight=1)
self.main.grid_rowconfigure(1, weight=1)
### Canvas for drawings
# Creating a figure of desired size
self.f = Figure(figsize=(6,6), dpi=100)
# Creating a canvas that lives inside the figure
self.Paper=FigureCanvasTkAgg(self.f, master=self.main)
# Making the canvas's drawings visible (updating)
self.Paper.show()
# positioning the canvas
self.Paper.get_tk_widget().grid(row=0,rowspan=3, column=0, sticky='NSWE')
# creating a toolbarframe for options regarding the plots
self.toolbarframe=tki.Frame(self.main)
self.toolbarframe.grid(row=3, column=0, sticky='NWE')
# Creating a toolbar for saving, zooming etc. (matplotlib standard)
self.toolbar = NavigationToolbar2TkAgg(self.Paper, self.toolbarframe)
self.toolbar.grid(row=0,column=0, sticky='NWE')
# setting the standard option on zoom
self.toolbar.zoom()
### Axis configuration toolbar
# A frame containing the axis config-menu
self.axisscaleframe=tki.Frame(self.main)
self.axisscaleframe.grid(row=5, column=0, sticky='SNEW')
# In that Frame, some Entry-boxes to specify scale
self.xaxisscalef=ttk.Entry(self.axisscaleframe, width=10)
self.xaxisscalef.insert(0,0)
self.xaxisscalet=ttk.Entry(self.axisscaleframe, width=10)
self.xaxisscalet.insert(0,15)
self.yaxisscalef=ttk.Entry(self.axisscaleframe, width=10)
self.yaxisscalef.insert(0,0)
self.yaxisscalet=ttk.Entry(self.axisscaleframe, width=10)
self.yaxisscalet.insert(0,15)
self.zaxisscalef=ttk.Entry(self.axisscaleframe, width=10)
self.zaxisscalef.insert(0,0)
self.zaxisscalet=ttk.Entry(self.axisscaleframe, width=10)
self.zaxisscalet.insert(0,15)
# And some Labels so we know what the boxes are for
self.xaxlab=ttk.Label(self.axisscaleframe, text='X-Axis', width=10)
self.yaxlab=ttk.Label(self.axisscaleframe, text='Y-Axis', width=10)
self.zaxlab=ttk.Label(self.axisscaleframe, text='Z-Axis', width=10)
self.axinfolab=ttk.Label(self.axisscaleframe, text='Adjust axis scale:')
# And a Button to validate the desired configuration
self.scaleset=ttk.Button(self.axisscaleframe, text='Set', command=self.SetAxis2)
self.scaleset.bind('<Return>', self.SetAxis)
# Let's organize all this in the axisscaleframe-grid
self.axinfolab.grid(row=0, column=0, sticky='W')
self.xaxlab.grid(row=1, column=0, sticky='W')
self.yaxlab.grid(row=2, column=0, sticky='W')
self.zaxlab.grid(row=3, column=0, sticky='W')
self.xaxisscalef.grid(row=1,column=1, sticky='W')
self.yaxisscalef.grid(row=2,column=1, sticky='W')
self.xaxisscalet.grid(row=1,column=2, sticky='W')
self.yaxisscalet.grid(row=2,column=2, sticky='W')
self.zaxisscalef.grid(row=3,column=1,sticky='W')
self.zaxisscalet.grid(row=3,column=2,sticky='W')
self.scaleset.grid(row=3,column=3,sticky='E')
def SetAxis(self,event):
self.SetAxis2()
def SetAxis2(self):
self.x1=float(self.xaxisscalef.get())
self.x2=float(self.xaxisscalet.get())
self.y1=float(self.yaxisscalef.get())
self.y2=float(self.yaxisscalet.get())
self.z1=float(self.zaxisscalef.get())
self.z2=float(self.zaxisscalet.get())
self.a.set_xlim(self.x1, self.x2)
self.a.set_ylim(self.y1, self.y2)
self.a.set_zlim(self.z1, self.z2)
self.Paper.show()
print "Set axis"
class Calculate3D(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.x=range(100)
self.y=range(100)
self.z=range(100)
print 'Done!'
controlset.a = controlset.f.add_subplot(111, projection='3d')
controlset.a.clear()
controlset.a.plot(self.x,self.y,self.z)
controlset.a.mouse_init()
controlset.a.set_xlabel('X')
controlset.a.set_ylabel('Y')
controlset.a.set_zlabel('Z')
controlset.a.set_title('Title')
controlset.Paper.show()
return
mainw=tki.Tk()
mainw.title("Example")
mainw.geometry('+10+10')
controlset=CS(mainw)
#for this example code, we run our Calculate3D class automatically
CL=Calculate3D()
CL.run()
mainw.mainloop()
Just run the code, and hit the "SET" Button. There is my problem.
Edit: Added Screenshot:
The Problem here is, that mplot3d has no OpenGL backend. The calculations for displaying the data are thus based on 2d.
I found the same issue here and a workaround here. Even though the workaround is not the best in my opinion because it depends on the resolution of your data.
I followed the second link anyway. So, what I'm doing now is copying the array and setting all the values above and under my desired scale to NaN. When plotting those, the lines will be cut off where the datapoints exceed the desired limit.
def SetAxis2(self):
self.dummyx=CL.x*1
self.dummyy=CL.y*1
self.dummyz=CL.z*1
#clipping manually
for i in nm.arange(len(self.dummyx)):
if self.dummyx[i] < self.x1:
self.dummyx[i] = nm.NaN
else:
pass
for i in nm.arange(len(self.dummyy)):
if self.dummyy[i] < self.y1:
self.dummyy[i] = nm.NaN
else:
pass
for i in nm.arange(len(self.dummyz)):
if self.dummyz[i] < self.z1:
self.dummyz[i] = nm.NaN
else:
pass
controlset.a.plot(self.dummyx,\
self.dummyy,\
self.dummyz)
self.a.set_xlim3d(self.x1, self.x2)
self.a.set_ylim3d(self.y1, self.y2)
self.a.set_zlim3d(self.z1, self.z2)
If now your scale is set from 0 to 10 and you have six datapoints: [-1, 3 4 12 5 1] The line will go from 3 to 4 and 5 to 1 because -1 and 12 will be set to NaN. An improvement regarding that problem would be good.
Mayavi might be better, but I haven't tried this as I wanted to stick with matplotlib.
Following code works even for meshgrid data representation:
# numpy.vectorize
def clip_z_data(z):
return z if Z_MIN <= z <= Z_MAX else n.nan
z = clip_z_data(z)
Z_MIN and Z_MAX are global, because vectorize can't handle extra attributes.