ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 177625|回复: 335

[原创] VBA窗体Listview控件完全教程

    [复制链接]

TA的精华主题

TA的得分主题

发表于 2018-7-14 20:27 | 显示全部楼层 |阅读模式
本帖已被收录到知识树中,索引项:控件
本帖最后由 ivccav 于 2018-7-16 20:02 编辑


Listview控件是微软通用控件MSCOMCTL.OCX中的一员,因其美观、易用,是VBA使用者最常用的控件之一。Listview控件的应用随处可见,比如你的计算机系统中使用的资源管理器,就是Treeview和Listview做出来的,资源管理器的左边是Treeview控件,右边是Listview。Listview有4种视图,Win7系统的资源管理器有7种显示方式,XP有5种,其中大图标对应Listview的lvwIcon,小图标对应lvwSmallIcon,列表对应lvwList,详细信息则对应lvwReport。


我们主要使用lvwReport视图,即报表视图。要使用Listview,首先需要在窗体的工具箱上→右键→附加控件,选择Microsoft Listview Control 6.0(SP6),如果找不到这个控件,那就需要下载MSCOMCTL.OCX,这是微软的通用控件文件。我在另一帖中已经提供了该控件的下载附件,以及版本冲突的解决办法,可自行去获取:

http://club.excelhome.net/thread-1423746-1-1.html

Listview控件运用非常简单,只有2个对象:ListItem和ColumnHeader对象,其对应的集合是ListItems 和ColumnHeaders 对象。ListItem对象表示Listview控件的行,而ColumnHeader对象表示控件的列,可对这两个对象设置相应属性。ColumnHeader对象决定了控件可以有多少列,当移除其中一列时,所有ListItem对象相应列的数据都将丢失。

本帖将要讲到的内容如下:

1.基础:Listview控件常用方法、属性和事件;
2.基础:Listview两个对象的常用方法和属性;
3.练手:Listview模糊查询窗体的设计;
4.实践:Listview窗体不重复多选的实现;
5.美化:Listview隔行换色的完美实现(前景色和背景色);
6.美化:Listview+Imagelist设置行高;
7.高级:Listview动态加载查询数据的实现;
8.高级:Listview排序方法的完美实现(按字符串、数字、日期和二进制排序);
9.高级:Listview可编辑功能的完美实现

更新说明 2018/7/16 20:02


1.修正了一个错误,删掉了2个字符。左方向键中InkEdit的Left的算法
应该为:
sngNewInkLeft = InkEdit1.Left - .ColumnHeaders(intCol).Width
原来为:
sngNewInkLeft = InkEdit1.Left - .ColumnHeaders(intCol+1).Width


2.增加了新增行和删除行功能,增加了数据库的版本。

附件全部在1楼




补充内容 (2018-9-8 21:09):
用NM_CUSTOMDRAW自定义绘制技术实现Listview控件隔行换色(每个单元格的前景色和背景色均可不同):

http://club.excelhome.net/thread-1428537-1-1.html

补充内容 (2018-9-25 20:00):
疏忽了,数据库为空时,不能rst.movefirst,已加If rst.EOF And rst.BOF Then  '判断空数据库,
修改的版本见60楼。

补充内容 (2019-1-13 12:12):
看到网友提问要帮助文件,搜了一下硬盘,发现曾经下载过,但是没有看过。现在分享给大家,里面是多个控件的帮助文件(Imagelist、Listview、Treeview、进度条等9个MSComctlLib库中的控件)。附件 在127楼


补充内容 (2019-1-20 19:57):
补充一款完全可以取代Listview+Listbox+Treeview+Textbox+Combobox的表格神器,操作数据库也无需代码:
http://club.excelhome.net/thread-1454440-1-1.html

补充内容 (2019-5-19 18:19):
Listview中双击打开文件或网址,在151楼:

http://club.excelhome.net/forum. ... p;extra=#pid9943018

补充内容 (2023-3-25 13:29):
使用纯代码实现Listview控件网格线颜色、行高和选中行高亮颜色的设置

https://club.excelhome.net/thread-1657761-1-1.html

补充内容 (2023-3-25 13:36):
设置网格线颜色、行高和选中行颜色,附件在304楼层

https://club.excelhome.net/forum ... ;extra=#pid11214070

补充内容 (2023-5-17 16:56):
Listview虚拟模式极速显示100000000行大数据的实现,标头带全选复选框

https://club.excelhome.net/thread-1662904-1-1.html

评分

39

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2018-7-14 20:33 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
本帖最后由 ivccav 于 2018-7-16 20:03 编辑

1.基础:Listview控件常用方法、属性和事件

1.1、常用属性

(1)FullRowSelect非常重要的属性。设置是否整行选择模式,当设置为True时,当点击某行时能整行高亮显示,不然只会选择该行的第一列。默认值False。只有在lvwReport视图该属性才有意义。

(2)GridLines
设置是否显示网格线。默认值False。只有在lvwReport视图该属性才有意义。

(3)AllowColumnReorder
设置是否可拖动列标头来对改变列的顺序。当拖动列的位置时,程序的输出依然不会乱序。默认值False。只有在lvwReport视图该属性才有意义。

(4)View
非常重要的属性。返回或设置 ListView 控件中 ListItem 对象的外观。有四个选项,lvwIcon是图标视图, lvwSmallIcon是小图标视图, lvwList是列表视图, lvwReport是报表视图。

(5)MultiSelect
设置是否可以选择多个ListItem项(即:行)。默认为False。按下 SHIFT键并单击鼠标,或按下 SHIFT 键和方向键(UP 键、DOWN 键、LEFT 键和 RIGHT 键)之一,可将选择从先前选定的 ListItem延伸到当前的 ListItem。按 CTRL 键并单击鼠标将选定或者撤消选定列表中的一个 ListItem。

(6)Appearance
控件在设计时的绘图风格。在运行时是只读的。值0 为平面效果,绘制控件和窗体没有可视化效果。值1 (缺省值)为3D。带有三维效果的绘制控件。

(7)LabelEdit
设置用户是否可以编辑控件中项的标签(每个ListItem的第一列)。值为lvwAutomatic和 lvwManual,我总是选择lvwManual,这样在单击ListItem的时候第一列不会进入编辑状态。在选中的对象被单击(如果 LabelEdit 属性被设置为 Automatic)时,对象的标签编辑被启动。也就是说,第一次单击使对象被选中,第二次单击启动该对象的标签编辑操作。相关属性LabelWrap决定当ListView控件为图标视图时标签是否可换行。默认值为True。

(8)CheckBoxes
设置Listitem项是否显示复选框。默认值False。可以显示复选框实现多选效果。

(9)SelectedItem
非常重要的属性。返回对所选 ListItem对象的引用。当选择多个ListItem时,SelectedItem.Index不可用。在程序中,我们要选中一行ListItem 对象,可用带有SelectedItem 属性的 Set 语句,如选择第一行,代码如下所示:Set ListView1.SelectedItem = ListView1.ListItems(1)

(10)BackColor
返回或设置Listview控件的背景颜色。

(11)ForeColor
返回或设置Listview控件中文本的颜色。

(12)BorderStyle
返回或设置Listview控件的边框样式。

(13)Font
设置字体、字号和粗细。颜色要在前景色属性设置。

(14)Height、Width、Left、Top 属性
设置Listview的高宽和位置。单位是磅,Left、Top是Listview相对于其容器的边缘距离。容器可为窗体,也可以为框架。


Listview完全教程电子文档.zip (1.62 MB, 下载次数: 7423)
程序完整代码(增加了新增和删除功能,增加了数据库版本).zip (453.16 KB, 下载次数: 4961)
所使用的两个控件(包含注册批处理).zip (539.63 KB, 下载次数: 5215)


评分

8

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2018-7-14 20:34 | 显示全部楼层
(15)HideSelection:决定当控件失去焦点时选择文本是否加亮显示。默认False。

(16)HoverSelection:设置当鼠标指针悬停于ListItem项上时是否自动选中。一般停留1秒左右即可自动选中,默认False。

(17)HotTracking:该属性的返回值确定是否使用灵敏的鼠标高亮度显示功能。热跟踪功能的作用是:在鼠标指针经过控件的时候为用户提供反馈信息。如果将 HotTracking 设置为 True,那么控件将对鼠标的移动作出反映:将鼠标指针下面的标头变为高亮度的。默认值False。

(18)ColumnHeaderIcons:设置为Imagelist 控件,该控件为表头 ColumnHeaders 集合提供图标。如果要为 ColumnHeader 对象设置图标,可将它的 Icon 属性设置成为一个索引值、关键字或者对象引用,以指向 ColumnHeaderIcons 属性指定的 ImageList 控件中的一个 ListImage 对象。

(19)ColumnHeaders:非常重要的属性。返回 ColumnHeader 对象集合的引用。

(20)ListItems:非常重要的属性。返回ListItem对象集合的引用。

(21)hWnd:返回Listview控件的句柄。

(22)Icons、SmallIcons:返回或设置与ListView控件中图标视图和小图标视图关联的ImageList控件。ListView 控件中的每个 ListItem 对象都具有 Icon 和 SmallIcon 属性,它们索引 ListImage 对象并决定将显示哪个图象。将 ImageList 与 ListView 控件关联后,就可在过程中使用 Index 或 Key 属性值引用 ListImage 对象。

(23)SortKey:要排序的列。其值为列号减1,是一个从零开始的数。

(24)SortOrder:此值决定ListView控件中的ListItem对象以升序或降序排序。0为升序,1为降序,默认升序。

(25)Sorted:设置确定集合中的项目是否排序。欲使SortOrder 和 SortKey 属性的设置值生效,必须将 Sorted 属性设置为 True。该属性只为一次性有效。当Listview列表的数据变化时,将不会自动排序,Sorted 属性将设置为 False

1.2、常用方法

(1) FindItem 方法

查找并返Listview控件中ListItem 对象的引用。语法:object.FindItem(string,value,index,match)。FindItem方法的语法包含下面部分:
①Object:必需的。对象表达式,其值是ListView控件。
②String:必需的。指定欲查找的ListItem对象的字符串表达式。
③Value:可选的。整数或常数,它指定字符串是否与ListItem对象的Text、Subitems及Tag属性相匹配,其值为如下3种:
lvwText :值0,(缺省)将字符串与 ListItem 对象的 Text 属性相匹配。
lvwSubitem :值1 ,将字符串与 ListItem 对象的 SubItems 属性相匹配。
lvwTag :值2 ,将字符串与 ListItem 对象的 Tag 属性相匹配。
④Index:可选的。唯一标识对象集合成员并指定搜索起始位置的整数或字符串。若为整数,其值为Index属性值;若为字符串,其值为Key属性值。未指定索引时缺省索引为 1。
⑤Match:可选的。指定项目的Text属性与字符串怎样匹配的整数或常数。match的设置值为:
lvwWholeWord:值0(缺省,)一个整数或常数,它指定若项目的Text属性由所搜索的整字开始时匹配成功。搜索条件非文本时忽略此设置。
lvwPartial:值1,一个整数或常数,它指定若项目的Text属性由所搜索的字符串开始时匹配成功。搜索条件非字符串时忽略此设置。

(2)SetFocus 方法

用代码形式设置Listview获得焦点。

(3)ZOrder 方法

将Listview控件放置在其图层的z-顺序的前端或后端。共有3个图层与窗体和容器相关联。底层是显示图形方法结果的绘图空间。中间层用来显示图形对象和 Label 控件。顶层显示所有非图形控件,如 CommandButton、CheckBox 、ListBox和Listview等控件。前层的层中包含的东西将覆盖该层后面的各层包含的东西。Zorder 只对该对象在其中显示的那一层内的各个对象进行重排,跨层无效。

评分

2

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2018-7-14 20:35 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助

1.3、常用事件

(1)Click 事件

单击事件。如果在 Click 事件中有代码,则 DlbClick 事件(双击事件)将永远不会被触发,因为 Click 事件是两个事件中首先被触发的事件。其结果是鼠标单击被 Click 事件截断,从而使 DblClick 事件不会发生。Click 事件和DlbClick 事件都无法区分鼠标键三键,为区别鼠标的左、中、右按钮,应使用 MouseDown 和 MouseUp 事件。

(2)DblClick 事件

双击事件。如果 DblClick 在系统双击时间限制内没有出现,则对象识别另一个 Click 事件。

(3)ColumnClick 事件

单击ListView控件中的标题时,该事件发生。在该事件Sub过程中,使用ColumnHeader.Index可方便的知道单击的是哪一列标题。ColumnClick 事件能让Listview获得焦点。经常使用单击标题来进行排序。

(4)ItemClick 事件

单击ListView控件中ListItem对象(行)时事件发生。语法为:
Private Sub object_ItemClick(ByVal Item As ListItem)
Item代表被单击的ListItem对象。此事件在Click事件之前触发。鼠标单击ListView控件的任何部分时触发标准的Click事件,而只有当鼠标单击ListItem对象的文本或图象时才触发ItemClick事件。当然,我们可以设置FullRowSelect为True,这样单击ListItem对象的非第1列,也能触发ItemClick 事件。这是一个很有用的事件,我们可以使用它来选择需要的行。后面会有应用的代码。

(5)ItemCheck事件

单击ListItem对象的复选框事件。需要设置Listview属性CheckBoxes为True。
使用Item.Checked来判断复选框是否勾选。使用该事件,我们可以实现不重复的多选,方便选择和录入数据。后面会有应用的代码。

(6)MouseMove 事件

当鼠标在Listview控件上移动时发生。能返回鼠标的坐标X,Y值,其值是距离Listview控件边缘的距离,单位是像素。

(7)MouseDown、MouseUp 事件

这些事件是当按下 ( MouseDown ) 或者释放 ( MouseUp ) 鼠标按钮时发生。能返回鼠标的坐标X,Y值,其值是距离Listview控件边缘的距离,单位是像素。MouseDown 和 MouseUp 事件能够区分出鼠标的左、右、和中间按钮,以及SHIFT, CTRL, 和 ALT 等键盘换挡键及其组合操作。

(8)KeyDown、KeyUp 、KeyPress 事件

当控件获得焦点时按下 ( KeyDown ) 、松开 ( KeyUp ) 或按了一个键(KeyPress)时发生。KeyPress事件不区分键盘是按下还是松开的状态,且只能识别ANSI 字符集的按键,应当使用 KeyDown 和 KeyUP 事件过程来处理任何不被 KeyPress 识别的击键,诸如:功能键、编辑键、定位键、方向键以及任何这些键和键盘换档键的组合等。Listview这种没有输入功能的控件用得不多,但后面用到的InkEdit控件会使用到这些事件。


评分

2

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2018-7-14 20:37 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
2.基础:Listview的2个对象的常用方法和属性

2.1、 ColumnHeader 对象、ColumnHeaders 集合属性

ColumnHeader 对象是 ListView 控件中包含标头文字的项目,ColumnHeaders 是其集合。
(1)Alignment 属性
列标头ColumnHeader 对象中文本的对齐方法。其值为lvwColumnLeft 0 (缺省)左对齐;lvwColumnRight 1 右对齐;lvwColumnCenter 2 居中。第1列只能选择左对齐。
(2) Count 属性
返回Listview控件的列数。
(3)Index 属性
列标头的索引属性。即其列号。
(4)Left属性
列标头ColumnHeader对象与Listview控件左边框的距离。标头没有Top属性。
(5)Text 属性
列标头ColumnHeader对象显示的文字。
(6)Icon 属性
设置列标头ColumnHeader的图标,其值为Imagelist控件中的索引或关键字。在点击列表头排序时,我们可以显示上下箭头图标。也可以是其他图标。
(7)Width 属性
指定该列的宽度。

2.2、 ColumnHeaders 集合方法

(1)Add 方法
将ColumnHeader对象添加到ListView控件的ColumnHeaders集合中。语法:object.Add(index, key, text, width, alignment, icon),一般只用text,width,alignment三个参数,这些参数在属性中已经说明,不再赘叙。
(2)Clear 方法
清除Listview全部标头。
(3)Remove 方法
清除Listview的一列标头

2.3、 ListItem 对象、ListItems 集合 属性  

ListItem包含文本和相关图标(ListImage对象)的索引,除此之外,当它为报表视图时,ListItem还包含代表子项目的字符串数组。其集合为ListItems。ListItem 对象可包含文本和图片,若要使用图片则必须通过 Icons 和 SmallIcons 属性引用 ImageList 控件。
(1)Count 属性
返回Listview控件的数据行数。
(2)Ghosted 属性
返回或设置ListView控件的ListItem对象不可用(此时控件是暗淡的)。通常使用 Ghosted 属性显示 ListItem 被剪切或由于某些原因被禁止使用。
(3)Index 属性
ListItem对象的索引属性,即其所在行号。
(4)Icon、SmallIcon 属性
返回或设置与ImageList控件中ListItem对象关联的图标或小图标的索引值或关键字值。使用 ListItem 对象中的图标之前必须将 ImageList 控件与包含对象的 ListView 控件相关联。参看Listview控件的Icon、SmallIcons属性
(5)Selected 属性
确定一个ListItem对象是否被选中的值。它仅返回一值,表明是否已用其它方法选中了ListItem对象。在 ListView 控件中,SelectedItem 属性总是引用第一个选中的项,因此,如多个项被选中,必须遍历所有选中的项,以校验每个项的 Selected 属性是否为True。无法使用 Selected 属性去选择 ListItem 对象,而必须用带有 SelectedItem 属性的 Set 语句,如:
Set ListView1.SelectedItem = ListView1.ListItems(1)
(6)Text 属性
返回或设置ListItem对象中第一列的文本标签。
(7)ToolTipText属性
返回或设置一个字符串的提示。设置了该值,当鼠标指针停留在对象上时显示指定的字符串。
(8)SubItems 属性
重要的属性。返回或设置一个字符串(子项目)数组,它代表ListView控件中ListItem对象的补充数据,ListItem对象的第一列赋值用Text属性,其他列用SubItems属性,比如某一行的第二列,用ListView1.ListItems(1).SubItems(1)赋值。
SubItems只是一个字符串数组,如果要设置前景色需使用ListSubItems集合。集合必须使用标准的集合方法Add添加每一列的内容,不可以使用ListSubItems(1)这种方式添加,否则报错。在赋值方面没有SubItems方便,不过毕竟是对象,可以设置ToolTipText、前景色、加粗等属性,文字隔行换色还必须依靠它。

2.4 、ListItems 集合 方法

(1)Add 方法
添加ListItem对象到ListView控件的ListItems集合中并返回新创建对象的引用。语法为:
object.Add(index, key, text, icon, smallIcon)
使用该方法时如果不指定index则在最后一行的末尾新增一行。新增一行后可以用SubItems 属性或者ListSubItems集合给该行除第一列之外的列赋值。
(2)Clear 方法
清楚Listview控件的所有数据行。不包括标头。
(3)Remove 方法
该方法从Listview数据行中删除一行。语法为object.Remove(index)。
(4)EnsureVisible 方法      
确保Listview对象的指定行可见。假如我们想每次加载数据时,最后一行可见,可以使用Listview1.ListItems(Listview1.ListItems.Count). EnsureVisible

Listview使用非常简单,以上属性和方法已经涵盖Listview 95%以上的属性和方法,且多数都容易理解,我把资料整理起来只为方便查阅。

TA的精华主题

TA的得分主题

 楼主| 发表于 2018-7-14 20:40 | 显示全部楼层
3.练手:Listview模糊查询窗体的设计

这一节设计一个窗体,从EXCEL工作表中加载数据到数组中,使用Textbox控件输入查询关键字,将符合查询条件的数据显示在Listview控件中。主要是为熟悉Listview控件的各种属性。按ALT+F11进入VBE编辑器。
第一步:插入→用户窗体,拖到适当大小;
第二步:插入一个标签,输入“查询内容”,插入一个TextBox控件,两者的字号设为四号,字体为微软雅黑,颜色深蓝色;
第三步:插入一个Listview控件,拖到适当大小;

结果如图:
3-1.png

选中Userform1,右键→查看代码,输入如下代码。

3-2.png
Dim arrData

Private Sub TextBox1_Change()
    Dim i As Long, j As Long
    Dim lstitem As ListItem
    Dim strKey As String
    If IsEmpty(arrData) Then Exit Sub '如果arrData为空就退出
    With ListView1
        .ListItems.Clear '清空原有所有数据
        For i = 2 To UBound(arrData)
            strKey = arrData(i, 3) & "/" & arrData(i, 4) & "/" & arrData(i, 5) '按3、4、5列任一列内容查询
            If InStr(strKey, UCase(TextBox1)) Then '可以使用Instr,也可以使用Like语句
                Set lstitem = .ListItems.Add 'Add方法在Listview最后面添加一行空行
                lstitem.Text = arrData(i, 1) '给新增的空行第一列赋值
                For j = 2 To UBound(arrData, 2)
                    lstitem.SubItems(j - 1) = arrData(i, j) '给新增行除第一列之外的其他列赋值,用SubItems属性
                Next
            End If
        Next
    End With
End Sub

Private Sub UserForm_Initialize()
    Dim i As Long, n As Long
    arrData = Range("a1").CurrentRegion
    If IsEmpty(arrData) Then Exit Sub
    With ListView1
        .Gridlines = True '显示网格线
        .FullRowSelect = True '整行选择
        .MultiSelect = True '多选
        .LabelEdit = lvwManual '第一列手动修改。不然点两下鼠标就进入编辑状态
        .CheckBoxes = True '显示复选框
        .View = lvwReport '报表视图
        .Font.Size = 12 '设置字号为四号
        For i = 1 To UBound(arrData, 2)
            .ColumnHeaders.Add , , arrData(1, i), 100 '添加标题,并设置宽度为100。
        Next
'        如果每列宽度不同,可用数组赋值,也可以用下面方法添加标题:
'        .ColumnHeaders.Add , , arrData(1, 1), 50
'        .ColumnHeaders.Add , , arrData(1, 2), 100
'        .ColumnHeaders.Add , , arrData(1, 3), 100
'        ……………………………………………………
    End With
End Sub

好了,本节的内容已经全部完成。成果如下图:

3-3.png



评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2018-7-14 20:43 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖最后由 ivccav 于 2018-7-14 20:46 编辑


4.实践:Listview窗体不重复多选的实现

上节中设计的窗体已经能完成查询信息的任务,但是还有很多可以加强的地方,比如想要在点击一行中的任何位置,都能勾选复选框,而不是只能点击那个小框框;比如想知道勾选了多少行数据;想不断地查询数据,而已勾选的数据不能重复出现;比如想把勾选数据输出到EXCEL工作表,等等。在第三节的程序中都没有办法实现,而这些都是这一节要介绍的。
窗体还是原来的窗体,我们只要改代码。知识点是怎么判断已经勾选了项目,还有在不断的查询中,移除未勾选的行,再增加新查询到的行。
定义一个模块级变量listCnt来记录勾选了多少项,用于在标签上显示。因此要在第三节窗体的基础上加一个标签Label2.
为了记录已经勾选的项数,可以每次循环整个Listview表的数据行,也可以利用ItemCheck事件随时记录,而不用循环整个Listview数据,效率更高,其代码如下:

Private Sub ListView1_ItemCheck(ByVal Item As MSComctlLib.ListItem)
    If Item.Checked Then listCnt = listCnt + 1 Else listCnt = listCnt - 1
    Label2.Caption = "已选择项数:" & listCnt
End Sub

为了方便录入,让单击一行数据中的任何位置,都能选中该行,需要使用ItemClick事件。该事件中的代码如下:

Private Sub ListView1_ItemClick(ByVal Item As MSComctlLib.ListItem)
    Item.Checked = Not Item.Checked
    listCnt = listCnt + IIf(Item.Checked, 1, -1)
    Label2.Caption = "已选择项数:" & listCnt
End Sub

    打开窗体,可能需要查询多次数据,才能完成数据的选择,期间已经勾选的项就不能简单的用Listview .ListItems.Clear清除掉。需要修改TextBox1_Change中的代码。

Private Sub TextBox1_Change()
    Dim i As Long, j As Long
    Dim lstitem As ListItem
    Dim strKey$, tb1$
    If IsEmpty(arrData) Then Exit Sub
    tb1 = UCase(TextBox1.Text)
    With ListView1
        If listCnt Then
            For i = .ListItems.Count To 1 Step -1 '如果已勾选,就保留,否则清除
                If Not .ListItems(i).Checked Then .ListItems.Remove i
            Next
            For i = 2 To UBound(arrData)
                strKey = arrData(i, 3) & "/" & arrData(i, 4) & "/" & arrData(i, 5)
                If InStr(strKey, UCase(TextBox1)) Then
                    For j = 1 To listCnt '如果新查询到的ID已在勾选列表中,直接下一个
                        If arrData(i, 1) = .ListItems(j).Text Then GoTo Line1
                    Next
                    Set lstitem = .ListItems.Add
                    lstitem.Text = arrData(i, 1)
                    For j = 2 To UBound(arrData, 2)
                        lstitem.SubItems(j - 1) = arrData(i, j)
                    Next
                End If
Line1:
            Next
        Else
            .ListItems.Clear '如果listCnt为0,直接清除整个Listview,效率更高
            For i = 2 To UBound(arrData)
                strKey = arrData(i, 3) & "/" & arrData(i, 4) & "/" & arrData(i, 5)
                If InStr(strKey, UCase(TextBox1)) Then
                    Set lstitem = .ListItems.Add
                    lstitem.Text = arrData(i, 1)
                    For j = 2 To UBound(arrData, 2)
                        lstitem.SubItems(j - 1) = arrData(i, j)
                    Next
                End If
            Next
        End If
    End With
End Sub



TA的精华主题

TA的得分主题

 楼主| 发表于 2018-7-14 20:45 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助

Remove的效率是比较低的,其实可以在For循环中再设置一个变量记录满足条件的行数,每次Change事件发生时,只输出20条或者50条数据,这样效率会提高很多倍。这样做一般并不会产生问题,因为查询者也不可能从查询结果的50条数据中去勾选需要的数据,而正常情况下,只要输入的关键字够长,筛选剩下的数据不会超过5条,把计数器设置为20足够了。

这里有一个技巧,就是怎么判断是否重复行的问题。上面我们在TextBox1_Change事件中是用ID来判断是否已经有数据被重复输出到Listview了。但很多工作表并没有ID,我们怎么办呢?其实可以在加载数组的时候,让数组多出一列,用For循环把EXCEL中的每一行的行号都放在该列,这个行号是唯一的,可以标识该行数据。在用窗体更新EXCEL数据时,这个方法也特别高效,因为知道行号,可以直接定位到行,跟数据库中的ID列有异曲同工之妙。有些人在窗体中用Find或者Match去匹配EXCEL中的数据,然后更新,这种方法效率太低了,没有使用的必要性。

最后一步是把已经勾选的数据输出到Excel工作表中。方法很简单,循环Listview控件,把复选框等于True的项目全部写到工作表中即可,可以一行一行的写,也可以先存到数组中,最后一次性输出。用数组的方法快很多,当然是采用数组的方法了。因为我们前面用了一个变量listCnt记录已经勾选的项数,定义数组的时候,行数就等于listCnt,列数为你想输出的行数即可。代码如下:

Sub ExportData() '数据导出,输出已勾选
    Dim drr, i&, j&, k&, c&
    With ListView1
        If .ListItems.Count = 0 Then Exit Sub
If listCnt = 0 Then Exit Sub
        r = Range("a" & Rows.Count).End(xlUp).Row '最后一行
        c = .ColumnHeaders.Count '列数
        ReDim drr(1 To listCnt, 1 To c)
        For i = 1 To .ListItems.Count
            If .ListItems(i).Checked Then
                k = k + 1
                drr(k, 1) = .ListItems(i).Text
                For j = 2 To c
                    drr(k, j) = .ListItems(i).SubItems(j - 1)
                Next
            End If
        Next
    End With
    If Range("a1") = "" And r = 1 Then r = 0 'A1单元为空End(xlUp).Row也会返回1
    Range("a" & r + 1).Resize(UBound(drr), UBound(drr, 2)) = drr
    Unload Me
End Sub

如果当初没有使用listCnt变量,不知道该定义多大的数组,怎么办?可以用ReDim Preserve,不断调整数组的第二维大小并保留数组值,在最后转置一次。需要注意的是,Preserve的效率也不是很高,不过比一行一行地输出到工作表还是快多了。

Sub ExportData() '数据导出,输出已勾选
    Dim drr, i&, j&, u2&, c&
    With ListView1
        If .ListItems.Count = 0 Then Exit Sub
If listCnt = 0 Then Exit Sub
        r = Range("a" & Rows.Count).End(xlUp).Row
        c = .ColumnHeaders.Count '列数
        For i = 1 To .ListItems.Count
            If .ListItems(i).Checked Then
                If IsEmpty(drr) Then
                    ReDim drr(1 To c, 1 To 1)
                Else
                    u2 = UBound(drr, 2)
                    ReDim Preserve drr(1 To c, 1 To u2 + 1)
                End If
                u2 = UBound(drr, 2)
                drr(1, u2) = .ListItems(i).Text
                For j = 2 To c
                    drr(j, u2) = .ListItems(i).SubItems(j - 1)
                Next
            End If
        Next
    End With
    If Range("a1") = "" And r = 1 Then r = 0 'A1单元为空End(xlUp).Row也会返回1
    Range("a" & r + 1).Resize(UBound(drr, 2), UBound(drr)) = Application.WorksheetFunction.Transpose(drr)
    Unload Me
End Sub

当然,如果不怕浪费内存,也可以直接定义1个行数跟Listview现有数据行一样多的数组,这样就不需要ReDim Preserve,最后也不用转置,速度非常快,就是会多输出很多没必要的空行而已。

还可以增加一个全选/全不选按键:

Private Sub CheckBox1_Click() '全不选,全选
    With ListView1
        If .ListItems.Count = 0 Then Exit Sub
        For i = 1 To .ListItems.Count
            .ListItems(i).Checked = CheckBox1
        Next
        CheckBox1.Caption = IIf(CheckBox1, "全不选", "全选")
        listCnt = IIf(CheckBox1, .ListItems.Count, 0)
        Label2.Caption = "已选择项数:" & listCnt
    End With
End Sub

还可以增加一个反选按键:

Private Sub CheckBox2_Click() '反选
    With ListView1
        If .ListItems.Count = 0 Then Exit Sub
        For i = 1 To .ListItems.Count
            .ListItems(i).Checked = Not .ListItems(i).Checked
        Next
        listCnt = .ListItems.Count - listCnt
        Label2.Caption = "已选择项数:" & listCnt
    End With
End Sub

一个可用的Listview窗体就这么完成了。最后的效果图如下:
4-1.png

评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2018-7-14 20:49 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册


5.美化:Listview隔行换色的完美实现(前景色和背景色)

5.1、Listview前景色隔行换色

改变文字的颜色很简单,在添加数据时,直接修改其颜色即可,可以每行不同色,也可以每列都不同色,甚至每个单元格都可以不同色,比如一些朋友会判断某个值是否大于零,来确定不同的颜色,起到突出作用。需要注意的是,改变非第一列的字体颜色时使用ListSubItems(Index)而不是SubItems属性,SubItems只是一个字符串数组,是没有办法提供颜色修改的。例如要实现奇数行红色,偶数行蓝色,按如下代码赋值给Listview即可,非常简单:

Set lstitem = .ListItems.Add
lstitem.Text = arrData(i, 1)
forecolor = IIf(lstitem.Index Mod 2, vbRed, vbBlue)
lstitem.forecolor = forecolor
For j = 2 To UBound(arrData, 2)
    lstitem.SubItems(j - 1) = arrData(i, j)
    lstitem.ListSubItems(j - 1).forecolor = forecolor
Next

赋值也可以不使用SubItems,因为SubItems是被ListSubItem对象淘汰的属性,保留只是为了向下兼容:

For j = 2 To UBound(arrData, 2)
    With lstitem.ListSubItems.Add
        .Text = arrData(i, j)
        .forecolor = forecolor
    End With
Next

5.2、Listview背景色隔行换色

首先申明,我是不喜欢隔行换色这个效果的,我觉得不实用,看起来眼花缭乱的,但是很多人喜欢这个功能,我也勉强讲一点点吧。

从这个帖子讲起吧:

http://club.excelhome.net/thread-1159258-1-1.html

这是论坛里的一个帖子,这个帖子使用的方法是知识树里面的帖,帖中有贴出地址。不得不吐槽一下知识树,里面有相当一部分帖子都相当普通,很多内容不是半成品,就是烂尾的,还看到在API那一块里面发现有多人都复制同一篇文章,并获得技术分的(原谅我的浅薄,我只看过知识树里的关于API的部分内容,看了几篇之后,发现没有什么意义,都是别人学习的片段或者笔记,根本没有任何学习到系统知识的可能,跟看微信公众号一样浪费时间,于是也就懒得再看什么知识树,有那个时间我宁愿拿一本厚厚的书慢慢啃。)。从其他地方抄过来的内容,只要够好,我是不反对的,毕竟每个人的精力有限,别人发现好的东西,分享给大家,也是一件好事。不可否认,论坛里面的精华帖,很多都不错的,像蓝版的字典帖,还有一篇关于正则表达式的帖,都很好,整理得比较系统,能学到知识。

Listview背景色是不能设置的,目前主流的方法就是用VB软件的pictureBox控件绘制背景色,模拟隔行换色的效果。很遗憾,VBA中不能直接使用pictureBox,需要注册控件,就跟上面帖子中的一样。除此之外,pictureBox高度有限制,最大只能为245745缇(16383像素),按照Listview看起来比较舒服的行高25个像素来计算,只能显示600行左右的数据,剩下的背景色将全部变成空白。如果为了一个不痛不痒的功能,安装第三方的控件,且有重大缺陷,我想很多人都不会尝试,且在查询数据的过程中,不停地绘制背景图,效率并不会太高吧!

办法总是想出来的。这个灵感来源我模糊记得曾经操作过让照片平铺,只需一张很小的照片,就能源源不断的铺满需要的背景。于是我就去Listview的属性里看有没有平铺这个设置,bingo!还真有这个。应该一切搞定。

在设计窗体时,我打印出Listview的行高:19.5,于是在EXCEL工作表中把行高改为19.5,然后把前十行中的奇数行换色淡蓝色,偶数行换成白色,复制这十行的区域(列数估计一下Listview控件的宽度+滚动条,足够宽即可),打开画图软件,粘贴,保存为JPG格式(也可以保存为BMP,就是图片质量太大太好,我怕影响效率,才改为压缩格式JPEG,这个格式叫联合图片专家组,貌似很强大),然后在Listview控件的Picture属性中引用该图片,对齐方式为平铺,打开窗体,满怀期待的希望看到那完美的时刻,很遗憾,没有出现。背景色的中间有条纹。怎么办?

我重新用画图软件打开刚才保存的图片,发现了问题,原来复制EXCEL工作表区域时,会在底边多一个像素,果断在图像——属性里面,把高度减小一个像素,保存图片,重新引用。打开窗体,8000行数据,没有一个像素的误差!完美!高效!

没有使用一个API,没有一行代码,就是简单的复制粘贴,就搞定了,我都不好意思说出来这是一种方法,更不好意思分享。但是看到大家使用了无数API和代码,却只能显示600行背景色的时候,我还是忍不住分享了。想起一个故事,说是某国宇航局为了设计一款能在太空中供宇航员使用的水笔,耗费巨资,依然一无所获,头疼不已,最后一个小学生说可以用铅笔代替,问题迎刃而解。

隔行更换背景色根本无需pictureBox,更不用安装多余的控件到自己的计算机。

补充说明一下,VBA的Range对象有一个CopyPicture 方法,可以把数据区域按图片复制到剪贴板,例如:使用代码Range("A1:AF10").CopyPicture xlScreen, xlBitmap就完成了刚才我手动复制的区域到剪贴板了。可以根据数据的多少,在一个工作表中设定好区域和颜色,CopyPicture到剪贴板,然后用API把剪贴板上的图片保存到硬盘上,用LoadPicture函数传递给Listview,这个方法理论上能保证100万行数据的背景色没有问题吧?

但我认为动态生成背景图片不是好办法,在程序的运行中,不断的生产图片,肯定会大量占用计算资源。在程序的设计之初,我们已经设定好Listview的行高,完全可以提前准备好背景图片供程序使用。有人会问,想在窗体中更改字体大小,这样Listview的行高跟着变化,原来的背景图案就不适用了。我的建议是,不需要更改字体这个功能。因为更改字体不仅是Listview背景图案不适配的问题,还有Listview的列宽都需要更改,不然可能每列显示可能也不完整,且我们的窗体是在某种字体下设计出来的,是真正的匠心之作,如果更改字体,画面看起来就会不协调。如果非要增加此功能,可以用Imagelist存储几张背景图,根据不同的字体调用不同的图案。


5-1.png



补充内容 (2018-9-8 21:08):
用NM_CUSTOMDRAW自定义绘制技术实现Listview控件隔行换色(每个单元格的前景色和背景色均可不同):

http://club.excelhome.net/thread-1428537-1-1.html

评分

2

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2018-7-14 20:52 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助


6.美化:Listview+Imagelist设置行高

Listview控件不可以调整行高,其行高由Listview的字体属性和图标属性之中的较大者决定。我们可以利用这个特点给Listview“设置”行高。在窗体中从工具箱选择ImageList控件,拖到窗体上任意位置。如果工具箱没有ImageList控件,可右键——附加控件,勾选Microsoft ImageList Control 6.0 (SP6)。在ImageList的属性页,点击“自定义”的省略号,在弹出的界面,点击“图像”选项卡,插入一张图片。Listview在关联ImageList控件之后,其行高就是该图片的高度。一般使用四号字体,该图片的分辨率高度为25像素即可。ImageList作为图标引用时,只能显示为第一张图片的宽度和高度,无论后面图片的尺寸有多大,且不可设置。

imagelist属性.png

imagelist属性页.png

如何获得这张图片呢?很多同学对像素、分辨率这些概念头晕,更不知道如何剪裁这些图片,其实可以使用系统的画图来制作一张图片即可。在系统的开始——附件中打开画图,选择菜单“图像”——“属性”,直接输入一个分辨率,比如5X25像素,确定,保存即可。如图:

Imagelist画图1.png

Imagelist画图2.png


得到这种5X25分辨率的图片,插入到Imagelist控件中,然后在窗体初始化时,使用一句简单的代码:

Listview1.SmallIcons = ImageList1

其他通通不用管,每行高度就是25像素了,其结果是每行之间的宽度更大,看起来更舒服。你也可以用其他分辨率的图片,格式BMP、ICO、JPG和GIF都可以。


评分

2

查看全部评分

您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-21 19:04 , Processed in 0.046140 second(s), 8 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表