Python tkinter grid, row value being ignored in a ScrolledText Widget - python-3.x

I want to have a space between the Entry widget and the ScrollText Widget. I thought it would be as simple as setting row=2 for the grid. The strange thing is no matter what I set for row the ScrollText widget always starts at row 1. I suppose I could add an empty text widget to make the space but I am confused as to why setting the row is not working for me.
from tkinter import *
from tkinter import scrolledtext
main = Tk()
main.title("Grid Prob")
main.geometry('750x750')
text = StringVar()
entSearch = Entry(main, textvariable = text, width = 50, font='arial 12',
highlightthickness=1)
entSearch.grid(row = 0, column = 1)
txt = """This is some text to be entered into the text widget. It should
word wrap when the text exceeds the width of the widget."""
textw = scrolledtext.ScrolledText(main,width=70,height=33)
# No matter what number I put for row the scroll widget always starts in
# row 1
textw.grid(column=1, row=3)
textw.config(background="light grey", foreground="black",
font='arial 12', wrap='word')
textw.insert(END, txt)
main.mainloop()

If I interpreted what Bryan Oakley said in the comments above correctly the problem is that since my code didn't put anything into row 2 by default it (row 2) has a height of 0. This means that I am putting my ScrollText Widget into row 3 as intended but since row 2 has a height of 0 visually it looks like it's being placed in row 2. I added a blank label to my code with a height of one and placed it on row 2. This provided the space that I was looking for. Here is the additional code I added.
label_1 = Label(main, width = "70", height = "1")
label_1.grid(row=1, columnspan=3)
It seems weird that tkinter makes you add widgets just to provide a space. If anyone knows of a better way to do this please let me know.

Related

Is there a way to center both item s in a Tk Label

I'm making a program and I need a image and an entry field. However when I try to put them in the same column it defaults the to the top left no matter the value. I tried column as various values and it either goes to the top left if they are the same or one goes to the center-ish and one is on the left. is there something I'm missing. Any help would be greatly appreciated.
from tkinter import *
import tkinter as tk
from PIL import Image,ImageTk
pg3 = Tk()
img1 = ImageTk.PhotoImage(Image.open("download.png"))
pg3.attributes("-fullscreen", True)
tk.Label(pg3, image = img1, anchor = "c").grid(row=0, column = 1)
e1 = tk.Entry(pg3)
e1.grid(row=1, column=1)
pg3.mainloop()
Use pack.
BaseWidget.pack() automatically centers all your elements.
P.S. If you want to horizontally center a few elements, you can use a Frame to grid the elements and then pack the frame.
Another way will be using pg3.columnconfigure(1, weight=1). It is an alias of grid_columnconfigure.
def grid_columnconfigure(self, index, cnf={}, **kw):
"""Configure column INDEX of a grid.
Valid resources are minsize (minimum size of the column),
weight (how much does additional space propagate to this column)
and pad (how much space to let additionally)."""
So you can set the weight to 1 to center it.

Issues when freezing wx.Grid columns with FreezeTo: bad cell position in grid, and HOME keyboard key behavior

Context
I have put in place column freezing in a wx.grid.Grid, using FreezeTo method.
def __init__(self, parent):
# relevant lines
self.grid = wx.grid.Grid(self.sbox_grid, size=(1000, 800))
self.grid.CreateGrid(self.row_number, self.col_number)
self.grid.FreezeTo(0, self.frozen_column_number)
The freezing by itself works well, as soon as I keep the standard label renderer (*).
The first few columns I have frozen always stay visible, and moving the horizontal scrollbar by hand is also ok.
(*) I was initially using the GridWithLabelRenderersMixin of wx.lib.mixins.gridlabelrenderer, but it totally breaks consistency between column label width and column width. Anyway I can deal with the standard renderer, so it is not really a problem.
I faced several issues, now all solved and detailed below.
Capture the cell position for frozen columns: cells or labels (SOLVED)
For cells, the window can be captured with GetFrozenColGridWindow.
So mouseover can be done simply with:
if widget == self.grid.GetFrozenColGridWindow():
(x, y) = self.grid.CalcUnscrolledPosition(event.GetX(), event.GetY())
row = self.grid.YToRow(y)
col = self.grid.XToCol(x)
# do whatever your want with row, col
For labels, the window exists but is NOT accessible with a method.
With a GetChildren on the grid, I have found that it is the last of the list (corresponding to the latest defined).
So it is not very reliable, but a relatively good placeholder for the missing GetGridFrozenColLabelWindow method.
wlist = self.grid.GetChildren()
frozen_col_label_window = wlist[-1]
if widget == frozen_col_label_window:
x = event.GetX()
y = event.GetY()
col = self.grid.XToCol(x, y)
# do stuff with col
Mouse position from non-frozen columns (labels or cells) is shifted (SOLVED)
The effective position for non-frozen columns labels or cells is shifted from the total width of all the frozen columns.
This one is easily handled by a shift in position, computed before calls to YToRow or XToCol methods.
The following code shows the position corrections:
class Report(wx.Panel):
def _freeze_x_shit(self):
"""Returns the horizontal position offset induced by columns freeze"""
offset = 0
for col in range(self.frozen_column_number):
offset += self.grid.GetColSize(col)
return offset
def on_mouse_over(self, event):
widget = event.GetEventObject()
# grid header
if widget == self.grid.GetGridColLabelWindow():
x = event.GetX()
y = event.GetY()
x += self._freeze_x_shit() # <-- position correction here
col = self.grid.XToCol(x, y)
# do whatever grid processing using col value
# grid cells
elif widget == self.grid.GetGridWindow():
(x, y) = self.grid.CalcUnscrolledPosition(event.GetX(), event.GetY())
x += self._freeze_x_shit() # <-- and also here
row = self.grid.YToRow(y)
col = self.grid.XToCol(x)
# do whatever grid cell processing using row and col values
event.Skip()
HOME keyboard key not working as intended (SOLVED)
I generally use the HOME key to immediately go at the utmost left of the grid, and the END key to go far right. This is the normal behavior with a non-frozen grid.
The END key does its jobs, but not the HOME key.
When pushing HOME on any grid cell, I got two effects:
the selected cell becomes the first column: this is OK
but the scrollbar position is not changed at all: I would expect the scroll position to be fully left
I have corrected it by a simple remapping using EVT_KEY_DOWN event:
def __init__(self, parent):
self.grid.Bind(wx.EVT_KEY_DOWN, self.on_key_event)
def on_key_event(self, event):
"""Remap the HOME key so it scrolls the grid to the left, as it did without the frozen columns
:param event: wx.EVT_KEY_DOWN event on the grid
:return:
"""
key_code = event.GetKeyCode()
if key_code == wx.WXK_HOME:
self.grid.Scroll(0, -1)
event.Skip()
my 3 issues concerning column Freeze in a grid are now solved. I have edited my initial post with my solutions.

How to make border for my table using python tkinter?

Apparently, my application can display the excel file but it is a bit messy without border for the table.
import pandas as pd
import xlrd
import tkinter as tk
from tkinter import*
from tkinter import ttk, filedialog
root = tk.Tk()
root.title("My Application")
width = 1000
height = 500
def browseFile():
global workbook, copyWorkbook, excel_file, sheetName, worksheet, df_table
fileName = filedialog.askopenfilename(initialdir = '/', title = 'New File', filetypes = (('excel file', '.xlsx'), ('excel file', '.xls'), ('all files', '*.*')))
excel_file = pd.ExcelFile(fileName)
workbook = xlrd.open_workbook(fileName)
sheetCount = workbook.nsheets
sheetName = []
tab = []
for x in range(workbook.nsheets):
tab.append(ttk.Frame(tabControl))
sheetName = workbook.sheet_names()
tabControl.add(tab[x], text = sheetName[x])
df_table = excel_file.parse(sheetName[x])
lblTable = Label(tab[x], text = df_table.to_string(index = False)).pack()
toolbar = Frame(root)
btnOpen = Button(toolbar, text = "Open", command = browseFile).pack(side = LEFT)
btnQuit = Button(toolbar, text = "Quit", command = root.quit).pack(side = RIGHT)
toolbar.pack(side = TOP, fill = X)
tabControl = ttk.Notebook(root)
tabHome = ttk.Frame(tabControl)
tabControl.pack(expand = 1, fill = 'both', side = LEFT)
root.mainloop()
I have tried search statements that can display the table with border, but no result found. How can I add border to the table? Is it possible to add border? If not, what other method that I can use?
The problem
Your problem here is that, your code puts the entire dataframe into a single Label widget. When I tried putting a border around this label, it appeared around the whole dataframe, rather than around each cell.
My solution
My solution to this is to go through all of the rows and create an individual Label widget for each cell, giving each of these its own border. I then organised them using .grid() rather than .pack(). For the border, I used the settings borderwidth=2, relief="ridge", but you can choose others.
I also added a feature that sets the width of each column to its longest value to prevent the label's contents overflowing.
I did not include anything to include row and column headers, but you replacing i.pop(0) with i[0] and changing header=none should add this feature.
Modified code
I have only included the for loop in your browseFile() function as I have not made any changes to the rest of your code.
for x in range(workbook.nsheets):
tab.append(ttk.Frame(tabControl))
sheetName = workbook.sheet_names()
tabControl.add(tab[x], text = sheetName[x])
df_table = excel_file.parse(sheetName[x], header=None) # header=None stops the first line of the data table being used as the column header, making it appear in the data table
# Iterates through each row, creating a Label widget for each cell on that row.
for i in df_table.itertuples():
i=list(i) # Converts the row to a more helpful list format
row = i.pop(0) # pop returns value at position, then removes it, as we don't wan't first value of tuple (row number) in spreadsheet
for j in range(len(i)):
# Next two lines get the current column in a list format and convert all items to a string, in order to determine the longest item to make all label widgets the correct width.
current_column = list(df_table.iloc[:, j])
for k in range(len(current_column)): current_column[k] = str(current_column[k])
# Makes label widget
lbl = Label(tab[x], text = i[j], borderwidth=2, relief="ridge", width=len(max(current_column, key=len)), justify=LEFT).grid(row=row, column=j+1)
PS: I really enjoyed solving this question. Thanks for asking!

Is filling a grid with images so I could control location a good practice?

I'm trying to locate a few images on a grid, but when I try to do something like
panel = Label(root, image = img)
panel.grid(row = a, column = b)
where a,b are values that should bring the image to the center of the grid, I always get the image on the top left corner, because empty columns/rows don't fill the space. So, using a friend's advice, I added a blank image and did something like
a = 0
b = 0
for img in ls2: # ls2 is a list containing instances of the blank image
a += 1
if a == 11:
a = 1
b += 1
panel = Label(root, image = img)
panel.grid(row = a, column = b)
Now, when I'm trying to locate a new image on row = x, column = y it goes as I wanted and this is my current solution. My question is, is this a good way to enable me use the whole grid?
No, it's not a good way. There is no need to create invisible widgets. It's hard to say what the good way is, however, because "use the whole grid" is somewhat vague.
If by "use the whole grid" you mean want to create a grid where all rows are the same height, and all of the columns are the same width, and the grid fills its containing window, the right solution is to create uniform rows columns using rowconfigure and columnconfigure.
For example, if you want to create a 4x4 grid of equal sized cells, you could do something like this:
import tkinter as tk
root = tk.Tk()
for row in range(4):
root.grid_rowconfigure(row, uniform="default", weight=1)
for column in range(4):
root.grid_columnconfigure(column, uniform="default", weight=1)
label_2_2 = tk.Label(root, text="row 2, column 2", borderwidth=2, relief="groove")
label_1_0 = tk.Label(root, text="row1, column 0", borderwidth=2, relief="groove")
label_1_0.grid(row=1, column=0)
label_2_2.grid(row=2, column=2)
root.mainloop()
The uniform option takes any arbitrary string. Every row (or column) with the same value will have the same dimensions.
The weight option tells grid how to allocate any extra space. By giving each row and each column an identical non-zero weight, all extra space will be apportioned equally, causing the rows and columns to grow or shrink to fit the window.
Just found the place geometry manager. It is more explicit and lets me control the exact locations of my images inside the widget.
panel.place(x=100, y=100) gives pixel precision location which was what I looked for in the first place.

Python 3, Tkinter, trying to get 9 buttons on 3 different rows

Ive just started using tkinter and have this so far:
from tkinter import *
BoardValue = ["-","-","-","-","-","-","-","-","-"]
window = Tk()
window.title("Noughts And Crosses")
window.geometry("250x250")
for b in BoardValue[0:3]:
btn = Button(window, text=b, relief=GROOVE, width=2).pack(side=LEFT)
for b in BoardValue[3:6]:
btn = Button(window, text=b, relief=GROOVE, width=2).pack(side=LEFT)
for b in BoardValue[6:9]:
btn = Button(window, text=b, relief=GROOVE, width=2).pack(side=LEFT)
window.mainloop()
I want 3 buttons on 3 rows, how can I do this?
There are two common solutions: one using grid, and one using pack with subframes for each row. There are other solutions but these are the most common. Of these two, when creating a grid of widgets, grid is the most natural solution.
Note: in both of the examples below, you can create the buttons in three separate loops if you want, but since the buttons are identical I chose to use a single loop to make the code a little easier to maintain (for example, if you decide to change the relief, you only have to change it in one line of code rather than three).
Using grid
You can use the grid geometry manager to align things in rows and columns. Since you can compute the row and column with simple math, you can reduce all of that code down to something like this:
for i, b in enumerate(BoardValue):
row = int(i/3)
col = i%3
btn = Button(window, text=b, relief=GROOVE, width=2)
btn.grid(row=row, column=col, sticky="nsew")
You may want to add a weight to the rows and columns if you want them to grow and shrink equally when you resize the window.
grid has an advantage over pack when building a matrix like this, since each cell is guaranteed to be the same size.
Using pack
If you know that each button will be the same width, you can use pack in combination with subframes. Pack the subframes along the top, and the buttons along the left or right. Again, you can use a little math to know when a new row is starting.
It would look something like this:
for i, b in enumerate(BoardValue):
if i%3 == 0:
row_frame = Frame(window)
row_frame.pack(side="top")
btn = Button(row_frame, text=b, relief=GROOVE, width=2)
btn.pack(side="left")
Using pack may not be the right choice if buttons could be different sizes, since there are no actual columns using this method.
The simple answer is to put the buttons in frames so you can control the packing order. E.g.;
i=0
for f in range(3):
frame[f] = Frame(window).pack(side=TOP)
for b in range(3):
btn[i] = Button(frame[f], text=i, relief=GROOVE, width=2).pack(side=LEFT)
i+=1
I've put the button & frame objects into lists in case you need to access the button objects later; you were overwriting your handles in the original code.

Resources