【Python小项目之Tkinter应用】Python的GUI库Tkinter实现随机点名工具或抽奖工具并封装成.exe可执行文件

news2024/11/27 2:31:08

文章目录

  • 一、项目背景
  • 二、需求分析
    • UI界面设计如下:
    • 具体需求如下:
  • 二、实现思路
  • 三、项目关键代码
    • 读取excel中的人员名单
    • 实现随机滚动抽取
    • 主函数中Tkinter的界面相关操作实现
      • 窗口相关
      • 背景图设置
      • 组件相关
    • 完整代码
  • 四、将程序封装成.exe可执行文件
    • 将代码转换成.py文件
  • 五、总结与拓展
    • 总结:
    • 拓展:

一、项目背景

受朋友所托,帮他在公司年会活动上做一个点名抽奖的小工具。经过沟通后,他发给我一个人员名单表格,是xlsx格式的excel工作表,并大概设计了一下抽奖工具的界面以及相关要求。话不多说,马上开始项目流程。

二、需求分析

客户需求总结如下:

UI界面设计如下:

在这里插入图片描述
这也是最终我们工具的实现效果,其中包含工具标题“秋夜派对”、一张喜庆的背景图、活动标题“谁是幸运儿?”表示抽奖、抽取人数输入栏、中间被抽取人显示区域、以及开始和结束按钮。

具体需求如下:

  • 要求每个被抽取人出现的概率一致,并随机抽取,而不是按名单顺序滚动
  • 对于滚动出现的名字,要求滚动速度达到肉眼看不出人名的效果,避免操作员根据人名点击,造成抽奖不是完全随机
  • 界面美观,组件居中,并且足够大,因为要在活动的投影上放映。
  • 保证可移植性,封装成应用程序,在任何PC机器上都可以无差错运行。

二、实现思路

针对本项目的客户要求,项目类型为面向客户的UI界面可视化问题,并与相关组件发生事件触发以实现目的。具体实现相关思路如下:

1. 本项目使用Python编写,小项目使用Python的GUI库Tkinter来编写比较方便直观。
2. 客户端给的输入为人员名单,是xlsx文件。那么我们需要应用openpyxl库来读取表格中的数据。
3. 实现名单中人员随机滚动需要用到random库以及tk中的after函数来实现给定时间调用递归函数,以此来达到控制滚动速度的目的。
4. 对于Tk中的窗口背景,还需要PIL库来导入图片。
5. 使用pyinstaller将程序封装成.exe可执行文件,相关依赖也要一起封装

其他具体的实现细节详见代码部分。

三、项目关键代码

读取excel中的人员名单

打开excel文件,观察到人员名单如下:
在这里插入图片描述
可以看到,人员名单是在sheet1中的第二行第二列(B列)开始的,第一列为人员序号,第一行为每项属性标识,这里只单拿出来第二列作为例子,人名是我随机生成的,如有雷同,纯属巧合!
观察过名单格式后,我们编写读取名单函数

#获取excel中的名单
def getPeopleList():
    #读取已有工作簿:路径,data_only为True表示计算excel中公式的值,本例没用,因为都是人名
    workbook = openpyxl.load_workbook("./people2.xlsx", data_only=True)
    list = [] #存储人名列表
    sheet = workbook[workbook.sheetnames[0]] #得到sheet1,excel第一页
    #按行读取,从表格第二行开始读取,直到最后,名字从第二行开始
    for row in range(2,sheet.max_row+1):
        if sheet.cell(row=row,column=2).value == "": #如果第二列为空则跳到下一行
            continue
        else:
            list.append(sheet.cell(row=row,column=2).value) #第二列不为空则添加到list中
    return list #返回得到的名字列表

其中,根据表格数据中的不同,可以改变相关的数值。比如文件名路径、不同sheet、从第几列读取,可以改变代码中相应的值,如果从第二行第一列开始,就将其中的column改为=1。

实现随机滚动抽取

这里我们用两个函数来实现,一个是最底层的randomResult来根据list名单实现随机结果,并传递给窗口。全局变量is_run初始设置为False。另一个是开始按钮的事件触发函数randomRun,这里我们帮朋友多做一个需求,那就是可以同时抽取多人。虽然也可以一个一个抽取,但是如果有其它游戏环节需要分组抽人,那么一组一组抽取显然比一个一个抽取效率要高。具体函数实现如下:

# 随机滚动得到所抽取的人数,递归
def randomResult(list,num):
    global is_run
    result = random.sample(list, num) #使结果随机
    var.set(result) #将随机结果传给窗口,并在窗口中显示随机结果
    if is_run:
        # 在给定时间后调用函数一次
        # 参数:毫秒,需要调用的函数,函数的参数,函数参数
        window.after(30, randomResult, list, num) #第一个参数控制速度,越小滚动越快

# 点击开始按钮触发的函数
def randomRun(list):
    global is_run
    if is_run:#此部分用于避免多次点击开始造成程序重复执行的错误
        return
    is_run = True
    num = insert_point()
    #如果输入人数不为空,则调用randomResult
    if num:
        num = int(num)
        randomResult(list,num)
    else:
        is_run = False

主函数中Tkinter的界面相关操作实现

在主函数中我们实现窗口的UI设计,各种组件布局等,包括背景图实现、label组件、按钮button组件、输入框entry组件等。比如我们要实现前文中UI界面的效果,简单举例如下:

窗口相关

from tkinter import *

if __name__ == '__main__': #主程序
    #window = Toplevel()

    window = Tk() #初始化TK窗口
    # 设定窗口大小
    # 格式:w*h±x±y
    # wh窗口宽高,xy窗口显示位置横纵坐标,加减表示正方向或负方向,左上角为原点,下和右是正方向
    window.geometry('1024x648+250+150') 
    window.resizable(0,0) #将窗口设置为不可拉伸,否则不同分辨率屏幕组件位置会出问题
    window.title('8.25秋夜派对') #设置窗口命名

背景图设置

这里我们使用tk的Canvas画布,将所给图像放在画布上,这里要注意层级关系,不然背景图可能会变成前景图挡住其他组件。背景图我这里是image2.jpg。

import tkinter
from PIL import Image,ImageTk

# 设置背景图
canvas_window = tkinter.Canvas(window,width=1024,height=648)
im = Image.open('./image2.jpg').resize((1024,648))
im_window = ImageTk.PhotoImage(im, master=window)
    
# 前两个参数是图像中心在画布中的位置,那么一半的话正好图像与窗口大小相同
canvas_window.create_image(1024//2,648//2,image=im_window)
canvas_window.pack(fill=BOTH,expand=YES)

由于我们设置了画布大小与窗口大小一致,所以读取图片时要将图片resize成画布大小,并将图像中心放置在画布中心,以此控制背景图完美契合窗口大小。这段代码可以作为tk窗口设置图像相关的模板范例。

组件相关

大标题我们用Label组件实现,使用place布局方式灵活的将其放置在我们预想的位置,由于Label组件有填充区域,所以将组件背景设置为透明,避免它和背景图直接有色彩不美观。

noteLable1 = Label(text="谁是幸运儿?",font='微软雅黑, 60')
noteLable1.place(anchor=NW,x=280,y=100)
noteLable1.config(bg=window["bg"])

第二行输入框我们用Label和entry组件实现,注意设置相同高度即可

noteLable = Label(text="请输入人数:",font='微软雅黑, 25')
noteLable.place(anchor=NW, x=270, y=230)
input = Entry(canvas_window, show=None,width=10,font='微软雅黑, 25') #设置一个输入框,用于输入抽奖人数
input.place(anchor=NW, x=570, y=230)

开始和结束按钮组件
这里比较重要的是按钮触发事件实现,在Button方法里command用lambda范式的方式执行按钮触发函数,比如开始按钮触发前文的randomRun函数实现名单随机滚动,停止按钮触发finalResult实现暂停功能并显示抽取结果。

startBt = Button(text="开始", command=lambda: randomRun(list=list),font='微软雅黑, 25') #开始按钮
confirmBt = Button(text="结束", command=lambda: finalResult(),font='微软雅黑, 25') #停止按钮
    
#pack()按钮放置位置
#布局排列:pack,grid,place
#不建议用place,虽然place可以精确组件位置,但是在不同电脑不同分辨率上会有问题
#pack可以选择参数fill填充,expand=True按照窗口大小伸缩
#grid的自适应性也比较强,有configure方法实现frame大小扩充
startBt.place(anchor=NW, x=350, y=500)
confirmBt.place(anchor=NW, x=550, y=500)

完整代码

本项目完整代码如下:

from tkinter import *
import tkinter
import tkinter.messagebox
import random
import openpyxl
from PIL import Image,ImageTk

is_run = False

#获取excel中的名单
def getPeopleList():
    #读取已有工作簿:路径,data_only为True表示计算excel中公式的值,本例没用,因为都是人名
    workbook = openpyxl.load_workbook("./people2.xlsx", data_only=True)
    list = [] #存储人名列表
    sheet = workbook[workbook.sheetnames[0]] #得到sheet1,excel第一页
    #按行读取,从表格第二行开始读取,直到最后,名字从第二行开始
    for row in range(2,sheet.max_row+1):
        if sheet.cell(row=row,column=2).value == "": #如果第二列为空则跳到下一行
            continue
        else:
            list.append(sheet.cell(row=row,column=2).value) #第二列不为空则添加到list中
    return list #返回得到的名字列表
    

# 点击开始按钮触发的函数
def randomRun(list):
    global is_run
    if is_run:#此部分用于避免多次点击开始造成程序重复执行的错误
        return
    is_run = True
    num = insert_point()
    #如果输入人数不为空,则调用randomResult
    if num:
        num = int(num)
        randomResult(list,num)
    else:
        is_run = False

# 获取输入人数
def insert_point():
     var = input.get() #获取输入的信息
     return var


# 随机滚动得到所抽取的人数,递归
def randomResult(list,num):
    global is_run
    result = random.sample(list, num) #使结果随机
    var.set(result) #将随机结果传给窗口,并在窗口中显示随机结果
    if is_run:
        # 在给定时间后调用函数一次
        # 参数:毫秒,需要调用的函数,函数的参数,函数参数
        window.after(30, randomResult, list, num) #第一个参数控制速度,越小滚动越快

#点击结束时触发,is_run置为false,使递归函数停止
def finalResult():
    global is_run
    is_run = False
 

# 打开指定图片文件,缩放指定尺寸
def get_image(filename,width,height):
    im = Image.open(filename).resize((width,height))
    return ImageTk.PhotoImage(im, master=window)


if __name__ == '__main__': #主程序
    #window = Toplevel()

    window = Tk() #初始化TK窗口
    
    # 设定窗口大小
    # 格式:w*h±x±y
    # wh窗口宽高,xy窗口显示位置横纵坐标,加减表示正方向或负方向,左上角为原点,下和右是正方向
    #window.geometry('1024x648+250+150') 
    window.resizable(0,0) #将窗口设置为不可拉伸,否则不同分辨率屏幕组件位置会出问题
    window.title('8.25秋夜派对')
    list = getPeopleList() #从peopleList.xlsx中获取待抽奖人员名单

    # 设置背景图
    canvas_window = tkinter.Canvas(window,width=1024,height=648)
    #canvas_window = tkinter.Canvas(window,width=1920,height = 1080)
    im = Image.open('./image2.jpg').resize((1024,648))
    #im = Image.open('./image2.jpg').resize((1920,1080))
    im_window = ImageTk.PhotoImage(im, master=window)
    
    # 前两个参数是图像中心在画布中的位置,那么一半的话正好图像与窗口大小相同
    canvas_window.create_image(1024//2,648//2,image=im_window)
    #canvas_window.create_image(0,0,image=im_window)
    canvas_window.pack(fill=BOTH,expand=YES)
    
    var = StringVar() #初始化一个字符串变量,用于滚动显示抽奖结果

    noteLable1 = Label(text="谁是幸运儿?",font='微软雅黑, 60')
    #noteLable1.pack(fill=)
    noteLable1.place(anchor=NW,x=280,y=100)
    noteLable1.config(bg=window["bg"])

    noteLable = Label(text="请输入人数:",font='微软雅黑, 25')
    noteLable.place(anchor=NW, x=270, y=230)
    input = Entry(canvas_window, show=None,width=10,font='微软雅黑, 25') #设置一个输入框,用于输入抽奖人数
    input.place(anchor=NW, x=570, y=230)

    resultLable = Label(textvariable=var,font='微软雅黑, 100',foreground='RED') #设置一个显示抽奖结果的文本框
    resultLable.place(anchor=NW,x=300,y=320)

    startBt = Button(text="开始", command=lambda: randomRun(list=list),font='微软雅黑, 25') #开始按钮
    confirmBt = Button(text="结束", command=lambda: finalResult(),font='微软雅黑, 25') #停止按钮
    
    #pack()按钮放置位置
    #布局排列:pack,grid,place
    #不建议用place,虽然place可以精确组件位置,但是在不同电脑不同分辨率上会有问题
    #pack可以选择参数fill填充,expand=True按照窗口大小伸缩
    #grid的自适应性也比较强,有configure方法实现frame大小扩充
    startBt.place(anchor=NW, x=350, y=500)
    confirmBt.place(anchor=NW, x=550, y=500)

    window.mainloop() #渲染窗口,一直显示

运行效果展示:
请添加图片描述
至此,我们实现了客户的需求,完成了随机点名/抽奖小工具的实现。

四、将程序封装成.exe可执行文件

将代码转换成.py文件

由于我是用jupyter notebook写的代码,所以要将.ipynb文件转换为.py文件。具体方法是将待转换的代码段放到要给单独的.ipynb文件下,该文件下不要有其他cell。
在这里插入图片描述
转换成.py文件后,我们配置打包的封装环境。
首先安装pyinstaller。

pip install pyinstaller

然后

pip list

看看库列表中是否有pyinstaller。若有,则安装成功。找到Pyinstaller.exe的位置
在这里插入图片描述
如图所示,第六行有from pyinstaller的目录,找到该目录的上一级目录,pyinstaller.exe的位置一般在Scripts中,我的目录是D:\Anaconda\Scripts。或者直接搜索pyinstaller.exe的位置即可。
接下来跳转到Scripts目录下:

(base) C:\Users\yzcong>D:

(base) D:\>cd :\Anaconda\Scripts

在该目录下执行:

pyinstaller -F D:\test\choujiang.py

-F是只生成一个.exe文件,-D是生成所有相关的依赖,最后是要打包的py文件的目录。
这里可能会出现提示,让我们一处pathlib,因为pyinstaller和它不兼容。
在这里插入图片描述
按照要求执行:

conda remove pathlib

conda命令可能比较慢,多等一会。出现
在这里插入图片描述wu
输入y继续执行,等结束后重新执行:

pyinstaller -F D:\test\choujiang.py

会进入程序打包阶段,打包完成后在当前的Scripts目录下会新生成个list文件夹,里面就是exe文件。
但是,
上述方法只是将一个简单的.py文件打包成.exe,但如果程序中有相关的依赖,那么这种方式就不适用。
本例运行exe后会产生错误:
在这里插入图片描述
因为我们使用了openpyxl库,而我们却没有将其一同打包。所以,将打包命令改为:

pyinstaller -F D:\test\choujiang.py --hidden-import openpyxl.cell._writer

缺什么补什么,将隐藏的依赖openpyxl.cell._writer包括进去即可。打包完成后提示如下:
在这里插入图片描述
注意要将人员表格和背景图与生成的.exe放在同一个目录下:
在这里插入图片描述
运行后测试成功!至此我们完成了本项目的实现。

五、总结与拓展

总结:

本项目实际上还存在许多问题:

  1. 由于每个组件都是使用place精确布局的,这就导致组件没有办法自适应窗口。也就是说,本例只能设置固定窗口大小。如果设置窗口大小可变,那么组件会错位。后续可能优化组件自适应窗口大小变化,现已能实现背景图随着窗口大小变化。
  2. 同上所述,中间显示滚动名称的字符串位置有限,如果同时抽取多个人,那么由于place精确位置的原因,多个人会显示不全。
  3. 封装后的.exe文件很大,可以考虑优化压缩大小,采用虚拟封装。

拓展:

  1. 窗口组件可以进一步优化,实现更丰富的功能。目前研究组件随窗口自适应大小变化,实现思路是当窗口分辨率改变时获取到大小,然后其他组件和背景图随着比例变化而更新。
  2. 打包方式可以拓展,除了pyinstaller之外,.py文件还有其他更高效的打包方式,值得拓展学习
  3. 本项目拓展到的知识点总结:
  • 背景图随窗口自适应改变大小
  • 各种组件的测试
  • 点击按钮实现界面切换
  • 获取窗口大小,屏幕真实分辨率和缩放分辨率
  • .py文件的其他打包方法(有没有可能用tkinter写一个基于pyinstaller的打包界面?)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/979850.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++基础】6. 常量

文章目录 【 1. 常量的分类 】1.1 整型常量1.2 浮点常量1.3 字符常量1.4 字符串常量1.5 布尔常量 【 2. 常量的定义 】2.1 #define 预处理器2.2 const 关键字 常量 是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。常量可以是任何的基本数…

Linux C进程间通信(IPC)

概述 每个进程有独立的进程空间: 好处————安全 缺点:开销大(独立的地址空间);进程的通信更加困难(对其他进程的操作开销也大) 广义上的进程间通信: A进程写给文件/数据库&am…

重拾html5

新增的position: sticky; 基于用户的滚动位置来定位,粘性定位的元素是依赖于用户的滚动,在 position:relative 与 position:fixed 定位之间切换。ie15以上的低版本不支持,Safari 需要使用 -webkit- prefix; vertical-align: midd…

ToBeWritten之数据源

也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 转移发布平台通知:将不再在CSDN博客发布新文章,敬…

【你问我答】Unity实现类似DNF地下城勇士的2D人物移动跳跃

文章目录 前言人物节点创建实现简单移动实现攻击效果实现跳跃人物移动跳跃完整代码人物脚底的影子效果最终运行效果源码参考完结 前言 之前有个小伙伴微信找我,想做一个类似DNF地下城勇士的移动跳跃功能,特别是关于2d的跳跃,之前还不是很有头…

Lua03——开发环境搭建

1 安装开发插件 在 idea 或 vscode 中安装 lua 的开发插件 EmmyLua 2 创建工程 在 idea 中创建一个新的工程 工程的类型选择 lua 输入工程名及目标目录 在工程结构的SDK中设置lua在本地安装目录 在工程结构的modules中选择 lua 3 编写第一个lua程序 在工程下添加程序包&#…

C# OpenVinoSharp PP-TinyPose 人体姿态识别

效果 项目 部分代码 using OpenCvSharp; using OpenCvSharp.Extensions; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms;name…

【代码随想录day24】不同的二叉搜索树

题目 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n 3 输出:5示例 2: 输入:n 1 输出&#xf…

TOWE模块化积木式定制PDU的应用优势

随着计算机网络技术发展,服务器、交换机、各种电子设备等关键设备的需求也日益增加,其承担的业务越来越关键,对设备所处的环境(如机房、机柜等)要求也越高,所有参与关键设备运行的设施都必须具有高可靠性与…

给苹果手机相册上锁,有3种方法!

手机跟个人的联系越来越密切,总有些不想让别人看到的图片。如果你使用的恰好是苹果手机,想要隐藏相册里的图片,要怎么做?本篇教大家3个方法。 方法1 将iOS更新至16.0版本,打开苹果手机的【设置】,点击【照…

Stable Diffuse AI 绘画 之 ControlNet 插件及其对应模型的下载安装

Stable Diffuse AI 绘画 之 ControlNet 插件及其对应模型的下载安装 目录 Stable Diffuse AI 绘画 之 ControlNet 插件及其对应模型的下载安装 一、简单介绍 二、ControlNet 插件下载安装 三、ControlNet 插件模型下载安装 四、ControlNet 插件其他的下载安装方式 五、Co…

原来Linux这么牛:称霸全球超级电脑 500 强!

还记得之前《全球超级电脑 500 强,中国拿走冠亚军》新闻?虽然昔日超级电脑强权的美国在超级计算机竞赛中落后,但不管哪国打造的超级电脑,还有一件事情值得留意喔──几乎全部都是执行以 Linux 为基础的操作系统(注&…

视频号小店怎么进优选联盟?聊下视频号店铺的选品细节,建议收藏

我是王路飞。 视频号小店可能还有很多人不太了解,但是你要知道,红利与机会从来不会消失。 它只会悄悄的转移,转移到你的认知以外,转移到那些新的平台,转移到那些被人忽略的事情里面。 而视频号小店就是目前除了抖音…

【PowerQuery】连接组的复制与粘贴

在实际的应用场景中,单一连接的场景非常少见。通常存在着两个或者两个以上的PowerQuery数据源。在这类场景下一个一个的复制数据源效率非常低下,是否存在更加有效率的数据源复制方式呢?接下来分享的连接组功能就是这样的概念。在PowerQuery中…

动手实践:从栈帧看字节码是如何在 JVM 中进行流转的

Java全能学习面试指南:https://www.javaxiaobear.cn/ 前面我们提到,类的初始化发生在类加载阶段,那对象都有哪些创建方式呢?除了我们常用的 new,还有下面这些方式: 使用 Class 的 newInstance 方法。使用…

【C++】—— 特殊类设计

目录 序言 (一)设计一个不能被拷贝的类 (二)设计一个只能在堆上创建对象的类 (三)设计一个只能在栈上创建对象的类 (四)设计一个不能被继承的类 总结 序言 特殊类设计是指在面…

AR产业变革中的“关键先生”和“关键力量”

今年6月的WWDC大会上,苹果发布了头显产品Vision Pro,苹果CEO库克形容它: 开启了空间计算时代。 AR产业曾红极一时,但因为一些技术硬伤又减弱了声量,整个产业在起伏中前行。必须承认,这次苹果发布Vision P…

百度文心一言可以接入微信小程序啦!

文心一言(英文名:ERNIE Bot)是百度全新一代知识增强大语言模型,文心大模型家族的新成员,能够与人对话互动,回答问题,协助创作,高效便捷地帮助人们获取信息、知识和灵感 …

python学习之【深拷贝】

#我的编程语言学习笔记# 前言 上一篇文章python学习之【浅拷贝】 学习了python中的浅拷贝相关内容,这篇文章接着学习深拷贝。 简单回顾 浅拷贝只拷贝浅层元素,深层元素的内存地址不改变 ;当对拷贝产生的新的对象的浅层元素进行更改时&…

擎创技术流 | 深入浅出运维可观测工具(三):eBPF如何兼容多架构模式性能管理

嗨~又见面了大家! 之前给大家分享过2篇eBPF技术干货,后台收到的反馈还挺好的,以至于总有朋友过来催更这一系列,这不第3篇在大家的千呼万唤下终于出来了。 新来的朋友点这里,键回看eBPF精彩技术贴,别忘了随…