19-й час. Графические объекты библиотеки Tk, II

19-й час

Графические объекты библиотеки Tk, II

  В предыдущей главе мы начали изучение графических объектов библиотеки Tkinter. Сейчас мы продолжим рассмотрение этой темы. Прочитав данную главу до конца, Вы сможете узнать дополнительные графические объекты, с которыми ещё не знакомы; комбинировать различные объекты в графическом интерфейсе небольшого приложения; создавать сложные полнофункциональные приложения, такие как tkeditor.

Объект переключатель

  Вы, наверное, ещё помните переключатели каналов и режимов на радиоаппаратуре, которые имели вид утапливаемых кнопок. Сейчас их почти повсеместно заменили сенсорные, но лет 20 назад практически вся радиоаппаратура пестрела такими кнопками (чем больше — тем круче). Именно из-за сходства с этими кнопками для данного типа графических объектов в английской терминологии было выбрано название Radio Button. В чем же это сходство? Вспомните, на радиоприемнике Вы нажимаете кнопку одного канала, и тут же другая кнопка, которая была нажатой до того, стремительным щелчком возвращается в исходную позицию в ряду с другими кнопками, т.е. сколько бы ни было кнопок в одном ряду, нажатой может быть только одна, ведь нельзя же слушать одновременно два канала. Кнопки переключателя (рис. 19.1) ведут себя точно так же. Вот почему в набор переключателей всегда входит несколько кнопок. Одна кнопка переключателя мертва, как муравей без муравейника. Как и в случае с другими графическими объектами, переключатели в системе UNIX ведут себя точно так же, только выглядят немного иначе (рис. 19.2).

Рис. 19.1. Объект переключатель

Рис. 19.2. Объект переключатель в стиле UNIX

  Но даже в Windows можно настроить вид кнопок переключателя, чтобы они выглядели как обычные кнопки (рис. 19.3).

Рис. 19.3. Альтернативный стиль отображения кнопок переключателя

  Проанализировав код программы вывода переключателя, который показан в листинге 19.1, можно понять, что при запуске программы без аргументов устанавливается стиль кнопок переключателя, заданный по умолчанию (круглые кнопки). При вводе любого аргумента устанавливается альтернативный стиль, показанный на рис. 19.3.

Листинг 19.1. Программа tkradiobutton.py

 from Tkinter import *
import sys

def die(event):
 
global v, periods
 
print periods[v.get()][0]
  sys.exit(0)

root = Tk()
button = Button(root)
button["text"] = "Quit"
button.bind("<Button>",die)
button.pack()

v = IntVar()
v.set(2)

periods = [
  ("kin", 0),
  ("uinal", 1),
  ("tun", 2),
  ("katun", 3),
  ("baktun", 4),
  ]

if len(sys.argv) > 1:
  indicator = 0
  filler=X
  expander=1
else:
  indicator = 1
  filler=None
  expander=0
for t, m in periods :
  b = Radiobutton(root,text=t, variable=v, value=m,\

    indicatoron=indicator)
 
if indicator == 1 :
    b.pack(anchor=W)
 
else :
  b.pack(expand=expander,fill=filler)

root.mainloop()

 

  *Прим. В. Шипкова: обратите внимание, как красиво построен цикл for в конце приведённого листинга - в качестве итераторов используются две(!!!) переменных - t и m. Вряд ли Вы где увидите нечто подобное.

  В строках 17, 18 используется переменная библиотеки Tk, в этот раз целочисленная. В данном случае использование числовой переменной более логично, хотя мы могли воспользоваться и строковой переменной, изменив наш набор methods, например, следующим образом: ("Метод 1", "mth1"), ("Метод 2", "mth2") и т.д. Чтобы вывести затем выбранное значение на экран, можно было бы воспользоваться методом v.get() или окном сообщения showinfo().

Объект шкала

  Этот объект шкала можно вывести на экран в горизонтальном (рис. 19.4) или вертикальном (рис. 19.5) положении.

Рис. 19.4. Горизонтальная шкала

Рис. 19.5. Вертикальная шкала

  Объект шкала используется каждый раз, когда необходимо дать возможность пользователю выбрать значение в заданном диапазоне. В нашем примере на горизонтальной шкале .можно выбрать значение в диапазоне от 0 до 100, т.е. 101 дискретное значение. В листинге 19.2 показан код вывода шкалы как в горизонтальном, так и в вертикальном положении.

Листинг 19.2. Программа tkscale.py

from Tkinter import *
import sys
import string

def die(event):
  sys.exit(0)

def reader(s):
  f=string.atoi(s)
  f=f-32
  c=f/9.
  c=c*5.
 
print "%s degrees F = %f degrees C" % (s, c)

root = Tk()
button = Button(root)
button["text"] = "Quit"
button.bind("<Button>",die)
button.pack()

scale1 = Scale(root, orient=HORIZONTAL)
scale1.pack()
scale2 = Scale(root, orient=VERTICAL,from_=-40, to=212,\

   command=reader)
scale2.pack()

root.mainloop()

  Запустив программу на выполнение, Вы увидите, что вертикальная шкала настроена таким образом, чтобы преобразовывать значения градусов по Фаренгейту (в пределах от -40 F до 212 F) в градусы по Цельсию (соответственно от -40°С до 100°С). Нижняя граница -40° была выбрана потому, что это значение совпадает в обеих шкалах.

  Исходные и расчётные значения динамически передаются программой в объект stdout, который выводит их на экран в окне терминала. Вероятно, это не самый элегантный подход к выводу данных в программе, использующей интерфейс GUI. Давайте внесём некоторые изменения в программу tkscale.py, Чтобы ввод и вывод данных происходил в одном окне. Мы добавим несколько ярлыков, удалим ненужную горизонтальную шкалу и подпишем нашу программу в строке заголовка, чтобы сразу стало ясно, зачем она нужна. Изменённый код программы показан в листинге 19.3.

Листинг 19.3. Программа tkscale2.py

 from Tkinter import *
import sys
import string

def die(event):
  sys.exit(0)

def reader(s):
 
global label
  f=string.atoi(s)
  f=f-32
  c=f/9.
  c=c*5.
  flabel["text"]="Degrees Fahrenheit: %s" % (s)
  label["text"]="Degrees Celsius: %d" % (int(c))

root=Tk()
button=Button(root,width=25)
button["text"]="Quit"
button.bind("<Button>",die)
button.pack()

flabel=Label(root,text="Degrees Fahrenheit:")
flabel.pack(anchor=W)
scale2=Scale(root, orient=VERTICAL, from_=-40, to=212, command=reader)
scale2.pack()
label=Label(root, text="Degrees Celsius:")
label.pack(anchor=W)

root.title("Fahrenheit to Celsius")

root.mainloop()

На рис. 19.6 показано, как теперь будет выглядеть окно приложения.

Рис. 19.6. Окно программы tkscale2.py

Поле редактора с полосой прокрутки

  Объект поле редактора (рис. 19.7) предназначен для ввода пользователем многострочного текста. Этот объект снабжен функциями, поддерживающими редактирование и выделение текста с помощью мыши и комбинаций быстрых клавиш. Так, блок текста, выделенный указателем мыши, можно скопировать, вырезать и вставить, используя комбинации клавиш, характерные для данной системы. Например, в Windows для этого используются комбинации клавиш <Ctrl+C>, <Ctrl+X> и <Ctrl+V> соответственно. В листинге 19.4 показан минимальный код вывода объекта поле редактора.

Рис. 19.7. Объект поле редактора

Листинг 19.4. Программа tktext.py

 from Tkinter import *
import sys

def die(event):
  sys.exit(0)

root=Tk()
f=Frame(root)
f.pack(expand=1, fill=BOTH)
button=Button(f,width=25)
button["text"] = "Button"
button.bind("<Button>",die)
button.pack()
t = Text(f, width=25, height=10, relief=RAISED, bd=2)
t.pack(side=LEFT, fill=BOTH, expand=1)

root.mainloop()

  В строке 16 свойству bd назначается ширина границы объекта текст. Имя bd — это сокращение от имени свойства borderwidth. Для некоторых свойств позволительно использование как полных имён, так и стандартных сокращений, являющихся синонимами.

Полосы прокрутки

  Полосы прокрутки используются для того, чтобы прокручивать вверх и вниз содержимое других объектов, например текст в поле редактора. Полоса прокрутки может быть горизонтальной, но для неё в Tkinter назначено очень мало функций. Гораздо шире используется вертикальная полоса прокрутки, которая применяется в таких объектах, как ScrolledText (прокручиваемое поле редактора), FileDialog (диалоговое окно Файл) и др.

Пример вертикальной полосы прокрутки показан на рис. 19.8.

Рис. 19.8. Полоса прокрутки

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

Листинг 19.5. Программа tkscrollbar.py

from Tkinter import *
from ScrolledText import *
import sys

def die(event):
  sys.exit(0)

root=Tk()
f=Frame(root)
f.pack(expand=1, fill=BOTH)
button=Button(f,width=25)
button["text"]="Quit"
button.bind("<Button>",die)
button.pack()

st=ScrolledText(f,background="white")
st.pack()

root.mainloop()

  Эта небольшая программа выводит на экран поле редактора. Безусловно, возможности использования данного редактора весьма ограничены. Вы даже не можете сохранить введенный текст. Тем не менее для решения подобной задачи в языке С потребовалась бы программа, состоящая из сотни строк. (В действительности так оно и есть, поскольку каждая строка кода Python-Tkinter вызывает выполнение целого каскада строк на языке С, положенных в основу используемых нами графических объектов.) Python — это язык программирования высокого уровня. Он освобождает Вас от решения таких рутинных низкоуровневых задач, как отслеживание текущего положения курсора и выбор способа вывода отдельных букв при нажатии клавиш. Комплексное решение множества низкоуровневых задач одной командой языка высокого уровня называется абстракцией. Пример поля редактора, выведенного на экран программой tkscrollbar.py, показан на рис. 19.9.

Рис. 19.9. Поле редактора с полосой прокрутки

  Хватит плескаться на мелководье. Давайте создадим такой текстовый редактор, который позволит нам сохранять введённый текст. Безусловно, нам также следует добавить строку меню, в которой, помимо опции сохранения файла будет команда открытия информационного окна с данными о версии программы. В общем, мы все это уже умеем делать. Единственное, чего Вы ещё не знаете, — где взять диалоговое окно для сохранения и открытия файлов. Полный код нашего текстового редактора показан в листинге 19.6.

  *Прим. В. Шипкова: программа блокнота рабочая, но всё-таки сохранить русский текст Вы не сможете. Это связано с тем, что Питон сам плохо справляется с преобразованием юникода с ASCII, с кодами большими чем 128. Но это всё поправимо. ;)

Листинг 19.6. Текстовый редактор tkeditor.py

from Tkinter import *
from ScrolledText import *
import tkMessageBox
from tkFileDialog import *
import fileinput

st=None

def die():
  sys.exit(0)

def openfile():
  global st
  pl=END
  oname=askopenfilename(filetypes=[("Python files",\

   "*.py")])
 
if oname:
   
for line in fileinput.input(oname):
      st.insert(pl,line)

def savefile():
  sname=asksaveasfilename()
 
if sname:
    ofp=open(sname,"w")
    ofp.write(st.get(1.0,END))
    ofp.flush()
    ofp.close()

def about():
  tkMessageBox.showinfo("Tkeditor", \

     "Simple tkeditor Version 0\n"\

     "Written 1999\n"\

     "For Teach Yourself Python in 24 Hours")


if __name__=="__main__":
 
global st
  root=Tk()
  bar=Menu(root)

  filem=Menu(bar)
  filem.add_command(label="Open...", command=openfile)
  filem.add_command(label="Save as...",\

    command=savefile)
  filem.add_separator()
  filem.add_command(label="Exit", command=die)

  helpm=Menu(bar)
  helpm.add_command(label="About", command=about)

  bar.add_cascade(label="File", menu=filem)
  bar.add_cascade(label="Help", menu=helpm)
  root.config(menu=bar)

  f=Frame(root,width=512)
  f.pack(expand=1, fill=BOTH)

  st=ScrolledText(f,background="white")
  st.pack(side=LEFT, fill=BOTH, expand=1)
  root.mainloop()

  На рис. 19.10 показан результат выполнения программы tkeditor.py. В поле редактора введён собственный код программы, и мы сохраняем программу в папке C:\Python. Диалоговое окно "Сохранение" вызывается с помощью функции обработки события savefile().

Рис. 19.10. Сохранение файла в редакторе tkeditor.py

  В листинге 19.5 содержится 59 строк, включая пустые. Ещё пару лет тому назад программа подобного текстового редактора состояла бы из сотен строк, а десяток лет тому назад — из тысяч строк.

  Конечно, для того чтобы этот редактор стал полноценным приложением, в него нужно ещё добавить много полезных средств и инструментов. Многие из них Вы сможете добавить сами. Например, хорошо было бы иметь возможность внедрять в текст графические изображения или сохранять файлы разных форматов. И сделать это действительно просто. Нужно только знать, где хранятся требуемые методы и объекты. А в этом как раз нет никакого секрета. На домашней странице Python Вы найдёте всю исчерпывающую информацию, а также ссылки на другие Web-страницы.

Окна верхнего уровня

  К окнам верхнего уровня относятся главные окна приложений, которые содержат все остальные графические объекты. Так, в меню нашей программы текстового редактора tkeditor.py можно добавить команду Создать..., которая будет открывать новое пустое окно редактора, в точности соответствующее исходному окну (чуть позже мы действительно добавим эту опцию). Такая возможность существует в редакторе IDLE. Выберите в меню File опцию New window, и тут же на экране появится новое окно интерпретатора Python. В листинге 19.7 показан один из простейших способов открытия окон верхнего уровня.

Листинг 19.7. Программа tktoplevel.py

from Tkinter import *
import sys

wl=[]

def die():
 
print "You created %d new toplevel widgets"%(len(wl))
  sys.exit(0)

def newwin():
  global root
  wl.append (Toplevel(root))

root=Tk()
bar=Menu(root)
filem=Menu(bar)
filem.add_command(label="New...", command=newwin)
filem.add_separator()
filem.add_command(label="Exit", command=die)

bar.add_cascade(label="File", menu=filem)
root.config(menu=bar)
root.mainloop()

  Теперь Опустим программу на выполнение и выберем команду Создать... из меню Файл 4 раза. Результат показан на рис. 19.11.

Рис. 19.11. Несколько окон верхнего уровня, открытые из окна программы tktoplevel.py

  Программа работает, правда, нам мало проку от множества окон, которые ничего не умеют делать, кроме как занимать пространство на экране. Гораздо полезнее было бы иметь несколько дополнительных полнофункциональных окон. Например, если в нашу программу текстового редактора добавить возможность открывать новые окна, мы могли бы одновременно работать с несколькими текстовыми файлами. Эту задачу можно выполнить несколькими способами, но лучшими будет создание класса окна редактора, который можно будет реализовать в программе неограниченное число раз. Мало того, что таким путём мы сократим основной код программы до нескольких строк, но мы также можем сохранить нашу программу как модуль и импортировать её затем во все другие приложения, где возникнет необходимость в текстовом редакторе. Измененный код программы текстового редактора, в которой теперь появился класс окна редактора editor (практически весь код программы — строки 20-68), показан в листинге 19.8. Этот класс содержит ряд методов. Кроме того, в программе определены ещё три функции, которые не было смысла делать членами класса. Это функции die() (строка 12), about() (строка 15) и neweditorf) (строка 70).

from Tkinter import *
from ScrolledText import *
import tkMessageBox
from tkFileDialog import *
import fileinput

tl=[]
root=None

def die():
  sys.exit(0)

def about():
  tkMessageBox.showinfo("Tkeditor", \

    "Simple tkeditor Version 1\n"\

    "Written 1999\n"\

    "For Teach Yourself Python in 24 Hours")

class editor:
 
def __init__(self, rt):
   
if rt==None:
      self.t=Tk()
   
else:
      self.t=Toplevel(rt)
    self.t.title("Tkeditor %d" % len(tl))
    self.bar = Menu(rt)

    self.filem = Menu(self.bar)
    self.filem.add_command(label="Open...",\

      command=self.openfile)
    self.filem.add_command(label="New...",\

      command=neweditor)
    self.filem.add_command(label="Save as...",\

      command=self.savefile)
    self.filem.add_command(label="Close",\

      command=self.close)
    self.filem.add_separator()
    self.filem.add_command(label="Exit", command=die)

    self.helpm = Menu(self.bar)
    self.helpm.add_command(label="About", command=about)

    self.bar.add_cascade(label="File", menu=self.filem)
    self.bar.add_cascade(label="Help", menu=self.helpm)
    self.t.config(menu=self.bar)

    self.f = Frame(self.t,width=512)
    self.f.pack(expand=1, fill=BOTH)

    self.st = ScrolledText(self.f,background="white")
    self.st.pack(side=LEFT, fill=BOTH, expand=1)

 
def close(self):
    self.t.destroy()

 
def openfile(self):
    pl=END
    oname = askopenfilename(filetypes=[("Python files",\

      "*.py")])
   
if oname:
     
for line in fileinput.input(oname):
        self.st.insert(pl,line)
        self.t.title(oname)

 
def savefile(self):
    sname=asksaveasfilename()
   
if sname:
      ofp=open(sname,"w")
      ofp.write(self.st.get(1.0,END))
      ofp.flush()
      ofp.close()
      self.t.title(sname)

 
def neweditor():
   
global root
    tl.append(editor(root))

if __name__=="__main__":
  root=None
  tl.append(editor(root))
  root=tl[0].t
  root.mainloop()

  Обратите внимание, что когда мы открываем новый файл или сохраняем файл под другим именем, изменяется заголовок текущего окна. Эти изменения реализуются строками программы 59 и 68 с помощью метода окна верхнего уровня title(). Окна верхнего уровня содержат ещё много полезных методов, о которых Вы можете узнать в документации библиотеки Tkinter.

  И опять-таки, обратите внимание, что вся программа текстового редактора с возможностью открытия нескольких окон реализована в 78 строках кода. Однажды в 1984 г. я потратил неделю на написание программы текстового редактора. Код программы составил около 10000 строк. Мне приходилось неоднократно выводить на печать весь код, потому что невозможно было удержать в голове, что делает тот или иной блок. Используемый мной язык программирования не поддерживал никаких абстракций, поэтому приходилось учитывать такие технические мелочи работы компьютера, о которых сейчас страшно и подумать. Текстовый редактор, который мы сейчас получили и который я до этого спроектировал за считанные часы, несравнимо, мощнее и эффективнее моего старого проекта. На рис. 19.12 показано, как работает наш текстовый редактор.

Рис. 19.12. Текстовый редактор с несколькими окнами редактирования

  На этом мы завершим знакомство с различными графическими редакторами библиотеки Tkinter.

Резюме

  Мы познакомились со всеми графическими объектами библиотеки Tkinter и написали две программы: tkscale2.py и multi-editor.ру. Последняя программа — это полнофункциональный текстовый редактор, позволяющий открывать несколько окон для одновременного редактирования разных файлов. Что примечательно, весь код этой сложной программы уместился в 78 строках. В следующих главах мы изучим графические функции библиотеки Tkinter, позволяющие рисовать в объекте холст. Изучив все возможности использования графических функций, в главе 23 мы создадим приложение для расчета наборов Мандельброта и создания удивительных по сложности и красоте рисунков в объекте холст.

Практикум

Вопросы и ответы

  Для большинства приложений Windows характерна следующая схема отношений между окнами приложения. Одно большое окно программы содержит одно или несколько окон документов, не превышающих по размеру главное окно приложения. Возможно ли создание такой конструкции с графическими объектами Tkinter?

  Речь идёт об интерфейсе MDI (Multiple Document Interface — многодокументный интерфейс). В Tkinter нет средств для поддержания подобного интерфейса. Почему бы Вам не заняться самим созданием класса такого интерфейса. Если бы я начинал что-либо подобное, то первое, что бы я сделал, это посетил домашнюю страницу Грега Мак-Фарлана. Я уже представлял Вам его в предыдущей главе. Напоминаю адрес: http://www.dscpl.com.au/pmw/.

  Когда я запустил программу multi-editor.ру на своём компьютере в системе Linux, то открылись ужасно старомодные диалоговые окна для открытия и сохранения файлов. Неужели я вынужден буду иметь с ними дело?

  Совсем нет, разработайте свои диалоговые окна или дожидайтесь того момента, когда в библиотеку Tk/TCL будут добавлены средства КDЕ или GNOME. Если Вы хоть немного знакомы с программированием на С или C++, то для Вас не составит труда разработать окна, отвечающие вашему вкусу и потребностям. Начните с посещения домашней страницы КDЕ (К Desktop Environment) по адресу http://kde.fnet.pl/ или с домашней страницы GNOME Developer (http://developer.gnome.org/).

Контрольные вопросы

  1. В чем отличие между корневым окном и объектом окна верхнего уровня?

а) Корневое окно не может содержать графические объекты.

б) Объект окна верхнего уровня может содержать только некоторые графические объекты, непосредственно унаследованные от него.

в) Ничем.

г) Корневое окно не имеет родительских окон, а объект окна верхнего уровня является дочерним для корневого окна.

  1. Почему в программе multi-editor.py для обработки событий используются методы?

    а) Функции не могут использоваться для обработки событий, только методы.

    б) Если бы использовались функции, мы бы никогда не узнали, из какого окна поступил вызов.

    в) Тем самым удалось существенно сократить число строк кода.

    г) Методы работают быстрее, чем функции.

Ответы

  1. г. Во всех приложениях Tkinter существует только одно корневое окно, которое не имеет родительских окон. Объектов окон верхнего уровня может быть сколько угодно, но все они наследуются от одного корневого окна.

  2. б. Методы принадлежат классам. Благодаря тому что определение поля редактора сделано в классе, метод обработки события всегда будет выполняться для того окна, из которого он был вызван.

Примеры и задания

  В наши программы tkeditor.py и multi-editor.py прокралась одна ошибка. При сохранении файла в него может добавляться лишняя пустая строка. Найдите ошибку и устраните её.

  Добавьте в наш текстовый редактор tkeditor.py новые средства и возможности. Например, сделайте следующее:

  • добавьте возможность открытия редактора из командной строки с указанием в качестве аргумента имени файла, который нужно открыть для редактирования;

  • добавьте возможность внедрять в текст графические изображения;
  • модернизируйте функцию команды "Сохранить как..." таким образом, чтобы рисунки сохранялись вместе с файлом;

  • добавьте в меню команду Сохранить;
  • добавьте возможность выбора шрифта для текста;
  • добавьте возможность изменять цвет шрифта;
  • попробуйте преобразовать программу tkeditor.py или multi-editor.ру в редактор документов HTML;

  • добавьте документацию в программу редактора;
  • представьте Ваше новое приложение на общедоступном ftp-сервере на суд общественности.

  *Прим. В. Шипкова: указанные задания весьма полезно выполнить. После их выполнения станет весьма понятно на сколько легко работать с Питоном. Попробуйте нечто подобное сделать на Visual Basic или Visual C++. ;)