简介:PPT抽奖程序-名单抽取是一份面向活动组织者、教师及VBA初学者的轻量演示文稿工具,用于在课堂、年会或联欢会中完成随机名单抽取。资源包仅含1个PPT文件,约51KB,体积小巧、便于拷贝与现场运行;核心由演示界面和VBA宏代码构成,提供名单输入、开始/停止按钮、随机索引生成及结果实时显示等基础功能。代码中通过Sleep控制抽取节奏,以Split按空格拆分参与名单,并用Rnd实现随机滚动,结构直观,适合作为PPT宏编程的入门案例。已有3398人学习,说明其在小型抽奖场景中具有一定参考价值。读者可据此理解随机抽取逻辑,并在此基础上调整名单分隔符、抽取间隔或扩展动画、音效等效果,快速改造为符合自身活动需求的抽奖页面。
1. 年会前一周被拉去救场:几十行VBA顶下的大屏名单抽取
上个月某公司的行政在年会前一周找到我,说大屏已经排好版,就差一个“名字滚起来、按一下停”的环节,不能装新软件、不能联网、操作的人只会点鼠标。这种需求最后往往落到 PPT 抽奖程序上:把参与名单粘进一个文本框,点按钮名字开始滚,再点一下停在谁身上就是谁,整个过程都在演示文稿里完成,不用切窗口。它适合年会、课堂点名、直播间互动这类当场要出结果、事后要能复现的场合;不适合需要留审计日志、需要可验证随机源的正式摇号。下面这份带 VBA 宏的名单抽取实现,代码不到四十行,但坑几乎全在参数和运行环境上,我按动手顺序把它拆一遍。
2. 先把宏跑起来:信任中心设置、控件搭台与pptm保存
拿到一份带宏的演示文稿,第一件事不是看代码,是让它能执行。很多人卡在这里就放弃了,以为是代码坏了,其实是安全策略在拦。这一章把“能跑起来”这条链路上的三个环节走一遍:宏开关、控件布局、保存格式。
2.1 宏为什么默认被拦:信任中心里那道开关
VBA 宏本质是一段可以被文档携带、打开即执行的本地代码,所以办公软件默认对它做了拦截。旧版本在“工具 → 宏 → 安全性”里选“低”就能放行,新版本统一挪进了信任中心,路径是“文件 → 选项 → 信任中心 → 信任中心设置 → 宏设置”。原始说明里那句“把宏安全性调成低级”,对应的就是这一步,只是不同年份的界面措辞不一样。
三种常见档位的行为差别值得记清楚,选错档位会出现“代码明明在,就是没反应”的错觉:
| 档位 | 打开带宏文件时的行为 | 适用场景 |
|---|---|---|
| 禁用所有宏,并发出通知 | 顶部出现黄色提示条,需手动点“启用内容” | 日常默认,推荐 |
| 禁用所有宏,不发出通知 | 静默拦截,代码完全不执行 | 不要用,排查时极易误判 |
| 启用所有宏 | 直接执行 | 只在离线、来源可信的单机上临时使用 |
我更推荐的做法是不动全局开关,而是把演示文稿所在目录加进“受信任位置”。路径在同一页面下的“受信任位置 → 添加新位置”,勾上“同时信任此位置的子文件夹”。这样这份文件每次打开都直接放行,其他来路不明的文档仍然被拦,比把全局调到最低安全得多。
还有一种比安全级别更隐蔽的拦截:文件从网络或聊天工具传过来时,系统会给它打上“来自其他计算机”的标记。表现是安全设置已经放行,打开还是提示宏被禁用。解决方式是关掉文件,右键属性,在常规页底部勾选“解除锁定”,再打开。这个操作我在现场遇到过两次,都属于“设置全对但就是不跑”的典型。
提示:调试阶段可以把安全级别临时放低,交付给使用方之前一定改回“禁用所有宏并发出通知”,再把文件目录加进受信任位置。现场机器不归你管,别指望别人记得改回来。
2.2 控件怎么摆:两个TextBox、一个CommandButton
代码里写死了TextBox1、TextBox2、CommandButton1三个名字,控件名对不上,点击按钮就是没反应,而且不报错。所以先开“开发工具”选项卡:文件 → 选项 → 自定义功能区,右侧主选项卡列表里勾上“开发工具”。
然后在目标幻灯片上插入 ActiveX 控件:开发工具 → 控件 → 文本框,画两个;再点命令按钮,画一个。画完必须点一次“设计模式”旁边的退出,否则控件处于设计态,放映时点不动。三个控件的属性按下面这张表改,改完再进代码:
| 控件 | 属性 | 建议值 | 说明 |
|---|---|---|---|
| TextBox1 | Name | TextBox1 | 参与名单输入框,不能改名 |
| TextBox1 | MultiLine | True | 名单长时自动换行,便于核对 |
| TextBox1 | WordWrap | True | 与上一项配合 |
| TextBox2 | Name | TextBox2 | 滚动结果展示框 |
| TextBox2 | Font | 黑体 / 72 以上 | 大屏可读,具体字号见 4.4 |
| TextBox2 | TextAlign | 2 - fmTextAlignCenter | 居中更像抽奖 |
| CommandButton1 | Name | CommandButton1 | 开始/停止按钮,不能改名 |
| CommandButton1 | Caption | 开始 | 代码靠这个字符串判断状态 |
这里有个选型问题值得说清楚:为什么用 ActiveX 控件而不是“形状 + 动作”?形状加动作只能触发一个无参数的宏,做不了“同一个按钮在开始和停止之间切换状态”,也拿不到文本框里的实时内容。ActiveX 控件挂在幻灯片上,能响应 Click 和 Change 事件,能读写 Text 属性,这才是这套抽奖逻辑成立的前提。
代价是 ActiveX 控件对运行环境更敏感:换台机器、换个版本、甚至换台不同缩放比例的显示器,控件位置都可能偏。稳妥的做法是把两个文本框和按钮的位置、尺寸记在一张纸上(选中控件后看“位置和大小”对话框里的数值),现场出问题时能五分钟内复原。
2.3 存成什么格式:.pptm才是带宏的家
代码写完,第一次保存就会翻车:默认的.pptx格式不支持宏,保存时弹一个“无法保存 Visual Basic 宏”的对话框,点“是”就把代码全删了。血泪经验是第一次就“另存为”,格式选“启用宏的演示文稿(*.pptm)”。如果现场只需要放映不需要编辑,选.ppsm也行,打开即放映。
存成 pptm 之后还有两个日常习惯要养成:一是每次改完代码手动按一次 Ctrl+S,VBA 编辑器里的改动不会随幻灯片自动落盘;二是把工程导出备份,VBA 编辑器 → 文件 → 导出文件,存成.bas,几十行的东西丢了重写很烦,但更烦的是现场丢。
放映前我自己会走一遍这四步:退出设计模式 → 从头放映到抽奖页 → 用真实名单点一次开始、点一次停 → 按 Esc 退出后再放一次,确认控件状态复位。第三步很多人省掉,结果到了台上,按钮文字还停在上次测试的“停”,第一次点击变成了停止。
3. 逐行拆CQ_do:Split切名单、Rnd取索引、DoEvents让界面活着
3.1 Sleep的声明:32位与64位Office的两套写法
原始代码第一行是Private Declare Sub Sleep Lib "kernel32"。这段在 32 位 Office 上没问题,在 64 位 Office 上编译就报“若要在 64 位系统上使用,则必须更新此项目中的代码”。原因是 Declare 语句要把参数和返回值的指针宽度讲清楚,64 位下必须加PtrSafe。
兼容写法用条件编译常量VBA7判断,一次写好两边都能跑:
#If VBA7 Then ' Office 2010 及以后:64 位必须加 PtrSafe Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) #Else ' Office 2007 及以前:不支持 PtrSafe 关键字 Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) #End IfdwMilliseconds单位是毫秒,传 30 就是停 30 毫秒。传 0 表示立即让出当前时间片,效果类似DoEvents,但不刷新界面。这个 API 在抽奖里的作用是控制滚动节奏:数值越小名字跳得越快,越大越像老式摇奖机在慢慢转。
这里有个容易被忽略的边界:Sleep 是阻塞调用,睡多久界面就冻多久。设成 30 毫秒几乎无感,设成 300 毫秒用户会以为程序卡死,连点几下按钮,反而触发多次状态切换。所以这个值不要超过 100,理由在 3.4 会再展开。
3.2 名单进数组:Split的四个参数与空格陷阱
核心那句是arrRM = Split(Me.TextBox1, " ", -1, 1)。Split 的完整签名是Split(expression, delimiter, limit, compare):
expression传Me.TextBox1,直接写控件名会取默认属性 Text,等价于Me.TextBox1.Text,但显式写.Text更清楚;delimiter是分隔符,这里是半角空格。原注释提醒可以换成";",换的时候注意中文分号;和英文分号;是两个不同字符;limit = -1表示不限制拆分出的元素个数,全部切开;如果传 3,就只切出前 3 段,剩下的整体作为第 3 段,做“多人一组”时能用到;compare = 1是vbTextCompare,文本比较。对空格分隔没有实际影响,保留即可。
真正的坑在数据本身。Split 是纯粹的字符切分,不做任何清洗,所以:
- 名单末尾多一个空格,
"张三 李四 王五 "会被切成 4 个元素,最后一个空字符串也被收进数组,抽到它时 TextBox2 显示空白,看起来像“程序抽了个寂寞”; - 名字之间用两个空格,中间会多出一个空元素;
- 从 Excel 或聊天记录里复制过来的名单,分隔符经常是中文全角空格、制表符、换行符,甚至混着中英文逗号,只按半角空格切,整段名单会变成一个大元素,怎么抽都是同一串字。
所以入口处加一道清洗,把各种分隔符统一成单空格,再切:
' 把各种来源的名单压成“单空格分隔、无首尾空格”的规范串 Private Function CleanList(ByVal raw As String) As String Dim s As String s = Replace(raw, vbCrLf, " ") ' 换行 s = Replace(s, vbCr, " ") s = Replace(s, vbLf, " ") s = Replace(s, vbTab, " ") ' 制表符(Excel 粘贴常见) s = Replace(s, ChrW(&H3000), " ") ' 全角空格 s = Replace(s, ChrW(&HFF0C), " ") ' 全角逗号 s = Replace(s, ChrW(&HFF1B), " ") ' 全角分号 s = Replace(s, ",", " ") s = Replace(s, ";", " ") Do While InStr(s, " ") > 0 s = Replace(s, " ", " ") ' 连续空格压成一个 Loop CleanList = Trim(s) ' 去掉首尾空格 End Function清洗放在点击开始时执行一次,不要每帧都跑,几百人的名单每 30 毫秒替换十几遍字符串,界面会明显发涩。清洗完再Split,数组里就不会有空元素,UBound才是真实人数。
3.3 随机索引:Int((UBound+1)*Rnd)覆盖了谁,Randomize又漏在哪
取索引那句是I = Int(((UBound(arrRM) + 1) * Rnd) + 0)。拆开看:Rnd返回[0, 1)区间的单精度浮点数,UBound + 1正好是元素个数 N,相乘得到[0, N),Int向下取整得到0到N-1的整数,正好覆盖数组全部下标,不会越界。那个+ 0是多余的,删掉不影响结果。
这个写法在数学上是对的,但漏了初始化。VBA 的Rnd在没有调用Randomize时,每次程序启动都用同一个默认种子,也就是说:关掉演示文稿重新打开,第一次点开始滚出来的序列和上次完全一样。如果第一下就停,中奖的人每次都是同一个。这在彩排时特别容易露馅,某高校的社团彩排了三次,三次都抽到同一个学号,主办方脸色都变了。
修法是在第一次抽之前播种一次:
Private seeded As Boolean ' 只在首次抽取前播种,避免高频调用导致种子重复 Private Sub EnsureSeed() If Not seeded Then Randomize ' 用系统计时器作为新种子 seeded = True End If End Sub这里要多说一句为什么不在每帧都调Randomize Timer。Timer返回的是当天已过秒数,单精度浮点,分辨率并不高;在 30 毫秒一帧的循环里连续调用,很可能拿到同一个值,等于没播种。常见的错误改法就是把它写进循环体内,看着改了,问题照旧。
顺带说明Rnd的几种参数形态,改代码时别踩:Rnd不带参数返回序列里的下一个数;Rnd(0)返回上一次生成的数,重复调用得到同一个值;Rnd(负数)用这个负数当种子重新开始序列——这个特性偶尔被用来做“可复现的抽奖”,但正式场合不建议,容易被质疑。
另外,Int(N * Rnd)在 N 较小时分布偏差可以忽略,几百人的名单完全够用。真要较真,可以用WorksheetFunction.RandBetween(0, N-1),但那要依赖表格对象,还得处理引用,现场部署反而更脆。
3.4 F标志位、DoEvents与30毫秒的节奏
滚动循环长这样:
F = 0 Do While True Sleep 30 I = Int(((UBound(arrRM) + 1) * Rnd) + 0) Me.TextBox2.Text = arrRM(I) If F = 1 Then Exit Do ' 由停止按钮把 F 置 1,下一帧退出 DoEvents ' 把控制权交回消息循环 Loop四个部件的分工要说清楚:
F是跨过程的停止标志,模块级变量。按钮第二次点击时把F置 1,循环在下一轮判断时退出。这是 VBA 里最朴素的“中断一个长循环”的手法,因为 VBA 没有真正的多线程,只能用标志位加DoEvents模拟协作式中断。DoEvents是整个循环的命门。没有它,VBA 会独占执行权,界面完全不刷新,TextBox2 的赋值你看不见,按钮点击也进不来,程序看起来死了,直到循环结束才一次性重绘。加上它,每帧把控制权交回系统,界面才能红起来、按钮才点得动。Sleep 30控制节奏。30 毫秒大约每秒 33 帧,视觉上是快速翻动;改成 60 到 80 毫秒会稳重一些,更像摇奖机;低于 10 毫秒帧率上去了但控件重绘跟不上,字会糊成一片,CPU 占用也高。- 退出判断放在赋值之后、
DoEvents之前,是为了保证“停下时屏幕上显示的那个名字”就是最终结果,不会出现点了停、结果又跳了一下的情况。
按钮那一侧的状态机同样简单,但顺序不能错:
Private Sub CommandButton1_Click() If Me.CommandButton1.Caption = "停" Then Me.CommandButton1.Caption = "开始" Call CQ_do("stop") ' 只置标志,不等循环结束 Else Me.CommandButton1.Caption = "停" Call CQ_do("start") ' 进入滚动循环 End If End Sub注意CQ_do("start")是在事件里同步执行的,循环不结束,这个 Click 过程就不会返回。所以“停止”那一次点击进来的其实是嵌套调用——外层还在循环里,内层是新一轮 Click。这能成立,靠的正是DoEvents把消息泵让了出来。理解这一点,才能明白为什么去掉DoEvents后按钮会彻底失灵。
4. 从“能抽”到“敢上台”:去重、多奖项与名单外置的三层改造
原版能跑,但只解决了“一个人抽一次”的场景。真到现场,运营会追加三个需求:抽过的人不能再抽、要分一二三等奖、名单两百多人不想手动粘。这三点改造都不复杂,但每一点都有对应的坑。
4.1 抽过不再抽:用Collection做不放回抽样
数组本身不提供删除能力,用ReDim Preserve反复缩容在几百人规模下性能很难看。常见做法是换Collection,它支持按下标读取和Remove:
Dim pool As Collection ' 用清洗后的名单初始化抽奖池 Private Sub InitPool(ByVal cleaned As String) Dim arr, i As Long Set pool = New Collection arr = Split(cleaned, " ") For i = LBound(arr) To UBound(arr) If Len(Trim(arr(i))) > 0 Then ' 第二个参数是 key,用序号避免同名的人冲突 pool.Add Trim(arr(i)), "k" & i End If Next i End Sub ' 抽一个并从池中移除,池空时返回空串 Private Function PickOne() As String Dim idx As Long If pool Is Nothing Then Exit Function If pool.Count = 0 Then PickOne = "": Exit Function EnsureSeed idx = Int(pool.Count * Rnd) + 1 ' Collection 下标从 1 开始 PickOne = pool(idx) pool.Remove idx End Function两个细节:Collection的下标是 1 起,和数组的 0 起不同,改造时最容易在这里差一位;Add的 key 必须唯一,用真实姓名当 key,遇到同名同姓直接抛错,用序号最稳。
滚动阶段仍然要“看起来在乱跳”,但停止时取的是PickOne的结果。所以循环里用池子随机取下标做视觉滚动,停止后单独调一次PickOne定结果,再把这个结果从池里删掉。视觉和结果分离,逻辑最清楚。
4.2 多奖项分级:先小后大的顺序与名单池的隔离
奖项一多,第一个要决策的是顺序。我的习惯是先抽三等奖、再二等奖、最后一等奖,悬念递进,现场气氛是往上走的;反过来先抽大奖,后面抽小奖时观众基本散了。
第二个决策是名单池要不要共用。规则上有两种:所有奖项共用一个池(一个人只能中一次),或者每个奖项独立池(可以重复中)。年会绝大多数是前者,学术场合的“每人只能被点到一次”也是前者。实现上就是全局一个pool,抽完不重置;如果某个奖项要独立池,就在切换奖项时重新InitPool一次。
按奖项批量抽的写法:
' 一次抽出 n 个不重复的中奖者,结果写回 TextBox2 并用逗号拼接 Private Function PickMany(ByVal n As Long) As String Dim i As Long, one As String, buf As String For i = 1 To n one = PickOne() If Len(one) = 0 Then Exit For ' 池子抽空,提前结束 buf = buf & IIf(Len(buf) = 0, "", "、") & one Next i PickMany = buf End Functionn超过池子剩余人数时不会报错,循环里判空提前退出,返回的串比预期短。这个边界要在彩排时主动试一次,别等现场。
4.3 名单外置:从txt或表格读,还是继续贴文本框
两百人以内的名单,粘进 TextBox1 完全够用,还能现场临时加人。上百人往上,粘贴容易丢行,我倾向外置。最轻的方案是一个同目录的 txt,一行一个名字:
' 从文本文件逐行读入,用空格拼成规范串 Private Function LoadNamesFromFile(ByVal path As String) As String Dim fnum As Integer, line As String, buf As String fnum = FreeFile Open path For Input As #fnum Do While Not EOF(fnum) Line Input #fnum, line line = Trim(line) If Len(line) > 0 Then buf = buf & " " & line Loop Close #fnum LoadNamesFromFile = Trim(buf) End Function调用时用ThisPresentation.Path & "\names.txt"拼路径,跟着文件走,换机器不用改代码。注意Open语句要求路径存在且文件没被 Excel 独占打开,否则抛“文件未找到”或“权限被拒绝”,这两种错误现场都很常见——尤其是名单刚从 Excel 导出、还开着没关。
如果名单本身就在 Excel 里维护,也可以直接读工作簿,但那要引入对表格对象的引用,或者用后期绑定CreateObject("Excel.Application"),启动一个隐藏的表格进程。后者在没装表格软件的机器上会直接失败,年会的公用机什么情况都有,我一般不用。
名单外置后,TextBox1 的角色从“输入框”变成“预览框”,只在初始化时写入清洗后的名单供人核对。这样现场要临时加人,仍然可以在框里补一句再点开始。
4.4 显示层:字号自适应与滚动历史区
大屏上最容易出问题的是长度不一的名字。三个字的名字用 96 号字很饱满,来个八个字的机构名就撑破文本框。按长度分档调字号是最省事的办法:
' 根据文本长度调整结果框字号,避免长名字溢出 Private Sub FitFont(ByVal txt As String) Dim n As Long n = Len(txt) Select Case True Case n <= 2: Me.TextBox2.Font.Size = 96 Case n <= 4: Me.TextBox2.Font.Size = 72 Case n <= 6: Me.TextBox2.Font.Size = 54 Case n <= 10: Me.TextBox2.Font.Size = 36 Case Else: Me.TextBox2.Font.Size = 24 End Select End Sub滚动循环里每帧调一次FitFont,字会随名字长短跳动,反而有节奏感;如果不想要这种跳动,就只在停止时调,滚动阶段固定字号。
历史区用一个TextBox3(多行、只读),每次出结果追加一行。原代码里注释掉的TextBox3就是干这个的,取消注释即可。追加时记得加vbCrLf,并且把光标移到末尾再赋值,否则文本会从顶部开始覆盖。历史区最大的价值不是好看,是出争议时能看到前几轮抽了谁——这类纠纷我在现场见过,有记录和没记录,处理起来是两个难度。
5. 避坑与排查:五个我现场遇到过的事故现场
5.1 按钮点了没反应,代码一行没执行
现象:放映模式下鼠标移到按钮上没变化,点下去毫无反应,也没有报错。
原因通常是三个之一:还在设计模式,控件处于可编辑状态不接收点击;控件被上层形状或图片盖住了,点击落到了图片上;或者宏根本没被放行,代码压根没加载。前两个看鼠标指针形状就能判断,第三个要退回编辑界面看有没有黄色提示条。
解决:先退出设计模式;选中控件用“置于顶层”确认层级;再检查信任中心提示条和文件是否被锁定。如果这些都对,把按钮改成“矩形形状 + 插入动作 → 运行宏”,让宏变成无参数的Public Sub,这条路径对 ActiveX 不友好的环境更稳:
' 无参入口,供形状动作或快捷键调用 Public Sub StartOrStop() If ActiveWindow.Selection.SlideRange(1).Shapes("CommandButton1").OLEFormat.Object.Caption = "停" Then Call CQ_do("stop") Else Call CQ_do("start") End If End Sub5.2 保存之后代码全没了
现象:明明写了代码,第二天打开是空白幻灯片,按钮还在,点了什么都不做。
原因:保存时选了.pptx。这个格式规范上不允许携带宏,另存确认对话框点“是”,VBA 工程就被剥离了。
解决:立刻“另存为 → 启用宏的演示文稿(*.pptm)”。已经丢了的只能从.bas导出备份或者回收站找。我自己的习惯是开工第一件事就另存为 pptm,再开始写代码,顺序反过来迟早翻车。
5.3 抽出来是空的,或者总人数对不上
现象:滚动时偶尔闪过空白,停下时结果框一片白;或者名单明明填了 30 人,抽来抽去只在前面十几个人里转。
原因:名单里有全角空格、制表符、首尾空格,或者从表格粘贴时带了换行。Split只认半角空格,遇到别的分隔符就不切,整段变成一个元素;首尾空格则切出空字符串元素。
解决:入口处统一走一遍 3.2 的CleanList,并且加一句自检,把实际人数显示出来对照:
Dim arrRM As Variant arrRM = Split(CleanList(Me.TextBox1.Text), " ") Me.TextBox2.Text = "共 " & (UBound(arrRM) + 1) & " 人"数字和预期不一致就说明名单还有脏字符,别急着上台。
5.4 每次重新打开,抽奖顺序一模一样
现象:彩排三次,第一次停的人每次都一样,第二第三次也高度重合。
原因:用了Rnd却没调Randomize,默认种子固定,序列可复现。这不是概率问题,是必然。
解决:首次抽取前Randomize一次,用模块级布尔量保证只播种一次。别在循环体内播种,Timer精度不够,反而可能拿到重复种子。
5.5 名单上了三百人,滚动开始卡顿、字发虚
现象:几十人时很流畅,换成三百多人的名单后,滚动明显一顿一顿,CPU 占用飙升,投影上还能看到残影。
原因:每 30 毫秒改一次 TextBox 的 Text,触发一次控件重绘,ActiveX 文本框的重绘开销不小;再加上历史区如果每帧都在拼接长字符串,开销会成倍放大。另外,开了硬件加速的机器上,频繁重绘还可能引起投影输出丢帧。
解决:滚动只更新结果框,历史区只在停止时写一次;把Sleep从 30 调到 50 到 80,肉眼几乎看不出差别但重绘次数少一半;名单超过五百人时,滚动阶段改成从池子里随机取 200 个做视觉循环,停止时才从完整池里抽真实结果。
注意:这五个问题里,5.1 和 5.4 在现场最容易翻车,因为它们不报错。彩排时一定要在目标机器上、用真实名单、完整走一遍开始和停止。
6. 上台前的三步压测:把随机性、名单边界和机器环境都验一遍
改造完成后别急着交付,我在现场之前固定走三步压测,每步都能提前暴露一类问题。
第一步,满员压测。把最终名单(实际人数,不是抽样)粘进去,连续点开始和停止三十次,看三件事:结果有没有出现空白、按钮文字状态是否每次都正确复位、历史区行数是否等于停止次数。这一步主要抓 5.3 那类数据脏的问题,样本少了测不出来。
第二步,分布抽检。写一个临时过程,把PickOne跑两千次,统计每个人被抽中的次数,看最大值和最小值差多少:
' 临时压测:不放回抽样跑多轮,输出每人中签次数 Public Sub StressTest(ByVal rounds As Long) Dim stat As Object, i As Long, j As Long, name As String, out As String Set stat = CreateObject("Scripting.Dictionary") For i = 1 To rounds InitPool CleanList(ThisPresentation.Slides(1).Shapes("TextBox1").OLEFormat.Object.Text) Do While pool.Count > 0 name = PickOne() If stat.Exists(name) Then stat(name) = stat(name) + 1 Else stat.Add name, 1 Loop Next i For Each name In stat.Keys out = out & name & "=" & stat(name) & vbCrLf Next Debug.Print out ' 输出到 VBA 编辑器的立即窗口 End Sub几百人的名单跑两千轮,每人的中签次数应该都贴着总轮数,明显偏低的那个多半是名字里混了不可见字符,导致它被当成两个不同的人分开计数。这一步跑完,随机性和数据洁净度一起验了。
第三步,冷启动演练。用一台不常动的公用机,走完整流程:从 U 盘拷贝文件 → 右键解除锁定 → 打开允许宏 → 退出设计模式 → 全屏放映 → 抽三次 → 关闭重开再抽三次。第三步的重点不是抽奖本身,是确认“换台机器还活着”。我遇到过一台机器因为安全策略把 ActiveX 控件禁了,文件能打开、按钮能显示,就是点不动,现场发现就来不及了。
我自己的后悔药是两份备份:一份.pptm加一份导出的.bas,都放进同一个文件夹连带名单 txt,拷两个 U 盘。另外提前在文件里留一个不删除的备用页,放一套“形状 + 动作运行宏”的降级按钮,ActiveX 出问题就切过去。从那以后我每次交付这类抽奖演示文稿,都强制在目标机器上把这三步走完才敢合上电脑——毕竟台下坐的是几十上百号人,代码只有三十行,出错的余地却一点不小。这份名单抽取的源码和清洗、去重、压测几个扩展过程我都整理在同一个工程里,拿到之后建议先跑第一步满员压测,把自己单位的名单喂进去看人数对不对,再谈界面好不好看。希望帮到你。
本文还有配套的精品资源,点击获取