VBA学习(65):Excel VBA 凭证打印/SQL连接Eexcel文件/Listview控件/CommandButton命令按钮控件

news2024/9/29 7:29:29

本期内容信息量相当的大,内容涉及很多方面,请耐心阅读,肯定不会让你失望的!建议收藏!

  • Excel中记账凭证的打印,几种思路

  • Excel表记账的缺点

  • 最新的打印方法:勾选凭证列表,点打印即可

  • Excel连接外部数据库(Excel文件)的方法

  • SQL语句查询Excel文件数据

  • 循环打印的设计思路

我们前面分享过好几期“财务记账模板”相关内容,通过这么一个实例,向大家介绍Excel公式函数、VBA在财务管理中的运用,感兴趣的小伙伴可以翻翻前面的文章,这里我就不贴链接了。

今天我们要分享的主题是“凭证打印”,相信很多采用Excel来记账的财务小伙伴们肯定有这个困扰,凭证录进去了,怎么才能方便地把它打印出来呢?这个问题,我也是一路踩坑过来的:

刚开始是采用套打方式,正好我还发过一篇文章,大家可以看看:Excel财务综合应用之一:小型账务系统( 第五部分 凭证打印)

后来觉得套打很麻烦,改为直接用空白的纸打印了,把凭证格式设计好即可。

图片

上面两种方式都是手工操作,筛选一张打印一张,如果一号凭证分录超过6条,那么再切换到“凭证打印2”接着打印。如果凭证量较少,尚可应付,如果凭证量多就很累了。

于是,就开动脑筋,想想能不能我点一下按钮,它就自动打印我需要的凭证?就像各种商业财务软件一样?经过一番努力,还真搞出来一个可以自动打印的凭证模板,它是一个单独的文件,与我们的“Excel财务记账模板”(实际使用的名称是:XXX公司_20XX年序时账,并且文件名称中一定要包含“序时账”,以供打印模板更新链接之用)放在同一个目录下,感觉还是比较爽的:

上面这版打印模板通过power query查询数据,实现打印功能,同时也包含了不少VBA代码,但这不是今天的重点,我们不展开。

随着工作量的增加,这种Excel记账模板的局限性就越发明显:

1、表格有时候非常慢,主要是公式、条件格式太多;

2、数据安全性极低,表现在两个方面,一是Excel文件有时候会莫名其妙地打不开了,你就哭吧,二是在操作的时候,非常容易误操作把一些数据给改了、删了,造成极大的麻烦。

于是我就下定决心,一定要搞一个“像样”的“财务管理系统”,以Excel为操作端,Access为数据存储端,以提高数据的安全性,操作的便利性。

经过大概3个多月的努力(平均到每天至少2-3个小时),终于开发完成,完全实现了一个小型财务软件所能有的功能。现在用起来不是一般的爽!有机会给大家介绍一下,现在分享的内容也有不少是来自这个“财务管理系统”。怎么看起来像打广告的?您先别急,就说到今天的重点了。

废话不多说了,我们试着打印一张凭证,把它打印到pdf文件中:

图片

上面这个凭证打印的功能,就是移植自我的“财务管理系统”,当然经过了不少修改。我们下面介绍一下实现的思路:

1、我们在“明细账”表中增加一个命令按钮CmdVoucherPrint,把其Caption改为“凭证打印”。修改、增加了几个字段(减少修改代码的工作量)

2、增加一个用户窗体Usf_VoucherList,我是通过复制来的:

图片

其中有很多其他按钮,在打印的时候是不显示的,我也没有把它删掉,代码也保留着,说不定后面还会用到,就这么着吧。

增加一张工作表vPrint,用于打印凭证内容,也是复制来的:

3、我们点击明细账中的“凭证按钮,启动Usf_VoucherList。

4、Usf_VoucherList启动时,读取明细账凭证数据到数组,我们这里采用的是SQL查询方式。

5、在这之前,我们需要定义几个自定义函数,不定义也行,直接在各个过程里写代码。但是,这几段代码可能会在很多地方用到,所以先定义一下:

'自定义函数,取得【文件扩展名】
Function GetExtn(iName)
    '获取文件后缀名
    GetExtn = Right(iName, Len(iName) - InStrRev(iName, ".") + 1)
End Function

代码解析:利用InStrRev函数,定位最右边一个“.”的位置,再结合Len、Right函数取得文件扩展

'自定义函数,取得【数据库连接字符串】
Function GetStrCnn(ByVal DbFile As String, Optional ByVal Psw As String = "")
    Dim sType$
    sType = GetExtn(DbFile)
    If InStr(sType, "accdb") Then
        GetStrCnn = "Provider=Microsoft.ACE.OLEDB.12.0;Jet OLEDB:Database Password=" & Psw & ";Data Source=" & DbFile
    ElseIf InStr(sType, "xl") Then
        GetStrCnn = "Provider=Microsoft.ACE.OLEDB.12.0;Extended Properties=Excel 12.0;Data Source=" & DbFile
    End If
End Function

代码解析:根据不同的文件类型,确定不同的连接字符串,我们这里主要是连接Excel文件。对于连接access数据库的情况下,如果有密码的,我们还要把密码赋值给psw。

'自定义函数,取得【数据库查询结果的记录数据】
Function GetData(DataFile, sql)
    On Error Resume Next
    Dim cnn As Object                            '数据库连接
    Dim rs As Object                             '记录集对象
    Dim StrCnn As String                         '连接语句
    Dim aData()
    Set cnn = CreateObject("ADODB.Connection")
    Set rs = CreateObject("ADODB.Recordset")
    On Error Resume Next
    StrCnn = GetStrCnn(DataFile)                  '取得连接字符串
    cnn.Open StrCnn                              '打开数据库链接
    Set rs = cnn.Execute(sql)                    '执行查询,并将结果输出到记录集对象
    GetData = rs.getrows                         '将记录输出到数组
    rs.Close
    cnn.Close
    Set cnn = Nothing
    Set rs = Nothing
End Function

代码解析:根据数据库文件,SQL语句,查询数据,将结果存到数组里,详见代码注释。

'自定义函数,取得【数据库查询结果的表头字段】
Function GetFields(DataFile, sql)
    Dim cnn As Object                            '数据库连接
    Dim rs As Object                             '记录集对象
    Dim StrCnn As String                         '连接语句
    Dim aData()
    Dim FieldsNum As Integer
    Set cnn = CreateObject("ADODB.Connection")
    Set rs = CreateObject("ADODB.Recordset")
    StrCnn = GetStrCnn(DataFile)             '取得连接字符串
    cnn.Open StrCnn                              '打开数据库链接
    Set rs = cnn.Execute(sql)                    '执行查询,并将结果输出到记录集对象
    FieldsNum = rs.Fields.Count              '字段数量
    ReDim aData(FieldsNum - 1)
    For i = 0 To FieldsNum - 1               '循环,把字段存入数组
        aData(i) = rs.Fields(i).Name
    Next
    GetFields = aData
    rs.Close
    cnn.Close
    Set rs = Nothing
    Set cnn = Nothing
End Function

代码解析:根据数据库文件,SQL语句,查询数据,将表头字段存到数组里,详见代码注释。

'自定义函数,【数字转大写人民币】
Function N2RMB(m)
    Y = Int(Round(100 * Abs(m)) / 100)
    j = Round(100 * Abs(m) + 0.00001) - Y * 100
    f = (j / 10 - Int(j / 10)) * 10
    a = IIf(Y < 1, "", Application.Text(Y, "[DBNum2]") & "元")
    b = IIf(j > 9.5, Application.Text(Int(j / 10), "[DBNum2]") & "角", IIf(Y < 1, "", IIf(f > 1, "零", "")))
    c = IIf(f < 1, "整", Application.Text(Round(f, 0), "[DBNum2]") & "分")
    N2RMB = IIf(Abs(m) < 0.005, "", IIf(m < 0, "负" & a & b & c, a & b & c))
End Function

代码解析:这个函数是网上抄来的,利用Text(nummber,"[DBNum2]")把数字转成中文大写。

Function ColorByName(colorName As String) As Long'这个函数是根据颜色名称来取得颜色值代码较多,前面也分享过这里就不贴了。有兴趣的同学可以点下面链接查看。也可以不用这个函数,直接给出代码值。

更新:Excel VBA 自定义函数/根据颜色名称中英文取得颜色值/

Excel VBA 自定义函数/取得颜色值/GetColor/ChatGPT来帮忙

6、窗体启动后,我们看到:

图片

几个按钮的功能我在图里标示,这里我们分析一下代码:

(1)全选

Private Sub CmdSelectAll_Click()
    With Me.LvVoucherList
        If Me.CmdSelectAll.Caption = "全选" Then
            For i = 1 To .ListItems.Count
                .ListItems(i).Checked = True
            Next
            Me.CmdSelectAll.Caption = "全消"
            Me.CmdSelectAll.BackColor = RGB(176, 224, 230)

        Else
             For i = 1 To .ListItems.Count
                .ListItems(i).Checked = False
            Next
            Me.CmdSelectAll.Caption = "全选"
            Me.CmdSelectAll.BackColor = RGB(143, 188, 143)

        End If
    End With
End Sub

点击一次,在“全选”,“全消”之间切换,同时改变控件的名称与颜色

(2)月份右边向上、向下箭头,用来切换月份:


Private Sub CmdUp_Click()
    With Me.CmbMonth
        For i = 0 To .ListCount - 1
            If .Text = .List(i) Then
                j = i
                Exit For
            End If
        Next
        If j = 0 Then
            .Text = .List(.ListCount - 1)
        Else
            .Text = .List(j - 1)
        End If
    End With
    Me.CmdSelectAll.Caption = "全选"
    Me.CmdSelectAll.BackColor = RGB(143, 188, 143)
    Me.LvDetail.ListItems.Clear
End Sub
Private Sub CmdDown_Click()
    With Me.CmbMonth
        For i = .ListCount - 1 To 0 Step -1
            If .Text = .List(i) Then
                j = i
                Exit For
            End If
        Next
        If j = .ListCount - 1 Then
            .Text = .List(0)
        Else
            .Text = .List(j + 1)
        End If
    End With
    Me.CmdSelectAll.Caption = "全选"
    Me.CmdSelectAll.BackColor = RGB(143, 188, 143)
    Me.LvDetail.ListItems.Clear
End Sub

代码解析:点击一次,me.cmbmonth的listindex增减1,遇到list开头再向上,则返回结尾,遇到结尾再向下则回到开头。原来是简单地在“20XX01~20XX12”之间循环,但是遇到某些月份没有数据就不好办了,要么报错,如果用On Error Resume Next则显示空白的列表,不爽。

(3)窗体启动代码:Private Sub UserForm_Activate(),代码较长,我贴到第二条文章,下面的解释是AI贡献的,我也懒得去写了,将就着看吧:

1. 声明变量:声明一个对象变量DicMonth,一个ListItem变量LvItem,一个字符串数组sData,以及其他一些变量。

2. 设置用户表单的一些属性:设置CmdUp、CmdDown按钮的高度、顶部和左边位置,设置用户表单的标题、背景颜色等。

3. 创建一个字典对象DicMonth。

4. 设置一些控件的属性:设置LbTitle、CmdSelectAll、CmdPrint等控件的属性。

5. 定义SQL查询语句:定义三个SQL查询语句,用于从明细账表中获取数据。

6. 获取数据:使用GetData函数从工作簿中获取数据,并将结果存储在aData变量中。

7. 获取字段名:使用GetFields函数从工作簿中获取字段名,并将结果存储在sTbtitle变量中。

8. 设置ListView控件的列头:根据字段名设置LvVoucherList和LvDetail控件的列头。

9. 设置ListView控件的属性:设置LvDetail和LvVoucherList控件的显示外观、表格线、排序、复选框等属性。

10. 遍历数据:遍历aData中的数据,将月份信息添加到字典对象DicMonth中。

11. 设置ComboBox控件的属性:将字典对象DicMonth的键值作为CmbMonth控件的列表项,并设置控件的样式和默认选中项。

12. 清空ListView控件的列表项:清空LvVoucherList控件的列表项。

13. 添加列表项:根据选中的月份,将符合条件的数据添加到LvVoucherList控件的列表项中。

14. 获取明细账表的字段名:使用GetFields函数从工作簿中获取明细账表的字段名,并将结果存储在tbTitle变量中。

15. 设置ListView控件的列头:根据明细账表的字段名设置LvDetail控件的列头。

总结:这段代码主要是在激活用户表单时,对表单中的一些控件进行设置,包括按钮的位置、大小,表单的标题、背景颜色等。同时,从工作簿中获取数据,并将数据添加到ListView控件中,以便用户查看和操作。通过设置ComboBox控件,可以让用户选择不同的月份,从而显示对应月份的数据。整个过程涉及到了一些Excel VBA编程的基本操作,如声明变量、定义SQL查询语句、获取数据、设置控件属性等。

(4)打印:Private Sub CmdPrint_Click(),代码较长,我也把它贴到第二条文章,下面的解释也是AI贡献的,基本能说明问题:

1. 定义所需的变量,如日期、凭证号、数组等。

2. 检查是否已选择打印机,如果没有,则退出子程序。

3. 关闭屏幕更新和警报,以提高性能。

4. 激活名为"vPrint"的工作表,并使其可见。

5. 获取用户选择的月份和已勾选的凭证号。

6. 如果没有勾选任何凭证,弹出提示框并退出子程序。

7. 根据勾选的凭证号,从名为"明细账"的工作表中获取相关数据。

8. 获取数据表的字段名,并确定各字段在数组中的位置。

9. 根据凭证号对数据进行分组,并计算每组的行数。

10. 遍历每个凭证,将其数据填充到"vPrint"工作表中。

11. 设置单元格格式,如数字格式、合计大写金额等。

12. 打印工作表,并在打印完成后等待1秒。

13. 计算总页数,并在打印完所有凭证后弹出提示框。

14. 卸载当前窗体,并激活名为"明细账"的工作表。

整个过程中,代码会不断读取和操作Excel工作表中的数据,以实现凭证的打印功能。

我补充解释一下实现凭证打印的关键点:

1、获取需要打印的凭证的凭证号,存到数组arrNumber里,也就是我们窗体中列表勾选的记录。

2、根据月份、arrNumber,从明细账中查询数据,存到arrSelected 

sql = " select * from  [明细账$] where 月份='" & iMonth & "' and 凭证号 in (" & numberStr & ")"
arrSelected = GetData(myDataFile, sql)

这里的numberStr来自前面的数组arrNumber

numberStr = Join(arrNumber, "','")
numberStr = "'" & numberStr & "'"

这里值得注意的是,numberStr作为SQL语句的条件,要注意类型的匹配。如果是整数数值,那么直接numberStr = Join(arrNumber, ",")就好,如果是文本,那要加上单引号,如上面两行所示。

3、重设arrNumber,取得每个凭证的分录数:


 sql = "select 凭证号,count(凭证号) as 分录数 from (" & sql & ") group by 凭证号"
 arrNumber = GetData(myDataFile, sql)

这里的SQL从面前的SQL中再次查询“凭证号”、“分录数”,再存到数组arrNumber中,这里也可以使用另一个数组,但定义的太多也容易乱。

4、循环arrNumber,根据凭证号从arrSelected中提取一个凭证号的记录,存到数组arrPrint中,然后再把arrPrint数据写入工作表vPrint

5、这里要处理凭证分录多于6条的情况,就是第3条的意义所在。

iPage = Application.WorksheetFunction.RoundUp(iRow / 6, 0)

循环1 to ipage ,每6条分录打印一次,凭证号相应设置成“记-001,2/2”格式:

.Cells(5, 7) = arrPrint(0, PosNumber) & "," & i & "/" & iPage

6、这里的细节有很多,不再细说了,有机会再分别讲吧。感兴趣的可以仔细分析一下代码。

另外,由于明细账表头字段修改,“科目汇总”代码也做了修改。对于双击汇总科目展示明细记录的代码,修改了LvDetail的字段宽度,根据明细账单元格的宽度来确定(arrWidthDetail):

With Sheets("明细账")
        For i = 1 To iCol
            If Cells(1, i) <> "" Then
                ReDim Preserve arrWidthDetail(i - 1)
                arrWidthDetail(i - 1) = Cells(1, i).Width
            End If
        Next
    End With

原来是这样的:

arrWidthDetail = Array(60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60)

由于明细账字段增加,它的元素个数都不够用了,报错。索性改了吧。

技术交流,软件开发,欢迎微信沟通:

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

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

相关文章

OpenCV中使用金字塔LK光流法(下)

接下来通过一个demo来调用calcOpticalFlowPyrLK()实现光流计算,需要注意的是该方法适用于具有丰富特征的像素点的光流计算,平坦区域的像素点往往会得到误差较大的结果。所以我们需要先选取得到一些角点,demo中通过goodFeaturesToTrack()这个接口实现角点提取。 如下有两张图…

nvm切换node版本(windows版本)

如果是win系统&#xff0c;不能直接通过npm来安装nvm(npm install nvm不行&#xff01;)。需要手动去nvm官网下载安装包安装nvm github官网 先卸载本地的node版本 npm ls -g --depth0 // 查看全局安装中是否有早前安装的node 1.点击进去Github上往下滑会发现有一个download。进…

IP网络协议

目录 一、IP协议简介 二、IP协议报头 三、IP网段划分&#xff08;子网划分&#xff09; 四、特殊的IP地址 五、IP地址的数量限制 六、私有IP地址和公网IP地址 七、路由 八、分片与组装 一、IP协议简介 IP指网际互连协议&#xff0c;Internet Protocol的缩写&#xff0…

Vue+ElementUI+Electron环境搭建及程序打包

一.环境 Node.js Element-ui Electron 二.Node.js 1.下载并安装Node.js 2.安装完成后,新建目录”node_cache“ ”node_global“ 3.新建及修改环境变量 4.执行如下命令 npm config set prefix "D:\Source_Install\nodejs\node_global" npm config set cache &q…

设计模式-简单工厂模式工厂方法模式

1. 简单工厂模式定义 简单工厂模式&#xff08;Simple Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它通过专门定义一个类来负责创建其他类的实例&#xff0c;这个类通常被称为工厂类。简单工厂模式并不是一种正式的设计模式&#xff0c;但它确实是一种常用的…

认识Kubebuilder

认识Kubebuilder 一、什么是Kubebuilder?Kubebuilder&#xff0c;K8s operator创建框架controller-runtime和controller-tools库 二、Kubebuilder&#xff0c;举例来说开源项目kuik 三、使用 kubebuilder init 创建基础项目四、使用kubebuilder create api生成控制器CachedIma…

gitea仓库迁移新服务器 更新远程仓库地址(git remote remove origin)

文章目录 引言I 镜像部署方式迁移案例迁移容器备份gitea服务器配置II 修改​远程仓库地址set-url语法案例III 扩展基于git命令方式进行代码迁移忽略被追踪的文件(update .gitignore)see also引言 由于部署git仓库的机器不稳定,决定进行服务器迁移。更新远程仓库地址的应用场景…

传统助贷机构如何利用CRM系统转型升级

传统助贷机构在利用CRM系统&#xff08;客户关系管理系统&#xff09;进行转型升级时&#xff0c;可以遵循以下几个关键步骤和策略&#xff0c;以优化客户管理、提升业务效率并实现业务增长&#xff1a; 一、明确转型升级目标 首先&#xff0c;传统助贷机构需要明确利用CRM系统…

使用docker compose一键部署 Openldap

使用docker compose一键部署 Openldap LDAP&#xff08;轻量级目录访问协议&#xff0c;Lightweight Directory Access Protocol&#xff09;是一种用于访问分布式目录服务的网络协议&#xff0c;OpenLDAP 是 LDAP 协议的一个开源实现&#xff0c;由 OpenLDAP 项目提供&#x…

python库(21):TextBlob库实现文本处理

1 TextBlob简介 TextBlob 是一个基于 Python 的文本处理库&#xff0c;能够让基础的自然语言处理任务变得异常简单。 它提供了一个简单直观的 API&#xff0c;让你能够轻松执行词性标注、名词短语提取、情感分析、文本分类和关键词提取等功能。 值得一提的是&#xff0c;Tex…

Linux git的基本使用 安装 提交

目录 安装git 首次使用git的配置 拉取仓库 步骤1&#xff1a;新建仓库 步骤2:复制仓库地址 步骤3&#xff1a;远端仓库拉取到本地 上传代码 常用指令 安装git sudo apt-get install git # Ubuntu/Debian sudo dnf install git # Fedora sudo yum insta…

BMS中内阻补偿的使用

在BMS&#xff08;电池管理系统&#xff09;中&#xff0c;内阻补偿的使用主要涉及以下几个步骤和方法&#xff1a; 1. 内阻测量 实时监测&#xff1a;通过专用电路或算法实时测量电池的内阻。常用的方法包括脉冲测试法和交流阻抗测试法。计算内阻&#xff1a;基于电流和电压…

基于AI+多技术融合在流域生态系统服务评价 制图、水资源水环境水生态分析、土壤侵蚀分析、流域产水分析、流域碳收支评估、气候变化影响等应用

流域生态系统服务在环境保护与资源管理中具有不可替代的重要性。随着全球气候变化和人类活动对自然环境的压力日益增大&#xff0c;流域生态系统的稳定性和健康状况面临严峻挑战。水资源短缺、洪水频发、水质污染、生物多样性减少等问题&#xff0c;正在威胁流域内及其下游区域…

【综合架构】Part 5.2 Ansible

安装设备&#xff1a;管理设备-m01-10.0.0.61 部署与配置 部署 yum install -y ansible 配置 步骤 1&#xff1a;修改配置文件&#xff1a;关闭Host_key_checking。 vim /etc/ansible/ansible.cfg 步骤 2&#xff1a;修改配置文件&#xff1a;开启日志功能。

作为HR如何解决候选人爽约的问题

为了降低候选人的爽约概率&#xff0c;HR可以直接在预约面试时&#xff0c;通过电话或短信等多种方式&#xff0c;与候选人进行沟通&#xff0c;确保对方完全清楚面试的时间、地点和流程。在双方沟通的过程中&#xff0c;HR一定要注意语气亲切&#xff0c;要让候选人感受到企业…

2020年中国海岸带10m土地覆盖图

2020年中国海岸带10m土地覆盖遥感图 数据介绍 土地利用/覆盖分类是研究海岸带动态变化过程、理解滨海社会-生态系统作用机制和支持可持续发展的重要基础。中国海岸带土地覆盖复杂多样&#xff0c;以往多类别地表覆盖和滨海湿地专题数据集难以兼顾陆域和海域信息&#xff0c;而…

软考攻略/超详细/系统集成项目管理工程师/基础知识分享07

第三章 信息技术服务 3.1 内涵与外延 3.1.1 服务的特征&#xff08;掌握&#xff09; 服务的特征包括&#xff1a;无形性、不可分离性、可变性和不可储存性等。 3.1.2 IT服务的内涵&#xff08;掌握&#xff09; IT服务除了具备服务的基本特征&#xff0c;还具备本质特征、形…

利用C++实现PCL点云可视化:示例程序及解析(持续更新)

【版权声明】本文为博主原创文章&#xff0c;未经博主允许严禁转载&#xff0c;我们会定期进行侵权检索。 参考书籍&#xff1a;《人工智能点云处理及深度学习算法》 本文为专栏《Python三维点云实战宝典》系列文章&#xff0c;专栏介绍地址“【python三维深度学习】python…

HTML <template> 标签的基本技巧

前言 HTML中的<template>标记是 Web 开发中一个功能强大但经常未得到充分利用的元素。它允许你定义可重复使用的内容&#xff0c;这些内容可以克隆并插入 DOM 中而无需最初渲染。 此功能对于创建动态、交互式 Web 应用程序特别有用。 在本文中&#xff0c;我们将探讨有…

二极管、电阻、电容、电感的种类及作用

系列文章目录 文章目录 系列文章目录前言二极管的种类二极管的作用电容的种类电容的作用电阻的作用电感的作用 前言 参考&#xff1a;这个UP的视频&#xff1a;8位和32位单片机最本质区别&#xff0c;2分钟看懂&#xff01; 二极管的种类 1.恒流二极管&#xff1a;可以用在恒…