Программирование на Python 3. Руководство издательство СимволПлюс
![]()
|
559 поведения виджета и его внешнего вида. Для виджета principalLabel мы определили отображаемый им текст, а в аргументе anchor передали значение tkinter.W, которое означает, что текст метки будет выравни ваться по западному (west), то есть по левому краю. Аргумент underline определяет, какой символ в тексте будет использоваться для обозначе ния горячей клавиши (например, Alt+P); далее будет показано, как ра ботать с горячими клавишами. (Горячая клавиша – это комбинация клавиш вида Alt+символ, где символ – это подчеркнутый символ метки; при нажатии горячей клавиши фокус ввода передается виджету, ассо циированному с горячей клавишей, обычно расположенному правее или ниже метки, в которой определен символ горячей клавиши.) Виджетам tkinter.Scale передается в качестве родителя, как обычно, объект self и ассоциированная с каждым из них переменная. Кроме того, в аргументе command им передаются ссылки на объекты функций (в данном случае методы). Эти методы будут вызываться автоматиче ски при каждом изменении положения движка, устанавливаются ми нимальное (имя аргумента from_ записывается с завершающим симво лом подчеркивания, потому что from – это зарезервированное ключе вое слово) и максимальное (to) значения и определяется горизонталь ная ориентация. Для некоторых движков указывается разрешение (resolution) – шаг изменения значений, а для rateScale – еще и число отображаемых цифр. Виджет actualAmountLabel также имеет ассоциированную переменную, благодаря этому позднее мы легко можем изменять отображаемый им текст. Кроме того, мы придали этой метке вид утопленного поля, что бы визуально он гармонировал с движками. principalLabel.grid(row=0, column=0, padx=2, pady=2, sticky=tkinter.W) principalScale.grid(row=0, column=1, padx=2, pady=2, sticky=tkinter.EW) rateLabel.grid(row=1, column=0, padx=2, pady=2, sticky=tkinter.W) rateScale.grid(row=1, column=1, padx=2, pady=2, sticky=tkinter.EW) yearsLabel.grid(row=2, column=0, padx=2, pady=2, sticky=tkinter.W) yearsScale.grid(row=2, column=1, padx=2, pady=2, sticky=tkinter.EW) amountLabel.grid(row=3, column=0, padx=2, pady=2, sticky=tkinter.W) actualAmountLabel.grid(row=3, column=1, padx=2, pady=2, sticky=tkinter.EW) После создания виджетов их нужно разместить. Мы будем использо вать схему размещения типа «сетка», как показано на рис. 13.3. Все виджеты поддерживают метод grid() (и некоторые другие методы схемы размещения, такие как pack()). Вызовом метода grid() виджет 560 Глава 13. Введение в программирование графического интерфейса помещается внутрь родительского виджета в ячейку сетки с указан ным номером строки и столбца. Имеется возможность заставить вид жет занять несколько столбцов и несколько строк, используя допол нительные именованные аргументы (rowspan и columnspan), а также имеется возможность добавить отступы вокруг виджета – с помощью именованных аргументов padx (отступы слева и справа) и pady (отступы сверху и снизу), определяя в них величину отступов в пикселях. Если виджет занимает больше места, чем ему требуется, с помощью аргу мента sticky можно указать, что он должен делать со свободным про странством; если аргумент не задан, виджет будет занимать середину выделенного пространства. Для всех меток в первом столбце в аргу менте sticky передается значение tkinter.W (west – выравнивание по за падному, то есть по левому краю), а для всех виджетов в правом столб це – значение tkinter.EW (eastwest – с востока на запад, то есть по ши рине), что заставляет их растягиваться по всей ширине доступного пространства. Все виджеты сохраняются в локальных переменных, но они не будут утилизированы сборщиком мусора после выхода из метода инициали зации благодаря отношениям родительпотомок, потому что для всех виджетов определен родительский виджет – главное окно. Иногда виджеты создаются в виде переменных экземпляра – например, когда необходимо обеспечить доступ к ним за пределами метода инициали зации, но в данном случае мы использовали переменные экземпляра в роли переменных, ассоциированных со значениями виджетов (self.principal, self.rate и self.years), благодаря чему они будут дос тупны за пределами метода инициализации. principalScale.focus_set() self.updateUi() parent.bind(" parent.bind(" parent.bind(" parent.bind(" parent.bind(" В конце метода инициализации фокус ввода передается виджету prin cipalScale , чтобы сразу после запуска программы пользователь мог principalLabel principalScale rateLabel rateScale yearsLabel yearsScale amountLabel actualAmountLabel Рис. 13.3. Схема размещения виджетов в окне программы Interest Программы в виде диалога 561 установить начальную сумму вклада. Затем вызывается метод self.up dateUi() , вычисляющий конечную общую сумму с учетом процентов. Затем выполняются привязки горячих клавиш. (К сожалению, тер мин привязка имеет три совершенно разных значения – привязка пе ременной, когда имя, то есть ссылка на объект, связывается с объек том; привязка горячей клавиши, когда событие нажатия или отпуска ния клавиши связывается с функцией или методом, вызываемым при появлении этого события; и привязка для библиотеки – это промежу точный программный код, обеспечивающий доступность библиотеки, написанной на другом языке программирования, в программах на языке Python, посредством использования модулей Python.) Привяз ка горячих клавиш особенно важна для пользователей с ограниченны ми возможностями, испытывающих сложности или вообще не способ ных использовать мышь, а также для тех, кто в совершенстве владеет клавиатурой и старается не пользоваться мышью, так как в их случае использование мыши замедляет темп работы. Первые три привязки горячих клавиш используются для передачи фо куса ввода виджетам движков. Например, для виджета principalLabel устанавливается отображаемый текст Principal $:, в котором нулевой символ выводится с подчеркиванием, поэтому текст метки будет вы глядеть, как Principal $:, и первая привязка горячей клавиши перемес тит фокус ввода в виджет principalScale, когда пользователь нажмет комбинацию клавиш Alt+P. То же относится и к другим двум привяз кам. Обратите внимание, что в привязках мы не указываем метод focus_set() непосредственно, потому что при вызове функции или ме тода по событию в первом аргументе им передается само событие, ко торое нам не нужно. По этой причине мы используем лямбдафунк ции, которые принимают, но игнорируют объекты событий и вызыва ют методы без нежелательного аргумента. Мы также создали две дополнительные горячие комбинации, то есть комбинации клавиш, которые вызывают определенное действие. Мы настроили комбинации клавиш Ctrl+Q и Esc, привязав их к методу self.quit() , который вызывает завершение программы. Имеется возможность привязывать горячие клавиши непосредственно к самим виджетам, но мы предпочли выполнить все привязки в роди тельском виджете (в приложении), поэтому они будут действовать не зависимо от того, где находится фокус ввода. Метод bind() из библиотеки Tk может использоваться для привязки нажатий клавиш и щелчков мышью, а также для привязки программ ных событий. Специальные клавиши, такие как Ctrl и Esc, в библиотеке Tk имеют собственные имена (Control и Escape), а символьным клави шам соответствуют имена, состоящие из самих символов. Комбинации клавиш создаются посредством помещения имен, составляющих ком бинацию клавиш, разделенных символом дефиса, в угловые скобки. 562 Глава 13. Введение в программирование графического интерфейса Создав и разместив виджеты и настроив горячие комбинации клавиш, мы определили основное поведение приложения. Теперь рассмотрим методы, отвечающие за реакцию на действия пользователя и допол няющие поведение приложения. def updateUi(self, *ignore): amount = self.principal.get() * ( (1 + (self.rate.get() / 100.0)) ** self.years.get()) self.amount.set("{0:.2f}".format(amount)) Этот метод вызывается всякий раз, когда пользователь изменяет сум му вклада, процент или интервал времени, поскольку этот метод свя зан с каждым из движков. Все, что он делает, – извлекает значения движков из ассоциированных переменных, выполняет вычисление процентов и записывает результат (в виде строки) в переменной, ассо циированной с меткой, представляющей окончательную сумму вкла да. Благодаря этому метка всегда отображает актуальное значение. def quit(self, event=None): self.parent.destroy() Этот метод вызывается, когда пользователь завершает работу с програм мой (нажатием комбинации клавиш Ctrl+Q или Esc, или щелчком мыши на кнопке закрытия окна). Поскольку в программе нет данных, кото рые требовалось бы сохранить, мы просто сообщаем родительскому вид жету (который представляет само приложение), что он должен уничто жить себя. Родительский виджет уничтожит все дочерние виджеты – все окна, которые в свою очередь уничтожат все свои дочерние видже ты, благодаря чему приложение корректно завершит свою работу. application = tkinter.Tk() path = os.path.join(os.path.dirname(__file__), "images/") if sys.platform.startswith("win"): icon = path + "interest.ico" else: icon = "@" + path + "interest.xbm" application.iconbitmap(icon) application.title("Interest") window = MainWindow(application) application.protocol("WM_DELETE_WINDOW", window.quit) application.mainloop() За определением класса главного (и в данном случае – единственного) окна следует программный код, который запускает программу. Снача ла создается объект, представляющий само приложение. Чтобы снаб дить программу ярлыком, в операционной системе Windows использу ется файл .ico, имя которого (и полный путь) передается методу icon bitmap() . Но в системах UNIX следует передавать файл с растровым изображением (то есть монохромное изображение). В библиотеке Tk имеется несколько встроенных растровых изображений, поэтому, что Программы с главным окном 563 бы обозначить, что изображение берется из файла в файловой системе, имя файла с изображением необходимо предварять символом @. Затем мы присваиваем заголовок приложению (он будет отображаться в по лосе заголовка окна) и создаем экземпляр класса MainWindow, которому в качестве родительского виджета передается ссылка на объект прило жения. В заключение вызывается метод protocol(), чтобы определить, какое действие выполнять, когда пользователь щелкнет на кнопке за крытия окна. В данном случае мы определили, что должен вызываться метод MainWindow.quit(). И в самом конце запускается цикл обработки событий – только когда мы достигнем этой точки, окно появится на эк ране и будет способно откликаться на действия пользователя. Программы с главным окном Программ в виде диалогов нередко бывает вполне достаточно для реше ния простых задач, но с ростом функциональных возможностей про грамм часто имеет смысл создавать полноценные приложения с глав ным окном, содержащим полосу меню и панели инструментов. Такие приложения обычно легче поддаются расширению по сравнению с про граммами в виде диалогов, потому что мы легко можем добавлять но вые пункты меню и инструментальные кнопки, не вызывая измене ний в размещении виджетов в главном окне. В этом разделе мы рассмотрим программу bookmarkstk.py, главное ок но которой изображено на рис. 13.4. Программа предназначена для ве дения списка закладок, каждая из которых представлена парой строк (имя, URL), и дает пользователям возможность добавлять, редактиро вать и удалять закладки, а также открывать их в вебброузере. Рис. 13.4. Программа Bookmarks 564 Глава 13. Введение в программирование графического интерфейса Программа имеет два окна: главное окно – с полосой меню, панелью инструментов, списком закладок и полосой состояния, и окно диало га – для добавления и редактирования закладок. Создание главного окна Главное окно напоминает окно диалога в том смысле, что оно также со держит виджеты, которые необходимо создать и разместить. Но, кроме того, нам потребуется создать полосу меню, пункты меню, панель ин струментов и полосу состояния, а также методы, выполняющие дейст вия по запросам пользователя. Пользовательский интерфейс целиком создается в методе инициализации главного окна, который мы рас смотрим, разбив его на пять частей, так как он достаточно длинный. class MainWindow: def __init__(self, parent): self.parent = parent self.filename = None self.dirty = False self.data = {} menubar = tkinter.Menu(self.parent) self.parent["menu"] = menubar В этом примере, вместо того чтобы наследовать стандартный виджет, как это было сделано в предыдущем примере, мы создадим обычный класс. В случае наследования мы можем переопределять методы на следуемого класса, но когда в этом нет необходимости, мы можем про сто использовать прием композиции, как сделано в данном примере. Визуальное представление приложения создается за счет создания пе ременных экземпляра, содержащих виджеты, которые помещаются внутрь виджета tkinter.Frame, как будет показано чуть ниже. Нам необходимо сохранить следующие данные: ссылку на родитель ский объект (приложение), имя текущего файла с закладками, флаг на личия изменений (если имеет значение True, это свидетельствует о том, что в данных имеются изменения, которые не были сохранены на диск) и сами закладки – словарь, ключами которого служат имена за кладок, а значениями – адреса URL. Чтобы создать полосу меню, необходимо создать объект tkinter.Menu, родителем которого является объектродитель окна, и сообщить роди телю, что у него имеется меню. (Может показаться странным, что поло са меню является пунктом меню, но библиотека Tk прошла очень длин ный путь развития, и в ней еще остались некоторые пережитки про шлого.) Полосы меню, создаваемые таким способом, не требуют специ ального размещения – библиотека Tk сделает это самостоятельно. fileMenu = tkinter.Menu(menubar) for label, command, shortcut_text, shortcut in ( Программы с главным окном 565 ("New...", self.fileNew, "Ctrl+N", " ("Open...", self.fileOpen, "Ctrl+O", " ("Save", self.fileSave, "Ctrl+S", " (None, None, None, None), ("Quit", self.fileQuit, "Ctrl+Q", " if label is None: fileMenu.add_separator() else: fileMenu.add_command(label=label, underline=0, command=command, accelerator=shortcut_text) self.parent.bind(shortcut, command) menubar.add_cascade(label="File", menu=fileMenu, underline=0) Таким способом создаются все полосы меню. Сначала создается объект tkinter.Menu , который является дочерним по отношению к полосе ме ню, а затем в меню добавляются команды и разделители. (Обратите внимание, что акселератором в терминологии библиотеки Tk называ ется горячая комбинация клавиш и что в параметре accelerator задает ся текст, обозначающий комбинацию клавиш, а вовсе не привязку клавиш.) Параметр underline определяет, какой символ должен выво диться с подчеркиванием; в данном случае с подчеркиванием будут выводиться первые символы всех пунктов меню, и они же будут иг рать роль акселераторов. При добавлении пункта меню (названного командой) мы также определяем горячую комбинацию клавиш, при вязывая ее к той же команде, которая будет выполняться при выборе соответствующего пункта меню. В самом конце меню добавляется в полосу меню вызовом метода add_cascade(). Мы опустили определение меню Edit, так как программный код, соз дающий его, по своей структуре идентичен программному коду, соз дающему меню File. frame = tkinter.Frame(self.parent) self.toolbar_images = [] toolbar = tkinter.Frame(frame) for image, command in ( ("images/filenew.gif", self.fileNew), ("images/fileopen.gif", self.fileOpen), ("images/filesave.gif", self.fileSave), ("images/editadd.gif", self.editAdd), ("images/editedit.gif", self.editEdit), ("images/editdelete.gif", self.editDelete), ("images/editshowwebpage.gif", self.editShowWebPage)): image = os.path.join(os.path.dirname(__file__), image) try: image = tkinter.PhotoImage(file=image) self.toolbar_images.append(image) button = tkinter.Button(toolbar, image=image, command=command) button.grid(row=0, column=len(self.toolbar_images) 1) except tkinter.TclError as err: 566 Глава 13. Введение в программирование графического интерфейса print(err) toolbar.grid(row=0, column=0, columnspan=2, sticky=tkinter.NW) Затем создается рабочее пространство, в котором будут размещаться все виджеты окна. Затем создается еще одна область, toolbar, содержащая горизонтальную полосу с кнопками с изображениями вместо текстовых надписей, которые будут играть роль инструментальных кнопок. Все кнопки располагаются друг за другом в ячейках сетки, содержащей од ну строку и столько столбцов, сколько требуется кнопок. В конце про странство с панелью инструментов помещается в первую строку сетки рабочего пространства окна, с выравниванием по северозападу, благо даря чему панель всегда будет находиться в верхнем левом углу окна. (Библиотека Tk автоматически помещает полосу меню выше всех вид жетов окна.) Схема расположения виджетов в главном окне показана на рис. 13.5, где полоса меню, размещаемая самой библиотекой Tk, выделена белым цветом, а виджеты, размещаемые нами, – серым. Когда изображение добавляется в кнопку, оно добавляется как слабая ссылка, поэтому сразу после выхода из области видимости эти изобра жения будут запланированы к утилизации. Нам следует избежать это го эффекта, потому что нам необходимо, чтобы изображения остава лись на кнопках после выхода из метода инициализации, поэтому мы создаем переменную экземпляра self.toolbar_images, которая будет хранить ссылки на изображения, предохраняя их от исчезновения на протяжении всего времени жизни программы. По умолчанию библиотека Tk имеет возможность читать файлы изо бражений только определенных форматов, поэтому мы вынуждены ис пользовать изображения в формате .gif. 1 Если какойто файл с изобра жением не будет найден, будет возбуждено исключение tkinter.Tcl Error , которое необходимо перехватить и обработать, чтобы избежать завершения программы изза нехватки какогонибудь изображения. 1 Если для библиотеки Tk установлено расширение Python Imaging Library, появляется возможность использовать все современные форматы графи ческих изображений. Подробности можно найти на сайте www.pythonwa re.com/products/pil/ menubar t o o l b a r self.listBox scrollbar self.statusbar |