So I'm trying to create a basic text-entry thing to understand the basics of Tkinter, but when I try and grid something, if the column or row is greater than 1, it will act as if it is 1
Here is my code:
from tkinter import *
window = Tk()
window.minsize(width=500, height=300)
# label
my_label = Label(text="Text", font=("Consolas", 24, "bold"))
my_label["text"] = "New Text"
my_label.config(text="New Text")
# my_label.pack() # adding keyword parameters such as "expand=True" or "side='left'" can affect where it is positioned
# my_label.place(x=100, y=40)
my_label.grid(column=10, row=15)
# Button
def button_clicked():
my_label["text"] = input.get()
button = Button(text="Click Me", command=button_clicked)
button.grid(row=1, column=1)
# Entry
input = Entry(width=10)
window.mainloop()
Now I want the label to be about 3/4 of the way across the screen, and as you see, I use (for the label) column 10 and row 15. Still, when I try and run this, the label will only go diagonally down from the button. Is this how Tkinter is supposed to work, or am I just doing it wrong? I would appreciate any help.
yes, that's basically how tkinter works, it is at column 10, it's just that columns 2-9 all have zero width as they contain nothing, so you end up seeing only 2 columns.
to make something "centered" you need to have its "bounding box" scale with the containing "frame" using grid_columnconfigure from the Tk official grid tutorial
my_label.grid(column=10, row=15)
window.grid_columnconfigure(10,weight=1) # column 10 will grow
window.grid_rowconfigure(15,weight=1) # row 15 will grow
this allows the bounding box to grow and the label will remain centered as it is not set to stick to any corner, but it is offset by the button.
but if you really wanted it to be in the exact center then you can make the label span more columns than the button.
my_label.grid(column=1,columnspan=2, row=2) # label spans columns 1 and 2
window.grid_columnconfigure(2,weight=1) # column 2 will grow
window.grid_rowconfigure(2,weight=1) # row 2 will grow.
you get the required result.
but now we want both the button and the label to be centered, we can set them both in the same column that is set to grow with the size of the window, and make both rows grow to fit the entire window.
my_label.grid(column=1,columnspan=2, row=2, sticky="N")
window.grid_columnconfigure(1,weight=1)
window.grid_rowconfigure([1,2],weight=1)
simply playing with grid_columnconfigure and column and columnspan and sticky and their row counterparts is usually enough to get any shape you want on any screen scale if you add some padding.
Related
I'm trying to create a tkinter gui, which starts with a multivariate time series and uses a tkinter gui to allow the user to select and plot certain channel readings in a sliding window created using plotly express. I'm just starting to learn about tkinter and am using Python 3.7 with IDLE on a Mac OS 10.15.4.
In my example below, my time series has seven labelled channels with 10000 time recordings each. The channel labels are 'a','b','c','d','e','f','g' and the series is stored in a 1000-by-7 array, X. Because I won't always know ahead of time the number of channels and their labels in future implementations of this script, I used a loop to create the labelled checkboxes here. The problem occurs when I try to create the data frame:
import numpy as np
import plotly.express as px
from tkinter import Tk,Button, Label, Checkbutton,BooleanVar
import pandas as pd
window = Tk()
window.title('My Window')
window.geometry('500x500')
np.random.seed(123)
X = np.random.randn(10000,7)
channels=['a','b','c','d','e','f','g']
num_channels=len(channels)
'''Checkbuttons for channels, appearing in one row. Each initially set true'''
channel_vars=[]
channel_buttons=[]
for i in range(num_channels):
channel_vars.append(BooleanVar())
channel_vars[i].set(True)
channel_buttons.append(Checkbutton(window,text=channels[i],var=channel_vars[i]))
channel_buttons[i].grid(row=0,column=i)
'''Determine selected indices'''
def _selected_indices():
indices=[i for i in range(num_channels) if channel_vars[i].get()]
print(indices)
selected_indices_btn = Button(window, text="Select",command=_selected_indices)
selected_indices_btn.grid(row=1, column=0)
''' Create data frame using only the selected channels'''
df=pd.DataFrame(X[:,indices],columns=channels[indices])
df['x']=df.index
'''Plot selected channels'''
def _plot_selected():
df_melt = pd.melt(df, id_vars="x", value_vars=df.columns[:-1])
fig=px.line(df_melt, x="x", y="value",color="variable",labels = {'x':'time
(sec)','variable':'channel'})
fig.update_layout(xaxis=dict(rangeslider=dict(visible=True),type="linear"))
fig.show()
plot_button = Button(master=window, text="Plot", command=_plot_selected)
plot_button.grid(row=2, column=0)
'''Quit button'''
def _quit():
window.quit()
window.destroy()
quit_button = Button(master=window, text="Quit", command=_quit)
quit_button.grid(row=3, column=0)
window.mainloop()
The error message tells me that "indices" is unknown when I try to construct my data frame, df, a result that shows I'm missing something basic in terms of callbacks and/or organizing my gui window.
Not to do with tkinter, it's just that you are assigning the indices values inside the _selected_indices function.
'''Determine selected indices'''
def _selected_indices():
indices=[i for i in range(num_channels) if channel_vars[i].get()]
print(indices)
You are then trying to use your indices outside the scope of the function.
''' Create data frame using only the selected channels'''
df=pd.DataFrame(X[:,indices],columns=channels[indices])
The bigger problem here is that returning a value from a function to use somewhere else in the program from a tkinter callback isnt ideal.
There is a question here with answers that will help better than I could: Python - returning from a Tkinter callback
Hope this helps answer your question.
I am coding a GUI where you have to enter a number. I want to have a scale but it fits my purpose very well. The problem is a scale goes to 1e-9 precision. This is much to precise for me. I am using ttk extension for tkinter. I have tried a couple of things but I saw that there is a digits option for the scale which, when there is a StringVar(), leaves the number of digits but it doesn't work, it cannot recognise the option. I tried to look in the library but I couldn't find anything, it was too complicated.
Here is how my code is formed:
scaleValue = StringVar()
scale = ttk.Scale(content, orient=HORIZONTAL, from_=1, to=5, variable=scaleValue, length=150, digits=1) # here digits ins't recongnised
scale.grid(column=x, row=y)
scaleValueLabel = ttk.Label(content, textvariable=scaleValue)
scaleValueLabel.grid(column=x', row=y')
Here is the documentation:
An integer specifying how many significant digits should be retained when converting the value of the scale to a string.
Official Documentation : http://www.tcl.tk/man/tcl8.6/TkCmd/scale.htm
digits is a parameter only available to tk.Scale. If you switch to using it, then your code will work:
scale = tk.Scale(root, orient=tk.HORIZONTAL, from_=1, to=5, variable=scaleValue, length=150, digits=5)
But if you really want to use ttk.Scale, you can use a different approach. Instead of using a textvariable in your Label, you can trace the changes on your variable, process the value first, and then pass back to your Label.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
scaleValue = tk.DoubleVar()
scale = ttk.Scale(root, orient=tk.HORIZONTAL, from_=1, to=5, variable=scaleValue, length=150) # here digits ins't recongnised
scale.grid(column=0, row=0)
scaleValueLabel = tk.Label(root, text="0")
scaleValueLabel.grid(column=0, row=1)
def set_digit(*args):
scaleValueLabel.config(text="{0:.2f}".format(scaleValue.get()))
scaleValue.trace("w", set_digit)
root.mainloop()
I have an issue in that I can compact this code (I've been told I can) but did not receive any help in doing so and don't have a clue how to do it.
Tried putting this into a for loop, but I want a 3x3 grid of buttons with the middle one being a listbox instead, dead centre of the buttons.
I have looked around, and after an hour, I got no answer.
Here I tried appending each button to a list and packing them in a for loop, but is it possible to do them each in a for loop and the Listbox done and packed separately after?
class MatchGame(Toplevel):
def __init__(self,master):
self.fr=Toplevel(master)
self.GameFrame=Frame(self.fr)
self.AllButtons=[]
self.AllButtons.append(Button(self.GameFrame,bg="red",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="green",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="dark blue",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="turquoise",height=5,width=15,text=""))
self.AllButtons.append(Listbox(self.GameFrame,bg="grey",height=5,width=15))
self.AllButtons.append(Button(self.GameFrame,bg="yellow",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="pink",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="orange",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="purple",height=5,width=15,text=""))
for x in range(0,len(self.AllButtons)):
AllButtons[x].grid(row=int(round(((x+1)/3)+0.5)),column=x%3)
self.GameFrame.grid(row=0,column=0)
Quit=Button(self.fr, text="Destroy This Game", bg="orange",command=self.fr.destroy)
Quit.grid(row=1,column=0)
It needs to have individual colours, same size and all that, but I don't know what to do. I'm fairly new to classes, and I can't work out for the life of me how to make this window with compact code (not 9 lines for each object, then packing them all.)
If you want to dynamically create a 3x3 grid of buttons. Then a nested for-loop seems to be your best option.
Example:
import tkinter as tk
root = tk.Tk()
# List of your colours
COLOURS = [['red', 'green', 'dark blue'],
['turquoise', 'grey', 'yellow'],
['pink', 'orange', 'purple']]
# Nested for-loop for a 3x3 grid
for x in range(3):
for y in range(3):
if x == 1 and y == 1: # If statement for the Listbox
tk.Listbox(root, bg = COLOURS[x][y], height=5, width=15).grid(row = x, column = y)
else:
tk.Button(root, bg = COLOURS[x][y], height=5, width=15).grid(row = x, column = y)
root.mainloop()
Hey so im trying to get to grips with tkinter so i can use it fully in my programs, however im struggling with the geometry manager, grid.
I want my program to resize the grid, based upon the size of the widnow, is there any way to do this? This is my code so far:
import tkinter
import time
import random
import sqlite3
window=tkinter.Tk()
window.title("Phone Book")
window.wm_iconbitmap('favicon (2).ico')
window.geometry("300x400")
entlbl1=tkinter.Label(window, text ="Add A Contact", font=("Helvetica", 23))
entlbl2=tkinter.Label(window, text ="Name:", font=("Helvetica", 16))
entlbl3=tkinter.Label(window, text ="Phone:", font=("Helvetica", 16))
entlbl4=tkinter.Label(window, text ="E-Mail:", font=("Helvetica", 16))
entbox1=tkinter.Entry(window)
entbox2=tkinter.Entry(window)
entbox3=tkinter.Entry(window)
entbtn1=tkinter.Button(window, text = "Add")
entbtn2=tkinter.Button(window, text = "Menu")
blanklbl=tkinter.Label(window)
entlbl1.grid(row=0,columnspan=4)
entlbl2.grid(row=1,sticky="w")
entlbl3.grid(row=2,sticky="w")
entlbl4.grid(row=3,sticky="w")
entbox1.grid(row=1, column=1)
entbox2.grid(row=2, column=1)
entbox3.grid(row=3, column=1)
entbtn1.grid(row=5,columnspan=2,sticky="WE")
entbtn2.grid(row=6,columnspan=2, sticky="WE")
blanklbl.grid(row=4)
When there is extra space in a window managed by grid, it allocates that extra space to rows and columns that have a non-zero "weight". To get your rows and columns to expand, give them a non-zero weight.
for row in range(7):
window.grid_rowconfigure(row, weight=1)
for col in range(3):
window.grid_columnconfigure(col, weight=1)
Of course, adjust that to whatever number of rows and columns you're actually using. You can give different weights to cause some rows or columns to grow more than others. For example, a column with a weight of 3 will grow 3 times faster than a column with a weight of 1.
Note: This is a q/a question where I answer my own question in an answer, merely for the benefit of others (or so that people who know a better way than what I provide can provide it).
In the following example, how do I make the bottom Text widget fill up all the empty space that appears when you maximize the screen (and so the top Text widget is directly at the top after maximizing)? I don't know a reason for that extra padding. I can get rid of some of the padding by adding extra rows for the second Text widget to fill and using rowspan, but there's still some left. I do not want the top Text widget to have a height greater than 1.
from tkinter import *
tk=Tk()
tk.columnconfigure(0, weight=1)
tk.rowconfigure(0, weight=1)
tk.rowconfigure(1, weight=1)
f=Frame(tk)
f2=Frame(tk)
f.grid(row=0, column=0, sticky=E+W)
f2.grid(row=1, column=0, sticky=N+S+E+W)
t=Text(f, height=1)
t2=Text(f2)
l=Label(f, text="label")
b=Button(f, text="button")
l.pack(side=LEFT)
t.pack(side=LEFT, fill=X, expand=True)
b.pack(side=LEFT)
t2.pack(fill=BOTH, expand=True)
tk.mainloop()
The simplest solution is to remove the weight from row 0. When you give a row (or column...) weight, you are telling Tkinter a proportion it can use to allocate extra space. If two columns have the same weight they get the same proportion of extra space. So, with both rows having a weight of 1, they each get an equal proportion of the extra space.
By Giving only the text widget row a weight of 1, only it will grow when the window resizes.
What seems to work best in most circumstances is to have exactly one row and one column with a non-zero space -- usually a text widget or canvas.
Just set the weight of row 1 to a large number like 100. Then it will fill in the extra space appropriately (although because the button is a different height than the Text widget, you'll still have a little gap there). I don't know if this is the most appropriate way, but it works.
Here:
from tkinter import *
tk=Tk()
tk.columnconfigure(0, weight=1)
tk.rowconfigure(0, weight=1)
tk.rowconfigure(1, weight=100)
f=Frame(tk)
f2=Frame(tk)
f.grid(row=0, column=0, sticky=E+W)
f2.grid(row=1, column=0, sticky=N+S+E+W)
t=Text(f, height=1)
t2=Text(f2)
l=Label(f, text="label")
b=Button(f, text="button")
l.pack(side=LEFT)
t.pack(side=LEFT, fill=X, expand=True)
b.pack(side=LEFT)
t2.pack(fill=BOTH, expand=True)
tk.mainloop()