298311657 发表于 2018-4-11 17:16:26

ListView虚表 使用sql分页查询的方法动态加载Sqlite数据库数据示例

本帖最后由 298311657 于 2018-4-11 17:20 编辑

看到有坛友留言给我,询问当数据库数据过大,使用listview虚表时,若还一次性把所有数据都读取到数组内,再显示出来,效率很慢,要如何解决此种问题。我的想法是使用sql的分页功能解决问题。
将数据库内的数据通过sql分页查询的方式,通过多次查询,将数据库的数据加载到数组内,然后再显示在listview表格上。
具体的看代码吧,语音组织能力有限。


#Region ;**** 编译指令由 AutoIt3Wrapper 选项编译窗口创建 ****
#AutoIt3Wrapper_UseX64=n
#EndRegion ;**** 编译指令由 AutoIt3Wrapper 选项编译窗口创建 ****
#cs ____________________________________
    Au3 版本: 3.3.14.2
    脚本作者: CrossDoor
    电子邮件: 382869232@qq.com
    QQ/TM: 382869232
#ce _______________脚本开始_________________
#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include <GuiImageList.au3>
#include <WindowsConstants.au3>
#include <WinAPISys.au3>
#include <GuiEdit.au3>
#include <array.au3>
#include <SQLite.au3>


Local $tText = DllStructCreate("wchar Text");建个结构,用来放listview列数据
Local $iPage = 1, $iPageCount = 1000, $bPage = True;数据分页变量$iPage每页显示数据数量变量$iPageCount
Local $GUI, $aShowdata[$iPageCount]

;==========生成数据库数据
Local $hQuery, $aRow
_SQLite_Startup(@ScriptDir & "\sqlite3.dll")
_SQLite_Open() ; 打开 :内存: 数据库
_SQLite_Exec(-1, "CREATE TABLE aTest (a,b,c);") ; 创建一个表

_SQLite_Exec(-1, "BEGIN TRANSACTION;");开启一个事务,在大量数据操作时可增加效率
For $i = 0 To 9999;一万数据
        _SQLite_Exec(-1, "INSERT INTO aTest(a,b,c) VALUES ('(列一)" & $i & "','(列二)" & $i & "','(列三)" & $i & "');")
        If @error Then
                _SQLite_Exec(-1, "ROLLBACK TRANSACTION;");回滚事务
                ExitLoop
        EndIf
Next
_SQLite_Exec(-1, "COMMIT TRANSACTION;");提交事务
ConsoleWrite("数据生成结束" & @CRLF)
;==========完毕

GetData()

GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")
$hGUI = GUICreate("ListView虚表 动态加载数据库数据示例 By_Crossdoor", 500, 350)

$hListView = GUICtrlCreateListView("Item1|nSubItem1|nSubItem2", 2, 2, 494, 294, $LVS_SHOWSELALWAYS + $LVS_OWNERDATA, $LVS_EX_GRIDLINES + $LVS_EX_FULLROWSELECT + $LVS_EX_DOUBLEBUFFER + $LVS_EX_CHECKBOXES)

;设置列宽
GUICtrlSendMsg($hListView, $LVM_SETCOLUMNWIDTH, 0, 150)
GUICtrlSendMsg($hListView, $LVM_SETCOLUMNWIDTH, 1, 150)
GUICtrlSendMsg($hListView, $LVM_SETCOLUMNWIDTH, 2, 150)
GUICtrlSendMsg($hListView, $LVM_SETITEMCOUNT, $iPageCount, 0);设定数据总数
GUISetState()

While 1
    $iMsg = GUIGetMsg()
    Switch $iMsg
      Case -3
            ExitLoop
    EndSwitch
WEnd
GUIDelete()
_SQLite_Exec(-1, "DROP TABLE aTest;") ; 删除表
_SQLite_Close()
_SQLite_Shutdown()

Func WM_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $hWnd, $iMsg, $iwParam
    Local $hWndFrom, $iIDFrom, $iCode, $tNMHDR, $tInfo, $s

    $tNMHDR = DllStructCreate($tagNMHDR, $ilParam)
    $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    $iIDFrom = DllStructGetData($tNMHDR, "IDFrom")
    $iCode = DllStructGetData($tNMHDR, "Code")
    Local $iIndex = DllStructGetData($tNMHDR, 'Index')
    Switch $iIDFrom
      Case $hListView
            Switch $iCode
                Case $NM_CLICK;单击
                  $tInfo = DllStructCreate($tagNMITEMACTIVATE, $ilParam)
                  $iIndex = DllStructGetData($tInfo, "Index")
                  $x = DllStructGetData($tInfo, "X")

                  If ($x < 16) And (3 < $x) And (BitAND(_GUICtrlListView_GetExtendedListViewStyle($hListView), $LVS_EX_CHECKBOXES) = $LVS_EX_CHECKBOXES) Then;使用x坐标来判断是否在复选框上点击
                        If $aShowdata[$iIndex] = 4096 Then
                            $aShowdata[$iIndex] = 8192;4096未选中 8192选中
                        Else
                            $aShowdata[$iIndex] = 4096;4096未选中 8192选中
                        EndIf
                        $tInfo = DllStructCreate($tagNMLVDISPINFO, $ilParam)
                        DllStructSetData($tInfo, "State", $aShowdata[$iIndex]);设置复选框状态
                        ;====立刻刷新复选框显示状态 重绘$iIndex-1到$iIndex+20的项
                        GUICtrlSendMsg($hListView, $LVM_REDRAWITEMS, $iIndex - 1, $iIndex + 20)
                  EndIf
                Case -150, -177 ;$LVN_GETDISPINFOA = -150, $LVN_GETDISPINFOW = -177   请求显示数据
                  If Not IsArray($aShowdata) Then ContinueCase
                  $tInfo = DllStructCreate($tagNMLVDISPINFO, $ilParam)
                  $iIndex = Int(DllStructGetData($tInfo, "Item"))
                  $iSub = Int(DllStructGetData($tInfo, "SubItem"))

                                        If $iIndex >= UBound($aShowdata)-1 And $bPage Then
                                                GetData();listview被拉到底部时,请求加载新的数据
                                                GUICtrlSendMsg($hListView, $LVM_SETITEMCOUNT, UBound($aShowdata), 0);新数据加载后,修改虚表显示的数据总数
                                        EndIf

                  If (BitAND(_GUICtrlListView_GetExtendedListViewStyle($hListView), $LVS_EX_CHECKBOXES) = $LVS_EX_CHECKBOXES) Then
                        ;===============设置复选框信息
                        DllStructSetData($tInfo, "Mask", BitOR($LVIF_STATE,DllStructGetData($tInfo, "Mask")))
                        DllStructSetData($tInfo, "StateMask", $LVIS_STATEIMAGEMASK)
                        DllStructSetData($tInfo, "State", $aShowdata[$iIndex])
                  EndIf

                  DllStructSetData($tText, "Text", $aShowdata[$iIndex][$iSub]);列数据放入$tText结构
                  DllStructSetData($tInfo, "Text", DllStructGetPtr($tText));用$tText结构的指针来设置列数据
                  DllStructSetData($tInfo, "TextMax", StringLen($aShowdata[$iIndex][$iSub]));设置列数据长度
            EndSwitch
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY


Func GetData()
        ;==========采用分页模式取数据库数据,先取 $iPage 页的数据到数组内
        Local $i = $iPageCount*($iPage-1), $b =True
        _SQLite_Query(-1, "SELECT * FROM aTest limit "& $iPageCount & " offset " & $iPageCount*($iPage-1) & ";", $hQuery)
        For $k = 0 To $iPageCount - 1
                If _SQLite_FetchData($hQuery, $aRow) = $SQLITE_OK Then; 依次获取行
                        If $b Then
                                ReDim $aShowdata[$iPage * $iPageCount]
                                $iPage+=1;取一次数据分页数加1
                                $b = False
                        EndIf
                        $aShowdata[$i] = $aRow
                        $aShowdata[$i] = $aRow
                        $aShowdata[$i] = $aRow
                        $aShowdata[$i] = 4096;复选框状态 4096未选中 8192选中
                        $i+=1
                Else
                        ConsoleWrite("表数据已经读取到底,将分页数据读取标志变量$bPage = False" & @CRLF)
                        $bPage = False
                        ExitLoop
                EndIf
        Next
        _SQLite_QueryFinalize($hQuery)
        ;==========完毕
EndFunc

志艺风云 发表于 2018-4-11 18:44:52

这个不错,有时间再研究研究。

kk_lee69 发表于 2018-4-11 19:59:50

回复 1# 298311657

我研究看看 感謝啦!!

298311657 发表于 2018-4-12 16:34:02

回复 3# kk_lee69

还有一种方法是根据LVN_ODCACHEHINT消息来请求数据,这种方法会在后台一直请求缓存数据直到数据全部读取完毕,而我上面的代码则是当先前请求的数据全部显示完毕才会请求新的数据,两种方法各有利弊,你可以自行选择。

关于分页查询,sqlite数据库使用的limit-offset指令;MySql数据库提供了分页的函数limit m,n;sqlserver数据库分页查询的方式比较多,NOT IN关键字、MAX(ID)函数、Server2005中的ROW_NUMBER、SQL Server 2012中的OFFSET-FETCH等。
不论使用哪种数据库,改变的都只是查询数据的sql语句,listview显示数据的方式都是一样的。


#Region ;**** 编译指令由 AutoIt3Wrapper 选项编译窗口创建 ****
#AutoIt3Wrapper_UseX64=n
#EndRegion ;**** 编译指令由 AutoIt3Wrapper 选项编译窗口创建 ****
#cs ____________________________________
    Au3 版本: 3.3.14.2
    脚本作者: CrossDoor
    电子邮件: 382869232@qq.com
    QQ/TM: 382869232
#ce _______________脚本开始_________________
#include <GUIConstantsEx.au3>
#include <GuiListView.au3>
#include <GuiImageList.au3>
#include <WindowsConstants.au3>
#include <WinAPISys.au3>
#include <GuiEdit.au3>
#include <array.au3>
#include <SQLite.au3>


Local $tText = DllStructCreate("wchar Text");建个结构,用来放listview列数据
Local $iPage = 1, $iPageCount = 100, $bPage = True;数据分页变量$iPage每页显示数据数量变量$iPageCount
Local $GUI, $aShowdata[$iPageCount]

_SQLite_Startup(@ScriptDir & "\sqlite3.dll")
_SQLite_Open() ; 打开 :内存: 数据库
_SQLite_Exec(-1, "CREATE TABLE aTest (a,b,c);") ; 创建一个表


;==========生成数据库数据
_SQLite_Exec(-1, "BEGIN TRANSACTION;");开启一个事务,在大量数据操作时可增加效率
For $i = 0 To 9999;一万数据
        _SQLite_Exec(-1, "INSERT INTO aTest(a,b,c) VALUES ('(列一)" & $i & "','(列二)" & $i & "','(列三)" & $i & "');")
        If @error Then
                _SQLite_Exec(-1, "ROLLBACK TRANSACTION;");回滚事务
                ExitLoop
        EndIf
Next
_SQLite_Exec(-1, "COMMIT TRANSACTION;");提交事务
ConsoleWrite("数据生成结束" & @CRLF)
;==========完毕

GetData()

GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")
$hGUI = GUICreate("ListView虚表 动态加载数据库数据示例 By_Crossdoor", 500, 350)

$hListView = GUICtrlCreateListView("Item1|nSubItem1|nSubItem2", 2, 2, 494, 294, $LVS_SHOWSELALWAYS + $LVS_OWNERDATA, $LVS_EX_GRIDLINES + $LVS_EX_FULLROWSELECT + $LVS_EX_DOUBLEBUFFER + $LVS_EX_CHECKBOXES)

;设置列宽
GUICtrlSendMsg($hListView, $LVM_SETCOLUMNWIDTH, 0, 150)
GUICtrlSendMsg($hListView, $LVM_SETCOLUMNWIDTH, 1, 150)
GUICtrlSendMsg($hListView, $LVM_SETCOLUMNWIDTH, 2, 150)
GUICtrlSendMsg($hListView, $LVM_SETITEMCOUNT, $iPageCount, 0);设定数据总数
GUISetState()

While 1
    $iMsg = GUIGetMsg()
    Switch $iMsg
      Case -3
            ExitLoop
    EndSwitch
WEnd
GUIDelete()
_SQLite_Exec(-1, "DROP TABLE aTest;") ; 删除表
_SQLite_Close()
_SQLite_Shutdown()

Func WM_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $hWnd, $iMsg, $iwParam
    Local $hWndFrom, $iIDFrom, $iCode, $tNMHDR, $tInfo, $s

    $tNMHDR = DllStructCreate($tagNMHDR, $ilParam)
    $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    $iIDFrom = DllStructGetData($tNMHDR, "IDFrom")
    $iCode = DllStructGetData($tNMHDR, "Code")
    Local $iIndex = DllStructGetData($tNMHDR, 'Index')
    Switch $iIDFrom
      Case $hListView
            Switch $iCode
                Case $NM_CLICK;单击
                  $tInfo = DllStructCreate($tagNMITEMACTIVATE, $ilParam)
                  $iIndex = DllStructGetData($tInfo, "Index")
                  $x = DllStructGetData($tInfo, "X")

                  If ($x < 16) And (3 < $x) And (BitAND(_GUICtrlListView_GetExtendedListViewStyle($hListView), $LVS_EX_CHECKBOXES) = $LVS_EX_CHECKBOXES) Then;使用x坐标来判断是否在复选框上点击
                        If $aShowdata[$iIndex] = 4096 Then
                            $aShowdata[$iIndex] = 8192;4096未选中 8192选中
                        Else
                            $aShowdata[$iIndex] = 4096;4096未选中 8192选中
                        EndIf
                        $tInfo = DllStructCreate($tagNMLVDISPINFO, $ilParam)
                        DllStructSetData($tInfo, "State", $aShowdata[$iIndex]);设置复选框状态
                        ;====立刻刷新复选框显示状态 重绘$iIndex-1到$iIndex+20的项
                        GUICtrlSendMsg($hListView, $LVM_REDRAWITEMS, $iIndex - 1, $iIndex + 20)
                  EndIf
                Case -150, -177 ;$LVN_GETDISPINFOA = -150, $LVN_GETDISPINFOW = -177   请求显示数据
                  If Not IsArray($aShowdata) Then ContinueCase
                  $tInfo = DllStructCreate($tagNMLVDISPINFO, $ilParam)
                  $iIndex = Int(DllStructGetData($tInfo, "Item"))
                  $iSub = Int(DllStructGetData($tInfo, "SubItem"))

                  If (BitAND(_GUICtrlListView_GetExtendedListViewStyle($hListView), $LVS_EX_CHECKBOXES) = $LVS_EX_CHECKBOXES) Then
                        ;===============设置复选框信息
                        DllStructSetData($tInfo, "Mask", BitOR($LVIF_STATE,DllStructGetData($tInfo, "Mask")))
                        DllStructSetData($tInfo, "StateMask", $LVIS_STATEIMAGEMASK)
                        DllStructSetData($tInfo, "State", $aShowdata[$iIndex])
                  EndIf

                  DllStructSetData($tText, "Text", $aShowdata[$iIndex][$iSub]);列数据放入$tText结构
                  DllStructSetData($tInfo, "Text", DllStructGetPtr($tText));用$tText结构的指针来设置列数据
                  DllStructSetData($tInfo, "TextMax", StringLen($aShowdata[$iIndex][$iSub]));设置列数据长度
                                Case $LVN_ODCACHEHINT;缓冲数据
                                        If $bPage Then
                                                GetData();listview被拉到底部时,请求加载新的数据
                                                GUICtrlSendMsg($hListView, $LVM_SETITEMCOUNT, UBound($aShowdata), 0);新数据加载后,修改虚表显示的数据总数
                                        EndIf
            EndSwitch
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY


Func GetData()
        ;==========采用分页模式取数据库数据
        Local $hQuery, $aRow
        Local $i = $iPageCount*($iPage-1), $b =True
        _SQLite_Query(-1, "SELECT * FROM aTest limit "& $iPageCount & " offset " & $iPageCount*($iPage-1) & ";", $hQuery)
        For $k = 0 To $iPageCount - 1
                If _SQLite_FetchData($hQuery, $aRow) = $SQLITE_OK Then; 依次获取行
                        If $b Then
                                ReDim $aShowdata[$iPage * $iPageCount]
                                $iPage+=1;取一次数据分页数加1
                                $b = False
                        EndIf
                        $aShowdata[$i] = $aRow
                        $aShowdata[$i] = $aRow
                        $aShowdata[$i] = $aRow
                        $aShowdata[$i] = 4096;复选框状态 4096未选中 8192选中
                        $i+=1
                Else
                        ConsoleWrite("表数据已经读取到底,将分页数据读取标志变量$bPage = False" & @CRLF)
                        $bPage = False
                        ExitLoop
                EndIf
        Next
        _SQLite_QueryFinalize($hQuery)
        ;==========完毕
EndFunc

Mranauto 发表于 2021-9-25 16:58:26

这个不错,有时间再研究研究。
页: [1]
查看完整版本: ListView虚表 使用sql分页查询的方法动态加载Sqlite数据库数据示例