先简单提下顶端的canvas,直译就是“画布”,画画得有纸,这就是纸。
root = tk.Tk()
root.title('MyChat')
root.geometry('800x500')
root.config(background='#F8F8FF')
#create canvas to show image
canvas = tk.Canvas(root, width=500, height=100)
image_file = tk.PhotoImage(file='img/title.gif')
image = canvas.create_image(120, 0, anchor='nw', image=image_file)
canvas.pack(side='top')
很简单,把一个photoimage对象显示在canvas,仅此而已。
canvas.create_image()方法的参数提下。前两个参数是图片从canvas的哪里开始,比如:
黑框是canvas,红框是image。如果是create_image(0,0)就是图中的样子。如果是create_image(50,50),就是这样:
下一个参数anchor是图片的起始位置,n(north),w(west),s(south),e(east),center。按上北下南左西右东的方位记忆。nw就是左上角开始,比如,一张图片是这样的:
那create_image(0,0,anchor='nw'),就是这样的:
如果是create_image(0,0,anchor='center'),就是这样的:
行吧,canvas就这样了,不是必须的,随便去设计吧。
来看菜单的设计。为了界面的友好性,每当配置完一项后,我们就在界面里显示当前的配置消息。在button下面放三个label来显示配置:
button = tk.Button(root, text='submit', command=insert_point)
button.pack()
#my ip
var_ip = tk.StringVar()
#his/her ip
var_send = tk.StringVar()
#my id
var_name = tk.StringVar()
#some friendly labels
label1 = tk.Label(root, textvariable=var_ip)
label2 = tk.Label(root, textvariable=var_send)
label3 = tk.Label(root, textvariable=var_name)
label1.pack()
label2.pack()
label3.pack()
而label显示的内容是变量,根据用户的输入变化,因此label的显示消息需要给stringvar。
菜单的逻辑很简单,主菜单叫config,下面是三个小的子菜单,分别用于配置自己和别人的ip以及自己的id。
#create menu object
menubar = tk.Menu(root)
ip_menu = tk.Menu(menubar, tearoff=0)
#config menu and add submenu
menubar.add_cascade(label='Config', menu=ip_menu)
ip_menu.add_command(label='Config ip', command=conf_ip)
ip_menu.add_command(label='Config send ip', command=conf_send)
ip_menu.add_command(label='Edit ID', command=edit_id)
#show menu
root.config(menu = menubar)
子菜单的绑定事件,就是配置功能的逻辑。
拿自己ip的配置conf_ip来说吧。
def conf_ip():
top = tk.Toplevel()
top.title('Config ip')
top.geometry('300x100')
e = tk.Entry(top)
e.pack()
def confirm():
global ip_port_recv, var_ip, sk_recv
var = e.get()
ip_port_recv = (var, 55555)
var_ip.set('我的IP:' + var)
sk_recv.bind(ip_port_recv)
t1 = threading.Thread(target=loop)
t1.setDaemon(True)
t1.start()
top.destroy()
b = tk.Button(top, text='Confirm', command=confirm)
b.pack()
当点击的时候,弹出一个子窗口,有一个输入框和点击按钮,点击按钮的功能逻辑:
输入框的IP作为接收数据的IP,及末尾label的显示变量,socket的对象绑定也放在这里面,同时接收数据的进程现在就可以启动了。
完成所有的东西后,记得把子窗口关掉。
另外两个功能的逻辑大同小异,记得还有一个ID变量,而我们最开始的文本框也就不是简单的插入输入框的数据了,前面要加上MyID:,因为这样更加真实些呀。
行吧,大概也就这样了。我把全部代码贴出来,有问题大家再讨论吧。
import tkinter as tk
import socket
import threading
#define object
sk_recv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sk_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip_port_recv = ('127.0.0.1', 55555)
ip_port_send = ('127.0.0.1', 55555)
#recive data run in a threading
def loop():
while True:
var = sk_recv.recv(1024).decode()
t.insert('insert', var + '\n')
#create windons
root = tk.Tk()
root.title('MyChat')
root.geometry('800x500')
root.config(background='#F8F8FF')
#create canvas to show image
canvas = tk.Canvas(root, width=500, height=100)
image_file = tk.PhotoImage(file='img/title.gif')
image = canvas.create_image(120, 0, anchor='nw', image=image_file)
canvas.pack(side='top')
#a label to say something
labe = tk.Label(root, text='Message Bar (No need to operate) :')
labe.pack()
t = tk.Text(root, height=10)
t.pack()
#config my ip
def conf_ip():
#create sub windon
top = tk.Toplevel()
top.title('Config ip')
top.geometry('300x100')
#a entry
e = tk.Entry(top)
e.pack()
#the function of button
def confirm():
global ip_port_recv, var_ip, sk_recv
var = e.get()
#change ip
ip_port_recv = (var, 55555)
#change label var
var_ip.set('我的IP:' + var)
#bing port
sk_recv.bind(ip_port_recv)
#loop to recive data
t1 = threading.Thread(target=loop)
t1.setDaemon(True)
t1.start()
#close subwindon
top.destroy()
#a button
b = tk.Button(top, text='Confirm', command=confirm)
b.pack()
#config his/her id
def conf_send():
top = tk.Toplevel()
top.title('Config send ip')
top.geometry('300x100')
e = tk.Entry(top)
e.pack()
def confirm():
global ip_port_send, var_send
var = e.get()
ip_port_send = (var, 55555)
var_send.set('他的IP:' + var)
top.destroy()
b = tk.Button(top, text='Confirm', command=confirm)
b.pack()
#Edit my id
def edit_id():
top = tk.Toplevel()
top.title('Edit ID')
top.geometry('300x100')
e = tk.Entry(top)
e.pack()
def confirm():
global var_name, name
var_name.set('我的ID:' + e.get())
name.set(e.get())
top.destroy()
b = tk.Button(top, text='Confirm', command=confirm)
b.pack()
#create menu
menubar = tk.Menu(root)
ip_menu = tk.Menu(menubar, tearoff=0)
#config menu and add submenu
menubar.add_cascade(label='Config', menu=ip_menu)
ip_menu.add_command(label='Config ip', command=conf_ip)
ip_menu.add_command(label='Config send ip', command=conf_send)
ip_menu.add_command(label='Edit ID', command=edit_id)
#show menu
root.config(menu = menubar)
#a friendly label
label = tk.Label(root, text='Input something:')
label.pack()
#a entry to input something for user
entry = tk.Entry(root)
entry.pack()
def insert_point():
global name
var = entry.get()
message = name.get() + ' : ' + var
t.insert('insert', message + '\n')
sk_send.sendto(message.encode(), ip_port_send)
entry.delete(0, tk.END)
#a button for sending message
button = tk.Button(root, text='submit', command=insert_point)
button.pack()
#some var
#my ip
var_ip = tk.StringVar()
#his/her ip
var_send = tk.StringVar()
#my id
var_name = tk.StringVar()
name = tk.StringVar()
#some friendly labels
label1 = tk.Label(root, textvariable=var_ip)
label2 = tk.Label(root, textvariable=var_send)
label3 = tk.Label(root, textvariable=var_name)
label1.pack()
label2.pack()
label3.pack()
root.mainloop()
这里可以给大家留下个问题,意犹未尽的可以继续玩。既然我可以在两台机器间传递文本消息了,那我为何不能传首歌、传个照片、发个视频呢?
逻辑上也很简单,拿.mp3文件来举例,在client端用二进制的方式读取文件,每次读取1024个字节,循环发送。server那边一边接收,一边用二进制的方式往文件里面写,最后保存成.mp3的文件就可以了。
在这里因为发送的数据是有序的,比如我依次发送了数据是A、B、C,你往文件里写的顺序也必须是A、B、C,顺序一打乱,文件也就错误了。也因此,传递数据时,我们需要TCP这种可靠的协议。保证每个数据到达,且保留顺序性
系列结束,再见。