下周我们公司的圣诞 Party 活动安排有抽奖环节,由于不方便采用手机抽奖,且目前选用的电脑端在线抽奖会出现卡顿情况,最近我就尝试着用 Python 实现抽奖功能。
目前进展不错,也想分享给大家,由于涉及隐私嘛,做了番保密修改。
先来看看效果视频吧(背景音乐很好听哦:)
人家需不需要呢咱也不敢问,反正抽奖程序是做好了,请大家过目:
运行前准备好参与抽奖的好汉名单,本程序会自动读取表格文件,将待抽奖的各位好汉展示在左侧奖池中,只要点击图中小鹿的红鼻子,会默认抽取三等奖(共十位)。
可以看到,
抽奖时好汉
名
字会在中央滚动展示,
当
再次点击
红鼻子完成单次抽奖时,中奖的
名字会从左侧奖池转移到
右侧获奖榜上
。
一、二等奖
分别五位,与三等奖抽取的
区别在于要先
选择右侧
1 号金色或 2
号银色
标志
,根据点选标志
抽取
相应奖项
。
当然,如果三等奖未完成,也可以点选 1 号金标 或 2 号银标 先行抽取,之后再通过点 3 号铜标 完成三等奖的抽取。在获奖榜满额时,再次抽奖会触发弹框提醒。
此外,左下方的 "Let's go!" 字样是重置开关,点击会重新载入数据进行抽奖。
当然,除了鼠标点击事件的控制,该抽奖程序也添加了键盘控制:例如数字键可以直接选择奖项,空格键等同于红鼻子控制,Esc 键退出抽奖等。
如上便是目前抽奖程序的功能和界面了,下面分享下我在设计与编码过程中的路线和想法。
需求与设计
首先归纳下整个抽奖程序的需求:
-
基本功能是实现名单中的随机抽取
-
活动穿插三轮抽奖,不能重复中奖
-
尽量美观
-
打消暗箱操作的怀疑
基于总结的需求点,我整理的设计方案如下:
-
名单自动载入至列表中
-
随机抽取名单列表,抽中后移除该元素
-
图形界面展现抽奖过程和结果,选用 tkinter 来实现
-
绑定鼠标、键盘控制抽奖过程
滚动随机数
首先搜索 “Python 抽奖程序”,在众多素材中看到了一份可以 tkinter 界面动态展示随机数的代码。
拷贝过来
运行,效果如图:
点击图中按钮时,屏幕中滚动出现 1000 以内的随机数,代码逻辑如下:
while True:
time.sleep(0.1)
r = random.choice(range(1000))
self.btn1['text'] = r
在 while 循环中设置 0.1 秒延迟,通过 random.choice() 在 range(1000) 生成随机数,将其绑定在 tkinter 界面上展现。这样随着 while 循环的进行,每个随机数在界面上停留 0.1 秒,就产生了滚动随机数的效果。
由于该代码中将整个抽奖过程定义为了一个对象,果断选取此份代码当作核心代码来予以拓展,也借此机会加深下相关理解。
我们要做的就是先消化吸收此代码,然后站在其肩膀上定制并完善自己需要的功能。
import tkinter
import time
import threading
import random
class Choujiang:
def __init__(self):
self.root = tkinter.Tk()
self.root.title('lowB版转盘')
self.root.minsize(600, 600)
self.isloop = False
self.newloop = False
self.setwindow()
self.root.mainloop()
def setwindow(self):
self.btn_start = tkinter.Button(self.root, text = '开始/停止', command = self.newtask,bg='gold')
self.btn_start.place(x=250, y=250, width=100, height=50)
self.btn1 = tkinter.Button(self.root, text='', bg='red')
self.btn1.config(font=("Courier", 50))
self.btn1.place(x=200, y=130, width=200, height=50)
self.girlfrends = list(range(1000))
def rounds(self):
if self.isloop == True:
return
i = 0
while True:
if self.newloop == True:
self.newloop = False
return
time.sleep(0.1)
r = random.choice(range(1000))
self.btn1['text'] = r
def newtask(self):
if self.isloop == False:
t = threading.Thread(target = self.rounds)
t.start()
self.isloop = True
elif self.isloop == True:
self.isloop = False
self.newloop = True
c = Choujiang()
滚动名单
因为公司有提供员工名做抽奖用,类比着来,也准备了一份梁山好汉的名单表格。基于刚刚动态展示随机数的代码,我们要做的是载入表格,将名单数据转化为列表。
data = pd.read_excel("demo.xlsx")
name_list=[item for item in data['名字']]
把刚代码中的提取随机数代码替换成 random.choice(名单列表) 就可以了。
while True:
time.sleep(0.1)
r = random.choice(name_list)
self.btn1['text'] = r
效果如下:
界面初始布局
在动态随机名字的界面基础上,添加背景图展现:
self.root = tkinter.Tk()
self.root.title("水泊梁山 2019 圣诞大抽奖")
self.root.geometry('1360x700+0+0')
self.root.resizable(False, False)
self.canvas = tkinter.Canvas(self.root,
width=1360,
height=700,
bg='white')
self.image = Image.open("static/background.png")
self.im = ImageTk.PhotoImage(self.image)
self.canvas.create_image(680, 350, image=self.im)
self.canvas.pack()
基于奖项划分,添加相应的按钮控制和中奖名单展现:
# 以一等奖为例,此处代码只是示例,参数不准确
# 添加一等奖的按钮组件 Button
self.first = tkinter.Button(self.root, text = '一等奖', command = self.set_first,bg='gold')
# 将该按钮置于界面相应位置
self.first.place(x=1070, y=180, width=80, height=50)
# 设置获奖榜展示字体
myFont = font.Font(size=20)
# 添加一等奖的获奖名单展示组件 Listbox
self.target_1 = tkinter.Listbox(self.root,height=5,font=myFont,bg="lemonchiffon",bd=0,fg="olive")
# 将展示组件添加到界面中
self.target_1.place(x=1080, y=240, width=70, height=130)
添加总名单的展示区域以及重设按钮:
self.source = tkinter.Text(self.root,bg="navajowhite",fg="dimgray")
self.source.place(x=100, y=250, width=300, height=290)
self.source.insert(tkinter.END,("、").join(self.data))
self.btn_reset = tkinter.Button(self.root, text = '重启', command = self.reset ,bg='gold')
self.btn_reset.place(x=180, y=550, width=100, height=50)
经过上述代码的布局,最初界面效果如下图:
进一步绑定并对应好数据,便可实现基本功能。
界面优化
首先是布局,两个思路:其一是在背景图上做文章,相应位置添加装饰元素;其二是 tkinter 组件上下功夫,优化组件展示样式或者替换成更美观的样式。
说实话,写功能代码的时间和此部分界面优化的时间比起来真的小巫见大巫了。首先并不清楚能不能实现某种样式,其次 tkinter 自己也不熟,好多对组件都是自己在 Photoshop 中定位并加工处理的。
背景图优化
背景图方面,添加了图中的小鹿,原图是这样的:
通过 PS 将其抠图放到背景图中当作“摇奖展示台”。
同时背景图添加公司名称、活动作为标题,
并在
左侧为
总名单展示区域添加边框。最终效果如下:
组件优化
最初的想法是,按钮不好看,尝试隐藏按钮保留功能,尝试无果。最终方案是,将 Button 组件换成 Label 组件,在 Label 中展示相应位置的背景图,通过 bind 绑定鼠标点击事件。换句话说,我将红鼻子区域的图片当成一个 Label 置于界面中并绑定抽奖动作,那么一点击红鼻子就会开启/停止抽奖。
所有用来替换 Button 组件的 Label 图片如上:
-
nose.png 即红鼻子局部图,对应 “开始/停止”;
-
001.png 即 1 号金标图,对应 “一等奖”;
-
002.png 即 2 号银标图,对应 “二等奖”;
-
003.png 即 3 号铜标图,对应 “三等奖”;
-
reset.png 为 "Let's go!" 局部图,对应 “重启”
对于展现获奖名单的 Listbox 组件,利用其每项占一行的特点,通过调整其组件大小和颜色,稍作优化,未做大改。对于被展现名字的处理,我采用的策略是检测到两个字的名字就给其中间加个中文空格,这样所有名字都会转化为三个中文字符,方便统一样式处理。
temp = random.choice(self.data)
if len(temp) == 2: