从本节开始,我们将开始新的学习和分享:360百科:GUI编程。前面写过的程序都是基于控制台的,即用户与程序的交互通过Console来完成的

图形用户接口编程

  • GUI的简介
  • Python常见的GUI库
  • 第一个GUI程序
  • GUI编程的整体描述
  • 常见组件汇总及说明
    • Lable
    • Options
    • Button
    • Entry
    • Text
  • GUI面向对象的应用程序开发(模板)

GUI的简介

所谓GUI(图形用户接口)是相较于命令行(DOS UNIX系列)而言的:和计算机使用的命令行界面相比,图形界面对于用户来说在视觉上更易于接受,展示效果也更加美观。

这种图形化交互界面在实现上类似于搭积木(or 拼图)游戏,一个个的组件(Widget)被组合到一起 放置到一个窗口(Window)里面,进行渲染呈现。(对于Python而言,丰富的组件足以支撑快速的实现 图形界面和用户交互

我们大家如果学过JSP 或者 ASP等,这些组件相当于页面的button text list等。然后加上对于组件上发生事件的处理响应就构成了一个完整的程序。

Python常见的GUI库

  • Tkinter(Tk interface):是Python的标准GUI库,支持扩平台的GUI程序开发。优点:适于简易图形用户界面的快速开发
  • wxPython:比Tkinter更加流行和功能更强大的GUI库,类似于Windows编程的Microsoft的MFC框架。优点:适合于大型应用程序的开发
  • PyQT:谈及GUI编程,我们大家都会想到QT。它是开源的GUI库,同样适用于大型图形用户接口编程。而这里的PyQT就是QT工具包标准的Python实现。当然,我们就可以直接使用QT Designer来设计和实现基于QT的GUI程序开发

OK,我们这里主要是学习Tkinter库,其中文文档在线阅读:Tk图形用户界面(GUI)和An Introduction to Tkinter

第一个GUI程序


注:上面的root.mainloop() 是主窗口的mainloop事件循环。负责监听用户的触发事件的发生,然后在发生事件后调用相应的事件处理,最后反复监测(死循环相当于)。

但是上面仅仅是一个空的主窗口,OK 下面我们来加上一些组件:

但是上面程序运行之后,弹出来的主窗口位置在于左上角。我们下面就借助于geometry('wxh±x±y')来设置主窗口的位置:

w 是宽度,h是高度
+x 表示距屏幕左边的距离,-x表示距屏幕右边的距离
+y 表示距屏幕上边的距离,-y表示距屏幕下边的距离

示例如下:

OK,这就是GUI编程的一个简单实例,下面我们来详细看一下GUI编程的整体描述:

GUI编程的整体描述

上面也说过了 图形用户界面的设计和 “拼图游戏” 差不多,相互集成在主窗口里面的诸多组件共同组成了整个用户界面。(若是一个组件里面还可以放置组件,则其被称为容器)下面是Tkinter的GUI组件继承关系图:

1、上面的Wm:主要是用作和窗口管理器间的通信
2、Misc:所有组件的根父类
3、TopLevel:最顶层的一个弹框(不搞定它,别的都动不了)
4、Pack、Place和Grid:布局管理器(管理组件 大小 位置),合理的排布组件
5、Tk:应用程序的主窗口(一般应用程序都直接or间接使用Tk)
6、BaseWidget:所有组件的父类
7、Widget:所有组件类的父类,它继承自四个父类。所有GUI组件都有这四个父类的属性和方法
8、Frame:它是Tkinter组件的一个框架组件,表示一个虚拟长方形的区域(可以看做是一个容器,从而实现复杂的布局)。

OK,如果我们大家想看一下 类的继承层次结构,可以在类的定义处,右键-->Diagram-->show Diagram

常见组件汇总及说明


OK,上面有我们的皇族坐镇,下面开始各个组件的详细说明:

Lable

Lable 即标签,主要用于显示静态信息 如:文本信息(也可以显示图像)。其常见属性如下:

1、width 和 height:用于指定区域大小。如果显示是文本,则以单个英文字符大小为单位(一个汉字宽度占 2个字符位置,高度和英文字符一样);如果显示是图像,则以像素为单位。默认值是根据具体显示的内容动态调整。
2、font:指定字体和字体大小,如:font = (font_name,size)
3、image:显示在 Label 上的图像,目前 tkinter 只支持 .gif 格式。
4、fg(foreground)和 bg(background):前景色 背景色
5、justify:针对多行文字的对齐,可设置 justify 属性,可选值"left"、"center" 和 "right"

下面来看一下,我们使用Lable实现:一行、图像和多行:

#coding=utf-8
'''
Author  :SongBaoBao
Project :MyGUI
FileName:Simple_Template.py
Currtime:2020/7/11--04:42
Commpany:Tsinghua University
MyCsdnIs:https://blog.csdn.net/weixin_43949535
MyLolLpl:Royal Never Give Up
'''
#
# 以后所有的GUI编程(OOP)的初始模板
# from tkinter import *
from tkinter import messageboxclass myApplication(Frame):"""类 Application继承了 Frame 及通过继承拥有了父类的特性;并组织整个 GUI 程序"""def __init__(self,master=None): # 定义构造函数super().__init__(master) # 调用父类的构造器,super()是代表父类的定义 而非父类对象self.master=masterself.pack() # self本身就是一个组件self.createWidget()def showMessage(self):messagebox.showinfo("Message","Your name is SongBaoBao!")def createWidget(self):"""创建窗口中的对象(创建组件),self就是这个组件容器:return:"""# ************************************** ## 创建一个Lableself.lable1=Label(self,text="你是一个小可爱!",width=16,height=2,bg="gold",fg="#5CADAD",font=("STXingkai",24))self.lable1.pack()# ************************************** ## 创建一个Lable 显示图像global myPicture # 声明成全局变量,否则本方法执行完 图像对象消失了(窗口无法显示)myPicture=PhotoImage(file="../bear.gif")self.lable2=Label(self,image=myPicture)self.lable2.pack()# ************************************** ## 创建一个Lable 显示多行文本self.lable3=Label(self,text="Royal Never Give Up\n皇族永不言败",borderwidth=2,relief="groove",justify="center",font=("STXingkai",32))self.lable3.pack()if __name__=='__main__':# 第一步:通过类的默认构造函数 来构造主窗口对象root=Tk()root.title("这是窗口标题")root.geometry('1000x800+500+250') # 距离左边500 上边250# 第二步:创建一个对象app = myApplication(master=root)  # 意思就是这个app会放在root主窗口里面# 最后开启事件循环root.mainloop()

来看一下效果:

大家也注意到了在上面 创建一个Lable的时候,通过传入不同的参数实现标签的不一样的展示效果

Options

这些Options的设置控制了该组件的不同状态,选项参数的设置是可以有下面三种方式:

# 创建一个Lable
self.lable1 = Label(self,text="你是一个小可爱!",height=2,fg="#5CADAD",font=("STXingkai",24)) # 这样传 这些参数都被构造方法的 **kw 进行接收
self.lable1["width"]=16 # 创建对象后,使用字典索引方式([]运算符重载)
self.lable1.config(bg="gold") #  创建对象后,使用 config()方法# 注:上面三种最终都会到_configure方法上


对于上面的这个Lable组件,我们可以有两种方式来查看Options选项:

第一种:config()方法的返回值字典

mydict=self.lable1.config()print(type(mydict))for key, value in mydict.items():print('\033[1;35m',key,'\033[0m',end=":")print(value)

打印结果如下所示:

第二种:查看组件的构造函数

class Label(Widget):"""Label widget which can display text and bitmaps."""def __init__(self, master=None, cnf={}, **kw):"""Construct a label widget with the parent MASTER.STANDARD OPTIONSactivebackground, activeforeground, anchor,background, bitmap, borderwidth, cursor,disabledforeground, font, foreground,highlightbackground, highlightcolor,highlightthickness, image, justify,padx, pady, relief, takefocus, text,textvariable, underline, wraplengthWIDGET-SPECIFIC OPTIONSheight, state, width"""Widget.__init__(self, master, 'label', cnf, kw)

上面两种方式都可以看到组件Lable的诸多Option,在第二种里面有:“standard options 标准选项”和“widget-specific options 组件特定选项”

OK 我们将常见的选项做一个简单的汇总 如下:

Button

按钮用来执行用户的单击操作。Button 可以包含文本,也可以包含图像。按钮被单击后会自动调用对应事件所绑定的方法。

下面就来看一下,文本button和图片button的使用:


注:有时候我们会遇到一个处于灰色状态的按钮(点不动):

self.b = Button(self, text="Help", state=DISABLED)self.b.pack()

Entry

上面汇总的时候,已经提过了:Entry是一个单行输入框(用户可以输入内容)。注:当用户输入的文字长度超过Entry 组件的宽度时,文字会自动向后滚动。如果想输入多行文本,则需要使用 Text 控件。

下面来看一个实例:

上面就完美的显示了:输入框里的默认提示信息的打印

上面我们在登录点击之后,后台就得到了用户输入的相关信息(上面在输入密码的时候是明码)于是修正如下:

源代码如下:

#coding=utf-8
'''
Author  :SongBaoBao
Project :MyGUI
FileName:myEntry.py
Currtime:2020/7/11--15:37
Commpany:Tsinghua University
MyCsdnIs:https://blog.csdn.net/weixin_43949535
MyLolLpl:Royal Never Give Up
'''
#
# 测试单行文本框
# from tkinter import *
from tkinter import messageboxclass myApplication(Frame):"""类 Application继承了 Frame 及通过继承拥有了父类的特性;并组织整个 GUI 程序"""def __init__(self,master=None): # 定义构造函数super().__init__(master) # 调用父类的构造器,super()是代表父类的定义 而非父类对象self.master=masterself.pack() # self本身就是一个组件self.createWidget()def showMessage(self,messageStr):print("现在登录的人的用户名:"+self.entry2.get()+"  密码是:",self.entry4.get())messagebox.showinfo("Message",messageStr)def createWidget(self):"""创建窗口中的对象(创建组件),self就是这个组件容器:return:"""# ************************************** ## 创建一个Lable 显示一行文本self.lable1 = Label(self, text="用户名:", borderwidth=2, relief="groove",justify="left", font=("STXingkai", 32),fg="#5CADAD")self.lable1.pack()# ************************************** ## 创建一个Entry 存放单行内容inputStr=StringVar() # 初始化 然后绑定(之后组件内容和inputStr变量内容保持一样)self.entry2=Entry(self,textvariable=inputStr)self.entry2.pack()inputStr.set("这里输入用户名")# ************************************** ## 创建一个Lable 显示一行文本self.lable3 = Label(self, text="密码", borderwidth=2, relief="groove",justify="left", font=("STXingkai", 32),fg="#5CADAD")self.lable3.pack()# ************************************** ## 创建一个Entry 存放单行内容inputStr2 = StringVar()  # 初始化 然后绑定(之后组件内容和inputStr变量内容保持一样)self.entry4 = Entry(self, textvariable=inputStr2,show='*')self.entry4.pack()# ************************************** ## 创建一个Button 进行登录self.button5=Button(self,text="点击登录",command=lambda:self.showMessage("登录成功!"))self.button5.pack()if __name__=='__main__':# 第一步:通过类的默认构造函数 来构造主窗口对象root=Tk()root.title("这是窗口标题")root.geometry('1000x800+500+250') # 距离左边500 上边250# 第二步:创建一个对象app = myApplication(master=root)  # 意思就是这个app会放在root主窗口里面# 最后开启事件循环root.mainloop()

Text

多行文本框主要是用于显示多行文本,还可以显示网页链接、图片、 HTML 页面、CSS 样式表、添加图片和添加组件等。因此,也常被当做简单的文本处理器、文本编辑器或者网页浏览器来使用。

OK,下面实现一个简单的文本编译器来详解Text特性(行开始为1,列开始为0
首先第一点 请大家先看一下下面这个例子(然后思考一下问题所在):

上面这个插入位置 好像没有起作用 !

我们来详细看一下这个代码就会发现问题所在:在self.text1.insert(1.0,"hello宋")结束的时候 还没有第二行,插入位置虽然为2.0 但是不起作用的。于是就直接将0123456789\nabcdefg 接在的后面 于是结果如下:

此时的Text里面也就没有第三行,同理 上面的llllll也就跟在上面一行的后面!

于是我们可以这么做:加上一行self.text1.insert(1.0,"hello\n宋")

OK,我们来把这个过程捋一捋:

1、在self.text1.insert(1.0,"hello\n宋") 之后,实际上Text已经有两行内容
2、上面这句之后,第二行只有一个 宋 字(为2字符)。2.0, "0123456789\nabcdefg"的意思就是在第二行的开始插入这句话。于是结束之后 新的第二行为:0123456789,第三行为:abcdefg宋
3、于是这时候我们也有了第三行,3.3, "llllll\n"表示在d的位置放置 "llllll\n"。于是换行之后,新的第三行为:abcllllll,第四行为:defg宋

好,这个小例子之后相信大家已经对Text的一些特点有了更加深入的了解!下面我们开始第二个例子(简易文本编译器):(下面是创建插件部分的内容

    def createWidget(self):"""创建窗口中的对象(创建组件),self就是这个组件容器:return:"""# ************************************** ## 创建一个Text 显示多行文本self.text1=Text(root,width=160,height=120,bg="gold",fg="#5CADAD",font=(72))self.text1.pack()self.text1.insert(1.0,"hello\n宋baobao")# ************************************** ## 创建多个Button 存放多个内容Button(self, text="插入文本", command = lambda:self.insertText("hello","world")).pack(side="left")Button(self, text="返还文本函数1", command=lambda: self.returnText1(1.1,2.1)).pack(side="left")Button(self, text="返还文本函数2", command=self.returnText2).pack(side="left")Button(self, text="增加一个图片", command=lambda: self.addPicture("../bear.gif")).pack(side="left")Button(self, text="增加一个按钮", command=lambda: self.addButton("这是一个按钮")).pack(side="left")Button(self, text="tag控制文本", command=self.setTag).pack(side="left")

最开始的效果就是:

1、插入文本函数:插入一个你喜欢的文本字符串

注:现在我的光标放在“宋”后面,结果如下:

2、返还文本函数1:一个区域内的文本

3、返还文本函数2:Text全部的文本

4、增加一个图片

注:它是在Text的最后面做的增加(我这里若是多次添加图片 显示不出来)

5、增加一个按钮
注:在光标后面添加的按钮

6、通过tag控制文本

点击上面的下划线链接之后:(是由webShow(self,event)实现的)

上面这个简单的小程序源码如下:

#coding=utf-8
'''
Author  :SongBaoBao
Project :MyGUI
FileName:myText.py
Currtime:2020/7/10--9:43
Commpany:Tsinghua University
MyCsdnIs:https://blog.csdn.net/weixin_43949535
MyLolLpl:Royal Never Give Up
'''
#
# 测试多行文本框 Text
#
from tkinter import *
import webbrowser
from tkinter import messageboxclass myApplication(Frame):"""类 Application继承了 Frame 及通过继承拥有了父类的特性;并组织整个 GUI 程序"""def __init__(self,master=None): # 定义构造函数super().__init__(master) # 调用父类的构造器,super()是代表父类的定义 而非父类对象self.master=masterself.pack() # self本身就是一个组件self.createWidget()# ************************************** ## 插入文本函数:插入一个你喜欢的文本字符串def insertText(self,message1,message2):self.text1.insert(INSERT,message1) # 意思是:在光标所在的地方插入message1self.text1.insert(END,message2) # 意思是:在光标所在的地方插入message2# ************************************** ## 返还文本函数1def returnText1(self,str1,str2):print(self.text1.get(str1,str2))# ************************************** ## 返还文本函数2def returnText2(self):print(self.text1.get(1.0,END))# ************************************** ## 增加一个图片def addPicture(self,path):global mypictureself.mypicture=PhotoImage(file=path)self.text1.image_create(END,image=self.mypicture)# ************************************** ## 增加一个按钮def addButton(self,text):button1=Button(self.text1,text=text)self.text1.window_create(INSERT,window=button1)# ************************************** ## 打开一个链接def webShow(self,event):webbrowser.open("https://blog.csdn.net/weixin_43949535")# ************************************** ## 设置相关标签def setTag(self):# 清理掉Text全部数据self.text1.delete(1.0,END)# 插入一些文本信息self.text1.insert(1.0,"皇族永不言败\nRNG加油!\n这是一个链接!")# 设置第一个标签self.text1.tag_add("皇族",1.0,1.12)self.text1.tag_config("皇族",background="gold",font=("STXingkai",12))# 设置第二个tagself.text1.tag_add("link",3.0,3.12)self.text1.tag_config("link",underline=True)self.text1.tag_bind("link","<Button-1>",self.webShow)def createWidget(self):"""创建窗口中的对象(创建组件),self就是这个组件容器:return:"""# ************************************** ## 创建一个Text 显示多行文本self.text1=Text(root,width=160,height=120,bg="gold",fg="#5CADAD",font=(72))self.text1.pack()self.text1.insert(1.0,"hello\n宋baobao")# ************************************** ## 创建多个Button 存放多个内容Button(self, text="插入文本", command = lambda:self.insertText("hello","world")).pack(side="left")Button(self, text="返还文本函数1", command=lambda: self.returnText1(1.1,2.1)).pack(side="left")Button(self, text="返还文本函数2", command=self.returnText2).pack(side="left")Button(self, text="增加一个图片", command=lambda: self.addPicture("../bear.gif")).pack(side="left")Button(self, text="增加一个按钮", command=lambda: self.addButton("这是一个按钮")).pack(side="left")Button(self, text="tag控制文本", command=self.setTag).pack(side="left")if __name__=='__main__':# 第一步:通过类的默认构造函数 来构造主窗口对象root=Tk()root.title("这是窗口标题")root.geometry('1000x800+500+250') # 距离左边500 上边250# 第二步:创建一个对象app = myApplication(master=root)  # 意思就是这个app会放在root主窗口里面# 最后开启事件循环root.mainloop()

GUI面向对象的应用程序开发(模板)

注:这个自从进入GUI编程之后,下面的代码示例(OOP思想)可以作为将来这一类的程序开发的最初模板:

#coding=utf-8
'''
Author  :SongBaoBao
Project :MyGUI
FileName:Simple_Template.py
Currtime:2020/7/11--04:42
Commpany:Tsinghua University
MyCsdnIs:https://blog.csdn.net/weixin_43949535
MyLolLpl:Royal Never Give Up
'''
#
# 以后所有的GUI编程(OOP)的初始模板
# from tkinter import *
from tkinter import messageboxclass myApplication(Frame):"""类 Application继承了 Frame 及通过继承拥有了父类的特性;并组织整个 GUI 程序"""def __init__(self,master=None): # 定义构造函数super().__init__(master) # 调用父类的构造器,super()是代表父类的定义 而非父类对象self.master=masterself.pack() # self本身就是一个组件self.createWidget()def showMessage(self):messagebox.showinfo("Message","Your name is SongBaoBao!")def createWidget(self):"""创建窗口中的对象(创建组件),self就是这个组件容器:return:"""# ************************************** ## 创建一个buttonself.button1=Button(self)self.button1["text"]="点击一下"self.button1.pack() # 通过布局管理器放到窗口里面self.button1["command"]=self.showMessage# ************************************** ## 创建一个退出buttonself.quitButton1=Button(self,text="退出",command=root.destroy)self.quitButton1.pack()if __name__=='__main__':# 第一步:通过类的默认构造函数 来构造主窗口对象root=Tk()root.title("这是窗口标题")root.geometry('500x300+500+250') # 距离左边500 上边250# 第二步:创建一个对象app = myApplication(master=root)  # 意思就是这个app会放在root主窗口里面# 最后开启事件循环root.mainloop()

Python的学习心得和知识总结(十二)|Python图形用户接口编程(Graphical User Interface编程 一)相关推荐

  1. Oracle的学习心得和知识总结(二十五)|Oracle数据库Real Application Testing之真实应用测试概述白皮书

    目录结构 注:提前言明 本文借鉴了以下博主.书籍或网站的内容,其列表如下: 1.参考书籍:<Oracle Database SQL Language Reference> 2.参考书籍:& ...

  2. PostgreSQL的学习心得和知识总结(二十四)|CentOS环境 配置生成coredump程序崩溃内存转储文件及gdb调试core文件

    目录结构 注:提前言明 本文借鉴了以下博主.书籍或网站的内容,其列表如下: 1.使用GDB分析core dump文件,点击前往 2.详解coredump,点击前往 3.PostgreSQL数据库仓库链 ...

  3. HTML的学习心得和知识总结(二)|HTML基础和高级标签汇总

    目录结构 HTML基础和高级标签汇总 文章快速说明索引 HTML 的基础标签 HTML 的高级标签 文章快速说明索引 学习目标: 可视化和部分交互功能的前端开发内容的学习:HTML CSS JavaS ...

  4. C++的学习心得和知识总结(十六)|基于EasyX实现小甲鱼Python飞机大战项目(C++版)

    目录结构 注:提前言明 本文借鉴了以下博主.书籍或网站的内容,其列表如下: 1.小甲鱼Python项目 – 飞机大战 2.本文使用的掩码图生成工具 自动生成遮罩图的程序,点击前往 3.EasyX官方链 ...

  5. C++的学习心得和知识总结(十八)|基于EasyX实现 2048游戏 项目(C/C++版)

    目录结构 注:提前言明 本文借鉴了以下博主.书籍或网站的内容,其列表如下: 1.网络热门游戏 2048,点击前往 2.EasyX官方链接,点击前往 3.中国色谱 颜色代码对照表(RGB多用于easyX ...

  6. 【飞桨/百度领航团/零基础Python】学习心得

    [飞桨/百度领航团/零基础Python]学习心得 课程链接:https://aistudio.baidu.com/aistudio/course/introduce/7073 初识python Pyt ...

  7. python学生信息管理系统心得体会-Python的学习心得

    Python的学习心得 首先很庆幸自己选到了这门个性化选修课,可能是我个人比较认为这门课程所用的语言很特别很奇妙,老师也很有趣,能让我们更好的了解Python这门课程真正的用途.在学习Python这门 ...

  8. python自学需要哪些基础知识-零基础学Python应该学习哪些入门知识及学习步骤安排...

    众所周知,Python以优雅.简洁著称,入行门槛低,可以从事Linux运维.Python Web网站工程师.Python自动化测试.数据分析.人工智能等职位!就目前来看,Python岗位人才缺口高达4 ...

  9. python心得体会-终于懂得python基础学习心得

    为了提高模块加载的速度,每个模块都会在__pycache__文件夹中放置该模块的预编译模块,命名为module.version.pyc,version是模块的预编译版本编码,一般都包含Python的版 ...

  10. python自学步骤-零基础学Python应该学习哪些入门知识及学习步骤安排

    众所周知,Python以优雅.简洁著称,入行门槛低,可以从事Linux运维.Python Web网站工程师.Python自动化测试.数据分析.人工智能等职位!就目前来看,Python岗位人才缺口高达4 ...

最新文章

  1. 【自动驾驶】10.百度Apollo平台 事件通信机制
  2. 轻松搞定日志的可视化(第一部分)
  3. 借助云开发轻松实现后台数据批量导出丨实战
  4. [原创]windows server 2012 AD架构 试验 系列 – 15解决AD复制冲突
  5. 虚拟按键自己触发的java代码_在SystemUI添加虚拟按键
  6. android nv21图片格式,Android -- 将NV21图像保存成JPEG
  7. iClip mac如何自定义声音?iClip剪切板管理软件更改声音的方法
  8. nginx 域名跳转
  9. ISO27001体系的价值(详解)
  10. PHP微信公众号授权登录
  11. 王小九用计算机弹桥边姑娘,抖音最火歌曲是哪首?QQ音乐开放平台《桥边姑娘》让“野狼”靠边站...
  12. 智能宠物饲养系统设计
  13. app上架360手机助手流程
  14. python 频数统计_日常答疑:Python实现分类频数统计
  15. Xshell的Sessions存放目录
  16. 找出字符串中第一个不重复的字符
  17. 【What if 系列】全球雪封
  18. [每日100问][2011-9-06]iphone开发笔记,今天你肿了么
  19. openCV中watershed的使用
  20. 黄金斗士原生android,28日京东开售 联想黄金斗士S8正式发布

热门文章

  1. 编译原理实验-用FLEX自动构造词法分析程序
  2. 用python批量发送短信_Python批量发短信
  3. CSDN博客成长记录
  4. Ethernet/IP以太网接M12 X-Coded 协议:port1(Ethernet连接)
  5. DBLink应用速成
  6. 将一个大写英文字母转换为小写输出 (12 分) - PTA
  7. Java JUC包的学习文章整理以及整体结构功能概述
  8. 数据结构C语言般卷纸真题,数据结构(C语言版)考研真题(A卷)
  9. 【学习笔记】通过雷达获取某一角度的距离信息
  10. html显示千分位及小数位,使用CSS格式化数字(小数位,千位分隔符等)