После первого вашего примера, вы должны иметь базовые идеи о том, как Tk программа может выглядеть и какие типы заданий нужно осуществлять. Мы шагнем назад и рассмотрим три общие концепции, которые вам нужно знать для понимания TK: виджеты, управление геометрией, и обработка событий.
Оконная иерархия примера метрического конвертера
Виджеты
Виджетами являются все, что вы видите на экране. В нашем примере у нас была кнопка, текстовое поле, несколько лэйблов, и фрейм. Другие являются такими штуками, как чекбоксы, древовидные просмотрщики, полосы прокрутки, текстовые пол, и т. д. Виджеты - являются тем, на что часто ссыляются как на "элементы управления"; вы также часто видели, что на них ссылаются как на "окна", в частности, в документации "Tk", идущая из корней X11 (под этой терминологией понимаются, как и окно верхнего уровня, так и такие вещи как кнопка, и они будут называться окнами).
Вот небольшой пример, показывающий некоторые Tk виджеты, которые мы охватим в ближайшее время.
Классы виджетов
Виджеты являются объектами, экземплярами классов, олицетворяющих кнопки, фреймы, и т. д. Итак, первая вещь, которую вам нужно сделать - распознать специфичный класс для виджета, который вы собирайтесь экземплировать. Это руководство и widget roundup помогут вам с этим.
Оконная иерархия
Другую вещь, которую вам нужно знать, это то, что нужно создать экземпляр родительского виджета. В Tk, все виджеты являются частью оконной иерархии, с одиночным корнем (root) на вершине иерархии. Эта иерархия может быть произвольно глубокой; итак, вы можете иметь ((кнопку во фрейме) в другом фрейме) и вместе с корневым окном.
Даже, если новое окно верхнего уровня является частью той же иерархии, с этим и всем остальным контентом формируется субдерево, которое содержится в полной иерархии. (То есть будет дерево внутри дерева).
Даже, если новое окно верхнего уровня является частью той же иерархии, с этим и всем остальным контентом формируется субдерево, которое содержится в полной иерархии. (То есть будет дерево внутри дерева).
In В нашем примере метрического конвертера, мы имеем простой фрейм, который был создан как потомок корневого окна, и этот фрейм содержал все остальные элементы управления как потомки. Корневое окно было контейнером для фрейма, и было поэтому родителем фрейма. Полная иерархия для примера выглядит примерно так:
Оконная иерархия примера метрического конвертера
Creating and Using Widgets
In Tcl, each widget is given an explicit pathname, which both differentiates it from other widgets, and also indicates its place in the window hierarchy. The root of the hierarchy, the toplevel widget that Tk automatically creates, is named simply "." (dot).
The frame, which was a child of the root, was named ".c". We could have put pretty much anything in place of the "c", naming it for example ".content". This name is purely for use by your program, so it's best to choose something meaningful. The controls that were children of the frame were given names like".c.feet", ".c.meters", ".c.flbl", and so on. If there were any widgets at a deeper level of the hierarchy, we'd add another "." and then a unique identifier.
So to create a widget, we need to provide the widget class, and the pathname. The pathname is used to indicate the widget's parent (which must of course exist also), and hence its position in the window hierarchy. For example:
This also creates a new object command with the same name as the widget's pathname, which will let us communicate with the widget. So the above code would produce new Tcl commands named ".b", ".f",".f.entry", and so on. You can then use that object command to communicate further with the widget, calling e.g. ".b invoke", or ".f.entry state disabled". Because of the obvious parallels with many object-oriented systems, we'll often refer to the object commands as objects, and calls on those objects (like the "invoke") as method calls. For example, you'll see below the use of the "configure" and "cget"methods.
Each separate widget is a Ruby object. When creating a widget, you must pass its parent as a parameter to the widget class' "new" method. The only exception is the "root" window, which is the toplevel window that will contain everything else. That is instantiated from the TkRoot class, and it does not have a parent. For example:
Whether or not you save the widget object in a variable is entirely up to you, and depends of course whether you'll need to refer to it later. Because the object is inserted into the widget hierarchy, it won't be garbage collected even if you don't keep your own reference to it.
If you snuck a peak at how Tcl manages widgets, you'll see each widget has a specific pathname; you'll also see this pathname referred to in Tk reference documentation. RubyTk chooses and manages all these pathnames for you behind the scenes, so you should never have to worry about them. If you do though, you can get the pathname from a widget with its "path" method.
In Perl, each widget is given an explicit pathname, which both differentiates it from other widgets, and also indicates its place in the window hierarchy. The root of the hierarchy, the toplevel widget that Tk automatically creates, is named simply "." (dot).
The frame, which was a child of the root, was named ".c". We could have put pretty much anything in place of the "c", naming it for example ".content". This name is purely for use by your program, so it's best to choose something meaningful. The controls that were children of the frame were given names like".c.feet", ".c.meters", ".c.flbl", and so on. If there were any widgets at a deeper level of the hierarchy, we'd add another "." and then a unique identifier.
So to create a widget, we need to provide the widget class, and the pathname. The pathname is used to indicate the widget's parent (which must of course exist also), and hence its position in the window hierarchy. For example:
Widget Objects
Many widgets have operations that you can call on them, such as to disable a widget, invoke a button's command, and so on. If we were doing this in Tcl, we'd write something like ".b invoke" or ".f.entry state disabled". Essentially you'd think of ".b" or ".f.entry" as objects, and "invoke" and "state" as methods, the latter having a single parameter "disabled". In Perl, you can call the exact Tcl command like this:
Admittedly, that's pretty lame. Ideally, we'd want to have these widgets behave just like Perl objects. Luckily, Tkx lets us do exactly that.
The first thing we need to do is get a reference to an object for these widgets. We can do that like this, passing the widget path name to "Tkx::widget->new":
Then we can invoke methods on these objects just how you'd expect:
$b->invoke; $e->state("disabled");
Creating each widget and then getting a reference to it as a separate step is a bit much. So there's another way to create widgets. If you have an object reference to the parent widget in the widget hierarchy, you can ask it to create a child.
In this case, Tkx will choose the widget pathname for you (it'll look something like ".b", ".f.e", ".f.e2", etc.).
You can get this path by calling the object's "_mpath" method (e.g. "$b->_mpath"). If you stringify the object itself (e.g. trying to print it out), it will also be the widget pathname.
You also saw in our earlier example that some Tkx commands (like "grid") take the widget pathname as their first argument. There's a way to do that using the widget object instead. We invoke a method which is the name of the command, prefixed by "g_", e.g. "g_grid". So this:
becomes this:
Translation Rules
What's somewhat scary about the Tkx module is that it implements all of this on a purely syntactic level. That is, it has no clue about buttons and entries and grid. It just knows that if it sees a method "new_something" it's creating a widget using the Tcl command "something", or if you call a method "g_otherthing", that it's going to invoke a Tcl command "otherthing", and pass the widget pathname associated with the object as a first parameter (followed by any other parameters passed to the method).
It's this pure syntactic mapping which gives Tkx its power and amazing brevity (check out the code in Tkx.pm!). It also explains why it can automatically track any changes in Tk. Any new commands and options don't need to be explicitly implemented in Tkx code; they are automatically available just by using the right syntax.
Now let's talk about those underscores in the Tkx commands and method names. If you've glanced at the Tcl version of the code in this tutorial, you know that they can take several forms, i.e.
- a single toplevel command, like "grid"
- a multi-word command (called an ensemble in Tcl), e.g. "wm title"
- a command in a namespace, e.g. "ttk::button"
- a single toplevel command with underscores, e.g. "tk_messageBox"
No translations are needed for simple commands like "grid", but the others need to be tweaked. To translate these into Perl, Tkx uses underscores in the following way:
- a single underscore is replaced with a space, e.g. "wm_title" in Perl becomes "wm title" in Tcl
- two underscores is replaced with the namespace qualifier "::", e.g. "ttk__button" becomes"ttk::button"
- three underscores is replaced with a single underscore, e.g. "tk___messageBox" becomes"tk_messageBox"
Given that this purely syntactic translation is all that Tkx does, let's state the obvious: whether or not you use the pure command form, which we used in the "feet to meters" example, or the object oriented form, they both do exactly the same thing (i.e. invoke the same underlying Tcl command).
Because you can get an object's widget pathname, or get an object reference from a widget pathname, you can also freely intermix them. Most programs of any length will probably be a bit simpler if they predominately use the object oriented form, but this is almost entirely a stylistic issue.
Feet to Meters, Object Style
Here's the earlier "feet to meters" example, this time rewritten in the object oriented style. The programs do exactly the same thing.
Каждый отдельный виджет является Python объектом. Когда создается виджет, вы обязаны передать родителя как параметр в создающую функцию. Единственным исключением является "корневое" окно, которое является самым верхним окном, которое будет содержать все остальное. Оно автоматически создается, и не имеет родителя. Например:
Сохранение виджета в переменной полностю зависит от вас, и, разумеется, зависит от того, будете ли вы ссылаться на этот виджет позже. Так как объект включен в иерархию виджетов, он не будет собран сборщиком мусора, даже если сами вы на него не ссылаетесь.
If you snuck a peak at how Tcl manages widgets, you'll see each widget has a specific pathname; you'll also see this pathname referred to in Tk reference documentation. Tkinter chooses and manages all these pathnames for you behind the scenes, so you should never have to worry about them. If you do though, you can get the pathname from a widget by calling "str(widget)".
Параметры конфигурации
Все виджеты также имеют несколько различных параметров конфигурации, которые обычно контролируют, как виджеты будут отображены и как они будут себя вести.
Доступные опции, зависят, напрямую зависят от класса виджета. Здесь много соглашений, меджу различными классами виджетов, так что параметры, которые делают практически одинаковые вещи, имеют тенденцию именоваться одинаково. Таким образом, и кнопка и лэйбл, имеют параметр "text" для регулировки текста, который они отображают, в то время ка полоса прокрутки не имеет параметра "text", так как он не нужен. Тем же макаром, кнопка имеет параметр "command", который говорит ей что делать при нажатии, в то время как лэйбл, который просто содержит статический текст, не имеет такового.
Параметры конфигурации могут быть указаны при первом создании виджета, путем передачи через имена и значения параметров, как опциональных параметров. Вы можете позже проверить, какое значение имеет конкретный параметр, и только вы исключительных случаях вы не сможете изменить его в любое время. Если вы не уверены, какие параметры у виджета, вы можете спросить об этом сам виджет. Он даст вам длинный список всех его параметров, и дл каждого параметра, вы можете увидеть имя параметра и его текущее значение (наряду с тремя другими атрибутами, о которых вы не должны обычно беспокоиться)
Это лучшая иллюстрация интерактивного диалога с интерпретатором.
Управление геометрией
Если вы игрались с виджетами, вы, наверное, заметили, что только создание их не завершает появление их на экране. Владение вещами, уже расположенными на экране, и точным их местоположением в окне - отдельный этап, называемый управлением геометрией.
В нашем примере, это позиционирование выполнялось с помощью команды "grid"("сетка"), куда мы передавали наряду со строкой и колонкой каждый виджет, который нужно было в них разместить, как вещи выравнивались по отношению к сетке, и т. п. Сетка (Grid) является примером менеджера геометрии (менеджера компоновки) (из присутствующих в Tk, сетка является наиболее полезной). Мы поговорим о сетке и ее деталях в следующей главе, но но сейчас мы посмотрим на менеджеры геометрии в целом.
Работа менеджера геометрии - выяснять точно, где какие виджеты собираются расположится. Это оказывается очень сложной проблемой оптимизации, и хороший менеджер геометрии опирается на довольно сложные алгоритмы. Хороший менеджер компоновки предоставляет гибкость, мощность и легкость в использовании, которая делает программистов счастливыми, и сетка Tk без сомнения является наилучшей. Плохой менеджер геометрии....эмм, все Java программисты, которые страдали от "GridBagLayout", поднимите руки, пожалуйста.
Проблема
The Проблемой менеджера геометрии является взятие разных виджетов, созданных программой, плюс инструкций, где они хотелись бы разместиться в окне программы (явно, или более часто, по отношению к другим виджетам), и затем действительно разместить их в окне.
В процессе этого, менеджер должен балансировать набором различных ограничений:
- Виджет должен иметь естественный размер (т. е. естественный размер лэбла будет обычно определятся его текстом и шрифтом текста), но
- The widgets may have a "natural" size (e.g. the natural width of a label would normally be determined by the text and font in it), but the toplevel all these different widgets are trying to fit into isn't big enough to accommodate them; the geometry manager must decide which widgets to shrink to fit, by how much, etc.
- If the toplevel window is bigger than the natural size of all the widgets, how is the extra space used? Is it just used for extra space between widgets, and if so, how is that space distributed? Is it used to make certain widgets bigger than they normally want to be?
- If the toplevel window is resized, how does the size and position of the widgets in it change? Will certain areas (e.g. a text entry area) expand or shrink, while other parts stay the same size, or is the area distributed differently? Do certain widgets have a minimum (or maximum) size that you want to avoid going under (over)?
- How can widgets in different parts of the user interface be aligned with each other, to present a clean layout and match platform guidelines to do with inter-widget spacing?
- For a complex user interface, which may have many frames nested in other frames nested in the window (etc.), how can all the above be accomplished, trading off the conflicting demands of different parts of the entire user interface?
Как это работает
Geometry management in Tk relies on the concept of master and slave widgets. A master is a widget, typically a toplevel window or a frame, which will contain other widgets, which are called slaves. You can think of a geometry manager as taking control of the master widget, and deciding what will be displayed within.
The geometry manager will ask each slave widget for its natural size, or how large it would ideally like to be displayed. It then takes that information and combines it with any parameters provided by the program when it asks the geometry manager to manage that particular slave widget. In our example, we passed grid a "column" and "row" number for each widget, which indicated the relative position of the widget with respect to others, and also a "sticky" parameter to suggest how the widget should be aligned or possibly stretched. We also used "columnconfigure" and"rowconfigure" to indicate the columns and rows we'd like to have expand if there is extra space available in the window. Of course, all these parameters are specific to grid; other geometry managers would use different ones.
The geometry manager takes all the information about the slaves, as well as the information about how large the master is, and uses its internal algorithms to determine the area each slave will be allocated (if any!). The slave is then responsible for drawing etc. within that particular rectangle. And of course, any time the size of the master changes (e.g. because the toplevel window was resized), the natural size of a slave changes (e.g. because we've changed the text in a label), or any of the geometry manager parameters change (e.g. like "row", "column", or "sticky") we repeat the whole thing.
This all works recursively as well. In our example, we had a content frame inside the toplevel window, and then a number of other controls in the content frame. We therefore had a geometry manager working on two different masters. At the outer level, the toplevel window was the master, and the content frame was the slave. At the inner level, the content frame was the master, with each of the other widgets being slaves. So the same widget can be both a master and a slave. This hierarchy can of course also be nested much more deeply.
While each master can have only one geometry manager (e.g. grid), it's entirely possible for different masters to have different geometry managers; while grid is generally used, others may make sense for a particular layout used in one part of your user interface. Also, we've been making the assumption that slave widgets are the immediate children of their master in the widget hierarchy. While this is usually the case, and mostly there's no good reason to do it any other way, it's also possible (with some restrictions) to get around this.
Обработка событий
In Tk, as in most other user interface toolkits, there is an event loop which receives events from the operating system. These are things like button presses, keystrokes, mouse movement, window resizing, and so on.
Generally, Tk takes care of managing this event loop for you. It will figure out what widget the event applies to (did the user click on this button? if a key was pressed, which textbox had the focus?), and dispatch it accordingly. Individual widgets know how to respond to events, so for example a button might change color when the mouse moves over it, and revert back when the mouse leaves.
Command Callbacks
Often though you want your program to handle particular events, for example doing something when a button is pushed. For those events that are pretty much essential to customize (what good is a button without something happening when you press it?), the widget will provide a callback as a widget configuration option. We saw this in the example with the"command" option of the button.
Callbacks in Tk tend to be simpler than in toolkits used with compiled languages (where a callback must generally be directed at a procedure with a certain set of parameters or an object method with a certain signature). Instead, the callback is just a normal bit of code that the interpreter evaluates. While it can be as complex as you want to make it, most times though you'll just want your callback to call some other procedure.
Event Bindings
For events that don't have a command callback associated with them, you can use Tk's "bind" to capture any event, and then (like with callbacks) execute an arbitrary piece of code.
Here is a (silly) example that shows how a label can have bindings set up for it to respond to different events, which it does so by just changing what is displayed in the label.
Note that the bind command lives in the global namespace; there is not a ttk::bindcommand.
The first three event bindings are pretty straightforward, just looking at simple events. The double click binding introduces the idea of an event modifier; in this case we want to trigger the event on a left mouse click (the "1"), but only when it's a double click (the "Double-").
The last binding also uses a modifier: capture mouse movement ("Motion"), but only when the right mouse button ("B3") is held down. This binding also shows an example of how to use event parameters, through the use of percent substitutions. Many events, such as mouse clicks or movement have as parameters additional information like the current position of the mouse. These percent substitutions let you capture them so they can be used in your script.
Tkx lets us provide command callbacks as just a Perl function (the first four), or as a two element array (the last case). The first element is the Perl code to be called, while the second array element specifies parameters to pass to that code. The function "Tkx::Ev()"will expand its parameter ("%x %y" in this case) when the callback is invoked, which will perform the percent substitutions. These then are passed as parameters to our function.
Tkinter expects you to provide a function as the event callback, whose first argument is an event object representing the event that triggered the callback. It's usually not worth the bother of defining regular named functions for one-off callbacks such as in this example, so we've just used Python's anonymous functions via lambda. The earlier feet to meters example used a regular defined function (calculate).
For a complete description of all the different event names, modifiers, and the different event parameters that are available with each, the best place to look is the "bind" command reference.
Virtual Events
Beyond the low-level operating system events like mouse clicks and window resizes, many widgets generate higher level events called virtual events. For example, a listbox widget will generate a "ListboxSelect" virtual event anytime the selection changes, regardless of whether that was because the user clicked on an item, moved to it with the arrow keys, or whatever. This avoids the problem of setting up multiple, possibly platform-specific event bindings to capture the change. Virtual events for a widget, if any, will be listed in the widget's documentation.
Multiple Bindings
Widgets can actually have a number of different event bindings trigger for a single event. Normally, events can be set up for: the individual widget itself, all widgets of a certain class (e.g. buttons), the toplevel window containing the widget, and all widgets in the application. Each of these will fire in sequence.
We saw this in our example when we set up a binding for the Return key on the toplevel window, and that applied to every widget within that window.
The default behavior of each widget class in Tk is itself defined with script-level event bindings, and so can be introspected and modified to alter the behavior of all widgets of a certain class. You can even completely modify the handling of this multiple sequence of events for each widget; see the"bindtags" command reference if you're curious.