Создание крупных таблиц с помощью grid

sozdanie krupnyh tablic s pomoshhju grid Экскурсия по tkinter, часть 2

До сих пор мы строили наборы меток и полей ввода из двух колонок. Это типичный вид форм ввода, но менеджер grid в библиотеке tkinter способен организовывать значительно более крупные матрицы. Так, в примере 9.23 создается массив меток, состоящий из пяти строк и четырех колонок, в котором каждая метка просто выводит номер своей строки и колонки (row.col). Если запустить этот сценарий, он создаст окно, изображенное на рис. 9.34.

Пример 9.23. PP4E\Gui\Tour\Grid\grid4.py

#  простая двухмерная таблица, в корневом окне Tk по умолчанию

from tkinter import *

for i in range(5):

for j in range(4):

lab = Label(text=’%d.%d’ % (i, j), relief=RIDGE) lab.grid(row=i, column=j, sticky=NSEW)

mainloop()

Если вы предположили, что это выглядит как способ программирования электронных таблиц, то вы, вероятно, на правильном пути. Пример 9.24 дает дальнейшее развитие этой мысли и добавляет кнопку, которая выводит в поток stdout текущие значения полей ввода в таблице (обычно в окно консоли).

Рис. 9.34. Массив 5х4 меток с координатами

 

Пример 9.24. PP4E\Gui\Tour\Grid\grid5.py

#  двухмерная таблица полей ввода, корневое окно Tk по умолчанию

from tkinter import *

rows = []

for i in range(5): cols = [] for j in range(4): ent = Entry(relief=RIDGE) ent.grid(row=i, column=j, sticky=NSEW) ent.insert(END, ‘%d.%d’ % (i, j)) cols.append(ent) rows.append(cols)

def onPress(): for row in rows: for col in row: print(col.get(), end=’ ‘) print()

Button(text=’Fetch’, command=onPress).grid() mainloop()

Если запустить этот сценарий, он создаст окно, изображенное на рис. 9.35, и сохранит все виджеты полей ввода в сетке в двухмерном списке списков. При нажатии кнопки Fetch сценарий выполнит обход списка списков полей ввода, чтобы получить и отобразить все текущие значения в сетке. Ниже приводится вывод после двух нажатий кнопки Fetch — одного перед изменениями в полях ввода и другого после:

#  :\\PP4E\Gui\Tour\Grid> python grid5.py

0.0 0.1 0.2 0.3

1.0 1.1 1.2 1.3

2.0 2.1 2.2 2.3

3.0 3.1 3.2 3.3


3.0 3.1 3.2 45

4.0 4.1 4.2 46


Рис. 9.35. Более крупная сетка полей ввода

 


 


Теперь, когда мы знаем, как создавать и выполнять обход массивов полей ввода, добавим еще несколько полезных кнопок. В примере 9.25 добавлен еще один ряд, в котором отображаются суммы по столбцам, и кнопки, которые обнуляют все поля и вычисляют суммы по столбцам.

Пример 9.25. PP4E\Gui\Tour\Grid\grid5b.py

# добавляет суммирование по столбцам и очистку полей ввода

from tkinter import * numrow, numcol = 5, 4

rows = []

for i in range(numrow):

cols = []

for j in range(numcol):

ent = Entry(relief=RIDGE)

ent.grid(row=i, column=j, sticky=NSEW)

ent.insert(END, ‘%d.%d’ % (i, j)) cols.append(ent) rows.append(cols)

sums = []

for i in range(numcol):

lab = Label(text=’?’, relief=SUNKEN) lab.grid(row=numrow, column=i, sticky=NSEW) sums.append(lab)

def onPrint():

for row in rows:

for col in row: print(col.get(), end=’ ‘) print() print()

def onSum():

tots = [0] * numcol

for i in range(numcol):

for j in range(numrow):

tots[i] += eval(rows[j][i].get()) # вычислить сумму

for i in range(numcol):

sums[i].config(text=str(tots[i])) # отобразить в интерфейсе

def onClear():

for row in rows:

for col in row:

col.delete(‘0’, END)

col.insert(END, ‘0.0’)

for sum in sums:

sum.config(text=’?’)

import sys

Button(text=’Sum’, command=onSum).grid(row=numrow+1, column=0)

Button(text=’Print’, command=onPrint).grid(row=numrow+1, column=1)

Button(text=’Clear’, command=onClear).grid(row=numrow+1, column=2) Button(text=’Quit’, command=sys.exit).grid(row=numrow+1, column=3) mainloop()

На рис. 9.36 изображено окно этого сценария после вычисления сумм по четырем столбцам чисел. Чтобы получить таблицу другого размера, измените переменные numrow и numcol в начале сценария.

Рис. 9.36. Добавление суммирования по столбцам

 

И наконец, пример 9.26, реализующий еще одно, последнее расширение, которое оформлено как класс, что обеспечивает возможность повторного его использования. Кроме того, в нем добавлена кнопка загрузки таблицы из файла с данными. Предполагается, что файл содержит строку для каждого ряда данных, а внутри строки колонки разделяются пробельными символами (пробелами или символами табуляции). При загрузке данных из файла автоматически изменяется размер таблицы, чтобы уместить все колонки.

Пример 9.26. PP4E\Gui\Tour\Grid\grid5c.py

#реализация в виде встраиваемого класса

from tkinter import *

from tkinter.filedialog import askopenfilename

from PP4E.Gui.Tour.quitter import Quitter # повт. использование, pack и grid

class SumGrid(Frame):

def __init__(self, parent=None, numrow=5, numcol=5): Frame.__init__(self, parent) self.numrow = numrow # я контейнерный фрейм

self.numcol = numcol # компоновку выполняет вызвавшая пр.,

self.makeWidgets(numrow, numcol) # иначе можно было бы использовать

# единственным способом

def makeWidgets(self, numrow, numcol): self.rows = [] for i in range(numrow): cols = [] for j in range(numcol):

ent = Entry(self, relief=RIDGE) ent.grid(row=i+1, column=j, sticky=NSEW) ent.insert(END, ‘%d.%d’ % (i, j)) cols.append(ent)

self.rows.append(cols) self.sums = []

for i in range(numcol):

lab = Label(self, text=’?’, relief=SUNKEN) lab.grid(row=numrow+1, column=i, sticky=NSEW) self.sums.append(lab)

Button(self, text=’Sum’, command=self.onSum).grid(row=0, column=0) Button(self, text=’Print’, command=self.onPrint).grid(row=0, column=1) Button(self, text=’Clear’, command=self.onClear).grid(row=0, column=2) Button(self, text=’Load’, command=self.onLoad).grid(row=0, column=3) Quitter(self).grid(row=0, column=4) # fails: Quitter(self).pack()

def onPrint(self): for row in self.rows: for col in row: print(col.get(), end=’ ‘) print() print()

def onSum(self):

tots = [0] * self.numcol for i in range(self.numcol):

for j in range(self.numrow)

 

tots[i] += eval(self.rows[j][i].get()) for i in range(self.numcol):

self.sums[i].config(text=str(tots[i]))

# суммировать данные

def

onClear(self):

for row in self.rows:

for col in row:

col.delete(‘0’, END)

# удалить содержимое

 

col.insert(END, ‘0.0’)

# зарезерв. значение

def

for sum in self.sums: sum.config(text=’?’)

onLoad(self):

file = askopenfilename() if file:

for row in self.rows:

for col in row: col.grid_forget()

# очистить интерфейс

 

for sum in self.sums:

sum.grid_forget()

filelines = open(file, ‘r’).readlines()

# загрузить данные

 

self.numrow = len(filelines)

# изменить размер табл.

 

self.numcol = len(filelines[0].split()) self.makeWidgets(self.numrow, self.numcol)

for (row, line) in enumerate(filelines):

# загрузить в интерфейс

fields = line.split()

for col in range(self.numcol):

self.rows[row][col].delete(‘0’, END) self.rows[row][col].insert(END, fields[col])

if __name__ == ‘__main__’: import sys root = Tk() root.title(‘Summer Grid’) if len(sys.argv) != 3:

SumGrid(root).pack() # .grid() здесь тоже работает

else:

rows, cols = eval(sys.argv[1]), eval(sys.argv[2]) SumGrid(root, rows, cols).pack() mainloop()

Обратите внимание, что класс SumGrid из этого модуля не применяет к себе самому ни метод grid, ни метод pack. Чтобы дать возможность прикрепления к контейнерам, где есть другие графические элементы, скомпонованные тем или иным способом, он оставляет управление собственной компоновкой неопределенным и требует, чтобы вызывающая программа сама применяла метод pack или grid к его экземплярам. Контейнеры могут выбрать любую схему компоновки для своих дочерних элементов, потому что они независимы в своем выборе, но прикрепляемым классам компонентов, предназначенным для использования с любыми менеджерами компоновки, нельзя поручить управлять собой, так как они не могут заранее знать политику своего родителя.

Это довольно длинный пример, в котором нет почти ничего нового в отношении компоновки по сетке или виджетов в целом, поэтому я оставлю его для самостоятельного изучения и просто покажу, что он делает. На рис. 9.37 изображено начальное окно, созданное этим сценарием, после того как была изменена последняя колонка и произведено суммирование — не забудьте включить корневой каталог PP4E дерева с примерами в путь поиска модулей (например, в переменную окружения PYTHONPATH), чтобы сценарий смог импортировать пакет.

Рис. 9.37. Добавлена загрузка данных из файла

 

По умолчанию класс создает сетку размером 5 на 5, но существует возможность определять другие размерности, как в конструкторе класса, так и в командной строке сценария. При нажатии кнопки Load выводится стандартный диалог выбора файла, с которым мы встречались ранее (рис. 9.38).

Файл данных grid5-data1.txt содержит семь строк и шесть колонок данных:

C:\\PP4E\Gui\Tour\Grid>type grid5-data1.txt

1 2 3 4 5 6

1 2 3 4 5 6

1 2 3 4 5 6

1 2 3 4 5 6

1 2 3 4 5 6

1 2 3 4 5 6

1 2 3 4 5 6

При загрузке его в наш графический интерфейс соответствующим образом изменяются размеры сетки — класс просто заново выполняет логику создания виджетов после удаления прежних элементов ввода с помощью метода grid_forget. Метод grid_forget отвязывает виджеты в сетке и в результате удаляет их с экрана. Другие способы удаления и перерисовки компонентов графического интерфейса предоставляют методы

Рис. 9.38. Диалог открытия файла в сценарии SumGrid

 

 

Рис. 9.39. Файл с данными загружен, отображен и просуммирован

 

pack_forget виджетов и withdraw окна, которые используются в обработчике события after примеров «будильников» в следующем разделе.

На рис. 9.39 показано, как выглядит окно после операций удаления и перерисовки виджетов, выполненных в результате щелчков на кнопках Load и Sum.

Файл с данными grid5-data2.txt имеет те же размерности, но в двух колонках он содержит не просто числа, а выражения. Так как этот сценарий преобразует значения полей ввода с помощью встроенной функции eval, в полях этой таблицы допускается использовать любые выражения Python, если они могут быть вычислены в области видимости метода onSum:

C:\\PP4E\Gui\Tour\Grid> type grid5-data2.txt

1 2 3 2*2 5 6

1 3-1 3 2<<1 5 6

1 5%3 3 pow(2,2) 5 6

1 2 3 2**2 5 6

1 2 3 [4,3][0] 5 6

1 {‘a’:2}[‘a’] 3 len(‘abcd’) 5 6

1 abs(-2) 3 eval(‘2+2’) 5 6

При суммировании этих полей выполняется содержащийся в них программный код на языке Python, что иллюстрирует рис. 9.40. Эта особенность может оказаться достаточно мощной. Представьте себе, например, полноценную сетку электронной таблицы — значения полей могут быть «фрагментами» программного кода на языке Python, которые динамически вычисляют значения, вызывают функции из модулей и даже загружают текущие котировки акций из Интернета с помощью инструментов, с которыми мы познакомимся в следующей части книги.

Однако эта особенность может представлять опасность — в поле может содержаться выражение, удаляющее содержимое вашего жесткого диска![XL] Если вы не до конца уверены в том, какими могут быть полученные выражения, не используйте функцию eval (осуществляйте преобразование, применяя более ограниченные функции, такие как int и float) или обеспечьте выполнение процесса Python с ограниченными правами доступа к системным компонентам, которые было бы нежелательно подвергать опасности.

Рис. 9.40. Выражения на языке Python в данных и таблице

 

Конечно, этому сценарию еще очень далеко до настоящей электронной таблицы. Сценарий подсчитывает суммы по колонкам и способен загружать данные из файлов, но ячейки не могут содержать формулы, ссылающиеся на другие ячейки. Однако из-за недостатка места в книге дальнейшие улучшения для достижения этой цели я оставляю читателям в качестве упражнения.

Я должен также отметить, что о размещении по сетке можно сказать больше, чем позволяет объем книги. Например, путем создания вложенных фреймов с собственными сетками можно строить более сложные структуры в виде иерархий компонентов, во многом подобно тому, как размещает вложенные фреймы менеджер компоновки pack. А теперь перейдем к последней теме обзора виджетов.

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий