首页 > 编程语言 > 详细

Python Tkinter 会话窗口(转载自https://blog.csdn.net/bnanoou/article/details/38515083)

时间:2019-01-22 17:05:08      阅读:211      评论:0      收藏:0      [点我收藏+]

Dialog Windows

While the standard dialogs described in the previous section may be sufficient for many simpler applications, most larger applications require more complicated dialogs. For example, to set configuration parameters for an application, you will probably want to let the user enter more than one value or string in each dialog.

前面章节中介绍的标准会话对于大多数简单应用已经足够了,对于很多大型应用就需要更复杂的会话。

Basically, creating a dialog window is no different from creating an application window. Just use the Toplevel widget, stuff the necessary entry fields, buttons, and other widgets into it, and let the user take care of the rest. (By the way, don’t use the ApplicationWindow class for this purpose; it will only confuse your users).

基本上,创建一个会话框和创建一个应用窗口是不一样的。使用Toplevel组件,填充上输入框、按钮和其他组件,让用户负责其他的。(不要使用ApplicationWindows类,它指挥让你的用户更加困扰)

But if you implement dialogs in this way, you may end up getting both your users and yourself into trouble. The standard dialogs all returned only when the user had finished her task and closed the dialog; but if you just display another toplevel window, everything will run in parallel. If you’re not careful, the user may be able to display several copies of the same dialog, and both she and your application will be hopelessly confused.

如果使用这种方式部署会话,可能会让用户和开发者都陷入麻烦。标准的会话框会在用户结束任务并关闭会话框之后返回,但是如果仅仅展示另外一个toplevel窗口,所有的都会并行运行。一不小心就会展示几个一模一样的会话框给 用户,用户和应用都会陷入不可救药的混乱。

In many situations, it is more practical to handle dialogs in a synchronous fashion; create the dialog, display it, wait for the user to close the dialog, and then resume execution of your application. The wait_window method is exactly what we need; it enters a local event loop, and doesn’t return until the given window is destroyed (either via the destroy method, or explicitly via the window manager):

很多时候采用同步风格去使用会话框是有效可行的,创建会话、显示、等待用户关闭会话,然后重新执行应用。wait_window方法,它会进入一个内部事件循环,直到窗口销毁才会返回。(既可以通过destroy方法,也可以使用窗口管理器销毁)

widget.wait_window(window)

(Note that the method waits until the window given as an argument is destroyed; the only reason this is a method is to avoid namespace pollution).

(请注意这个方法在窗口将会一直等待,除非参数windows被赋值为destroyed, 这样做的是为了避免命名空间被污染)

In the following example, the MyDialog class creates a Toplevel widget, and adds some widgets to it. The caller then uses wait_window to wait until the dialog is closed. If the user clicks OK, the entry field’s value is printed, and the dialog is then explicitly destroyed.

在下面的例子中,MyDialog类创建了一个Toplevel组件,并添加了一些组件给它。调用者使用wait_window去等待,直到会话被关闭。如果用户点击了OK,那么输入域的值将会被打印,会话框也会被销毁。

Creating a simple dialog

from Tkinter import *
 
class MyDialog:
 
    def __init__(self, parent):
 
        top = self.top = Toplevel(parent)
 
        Label(top, text="Value").pack()
 
        self.e = Entry(top)
        self.e.pack(padx=5)
 
        b = Button(top, text="OK", command=self.ok)
        b.pack(pady=5)
 
    def ok(self):
 
        print "value is", self.e.get()
 
        self.top.destroy()
 
 
root = Tk()
Button(root, text="Hello!").pack()
root.update()
 
d = MyDialog(root)
 
root.wait_window(d.top)

 

If you run this program, you can type something into the entry field, and then click OK, after which the program terminates (note that we didn’t call the mainloop method here; the local event loop handled by wait_window was sufficient). But there are a few problems with this example:

运行这个程序,用户可以在输入框中输入些什么,然后点击OK,然后程序结束。(注意这里没有调用mainloop方法,wai_window操作的本地事件循环已经足够了)。这个例子有一些小问题:

 

  • The root window is still active. You can click on the button in the root window also when the dialog is displayed. If the dialog depends on the current application state, letting the users mess around with the application itself may be disastrous. And just being able to display multiple dialogs (or even multiple copies of one dialog) is a sure way to confuse your users.

  • 首先根窗口依旧被激活。当会话框被显示的时候依旧可以点击根窗口上的按钮。如果会话依赖于当前的应用状态,让用户去操作应用本身将会是灾难性的。能够显示多个会话(或者一个会话的多个拷贝)会让用户困惑

  • You have to explicitly click in the entry field to move the cursor into it, and also click on the OK button. Pressning Enter in the entry field is not sufficient.

  • 只有准确的点击输入框才能把光标移到其中,同样在点击OK按钮的时候也需要准确的点击。

  • There should be some controlled way to cancel the dialog (and as we learned earlier, we really should handle theWM_DELETE_WINDOW protocol too).

  • 需要一些可控的方式去取消会话(就像之前学到的,使用WM_DELETE_WINDOW)

To address the first problem, Tkinter provides a method called grab_set, which makes sure that no mouse or keyboard events are sent to the wrong window.

Tkinter 提供了grab_set方法去解决第一个问题。这个方法可以确保鼠标或者键盘事件不会被发送到错误的窗口。

The second problem consists of several parts; first, we need to explicitly move the keyboard focus to the dialog. This can be done with the focus_set method. Second, we need to bind the Enter key so it calls the ok method. This is easy, just use the bind method on the Toplevel widget (and make sure to modify the ok method to take an optional argument so it doesn’t choke on the event object).

第二个问题由几部分组成。首先需要把键盘焦点放置到会话框,focuse_set方法可以完成这件事。其次,我们需要把Enter键和OK方法映射在一起。很简单,只要在Toplevel组件上使用bind方法(修改ok方法的参数以确保它不会阻塞事件对象)

The third problem, finally, can be handled by adding an additional Cancel button which calls the destroy method, and also use bind and protocol to do the same when the user presses Escape or explicitly closes the window.

第三个问题可以通过添加一个额外的调用destroy方法的Cancel按钮来解决,当用户按下Escape或者通过窗口关闭,用bind和protocol做同样的操作。

The following Dialog class provides all this, and a few additional tricks. To implement your own dialogs, simply inherit from this class and override the body and apply methods. The former should create the dialog body, the latter is called when the user clicks OK.

下面的Dialog类提供了所有的方法和一些技巧。直接从这个继承这个类并重载body和apply方法就可以部署自己的会话框。

A dialog support class (File: tkSimpleDialog.py)

 

from Tkinter import *
import os
 
class Dialog(Toplevel):
 
    def __init__(self, parent, title = None):
 
        Toplevel.__init__(self, parent)
        self.transient(parent)
 
        if title:
            self.title(title)
 
        self.parent = parent
 
        self.result = None
 
        body = Frame(self)
        self.initial_focus = self.body(body)
        body.pack(padx=5, pady=5)
 
        self.buttonbox()
 
        self.grab_set()
 
        if not self.initial_focus:
            self.initial_focus = self
 
        self.protocol("WM_DELETE_WINDOW", self.cancel)
 
        self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
                                  parent.winfo_rooty()+50))
 
        self.initial_focus.focus_set()
 
        self.wait_window(self)
 
    #
    # construction hooks
 
    def body(self, master):
        # create dialog body.  return widget that should have
        # initial focus.  this method should be overridden
 
        pass
 
    def buttonbox(self):
        # add standard button box. override if you dont want the
        # standard buttons
 
        box = Frame(self)
 
        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="Cancel", width=10, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)
 
        self.bind("<Return>", self.ok)
        self.bind("<Escape>", self.cancel)
 
        box.pack()
 
    #
    # standard button semantics
 
    def ok(self, event=None):
 
        if not self.validate():
            self.initial_focus.focus_set() # put focus back
            return
 
        self.withdraw()
        self.update_idletasks()
 
        self.apply()
 
        self.cancel()
 
    def cancel(self, event=None):
 
        # put focus back to the parent window
        self.parent.focus_set()
        self.destroy()
 
    #
    # command hooks
 
    def validate(self):
 
        return 1 # override
 
    def apply(self):
 
        pass # override

 

The main trickery is done in the constructor; first, transient is used to associate this window with a parent window (usually the application window from which the dialog was launched). The dialog won’t show up as an icon in the window manager (it won’t appear in the task bar under Windows, for example), and if you iconify the parent window, the dialog will be hidden as well. Next, the constructor creates the dialog body, and then calls grab_set to make the dialog modal, geometry to position the dialog relative to the parent window, focus_set to move the keyboard focus to the appropriate widget (usually the widget returned by the body method), and finally wait_window.

首先transient把当前窗口和一个父窗口关联起来(通常是会话框发起的应用窗口)。在窗口管理器中会话框不是被显示为一个图标,如果图标化父窗口,那么会话框也会被隐藏。构造器创建了会话框主体,然后调用grab_set方法生成可视化的会话框。focus_set方法把键盘焦点定位到合适的组件(通常是body方法返回的那个组件)最好是wait_window方法。

 

Note that we use the protocol method to make sure an explicit close is treated as a cancel, and in the buttonbox method, we bind the Enter key to OK, and Escape to Cancel. The default=ACTIVE call marks the OK button as a default button in a platform specific way.

注意我们使用protocol方法去缺乏一个精准关闭动作被当成取消动作,在buttonbox方法中,Entry键和Ok绑定在一起,ESCape和Cancel绑定在一起。default=Active表示OK按钮是默认按钮。

 

Using this class is much easier than figuring out how it’s implemented; just create the necessary widgets in the body method, and extract the result and carry out whatever you wish to do in the apply method. Here’s a simple example (we’ll take a closer look at the grid method in a moment).

使用这个类比理解它如何部署要容易得多。在body方法中创建必须的组件,在apply方法中提取结果和执行想做的。这里有一个简单例子(近距离研究一下grid方法)

Creating a simple dialog, revisited

 

import tkSimpleDialog
 
class MyDialog(tkSimpleDialog.Dialog):
 
    def body(self, master):
 
        Label(master, text="First:").grid(row=0)
        Label(master, text="Second:").grid(row=1)
 
        self.e1 = Entry(master)
        self.e2 = Entry(master)
 
        self.e1.grid(row=0, column=1)
        self.e2.grid(row=1, column=1)
        return self.e1 # initial focus
 
    def apply(self):
        first = int(self.e1.get())
        second = int(self.e2.get())
        print first, second # or something

 

And here’s the resulting dialog:
Running the dialog2.py script

技术分享图片

Note that the body method may optionally return a widget that should receive focus when the dialog is displayed. If this is not relevant for your dialog, simply return None (or omit the return statement).

请注意body方法可以得到一个得到焦点的组件。如果会话框不需要这一点,只要返回None即可(或者省略掉返回状态)。

The above example did the actual processing in the apply method (okay, a more realistic example should probably to something with the result, rather than just printing it). But instead of doing the processing in the apply method, you can store the entered data in an instance attribute:

上面的示例使用apply方法完成了流程(一个更加生动的例子应该对结果做一些处理,而不是简单的打印出来)。不用apply方法完成流程,可以使用一个实例属性存储输入数据:

 

  ...
 
    def apply(self):
        first = int(self.e1.get())
        second = int(self.e2.get())
        self.result = first, second
 
d = MyDialog(root)
print d.result

 

Note that if the dialog is cancelled, the apply method is never called, and the result attribute is never set. The Dialog constructor sets this attribute to None, so you can simply test the result before doing any processing of it. If you wish to return data in other attributes, make sure to initialize them in the body method (or simply set result to 1 in the apply method, and test it before accessing the other attributes).

如果会话框被取消,那么apply方法将不再会被调用,result属性也不会被设置。Dialog构造器会把这个属性设置为None,所以你可以在对它做任何处理之前简单的测试结果。如果你希望在其他属性里返回数值,请确保在body方法中对它们做了初始化(在apply方法中把result设置为1,并在访问其他属性前对它做测试)。

Grid Layouts

While the pack manager was convenient to use when we designed application windows, it may not be that easy to use for dialogs. A typical dialog may include a number of entry fields and check boxes, with corresponding labels that should be properly aligned. Consider the following simple example:

在设计应用窗口的时候pack管理器是很有效的,但对于设计会话框来说不一定好用。一个典型的会话框可能包含一些排成一排的一致的输入框或者复选框。参见下面这个简单例子:

Simple Dialog Layout

技术分享图片

To implement this using the pack manager, we could create a frame to hold the label “first:”, and the corresponding entry field, and use side=LEFT when packing them. Add a corresponding frame for the next line, and pack the frames and the check button into an outer frame using side=TOP. Unfortunately, packing the labels in this fashion makes it impossible to get the entry fields lined up, and if we use side=RIGHT to pack the entry field instead, things break down if the entry fields have different width. By carefully using width options, padding, side and anchor packer options, etc., we can get reasonable results with some effort. But there’s a much easier way: use the grid manager instead.

想要使用pack管理器去实现这个会话框,需要先创建一个frame去实习标签“first”和同一排的输入框,在使用它们的时候设置side=LEFT。 为第二行添加一个一样的frame,使用side = TOP 把标签们和复选框pack到一个外部frame里面。很不幸,以这样的方式pack这些标签可能会导致输入框排成一排,如果使用side=RIGHT去整合输入框,假如这些输入框的宽度不一样的话会导致对不齐。使用width选项,padding、side和anchor packer选项的时候都要小心,需要付出一些努力才能得到想要的结果。但是这里有更加简便的办法:使用grid管理器。

This manager splits the master widget (typically a frame) into a 2-dimensional grid, or table. For each widget, you only have to specify where in this grid it should appear, and the grid managers takes care of the rest. The following body method shows how to get the above layout:

这个管理器把主组件(通常是一个frame)分隔成二维的坐标或表格。对于每一个组件,仅需要指定它应该出现在这个坐标的什么位置,坐标管理器会处理其他的。下面的body方法展示了如何获得上面的布局。

Using the grid geometry maanager

 

def body(self, master):
 
    Label(master, text="First:").grid(row=0, sticky=W)
    Label(master, text="Second:").grid(row=1, sticky=W)
 
    self.e1 = Entry(master)
    self.e2 = Entry(master)
 
    self.e1.grid(row=0, column=1)
    self.e2.grid(row=1, column=1)
 
    self.cb = Checkbutton(master, text="Hardcopy")
    self.cb.grid(row=2, columnspan=2, sticky=W)

 

For each widget that should be handled by the grid manager, you call the grid method with the row and column options, telling the manager where to put the widget. The topmost row, and the leftmost column, is numbered 0 (this is also the default). Here, the check button is placed beneath the label and entry widgets, and the columnspan option is used to make it occupy more than one cell. Here’s the result:

对于每一个坐标管理器要操作的组件,使用grid方法的row和column选项,告诉管理器把组件放置到什么位置。最上面的那行的最左边一列编号为0,复选框被放到标签和输入框的下方,使用columnspan选项可以让它占据一个以上的单元格。这里是结果:

Using the grid manager

技术分享图片

If you look carefully, you’ll notice a small difference between this dialog, and the dialog shown by the dialog2.py script. Here, the labels are aligned to the left margin. If you compare the code, you’ll find that the only difference is an option called sticky.

仔细看,会发现这个会话框和dialog2.py展示的会话框有一点小小的不同。这里标签是靠左对齐。对比一下代码,会发现唯一的不同就是sticky选项。

When its time to display the frame widget, the grid geometry manager loops over all widgets, calculating a suitable width for each row, and a suitable height for each column. For any widget where the resulting cell turns out to be larger than the widget, the widget is centered by default. The sticky option is used to modify this behavior. By setting it to one of E, W, S, N,NW, NE, SE, or SW, you can align the widget to any side or corner of the cell. But you can also use this option to stretch the widget if necessary; if you set the option to E+W, the widget will be stretched to occupy the full width of the cell. And if you set it to E+W+N+S (or NW+SE, etc), the widget will be stretched in both directions. In practice, the sticky option replaces the fill, expand, and anchor options used by the pack manager.

当展示frame组件的时候,坐标管理器会在所有组件上循环,为每一行计算出合适的宽度,为每一列计算出合适的高度。对于任意组件,如果单元格比组件大,那么默认会被居住放置。sticky选项就是用来修改这个行为的。通过把它设置为E,W,S,N,NW,NE,SE,SW中的任意一个,就可以靠边或者居中放置组件。如果需要的话也可以使用这个选项去平铺这个组件。如果把这个选项设置为E+W,组件会被拉伸并填充到单元格的宽度。如果设置成E+W+N+S(或者NW+SE等)组件会在所有的方向被拉伸。实际使用时pack管理器会使用fill,expand和anchor选项来代替。

The grid manager provides many other options allowing you to tune the look and behavior of the resulting layout. These include padx and pady which are used to add extra padding to widget cells, and many others. See the Grid Geometry Manager chapter for details.

坐标管理器提供了一些其他的选项去调整布局的外观和行为。包括用来调整组件单元格填充的padx和pady,其他还有很多。详细可以查询Grid Geometry Manager 章节。

Validating Data

What if the user types bogus data into the dialog? In our current example, the apply method will raise an exception if the contents of an entry field is not an integer. We could of course handle this with a try/except and a standard message box:

如果用户在会话框中输入非法数据会怎样?在最近的例子中,如果输入框中得到的不是整数,apply方法都会抛出异常。当然可以使用try/except和一个标准的消息框来操作。

 

...
 
def apply(self):
    try:
        first = int(self.e1.get())
        second = int(self.e2.get())
        dosomething((first, second))
    except ValueError:
        tkMessageBox.showwarning(
            "Bad input",
            "Illegal values, please try again"
        )

 

There’s a problem with this solution: the ok method has already removed the dialog from the screen when the apply method is called, and it will destroy it as soon as we return. This design is intentional; if we carry out some potentially lengthy processing in the apply method, it would be very confusing if the dialog wasn’t removed before we finished. The Dialog class already contain hooks for another solution: a separate validate method which is called before the dialog is removed.

这种方案会有一个问题:当调用apply方法的时候,ok方法已经从屏幕上移除和销毁了会话框。这是有意设计的;Dialog类已经提供了另外一种解决方案:一个独立的validae方法,这个方法会在会话框被移除前调用。

In the following example, we simply moved the code from apply to validate, and changed it to store the result in an instance attribute. This is then used in the apply method to carry out the work.

在下面的例子中,在代码中用validate取代apply

 

...
 
   def validate(self):
       try:
           first= int(self.e1.get())
           second = int(self.e2.get())
           self.result = first, second
           return 1
       except ValueError:
           tkMessageBox.showwarning(
               "Bad input",
               "Illegal values, please try again"
           )
           return 0
 
   def apply(self):
       dosomething(self.result)

 

Note that if we left the processing to the calling program (as shown above), we don’t even have to implement the apply method.

Python Tkinter 会话窗口(转载自https://blog.csdn.net/bnanoou/article/details/38515083)

原文:https://www.cnblogs.com/shiyongge/p/10304569.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!