如何手工打造一款公平的年会抽奖软件(二)

上一篇说到,我得到了员工的嘱托和领导的信任,临危受命制作一款公平抽奖软件。

当时我就竖起手指敬了个礼,饱含热泪地接下了任务,带着这么多人的期盼,我得加把劲放手干不是吗!

OK,上一篇中程序的基本架子有了,那接下来就要进一步完善强化某些功能了。

(文末有安装包下载,免费分享给小伙伴儿们使用哦!)


一、界面控件布局要美丽

抽奖是个高兴的事儿,即便没中奖人人也乐得参与,万一要中了呢,玩的就是个心跳。

既然大家高高兴兴的来,那就不能扫了大家的兴致,这抽奖界面的模样怎么也得打扮得好看点儿。

背景图片那自不用多说,来张大红的,特喜庆的那种。

虽然都是红的,但考虑到各人喜好不同,可以在程序目录中放上一张文件名为 Background.jpg 的图片文件作为自定义背景图片,程序启动自动加载哦。

你说你还要自己去找图片,麻烦不,我这就有啊!

好吧,我认识的小伙伴儿们有几个得了懒癌,都是我给惯的,那就再让我惯你们一次吧。

我也是从网上扯了几张图片,放在文末自行下载吧。


除了背景图这张脸,还有就是脸上的五官了。

为了更加方便地调整五官的位置,程序上开了个菜单,点击 手动移动控件 这一菜单项即可启用或禁用这项功能。

启用后就可以用鼠标拖动界面上的控件,嘿嘿,想放哪儿就放哪儿,爽不爽?

拖一次爽一次,一直拖一真爽,不过拖完了别忘记再点一次菜单项禁用拖拽功能。


当然,我相信你们之中肯定隐藏着精致强迫症患者,好吧,那还可以用数字坐标来调整控件位置。


二、最简单有效的数字滚动效果

抽奖时要有动态效果,我们不搞复杂的,最简单的就是数字滚动了。

不用多说,这个等待摇奖停止的动态滚动过程才是最让人激动和期待的,完全有一种瞬间让人感觉生活还是有点儿盼头的哈。

具体怎么做,往下看!

先定义一个 Timer 控件,命名为 tmrLuckDraw ,然后随机生成11位数字,显示到标签 lblCardID 上。

tmrLuckDraw.Interval = 1 时即可触发滚动效果,这个数值越大,数字滚动得越慢。

' RndNumCardId 函数产生11位数字字符串
Dim strRndCircle As String
strRndCircle = RndNumCardId(999999999)
lblCardId.Caption = strRndCircle
lblCardId.Refresh
 


这里的数字滚动设定为11位的手机号码,当然这一项也可以改为员工的工号或其它可以唯一标识员工的字串。

如果需要修改成其它位数的字串,可以联系我定制,人民币是我大哥,他让我干啥我就干啥!


三、用快捷按键控制抽奖的开始和停止

鼠标点击抽奖比较LOW,也不好控制,用按键才是正道。

而且,注意,开始和暂停的按键最好不要相同,因为你多按一下人就有可能会凌乱,还是分开好,垃圾都讲究分类了不是。

另外,当窗体非聚焦状态下,定义在窗体上的按键可能会不起作用,所以建议用全局消息定义和处理按键。

具体可分两步,第一步先定义按键,第二步再在系统消息函数中写上相应按键的处理代码。


在主窗体的 Load 事件中定义相应的快捷按键。

开始抽奖为 F11 键,ID为1;暂停抽奖为 空格 ,ID为2。

Dim ret As Long

'记录原来的window程序地址
preWinProc = GetWindowLong(Me.hwnd, GWL_WNDPROC)
'用自定义程序代替原来的window程序
ret = SetWindowLong(Me.hwnd, GWL_WNDPROC, AddressOf Wndproc)

'开始抽奖
idHotKey = 1 ' ID为1
Modifiers = 0 ' 组合键使用加号连接,此处为空(MOD_SHIFT,MOD_ALT,MOD_CONTROL)
uVirtKey = vbKeyF11 ' F11键
ret = RegisterHotKey(Me.hwnd, idHotKey, Modifiers, uVirtKey)

'暂停抽奖
idHotKey = 2 ' ID为2
Modifiers = 0 ' 组合键使用加号连接,此处为空(MOD_SHIFT,MOD_ALT,MOD_CONTROL)
uVirtKey = vbKeySpace ' 空格键
ret = RegisterHotKey(Me.hwnd, idHotKey, Modifiers, uVirtKey)
 


快捷按键的处理,通过以下 Wndproc 函数调用,通过前面定义的ID来判断按键。

Public Function Wndproc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

    If Msg = WM_HOTKEY Then

        Select Case wParam
            Case 1
                ' 抽奖开始的处理代码...
            Case 2
                ' 抽奖暂停的处理代码...
        End Select

    End If

    '如果不是热键信息则调用原来的程序
    Wndproc = CallWindowProc(preWinProc, hwnd, Msg, wParam, lParam)
End Function
 


四、抽奖过程中断了怎么办

除了中奖本身还有一种情况让人感觉激动,那就是好好抽着奖,它居然死机了!

重新打开一看,好么,啥都没了!

前面抽一半的还算数吗,奖品都领了,你说这闹不闹心!

别着急,不怕死机、停电,我这儿有办法!


一个简单的办法,就是在重新加载奖项时,先判断是否存在上一次的抽奖结果。

如果存在(比如抽奖剩余数大于1),则表示上一次抽奖过程中断,那么可以给出两种选择。

一种是继续上一次抽奖,另一种是重置上一次结果,开始新一轮的抽奖。

其实你仔细研究就会发现,这两种选择其实有先后顺序,完全可以合并为一种。

那么就简单了,一旦抽奖过程遭遇中断,重启程序后自动继续上一次抽奖过程。

如果不想继续,那么完全可以重置结果从头来过。

objIteSql.p_Sql = "SELECT luckdraw FROM result_temp WHERE counts_rest > 0"
If objIteSql.SelectSQL = True Then
    If objIteSql.p_RecordCount > 0 Then
        '上次抽奖未结束,则自动导入剩余的奖项和计数
    End If
End If
 


五、如何做到奖项任意抽取

一般情况下,抽奖总是按奖项先低后高的顺序抽取的。

但你是知道的,这仅仅是一般情况,世事无常,总会有特殊情况的嘛。

那特殊情况是个啥?

比如有人喜欢先抽三等奖,再抽五等奖,最后抽一等奖。

哈哈,有这么奇葩吗?

哎,你还别说,这只是初级奇葩哦!

高级奇葩的需求是,先抽三等奖3名,再抽五等奖5名,最后抽一等奖1名。

噗~一口农夫山泉喷出,一点儿也不甜!

说奇葩其实也不能说有啥奇怪的,因为有时的确不一定按固定顺序抽奖。

比如中间临时要补个奖项呢?


想想复杂,做起来...好像也没那么简单好不好!

WELL,我擦擦汗水(眼泪)尽量做就是了。

放几个 ComboBox 下拉框,再放几个文本框,分别用来显示奖项、剩余数量、已抽取数量和每次可抽取数量。

在开始每一次抽奖前,可自由选择奖项和抽取数量。

按下暂停按键后,程序通过这些项目和数量来进一步判断和计算。

代码太多,就不放上来了。


六、数据如何导入,抽奖结果又如何导出来呢

数据导入就两样,一个是人员信息,包括编号(手机号)、姓名、部门啥的,还一个是奖项信息,包括名称和数量。

数据库使用 SQLite ,速度更快,关键是断电不丢数据啊!

前文参考:

VB+SQLite组合,真香!(一)

VB+SQLite组合,真香!(二)


接着,使用 MSFlexGrid 控件来填充结果数据。

这个控件是微软出品,比较原始,所以导出速度并不理想,但作为抽奖结果没几条数据,用用还算凑合。

以后需要改进的话,可以使用 MSHFlexGrid 或其它更高级的表格控件。


Dim strSql As String
strSql = "SELECT a.cardid, a.luckdraw, a.datetimes, b.username, b.department FROM result AS A LEFT OUTER JOIN members AS B ON a.cardid=b.cardid"

msfgResult.Redraw = False
msfgResult.Tag = ""
Call fillGridScanlog(msfgResult, strSql)
msfgResult.Redraw = True

' MSFlexGrid 填充数据函数
Private Sub fillGridScanlog(grid As MSFlexGrid, Optional strSql As String)
    Dim strRowData As String, rowindex As Integer
    rowindex = 1

    With grid
        If Len(.Tag) = 0 Then
            .Rows = 1
            .Cols = 6
            .Row = 0
            .Col = 0
            .Text = "NO"
            .ColWidth(0) = 400
            .ColAlignment(0) = flexAlignCenterCenter
            .Col = 1
            .Text = "编号"
            .ColWidth(1) = 1200
            .ColAlignment(1) = flexAlignLeftCenter
            .Col = 2
            .Text = "姓名"
            .ColWidth(2) = 1200
            .ColAlignment(2) = flexAlignLeftCenter
            .Col = 3
            .Text = "部门"
            .ColWidth(3) = 1500
            .ColAlignment(3) = flexAlignLeftCenter
            .Col = 4
            .Text = "奖项"
            .ColWidth(4) = 3000
            .ColAlignment(4) = flexAlignLeftCenter
            .Col = 5
            .Text = "时间"
            .ColWidth(5) = 2400
            .ColAlignment(5) = flexAlignLeftCenter

            Dim objIteSql As New cls_iteSql
            objIteSql.p_DbFilePath = strDbPath
            objIteSql.p_CheckSql = False
            objIteSql.p_Sql = strSql

            If objIteSql.SelectSQL = True Then
                Dim j As Long
                Dim intRecordCount As Long

                intRecordCount = objIteSql.p_RecordCount - 1

                For j = 0 To intRecordCount
                    strRowData = rowindex & vbTab
                    strRowData = strRowData & Trim(objIteSql.p_RecordSetValue(j, 0)) & vbTab
                    strRowData = strRowData & Trim(objIteSql.p_RecordSetValue(j, 3)) & vbTab
                    strRowData = strRowData & Trim(objIteSql.p_RecordSetValue(j, 4)) & vbTab
                    strRowData = strRowData & Trim(objIteSql.p_RecordSetValue(j, 1)) & vbTab
                    strRowData = strRowData & Trim(objIteSql.p_RecordSetValue(j, 2)) & vbTab

                    .AddItem strRowData
                    rowindex = rowindex + 1
                Next
            End If
            Set objIteSql = Nothing
            .Tag = 1
        End If
    End With
End Sub
 


七、公平抽奖真的公平吗

到最后了,重点差点忘了说,这最终应该是一个公平抽奖。

公司两千多号人,怎么做到公平抽奖呢?

我们不扯那些什么正态分布啥的概念,我们就用史上最简单最单纯的算法,像洗牌一样,随机洗牌,一次不行那就多洗几次。

说直白一点儿就是,把公司员工的手机号(或编号)排列成一个一维数组,然后从数组的第一位开始,将当前位的号码与其他随机位置的号码互换,然后第二位与随机位置号码互换,以此类推直至最后一位。

这样就等于洗牌了嘛,当然,一次肯定不够,我设定最少洗10次,这样顺序就打得比较乱了。

然后实际抽奖时,随机从已经打乱顺序的数组中选取一个,那么就可以实现随机中奖了。

当然在这个过程中,已经中奖的人员事先会被剔除数组列中的。

' rstTmpdata 是人员数组,intShuffle 是洗牌次数
For intCC = 0 To intShuffle
    lngRecordCount = UBound(rstTmpdata) - 1

' 从头至尾,当前位置与随机位置互换
    For j = 0 To lngRecordCount
        intPos = RndNum(lngRecordCount + 1)

        rTpData = rstTmpdata(intPos)
        rstTmpdata(intPos) = rstTmpdata(lngRecordCount)
        rstTmpdata(lngRecordCount) = rTpData

        lngRecordCount = lngRecordCount - 1
    Next

Next
 


由于人员顺序是打乱的,抽取的时候也是在这打乱的基础上随机抽取的,所以说够公平了吧。

如果你觉得打得还不够乱不够随机,程序里你可以指定洗牌次数,不过最好不要洗太多次,因为程序速度可能会受影响。

鉴于更公平的原则,洗牌和随机抽取都是在按下暂停时才开始计算的。

也就是说,在开始抽奖(数字开始滚动)时,并没有已经产生中奖者,而是按下暂停键后才开始洗牌、开始抽取中奖者。

并且需要强调的是,每抽取一次中奖就随机洗牌一轮(多次),保证每次数组的排列是不同的,够公平不?


最后,免费下载、免费注册、免费使用

程序经过多年的测试和实际使用,效果还是不错的,得到了广大员工和领导的肯定,问题是到现在我怎么还没有中过奖呢?!

好吧,在这儿免费分享程序给小伙伴儿们,未注册版限制10人,但是可以免费注册!

程序不收钱,不是我不喜欢钱,只是这次不收钱,怎么搞?

看下面这行字~

关注公众号@网管小贾,发送 xjluckdraw 免费获取注册码,注册后没有任何使用限制了。

欢迎留言区三连,谢谢!


安装包免费下载链接(文章ID在二维码下方附近)

Setup_XJLuckDraw.exe (3.81M)

下载链接:https://pan.baidu.com/s/1lx9esi_jrMSA07GrDzlfEw

提取码:


输入密码,继续阅读



扫码关注微信公众号,回复文章ID免费获取密码


<文章ID:000791>

背景图片打包下载

60个年会抽奖背景.7z (15.81M)

下载链接:https://pan.baidu.com/s/1gxJvDlx6fm2IuIkx7PjC1Q

提取码:


输入密码,继续阅读



扫码关注微信公众号,回复文章ID免费获取密码


<文章ID:000791>

上一篇链接:如何手工打造一款公平的年会抽奖软件(一)


WeChat@网管小贾 | www.sysadm.cc



评论  
0 #1 网管小贾 2021-06-15 08:49
抽奖软件下载链接已更新到百度网盘。
引用
提交评论

安全码
刷新

© 2020-present 网管小贾 | 微信公众号 @网管小贾
许可协议:CC-BY-NC 4.0 | 转载文章请注明作者出处及相关链接