尝试用 Python 做了一个在线多聊天室的服务器程序,通过 shell 登陆。开发环境:MAC OS 10.10,Python 2.7.9。经过测试,发现了一些问题: - 无法支持中文聊天 - 消息输入、输出使用同一窗口,其他人发送的消息会冲乱当前正在输入的内容 - windows 的 shell 好像不支持消息输错回退 于是决定做一个 GUI 的客户端。 Python 的 GUI 模块很多,选择 Tkinter(以下简称Tk) 是因为 Python 自带、而且几个操作系统都支持。 客户端的开发有两个步骤:界面开发 以及 与服务器对接。 界面开发需要有三个界面,分别用来输入昵称、通过按钮选择聊天室、进行聊天。 运行 Tk 先要创建一个根窗口,就像一面空墙,需要进行装饰。可以通过geometry指定窗口大小、位置。 from Tkinter import * #导入模块 root = Tk() #创建一个根窗口 root.mainloop() #进入窗口的主循环,否则无法显示界面 居中 root.geometry(self, newGeometry=None) # 通过 widthxheight+x+y (宽x高+左上角X轴坐标+左上角Y轴坐标)的方式,设置一个新的 geometry root.geometry(‘%sx%s+%s+%s’ % ( root.winfo_width() , # 窗口宽度 root.winfo_height() , # 窗口高度 (root.winfo_screenwidth() – root.winfo_width())/2, # (屏幕宽度 – 窗口宽度)/2 (root.winfo_screenheight() – root.winfo_height())/2 # (屏幕高度 – 窗口高度)/2 )) 然后根据需要创建窗口里的组件,包括规定组件的大小、颜色,最后按照一定的位置摆放这些组件。 Tk 提供了很多组件,用来实现各种功能,包括 输入框(Entry)、按钮(Button)、显示文本的标签(Label)、滚动条(Scrollbar)、字符串列表框(Listbox) 等。 每个组件都有一些参数可以配置,常用的配置方法有两种: - widgetclass(master, option=value, …)。组件(放在哪个窗口, 参数=值, …),第一个参数指定了放置到哪一个窗口,可以是根窗口,或是框架控件(Frame) 或者 - widgetclass.config(option=value, …) 创建标签,显示文本 inputText = Label(self) #创建一个标签,用于显示文本信息 inputText["text"] = “欢迎,请输入昵称:” #标签的文本内容 inputText.pack(side=”top”) #指定将标签在窗口中向上放置 获取输入框的内容 创建名为 server_ip 的 StringVar(),和 Entry 的 textvariable参数进行绑定,输入的内容通过 server_ip.get() 获取。输入框还可以用 server_ip.set(’127.0.0.1′) 设置默认值。 server_ip = StringVar() server_ip.set(’127.0.0.1′) input_ip = Entry(self, textvariable=server_ip) input_ip["width"] = 5 input_ip.pack(side=”left”, ipadx=30, padx=5) ip = server_ip.get() 组件的函数调用,有 直接绑定函数 和 间接绑定事件 两种方式。 当需要指定按钮按下时,执行什么方法/函数,可以使用command参数绑定函数 QUIT = Button(root) QUIT["text"] = “QUIT” QUIT["fg"] = “red” QUIT["command"] = root.quit # 结束 Tkinter 所有组件 QUIT.pack(side=”left”) def quit(): pass 组件也可以bind绑定触发事件(键盘、鼠标),并指定 事件的行为。比如,为输入框绑定回车事件,指定调用 send_message函数 对输入的内容进行处理。使得回车就可以发送消息,而不用点击按钮。 frame_l_m = Frame(self) #创建一个框架控件 message_input = StringVar() message_send = Entry(frame_l_m, textvariable=message_input) message_send["width"] = 70 message_send.bind(”, send_message) message_send.pack(fill=X) frame_l_m.pack() def send_message(): pass 显示消息的窗口(我选择使用 Listbox 实现) 带有 滚动条,需要两步: 1. 用 Listbox 的 yscrollcommand参数,调用 scrollbar 的 set 方法 2. 设置 scrollbar 的command参数为 Listbox 的 yview(纵向滚动条)或 xview(横向) 方法 对于其他需要和滚动条绑定的组件都需要做以上两个设置。 另外,滚动条默认在顶端,如果希望能够自动下拉到聊天窗口的最底端,显示最新的消息,可以使用 Listbox 的 yview_moveto 方法,指定值为1.0。 注意,scrollbar 的位置是由 Listbox 确定的,所以应该找 Listbox 的方法,而不是 scrollbar 的方法 frame_l_t = Frame(self) #可以是 根窗口,或框架组件 scrollbar = Scrollbar(frame_l_t) chatText = Listbox(frame_l_t, width=70, height=18, yscrollcommand=scrollbar.set) chatText.yview_moveto(1.0) scrollbar.config(command=chatText.yview) scrollbar.pack(side=”right”, fill=Y) chatText.pack(side=”left”) 将 输入框的内容 移到 显示消息的组件,并清空 输入框的内容。这需要用到 Listbox 的 insert方法 和 Entry 的 delete方法。 insert,指定从END(最后),插入消息 send_mesg。 delete,指定删除从 最开始0到END最后。 frame_l_t = Frame(self) frame_l_m = Frame(self) scrollbar = Scrollbar(frame_l_t) chatText = Listbox(frame_l_t, width=70, height=18, yscrollcommand=scrollbar.set) # 消息显示组件 scrollbar.config(command=chatText.yview) scrollbar.pack(side=”right”, fill=Y) chatText.pack(side=”left”) frame_l_t.pack() message_input = StringVar() message_send = Entry(frame_l_m, textvariable=message_input) # 消息输入组件 message_send["width"] = 70 message_send.bind(”, send_message) message_send.pack(fill=X) frame_l_m.pack() def send_message(self, event): send_mesg = message_input.get() chatText.insert(END, send_mesg) # 在消息显示组件显示 chatText.yview_moveto(1.0) # 将滚动条拉至最低 message_send.delete(0, END) # 从输入框删除 最后就是放置组件了,位置的管理有三种方式:pack(块)、grid(单元格)、place(位置)。 如果不配置管理方式,窗口/组件不会显示。 pack: 较简单,也最常用,如同拼七巧板,简单地将 组件\框架控件 作为一个方块进行堆砌。默认将组块从上到下放置。可以使用参数 fill、expand、side 进行控制。 fill表示如何组件填充方向,有三个值可选,X横向Y纵向BOTH横向和纵向 填充。但不会使用窗口中多出的空间。 expand设置是否使用窗口多出的空间,默认是0不使用,如果是非零值,通常使用1,将会对窗口未使用的部分进行填充,填充方向根据 fill 决定。 side确定组件摆放顺序,只使用TOP(默认),从上向下依次放置,LEFT,从左到右依次放置,也可以使用BOTTOM或RIGHT。但是,简单通过这样的方式摆放组件,并不一定会得到想要的效果,可以用Frame作为子窗口,对部分组件进行安放,然后再放置Frame。 grid: 适合摆放复杂的界面。由于pack的放置不一定满意,除了用Frame优化外,还可以使用grid进行放置。不需要指定窗口尺寸,grid会自动检测组件大小决定。用法类似描述Excel单元格坐标,参数row行,column列(默认为0),stickycolumnspanrowspan row,将组件放入指定的某一行,数值从0开始。如果不指定,默认放在第一个空行。 column,将组件放入指定的某一列,数值从0开始,默认为0。 sticky,组件默认在单元格中居中对齐,可以通过对该参数设置N,S,E,W中的一个或多个值,改变在单元格中的对齐方式。 columnspan,组件可以占用不止一列单元格的空间 rowspan,组件也可以占用不止一行单元格的空间 place 最复杂、精细的控制,这里就不说明了…… 界面的跳转 需要实现显示下一个窗口/界面的同时,关闭现有的窗口/界面。在当前窗口/界面的类中,定义方法,先创建并显示新的窗口/界面,然后使用当前窗口/界面的 destroy 方法,关闭 当前窗口/界面 以及 当前窗口/界面中所有的组件。 一定要先创建新窗口,再关闭现有的窗口 root = Tk() app = Chat(master=root) class Chat(Frame): def __init__(self, master=None): Frame.__init__(self, master) self.pack() # 用来管理和显示组件,默认 side = “top” def room_pm(self): root = Tk() # 创建新窗口 app = Room(master=root, name=”pm”) self.master.destroy() # 与 quit 不同,只销毁 当前组件 和 其子组件 Tkinter 的中文输入遇到过一点问题:中文输入法无法在输入框输入中文,只能打出拼音,但是可以将中文复制粘贴进去、标签、按钮、字符串列表框 等组建也可以显示中文。搜索后知道,是 MAC 自带的 Tkinter版本过低,下载新版本安装一下就解决了。但并不是要安装最新版本, 具体解决过程 服务器对接因为客户端是通过命令行 telnet 登陆服务器,而 Python 自带 telnetlib模块,可以实现 telnet 功能。 from telnetlib import * host = “127.0.0.1″ port = 5000 server = Telnet(host, port) 客户端 简单、特定消息 的 发送和接受 通过 telnetlib 的 write 和 read_until 方法。 server.write(“/back” + “\r\n”) # 在服务器端, \r\n 表示换行(回车) server.write(send_mesg.encode(“utf-8″)+”\r\n”) # 发送中文 server.read_until(“More helps use: /help”, 1) # 接收消息,直到收到指定字符串为止。也可以指定等待的秒数,接收目前收到的信息。 server.read_until(“!”) 进入聊天室后,由于需要同时进行 循环显示窗口、不断侦探/接收来自服务器的聊天消息 两个任务。 我选择在聊天室实例中,通过创建线程,调用 receiveMessage 方法接收聊天消息。用 telnetlib模块的 get_socket()方法,获得 socket对象,并通过这个对象,调用 recv方法 与服务器通信,接收消息。 import thread class Room(Frame): def __init__(self, master=None, name=None): … def receiveMessage(self): socket = server.get_socket() while 1: clientMsg = socket.recv(4096) if not clientMsg: continue else: self.chatText.insert(END, clientMsg) self.chatText.yview_moveto(1.0) def startNewThread(self): thread.start_new_thread(self.receiveMessage, ()) 但 Tkinter 一直报错: RuntimeError: main thread is not in main loop 因为 Tkinter 并非是真正的可以实现 多线程,还有很多问题。 三个解决方案: 1. 官方的方案:将 Tk 代码放入主线程,并将 现在线程 的代码放入 工作线程 2. 使用第三方库,例如twisted 3. 使用 mkTkinter。官方对 Tkinter 多线程 问题的修复版本。直接从官网下载单文件模块即可。 我选择了使用 mkTkinter 替换 Tkinter。只需从官网 下载 mtTkinter,放在同一目录就可以了,方法名称同 Tkinter一样。 # from Tkinter import * from mtTkinter import * (责任编辑:好模板) |